diff --git a/.clang-format b/.clang-format index 813a84d87cb..84b96ab5590 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,5 @@ --- -# This configuration requires clang-format version 15 exactly. +# This configuration requires clang-format version 18 exactly. BasedOnStyle: Mozilla AlignOperands: false AllowShortFunctionsOnASingleLine: InlineOnly @@ -16,6 +16,7 @@ BraceWrapping: BreakBeforeBraces: Custom ColumnLimit: 79 IndentPPDirectives: AfterHash +QualifierAlignment: Right SortUsingDeclarations: false SpaceAfterTemplateKeyword: true IncludeBlocks: Regroup diff --git a/.clang-tidy b/.clang-tidy index 1b776e17f52..35503d40fba 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,33 +2,52 @@ Checks: "-*,\ bugprone-*,\ -bugprone-assignment-in-if-condition,\ +-bugprone-crtp-constructor-accessibility,\ -bugprone-easily-swappable-parameters,\ +-bugprone-empty-catch,\ -bugprone-implicit-widening-of-multiplication-result,\ +-bugprone-inc-dec-in-conditions,\ -bugprone-macro-parentheses,\ --bugprone-misplaced-widening-cast,\ +-bugprone-multi-level-implicit-pointer-conversion,\ -bugprone-narrowing-conversions,\ +-bugprone-return-const-ref-from-parameter,\ +-bugprone-suspicious-stringview-data-usage,\ +-bugprone-switch-missing-default-case,\ -bugprone-too-small-loop-variable,\ -bugprone-unchecked-optional-access,\ +-bugprone-unused-local-non-trivial-variable,\ +-bugprone-unused-return-value,\ +-bugprone-use-after-move,\ misc-*,\ -misc-confusable-identifiers,\ -misc-const-correctness,\ --misc-no-recursion,\ +-misc-include-cleaner,\ -misc-non-private-member-variables-in-classes,\ +-misc-no-recursion,\ -misc-static-assert,\ -misc-use-anonymous-namespace,\ +-misc-use-internal-linkage,\ modernize-*,\ -modernize-avoid-c-arrays,\ +-modernize-concat-nested-namespaces,\ -modernize-macro-to-enum,\ -modernize-return-braced-init-list,\ +-modernize-type-traits,\ -modernize-use-emplace,\ -modernize-use-nodiscard,\ -modernize-use-noexcept,\ -modernize-use-trailing-return-type,\ -modernize-use-transparent-functors,\ performance-*,\ --performance-inefficient-vector-operation,\ +-performance-avoid-endl,\ +-performance-enum-size,\ +-performance-unnecessary-copy-initialization,\ +-performance-unnecessary-value-param,\ readability-*,\ +-readability-avoid-nested-conditional-operator,\ +-readability-avoid-unconditional-preprocessor-if,\ -readability-convert-member-functions-to-static,\ +-readability-enum-initial-value,\ -readability-function-cognitive-complexity,\ -readability-function-size,\ -readability-identifier-length,\ @@ -37,12 +56,13 @@ readability-*,\ -readability-inconsistent-declaration-parameter-name,\ -readability-magic-numbers,\ -readability-make-member-function-const,\ +-readability-math-missing-parentheses,\ -readability-named-parameter,\ --readability-redundant-declaration,\ --readability-redundant-member-init,\ -readability-simplify-boolean-expr,\ +-readability-static-accessed-through-instance,\ -readability-suspicious-call-argument,\ -readability-uppercase-literal-suffix,\ +-readability-use-std-min-max,\ cmake-*,\ -cmake-ostringstream-use-cmstrcat,\ -cmake-string-concatenation-use-cmstrcat,\ diff --git a/.codespellrc b/.codespellrc index 00c6c521a8c..383e9ce243c 100644 --- a/.codespellrc +++ b/.codespellrc @@ -4,5 +4,59 @@ check-hidden = # Disable warnings about binary files quiet-level = 2 builtin = clear,rare,en-GB_to_en-US -skip = */.git,*/build,*/Copyright.txt,*/CTestCustom.cmake.in,*/doxygen.config,*/Modules/Internal/CPack/NSIS.template.in,*/Source/CursesDialog/form/*,*/Source/kwsys/*,*/Tests/RunCMake/CPack/tests/DMG_SLA/German.*,*/Tests/RunCMake/ParseImplicitData/*.input,*/Tests/StringFileTest/test.utf8,*.pfx,*/Utilities/cm* -ignore-words-list = aci,ags,ake,ans,ba,ccompiler,cconfiguration,certi,conly,dependees,dne,dum,earch,ect,filetest,fo,helpfull,hiden,isnt,keypair,nd,ned,nin,nknown,ot,pard,seh,ser,te,upto,varn,vas,wee + +# Skip paths matching fnmatch glob patterns. +skip = + .git, + .typos.toml, + build, + CONTRIBUTORS.rst, + CTestCustom.cmake.in, + Modules/Internal/CPack/NSIS.template.in, + Source/CursesDialog/form/*, + Source/kwsys/*, + Tests/RunCMake/CPack/tests/DMG_SLA/German.*, + Tests/RunCMake/ParseImplicitData/*.input, + Tests/StringFileTest/test.utf8, + Utilities/cm*, + *.pfx, + +# noqa: spellcheck off +ignore-words-list = + abd, + aci, + ags, + ake, + ans, + ba, + ccompiler, + cconfiguration, + certi, + conly, + copyin, + dependees, + dne, + dum, + earch, + ect, + filetest, + fo, + helpfull, + hiden, + isnt, + keypair, + nd, + ned, + nin, + nknown, + ot, + pard, + sectionin, + seh, + ser, + te, + upto, + varn, + vas, + wee, +# noqa: spellcheck on diff --git a/.gitattributes b/.gitattributes index 96a11664ef6..d414c68fa57 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,15 +1,22 @@ .git* export-ignore .hooks* export-ignore +.clang-format export-ignore +.clang-tidy export-ignore .codespellrc export-ignore .editorconfig export-ignore +.pre-commit-config.yaml export-ignore +.typos.toml export-ignore # Custom attribute to mark sources as using our C code style. -[attr]our-c-style whitespace=tab-in-indent format.clang-format=15 +[attr]our-c-style whitespace=tab-in-indent format.clang-format=18 # Custom attribute to mark sources as generated. # Do not perform whitespace checks. Do not format. [attr]generated whitespace=-tab-in-indent,-indent-with-non-tab -format.clang-format +# Custom attribute to mark files as TABs indented. +[attr]tab-indent whitespace=-tab-in-indent + bootstrap eol=lf configure eol=lf *.[1-9] eol=lf @@ -19,9 +26,28 @@ configure eol=lf *.bat eol=crlf *.bat.in eol=crlf +*.cmd eol=crlf *.sln eol=crlf *.vcproj eol=crlf +Makefile tab-indent +Makefile.in tab-indent +NSIS.template.in tab-indent +coverage.xml.in tab-indent +*.F tab-indent +*.f tab-indent +*.pbxproj.in tab-indent +*.plist.in tab-indent +*.plist tab-indent +*.sln tab-indent +*.s tab-indent +*.vcproj tab-indent +*.vcproj.in tab-indent +*.vfproj.in tab-indent +*.xib tab-indent +*.make tab-indent +.hooks-config tab-indent + *.pfx -text *.png -text *.png.in -text @@ -31,8 +57,10 @@ configure eol=lf *.cpp our-c-style *.cu our-c-style *.cxx our-c-style +*.H our-c-style *.h our-c-style *.hh our-c-style +*.hip our-c-style *.hpp our-c-style *.hxx our-c-style *.notcu our-c-style diff --git a/.github/workflows/esy.yml b/.github/workflows/esy.yml new file mode 100644 index 00000000000..f7db07910f1 --- /dev/null +++ b/.github/workflows/esy.yml @@ -0,0 +1,76 @@ +name: Esy +on: + - push + - pull_request + +jobs: + build: + name: Build and test + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - name: Setup node.js + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 + with: + node-version: "22" + check-latest: true + + # Install `esy` to build the project + # It also adds `shx` globally for cross-platform shell commands + - name: Setup environment + run: | + npm i -g esy@0.8.0 + npm i -g shx + + - name: Checkout project + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + submodules: "recursive" + + - name: Install local dependencies + run: | + esy install + + - name: Esy cache + id: esy-cache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: _export + key: ${{ runner.os }}-esy-${{ hashFiles('esy.lock/index.json') }} + + - name: Import esy cache + if: steps.esy-cache.outputs.cache-hit == 'true' + run: | + esy import-dependencies _export + shx rm -rf _export + + # Build the project in release to make sure deps are specified correctly + - name: Build release dependencies + if: steps.esy-cache.outputs.cache-hit != 'true' + run: | + esy build-dependencies --release + + - name: Build project in release + run: | + esy build --release + + # Then build in non-release so we can test + - name: Build dependencies + if: steps.esy-cache.outputs.cache-hit != 'true' + run: | + esy build-dependencies + + - name: Build project + run: | + esy build + + # Re-export dependencies if anything has changed or if it is the first time + - name: Build esy cache + if: steps.esy-cache.outputs.cache-hit != 'true' + run: | + esy export-dependencies diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml deleted file mode 100644 index 579318e015e..00000000000 --- a/.github/workflows/test-build.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Test Build - -on: [push, pull_request] - -jobs: - test-build: - strategy: - fail-fast: false - matrix: - os: - - windows-latest - - macos-latest - - macos-13 - - macos-15 - - ubuntu-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 - with: - node-version: 22 - - - name: Install esy - run: | - npm install -g esy - - - uses: actions/checkout@v4 - - - name: Build - run: | - esy diff --git a/.gitignore b/.gitignore index faace89df37..16c1479933e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,15 @@ +# Help tools that honor '.gitignore' (redundant for Git itself). +/.git + /CMakeUserPresets.json # Common build directories /build*/ +# CI jobs that run in symlinked trees produce these artifacts. +/real_work/ +/work + # MacOS Finder files. .DS_Store @@ -29,6 +36,10 @@ # Visual Studio build directory /out/ +# clang-tidy output +/clang-tidy-fixes.patch + +# esy Bootstrap.cmk _esy node_modules diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 297afda7aa6..cccd68c9d69 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,8 +16,13 @@ stages: - build - test - test-ext + - package - upload +variables: + # Some jobs that place their artifacts in a different directory will override this. + CMAKE_CI_BUILD_DIR: build + ################################################################################ # Job declarations # @@ -31,11 +36,12 @@ stages: # Additionally, jobs may also contain: # # - artifacts -# - dependency/needs jobs for required jobs +# - needs jobs for required jobs ################################################################################ # Job prefixes: # - `b:` build +# - `k:` package # - `l:` lint # - `p:` prep # - `t:` test @@ -55,7 +61,7 @@ p:source-package: p:doc-package: extends: - - .fedora38_sphinx_package + - .fedora42_sphinx_package - .cmake_prep_doc_linux - .linux_x86_64_tags - .cmake_doc_artifacts @@ -65,54 +71,58 @@ u:source-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - p:source-package needs: - p:source-package +b:version-update: + extends: + - .cmake_version_update_linux + - .linux_x86_64_tags + - .run_version_update + # Documentation builds b:cmake.org-help: extends: - .cmake_org_help - .run_cmake_org_help - dependencies: [] needs: [] u:cmake.org-help: extends: - .rsync_upload_help - .run_cmake_org_help - dependencies: - - b:cmake.org-help needs: - b:cmake.org-help # Lint builds -l:codespell: +l:spellcheck: extends: - - .cmake_codespell_linux + - .cmake_spellcheck_linux - .linux_x86_64_tags - .run_automatically -l:iwyu-debian10: +l:iwyu-debian12: extends: - - .debian10_iwyu + - .debian12_iwyu - .cmake_build_linux - .linux_x86_64_tags + - .cmake_cdash_artifacts - .run_automatically -l:tidy-fedora38: +l:tidy-fedora42: extends: - - .fedora38_tidy + - .fedora42_tidy - .cmake_build_linux + - .cmake_tidy_artifacts - .linux_x86_64_tags + - .cmake_cdash_artifacts - .run_automatically -l:sphinx-fedora38: +l:sphinx-fedora42: extends: - - .fedora38_sphinx + - .fedora42_sphinx - .cmake_build_linux - .cmake_sphinx_artifacts - .linux_x86_64_tags @@ -121,9 +131,9 @@ l:sphinx-fedora38: CMAKE_CI_JOB_CONTINUOUS: "true" CMAKE_CI_JOB_HELP: "true" -l:clang-analyzer-fedora38: +l:clang-analyzer-fedora42: extends: - - .fedora38_clang_analyzer + - .fedora42_clang_analyzer - .cmake_build_linux - .linux_x86_64_tags - .run_automatically @@ -132,7 +142,7 @@ l:clang-analyzer-fedora38: # Linux builds -b:centos6-x86_64: +b:centos7-x86_64: extends: - .linux_release_x86_64 - .cmake_build_linux_release @@ -140,7 +150,7 @@ b:centos6-x86_64: - .linux_x86_64_tags - .run_manually variables: - CMAKE_CI_ARTIFACTS_NAME: "artifacts-centos6-x86_64" + CMAKE_CI_ARTIFACTS_NAME: "artifacts-centos7-x86_64" b:centos7-aarch64: extends: @@ -153,20 +163,20 @@ b:centos7-aarch64: CMAKE_CI_ARTIFACTS_NAME: "artifacts-centos7-aarch64" CMAKE_CI_NO_MR: "true" -t:debian10-ninja: +t:debian12-ninja: extends: - - .debian10_ninja + - .debian12_ninja - .cmake_test_linux_release - .linux_x86_64_tags - .cmake_junit_artifacts - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY_NINJA: "true" -t:debian10-aarch64-ninja: +t:debian12-aarch64-ninja: extends: - - .debian10_aarch64_ninja + - .debian12_aarch64_ninja - .cmake_test_linux_release - .linux_aarch64_tags - .cmake_junit_artifacts @@ -175,84 +185,136 @@ t:debian10-aarch64-ninja: variables: CMAKE_CI_NO_MR: "true" -t:debian10-ninja-clang: +t:debian12-ninja-clang: + extends: + - .debian12_ninja_clang + - .cmake_test_linux_release + - .linux_x86_64_tags + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:debian12-makefiles-clang: extends: - - .debian10_ninja_clang + - .debian12_makefiles_clang - .cmake_test_linux_release - .linux_x86_64_tags - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:debian10-makefiles-clang: +t:debian12-ninja-multi-symlinked: extends: - - .debian10_makefiles_clang + - .debian12_ninja_multi_symlinked - .cmake_test_linux_release - .linux_x86_64_tags + - .cmake_junit_artifacts + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + CMAKE_CI_JOB_NIGHTLY_NINJA: "true" + +t:debian12-hip-radeon: + extends: + - .debian12_hip_radeon + - .cmake_test_linux_release + - .linux_x86_64_tags_rocm5.2 + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:fedora42-hip-radeon: + extends: + - .fedora42_hip_radeon + - .cmake_test_linux_release + - .linux_x86_64_tags_rocm6.2 - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:fedora38-ninja-clang: +t:fedora42-makefiles-lfortran: extends: - - .fedora38_ninja_clang + - .fedora42_makefiles_lfortran - .cmake_test_linux_release - .linux_x86_64_tags - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:fedora38-ninja-multi-clang: +t:fedora42-ninja-lfortran: extends: - - .fedora38_ninja_multi_clang + - .fedora42_ninja_lfortran - .cmake_test_linux_release - .linux_x86_64_tags - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:fedora38-makefiles-clang: +t:fedora42-ninja-clang: extends: - - .fedora38_makefiles_clang + - .fedora42_ninja_clang - .cmake_test_linux_release - .linux_x86_64_tags - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:fedora38-makefiles: +t:fedora42-ninja-multi-clang: extends: - - .fedora38_makefiles + - .fedora42_ninja_multi_clang - .cmake_test_linux_release - .linux_x86_64_tags - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" -t:fedora38-makefiles-nospace: +t:fedora42-makefiles-clang: extends: - - .fedora38_makefiles + - .fedora42_makefiles_clang - .cmake_test_linux_release - .linux_x86_64_tags + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:fedora42-makefiles: + extends: + - .fedora42_makefiles + - .cmake_test_linux_release + - .linux_x86_64_v3_tags + - .run_dependent + - .needs_centos7_x86_64 + +t:fedora42-makefiles-nospace: + extends: + - .fedora42_makefiles + - .cmake_test_linux_release + - .linux_x86_64_v3_tags - .cmake_junit_artifacts - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake-ci" - CMAKE_CI_BUILD_NAME: fedora38_makefiles_nospace + CMAKE_CI_BUILD_NAME: fedora42_makefiles_nospace CMAKE_CI_JOB_NIGHTLY: "true" -t:nvhpc22.11-ninja: +t:nvhpc24.9-ninja: extends: - .nvhpc_ninja - .cmake_test_linux_release - - .linux_x86_64_v3_tags_cuda + - .linux_x86_64_v3_tags_cuda_arch_52 - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" @@ -260,69 +322,144 @@ t:cuda9.2-nvidia: extends: - .cuda9.2_nvidia - .cmake_test_linux_release - - .linux_x86_64_tags_cuda + - .linux_x86_64_tags_cuda_arch_30 - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: - CMAKE_CI_NO_MR: "true" + CMAKE_CI_JOB_NIGHTLY: "true" t:cuda10.2-nvidia: extends: - .cuda10.2_nvidia - .cmake_test_linux_release - - .linux_x86_64_tags_cuda + - .linux_x86_64_tags_cuda_arch_52 - .cmake_junit_artifacts - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" t:cuda10.2-clang: extends: - .cuda10.2_clang - .cmake_test_linux_release - - .linux_x86_64_tags_cuda + - .linux_x86_64_tags_cuda_arch_52 - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: - CMAKE_CI_NO_MR: "true" + CMAKE_CI_JOB_NIGHTLY: "true" t:cuda11.6-nvidia: extends: - .cuda11.6_nvidia - .cmake_test_linux_release - - .linux_x86_64_tags_cuda + - .linux_x86_64_tags_cuda_arch_52 - .cmake_junit_artifacts - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" t:cuda11.6-clang: extends: - .cuda11.6_clang - .cmake_test_linux_release - - .linux_x86_64_tags_cuda + - .linux_x86_64_tags_cuda_arch_52 - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: - CMAKE_CI_NO_MR: "true" + CMAKE_CI_JOB_NIGHTLY: "true" t:cuda11.8-minimal-ninja: extends: - .cuda11.8_minimal_nvidia - .cmake_test_linux_release - - .linux_x86_64_tags_cuda + - .linux_x86_64_tags_cuda_arch_52 - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: - CMAKE_CI_NO_MR: "true" + CMAKE_CI_JOB_NIGHTLY: "true" -t:hip5.5-radeon: +t:cuda11.8-minimal-splayed-ninja: extends: - - .hip5.5_radeon + - .cuda11.8_splayed_nvidia - .cmake_test_linux_release - - .linux_x86_64_tags_radeon + - .linux_x86_64_tags_cuda_arch_52 - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: - CMAKE_CI_NO_MR: "true" + CMAKE_CI_JOB_NIGHTLY: "true" + +t:cuda12.2-nvidia: + extends: + - .cuda12.2_nvidia + - .cmake_test_linux_release + - .linux_x86_64_tags_cuda_arch_52 + - .cmake_junit_artifacts + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:cuda12.2-clang: + extends: + - .cuda12.2_clang + - .cmake_test_linux_release + - .linux_x86_64_tags_cuda_arch_52 + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:cuda12.6-nvidia: + extends: + - .cuda12.6_nvidia + - .cmake_test_linux_release + - .linux_x86_64_tags_cuda_arch_52 + - .cmake_junit_artifacts + - .run_dependent + - .needs_centos7_x86_64 + +t:cuda12.6-nvidia-clang: + extends: + - .cuda12.6_nvidia_clang + - .cmake_test_linux_release + - .linux_x86_64_tags_cuda_arch_52 + - .cmake_junit_artifacts + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:cuda12.6-clang: + extends: + - .cuda12.6_clang + - .cmake_test_linux_release + - .linux_x86_64_tags_cuda_arch_52 + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:hip6.3-nvidia: + extends: + - .hip6.3_nvidia + - .cmake_test_linux_release + - .linux_x86_64_tags_cuda_arch_52 + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:hip6.3-radeon: + extends: + - .hip6.3_radeon + - .cmake_test_linux_release + - .linux_x86_64_tags_rocm6.3 + - .run_dependent + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" t:linux-gcc-cxx-modules-ninja: extends: @@ -330,7 +467,7 @@ t:linux-gcc-cxx-modules-ninja: - .cmake_test_linux_release - .linux_x86_64_tags - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" @@ -340,13 +477,24 @@ t:linux-gcc-cxx-modules-ninja-multi: - .cmake_test_linux_release - .linux_x86_64_tags - .run_dependent - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:debian10-legacy: + extends: + - .debian10_legacy + - .cmake_test_linux_release + - .linux_x86_64_tags + - .cmake_junit_artifacts + - .run_dependent + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" -b:fedora38-ninja: +b:fedora42-ninja: extends: - - .fedora38_ninja + - .fedora42_ninja - .cmake_build_linux - .cmake_build_artifacts - .linux_x86_64_tags @@ -354,81 +502,85 @@ b:fedora38-ninja: variables: CMAKE_CI_JOB_CONTINUOUS: "true" -b:debian10-makefiles-inplace: +b:fedora42-makefiles-symlinked: extends: - - .debian10_makefiles_inplace + - .fedora42_makefiles_symlinked + - .cmake_build_linux + - .cmake_build_artifacts + - .linux_x86_64_tags + - .run_manually + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +b:debian12-makefiles-inplace: + extends: + - .debian12_makefiles_inplace - .cmake_build_linux_standalone - .linux_x86_64_tags - .run_manually variables: CMAKE_CI_JOB_NIGHTLY: "true" -b:debian10-extdeps: +b:debian12-extdeps: extends: - - .debian10_extdeps + - .debian12_extdeps - .cmake_build_linux_standalone - .linux_x86_64_tags - .run_manually variables: CMAKE_CI_JOB_NIGHTLY: "true" -b:debian10-aarch64-extdeps: +b:debian12-aarch64-extdeps: extends: - - .debian10_aarch64_extdeps + - .debian12_aarch64_extdeps - .cmake_build_linux_standalone - .linux_aarch64_tags - .run_manually variables: CMAKE_CI_JOB_NIGHTLY: "true" -b:fedora38-extdeps: +b:fedora42-extdeps: extends: - - .fedora38_extdeps + - .fedora42_extdeps - .cmake_build_linux_standalone - .linux_x86_64_tags - .run_manually variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:fedora38-ninja: +t:fedora42-ninja: extends: - - .fedora38_ninja + - .fedora42_ninja - .cmake_test_linux - .linux_x86_64_tags_x11 - .cmake_test_artifacts - .run_dependent - dependencies: - - b:fedora38-ninja needs: - - b:fedora38-ninja + - b:fedora42-ninja variables: CMAKE_CI_JOB_CONTINUOUS: "true" -t:fedora38-ninja-multi: +t:fedora42-makefiles-symlinked: extends: - - .fedora38_ninja_multi - - .cmake_test_linux_external - - .linux_x86_64_tags - - .cmake_junit_artifacts + - .fedora42_makefiles_symlinked + - .cmake_test_linux + - .linux_x86_64_tags_x11 + - .cmake_test_artifacts - .run_dependent - dependencies: - - t:fedora38-ninja needs: - - t:fedora38-ninja - -t:intel2016-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles + - b:fedora42-makefiles-symlinked variables: - CMAKE_CI_BUILD_NAME: intel2016_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2016-el7 + CMAKE_CI_JOB_NIGHTLY: "true" -t:intel2016u1-makefiles: +t:fedora42-ninja-multi: extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2016u1_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2016u1-el7 + - .fedora42_ninja_multi + - .cmake_test_linux_external + - .linux_x86_64_tags + - .cmake_junit_artifacts + - .run_dependent + needs: + - t:fedora42-ninja t:intel2016u2-makefiles: extends: @@ -437,62 +589,6 @@ t:intel2016u2-makefiles: CMAKE_CI_BUILD_NAME: intel2016u2_makefiles CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2016u2-el7 -t:intel2017-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017-el7 - -t:intel2017u1-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017u1_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u1-el7 - -t:intel2017u2-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017u2_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u2-el7 - -t:intel2017u3-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017u3_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u3-el7 - -t:intel2017u4-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017u4_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u4-el7 - -t:intel2017u5-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017u5_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u5-el7 - -t:intel2017u6-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017u6_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u6-el7 - -t:intel2017u7-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2017u7_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u7-el7 - t:intel2017u8-makefiles: extends: - .cmake_test_linux_intelclassic_makefiles @@ -500,34 +596,6 @@ t:intel2017u8-makefiles: CMAKE_CI_BUILD_NAME: intel2017u8_makefiles CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2017u8-el7 -t:intel2018-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2018_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2018-el7 - -t:intel2018u1-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2018u1_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2018u1-el7 - -t:intel2018u2-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2018u2_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2018u2-el7 - -t:intel2018u3-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2018u3_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2018u3-el7 - t:intel2018u4-makefiles: extends: - .cmake_test_linux_intelclassic_makefiles @@ -535,34 +603,6 @@ t:intel2018u4-makefiles: CMAKE_CI_BUILD_NAME: intel2018u4_makefiles CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2018u4-el7 -t:intel2019-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2019_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2019-el7 - -t:intel2019u1-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2019u1_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2019u1-el7 - -t:intel2019u2-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2019u2_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2019u2-el7 - -t:intel2019u3-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2019u3_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2019u3-el7 - t:intel2019u4-makefiles: extends: - .cmake_test_linux_intelclassic_makefiles @@ -570,20 +610,6 @@ t:intel2019u4-makefiles: CMAKE_CI_BUILD_NAME: intel2019u4_makefiles CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2019u4-el7 -t:intel2020-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2020_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2020-el8 - -t:intel2020u2-makefiles: - extends: - - .cmake_test_linux_intelclassic_makefiles - variables: - CMAKE_CI_BUILD_NAME: intel2020u2_makefiles - CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2020u2-el8 - t:intel2020u4-makefiles: extends: - .cmake_test_linux_intelclassic_makefiles @@ -661,6 +687,13 @@ t:intel2021.9.0-makefiles: CMAKE_CI_BUILD_NAME: intel2021.9.0_makefiles CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.1.0-el8 +t:intel2021.10.0-makefiles: + extends: + - .cmake_test_linux_intelclassic_makefiles + variables: + CMAKE_CI_BUILD_NAME: intel2021.10.0_makefiles + CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.2.1-el8 + t:oneapi2021.1.1-makefiles: extends: - .cmake_test_linux_inteloneapi_makefiles @@ -731,6 +764,48 @@ t:oneapi2023.1.0-makefiles: CMAKE_CI_BUILD_NAME: oneapi2023.1.0_makefiles CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.1.0-el8 +t:oneapi2023.2.0-makefiles: + extends: + - .cmake_test_linux_inteloneapi_makefiles + variables: + CMAKE_CI_BUILD_NAME: oneapi2023.2.1_makefiles + CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.2.1-el8 + +t:oneapi2024.0.0-makefiles: + extends: + - .cmake_test_linux_inteloneapi_makefiles + variables: + CMAKE_CI_BUILD_NAME: oneapi2024.0.0_makefiles + CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2024.0.0-el8 + +t:oneapi2024.1.0-makefiles: + extends: + - .cmake_test_linux_inteloneapi_makefiles + variables: + CMAKE_CI_BUILD_NAME: oneapi2024.1.0_makefiles + CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2024.1.0-el8 + +t:oneapi2024.2.0-makefiles: + extends: + - .cmake_test_linux_inteloneapi_makefiles + variables: + CMAKE_CI_BUILD_NAME: oneapi2024.2.0_makefiles + CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2024.2.0-rocky9 + +t:oneapi2025.0.0-makefiles: + extends: + - .cmake_test_linux_inteloneapi_makefiles + variables: + CMAKE_CI_BUILD_NAME: oneapi2025.0.0_makefiles + CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2025.0.0-rocky9 + +t:oneapi2025.1.0-makefiles: + extends: + - .cmake_test_linux_inteloneapi_makefiles + variables: + CMAKE_CI_BUILD_NAME: oneapi2025.1.0_makefiles + CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2025.1.0-rocky9 + b:linux-x86_64-package: extends: - .linux_package @@ -739,8 +814,6 @@ b:linux-x86_64-package: - .cmake_release_artifacts - .linux_x86_64_tags - .run_only_for_package - dependencies: - - p:doc-package needs: - p:doc-package variables: @@ -750,8 +823,6 @@ u:linux-x86_64-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - b:linux-x86_64-package needs: - b:linux-x86_64-package @@ -763,8 +834,6 @@ b:linux-aarch64-package: - .cmake_release_artifacts - .linux_aarch64_tags - .run_only_for_package - dependencies: - - p:doc-package needs: - p:doc-package variables: @@ -774,16 +843,54 @@ u:linux-aarch64-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - b:linux-aarch64-package needs: - b:linux-aarch64-package +b:sunos-x86_64-package: + extends: + - .sunos_package + - .sunos_release_x86_64 + - .cmake_build_sunos_release + - .cmake_release_artifacts + - .linux_x86_64_tags + - .run_only_for_package + needs: + - p:doc-package + variables: + CMAKE_CI_ARTIFACTS_NAME: "artifacts-sunos-x86_64" + +u:sunos-x86_64-package: + extends: + - .rsync_upload_package + - .run_only_for_package + needs: + - b:sunos-x86_64-package + +b:sunos-sparc64-package: + extends: + - .sunos_package + - .sunos_release_sparc64 + - .cmake_build_sunos_release + - .cmake_release_artifacts + - .linux_x86_64_tags + - .run_only_for_package + needs: + - p:doc-package + variables: + CMAKE_CI_ARTIFACTS_NAME: "artifacts-sunos-sparc64" + +u:sunos-sparc64-package: + extends: + - .rsync_upload_package + - .run_only_for_package + needs: + - b:sunos-sparc64-package + ## Sanitizer builds -b:fedora38-asan: +b:fedora42-asan: extends: - - .fedora38_asan + - .fedora42_asan - .cmake_build_linux - .cmake_build_artifacts - .linux_x86_64_tags @@ -791,16 +898,14 @@ b:fedora38-asan: variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:fedora38-asan: +t:fedora42-asan: extends: - - .fedora38_asan + - .fedora42_asan - .cmake_memcheck_linux - .linux_x86_64_tags - .run_dependent - dependencies: - - b:fedora38-asan needs: - - b:fedora38-asan + - b:fedora42-asan variables: CMAKE_CI_JOB_NIGHTLY: "true" @@ -826,6 +931,35 @@ b:macos-arm64-ninja: variables: CMAKE_CI_JOB_CONTINUOUS: "true" +b:macos-arm64-ninja-symlinked: + extends: + - .macos_arm64_ninja_symlinked + - .cmake_build_macos + - .cmake_build_artifacts + - .macos_arm64_tags + - .run_manually + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +b:macos-arm64-curl: + extends: + - .macos_arm64_curl + - .cmake_build_macos + - .cmake_build_artifacts + - .macos_arm64_tags + - .run_manually + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +b:macos-arm64-pch: + extends: + - .macos_arm64_pch + - .cmake_build_macos + - .macos_arm64_tags + - .run_manually + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + t:macos-x86_64-ninja: extends: - .macos_x86_64_ninja @@ -833,8 +967,6 @@ t:macos-x86_64-ninja: - .cmake_test_artifacts - .macos_x86_64_tags - .run_dependent - dependencies: - - b:macos-x86_64-ninja needs: - b:macos-x86_64-ninja variables: @@ -847,14 +979,36 @@ t:macos-arm64-ninja: - .cmake_test_artifacts - .macos_arm64_tags - .run_dependent - dependencies: - - b:macos-arm64-ninja needs: - b:macos-arm64-ninja variables: CMAKE_CI_JOB_CONTINUOUS: "true" CMAKE_CI_JOB_NIGHTLY_NINJA: "true" +t:macos-arm64-ninja-symlinked: + extends: + - .macos_arm64_ninja_symlinked + - .cmake_test_macos + - .cmake_test_artifacts + - .macos_arm64_tags + - .run_dependent + needs: + - b:macos-arm64-ninja-symlinked + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + CMAKE_CI_JOB_NIGHTLY_NINJA: "true" + +t:macos-arm64-curl: + extends: + - .macos_arm64_curl + - .cmake_test_macos + - .macos_arm64_tags + - .run_dependent + needs: + - b:macos-arm64-curl + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + b:macos-x86_64-makefiles: extends: - .macos_x86_64_makefiles @@ -869,8 +1023,6 @@ t:macos-x86_64-makefiles: - .cmake_test_macos - .macos_x86_64_tags - .run_dependent - dependencies: - - b:macos-x86_64-makefiles needs: - b:macos-x86_64-makefiles @@ -881,8 +1033,6 @@ t:macos-arm64-ninja-multi: - .macos_arm64_tags_ext - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:macos-arm64-ninja needs: - t:macos-arm64-ninja variables: @@ -895,8 +1045,6 @@ t:macos-x86_64-xcode: - .macos_x86_64_tags_ext - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:macos-x86_64-ninja needs: - t:macos-x86_64-ninja variables: @@ -909,10 +1057,20 @@ t:macos-arm64-xcode: - .macos_arm64_tags_ext - .cmake_junit_artifacts - .run_dependent - dependencies: + needs: - t:macos-arm64-ninja + +t:macos-arm64-xcode-symlinked: + extends: + - .macos_arm64_xcode_symlinked + - .cmake_test_macos_external + - .macos_arm64_tags_ext + - .cmake_junit_artifacts + - .run_dependent needs: - t:macos-arm64-ninja + variables: + CMAKE_CI_JOB_NIGHTLY: "true" t:macos-x86_64-ninja-ub: extends: @@ -921,8 +1079,6 @@ t:macos-x86_64-ninja-ub: - .macos_x86_64_tags_ext - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:macos-x86_64-ninja needs: - t:macos-x86_64-ninja variables: @@ -935,8 +1091,6 @@ t:macos-arm64-xcode-ub: - .macos_arm64_tags_ext - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:macos-arm64-ninja needs: - t:macos-arm64-ninja variables: @@ -949,8 +1103,6 @@ b:macos-package: - .cmake_release_artifacts - .macos_arm64_tags_package - .run_only_for_package - dependencies: - - p:doc-package needs: - p:doc-package variables: @@ -960,8 +1112,6 @@ u:macos-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - b:macos-package needs: - b:macos-package @@ -972,8 +1122,6 @@ b:macos10.10-package: - .cmake_release_artifacts - .macos_arm64_tags_package - .run_only_for_package - dependencies: - - p:doc-package needs: - p:doc-package variables: @@ -983,8 +1131,6 @@ u:macos10.10-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - b:macos10.10-package needs: - b:macos10.10-package @@ -997,6 +1143,17 @@ b:windows-vs2022-x64-ninja: - .cmake_build_artifacts - .windows_x86_64_tags_nonconcurrent_vs2022 - .run_manually + variables: + CMAKE_CI_JOB_CONTINUOUS: "true" + +b:windows-vs2022-x64-pch: + extends: + - .windows_vs2022_x64_pch + - .cmake_build_windows + - .windows_x86_64_tags_nonconcurrent_vs2022 + - .run_manually + variables: + CMAKE_CI_JOB_NIGHTLY: "true" t:windows-vs2022-x64-ninja: extends: @@ -1005,11 +1162,10 @@ t:windows-vs2022-x64-ninja: - .windows_x86_64_tags_nonconcurrent_vs2022 - .cmake_test_artifacts - .run_dependent - dependencies: - - b:windows-vs2022-x64-ninja needs: - b:windows-vs2022-x64-ninja variables: + CMAKE_CI_JOB_CONTINUOUS: "true" CMAKE_CI_JOB_NIGHTLY_NINJA: "true" t:windows-vs2022-x64-ninja-multi: @@ -1019,34 +1175,50 @@ t:windows-vs2022-x64-ninja-multi: - .windows_x86_64_tags_concurrent_vs2022 - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:windows-vs2022-x64: +.t:windows-vs2022-x64: extends: - .windows_vs2022_x64 - .cmake_test_windows_external - - .windows_x86_64_tags_concurrent_vs2022 - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja +t:windows-vs2022-x64: + extends: + - .t:windows-vs2022-x64 + - .windows_x86_64_tags_concurrent_vs2022 + variables: + CMAKE_CI_JOB_NIGHTLY: "false" + +t:windows-vs2022-x64-nightly: + extends: + - .t:windows-vs2022-x64 + - .windows_x86_64_tags_concurrent_vs2022_android + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:windows-vs2022-x64-i18n: + extends: + - .t:windows-vs2022-x64 + - .windows_x86_64_tags_concurrent_vs2022_android + variables: + GIT_CLONE_PATH: "$CI_BUILDS_DIR\\cmake i18n cï\\$CI_CONCURRENT_ID" + CMAKE_CONFIGURATION: windows_vs2022_x64_i18n + CMAKE_CI_JOB_NIGHTLY: "true" + t:windows-vs2019-x64: extends: - .windows_vs2019_x64 - .cmake_test_windows_external - - .windows_x86_64_tags_concurrent_vs2019 + - .windows_x86_64_tags_concurrent_vs2019_android - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: @@ -1059,8 +1231,6 @@ t:windows-vs2022-x64-nmake: - .windows_x86_64_tags_concurrent_vs2022 - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: @@ -1073,8 +1243,6 @@ t:windows-vs2022-x64-jom: - .windows_x86_64_tags_concurrent_vs2022 - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: @@ -1087,8 +1255,6 @@ t:windows-borland5.5: - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: @@ -1101,101 +1267,87 @@ t:windows-borland5.8: - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: CMAKE_CI_JOB_NIGHTLY: "true" -t:windows-clang16.0-cl-ninja: +t:windows-clang19.1-cl-ninja: extends: - .windows_clang_ninja - .cmake_test_windows_external - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: - CMAKE_CI_BUILD_NAME: windows_clang16.0_cl_ninja + CMAKE_CI_BUILD_NAME: windows_clang19.1_cl_ninja CMAKE_CI_JOB_NIGHTLY: "true" -t:windows-clang16.0-cl-nmake: +t:windows-clang19.1-cl-nmake: extends: - .windows_clang_nmake - .cmake_test_windows_external - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: - CMAKE_CI_BUILD_NAME: windows_clang16.0_cl_nmake + CMAKE_CI_BUILD_NAME: windows_clang19.1_cl_nmake CMAKE_CI_JOB_NIGHTLY: "true" -t:windows-clang16.0-gnu-ninja: +t:windows-clang19.1-gnu-ninja: extends: - .windows_clang_ninja - .cmake_test_windows_external - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: - CMAKE_CI_BUILD_NAME: windows_clang16.0_gnu_ninja + CMAKE_CI_BUILD_NAME: windows_clang19.1_gnu_ninja CMAKE_CI_JOB_NIGHTLY: "true" -t:windows-clang16.0-gnu-nmake: +t:windows-clang19.1-gnu-nmake: extends: - .windows_clang_nmake - .cmake_test_windows_external - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: - CMAKE_CI_BUILD_NAME: windows_clang16.0_gnu_nmake + CMAKE_CI_BUILD_NAME: windows_clang19.1_gnu_nmake CMAKE_CI_JOB_NIGHTLY: "true" -t:windows-intel2021.9.0-ninja: +t:windows-intel2021.9-ninja: extends: - .windows_intelclassic_ninja - .cmake_test_windows_external - - .windows_x86_64_tags_concurrent + - .windows_x86_64_tags_concurrent_vs2022_msvc14.43 - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: - CMAKE_CI_BUILD_NAME: windows_intel2021.9.0_ninja + CMAKE_CI_BUILD_NAME: windows_intel2021.9_ninja CMAKE_CI_JOB_NIGHTLY: "true" -t:windows-oneapi2023.1.0-ninja: +t:windows-oneapi2025.1-ninja: extends: - .windows_inteloneapi_ninja - .cmake_test_windows_external - - .windows_x86_64_tags_concurrent + - .windows_x86_64_tags_concurrent_vs2022 - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: - CMAKE_CI_BUILD_NAME: windows_oneapi2023.1.0_ninja + CMAKE_CI_BUILD_NAME: windows_oneapi2025.1_ninja CMAKE_CI_JOB_NIGHTLY: "true" t:mingw_osdn_io-mingw_makefiles: @@ -1205,8 +1357,6 @@ t:mingw_osdn_io-mingw_makefiles: - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: @@ -1219,8 +1369,6 @@ t:mingw_osdn_io-msys_makefiles: - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: @@ -1233,8 +1381,6 @@ t:windows-msvc-v71-nmake: - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-vs2022-x64-ninja needs: - t:windows-vs2022-x64-ninja variables: @@ -1247,8 +1393,18 @@ t:windows-openwatcom1.9: - .windows_x86_64_tags_concurrent - .cmake_junit_artifacts - .run_dependent - dependencies: + needs: - t:windows-vs2022-x64-ninja + variables: + CMAKE_CI_JOB_NIGHTLY: "true" + +t:windows-orangec6.73.1: + extends: + - .windows_orangec6.73.1 + - .cmake_test_windows_external + - .windows_x86_64_tags_concurrent + - .cmake_junit_artifacts + - .run_dependent needs: - t:windows-vs2022-x64-ninja variables: @@ -1273,8 +1429,6 @@ t:windows-arm64-vs2022-ninja: - .windows_arm64_tags_nonconcurrent_vs2022 - .cmake_test_artifacts - .run_dependent - dependencies: - - b:windows-arm64-vs2022-ninja needs: - b:windows-arm64-vs2022-ninja variables: @@ -1287,8 +1441,6 @@ t:windows-arm64-vs2022: - .windows_arm64_tags_concurrent_vs2022 - .cmake_junit_artifacts - .run_dependent - dependencies: - - t:windows-arm64-vs2022-ninja needs: - t:windows-arm64-vs2022-ninja variables: @@ -1300,13 +1452,23 @@ b:windows-x86_64-package: extends: - .windows_x86_64_package - .cmake_build_windows - - .cmake_release_artifacts + - .cmake_build_package_artifacts - .windows_x86_64_tags_nonconcurrent_vs2022 - .run_only_for_package - dependencies: - - p:doc-package needs: - p:doc-package + variables: + CMAKE_CI_ARTIFACTS_NAME: "artifacts-windows-x86_64-build" + +k:windows-x86_64-package: + extends: + - .windows_x86_64_package + - .cmake_package_windows + - .cmake_release_artifacts + - .windows_x86_64_tags_nonconcurrent_sign + - .run_only_for_package + needs: + - b:windows-x86_64-package variables: CMAKE_CI_ARTIFACTS_NAME: "artifacts-windows-x86_64" @@ -1314,22 +1476,30 @@ u:windows-x86_64-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - b:windows-x86_64-package needs: - - b:windows-x86_64-package + - k:windows-x86_64-package b:windows-i386-package: extends: - .windows_i386_package - .cmake_build_windows - - .cmake_release_artifacts + - .cmake_build_package_artifacts - .windows_x86_64_tags_nonconcurrent_vs2022 - .run_only_for_package - dependencies: - - p:doc-package needs: - p:doc-package + variables: + CMAKE_CI_ARTIFACTS_NAME: "artifacts-windows-i386-build" + +k:windows-i386-package: + extends: + - .windows_i386_package + - .cmake_package_windows + - .cmake_release_artifacts + - .windows_x86_64_tags_nonconcurrent_sign + - .run_only_for_package + needs: + - b:windows-i386-package variables: CMAKE_CI_ARTIFACTS_NAME: "artifacts-windows-i386" @@ -1337,22 +1507,30 @@ u:windows-i386-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - b:windows-i386-package needs: - - b:windows-i386-package + - k:windows-i386-package b:windows-arm64-package: extends: - .windows_arm64_package - .cmake_build_windows - - .cmake_release_artifacts + - .cmake_build_package_artifacts - .windows_x86_64_tags_nonconcurrent_vs2022_arm64 - .run_only_for_package - dependencies: - - p:doc-package needs: - p:doc-package + variables: + CMAKE_CI_ARTIFACTS_NAME: "artifacts-windows-arm64-build" + +k:windows-arm64-package: + extends: + - .windows_arm64_package + - .cmake_package_windows + - .cmake_release_artifacts + - .windows_x86_64_tags_nonconcurrent_sign + - .run_only_for_package + needs: + - b:windows-arm64-package variables: CMAKE_CI_ARTIFACTS_NAME: "artifacts-windows-arm64" @@ -1360,7 +1538,5 @@ u:windows-arm64-package: extends: - .rsync_upload_package - .run_only_for_package - dependencies: - - b:windows-arm64-package needs: - - b:windows-arm64-package + - k:windows-arm64-package diff --git a/.gitlab/.gitignore b/.gitlab/.gitignore index 852dfa64d8d..ef38d5fd851 100644 --- a/.gitlab/.gitignore +++ b/.gitlab/.gitignore @@ -2,6 +2,7 @@ /5.15.1-0-202009071110* /bcc* /cmake* +/iar /intel /ispc* /innosetup @@ -10,11 +11,19 @@ /mingw /msvc* /ninja* +/nuget /openmp /open-watcom* +/orangec /python* /qt* /sccache* +/swift +/ticlang +/tmp /unstable-jom* /watcom -/wix* +/wix3 +/wix4 +/clang-tidy-fixes +/num_warnings.txt diff --git a/.gitlab/artifacts.yml b/.gitlab/artifacts.yml index 6c4cc0df755..f11706c0168 100644 --- a/.gitlab/artifacts.yml +++ b/.gitlab/artifacts.yml @@ -5,63 +5,96 @@ expire_in: 1d paths: # Test specifications. - - build/**/CTestTestfile.cmake + - ${CMAKE_CI_BUILD_DIR}/**/CTestTestfile.cmake # Allow CMake to find CMAKE_ROOT. - - build/CMakeFiles/CMakeSourceDir.txt + - ${CMAKE_CI_BUILD_DIR}/CMakeFiles/CMakeSourceDir.txt # Take the install tree. - - build/install/ + - ${CMAKE_CI_BUILD_DIR}/install/ # We need the main binaries. - - build/bin/ + - ${CMAKE_CI_BUILD_DIR}/bin/ # The cache is needed for the installation test. - - build/CMakeCache.txt + - ${CMAKE_CI_BUILD_DIR}/CMakeCache.txt # Test binaries. Eventually these might be better under # `Source/Tests` or the like. - - build/Tests/EnforceConfig.cmake - - build/Tests/CMakeBuildTest.cmake - - build/Tests/CMakeBuildDoubleProjectTest.cmake - - build/Tests/CMake*/runcompilecommands - - build/Tests/CMake*/runcompilecommands.exe - - build/Tests/CMake*/test* - - build/Tests/CMake*/PseudoMemcheck/valgrind - - build/Tests/CMake*/PseudoMemcheck/purify - - build/Tests/CMake*/PseudoMemcheck/memcheck_fail - - build/Tests/CMake*/PseudoMemcheck/BC - - build/Tests/CMake*/PseudoMemcheck/cuda-memcheck - - build/Tests/CMake*/PseudoMemcheck/valgrind.exe - - build/Tests/CMake*/PseudoMemcheck/purify.exe - - build/Tests/CMake*/PseudoMemcheck/memcheck_fail.exe - - build/Tests/CMake*/PseudoMemcheck/BC.exe - - build/Tests/CMake*/PseudoMemcheck/cuda-memcheck.exe - - build/Tests/CMake*/PseudoMemcheck/NoLog - - build/Tests/CMake*Lib/*LibTests - - build/Tests/CMake*Lib/*LibTests.exe - - build/Source/kwsys/cmsysTest* - - build/Source/kwsys/testConsoleBufChild.exe - - build/Utilities/cmcurl/curltest - - build/Utilities/cmcurl/curltest.exe - - build/Utilities/KWIML/test/kwiml_test - - build/Utilities/KWIML/test/kwiml_test.exe - - build/Source/kwsys/*cmsysTestDynload.* - - build/Source/kwsys/dynloaddir/cmsysTestDynloadImpl.dll - - build/Source/kwsys/dynloaddir/cmsysTestDynloadUse.dll + - ${CMAKE_CI_BUILD_DIR}/Tests/EnforceConfig.cmake + - ${CMAKE_CI_BUILD_DIR}/Tests/CMakeBuildTest.cmake + - ${CMAKE_CI_BUILD_DIR}/Tests/CMakeBuildDoubleProjectTest.cmake + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/runcompilecommands + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/runcompilecommands.exe + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/test* + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/valgrind + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/purify + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/memcheck_fail + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/BC + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/cuda-memcheck + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/valgrind.exe + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/purify.exe + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/memcheck_fail.exe + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/BC.exe + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/cuda-memcheck.exe + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*/PseudoMemcheck/NoLog + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*Lib/*LibTests + - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*Lib/*LibTests.exe + - ${CMAKE_CI_BUILD_DIR}/Source/kwsys/cmsysTest* + - ${CMAKE_CI_BUILD_DIR}/Utilities/cmcurl/curltest + - ${CMAKE_CI_BUILD_DIR}/Utilities/cmcurl/curltest.exe + - ${CMAKE_CI_BUILD_DIR}/Utilities/KWIML/test/kwiml_test + - ${CMAKE_CI_BUILD_DIR}/Utilities/KWIML/test/kwiml_test.exe + - ${CMAKE_CI_BUILD_DIR}/Source/kwsys/*cmsysTestDynload.* + - ${CMAKE_CI_BUILD_DIR}/Source/kwsys/dynloaddir/cmsysTestDynloadImpl.dll + - ${CMAKE_CI_BUILD_DIR}/Source/kwsys/dynloaddir/cmsysTestDynloadUse.dll # Test directories. - - build/Tests/CTest* - - build/Tests/Find* - - build/Tests/Qt* - - build/Tests/RunCMake/ - - build/Tests/CMakeOnly/ - - build/Tests/CMakeTests/ - - build/Tests/CMakeGUI/ - - build/Tests/FortranC/ + - ${CMAKE_CI_BUILD_DIR}/Tests/CTest* + - ${CMAKE_CI_BUILD_DIR}/Tests/Find* + - ${CMAKE_CI_BUILD_DIR}/Tests/Qt* + - ${CMAKE_CI_BUILD_DIR}/Tests/RunCMake/ + - ${CMAKE_CI_BUILD_DIR}/Tests/CMakeOnly/ + - ${CMAKE_CI_BUILD_DIR}/Tests/CMakeTests/ + - ${CMAKE_CI_BUILD_DIR}/Tests/CMakeGUI/ + - ${CMAKE_CI_BUILD_DIR}/Tests/FortranC/ # CTest/CDash information. - - build/Testing/ - - build/DartConfiguation.tcl - - build/CTestCustom.cmake + - ${CMAKE_CI_BUILD_DIR}/Testing/ + - ${CMAKE_CI_BUILD_DIR}/DartConfiguration.tcl + - ${CMAKE_CI_BUILD_DIR}/CTestCustom.cmake + - ${CMAKE_CI_BUILD_DIR}/cdash-build-id + reports: + annotations: + - ${CMAKE_CI_BUILD_DIR}/annotations.json + +.cmake_build_package_artifacts: + artifacts: + expire_in: 1d + name: "$CMAKE_CI_ARTIFACTS_NAME" + paths: + # Allow CPack to find CMAKE_ROOT and license text. + - build/CMakeFiles/CMakeSourceDir.txt + - build/CMakeFiles/LICENSE.txt + + # Install rules. + - build/**/cmake_install.cmake + + # We need the main binaries. + - build/bin/ + + # Pass through the documentation. + - build/install-doc/ + + # CPack configuration. + - build/CPackConfig.cmake + - build/CMakeCPackOptions.cmake + - build/Source/QtDialog/QtDialogCPack.cmake + + # CPack/IFW packaging files. + - build/CMake*.qs + + # CPack/WIX packaging files. + - build/Utilities/Release/WiX/custom_action_dll*.wxs + - build/Utilities/Release/WiX/CustomAction/CMakeWiXCustomActions.* .cmake_release_artifacts: artifacts: @@ -71,34 +104,54 @@ when: always paths: # Any packages made. - - build/cmake-*-linux-x86_64.* - - build/cmake-*-linux-aarch64.* - - build/cmake-*-macos*-universal.* - - build/cmake-*-windows-x86_64.* - - build/cmake-*-windows-i386.* - - build/cmake-*-windows-arm64.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-linux-x86_64.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-linux-aarch64.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-macos*-universal.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-sunos-x86_64.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-sunos-sparc64.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-windows-x86_64.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-windows-i386.* + - ${CMAKE_CI_BUILD_DIR}/cmake-*-windows-arm64.* # Any source packages made. - - build/cmake-*.tar.gz - - build/cmake-*.zip + - ${CMAKE_CI_BUILD_DIR}/cmake-*.tar.gz + - ${CMAKE_CI_BUILD_DIR}/cmake-*.zip # Any unsigned packages made. - - build/unsigned/cmake-* + - ${CMAKE_CI_BUILD_DIR}/unsigned/cmake-* + reports: + annotations: + - ${CMAKE_CI_BUILD_DIR}/annotations.json + +.cmake_cdash_artifacts: + artifacts: + expire_in: 1d + when: always + reports: + annotations: + - ${CMAKE_CI_BUILD_DIR}/annotations.json .cmake_junit_artifacts: artifacts: expire_in: 1d when: always reports: + annotations: + - ${CMAKE_CI_BUILD_DIR}/annotations.json junit: - - build/junit.xml + - ${CMAKE_CI_BUILD_DIR}/junit.xml .cmake_sphinx_artifacts: artifacts: expire_in: 1d when: always + reports: + annotations: + - ${CMAKE_CI_BUILD_DIR}/annotations.json paths: # Take the sphinx logs. - - build/build-*.log - - build/linkcheck/output.* + - ${CMAKE_CI_BUILD_DIR}/build-*.log + - ${CMAKE_CI_BUILD_DIR}/linkcheck/output.* + # Take the HTML output. + - ${CMAKE_CI_BUILD_DIR}/html/ .cmake_test_artifacts: artifacts: @@ -107,22 +160,34 @@ when: always reports: junit: - - build/junit.xml + - ${CMAKE_CI_BUILD_DIR}/junit.xml + annotations: + - ${CMAKE_CI_BUILD_DIR}/annotations.json paths: # Take the install tree. - - build/install/ + - ${CMAKE_CI_BUILD_DIR}/install/ .cmake_doc_artifacts: artifacts: expire_in: 1d + reports: + annotations: + - ${CMAKE_CI_BUILD_DIR}/annotations.json paths: # Take the install tree. - - build/install-doc/ + - ${CMAKE_CI_BUILD_DIR}/install-doc/ .cmake_org_help_artifacts: artifacts: expire_in: 1d paths: - - build/html + - ${CMAKE_CI_BUILD_DIR}/html exclude: - - build/html/.buildinfo + - ${CMAKE_CI_BUILD_DIR}/html/.buildinfo + +.cmake_tidy_artifacts: + artifacts: + expire_in: 1d + when: always + paths: + - clang-tidy-fixes.patch diff --git a/.gitlab/ci/CMakeCPack.cmake b/.gitlab/ci/CMakeCPack.cmake deleted file mode 100644 index 971fe54dc4f..00000000000 --- a/.gitlab/ci/CMakeCPack.cmake +++ /dev/null @@ -1,3 +0,0 @@ -if(NOT "$ENV{CMAKE_CI_PACKAGE}" MATCHES "^(dev)?$") - configure_file(${CMAKE_CURRENT_LIST_DIR}/package_info.cmake.in ${CMake_BINARY_DIR}/ci_package_info.cmake @ONLY) -endif() diff --git a/.gitlab/ci/borland-env.ps1 b/.gitlab/ci/borland-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/borland.ps1 b/.gitlab/ci/borland.ps1 old mode 100755 new mode 100644 index 146a0471f34..c8daafda50d --- a/.gitlab/ci/borland.ps1 +++ b/.gitlab/ci/borland.ps1 @@ -28,6 +28,7 @@ if ($hash.Hash -ne $sha256sum) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") Move-Item -Path "$outdir\$filename" -Destination "$outdir\bcc" +Remove-Item "$outdir\$tarball" $tools = "bcc32", "ilink32" foreach ($tool in $tools) { diff --git a/.gitlab/ci/clang-env.ps1 b/.gitlab/ci/clang-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/clang.ps1 b/.gitlab/ci/clang.ps1 old mode 100755 new mode 100644 index 1fc8d8e1e52..61361b14388 --- a/.gitlab/ci/clang.ps1 +++ b/.gitlab/ci/clang.ps1 @@ -1,10 +1,10 @@ $erroractionpreference = "stop" -if ("$env:CMAKE_CI_BUILD_NAME".Contains("clang16.0")) { - # LLVM/Clang 16.0 - # https://github.com/llvm/llvm-project/releases/tag/llvmorg-16.0.0 - $filename = "llvm-16.0.0-win-x86_64-1" - $sha256sum = "13F48356BA5892A82E8BB25EB283FDDAA8F23A0F209B6BF6525D2C5E1285B950" +if ("$env:CMAKE_CI_BUILD_NAME".Contains("clang19.1")) { + # LLVM/Clang 19.1.0 + # https://github.com/llvm/llvm-project/releases/tag/llvmorg-19.1.0 + $filename = "llvm-19.1.0-win-x86_64-1" + $sha256sum = "C1F974511A6FA2DC5B4892996C064A55BF81D7F244514F8AB5A453110ADEC0EC" } else { throw ('unknown CMAKE_CI_BUILD_NAME: ' + "$env:CMAKE_CI_BUILD_NAME") } @@ -23,6 +23,7 @@ if ($hash.Hash -ne $sha256sum) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") Move-Item -Path "$outdir\$filename" -Destination "$outdir\llvm" +Remove-Item "$outdir\$tarball" $bin = "$outdir\llvm\bin" $lib = "$outdir\llvm\lib" diff --git a/.gitlab/ci/cmake-env.ps1 b/.gitlab/ci/cmake-env.ps1 new file mode 100644 index 00000000000..505fa446310 --- /dev/null +++ b/.gitlab/ci/cmake-env.ps1 @@ -0,0 +1,5 @@ +$pwdpath = $pwd.Path +& "$pwsh" -File ".gitlab/ci/cmake.ps1" +Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\cmake\bin;$env:PATH" +cmake --version +$cmake = "cmake" diff --git a/.gitlab/ci/cmake-env.sh b/.gitlab/ci/cmake-env.sh new file mode 100644 index 00000000000..686a78f379f --- /dev/null +++ b/.gitlab/ci/cmake-env.sh @@ -0,0 +1,3 @@ +.gitlab/ci/cmake.sh +export PATH="$PWD/.gitlab/cmake/bin:$PATH" +cmake --version diff --git a/.gitlab/ci/cmake.ps1 b/.gitlab/ci/cmake.ps1 old mode 100755 new mode 100644 index 3efb67ab713..1353150f181 --- a/.gitlab/ci/cmake.ps1 +++ b/.gitlab/ci/cmake.ps1 @@ -1,12 +1,12 @@ $erroractionpreference = "stop" -$version = "3.24.1" +$version = "3.31.5" if ("$env:PROCESSOR_ARCHITECTURE" -eq "AMD64") { - $sha256sum = "C1B17431A16337D517F7BA78C7067B6F143A12686CB8087F3DD32F3FA45F5AAE" + $sha256sum = "D4D2D4B9CCD68DAE975A066FCD42EA9807EF40F79EE6971923FD3788E7917585" $platform = "windows-x86_64" } elseif ("$env:PROCESSOR_ARCHITECTURE" -eq "ARM64") { - $sha256sum = "D94683F3B0E63F6EF194C621194F6E26F3735EDA70750395E0F2BBEE4023FB95" + $sha256sum = "A734E4E970FDAA4B5957157C059556F56CA5D655014CD4B5FD9194AABA316F31" $platform = "windows-arm64" } else { throw ('unknown PROCESSOR_ARCHITECTURE: ' + "$env:PROCESSOR_ARCHITECTURE") @@ -27,3 +27,4 @@ if ($hash.Hash -ne $sha256sum) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") Move-Item -Path "$outdir\$filename" -Destination "$outdir\cmake" +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/ci/cmake.sh b/.gitlab/ci/cmake.sh index 137da067930..10aba43b8e4 100755 --- a/.gitlab/ci/cmake.sh +++ b/.gitlab/ci/cmake.sh @@ -2,22 +2,22 @@ set -e -readonly version="3.24.1" +readonly version="3.31.5" case "$(uname -s)-$(uname -m)" in Linux-x86_64) shatool="sha256sum" - sha256sum="827bf068cfaa23a9fb95f990c9f8a7ed8f2caeb3af62b5c0a2fed7a8dd6dde3e" + sha256sum="2984e70515ff60c5e4a41922b5d715a8168a696a89721e3b114e36f453244f72" platform="linux-x86_64" ;; Linux-aarch64) shatool="sha256sum" - sha256sum="d50c40135df667ed659f8e4eb7cf7d53421250304f7b3e1a70af9cf3d0f2ab18" + sha256sum="eb92af175ea91e3706ff62484088c3a3774ef3e1a8c399111785dd5f47010164" platform="linux-aarch64" ;; Darwin-*) shatool="shasum -a 256" - sha256sum="71bb8db69826d74c395a3c3bbf8b773dbe9f54a2c7331266ba70da303e9c97a1" + sha256sum="cc8e3d9bef7eee70db52601a5ed60d221436a8def18388effdab0e7d0866f50d" platform="macos-universal" ;; *) @@ -39,6 +39,7 @@ curl -OL "https://github.com/Kitware/CMake/releases/download/v$version/$tarball" $shatool --check cmake.sha256sum tar xf "$tarball" mv "$filename" cmake +rm "$tarball" cmake.sha256sum if [ "$( uname -s )" = "Darwin" ]; then ln -s CMake.app/Contents/bin cmake/bin diff --git a/.gitlab/ci/cmake_version_update.sh b/.gitlab/ci/cmake_version_update.sh new file mode 100755 index 00000000000..e73419d78ad --- /dev/null +++ b/.gitlab/ci/cmake_version_update.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -e + +if test "$CI_COMMIT_REF_NAME" != "master"; then + echo "The version update may run only on the 'master' branch." + exit 1 +fi + +# A project-specific access token must be provided by +# the pipeline schedule under which this job runs. +if test -z "$CMAKE_CI_GIT_ACCESS_TOKEN"; then + echo "No CMAKE_CI_GIT_ACCESS_TOKEN is available." + exit 1 +fi + +git config user.name "${CMAKE_CI_AUTHOR_NAME-Kitware Robot}" +git config user.email "${CMAKE_CI_AUTHOR_EMAIL-kwrobot@kitware.com}" +git remote add upstream "https://oauth2:$CMAKE_CI_GIT_ACCESS_TOKEN@gitlab.kitware.com/$CI_PROJECT_PATH.git" + +# Repeat a few times in case we lose a race. +n=6 +for try in $(seq $n); do + git fetch upstream "$CI_COMMIT_REF_NAME" + git reset -q --hard FETCH_HEAD + Source/CMakeVersion.bash + git update-index -q --ignore-missing --refresh + modified=$(git diff-index --name-only HEAD -- "Source/CMakeVersion.cmake" "LICENSE.rst") + if test -n "$modified"; then + echo "version changed" + git add -u + git commit -m "CMake Nightly Date Stamp" + if git push --push-option=ci.skip upstream "HEAD:$CI_COMMIT_REF_NAME"; then + exit 0 + else + echo "Try #$try failed to fast-forward." + fi + else + echo "version unchanged" + exit 0 + fi + sleep 30 +done + +# Give up after failing too many times. +exit 1 diff --git a/.gitlab/ci/codespell.bash b/.gitlab/ci/codespell.bash new file mode 100755 index 00000000000..bbaffd231f9 --- /dev/null +++ b/.gitlab/ci/codespell.bash @@ -0,0 +1,21 @@ +#!/bin/sh + +set -e + +result=0 + +# 'codespell' with no arguments adds a leading './' to all paths. +# Avoid that by globbing top-level entries explicitly. +shopt -s dotglob +echo "Running 'codespell' on source code..." +codespell * || result=1 +shopt -u dotglob + +if [ -n "$CI_MERGE_REQUEST_DIFF_BASE_SHA" ]; then + for COMMIT in $(git rev-list "^$CI_MERGE_REQUEST_DIFF_BASE_SHA" "$CI_COMMIT_SHA"); do + echo "Running 'codespell' on commit message of $COMMIT..." + git show --format=%B -s "$COMMIT" | codespell - || result=1 + done +fi + +exit $result diff --git a/.gitlab/ci/codespell.sh b/.gitlab/ci/codespell.sh deleted file mode 100755 index fd052bd0aa2..00000000000 --- a/.gitlab/ci/codespell.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -set -e - -result=0 -echo "Running codespell on source code..." -codespell || result=1 - -if [ -n "$CI_MERGE_REQUEST_DIFF_BASE_SHA" ]; then - for COMMIT in $(git rev-list "^$CI_MERGE_REQUEST_DIFF_BASE_SHA" "$CI_COMMIT_SHA"); do - echo "Running codespell on commit message of $COMMIT..." - git show --format=%B -s "$COMMIT" | codespell - || result=1 - done -fi - -exit $result diff --git a/.gitlab/ci/configure_common.cmake b/.gitlab/ci/configure_common.cmake index c207a741985..f03c4b8396d 100644 --- a/.gitlab/ci/configure_common.cmake +++ b/.gitlab/ci/configure_common.cmake @@ -13,6 +13,10 @@ set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY "ON" CACHE BOOL "") set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "") set(CMake_TEST_INSTALL "OFF" CACHE BOOL "") +set(CTEST_TEST_CTEST ON CACHE BOOL "") +set(CMAKE_RUN_LONG_TESTS ON CACHE BOOL "") +set(CMAKE_TESTS_CDASH_SERVER "NOTFOUND" CACHE STRING "") + if (NOT "$ENV{CMAKE_CI_BUILD_TYPE}" STREQUAL "") set(CMAKE_BUILD_TYPE "$ENV{CMAKE_CI_BUILD_TYPE}" CACHE STRING "") endif () diff --git a/.gitlab/ci/configure_cuda10.2_clang.cmake b/.gitlab/ci/configure_cuda10.2_clang.cmake index e13ca881d6c..686e783e398 100644 --- a/.gitlab/ci/configure_cuda10.2_clang.cmake +++ b/.gitlab/ci/configure_cuda10.2_clang.cmake @@ -1,3 +1,5 @@ set(CMake_TEST_CUDA "Clang" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_CUPTI "ON" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda10.2_nvidia.cmake b/.gitlab/ci/configure_cuda10.2_nvidia.cmake index 519699b0ccf..a6c6fdbbe2b 100644 --- a/.gitlab/ci/configure_cuda10.2_nvidia.cmake +++ b/.gitlab/ci/configure_cuda10.2_nvidia.cmake @@ -1,3 +1,5 @@ set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_CUPTI "ON" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda11.6_clang.cmake b/.gitlab/ci/configure_cuda11.6_clang.cmake index e13ca881d6c..f9490c7bab0 100644 --- a/.gitlab/ci/configure_cuda11.6_clang.cmake +++ b/.gitlab/ci/configure_cuda11.6_clang.cmake @@ -1,3 +1,4 @@ set(CMake_TEST_CUDA "Clang" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda11.6_nvidia.cmake b/.gitlab/ci/configure_cuda11.6_nvidia.cmake index 519699b0ccf..a6c6fdbbe2b 100644 --- a/.gitlab/ci/configure_cuda11.6_nvidia.cmake +++ b/.gitlab/ci/configure_cuda11.6_nvidia.cmake @@ -1,3 +1,5 @@ set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_CUPTI "ON" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda11.8_minimal_nvidia.cmake b/.gitlab/ci/configure_cuda11.8_minimal_nvidia.cmake index 519699b0ccf..b25bc1f5473 100644 --- a/.gitlab/ci/configure_cuda11.8_minimal_nvidia.cmake +++ b/.gitlab/ci/configure_cuda11.8_minimal_nvidia.cmake @@ -1,3 +1,4 @@ set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda11.8_splayed_nvidia.cmake b/.gitlab/ci/configure_cuda11.8_splayed_nvidia.cmake new file mode 100644 index 00000000000..b25bc1f5473 --- /dev/null +++ b/.gitlab/ci/configure_cuda11.8_splayed_nvidia.cmake @@ -0,0 +1,4 @@ +set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda12.2_clang.cmake b/.gitlab/ci/configure_cuda12.2_clang.cmake new file mode 100644 index 00000000000..ed53d1beaee --- /dev/null +++ b/.gitlab/ci/configure_cuda12.2_clang.cmake @@ -0,0 +1,7 @@ +set(CMake_TEST_CUDA "Clang" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_STANDARDS "03;11;14;17;20;23" CACHE STRING "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CUDA "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda12.2_nvidia.cmake b/.gitlab/ci/configure_cuda12.2_nvidia.cmake new file mode 100644 index 00000000000..3019a99f37c --- /dev/null +++ b/.gitlab/ci/configure_cuda12.2_nvidia.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_cuda12.2_nvidia_common.cmake") diff --git a/.gitlab/ci/configure_cuda12.2_nvidia_common.cmake b/.gitlab/ci/configure_cuda12.2_nvidia_common.cmake new file mode 100644 index 00000000000..392d6695cf8 --- /dev/null +++ b/.gitlab/ci/configure_cuda12.2_nvidia_common.cmake @@ -0,0 +1,8 @@ +set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_CUPTI "ON" CACHE STRING "") +set(CMake_TEST_CUDA_STANDARDS "03;11;14;17;20" CACHE STRING "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CUDA "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda12.6_clang.cmake b/.gitlab/ci/configure_cuda12.6_clang.cmake new file mode 100644 index 00000000000..ed53d1beaee --- /dev/null +++ b/.gitlab/ci/configure_cuda12.6_clang.cmake @@ -0,0 +1,7 @@ +set(CMake_TEST_CUDA "Clang" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_STANDARDS "03;11;14;17;20;23" CACHE STRING "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CUDA "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda12.6_nvidia.cmake b/.gitlab/ci/configure_cuda12.6_nvidia.cmake new file mode 100644 index 00000000000..5aeebf60c6a --- /dev/null +++ b/.gitlab/ci/configure_cuda12.6_nvidia.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_cuda12.6_nvidia_common.cmake") diff --git a/.gitlab/ci/configure_cuda12.6_nvidia_clang.cmake b/.gitlab/ci/configure_cuda12.6_nvidia_clang.cmake new file mode 100644 index 00000000000..5aeebf60c6a --- /dev/null +++ b/.gitlab/ci/configure_cuda12.6_nvidia_clang.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_cuda12.6_nvidia_common.cmake") diff --git a/.gitlab/ci/configure_cuda12.6_nvidia_common.cmake b/.gitlab/ci/configure_cuda12.6_nvidia_common.cmake new file mode 100644 index 00000000000..392d6695cf8 --- /dev/null +++ b/.gitlab/ci/configure_cuda12.6_nvidia_common.cmake @@ -0,0 +1,8 @@ +set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_CUPTI "ON" CACHE STRING "") +set(CMake_TEST_CUDA_STANDARDS "03;11;14;17;20" CACHE STRING "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CUDA "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_cuda9.2_nvidia.cmake b/.gitlab/ci/configure_cuda9.2_nvidia.cmake index 519699b0ccf..68e73b64696 100644 --- a/.gitlab/ci/configure_cuda9.2_nvidia.cmake +++ b/.gitlab/ci/configure_cuda9.2_nvidia.cmake @@ -1,3 +1,4 @@ set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "30" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_aarch64_ninja.cmake b/.gitlab/ci/configure_debian10_aarch64_ninja.cmake deleted file mode 100644 index dff0db15687..00000000000 --- a/.gitlab/ci/configure_debian10_aarch64_ninja.cmake +++ /dev/null @@ -1,97 +0,0 @@ -set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_CVS "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "") -set(CMake_TEST_FindALSA "ON" CACHE BOOL "") -set(CMake_TEST_FindBLAS "All;static=1;Generic" CACHE STRING "") -set(CMake_TEST_FindBoost "ON" CACHE BOOL "") -set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "") -set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "") -set(CMake_TEST_FindCups "ON" CACHE BOOL "") -set(CMake_TEST_FindCURL "ON" CACHE BOOL "") -set(CMake_TEST_FindDevIL "ON" CACHE BOOL "") -set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "") -set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "") -set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "") -set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "") -set(CMake_TEST_FindFreetype "ON" CACHE BOOL "") -set(CMake_TEST_FindGDAL "ON" CACHE BOOL "") -set(CMake_TEST_FindGIF "ON" CACHE BOOL "") -set(CMake_TEST_FindGit "ON" CACHE BOOL "") -set(CMake_TEST_FindGLEW "ON" CACHE BOOL "") -set(CMake_TEST_FindGLUT "ON" CACHE BOOL "") -set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "") -set(CMake_TEST_FindGSL "ON" CACHE BOOL "") -set(CMake_TEST_FindGTest "ON" CACHE BOOL "") -set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/bin/h5pcc.mpich" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_MPICH_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") -# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/bin/h5c++.mpich" CACHE FILEPATH "") # h5c++.mpich does not exist -# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/bin/h5pfc.mpich" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/bin/h5pcc.openmpi" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") -# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/bin/h5c++.openmpi" CACHE FILEPATH "") # h5c++.openmpi does not exist -# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/bin/h5pfc.openmpi" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "") -set(CMake_TEST_FindIconv "ON" CACHE BOOL "") -set(CMake_TEST_FindICU "ON" CACHE BOOL "") -set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "") -set(CMake_TEST_FindIntl "ON" CACHE BOOL "") -set(CMake_TEST_FindJNI "ON" CACHE BOOL "") -set(CMake_TEST_FindJPEG "ON" CACHE BOOL "") -set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "") -set(CMake_TEST_FindLAPACK "All;static=1;Generic" CACHE STRING "") -set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "") -set(CMake_TEST_FindLibinput "ON" CACHE BOOL "") -set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "") -set(CMake_TEST_FindLibUV "ON" CACHE BOOL "") -set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "") -set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI "ON" CACHE BOOL "") -set(CMake_TEST_FindODBC "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "") -set(CMake_TEST_FindPatch "ON" CACHE BOOL "") -set(CMake_TEST_FindPNG "ON" CACHE BOOL "") -set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "") -set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "") -set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "") -set(CMake_TEST_FindPython "ON" CACHE BOOL "") -set(CMake_TEST_FindPython_NumPy "ON" CACHE BOOL "") -set(CMake_TEST_FindPython_PyPy "ON" CACHE BOOL "") -set(CMake_TEST_FindRuby "ON" CACHE BOOL "") -set(CMake_TEST_FindSDL "ON" CACHE BOOL "") -set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "") -set(CMake_TEST_FindTIFF "ON" CACHE BOOL "") -set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "") -set(CMake_TEST_FindX11 "ON" CACHE BOOL "") -set(CMake_TEST_FindXalanC "ON" CACHE BOOL "") -set(CMake_TEST_FindXercesC "ON" CACHE BOOL "") -set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "") -set(CMake_TEST_Qt5 "ON" CACHE BOOL "") -set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") -set(CMake_TEST_UseSWIG "ON" CACHE BOOL "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_iwyu.cmake b/.gitlab/ci/configure_debian10_iwyu.cmake deleted file mode 100644 index abe750d3de2..00000000000 --- a/.gitlab/ci/configure_debian10_iwyu.cmake +++ /dev/null @@ -1,6 +0,0 @@ -set(CMake_RUN_IWYU ON CACHE BOOL "") -# Uncomment to diagnose IWYU problems as needed. -#set(CMake_IWYU_VERBOSE ON CACHE BOOL "") -set(IWYU_COMMAND "/usr/bin/include-what-you-use-6.0" CACHE FILEPATH "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_debian10_legacy.cmake b/.gitlab/ci/configure_debian10_legacy.cmake new file mode 100644 index 00000000000..11a5dc0254f --- /dev/null +++ b/.gitlab/ci/configure_debian10_legacy.cmake @@ -0,0 +1,6 @@ +set(CMake_TEST_FindPython2 "ON" CACHE BOOL "") +set(CMake_TEST_FindPython2_IronPython "ON" CACHE BOOL "") +set(CMake_TEST_FindPython2_NumPy "ON" CACHE BOOL "") +set(CMake_TEST_FindPython2_PyPy "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_makefiles_clang.cmake b/.gitlab/ci/configure_debian10_makefiles_clang.cmake deleted file mode 100644 index 20863a2fb08..00000000000 --- a/.gitlab/ci/configure_debian10_makefiles_clang.cmake +++ /dev/null @@ -1 +0,0 @@ -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_ninja.cmake b/.gitlab/ci/configure_debian10_ninja.cmake deleted file mode 100644 index 211a2a74d9b..00000000000 --- a/.gitlab/ci/configure_debian10_ninja.cmake +++ /dev/null @@ -1,107 +0,0 @@ -set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_CVS "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "") -if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") - set(CMake_TEST_CTestUpdate_P4 "ON" CACHE BOOL "") -endif() - -set(CMake_TEST_FindALSA "ON" CACHE BOOL "") -set(CMake_TEST_FindBLAS "All;static=1;Generic" CACHE STRING "") -set(CMake_TEST_FindBoost "ON" CACHE BOOL "") -set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "") -set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "") -set(CMake_TEST_FindCups "ON" CACHE BOOL "") -set(CMake_TEST_FindCURL "ON" CACHE BOOL "") -set(CMake_TEST_FindDevIL "ON" CACHE BOOL "") -set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "") -set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "") -set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "") -set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "") -set(CMake_TEST_FindFreetype "ON" CACHE BOOL "") -set(CMake_TEST_FindGDAL "ON" CACHE BOOL "") -set(CMake_TEST_FindGIF "ON" CACHE BOOL "") -set(CMake_TEST_FindGit "ON" CACHE BOOL "") -set(CMake_TEST_FindGLEW "ON" CACHE BOOL "") -set(CMake_TEST_FindGLUT "ON" CACHE BOOL "") -set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "") -set(CMake_TEST_FindGSL "ON" CACHE BOOL "") -set(CMake_TEST_FindGTest "ON" CACHE BOOL "") -set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/bin/h5pcc.mpich" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_MPICH_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") -# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/bin/h5c++.mpich" CACHE FILEPATH "") # h5c++.mpich does not exist -# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/bin/h5pfc.mpich" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/bin/h5pcc.openmpi" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") -# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/bin/h5c++.openmpi" CACHE FILEPATH "") # h5c++.openmpi does not exist -# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/bin/h5pfc.openmpi" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "") -set(CMake_TEST_FindIconv "ON" CACHE BOOL "") -set(CMake_TEST_FindICU "ON" CACHE BOOL "") -set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "") -set(CMake_TEST_FindIntl "ON" CACHE BOOL "") -set(CMake_TEST_FindJNI "ON" CACHE BOOL "") -set(CMake_TEST_FindJPEG "ON" CACHE BOOL "") -set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "") -set(CMake_TEST_FindLAPACK "All;static=1;Generic" CACHE STRING "") -set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "") -set(CMake_TEST_FindLibinput "ON" CACHE BOOL "") -set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "") -set(CMake_TEST_FindLibUV "ON" CACHE BOOL "") -set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "") -set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI "ON" CACHE BOOL "") -set(CMake_TEST_FindODBC "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "") -set(CMake_TEST_FindPatch "ON" CACHE BOOL "") -set(CMake_TEST_FindPNG "ON" CACHE BOOL "") -set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "") -set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "") -set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "") -set(CMake_TEST_FindPython "ON" CACHE BOOL "") -set(CMake_TEST_FindPython_IronPython "ON" CACHE BOOL "") -set(CMake_TEST_FindPython_NumPy "ON" CACHE BOOL "") -set(CMake_TEST_FindPython_PyPy "ON" CACHE BOOL "") -set(CMake_TEST_FindRuby "ON" CACHE BOOL "") -set(CMake_TEST_FindRuby_RVM "ON" CACHE BOOL "") -set(CMake_TEST_FindSDL "ON" CACHE BOOL "") -set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "") -set(CMake_TEST_FindTIFF "ON" CACHE BOOL "") -set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "") -set(CMake_TEST_FindX11 "ON" CACHE BOOL "") -set(CMake_TEST_FindXalanC "ON" CACHE BOOL "") -set(CMake_TEST_FindXercesC "ON" CACHE BOOL "") -set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "") -set(CMake_TEST_Qt5 "ON" CACHE BOOL "") -set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") -set(CMake_TEST_UseSWIG "ON" CACHE BOOL "") - -if (NOT "$ENV{SWIFTC}" STREQUAL "") - set(CMAKE_Swift_COMPILER "$ENV{SWIFTC}" CACHE FILEPATH "") -endif() - -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_ninja_clang.cmake b/.gitlab/ci/configure_debian10_ninja_clang.cmake deleted file mode 100644 index 20863a2fb08..00000000000 --- a/.gitlab/ci/configure_debian10_ninja_clang.cmake +++ /dev/null @@ -1 +0,0 @@ -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_aarch64_extdeps.cmake b/.gitlab/ci/configure_debian12_aarch64_extdeps.cmake similarity index 100% rename from .gitlab/ci/configure_debian10_aarch64_extdeps.cmake rename to .gitlab/ci/configure_debian12_aarch64_extdeps.cmake diff --git a/.gitlab/ci/configure_debian12_aarch64_ninja.cmake b/.gitlab/ci/configure_debian12_aarch64_ninja.cmake new file mode 100644 index 00000000000..b53382280b5 --- /dev/null +++ b/.gitlab/ci/configure_debian12_aarch64_ninja.cmake @@ -0,0 +1,116 @@ +set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") + +set(blas_lapack_cases + All + static=1 Generic + model=lp64 + static=0 thread=SEQ NVPL thread=OMP NVPL thread= NVPL + model=ilp64 + static=0 thread=SEQ NVPL thread=OMP NVPL thread= NVPL + ) + +set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_CVS "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "") +set(CMake_TEST_FindALSA "ON" CACHE BOOL "") +set(CMake_TEST_FindASPELL "ON" CACHE BOOL "") +set(CMake_TEST_FindBLAS "${blas_lapack_cases}" CACHE STRING "") +set(CMake_TEST_FindBoost "ON" CACHE BOOL "") +set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "") +set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "") +set(CMake_TEST_FindCups "ON" CACHE BOOL "") +set(CMake_TEST_FindCURL "ON" CACHE BOOL "") +set(CMake_TEST_FindDevIL "ON" CACHE BOOL "") +set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "") +set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "") +set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "") +set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "") +set(CMake_TEST_FindFreetype "ON" CACHE BOOL "") +set(CMake_TEST_FindGDAL "ON" CACHE BOOL "") +set(CMake_TEST_FindGIF "ON" CACHE BOOL "") +set(CMake_TEST_FindGit "ON" CACHE BOOL "") +set(CMake_TEST_FindGLEW "ON" CACHE BOOL "") +set(CMake_TEST_FindGLUT "ON" CACHE BOOL "") +set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "") +set(CMake_TEST_FindGSL "ON" CACHE BOOL "") +set(CMake_TEST_FindGTest "ON" CACHE BOOL "") +set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/bin/h5pcc.mpich" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_MPICH_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/bin/h5c++.mpich" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/bin/h5pfc.mpich" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/bin/h5pcc.openmpi" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/bin/h5c++.openmpi" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/bin/h5pfc.openmpi" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "") +set(CMake_TEST_FindIconv "ON" CACHE BOOL "") +set(CMake_TEST_FindICU "ON" CACHE BOOL "") +set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "") +set(CMake_TEST_FindIntl "ON" CACHE BOOL "") +set(CMake_TEST_FindJNI "ON" CACHE BOOL "") +set(CMake_TEST_FindJPEG "ON" CACHE BOOL "") +set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "") +set(CMake_TEST_FindLAPACK "${blas_lapack_cases}" CACHE STRING "") +set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "") +set(CMake_TEST_FindLibinput "ON" CACHE BOOL "") +set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "") +set(CMake_TEST_FindLibUV "ON" CACHE BOOL "") +set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "") +set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI "ON" CACHE BOOL "") +set(CMake_TEST_FindODBC "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "") +set(CMake_TEST_FindPatch "ON" CACHE BOOL "") +set(CMake_TEST_FindPNG "ON" CACHE BOOL "") +set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "") +set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "") +set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3 "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3_NumPy "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3_PyPy "ON" CACHE BOOL "") +set(CMake_TEST_FindRuby "ON" CACHE BOOL "") +set(CMake_TEST_FindSDL "ON" CACHE BOOL "") +set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "") +set(CMake_TEST_FindTIFF "ON" CACHE BOOL "") +set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "") +set(CMake_TEST_FindX11 "ON" CACHE BOOL "") +set(CMake_TEST_FindXalanC "ON" CACHE BOOL "") +set(CMake_TEST_FindXercesC "ON" CACHE BOOL "") +set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "") +set(CMake_TEST_Qt5 "ON" CACHE BOOL "") +set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.3" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") +set(CMake_TEST_UseSWIG "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_extdeps.cmake b/.gitlab/ci/configure_debian12_extdeps.cmake similarity index 100% rename from .gitlab/ci/configure_debian10_extdeps.cmake rename to .gitlab/ci/configure_debian12_extdeps.cmake diff --git a/.gitlab/ci/configure_debian12_hip_radeon.cmake b/.gitlab/ci/configure_debian12_hip_radeon.cmake new file mode 100644 index 00000000000..d12922aa6e4 --- /dev/null +++ b/.gitlab/ci/configure_debian12_hip_radeon.cmake @@ -0,0 +1,4 @@ +set(CMake_TEST_HIP "amd" CACHE BOOL "") +set(CMake_TEST_HIP_STANDARDS "98;11;14;17;20;23" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian12_iwyu.cmake b/.gitlab/ci/configure_debian12_iwyu.cmake new file mode 100644 index 00000000000..37ccbf455df --- /dev/null +++ b/.gitlab/ci/configure_debian12_iwyu.cmake @@ -0,0 +1,7 @@ +set(CMake_RUN_IWYU ON CACHE BOOL "") +set(CMake_IWYU_OPTIONS "-DCMAKE_IWYU_FORWARD_STD_HASH" CACHE STRING "") +# Uncomment to diagnose IWYU problems as needed. +#set(CMake_IWYU_VERBOSE ON CACHE BOOL "") +set(IWYU_COMMAND "/usr/bin/include-what-you-use-15" CACHE FILEPATH "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_debian12_makefiles_clang.cmake b/.gitlab/ci/configure_debian12_makefiles_clang.cmake new file mode 100644 index 00000000000..9bd62751455 --- /dev/null +++ b/.gitlab/ci/configure_debian12_makefiles_clang.cmake @@ -0,0 +1,9 @@ +set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") + +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_IAR_TOOLCHAINS "/opt/iarsystems" CACHE PATH "") + set(CMake_TEST_TICLANG_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/ticlang" CACHE PATH "") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian10_makefiles_inplace.cmake b/.gitlab/ci/configure_debian12_makefiles_inplace.cmake similarity index 100% rename from .gitlab/ci/configure_debian10_makefiles_inplace.cmake rename to .gitlab/ci/configure_debian12_makefiles_inplace.cmake diff --git a/.gitlab/ci/configure_debian12_ninja.cmake b/.gitlab/ci/configure_debian12_ninja.cmake new file mode 100644 index 00000000000..efca7a1d192 --- /dev/null +++ b/.gitlab/ci/configure_debian12_ninja.cmake @@ -0,0 +1,4 @@ +set(CMake_TEST_ASM_NASM "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_debian12_ninja_common.cmake") +set(CMake_TEST_UseSWIG "ON" CACHE BOOL "") diff --git a/.gitlab/ci/configure_debian12_ninja_clang.cmake b/.gitlab/ci/configure_debian12_ninja_clang.cmake new file mode 100644 index 00000000000..1a8e192da31 --- /dev/null +++ b/.gitlab/ci/configure_debian12_ninja_clang.cmake @@ -0,0 +1,6 @@ +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_IAR_TOOLCHAINS "/opt/iarsystems" CACHE PATH "") + set(CMake_TEST_TICLANG_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/ticlang" CACHE PATH "") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian12_ninja_common.cmake b/.gitlab/ci/configure_debian12_ninja_common.cmake new file mode 100644 index 00000000000..3022725255c --- /dev/null +++ b/.gitlab/ci/configure_debian12_ninja_common.cmake @@ -0,0 +1,123 @@ +set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") + +set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_CVS "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "") +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_CTestUpdate_P4 "ON" CACHE BOOL "") +endif() + +set(CMake_TEST_FindALSA "ON" CACHE BOOL "") +set(CMake_TEST_FindASPELL "ON" CACHE BOOL "") +set(CMake_TEST_FindBacktrace "ON" CACHE BOOL "") +set(CMake_TEST_FindBLAS "All;static=1;Generic" CACHE STRING "") +set(CMake_TEST_FindBoost "ON" CACHE BOOL "") +set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "") +set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "") +set(CMake_TEST_FindCups "ON" CACHE BOOL "") +set(CMake_TEST_FindCURL "ON" CACHE BOOL "") +set(CMake_TEST_FindDevIL "ON" CACHE BOOL "") +set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "") +set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "") +set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "") +set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "") +set(CMake_TEST_FindFreetype "ON" CACHE BOOL "") +set(CMake_TEST_FindGDAL "ON" CACHE BOOL "") +set(CMake_TEST_FindGIF "ON" CACHE BOOL "") +set(CMake_TEST_FindGit "ON" CACHE BOOL "") +set(CMake_TEST_FindGLEW "ON" CACHE BOOL "") +set(CMake_TEST_FindGLUT "ON" CACHE BOOL "") +set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "") +set(CMake_TEST_FindGSL "ON" CACHE BOOL "") +set(CMake_TEST_FindGTest "ON" CACHE BOOL "") +set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/bin/h5pcc.mpich" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_MPICH_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/bin/h5c++.mpich" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/bin/h5pfc.mpich" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/bin/h5pcc.openmpi" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/bin/h5c++.openmpi" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/bin/h5pfc.openmpi" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "") +set(CMake_TEST_FindIconv "ON" CACHE BOOL "") +set(CMake_TEST_FindICU "ON" CACHE BOOL "") +set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "") +set(CMake_TEST_FindIntl "ON" CACHE BOOL "") +set(CMake_TEST_FindJNI "ON" CACHE BOOL "") +set(CMake_TEST_FindJPEG "ON" CACHE BOOL "") +set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "") +set(CMake_TEST_FindLAPACK "All;static=1;Generic" CACHE STRING "") +set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "") +set(CMake_TEST_FindLibinput "ON" CACHE BOOL "") +set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "") +set(CMake_TEST_FindLibUV "ON" CACHE BOOL "") +set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "") +set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI "ON" CACHE BOOL "") +set(CMake_TEST_FindODBC "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "") +set(CMake_TEST_FindPatch "ON" CACHE BOOL "") +set(CMake_TEST_FindPNG "ON" CACHE BOOL "") +set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "") +set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "") +set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3 "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3_IronPython "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3_PyPy "ON" CACHE BOOL "") +set(CMake_TEST_FindRuby "ON" CACHE BOOL "") +#set(CMake_TEST_FindRuby_RBENV "ON" CACHE BOOL "") # fails because system and rbenv versions are same +set(CMake_TEST_FindRuby_RVM "ON" CACHE BOOL "") +set(CMake_TEST_FindSDL "ON" CACHE BOOL "") +set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "") +set(CMake_TEST_FindTIFF "ON" CACHE BOOL "") +set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "") +set(CMake_TEST_FindX11 "ON" CACHE BOOL "") +set(CMake_TEST_FindXalanC "ON" CACHE BOOL "") +set(CMake_TEST_FindXercesC "ON" CACHE BOOL "") + +set(CMake_TEST_ELF_LARGE "ON" CACHE BOOL "") +set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "") +set(CMake_TEST_Qt5 "ON" CACHE BOOL "") +set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.3" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") + +if (NOT "$ENV{SWIFTC}" STREQUAL "") + set(CMAKE_Swift_COMPILER "$ENV{SWIFTC}" CACHE FILEPATH "") +endif() + +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMAKE_TESTS_CDASH_SERVER "https://open.cdash.org" CACHE STRING "") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_debian12_ninja_multi_symlinked.cmake b/.gitlab/ci/configure_debian12_ninja_multi_symlinked.cmake new file mode 100644 index 00000000000..646dac8c5fa --- /dev/null +++ b/.gitlab/ci/configure_debian12_ninja_multi_symlinked.cmake @@ -0,0 +1,7 @@ +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_IAR_TOOLCHAINS "/opt/iarsystems" CACHE PATH "") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/configure_symlinked_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_debian12_ninja_common.cmake") +set(CMake_TEST_UseSWIG "OFF" CACHE BOOL "") diff --git a/.gitlab/ci/configure_fedora38_asan.cmake b/.gitlab/ci/configure_fedora38_asan.cmake deleted file mode 100644 index 8eae500363a..00000000000 --- a/.gitlab/ci/configure_fedora38_asan.cmake +++ /dev/null @@ -1,4 +0,0 @@ -set(CMAKE_C_FLAGS "-fsanitize=address" CACHE STRING "") -set(CMAKE_CXX_FLAGS "-fsanitize=address" CACHE STRING "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake") diff --git a/.gitlab/ci/configure_fedora38_clang_analyzer.cmake b/.gitlab/ci/configure_fedora38_clang_analyzer.cmake deleted file mode 100644 index c11eef14d48..00000000000 --- a/.gitlab/ci/configure_fedora38_clang_analyzer.cmake +++ /dev/null @@ -1,3 +0,0 @@ -set(configure_no_sccache 1) - -include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake") diff --git a/.gitlab/ci/configure_fedora38_common.cmake b/.gitlab/ci/configure_fedora38_common.cmake deleted file mode 100644 index 4484e264ff0..00000000000 --- a/.gitlab/ci/configure_fedora38_common.cmake +++ /dev/null @@ -1,7 +0,0 @@ -set(BUILD_CursesDialog ON CACHE BOOL "") -set(BUILD_QtDialog ON CACHE BOOL "") -set(CMake_QT_MAJOR_VERSION "5" CACHE STRING "") -set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "") -set(CMake_TEST_JSON_SCHEMA ON CACHE BOOL "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_fedora38_common_clang.cmake b/.gitlab/ci/configure_fedora38_common_clang.cmake deleted file mode 100644 index 70c9df9dcf4..00000000000 --- a/.gitlab/ci/configure_fedora38_common_clang.cmake +++ /dev/null @@ -1,6 +0,0 @@ -set(CMAKE_Fortran_COMPILER "/usr/bin/flang-new" CACHE FILEPATH "") -set(CMAKE_Fortran_COMPILER_ID "LLVMFlang" CACHE STRING "") -set(CMAKE_Fortran_COMPILER_SUPPORTS_F90 "1" CACHE BOOL "") -set(CMAKE_Fortran_FLAGS "-flang-experimental-exec" CACHE STRING "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora38_makefiles.cmake b/.gitlab/ci/configure_fedora38_makefiles.cmake deleted file mode 100644 index c2f99822f13..00000000000 --- a/.gitlab/ci/configure_fedora38_makefiles.cmake +++ /dev/null @@ -1,100 +0,0 @@ -set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "") -set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "") -if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") - set(CMake_TEST_CTestUpdate_P4 "ON" CACHE BOOL "") -endif() - -set(CMake_TEST_FindALSA "ON" CACHE BOOL "") -set(CMake_TEST_FindBLAS "All;static=1;Generic" CACHE STRING "") -set(CMake_TEST_FindBoost "ON" CACHE BOOL "") -set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "") -set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "") -set(CMake_TEST_FindCups "ON" CACHE BOOL "") -set(CMake_TEST_FindCURL "ON" CACHE BOOL "") -set(CMake_TEST_FindDevIL "ON" CACHE BOOL "") -set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "") -set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "") -set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "") -set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "") -set(CMake_TEST_FindFreetype "ON" CACHE BOOL "") -set(CMake_TEST_FindGDAL "ON" CACHE BOOL "") -set(CMake_TEST_FindGIF "ON" CACHE BOOL "") -set(CMake_TEST_FindGit "ON" CACHE BOOL "") -set(CMake_TEST_FindGLEW "ON" CACHE BOOL "") -set(CMake_TEST_FindGLUT "ON" CACHE BOOL "") -set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "") -set(CMake_TEST_FindGSL "ON" CACHE BOOL "") -set(CMake_TEST_FindGTest "ON" CACHE BOOL "") -set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "") -set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/lib64/mpich/bin/h5pcc" CACHE FILEPATH "") -# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/lib64/mpich/bin/h5pc++" CACHE FILEPATH "") # h5pc++ does not exist -set(CMake_TEST_FindHDF5_MPICH_ENVMOD "PATH=path_list_prepend:/usr/lib64/mpich/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/mpich/lib" CACHE STRING "") -set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/lib64/mpich/bin/h5pfc" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/lib64/openmpi/bin/h5pcc" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_OpenMPI_ENVMOD "PATH=path_list_prepend:/usr/lib64/openmpi/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/openmpi/lib" CACHE STRING "") -# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/lib64/openmpi/bin/h5pc++" CACHE FILEPATH "") # h5pc++ does not exist -set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/lib64/openmpi/bin/h5pfc" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "") -set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "") -set(CMake_TEST_FindIconv "ON" CACHE BOOL "") -set(CMake_TEST_FindICU "ON" CACHE BOOL "") -set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "") -set(CMake_TEST_FindIntl "ON" CACHE BOOL "") -set(CMake_TEST_FindJNI "ON" CACHE BOOL "") -set(CMake_TEST_FindJPEG "ON" CACHE BOOL "") -set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "") -set(CMake_TEST_FindLAPACK "All;static=1;Generic" CACHE STRING "") -set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "") -set(CMake_TEST_FindLibinput "ON" CACHE BOOL "") -set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "") -set(CMake_TEST_FindLibUV "ON" CACHE BOOL "") -set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "") -set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_FindMPI_ENVMOD "PATH=path_list_prepend:/usr/lib64/mpich/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/mpich/lib" CACHE STRING "") -set(CMake_TEST_FindMPI "ON" CACHE BOOL "") -set(CMake_TEST_FindODBC "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "") -set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "") -set(CMake_TEST_FindPatch "ON" CACHE BOOL "") -set(CMake_TEST_FindPNG "ON" CACHE BOOL "") -set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "") -set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "") -set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "") -set(CMake_TEST_FindPython "ON" CACHE BOOL "") -set(CMake_TEST_FindPython_NumPy "ON" CACHE BOOL "") -set(CMake_TEST_FindPython_PyPy "ON" CACHE BOOL "") -set(CMake_TEST_FindRuby "ON" CACHE BOOL "") -set(CMake_TEST_FindRuby_RVM "ON" CACHE BOOL "") -set(CMake_TEST_FindSDL "ON" CACHE BOOL "") -set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "") -set(CMake_TEST_FindTIFF "ON" CACHE BOOL "") -set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "") -set(CMake_TEST_FindX11 "ON" CACHE BOOL "") -set(CMake_TEST_FindXalanC "ON" CACHE BOOL "") -set(CMake_TEST_FindXercesC "ON" CACHE BOOL "") -set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "") -set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "") -if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") - set(CMake_TEST_ISPC "ON" CACHE STRING "") -endif() -set(CMake_TEST_Qt5 "ON" CACHE BOOL "") -set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") -set(CMake_TEST_UseSWIG "ON" CACHE BOOL "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora38_makefiles_clang.cmake b/.gitlab/ci/configure_fedora38_makefiles_clang.cmake deleted file mode 100644 index ff30ad95a46..00000000000 --- a/.gitlab/ci/configure_fedora38_makefiles_clang.cmake +++ /dev/null @@ -1 +0,0 @@ -include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake") diff --git a/.gitlab/ci/configure_fedora38_ninja.cmake b/.gitlab/ci/configure_fedora38_ninja.cmake deleted file mode 100644 index ac6b9f692b2..00000000000 --- a/.gitlab/ci/configure_fedora38_ninja.cmake +++ /dev/null @@ -1,14 +0,0 @@ -set(CMake_TEST_GUI "ON" CACHE BOOL "") -if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") - set(CMake_TEST_ISPC "ON" CACHE STRING "") -endif() -set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") - -# "Release" flags without "-DNDEBUG" so we get assertions. -set(CMAKE_C_FLAGS_RELEASE "-O3" CACHE STRING "") -set(CMAKE_CXX_FLAGS_RELEASE "-O3" CACHE STRING "") - -# Cover compilation with C++11 only and not higher standards. -set(CMAKE_CXX_STANDARD "11" CACHE STRING "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake") diff --git a/.gitlab/ci/configure_fedora38_ninja_clang.cmake b/.gitlab/ci/configure_fedora38_ninja_clang.cmake deleted file mode 100644 index 44546475d73..00000000000 --- a/.gitlab/ci/configure_fedora38_ninja_clang.cmake +++ /dev/null @@ -1,4 +0,0 @@ -set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "") -set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake") diff --git a/.gitlab/ci/configure_fedora38_ninja_multi.cmake b/.gitlab/ci/configure_fedora38_ninja_multi.cmake deleted file mode 100644 index 94af721f239..00000000000 --- a/.gitlab/ci/configure_fedora38_ninja_multi.cmake +++ /dev/null @@ -1,5 +0,0 @@ -if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") - set(CMake_TEST_ISPC "ON" CACHE STRING "") -endif() - -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake b/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake deleted file mode 100644 index 44546475d73..00000000000 --- a/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake +++ /dev/null @@ -1,4 +0,0 @@ -set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "") -set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake") diff --git a/.gitlab/ci/configure_fedora38_tidy.cmake b/.gitlab/ci/configure_fedora38_tidy.cmake deleted file mode 100644 index 5b062da3732..00000000000 --- a/.gitlab/ci/configure_fedora38_tidy.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(CMake_RUN_CLANG_TIDY ON CACHE BOOL "") -set(CMake_USE_CLANG_TIDY_MODULE ON CACHE BOOL "") -set(CMake_CLANG_TIDY_MODULE "$ENV{CI_PROJECT_DIR}/Utilities/ClangTidyModule/build/libcmake-clang-tidy-module.so" CACHE FILEPATH "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake") diff --git a/.gitlab/ci/configure_fedora42_asan.cmake b/.gitlab/ci/configure_fedora42_asan.cmake new file mode 100644 index 00000000000..60a3cb1c2d6 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_asan.cmake @@ -0,0 +1,5 @@ +set(CMAKE_C_FLAGS "-fsanitize=address" CACHE STRING "") +set(CMAKE_CXX_FLAGS "-fsanitize=address" CACHE STRING "") +set(CMake_QT_MAJOR_VERSION "5" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common.cmake") diff --git a/.gitlab/ci/configure_fedora42_clang_analyzer.cmake b/.gitlab/ci/configure_fedora42_clang_analyzer.cmake new file mode 100644 index 00000000000..c484570b4c2 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_clang_analyzer.cmake @@ -0,0 +1,4 @@ +set(configure_no_sccache 1) +set(CMake_QT_MAJOR_VERSION "5" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common.cmake") diff --git a/.gitlab/ci/configure_fedora42_common.cmake b/.gitlab/ci/configure_fedora42_common.cmake new file mode 100644 index 00000000000..dee78abfe73 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_common.cmake @@ -0,0 +1,6 @@ +set(BUILD_CursesDialog ON CACHE BOOL "") +set(BUILD_QtDialog ON CACHE BOOL "") +set(CMake_TEST_JQ "/usr/bin/jq" CACHE PATH "") +set(CMake_TEST_JSON_SCHEMA ON CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_fedora42_common_clang.cmake b/.gitlab/ci/configure_fedora42_common_clang.cmake new file mode 100644 index 00000000000..a4ae3f3f308 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_common_clang.cmake @@ -0,0 +1,17 @@ +set(CMAKE_Fortran_COMPILER "/usr/bin/flang-new" CACHE FILEPATH "") +set(CMAKE_Fortran_COMPILER_ID "LLVMFlang" CACHE STRING "") +set(CMAKE_Fortran_COMPILER_SUPPORTS_F90 "1" CACHE BOOL "") + +set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23;26" CACHE STRING "") + +set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_Fortran "OFF" CACHE BOOL "") # flang-new fails producing LLVM IR +set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora42_common_lfortran.cmake b/.gitlab/ci/configure_fedora42_common_lfortran.cmake new file mode 100644 index 00000000000..48469e23785 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_common_lfortran.cmake @@ -0,0 +1,5 @@ +set(CMAKE_Fortran_COMPILER "/usr/bin/lfortran" CACHE FILEPATH "") +set(CMAKE_Fortran_COMPILER_ID "LFortran" CACHE STRING "") +set(CMAKE_Fortran_COMPILER_SUPPORTS_F90 "1" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora38_extdeps.cmake b/.gitlab/ci/configure_fedora42_extdeps.cmake similarity index 100% rename from .gitlab/ci/configure_fedora38_extdeps.cmake rename to .gitlab/ci/configure_fedora42_extdeps.cmake diff --git a/.gitlab/ci/configure_fedora42_hip_radeon.cmake b/.gitlab/ci/configure_fedora42_hip_radeon.cmake new file mode 100644 index 00000000000..8271af3e149 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_hip_radeon.cmake @@ -0,0 +1,4 @@ +set(CMake_TEST_HIP "amd" CACHE BOOL "") +set(CMake_TEST_HIP_STANDARDS "98;11;14;17;20;23;26" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora42_makefiles.cmake b/.gitlab/ci/configure_fedora42_makefiles.cmake new file mode 100644 index 00000000000..e84dcbd6468 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_makefiles.cmake @@ -0,0 +1,117 @@ +set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23;26" CACHE STRING "") + +set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "") +set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "") +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_CTestUpdate_P4 "ON" CACHE BOOL "") +endif() + +set(CMake_TEST_ASM_NASM "ON" CACHE BOOL "") +set(CMake_TEST_FindALSA "ON" CACHE BOOL "") +set(CMake_TEST_FindASPELL "ON" CACHE BOOL "") +set(CMake_TEST_FindBacktrace "ON" CACHE BOOL "") +set(CMake_TEST_FindBLAS "All;static=1;Generic" CACHE STRING "") +set(CMake_TEST_FindBoost "ON" CACHE BOOL "") +set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "") +set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "") +set(CMake_TEST_FindCups "ON" CACHE BOOL "") +set(CMake_TEST_FindCURL "ON" CACHE BOOL "") +set(CMake_TEST_FindDevIL "ON" CACHE BOOL "") +set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "") +set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "") +set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "") +set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "") +set(CMake_TEST_FindFreetype "ON" CACHE BOOL "") +set(CMake_TEST_FindGDAL "ON" CACHE BOOL "") +set(CMake_TEST_FindGIF "ON" CACHE BOOL "") +set(CMake_TEST_FindGit "ON" CACHE BOOL "") +set(CMake_TEST_FindGLEW "ON" CACHE BOOL "") +set(CMake_TEST_FindGLUT "ON" CACHE BOOL "") +set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "") +set(CMake_TEST_FindGSL "ON" CACHE BOOL "") +set(CMake_TEST_FindGTest "ON" CACHE BOOL "") +set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "") +set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/lib64/mpich/bin/h5pcc" CACHE FILEPATH "") +# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/lib64/mpich/bin/h5pc++" CACHE FILEPATH "") # h5pc++ does not exist +set(CMake_TEST_FindHDF5_MPICH_ENVMOD "PATH=path_list_prepend:/usr/lib64/mpich/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/mpich/lib" CACHE STRING "") +set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/lib64/mpich/bin/h5pfc" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/lib64/openmpi/bin/h5pcc" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_OpenMPI_ENVMOD "PATH=path_list_prepend:/usr/lib64/openmpi/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/openmpi/lib" CACHE STRING "") +# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/lib64/openmpi/bin/h5pc++" CACHE FILEPATH "") # h5pc++ does not exist +set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/lib64/openmpi/bin/h5pfc" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "") +set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "") +set(CMake_TEST_FindIconv "ON" CACHE BOOL "") +set(CMake_TEST_FindICU "ON" CACHE BOOL "") +set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "") +set(CMake_TEST_FindIntl "ON" CACHE BOOL "") +set(CMake_TEST_FindJasper "ON" CACHE BOOL "") +set(CMake_TEST_FindJNI "ON" CACHE BOOL "") +set(CMake_TEST_FindJPEG "ON" CACHE BOOL "") +set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "") +set(CMake_TEST_FindLAPACK "All;static=1;Generic" CACHE STRING "") +set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "") +set(CMake_TEST_FindLibinput "ON" CACHE BOOL "") +set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "") +set(CMake_TEST_FindLibUV "ON" CACHE BOOL "") +set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "") +set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindMPI_ENVMOD "PATH=path_list_prepend:/usr/lib64/mpich/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/mpich/lib;FI_PROVIDER=set:tcp" CACHE STRING "") +set(CMake_TEST_FindMPI "ON" CACHE BOOL "") +set(CMake_TEST_FindODBC "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenACC_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "") +set(CMake_TEST_FindPatch "ON" CACHE BOOL "") +set(CMake_TEST_FindPNG "ON" CACHE BOOL "") +set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "") +set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "") +set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3 "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3_NumPy "ON" CACHE BOOL "") +set(CMake_TEST_FindPython2_PyPy "ON" CACHE BOOL "") +set(CMake_TEST_FindPython3_PyPy "ON" CACHE BOOL "") +set(CMake_TEST_FindRuby "ON" CACHE BOOL "") +set(CMake_TEST_FindRuby_RBENV "ON" CACHE BOOL "") +set(CMake_TEST_FindRuby_RVM "ON" CACHE BOOL "") +set(CMake_TEST_FindSDL "ON" CACHE BOOL "") +set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "") +set(CMake_TEST_FindTIFF "ON" CACHE BOOL "") +set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "") +set(CMake_TEST_FindX11 "ON" CACHE BOOL "") +set(CMake_TEST_FindXalanC "ON" CACHE BOOL "") +set(CMake_TEST_FindXercesC "ON" CACHE BOOL "") + +set(CMake_TEST_ELF_LARGE "ON" CACHE BOOL "") +set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "") +set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "") +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_ISPC "ON" CACHE STRING "") +endif() +set(CMake_TEST_Qt5 "ON" CACHE BOOL "") +set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.3" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") +set(CMake_TEST_UseSWIG "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora42_makefiles_clang.cmake b/.gitlab/ci/configure_fedora42_makefiles_clang.cmake new file mode 100644 index 00000000000..36588a52ddc --- /dev/null +++ b/.gitlab/ci/configure_fedora42_makefiles_clang.cmake @@ -0,0 +1,5 @@ +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMAKE_TESTS_CDASH_SERVER "https://open.cdash.org" CACHE STRING "") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common_clang.cmake") diff --git a/.gitlab/ci/configure_fedora42_makefiles_lfortran.cmake b/.gitlab/ci/configure_fedora42_makefiles_lfortran.cmake new file mode 100644 index 00000000000..ae32c04ed3c --- /dev/null +++ b/.gitlab/ci/configure_fedora42_makefiles_lfortran.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common_lfortran.cmake") diff --git a/.gitlab/ci/configure_fedora42_makefiles_symlinked.cmake b/.gitlab/ci/configure_fedora42_makefiles_symlinked.cmake new file mode 100644 index 00000000000..e498a4819c4 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_makefiles_symlinked.cmake @@ -0,0 +1,5 @@ +set(CMake_QT_MAJOR_VERSION "6" CACHE STRING "") +set(CMake_TEST_GUI "ON" CACHE BOOL "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_symlinked_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common.cmake") diff --git a/.gitlab/ci/configure_fedora42_ninja.cmake b/.gitlab/ci/configure_fedora42_ninja.cmake new file mode 100644 index 00000000000..07ce93b339a --- /dev/null +++ b/.gitlab/ci/configure_fedora42_ninja.cmake @@ -0,0 +1,20 @@ +set(CMake_TEST_GUI "ON" CACHE BOOL "") +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_ISPC "ON" CACHE STRING "") +endif() +set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.3" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") + +# "Release" flags without "-DNDEBUG" so we get assertions. +set(CMAKE_C_FLAGS_RELEASE "-O3" CACHE STRING "") +set(CMAKE_CXX_FLAGS_RELEASE "-O3" CACHE STRING "") + +# Cover compilation with C++11 only and not higher standards. +set(CMAKE_CXX_STANDARD "11" CACHE STRING "") +# Qt 6 requires C++17, so use Qt 5. +set(CMake_QT_MAJOR_VERSION "5" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common.cmake") diff --git a/.gitlab/ci/configure_fedora42_ninja_clang.cmake b/.gitlab/ci/configure_fedora42_ninja_clang.cmake new file mode 100644 index 00000000000..c95388fe1d8 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_ninja_clang.cmake @@ -0,0 +1,3 @@ +set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database,import_std23" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common_clang.cmake") diff --git a/.gitlab/ci/configure_fedora42_ninja_lfortran.cmake b/.gitlab/ci/configure_fedora42_ninja_lfortran.cmake new file mode 100644 index 00000000000..ae32c04ed3c --- /dev/null +++ b/.gitlab/ci/configure_fedora42_ninja_lfortran.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common_lfortran.cmake") diff --git a/.gitlab/ci/configure_fedora42_ninja_multi.cmake b/.gitlab/ci/configure_fedora42_ninja_multi.cmake new file mode 100644 index 00000000000..b4d9a70ddfb --- /dev/null +++ b/.gitlab/ci/configure_fedora42_ninja_multi.cmake @@ -0,0 +1,6 @@ +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_ISPC "ON" CACHE STRING "") +endif() +set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_fedora42_ninja_multi_clang.cmake b/.gitlab/ci/configure_fedora42_ninja_multi_clang.cmake new file mode 100644 index 00000000000..a1c7fc0d562 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_ninja_multi_clang.cmake @@ -0,0 +1,3 @@ +set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common_clang.cmake") diff --git a/.gitlab/ci/configure_fedora38_sphinx.cmake b/.gitlab/ci/configure_fedora42_sphinx.cmake similarity index 100% rename from .gitlab/ci/configure_fedora38_sphinx.cmake rename to .gitlab/ci/configure_fedora42_sphinx.cmake diff --git a/.gitlab/ci/configure_fedora38_sphinx_package.cmake b/.gitlab/ci/configure_fedora42_sphinx_package.cmake similarity index 100% rename from .gitlab/ci/configure_fedora38_sphinx_package.cmake rename to .gitlab/ci/configure_fedora42_sphinx_package.cmake diff --git a/.gitlab/ci/configure_fedora42_tidy.cmake b/.gitlab/ci/configure_fedora42_tidy.cmake new file mode 100644 index 00000000000..f5dbe06d740 --- /dev/null +++ b/.gitlab/ci/configure_fedora42_tidy.cmake @@ -0,0 +1,7 @@ +set(CMake_RUN_CLANG_TIDY ON CACHE BOOL "") +set(CMake_USE_CLANG_TIDY_MODULE ON CACHE BOOL "") +set(CMake_CLANG_TIDY_MODULE "$ENV{CI_PROJECT_DIR}/Utilities/ClangTidyModule/build/libcmake-clang-tidy-module.so" CACHE FILEPATH "") +set(CMake_CLANG_TIDY_EXPORT_FIXES_DIR "$ENV{CI_PROJECT_DIR}/.gitlab/clang-tidy-fixes" CACHE PATH "") +set(CMake_QT_MAJOR_VERSION "5" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora42_common.cmake") diff --git a/.gitlab/ci/configure_hip5.5_radeon.cmake b/.gitlab/ci/configure_hip5.5_radeon.cmake deleted file mode 100644 index 58036b05a02..00000000000 --- a/.gitlab/ci/configure_hip5.5_radeon.cmake +++ /dev/null @@ -1,3 +0,0 @@ -set(CMake_TEST_HIP "ON" CACHE BOOL "") - -include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_hip6.3_nvidia.cmake b/.gitlab/ci/configure_hip6.3_nvidia.cmake new file mode 100644 index 00000000000..036a2275b4c --- /dev/null +++ b/.gitlab/ci/configure_hip6.3_nvidia.cmake @@ -0,0 +1,4 @@ +set(CMake_TEST_HIP "nvidia" CACHE BOOL "") +set(CMake_TEST_HIP_STANDARDS "98;11;14;17" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_hip6.3_radeon.cmake b/.gitlab/ci/configure_hip6.3_radeon.cmake new file mode 100644 index 00000000000..06e47c9fd88 --- /dev/null +++ b/.gitlab/ci/configure_hip6.3_radeon.cmake @@ -0,0 +1,4 @@ +set(CMake_TEST_HIP "amd" CACHE BOOL "") +set(CMake_TEST_CUDA_STANDARDS "98;11;14;17;20;23;26" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake index e9121aec0fe..b7faa3699d0 100644 --- a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake +++ b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake @@ -1,4 +1,3 @@ -set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi" CACHE STRING "") -set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_gcc.cmake" CACHE STRING "") +set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,bmionly,build_database,import_std23" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake index e9121aec0fe..b7faa3699d0 100644 --- a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake +++ b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake @@ -1,4 +1,3 @@ -set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi" CACHE STRING "") -set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_gcc.cmake" CACHE STRING "") +set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,bmionly,build_database,import_std23" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_macos_arm64_curl.cmake b/.gitlab/ci/configure_macos_arm64_curl.cmake new file mode 100644 index 00000000000..73263cb1941 --- /dev/null +++ b/.gitlab/ci/configure_macos_arm64_curl.cmake @@ -0,0 +1,10 @@ +# Build with our vendored curl instead of the default system version. +set(CMAKE_USE_SYSTEM_CURL "OFF" CACHE BOOL "") + +set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") + +include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_macos_arm64_ninja.cmake b/.gitlab/ci/configure_macos_arm64_ninja.cmake index f2068a12b8c..eb319c9694b 100644 --- a/.gitlab/ci/configure_macos_arm64_ninja.cmake +++ b/.gitlab/ci/configure_macos_arm64_ninja.cmake @@ -1,8 +1,22 @@ +set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") + set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") set(CMake_TEST_GUI "ON" CACHE BOOL "") set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") + +# "Release" flags without "-DNDEBUG" so we get assertions. +set(CMAKE_C_FLAGS_RELEASE "-O3" CACHE STRING "") +set(CMAKE_CXX_FLAGS_RELEASE "-O3" CACHE STRING "") + +# https://libcxx.llvm.org/Hardening.html +set(CMAKE_CXX_FLAGS "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" CACHE STRING "") + include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake") include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_macos_arm64_ninja_multi.cmake b/.gitlab/ci/configure_macos_arm64_ninja_multi.cmake index b22285c65b7..5b19c60c1bc 100644 --- a/.gitlab/ci/configure_macos_arm64_ninja_multi.cmake +++ b/.gitlab/ci/configure_macos_arm64_ninja_multi.cmake @@ -1,6 +1,11 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") set(CMake_TEST_ISPC "ON" CACHE STRING "") + set(CMAKE_TESTS_CDASH_SERVER "https://open.cdash.org" CACHE STRING "") endif() +# FIXME: sccache sometimes fails with "Compiler killed by signal 9". +# This job does not compile much anyway, so suppress it for now. +set(configure_no_sccache 1) + include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_macos_arm64_ninja_symlinked.cmake b/.gitlab/ci/configure_macos_arm64_ninja_symlinked.cmake new file mode 100644 index 00000000000..63d0dc73e73 --- /dev/null +++ b/.gitlab/ci/configure_macos_arm64_ninja_symlinked.cmake @@ -0,0 +1,2 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_symlinked_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_arm64_ninja.cmake") diff --git a/.gitlab/ci/configure_macos_arm64_pch.cmake b/.gitlab/ci/configure_macos_arm64_pch.cmake new file mode 100644 index 00000000000..e2676ba8b06 --- /dev/null +++ b/.gitlab/ci/configure_macos_arm64_pch.cmake @@ -0,0 +1,7 @@ +set(CMake_BUILD_PCH "ON" CACHE BOOL "") + +# sccache does not forward the PCH '-Xarch_arm64 "-include/..."' flag correctly. +set(configure_no_sccache 1) + +include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_macos_arm64_xcode_symlinked.cmake b/.gitlab/ci/configure_macos_arm64_xcode_symlinked.cmake new file mode 100644 index 00000000000..66b18e16b26 --- /dev/null +++ b/.gitlab/ci/configure_macos_arm64_xcode_symlinked.cmake @@ -0,0 +1,2 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_symlinked_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_arm64_xcode.cmake") diff --git a/.gitlab/ci/configure_macos_common.cmake b/.gitlab/ci/configure_macos_common.cmake index e78c9cec334..614f2111490 100644 --- a/.gitlab/ci/configure_macos_common.cmake +++ b/.gitlab/ci/configure_macos_common.cmake @@ -16,3 +16,9 @@ set(BUILD_QtDialog ON CACHE BOOL "") # when CMake itself is configured. Use a version that is not # newer than the macOS version running on any CI host. set(CMake_TEST_XCTest_DEPLOYMENT_TARGET "10.15" CACHE STRING "") + +if("$ENV{CMAKE_CONFIGURATION}" MATCHES "macos_arm64") + set(CMake_TEST_APPLE_SILICON ON CACHE BOOL "") +else() + set(CMake_TEST_APPLE_SILICON OFF CACHE BOOL "") +endif() diff --git a/.gitlab/ci/configure_macos_x86_64_makefiles.cmake b/.gitlab/ci/configure_macos_x86_64_makefiles.cmake index 5d1620d85bf..99beb68fbeb 100644 --- a/.gitlab/ci/configure_macos_x86_64_makefiles.cmake +++ b/.gitlab/ci/configure_macos_x86_64_makefiles.cmake @@ -7,6 +7,9 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") set(CMake_TEST_ISPC "ON" CACHE STRING "") endif() set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake") include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_macos_x86_64_ninja.cmake b/.gitlab/ci/configure_macos_x86_64_ninja.cmake index 5d1620d85bf..9de043e6e5c 100644 --- a/.gitlab/ci/configure_macos_x86_64_ninja.cmake +++ b/.gitlab/ci/configure_macos_x86_64_ninja.cmake @@ -1,3 +1,6 @@ +set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") + set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "") set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") @@ -7,6 +10,16 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") set(CMake_TEST_ISPC "ON" CACHE STRING "") endif() set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") + +# "Release" flags without "-DNDEBUG" so we get assertions. +set(CMAKE_C_FLAGS_RELEASE "-O3" CACHE STRING "") +set(CMAKE_CXX_FLAGS_RELEASE "-O3" CACHE STRING "") + +# https://libcxx.llvm.org/Hardening.html +set(CMAKE_CXX_FLAGS "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake") include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_macos_x86_64_xcode.cmake b/.gitlab/ci/configure_macos_x86_64_xcode.cmake index 1b976d261ae..f1b91ec9a39 100644 --- a/.gitlab/ci/configure_macos_x86_64_xcode.cmake +++ b/.gitlab/ci/configure_macos_x86_64_xcode.cmake @@ -1,2 +1,3 @@ +set(CMake_TEST_RunCMake_ExternalProject_RUN_SERIAL ON CACHE BOOL "") include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake") include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_mingw_osdn_io_common.cmake b/.gitlab/ci/configure_mingw_osdn_io_common.cmake index 55dce1d3ed6..d3162330282 100644 --- a/.gitlab/ci/configure_mingw_osdn_io_common.cmake +++ b/.gitlab/ci/configure_mingw_osdn_io_common.cmake @@ -1,5 +1,8 @@ set(CMake_TEST_Java OFF CACHE BOOL "") +get_filename_component(mingw_dir "${CMAKE_CURRENT_LIST_DIR}/../mingw" ABSOLUTE) +set(CMake_TEST_MSYSTEM_PREFIX "${mingw_dir}" CACHE STRING "") + set(configure_no_sccache 1) include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_nvhpc_ninja.cmake b/.gitlab/ci/configure_nvhpc_ninja.cmake index ca8ba932f59..e25ff7280c5 100644 --- a/.gitlab/ci/configure_nvhpc_ninja.cmake +++ b/.gitlab/ci/configure_nvhpc_ninja.cmake @@ -1,4 +1,9 @@ set(CMake_TEST_CUDA "NVIDIA" CACHE STRING "") +set(CMake_TEST_CUDA_ARCH "52" CACHE STRING "") +set(CMake_TEST_CUDA_CUPTI "ON" CACHE STRING "") + +set(CMake_TEST_C_STANDARDS "90;99;11;17" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") set(configure_no_sccache 1) diff --git a/.gitlab/ci/configure_symlinked_common.cmake b/.gitlab/ci/configure_symlinked_common.cmake new file mode 100644 index 00000000000..a5e427e422f --- /dev/null +++ b/.gitlab/ci/configure_symlinked_common.cmake @@ -0,0 +1,7 @@ +# Ensure that the symlink tree is set up correctly. +if(NOT CMAKE_SOURCE_DIR STREQUAL "$ENV{CI_PROJECT_DIR}/work/cmake") + message(FATAL_ERROR "Expected value of CMAKE_SOURCE_DIR:\n $ENV{CI_PROJECT_DIR}/work/cmake\nActual value:\n ${CMAKE_SOURCE_DIR}") +endif() +if(NOT CMAKE_BINARY_DIR STREQUAL "$ENV{CI_PROJECT_DIR}/work/build") + message(FATAL_ERROR "Expected value of CMAKE_BINARY_DIR:\n $ENV{CI_PROJECT_DIR}/work/build\nActual value:\n ${CMAKE_BINARY_DIR}") +endif() diff --git a/.gitlab/ci/configure_windows_arm64_vs2022_ninja.cmake b/.gitlab/ci/configure_windows_arm64_vs2022_ninja.cmake index a12ee6cbb11..71dee5e8b63 100644 --- a/.gitlab/ci/configure_windows_arm64_vs2022_ninja.cmake +++ b/.gitlab/ci/configure_windows_arm64_vs2022_ninja.cmake @@ -1,8 +1,13 @@ # Qt host tools are not yet available natively on windows-arm64. set(CMake_TEST_GUI "OFF" CACHE BOOL "") -set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") set(BUILD_QtDialog "OFF" CACHE BOOL "") set(CMAKE_PREFIX_PATH "" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") + include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_msvc_cxx_modules_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_wix_common.cmake") include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_vs_common_ninja.cmake") diff --git a/.gitlab/ci/configure_windows_clang_common.cmake b/.gitlab/ci/configure_windows_clang_common.cmake index 55dce1d3ed6..0d002854dcb 100644 --- a/.gitlab/ci/configure_windows_clang_common.cmake +++ b/.gitlab/ci/configure_windows_clang_common.cmake @@ -1,3 +1,16 @@ +if("$ENV{CMAKE_CI_BUILD_NAME}" MATCHES "(^|_)gnu(_|$)") + set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") + set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23;26" CACHE STRING "") +else() + # Testing for clang-cl. + set(CMake_TEST_C_STANDARDS "90;99;11;17;23" CACHE STRING "") + set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") +endif() + +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_Fortran "OFF" CACHE BOOL "") set(CMake_TEST_Java OFF CACHE BOOL "") set(configure_no_sccache 1) diff --git a/.gitlab/ci/configure_windows_clang_ninja.cmake b/.gitlab/ci/configure_windows_clang_ninja.cmake index d1f0f524eb2..214c7548915 100644 --- a/.gitlab/ci/configure_windows_clang_ninja.cmake +++ b/.gitlab/ci/configure_windows_clang_ninja.cmake @@ -1,5 +1,4 @@ if("$ENV{CMAKE_CI_BUILD_NAME}" MATCHES "(^|_)gnu(_|$)") - set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "") - set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "") + set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "") endif() include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_clang_common.cmake") diff --git a/.gitlab/ci/configure_windows_i386_package.cmake b/.gitlab/ci/configure_windows_i386_package.cmake index 65e1dcb345d..c80ebcce2e9 100644 --- a/.gitlab/ci/configure_windows_i386_package.cmake +++ b/.gitlab/ci/configure_windows_i386_package.cmake @@ -1,6 +1,9 @@ # CPack package file name component for this platform. set(CPACK_SYSTEM_NAME "windows-i386" CACHE STRING "") +# Tell WiX to package for this architecture. +set(CPACK_WIX_ARCHITECTURE "x86" CACHE STRING "") + # Use APIs from at most Windows 7 set(CMAKE_C_FLAGS "-D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "") set(CMAKE_CXX_FLAGS "-GR -EHsc -D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "") diff --git a/.gitlab/ci/configure_windows_intelcompiler_common.cmake b/.gitlab/ci/configure_windows_intelcompiler_common.cmake index 55dce1d3ed6..0777eaf8d33 100644 --- a/.gitlab/ci/configure_windows_intelcompiler_common.cmake +++ b/.gitlab/ci/configure_windows_intelcompiler_common.cmake @@ -1,3 +1,7 @@ +set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "") +set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "") set(CMake_TEST_Java OFF CACHE BOOL "") set(configure_no_sccache 1) diff --git a/.gitlab/ci/configure_windows_msvc_common.cmake b/.gitlab/ci/configure_windows_msvc_common.cmake index 6d66a050245..946eca75de0 100644 --- a/.gitlab/ci/configure_windows_msvc_common.cmake +++ b/.gitlab/ci/configure_windows_msvc_common.cmake @@ -1,2 +1,4 @@ +set(CMake_TEST_Java OFF CACHE BOOL "") + set(configure_no_sccache 1) include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_windows_msvc_cxx_modules_common.cmake b/.gitlab/ci/configure_windows_msvc_cxx_modules_common.cmake index 5f2a215c982..89a58413f12 100644 --- a/.gitlab/ci/configure_windows_msvc_cxx_modules_common.cmake +++ b/.gitlab/ci/configure_windows_msvc_cxx_modules_common.cmake @@ -1,2 +1 @@ -set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,shared,export_bmi,install_bmi" CACHE STRING "") -set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_msvc.cmake" CACHE STRING "") +set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,shared,export_bmi,install_bmi,bmionly,import_std23,build_database" CACHE STRING "") diff --git a/.gitlab/ci/configure_windows_orangec6.73.1.cmake b/.gitlab/ci/configure_windows_orangec6.73.1.cmake new file mode 100644 index 00000000000..e667b942db5 --- /dev/null +++ b/.gitlab/ci/configure_windows_orangec6.73.1.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_orangec_common.cmake") diff --git a/.gitlab/ci/configure_windows_orangec_common.cmake b/.gitlab/ci/configure_windows_orangec_common.cmake new file mode 100644 index 00000000000..55dce1d3ed6 --- /dev/null +++ b/.gitlab/ci/configure_windows_orangec_common.cmake @@ -0,0 +1,5 @@ +set(CMake_TEST_Java OFF CACHE BOOL "") + +set(configure_no_sccache 1) + +include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake") diff --git a/.gitlab/ci/configure_windows_package_common.cmake b/.gitlab/ci/configure_windows_package_common.cmake index 541a5414f1d..70fca72ee62 100644 --- a/.gitlab/ci/configure_windows_package_common.cmake +++ b/.gitlab/ci/configure_windows_package_common.cmake @@ -19,6 +19,6 @@ set(CMake_TEST_Qt5 OFF CACHE BOOL "") set(CMake_TEST_Qt6 OFF CACHE BOOL "") set(Python_FIND_REGISTRY NEVER CACHE STRING "") -set(CMake_CPACK_CUSTOM_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/CMakeCPack.cmake" CACHE FILEPATH "") +set(CMake_BUILD_WIX_CUSTOM_ACTION ON CACHE BOOL "") include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake") diff --git a/.gitlab/ci/configure_windows_vs2019_x64.cmake b/.gitlab/ci/configure_windows_vs2019_x64.cmake index c7d41ea1d43..e4795b94cae 100644 --- a/.gitlab/ci/configure_windows_vs2019_x64.cmake +++ b/.gitlab/ci/configure_windows_vs2019_x64.cmake @@ -1 +1,6 @@ +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_ANDROID_VS16 ON CACHE BOOL "") + set(CMAKE_TESTS_CDASH_SERVER "https://open.cdash.org" CACHE STRING "") +endif() + include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_vs_common.cmake") diff --git a/.gitlab/ci/configure_windows_vs2022_x64.cmake b/.gitlab/ci/configure_windows_vs2022_x64.cmake index 51ee514eb25..c29a9ec03e3 100644 --- a/.gitlab/ci/configure_windows_vs2022_x64.cmake +++ b/.gitlab/ci/configure_windows_vs2022_x64.cmake @@ -1,3 +1,7 @@ +if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_ANDROID_VS17 ON CACHE BOOL "") +endif() + set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions,shared" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_msvc_cxx_modules_common.cmake") diff --git a/.gitlab/ci/configure_windows_vs2022_x64_i18n.cmake b/.gitlab/ci/configure_windows_vs2022_x64_i18n.cmake new file mode 100644 index 00000000000..3932f8a6a36 --- /dev/null +++ b/.gitlab/ci/configure_windows_vs2022_x64_i18n.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_vs2022_x64.cmake") diff --git a/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake b/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake index 54abf722a4b..3fb894cca71 100644 --- a/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake +++ b/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake @@ -1,8 +1,23 @@ +set(CMake_TEST_C_STANDARDS "90;99;11;17" CACHE STRING "") +set(CMake_TEST_CXX_STANDARDS "98;11;14;17;20;23" CACHE STRING "") + if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") set(CMake_TEST_CPACK_INNOSETUP "ON" CACHE STRING "") + set(CMake_TEST_CPACK_NUGET "ON" CACHE STRING "") + set(CMake_TEST_IAR_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/iar" CACHE PATH "") set(CMake_TEST_ISPC "ON" CACHE STRING "") + set(CMake_TEST_Swift "ON" CACHE STRING "") endif() + set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "") +set(CMake_TEST_TLS_VERSION "1.2" CACHE STRING "") +set(CMake_TEST_TLS_VERSION_URL_BAD "https://badtls-v1-1.kitware.com:8011" CACHE STRING "") + +# Release flags without -DNDEBUG so we get assertions. +set(CMAKE_C_FLAGS_RELEASE "-O2 -Ob2" CACHE STRING "") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -Ob2" CACHE STRING "") include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_msvc_cxx_modules_common.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_wix_common.cmake") include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_vs_common_ninja.cmake") diff --git a/.gitlab/ci/configure_windows_vs2022_x64_ninja_multi.cmake b/.gitlab/ci/configure_windows_vs2022_x64_ninja_multi.cmake index 2b0c76d8512..3a0edddb0ce 100644 --- a/.gitlab/ci/configure_windows_vs2022_x64_ninja_multi.cmake +++ b/.gitlab/ci/configure_windows_vs2022_x64_ninja_multi.cmake @@ -1,5 +1,7 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "") + set(CMake_TEST_IAR_TOOLCHAINS "$ENV{CI_PROJECT_DIR}/.gitlab/iar" CACHE PATH "") set(CMake_TEST_ISPC "ON" CACHE STRING "") + set(CMake_TEST_Swift "ON" CACHE STRING "") endif() include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_msvc_cxx_modules_common.cmake") diff --git a/.gitlab/ci/configure_windows_vs2022_x64_pch.cmake b/.gitlab/ci/configure_windows_vs2022_x64_pch.cmake new file mode 100644 index 00000000000..2a2eed7a0ec --- /dev/null +++ b/.gitlab/ci/configure_windows_vs2022_x64_pch.cmake @@ -0,0 +1,2 @@ +set(CMake_BUILD_PCH "ON" CACHE BOOL "") +include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_common.cmake") diff --git a/.gitlab/ci/configure_windows_wix_common.cmake b/.gitlab/ci/configure_windows_wix_common.cmake new file mode 100644 index 00000000000..4219c41a48a --- /dev/null +++ b/.gitlab/ci/configure_windows_wix_common.cmake @@ -0,0 +1,5 @@ +get_filename_component(wix3_dir "${CMAKE_CURRENT_LIST_DIR}/../wix3" ABSOLUTE) +set(CMake_TEST_CPACK_WIX3 "${wix3_dir}" CACHE PATH "") + +get_filename_component(wix4_dir "${CMAKE_CURRENT_LIST_DIR}/../wix4" ABSOLUTE) +set(CMake_TEST_CPACK_WIX4 "${wix4_dir}" CACHE PATH "") diff --git a/.gitlab/ci/configure_windows_x86_64_package.cmake b/.gitlab/ci/configure_windows_x86_64_package.cmake index 3a141a7a7ff..051e3fc6290 100644 --- a/.gitlab/ci/configure_windows_x86_64_package.cmake +++ b/.gitlab/ci/configure_windows_x86_64_package.cmake @@ -1,6 +1,9 @@ # CPack package file name component for this platform. set(CPACK_SYSTEM_NAME "windows-x86_64" CACHE STRING "") +# Tell WiX to package for this architecture. +set(CPACK_WIX_ARCHITECTURE "x64" CACHE STRING "") + # Use APIs from at most Windows 7 set(CMAKE_C_FLAGS "-D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "") set(CMAKE_CXX_FLAGS "-GR -EHsc -D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "") diff --git a/.gitlab/ci/ctest_annotation.cmake b/.gitlab/ci/ctest_annotation.cmake new file mode 100644 index 00000000000..a2197532305 --- /dev/null +++ b/.gitlab/ci/ctest_annotation.cmake @@ -0,0 +1,32 @@ +function (ctest_annotation_report file) + set(label "") + + if (EXISTS "${file}") + file(READ "${file}" json) + else () + set(json "{\"CDash\": []}") + endif () + + foreach (arg IN LISTS ARGN) + if (NOT label) + set(label "${arg}") + continue () + endif () + + set(item "{\"external_link\":{\"label\":\"${label}\",\"url\":\"${arg}\"}}") + set(label "") + + string(JSON length LENGTH "${json}" "CDash") + string(JSON json SET "${json}" "CDash" "${length}" "${item}") + endforeach () + + file(WRITE "${file}" "${json}") +endfunction () + +if (NOT DEFINED build_id) + include("${CTEST_BINARY_DIRECTORY}/cdash-build-id" OPTIONAL) +endif () +function (store_build_id build_id) + file(WRITE "${CTEST_BINARY_DIRECTORY}/cdash-build-id" + "set(build_id \"${build_id}\")\n") +endfunction () diff --git a/.gitlab/ci/ctest_build.cmake b/.gitlab/ci/ctest_build.cmake index e874a62ca23..15b4dab6cc3 100644 --- a/.gitlab/ci/ctest_build.cmake +++ b/.gitlab/ci/ctest_build.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.29) include("${CMAKE_CURRENT_LIST_DIR}/gitlab_ci.cmake") @@ -45,11 +45,17 @@ if (iwyu_source_name AND "$ENV{CMAKE_CONFIGURATION}" MATCHES "iwyu") endif () ctest_build( + NUMBER_ERRORS num_errors NUMBER_WARNINGS num_warnings RETURN_VALUE build_result ${ctest_build_args}) ctest_submit(PARTS Build) +include("${CMAKE_CURRENT_LIST_DIR}/ctest_annotation.cmake") +ctest_annotation_report("${CTEST_BINARY_DIRECTORY}/annotations.json" + "Build Errors (${num_errors})" "https://open.cdash.org/viewBuildError.php?buildid=${build_id}" + "Build Warnings (${num_warnings})" "https://open.cdash.org/viewBuildError.php?type=1&buildid=${build_id}") + if (build_result) message(FATAL_ERROR "Failed to build") @@ -59,6 +65,7 @@ if ("$ENV{CTEST_NO_WARNINGS_ALLOWED}" AND num_warnings GREATER 0) message(FATAL_ERROR "Found ${num_warnings} warnings (treating as fatal).") endif () +file(WRITE "$ENV{CI_PROJECT_DIR}/.gitlab/num_warnings.txt" "${num_warnings}\n") if (ctest_build_args) message(FATAL_ERROR diff --git a/.gitlab/ci/ctest_configure.cmake b/.gitlab/ci/ctest_configure.cmake index 2682055d177..13ed4c6505c 100644 --- a/.gitlab/ci/ctest_configure.cmake +++ b/.gitlab/ci/ctest_configure.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.29) include("${CMAKE_CURRENT_LIST_DIR}/gitlab_ci.cmake") @@ -22,10 +22,18 @@ ctest_configure( # Read the files from the build directory. ctest_read_custom_files("${CTEST_BINARY_DIRECTORY}") -# We can now submit because we've configured. This is a cmb-superbuild-ism. -ctest_submit(PARTS Update) +# We can now submit because we've configured. +ctest_submit(PARTS Update + BUILD_ID build_id) ctest_submit(PARTS Configure) +include("${CMAKE_CURRENT_LIST_DIR}/ctest_annotation.cmake") +ctest_annotation_report("${CTEST_BINARY_DIRECTORY}/annotations.json" + "Build Summary" "https://open.cdash.org/build/${build_id}" + "Update" "https://open.cdash.org/build/${build_id}/update" + "Configure" "https://open.cdash.org/build/${build_id}/configure") +store_build_id("${build_id}") + if (configure_result) message(FATAL_ERROR "Failed to configure") diff --git a/.gitlab/ci/ctest_exclusions.cmake b/.gitlab/ci/ctest_exclusions.cmake index a2789c3af8a..89a5ace9563 100644 --- a/.gitlab/ci/ctest_exclusions.cmake +++ b/.gitlab/ci/ctest_exclusions.cmake @@ -27,13 +27,6 @@ if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "_jom") ) endif() -if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "nvhpc_") - list(APPEND test_exclusions - # FIXME(#24187): This test fails with NVHPC as the CUDA host compiler. - "^CudaOnly.SeparateCompilationPTX$" - ) -endif() - string(REPLACE ";" "|" test_exclusions "${test_exclusions}") if (test_exclusions) set(test_exclusions "(${test_exclusions})") diff --git a/.gitlab/ci/ctest_memcheck.cmake b/.gitlab/ci/ctest_memcheck.cmake index dac907c1577..36c7777e17e 100644 --- a/.gitlab/ci/ctest_memcheck.cmake +++ b/.gitlab/ci/ctest_memcheck.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.29) include("${CMAKE_CURRENT_LIST_DIR}/gitlab_ci.cmake") @@ -34,6 +34,15 @@ ctest_memcheck( ctest_submit(PARTS Test) ctest_submit(PARTS Memcheck) +include("${CMAKE_CURRENT_LIST_DIR}/ctest_annotation.cmake") +ctest_annotation_report("${CTEST_BINARY_DIRECTORY}/annotations.json" + "Build Summary" "https://open.cdash.org/build/${build_id}" + "All Tests" "https://open.cdash.org/viewTest.php?buildid=${build_id}" + "Dynamic Analysis" "https://open.cdash.org/viewDynamicAnalysis.php?buildid=${build_id}" + "Test Failures" "https://open.cdash.org/viewTest.php?onlyfailed&buildid=${build_id}" + "Tests Not Run" "https://open.cdash.org/viewTest.php?onlynotrun&buildid=${build_id}" + "Test Passes" "https://open.cdash.org/viewTest.php?onlypassed&buildid=${build_id}") + if (test_result) message(FATAL_ERROR "Failed to test") diff --git a/.gitlab/ci/ctest_memcheck_fedora38_asan.lsan.supp b/.gitlab/ci/ctest_memcheck_fedora42_asan.lsan.supp similarity index 100% rename from .gitlab/ci/ctest_memcheck_fedora38_asan.lsan.supp rename to .gitlab/ci/ctest_memcheck_fedora42_asan.lsan.supp diff --git a/.gitlab/ci/ctest_standalone.cmake b/.gitlab/ci/ctest_standalone.cmake index 36ba71c4a5a..a55ab6cc290 100644 --- a/.gitlab/ci/ctest_standalone.cmake +++ b/.gitlab/ci/ctest_standalone.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.29) include("${CMAKE_CURRENT_LIST_DIR}/gitlab_ci.cmake") include("${CMAKE_CURRENT_LIST_DIR}/env_$ENV{CMAKE_CONFIGURATION}.cmake" OPTIONAL) @@ -38,9 +38,16 @@ ctest_configure( ctest_read_custom_files("${CTEST_BINARY_DIRECTORY}") # We can now submit because we've configured. This is a cmb-superbuild-ism. -ctest_submit(PARTS Update) +ctest_submit(PARTS Update + BUILD_ID build_id) ctest_submit(PARTS Configure) +include("${CMAKE_CURRENT_LIST_DIR}/ctest_annotation.cmake") +ctest_annotation_report("${CTEST_BINARY_DIRECTORY}/annotations.json" + "Build Summary" "https://open.cdash.org/build/${build_id}" + "Update" "https://open.cdash.org/build/${build_id}/update" + "Configure" "https://open.cdash.org/build/${build_id}/configure") + if (configure_result) ctest_submit(PARTS Done) message(FATAL_ERROR @@ -54,10 +61,15 @@ elseif (CTEST_CMAKE_GENERATOR MATCHES "Ninja") endif () ctest_build( + NUMBER_ERRORS num_errors NUMBER_WARNINGS num_warnings RETURN_VALUE build_result) ctest_submit(PARTS Build) +ctest_annotation_report("${CTEST_BINARY_DIRECTORY}/annotations.json" + "Build Errors (${num_errors})" "https://open.cdash.org/viewBuildError.php?buildid=${build_id}" + "Build Warnings (${num_warnings})" "https://open.cdash.org/viewBuildError.php?type=1&buildid=${build_id}") + if (build_result) ctest_submit(PARTS Done) message(FATAL_ERROR @@ -86,6 +98,12 @@ ctest_test( EXCLUDE "${test_exclusions}") ctest_submit(PARTS Test) +ctest_annotation_report("${CTEST_BINARY_DIRECTORY}/annotations.json" + "All Tests" "https://open.cdash.org/viewTest.php?buildid=${build_id}" + "Test Failures" "https://open.cdash.org/viewTest.php?onlyfailed&buildid=${build_id}" + "Tests Not Run" "https://open.cdash.org/viewTest.php?onlynotrun&buildid=${build_id}" + "Test Passes" "https://open.cdash.org/viewTest.php?onlypassed&buildid=${build_id}") + if (test_result) ctest_submit(PARTS Done) message(FATAL_ERROR diff --git a/.gitlab/ci/ctest_test.cmake b/.gitlab/ci/ctest_test.cmake index b02d032fa50..fa60dd06da9 100644 --- a/.gitlab/ci/ctest_test.cmake +++ b/.gitlab/ci/ctest_test.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.29) include("${CMAKE_CURRENT_LIST_DIR}/gitlab_ci.cmake") @@ -25,6 +25,14 @@ ctest_test( EXCLUDE "${test_exclusions}") ctest_submit(PARTS Test) +include("${CMAKE_CURRENT_LIST_DIR}/ctest_annotation.cmake") +ctest_annotation_report("${CTEST_BINARY_DIRECTORY}/annotations.json" + "Build Summary" "https://open.cdash.org/build/${build_id}" + "All Tests" "https://open.cdash.org/viewTest.php?buildid=${build_id}" + "Test Failures" "https://open.cdash.org/viewTest.php?onlyfailed&buildid=${build_id}" + "Tests Not Run" "https://open.cdash.org/viewTest.php?onlynotrun&buildid=${build_id}" + "Test Passes" "https://open.cdash.org/viewTest.php?onlypassed&buildid=${build_id}") + if (test_result) message(FATAL_ERROR "Failed to test") diff --git a/.gitlab/ci/cxx_modules_rules_clang.cmake b/.gitlab/ci/cxx_modules_rules_clang.cmake deleted file mode 100644 index a8e1ff68ab0..00000000000 --- a/.gitlab/ci/cxx_modules_rules_clang.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(CMake_TEST_CXXModules_UUID "a246741c-d067-4019-a8fb-3d16b0c9d1d3") - -# Default to C++ extensions being off. Clang's modules support have trouble -# with extensions right now. -set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/.gitlab/ci/cxx_modules_rules_gcc.cmake b/.gitlab/ci/cxx_modules_rules_gcc.cmake deleted file mode 100644 index 3777506602a..00000000000 --- a/.gitlab/ci/cxx_modules_rules_gcc.cmake +++ /dev/null @@ -1,9 +0,0 @@ -set(CMake_TEST_CXXModules_UUID "a246741c-d067-4019-a8fb-3d16b0c9d1d3") - -string(CONCAT CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE - " -E -x c++ " - " -MT -MD -MF " - " -fmodules-ts -fdep-file= -fdep-output= -fdep-format=trtbd" - " -o ") -set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "gcc") -set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "-fmodules-ts -fmodule-mapper= -fdep-format=trtbd -x c++") diff --git a/.gitlab/ci/cxx_modules_rules_msvc.cmake b/.gitlab/ci/cxx_modules_rules_msvc.cmake deleted file mode 100644 index 2b09b0e26db..00000000000 --- a/.gitlab/ci/cxx_modules_rules_msvc.cmake +++ /dev/null @@ -1 +0,0 @@ -set(CMake_TEST_CXXModules_UUID "a246741c-d067-4019-a8fb-3d16b0c9d1d3") diff --git a/.gitlab/ci/docker/cuda12.2/Dockerfile b/.gitlab/ci/docker/cuda12.2/Dockerfile new file mode 100644 index 00000000000..e46f2287d85 --- /dev/null +++ b/.gitlab/ci/docker/cuda12.2/Dockerfile @@ -0,0 +1,9 @@ +FROM kitware/nvidia-cuda:12.2.2-devel-ubuntu22.04 +MAINTAINER Brad King + +COPY llvm.list /etc/apt/sources.list.d/llvm.list +COPY llvm-snapshot.gpg.key /root/llvm-snapshot.gpg.key +RUN apt-key add /root/llvm-snapshot.gpg.key + +COPY install_deps.sh /root/install_deps.sh +RUN sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/cuda12.2/install_deps.sh b/.gitlab/ci/docker/cuda12.2/install_deps.sh new file mode 100755 index 00000000000..4708277972c --- /dev/null +++ b/.gitlab/ci/docker/cuda12.2/install_deps.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +set -e + +apt-get update + +# Install dependency without interaction. +env DEBIAN_FRONTEND=noninteractive \ + TZ=America/New_York \ + apt-get install -y \ + tzdata + +# Install development tools. +apt-get install -y \ + g++ \ + clang-18 \ + libomp-18-dev \ + curl \ + git + +apt-get clean diff --git a/.gitlab/ci/docker/cuda12.2/llvm-snapshot.gpg.key b/.gitlab/ci/docker/cuda12.2/llvm-snapshot.gpg.key new file mode 100644 index 00000000000..aa6b105aa3d --- /dev/null +++ b/.gitlab/ci/docker/cuda12.2/llvm-snapshot.gpg.key @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +mQINBFE9lCwBEADi0WUAApM/mgHJRU8lVkkw0CHsZNpqaQDNaHefD6Rw3S4LxNmM +EZaOTkhP200XZM8lVdbfUW9xSjA3oPldc1HG26NjbqqCmWpdo2fb+r7VmU2dq3NM +R18ZlKixiLDE6OUfaXWKamZsXb6ITTYmgTO6orQWYrnW6ckYHSeaAkW0wkDAryl2 +B5v8aoFnQ1rFiVEMo4NGzw4UX+MelF7rxaaregmKVTPiqCOSPJ1McC1dHFN533FY +Wh/RVLKWo6npu+owtwYFQW+zyQhKzSIMvNujFRzhIxzxR9Gn87MoLAyfgKEzrbbT +DhqqNXTxS4UMUKCQaO93TzetX/EBrRpJj+vP640yio80h4Dr5pAd7+LnKwgpTDk1 +G88bBXJAcPZnTSKu9I2c6KY4iRNbvRz4i+ZdwwZtdW4nSdl2792L7Sl7Nc44uLL/ +ZqkKDXEBF6lsX5XpABwyK89S/SbHOytXv9o4puv+65Ac5/UShspQTMSKGZgvDauU +cs8kE1U9dPOqVNCYq9Nfwinkf6RxV1k1+gwtclxQuY7UpKXP0hNAXjAiA5KS5Crq +7aaJg9q2F4bub0mNU6n7UI6vXguF2n4SEtzPRk6RP+4TiT3bZUsmr+1ktogyOJCc +Ha8G5VdL+NBIYQthOcieYCBnTeIH7D3Sp6FYQTYtVbKFzmMK+36ERreL/wARAQAB +tD1TeWx2ZXN0cmUgTGVkcnUgLSBEZWJpYW4gTExWTSBwYWNrYWdlcyA8c3lsdmVz +dHJlQGRlYmlhbi5vcmc+iQI4BBMBAgAiBQJRPZQsAhsDBgsJCAcDAgYVCAIJCgsE +FgIDAQIeAQIXgAAKCRAVz00Yr090Ibx+EADArS/hvkDF8juWMXxh17CgR0WZlHCC +9CTBWkg5a0bNN/3bb97cPQt/vIKWjQtkQpav6/5JTVCSx2riL4FHYhH0iuo4iAPR +udC7Cvg8g7bSPrKO6tenQZNvQm+tUmBHgFiMBJi92AjZ/Qn1Shg7p9ITivFxpLyX +wpmnF1OKyI2Kof2rm4BFwfSWuf8Fvh7kDMRLHv+MlnK/7j/BNpKdozXxLcwoFBmn +l0WjpAH3OFF7Pvm1LJdf1DjWKH0Dc3sc6zxtmBR/KHHg6kK4BGQNnFKujcP7TVdv +gMYv84kun14pnwjZcqOtN3UJtcx22880DOQzinoMs3Q4w4o05oIF+sSgHViFpc3W +R0v+RllnH05vKZo+LDzc83DQVrdwliV12eHxrMQ8UYg88zCbF/cHHnlzZWAJgftg +hB08v1BKPgYRUzwJ6VdVqXYcZWEaUJmQAPuAALyZESw94hSo28FAn0/gzEc5uOYx +K+xG/lFwgAGYNb3uGM5m0P6LVTfdg6vDwwOeTNIExVk3KVFXeSQef2ZMkhwA7wya +KJptkb62wBHFE+o9TUdtMCY6qONxMMdwioRE5BYNwAsS1PnRD2+jtlI0DzvKHt7B +MWd8hnoUKhMeZ9TNmo+8CpsAtXZcBho0zPGz/R8NlJhAWpdAZ1CmcPo83EW86Yq7 +BxQUKnNHcwj2ebkCDQRRPZQsARAA4jxYmbTHwmMjqSizlMJYNuGOpIidEdx9zQ5g +zOr431/VfWq4S+VhMDhs15j9lyml0y4ok215VRFwrAREDg6UPMr7ajLmBQGau0Fc +bvZJ90l4NjXp5p0NEE/qOb9UEHT7EGkEhaZ1ekkWFTWCgsy7rRXfZLxB6sk7pzLC +DshyW3zjIakWAnpQ5j5obiDy708pReAuGB94NSyb1HoW/xGsGgvvCw4r0w3xPStw +F1PhmScE6NTBIfLliea3pl8vhKPlCh54Hk7I8QGjo1ETlRP4Qll1ZxHJ8u25f/ta +RES2Aw8Hi7j0EVcZ6MT9JWTI83yUcnUlZPZS2HyeWcUj+8nUC8W4N8An+aNps9l/ +21inIl2TbGo3Yn1JQLnA1YCoGwC34g8QZTJhElEQBN0X29ayWW6OdFx8MDvllbBV +ymmKq2lK1U55mQTfDli7S3vfGz9Gp/oQwZ8bQpOeUkc5hbZszYwP4RX+68xDPfn+ +M9udl+qW9wu+LyePbW6HX90LmkhNkkY2ZzUPRPDHZANU5btaPXc2H7edX4y4maQa +xenqD0lGh9LGz/mps4HEZtCI5CY8o0uCMF3lT0XfXhuLksr7Pxv57yue8LLTItOJ +d9Hmzp9G97SRYYeqU+8lyNXtU2PdrLLq7QHkzrsloG78lCpQcalHGACJzrlUWVP/ +fN3Ht3kAEQEAAYkCHwQYAQIACQUCUT2ULAIbDAAKCRAVz00Yr090IbhWEADbr50X +OEXMIMGRLe+YMjeMX9NG4jxs0jZaWHc/WrGR+CCSUb9r6aPXeLo+45949uEfdSsB +pbaEdNWxF5Vr1CSjuO5siIlgDjmT655voXo67xVpEN4HhMrxugDJfCa6z97P0+ML +PdDxim57uNqkam9XIq9hKQaurxMAECDPmlEXI4QT3eu5qw5/knMzDMZj4Vi6hovL +wvvAeLHO/jsyfIdNmhBGU2RWCEZ9uo/MeerPHtRPfg74g+9PPfP6nyHD2Wes6yGd +oVQwtPNAQD6Cj7EaA2xdZYLJ7/jW6yiPu98FFWP74FN2dlyEA2uVziLsfBrgpS4l +tVOlrO2YzkkqUGrybzbLpj6eeHx+Cd7wcjI8CalsqtL6cG8cUEjtWQUHyTbQWAgG +5VPEgIAVhJ6RTZ26i/G+4J8neKyRs4vz+57UGwY6zI4AB1ZcWGEE3Bf+CDEDgmnP +LSwbnHefK9IljT9XU98PelSryUO/5UPw7leE0akXKB4DtekToO226px1VnGp3Bov +1GBGvpHvL2WizEwdk+nfk8LtrLzej+9FtIcq3uIrYnsac47Pf7p0otcFeTJTjSq3 +krCaoG4Hx0zGQG2ZFpHrSrZTVy6lxvIdfi0beMgY6h78p6M9eYZHQHc02DjFkQXN +bXb5c6gCHESH5PXwPU4jQEE7Ib9J6sbk7ZT2Mw== +=j+4q +-----END PGP PUBLIC KEY BLOCK----- diff --git a/.gitlab/ci/docker/cuda12.2/llvm.list b/.gitlab/ci/docker/cuda12.2/llvm.list new file mode 100644 index 00000000000..350a712c3f8 --- /dev/null +++ b/.gitlab/ci/docker/cuda12.2/llvm.list @@ -0,0 +1,2 @@ +deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main +deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main diff --git a/.gitlab/ci/docker/cuda12.6/Dockerfile b/.gitlab/ci/docker/cuda12.6/Dockerfile new file mode 100644 index 00000000000..b47bcfd101c --- /dev/null +++ b/.gitlab/ci/docker/cuda12.6/Dockerfile @@ -0,0 +1,9 @@ +FROM kitware/nvidia-cuda:12.6.3-devel-ubuntu24.04 +MAINTAINER Brad King + +COPY llvm.list /etc/apt/sources.list.d/llvm.list +COPY llvm-snapshot.gpg.key /root/llvm-snapshot.gpg.key +RUN apt-key add /root/llvm-snapshot.gpg.key + +COPY install_deps.sh /root/install_deps.sh +RUN sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/cuda12.6/install_deps.sh b/.gitlab/ci/docker/cuda12.6/install_deps.sh new file mode 100755 index 00000000000..4708277972c --- /dev/null +++ b/.gitlab/ci/docker/cuda12.6/install_deps.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +set -e + +apt-get update + +# Install dependency without interaction. +env DEBIAN_FRONTEND=noninteractive \ + TZ=America/New_York \ + apt-get install -y \ + tzdata + +# Install development tools. +apt-get install -y \ + g++ \ + clang-18 \ + libomp-18-dev \ + curl \ + git + +apt-get clean diff --git a/.gitlab/ci/docker/cuda12.6/llvm-snapshot.gpg.key b/.gitlab/ci/docker/cuda12.6/llvm-snapshot.gpg.key new file mode 100644 index 00000000000..aa6b105aa3d --- /dev/null +++ b/.gitlab/ci/docker/cuda12.6/llvm-snapshot.gpg.key @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +mQINBFE9lCwBEADi0WUAApM/mgHJRU8lVkkw0CHsZNpqaQDNaHefD6Rw3S4LxNmM +EZaOTkhP200XZM8lVdbfUW9xSjA3oPldc1HG26NjbqqCmWpdo2fb+r7VmU2dq3NM +R18ZlKixiLDE6OUfaXWKamZsXb6ITTYmgTO6orQWYrnW6ckYHSeaAkW0wkDAryl2 +B5v8aoFnQ1rFiVEMo4NGzw4UX+MelF7rxaaregmKVTPiqCOSPJ1McC1dHFN533FY +Wh/RVLKWo6npu+owtwYFQW+zyQhKzSIMvNujFRzhIxzxR9Gn87MoLAyfgKEzrbbT +DhqqNXTxS4UMUKCQaO93TzetX/EBrRpJj+vP640yio80h4Dr5pAd7+LnKwgpTDk1 +G88bBXJAcPZnTSKu9I2c6KY4iRNbvRz4i+ZdwwZtdW4nSdl2792L7Sl7Nc44uLL/ +ZqkKDXEBF6lsX5XpABwyK89S/SbHOytXv9o4puv+65Ac5/UShspQTMSKGZgvDauU +cs8kE1U9dPOqVNCYq9Nfwinkf6RxV1k1+gwtclxQuY7UpKXP0hNAXjAiA5KS5Crq +7aaJg9q2F4bub0mNU6n7UI6vXguF2n4SEtzPRk6RP+4TiT3bZUsmr+1ktogyOJCc +Ha8G5VdL+NBIYQthOcieYCBnTeIH7D3Sp6FYQTYtVbKFzmMK+36ERreL/wARAQAB +tD1TeWx2ZXN0cmUgTGVkcnUgLSBEZWJpYW4gTExWTSBwYWNrYWdlcyA8c3lsdmVz +dHJlQGRlYmlhbi5vcmc+iQI4BBMBAgAiBQJRPZQsAhsDBgsJCAcDAgYVCAIJCgsE +FgIDAQIeAQIXgAAKCRAVz00Yr090Ibx+EADArS/hvkDF8juWMXxh17CgR0WZlHCC +9CTBWkg5a0bNN/3bb97cPQt/vIKWjQtkQpav6/5JTVCSx2riL4FHYhH0iuo4iAPR +udC7Cvg8g7bSPrKO6tenQZNvQm+tUmBHgFiMBJi92AjZ/Qn1Shg7p9ITivFxpLyX +wpmnF1OKyI2Kof2rm4BFwfSWuf8Fvh7kDMRLHv+MlnK/7j/BNpKdozXxLcwoFBmn +l0WjpAH3OFF7Pvm1LJdf1DjWKH0Dc3sc6zxtmBR/KHHg6kK4BGQNnFKujcP7TVdv +gMYv84kun14pnwjZcqOtN3UJtcx22880DOQzinoMs3Q4w4o05oIF+sSgHViFpc3W +R0v+RllnH05vKZo+LDzc83DQVrdwliV12eHxrMQ8UYg88zCbF/cHHnlzZWAJgftg +hB08v1BKPgYRUzwJ6VdVqXYcZWEaUJmQAPuAALyZESw94hSo28FAn0/gzEc5uOYx +K+xG/lFwgAGYNb3uGM5m0P6LVTfdg6vDwwOeTNIExVk3KVFXeSQef2ZMkhwA7wya +KJptkb62wBHFE+o9TUdtMCY6qONxMMdwioRE5BYNwAsS1PnRD2+jtlI0DzvKHt7B +MWd8hnoUKhMeZ9TNmo+8CpsAtXZcBho0zPGz/R8NlJhAWpdAZ1CmcPo83EW86Yq7 +BxQUKnNHcwj2ebkCDQRRPZQsARAA4jxYmbTHwmMjqSizlMJYNuGOpIidEdx9zQ5g +zOr431/VfWq4S+VhMDhs15j9lyml0y4ok215VRFwrAREDg6UPMr7ajLmBQGau0Fc +bvZJ90l4NjXp5p0NEE/qOb9UEHT7EGkEhaZ1ekkWFTWCgsy7rRXfZLxB6sk7pzLC +DshyW3zjIakWAnpQ5j5obiDy708pReAuGB94NSyb1HoW/xGsGgvvCw4r0w3xPStw +F1PhmScE6NTBIfLliea3pl8vhKPlCh54Hk7I8QGjo1ETlRP4Qll1ZxHJ8u25f/ta +RES2Aw8Hi7j0EVcZ6MT9JWTI83yUcnUlZPZS2HyeWcUj+8nUC8W4N8An+aNps9l/ +21inIl2TbGo3Yn1JQLnA1YCoGwC34g8QZTJhElEQBN0X29ayWW6OdFx8MDvllbBV +ymmKq2lK1U55mQTfDli7S3vfGz9Gp/oQwZ8bQpOeUkc5hbZszYwP4RX+68xDPfn+ +M9udl+qW9wu+LyePbW6HX90LmkhNkkY2ZzUPRPDHZANU5btaPXc2H7edX4y4maQa +xenqD0lGh9LGz/mps4HEZtCI5CY8o0uCMF3lT0XfXhuLksr7Pxv57yue8LLTItOJ +d9Hmzp9G97SRYYeqU+8lyNXtU2PdrLLq7QHkzrsloG78lCpQcalHGACJzrlUWVP/ +fN3Ht3kAEQEAAYkCHwQYAQIACQUCUT2ULAIbDAAKCRAVz00Yr090IbhWEADbr50X +OEXMIMGRLe+YMjeMX9NG4jxs0jZaWHc/WrGR+CCSUb9r6aPXeLo+45949uEfdSsB +pbaEdNWxF5Vr1CSjuO5siIlgDjmT655voXo67xVpEN4HhMrxugDJfCa6z97P0+ML +PdDxim57uNqkam9XIq9hKQaurxMAECDPmlEXI4QT3eu5qw5/knMzDMZj4Vi6hovL +wvvAeLHO/jsyfIdNmhBGU2RWCEZ9uo/MeerPHtRPfg74g+9PPfP6nyHD2Wes6yGd +oVQwtPNAQD6Cj7EaA2xdZYLJ7/jW6yiPu98FFWP74FN2dlyEA2uVziLsfBrgpS4l +tVOlrO2YzkkqUGrybzbLpj6eeHx+Cd7wcjI8CalsqtL6cG8cUEjtWQUHyTbQWAgG +5VPEgIAVhJ6RTZ26i/G+4J8neKyRs4vz+57UGwY6zI4AB1ZcWGEE3Bf+CDEDgmnP +LSwbnHefK9IljT9XU98PelSryUO/5UPw7leE0akXKB4DtekToO226px1VnGp3Bov +1GBGvpHvL2WizEwdk+nfk8LtrLzej+9FtIcq3uIrYnsac47Pf7p0otcFeTJTjSq3 +krCaoG4Hx0zGQG2ZFpHrSrZTVy6lxvIdfi0beMgY6h78p6M9eYZHQHc02DjFkQXN +bXb5c6gCHESH5PXwPU4jQEE7Ib9J6sbk7ZT2Mw== +=j+4q +-----END PGP PUBLIC KEY BLOCK----- diff --git a/.gitlab/ci/docker/cuda12.6/llvm.list b/.gitlab/ci/docker/cuda12.6/llvm.list new file mode 100644 index 00000000000..b39b6d4e3ca --- /dev/null +++ b/.gitlab/ci/docker/cuda12.6/llvm.list @@ -0,0 +1,2 @@ +deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main +deb-src http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main diff --git a/.gitlab/ci/docker/debian10-aarch64/Dockerfile b/.gitlab/ci/docker/debian10-aarch64/Dockerfile deleted file mode 100644 index a0687e3e301..00000000000 --- a/.gitlab/ci/docker/debian10-aarch64/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG BASE_IMAGE=arm64v8/debian:10 - -FROM ${BASE_IMAGE} AS apt-cache -# Populate APT cache w/ the fresh metadata and prefetch packages. -# Use an empty `docker-clean` file to "hide" the image-provided -# file to disallow removing packages after `apt-get` operations. -RUN --mount=type=tmpfs,target=/var/log \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - apt-get update \ - && apt-get --download-only -y install $(grep -h '^[^#]\+$' /root/*.lst) - -FROM ${BASE_IMAGE} -LABEL maintainer="Brad King " - -RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ - --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - --mount=type=bind,source=dpkg-exclude,target=/etc/dpkg/dpkg.cfg.d/exclude \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ - --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/debian10-aarch64/deps_packages.lst b/.gitlab/ci/docker/debian10-aarch64/deps_packages.lst deleted file mode 100644 index ca833230bc0..00000000000 --- a/.gitlab/ci/docker/debian10-aarch64/deps_packages.lst +++ /dev/null @@ -1,94 +0,0 @@ -# Install build requirements. -libssl-dev - -# Install development tools. -g++ -curl -git - -# Install optional external build dependencies. -libarchive-dev -libbz2-dev -libcurl4-gnutls-dev -libexpat1-dev -libjsoncpp-dev -liblzma-dev -libncurses-dev -librhash-dev -libuv1-dev -libzstd-dev -zlib1g-dev - -# Install iwyu runtime deps. -clang-6.0 -libncurses6 - -# Tools needed for the test suite. -jq - -# Packages needed to test CTest. -bzr bzr-xmloutput -cvs -subversion -mercurial - -# Packages needed to test find modules. -alsa-utils -doxygen graphviz -freeglut3-dev -gnutls-dev -libarchive-dev -libblas-dev -libboost-dev -libboost-filesystem-dev -libboost-program-options-dev -libboost-python-dev -libboost-thread-dev -libbz2-dev -libcups2-dev -libcurl4-gnutls-dev -libdevil-dev -libfontconfig1-dev -libfreetype6-dev -libgdal-dev -libgif-dev -libgl1-mesa-dev -libglew-dev -libgmock-dev -libgrpc++-dev libgrpc-dev -libgsl-dev -libgtest-dev -libgtk2.0-dev -libhdf5-dev -libhdf5-mpich-dev -libhdf5-openmpi-dev -libicu-dev -libinput-dev -libjpeg-dev -libjsoncpp-dev -liblapack-dev -liblzma-dev -libmagick++-dev -libopenal-dev -libopenmpi-dev openmpi-bin -libosp-dev -libpng-dev -libpq-dev postgresql-server-dev-11 -libprotobuf-dev libprotobuf-c-dev libprotoc-dev protobuf-compiler protobuf-compiler-grpc -libsdl-dev -libsqlite3-dev -libtiff-dev -libuv1-dev -libwxgtk3.0-dev -libx11-dev -libxalan-c-dev -libxerces-c-dev -libxml2-dev libxml2-utils -libxslt-dev xsltproc -openjdk-11-jdk -python2 python2-dev python-numpy pypy pypy-dev -python3 python3-dev python3-numpy pypy3 pypy3-dev python3-venv -qtbase5-dev qtbase5-dev-tools -ruby ruby-dev -swig -unixodbc-dev diff --git a/.gitlab/ci/docker/debian10-x86_64/Dockerfile b/.gitlab/ci/docker/debian10-x86_64/Dockerfile new file mode 100644 index 00000000000..c39b380bc37 --- /dev/null +++ b/.gitlab/ci/docker/debian10-x86_64/Dockerfile @@ -0,0 +1,27 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE=debian:10 + +FROM ${BASE_IMAGE} AS apt-cache +# Populate APT cache w/ the fresh metadata and prefetch packages. +# Use an empty `docker-clean` file to "hide" the image-provided +# file to disallow removing packages after `apt-get` operations. +RUN --mount=type=tmpfs,target=/var/log \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + apt-get update \ + && apt-get --download-only -y install $(grep -h '^[^#]\+$' /root/*.lst) + + +FROM ${BASE_IMAGE} +LABEL maintainer="Brad King " + +RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + --mount=type=bind,source=dpkg-exclude,target=/etc/dpkg/dpkg.cfg.d/exclude \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ + --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ + --mount=type=tmpfs,target=/var/log \ + --mount=type=tmpfs,target=/tmp \ + sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/debian10-x86_64/deps_packages.lst b/.gitlab/ci/docker/debian10-x86_64/deps_packages.lst new file mode 100644 index 00000000000..b415421645d --- /dev/null +++ b/.gitlab/ci/docker/debian10-x86_64/deps_packages.lst @@ -0,0 +1,16 @@ +# Install build requirements. +libssl-dev + +# Install development tools. +g++ +curl +git + +# Tools needed for the test suite. +jq + +# Packages needed to test find modules. +python2 python2-dev python-numpy pypy pypy-dev + +# CMake_TEST_FindPython2_IronPython +libmono-system-windows-forms4.0-cil diff --git a/.gitlab/ci/docker/debian10-aarch64/docker-clean b/.gitlab/ci/docker/debian10-x86_64/docker-clean similarity index 100% rename from .gitlab/ci/docker/debian10-aarch64/docker-clean rename to .gitlab/ci/docker/debian10-x86_64/docker-clean diff --git a/.gitlab/ci/docker/debian10-aarch64/dpkg-exclude b/.gitlab/ci/docker/debian10-x86_64/dpkg-exclude similarity index 100% rename from .gitlab/ci/docker/debian10-aarch64/dpkg-exclude rename to .gitlab/ci/docker/debian10-x86_64/dpkg-exclude diff --git a/.gitlab/ci/docker/debian10-x86_64/install_deps.sh b/.gitlab/ci/docker/debian10-x86_64/install_deps.sh new file mode 100755 index 00000000000..002f136c05e --- /dev/null +++ b/.gitlab/ci/docker/debian10-x86_64/install_deps.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +apt-get install -y $(grep '^[^#]\+$' /root/deps_packages.lst) + +curl -L -O https://github.com/IronLanguages/ironpython2/releases/download/ipy-2.7.12/ironpython_2.7.12.deb +echo 'b7b90c82cf311dd3faf290ce3f274af5128b96db884a88dd643ce141bbf12fb9 ironpython_2.7.12.deb' > ironpython.sha256sum +sha256sum --check ironpython.sha256sum +dpkg -i ironpython_2.7.12.deb +rm ironpython_2.7.12.deb ironpython.sha256sum diff --git a/.gitlab/ci/docker/debian10/Dockerfile b/.gitlab/ci/docker/debian10/Dockerfile deleted file mode 100644 index d86642876ce..00000000000 --- a/.gitlab/ci/docker/debian10/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG BASE_IMAGE=debian:10 - -FROM ${BASE_IMAGE} AS apt-cache -# Populate APT cache w/ the fresh metadata and prefetch packages. -# Use an empty `docker-clean` file to "hide" the image-provided -# file to disallow removing packages after `apt-get` operations. -RUN --mount=type=tmpfs,target=/var/log \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \ - --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \ - apt-get update \ - && apt-get --download-only -y install $(grep -h '^[^#]\+$' /root/*.lst) - - -FROM ${BASE_IMAGE} AS iwyu-build -LABEL maintainer="Ben Boeckel " - -RUN --mount=type=bind,source=install_iwyu.sh,target=/root/install_iwyu.sh \ - --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ - --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_iwyu.sh - - -FROM ${BASE_IMAGE} AS rvm-build -LABEL maintainer="Ben Boeckel " - -RUN --mount=type=bind,source=install_rvm.sh,target=/root/install_rvm.sh \ - --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ - --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_rvm.sh - - -FROM ${BASE_IMAGE} -LABEL maintainer="Ben Boeckel " - -RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ - --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - --mount=type=bind,source=dpkg-exclude,target=/etc/dpkg/dpkg.cfg.d/exclude \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ - --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_deps.sh - -RUN --mount=type=bind,from=iwyu-build,source=/root,target=/root \ - tar -C / -xf /root/iwyu.tar \ - && ln -s /usr/lib/llvm-6.0/bin/include-what-you-use /usr/bin/include-what-you-use-6.0 - -RUN --mount=type=bind,from=rvm-build,source=/root,target=/root \ - tar -C /usr/local -xf /root/rvm.tar diff --git a/.gitlab/ci/docker/debian10/deps_packages.lst b/.gitlab/ci/docker/debian10/deps_packages.lst deleted file mode 100644 index 0b79675846c..00000000000 --- a/.gitlab/ci/docker/debian10/deps_packages.lst +++ /dev/null @@ -1,100 +0,0 @@ -# Install build requirements. -libssl-dev - -# Install development tools. -g++ -curl -git - -# Install optional external build dependencies. -libarchive-dev -libbz2-dev -libcurl4-gnutls-dev -libexpat1-dev -libjsoncpp-dev -liblzma-dev -libncurses-dev -librhash-dev -libuv1-dev -libzstd-dev -zlib1g-dev - -# Install iwyu runtime deps. -clang-6.0 -libncurses6 - -# Tools needed for the test suite. -jq - -# Packages needed to test CTest. -bzr bzr-xmloutput -cvs -subversion -mercurial - -# Install swift runtime deps. -libncurses5 - -# Packages needed to test find modules. -alsa-utils -doxygen graphviz -freeglut3-dev -gnutls-dev -libarchive-dev -libblas-dev -libboost-dev -libboost-filesystem-dev -libboost-program-options-dev -libboost-python-dev -libboost-thread-dev -libbz2-dev -libcups2-dev -libcurl4-gnutls-dev -libdevil-dev -libfontconfig1-dev -libfreetype6-dev -libgdal-dev -libgif-dev -libgl1-mesa-dev -libglew-dev -libgmock-dev -libgrpc++-dev libgrpc-dev -libgsl-dev -libgtest-dev -libgtk2.0-dev -libhdf5-dev -libhdf5-mpich-dev -libhdf5-openmpi-dev -libicu-dev -libinput-dev -libjpeg-dev -libjsoncpp-dev -liblapack-dev -liblzma-dev -libmagick++-dev -libopenal-dev -libopenmpi-dev openmpi-bin -libosp-dev -libpng-dev -libpq-dev postgresql-server-dev-11 -libprotobuf-dev libprotobuf-c-dev libprotoc-dev protobuf-compiler protobuf-compiler-grpc -libsdl-dev -libsqlite3-dev -libtiff-dev -libuv1-dev -libwxgtk3.0-dev -libx11-dev -libxalan-c-dev -libxerces-c-dev -libxml2-dev libxml2-utils -libxslt-dev xsltproc -openjdk-11-jdk -python2 python2-dev python-numpy pypy pypy-dev -python3 python3-dev python3-numpy pypy3 pypy3-dev python3-venv -qtbase5-dev qtbase5-dev-tools -ruby ruby-dev -swig -unixodbc-dev - -# CMake_TEST_FindPython_IronPython -libmono-system-windows-forms4.0-cil diff --git a/.gitlab/ci/docker/debian10/install_deps.sh b/.gitlab/ci/docker/debian10/install_deps.sh deleted file mode 100755 index a00e322cd8d..00000000000 --- a/.gitlab/ci/docker/debian10/install_deps.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -set -e - -apt-get install -y $(grep '^[^#]\+$' /root/deps_packages.lst) - -curl -L -O https://github.com/IronLanguages/ironpython2/releases/download/ipy-2.7.10/ironpython_2.7.10.deb -echo 'e1aceec1d49ffa66e9059a52168a734999dcccc50164a60e2936649cae698f3e ironpython_2.7.10.deb' > ironpython.sha256sum -sha256sum --check ironpython.sha256sum -dpkg -i ironpython_2.7.10.deb -rm ironpython_2.7.10.deb ironpython.sha256sum - -# Perforce -curl -L https://www.perforce.com/downloads/perforce/r21.2/bin.linux26x86_64/helix-core-server.tgz -o - \ - | tar -C /usr/local/bin -xvzf - -- p4 p4d diff --git a/.gitlab/ci/docker/debian10/install_iwyu.sh b/.gitlab/ci/docker/debian10/install_iwyu.sh deleted file mode 100755 index 4814a7161e0..00000000000 --- a/.gitlab/ci/docker/debian10/install_iwyu.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -set -e - -# Install development tools. -apt-get install -y $(grep '^[^#]\+$' /root/iwyu_packages.lst) - -cd /root -git clone "https://github.com/include-what-you-use/include-what-you-use.git" -cd include-what-you-use -readonly llvm_version="$( clang-6.0 --version | head -n1 | cut -d' ' -f3 | cut -d. -f-2 )" -git checkout "clang_$llvm_version" -mkdir build -cd build - -cmake -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - "-DCMAKE_INSTALL_PREFIX=/usr/lib/llvm-$llvm_version" \ - "-DIWYU_LLVM_ROOT_PATH=/usr/lib/llvm-$llvm_version" \ - .. -ninja -DESTDIR=/root/iwyu-destdir ninja install -tar -C /root/iwyu-destdir -cf /root/iwyu.tar . diff --git a/.gitlab/ci/docker/debian10/install_rvm.sh b/.gitlab/ci/docker/debian10/install_rvm.sh deleted file mode 100755 index c6fff70d024..00000000000 --- a/.gitlab/ci/docker/debian10/install_rvm.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -set -e - -apt-get install -y $(grep '^[^#]\+$' /root/rvm_packages.lst) - -gpg2 --keyserver hkps://keyserver.ubuntu.com \ - --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \ - 7D2BAF1CF37B13E2069D6956105BD0E739499BDB - -curl -sSL https://get.rvm.io | bash -s stable - -# keep version in sync with `env_debian*_ninja.sh` -/usr/local/rvm/bin/rvm install ruby-2.7.0 - -for p in archives examples gem-cache log src; do - touch /usr/local/rvm/${p}/.tar_exclude -done - -tar -C /usr/local --exclude-tag-under=.tar_exclude -cf /root/rvm.tar rvm diff --git a/.gitlab/ci/docker/debian10/iwyu_packages.lst b/.gitlab/ci/docker/debian10/iwyu_packages.lst deleted file mode 100644 index 9e291c9b046..00000000000 --- a/.gitlab/ci/docker/debian10/iwyu_packages.lst +++ /dev/null @@ -1,9 +0,0 @@ -# Install development tools. -clang-6.0 -libclang-6.0-dev -llvm-6.0-dev -libz-dev -g++ -cmake -ninja-build -git diff --git a/.gitlab/ci/docker/debian12-aarch64/Dockerfile b/.gitlab/ci/docker/debian12-aarch64/Dockerfile new file mode 100644 index 00000000000..2344ce53a9d --- /dev/null +++ b/.gitlab/ci/docker/debian12-aarch64/Dockerfile @@ -0,0 +1,34 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE=arm64v8/debian:12 + +FROM ${BASE_IMAGE} AS cuda-keyring +ADD https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/sbsa/cuda-keyring_1.1-1_all.deb /root/ +RUN --mount=type=tmpfs,target=/var/log \ + apt-get update \ + && apt-get -y install ca-certificates \ + && dpkg -i /root/cuda-keyring_1.1-1_all.deb \ + && rm /root/cuda-keyring_1.1-1_all.deb + +FROM cuda-keyring AS apt-cache +# Populate APT cache w/ the fresh metadata and prefetch packages. +# Use an empty `docker-clean` file to "hide" the image-provided +# file to disallow removing packages after `apt-get` operations. +RUN --mount=type=tmpfs,target=/var/log \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + apt-get update \ + && apt-get --download-only -y install $(grep -h '^[^#]\+$' /root/*.lst) + +FROM cuda-keyring +LABEL maintainer="Brad King " + +RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + --mount=type=bind,source=dpkg-exclude,target=/etc/dpkg/dpkg.cfg.d/exclude \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ + --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ + --mount=type=tmpfs,target=/var/log \ + --mount=type=tmpfs,target=/tmp \ + sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/debian12-aarch64/deps_packages.lst b/.gitlab/ci/docker/debian12-aarch64/deps_packages.lst new file mode 100644 index 00000000000..2328194a882 --- /dev/null +++ b/.gitlab/ci/docker/debian12-aarch64/deps_packages.lst @@ -0,0 +1,99 @@ +# Install build requirements. +libssl-dev + +# Install development tools. +g++ +curl +git + +# Install optional external build dependencies. +libarchive-dev +libbz2-dev +libcurl4-gnutls-dev +libexpat1-dev +libjsoncpp-dev +liblzma-dev +libncurses-dev +librhash-dev +libuv1-dev +libzstd-dev +zlib1g-dev + +# Install iwyu runtime deps. +clang-15 +libncurses6 + +# Tools needed for the test suite. +jq + +# Packages needed to test CTest. +bzr +cvs +subversion +mercurial + +# Packages needed to test find modules. +alsa-utils +aspell +aspell-en +doxygen graphviz +freeglut3-dev +gnutls-dev +libarchive-dev +libaspell-dev +libblas-dev +libboost-dev +libboost-filesystem-dev +libboost-program-options-dev +libboost-python-dev +libboost-thread-dev +libbz2-dev +libcups2-dev +libcurl4-gnutls-dev +libdevil-dev +libfontconfig1-dev +libfreetype6-dev +libgdal-dev +libgif-dev +libgl1-mesa-dev +libglew-dev +libgmock-dev +libgrpc++-dev libgrpc-dev +libgsl-dev +libgtest-dev +libgtk2.0-dev +libhdf5-dev +libhdf5-mpich-dev +libhdf5-openmpi-dev +libicu-dev +libinput-dev +libjpeg-dev +libjsoncpp-dev +liblapack-dev +liblzma-dev +libmagick++-dev +libnvpl-blas-dev +libnvpl-common-dev +libnvpl-lapack-dev +libopenal-dev +libopenmpi-dev openmpi-bin +libosp-dev +libpng-dev +libpq-dev postgresql-server-dev-15 +libprotobuf-dev libprotobuf-c-dev libprotoc-dev protobuf-compiler protobuf-compiler-grpc +libsdl1.2-dev +libsqlite3-dev +libtiff-dev +libuv1-dev +libwxgtk3.2-dev +libx11-dev +libxalan-c-dev +libxerces-c-dev +libxml2-dev libxml2-utils +libxslt-dev xsltproc +openjdk-17-jdk +python3 python3-dev python3-numpy pypy3 pypy3-dev python3-venv +qtbase5-dev qtbase5-dev-tools +ruby ruby-dev +swig +unixodbc-dev diff --git a/.gitlab/ci/docker/debian10/docker-clean b/.gitlab/ci/docker/debian12-aarch64/docker-clean similarity index 100% rename from .gitlab/ci/docker/debian10/docker-clean rename to .gitlab/ci/docker/debian12-aarch64/docker-clean diff --git a/.gitlab/ci/docker/debian10/dpkg-exclude b/.gitlab/ci/docker/debian12-aarch64/dpkg-exclude similarity index 100% rename from .gitlab/ci/docker/debian10/dpkg-exclude rename to .gitlab/ci/docker/debian12-aarch64/dpkg-exclude diff --git a/.gitlab/ci/docker/debian10-aarch64/install_deps.sh b/.gitlab/ci/docker/debian12-aarch64/install_deps.sh similarity index 100% rename from .gitlab/ci/docker/debian10-aarch64/install_deps.sh rename to .gitlab/ci/docker/debian12-aarch64/install_deps.sh diff --git a/.gitlab/ci/docker/debian12-x86_64/Dockerfile b/.gitlab/ci/docker/debian12-x86_64/Dockerfile new file mode 100644 index 00000000000..3a9a6a14abf --- /dev/null +++ b/.gitlab/ci/docker/debian12-x86_64/Dockerfile @@ -0,0 +1,64 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE=debian:12 + +FROM ${BASE_IMAGE} AS apt-cache +# Populate APT cache w/ the fresh metadata and prefetch packages. +# Use an empty `docker-clean` file to "hide" the image-provided +# file to disallow removing packages after `apt-get` operations. +RUN --mount=type=tmpfs,target=/var/log \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \ + --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \ + apt-get update \ + && apt-get --download-only -y install $(grep -h '^[^#]\+$' /root/*.lst) + + +FROM ${BASE_IMAGE} AS iwyu-build +LABEL maintainer="Ben Boeckel " + +RUN --mount=type=bind,source=install_iwyu.sh,target=/root/install_iwyu.sh \ + --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ + --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ + --mount=type=tmpfs,target=/var/log \ + --mount=type=tmpfs,target=/tmp \ + sh /root/install_iwyu.sh + + +FROM ${BASE_IMAGE} AS rvm-build +LABEL maintainer="Ben Boeckel " + +RUN --mount=type=bind,source=install_rvm.sh,target=/root/install_rvm.sh \ + --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ + --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ + --mount=type=tmpfs,target=/var/log \ + --mount=type=tmpfs,target=/tmp \ + sh /root/install_rvm.sh + + +FROM ${BASE_IMAGE} +LABEL maintainer="Ben Boeckel " + +ENV RBENV_ROOT=/opt/rbenv + +RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + --mount=type=bind,source=dpkg-exclude,target=/etc/dpkg/dpkg.cfg.d/exclude \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ + --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ + --mount=type=tmpfs,target=/var/log \ + --mount=type=tmpfs,target=/tmp \ + sh /root/install_deps.sh + +RUN --mount=type=bind,from=iwyu-build,source=/root,target=/root \ + tar -C / -xf /root/iwyu.tar \ + && ln -s /usr/lib/llvm-15/bin/include-what-you-use /usr/bin/include-what-you-use-15 + +RUN --mount=type=bind,from=rvm-build,source=/root,target=/root \ + tar -C /usr/local -xf /root/rvm.tar diff --git a/.gitlab/ci/docker/debian12-x86_64/deps_packages.lst b/.gitlab/ci/docker/debian12-x86_64/deps_packages.lst new file mode 100644 index 00000000000..e6f1188537c --- /dev/null +++ b/.gitlab/ci/docker/debian12-x86_64/deps_packages.lst @@ -0,0 +1,114 @@ +# Install build requirements. +libssl-dev + +# Install development tools. +g++ +curl +git + +# Install optional external build dependencies. +libarchive-dev +libbz2-dev +libcurl4-gnutls-dev +libexpat1-dev +libjsoncpp-dev +liblzma-dev +libncurses-dev +librhash-dev +libuv1-dev +libzstd-dev +zlib1g-dev + +# Install iwyu runtime deps. +clang-15 +libncurses6 + +# Tools needed for the test suite. +jq + +# Packages needed to test CTest. +bzr +cvs +subversion +mercurial + +# Install ASM_NASM language toolchain. +nasm + +# Install HIP language toolchain. +hipcc + +# Install swift runtime deps. +libncurses5 + +# Install IAR compiler package dependencies. +libusb-1.0-0 +udev +sudo + +# Packages needed to test find modules. +alsa-utils +aspell +aspell-en +doxygen graphviz +freeglut3-dev +gnutls-dev +libarchive-dev +libaspell-dev +libblas-dev +libboost-dev +libboost-filesystem-dev +libboost-program-options-dev +libboost-python-dev +libboost-thread-dev +libbz2-dev +libcups2-dev +libcurl4-gnutls-dev +libdevil-dev +libfontconfig1-dev +libfreetype6-dev +libgdal-dev +libgif-dev +libgl1-mesa-dev +libglew-dev +libgmock-dev +libgrpc++-dev libgrpc-dev +libgsl-dev +libgtest-dev +libgtk2.0-dev +libhdf5-dev +libhdf5-mpich-dev +libhdf5-openmpi-dev +libicu-dev +libinput-dev +libjpeg-dev +libjsoncpp-dev +liblapack-dev +liblzma-dev +libmagick++-dev +libopenal-dev +libopenmpi-dev openmpi-bin +libosp-dev +libpng-dev +libpq-dev postgresql-server-dev-15 +libprotobuf-dev libprotobuf-c-dev libprotoc-dev protobuf-compiler protobuf-compiler-grpc +libsdl1.2-dev +libsqlite3-dev +libtiff-dev +libuv1-dev +libwxgtk3.2-dev +libx11-dev +libxalan-c-dev +libxerces-c-dev +libxml2-dev libxml2-utils +libxslt-dev xsltproc +openjdk-17-jdk +python3 python3-dev python3-numpy pypy3 pypy3-dev python3-venv +qtbase5-dev qtbase5-dev-tools +rbenv ruby-build +ruby ruby-dev +swig +unixodbc-dev + +# Packages needed to test ironpython. +libmono-system-windows-forms4.0-cil diff --git a/.gitlab/ci/docker/hip5.5/docker-clean b/.gitlab/ci/docker/debian12-x86_64/docker-clean similarity index 100% rename from .gitlab/ci/docker/hip5.5/docker-clean rename to .gitlab/ci/docker/debian12-x86_64/docker-clean diff --git a/.gitlab/ci/docker/hip5.5/dpkg-exclude b/.gitlab/ci/docker/debian12-x86_64/dpkg-exclude similarity index 100% rename from .gitlab/ci/docker/hip5.5/dpkg-exclude rename to .gitlab/ci/docker/debian12-x86_64/dpkg-exclude diff --git a/.gitlab/ci/docker/debian12-x86_64/install_deps.sh b/.gitlab/ci/docker/debian12-x86_64/install_deps.sh new file mode 100755 index 00000000000..1493f1e5436 --- /dev/null +++ b/.gitlab/ci/docker/debian12-x86_64/install_deps.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +apt-get install -y $(grep '^[^#]\+$' /root/deps_packages.lst) + +curl -L -O https://github.com/IronLanguages/ironpython3/releases/download/v3.4.0/ironpython_3.4.0.deb +echo '7dcd10b7a0ec0342bd7e20eebb597a96bb15267eb797d59358a3b1cfaa3e1adc ironpython_3.4.0.deb' > ironpython.sha256sum +sha256sum --check ironpython.sha256sum +dpkg -i ironpython_3.4.0.deb +rm ironpython_3.4.0.deb ironpython.sha256sum + +# Ruby rbenv +rbenv install 3.1.2 +rbenv global 3.1.2 + +# Perforce +curl -L https://www.perforce.com/downloads/perforce/r21.2/bin.linux26x86_64/helix-core-server.tgz -o - \ + | tar -C /usr/local/bin -xvzf - -- p4 p4d diff --git a/.gitlab/ci/docker/debian12-x86_64/install_iwyu.sh b/.gitlab/ci/docker/debian12-x86_64/install_iwyu.sh new file mode 100755 index 00000000000..bbeceb8f131 --- /dev/null +++ b/.gitlab/ci/docker/debian12-x86_64/install_iwyu.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +set -e + +# Install development tools. +apt-get install -y $(grep '^[^#]\+$' /root/iwyu_packages.lst) + +cd /root +git clone "https://github.com/include-what-you-use/include-what-you-use.git" +cd include-what-you-use +readonly llvm_version="$( clang-15 --version | head -n1 | cut -d' ' -f4 | cut -d. -f-1 )" +git checkout "clang_$llvm_version" +mkdir build +cd build + +cmake -GNinja \ + -DCMAKE_BUILD_TYPE=Release \ + "-DCMAKE_INSTALL_PREFIX=/usr/lib/llvm-$llvm_version" \ + .. +ninja +DESTDIR=/root/iwyu-destdir ninja install +tar -C /root/iwyu-destdir -cf /root/iwyu.tar . diff --git a/.gitlab/ci/docker/debian12-x86_64/install_rvm.sh b/.gitlab/ci/docker/debian12-x86_64/install_rvm.sh new file mode 100755 index 00000000000..1ad42c4580f --- /dev/null +++ b/.gitlab/ci/docker/debian12-x86_64/install_rvm.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +apt-get install -y $(grep '^[^#]\+$' /root/rvm_packages.lst) + +gpg2 --keyserver hkps://keyserver.ubuntu.com \ + --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \ + 7D2BAF1CF37B13E2069D6956105BD0E739499BDB + +curl -sSL https://get.rvm.io | bash -s stable + +# keep version in sync with `env_debian*_ninja.sh` +/usr/local/rvm/bin/rvm install ruby-3.2.2 + +for p in archives examples gem-cache log src; do + touch /usr/local/rvm/${p}/.tar_exclude +done + +tar -C /usr/local --exclude-tag-under=.tar_exclude -cf /root/rvm.tar rvm diff --git a/.gitlab/ci/docker/debian12-x86_64/iwyu_packages.lst b/.gitlab/ci/docker/debian12-x86_64/iwyu_packages.lst new file mode 100644 index 00000000000..2dbddba9f84 --- /dev/null +++ b/.gitlab/ci/docker/debian12-x86_64/iwyu_packages.lst @@ -0,0 +1,9 @@ +# Install development tools. +clang-15 +libclang-15-dev +llvm-15-dev +libz-dev +g++ +cmake +ninja-build +git diff --git a/.gitlab/ci/docker/debian10/rvm_packages.lst b/.gitlab/ci/docker/debian12-x86_64/rvm_packages.lst similarity index 100% rename from .gitlab/ci/docker/debian10/rvm_packages.lst rename to .gitlab/ci/docker/debian12-x86_64/rvm_packages.lst diff --git a/.gitlab/ci/docker/fedora38/Dockerfile b/.gitlab/ci/docker/fedora38/Dockerfile deleted file mode 100644 index 4918693531a..00000000000 --- a/.gitlab/ci/docker/fedora38/Dockerfile +++ /dev/null @@ -1,58 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG BASE_IMAGE=fedora:38 - -FROM ${BASE_IMAGE} AS dnf-cache -# Populate DNF cache w/ the fresh metadata and prefetch packages. -RUN --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \ - --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - dnf install \ - --setopt=install_weak_deps=False \ - --setopt=fastestmirror=True \ - --setopt=max_parallel_downloads=10 \ - --downloadonly \ - -y \ - $(grep -h '^[^#]\+$' /root/*.lst) - - -FROM ${BASE_IMAGE} AS rvm-build -LABEL maintainer="Ben Boeckel " - -RUN --mount=type=bind,source=install_rvm.sh,target=/root/install_rvm.sh \ - --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \ - --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_rvm.sh - - -FROM ${BASE_IMAGE} AS iwyu-build -LABEL maintainer="Kyle Edwards " - -RUN --mount=type=bind,source=install_iwyu.sh,target=/root/install_iwyu.sh \ - --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \ - --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_iwyu.sh - - -FROM ${BASE_IMAGE} -LABEL maintainer="Ben Boeckel " - -RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ - --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \ - --mount=type=cache,target=/var/cache/pip \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_deps.sh - -RUN --mount=type=bind,from=rvm-build,source=/root,target=/root \ - tar -C /usr/local -xf /root/rvm.tar - -RUN --mount=type=bind,from=iwyu-build,source=/root,target=/root \ - tar -C / -xf /root/iwyu.tar diff --git a/.gitlab/ci/docker/fedora38/deps_packages.lst b/.gitlab/ci/docker/fedora38/deps_packages.lst deleted file mode 100644 index c7c13855d4e..00000000000 --- a/.gitlab/ci/docker/fedora38/deps_packages.lst +++ /dev/null @@ -1,115 +0,0 @@ -# Install build requirements. -ncurses-devel -openssl-devel -qt5-qtbase-devel -qt6-qtbase-devel - -# Install development tools. -clang -clang-tools-extra -clang-tools-extra-devel -compiler-rt -flang -flang-devel -gcc-c++ -git-core -make - -# Install optional external build dependencies. -bzip2-devel -expat-devel -jsoncpp-devel -libarchive-devel -libcurl-devel -libuv-devel -libuv-devel -libzstd-devel -rhash-devel -xz-devel -zlib-devel - -# Install documentation tools. -python3-sphinx -texinfo -qt5-qttools-devel -qt6-qttools-devel - -# Install lint tools. -clang-analyzer -codespell - -# Tools needed for the test suite. -findutils -file -jq -which - -# Packages needed to test CTest. -breezy -subversion -mercurial - -# Packages needed to test CPack. -rpm-build - -# Packages needed to test find modules. -alsa-lib-devel -blas-devel -boost-devel boost-python3-devel -bzip2-devel -cups-devel -DevIL-devel -doxygen -expat-devel -fontconfig-devel -freeglut-devel -freetype-devel -gdal-devel -gettext -giflib-devel -glew-devel -gmock -gnutls-devel -grpc-devel grpc-plugins -gsl-devel -gtest-devel -gtk2-devel -hdf5-devel -hdf5-mpich-devel -hdf5-openmpi-devel -ImageMagick-c++-devel -java-11-openjdk-devel -jsoncpp-devel -lapack-devel -libarchive-devel -libcurl-devel -libicu-devel -libinput-devel systemd-devel -libjpeg-turbo-devel -libpng-devel -opensp-devel -postgresql-server-devel -libtiff-devel -libuv-devel -libxml2-devel -libxslt-devel -mpich-devel -openal-soft-devel -openmpi-devel -patch -perl -protobuf-devel protobuf-c-devel protobuf-lite-devel -pypy2 pypy2-devel -pypy3 pypy3-devel -python2 python2-devel -python3 python3-devel python3-numpy -python3-jsmin python3-jsonschema -ruby rubygems ruby-devel -SDL-devel -sqlite-devel -swig -unixODBC-devel -wxGTK-devel -xalan-c-devel -xerces-c-devel -xz-devel diff --git a/.gitlab/ci/docker/fedora38/install_deps.sh b/.gitlab/ci/docker/fedora38/install_deps.sh deleted file mode 100755 index cd2701ebee5..00000000000 --- a/.gitlab/ci/docker/fedora38/install_deps.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -set -e - -dnf install \ - --setopt=install_weak_deps=False \ - --setopt=fastestmirror=True \ - --setopt=max_parallel_downloads=10 \ - -y \ - $(grep '^[^#]\+$' /root/deps_packages.lst) - -# Fedora no longer packages python2 numpy. -curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o - | python2 -pip2.7 install --disable-pip-version-check --no-input --no-compile --cache-dir /var/cache/pip numpy - -# Remove demos and Python2 tests -for p in Demo test; do - rm -rf /usr/lib64/python2.7/${p} -done - -# Remove tests for numpy -for v in 2.7 3.11; do - find /usr/lib64/python${v}/site-packages/numpy -type d -a -name tests -exec rm -rf {} + -done - -# Remove some other packages tests -find /usr/lib64/python3.11/site-packages/breezy -type d -a -name tests -exec rm -rf {} + - -# Perforce -curl -L https://www.perforce.com/downloads/perforce/r21.2/bin.linux26x86_64/helix-core-server.tgz -o - \ - | tar -C /usr/local/bin -xvzf - -- p4 p4d diff --git a/.gitlab/ci/docker/fedora38/install_iwyu.sh b/.gitlab/ci/docker/fedora38/install_iwyu.sh deleted file mode 100755 index 684e3550584..00000000000 --- a/.gitlab/ci/docker/fedora38/install_iwyu.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -set -e - -# Install development tools. -dnf install \ - --setopt=install_weak_deps=False \ - --setopt=fastestmirror=True \ - --setopt=max_parallel_downloads=10 \ - -y \ - $(grep '^[^#]\+$' /root/iwyu_packages.lst) - -cd /root -git clone "https://github.com/include-what-you-use/include-what-you-use.git" -cd include-what-you-use -readonly llvm_full_version="$( clang --version | head -n1 | cut -d' ' -f3 )" -readonly llvm_version="$( echo "$llvm_full_version" | cut -d. -f-1 )" -git checkout "clang_$llvm_version" -git apply < ~/.gemrc + +# Ruby rbenv +export RUBY_CONFIGURE_OPTS=--disable-install-doc +export RUBY_BUILD_CURL_OPTS=-C- +rbenv install 3.4.3 -k -s -v + +cat </tmp/exclude.lst +*LICENSE* +*/doc/* +*/man/* +*.md +BSDL +CONTRIBUTING.* +COPYING +LEGAL +PSFL +README.rdoc +History.rdoc +gem_make.out +test-unit-*/test +rss-*/test +EOF +tar -cf /root/rbenv.tar --exclude-from=/tmp/exclude.lst ${RBENV_ROOT} diff --git a/.gitlab/ci/docker/fedora42/build_rust.sh b/.gitlab/ci/docker/fedora42/build_rust.sh new file mode 100755 index 00000000000..f20949b9801 --- /dev/null +++ b/.gitlab/ci/docker/fedora42/build_rust.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +typos_version=1.29.4 +cargo install --root /usr/local --version "$typos_version" typos-cli + +strip /usr/local/bin/typos + +tar -C /usr/local -cf /root/rust.tar bin/typos diff --git a/.gitlab/ci/docker/fedora42/build_rvm.sh b/.gitlab/ci/docker/fedora42/build_rvm.sh new file mode 100755 index 00000000000..22c4333e4c7 --- /dev/null +++ b/.gitlab/ci/docker/fedora42/build_rvm.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +set -e + +echo "gem: --no-document" > ~/.gemrc + +gpg2 --keyserver hkps://keyserver.ubuntu.com \ + --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \ + 7D2BAF1CF37B13E2069D6956105BD0E739499BDB + +curl -sSL https://get.rvm.io | bash -s stable --ignore-dotfiles + +export rvm_silence_banner=1 + +# keep version in sync with `env_fedora*_makefiles.cmake` +/usr/local/rvm/bin/rvm install ruby-3.3.8 --no-docs --disable-binary + +for p in archives docs examples gem-cache log src; do + touch /usr/local/rvm/${p}/.tar_exclude +done + +cat </tmp/exclude.lst +*LICENSE* +*/doc/* +*/man/* +*.md +BSDL +CONTRIBUTING.* +COPYING +LEGAL +PSFL +README.rdoc +History.rdoc +gem_make.out +test-unit-*/test +rss-*/test +EOF +tar -C /usr/local \ + --exclude-tag-under=.tar_exclude \ + --exclude-from=/tmp/exclude.lst \ + -cf /root/rvm.tar rvm diff --git a/.gitlab/ci/docker/fedora42/deps_packages.lst b/.gitlab/ci/docker/fedora42/deps_packages.lst new file mode 100644 index 00000000000..56825142f59 --- /dev/null +++ b/.gitlab/ci/docker/fedora42/deps_packages.lst @@ -0,0 +1,141 @@ +# Install build requirements. +ncurses-devel +openssl-devel +qt5-qtbase-devel +qt6-qtbase-devel + +# Install development tools. +clang +clang-devel +clang-tools-extra +clang-tools-extra-devel +compiler-rt +flang +gcc-c++ +git-core +lfortran +llvm-devel +make + +# Install optional external build dependencies. +bzip2-devel +expat-devel +jsoncpp-devel +libarchive-devel +libcurl-devel +libuv-devel +libzstd-devel +rhash-devel +xz-devel +zlib-devel + +# Install documentation tools. +python3-sphinx +python3-sphinxcontrib-qthelp +qt5-qttools-devel +qt6-qttools-devel +texinfo + +# Install lint tools. +clang-analyzer +codespell + +# Tools needed for the test suite. +file +findutils +jq +which + +# Install ASM_NASM language toolchain. +nasm + +# Packages needed to test CTest. +breezy +mercurial +subversion + +# Packages needed to test CPack. +rpm-build + +# Packages needed to test find modules. +alsa-lib-devel +aspell +aspell-devel +aspell-en +blas-devel +boost-devel +boost-python3-devel +bzip2-devel +cups-devel +DevIL-devel +doxygen +expat-devel +fontconfig-devel +freeglut-devel +freetype-devel +gdal-devel +gettext +giflib-devel +glew-devel +gmock +gnutls-devel +grpc-devel +grpc-plugins +gsl-devel +gtest-devel +gtk2-devel +hdf5-devel +hdf5-mpich-devel +hdf5-openmpi-devel +ImageMagick-c++-devel +jasper-devel +java-21-openjdk-devel +jsoncpp-devel +lapack-devel +libarchive-devel +libcurl-devel +libicu-devel +libinput-devel +libjpeg-turbo-devel +libomp-devel +libpng-devel +libtiff-devel +libuv-devel +libxml2-devel +libxslt-devel +mpich-devel +openal-soft-devel +openmpi-devel +opensp-devel +patch +perl +postgresql-server-devel +protobuf-c-devel +protobuf-devel +protobuf-lite-devel +pypy2 +pypy2-devel +pypy3 +pypy3-devel +python3 +python3-devel +python3-jsmin +python3-jsonschema +python3-numpy +rbenv +ruby +ruby-build-rbenv +ruby-devel +rubygems +SDL-devel +sqlite-devel +swig +systemd-devel +unixODBC-devel +wxGTK-devel +xalan-c-devel +xerces-c-devel +xz-devel + +# Packages needed to test third-party binaries. +ncurses-compat-libs diff --git a/.gitlab/ci/docker/fedora42/dnf.conf b/.gitlab/ci/docker/fedora42/dnf.conf new file mode 100644 index 00000000000..1189746973a --- /dev/null +++ b/.gitlab/ci/docker/fedora42/dnf.conf @@ -0,0 +1,32 @@ +[main] +autocheck_running_kernel=0 +debuglevel=2 +diskspacecheck=0 +fastestmirror=1 +gpgcheck=1 +installonly_limit=5 +install_weak_deps=0 +keepcache=1 +log_rotate=0 +max_parallel_downloads=10 +metadata_expire=90m +multilib_policy=best +plugins=1 +tsflags=nodocs + +# Enable color for all output +color=always + +# Default color options according to yum.conf(5) +color_list_installed_older=bold,black +color_list_installed_newer=bold,yellow +color_list_installed_reinstall=normal +color_list_installed_extra=bold,red +color_list_available_upgrade=bold,blue +color_list_available_downgrade=dim,yellow +color_list_available_install=normal +color_list_available_reinstall =bold,underline,green +color_search_match=bold,magenta +color_update_installed=green +color_update_local=cyan +color_update_remote=yellow diff --git a/.gitlab/ci/docker/fedora42/install_deps.sh b/.gitlab/ci/docker/fedora42/install_deps.sh new file mode 100755 index 00000000000..d9c9b5ca5ab --- /dev/null +++ b/.gitlab/ci/docker/fedora42/install_deps.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +dnf install -y $(grep '^[^#]\+$' /root/deps_packages.lst) + +# Remove tests for Python packages +for v in 3.13; do + find /usr/lib64/python${v}/site-packages -type d -a -name tests -exec rm -rf {} + +done diff --git a/.gitlab/ci/docker/fedora42/iwyu_packages.lst b/.gitlab/ci/docker/fedora42/iwyu_packages.lst new file mode 100644 index 00000000000..a98183679cc --- /dev/null +++ b/.gitlab/ci/docker/fedora42/iwyu_packages.lst @@ -0,0 +1,7 @@ +clang-devel +cmake +gcc-c++ +git +llvm-devel +ninja-build +zlib-devel diff --git a/.gitlab/ci/docker/fedora42/rbenv_packages.lst b/.gitlab/ci/docker/fedora42/rbenv_packages.lst new file mode 100644 index 00000000000..d16afff8510 --- /dev/null +++ b/.gitlab/ci/docker/fedora42/rbenv_packages.lst @@ -0,0 +1,18 @@ +rbenv +ruby-build-rbenv + +# Packages needed for `rbenv` +# https://github.com/rbenv/ruby-build/wiki#fedora +autoconf +bzip2 +gcc +gdbm-devel +libffi-devel +libyaml-devel +make +ncurses-devel +openssl-devel +patch +readline-devel +rust +zlib-devel diff --git a/.gitlab/ci/docker/fedora42/rust_packages.lst b/.gitlab/ci/docker/fedora42/rust_packages.lst new file mode 100644 index 00000000000..1af6be6048c --- /dev/null +++ b/.gitlab/ci/docker/fedora42/rust_packages.lst @@ -0,0 +1 @@ +rust-cargo-devel diff --git a/.gitlab/ci/docker/fedora38/rvm_packages.lst b/.gitlab/ci/docker/fedora42/rvm_packages.lst similarity index 93% rename from .gitlab/ci/docker/fedora38/rvm_packages.lst rename to .gitlab/ci/docker/fedora42/rvm_packages.lst index 1dc852e1473..3ab692c47f0 100644 --- a/.gitlab/ci/docker/fedora38/rvm_packages.lst +++ b/.gitlab/ci/docker/fedora42/rvm_packages.lst @@ -3,8 +3,10 @@ automake bison bzip2 findutils +gawk gcc-c++ glibc-devel +gnupg2 libffi-devel libtool libyaml-devel diff --git a/.gitlab/ci/docker/gcc_cxx_modules/Dockerfile b/.gitlab/ci/docker/gcc_cxx_modules/Dockerfile index e0af0b9f0fb..7f6e297ef6e 100644 --- a/.gitlab/ci/docker/gcc_cxx_modules/Dockerfile +++ b/.gitlab/ci/docker/gcc_cxx_modules/Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:36 +FROM fedora:41 MAINTAINER Ben Boeckel # Install build dependencies for packages. diff --git a/.gitlab/ci/docker/gcc_cxx_modules/install_gcc.sh b/.gitlab/ci/docker/gcc_cxx_modules/install_gcc.sh index 20ea35f6f78..32cb9ec4c6e 100755 --- a/.gitlab/ci/docker/gcc_cxx_modules/install_gcc.sh +++ b/.gitlab/ci/docker/gcc_cxx_modules/install_gcc.sh @@ -2,8 +2,8 @@ set -e -readonly revision="p1689r5-cmake-ci-20220614" # 3075e510e3d29583f8886b95aff044c0474c84a5 -readonly tarball="https://github.com/mathstuf/gcc/archive/$revision.tar.gz" +readonly revision="10e702789eeabcc88451e34c2a5c7dccb96190a5" # master as of 21 Nov 2024 +readonly tarball="git://gcc.gnu.org/git/gcc.git" readonly workdir="$HOME/gcc" readonly srcdir="$workdir/gcc" @@ -12,15 +12,14 @@ readonly njobs="$( nproc )" mkdir -p "$workdir" cd "$workdir" -curl -L "$tarball" > "gcc-$revision.tar.gz" -tar xf "gcc-$revision.tar.gz" -mv "gcc-$revision" "$srcdir" +git clone "$tarball" "$srcdir" +git -C "$srcdir" checkout "$revision" mkdir -p "$builddir" cd "$builddir" "$srcdir/configure" \ --disable-multilib \ --enable-languages=c,c++ \ - --prefix="/opt/gcc-p1689" + --prefix="/opt/gcc-importstd" make "-j$njobs" make "-j$njobs" install-strip rm -rf "$workdir" diff --git a/.gitlab/ci/docker/hip5.5/Dockerfile b/.gitlab/ci/docker/hip5.5/Dockerfile deleted file mode 100644 index 2deb3c69de9..00000000000 --- a/.gitlab/ci/docker/hip5.5/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG BASE_IMAGE=rocm/dev-ubuntu-22.04:5.5 - -FROM ${BASE_IMAGE} AS apt-cache -# Populate APT cache w/ the fresh metadata and prefetch packages. -# Use an empty `docker-clean` file to "hide" the image-provided -# file to disallow removing packages after `apt-get` operations. -RUN --mount=type=tmpfs,target=/var/log \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - apt-get update \ - && apt-get --download-only -y install $(grep -h '^[^#]\+$' /root/*.lst) - -FROM ${BASE_IMAGE} -MAINTAINER Brad King - -ENV PATH="/opt/rocm/bin:$PATH" - -RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ - --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ - --mount=type=bind,source=dpkg-exclude,target=/etc/dpkg/dpkg.cfg.d/exclude \ - --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ - --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ - --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ - --mount=type=tmpfs,target=/var/log \ - --mount=type=tmpfs,target=/tmp \ - sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/hip5.5/deps_packages.lst b/.gitlab/ci/docker/hip5.5/deps_packages.lst deleted file mode 100644 index 98479250e1d..00000000000 --- a/.gitlab/ci/docker/hip5.5/deps_packages.lst +++ /dev/null @@ -1,4 +0,0 @@ -# Install development tools. -g++ -curl -git diff --git a/.gitlab/ci/docker/hip6.3/Dockerfile b/.gitlab/ci/docker/hip6.3/Dockerfile new file mode 100644 index 00000000000..a9f830398f4 --- /dev/null +++ b/.gitlab/ci/docker/hip6.3/Dockerfile @@ -0,0 +1,37 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE=rocm/dev-ubuntu-24.04:6.3.2 + +FROM ${BASE_IMAGE} AS cuda-keyring +ADD https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb /root/ +RUN --mount=type=tmpfs,target=/var/log \ + dpkg -i /root/cuda-keyring_1.1-1_all.deb \ + && rm /root/cuda-keyring_1.1-1_all.deb + +FROM cuda-keyring AS apt-cache +# Populate APT cache w/ the fresh metadata and prefetch packages. +# Use an empty `docker-clean` file to "hide" the image-provided +# file to disallow removing packages after `apt-get` operations. +RUN --mount=type=tmpfs,target=/var/log \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + apt-get update \ + && apt-get --download-only -y install $(grep -h '^[^#]\+$' /root/*.lst) + +FROM cuda-keyring +MAINTAINER Brad King + +ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility +ENV NVIDIA_REQUIRE_CUDA=cuda>=12.6 +ENV NVIDIA_VISIBLE_DEVICES=all +ENV PATH="/opt/rocm/bin:$PATH" + +RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \ + --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \ + --mount=type=bind,source=dpkg-exclude,target=/etc/dpkg/dpkg.cfg.d/exclude \ + --mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \ + --mount=type=cache,from=apt-cache,source=/var/lib/apt/lists,target=/var/lib/apt/lists \ + --mount=type=cache,from=apt-cache,source=/var/cache/apt,target=/var/cache/apt,sharing=private \ + --mount=type=tmpfs,target=/var/log \ + --mount=type=tmpfs,target=/tmp \ + sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/hip6.3/deps_packages.lst b/.gitlab/ci/docker/hip6.3/deps_packages.lst new file mode 100644 index 00000000000..2ecf81eaab1 --- /dev/null +++ b/.gitlab/ci/docker/hip6.3/deps_packages.lst @@ -0,0 +1,23 @@ +# Install development tools. +g++ +curl +git + +# NVIDIA CUDA Compiler +cuda-keyring +cuda-nvcc-12-6 +cuda-profiler-api-12-6 + +# NVIDIA CUDA Toolkit +# These are not needed for HIP, but having them in +# the environment allows us to run CUDA tests too. +cuda-nvrtc-dev-12-6 +cuda-nvtx-12-6 +cuda-opencl-dev-12-6 +libcublas-dev-12-6 +libcufft-dev-12-6 +libcurand-dev-12-6 +libcusolver-dev-12-6 +libcusparse-dev-12-6 +libnpp-dev-12-6 +libnvjitlink-dev-12-6 diff --git a/Tests/CPackWiXGenerator/file with spaces.h b/.gitlab/ci/docker/hip6.3/docker-clean similarity index 100% rename from Tests/CPackWiXGenerator/file with spaces.h rename to .gitlab/ci/docker/hip6.3/docker-clean diff --git a/.gitlab/ci/docker/hip6.3/dpkg-exclude b/.gitlab/ci/docker/hip6.3/dpkg-exclude new file mode 100644 index 00000000000..60b656549cc --- /dev/null +++ b/.gitlab/ci/docker/hip6.3/dpkg-exclude @@ -0,0 +1,21 @@ +# Drop all man pages +path-exclude=/usr/share/man/* + +# Drop all info pages +path-exclude=/usr/share/info/* + +# Drop all README files except from the some packages +path-exclude=/usr/**/*README* +path-include=/usr/share/devscripts/templates/README.mk-build-deps +path-include=/usr/share/equivs/template/debian/README.Debian.in + +# Drop all translations +path-exclude=/usr/share/locale/*/LC_MESSAGES/*.mo + +# Drop all documentation ... +path-exclude=/usr/share/doc/* +path-exclude=/usr/share/doc-base/* +path-exclude=/usr/share/gtk-doc/* + +# Per package excludes +path-exclude=/usr/share/gnupg/help.*.txt diff --git a/.gitlab/ci/docker/hip5.5/install_deps.sh b/.gitlab/ci/docker/hip6.3/install_deps.sh similarity index 100% rename from .gitlab/ci/docker/hip5.5/install_deps.sh rename to .gitlab/ci/docker/hip6.3/install_deps.sh diff --git a/.gitlab/ci/docker/ninja/centos7-aarch64.bash b/.gitlab/ci/docker/ninja/centos7-aarch64.bash deleted file mode 100755 index 4052f29a423..00000000000 --- a/.gitlab/ci/docker/ninja/centos7-aarch64.bash +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -cleanup() { - docker container rm -fv "$build_container" >/dev/null 2>&1 || true - docker image rm -f "$build_image" >/dev/null 2>&1 || true -} - -readonly suffix="-$(date -u +%Y-%m-%d)-${RANDOM}" -readonly build_container="ninja-build-linux-aarch64$suffix" -readonly build_image="ninja:build-linux-aarch64$suffix" -readonly git_tag="${1-v1.11.0}" - -trap "cleanup" EXIT INT TERM - -docker image build --build-arg GIT_TAG="$git_tag" --tag="$build_image" "${BASH_SOURCE%/*}/centos7-aarch64" -docker container create --name "$build_container" "$build_image" -docker cp "$build_container:/ninja/ninja" "ninja" diff --git a/.gitlab/ci/docker/ninja/centos7-aarch64/Dockerfile b/.gitlab/ci/docker/ninja/centos7-aarch64/Dockerfile deleted file mode 100644 index 3fb13b0455c..00000000000 --- a/.gitlab/ci/docker/ninja/centos7-aarch64/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM kitware/cmake:build-linux-aarch64-base-2020-12-21 -MAINTAINER Brad King - -ARG GIT_TAG=v1.11.0 - -COPY build_ninja.sh /root/build_ninja.sh -RUN scl enable devtoolset-7 -- sh /root/build_ninja.sh $GIT_TAG diff --git a/.gitlab/ci/docker/ninja/centos7-aarch64/build_ninja.sh b/.gitlab/ci/docker/ninja/centos7-aarch64/build_ninja.sh deleted file mode 100755 index 91fb2d4896e..00000000000 --- a/.gitlab/ci/docker/ninja/centos7-aarch64/build_ninja.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -e - -git clone https://github.com/ninja-build/ninja.git -cd ninja -git checkout "${1-v1.11.0}" -./configure.py --bootstrap -./ninja all -./ninja_test -strip ninja diff --git a/.gitlab/ci/docker/nvhpc22.11/Dockerfile b/.gitlab/ci/docker/nvhpc22.11/Dockerfile deleted file mode 100644 index 078ae376114..00000000000 --- a/.gitlab/ci/docker/nvhpc22.11/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -# https://catalog.ngc.nvidia.com/orgs/nvidia/containers/nvhpc/tags -FROM kitware/nvidia-nvhpc:22.11-devel-cuda_multi-ubuntu22.04 -MAINTAINER Brad King - -COPY install_deps.sh /root/install_deps.sh -RUN sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/nvhpc24.9/Dockerfile b/.gitlab/ci/docker/nvhpc24.9/Dockerfile new file mode 100644 index 00000000000..b94b14adda0 --- /dev/null +++ b/.gitlab/ci/docker/nvhpc24.9/Dockerfile @@ -0,0 +1,6 @@ +# https://catalog.ngc.nvidia.com/orgs/nvidia/containers/nvhpc/tags +FROM kitware/nvidia-nvhpc:24.9-devel-cuda_multi-ubuntu24.04 +MAINTAINER Brad King + +COPY install_deps.sh /root/install_deps.sh +RUN sh /root/install_deps.sh diff --git a/.gitlab/ci/docker/nvhpc22.11/install_deps.sh b/.gitlab/ci/docker/nvhpc24.9/install_deps.sh similarity index 100% rename from .gitlab/ci/docker/nvhpc22.11/install_deps.sh rename to .gitlab/ci/docker/nvhpc24.9/install_deps.sh diff --git a/.gitlab/ci/download_qt.cmake b/.gitlab/ci/download_qt.cmake index b02ceb06574..d2560dd1254 100644 --- a/.gitlab/ci/download_qt.cmake +++ b/.gitlab/ci/download_qt.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.29) # Input variables. set(qt_version_major "5") @@ -15,11 +15,11 @@ if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows.*package") set(qt_url_root "https://cmake.org/files/dependencies") set(qt_url_path "") if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows_x86_64_package") - list(APPEND qt_files "qt-5.12.1-win-x86_64-msvc_v142-1.zip") - set(qt_subdir "qt-5.12.1-win-x86_64-msvc_v142-1") + list(APPEND qt_files "qt-5.15.10-win-x86_64-msvc_v142-1.zip") + set(qt_subdir "qt-5.15.10-win-x86_64-msvc_v142-1") elseif ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows_i386_package") - list(APPEND qt_files "qt-5.12.1-win-i386-msvc_v142-1.zip") - set(qt_subdir "qt-5.12.1-win-i386-msvc_v142-1") + list(APPEND qt_files "qt-5.15.10-win-i386-msvc_v142-1.zip") + set(qt_subdir "qt-5.15.10-win-i386-msvc_v142-1") elseif ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows_arm64_package") list(APPEND qt_files "qt-6.3.0-win-arm64-msvc_v143-1.zip") set(qt_subdir "qt-6.3.0-win-arm64-msvc_v143-1") @@ -122,6 +122,7 @@ foreach (qt_file IN LISTS qt_files) message(FATAL_ERROR "Failed to extract ${qt_file}: ${err}") endif () + file(REMOVE "${qt_file}") endforeach () # The Windows tarballs have some unfortunate permissions in them that prevent diff --git a/.gitlab/ci/download_qt_hashes.cmake b/.gitlab/ci/download_qt_hashes.cmake index 4c48a47b268..1b9cf1473c0 100644 --- a/.gitlab/ci/download_qt_hashes.cmake +++ b/.gitlab/ci/download_qt_hashes.cmake @@ -13,8 +13,8 @@ set("5.15.1-0-202009071110qtbase-MacOS-MacOS_10_13-Clang-MacOS-MacOS_10_13-X86_6 set("qt-5.9.9-macosx10.10-x86_64-arm64.tar.xz_hash" d4449771afa0bc6a809c14f1e6d939e7732494cf059503ae451e2bfe8fc60cc1) set("qt-5.15.2-macosx10.13-x86_64-arm64.tar.xz_hash" 7b9463a01c8beeee5bf8d01c70deff2d08561cd20aaf6f7a2f41cf8b68ce8a6b) -set("qt-5.12.1-win-i386-msvc_v142-1.zip_hash" aa78711fdaa5d9b146bf7ddcf15983f9fbb3f995462f2d043f8cca74b40ddd11) -set("qt-5.12.1-win-x86_64-msvc_v142-1.zip_hash" c2fc068b9dac40bb420e28e1ee15ce4f2ccfc866d767f3b99b6bb435b7c4f44b) +set("qt-5.15.10-win-i386-msvc_v142-1.zip_hash" c158cebc054d3f4f09733772a8a04789e2884912d45782e8c0c5e6a0b2773e92) +set("qt-5.15.10-win-x86_64-msvc_v142-1.zip_hash" d55c017aef359f6aa8c592b18ba13cc120c749417b55671548970690126cd139) set("qt-6.3.0-win-arm64-msvc_v143-1.zip_hash" f794c035fd4ff9f04468e1787a60d93d7496119c0060c3173a76d24a6b551b14) set("qt-6.3.0-win-i386-msvc_v143-1.zip_hash" 972bc707f78d11b44f360643ca4d0c898e761f7add43b96117d958c70d84a443) diff --git a/.gitlab/ci/env.ps1 b/.gitlab/ci/env.ps1 old mode 100755 new mode 100644 index 72a8cb85e97..754521bb887 --- a/.gitlab/ci/env.ps1 +++ b/.gitlab/ci/env.ps1 @@ -1,4 +1,12 @@ $pwsh = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName + +# Place temporary files inside job directory. +$tmp = New-Item -Force -ItemType Directory -Path "$pwd\.gitlab\tmp" +$tmp = (New-Object -ComObject Scripting.FileSystemObject).GetFolder("$tmp").ShortPath +Set-Item -Force -Path "env:TEMP" -Value "$tmp" +Set-Item -Force -Path "env:TMP" -Value "$tmp" +$tmp = $null + if (Test-Path -Path ".gitlab/ci/env_$env:CMAKE_CONFIGURATION.ps1" -PathType Leaf) { . ".gitlab/ci/env_$env:CMAKE_CONFIGURATION.ps1" } diff --git a/.gitlab/ci/env.sh b/.gitlab/ci/env.sh index 7634f5deeed..de5cf6f0c85 100644 --- a/.gitlab/ci/env.sh +++ b/.gitlab/ci/env.sh @@ -9,6 +9,23 @@ quietly() { rm -f "$log" } +if test -n "$CMAKE_CI_IN_SYMLINK_TREE"; then + mkdir -p "$CI_PROJECT_DIR/real_work/work/build" + ln -s real_work/work "$CI_PROJECT_DIR/work" + git worktree prune + git worktree add "$CI_PROJECT_DIR/work/cmake" HEAD + + # Assert that the hash matches. + test "$(git -C "$CI_PROJECT_DIR/work/cmake" rev-parse HEAD)" = "$(git -C "$CI_PROJECT_DIR" rev-parse HEAD)" +fi + if test -r ".gitlab/ci/env_${CMAKE_CONFIGURATION}.sh"; then source ".gitlab/ci/env_${CMAKE_CONFIGURATION}.sh" fi + +case "$(uname -s)-$(uname -m)" in + Linux-*) + # Remove superuser tools from PATH. + export PATH=$(echo "$PATH" | sed 's|:[^:]*/sbin:|:|g') + ;; +esac diff --git a/.gitlab/ci/env_cuda11.8_splayed_nvidia.sh b/.gitlab/ci/env_cuda11.8_splayed_nvidia.sh new file mode 100644 index 00000000000..38e788d349e --- /dev/null +++ b/.gitlab/ci/env_cuda11.8_splayed_nvidia.sh @@ -0,0 +1,36 @@ +# +# Splay the libraries and includes to emulate conda where +# things are split between the host and build prefix +# +# /usr/local/cuda/include/crt/ -> /tmp/cuda/include/crt +# /usr/local/cuda/lib64/stubs/ -> /tmp/cuda/stubs/ +# /usr/local/cuda/lib64/libcudart* -> /tmp/cuda/libs/ +# +# Also reduce to minimal subset of libraries by removing +# static libraries to emulate a minimal cuda install +mkdir -p /tmp/cuda/libs +mkdir -p /tmp/cuda/stubs +mkdir -p /tmp/cuda/include + +mv /usr/local/cuda/lib64/libcuda* /tmp/cuda/libs +mv /usr/local/cuda/lib64/stubs/ /tmp/cuda/stubs/ +mv /usr/local/cuda/include/crt/ /tmp/cuda/include/ + +# patch the nvcc.profile to handle the splayed layout +# which allows verification +mv /usr/local/cuda/bin/nvcc.profile /usr/local/cuda/bin/nvcc.profile.orig +echo " +TOP = \$(_HERE_)/.. + +NVVMIR_LIBRARY_DIR = \$(TOP)/\$(_NVVM_BRANCH_)/libdevice + +LD_LIBRARY_PATH += \$(TOP)/lib: +PATH += \$(TOP)/\$(_NVVM_BRANCH_)/bin:\$(_HERE_): + +INCLUDES += \"-I\$(TOP)/\$(_TARGET_DIR_)/include\" \$(_SPACE_) \"-I/tmp/cuda/include\" \$(_SPACE_) + +LIBRARIES =+ \$(_SPACE_) \"-L\$(TOP)/\$(_TARGET_DIR_)/lib\$(_TARGET_SIZE_)\" \"-L/tmp/cuda/stubs/\" \"-L/tmp/cuda/libs\" + +CUDAFE_FLAGS += +PTXAS_FLAGS += +" > /usr/local/cuda/bin/nvcc.profile diff --git a/.gitlab/ci/env_cuda12.2_clang.sh b/.gitlab/ci/env_cuda12.2_clang.sh new file mode 100644 index 00000000000..4b71b421540 --- /dev/null +++ b/.gitlab/ci/env_cuda12.2_clang.sh @@ -0,0 +1,3 @@ +export CC=/usr/bin/clang-18 +export CXX=/usr/bin/clang++-18 +export CUDACXX=/usr/bin/clang++-18 diff --git a/.gitlab/ci/env_cuda12.6_clang.sh b/.gitlab/ci/env_cuda12.6_clang.sh new file mode 100644 index 00000000000..4b71b421540 --- /dev/null +++ b/.gitlab/ci/env_cuda12.6_clang.sh @@ -0,0 +1,3 @@ +export CC=/usr/bin/clang-18 +export CXX=/usr/bin/clang++-18 +export CUDACXX=/usr/bin/clang++-18 diff --git a/.gitlab/ci/env_cuda12.6_nvidia_clang.sh b/.gitlab/ci/env_cuda12.6_nvidia_clang.sh new file mode 100644 index 00000000000..4bdf54cd181 --- /dev/null +++ b/.gitlab/ci/env_cuda12.6_nvidia_clang.sh @@ -0,0 +1,5 @@ +export CC=/usr/bin/clang-18 +export CXX=/usr/bin/clang++-18 +export CUDACXX=/usr/local/cuda/bin/nvcc +export CUDAHOSTCXX=/usr/bin/clang++-18 +export CUDAFLAGS= diff --git a/.gitlab/ci/env_debian10_makefiles_clang.sh b/.gitlab/ci/env_debian10_makefiles_clang.sh deleted file mode 100644 index e0d5d6105b3..00000000000 --- a/.gitlab/ci/env_debian10_makefiles_clang.sh +++ /dev/null @@ -1,2 +0,0 @@ -export CC=/usr/bin/clang-7 -export CXX=/usr/bin/clang++-7 diff --git a/.gitlab/ci/env_debian10_ninja.sh b/.gitlab/ci/env_debian10_ninja.sh deleted file mode 100644 index ba8ad479dd1..00000000000 --- a/.gitlab/ci/env_debian10_ninja.sh +++ /dev/null @@ -1,11 +0,0 @@ -export MY_RUBY_HOME="/usr/local/rvm/rubies/ruby-2.7.0" - -if test -z "$CI_MERGE_REQUEST_ID"; then - curl -L -O "https://download.swift.org/swift-5.7.1-release/ubuntu1804/swift-5.7.1-RELEASE/swift-5.7.1-RELEASE-ubuntu18.04.tar.gz" - echo '2b30f9efc969d9e96f0836d0871130dffb369822a3823ee6f3db44c29c1698e3 swift-5.7.1-RELEASE-ubuntu18.04.tar.gz' > swift.sha256sum - sha256sum --check swift.sha256sum - mkdir /opt/swift - tar xzf swift-5.7.1-RELEASE-ubuntu18.04.tar.gz -C /opt/swift --strip-components=2 - rm swift-5.7.1-RELEASE-ubuntu18.04.tar.gz swift.sha256sum - export SWIFTC="/opt/swift/bin/swiftc" -fi diff --git a/.gitlab/ci/env_debian10_ninja_clang.sh b/.gitlab/ci/env_debian10_ninja_clang.sh deleted file mode 100644 index e0d5d6105b3..00000000000 --- a/.gitlab/ci/env_debian10_ninja_clang.sh +++ /dev/null @@ -1,2 +0,0 @@ -export CC=/usr/bin/clang-7 -export CXX=/usr/bin/clang++-7 diff --git a/.gitlab/ci/env_debian10_aarch64_extdeps.sh b/.gitlab/ci/env_debian12_aarch64_extdeps.sh similarity index 100% rename from .gitlab/ci/env_debian10_aarch64_extdeps.sh rename to .gitlab/ci/env_debian12_aarch64_extdeps.sh diff --git a/.gitlab/ci/env_debian10_extdeps.sh b/.gitlab/ci/env_debian12_extdeps.sh similarity index 100% rename from .gitlab/ci/env_debian10_extdeps.sh rename to .gitlab/ci/env_debian12_extdeps.sh diff --git a/.gitlab/ci/env_debian12_hip_radeon.sh b/.gitlab/ci/env_debian12_hip_radeon.sh new file mode 100644 index 00000000000..793c985ff7f --- /dev/null +++ b/.gitlab/ci/env_debian12_hip_radeon.sh @@ -0,0 +1,9 @@ +export HIPCXX=/usr/bin/clang++-15 +export HIPFLAGS='--rocm-path=/usr --rocm-device-lib-path=/usr/lib/x86_64-linux-gnu/amdgcn/bitcode' + +# FIXME(debian): Clang is supposed to automatically parse a HIP version file. +# The ROCm installer places it at '$prefix/bin/.hipVersion', but the package +# on Debian moves it to '$prefix/share/hip/version'. llvm-toolchain package +# version 15.0.7-4 has 'debian/patches/amdgpu/usr-search-paths.patch' for this, +# but Debian 12 currently provides version 15.0.6-4 without the patch. +export HIPFLAGS="$HIPFLAGS --hip-version=5.2" diff --git a/.gitlab/ci/env_debian12_makefiles_clang.sh b/.gitlab/ci/env_debian12_makefiles_clang.sh new file mode 100644 index 00000000000..e4ee2493e1b --- /dev/null +++ b/.gitlab/ci/env_debian12_makefiles_clang.sh @@ -0,0 +1,7 @@ +if test "$CMAKE_CI_NIGHTLY" = "true"; then + source .gitlab/ci/iar-env.sh + source .gitlab/ci/ticlang-env.sh +fi + +export CC=/usr/bin/clang-15 +export CXX=/usr/bin/clang++-15 diff --git a/.gitlab/ci/env_debian12_ninja.sh b/.gitlab/ci/env_debian12_ninja.sh new file mode 100644 index 00000000000..2b8ff2aa034 --- /dev/null +++ b/.gitlab/ci/env_debian12_ninja.sh @@ -0,0 +1,5 @@ +export MY_RUBY_HOME="/usr/local/rvm/rubies/ruby-3.2.2" + +if test -z "$CI_MERGE_REQUEST_ID"; then + source .gitlab/ci/swift-env.sh +fi diff --git a/.gitlab/ci/env_debian12_ninja_clang.sh b/.gitlab/ci/env_debian12_ninja_clang.sh new file mode 100644 index 00000000000..e4ee2493e1b --- /dev/null +++ b/.gitlab/ci/env_debian12_ninja_clang.sh @@ -0,0 +1,7 @@ +if test "$CMAKE_CI_NIGHTLY" = "true"; then + source .gitlab/ci/iar-env.sh + source .gitlab/ci/ticlang-env.sh +fi + +export CC=/usr/bin/clang-15 +export CXX=/usr/bin/clang++-15 diff --git a/.gitlab/ci/env_debian12_ninja_multi_symlinked.sh b/.gitlab/ci/env_debian12_ninja_multi_symlinked.sh new file mode 100644 index 00000000000..5734117e26c --- /dev/null +++ b/.gitlab/ci/env_debian12_ninja_multi_symlinked.sh @@ -0,0 +1,9 @@ +export MY_RUBY_HOME="/usr/local/rvm/rubies/ruby-3.2.2" + +if test "$CMAKE_CI_NIGHTLY" = "true"; then + source .gitlab/ci/iar-env.sh +fi + +if test -z "$CI_MERGE_REQUEST_ID"; then + source .gitlab/ci/swift-env.sh +fi diff --git a/.gitlab/ci/env_fedora38_common_clang.sh b/.gitlab/ci/env_fedora38_common_clang.sh deleted file mode 100644 index fc9c04134b2..00000000000 --- a/.gitlab/ci/env_fedora38_common_clang.sh +++ /dev/null @@ -1,4 +0,0 @@ -export CC=/usr/bin/clang-16 -export CXX=/usr/bin/clang++-16 -export FC=/usr/bin/flang-new -export FFLAGS=-flang-experimental-exec diff --git a/.gitlab/ci/env_fedora38_makefiles.cmake b/.gitlab/ci/env_fedora38_makefiles.cmake deleted file mode 100644 index ef13cda7b57..00000000000 --- a/.gitlab/ci/env_fedora38_makefiles.cmake +++ /dev/null @@ -1 +0,0 @@ -set(ENV{MY_RUBY_HOME} "/usr/local/rvm/rubies/ruby-3.0.4") diff --git a/.gitlab/ci/env_fedora38_makefiles.sh b/.gitlab/ci/env_fedora38_makefiles.sh deleted file mode 100644 index c482642e690..00000000000 --- a/.gitlab/ci/env_fedora38_makefiles.sh +++ /dev/null @@ -1,8 +0,0 @@ -if test "$CMAKE_CI_NIGHTLY" = "true"; then - source .gitlab/ci/ispc-env.sh -fi - -# Patch HDF5 Fortran compiler wrappers to work around Fedora bug. -# https://bugzilla.redhat.com/show_bug.cgi?id=2183289 -sed -i '/^includedir=/ s|/mpich-x86_64||' /usr/lib64/mpich/bin/h5pfc -sed -i '/^includedir=/ s|/openmpi-x86_64||' /usr/lib64/openmpi/bin/h5pfc diff --git a/.gitlab/ci/env_fedora38_makefiles_clang.sh b/.gitlab/ci/env_fedora38_makefiles_clang.sh deleted file mode 100644 index 9f3edde7d72..00000000000 --- a/.gitlab/ci/env_fedora38_makefiles_clang.sh +++ /dev/null @@ -1 +0,0 @@ -. .gitlab/ci/env_fedora38_common_clang.sh diff --git a/.gitlab/ci/env_fedora38_ninja_clang.sh b/.gitlab/ci/env_fedora38_ninja_clang.sh deleted file mode 100644 index 9f3edde7d72..00000000000 --- a/.gitlab/ci/env_fedora38_ninja_clang.sh +++ /dev/null @@ -1 +0,0 @@ -. .gitlab/ci/env_fedora38_common_clang.sh diff --git a/.gitlab/ci/env_fedora38_ninja_multi_clang.sh b/.gitlab/ci/env_fedora38_ninja_multi_clang.sh deleted file mode 100644 index 9f3edde7d72..00000000000 --- a/.gitlab/ci/env_fedora38_ninja_multi_clang.sh +++ /dev/null @@ -1 +0,0 @@ -. .gitlab/ci/env_fedora38_common_clang.sh diff --git a/.gitlab/ci/env_fedora38_asan.sh b/.gitlab/ci/env_fedora42_asan.sh similarity index 100% rename from .gitlab/ci/env_fedora38_asan.sh rename to .gitlab/ci/env_fedora42_asan.sh diff --git a/.gitlab/ci/env_fedora38_clang_analyzer.sh b/.gitlab/ci/env_fedora42_clang_analyzer.sh similarity index 100% rename from .gitlab/ci/env_fedora38_clang_analyzer.sh rename to .gitlab/ci/env_fedora42_clang_analyzer.sh diff --git a/.gitlab/ci/env_fedora42_common_clang.sh b/.gitlab/ci/env_fedora42_common_clang.sh new file mode 100644 index 00000000000..43775d285d6 --- /dev/null +++ b/.gitlab/ci/env_fedora42_common_clang.sh @@ -0,0 +1,3 @@ +export CC=/usr/bin/clang-20 +export CXX=/usr/bin/clang++-20 +export FC=/usr/bin/flang-20 diff --git a/.gitlab/ci/env_fedora42_common_lfortran.sh b/.gitlab/ci/env_fedora42_common_lfortran.sh new file mode 100644 index 00000000000..499369c2f2b --- /dev/null +++ b/.gitlab/ci/env_fedora42_common_lfortran.sh @@ -0,0 +1 @@ +export FC=/usr/bin/lfortran diff --git a/.gitlab/ci/env_fedora38_extdeps.sh b/.gitlab/ci/env_fedora42_extdeps.sh similarity index 100% rename from .gitlab/ci/env_fedora38_extdeps.sh rename to .gitlab/ci/env_fedora42_extdeps.sh diff --git a/.gitlab/ci/env_fedora42_hip_radeon.sh b/.gitlab/ci/env_fedora42_hip_radeon.sh new file mode 100644 index 00000000000..cc57cc71acb --- /dev/null +++ b/.gitlab/ci/env_fedora42_hip_radeon.sh @@ -0,0 +1,2 @@ +export HIPCXX=/usr/bin/clang++-20 +export HIPFLAGS='--rocm-path=/usr --rocm-device-lib-path=/usr/lib64/rocm/llvm/lib/clang/18/amdgcn/bitcode' diff --git a/.gitlab/ci/env_fedora42_makefiles.cmake b/.gitlab/ci/env_fedora42_makefiles.cmake new file mode 100644 index 00000000000..722c10ea168 --- /dev/null +++ b/.gitlab/ci/env_fedora42_makefiles.cmake @@ -0,0 +1 @@ +set(ENV{MY_RUBY_HOME} "/usr/local/rvm/rubies/ruby-3.3.8") diff --git a/.gitlab/ci/env_fedora38_ninja.sh b/.gitlab/ci/env_fedora42_makefiles.sh similarity index 100% rename from .gitlab/ci/env_fedora38_ninja.sh rename to .gitlab/ci/env_fedora42_makefiles.sh diff --git a/.gitlab/ci/env_fedora42_makefiles_clang.sh b/.gitlab/ci/env_fedora42_makefiles_clang.sh new file mode 100644 index 00000000000..8067256931c --- /dev/null +++ b/.gitlab/ci/env_fedora42_makefiles_clang.sh @@ -0,0 +1 @@ +. .gitlab/ci/env_fedora42_common_clang.sh diff --git a/.gitlab/ci/env_fedora42_makefiles_lfortran.sh b/.gitlab/ci/env_fedora42_makefiles_lfortran.sh new file mode 100644 index 00000000000..9d559f90209 --- /dev/null +++ b/.gitlab/ci/env_fedora42_makefiles_lfortran.sh @@ -0,0 +1 @@ +. .gitlab/ci/env_fedora42_common_lfortran.sh diff --git a/.gitlab/ci/env_fedora42_makefiles_symlinked.cmake b/.gitlab/ci/env_fedora42_makefiles_symlinked.cmake new file mode 100644 index 00000000000..fcf100a7d0a --- /dev/null +++ b/.gitlab/ci/env_fedora42_makefiles_symlinked.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/env_fedora42_makefiles.cmake") diff --git a/.gitlab/ci/env_fedora42_makefiles_symlinked.sh b/.gitlab/ci/env_fedora42_makefiles_symlinked.sh new file mode 100644 index 00000000000..c18e12505a7 --- /dev/null +++ b/.gitlab/ci/env_fedora42_makefiles_symlinked.sh @@ -0,0 +1 @@ +. .gitlab/ci/env_fedora42_makefiles.sh diff --git a/.gitlab/ci/env_fedora38_ninja_multi.sh b/.gitlab/ci/env_fedora42_ninja.sh similarity index 100% rename from .gitlab/ci/env_fedora38_ninja_multi.sh rename to .gitlab/ci/env_fedora42_ninja.sh diff --git a/.gitlab/ci/env_fedora42_ninja_clang.sh b/.gitlab/ci/env_fedora42_ninja_clang.sh new file mode 100644 index 00000000000..8067256931c --- /dev/null +++ b/.gitlab/ci/env_fedora42_ninja_clang.sh @@ -0,0 +1 @@ +. .gitlab/ci/env_fedora42_common_clang.sh diff --git a/.gitlab/ci/env_fedora42_ninja_lfortran.sh b/.gitlab/ci/env_fedora42_ninja_lfortran.sh new file mode 100644 index 00000000000..9d559f90209 --- /dev/null +++ b/.gitlab/ci/env_fedora42_ninja_lfortran.sh @@ -0,0 +1 @@ +. .gitlab/ci/env_fedora42_common_lfortran.sh diff --git a/.gitlab/ci/env_fedora42_ninja_multi.sh b/.gitlab/ci/env_fedora42_ninja_multi.sh new file mode 100644 index 00000000000..217ff305df7 --- /dev/null +++ b/.gitlab/ci/env_fedora42_ninja_multi.sh @@ -0,0 +1,3 @@ +if test "$CMAKE_CI_NIGHTLY" = "true"; then + source .gitlab/ci/ispc-env.sh +fi diff --git a/.gitlab/ci/env_fedora42_ninja_multi_clang.sh b/.gitlab/ci/env_fedora42_ninja_multi_clang.sh new file mode 100644 index 00000000000..8067256931c --- /dev/null +++ b/.gitlab/ci/env_fedora42_ninja_multi_clang.sh @@ -0,0 +1 @@ +. .gitlab/ci/env_fedora42_common_clang.sh diff --git a/.gitlab/ci/env_hip6.3_nvidia.sh b/.gitlab/ci/env_hip6.3_nvidia.sh new file mode 100644 index 00000000000..3b326cb9506 --- /dev/null +++ b/.gitlab/ci/env_hip6.3_nvidia.sh @@ -0,0 +1,4 @@ +export HIP_PLATFORM=nvidia +export CUDA_PATH=/usr/local/cuda-12.6 +export PATH=/usr/local/cuda-12.6/bin:$PATH +export LD_LIBRARY_PATH=/usr/local/cuda-12.6/lib64 diff --git a/.gitlab/ci/env_macos_arm64_ninja_symlinked.sh b/.gitlab/ci/env_macos_arm64_ninja_symlinked.sh new file mode 100644 index 00000000000..b78cefcb725 --- /dev/null +++ b/.gitlab/ci/env_macos_arm64_ninja_symlinked.sh @@ -0,0 +1 @@ +. .gitlab/ci/env_macos_arm64_ninja.sh diff --git a/.gitlab/ci/env_mingw_osdn_io_mingw_makefiles.ps1 b/.gitlab/ci/env_mingw_osdn_io_mingw_makefiles.ps1 old mode 100755 new mode 100644 index e2d573eb6fc..f6e1f0a3fb7 --- a/.gitlab/ci/env_mingw_osdn_io_mingw_makefiles.ps1 +++ b/.gitlab/ci/env_mingw_osdn_io_mingw_makefiles.ps1 @@ -1,3 +1 @@ -$pwdpath = $pwd.Path -& "$pwsh" -File ".gitlab/ci/mingw.ps1" -Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\mingw\bin;$env:PATH" +. .gitlab/ci/mingw-env.ps1 diff --git a/.gitlab/ci/env_mingw_osdn_io_msys_makefiles.ps1 b/.gitlab/ci/env_mingw_osdn_io_msys_makefiles.ps1 old mode 100755 new mode 100644 index 6eccb720a37..f6e1f0a3fb7 --- a/.gitlab/ci/env_mingw_osdn_io_msys_makefiles.ps1 +++ b/.gitlab/ci/env_mingw_osdn_io_msys_makefiles.ps1 @@ -1,5 +1 @@ -$pwdpath = $pwd.Path -& "$pwsh" -File ".gitlab/ci/mingw.ps1" -Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\mingw\msys\1.0\bin;$pwdpath\.gitlab\mingw\bin;$env:PATH" -$env:MSYSTEM = 'MINGW32' -$env:MAKE_MODE = 'unix' +. .gitlab/ci/mingw-env.ps1 diff --git a/.gitlab/ci/env_windows_arm64_vs2022_ninja.ps1 b/.gitlab/ci/env_windows_arm64_vs2022_ninja.ps1 new file mode 100644 index 00000000000..eb7bf6e8737 --- /dev/null +++ b/.gitlab/ci/env_windows_arm64_vs2022_ninja.ps1 @@ -0,0 +1,2 @@ +& "$pwsh" -File .gitlab/ci/wix3.ps1 +& "$pwsh" -File .gitlab/ci/wix4.ps1 diff --git a/.gitlab/ci/env_windows_borland5.5.ps1 b/.gitlab/ci/env_windows_borland5.5.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_borland5.8.ps1 b/.gitlab/ci/env_windows_borland5.8.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_clang_ninja.ps1 b/.gitlab/ci/env_windows_clang_ninja.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_clang_nmake.ps1 b/.gitlab/ci/env_windows_clang_nmake.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_intelclassic_ninja.ps1 b/.gitlab/ci/env_windows_intelclassic_ninja.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_inteloneapi_ninja.ps1 b/.gitlab/ci/env_windows_inteloneapi_ninja.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_msvc_v71_nmake.ps1 b/.gitlab/ci/env_windows_msvc_v71_nmake.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_openwatcom1.9.ps1 b/.gitlab/ci/env_windows_openwatcom1.9.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_orangec6.73.1.ps1 b/.gitlab/ci/env_windows_orangec6.73.1.ps1 new file mode 100644 index 00000000000..96e36a18051 --- /dev/null +++ b/.gitlab/ci/env_windows_orangec6.73.1.ps1 @@ -0,0 +1,2 @@ +. .gitlab/ci/ninja-env.ps1 +. .gitlab/ci/orangec-env.ps1 diff --git a/.gitlab/ci/env_windows_vs2022_x64.ps1 b/.gitlab/ci/env_windows_vs2022_x64.ps1 new file mode 100644 index 00000000000..42aec1132db --- /dev/null +++ b/.gitlab/ci/env_windows_vs2022_x64.ps1 @@ -0,0 +1,4 @@ +if ("$env:CMAKE_CI_NIGHTLY" -eq "true") { + $cmake = "build\install\bin\cmake" + . ".gitlab/ci/qt-env.ps1" +} diff --git a/.gitlab/ci/env_windows_vs2022_x64_jom.ps1 b/.gitlab/ci/env_windows_vs2022_x64_jom.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1 b/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1 old mode 100755 new mode 100644 index 50a03ca8284..8ba594401a3 --- a/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1 +++ b/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1 @@ -1,4 +1,14 @@ if ("$env:CMAKE_CI_NIGHTLY" -eq "true") { + if ("$env:CI_JOB_STAGE" -ne "build") { + # As a special case, we do not actually fetch IAR tooling + # in the build job. It is not used until the test job. + . ".gitlab/ci/iar-env.ps1" + } . ".gitlab/ci/innosetup-env.ps1" . ".gitlab/ci/ispc-env.ps1" + . ".gitlab/ci/nuget-env.ps1" + . ".gitlab/ci/swift-env.ps1" } + +& "$pwsh" -File .gitlab/ci/wix3.ps1 +& "$pwsh" -File .gitlab/ci/wix4.ps1 diff --git a/.gitlab/ci/env_windows_vs2022_x64_ninja_multi.ps1 b/.gitlab/ci/env_windows_vs2022_x64_ninja_multi.ps1 old mode 100755 new mode 100644 index 44bb09037d5..64e3c65749a --- a/.gitlab/ci/env_windows_vs2022_x64_ninja_multi.ps1 +++ b/.gitlab/ci/env_windows_vs2022_x64_ninja_multi.ps1 @@ -1,5 +1,7 @@ if ("$env:CMAKE_CI_NIGHTLY" -eq "true") { + . ".gitlab/ci/iar-env.ps1" . ".gitlab/ci/ispc-env.ps1" + . ".gitlab/ci/swift-env.ps1" } . .gitlab/ci/ninja-env.ps1 diff --git a/.gitlab/ci/env_windows_vs2022_x64_nmake.ps1 b/.gitlab/ci/env_windows_vs2022_x64_nmake.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/extdeps-linux.sh b/.gitlab/ci/extdeps-linux.sh index f091525b8fa..87ed1d8ca48 100755 --- a/.gitlab/ci/extdeps-linux.sh +++ b/.gitlab/ci/extdeps-linux.sh @@ -71,7 +71,7 @@ rm -rf jsoncpp-1.6.0* git clone https://github.com/google/cppdap.git cd cppdap -git checkout 03cc18678ed2ed8b2424ec99dee7e4655d876db5 # 2023-05-25 +git checkout c69444ed76f7468b232ac4f989cb8f2bdc100185 # 2024-08-02 cd .. cmake -S cppdap -B cppdap-build \ -DCPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE=ON \ diff --git a/.gitlab/ci/gitlab_ci.cmake b/.gitlab/ci/gitlab_ci.cmake index 080c93b2e20..d0d4a59dd98 100644 --- a/.gitlab/ci/gitlab_ci.cmake +++ b/.gitlab/ci/gitlab_ci.cmake @@ -5,8 +5,13 @@ endif () # Set up the source and build paths. set(CTEST_SOURCE_DIRECTORY "$ENV{CI_PROJECT_DIR}") +if("$ENV{CMAKE_CI_IN_SYMLINK_TREE}") + set(CTEST_SOURCE_DIRECTORY "$ENV{CI_PROJECT_DIR}/work/cmake") +endif() if("$ENV{CMAKE_CI_INPLACE}") set(CTEST_BINARY_DIRECTORY "${CTEST_SOURCE_DIRECTORY}") +elseif("$ENV{CMAKE_CI_IN_SYMLINK_TREE}") + set(CTEST_BINARY_DIRECTORY "$ENV{CI_PROJECT_DIR}/work/build") else() set(CTEST_BINARY_DIRECTORY "${CTEST_SOURCE_DIRECTORY}/build") endif() diff --git a/.gitlab/ci/iar-env.ps1 b/.gitlab/ci/iar-env.ps1 new file mode 100644 index 00000000000..727ebed281a --- /dev/null +++ b/.gitlab/ci/iar-env.ps1 @@ -0,0 +1,15 @@ +$pwdpath = $pwd.Path +& "$pwsh" -File ".gitlab/ci/iar.ps1" +Set-Item -Force -Path "env:IAR_LMS_SETTINGS_DIR" -Value "$pwdpath\.gitlab\iar\license" +$exes = Get-Item -Path "$pwdpath\.gitlab\iar\*\*\bin\icc*.exe" +$exes | ForEach-Object { Write-Host $_.FullName } + +if ($env:CMAKE_CI_IAR_LICENSE_SERVER) { + $llms = Get-Item -Path "$pwdpath\.gitlab\iar\*\common\bin\lightlicensemanager.exe" + foreach ($llm in $llms) { + &$llm.FullName setup --host "$env:CMAKE_CI_IAR_LICENSE_SERVER" + } + foreach ($exe in $exes) { + &$exe.FullName --version + } +} diff --git a/.gitlab/ci/iar-env.sh b/.gitlab/ci/iar-env.sh new file mode 100644 index 00000000000..9ec74942d78 --- /dev/null +++ b/.gitlab/ci/iar-env.sh @@ -0,0 +1,32 @@ +files=' +bxarm-9.60.4.deb +bxavr-8.10.2.deb +bxrh850-3.10.2.deb +bxriscv-3.30.1.deb +bxrl78-5.10.3.deb +bxrx-5.10.1.deb +' +for f in $files; do + # This URL is only visible inside of Kitware's network. + curl -OJLs https://cmake.org/files/dependencies/internal/iar/$f +done + +echo ' +cd92a136bc9bbb1e713121cb407131b54250d2ae30809df3cd752c6383e1878f bxarm-9.60.4.deb +4a1065291952a23a8bfbbaa4eb36ca49b0af8653b8faab34ce955d9d48d64506 bxavr-8.10.2.deb +b14085a0f21750c58168125d3cece2e3fcbd4c6495c652b5e65b6637bac0ac31 bxrh850-3.10.2.deb +517e18dffdd4345f97c480b5128c7feea25ec1c3f06e62d8e2e6808c401d514a bxriscv-3.30.1.deb +3deca7f6afd5f47684464ad748334ab0690097a109d9c680603450074fc32ccf bxrl78-5.10.3.deb +260e592c48cbaf902b13bdb2feeeba83068978131fcb5c027dab17e715dec7e7 bxrx-5.10.1.deb +' > bxdebs.sha256sum +sha256sum --check bxdebs.sha256sum + +dpkg -i bx*.deb +rm bx*.deb bxdebs.sha256sum + +find /opt/iarsystems -executable -wholename "*bin/icc*" + +if test -n "$CMAKE_CI_IAR_LICENSE_SERVER"; then + find /opt/iarsystems -executable -wholename '*bin/lightlicensemanager' -exec {} setup --host "$CMAKE_CI_IAR_LICENSE_SERVER" ';' + find /opt/iarsystems -executable -wholename "*bin/icc*" -exec {} --version ';' +fi diff --git a/.gitlab/ci/iar.ps1 b/.gitlab/ci/iar.ps1 new file mode 100644 index 00000000000..2034fa27ea9 --- /dev/null +++ b/.gitlab/ci/iar.ps1 @@ -0,0 +1,30 @@ +$erroractionpreference = "stop" +$ProgressPreference = 'SilentlyContinue' +Add-Type -AssemblyName System.IO.Compression.FileSystem + +$outdir = $pwd.Path +$outdir = "$outdir\.gitlab" +$iar_dir = New-Item -Force -ItemType Directory -Path "$outdir\iar" + +$files = @{ + "bxarm-9.60.4.11196-1.zip" = "0DE5F610D0FA3A6513C856BC2403A84D5277F1F6D0D65A6022D1FD745BC4AF6A" + "BXAVR-8102-1.zip" = "862EFD23531854506070D5647F9B32197B80E5A727304BFBD8E386A3DAADF093" + "BXRH850-3102-1.zip" = "8D1D009A0D138C7CA8431316123CB85CE1B41319A68B997F90D2E338CD469C7F" + "BXRISCV-3301-1.zip" = "59FF23F7B98EE72567A23942DE799AF137791A19BFEC102B2A59821FABBCA55A" + "BXRL78-5103-1.zip" = "00398E7197735A7B0A4310BF906808E883548814475C12D6EF2C03388F77E6A7" + "BXRX-5101-1.zip" = "D63E95ECD454B4998946C2D9DC1CB6CEF69CE15524C11A123263E6A8E88D9899" +} + +foreach ($f in $files.GetEnumerator()) { + $tarball = $f.Name + + # This URL is only visible inside of Kitware's network. + Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/internal/iar/$tarball" -OutFile "$outdir\$tarball" + $hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256 + if ($hash.Hash -ne $f.Value) { + exit 1 + } + + [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$iar_dir") + Remove-Item "$outdir\$tarball" +} diff --git a/.gitlab/ci/innosetup-env.ps1 b/.gitlab/ci/innosetup-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/innosetup.ps1 b/.gitlab/ci/innosetup.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/intel-env.ps1 b/.gitlab/ci/intel-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/intel-vars.ps1 b/.gitlab/ci/intel-vars.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/intel.ps1 b/.gitlab/ci/intel.ps1 old mode 100755 new mode 100644 index 2262669d89e..c6bf710713b --- a/.gitlab/ci/intel.ps1 +++ b/.gitlab/ci/intel.ps1 @@ -1,8 +1,19 @@ $erroractionpreference = "stop" -if ("$env:CMAKE_CI_BUILD_NAME" -match "(^|_)(oneapi2023\.1\.0|intel2021\.9\.0)(_|$)") { +if ("$env:CMAKE_CI_BUILD_NAME" -match "(^|_)(oneapi2025\.1)(_|$)") { + # Intel oneAPI 2025.1.2 + $version = "2025.1.2" + $version_dir = "2025.1" + $bin_dir = "bin" + $llvm_dir = "bin\compiler" + $filename = "intel-oneapi-$version-windows-1" + $sha256sum = "CA7D5B4E5C11AA9FAF6ED56045D2440F620A2DA3D8BF2B8B644280C9B10DE844" +} elseif ("$env:CMAKE_CI_BUILD_NAME" -match "(^|_)(intel2021\.9)(_|$)") { # Intel oneAPI 2023.1.0 $version = "2023.1.0" + $version_dir = "2023.1.0" + $bin_dir = "windows\bin" + $llvm_dir = "windows\bin-llvm" $filename = "intel-oneapi-$version-windows-1" $sha256sum = "5AFCA9E0B03894565209B1295476163ABEBB1F1388E0F3EF5B4D0F9189E65BDC" } else { @@ -26,12 +37,12 @@ Move-Item -Path "$outdir\$filename" -Destination "$outdir\intel" Remove-Item "$outdir\$tarball" $compiler = "$outdir\intel\compiler" -$bin = "$compiler\$version\windows\bin" -$null = New-Item -ItemType Junction -Path "$compiler\latest" -Target "$compiler\$version" +$bin = "$compiler\$version_dir\$bin_dir" +$null = New-Item -ItemType Junction -Path "$compiler\latest" -Target "$compiler\$version_dir" $null = New-Item -ItemType HardLink -Path "$bin\icx-cl.exe" -Target "$bin\icx.exe" $null = New-Item -ItemType HardLink -Path "$bin\icx-cc.exe" -Target "$bin\icx.exe" $null = New-Item -ItemType HardLink -Path "$bin\icpx.exe" -Target "$bin\icx.exe" -$bin = "$compiler\$version\windows\bin-llvm" +$bin = "$compiler\$version_dir\$llvm_dir" $null = New-Item -ItemType HardLink -Path "$bin\clang-cl.exe" -Target "$bin\clang.exe" $null = New-Item -ItemType HardLink -Path "$bin\clang-cpp.exe" -Target "$bin\clang.exe" $null = New-Item -ItemType HardLink -Path "$bin\clang++.exe" -Target "$bin\clang.exe" @@ -40,3 +51,8 @@ $null = New-Item -ItemType HardLink -Path "$bin\ld.lld.exe" -Target "$bin\lld $null = New-Item -ItemType HardLink -Path "$bin\llvm-lib.exe" -Target "$bin\llvm-ar.exe" Clear-Variable -Name bin Clear-Variable -Name compiler + +Clear-Variable -Name llvm_dir +Clear-Variable -Name bin_dir +Clear-Variable -Name version_dir +Clear-Variable -Name version diff --git a/.gitlab/ci/ispc-env.ps1 b/.gitlab/ci/ispc-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/ispc.ps1 b/.gitlab/ci/ispc.ps1 old mode 100755 new mode 100644 index 524896ffa0f..d390061bbda --- a/.gitlab/ci/ispc.ps1 +++ b/.gitlab/ci/ispc.ps1 @@ -17,3 +17,4 @@ if ($hash.Hash -ne $sha256sum) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") Move-Item -Path "$outdir\$filename" -Destination "$outdir\ispc" +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/ci/ispc.sh b/.gitlab/ci/ispc.sh index 59ee200ea64..7728c3250d7 100755 --- a/.gitlab/ci/ispc.sh +++ b/.gitlab/ci/ispc.sh @@ -12,12 +12,12 @@ case "$(uname -s)-$(uname -m)" in ;; Darwin-arm64) shatool="shasum -a 256" - sha256sum="62cee043a3a4dbff8c2f6d3885a7e573901bbc1325dd93d50f92904b7ea67fec" + sha256sum="c423a5a88d7a9a6ed667e41d025801c123fa0c5fd384d4ea138fa1fcf2bc24c9" platform="macOS.arm64" ;; Darwin-x86_64) shatool="shasum -a 256" - sha256sum="da0f11a048a316081a8ad8170d48b170b2ed7efc3b140fc88b8611238809c8e4" + sha256sum="e25222d2d6f4f8e3561556ac73f88721ceb5486439d6c2a566d37407ad9a5907" platform="macOS.x86_64" ;; *) @@ -39,3 +39,4 @@ curl -OL "https://github.com/ispc/ispc/releases/download/v$version/$tarball" $shatool --check ispc.sha256sum tar xf "$tarball" mv "$filename" ispc +rm "$tarball" ispc.sha256sum diff --git a/.gitlab/ci/jom.ps1 b/.gitlab/ci/jom.ps1 old mode 100755 new mode 100644 index 6c28005f663..430d3232bf4 --- a/.gitlab/ci/jom.ps1 +++ b/.gitlab/ci/jom.ps1 @@ -13,3 +13,4 @@ if ($hash.Hash -ne $sha256sum) { } Expand-Archive -Path "$outdir\$tarball" -DestinationPath "$outdir\jom" +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/ci/mingw-env.ps1 b/.gitlab/ci/mingw-env.ps1 new file mode 100644 index 00000000000..d68a7f7d9ea --- /dev/null +++ b/.gitlab/ci/mingw-env.ps1 @@ -0,0 +1,5 @@ +$pwdpath = $pwd.Path +& "$pwsh" -File ".gitlab/ci/mingw.ps1" +Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\mingw\bin;$pwdpath\.gitlab\mingw\msys\1.0\bin;$env:PATH" +$env:MSYSTEM = 'MINGW32' +$env:MAKE_MODE = 'unix' diff --git a/.gitlab/ci/mingw.ps1 b/.gitlab/ci/mingw.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/msvc.ps1 b/.gitlab/ci/msvc.ps1 old mode 100755 new mode 100644 index e8388a47967..a6b6fbf5323 --- a/.gitlab/ci/msvc.ps1 +++ b/.gitlab/ci/msvc.ps1 @@ -23,6 +23,7 @@ if ($hash.Hash -ne $sha256sum) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") Move-Item -Path "$outdir\$filename" -Destination "$outdir\msvc" +Remove-Item "$outdir\$tarball" $bat = Get-Content -path "$outdir\msvc\$vcvars.in" -Raw $bat = $bat -replace "@VS_ROOT@","$outdir\msvc" diff --git a/.gitlab/ci/ninja-env.ps1 b/.gitlab/ci/ninja-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/ninja-env.sh b/.gitlab/ci/ninja-env.sh new file mode 100644 index 00000000000..744b9f3271f --- /dev/null +++ b/.gitlab/ci/ninja-env.sh @@ -0,0 +1,3 @@ +.gitlab/ci/ninja.sh +export PATH=$PWD/.gitlab:$PATH +ninja --version diff --git a/.gitlab/ci/ninja-nightly.ps1 b/.gitlab/ci/ninja-nightly.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/ninja-nightly.sh b/.gitlab/ci/ninja-nightly.sh index b78b64e2404..739d1d58707 100755 --- a/.gitlab/ci/ninja-nightly.sh +++ b/.gitlab/ci/ninja-nightly.sh @@ -5,7 +5,7 @@ set -e cd .gitlab git clone https://github.com/ninja-build/ninja.git ninja-src -cmake -S ninja-src -B ninja-src/build -DCMAKE_BUILD_TYPE=Release +cmake -S ninja-src -B ninja-src/build -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release cmake --build ninja-src/build --parallel --target ninja mv ninja-src/build/ninja . rm -rf ninja-src diff --git a/.gitlab/ci/ninja.ps1 b/.gitlab/ci/ninja.ps1 old mode 100755 new mode 100644 index 0af3b4fdcc1..81d3f2e5136 --- a/.gitlab/ci/ninja.ps1 +++ b/.gitlab/ci/ninja.ps1 @@ -5,9 +5,18 @@ if ("$env:CMAKE_CI_JOB_NIGHTLY_NINJA" -eq "true" -And "$env:CMAKE_CI_NIGHTLY" -e exit $LASTEXITCODE } -$version = "1.11.0" -$sha256sum = "D0EE3DA143211AA447E750085876C9B9D7BCDD637AB5B2C5B41349C617F22F3B" -$filename = "ninja-win" +$version = "1.12.1" + +if ("$env:PROCESSOR_ARCHITECTURE" -eq "AMD64") { + $sha256sum = "F550FEC705B6D6FF58F2DB3C374C2277A37691678D6ABA463ADCBB129108467A" + $filename = "ninja-win" +} elseif ("$env:PROCESSOR_ARCHITECTURE" -eq "ARM64") { + $sha256sum = "79C96A50E0DEAFEC212CFA85AA57C6B74003F52D9D1673DDCD1EAB1C958C5900" + $filename = "ninja-winarm64" +} else { + throw ('unknown PROCESSOR_ARCHITECTURE: ' + "$env:PROCESSOR_ARCHITECTURE") +} + $tarball = "$filename.zip" $outdir = $pwd.Path @@ -21,3 +30,4 @@ if ($hash.Hash -ne $sha256sum) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/ci/ninja.sh b/.gitlab/ci/ninja.sh index ce39b462fc7..530feac1ea8 100755 --- a/.gitlab/ci/ninja.sh +++ b/.gitlab/ci/ninja.sh @@ -6,25 +6,23 @@ if test "$CMAKE_CI_JOB_NIGHTLY_NINJA" = "true" -a "$CMAKE_CI_NIGHTLY" = "true"; exec .gitlab/ci/ninja-nightly.sh fi -readonly version="1.11.0" +readonly version="1.12.1" baseurl="https://github.com/ninja-build/ninja/releases/download/v$version" case "$(uname -s)-$(uname -m)" in Linux-x86_64) shatool="sha256sum" - sha256sum="9726e730d5b8599f82654dc80265e64a10a8a817552c34153361ed0c017f9f02" + sha256sum="6f98805688d19672bd699fbbfa2c2cf0fc054ac3df1f0e6a47664d963d530255" filename="ninja-linux" ;; Linux-aarch64) shatool="sha256sum" - sha256sum="b002eb77cfcef6d329cccf8b1cc7ad138302d6e19b5b76b10b4c4d38564b47b5" - # Use binary built by adjacent 'docker/ninja/centos7-aarch64.bash' script. - baseurl="https://cmake.org/files/dependencies" - filename="ninja-$version-1-linux-aarch64" + sha256sum="5c25c6570b0155e95fce5918cb95f1ad9870df5768653afe128db822301a05a1" + filename="ninja-linux-aarch64" ;; Darwin-*) shatool="shasum -a 256" - sha256sum="21915277db59756bfc61f6f281c1f5e3897760b63776fd3d360f77dd7364137f" + sha256sum="89a287444b5b3e98f88a945afa50ce937b8ffd1dcc59c555ad9b1baf855298c9" filename="ninja-mac" ;; *) @@ -43,3 +41,4 @@ echo "$sha256sum $tarball" > ninja.sha256sum curl -OL "$baseurl/$tarball" $shatool --check ninja.sha256sum ./cmake/bin/cmake -E tar xf "$tarball" +rm "$tarball" ninja.sha256sum diff --git a/.gitlab/ci/nuget-env.ps1 b/.gitlab/ci/nuget-env.ps1 new file mode 100644 index 00000000000..7dee5a02382 --- /dev/null +++ b/.gitlab/ci/nuget-env.ps1 @@ -0,0 +1,4 @@ +$pwdpath = $pwd.Path +& "$pwsh" -File ".gitlab/ci/nuget.ps1" +Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\nuget;$env:PATH" +nuget | Select -First 1 diff --git a/.gitlab/ci/nuget.ps1 b/.gitlab/ci/nuget.ps1 new file mode 100644 index 00000000000..1decb019d5f --- /dev/null +++ b/.gitlab/ci/nuget.ps1 @@ -0,0 +1,21 @@ +$erroractionpreference = "stop" + +$version = "6.9.1.3" +$sha256sum = "562A2CE2D570D68DB4472CB82CDF1FC4245D5C73B84BC8361880CBE389702F65" +$filename = "nuget-$version-win-i386-1" +$tarball = "$filename.zip" + +$outdir = $pwd.Path +$outdir = "$outdir\.gitlab" +$ProgressPreference = 'SilentlyContinue' +# This URL is only visible inside of Kitware's network. +Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/internal/$tarball" -OutFile "$outdir\$tarball" +$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256 +if ($hash.Hash -ne $sha256sum) { + exit 1 +} + +Add-Type -AssemblyName System.IO.Compression.FileSystem +[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") +Move-Item -Path "$outdir\$filename" -Destination "$outdir\nuget" +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/ci/openwatcom-env.ps1 b/.gitlab/ci/openwatcom-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/openwatcom.ps1 b/.gitlab/ci/openwatcom.ps1 old mode 100755 new mode 100644 index 4f1012cc8fd..eb617c33796 --- a/.gitlab/ci/openwatcom.ps1 +++ b/.gitlab/ci/openwatcom.ps1 @@ -23,3 +23,4 @@ if ($hash.Hash -ne $sha256sum) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") Move-Item -Path "$outdir\$filename" -Destination "$outdir\watcom" +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/ci/orangec-env.ps1 b/.gitlab/ci/orangec-env.ps1 new file mode 100644 index 00000000000..3a5d23200b9 --- /dev/null +++ b/.gitlab/ci/orangec-env.ps1 @@ -0,0 +1,8 @@ +Invoke-Expression -Command .gitlab/ci/orangec.ps1 +$pwdpath = $pwd.Path +Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\orangec\bin;$env:PATH" +Set-Item -Force -Path "env:ORANGEC" -Value "$pwdpath\.gitlab\orangec" + +$env:CC = "occ" +$env:CXX = "occ" +occ --version diff --git a/.gitlab/ci/orangec.ps1 b/.gitlab/ci/orangec.ps1 new file mode 100644 index 00000000000..2201e1229d3 --- /dev/null +++ b/.gitlab/ci/orangec.ps1 @@ -0,0 +1,24 @@ +$erroractionpreference = "stop" + +if ("$env:CMAKE_CONFIGURATION".Contains("orangec6.73.1")) { + # OrangeC 6.73.1 + $archive = "ZippedBinaries6738.zip" + $release = "Orange-C-v6.73.1" + $sha256sum = "29BC506AB105B2BF1002129C37826B2153DF1C8D0F22B9A2C38ACA3FB72A5B89" +} else { + throw ('unknown CMAKE_CONFIGURATION: ' + "$env:CMAKE_CONFIGURATION") +} + +$outdir = $pwd.Path +$outdir = "$outdir\.gitlab" +$ProgressPreference = 'SilentlyContinue' +Invoke-WebRequest -Uri "https://github.com/LADSoft/OrangeC/releases/download/$release/$archive" -OutFile "$outdir\$archive" +$hash = Get-FileHash "$outdir\$archive" -Algorithm SHA256 +if ($hash.Hash -ne $sha256sum) { + exit 1 +} + +Add-Type -AssemblyName System.IO.Compression.FileSystem +[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$archive", "$outdir") +# The archive contains directory 'orangec', placed at '$outdir\orangec'. +Remove-Item "$outdir\$archive" diff --git a/.gitlab/ci/package_info.cmake.in b/.gitlab/ci/package_info.cmake.in deleted file mode 100644 index f9a5bb778b0..00000000000 --- a/.gitlab/ci/package_info.cmake.in +++ /dev/null @@ -1 +0,0 @@ -set(CPACK_PACKAGE_FILE_NAME "@CPACK_PACKAGE_FILE_NAME@") diff --git a/.gitlab/ci/package_windows.ps1 b/.gitlab/ci/package_windows.ps1 old mode 100755 new mode 100644 index 9ec2942eaf1..5b787f404bc --- a/.gitlab/ci/package_windows.ps1 +++ b/.gitlab/ci/package_windows.ps1 @@ -1,7 +1,3 @@ -if (Test-Path -Path "build/ci_package_info.cmake" -PathType Leaf) { - cmake -P .gitlab/ci/package_windows_build.cmake -} else { - cd build - cpack -G ZIP - cpack -G WIX -} +cd build +. ../Utilities/Release/win/sign-package.ps1 -cpack cpack +if (-not $?) { Exit $LastExitCode } diff --git a/.gitlab/ci/package_windows_build.cmake b/.gitlab/ci/package_windows_build.cmake deleted file mode 100644 index feb379c9bec..00000000000 --- a/.gitlab/ci/package_windows_build.cmake +++ /dev/null @@ -1,41 +0,0 @@ -cmake_minimum_required(VERSION 3.24) -include(build/ci_package_info.cmake) - -set(build "${CMAKE_CURRENT_BINARY_DIR}/build") - -file(GLOB paths RELATIVE "${CMAKE_CURRENT_BINARY_DIR}" - # Allow CPack to find CMAKE_ROOT. - "${build}/CMakeFiles/CMakeSourceDir.txt" - - # We need the main binaries. - "${build}/bin" - - # Pass through the documentation. - "${build}/install-doc" - - # CPack configuration. - "${build}/CPackConfig.cmake" - "${build}/CMakeCPackOptions.cmake" - "${build}/Source/QtDialog/QtDialogCPack.cmake" - - # CPack/IFW packaging files. - "${build}/CMake*.qs" - - # CPack/WIX packaging files. - "${build}/Utilities/Release/WiX/custom_action_dll*.wxs" - "${build}/Utilities/Release/WiX/CustomAction/CMakeWiXCustomActions.*" - ) - -file(GLOB_RECURSE paths_recurse RELATIVE "${CMAKE_CURRENT_BINARY_DIR}" - # Install rules. - "${build}/cmake_install.cmake" - "${build}/*/cmake_install.cmake" - ) - -# Create a "package" containing the build-tree files needed to build a package. -file(MAKE_DIRECTORY build/unsigned) -file(ARCHIVE_CREATE - OUTPUT build/unsigned/${CPACK_PACKAGE_FILE_NAME}.build.zip - PATHS ${paths} ${paths_recurse} - FORMAT zip - ) diff --git a/.gitlab/ci/post_build.ps1 b/.gitlab/ci/post_build.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/post_build_fedora42_tidy.sh b/.gitlab/ci/post_build_fedora42_tidy.sh new file mode 100644 index 00000000000..a36663a9246 --- /dev/null +++ b/.gitlab/ci/post_build_fedora42_tidy.sh @@ -0,0 +1,21 @@ +git config user.name "Kitware Robot" +git config user.email "kwrobot@kitware.com" + +clang-apply-replacements --style=file .gitlab/clang-tidy-fixes +git add . + +if [ -n "$(git status --porcelain)" ]; then + quietly git commit --file=- < + + +EOF + git format-patch --output=clang-tidy-fixes.patch -1 -N + echo "Patch from clang-tidy available, check artifacts of this CI job." >&2 +fi + +readonly num_warnings="$(cat .gitlab/num_warnings.txt)" +if [ "$num_warnings" -ne 0 ]; then + echo "Found $num_warnings warnings (treating as fatal)." >&2 + exit 1 +fi diff --git a/.gitlab/ci/post_build_windows_arm64_package.ps1 b/.gitlab/ci/post_build_windows_arm64_package.ps1 deleted file mode 100755 index f98d99597d1..00000000000 --- a/.gitlab/ci/post_build_windows_arm64_package.ps1 +++ /dev/null @@ -1 +0,0 @@ -. .gitlab/ci/package_windows.ps1 diff --git a/.gitlab/ci/post_build_windows_i386_package.ps1 b/.gitlab/ci/post_build_windows_i386_package.ps1 deleted file mode 100755 index f98d99597d1..00000000000 --- a/.gitlab/ci/post_build_windows_i386_package.ps1 +++ /dev/null @@ -1 +0,0 @@ -. .gitlab/ci/package_windows.ps1 diff --git a/.gitlab/ci/post_build_windows_x86_64_package.ps1 b/.gitlab/ci/post_build_windows_x86_64_package.ps1 deleted file mode 100755 index f98d99597d1..00000000000 --- a/.gitlab/ci/post_build_windows_x86_64_package.ps1 +++ /dev/null @@ -1 +0,0 @@ -. .gitlab/ci/package_windows.ps1 diff --git a/.gitlab/ci/pre_build.ps1 b/.gitlab/ci/pre_build.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/pre_build_fedora38_tidy.sh b/.gitlab/ci/pre_build_fedora42_tidy.sh similarity index 100% rename from .gitlab/ci/pre_build_fedora38_tidy.sh rename to .gitlab/ci/pre_build_fedora42_tidy.sh diff --git a/.gitlab/ci/python-env.ps1 b/.gitlab/ci/python-env.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/python.ps1 b/.gitlab/ci/python.ps1 old mode 100755 new mode 100644 diff --git a/.gitlab/ci/qt-env.ps1 b/.gitlab/ci/qt-env.ps1 old mode 100755 new mode 100644 index 7eff55fc446..22b10992e0c --- a/.gitlab/ci/qt-env.ps1 +++ b/.gitlab/ci/qt-env.ps1 @@ -1,6 +1,9 @@ +if ($cmake -eq $null) { + throw ('$cmake powershell variable not set ') +} if ("$env:PROCESSOR_ARCHITECTURE" -eq "AMD64") { $pwdpath = $pwd.Path - cmake -P .gitlab/ci/download_qt.cmake + & $cmake -P .gitlab/ci/download_qt.cmake Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\qt\bin;$env:PATH" qmake -v } elseif ("$env:PROCESSOR_ARCHITECTURE" -eq "ARM64") { diff --git a/.gitlab/ci/repackage/iar.ps1 b/.gitlab/ci/repackage/iar.ps1 new file mode 100644 index 00000000000..5a8c1e56f79 --- /dev/null +++ b/.gitlab/ci/repackage/iar.ps1 @@ -0,0 +1,49 @@ +# IAR Tooling for Windows is available only via installers. +# Run an installer and repackage the installation directory. + +#Requires -RunAsAdministrator + +param ( + [Parameter(Mandatory=$true)] + [string]$installer, + [string]$revision = "1", + [string]$basedir = "c:\iar" + ) + +$erroractionpreference = "stop" + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +$installer_file = Get-Item $installer +$installer_name = $installer_file.Name +$package_name = $installer_file.Basename + "-" + $revision +$package_dir = "$basedir\$package_name" +$exclude = @( + "arm/config/debugger" + "arm/config/flashloader" + "arm/drivers" + "arm/src" + ) + +Write-Host "Installing to: $package_dir" +Start-Process -Wait -FilePath "$installer_file" -ArgumentList "/hide_usd /autoinstall/$package_dir" +foreach ($p in $exclude) { + Remove-Item "$package_dir/$p" -Recurse -Force +} + +@" +This was repackaged from an installation by "$installer_name" +using CMake's ".gitlab/ci/repackage/iar.ps1" script. + +Obtain a network license as follows: + + set IAR_LMS_SETTINGS_DIR=%cd%\license + %cd%\common\bin\lightlicensemanager setup --host %LicenseServerHostname% + +"@ | Add-Content -NoNewline "$package_dir/README.txt" + + +Write-Host "Repackaging to: $package_name.zip" +$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal +$includeBaseDirectory = $true +[System.IO.Compression.ZipFile]::CreateFromDirectory("$package_dir", "$package_name.zip", $compressionLevel, $includeBaseDirectory) diff --git a/.gitlab/ci/repackage/intel.ps1 b/.gitlab/ci/repackage/intel.ps1 new file mode 100644 index 00000000000..60d1b7000b0 --- /dev/null +++ b/.gitlab/ci/repackage/intel.ps1 @@ -0,0 +1,153 @@ +# Intel Compilers for Windows are available only via installers. +# Run an installer and repackage the installation directory. + +# From the Intel oneAPI download page, download offline +# installers for the Base and HPC products, as documented +# by their "Install through a Command Line" sections. + +#Requires -RunAsAdministrator + +param ( + [Parameter(Mandatory=$true)] + [string]$installer, + [string]$revision = "1", + [string]$basedir = "c:\intel" + ) + +$erroractionpreference = "stop" + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +$base_installer_file = Get-Item -Path $installer +$base_installer_name = $base_installer_file.Name +if ($base_installer_name -match '^intel-oneapi-base-toolkit-(?(?[0-9][0-9][0-9][0-9]\.[0-9])\.[0-9])\.') { + $version2 = $Matches.version2 + $version = $Matches.version +} else { + Write-Host "Base installer file does not match expected pattern." + Exit 1 +} + +$hpc_installer_file = Get-Item -Path (Join-Path $base_installer_file.Directory "intel-oneapi-hpc-toolkit-$version.*") +if (-not $hpc_installer_file) { + Write-Host "HPC installer file not found next to base installer." + Exit 1 +} +$hpc_installer_name = $hpc_installer_file.Name +Write-Host "Version: '$version'" +Write-Host "Base Installer: '$base_installer_file'" +Write-Host "HPC Installer: '$hpc_installer_file'" + +$package_name = "intel-oneapi-$version-windows-$revision" +$package_dir = New-Item -Force -ItemType Directory -Path "$basedir\$package_name" +if (-not $package_dir) { + Write-Host "Failed to create package install dir." + Exit 1 +} +$compiler_exclude = @( + ".toolkit_linking_tool" + "bin\*.o" + "bin\*.rtl" + "bin\*.spv" + "bin\1033" + "bin\OpenCL.dll" + "bin\aocl-ioc64.exe" + "bin\cl.cfg" + "bin\common_clang64.dll" + "bin\compiler\append-file.exe" + "bin\compiler\clang++.exe" + "bin\compiler\clang-cl.exe" + "bin\compiler\clang-cpp.exe" + "bin\compiler\clang-format.exe" + "bin\compiler\clang-include-fixer.exe" + "bin\compiler\clang-linker-wrapper.exe" + "bin\compiler\clang-offload-*.exe" + "bin\compiler\clang-tidy.exe" + "bin\compiler\clangd.exe" + "bin\compiler\file-table-tform.exe" + "bin\compiler\ld.lld.exe" + "bin\compiler\lld-link.exe" + "bin\compiler\llvm-cov.exe" + "bin\compiler\llvm-lib.exe" + "bin\compiler\llvm-profdata.exe" + "bin\compiler\llvm-profgen.exe" + "bin\compiler\llvm-spirv.exe" + "bin\compiler\llvm-symbolizer.exe" + "bin\compiler\modularize.exe" + "bin\compiler\spirv-to-ir-wrapper.exe" + "bin\compiler\sycl-post-link.exe" + "bin\compiler\yaml2obj.exe" + "bin\deftofd.exe" + "bin\dpcpp-cl.exe" + "bin\dpcpp.exe" + "bin\icpx.exe" + "bin\icx-cc.exe" + "bin\icx-cl.exe" + "bin\intelocl64.*" + "bin\ioc64.exe" + "bin\libhwloc-15.dll" + "bin\libintelocl.so-gdb.py" + "bin\libiomp5md.pdb" + "bin\libocl_*.dll" + "bin\opencl-aot.exe" + "bin\pstloffload*.dll" + "bin\run-clang-tidy" + "bin\svml_dispmd.dll" + "bin\sycl*" + "bin\tcm*" + "bin\ur_*.dll" + "share" + ) + +Write-Host "Installing to: $package_dir" +$install_args = "-s -r yes -a --silent --eula accept" +$install_args = $install_args + " --instance repackage" +$install_args = $install_args + " -p=NEED_VS2019_INTEGRATION=0" +$install_args = $install_args + " -p=NEED_VS2022_INTEGRATION=0" +Start-Process -Wait -FilePath "$base_installer_file" ` + -ArgumentList "$install_args --install-dir $package_dir --components intel.oneapi.win.cpp-dpcpp-common" +Write-Host "" +Start-Process -Wait -FilePath "$hpc_installer_file" ` + -ArgumentList "$install_args --components intel.oneapi.win.ifort-compiler" +Write-Host "" +Get-Item -Path "$package_dir\*" -Exclude compiler,setvars.bat,setvars-vcvarsall.bat | ForEach-Object { + Remove-Item "$_" -Recurse -Force +} +Remove-Item "$package_dir/compiler/latest" -Recurse -Force +foreach ($p in $compiler_exclude) { + Remove-Item "$package_dir/compiler/$version2/$p" -Recurse -Force +} + +@" +This was repackaged from an installation by: + +* $base_installer_name +* $hpc_installer_name + +using CMake's ".gitlab/ci/repackage/intel.ps1" script. + +Duplicate files were removed from this distribution. +Restore them using hard links: + +* compiler/$version2/bin/compiler/clang++.exe -> clang.exe +* compiler/$version2/bin/compiler/clang-cl.exe -> clang.exe +* compiler/$version2/bin/compiler/clang-cpp.exe -> clang.exe +* compiler/$version2/bin/compiler/ld.lld.exe -> lld.exe +* compiler/$version2/bin/compiler/lld-link.exe -> lld.exe +* compiler/$version2/bin/compiler/llvm-lib.exe -> llvm-ar.exe +* compiler/$version2/bin/icpx.exe -> icx.exe +* compiler/$version2/bin/icx-cc.exe -> icx.exe +* compiler/$version2/bin/icx-cl.exe -> icx.exe + +Also add a directory junction: + +* compiler/latest -> $version2 + +Then use "setvars.bat" to establish an environment. + +"@ | Add-Content -NoNewline "$package_dir/README.txt" + +Write-Host "Repackaging to: $package_name.zip" +$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal +$includeBaseDirectory = $true +[System.IO.Compression.ZipFile]::CreateFromDirectory("$package_dir", "$package_name.zip", $compressionLevel, $includeBaseDirectory) diff --git a/.gitlab/ci/repackage/wix.ps1 b/.gitlab/ci/repackage/wix.ps1 new file mode 100644 index 00000000000..6dbd46667a0 --- /dev/null +++ b/.gitlab/ci/repackage/wix.ps1 @@ -0,0 +1,55 @@ +# WiX Toolset 4+ is provided only via nuget packages. +# Download the package artifacts, extract the parts we need, and repackage them. + +param ( + [Parameter(Mandatory=$true)] + [string]$version + ) + +$erroractionpreference = "stop" + +$version_major = $version.Substring(0, $version.IndexOf('.')) + +$release = "v" + $version +$pkg_wix = "wix.$version.nupkg" +$pkg_wixui = "WixToolset.UI.wixext.$version.nupkg" +$packages = $pkg_wix, $pkg_wixui + +$wix_artifacts = "wix-artifacts.zip" + +$ProgressPreference = 'SilentlyContinue' +Invoke-WebRequest -Uri "https://github.com/wixtoolset/wix/releases/download/$release/artifacts.zip" -OutFile "$wix_artifacts" + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +$zip = [System.IO.Compression.ZipFile]::Open("$wix_artifacts", "read") +$zip.Entries | Where-Object FullName -in $packages | ForEach-Object { + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$($_.Name)", $true) +} +$zip.Dispose() +Remove-Item "$wix_artifacts" + +$wix_dir = "wix-$version-win-any-1" +[System.IO.Compression.ZipFile]::ExtractToDirectory($pkg_wix, "wix-tmp") +Move-Item -Path "wix-tmp/tools/net6.0/any" -Destination "$wix_dir" +Remove-Item "wix-tmp" -Recurse -Force +Remove-Item "$pkg_wix" + +$ext_dir = New-Item -Force -ItemType Directory -Path "$wix_dir/.wix/extensions/WixToolset.UI.wixext/$version/wixext$version_major" +$zip = [System.IO.Compression.ZipFile]::Open($pkg_wixui, "read") +$zip.Entries | Where-Object Name -eq "WixToolset.UI.wixext.dll" | ForEach-Object { + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, (Join-Path $ext_dir $_.Name), $true) +} +$zip.Dispose() +Remove-Item "$pkg_wixui" + +@" +This was extracted from WiX Toolset nuget packages and repackaged. +Point both PATH and WIX_EXTENSIONS environment variables at this directory. + +"@ | Add-Content -NoNewline "$wix_dir/README.txt" + +$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal +$includeBaseDirectory = $true +[System.IO.Compression.ZipFile]::CreateFromDirectory("$wix_dir", "$wix_dir.zip", $compressionLevel, $includeBaseDirectory) +Remove-Item "$wix_dir" -Recurse -Force diff --git a/.gitlab/ci/sccache-env.ps1 b/.gitlab/ci/sccache-env.ps1 new file mode 100644 index 00000000000..66dc6ebb315 --- /dev/null +++ b/.gitlab/ci/sccache-env.ps1 @@ -0,0 +1 @@ +Set-Item -Force -Path "env:PATH" -Value "$env:PATH;$env:SCCACHE_PATH" diff --git a/.gitlab/ci/sccache-env.sh b/.gitlab/ci/sccache-env.sh new file mode 100644 index 00000000000..4b170a47d91 --- /dev/null +++ b/.gitlab/ci/sccache-env.sh @@ -0,0 +1,2 @@ +.gitlab/ci/sccache.sh +export PATH="$PWD/.gitlab:$PATH" diff --git a/.gitlab/ci/signtool-env.ps1 b/.gitlab/ci/signtool-env.ps1 new file mode 100644 index 00000000000..5467b98c5d4 --- /dev/null +++ b/.gitlab/ci/signtool-env.ps1 @@ -0,0 +1,22 @@ +if ("$env:PROCESSOR_ARCHITECTURE" -eq "AMD64") { + $arch = "x64" +} elseif ("$env:PROCESSOR_ARCHITECTURE" -eq "ARM64") { + $arch = "arm64" +} else { + throw ('unknown PROCESSOR_ARCHITECTURE: ' + "$env:PROCESSOR_ARCHITECTURE") +} + +$regKey = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' +$signtoolPath = $null +if ($sdkDir = Get-ItemPropertyValue -Path $regKey -Name "InstallationFolder") { + if ($sdkBin = Get-ChildItem -Path "$sdkDir/bin" -Recurse -Name "$arch" | + Where-Object { Test-Path -Path "$sdkDir/bin/$_/signtool.exe" -PathType Leaf } | + Select-Object -Last 1) { + $signtoolPath = "$sdkDir/bin/$sdkBin" + } +} +if ($signtoolPath) { + Set-Item -Force -Path "env:PATH" -Value "$env:PATH;$signtoolPath" +} else { + throw ('No signtool.exe found in Windows SDK') +} diff --git a/.gitlab/ci/swift-env.ps1 b/.gitlab/ci/swift-env.ps1 new file mode 100644 index 00000000000..871b31c3b8a --- /dev/null +++ b/.gitlab/ci/swift-env.ps1 @@ -0,0 +1,6 @@ +$pwdpath = $pwd.Path +& "$pwsh" -File ".gitlab/ci/swift.ps1" +Set-Item -Force -Path "env:DEVELOPER_DIR" -Value "$pwdpath\.gitlab\swift" +Set-Item -Force -Path "env:SDKROOT" -Value "$pwdpath\.gitlab\swift\Platforms\Windows.platform\Developer\SDKs\Windows.sdk" +Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\swift\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin;$env:PATH" +swiftc --version diff --git a/.gitlab/ci/swift-env.sh b/.gitlab/ci/swift-env.sh new file mode 100644 index 00000000000..fa0c81e175c --- /dev/null +++ b/.gitlab/ci/swift-env.sh @@ -0,0 +1,7 @@ +curl -L -O "https://download.swift.org/swift-5.7.1-release/ubuntu1804/swift-5.7.1-RELEASE/swift-5.7.1-RELEASE-ubuntu18.04.tar.gz" +echo '2b30f9efc969d9e96f0836d0871130dffb369822a3823ee6f3db44c29c1698e3 swift-5.7.1-RELEASE-ubuntu18.04.tar.gz' > swift.sha256sum +sha256sum --check swift.sha256sum +mkdir /opt/swift +tar xzf swift-5.7.1-RELEASE-ubuntu18.04.tar.gz -C /opt/swift --strip-components=2 +rm swift-5.7.1-RELEASE-ubuntu18.04.tar.gz swift.sha256sum +export SWIFTC="/opt/swift/bin/swiftc" diff --git a/.gitlab/ci/swift.ps1 b/.gitlab/ci/swift.ps1 new file mode 100644 index 00000000000..b970dcec0b1 --- /dev/null +++ b/.gitlab/ci/swift.ps1 @@ -0,0 +1,38 @@ +$erroractionpreference = "stop" + +$version = "5.9.2" +$sha256sum = "8C053108528EB2DAD84C33D6F0834A3A1444D21BA1A89D591AB149304A62F6B5" +$filename = "swift-$version-win-x86_64-1" +$tarball = "$filename.zip" + +$outdir = $pwd.Path +$outdir = "$outdir\.gitlab" +$ProgressPreference = 'SilentlyContinue' +# This URL is only visible inside of Kitware's network. See above filename table. +Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/internal/$tarball" -OutFile "$outdir\$tarball" +$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256 +if ($hash.Hash -ne $sha256sum) { + exit 1 +} + +Add-Type -AssemblyName System.IO.Compression.FileSystem +[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") +Move-Item -Path "$outdir\$filename" -Destination "$outdir\swift" +Remove-Item "$outdir\$tarball" + +$bin = "$outdir\swift\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin" +$null = New-Item -ItemType HardLink -Path "$bin\clang++.exe" -Target "$bin\clang.exe" +$null = New-Item -ItemType HardLink -Path "$bin\clang-cl.exe" -Target "$bin\clang.exe" +$null = New-Item -ItemType HardLink -Path "$bin\clang-cpp.exe" -Target "$bin\clang.exe" +$null = New-Item -ItemType HardLink -Path "$bin\ld.lld.exe" -Target "$bin\lld.exe" +$null = New-Item -ItemType HardLink -Path "$bin\ld64.lld.exe" -Target "$bin\lld.exe" +$null = New-Item -ItemType HardLink -Path "$bin\lld-link.exe" -Target "$bin\lld.exe" +$null = New-Item -ItemType HardLink -Path "$bin\llvm-dlltool.exe" -Target "$bin\llvm-ar.exe" +$null = New-Item -ItemType HardLink -Path "$bin\llvm-lib.exe" -Target "$bin\llvm-ar.exe" +$null = New-Item -ItemType HardLink -Path "$bin\llvm-ranlib.exe" -Target "$bin\llvm-ar.exe" +$null = New-Item -ItemType HardLink -Path "$bin\llvm-objcopy.exe" -Target "$bin\llvm-strip.exe" +$null = New-Item -ItemType HardLink -Path "$bin\swiftc.exe" -Target "$bin\swift.exe" +$null = New-Item -ItemType HardLink -Path "$bin\swift-api-digester.exe" -Target "$bin\swift-frontend.exe" +$null = New-Item -ItemType HardLink -Path "$bin\swift-autolink-extract.exe" -Target "$bin\swift-frontend.exe" +$null = New-Item -ItemType HardLink -Path "$bin\swift-symbolgraph-extract.exe" -Target "$bin\swift-frontend.exe" +Clear-Variable -Name bin diff --git a/.gitlab/ci/ticlang-env.sh b/.gitlab/ci/ticlang-env.sh new file mode 100644 index 00000000000..448c0d76718 --- /dev/null +++ b/.gitlab/ci/ticlang-env.sh @@ -0,0 +1,2 @@ +.gitlab/ci/ticlang.sh +.gitlab/ticlang/bin/tiarmclang --version diff --git a/.gitlab/ci/ticlang.sh b/.gitlab/ci/ticlang.sh new file mode 100755 index 00000000000..66fa863a302 --- /dev/null +++ b/.gitlab/ci/ticlang.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64) + shatool="sha256sum" + sha256sum="c69ac58e403b82eac1c407cc67b35fab5d95c5d8db75b019095f9412aacff27d" + filename="ti_cgt_armllvm_3.2.1.LTS_linux-x64_installer.bin" + dirname="ti-cgt-armllvm_3.2.1.LTS" + ;; + *) + echo "Unrecognized platform $(uname -s)-$(uname -m)" + exit 1 + ;; +esac +readonly shatool +readonly sha256sum + +cd .gitlab + +echo "$sha256sum $filename" > ticlang.sha256sum +curl -OL "https://cmake.org/files/dependencies/internal/$filename" +$shatool --check ticlang.sha256sum +chmod +x "$filename" +"./$filename" --mode unattended --prefix . +mv "$dirname" ticlang +rm -f "$filename" ticlang.sha256sum diff --git a/.gitlab/ci/typos.bash b/.gitlab/ci/typos.bash new file mode 100755 index 00000000000..4c923830eb0 --- /dev/null +++ b/.gitlab/ci/typos.bash @@ -0,0 +1,23 @@ +#!/bin/sh + +set -e + +result=0 + +echo "Running 'typos' on source code..." +typos || result=1 + +cfg='.typos.toml' +tmp_cfg="${TEMP:-/tmp}/$cfg" +# Uncomment `extend-ignore-identifiers-re` in the top-level config file +# to make Git hashes (possibly used in commit messages) valid "identifiers". +sed 's/^#\s*\(extend-ignore-identifiers-re\)/\1/' "$cfg" >"$tmp_cfg" + +if [ -n "$CI_MERGE_REQUEST_DIFF_BASE_SHA" ]; then + for COMMIT in $(git rev-list "^$CI_MERGE_REQUEST_DIFF_BASE_SHA" "$CI_COMMIT_SHA"); do + echo "Running 'typos' on commit message of $COMMIT..." + git show --format=%B -s "$COMMIT" | typos -c "$tmp_cfg" - || result=1 + done +fi + +exit $result diff --git a/.gitlab/ci/vcvarsall.ps1 b/.gitlab/ci/vcvarsall.ps1 old mode 100755 new mode 100644 index f91b1008400..f8c4150ef03 --- a/.gitlab/ci/vcvarsall.ps1 +++ b/.gitlab/ci/vcvarsall.ps1 @@ -1,9 +1,7 @@ $erroractionpreference = "stop" -cmd /c "`"$env:VCVARSALL`" $env:VCVARSPLATFORM -vcvars_ver=$env:VCVARSVERSION & set" | -foreach { - if ($_ -match "=") { - $v = $_.split("=") - [Environment]::SetEnvironmentVariable($v[0], $v[1]) - } +$all_env = cmd /c "`"$env:VCVARSALL`" $env:VCVARSPLATFORM -vcvars_ver=$env:VCVARSVERSION >NUL & powershell -Command `"Get-ChildItem env: | Select-Object -Property Key,Value | ConvertTo-Json`"" | ConvertFrom-Json + +foreach ($envvar in $all_env) { + [Environment]::SetEnvironmentVariable($envvar.Key, $envvar.Value) } diff --git a/.gitlab/ci/wix.ps1 b/.gitlab/ci/wix.ps1 deleted file mode 100755 index b7cb3f3402b..00000000000 --- a/.gitlab/ci/wix.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -$erroractionpreference = "stop" - -$release = "v3.14.0.6526" -$sha256sum = "4C89898DF3BCAB13E12F7CA54399C35AD273475AD2CB6284611D00AE2D063C2C" -$filename = "wix-3.14.0.6526-win-i386" -$tarball = "$filename.zip" - -$outdir = $pwd.Path -$outdir = "$outdir\.gitlab" -$ProgressPreference = 'SilentlyContinue' -#Invoke-WebRequest -Uri "https://wixtoolset.org/downloads/$release/$tarball" -OutFile "$outdir\$tarball" -Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/$tarball" -OutFile "$outdir\$tarball" -$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256 -if ($hash.Hash -ne $sha256sum) { - exit 1 -} - -Add-Type -AssemblyName System.IO.Compression.FileSystem -[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir\wix\bin") diff --git a/.gitlab/ci/wix3.ps1 b/.gitlab/ci/wix3.ps1 new file mode 100644 index 00000000000..8f5ae4bc696 --- /dev/null +++ b/.gitlab/ci/wix3.ps1 @@ -0,0 +1,21 @@ +$erroractionpreference = "stop" + +$release = "wix314rtm" +$sha256sum = "13F067F38969FAF163D93A804B48EA0576790A202C8F10291F2000F0E356E934" +#$filename = "wix314-binaries" +$filename = "wix-3.14.0.8606-win-i386" +$tarball = "$filename.zip" + +$outdir = $pwd.Path +$outdir = "$outdir\.gitlab" +$ProgressPreference = 'SilentlyContinue' +#Invoke-WebRequest -Uri "https://github.com/wixtoolset/wix3/releases/download/$release/$tarball" -OutFile "$outdir\$tarball" +Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/$tarball" -OutFile "$outdir\$tarball" +$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256 +if ($hash.Hash -ne $sha256sum) { + exit 1 +} + +Add-Type -AssemblyName System.IO.Compression.FileSystem +[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir\wix3") +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/ci/wix4-env.ps1 b/.gitlab/ci/wix4-env.ps1 new file mode 100644 index 00000000000..f1fe8b6c5c6 --- /dev/null +++ b/.gitlab/ci/wix4-env.ps1 @@ -0,0 +1,7 @@ +& "$pwsh" -File .gitlab/ci/wix4.ps1 + +$pwdpath = $pwd.Path +Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\wix4;$env:PATH" +Set-Item -Force -Path "env:WIX_EXTENSIONS" -Value "$pwdpath\.gitlab\wix4" +Write-Host "wix version: $(wix --version)" +Write-Host "wix extensions: $(wix extension list -g)" diff --git a/.gitlab/ci/wix4.ps1 b/.gitlab/ci/wix4.ps1 new file mode 100644 index 00000000000..6209f2bf3fb --- /dev/null +++ b/.gitlab/ci/wix4.ps1 @@ -0,0 +1,20 @@ +$erroractionpreference = "stop" + +$version = "4.0.4" +$sha256sum = "FB6E94C89B12FB65D3AA0CF9E9C630DAFCC7D57F1E66C7D6035CAD37A38CC284" +$filename = "wix-$version-win-any-1" +$tarball = "$filename.zip" + +$outdir = $pwd.Path +$outdir = "$outdir\.gitlab" +$ProgressPreference = 'SilentlyContinue' +Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/$tarball" -OutFile "$outdir\$tarball" +$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256 +if ($hash.Hash -ne $sha256sum) { + exit 1 +} + +Add-Type -AssemblyName System.IO.Compression.FileSystem +[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir") +Move-Item -Path "$outdir\$filename" -Destination "$outdir\wix4" +Remove-Item "$outdir\$tarball" diff --git a/.gitlab/os-linux.yml b/.gitlab/os-linux.yml index 451a8e2e7a4..9f084163557 100644 --- a/.gitlab/os-linux.yml +++ b/.gitlab/os-linux.yml @@ -5,17 +5,17 @@ ### Release .linux_prep_source: - image: "fedora:38" + image: "fedora:42" variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" .linux_release_x86_64: - image: "kitware/cmake:build-linux-x86_64-deps-2020-04-02@sha256:77e9ab183f34680990db9da5945473e288f0d6556bce79ecc1589670d656e157" + image: "kitware/cmake:build-linux-x86_64-deps-2023-08-16@sha256:aa0ebdbd90a51cc83d31f393c5c48ec4599a28f7ccdc288558522c6265b24fae" variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" - LAUNCHER: "scl enable devtoolset-6 rh-python36 --" + LAUNCHER: "scl enable devtoolset-7 --" CMAKE_ARCH: x86_64 .linux_release_aarch64: @@ -30,37 +30,51 @@ variables: BOOTSTRAP_ARGS: '-- "-DCMake_DOC_ARTIFACT_PREFIX=$CI_PROJECT_DIR/build/install-doc"' -.needs_centos6_x86_64: - dependencies: - - b:centos6-x86_64 +.sunos_release_x86_64: + image: "kitware/cmake:build-sunos-x86_64-deps-2025-02-27" + + variables: + GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" + CMAKE_ARCH: x86_64 + +.sunos_release_sparc64: + image: "kitware/cmake:build-sunos-sparc64-deps-2025-02-27" + + variables: + GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" + CMAKE_ARCH: sparc64 + +.sunos_package: + variables: + CMake_DOC_ARTIFACT_PREFIX: "$CI_PROJECT_DIR/build/install-doc" + +.needs_centos7_x86_64: needs: - - b:centos6-x86_64 + - b:centos7-x86_64 .needs_centos7_aarch64: - dependencies: - - b:centos7-aarch64 needs: - b:centos7-aarch64 ### Debian -.debian10: - image: "kitware/cmake:ci-debian10-x86_64-2023-03-29" +.debian12: + image: "kitware/cmake:ci-debian12-x86_64-2025-03-31" variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" CMAKE_ARCH: x86_64 -.debian10_iwyu: - extends: .debian10 +.debian12_iwyu: + extends: .debian12 variables: - CMAKE_CONFIGURATION: debian10_iwyu + CMAKE_CONFIGURATION: debian12_iwyu CTEST_NO_WARNINGS_ALLOWED: 1 CMAKE_CI_NO_INSTALL: 1 -.debian10_aarch64: - image: "kitware/cmake:ci-debian10-aarch64-2023-03-29" +.debian12_aarch64: + image: "kitware/cmake:ci-debian12-aarch64-2025-03-31" variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" @@ -68,156 +82,202 @@ ### Fedora -.fedora38: - image: "kitware/cmake:ci-fedora38-x86_64-2023-05-22" +.fedora42: + image: "kitware/cmake:ci-fedora42-x86_64-2025-04-22" variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci/long file name for testing purposes" CMAKE_ARCH: x86_64 +.fedora42_hip: + image: "kitware/cmake:ci-fedora42-hip-x86_64-2025-04-22" + + variables: + # FIXME(rocclr): device modules fail loading from binaries in paths with spaces + GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake-ci" + CMAKE_ARCH: x86_64 + #### Lint builds -.fedora38_tidy: - extends: .fedora38 +.fedora42_tidy: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_tidy - CTEST_NO_WARNINGS_ALLOWED: 1 + CMAKE_CONFIGURATION: fedora42_tidy CMAKE_CI_NO_INSTALL: 1 -.fedora38_clang_analyzer: - extends: .fedora38 +.fedora42_clang_analyzer: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_clang_analyzer + CMAKE_CONFIGURATION: fedora42_clang_analyzer CMAKE_CI_BUILD_TYPE: Debug CTEST_NO_WARNINGS_ALLOWED: 1 CMAKE_CI_NO_INSTALL: 1 -.fedora38_sphinx: - extends: .fedora38 +.fedora42_sphinx: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_sphinx + CMAKE_CONFIGURATION: fedora42_sphinx CTEST_NO_WARNINGS_ALLOWED: 1 CTEST_SOURCE_SUBDIRECTORY: "Utilities/Sphinx" CMAKE_CI_NO_INSTALL: 1 -.fedora38_sphinx_package: - extends: .fedora38 +.fedora42_sphinx_package: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_sphinx_package + CMAKE_CONFIGURATION: fedora42_sphinx_package CTEST_SOURCE_SUBDIRECTORY: "Utilities/Sphinx" #### Build and test -.debian10_ninja: - extends: .debian10 +.debian12_ninja: + extends: .debian12 variables: - CMAKE_CONFIGURATION: debian10_ninja + CMAKE_CONFIGURATION: debian12_ninja CTEST_NO_WARNINGS_ALLOWED: 1 -.debian10_aarch64_ninja: - extends: .debian10_aarch64 +.debian12_aarch64_ninja: + extends: .debian12_aarch64 variables: - CMAKE_CONFIGURATION: debian10_aarch64_ninja + CMAKE_CONFIGURATION: debian12_aarch64_ninja CTEST_NO_WARNINGS_ALLOWED: 1 -.debian10_makefiles_inplace: - extends: .debian10 +.debian12_makefiles_inplace: + extends: .debian12 variables: - CMAKE_CONFIGURATION: debian10_makefiles_inplace + CMAKE_CONFIGURATION: debian12_makefiles_inplace CMAKE_GENERATOR: "Unix Makefiles" CMAKE_CI_BOOTSTRAP: 1 CMAKE_CI_INPLACE: 1 CMAKE_CI_NO_INSTALL: 1 CTEST_NO_WARNINGS_ALLOWED: 1 -.debian10_extdeps: - extends: .debian10 +.debian12_ninja_multi_symlinked: + extends: .debian12 variables: - CMAKE_CONFIGURATION: debian10_extdeps + CMAKE_CONFIGURATION: debian12_ninja_multi_symlinked + CMAKE_GENERATOR: "Ninja Multi-Config" + CTEST_NO_WARNINGS_ALLOWED: 1 + CMAKE_CI_IN_SYMLINK_TREE: 1 + CMAKE_CI_BUILD_DIR: "real_work/work/build" + +.debian12_extdeps: + extends: .debian12 + + variables: + CMAKE_CONFIGURATION: debian12_extdeps CMAKE_CI_BUILD_TYPE: Release CTEST_NO_WARNINGS_ALLOWED: 1 -.debian10_aarch64_extdeps: - extends: .debian10_aarch64 +.debian12_aarch64_extdeps: + extends: .debian12_aarch64 variables: - CMAKE_CONFIGURATION: debian10_aarch64_extdeps + CMAKE_CONFIGURATION: debian12_aarch64_extdeps CMAKE_CI_BUILD_TYPE: Release CTEST_NO_WARNINGS_ALLOWED: 1 -.fedora38_extdeps: - extends: .fedora38 +.fedora42_extdeps: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_extdeps + CMAKE_CONFIGURATION: fedora42_extdeps CMAKE_CI_BUILD_TYPE: Release CTEST_NO_WARNINGS_ALLOWED: 1 -.fedora38_ninja: - extends: .fedora38 +.fedora42_ninja: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_ninja + CMAKE_CONFIGURATION: fedora42_ninja CMAKE_CI_BUILD_TYPE: Release CTEST_NO_WARNINGS_ALLOWED: 1 -.fedora38_ninja_multi: - extends: .fedora38 +.fedora42_ninja_multi: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_ninja_multi + CMAKE_CONFIGURATION: fedora42_ninja_multi CTEST_NO_WARNINGS_ALLOWED: 1 CMAKE_GENERATOR: "Ninja Multi-Config" -.fedora38_makefiles: - extends: .fedora38 +.fedora42_makefiles: + extends: .fedora42 + + variables: + CMAKE_CONFIGURATION: fedora42_makefiles + CTEST_NO_WARNINGS_ALLOWED: 1 + CMAKE_GENERATOR: "Unix Makefiles" + +.fedora42_makefiles_symlinked: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_makefiles + CMAKE_CONFIGURATION: fedora42_makefiles_symlinked CTEST_NO_WARNINGS_ALLOWED: 1 CMAKE_GENERATOR: "Unix Makefiles" + CMAKE_CI_IN_SYMLINK_TREE: 1 + CMAKE_CI_BUILD_DIR: "real_work/work/build" ### Clang Compiler -.debian10_makefiles_clang: - extends: .debian10 +.debian12_makefiles_clang: + extends: .debian12 variables: - CMAKE_CONFIGURATION: debian10_makefiles_clang + CMAKE_CONFIGURATION: debian12_makefiles_clang CMAKE_GENERATOR: "Unix Makefiles" -.debian10_ninja_clang: - extends: .debian10 +.debian12_ninja_clang: + extends: .debian12 variables: - CMAKE_CONFIGURATION: debian10_ninja_clang + CMAKE_CONFIGURATION: debian12_ninja_clang + +.fedora42_makefiles_clang: + extends: .fedora42 + + variables: + CMAKE_CONFIGURATION: fedora42_makefiles_clang + CMAKE_GENERATOR: "Unix Makefiles" -.fedora38_makefiles_clang: - extends: .fedora38 +.fedora42_makefiles_lfortran: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_makefiles_clang + # FIXME(lfortran): -rpath flags with spaces not forwarded + GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake-ci" + CMAKE_CONFIGURATION: fedora42_makefiles_lfortran CMAKE_GENERATOR: "Unix Makefiles" + CTEST_LABELS: "Fortran" -.fedora38_ninja_clang: - extends: .fedora38 +.fedora42_ninja_lfortran: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_ninja_clang + # FIXME(lfortran): -rpath flags with spaces not forwarded + GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake-ci" + CMAKE_CONFIGURATION: fedora42_ninja_lfortran + CTEST_LABELS: "Fortran" -.fedora38_ninja_multi_clang: - extends: .fedora38 +.fedora42_ninja_clang: + extends: .fedora42 variables: - CMAKE_CONFIGURATION: fedora38_ninja_multi_clang + CMAKE_CONFIGURATION: fedora42_ninja_clang + +.fedora42_ninja_multi_clang: + extends: .fedora42 + + variables: + CMAKE_CONFIGURATION: fedora42_ninja_multi_clang CMAKE_GENERATOR: "Ninja Multi-Config" ### Sanitizers @@ -233,13 +293,13 @@ CTEST_MEMORYCHECK_TYPE: AddressSanitizer CTEST_MEMORYCHECK_SANITIZER_OPTIONS: "" -.fedora38_asan: +.fedora42_asan: extends: - - .fedora38 + - .fedora42 - .fedora_asan_addon variables: - CMAKE_CONFIGURATION: fedora38_asan + CMAKE_CONFIGURATION: fedora42_asan ### Intel Compiler @@ -265,7 +325,7 @@ ### NVHPC Compiler .nvhpc: - image: "kitware/cmake:ci-nvhpc22.11-x86_64-2022-12-06" + image: "kitware/cmake:ci-nvhpc24.9-x86_64-2024-09-27" variables: CMAKE_ARCH: x86_64 @@ -342,33 +402,104 @@ CMAKE_CONFIGURATION: cuda11.8_minimal_nvidia CTEST_NO_WARNINGS_ALLOWED: 1 +.cuda11.8_splayed_nvidia: + extends: .cuda11.8_minimal + variables: + CMAKE_CONFIGURATION: cuda11.8_splayed_nvidia + CTEST_NO_WARNINGS_ALLOWED: 1 + +.cuda12.2: + extends: .cuda + image: "kitware/cmake:ci-cuda12.2-x86_64-2024-09-25" + variables: + CMAKE_ARCH: x86_64 + +.cuda12.2_nvidia: + extends: .cuda12.2 + variables: + CMAKE_CONFIGURATION: cuda12.2_nvidia + CTEST_NO_WARNINGS_ALLOWED: 1 + +.cuda12.2_clang: + extends: .cuda12.2 + variables: + CMAKE_CONFIGURATION: cuda12.2_clang + CTEST_NO_WARNINGS_ALLOWED: 1 + +.cuda12.6: + extends: .cuda + image: "kitware/cmake:ci-cuda12.6-x86_64-2025-01-30" + variables: + CMAKE_ARCH: x86_64 + +.cuda12.6_nvidia: + extends: .cuda12.6 + variables: + CMAKE_CONFIGURATION: cuda12.6_nvidia + CTEST_NO_WARNINGS_ALLOWED: 1 + +.cuda12.6_nvidia_clang: + extends: .cuda12.6 + variables: + CMAKE_CONFIGURATION: cuda12.6_nvidia_clang + CTEST_NO_WARNINGS_ALLOWED: 1 + +.cuda12.6_clang: + extends: .cuda12.6 + variables: + CMAKE_CONFIGURATION: cuda12.6_clang + CTEST_NO_WARNINGS_ALLOWED: 1 + ### HIP builds -.hip5.5: - image: "kitware/cmake:ci-hip5.5-x86_64-2023-06-01" +.hip6.3: + image: "kitware/cmake:ci-hip6.3-x86_64-2025-02-14" variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" CMAKE_ARCH: x86_64 CTEST_LABELS: "HIP" -.hip5.5_radeon: - extends: .hip5.5 +.hip6.3_radeon: + extends: .hip6.3 variables: - CMAKE_CONFIGURATION: hip5.5_radeon + # FIXME(rocclr): device modules fail loading from binaries in paths with spaces + GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake-ci" + CMAKE_CONFIGURATION: hip6.3_radeon CMAKE_GENERATOR: "Ninja Multi-Config" +.debian12_hip_radeon: + extends: .debian12 + + variables: + CMAKE_CONFIGURATION: debian12_hip_radeon + CTEST_LABELS: "HIP" + +.fedora42_hip_radeon: + extends: .fedora42_hip + + variables: + CMAKE_CONFIGURATION: fedora42_hip_radeon + CTEST_LABELS: "HIP" + +.hip6.3_nvidia: + extends: .hip6.3 + + variables: + CMAKE_CONFIGURATION: hip6.3_nvidia + CTEST_LABELS: "HIP" + ### C++ modules .gcc_cxx_modules_x86_64: - image: "kitware/cmake:ci-gcc_cxx_modules-x86_64-2022-06-21" + image: "kitware/cmake:ci-gcc_cxx_modules-x86_64-2024-12-23" variables: GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" CMAKE_ARCH: x86_64 - CC: "/opt/gcc-p1689/bin/gcc" - CXX: "/opt/gcc-p1689/bin/g++" + CC: "/opt/gcc-importstd/bin/gcc" + CXX: "/opt/gcc-importstd/bin/g++" .gcc_cxx_modules_ninja: extends: .gcc_cxx_modules_x86_64 @@ -383,6 +514,22 @@ CMAKE_CONFIGURATION: linux_gcc_cxx_modules_ninja_multi CMAKE_GENERATOR: "Ninja Multi-Config" +### Debian 10 legacy packages + +.debian10: + image: "kitware/cmake:ci-debian10-x86_64-2023-07-31" + + variables: + GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci" + CMAKE_ARCH: x86_64 + +.debian10_legacy: + extends: .debian10 + + variables: + CMAKE_CONFIGURATION: debian10_legacy + CTEST_LABELS: "Python2" + ## Tags .linux_x86_64_tags: @@ -392,6 +539,13 @@ - docker - linux-x86_64 +.linux_x86_64_v3_tags: + tags: + - cmake + - build + - docker + - linux-x86_64-v3 + .linux_x86_64_tags_x11: tags: - cmake @@ -399,24 +553,48 @@ - linux-x86_64 - x11 -.linux_x86_64_tags_cuda: +.linux_x86_64_tags_cuda_arch_30: + tags: + - cmake + - cuda-arch-30 + - docker + - linux-x86_64 + +.linux_x86_64_tags_cuda_arch_52: tags: - cmake - - cuda-rt + - cuda-arch-52 - docker - linux-x86_64 -.linux_x86_64_v3_tags_cuda: +.linux_x86_64_v3_tags_cuda_arch_52: tags: - cmake - - cuda-rt + - cuda-arch-52 - docker - linux-x86_64-v3 -.linux_x86_64_tags_radeon: +.linux_x86_64_tags_rocm5.2: + tags: + - cmake + - radeon + - rocm-5.2 + - docker + - linux-x86_64 + +.linux_x86_64_tags_rocm6.2: tags: - cmake - radeon + - rocm-6.2 + - docker + - linux-x86_64 + +.linux_x86_64_tags_rocm6.3: + tags: + - cmake + - radeon + - rocm-6.3 - docker - linux-x86_64 @@ -431,12 +609,8 @@ .before_script_linux: &before_script_linux - source .gitlab/ci/env.sh - - .gitlab/ci/cmake.sh - - export PATH=$PWD/.gitlab/cmake/bin:$PATH - - .gitlab/ci/ninja.sh - - export PATH=$PWD/.gitlab:$PATH - - cmake --version - - ninja --version + - source .gitlab/ci/cmake-env.sh + - source .gitlab/ci/ninja-env.sh .cmake_prep_source_linux: stage: prep @@ -446,8 +620,8 @@ - dnf install --setopt=install_weak_deps=False -y git-core - v="$(.gitlab/ci/cmake_version.sh)" - mkdir -p build/ - - git archive --format=tgz "--prefix=cmake-$v/" -o "build/cmake-$v.tar.gz" HEAD - - git -c core.autocrlf=true -c core.eol=crlf archive --format=zip --prefix="cmake-$v/" -o "build/cmake-$v.zip" HEAD + - git archive --format=tgz --prefix="cmake-$v/" -o "build/cmake-$v.tar.gz" HEAD + - git archive --format=zip --prefix="cmake-$v/" -o "build/cmake-$v.zip" HEAD interruptible: true @@ -461,11 +635,19 @@ interruptible: true -.cmake_codespell_linux: +.cmake_version_update_linux: stage: build - extends: .fedora38 + extends: .fedora42 script: - - .gitlab/ci/codespell.sh + - .gitlab/ci/cmake_version_update.sh + interruptible: false # The job internally fetches and retries. + +.cmake_spellcheck_linux: + stage: build + extends: .fedora42 + script: + - .gitlab/ci/codespell.bash + - .gitlab/ci/typos.bash interruptible: true .cmake_build_linux: @@ -511,8 +693,7 @@ - mkdir -p build/ - cp Utilities/Release/linux/$CMAKE_ARCH/cache.txt build/CMakeCache.txt # Make sccache available. - - .gitlab/ci/sccache.sh - - export PATH=$PWD/.gitlab:$PATH + - source .gitlab/ci/sccache-env.sh # Append sccache settings to the cache. - echo "CMAKE_C_COMPILER_LAUNCHER:STRING=sccache" >> build/CMakeCache.txt - echo "CMAKE_CXX_COMPILER_LAUNCHER:STRING=sccache" >> build/CMakeCache.txt @@ -588,7 +769,7 @@ - .cmake_test_linux_release - .linux_x86_64_tags - .run_manually - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" @@ -598,16 +779,43 @@ - .cmake_test_linux_release - .linux_x86_64_tags - .run_manually - - .needs_centos6_x86_64 + - .needs_centos7_x86_64 variables: CMAKE_CI_JOB_NIGHTLY: "true" +.cmake_build_sunos_release: + stage: build + + script: + - *before_script_linux + # SunOS sysroot + - Utilities/Release/sunos/docker/sysroot.bash $CMAKE_ARCH + # Initial cache + - mkdir -p build/ + - cp Utilities/Release/sunos/$CMAKE_ARCH/cache.txt build/CMakeCache.txt + # Make sccache available. + - source .gitlab/ci/sccache-env.sh + - echo "CMAKE_C_COMPILER_LAUNCHER:STRING=sccache" >> build/CMakeCache.txt + - echo "CMAKE_CXX_COMPILER_LAUNCHER:STRING=sccache" >> build/CMakeCache.txt + # Build + - cd build/ + - cmake .. -GNinja + -DCMAKE_DOC_DIR=doc/cmake + -DCMake_DOC_ARTIFACT_PREFIX="$CMake_DOC_ARTIFACT_PREFIX" + -DCMAKE_TOOLCHAIN_FILE="$CI_PROJECT_DIR/Utilities/Release/sunos/$CMAKE_ARCH/toolchain.cmake" + - ninja + # Package + - cpack -G "TGZ;STGZ" + - sccache --show-stats + + interruptible: true + ### Documentation .cmake_org_help: stage: build extends: - - .fedora38 + - .fedora42 - .linux_x86_64_tags - .cmake_org_help_artifacts script: @@ -621,5 +829,3 @@ -DCMake_SPHINX_CMAKE_ORG_OUTDATED=$CMAKE_CI_SPHINX_OUTDATED -DCMake_VERSION_NO_GIT=$CMAKE_CI_VERSION_NO_GIT - ninja - # FIXME(#25175): non-main index entries are scored too high. - - sed -i '/search for explicit entries in index directives/,/^$/d' html/_static/searchtools.js diff --git a/.gitlab/os-macos.yml b/.gitlab/os-macos.yml index 09d75984929..f9c344e6056 100644 --- a/.gitlab/os-macos.yml +++ b/.gitlab/os-macos.yml @@ -7,7 +7,7 @@ GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci ext/$CI_CONCURRENT_ID" # TODO: Factor this out so that each job selects the Xcode version to # use so that different versions can be tested in a single pipeline. - DEVELOPER_DIR: "/Applications/Xcode-14.3.app/Contents/Developer" + DEVELOPER_DIR: "/Applications/Xcode-26.0.app/Contents/Developer" # Avoid conflicting with other projects running on the same machine. SCCACHE_SERVER_PORT: 4227 @@ -29,6 +29,7 @@ variables: CMAKE_CONFIGURATION: macos_x86_64_ninja + CMAKE_CI_BUILD_TYPE: Release CTEST_NO_WARNINGS_ALLOWED: 1 .macos_arm64_ninja: @@ -36,8 +37,31 @@ variables: CMAKE_CONFIGURATION: macos_arm64_ninja + CMAKE_CI_BUILD_TYPE: Release CTEST_NO_WARNINGS_ALLOWED: 1 +.macos_arm64_ninja_symlinked: + extends: .macos_build + + variables: + CMAKE_CONFIGURATION: macos_arm64_ninja_symlinked + CTEST_NO_WARNINGS_ALLOWED: 1 + CMAKE_CI_IN_SYMLINK_TREE: 1 + CMAKE_CI_BUILD_DIR: "real_work/work/build" + +.macos_arm64_curl: + extends: .macos_build + + variables: + CMAKE_CONFIGURATION: macos_arm64_curl + CTEST_NO_WARNINGS_ALLOWED: 1 + +.macos_arm64_pch: + extends: .macos_arm64_ninja + + variables: + CMAKE_CONFIGURATION: macos_arm64_pch + .macos_x86_64_makefiles: extends: .macos_build @@ -80,6 +104,16 @@ CMAKE_GENERATOR: Xcode CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true" +.macos_arm64_xcode_symlinked: + extends: .macos + + variables: + CMAKE_CONFIGURATION: macos_arm64_xcode_symlinked + CMAKE_GENERATOR: Xcode + CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true" + CMAKE_CI_IN_SYMLINK_TREE: 1 + CMAKE_CI_BUILD_DIR: "real_work/work/build" + .macos_arm64_xcode_ub: extends: .macos @@ -110,7 +144,7 @@ - cmake # Since this is a bare runner, pin to a project. - macos-x86_64 - shell - - xcode-14.3 + - xcode-26.0 - nonconcurrent .macos_x86_64_tags_ext: @@ -118,7 +152,7 @@ - cmake # Since this is a bare runner, pin to a project. - macos-x86_64 - shell - - xcode-14.3 + - xcode-26.0 - concurrent .macos_arm64_tags: @@ -126,7 +160,7 @@ - cmake # Since this is a bare runner, pin to a project. - macos-arm64 - shell - - xcode-14.3 + - xcode-26.0 - nonconcurrent .macos_arm64_tags_ext: @@ -134,7 +168,7 @@ - cmake # Since this is a bare runner, pin to a project. - macos-arm64 - shell - - xcode-14.3 + - xcode-26.0 - concurrent .macos_arm64_tags_package: @@ -142,7 +176,7 @@ - cmake # Since this is a bare runner, pin to a project. - macos-arm64 - shell - - xcode-14.3 + - xcode-26.0 - nonconcurrent - finder @@ -150,12 +184,8 @@ .before_script_macos: &before_script_macos - source .gitlab/ci/env.sh - - .gitlab/ci/cmake.sh - - export PATH=$PWD/.gitlab/cmake/bin:$PATH - - .gitlab/ci/ninja.sh - - export PATH=$PWD/.gitlab:$PATH - - cmake --version - - ninja --version + - source .gitlab/ci/cmake-env.sh + - source .gitlab/ci/ninja-env.sh # Download Qt - cmake -P .gitlab/ci/download_qt.cmake - export CMAKE_PREFIX_PATH=$PWD/.gitlab/qt${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH} diff --git a/.gitlab/os-windows.yml b/.gitlab/os-windows.yml index 026f2f44223..a37733e14ec 100644 --- a/.gitlab/os-windows.yml +++ b/.gitlab/os-windows.yml @@ -26,34 +26,47 @@ variables: # Debug and RelWithDebinfo build types use the `/Zi` which results in - # uncacheable compiations. + # uncacheable compilations. # https://github.com/mozilla/sccache/issues/242 CMAKE_CI_BUILD_TYPE: Release CTEST_NO_WARNINGS_ALLOWED: 1 +.windows_vcvarsall_vs2022_x64_msvc14.43: + variables: + VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat" + VCVARSPLATFORM: "x64" + VCVARSVERSION: "14.43.34808" + .windows_vcvarsall_vs2022_x64: variables: VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat" VCVARSPLATFORM: "x64" - VCVARSVERSION: "14.36.32532" + VCVARSVERSION: "14.44.35207" .windows_vcvarsall_vs2022_x86: variables: VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat" VCVARSPLATFORM: "x86" - VCVARSVERSION: "14.36.32532" + VCVARSVERSION: "14.44.35207" .windows_vcvarsall_vs2022_x64_arm64: variables: VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat" VCVARSPLATFORM: "x64_arm64" - VCVARSVERSION: "14.36.32532" + VCVARSVERSION: "14.44.35207" .windows_arm64_vcvarsall_vs2022: variables: VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat" VCVARSPLATFORM: "arm64" - VCVARSVERSION: "14.36.32532" + VCVARSVERSION: "14.44.35207" + +.windows_vs2022_x64_pch: + extends: + - .windows_vs2022_x64_ninja + + variables: + CMAKE_CONFIGURATION: windows_vs2022_x64_pch .windows_vs2022_x64_ninja: extends: @@ -112,7 +125,7 @@ CMAKE_CONFIGURATION: windows_vs2022_x64 CMAKE_GENERATOR: "Visual Studio 17 2022" CMAKE_GENERATOR_PLATFORM: "x64" - CMAKE_GENERATOR_TOOLSET: "v143,version=14.36.32532" + CMAKE_GENERATOR_TOOLSET: "v143,version=14.44.35207" CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true" .windows_vs2019_x64: @@ -225,7 +238,7 @@ .windows_intelclassic_ninja: extends: - .windows_ninja - - .windows_vcvarsall_vs2022_x64 + - .windows_vcvarsall_vs2022_x64_msvc14.43 variables: CMAKE_CONFIGURATION: windows_intelclassic_ninja @@ -254,6 +267,20 @@ variables: CMAKE_CONFIGURATION: windows_openwatcom1.9 +.windows_orangec: + extends: .windows + + variables: + CMAKE_GENERATOR: "Ninja" + CMAKE_CI_BUILD_TYPE: Release + CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true" + +.windows_orangec6.73.1: + extends: .windows_orangec + + variables: + CMAKE_CONFIGURATION: windows_orangec6.73.1 + .windows_arm64_vs2022: extends: .windows @@ -261,7 +288,7 @@ CMAKE_CONFIGURATION: windows_arm64_vs2022 CMAKE_GENERATOR: "Visual Studio 17 2022" CMAKE_GENERATOR_PLATFORM: "ARM64" - CMAKE_GENERATOR_TOOLSET: "v143,version=14.36.32532" + CMAKE_GENERATOR_TOOLSET: "v143,version=14.44.35207" CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true" .mingw_osdn_io: @@ -289,13 +316,21 @@ ## Tags +.windows_x86_64_tags_nonconcurrent_sign: + tags: + - cmake # Since this is a bare runner, pin to a project. + - windows-x86_64 + - shell + - sign-windows-v1 + - nonconcurrent + .windows_x86_64_tags_nonconcurrent_vs2022: tags: - cmake # Since this is a bare runner, pin to a project. - windows-x86_64 - shell - vs2022 - - msvc-19.36 + - msvc-14.44 - nonconcurrent .windows_x86_64_tags_nonconcurrent_vs2022_arm64: @@ -304,7 +339,7 @@ - windows-x86_64 - shell - vs2022 - - msvc-19.36-arm64 + - msvc-14.44-arm64 - nonconcurrent .windows_x86_64_tags_concurrent_vs2022: @@ -313,15 +348,35 @@ - windows-x86_64 - shell - vs2022 - - msvc-19.36 + - msvc-14.44 + - concurrent + +.windows_x86_64_tags_concurrent_vs2022_msvc14.43: + tags: + - cmake # Since this is a bare runner, pin to a project. + - windows-x86_64 + - shell + - vs2022 + - msvc-14.43 - concurrent -.windows_x86_64_tags_concurrent_vs2019: +.windows_x86_64_tags_concurrent_vs2022_android: + tags: + - cmake # Since this is a bare runner, pin to a project. + - windows-x86_64 + - shell + - vs2022 + - vs17-android + - msvc-14.44 + - concurrent + +.windows_x86_64_tags_concurrent_vs2019_android: tags: - cmake # Since this is a bare runner, pin to a project. - windows-x86_64 - shell - vs2019 + - vs16-android - msvc-19.29-16.11 - concurrent @@ -338,7 +393,7 @@ - windows-arm64 - shell - vs2022 - - msvc-19.36 + - msvc-14.44 - nonconcurrent .windows_arm64_tags_concurrent_vs2022: @@ -347,21 +402,15 @@ - windows-arm64 - shell - vs2022 - - msvc-19.36 + - msvc-14.44 - concurrent ## Windows-specific scripts .before_script_windows: &before_script_windows - . .gitlab/ci/env.ps1 - - $pwdpath = $pwd.Path - - (& "$pwsh" -File ".gitlab/ci/wix.ps1") - - Set-Item -Force -Path "env:WIX" -Value "$pwdpath\.gitlab\wix" - - (& "$pwsh" -File ".gitlab/ci/cmake.ps1") - - Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\cmake\bin;$env:PATH" + - . .gitlab/ci/cmake-env.ps1 - . .gitlab/ci/ninja-env.ps1 - - (& "$env:WIX\bin\light.exe" -help) | Select -First 1 - - cmake --version - . .gitlab/ci/qt-env.ps1 - . .gitlab/ci/python-env.ps1 @@ -374,7 +423,7 @@ script: - *before_script_windows - - Set-Item -Force -Path "env:PATH" -Value "$env:PATH;$env:SCCACHE_PATH" + - . .gitlab/ci/sccache-env.ps1 - Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1 - sccache --start-server - sccache --show-stats @@ -387,6 +436,18 @@ interruptible: true +.cmake_package_windows: + stage: package + environment: + name: sign-windows + script: + - . .gitlab/ci/env.ps1 + - . .gitlab/ci/signtool-env.ps1 + - . .gitlab/ci/cmake-env.ps1 + - . .gitlab/ci/wix4-env.ps1 + - . .gitlab/ci/package_windows.ps1 + interruptible: true + .cmake_test_windows: stage: test diff --git a/.gitlab/rules.yml b/.gitlab/rules.yml index b85b7280e55..efb7290f76d 100644 --- a/.gitlab/rules.yml +++ b/.gitlab/rules.yml @@ -2,7 +2,9 @@ .run_manually: rules: - - if: '$CMAKE_CI_PACKAGE != null' + - if: '$CMAKE_CI_PACKAGE != null || $CMAKE_CI_VERSION_UPDATE != null' + when: never + - if: '($CMAKE_CI_NIGHTLY == "true" && $CMAKE_CI_JOB_NIGHTLY == "false")' when: never - if: '$CMAKE_CI_NIGHTLY == "true"' when: on_success @@ -23,7 +25,9 @@ .run_automatically: rules: - - if: '$CMAKE_CI_PACKAGE != null' + - if: '$CMAKE_CI_PACKAGE != null || $CMAKE_CI_VERSION_UPDATE != null' + when: never + - if: '($CMAKE_CI_NIGHTLY == "true" && $CMAKE_CI_JOB_NIGHTLY == "false")' when: never - if: '$CMAKE_CI_NIGHTLY == "true"' when: on_success @@ -44,7 +48,9 @@ .run_dependent: rules: - - if: '$CMAKE_CI_PACKAGE != null' + - if: '$CMAKE_CI_PACKAGE != null || $CMAKE_CI_VERSION_UPDATE != null' + when: never + - if: '($CMAKE_CI_NIGHTLY == "true" && $CMAKE_CI_JOB_NIGHTLY == "false")' when: never - if: '($CMAKE_CI_NIGHTLY == "true" && $CMAKE_CI_NIGHTLY_IGNORE_DEPS == "true")' when: always @@ -64,7 +70,9 @@ .run_only_for_package: rules: - - if: '$CMAKE_CI_PACKAGE == "dev"' + - if: '$CMAKE_CI_PACKAGE == "dev" && $CI_JOB_STAGE != "upload"' + when: on_success + - if: '$CMAKE_CI_PACKAGE == "dev" && $CI_JOB_STAGE == "upload"' variables: RSYNC_DESTINATION: "kitware@cmake.org:dev/" when: on_success @@ -80,6 +88,8 @@ .run_cmake_org_help: rules: + - if: '$CMAKE_CI_VERSION_UPDATE != null' + when: never - if: '$CMAKE_CI_PACKAGE =~ /v[0-9]+\.[0-9]+/' variables: RSYNC_DESTINATION: "kitware@cmake.org:$CMAKE_CI_PACKAGE/" @@ -99,3 +109,9 @@ CMAKE_CI_VERSION_NO_GIT: "OFF" when: on_success - when: never + +.run_version_update: + rules: + - if: '$CMAKE_CI_VERSION_UPDATE != null' + when: on_success + - when: never diff --git a/.gitlab/upload.yml b/.gitlab/upload.yml index caa2119c314..c489c5448b7 100644 --- a/.gitlab/upload.yml +++ b/.gitlab/upload.yml @@ -1,7 +1,7 @@ # Steps for uploading artifacts .rsync_upload_package: - image: "fedora:38" + image: "fedora:42" stage: upload tags: - cmake @@ -21,7 +21,7 @@ .rsync_upload_help: stage: upload - image: "fedora:38" + image: "fedora:42" tags: - cmake - docker diff --git a/.hooks-config b/.hooks-config index 064371c1238..320bf4c41c5 100644 --- a/.hooks-config +++ b/.hooks-config @@ -1,5 +1,5 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. +# file LICENSE.rst or https://cmake.org/licensing for details. # Loaded by .git/hooks/(pre-commit|commit-msg|prepare-commit-msg) # during git commit after local hooks have been installed. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..8b6d940ff43 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,146 @@ +# Read the docs here: https://pre-commit.com +# List of some available hooks: https://pre-commit.com/hooks.html +# +# Install `pre-commit`: +# +# $ pip install pre-commit +# +# Install hooks to your clone: +# $ pre-commit install +# + +default_stages: [pre-commit] +default_install_hook_types: [commit-msg, pre-commit] +fail_fast: false +# NOTE Exclude third-party sources and some files globally. +# See `Utilities/Scripts/update-*.bash` scripts and +# https://pre-commit.com/#regular-expressions +exclude: >- + (?x)Auxiliary/vim + | Licenses/.*\.txt$ + | Source/(CursesDialog/form|kwsys) + | Utilities/(cm.*|GitSetup|KWIML) + | .*\.patch$ + +repos: + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-case-conflict + - id: check-json + # NOTE Some tests have intentionally broken JSONs. + exclude: >- + (?x)Tests/( + CMakeLib/testCTestResourceSpec_data/spec13 + | RunCMake/CTestResourceAllocation/invalid + )\.json + - id: check-yaml + - id: end-of-file-fixer + # NOTE Exclude tests directory: some test files have no + # the trailing EOL in the file intentionally but some + # just missed it. + # TODO Add the missed trailing EOL in the following files + # if possible. + exclude: >- + (?x)Tests/( + DelphiCoverage/src/UTCovTest\.pas + | FortranModules/in_interface/module\.f90 + | JavascriptCoverage/output\.json\.in + | Module/ExternalData/Alt/( + MyAlgoMap1-md5/dded55e43cd6529ee35d24113dfc87a3 + | SHA1/85158f0c1996837976e858c42a9a7634bfe91b93 + ) + | RunCMake/( + CMP0055/CMP0055-(NEW|OLD)-Reject-Arguments\.cmake + | CommandLine/E_cat_good_binary_cat-stdout\.txt + | define_property/define_property-INITIALIZE_FROM_VARIABLE-invalid_[12]-result\.txt + | FindMatlab/MatlabTest2-stderr\.txt + | string/UTF-(16|32)LE\.txt + | Syntax/BOM-UTF-(16|32)-LE.cmake + | Syntax/CommandEOF\.cmake + | VS10Project/shader2?\.hlsl + ) + | SourceGroups/README\.txt + | StringFileTest/test\.utf8 + | VSWindowsFormsResx/WindowsFormsResx/MyForm\.resx + | VSXaml/Package\.appxmanifest + ) + - id: mixed-line-ending + - id: trailing-whitespace + + - repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: git-check + pass_filenames: false + + - repo: https://github.com/sphinx-contrib/sphinx-lint + rev: v1.0.0 + hooks: + - id: sphinx-lint + # NOTE Looks like `bad-dedent` gives too many false-positives. + args: ['--disable', 'bad-dedent'] + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-backticks + # NOTE The `productionlist` directive can give false-positives + exclude: >- + (?x)Help/( + dev/maint\.rst + | manual/cmake-(developer|language)\.7\.rst + | variable/CMAKE_MESSAGE_CONTEXT\.rst + ) + | Tests/CMakeLib/testRST\.rst + - id: rst-directive-colons + - id: rst-inline-touching-normal + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.0 + hooks: + - id: codespell + stages: [commit-msg, pre-commit] + + - repo: https://github.com/crate-ci/typos + rev: v1.30.0 + hooks: + - id: typos + # NOTE Override hook's default args to prevent automatic + # fixing of found typos. Let the dev decide what to fix! + args: ['--force-exclude'] + stages: [commit-msg, pre-commit] + + - repo: https://github.com/pre-commit/mirrors-clang-format + # ATTENTION CMake's `clang-format` is version 18. + # DO NOT UPDATE THIS VERSION unless officially supported + # `clang-format` get bumped. + rev: v18.1.8 + hooks: + - id: clang-format + types_or: [c, c++, cuda] + exclude_types: [objective-c++] + # The following exclude list based on the output of: + # $ git ls-files | git check-attr --stdin format.clang-format | grep ... + # and + # $ find -name .gitattributes -exec grep -Hn clang-format {} + + exclude: >- + (?x)( + Source/LexerParser + | Tests/( + CSharpLinkFromCxx/UsefulManagedCppClass.* + | CompileFeatures/cxx_right_angle_brackets\.cpp + | PositionIndependentTargets/pic_test\.h + | RunCMake/( + CommandLine/cmake_depends/test_UTF-16LE\.h + | CXXModules/examples/circular/circular-[ab]\.cppm + | GenerateExportHeader/reference + ) + | VSWinStorePhone/Direct3DApp1/Direct3DApp1\.cpp + ) + ) diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 00000000000..5ad2245aa7b --- /dev/null +++ b/.typos.toml @@ -0,0 +1,79 @@ +# The manual about all configuration options is here: +# https://github.com/crate-ci/typos/blob/master/docs/reference.md + +[default] +check-file = true +check-filename = true +extend-ignore-re = [ + # NOTE Allow to mark a block of text to exclude from spellchecking + "(?s)(#|/(/|\\*)|\\.\\.)\\s*(NOQA|noqa):? spellcheck(: *|=| +)off.*?\\n\\s*(#|/(/|\\*)|\\.\\.)\\s*(NOQA|noqa):? spellcheck(: *|=| +)on" + # NOTE Allow to mark a line to exclude from spellchecking + , "(?Rm)^.*(#|/(/|\\*)|\\.\\.)\\s*(NOQA|noqa):? spellcheck(: *|=| +)disable-line$" + # NOTE Stop checking from this line to the end of file + # This line is a marker added by Git to the `COMMIT_EDITMSG`. + , "(?sm)^# ------------------------ >8 ------------------------$.*" + ] +locale = "en-us" +# ATTENTION If, for any reason, you want to add the +# `extend-ignore-identifiers-re` to this section, +# please also modify the `.gitlab/ci/typos.bash` +# script accordingly. +#extend-ignore-identifiers-re=["\\b[0-9a-f]{10}\\b"] + +# Add repo-wide false positives here in the form of `word = "word"`. +# Check the manual for details. +[default.extend-words] +HPE = "HPE" +# British spelling of `XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER` property name. +BEHAVIOUR = "BEHAVIOUR" +# Misspelled `Fortran_BUILDING_INSTRINSIC_MODULES` property name kept for compatibility: `INSTRINSIC` should be `INTRINSIC`. +INSTRINSIC = "INSTRINSIC" +# This is a file extension for `cobertura-merge` +ser = "ser" +# The Ninja option name +restat = "restat" +# SpectreMitigation +Spectre = "Spectre" + +[type.cmake.extend-identifiers] +COMMANDs = "COMMANDs" +xCOMMANDx = "xCOMMANDx" +TYPEs = "TYPEs" + +[type.cmake.extend-words] +# Some compiler's options trigger false-positives +Fo = "Fo" +ot = "ot" +# Part of compiler executable name, e.g., `arm-unknown-nto-qnx6`, but also could be in a literal string. +nto = "nto" + +[type.cpp.extend-identifiers] +APPENDed = "APPENDed" + +[type.json.extend-identifiers] +# Some compiler options from `Templates/MSBuild/FlagTables/*.json` trigger too many false-positives. +Fo = "Fo" +fo = "fo" +Ot = "Ot" +SEH = "SEH" + +[type.py.extend-identifiers] +typ = "typ" + +[files] +ignore-hidden = false +ignore-dot = false +extend-exclude = [ + "CONTRIBUTORS.rst" + # Exclude third-party sources. + , "Source/CursesDialog/form/" + , "Source/kwsys/" + , "Source/bindexplib.cxx" + , "Source/cmcldeps.cxx" + , "Source/QtDialog/*.ui" + , "Utilities/cm*" + , "Utilities/ClangTidyModule" + , "Utilities/KWIML" + # FIXME: Fix spelling typos in tests. Exclude for now. + , "Tests" + ] diff --git a/Auxiliary/bash-completion/cmake b/Auxiliary/bash-completion/cmake index bed7248f70c..3c30d3442d8 100644 --- a/Auxiliary/bash-completion/cmake +++ b/Auxiliary/bash-completion/cmake @@ -2,14 +2,22 @@ _cmake() { - local cur prev words cword split=false - if type -t _init_completion >/dev/null; then - _init_completion -n = || return + local is_old_completion=false + local is_init_completion=false + + local cur prev words cword split was_split + if type -t _comp_initialize >/dev/null; then + _comp_initialize -s || return + elif type -t _init_completion >/dev/null; then + _init_completion -s || return + is_init_completion=true else # manual initialization for older bash completion versions COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" + is_old_completion=true + split=false fi # Workaround for options like -DCMAKE_BUILD_TYPE=Release @@ -89,7 +97,9 @@ _cmake() ;; esac - _split_longopt && split=true + if $is_old_completion; then + _split_longopt && split=true + fi case "$prev" in -C|-P|--graphviz|--system-information) @@ -163,7 +173,9 @@ _cmake() printf -v quoted %q "$cur" if [[ ! "${IFS}${COMP_WORDS[*]}${IFS}" =~ "${IFS}--build${IFS}" ]]; then - COMPREPLY=( $( compgen -W "configure${IFS}build${IFS}test${IFS}all" -- "$quoted" ) ) + COMPREPLY=( + $( compgen -W "configure${IFS}build${IFS}package${IFS}test${IFS}workflow${IFS}all" -- "$quoted" ) + ) fi return ;; @@ -172,12 +184,16 @@ _cmake() local quoted printf -v quoted %q "$cur" - local build_or_configure="configure" - if [[ "${IFS}${COMP_WORDS[*]}${IFS}" =~ "${IFS}--build${IFS}" ]]; then - build_or_configure="build" + local preset_type + if [[ "${IFS}${COMP_WORDS[*]}${IFS}" =~ "${IFS}--workflow${IFS}" ]]; then + preset_type="workflow" + elif [[ "${IFS}${COMP_WORDS[*]}${IFS}" =~ "${IFS}--build${IFS}" ]]; then + preset_type="build" + else + preset_type="configure" fi - local presets=$( cmake --list-presets="$build_or_configure" 2>/dev/null | + local presets=$( cmake --list-presets="$preset_type" 2>/dev/null | grep -o "^ \".*\"" | sed \ -e "s/^ //g" \ -e "s/\"//g" \ @@ -185,11 +201,35 @@ _cmake() COMPREPLY=( $( compgen -W "$presets" -- "$quoted" ) ) return ;; + --workflow) + local quoted + printf -v quoted %q "$cur" + # Options allowed right after `--workflow` + local workflow_options='--preset --list-presets --fresh' + + if [[ "$cur" == -* ]]; then + COMPREPLY=( $( compgen -W "$workflow_options" -- "$quoted" ) ) + else + local presets=$( cmake --list-presets=workflow 2>/dev/null | + grep -o "^ \".*\"" | sed \ + -e "s/^ //g" \ + -e "s/\"//g" \ + -e 's/ /\\\\ /g' ) + COMPREPLY=( $( compgen -W "$presets $workflow_options" -- "$quoted" ) ) + fi + return + ;; esac - $split && return + if ($is_old_completion || $is_init_completion); then + $split && return + else + [[ $was_split ]] && return + fi if [[ "$cur" == -* ]]; then + # FIXME(#26100): `cmake --help` is missing some options and does not + # have any mode-specific options like `cmake --build`'s `--config`. COMPREPLY=( $(compgen -W '$( _parse_help "$1" --help )' -- ${cur}) ) [[ $COMPREPLY == *= ]] && compopt -o nospace [[ $COMPREPLY ]] && return diff --git a/Auxiliary/bash-completion/cpack b/Auxiliary/bash-completion/cpack index cf5751fa618..5fc47ab42ad 100644 --- a/Auxiliary/bash-completion/cpack +++ b/Auxiliary/bash-completion/cpack @@ -3,7 +3,9 @@ _cpack() { local cur prev words cword - if type -t _init_completion >/dev/null; then + if type -t _comp_initialize >/dev/null; then + _comp_initialize -n = || return + elif type -t _init_completion >/dev/null; then _init_completion -n = || return else # manual initialization for older bash completion versions diff --git a/Auxiliary/bash-completion/ctest b/Auxiliary/bash-completion/ctest index 3c629d20369..51cc6820e46 100644 --- a/Auxiliary/bash-completion/ctest +++ b/Auxiliary/bash-completion/ctest @@ -3,7 +3,9 @@ _ctest() { local cur prev words cword - if type -t _init_completion >/dev/null; then + if type -t _comp_initialize >/dev/null; then + _comp_initialize -n = || return + elif type -t _init_completion >/dev/null; then _init_completion -n = || return else # manual initialization for older bash completion versions diff --git a/Auxiliary/cmake-mode.el b/Auxiliary/cmake-mode.el index 6bd23bf3886..7cfb2ba5e2c 100644 --- a/Auxiliary/cmake-mode.el +++ b/Auxiliary/cmake-mode.el @@ -1,9 +1,9 @@ -;;; cmake-mode.el --- major-mode for editing CMake sources +;;; cmake-mode.el --- major-mode for editing CMake sources -*- lexical-binding: t; -*- ;; Package-Requires: ((emacs "24.1")) ; Distributed under the OSI-approved BSD 3-Clause License. See accompanying -; file Copyright.txt or https://cmake.org/licensing for details. +; file LICENSE.rst or https://cmake.org/licensing for details. ;------------------------------------------------------------------------------ @@ -182,14 +182,14 @@ set the path with these commands: ) ) -(defun cmake-point-in-indendation () - (string-match "^[ \\t]*$" (buffer-substring (point-at-bol) (point)))) +(defun cmake-point-in-indentation () + (string-match "^[ \\t]*$" (buffer-substring (line-beginning-position) (point)))) (defun cmake-indent-line-to (column) "Indent the current line to COLUMN. If point is within the existing indentation it is moved to the end of the indentation. Otherwise it retains the same position on the line" - (if (cmake-point-in-indendation) + (if (cmake-point-in-indentation) (indent-line-to column) (save-excursion (indent-line-to column)))) @@ -258,15 +258,6 @@ Return t unless search stops due to end of buffer." (forward-line) t))) -(defun cmake-mark-defun () - "Mark the current CMake function or macro. - -This puts the mark at the end, and point at the beginning." - (interactive) - (cmake-end-of-defun) - (push-mark nil :nomsg :activate) - (cmake-beginning-of-defun)) - ;------------------------------------------------------------------------------ @@ -288,7 +279,7 @@ This puts the mark at the end, and point at the beginning." ;------------------------------------------------------------------------------ -(defun cmake--syntax-propertize-until-bracket-close (syntax) +(defun cmake--syntax-propertize-until-bracket-close (syntax end) ;; This function assumes that a previous search has matched the ;; beginning of a bracket_comment or bracket_argument and that the ;; second capture group has matched the equal signs between the two @@ -316,10 +307,10 @@ This puts the mark at the end, and point at the beginning." (syntax-propertize-rules ("\\(#\\)\\[\\(=*\\)\\[" (1 - (prog1 "!" (cmake--syntax-propertize-until-bracket-close "!")))) + (prog1 "!" (cmake--syntax-propertize-until-bracket-close "!" end)))) ("\\(\\[\\)\\(=*\\)\\[" (1 - (prog1 "|" (cmake--syntax-propertize-until-bracket-close "|")))))) + (prog1 "|" (cmake--syntax-propertize-until-bracket-close "|" end)))))) ;; Syntax table for this mode. (defvar cmake-mode-syntax-table nil @@ -346,6 +337,10 @@ This puts the mark at the end, and point at the beginning." (define-derived-mode cmake-mode prog-mode "CMake" "Major mode for editing CMake source files." + ;; Setup jumping to beginning/end of a CMake function/macro. + (set (make-local-variable 'beginning-of-defun-function) #'cmake-beginning-of-defun) + (set (make-local-variable 'end-of-defun-function) #'cmake-end-of-defun) + ; Setup font-lock mode. (set (make-local-variable 'font-lock-defaults) '(cmake-font-lock-keywords)) ; Setup indentation function. @@ -356,11 +351,6 @@ This puts the mark at the end, and point at the beginning." (set (make-local-variable 'syntax-propertize-function) cmake--syntax-propertize-function) (add-hook 'syntax-propertize-extend-region-functions #'syntax-propertize-multiline nil t)) -;; Default cmake-mode key bindings -(define-key cmake-mode-map "\e\C-a" #'cmake-beginning-of-defun) -(define-key cmake-mode-map "\e\C-e" #'cmake-end-of-defun) -(define-key cmake-mode-map "\e\C-h" #'cmake-mark-defun) - ; Help mode starts here @@ -490,7 +480,8 @@ and store the result as a list in LISTVAR." ;;;###autoload (defun cmake-help () - "Queries for any of the four available help topics and prints out the appropriate page." + "Queries for any of the four available help topics and prints out the +appropriate page." (interactive) (let* ((default-entry (cmake-symbol-at-point)) (command-list (cmake-get-list "command")) diff --git a/Auxiliary/cmake.m4 b/Auxiliary/cmake.m4 index 39826bcd2ee..19ead8fda84 100644 --- a/Auxiliary/cmake.m4 +++ b/Auxiliary/cmake.m4 @@ -1,6 +1,10 @@ dnl Distributed under the OSI-approved BSD 3-Clause License. See accompanying -dnl file Copyright.txt or https://cmake.org/licensing for details. +dnl file LICENSE.rst or https://cmake.org/licensing for details. +# CMAKE_FIND_BINARY +# ----------------- +# Finds the cmake command-line binary and sets its absolute path in the +# CMAKE_BINARY variable. AC_DEFUN([CMAKE_FIND_BINARY], [AC_ARG_VAR([CMAKE_BINARY], [path to the cmake binary])dnl @@ -9,13 +13,33 @@ if test "x$ac_cv_env_CMAKE_BINARY_set" != "xset"; then fi ])dnl -# $1: package name -# $2: language (e.g. C/CXX/Fortran) -# $3: The compiler ID, defaults to GNU. -# Possible values are: GNU, Intel, Clang, SunPro, HP, XL, VisualAge, PGI, -# PathScale, Cray, SCO, MSVC, LCC -# $4: optional extra arguments to cmake, e.g. "-DCMAKE_SIZEOF_VOID_P=8" -# $5: optional path to cmake binary +# CMAKE_FIND_PACKAGE(package, lang, [compiler-id], [cmake-args], +# [action-if-found], [action-if-not-found]) +# -------------------------------------------------------------- +# Finds a package with CMake. +# +# package: +# The name of the package as called in CMake with find_package(package). +# +# lang: +# The programming language to use (e.g., C, CXX, Fortran). +# See https://cmake.org/cmake/help/latest/command/enable_language.html +# for a complete list of supported languages. +# +# compiler-id: +# (Optional) The compiler ID to use. Defaults to GNU. +# See https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html +# for possible values. +# +# cmake-args: +# (Optional) Additional arguments to pass to cmake command, e.g., +# -DCMAKE_SIZEOF_VOID_P=8. +# +# action-if-found: +# (Optional) Commands to execute if the package is found. +# +# action-if-not-found: +# (Optional) Commands to execute if the package is not found. AC_DEFUN([CMAKE_FIND_PACKAGE], [ AC_REQUIRE([CMAKE_FIND_BINARY])dnl diff --git a/Auxiliary/vim/.gitattributes b/Auxiliary/vim/.gitattributes new file mode 100644 index 00000000000..c40dd66bd4f --- /dev/null +++ b/Auxiliary/vim/.gitattributes @@ -0,0 +1 @@ +extract-upper-case.pl tab-indent diff --git a/Auxiliary/vim/cmake.vim.in b/Auxiliary/vim/cmake.vim.in index 6edc0407354..39d7193fcd0 100644 --- a/Auxiliary/vim/cmake.vim.in +++ b/Auxiliary/vim/cmake.vim.in @@ -22,20 +22,20 @@ set cpo&vim syn region cmakeBracketArgument start="\[\z(=\?\|=[0-9]*\)\[" end="\]\z1\]" contains=cmakeTodo,@Spell syn region cmakeComment start="#" end="$" contains=cmakeTodo,@Spell -syn region cmakeBracketComment start="#\[\z(=\?\|=[0-9]*\)\[" end="\]\z1\]" contains=cmakeTodo,@Spell +syn region cmakeBracketComment start="\[\z(=*\)\[" end="\]\z1\]" contains=cmakeTodo,@Spell syn match cmakeEscaped /\(\\\\\|\\"\|\\n\|\\t\)/ contained syn region cmakeRegistry start="\[" end="]" contained oneline contains=cmakeTodo,cmakeEscaped syn region cmakeGeneratorExpression start="$<" end=">" contained oneline contains=cmakeVariableValue,cmakeProperty,cmakeGeneratorExpressions,cmakeTodo -syn region cmakeString start='"' end='"' contained contains=cmakeTodo,cmakeVariableValue,cmakeEscaped +syn region cmakeString start='"' end='"' contained contains=cmakeTodo,cmakeVariableValue,cmakeEscaped,@Spell syn region cmakeVariableValue start="${" end="}" contained oneline contains=cmakeVariable,cmakeTodo,cmakeVariableValue syn region cmakeEnvironment start="$ENV{" end="}" contained oneline contains=cmakeTodo -syn region cmakeArguments start="(" end=")" contains=ALLBUT,cmakeGeneratorExpressions,cmakeCommand,cmakeCommandConditional,cmakeCommandRepeat,cmakeCommandDeprecated,cmakeCommandManuallyAdded,cmakeArguments,cmakeTodo +syn region cmakeArguments start="(" end=")" contains=ALLBUT,cmakeGeneratorExpressions,cmakeCommand,cmakeCommandConditional,cmakeCommandRepeat,cmakeCommandDeprecated,cmakeCommandManuallyAdded,cmakeArguments,cmakeTodo,@Spell syn case match diff --git a/Auxiliary/vim/extract-upper-case.pl b/Auxiliary/vim/extract-upper-case.pl index 11791991d77..7f40b7436d2 100755 --- a/Auxiliary/vim/extract-upper-case.pl +++ b/Auxiliary/vim/extract-upper-case.pl @@ -16,10 +16,10 @@ my %keywords; # command => keyword-list # find cmake/Modules/ | sed -rn 's/.*CMakeDetermine(.+)Compiler.cmake/\1/p' | sort -my @languages = qw(ASM ASM_MASM ASM_NASM C CSharp CUDA CXX Fortran Java RC Swift); +my @languages = qw(ASM ASM_MASM ASM_NASM C CSharp CUDA CXX Fortran Java RC Swift HIP); # unwanted upper-cases -my %unwanted = map { $_ => 1 } qw(VS CXX IDE NOTFOUND NO_ DFOO DBAR NEW); +my %unwanted = map { $_ => 1 } qw(VS CXX IDE NOTFOUND NO_ DFOO DBAR NEW GNU); # cannot remove ALL - exists for add_custom_command # control-statements @@ -30,7 +30,7 @@ my %deprecated = map { $_ => 1 } qw(build_name exec_program export_library_dependencies install_files install_programs install_targets link_libraries make_directory output_required_files remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file); # add some (popular) modules -push @modules, "ExternalProject"; +push @modules, "ExternalProject", "FetchContent"; # variables open(CMAKE, "$cmake --help-variable-list|") or die "could not run cmake"; diff --git a/Auxiliary/vim/indent/cmake.vim b/Auxiliary/vim/indent/cmake.vim index 0c662faa016..28ecf8443f3 100644 --- a/Auxiliary/vim/indent/cmake.vim +++ b/Auxiliary/vim/indent/cmake.vim @@ -17,6 +17,8 @@ let b:did_indent = 1 setlocal indentexpr=CMakeGetIndent(v:lnum) setlocal indentkeys+==ENDIF(,ENDFOREACH(,ENDMACRO(,ELSE(,ELSEIF(,ENDWHILE( +let b:undo_indent = "setl inde< indk<" + " Only define the function once. if exists("*CMakeGetIndent") finish diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim index 83f13d8ef88..64e18e814fe 100644 --- a/Auxiliary/vim/syntax/cmake.vim +++ b/Auxiliary/vim/syntax/cmake.vim @@ -1,13 +1,13 @@ " Vim syntax file " Program: CMake - Cross-Platform Makefile Generator -" Version: cmake version 3.19.20201028-gdab947f +" Version: cmake version 3.27.20230713-gdc88dd5 " Language: CMake " Author: Andy Cedilnik , " Nicholas Hutchinson , " Patrick Boettcher " Maintainer: Dimitri Merejkowsky " Former Maintainer: Karthik Krishnan -" Last Change: 2020 oct. 28 +" Last Change: 2023 Jul 13 " " License: The CMake license applies to this file. See " https://cmake.org/licensing @@ -19,23 +19,23 @@ endif let s:keepcpo= &cpo set cpo&vim -syn region cmakeBracketArgument start="\[\z(=\?\|=[0-9]*\)\[" end="\]\z1\]" contains=cmakeTodo,@Spell +syn region cmakeBracketArgument start="\[\z(=*\)\[" end="\]\z1\]" contains=cmakeTodo,@Spell -syn region cmakeComment start="#" end="$" contains=cmakeTodo,@Spell -syn region cmakeBracketComment start="#\[\z(=\?\|=[0-9]*\)\[" end="\]\z1\]" contains=cmakeTodo,@Spell +syn region cmakeComment start="#\(\[=*\[\)\@!" end="$" contains=cmakeTodo,@Spell +syn region cmakeBracketComment start="#\[\z(=*\)\[" end="\]\z1\]" contains=cmakeTodo,@Spell syn match cmakeEscaped /\(\\\\\|\\"\|\\n\|\\t\)/ contained syn region cmakeRegistry start="\[" end="]" contained oneline contains=cmakeTodo,cmakeEscaped syn region cmakeGeneratorExpression start="$<" end=">" contained oneline contains=cmakeVariableValue,cmakeProperty,cmakeGeneratorExpressions,cmakeTodo -syn region cmakeString start='"' end='"' contained contains=cmakeTodo,cmakeVariableValue,cmakeEscaped +syn region cmakeString start='"' end='"' contained contains=cmakeTodo,cmakeVariableValue,cmakeEscaped,@Spell syn region cmakeVariableValue start="${" end="}" contained oneline contains=cmakeVariable,cmakeTodo,cmakeVariableValue syn region cmakeEnvironment start="$ENV{" end="}" contained oneline contains=cmakeTodo -syn region cmakeArguments start="(" end=")" contains=ALLBUT,cmakeGeneratorExpressions,cmakeCommand,cmakeCommandConditional,cmakeCommandRepeat,cmakeCommandDeprecated,cmakeCommandManuallyAdded,cmakeArguments,cmakeTodo +syn region cmakeArguments start="(" end=")" contains=ALLBUT,cmakeGeneratorExpressions,cmakeCommand,cmakeCommandConditional,cmakeCommandRepeat,cmakeCommandDeprecated,cmakeCommandManuallyAdded,cmakeArguments,cmakeTodo,@Spell syn case match @@ -70,12 +70,15 @@ syn keyword cmakeProperty contained \ ATTACHED_FILES \ ATTACHED_FILES_ON_FAIL \ AUTOGEN_BUILD_DIR + \ AUTOGEN_COMMAND_LINE_LENGTH_MAX \ AUTOGEN_ORIGIN_DEPENDS \ AUTOGEN_PARALLEL \ AUTOGEN_SOURCE_GROUP \ AUTOGEN_USE_SYSTEM_INCLUDE \ AUTOGEN_TARGETS_FOLDER \ AUTOGEN_TARGET_DEPENDS + \ AUTOGEN_USE_SYSTEM_INCLUDE + \ AUTOGEN_BETTER_GRAPH_MULTI_CONFIG \ AUTOMOC \ AUTOMOC_COMPILER_PREDEFINES \ AUTOMOC_DEPEND_FILTERS @@ -120,6 +123,7 @@ syn keyword cmakeProperty contained \ COMPILE_OPTIONS \ COMPILE_PDB_NAME \ COMPILE_PDB_OUTPUT_DIRECTORY + \ COMPILE_WARNING_AS_ERROR \ COST \ CPACK_DESKTOP_SHORTCUTS \ CPACK_NEVER_OVERWRITE @@ -140,6 +144,10 @@ syn keyword cmakeProperty contained \ CUDA_STANDARD \ CUDA_STANDARD_REQUIRED \ CXX_EXTENSIONS + \ CXX_MODULE_DIRS + \ CXX_MODULE_SET + \ CXX_MODULE_SETS + \ CXX_SCAN_FOR_MODULES \ CXX_STANDARD \ CXX_STANDARD_REQUIRED \ C_EXTENSIONS @@ -147,6 +155,7 @@ syn keyword cmakeProperty contained \ C_STANDARD_REQUIRED \ DEBUG_CONFIGURATIONS \ DEBUG_POSTFIX + \ DEBUGGER_WORKING_DIRECTORY \ DEFINE_SYMBOL \ DEFINITIONS \ DEPENDS @@ -156,6 +165,7 @@ syn keyword cmakeProperty contained \ DISABLED \ DISABLED_FEATURES \ DISABLE_PRECOMPILE_HEADERS + \ DLL_NAME_WITH_SOVERSION \ DOTNET_SDK \ DOTNET_TARGET_FRAMEWORK \ DOTNET_TARGET_FRAMEWORK_VERSION @@ -168,7 +178,9 @@ syn keyword cmakeProperty contained \ ENVIRONMENT_MODIFICATION \ EXCLUDE_FROM_ALL \ EXCLUDE_FROM_DEFAULT_BUILD + \ EXPORT_COMPILE_COMMANDS \ EXPORT_NAME + \ EXPORT_NO_SYSTEM \ EXPORT_PROPERTIES \ EXTERNAL_OBJECT \ EchoString @@ -183,6 +195,7 @@ syn keyword cmakeProperty contained \ FOLDER \ FRAMEWORK \ FRAMEWORK_VERSION + \ Fortran_BUILDING_INTRINSIC_MODULES \ Fortran_FORMAT \ Fortran_MODULE_DIRECTORY \ Fortran_PREPROCESS @@ -195,7 +208,10 @@ syn keyword cmakeProperty contained \ GLOBAL_DEPENDS_NO_CYCLES \ GNUtoMS \ HAS_CXX + \ HEADER_DIRS \ HEADER_FILE_ONLY + \ HEADER_SET + \ HEADER_SETS \ HELPSTRING \ HIP_ARCHITECTURES \ HIP_EXTENSIONS @@ -214,8 +230,10 @@ syn keyword cmakeProperty contained \ IMPORTED_LINK_INTERFACE_MULTIPLICITY \ IMPORTED_LOCATION \ IMPORTED_NO_SONAME + \ IMPORTED_NO_SYSTEM \ IMPORTED_OBJECTS \ IMPORTED_SONAME + \ IMPORTED_TARGETS \ IMPORT_PREFIX \ IMPORT_SUFFIX \ INCLUDE_DIRECTORIES @@ -224,15 +242,21 @@ syn keyword cmakeProperty contained \ INSTALL_REMOVE_ENVIRONMENT_RPATH \ INSTALL_RPATH \ INSTALL_RPATH_USE_LINK_PATH + \ INTERFACE_AUTOMOC_MACRO_NAMES \ INTERFACE_AUTOUIC_OPTIONS \ INTERFACE_AUTOMOC_MACRO_NAMES \ INTERFACE_COMPILE_DEFINITIONS \ INTERFACE_COMPILE_FEATURES \ INTERFACE_COMPILE_OPTIONS + \ INTERFACE_CXX_MODULE_SETS + \ INTERFACE_HEADER_SETS + \ INTERFACE_HEADER_SETS_TO_VERIFY \ INTERFACE_INCLUDE_DIRECTORIES \ INTERFACE_LINK_DEPENDS \ INTERFACE_LINK_DIRECTORIES \ INTERFACE_LINK_LIBRARIES + \ INTERFACE_LINK_LIBRARIES_DIRECT + \ INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE \ INTERFACE_LINK_OPTIONS \ INTERFACE_POSITION_INDEPENDENT_CODE \ INTERFACE_PRECOMPILE_HEADERS @@ -242,6 +266,7 @@ syn keyword cmakeProperty contained \ IN_TRY_COMPILE \ IOS_INSTALL_COMBINED \ ISPC_HEADER_DIRECTORY + \ ISPC_HEADER_SUFFIX \ ISPC_INSTRUCTION_SETS \ JOB_POOLS \ JOB_POOL_COMPILE @@ -260,6 +285,8 @@ syn keyword cmakeProperty contained \ LINK_INTERFACE_LIBRARIES \ LINK_INTERFACE_MULTIPLICITY \ LINK_LIBRARIES + \ LINK_LIBRARIES_ONLY_TARGETS + \ LINK_LIBRARY_OVERRIDE \ LINK_OPTIONS \ LINK_SEARCH_END_STATIC \ LINK_SEARCH_START_STATIC @@ -277,6 +304,8 @@ syn keyword cmakeProperty contained \ MANUALLY_ADDED_DEPENDENCIES \ MEASUREMENT \ MODIFIED + \ MSVC_DEBUG_INFORMATION_FORMAT + \ MSVC_RUNTIME_CHECKS \ MSVC_RUNTIME_LIBRARY \ NAME \ NO_SONAME @@ -343,11 +372,13 @@ syn keyword cmakeProperty contained \ SUBDIRECTORIES \ SUFFIX \ SYMBOLIC + \ SYSTEM \ Swift_DEPENDENCIES_FILE \ Swift_DIAGNOSTICS_FILE \ Swift_LANGUAGE_VERSION \ Swift_MODULE_DIRECTORY \ Swift_MODULE_NAME + \ Swift_COMPILATION_MODE \ TARGET_ARCHIVES_MAY_BE_SHARED_LIBS \ TARGET_MESSAGES \ TARGET_SUPPORTS_SHARED_LIBS @@ -356,16 +387,20 @@ syn keyword cmakeProperty contained \ TEST_INCLUDE_FILES \ TIMEOUT \ TIMEOUT_AFTER_MATCH + \ TIMEOUT_SIGNAL_GRACE_PERIOD + \ TIMEOUT_SIGNAL_NAME \ TYPE \ UNITY_BUILD \ UNITY_BUILD_BATCH_SIZE \ UNITY_BUILD_CODE_AFTER_INCLUDE \ UNITY_BUILD_CODE_BEFORE_INCLUDE \ UNITY_BUILD_MODE + \ UNITY_BUILD_UNIQUE_ID \ UNITY_GROUP \ USE_FOLDERS \ VALUE \ VARIABLES + \ VERIFY_INTERFACE_HEADER_SETS \ VERSION \ VISIBILITY_INLINES_HIDDEN \ VS_CONFIGURATION_TYPE @@ -383,6 +418,7 @@ syn keyword cmakeProperty contained \ VS_DOTNET_STARTUP_OBJECT \ VS_DOTNET_TARGET_FRAMEWORK_VERSION \ VS_DPI_AWARE + \ VS_FRAMEWORK_REFERENCES \ VS_GLOBAL_KEYWORD \ VS_GLOBAL_PROJECT_TYPES \ VS_GLOBAL_ROOTNAMESPACE @@ -417,16 +453,20 @@ syn keyword cmakeProperty contained \ VS_STARTUP_PROJECT \ VS_TOOL_OVERRIDE \ VS_USER_PROPS + \ VS_FILTER_PROPS \ VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION \ VS_WINRT_COMPONENT \ VS_WINRT_EXTENSIONS \ VS_WINRT_REFERENCES \ VS_XAML_TYPE + \ WATCOM_RUNTIME_LIBRARY \ WILL_FAIL \ WIN32_EXECUTABLE \ WINDOWS_EXPORT_ALL_SYMBOLS \ WORKING_DIRECTORY \ WRAP_EXCLUDE + \ XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY + \ XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY \ XCODE_EMIT_EFFECTIVE_PLATFORM_NAME \ XCODE_EXPLICIT_FILE_TYPE \ XCODE_FILE_ATTRIBUTES @@ -443,10 +483,13 @@ syn keyword cmakeProperty contained \ XCODE_SCHEME_DISABLE_MAIN_THREAD_CHECKER \ XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS \ XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE + \ XCODE_SCHEME_ENABLE_GPU_API_VALIDATION + \ XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION \ XCODE_SCHEME_ENVIRONMENT \ XCODE_SCHEME_EXECUTABLE \ XCODE_SCHEME_GUARD_MALLOC \ XCODE_SCHEME_LAUNCH_MODE + \ XCODE_SCHEME_LLDB_INIT_FILE \ XCODE_SCHEME_MAIN_THREAD_CHECKER_STOP \ XCODE_SCHEME_MALLOC_GUARD_EDGES \ XCODE_SCHEME_MALLOC_SCRIBBLE @@ -458,18 +501,22 @@ syn keyword cmakeProperty contained \ XCODE_SCHEME_ENABLE_GPU_API_VALIDATION \ XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION \ XCODE_SCHEME_LAUNCH_CONFIGURATION + \ XCODE_SCHEME_TEST_CONFIGURATION \ XCODE_SCHEME_WORKING_DIRECTORY \ XCODE_SCHEME_ZOMBIE_OBJECTS + \ XCODE_XCCONFIG \ XCTEST syn keyword cmakeVariable contained \ ANDROID \ APPLE \ BORLAND + \ BSD \ BUILD_SHARED_LIBS \ CACHE \ CMAKE_ABSOLUTE_DESTINATION_FILES \ CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY + \ CMAKE_ADSP_ROOT \ CMAKE_AIX_EXPORT_ALL_SYMBOLS \ CMAKE_ANDROID_ANT_ADDITIONAL_OPTIONS \ CMAKE_ANDROID_API @@ -479,6 +526,7 @@ syn keyword cmakeVariable contained \ CMAKE_ANDROID_ARM_MODE \ CMAKE_ANDROID_ARM_NEON \ CMAKE_ANDROID_ASSETS_DIRECTORIES + \ CMAKE_ANDROID_EXCEPTIONS \ CMAKE_ANDROID_GUI \ CMAKE_ANDROID_JAR_DEPENDENCIES \ CMAKE_ANDROID_JAR_DIRECTORIES @@ -489,14 +537,17 @@ syn keyword cmakeVariable contained \ CMAKE_ANDROID_NDK_DEPRECATED_HEADERS \ CMAKE_ANDROID_NDK_TOOLCHAIN_HOST_TAG \ CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION + \ CMAKE_ANDROID_NDK_VERSION \ CMAKE_ANDROID_PROCESS_MAX \ CMAKE_ANDROID_PROGUARD \ CMAKE_ANDROID_PROGUARD_CONFIG_PATH + \ CMAKE_ANDROID_RTTI \ CMAKE_ANDROID_SECURE_PROPS_PATH \ CMAKE_ANDROID_SKIP_ANT_STEP \ CMAKE_ANDROID_STANDALONE_TOOLCHAIN \ CMAKE_ANDROID_STL_TYPE \ CMAKE_APPBUNDLE_PATH + \ CMAKE_APPLE_SILICON_PROCESSOR \ CMAKE_AR \ CMAKE_ARCHIVE_OUTPUT_DIRECTORY \ CMAKE_ARGC @@ -508,12 +559,15 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_ARCHIVE_APPEND \ CMAKE_ASM_ARCHIVE_CREATE \ CMAKE_ASM_ARCHIVE_FINISH + \ CMAKE_ASM_BYTE_ORDER \ CMAKE_ASM_CLANG_TIDY + \ CMAKE_ASM_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_ASM_COMPILER \ CMAKE_ASM_COMPILER_ABI \ CMAKE_ASM_COMPILER_AR \ CMAKE_ASM_COMPILER_ARCHITECTURE_ID \ CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_ASM_COMPILER_FRONTEND_VARIANT \ CMAKE_ASM_COMPILER_ID \ CMAKE_ASM_COMPILER_LAUNCHER \ CMAKE_ASM_COMPILER_LOADED @@ -528,6 +582,8 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_CREATE_SHARED_LIBRARY \ CMAKE_ASM_CREATE_SHARED_MODULE \ CMAKE_ASM_CREATE_STATIC_LIBRARY + \ CMAKE_ASM_EXTENSIONS + \ CMAKE_ASM_EXTENSIONS_DEFAULT \ CMAKE_ASM_FLAGS \ CMAKE_ASM_FLAGS_DEBUG \ CMAKE_ASM_FLAGS_DEBUG_INIT @@ -546,6 +602,7 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_INCLUDE_WHAT_YOU_USE \ CMAKE_ASM_INIT \ CMAKE_ASM_LIBRARY_ARCHITECTURE + \ CMAKE_ASM_LINKER_LAUNCHER \ CMAKE_ASM_LINKER_PREFERENCE \ CMAKE_ASM_LINKER_PREFERENCE_PROPAGATES \ CMAKE_ASM_LINKER_WRAPPER_FLAG @@ -554,6 +611,7 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_LINK_LIBRARY_FILE_FLAG \ CMAKE_ASM_LINK_LIBRARY_FLAG \ CMAKE_ASM_LINK_LIBRARY_SUFFIX + \ CMAKE_ASM_LINK_WHAT_YOU_USE_FLAG \ CMAKE_ASM_MASM \ CMAKE_ASM_MASM_ANDROID_TOOLCHAIN_MACHINE \ CMAKE_ASM_MASM_ANDROID_TOOLCHAIN_PREFIX @@ -561,12 +619,15 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_MASM_ARCHIVE_APPEND \ CMAKE_ASM_MASM_ARCHIVE_CREATE \ CMAKE_ASM_MASM_ARCHIVE_FINISH + \ CMAKE_ASM_MASM_BYTE_ORDER \ CMAKE_ASM_MASM_CLANG_TIDY + \ CMAKE_ASM_MASM_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_ASM_MASM_COMPILER \ CMAKE_ASM_MASM_COMPILER_ABI \ CMAKE_ASM_MASM_COMPILER_AR \ CMAKE_ASM_MASM_COMPILER_ARCHITECTURE_ID \ CMAKE_ASM_MASM_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_ASM_MASM_COMPILER_FRONTEND_VARIANT \ CMAKE_ASM_MASM_COMPILER_ID \ CMAKE_ASM_MASM_COMPILER_LAUNCHER \ CMAKE_ASM_MASM_COMPILER_LOADED @@ -581,6 +642,8 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_MASM_CREATE_SHARED_LIBRARY \ CMAKE_ASM_MASM_CREATE_SHARED_MODULE \ CMAKE_ASM_MASM_CREATE_STATIC_LIBRARY + \ CMAKE_ASM_MASM_EXTENSIONS + \ CMAKE_ASM_MASM_EXTENSIONS_DEFAULT \ CMAKE_ASM_MASM_FLAGS \ CMAKE_ASM_MASM_FLAGS_DEBUG \ CMAKE_ASM_MASM_FLAGS_DEBUG_INIT @@ -599,6 +662,7 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_MASM_INCLUDE_WHAT_YOU_USE \ CMAKE_ASM_MASM_INIT \ CMAKE_ASM_MASM_LIBRARY_ARCHITECTURE + \ CMAKE_ASM_MASM_LINKER_LAUNCHER \ CMAKE_ASM_MASM_LINKER_PREFERENCE \ CMAKE_ASM_MASM_LINKER_PREFERENCE_PROPAGATES \ CMAKE_ASM_MASM_LINKER_WRAPPER_FLAG @@ -607,14 +671,19 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_MASM_LINK_LIBRARY_FILE_FLAG \ CMAKE_ASM_MASM_LINK_LIBRARY_FLAG \ CMAKE_ASM_MASM_LINK_LIBRARY_SUFFIX + \ CMAKE_ASM_MASM_LINK_WHAT_YOU_USE_FLAG \ CMAKE_ASM_MASM_OUTPUT_EXTENSION \ CMAKE_ASM_MASM_PLATFORM_ID \ CMAKE_ASM_MASM_SIMULATE_ID \ CMAKE_ASM_MASM_SIMULATE_VERSION \ CMAKE_ASM_MASM_SIZEOF_DATA_PTR \ CMAKE_ASM_MASM_SOURCE_FILE_EXTENSIONS + \ CMAKE_ASM_MASM_STANDARD + \ CMAKE_ASM_MASM_STANDARD_DEFAULT \ CMAKE_ASM_MASM_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_ASM_MASM_STANDARD_LIBRARIES + \ CMAKE_ASM_MASM_STANDARD_REQUIRED + \ CMAKE_ASM_MASM_SUPPORTED \ CMAKE_ASM_MASM_VISIBILITY_PRESET \ CMAKE_ASM_NASM \ CMAKE_ASM_NASM_ANDROID_TOOLCHAIN_MACHINE @@ -623,12 +692,15 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_NASM_ARCHIVE_APPEND \ CMAKE_ASM_NASM_ARCHIVE_CREATE \ CMAKE_ASM_NASM_ARCHIVE_FINISH + \ CMAKE_ASM_NASM_BYTE_ORDER \ CMAKE_ASM_NASM_CLANG_TIDY + \ CMAKE_ASM_NASM_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_ASM_NASM_COMPILER \ CMAKE_ASM_NASM_COMPILER_ABI \ CMAKE_ASM_NASM_COMPILER_AR \ CMAKE_ASM_NASM_COMPILER_ARCHITECTURE_ID \ CMAKE_ASM_NASM_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_ASM_NASM_COMPILER_FRONTEND_VARIANT \ CMAKE_ASM_NASM_COMPILER_ID \ CMAKE_ASM_NASM_COMPILER_LAUNCHER \ CMAKE_ASM_NASM_COMPILER_LOADED @@ -643,6 +715,8 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_NASM_CREATE_SHARED_LIBRARY \ CMAKE_ASM_NASM_CREATE_SHARED_MODULE \ CMAKE_ASM_NASM_CREATE_STATIC_LIBRARY + \ CMAKE_ASM_NASM_EXTENSIONS + \ CMAKE_ASM_NASM_EXTENSIONS_DEFAULT \ CMAKE_ASM_NASM_FLAGS \ CMAKE_ASM_NASM_FLAGS_DEBUG \ CMAKE_ASM_NASM_FLAGS_DEBUG_INIT @@ -661,6 +735,7 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_NASM_INCLUDE_WHAT_YOU_USE \ CMAKE_ASM_NASM_INIT \ CMAKE_ASM_NASM_LIBRARY_ARCHITECTURE + \ CMAKE_ASM_NASM_LINKER_LAUNCHER \ CMAKE_ASM_NASM_LINKER_PREFERENCE \ CMAKE_ASM_NASM_LINKER_PREFERENCE_PROPAGATES \ CMAKE_ASM_NASM_LINKER_WRAPPER_FLAG @@ -669,14 +744,19 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_NASM_LINK_LIBRARY_FILE_FLAG \ CMAKE_ASM_NASM_LINK_LIBRARY_FLAG \ CMAKE_ASM_NASM_LINK_LIBRARY_SUFFIX + \ CMAKE_ASM_NASM_LINK_WHAT_YOU_USE_FLAG \ CMAKE_ASM_NASM_OUTPUT_EXTENSION \ CMAKE_ASM_NASM_PLATFORM_ID \ CMAKE_ASM_NASM_SIMULATE_ID \ CMAKE_ASM_NASM_SIMULATE_VERSION \ CMAKE_ASM_NASM_SIZEOF_DATA_PTR \ CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS + \ CMAKE_ASM_NASM_STANDARD + \ CMAKE_ASM_NASM_STANDARD_DEFAULT \ CMAKE_ASM_NASM_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_ASM_NASM_STANDARD_LIBRARIES + \ CMAKE_ASM_NASM_STANDARD_REQUIRED + \ CMAKE_ASM_NASM_SUPPORTED \ CMAKE_ASM_NASM_VISIBILITY_PRESET \ CMAKE_ASM_OUTPUT_EXTENSION \ CMAKE_ASM_PLATFORM_ID @@ -684,9 +764,15 @@ syn keyword cmakeVariable contained \ CMAKE_ASM_SIMULATE_VERSION \ CMAKE_ASM_SIZEOF_DATA_PTR \ CMAKE_ASM_SOURCE_FILE_EXTENSIONS + \ CMAKE_ASM_STANDARD + \ CMAKE_ASM_STANDARD_DEFAULT \ CMAKE_ASM_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_ASM_STANDARD_LIBRARIES + \ CMAKE_ASM_STANDARD_REQUIRED + \ CMAKE_ASM_SUPPORTED \ CMAKE_ASM_VISIBILITY_PRESET + \ CMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG + \ CMAKE_AUTOGEN_COMMAND_LINE_LENGTH_MAX \ CMAKE_AUTOGEN_ORIGIN_DEPENDS \ CMAKE_AUTOGEN_PARALLEL \ CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE @@ -694,15 +780,18 @@ syn keyword cmakeVariable contained \ CMAKE_AUTOMOC \ CMAKE_AUTOMOC_COMPILER_PREDEFINES \ CMAKE_AUTOMOC_DEPEND_FILTERS + \ CMAKE_AUTOMOC_EXECUTABLE \ CMAKE_AUTOMOC_MACRO_NAMES \ CMAKE_AUTOMOC_MOC_OPTIONS \ CMAKE_AUTOMOC_PATH_PREFIX \ CMAKE_AUTOMOC_RELAXED_MODE \ CMAKE_AUTOMOC_EXECUTABLE \ CMAKE_AUTORCC + \ CMAKE_AUTORCC_EXECUTABLE \ CMAKE_AUTORCC_OPTIONS \ CMAKE_AUTORCC_EXECUTABLE \ CMAKE_AUTOUIC + \ CMAKE_AUTOUIC_EXECUTABLE \ CMAKE_AUTOUIC_OPTIONS \ CMAKE_AUTOUIC_SEARCH_PATHS \ CMAKE_AUTOUIC_EXECUTABLE @@ -733,6 +822,7 @@ syn keyword cmakeVariable contained \ CMAKE_COMPILER_IS_GNUCXX \ CMAKE_COMPILER_IS_GNUG77 \ CMAKE_COMPILE_PDB_OUTPUT_DIRECTORY + \ CMAKE_COMPILE_WARNING_AS_ERROR \ CMAKE_CONFIGURATION_TYPES \ CMAKE_CPACK_COMMAND \ CMAKE_CROSSCOMPILING @@ -745,12 +835,15 @@ syn keyword cmakeVariable contained \ CMAKE_CSharp_ARCHIVE_APPEND \ CMAKE_CSharp_ARCHIVE_CREATE \ CMAKE_CSharp_ARCHIVE_FINISH + \ CMAKE_CSharp_BYTE_ORDER \ CMAKE_CSharp_CLANG_TIDY + \ CMAKE_CSharp_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_CSharp_COMPILER \ CMAKE_CSharp_COMPILER_ABI \ CMAKE_CSharp_COMPILER_AR \ CMAKE_CSharp_COMPILER_ARCHITECTURE_ID \ CMAKE_CSharp_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_CSharp_COMPILER_FRONTEND_VARIANT \ CMAKE_CSharp_COMPILER_ID \ CMAKE_CSharp_COMPILER_LAUNCHER \ CMAKE_CSharp_COMPILER_LOADED @@ -765,6 +858,8 @@ syn keyword cmakeVariable contained \ CMAKE_CSharp_CREATE_SHARED_LIBRARY \ CMAKE_CSharp_CREATE_SHARED_MODULE \ CMAKE_CSharp_CREATE_STATIC_LIBRARY + \ CMAKE_CSharp_EXTENSIONS + \ CMAKE_CSharp_EXTENSIONS_DEFAULT \ CMAKE_CSharp_FLAGS \ CMAKE_CSharp_FLAGS_DEBUG \ CMAKE_CSharp_FLAGS_DEBUG_INIT @@ -783,6 +878,7 @@ syn keyword cmakeVariable contained \ CMAKE_CSharp_INCLUDE_WHAT_YOU_USE \ CMAKE_CSharp_INIT \ CMAKE_CSharp_LIBRARY_ARCHITECTURE + \ CMAKE_CSharp_LINKER_LAUNCHER \ CMAKE_CSharp_LINKER_PREFERENCE \ CMAKE_CSharp_LINKER_PREFERENCE_PROPAGATES \ CMAKE_CSharp_LINKER_WRAPPER_FLAG @@ -791,14 +887,19 @@ syn keyword cmakeVariable contained \ CMAKE_CSharp_LINK_LIBRARY_FILE_FLAG \ CMAKE_CSharp_LINK_LIBRARY_FLAG \ CMAKE_CSharp_LINK_LIBRARY_SUFFIX + \ CMAKE_CSharp_LINK_WHAT_YOU_USE_FLAG \ CMAKE_CSharp_OUTPUT_EXTENSION \ CMAKE_CSharp_PLATFORM_ID \ CMAKE_CSharp_SIMULATE_ID \ CMAKE_CSharp_SIMULATE_VERSION \ CMAKE_CSharp_SIZEOF_DATA_PTR \ CMAKE_CSharp_SOURCE_FILE_EXTENSIONS + \ CMAKE_CSharp_STANDARD + \ CMAKE_CSharp_STANDARD_DEFAULT \ CMAKE_CSharp_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_CSharp_STANDARD_LIBRARIES + \ CMAKE_CSharp_STANDARD_REQUIRED + \ CMAKE_CSharp_SUPPORTED \ CMAKE_CSharp_VISIBILITY_PRESET \ CMAKE_CTEST_ARGUMENTS \ CMAKE_CTEST_COMMAND @@ -810,12 +911,15 @@ syn keyword cmakeVariable contained \ CMAKE_CUDA_ARCHIVE_APPEND \ CMAKE_CUDA_ARCHIVE_CREATE \ CMAKE_CUDA_ARCHIVE_FINISH + \ CMAKE_CUDA_BYTE_ORDER \ CMAKE_CUDA_CLANG_TIDY + \ CMAKE_CUDA_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_CUDA_COMPILER \ CMAKE_CUDA_COMPILER_ABI \ CMAKE_CUDA_COMPILER_AR \ CMAKE_CUDA_COMPILER_ARCHITECTURE_ID \ CMAKE_CUDA_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_CUDA_COMPILER_FRONTEND_VARIANT \ CMAKE_CUDA_COMPILER_ID \ CMAKE_CUDA_COMPILER_LAUNCHER \ CMAKE_CUDA_COMPILER_LOADED @@ -832,6 +936,7 @@ syn keyword cmakeVariable contained \ CMAKE_CUDA_CREATE_SHARED_MODULE \ CMAKE_CUDA_CREATE_STATIC_LIBRARY \ CMAKE_CUDA_EXTENSIONS + \ CMAKE_CUDA_EXTENSIONS_DEFAULT \ CMAKE_CUDA_FLAGS \ CMAKE_CUDA_FLAGS_DEBUG \ CMAKE_CUDA_FLAGS_DEBUG_INIT @@ -851,6 +956,7 @@ syn keyword cmakeVariable contained \ CMAKE_CUDA_INCLUDE_WHAT_YOU_USE \ CMAKE_CUDA_INIT \ CMAKE_CUDA_LIBRARY_ARCHITECTURE + \ CMAKE_CUDA_LINKER_LAUNCHER \ CMAKE_CUDA_LINKER_PREFERENCE \ CMAKE_CUDA_LINKER_PREFERENCE_PROPAGATES \ CMAKE_CUDA_LINKER_WRAPPER_FLAG @@ -859,6 +965,7 @@ syn keyword cmakeVariable contained \ CMAKE_CUDA_LINK_LIBRARY_FILE_FLAG \ CMAKE_CUDA_LINK_LIBRARY_FLAG \ CMAKE_CUDA_LINK_LIBRARY_SUFFIX + \ CMAKE_CUDA_LINK_WHAT_YOU_USE_FLAG \ CMAKE_CUDA_OUTPUT_EXTENSION \ CMAKE_CUDA_PLATFORM_ID \ CMAKE_CUDA_RESOLVE_DEVICE_SYMBOLS @@ -869,9 +976,11 @@ syn keyword cmakeVariable contained \ CMAKE_CUDA_SIZEOF_DATA_PTR \ CMAKE_CUDA_SOURCE_FILE_EXTENSIONS \ CMAKE_CUDA_STANDARD + \ CMAKE_CUDA_STANDARD_DEFAULT \ CMAKE_CUDA_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_CUDA_STANDARD_LIBRARIES \ CMAKE_CUDA_STANDARD_REQUIRED + \ CMAKE_CUDA_SUPPORTED \ CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES \ CMAKE_CUDA_VISIBILITY_PRESET \ CMAKE_CURRENT_BINARY_DIR @@ -890,12 +999,15 @@ syn keyword cmakeVariable contained \ CMAKE_CXX_ARCHIVE_APPEND \ CMAKE_CXX_ARCHIVE_CREATE \ CMAKE_CXX_ARCHIVE_FINISH + \ CMAKE_CXX_BYTE_ORDER \ CMAKE_CXX_CLANG_TIDY + \ CMAKE_CXX_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_CXX_COMPILER \ CMAKE_CXX_COMPILER_ABI \ CMAKE_CXX_COMPILER_AR \ CMAKE_CXX_COMPILER_ARCHITECTURE_ID \ CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_CXX_COMPILER_FRONTEND_VARIANT \ CMAKE_CXX_COMPILER_ID \ CMAKE_CXX_COMPILER_LAUNCHER \ CMAKE_CXX_COMPILER_LOADED @@ -912,6 +1024,7 @@ syn keyword cmakeVariable contained \ CMAKE_CXX_CREATE_SHARED_MODULE \ CMAKE_CXX_CREATE_STATIC_LIBRARY \ CMAKE_CXX_EXTENSIONS + \ CMAKE_CXX_EXTENSIONS_DEFAULT \ CMAKE_CXX_FLAGS \ CMAKE_CXX_FLAGS_DEBUG \ CMAKE_CXX_FLAGS_DEBUG_INIT @@ -939,16 +1052,20 @@ syn keyword cmakeVariable contained \ CMAKE_CXX_LINK_LIBRARY_FILE_FLAG \ CMAKE_CXX_LINK_LIBRARY_FLAG \ CMAKE_CXX_LINK_LIBRARY_SUFFIX + \ CMAKE_CXX_LINK_WHAT_YOU_USE_FLAG \ CMAKE_CXX_OUTPUT_EXTENSION \ CMAKE_CXX_PLATFORM_ID + \ CMAKE_CXX_SCAN_FOR_MODULES \ CMAKE_CXX_SIMULATE_ID \ CMAKE_CXX_SIMULATE_VERSION \ CMAKE_CXX_SIZEOF_DATA_PTR \ CMAKE_CXX_SOURCE_FILE_EXTENSIONS \ CMAKE_CXX_STANDARD + \ CMAKE_CXX_STANDARD_DEFAULT \ CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_CXX_STANDARD_LIBRARIES \ CMAKE_CXX_STANDARD_REQUIRED + \ CMAKE_CXX_SUPPORTED \ CMAKE_CXX_VISIBILITY_PRESET \ CMAKE_C_ANDROID_TOOLCHAIN_MACHINE \ CMAKE_C_ANDROID_TOOLCHAIN_PREFIX @@ -956,12 +1073,15 @@ syn keyword cmakeVariable contained \ CMAKE_C_ARCHIVE_APPEND \ CMAKE_C_ARCHIVE_CREATE \ CMAKE_C_ARCHIVE_FINISH + \ CMAKE_C_BYTE_ORDER \ CMAKE_C_CLANG_TIDY + \ CMAKE_C_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_C_COMPILER \ CMAKE_C_COMPILER_ABI \ CMAKE_C_COMPILER_AR \ CMAKE_C_COMPILER_ARCHITECTURE_ID \ CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_C_COMPILER_FRONTEND_VARIANT \ CMAKE_C_COMPILER_ID \ CMAKE_C_COMPILER_LAUNCHER \ CMAKE_C_COMPILER_LOADED @@ -978,6 +1098,7 @@ syn keyword cmakeVariable contained \ CMAKE_C_CREATE_SHARED_MODULE \ CMAKE_C_CREATE_STATIC_LIBRARY \ CMAKE_C_EXTENSIONS + \ CMAKE_C_EXTENSIONS_DEFAULT \ CMAKE_C_FLAGS \ CMAKE_C_FLAGS_DEBUG \ CMAKE_C_FLAGS_DEBUG_INIT @@ -1005,6 +1126,7 @@ syn keyword cmakeVariable contained \ CMAKE_C_LINK_LIBRARY_FILE_FLAG \ CMAKE_C_LINK_LIBRARY_FLAG \ CMAKE_C_LINK_LIBRARY_SUFFIX + \ CMAKE_C_LINK_WHAT_YOU_USE_FLAG \ CMAKE_C_OUTPUT_EXTENSION \ CMAKE_C_PLATFORM_ID \ CMAKE_C_SIMULATE_ID @@ -1012,17 +1134,22 @@ syn keyword cmakeVariable contained \ CMAKE_C_SIZEOF_DATA_PTR \ CMAKE_C_SOURCE_FILE_EXTENSIONS \ CMAKE_C_STANDARD + \ CMAKE_C_STANDARD_DEFAULT \ CMAKE_C_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_C_STANDARD_LIBRARIES \ CMAKE_C_STANDARD_REQUIRED + \ CMAKE_C_SUPPORTED \ CMAKE_C_VISIBILITY_PRESET \ CMAKE_DEBUG_POSTFIX \ CMAKE_DEBUG_TARGET_PROPERTIES + \ CMAKE_DEBUGGER_WORKING_DIRECTORY \ CMAKE_DEFAULT_BUILD_TYPE \ CMAKE_DEFAULT_CONFIGS \ CMAKE_DEPENDS_IN_PROJECT_ONLY + \ CMAKE_DEPENDS_USE_COMPILER \ CMAKE_DIRECTORY_LABELS \ CMAKE_DISABLE_PRECOMPILE_HEADERS + \ CMAKE_DLL_NAME_WITH_SOVERSION \ CMAKE_DL_LIBS \ CMAKE_DOTNET_SDK \ CMAKE_DOTNET_TARGET_FRAMEWORK @@ -1036,11 +1163,25 @@ syn keyword cmakeVariable contained \ CMAKE_ENABLE_EXPORTS \ CMAKE_ERROR_DEPRECATED \ CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION + \ CMAKE_EXECUTABLE_ENABLE_EXPORTS \ CMAKE_EXECUTABLE_SUFFIX + \ CMAKE_EXECUTABLE_SUFFIX_ASM + \ CMAKE_EXECUTABLE_SUFFIX_ASM_MASM + \ CMAKE_EXECUTABLE_SUFFIX_ASM_NASM + \ CMAKE_EXECUTABLE_SUFFIX_C + \ CMAKE_EXECUTABLE_SUFFIX_CSharp + \ CMAKE_EXECUTABLE_SUFFIX_CUDA + \ CMAKE_EXECUTABLE_SUFFIX_CXX + \ CMAKE_EXECUTABLE_SUFFIX_Fortran + \ CMAKE_EXECUTABLE_SUFFIX_HIP + \ CMAKE_EXECUTABLE_SUFFIX_Java + \ CMAKE_EXECUTABLE_SUFFIX_RC + \ CMAKE_EXECUTABLE_SUFFIX_Swift \ CMAKE_EXECUTE_PROCESS_COMMAND_ECHO \ CMAKE_EXE_LINKER_FLAGS \ CMAKE_EXE_LINKER_FLAGS_INIT \ CMAKE_EXPORT_COMPILE_COMMANDS + \ CMAKE_EXPORT_SARIF \ CMAKE_EXPORT_NO_PACKAGE_REGISTRY \ CMAKE_EXPORT_PACKAGE_REGISTRY \ CMAKE_EXTRA_GENERATOR @@ -1056,9 +1197,11 @@ syn keyword cmakeVariable contained \ CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY \ CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY \ CMAKE_FIND_PACKAGE_PREFER_CONFIG + \ CMAKE_FIND_PACKAGE_REDIRECTS_DIR \ CMAKE_FIND_PACKAGE_RESOLVE_SYMLINKS \ CMAKE_FIND_PACKAGE_SORT_DIRECTION \ CMAKE_FIND_PACKAGE_SORT_ORDER + \ CMAKE_FIND_PACKAGE_TARGETS_GLOBAL \ CMAKE_FIND_PACKAGE_WARN_NO_MODULE \ CMAKE_FIND_ROOT_PATH \ CMAKE_FIND_ROOT_PATH_MODE_INCLUDE @@ -1069,6 +1212,7 @@ syn keyword cmakeVariable contained \ CMAKE_FIND_USE_INSTALL_PREFIX \ CMAKE_FIND_USE_CMAKE_PATH \ CMAKE_FIND_USE_CMAKE_SYSTEM_PATH + \ CMAKE_FIND_USE_INSTALL_PREFIX \ CMAKE_FIND_USE_PACKAGE_REGISTRY \ CMAKE_FIND_USE_PACKAGE_ROOT_PATH \ CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH @@ -1083,12 +1227,15 @@ syn keyword cmakeVariable contained \ CMAKE_Fortran_ARCHIVE_APPEND \ CMAKE_Fortran_ARCHIVE_CREATE \ CMAKE_Fortran_ARCHIVE_FINISH + \ CMAKE_Fortran_BYTE_ORDER \ CMAKE_Fortran_CLANG_TIDY + \ CMAKE_Fortran_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_Fortran_COMPILER \ CMAKE_Fortran_COMPILER_ABI \ CMAKE_Fortran_COMPILER_AR \ CMAKE_Fortran_COMPILER_ARCHITECTURE_ID \ CMAKE_Fortran_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_Fortran_COMPILER_FRONTEND_VARIANT \ CMAKE_Fortran_COMPILER_ID \ CMAKE_Fortran_COMPILER_LAUNCHER \ CMAKE_Fortran_COMPILER_LOADED @@ -1103,6 +1250,8 @@ syn keyword cmakeVariable contained \ CMAKE_Fortran_CREATE_SHARED_LIBRARY \ CMAKE_Fortran_CREATE_SHARED_MODULE \ CMAKE_Fortran_CREATE_STATIC_LIBRARY + \ CMAKE_Fortran_EXTENSIONS + \ CMAKE_Fortran_EXTENSIONS_DEFAULT \ CMAKE_Fortran_FLAGS \ CMAKE_Fortran_FLAGS_DEBUG \ CMAKE_Fortran_FLAGS_DEBUG_INIT @@ -1122,6 +1271,7 @@ syn keyword cmakeVariable contained \ CMAKE_Fortran_INCLUDE_WHAT_YOU_USE \ CMAKE_Fortran_INIT \ CMAKE_Fortran_LIBRARY_ARCHITECTURE + \ CMAKE_Fortran_LINKER_LAUNCHER \ CMAKE_Fortran_LINKER_PREFERENCE \ CMAKE_Fortran_LINKER_PREFERENCE_PROPAGATES \ CMAKE_Fortran_LINKER_WRAPPER_FLAG @@ -1130,6 +1280,7 @@ syn keyword cmakeVariable contained \ CMAKE_Fortran_LINK_LIBRARY_FILE_FLAG \ CMAKE_Fortran_LINK_LIBRARY_FLAG \ CMAKE_Fortran_LINK_LIBRARY_SUFFIX + \ CMAKE_Fortran_LINK_WHAT_YOU_USE_FLAG \ CMAKE_Fortran_MODDIR_DEFAULT \ CMAKE_Fortran_MODDIR_FLAG \ CMAKE_Fortran_MODOUT_FLAG @@ -1141,8 +1292,12 @@ syn keyword cmakeVariable contained \ CMAKE_Fortran_SIMULATE_VERSION \ CMAKE_Fortran_SIZEOF_DATA_PTR \ CMAKE_Fortran_SOURCE_FILE_EXTENSIONS + \ CMAKE_Fortran_STANDARD + \ CMAKE_Fortran_STANDARD_DEFAULT \ CMAKE_Fortran_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_Fortran_STANDARD_LIBRARIES + \ CMAKE_Fortran_STANDARD_REQUIRED + \ CMAKE_Fortran_SUPPORTED \ CMAKE_Fortran_VISIBILITY_PRESET \ CMAKE_GENERATOR \ CMAKE_GENERATOR_INSTANCE @@ -1162,12 +1317,15 @@ syn keyword cmakeVariable contained \ CMAKE_HIP_ARCHIVE_APPEND \ CMAKE_HIP_ARCHIVE_CREATE \ CMAKE_HIP_ARCHIVE_FINISH + \ CMAKE_HIP_BYTE_ORDER \ CMAKE_HIP_CLANG_TIDY + \ CMAKE_HIP_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_HIP_COMPILER \ CMAKE_HIP_COMPILER_ABI \ CMAKE_HIP_COMPILER_AR \ CMAKE_HIP_COMPILER_ARCHITECTURE_ID \ CMAKE_HIP_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_HIP_COMPILER_FRONTEND_VARIANT \ CMAKE_HIP_COMPILER_ID \ CMAKE_HIP_COMPILER_LAUNCHER \ CMAKE_HIP_COMPILER_LOADED @@ -1184,6 +1342,7 @@ syn keyword cmakeVariable contained \ CMAKE_HIP_CREATE_SHARED_MODULE \ CMAKE_HIP_CREATE_STATIC_LIBRARY \ CMAKE_HIP_EXTENSIONS + \ CMAKE_HIP_EXTENSIONS_DEFAULT \ CMAKE_HIP_FLAGS \ CMAKE_HIP_FLAGS_DEBUG \ CMAKE_HIP_FLAGS_DEBUG_INIT @@ -1211,6 +1370,7 @@ syn keyword cmakeVariable contained \ CMAKE_HIP_LINK_LIBRARY_FILE_FLAG \ CMAKE_HIP_LINK_LIBRARY_FLAG \ CMAKE_HIP_LINK_LIBRARY_SUFFIX + \ CMAKE_HIP_LINK_WHAT_YOU_USE_FLAG \ CMAKE_HIP_OUTPUT_EXTENSION \ CMAKE_HIP_PLATFORM_ID \ CMAKE_HIP_SIMULATE_ID @@ -1218,12 +1378,16 @@ syn keyword cmakeVariable contained \ CMAKE_HIP_SIZEOF_DATA_PTR \ CMAKE_HIP_SOURCE_FILE_EXTENSIONS \ CMAKE_HIP_STANDARD + \ CMAKE_HIP_STANDARD_DEFAULT \ CMAKE_HIP_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_HIP_STANDARD_LIBRARIES \ CMAKE_HIP_STANDARD_REQUIRED + \ CMAKE_HIP_SUPPORTED \ CMAKE_HIP_VISIBILITY_PRESET \ CMAKE_HOME_DIRECTORY \ CMAKE_HOST_APPLE + \ CMAKE_HOST_BSD + \ CMAKE_HOST_LINUX \ CMAKE_HOST_SOLARIS \ CMAKE_HOST_SYSTEM \ CMAKE_HOST_SYSTEM_NAME @@ -1232,6 +1396,7 @@ syn keyword cmakeVariable contained \ CMAKE_HOST_UNIX \ CMAKE_HOST_WIN32 \ CMAKE_IGNORE_PATH + \ CMAKE_IGNORE_PREFIX_PATH \ CMAKE_IMPORT_LIBRARY_PREFIX \ CMAKE_IMPORT_LIBRARY_SUFFIX \ CMAKE_INCLUDE_CURRENT_DIR @@ -1252,6 +1417,7 @@ syn keyword cmakeVariable contained \ CMAKE_INTERPROCEDURAL_OPTIMIZATION \ CMAKE_IOS_INSTALL_COMBINED \ CMAKE_ISPC_HEADER_DIRECTORY + \ CMAKE_ISPC_HEADER_SUFFIX \ CMAKE_ISPC_INSTRUCTION_SETS \ CMAKE_JOB_POOLS \ CMAKE_JOB_POOL_COMPILE @@ -1264,12 +1430,15 @@ syn keyword cmakeVariable contained \ CMAKE_Java_ARCHIVE_APPEND \ CMAKE_Java_ARCHIVE_CREATE \ CMAKE_Java_ARCHIVE_FINISH + \ CMAKE_Java_BYTE_ORDER \ CMAKE_Java_CLANG_TIDY + \ CMAKE_Java_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_Java_COMPILER \ CMAKE_Java_COMPILER_ABI \ CMAKE_Java_COMPILER_AR \ CMAKE_Java_COMPILER_ARCHITECTURE_ID \ CMAKE_Java_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_Java_COMPILER_FRONTEND_VARIANT \ CMAKE_Java_COMPILER_ID \ CMAKE_Java_COMPILER_LAUNCHER \ CMAKE_Java_COMPILER_LOADED @@ -1284,6 +1453,8 @@ syn keyword cmakeVariable contained \ CMAKE_Java_CREATE_SHARED_LIBRARY \ CMAKE_Java_CREATE_SHARED_MODULE \ CMAKE_Java_CREATE_STATIC_LIBRARY + \ CMAKE_Java_EXTENSIONS + \ CMAKE_Java_EXTENSIONS_DEFAULT \ CMAKE_Java_FLAGS \ CMAKE_Java_FLAGS_DEBUG \ CMAKE_Java_FLAGS_DEBUG_INIT @@ -1302,6 +1473,7 @@ syn keyword cmakeVariable contained \ CMAKE_Java_INCLUDE_WHAT_YOU_USE \ CMAKE_Java_INIT \ CMAKE_Java_LIBRARY_ARCHITECTURE + \ CMAKE_Java_LINKER_LAUNCHER \ CMAKE_Java_LINKER_PREFERENCE \ CMAKE_Java_LINKER_PREFERENCE_PROPAGATES \ CMAKE_Java_LINKER_WRAPPER_FLAG @@ -1310,15 +1482,22 @@ syn keyword cmakeVariable contained \ CMAKE_Java_LINK_LIBRARY_FILE_FLAG \ CMAKE_Java_LINK_LIBRARY_FLAG \ CMAKE_Java_LINK_LIBRARY_SUFFIX + \ CMAKE_Java_LINK_WHAT_YOU_USE_FLAG \ CMAKE_Java_OUTPUT_EXTENSION \ CMAKE_Java_PLATFORM_ID \ CMAKE_Java_SIMULATE_ID \ CMAKE_Java_SIMULATE_VERSION \ CMAKE_Java_SIZEOF_DATA_PTR \ CMAKE_Java_SOURCE_FILE_EXTENSIONS + \ CMAKE_Java_STANDARD + \ CMAKE_Java_STANDARD_DEFAULT \ CMAKE_Java_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_Java_STANDARD_LIBRARIES + \ CMAKE_Java_STANDARD_REQUIRED + \ CMAKE_Java_SUPPORTED \ CMAKE_Java_VISIBILITY_PRESET + \ CMAKE_KATE_FILES_MODE + \ CMAKE_KATE_MAKE_ARGUMENTS \ CMAKE_LIBRARY_ARCHITECTURE \ CMAKE_LIBRARY_ARCHITECTURE_REGEX \ CMAKE_LIBRARY_OUTPUT_DIRECTORY @@ -1326,14 +1505,17 @@ syn keyword cmakeVariable contained \ CMAKE_LIBRARY_PATH_FLAG \ CMAKE_LINK_DEF_FILE_FLAG \ CMAKE_LINK_DEPENDS_NO_SHARED + \ CMAKE_LINK_DEPENDS_USE_LINKER \ CMAKE_LINK_DIRECTORIES_BEFORE \ CMAKE_LINK_INTERFACE_LIBRARIES + \ CMAKE_LINK_LIBRARIES_ONLY_TARGETS \ CMAKE_LINK_LIBRARY_FILE_FLAG \ CMAKE_LINK_LIBRARY_FLAG \ CMAKE_LINK_LIBRARY_SUFFIX \ CMAKE_LINK_SEARCH_END_STATIC \ CMAKE_LINK_SEARCH_START_STATIC \ CMAKE_LINK_WHAT_YOU_USE + \ CMAKE_LINK_WHAT_YOU_USE_CHECK \ CMAKE_MACOSX_BUNDLE \ CMAKE_MACOSX_RPATH \ CMAKE_MAJOR_VERSION @@ -1351,6 +1533,8 @@ syn keyword cmakeVariable contained \ CMAKE_MODULE_LINKER_FLAGS_INIT \ CMAKE_MODULE_PATH \ CMAKE_MSVCIDE_RUN_PATH + \ CMAKE_MSVC_DEBUG_INFORMATION_FORMAT + \ CMAKE_MSVC_RUNTIME_CHECKS \ CMAKE_MSVC_RUNTIME_LIBRARY \ CMAKE_NETRC \ CMAKE_NETRC_FILE @@ -1378,6 +1562,7 @@ syn keyword cmakeVariable contained \ CMAKE_PCH_INSTANTIATE_TEMPLATES \ CMAKE_PCH_WARN_INVALID \ CMAKE_PDB_OUTPUT_DIRECTORY + \ CMAKE_PLATFORM_NO_VERSIONED_SONAME \ CMAKE_POSITION_INDEPENDENT_CODE \ CMAKE_PREFIX_PATH \ CMAKE_PROGRAM_PATH @@ -1386,6 +1571,7 @@ syn keyword cmakeVariable contained \ CMAKE_PROJECT_INCLUDE \ CMAKE_PROJECT_INCLUDE_BEFORE \ CMAKE_PROJECT_NAME + \ CMAKE_PROJECT_TOP_LEVEL_INCLUDES \ CMAKE_PROJECT_VERSION \ CMAKE_PROJECT_VERSION_MAJOR \ CMAKE_PROJECT_VERSION_MINOR @@ -1399,12 +1585,15 @@ syn keyword cmakeVariable contained \ CMAKE_RC_ARCHIVE_APPEND \ CMAKE_RC_ARCHIVE_CREATE \ CMAKE_RC_ARCHIVE_FINISH + \ CMAKE_RC_BYTE_ORDER \ CMAKE_RC_CLANG_TIDY + \ CMAKE_RC_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_RC_COMPILER \ CMAKE_RC_COMPILER_ABI \ CMAKE_RC_COMPILER_AR \ CMAKE_RC_COMPILER_ARCHITECTURE_ID \ CMAKE_RC_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_RC_COMPILER_FRONTEND_VARIANT \ CMAKE_RC_COMPILER_ID \ CMAKE_RC_COMPILER_LAUNCHER \ CMAKE_RC_COMPILER_LOADED @@ -1419,6 +1608,8 @@ syn keyword cmakeVariable contained \ CMAKE_RC_CREATE_SHARED_LIBRARY \ CMAKE_RC_CREATE_SHARED_MODULE \ CMAKE_RC_CREATE_STATIC_LIBRARY + \ CMAKE_RC_EXTENSIONS + \ CMAKE_RC_EXTENSIONS_DEFAULT \ CMAKE_RC_FLAGS \ CMAKE_RC_FLAGS_DEBUG \ CMAKE_RC_FLAGS_DEBUG_INIT @@ -1437,6 +1628,7 @@ syn keyword cmakeVariable contained \ CMAKE_RC_INCLUDE_WHAT_YOU_USE \ CMAKE_RC_INIT \ CMAKE_RC_LIBRARY_ARCHITECTURE + \ CMAKE_RC_LINKER_LAUNCHER \ CMAKE_RC_LINKER_PREFERENCE \ CMAKE_RC_LINKER_PREFERENCE_PROPAGATES \ CMAKE_RC_LINKER_WRAPPER_FLAG @@ -1445,19 +1637,25 @@ syn keyword cmakeVariable contained \ CMAKE_RC_LINK_LIBRARY_FILE_FLAG \ CMAKE_RC_LINK_LIBRARY_FLAG \ CMAKE_RC_LINK_LIBRARY_SUFFIX + \ CMAKE_RC_LINK_WHAT_YOU_USE_FLAG \ CMAKE_RC_OUTPUT_EXTENSION \ CMAKE_RC_PLATFORM_ID \ CMAKE_RC_SIMULATE_ID \ CMAKE_RC_SIMULATE_VERSION \ CMAKE_RC_SIZEOF_DATA_PTR \ CMAKE_RC_SOURCE_FILE_EXTENSIONS + \ CMAKE_RC_STANDARD + \ CMAKE_RC_STANDARD_DEFAULT \ CMAKE_RC_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_RC_STANDARD_LIBRARIES + \ CMAKE_RC_STANDARD_REQUIRED + \ CMAKE_RC_SUPPORTED \ CMAKE_RC_VISIBILITY_PRESET \ CMAKE_ROOT \ CMAKE_RULE_MESSAGES \ CMAKE_RUNTIME_OUTPUT_DIRECTORY \ CMAKE_SCRIPT_MODE_FILE + \ CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS \ CMAKE_SHARED_LIBRARY_PREFIX \ CMAKE_SHARED_LIBRARY_SUFFIX \ CMAKE_SHARED_LINKER_FLAGS @@ -1470,6 +1668,7 @@ syn keyword cmakeVariable contained \ CMAKE_SKIP_INSTALL_RPATH \ CMAKE_SKIP_INSTALL_RULES \ CMAKE_SKIP_RPATH + \ CMAKE_SKIP_TEST_ALL_DEPENDENCY \ CMAKE_SOURCE_DIR \ CMAKE_STAGING_PREFIX \ CMAKE_STATIC_LIBRARY_PREFIX @@ -1486,6 +1685,7 @@ syn keyword cmakeVariable contained \ CMAKE_SYSTEM_APPBUNDLE_PATH \ CMAKE_SYSTEM_FRAMEWORK_PATH \ CMAKE_SYSTEM_IGNORE_PATH + \ CMAKE_SYSTEM_IGNORE_PREFIX_PATH \ CMAKE_SYSTEM_INCLUDE_PATH \ CMAKE_SYSTEM_LIBRARY_PATH \ CMAKE_SYSTEM_NAME @@ -1500,12 +1700,15 @@ syn keyword cmakeVariable contained \ CMAKE_Swift_ARCHIVE_APPEND \ CMAKE_Swift_ARCHIVE_CREATE \ CMAKE_Swift_ARCHIVE_FINISH + \ CMAKE_Swift_BYTE_ORDER \ CMAKE_Swift_CLANG_TIDY + \ CMAKE_Swift_CLANG_TIDY_EXPORT_FIXES_DIR \ CMAKE_Swift_COMPILER \ CMAKE_Swift_COMPILER_ABI \ CMAKE_Swift_COMPILER_AR \ CMAKE_Swift_COMPILER_ARCHITECTURE_ID \ CMAKE_Swift_COMPILER_EXTERNAL_TOOLCHAIN + \ CMAKE_Swift_COMPILER_FRONTEND_VARIANT \ CMAKE_Swift_COMPILER_ID \ CMAKE_Swift_COMPILER_LAUNCHER \ CMAKE_Swift_COMPILER_LOADED @@ -1520,6 +1723,8 @@ syn keyword cmakeVariable contained \ CMAKE_Swift_CREATE_SHARED_LIBRARY \ CMAKE_Swift_CREATE_SHARED_MODULE \ CMAKE_Swift_CREATE_STATIC_LIBRARY + \ CMAKE_Swift_EXTENSIONS + \ CMAKE_Swift_EXTENSIONS_DEFAULT \ CMAKE_Swift_FLAGS \ CMAKE_Swift_FLAGS_DEBUG \ CMAKE_Swift_FLAGS_DEBUG_INIT @@ -1539,6 +1744,7 @@ syn keyword cmakeVariable contained \ CMAKE_Swift_INIT \ CMAKE_Swift_LANGUAGE_VERSION \ CMAKE_Swift_LIBRARY_ARCHITECTURE + \ CMAKE_Swift_LINKER_LAUNCHER \ CMAKE_Swift_LINKER_PREFERENCE \ CMAKE_Swift_LINKER_PREFERENCE_PROPAGATES \ CMAKE_Swift_LINKER_WRAPPER_FLAG @@ -1547,6 +1753,7 @@ syn keyword cmakeVariable contained \ CMAKE_Swift_LINK_LIBRARY_FILE_FLAG \ CMAKE_Swift_LINK_LIBRARY_FLAG \ CMAKE_Swift_LINK_LIBRARY_SUFFIX + \ CMAKE_Swift_LINK_WHAT_YOU_USE_FLAG \ CMAKE_Swift_MODULE_DIRECTORY \ CMAKE_Swift_NUM_THREADS \ CMAKE_Swift_OUTPUT_EXTENSION @@ -1555,16 +1762,25 @@ syn keyword cmakeVariable contained \ CMAKE_Swift_SIMULATE_VERSION \ CMAKE_Swift_SIZEOF_DATA_PTR \ CMAKE_Swift_SOURCE_FILE_EXTENSIONS + \ CMAKE_Swift_STANDARD + \ CMAKE_Swift_STANDARD_DEFAULT \ CMAKE_Swift_STANDARD_INCLUDE_DIRECTORIES \ CMAKE_Swift_STANDARD_LIBRARIES + \ CMAKE_Swift_STANDARD_REQUIRED + \ CMAKE_Swift_SUPPORTED \ CMAKE_Swift_VISIBILITY_PRESET + \ CMAKE_TASKING_TOOLSET + \ CMAKE_TLS_CAINFO + \ CMAKE_TLS_VERIFY \ CMAKE_TOOLCHAIN_FILE \ CMAKE_TRY_COMPILE_CONFIGURATION + \ CMAKE_TRY_COMPILE_NO_PLATFORM_VARIABLES \ CMAKE_TRY_COMPILE_PLATFORM_VARIABLES \ CMAKE_TRY_COMPILE_TARGET_TYPE \ CMAKE_TWEAK_VERSION \ CMAKE_UNITY_BUILD \ CMAKE_UNITY_BUILD_BATCH_SIZE + \ CMAKE_UNITY_BUILD_UNIQUE_ID \ CMAKE_USER_MAKE_RULES_OVERRIDE \ CMAKE_USER_MAKE_RULES_OVERRIDE_ASM \ CMAKE_USER_MAKE_RULES_OVERRIDE_ASM_MASM @@ -1580,8 +1796,13 @@ syn keyword cmakeVariable contained \ CMAKE_USER_MAKE_RULES_OVERRIDE_Swift \ CMAKE_USE_RELATIVE_PATHS \ CMAKE_VERBOSE_MAKEFILE + \ CMAKE_VERIFY_INTERFACE_HEADER_SETS \ CMAKE_VERSION \ CMAKE_VISIBILITY_INLINES_HIDDEN + \ CMAKE_VS_DEBUGGER_COMMAND + \ CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS + \ CMAKE_VS_DEBUGGER_ENVIRONMENT + \ CMAKE_VS_DEBUGGER_WORKING_DIRECTORY \ CMAKE_VS_DEVENV_COMMAND \ CMAKE_VS_GLOBALS \ CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD @@ -1589,6 +1810,8 @@ syn keyword cmakeVariable contained \ CMAKE_VS_INTEL_Fortran_PROJECT_VERSION \ CMAKE_VS_JUST_MY_CODE_DEBUGGING \ CMAKE_VS_MSBUILD_COMMAND + \ CMAKE_VS_NO_COMPILE_BATCHING + \ CMAKE_VS_NUGET_PACKAGE_RESTORE \ CMAKE_VS_NsightTegra_VERSION \ CMAKE_VS_PLATFORM_NAME \ CMAKE_VS_PLATFORM_NAME_DEFAULT @@ -1604,11 +1827,17 @@ syn keyword cmakeVariable contained \ CMAKE_VS_SDK_LIBRARY_WINRT_DIRECTORIES \ CMAKE_VS_SDK_REFERENCE_DIRECTORIES \ CMAKE_VS_SDK_SOURCE_DIRECTORIES + \ CMAKE_VS_TARGET_FRAMEWORK_IDENTIFIER + \ CMAKE_VS_TARGET_FRAMEWORK_TARGETS_VERSION + \ CMAKE_VS_TARGET_FRAMEWORK_VERSION + \ CMAKE_VS_VERSION_BUILD_NUMBER + \ CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION \ CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION \ CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM \ CMAKE_VS_WINRT_BY_DEFAULT \ CMAKE_WARN_DEPRECATED \ CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION + \ CMAKE_WATCOM_RUNTIME_LIBRARY \ CMAKE_WIN32_EXECUTABLE \ CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS \ CMAKE_XCODE_BUILD_SYSTEM @@ -1623,9 +1852,12 @@ syn keyword cmakeVariable contained \ CMAKE_XCODE_SCHEME_DISABLE_MAIN_THREAD_CHECKER \ CMAKE_XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS \ CMAKE_XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE + \ CMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION + \ CMAKE_XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION \ CMAKE_XCODE_SCHEME_ENVIRONMENT \ CMAKE_XCODE_SCHEME_GUARD_MALLOC \ CMAKE_XCODE_SCHEME_LAUNCH_MODE + \ CMAKE_XCODE_SCHEME_LLDB_INIT_FILE \ CMAKE_XCODE_SCHEME_MAIN_THREAD_CHECKER_STOP \ CMAKE_XCODE_SCHEME_MALLOC_GUARD_EDGES \ CMAKE_XCODE_SCHEME_MALLOC_SCRIBBLE @@ -1637,10 +1869,13 @@ syn keyword cmakeVariable contained \ CMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION \ CMAKE_XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION \ CMAKE_XCODE_SCHEME_LAUNCH_CONFIGURATION + \ CMAKE_XCODE_SCHEME_TEST_CONFIGURATION \ CMAKE_XCODE_SCHEME_WORKING_DIRECTORY \ CMAKE_XCODE_SCHEME_ZOMBIE_OBJECTS + \ CMAKE_XCODE_XCCONFIG \ CPACK_ABSOLUTE_DESTINATION_FILES \ CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY + \ CPACK_CUSTOM_INSTALL_VARIABLES \ CPACK_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION \ CPACK_INCLUDE_TOPLEVEL_DIRECTORY \ CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS @@ -1675,6 +1910,7 @@ syn keyword cmakeVariable contained \ CTEST_CUSTOM_PRE_MEMCHECK \ CTEST_CUSTOM_PRE_TEST \ CTEST_CUSTOM_TESTS_IGNORE + \ CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION \ CTEST_CUSTOM_WARNING_EXCEPTION \ CTEST_CUSTOM_WARNING_MATCH \ CTEST_CVS_CHECKOUT @@ -1707,6 +1943,7 @@ syn keyword cmakeVariable contained \ CTEST_RESOURCE_SPEC_FILE \ CTEST_RUN_CURRENT_SCRIPT \ CTEST_SCP_COMMAND + \ CTEST_SCRIPT_DIRECTORY \ CTEST_SITE \ CTEST_SOURCE_DIRECTORY \ CTEST_SUBMIT_INACTIVITY_TIMEOUT @@ -2005,6 +2242,7 @@ syn keyword cmakeVariable contained \ GHSMULTI \ IOS \ LIBRARY_OUTPUT_PATH + \ LINUX \ MINGW \ MSVC \ MSVC10 @@ -2023,6 +2261,7 @@ syn keyword cmakeVariable contained \ PROJECT_BINARY_DIR \ PROJECT_DESCRIPTION \ PROJECT_HOMEPAGE_URL + \ PROJECT_IS_TOP_LEVEL \ PROJECT_NAME \ PROJECT_SOURCE_DIR \ PROJECT_VERSION @@ -2040,6 +2279,7 @@ syn keyword cmakeVariable contained syn keyword cmakeModule contained \ ExternalProject + \ FetchContent syn keyword cmakeKWExternalProject contained \ AWS @@ -2053,10 +2293,10 @@ syn keyword cmakeKWExternalProject contained \ CMAKE_CACHE_ARGS \ CMAKE_CACHE_DEFAULT_ARGS \ CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY - \ CMAKE_TLS_CAINFO - \ CMAKE_TLS_VERIFY + \ CMAKE_INSTALL_MODE \ COMMENT \ CONFIGURE_COMMAND + \ CONFIGURE_HANDLED_BY_BUILD \ CVS \ CVSROOT \ CVS_MODULE @@ -2068,6 +2308,7 @@ syn keyword cmakeKWExternalProject contained \ DOWNLOADED_FILE \ DOWNLOAD_COMMAND \ DOWNLOAD_DIR + \ DOWNLOAD_EXTRACT_TIMESTAMP \ DOWNLOAD_NAME \ DOWNLOAD_NO_EXTRACT \ DOWNLOAD_NO_PROGRESS @@ -2159,24 +2400,90 @@ syn keyword cmakeKWExternalProject contained \ USES_TERMINAL_UPDATE \ WORKING_DIRECTORY +syn keyword cmakeKWFetchContent contained + \ ALWAYS + \ BINARY_DIR + \ BUILD_COMMAND + \ BYPASS_PROVIDER + \ CMAKE_PROJECT_ + \ CONFIGURE_COMMAND + \ COPY + \ CORRECT + \ DCMAKE_TOOLCHAIN_FILE + \ DESTINATION + \ DOWNLOAD_NO_EXTRACT + \ EXISTS + \ FETCHCONTENT_BASE_DIR + \ FETCHCONTENT_FULLY_DISCONNECTED + \ FETCHCONTENT_MAKEAVAILABLE_SERIAL + \ FETCHCONTENT_QUIET + \ FETCHCONTENT_SOURCE_DIR_ + \ FETCHCONTENT_TRY_FIND_PACKAGE_MODE + \ FETCHCONTENT_UPDATES_DISCONNECTED + \ FETCHCONTENT_UPDATES_DISCONNECTED_ + \ FIND_PACKAGE_ARGS + \ GIT_REPOSITORY + \ GIT_TAG + \ GLOBAL + \ GTEST_BOTH_LIBRARIES + \ GTEST_LIBRARIES + \ GTEST_MAIN_LIBRARIES + \ INSTALL_COMMAND + \ INTERNAL + \ NAME + \ NAMES + \ NEVER + \ NOTE + \ OFF + \ OPTIONAL + \ OPT_IN + \ OVERRIDE_FIND_PACKAGE + \ PACKAGE_VERSION_COMPATIBLE + \ PACKAGE_VERSION_EXACT + \ QUIET + \ SOURCE_SUBDIR + \ STREQUAL + \ SUBBUILD_DIR + \ SVN_REPOSITORY + \ SVN_REVISION + \ SYSTEM + \ TARGET + \ TEST_COMMAND + \ TRUE + \ URL + \ URL_HASH + \ VERIFY_INTERFACE_HEADER_SETS + \ WRITE + \ WRONG + \ _BINARY_DIR + \ _INCLUDE + \ _POPULATED + \ _SOURCE_DIR + syn keyword cmakeKWadd_compile_definitions contained \ COMPILE_DEFINITIONS \ VAR syn keyword cmakeKWadd_compile_options contained + \ CMAKE_ + \ COMPILE_LANGUAGE \ COMPILE_OPTIONS + \ CONFIG \ SHELL \ UNIX_COMMAND - \ WX + \ _FLAGS + \ _FLAGS_ syn keyword cmakeKWadd_custom_command contained \ APPEND \ ARGS + \ BNF \ BYPRODUCTS \ CC \ COMMAND \ COMMAND_EXPAND_LISTS \ COMMENT + \ CONFIG \ CROSSCOMPILING_EMULATOR \ DEPENDS \ DEPENDS_EXPLICIT_ONLY @@ -2188,6 +2495,7 @@ syn keyword cmakeKWadd_custom_command contained \ JOB_POOLS \ JOIN \ MAIN_DEPENDENCY + \ MODULE \ NOT \ OUTPUT \ PATH @@ -2263,17 +2571,21 @@ syn keyword cmakeKWadd_library contained \ FRAMEWORK \ GLOBAL \ HEADER_FILE_ONLY + \ HEADER_SETS \ IMPORTED \ IMPORTED_ \ IMPORTED_IMPLIB \ IMPORTED_IMPLIB_ \ IMPORTED_LOCATION \ IMPORTED_LOCATION_ + \ IMPORTED_NO_SONAME \ IMPORTED_OBJECTS \ IMPORTED_OBJECTS_ + \ IMPORTED_SONAME \ INTERFACE \ INTERFACE_ \ INTERFACE_SOURCES + \ LC_ID_DYLIB \ LIBRARY_OUTPUT_DIRECTORY \ MODULE \ OBJECT @@ -2283,22 +2595,25 @@ syn keyword cmakeKWadd_library contained \ POST_BUILD \ PRE_BUILD \ PRE_LINK - \ PRIVATE - \ PUBLIC + \ PRIVATE_HEADER + \ PUBLIC_HEADER \ RUNTIME_OUTPUT_DIRECTORY \ SHARED + \ SONAME \ SOURCES \ STATIC + \ TARGETS \ TARGET_OBJECTS + \ TARGET_RUNTIME_DLLS \ UNKNOWN syn keyword cmakeKWadd_link_options contained \ CMAKE_ + \ CONFIG \ CUDA_RESOLVE_DEVICE_SYMBOLS \ CUDA_SEPARABLE_COMPILATION \ DEVICE_LINK \ GCC - \ GNU \ HOST_LINK \ LANG \ LINKER @@ -2306,11 +2621,14 @@ syn keyword cmakeKWadd_link_options contained \ SHELL \ STATIC_LIBRARY_OPTIONS \ UNIX_COMMAND + \ _FLAGS + \ _FLAGS_ \ _LINKER_WRAPPER_FLAG \ _LINKER_WRAPPER_FLAG_SEP syn keyword cmakeKWadd_subdirectory contained \ EXCLUDE_FROM_ALL + \ SYSTEM syn keyword cmakeKWadd_test contained \ BUILD_TESTING @@ -2326,13 +2644,61 @@ syn keyword cmakeKWadd_test contained \ WILL_FAIL \ WORKING_DIRECTORY +syn keyword cmakeKWblock contained + \ PARENT_SCOPE + \ POLICIES + \ PROPAGATE + \ PUSH + \ SCOPE_FOR + \ TRUE + \ VARIABLES + syn keyword cmakeKWbuild_command contained \ CONFIGURATION + \ PARALLEL_LEVEL \ TARGET +syn keyword cmakeKWcmake_file_api contained + \ API + \ API_VERSION + \ BUILD_DIR + \ CMAKEFILES + \ CODEMODEL + \ COMMAND + \ CONFIG + \ QUERY + \ TOOLCHAINS + syn keyword cmakeKWcmake_host_system_information contained + \ APPEND \ AVAILABLE_PHYSICAL_MEMORY \ AVAILABLE_VIRTUAL_MEMORY + \ BOTH + \ CMAKE_GET_OS_RELEASE_FALLBACK_CONTENT + \ CMAKE_GET_OS_RELEASE_FALLBACK_RESULT_ + \ CMAKE_GET_OS_RELEASE_FALLBACK_RESULT_ID + \ CMAKE_GET_OS_RELEASE_FALLBACK_RESULT_NAME + \ CMAKE_GET_OS_RELEASE_FALLBACK_RESULT_PRETTY_NAME + \ CMAKE_GET_OS_RELEASE_FALLBACK_RESULT_VERSION + \ CMAKE_GET_OS_RELEASE_FALLBACK_RESULT_VERSION_ID + \ CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS + \ DISTRIB_INFO + \ DISTRIB_PRETTY_NAME + \ DISTRO + \ DISTRO_BUG_REPORT_URL + \ DISTRO_HOME_URL + \ DISTRO_ID + \ DISTRO_ID_LIKE + \ DISTRO_NAME + \ DISTRO_PRETTY_NAME + \ DISTRO_PRIVACY_POLICY_URL + \ DISTRO_SUPPORT_URL + \ DISTRO_UBUNTU_CODENAME + \ DISTRO_VERSION + \ DISTRO_VERSION_CODENAME + \ DISTRO_VERSION_ID + \ ERROR_VARIABLE + \ EXISTS \ FQDN \ HAS_FPU \ HAS_MMX @@ -2341,38 +2707,100 @@ syn keyword cmakeKWcmake_host_system_information contained \ HAS_SSE \ HAS_SSE_FP \ HAS_SSE_MMX + \ HKCC + \ HKCR + \ HKCU + \ HKEY_CLASSES_ROOT + \ HKEY_CURRENT_CONFIG + \ HKEY_CURRENT_USER + \ HKEY_LOCAL_MACHINE + \ HKEY_USERS + \ HKLM + \ HKU \ HOSTNAME \ ID + \ LIMIT_COUNT + \ LISTS + \ LTS + \ MATCHES + \ NNN + \ NOT \ NUMBER_OF_LOGICAL_CORES \ NUMBER_OF_PHYSICAL_CORES \ OS_NAME \ OS_PLATFORM \ OS_RELEASE \ OS_VERSION + \ PRETTY_NAME \ PROCESSOR_DESCRIPTION \ PROCESSOR_NAME \ PROCESSOR_SERIAL_NUMBER \ QUERY + \ REG_DWORD + \ REG_EXPAND_SZ + \ REG_MULTI_SZ + \ REG_QWORD + \ REG_SZ \ RESULT + \ SEPARATOR + \ SOFTWARE + \ STATUS + \ STRINGS + \ SUBKEYS + \ TARGET \ TOTAL_PHYSICAL_MEMORY \ TOTAL_VIRTUAL_MEMORY + \ VALUE_NAMES + \ VAR + \ VIEW + \ WINDOWS_REGISTRY syn keyword cmakeKWcmake_language contained \ AND + \ ANY + \ APPEND + \ ARGN + \ BINARY_DIR + \ BYPASS_PROVIDER \ CALL \ CANCEL_CALL \ CODE + \ COMMAND + \ COMMAND_ERROR_IS_FATAL + \ DCMAKE_PROJECT_TOP_LEVEL_INCLUDES \ DEFER \ DIRECTORY \ EVAL \ FALSE + \ FETCHCONTENT_MAKEAVAILABLE_SERIAL + \ FETCHCONTENT_SOURCE_DIR_ + \ FETCHCONTENT_TRY_FIND_PACKAGE_MODE + \ FIND_PACKAGE + \ FIND_PACKAGE_ARGS \ GET_CALL_IDS - \ ID + \ GET_MESSAGE_LOG_LEVEL + \ GIT_REPOSITORY + \ GIT_SUBMODULES + \ GIT_TAG \ ID_VAR - \ OR + \ MATCHES + \ MYCOMP_PROVIDER_INSTALL_DIR + \ NEVER + \ NOT + \ OVERRIDE_FIND_PACKAGE + \ PATH + \ POP_BACK + \ QUIET + \ SET_DEPENDENCY_PROVIDER + \ SOURCE_DIR \ STATUS + \ STREQUAL + \ SUPPORTED_METHODS \ TRUE + \ VERSION \ WRITE + \ _FOUND + \ _PATH syn keyword cmakeKWcmake_minimum_required contained \ FATAL_ERROR @@ -2407,22 +2835,21 @@ syn keyword cmakeKWcmake_path contained \ ABSOLUTE_PATH \ AND \ APPEND + \ APPEND_STRING \ BASE_DIRECTORY - \ CMAKE_PATH \ COMPARE - \ CONCAT \ CONVERT - \ ELSEIF - \ ENDIF + \ EQUAL \ EXTENSION \ EXTENSION_DEF \ FALSE + \ FILENAME \ FILENAME_DEF \ GET \ GET_EXTENSION \ GET_FILENAME \ GET_PARENT_PATH - \ GET_RELATIVE_PATH + \ GET_RELATIVE_PART \ GET_ROOT_DIRECTORY \ GET_ROOT_NAME \ GET_ROOT_PATH @@ -2431,12 +2858,11 @@ syn keyword cmakeKWcmake_path contained \ HAS_EXTENSION \ HAS_FILENAME \ HAS_PARENT_PATH - \ HAS_RELATIVE_PATH + \ HAS_RELATIVE_PART \ HAS_ROOT_DIRECTORY \ HAS_ROOT_NAME \ HAS_ROOT_PATH \ HAS_STEM - \ IF \ IS_ABSOLUTE \ IS_PREFIX \ IS_RELATIVE @@ -2445,23 +2871,21 @@ syn keyword cmakeKWcmake_path contained \ NATIVE_PATH \ NORMALIZE \ NORMAL_PATH - \ NOT \ NOT_EQUAL \ OP - \ OS \ OUTPUT_VARIABLE \ PARENT_PATH - \ PROXIMATE_PATH \ REAL_PATH + \ RELATIVE_PART \ RELATIVE_PATH \ REMOVE_EXTENSION \ REMOVE_FILENAME \ REPLACE_EXTENSION \ REPLACE_FILENAME - \ RETURN \ ROOT_DIRECTORY \ ROOT_NAME \ ROOT_PATH + \ SET \ STEM \ STREQUAL \ TO_CMAKE_PATH_LIST @@ -2476,8 +2900,10 @@ syn keyword cmakeKWcmake_policy contained \ NNNN \ NO_POLICY_SCOPE \ OLD + \ POLICIES \ POP \ PUSH + \ SCOPE_FOR \ SET \ VERSION @@ -2489,9 +2915,14 @@ syn keyword cmakeKWconfigure_file contained \ FILE_PERMISSIONS \ FOO_ENABLE \ FOO_STRING + \ GENERATE + \ INTERFACE \ LF \ NEWLINE_STYLE \ NO_SOURCE_PERMISSIONS + \ PRIVATE + \ PUBLIC + \ SYSTEM \ USE_SOURCE_PERMISSIONS \ VAR @@ -2506,6 +2937,7 @@ syn keyword cmakeKWctest_build contained \ APPEND \ BUILD \ CAPTURE_CMAKE_ERROR + \ CMAKE_BUILD_PARALLEL_LEVEL \ CONFIGURATION \ CTEST_BUILD_CONFIGURATION \ CTEST_BUILD_FLAGS @@ -2513,6 +2945,7 @@ syn keyword cmakeKWctest_build contained \ FLAGS \ NUMBER_ERRORS \ NUMBER_WARNINGS + \ PARALLEL_LEVEL \ QUIET \ RETURN_VALUE \ TARGET @@ -2537,6 +2970,7 @@ syn keyword cmakeKWctest_coverage contained syn keyword cmakeKWctest_memcheck contained \ APPEND \ BUILD + \ CAPTURE_CMAKE_ERROR \ DEFECT_COUNT \ EXCLUDE \ EXCLUDE_FIXTURE @@ -2547,11 +2981,15 @@ syn keyword cmakeKWctest_memcheck contained \ INCLUDE_LABEL \ OFF \ ON + \ OUTPUT_JUNIT \ PARALLEL_LEVEL \ QUIET + \ REPEAT + \ RESOURCE_SPEC_FILE \ RETURN_VALUE \ SCHEDULE_RANDOM \ START + \ STOP_ON_FAILURE \ STOP_TIME \ STRIDE \ TEST_LOAD @@ -2587,6 +3025,8 @@ syn keyword cmakeKWctest_submit contained syn keyword cmakeKWctest_test contained \ AFTER_TIMEOUT \ APPEND + \ ATTACHED_FILES + \ ATTACHED_FILES_ON_FAIL \ BUILD \ CAPTURE_CMAKE_ERROR \ CPU @@ -2597,8 +3037,10 @@ syn keyword cmakeKWctest_test contained \ EXCLUDE_LABEL \ INCLUDE \ INCLUDE_LABEL + \ LABELS \ OFF \ ON + \ OUTPUT_JUNIT \ PARALLEL_LEVEL \ QUIET \ REPEAT @@ -2612,6 +3054,8 @@ syn keyword cmakeKWctest_test contained \ TEST_LOAD \ UNTIL_FAIL \ UNTIL_PASS + \ URL + \ XML syn keyword cmakeKWctest_update contained \ CAPTURE_CMAKE_ERROR @@ -2629,15 +3073,18 @@ syn keyword cmakeKWdefine_property contained \ APPEND_STRING \ BRIEF_DOCS \ CACHED_VARIABLE + \ CMAKE_ \ DIRECTORY \ FULL_DOCS \ GLOBAL \ INHERITED + \ INITIALIZE_FROM_VARIABLE \ PROPERTY \ SOURCE \ TARGET \ TEST \ VARIABLE + \ _CMAKE_ syn keyword cmakeKWdoxygen_add_docs contained \ ALL @@ -2647,6 +3094,10 @@ syn keyword cmakeKWdoxygen_add_docs contained syn keyword cmakeKWenable_language contained \ ASM + \ ASM_MARMASM + \ ASM_MASM + \ ASM_NASM + \ ATT \ CUDA \ HIP \ ISPC @@ -2684,6 +3135,7 @@ syn keyword cmakeKWexecute_process contained \ OUTPUT_QUIET \ OUTPUT_STRIP_TRAILING_WHITESPACE \ OUTPUT_VARIABLE + \ POSIX \ RESULTS_VARIABLE \ RESULT_VARIABLE \ RFC @@ -2691,17 +3143,16 @@ syn keyword cmakeKWexecute_process contained \ STDOUT \ TIMEOUT \ UTF - \ VERBATIM \ WORKING_DIRECTORY syn keyword cmakeKWexport contained \ ANDROID_MK \ APPEND \ CONFIG + \ CXX_MODULES_DIRECTORY \ EXPORT \ EXPORT_LINK_INTERFACE_LIBRARIES \ FILE - \ IMPORTED \ IMPORTED_ \ NAMESPACE \ NDK @@ -2717,7 +3168,6 @@ syn keyword cmakeKWexport_library_dependencies contained \ SET syn keyword cmakeKWfile contained - \ ALGO \ APPEND \ ARCHIVE_CREATE \ ARCHIVE_EXTRACT @@ -2729,9 +3179,8 @@ syn keyword cmakeKWfile contained \ CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND \ CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM \ CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL + \ CMAKE_INSTALL_MODE \ CMAKE_OBJDUMP - \ CMAKE_TLS_CAINFO - \ CMAKE_TLS_VERIFY \ CODE \ COMPILE_FEATURES \ COMPRESSION @@ -2742,7 +3191,8 @@ syn keyword cmakeKWfile contained \ CONFLICTING_DEPENDENCIES_PREFIX \ CONTENT \ CONVERT - \ COPY + \ COPYONLY + \ COPY_FILE \ COPY_ON_ERROR \ CREATE_LINK \ CRLF @@ -2755,6 +3205,7 @@ syn keyword cmakeKWfile contained \ ENCODING \ ESCAPE_QUOTES \ EXECUTABLES + \ EXPAND_TILDE \ EXPECTED_HASH \ FILES_MATCHING \ FILE_PERMISSIONS @@ -2772,10 +3223,12 @@ syn keyword cmakeKWfile contained \ GUARD \ HASH \ HEX + \ HOME \ HTTPHEADER \ IGNORED \ INACTIVITY_TIMEOUT \ INPUT + \ INPUT_MAY_BE_RECENT \ INSTALL \ IS_ABSOLUTE \ LENGTH_MAXIMUM @@ -2800,9 +3253,11 @@ syn keyword cmakeKWfile contained \ NEWLINE_STYLE \ NOT \ NO_HEX_CONVERSION + \ NO_REPLACE \ NO_SOURCE_PERMISSIONS \ OFFSET \ ONLY + \ ONLY_IF_DIFFERENT \ OPTIONAL \ OUTPUT \ OWNER_EXECUTE @@ -2812,11 +3267,15 @@ syn keyword cmakeKWfile contained \ PATTERN \ PATTERNS \ PERMISSIONS + \ POST_EXCLUDE_FILES \ POST_EXCLUDE_REGEXES + \ POST_INCLUDE_FILES \ POST_INCLUDE_REGEXES \ PRE_EXCLUDE_REGEXES \ PRE_INCLUDE_REGEXES \ PROCESS + \ RANGE_END + \ RANGE_START \ READ \ READ_SYMLINK \ REAL_PATH @@ -2833,7 +3292,10 @@ syn keyword cmakeKWfile contained \ RESULT_VARIABLE \ RPATH \ RUNPATH + \ RUNTIME_DEPENDENCY_SET \ SCRIPT + \ SETGID + \ SETUID \ SHARED \ SHOW_PROGRESS \ SIZE @@ -2856,6 +3318,7 @@ syn keyword cmakeKWfile contained \ UNRESOLVED_DEPENDENCIES_VAR \ UPLOAD \ URL + \ USERPROFILE \ USERPWD \ USE_SOURCE_PERMISSIONS \ UTC @@ -2869,54 +3332,87 @@ syn keyword cmakeKWfile contained \ _FILENAMES syn keyword cmakeKWfind_file contained + \ BOTH + \ CATEGORY \ CMAKE_FIND_ROOT_PATH_BOTH + \ CMAKE_FIND_USE_ \ DOC \ DVAR \ FALSE + \ FIND_XXX_REGISTRY_VIEW \ HINTS + \ HOST \ INCLUDE + \ MATCHES \ NAMES + \ NOT + \ NO_CACHE \ NO_CMAKE_ENVIRONMENT_PATH \ NO_CMAKE_FIND_ROOT_PATH + \ NO_CMAKE_INSTALL_PREFIX \ NO_CMAKE_PATH \ NO_CMAKE_SYSTEM_PATH \ NO_DEFAULT_PATH \ NO_PACKAGE_ROOT_PATH \ NO_SYSTEM_ENVIRONMENT_PATH \ ONLY_CMAKE_FIND_ROOT_PATH + \ PACKAGENAME + \ PARENT_SCOPE \ PATHS \ PATH_SUFFIXES + \ REGISTRY_VIEW \ REQUIRED + \ TARGET + \ VALIDATOR \ VAR syn keyword cmakeKWfind_library contained + \ BOTH + \ CATEGORY \ CMAKE_FIND_ROOT_PATH_BOTH + \ CMAKE_FIND_USE_ \ DOC \ DVAR \ FALSE + \ FIND_XXX_REGISTRY_VIEW \ HINTS - \ INCLUDE + \ HOST + \ LIB + \ MATCHES \ NAMES \ NAMES_PER_DIR + \ NOT + \ NO_CACHE \ NO_CMAKE_ENVIRONMENT_PATH \ NO_CMAKE_FIND_ROOT_PATH + \ NO_CMAKE_INSTALL_PREFIX \ NO_CMAKE_PATH \ NO_CMAKE_SYSTEM_PATH \ NO_DEFAULT_PATH \ NO_PACKAGE_ROOT_PATH \ NO_SYSTEM_ENVIRONMENT_PATH \ ONLY_CMAKE_FIND_ROOT_PATH + \ PACKAGENAME + \ PARENT_SCOPE \ PATHS \ PATH_SUFFIXES + \ REGISTRY_VIEW \ REQUIRED + \ TARGET + \ VALIDATOR \ VAR syn keyword cmakeKWfind_package contained \ ABI + \ BOTH \ BUNDLE + \ BYPASS_PROVIDER + \ CATEGORY \ CMAKE_DISABLE_FIND_PACKAGE_ \ CMAKE_REQUIRE_FIND_PACKAGE_ \ CMAKE_FIND_ROOT_PATH_BOTH + \ CMAKE_FIND_USE_ + \ CMAKE_REQUIRE_FIND_PACKAGE_ \ COMPONENTS \ CONFIG \ CONFIGS @@ -2927,7 +3423,9 @@ syn keyword cmakeKWfind_package contained \ FALSE \ FIND_PACKAGE_VERSION_FORMAT \ FRAMEWORK + \ GLOBAL \ HINTS + \ HOST \ INCLUDE \ MODULE \ NAMES @@ -2935,6 +3433,7 @@ syn keyword cmakeKWfind_package contained \ NO_CMAKE_BUILDS_PATH \ NO_CMAKE_ENVIRONMENT_PATH \ NO_CMAKE_FIND_ROOT_PATH + \ NO_CMAKE_INSTALL_PREFIX \ NO_CMAKE_PACKAGE_REGISTRY \ NO_CMAKE_PATH \ NO_CMAKE_SYSTEM_PACKAGE_REGISTRY @@ -2944,8 +3443,10 @@ syn keyword cmakeKWfind_package contained \ NO_PACKAGE_ROOT_PATH \ NO_POLICY_SCOPE \ NO_SYSTEM_ENVIRONMENT_PATH + \ OLD \ ONLY_CMAKE_FIND_ROOT_PATH \ OPTIONAL_COMPONENTS + \ PACKAGENAME \ PACKAGE_FIND_NAME \ PACKAGE_FIND_VERSION \ PACKAGE_FIND_VERSION_COMPLETE @@ -2974,60 +3475,92 @@ syn keyword cmakeKWfind_package contained \ PATHS \ PATH_SUFFIXES \ QUIET + \ REGISTRY_VIEW \ REQUIRED \ SET + \ TARGET \ TRUE + \ VALUE \ _CONFIG \ _CONSIDERED_CONFIGS \ _CONSIDERED_VERSIONS \ _DIR \ _FIND_COMPONENTS \ _FIND_QUIETLY + \ _FIND_REGISTRY_VIEW \ _FIND_REQUIRED \ _FIND_REQUIRED_ \ _FIND_VERSION_EXACT \ _FOUND syn keyword cmakeKWfind_path contained + \ BOTH + \ CATEGORY \ CMAKE_FIND_ROOT_PATH_BOTH + \ CMAKE_FIND_USE_ \ DOC \ DVAR \ FALSE + \ FIND_XXX_REGISTRY_VIEW \ HINTS + \ HOST \ INCLUDE + \ MATCHES \ NAMES + \ NOT + \ NO_CACHE \ NO_CMAKE_ENVIRONMENT_PATH \ NO_CMAKE_FIND_ROOT_PATH + \ NO_CMAKE_INSTALL_PREFIX \ NO_CMAKE_PATH \ NO_CMAKE_SYSTEM_PATH \ NO_DEFAULT_PATH \ NO_PACKAGE_ROOT_PATH \ NO_SYSTEM_ENVIRONMENT_PATH \ ONLY_CMAKE_FIND_ROOT_PATH + \ PACKAGENAME + \ PARENT_SCOPE \ PATHS \ PATH_SUFFIXES + \ REGISTRY_VIEW \ REQUIRED + \ TARGET + \ VALIDATOR \ VAR syn keyword cmakeKWfind_program contained + \ BOTH + \ CATEGORY \ CMAKE_FIND_ROOT_PATH_BOTH + \ CMAKE_FIND_USE_ \ DOC \ DVAR \ FALSE + \ FIND_XXX_REGISTRY_VIEW \ HINTS + \ HOST + \ MATCHES \ NAMES \ NAMES_PER_DIR + \ NOT + \ NO_CACHE \ NO_CMAKE_ENVIRONMENT_PATH \ NO_CMAKE_FIND_ROOT_PATH + \ NO_CMAKE_INSTALL_PREFIX \ NO_CMAKE_PATH \ NO_CMAKE_SYSTEM_PATH \ NO_DEFAULT_PATH \ NO_PACKAGE_ROOT_PATH \ NO_SYSTEM_ENVIRONMENT_PATH \ ONLY_CMAKE_FIND_ROOT_PATH + \ PACKAGENAME + \ PARENT_SCOPE \ PATHS \ PATH_SUFFIXES + \ REGISTRY_VIEW \ REQUIRED + \ TARGET + \ VALIDATOR \ VAR syn keyword cmakeKWfltk_wrap_ui contained @@ -3070,17 +3603,19 @@ syn keyword cmakeKWget_filename_component contained \ NAME \ NAME_WE \ NAME_WLE - \ PATH \ PROGRAM \ PROGRAM_ARGS + \ QUERY \ REALPATH \ REAL_PATH + \ WINDOWS_REGISTRY syn keyword cmakeKWget_property contained \ BRIEF_DOCS \ DEFINED \ DIRECTORY \ FULL_DOCS + \ GENERATED \ GLOBAL \ INSTALL \ PROPERTY @@ -3093,6 +3628,7 @@ syn keyword cmakeKWget_property contained syn keyword cmakeKWget_source_file_property contained \ DIRECTORY + \ GENERATED \ INHERITED \ LOCATION \ TARGET_DIRECTORY @@ -3109,6 +3645,7 @@ syn keyword cmakeKWif contained \ CMAKE_MATCH_ \ CMP \ COMMAND + \ COMPARE \ DEFINED \ EQUAL \ EXISTS @@ -3128,6 +3665,7 @@ syn keyword cmakeKWif contained \ NOT \ OFF \ OR + \ PATH_EQUAL \ POLICY \ STREQUAL \ STRGREATER @@ -3172,11 +3710,13 @@ syn keyword cmakeKWinclude_guard contained syn keyword cmakeKWinstall contained \ AFTER \ AIX + \ ALL_COMPONENTS \ APT \ ARCHIVE \ BEFORE \ BUILD_TYPE \ BUNDLE + \ BUNDLE_EXECUTABLE \ CMAKE_INSTALL_BINDIR \ CMAKE_INSTALL_DATADIR \ CMAKE_INSTALL_DATAROOTDIR @@ -3187,6 +3727,7 @@ syn keyword cmakeKWinstall contained \ CMAKE_INSTALL_LOCALEDIR \ CMAKE_INSTALL_LOCALSTATEDIR \ CMAKE_INSTALL_MANDIR + \ CMAKE_INSTALL_MODE \ CMAKE_INSTALL_RUNSTATEDIR \ CMAKE_INSTALL_SBINDIR \ CMAKE_INSTALL_SHARESTATEDIR @@ -3195,6 +3736,8 @@ syn keyword cmakeKWinstall contained \ COMPONENT \ CONFIGURATIONS \ CVS + \ CXX_MODULES_BMI + \ CXX_MODULES_DIRECTORY \ DATA \ DATAROOT \ DBUILD_TYPE @@ -3207,6 +3750,7 @@ syn keyword cmakeKWinstall contained \ DOC \ ENABLE_EXPORTS \ EXCLUDE_FROM_ALL + \ EXECUTABLES \ EXPORT \ EXPORT_ANDROID_MK \ EXPORT_LINK_INTERFACE_LIBRARIES @@ -3214,14 +3758,18 @@ syn keyword cmakeKWinstall contained \ FILES \ FILES_MATCHING \ FILE_PERMISSIONS + \ FILE_SET \ FRAMEWORK + \ GET_RUNTIME_DEPENDENCIES \ GROUP_EXECUTE \ GROUP_READ \ GROUP_WRITE - \ IMPORTED_ + \ HEADERS + \ IMPORTED_RUNTIME_ARTIFACTS \ INCLUDES \ INFO \ INSTALL_PREFIX + \ INTERFACE \ INTERFACE_INCLUDE_DIRECTORIES \ LIBRARY \ LOCALE @@ -3241,18 +3789,24 @@ syn keyword cmakeKWinstall contained \ OWNER_WRITE \ PATTERN \ PERMISSIONS + \ POST_EXCLUDE_FILES + \ POST_EXCLUDE_REGEXES + \ POST_INCLUDE_FILES + \ POST_INCLUDE_REGEXES \ POST_INSTALL_SCRIPT + \ PRE_EXCLUDE_REGEXES + \ PRE_INCLUDE_REGEXES \ PRE_INSTALL_SCRIPT \ PRIVATE_HEADER \ PROGRAMS \ PROPERTIES \ PUBLIC_HEADER - \ REGEX \ RENAME \ RESOURCE \ RPM \ RUNSTATE - \ RUNTIME + \ RUNTIME_DEPENDENCIES + \ RUNTIME_DEPENDENCY_SET \ SBIN \ SCRIPT \ SETGID @@ -3333,6 +3887,11 @@ syn keyword cmakeKWlist contained \ TOLOWER \ TOUPPER \ TRANSFORM + \ TRANSFORM_APPEND + \ TRANSFORM_GENEX_STRIP + \ TRANSFORM_REPLACE + \ TRANSFORM_STRIP + \ TRANSFORM_TOLOWER syn keyword cmakeKWload_cache contained \ EXCLUDE @@ -3370,10 +3929,15 @@ syn keyword cmakeKWmessage contained \ CHECK_FAIL \ CHECK_PASS \ CHECK_START + \ CONFIGURE_LOG \ DEBUG + \ DEFINED \ DEPRECATION \ FATAL_ERROR + \ GET_MESSAGE_LOG_LEVEL \ GUI + \ INTERNAL + \ MY_CHECK_RESULT \ NOTICE \ POP_BACK \ SEND_ERROR @@ -3384,10 +3948,13 @@ syn keyword cmakeKWmessage contained syn keyword cmakeKWoption contained \ OFF - \ ON syn keyword cmakeKWproject contained \ ASM + \ ASM_MARMASM + \ ASM_MASM + \ ASM_NASM + \ ATT \ CMAKE_PROJECT_ \ CUDA \ DESCRIPTION @@ -3405,6 +3972,7 @@ syn keyword cmakeKWproject contained \ _DESCRIPTION \ _HOMEPAGE_URL \ _INCLUDE_BEFORE + \ _IS_TOP_LEVEL \ _SOURCE_DIR \ _VERSION \ _VERSION_MAJOR @@ -3424,6 +3992,11 @@ syn keyword cmakeKWremove contained syn keyword cmakeKWreturn contained \ DEFER + \ PARENT_SCOPE + \ PROPAGATE + \ SCOPE_FOR + \ VARIABLES + \ VERSION syn keyword cmakeKWseparate_arguments contained \ MSDN @@ -3439,10 +4012,13 @@ syn keyword cmakeKWset contained \ FORCE \ INTERNAL \ OFF + \ OLD \ ON \ PARENT_SCOPE + \ PROPAGATE \ STRING \ STRINGS + \ VAR syn keyword cmakeKWset_directory_properties contained \ DIRECTORY @@ -3452,9 +4028,11 @@ syn keyword cmakeKWset_property contained \ APPEND \ APPEND_STRING \ DIRECTORY + \ GENERATED \ GLOBAL \ INHERITED \ INSTALL + \ NAME \ PROPERTY \ SOURCE \ TARGET @@ -3464,17 +4042,17 @@ syn keyword cmakeKWset_property contained syn keyword cmakeKWset_source_files_properties contained \ DIRECTORY + \ GENERATED \ PROPERTIES \ SOURCE \ TARGET_DIRECTORY syn keyword cmakeKWset_target_properties contained \ PROPERTIES - \ TARGET syn keyword cmakeKWset_tests_properties contained + \ NAME \ PROPERTIES - \ TEST syn keyword cmakeKWsite_name contained \ HOSTNAME @@ -3506,9 +4084,9 @@ syn keyword cmakeKWstring contained \ GUID \ HASH \ HEX + \ ISO \ JOIN \ JSON - \ JSONLENGTH \ LENGTH \ LESS \ LESS_EQUAL @@ -3573,7 +4151,10 @@ syn keyword cmakeKWtarget_compile_features contained syn keyword cmakeKWtarget_compile_options contained \ ALIAS \ BEFORE + \ CMAKE_ + \ COMPILE_LANGUAGE \ COMPILE_OPTIONS + \ CONFIG \ IMPORTED \ INTERFACE \ INTERFACE_COMPILE_OPTIONS @@ -3581,8 +4162,11 @@ syn keyword cmakeKWtarget_compile_options contained \ PUBLIC \ SHELL \ UNIX_COMMAND + \ _FLAGS + \ _FLAGS_ syn keyword cmakeKWtarget_include_directories contained + \ AFTER \ ALIAS \ BEFORE \ BUILD_INTERFACE @@ -3619,6 +4203,7 @@ syn keyword cmakeKWtarget_link_libraries contained \ IMPORTED_NO_SONAME \ INTERFACE \ INTERFACE_LINK_LIBRARIES + \ LINK_FLAGS \ LINK_INTERFACE_LIBRARIES \ LINK_INTERFACE_LIBRARIES_DEBUG \ LINK_INTERFACE_MULTIPLICITY @@ -3631,16 +4216,17 @@ syn keyword cmakeKWtarget_link_libraries contained \ PUBLIC \ SHARED \ STATIC + \ TARGET_OBJECTS syn keyword cmakeKWtarget_link_options contained \ ALIAS \ BEFORE \ CMAKE_ + \ CONFIG \ CUDA_RESOLVE_DEVICE_SYMBOLS \ CUDA_SEPARABLE_COMPILATION \ DEVICE_LINK \ GCC - \ GNU \ HOST_LINK \ IMPORTED \ INTERFACE @@ -3653,6 +4239,8 @@ syn keyword cmakeKWtarget_link_options contained \ SHELL \ STATIC_LIBRARY_OPTIONS \ UNIX_COMMAND + \ _FLAGS + \ _FLAGS_ \ _LINKER_WRAPPER_FLAG \ _LINKER_WRAPPER_FLAG_SEP @@ -3677,15 +4265,45 @@ syn keyword cmakeKWtarget_precompile_headers contained syn keyword cmakeKWtarget_sources contained \ ALIAS + \ BASE_DIRS + \ BUILD_INTERFACE + \ CONFIG + \ CORRECT + \ CXX_MODULES + \ CXX_MODULE_DIRS + \ CXX_MODULE_DIRS_ + \ CXX_MODULE_SETS + \ CXX_MODULE_SET_ + \ EXPORT + \ FILES + \ FILE_SET + \ FRAMEWORK + \ HEADERS + \ HEADER_DIRS + \ HEADER_DIRS_ + \ HEADER_FILE_ONLY + \ HEADER_SETS + \ HEADER_SET_ \ IMPORTED + \ INCLUDE_DIRECTORIES \ INTERFACE + \ INTERFACE_CXX_MODULE_SETS + \ INTERFACE_HEADER_SETS + \ INTERFACE_INCLUDE_DIRECTORIES \ INTERFACE_SOURCES + \ NAME \ PRIVATE \ PUBLIC \ SOURCES + \ SOURCE_DIR + \ TARGETS + \ TRUE + \ TYPE + \ WRONG syn keyword cmakeKWtry_compile contained \ ALL_BUILD + \ BINARY_DIR \ CMAKE_FLAGS \ COMPILE_DEFINITIONS \ COPY_FILE @@ -3713,8 +4331,11 @@ syn keyword cmakeKWtry_compile contained \ LINK_DIRECTORIES \ LINK_LIBRARIES \ LINK_OPTIONS + \ LOG_DESCRIPTION \ MULTI \ NOT + \ NO_CACHE + \ NO_LOG \ OBJCXX_EXTENSIONS \ OBJCXX_STANDARD \ OBJCXX_STANDARD_REQUIRED @@ -3723,9 +4344,16 @@ syn keyword cmakeKWtry_compile contained \ OBJC_STANDARD_REQUIRED \ OUTPUT_VARIABLE \ PRIVATE + \ PROJECT + \ RESULTVAR \ SOURCES + \ SOURCE_DIR + \ SOURCE_FROM_CONTENT + \ SOURCE_FROM_FILE + \ SOURCE_FROM_VAR \ STATIC_LIBRARY \ STATIC_LIBRARY_OPTIONS + \ TARGET \ TRUE \ TYPE \ VALUE @@ -3738,18 +4366,28 @@ syn keyword cmakeKWtry_run contained \ CMAKE_FLAGS \ COMPILE_DEFINITIONS \ COMPILE_OUTPUT_VARIABLE - \ DLINK_LIBRARIES - \ DVAR + \ COPY_FILE + \ COPY_FILE_ERROR \ FAILED_TO_RUN \ FALSE - \ INCLUDE_DIRECTORIES - \ LINK_DIRECTORIES + \ LANG \ LINK_LIBRARIES \ LINK_OPTIONS + \ LOG_DESCRIPTION + \ NO_CACHE + \ NO_LOG + \ RUN_OUTPUT_STDERR_VARIABLE + \ RUN_OUTPUT_STDOUT_VARIABLE \ RUN_OUTPUT_VARIABLE + \ SOURCES + \ SOURCE_FROM_CONTENT + \ SOURCE_FROM_FILE + \ SOURCE_FROM_VAR \ TRUE - \ TYPE - \ VALUE + \ WORKING_DIRECTORY + \ _EXTENSIONS + \ _STANDARD + \ _STANDARD_REQUIRED \ __TRYRUN_OUTPUT syn keyword cmakeKWunset contained @@ -3783,26 +4421,38 @@ syn keyword cmakeKWwrite_file contained syn keyword cmakeGeneratorExpressions contained - \ AND + \ ABSOLUTE_PATH + \ ACTION + \ AIX \ ANGLE + \ APPEND \ ARCHIVE_OUTPUT_NAME \ ARCHIVE_OUTPUT_NAME_ + \ ASCENDING \ BAR \ BOOL \ BUILD_INTERFACE - \ CMAKE_ - \ COMMA - \ COMMAND + \ BUILD_LOCAL_INTERFACE + \ CMAKE_LINK_GROUP_USING_ + \ CMAKE_LINK_LIBRARY_USING_ + \ CMAKE_PATH + \ CODE + \ COMMAND_CONFIG + \ COMMAND_EXPAND_LISTS + \ COMPARE \ COMPILE_DEFINITIONS \ COMPILE_FEATURES \ COMPILE_LANGUAGE \ COMPILE_LANG_AND_ID + \ COMPILE_ONLY \ COMPILING_CUDA + \ COMPILING_CXX \ COMPILING_CXX_WITH_CLANG \ COMPILING_CXX_WITH_INTEL \ COMPILING_C_WITH_CLANG \ CONFIG \ CONFIGURATION + \ CONTENT \ CUDA_COMPILER_ID \ CUDA_COMPILER_VERSION \ CUDA_RESOLVE_DEVICE_SYMBOLS @@ -3817,71 +4467,146 @@ syn keyword cmakeGeneratorExpressions contained \ C_STANDARD \ DEBUG_MODE \ DEBUG_POSTFIX + \ DENABLE_SOME_FEATURE + \ DESCENDING \ DEVICE_LINK \ DLL + \ ENABLE_EXPORTS \ EXCLUDE \ EXPORT + \ EXTENSION_DEF \ FALSE + \ FILENAME_DEF + \ FILE_BASENAME \ FILTER + \ FIND \ FOO_EXTRA_THINGS \ GENERATE \ GENEX_EVAL - \ GNU + \ GET_EXTENSION + \ GET_FILENAME + \ GET_PARENT_PATH + \ GET_RELATIVE_PART + \ GET_ROOT_DIRECTORY + \ GET_ROOT_NAME + \ GET_ROOT_PATH + \ GET_STEM + \ HAS_ + \ HAS_EXTENSION + \ HAS_FILENAME + \ HAS_PARENT_PATH + \ HAS_RELATIVE_PART + \ HAS_ROOT_DIRECTORY + \ HAS_ROOT_NAME + \ HAS_ROOT_PATH + \ HAS_STEM + \ HAVE_SOME_FEATURE \ HIP_COMPILER_ID \ HIP_COMPILER_VERSION \ HIP_STANDARD \ HOST_LINK \ IF \ IGNORE + \ IMPORTED_LOCATION \ IMPORT_PREFIX \ IMPORT_SUFFIX \ INCLUDE_DIRECTORIES + \ INSENSITIVE + \ INSERT \ INSTALL_INTERFACE \ INSTALL_NAME_DIR \ INSTALL_PREFIX - \ INTERFACE + \ INSTALL_RPATH \ INTERFACE_LINK_LIBRARIES + \ INTERFACE_LINK_LIBRARIES_DIRECT \ IN_LIST \ ISPC_COMPILER_ID \ ISPC_COMPILER_VERSION + \ IS_ABSOLUTE + \ IS_PREFIX + \ IS_RELATIVE \ JOIN \ LANG \ LANG_COMPILER_ID + \ LAST_ONLY + \ LENGTH \ LIBRARY_OUTPUT_NAME \ LIBRARY_OUTPUT_NAME_ + \ LINK_GROUP + \ LINK_GROUP_PREDEFINED_FEATURES \ LINK_LANGUAGE \ LINK_LANG_AND_ID \ LINK_LIBRARIES + \ LINK_LIBRARY + \ LINK_LIBRARY_OVERRIDE + \ LINK_LIBRARY_OVERRIDE_ + \ LINK_LIBRARY_PREDEFINED_FEATURES \ LINK_ONLY \ LOWER_CASE \ MAKE_C_IDENTIFIER \ MAP_IMPORTED_CONFIG_ + \ MODULE + \ NATURAL \ NO + \ NORMALIZE + \ NORMAL_PATH \ NOT \ OBJCXX_COMPILER_ID \ OBJCXX_COMPILER_VERSION \ OBJC_COMPILER_ID \ OBJC_COMPILER_VERSION + \ OBJECT \ OFF \ OLD_COMPILER + \ ORDER + \ OUTPUT + \ OUTPUT_CONFIG \ OUTPUT_NAME \ OUTPUT_NAME_ + \ PATH + \ PATH_EQUAL \ PDB_NAME \ PDB_NAME_ \ PDB_OUTPUT_DIRECTORY \ PDB_OUTPUT_DIRECTORY_ \ PLATFORM_ID + \ POP_BACK + \ POP_FRONT \ POSIX + \ POST_BUILD + \ PREPEND \ PRIVATE \ PUBLIC + \ REGEX + \ RELATIVE_PATH + \ REMOVE_AT \ REMOVE_DUPLICATES + \ REMOVE_EXTENSION + \ REMOVE_FILENAME + \ REMOVE_ITEM + \ REPLACE + \ REPLACE_EXTENSION + \ REPLACE_FILENAME + \ REQUIRED + \ RESCAN + \ REVERSE + \ RPATH + \ RUNTIME_DEPENDENCY_SET \ RUNTIME_OUTPUT_NAME \ RUNTIME_OUTPUT_NAME_ + \ SCRIPT \ SDK + \ SELECTOR \ SEMICOLON + \ SENSITIVE + \ SHARED \ SHELL_PATH + \ SORT \ STATIC \ STREQUAL + \ STRING + \ STRIP + \ SUBLIST \ TARGET_BUNDLE_CONTENT_DIR \ TARGET_BUNDLE_DIR \ TARGET_BUNDLE_DIR_NAME @@ -3893,12 +4618,30 @@ syn keyword cmakeGeneratorExpressions contained \ TARGET_FILE_PREFIX \ TARGET_FILE_SUFFIX \ TARGET_GENEX_EVAL + \ TARGET_IMPORT_FILE + \ TARGET_IMPORT_FILE_BASE_NAME + \ TARGET_IMPORT_FILE_DIR + \ TARGET_IMPORT_FILE_NAME + \ TARGET_IMPORT_FILE_PREFIX + \ TARGET_IMPORT_FILE_SUFFIX \ TARGET_LINKER_FILE \ TARGET_LINKER_FILE_BASE_NAME \ TARGET_LINKER_FILE_DIR \ TARGET_LINKER_FILE_NAME \ TARGET_LINKER_FILE_PREFIX \ TARGET_LINKER_FILE_SUFFIX + \ TARGET_LINKER_IMPORT_FILE + \ TARGET_LINKER_IMPORT_FILE_BASE_NAME + \ TARGET_LINKER_IMPORT_FILE_DIR + \ TARGET_LINKER_IMPORT_FILE_NAME + \ TARGET_LINKER_IMPORT_FILE_PREFIX + \ TARGET_LINKER_IMPORT_FILE_SUFFIX + \ TARGET_LINKER_LIBRARY_FILE + \ TARGET_LINKER_LIBRARY_FILE_BASE_NAME + \ TARGET_LINKER_LIBRARY_FILE_DIR + \ TARGET_LINKER_LIBRARY_FILE_NAME + \ TARGET_LINKER_LIBRARY_FILE_PREFIX + \ TARGET_LINKER_LIBRARY_FILE_SUFFIX \ TARGET_NAME_IF_EXISTS \ TARGET_OBJECTS \ TARGET_PDB_FILE @@ -3907,16 +4650,34 @@ syn keyword cmakeGeneratorExpressions contained \ TARGET_PDB_FILE_NAME \ TARGET_POLICY \ TARGET_PROPERTY + \ TARGET_RUNTIME_DLLS + \ TARGET_RUNTIME_DLL_DIRS \ TARGET_SONAME_FILE \ TARGET_SONAME_FILE_DIR \ TARGET_SONAME_FILE_NAME + \ TARGET_SONAME_IMPORT_FILE + \ TARGET_SONAME_IMPORT_FILE_DIR + \ TARGET_SONAME_IMPORT_FILE_NAME + \ TOLOWER + \ TOUPPER + \ TRANSFORM + \ TRANSFORM_APPEND + \ TRANSFORM_REPLACE + \ TRANSFORM_STRIP + \ TRANSFORM_TOLOWER + \ UNKNOWN \ UPPER_CASE + \ VERBATIM \ VERSION_EQUAL - \ VERSION_GREATER \ VERSION_GREATER_EQUAL \ VERSION_LESS \ VERSION_LESS_EQUAL + \ WHOLE_ARCHIVE + \ WRONG + \ _LINK_GROUP_USING_ + \ _LINK_LIBRARY_USING_ \ _POSTFIX + \ _SUPPORTED syn case ignore @@ -3936,6 +4697,7 @@ syn keyword cmakeCommand \ block \ break \ build_command + \ cmake_file_api \ cmake_host_system_information \ cmake_language \ cmake_minimum_required @@ -4085,6 +4847,7 @@ hi def link cmakeVariableValue Type hi def link cmakeVariable Identifier hi def link cmakeKWExternalProject ModeMsg +hi def link cmakeKWFetchContent ModeMsg hi def link cmakeKWadd_compile_definitions ModeMsg hi def link cmakeKWadd_compile_options ModeMsg hi def link cmakeKWadd_custom_command ModeMsg @@ -4096,7 +4859,9 @@ hi def link cmakeKWadd_library ModeMsg hi def link cmakeKWadd_link_options ModeMsg hi def link cmakeKWadd_subdirectory ModeMsg hi def link cmakeKWadd_test ModeMsg +hi def link cmakeKWblock ModeMsg hi def link cmakeKWbuild_command ModeMsg +hi def link cmakeKWcmake_file_api ModeMsg hi def link cmakeKWcmake_host_system_information ModeMsg hi def link cmakeKWcmake_language ModeMsg hi def link cmakeKWcmake_minimum_required ModeMsg diff --git a/CMakeCPack.cmake b/CMakeCPack.cmake index 798affd2f20..3ac60303189 100644 --- a/CMakeCPack.cmake +++ b/CMakeCPack.cmake @@ -1,5 +1,5 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. +# file LICENSE.rst or https://cmake.org/licensing for details. option(CMAKE_INSTALL_DEBUG_LIBRARIES "Install Microsoft runtime debug libraries with CMake." FALSE) @@ -14,10 +14,14 @@ if(CMake_INSTALL_DEPENDENCIES) include(${CMake_SOURCE_DIR}/Modules/InstallRequiredSystemLibraries.cmake) endif() +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/LICENSE.txt") +file(READ "${CMake_LICENSE_FILE}" license_text) +string(REPLACE "`Contributors `_" "Contributors" license_text "${license_text}") +file(WRITE "${CPACK_RESOURCE_FILE_LICENSE}" "${license_text}") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "CMake is a build tool") set(CPACK_PACKAGE_VENDOR "Kitware") -set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/Copyright.txt") -set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/Copyright.txt") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CPACK_RESOURCE_FILE_LICENSE}") set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${CMake_VERSION}") set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") @@ -28,7 +32,7 @@ set(CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE OFF) # - Root install directory (displayed to end user at installer-run time) # - "NSIS package/display name" (text used in the installer GUI) # - Registry key used to store info about the installation -if(CMAKE_CL_64) +if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64") set(CPACK_NSIS_PACKAGE_NAME "${CPACK_PACKAGE_NAME} ${CPACK_PACKAGE_VERSION} (Win64)") else() @@ -40,18 +44,10 @@ set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "${CPACK_NSIS_PACKAGE_NAME}") if(NOT DEFINED CPACK_SYSTEM_NAME) # make sure package is not Cygwin-unknown, for Cygwin just # cygwin is good for the system name - if("x${CMAKE_SYSTEM_NAME}" STREQUAL "xCYGWIN") - set(CPACK_SYSTEM_NAME Cygwin) - else() - set(CPACK_SYSTEM_NAME ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}) - endif() -endif() -if(${CPACK_SYSTEM_NAME} MATCHES Windows) - if(CMAKE_CL_64) - set(CPACK_SYSTEM_NAME win64-x64) - set(CPACK_IFW_TARGET_DIRECTORY "@RootDir@/Program Files/${CMAKE_PROJECT_NAME}") + if(CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") + set(CPACK_SYSTEM_NAME cygwin) else() - set(CPACK_SYSTEM_NAME win32-x86) + string(TOLOWER "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}" CPACK_SYSTEM_NAME) endif() endif() @@ -166,7 +162,7 @@ _cmifwarg("Package {%- endif %} +{%- if render_sidebar %} + +{%- endif %} {%- endblock %} diff --git a/Utilities/Sphinx/templates/localtoc.html b/Utilities/Sphinx/templates/localtoc.html new file mode 100644 index 00000000000..0065fe7a326 --- /dev/null +++ b/Utilities/Sphinx/templates/localtoc.html @@ -0,0 +1,8 @@ +{# Sphinx sidebar template: local table of contents. + Overridden to remove the "#" link from the "Table of Contents". #} +{%- if display_toc %} +
+

{{ _('Table of Contents') }}

+ {{ toc }} +
+{%- endif %} diff --git a/Utilities/Sphinx/update_versions.py b/Utilities/Sphinx/update_versions.py index 893e7a719d9..d210ab06062 100755 --- a/Utilities/Sphinx/update_versions.py +++ b/Utilities/Sphinx/update_versions.py @@ -1,38 +1,63 @@ #!/usr/bin/env python3 """ -This script inserts "versionadded" directive into every .rst document -and every .cmake module with .rst documentation comment. +This script inserts "versionadded" directives into .rst documents found in the +Help/ directory and module documentation comments found in the Modules/ +directory. It can be run from any directory within the CMake repository. + +Each file is assigned a CMake version in which it first appears, +according to the git version tags. + +Options: + + --overwrite Replace existing "versionadded" directives. + Default: existing directives are left unchanged. + + --baseline Files present in this tag don't need a version directive. + Default: v3.0.0 + + --since Files present in this tag will be ignored. + Only newer files will be operated on. + Default: v3.0.0 + + --next-version The next CMake version, which hasn't been tagged yet. + Default: extracted from Source/CMakeVersion.cmake """ import re import pathlib import subprocess import argparse -tag_re = re.compile(r'^v3\.(\d+)\.(\d+)(?:-rc(\d+))?$') +tag_re = re.compile(r'^v[34]\.(\d+)\.(\d+)(?:-rc(\d+))?$') path_re = re.compile(r'Help/(?!dev|guide|manual|cpack_|release).*\.rst|Modules/[^/]*\.cmake$') def git_root(): + """Return the root of the .git repository from the current directory.""" result = subprocess.run( ['git', 'rev-parse', '--show-toplevel'], check=True, universal_newlines=True, capture_output=True) return pathlib.Path(result.stdout.strip()) def git_tags(): + """Return a list of CMake version tags from the repository.""" result = subprocess.run(['git', 'tag'], check=True, universal_newlines=True, capture_output=True) return [tag for tag in result.stdout.splitlines() if tag_re.match(tag)] def git_list_tree(ref): + """Return a list of help and module files in a given git reference.""" result = subprocess.run( ['git', 'ls-tree', '-r', '--full-name', '--name-only', ref, ':/'], check=True, universal_newlines=True, capture_output=True) return [path for path in result.stdout.splitlines() if path_re.match(path)] def tag_version(tag): + """Extract a clean CMake version from a git version tag.""" return re.sub(r'^v|\.0(-rc\d+)?$', '', tag) def tag_sortkey(tag): + """Sorting key for a git version tag.""" return tuple(int(part or '1000') for part in tag_re.match(tag).groups()) def make_version_map(baseline, since, next_version): + """Map repository file paths to CMake versions in which they first appear.""" versions = {} if next_version: for path in git_list_tree('HEAD'): @@ -53,9 +78,10 @@ def make_version_map(baseline, since, next_version): rb'set\(CMake_VERSION_MAJOR\s+(\d+)\)\s+set\(CMake_VERSION_MINOR\s+(\d+)\)\s+set\(CMake_VERSION_PATCH\s+(\d+)\)', re.S) def cmake_version(path): + """Extract the current MAJOR.MINOR CMake version from CMakeVersion.cmake found at `path`.""" match = cmake_version_re.search(path.read_bytes()) major, minor, patch = map(int, match.groups()) - minor += patch > 20000000 + minor += patch > 20000000 # nightly version will become the next minor return f'{major}.{minor}' stamp_re = re.compile( @@ -96,7 +122,7 @@ def main(): parser = argparse.ArgumentParser(allow_abbrev=False) parser.add_argument('--overwrite', action='store_true', help="overwrite existing version tags") parser.add_argument('--baseline', metavar='TAG', default='v3.0.0', - help="files present in this tag won't be stamped (default: v3.0.0)") + help="files present in this tag don't need a version directive (default: v3.0.0)") parser.add_argument('--since', metavar='TAG', help="apply changes only to files added after this tag") parser.add_argument('--next-version', metavar='VER', diff --git a/Utilities/ast-grep/rule-tests/__snapshots__/cmstrcat-adjacent-literals-snapshot.yml b/Utilities/ast-grep/rule-tests/__snapshots__/cmstrcat-adjacent-literals-snapshot.yml new file mode 100644 index 00000000000..961e51079a7 --- /dev/null +++ b/Utilities/ast-grep/rule-tests/__snapshots__/cmstrcat-adjacent-literals-snapshot.yml @@ -0,0 +1,312 @@ +id: cmstrcat-adjacent-literals +snapshots: + cmStrCat("literal", "literal"): + labels: + - source: '"literal"' + style: primary + start: 9 + end: 18 + - source: cmStrCat("literal", "literal") + style: secondary + start: 0 + end: 30 + - source: ',' + style: secondary + start: 18 + end: 19 + - source: '"literal"' + style: secondary + start: 20 + end: 29 + cmStrCat("literal", "literal", "literal"): + labels: + - source: '"literal"' + style: primary + start: 9 + end: 18 + - source: cmStrCat("literal", "literal", "literal") + style: secondary + start: 0 + end: 41 + - source: ',' + style: secondary + start: 18 + end: 19 + - source: '"literal"' + style: secondary + start: 20 + end: 29 + cmStrCat("literal", "literal", "literal", variable): + labels: + - source: '"literal"' + style: primary + start: 9 + end: 18 + - source: cmStrCat("literal", "literal", "literal", variable) + style: secondary + start: 0 + end: 51 + - source: '"literal"' + style: secondary + start: 20 + end: 29 + cmStrCat("literal", "literal", variable): + labels: + - source: '"literal"' + style: primary + start: 9 + end: 18 + - source: cmStrCat("literal", "literal", variable) + style: secondary + start: 0 + end: 40 + - source: ',' + style: secondary + start: 18 + end: 19 + - source: '"literal"' + style: secondary + start: 20 + end: 29 + cmStrCat(variable, "literal", "literal" "literal"): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", "literal" "literal") + style: secondary + start: 0 + end: 50 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: '"literal" "literal"' + style: secondary + start: 30 + end: 49 + cmStrCat(variable, "literal", "literal"): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", "literal") + style: secondary + start: 0 + end: 40 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: '"literal"' + style: secondary + start: 30 + end: 39 + cmStrCat(variable, "literal", "literal", "literal"): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", "literal", "literal") + style: secondary + start: 0 + end: 51 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: '"literal"' + style: secondary + start: 30 + end: 39 + cmStrCat(variable, "literal", "literal", "literal", variable, "literal"): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", "literal", "literal", variable, "literal") + style: secondary + start: 0 + end: 72 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: '"literal"' + style: secondary + start: 30 + end: 39 + cmStrCat(variable, "literal", "literal", variable): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", "literal", variable) + style: secondary + start: 0 + end: 50 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: '"literal"' + style: secondary + start: 30 + end: 39 + cmStrCat(variable, "literal", "string_view"_s, variable): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", "string_view"_s, variable) + style: secondary + start: 0 + end: 56 + - source: _s + style: secondary + start: 43 + end: 45 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: '"string_view"_s' + style: secondary + start: 30 + end: 45 + cmStrCat(variable, "literal", 'l', variable): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", 'l', variable) + style: secondary + start: 0 + end: 44 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: '''l''' + style: secondary + start: 30 + end: 33 + cmStrCat(variable, "literal", R"(raw_literal)", variable): + labels: + - source: '"literal"' + style: primary + start: 19 + end: 28 + - source: cmStrCat(variable, "literal", R"(raw_literal)", variable) + style: secondary + start: 0 + end: 57 + - source: ',' + style: secondary + start: 28 + end: 29 + - source: R"(raw_literal)" + style: secondary + start: 30 + end: 46 + cmStrCat(variable, "string_view"_s, "literal", variable): + labels: + - source: '"string_view"_s' + style: primary + start: 19 + end: 34 + - source: _s + style: secondary + start: 32 + end: 34 + - source: cmStrCat(variable, "string_view"_s, "literal", variable) + style: secondary + start: 0 + end: 56 + - source: ',' + style: secondary + start: 34 + end: 35 + - source: '"literal"' + style: secondary + start: 36 + end: 45 + cmStrCat(variable, 'c', "literal" "literal"): + labels: + - source: '''c''' + style: primary + start: 19 + end: 22 + - source: cmStrCat(variable, 'c', "literal" "literal") + style: secondary + start: 0 + end: 44 + - source: ',' + style: secondary + start: 22 + end: 23 + - source: '"literal" "literal"' + style: secondary + start: 24 + end: 43 + cmStrCat(variable, 'c', "literal", variable): + labels: + - source: '''c''' + style: primary + start: 19 + end: 22 + - source: cmStrCat(variable, 'c', "literal", variable) + style: secondary + start: 0 + end: 44 + - source: ',' + style: secondary + start: 22 + end: 23 + - source: '"literal"' + style: secondary + start: 24 + end: 33 + cmStrCat(variable, 'c', 'l', variable): + labels: + - source: '''c''' + style: primary + start: 19 + end: 22 + - source: cmStrCat(variable, 'c', 'l', variable) + style: secondary + start: 0 + end: 38 + - source: ',' + style: secondary + start: 22 + end: 23 + - source: '''l''' + style: secondary + start: 24 + end: 27 + cmStrCat(variable, R"(raw_literal)", "literal", variable): + labels: + - source: R"(raw_literal)" + style: primary + start: 19 + end: 35 + - source: cmStrCat(variable, R"(raw_literal)", "literal", variable) + style: secondary + start: 0 + end: 57 + - source: ',' + style: secondary + start: 35 + end: 36 + - source: '"literal"' + style: secondary + start: 37 + end: 46 diff --git a/Utilities/ast-grep/rule-tests/__snapshots__/cmstrcat-to-char-literal-snapshot.yml b/Utilities/ast-grep/rule-tests/__snapshots__/cmstrcat-to-char-literal-snapshot.yml new file mode 100644 index 00000000000..f79c555b7c5 --- /dev/null +++ b/Utilities/ast-grep/rule-tests/__snapshots__/cmstrcat-to-char-literal-snapshot.yml @@ -0,0 +1,342 @@ +id: cmstrcat-to-char-literal +snapshots: + cmStrCat("'"): + fixed: cmStrCat('\'') + labels: + - source: '"''"' + style: primary + start: 9 + end: 12 + - source: cmStrCat("'") + style: secondary + start: 0 + end: 13 + cmStrCat("'", other): + fixed: cmStrCat('\'', other) + labels: + - source: '"''"' + style: primary + start: 9 + end: 12 + - source: cmStrCat("'", other) + style: secondary + start: 0 + end: 20 + - source: ',' + style: secondary + start: 12 + end: 13 + - source: ( + style: secondary + start: 8 + end: 9 + cmStrCat("\""): + fixed: cmStrCat('"') + labels: + - source: '"\""' + style: primary + start: 9 + end: 13 + - source: cmStrCat("\"") + style: secondary + start: 0 + end: 14 + cmStrCat("\"", other): + fixed: cmStrCat('"', other) + labels: + - source: '"\""' + style: primary + start: 9 + end: 13 + - source: cmStrCat("\"", other) + style: secondary + start: 0 + end: 21 + - source: ',' + style: secondary + start: 13 + end: 14 + - source: ( + style: secondary + start: 8 + end: 9 + cmStrCat("\'"): + fixed: cmStrCat('\'') + labels: + - source: '"\''"' + style: primary + start: 9 + end: 13 + - source: cmStrCat("\'") + style: secondary + start: 0 + end: 14 + cmStrCat("\'", other): + fixed: cmStrCat('\'', other) + labels: + - source: '"\''"' + style: primary + start: 9 + end: 13 + - source: cmStrCat("\'", other) + style: secondary + start: 0 + end: 21 + - source: ',' + style: secondary + start: 13 + end: 14 + - source: ( + style: secondary + start: 8 + end: 9 + cmStrCat("\n"): + fixed: cmStrCat('\n') + labels: + - source: '"\n"' + style: primary + start: 9 + end: 13 + - source: cmStrCat("\n") + style: secondary + start: 0 + end: 14 + cmStrCat("\n", other): + fixed: cmStrCat('\n', other) + labels: + - source: '"\n"' + style: primary + start: 9 + end: 13 + - source: cmStrCat("\n", other) + style: secondary + start: 0 + end: 21 + - source: ',' + style: secondary + start: 13 + end: 14 + - source: ( + style: secondary + start: 8 + end: 9 + cmStrCat("n"): + fixed: cmStrCat('n') + labels: + - source: '"n"' + style: primary + start: 9 + end: 12 + - source: cmStrCat("n") + style: secondary + start: 0 + end: 13 + cmStrCat("n", other): + fixed: cmStrCat('n', other) + labels: + - source: '"n"' + style: primary + start: 9 + end: 12 + - source: cmStrCat("n", other) + style: secondary + start: 0 + end: 20 + - source: ',' + style: secondary + start: 12 + end: 13 + - source: ( + style: secondary + start: 8 + end: 9 + cmStrCat(other, "'"): + fixed: cmStrCat(other, '\'') + labels: + - source: '"''"' + style: primary + start: 16 + end: 19 + - source: cmStrCat(other, "'") + style: secondary + start: 0 + end: 20 + - source: ) + style: secondary + start: 19 + end: 20 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "'", other): + fixed: cmStrCat(other, '\'', other) + labels: + - source: '"''"' + style: primary + start: 16 + end: 19 + - source: cmStrCat(other, "'", other) + style: secondary + start: 0 + end: 27 + - source: ',' + style: secondary + start: 19 + end: 20 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "\""): + fixed: cmStrCat(other, '"') + labels: + - source: '"\""' + style: primary + start: 16 + end: 20 + - source: cmStrCat(other, "\"") + style: secondary + start: 0 + end: 21 + - source: ) + style: secondary + start: 20 + end: 21 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "\"", other): + fixed: cmStrCat(other, '"', other) + labels: + - source: '"\""' + style: primary + start: 16 + end: 20 + - source: cmStrCat(other, "\"", other) + style: secondary + start: 0 + end: 28 + - source: ',' + style: secondary + start: 20 + end: 21 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "\'"): + fixed: cmStrCat(other, '\'') + labels: + - source: '"\''"' + style: primary + start: 16 + end: 20 + - source: cmStrCat(other, "\'") + style: secondary + start: 0 + end: 21 + - source: ) + style: secondary + start: 20 + end: 21 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "\'", other): + fixed: cmStrCat(other, '\'', other) + labels: + - source: '"\''"' + style: primary + start: 16 + end: 20 + - source: cmStrCat(other, "\'", other) + style: secondary + start: 0 + end: 28 + - source: ',' + style: secondary + start: 20 + end: 21 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "\n"): + fixed: cmStrCat(other, '\n') + labels: + - source: '"\n"' + style: primary + start: 16 + end: 20 + - source: cmStrCat(other, "\n") + style: secondary + start: 0 + end: 21 + - source: ) + style: secondary + start: 20 + end: 21 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "\n", other): + fixed: cmStrCat(other, '\n', other) + labels: + - source: '"\n"' + style: primary + start: 16 + end: 20 + - source: cmStrCat(other, "\n", other) + style: secondary + start: 0 + end: 28 + - source: ',' + style: secondary + start: 20 + end: 21 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "n"): + fixed: cmStrCat(other, 'n') + labels: + - source: '"n"' + style: primary + start: 16 + end: 19 + - source: cmStrCat(other, "n") + style: secondary + start: 0 + end: 20 + - source: ) + style: secondary + start: 19 + end: 20 + - source: ',' + style: secondary + start: 14 + end: 15 + cmStrCat(other, "n", other): + fixed: cmStrCat(other, 'n', other) + labels: + - source: '"n"' + style: primary + start: 16 + end: 19 + - source: cmStrCat(other, "n", other) + style: secondary + start: 0 + end: 27 + - source: ',' + style: secondary + start: 19 + end: 20 + - source: ',' + style: secondary + start: 14 + end: 15 diff --git a/Utilities/ast-grep/rule-tests/__snapshots__/rm-cmdbg-includes-snapshot.yml b/Utilities/ast-grep/rule-tests/__snapshots__/rm-cmdbg-includes-snapshot.yml new file mode 100644 index 00000000000..6b06a3e5a5e --- /dev/null +++ b/Utilities/ast-grep/rule-tests/__snapshots__/rm-cmdbg-includes-snapshot.yml @@ -0,0 +1,16 @@ +id: rm-cmdbg-includes +snapshots: + '#include "cmDebugTools.h"': + fixed: '' + labels: + - source: '#include "cmDebugTools.h"' + style: primary + start: 0 + end: 25 + '#include ': + fixed: '' + labels: + - source: '#include ' + style: primary + start: 0 + end: 25 diff --git a/Utilities/ast-grep/rule-tests/__snapshots__/rm-cmdbg-macros-snapshot.yml b/Utilities/ast-grep/rule-tests/__snapshots__/rm-cmdbg-macros-snapshot.yml new file mode 100644 index 00000000000..47d919999ca --- /dev/null +++ b/Utilities/ast-grep/rule-tests/__snapshots__/rm-cmdbg-macros-snapshot.yml @@ -0,0 +1,30 @@ +id: rm-cmdbg-macros +snapshots: + CM_DBG(arg);: + fixed: arg; + labels: + - source: CM_DBG(arg) + style: primary + start: 0 + end: 11 + a = CM_DBG(arg);: + fixed: a = arg; + labels: + - source: CM_DBG(arg) + style: primary + start: 4 + end: 15 + b = a + CM_DBG(arg);: + fixed: b = a + arg; + labels: + - source: CM_DBG(arg) + style: primary + start: 8 + end: 19 + f(CM_DBG(arg));: + fixed: f(arg); + labels: + - source: CM_DBG(arg) + style: primary + start: 2 + end: 13 diff --git a/Utilities/ast-grep/rule-tests/cmstrcat-adjacent-literals-test.yml b/Utilities/ast-grep/rule-tests/cmstrcat-adjacent-literals-test.yml new file mode 100644 index 00000000000..a20a04dd5fc --- /dev/null +++ b/Utilities/ast-grep/rule-tests/cmstrcat-adjacent-literals-test.yml @@ -0,0 +1,39 @@ +--- +id: cmstrcat-adjacent-literals +valid: + - 'cmStrCat("literal", variable)' + - 'cmStrCat(variable, "literal")' + - 'cmStrCat(variable, "literal", variable)' + - 'cmStrCat(variable, variable, "literal")' + - 'cmStrCat("literal", variable, variable)' + - 'cmStrCat(variable, "literal", variable)' + - 'cmStrCat("literal", binary + expr, "literal")' + - 'cmStrCat("literal", cond ? t : f, "literal")' + - 'cmStrCat("literal", field.expr, "literal")' + - 'cmStrCat("literal", identifier, "literal")' + - 'cmStrCat("literal", [lambda](){}, "literal")' + - 'cmStrCat("literal", 4, "literal")' + - 'cmStrCat("literal", (parens), "literal")' + - 'cmStrCat("literal", *ptr_expr, "literal")' + - 'cmStrCat("literal", qualified::ident, "literal")' + - 'cmStrCat("literal", subscript[expr], "literal")' + - 'cmStrCat("literal", +unary_expr, "literal")' + - 'cmStrCat("literal", ++update, "literal")' + - 'cmStrCat("literal", "udl"_unit, "literal")' +invalid: + - 'cmStrCat("literal", "literal")' + - 'cmStrCat("literal", "literal", "literal")' + - 'cmStrCat("literal", "literal", variable)' + - 'cmStrCat(variable, "literal", "literal")' + - 'cmStrCat(variable, "literal", "literal", "literal")' + - 'cmStrCat(variable, "literal", "literal", "literal", variable, "literal")' + - 'cmStrCat(variable, "literal", "literal", variable)' + - "cmStrCat(variable, \"literal\", 'l', variable)" + - "cmStrCat(variable, 'c', 'l', variable)" + - "cmStrCat(variable, 'c', \"literal\", variable)" + - "cmStrCat(variable, 'c', \"literal\" \"literal\")" + - 'cmStrCat(variable, "literal", "literal" "literal")' + - 'cmStrCat(variable, "literal", R"(raw_literal)", variable)' + - 'cmStrCat(variable, R"(raw_literal)", "literal", variable)' + - 'cmStrCat(variable, "literal", "string_view"_s, variable)' + - 'cmStrCat(variable, "string_view"_s, "literal", variable)' diff --git a/Utilities/ast-grep/rule-tests/cmstrcat-to-char-literal-test.yml b/Utilities/ast-grep/rule-tests/cmstrcat-to-char-literal-test.yml new file mode 100644 index 00000000000..b455f8fb81a --- /dev/null +++ b/Utilities/ast-grep/rule-tests/cmstrcat-to-char-literal-test.yml @@ -0,0 +1,24 @@ +--- +id: cmstrcat-to-char-literal +valid: + - 'cmStrCat(other, "li")' + - 'cmStrCat(other, variable)' + - "cmStrCat(other, 'c')" + - "cmStrCat(other, '\\n')" + - "cmStrCat(other, \"a\" \"b\")" +invalid: + - 'cmStrCat(other, "\n")' + - 'cmStrCat(other, "n")' + - 'cmStrCat(other, "\"")' + - "cmStrCat(other, \"\\'\")" + - "cmStrCat(other, \"'\")" + - 'cmStrCat("\n", other)' + - 'cmStrCat("n", other)' + - 'cmStrCat("\"", other)' + - "cmStrCat(\"\\'\", other)" + - "cmStrCat(\"'\", other)" + - 'cmStrCat(other, "\n", other)' + - 'cmStrCat(other, "n", other)' + - 'cmStrCat(other, "\"", other)' + - "cmStrCat(other, \"\\'\", other)" + - "cmStrCat(other, \"'\", other)" diff --git a/Utilities/ast-grep/rule-tests/rm-cmdbg-includes.yml b/Utilities/ast-grep/rule-tests/rm-cmdbg-includes.yml new file mode 100644 index 00000000000..306b8d2ff12 --- /dev/null +++ b/Utilities/ast-grep/rule-tests/rm-cmdbg-includes.yml @@ -0,0 +1,8 @@ +--- +id: rm-cmdbg-includes +valid: + - '#include "NotcmDebugTools.h"' + - '#include ' +invalid: + - '#include "cmDebugTools.h"' + - '#include ' diff --git a/Utilities/ast-grep/rule-tests/rm-cmdbg-macros.yml b/Utilities/ast-grep/rule-tests/rm-cmdbg-macros.yml new file mode 100644 index 00000000000..dbbcda66b32 --- /dev/null +++ b/Utilities/ast-grep/rule-tests/rm-cmdbg-macros.yml @@ -0,0 +1,10 @@ +--- +id: rm-cmdbg-macros +valid: + - CM_DBG; + - macro(expr); +invalid: + - CM_DBG(arg); + - f(CM_DBG(arg)); + - b = a + CM_DBG(arg); + - a = CM_DBG(arg); diff --git a/Utilities/ast-grep/rules/cmstrcat-adjacent-literals.yml b/Utilities/ast-grep/rules/cmstrcat-adjacent-literals.yml new file mode 100644 index 00000000000..bc001a36a78 --- /dev/null +++ b/Utilities/ast-grep/rules/cmstrcat-adjacent-literals.yml @@ -0,0 +1,19 @@ +--- +id: cmstrcat-adjacent-literals +language: Cpp +severity: warning +message: Adjacent `cmStrCat` arguments which are string literals should be combined +ignores: + - Utilities/ClangTidyModule/Tests/** +rule: + matches: string-literal + inside: + matches: cmstrcat-call + stopBy: + kind: call_expression + precedes: + matches: string-literal + follows: + pattern: ',' + stopBy: + matches: cmstrcat-arg diff --git a/Utilities/ast-grep/rules/cmstrcat-to-char-literal.yaml b/Utilities/ast-grep/rules/cmstrcat-to-char-literal.yaml new file mode 100644 index 00000000000..252d16d26ef --- /dev/null +++ b/Utilities/ast-grep/rules/cmstrcat-to-char-literal.yaml @@ -0,0 +1,36 @@ +--- +id: cmstrcat-to-char-literal +language: Cpp +severity: warning +message: "`cmStrCat` string literal arguments which can be char literals should be" +rule: + kind: string_literal + pattern: $ARG + follows: + regex: '(,|[(])' + precedes: + regex: '(,|[)])' + inside: + matches: cmstrcat-call + stopBy: + kind: call_expression +constraints: + ARG: + regex: '^"(.|\\.)"$' +transform: + ARG_CHANGE_QUOTE: + replace: + source: $ARG + replace: '(^"|"$)' + by: "'" + ARG_ESCAPE_SINGLE_QUOTE: + replace: + source: $ARG_CHANGE_QUOTE + replace: "'''" + by: "'\\''" + ARG_OUT: + replace: + source: $ARG_ESCAPE_SINGLE_QUOTE + replace: '\\"' + by: '"' +fix: $ARG_OUT diff --git a/Utilities/ast-grep/rules/rm-cmdbg-includes.yml b/Utilities/ast-grep/rules/rm-cmdbg-includes.yml new file mode 100644 index 00000000000..b3ca0c1090c --- /dev/null +++ b/Utilities/ast-grep/rules/rm-cmdbg-includes.yml @@ -0,0 +1,13 @@ +--- +id: rm-cmdbg-includes +language: Cpp +severity: 'off' +message: "Remove `cmDebugTools.h` includes before submission" +ignores: + # Unit tests for the header. + - Tests/CMakeLib/testDebug.cxx +rule: + any: + - pattern: '#include "cmDebugTools.h"' + - pattern: '#include ' +fix: '' diff --git a/Utilities/ast-grep/rules/rm-cmdbg-macros.yml b/Utilities/ast-grep/rules/rm-cmdbg-macros.yml new file mode 100644 index 00000000000..ed06ba6abed --- /dev/null +++ b/Utilities/ast-grep/rules/rm-cmdbg-macros.yml @@ -0,0 +1,12 @@ +--- +id: rm-cmdbg-macros +language: Cpp +severity: 'off' +message: "Remove `CM_DBG` usage before submission" +ignores: + # Unit tests for the header. + - Tests/CMakeLib/testDebug.cxx +rule: + pattern: CM_DBG($EXPR) + kind: call_expression +fix: $EXPR diff --git a/Utilities/ast-grep/utils/cmstrcat-arg.yml b/Utilities/ast-grep/utils/cmstrcat-arg.yml new file mode 100644 index 00000000000..84160216eb8 --- /dev/null +++ b/Utilities/ast-grep/utils/cmstrcat-arg.yml @@ -0,0 +1,23 @@ +--- +id: cmstrcat-arg +language: Cpp +rule: + any: + - kind: binary_expression + - kind: call_expression + - kind: char_literal + - kind: concatenated_string + - kind: conditional_expression + - kind: field_expression + - kind: identifier + - kind: lambda_expression + - kind: number_literal + - kind: parenthesized_expression + - kind: pointer_expression + - kind: qualified_identifier + - kind: raw_string_literal + - kind: string_literal + - kind: subscript_expression + - kind: unary_expression + - kind: update_expression + - kind: user_defined_literal diff --git a/Utilities/ast-grep/utils/cmstrcat-call.yml b/Utilities/ast-grep/utils/cmstrcat-call.yml new file mode 100644 index 00000000000..34e68f9db78 --- /dev/null +++ b/Utilities/ast-grep/utils/cmstrcat-call.yml @@ -0,0 +1,5 @@ +--- +id: cmstrcat-call +language: Cpp +rule: + pattern: cmStrCat($$$) diff --git a/Utilities/ast-grep/utils/string-literal.yml b/Utilities/ast-grep/utils/string-literal.yml new file mode 100644 index 00000000000..1f6b5572961 --- /dev/null +++ b/Utilities/ast-grep/utils/string-literal.yml @@ -0,0 +1,13 @@ +--- +id: string-literal +language: Cpp +rule: + any: + - kind: char_literal + - kind: concatenated_string + - kind: raw_string_literal + - kind: string_literal + - kind: user_defined_literal + has: + kind: literal_suffix + regex: '^_s$' diff --git a/Utilities/cm3p/Setup.Configuration.h b/Utilities/cm3p/Setup.Configuration.h index 9f4190e4f59..3abcc0b9168 100644 --- a/Utilities/cm3p/Setup.Configuration.h +++ b/Utilities/cm3p/Setup.Configuration.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/cm3p/archive.h b/Utilities/cm3p/archive.h index a775400e4f6..2b92eb93d40 100644 --- a/Utilities/cm3p/archive.h +++ b/Utilities/cm3p/archive.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the libarchive configured for CMake. */ diff --git a/Utilities/cm3p/archive_entry.h b/Utilities/cm3p/archive_entry.h index 0f8376c795b..2bd682cabf3 100644 --- a/Utilities/cm3p/archive_entry.h +++ b/Utilities/cm3p/archive_entry.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the libarchive configured for CMake. */ diff --git a/Utilities/cm3p/bzlib.h b/Utilities/cm3p/bzlib.h index c0eef0322de..98fccb1ad6d 100644 --- a/Utilities/cm3p/bzlib.h +++ b/Utilities/cm3p/bzlib.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the bzip2 library configured for CMake. */ diff --git a/Utilities/cm3p/cppdap/dap.h b/Utilities/cm3p/cppdap/dap.h index 84fd3324823..72f4d263acb 100644 --- a/Utilities/cm3p/cppdap/dap.h +++ b/Utilities/cm3p/cppdap/dap.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the cppdap library configured for CMake. */ diff --git a/Utilities/cm3p/cppdap/future.h b/Utilities/cm3p/cppdap/future.h index ad45b6bcfdb..c11d6722870 100644 --- a/Utilities/cm3p/cppdap/future.h +++ b/Utilities/cm3p/cppdap/future.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the cppdap library configured for CMake. */ diff --git a/Utilities/cm3p/cppdap/io.h b/Utilities/cm3p/cppdap/io.h index e0401f8339a..7bc6df50a35 100644 --- a/Utilities/cm3p/cppdap/io.h +++ b/Utilities/cm3p/cppdap/io.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the cppdap library configured for CMake. */ diff --git a/Utilities/cm3p/cppdap/optional.h b/Utilities/cm3p/cppdap/optional.h index 777184d0739..b74b55981a7 100644 --- a/Utilities/cm3p/cppdap/optional.h +++ b/Utilities/cm3p/cppdap/optional.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the cppdap library configured for CMake. */ diff --git a/Utilities/cm3p/cppdap/protocol.h b/Utilities/cm3p/cppdap/protocol.h index da70369f8a1..5b174645d38 100644 --- a/Utilities/cm3p/cppdap/protocol.h +++ b/Utilities/cm3p/cppdap/protocol.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the cppdap library configured for CMake. */ diff --git a/Utilities/cm3p/cppdap/session.h b/Utilities/cm3p/cppdap/session.h index d4468e7ce5e..77196c4a9e0 100644 --- a/Utilities/cm3p/cppdap/session.h +++ b/Utilities/cm3p/cppdap/session.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the cppdap library configured for CMake. */ diff --git a/Utilities/cm3p/cppdap/types.h b/Utilities/cm3p/cppdap/types.h index 3fc2a883173..f3ceff530cc 100644 --- a/Utilities/cm3p/cppdap/types.h +++ b/Utilities/cm3p/cppdap/types.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the cppdap library configured for CMake. */ diff --git a/Utilities/cm3p/curl/curl.h b/Utilities/cm3p/curl/curl.h index 272db8d9f3d..7b78dcb6674 100644 --- a/Utilities/cm3p/curl/curl.h +++ b/Utilities/cm3p/curl/curl.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the curl library configured for CMake. */ diff --git a/Utilities/cm3p/expat.h b/Utilities/cm3p/expat.h index bcf6195e433..251db454bda 100644 --- a/Utilities/cm3p/expat.h +++ b/Utilities/cm3p/expat.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the expat library configured for CMake. */ diff --git a/Utilities/cm3p/json/forwards.h b/Utilities/cm3p/json/forwards.h index c55c5c115b3..84fa6645442 100644 --- a/Utilities/cm3p/json/forwards.h +++ b/Utilities/cm3p/json/forwards.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the jsoncpp library configured for CMake. */ diff --git a/Utilities/cm3p/json/json.h b/Utilities/cm3p/json/json.h index 5671e91a4bf..c265693a8bc 100644 --- a/Utilities/cm3p/json/json.h +++ b/Utilities/cm3p/json/json.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the jsoncpp library configured for CMake. */ diff --git a/Utilities/cm3p/json/reader.h b/Utilities/cm3p/json/reader.h index 9fa8d2d7797..a4d1572e169 100644 --- a/Utilities/cm3p/json/reader.h +++ b/Utilities/cm3p/json/reader.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the jsoncpp library configured for CMake. */ diff --git a/Utilities/cm3p/json/value.h b/Utilities/cm3p/json/value.h index fc3b5f4a568..a2df097343d 100644 --- a/Utilities/cm3p/json/value.h +++ b/Utilities/cm3p/json/value.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the jsoncpp library configured for CMake. */ diff --git a/Utilities/cm3p/json/version.h b/Utilities/cm3p/json/version.h new file mode 100644 index 00000000000..13b8b016079 --- /dev/null +++ b/Utilities/cm3p/json/version.h @@ -0,0 +1,11 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +/* Use the jsoncpp library configured for CMake. */ +#include "cmThirdParty.h" +#ifdef CMAKE_USE_SYSTEM_JSONCPP +# include // IWYU pragma: export +#else +# include // IWYU pragma: export +#endif diff --git a/Utilities/cm3p/json/writer.h b/Utilities/cm3p/json/writer.h index 7ee1e43af26..a0a43f39e6c 100644 --- a/Utilities/cm3p/json/writer.h +++ b/Utilities/cm3p/json/writer.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the jsoncpp library configured for CMake. */ diff --git a/Utilities/cm3p/kwiml/abi.h b/Utilities/cm3p/kwiml/abi.h index 8d5189a6dcf..760ae671618 100644 --- a/Utilities/cm3p/kwiml/abi.h +++ b/Utilities/cm3p/kwiml/abi.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the KWIML library configured for CMake. */ diff --git a/Utilities/cm3p/kwiml/int.h b/Utilities/cm3p/kwiml/int.h index 2669df8358c..b716b569d53 100644 --- a/Utilities/cm3p/kwiml/int.h +++ b/Utilities/cm3p/kwiml/int.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the KWIML library configured for CMake. */ diff --git a/Utilities/cm3p/lzma.h b/Utilities/cm3p/lzma.h index 7842f6bb45c..4820aeba9a0 100644 --- a/Utilities/cm3p/lzma.h +++ b/Utilities/cm3p/lzma.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the liblzma configured for CMake. */ diff --git a/Utilities/cm3p/rhash.h b/Utilities/cm3p/rhash.h index 58285574a48..8ce707585a0 100644 --- a/Utilities/cm3p/rhash.h +++ b/Utilities/cm3p/rhash.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the LibRHash library configured for CMake. */ diff --git a/Utilities/cm3p/uv.h b/Utilities/cm3p/uv.h index 36a86b696a4..83f9d8e9083 100644 --- a/Utilities/cm3p/uv.h +++ b/Utilities/cm3p/uv.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the libuv library configured for CMake. */ diff --git a/Utilities/cm3p/zlib.h b/Utilities/cm3p/zlib.h index 6b82aa21155..635580cf892 100644 --- a/Utilities/cm3p/zlib.h +++ b/Utilities/cm3p/zlib.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the zlib library configured for CMake. */ diff --git a/Utilities/cm3p/zstd.h b/Utilities/cm3p/zstd.h index 51972de3421..171a754c540 100644 --- a/Utilities/cm3p/zstd.h +++ b/Utilities/cm3p/zstd.h @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Use the libzstd configured for CMake. */ diff --git a/Utilities/cmThirdParty.h.in b/Utilities/cmThirdParty.h.in index da325b1c16f..fa3c7ed888a 100644 --- a/Utilities/cmThirdParty.h.in +++ b/Utilities/cmThirdParty.h.in @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Whether CMake is using its own utility libraries. */ diff --git a/Utilities/cmThirdPartyChecks.cmake b/Utilities/cmThirdPartyChecks.cmake index 560566709c9..a316b6b95a1 100644 --- a/Utilities/cmThirdPartyChecks.cmake +++ b/Utilities/cmThirdPartyChecks.cmake @@ -1,5 +1,5 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. +# file LICENSE.rst or https://cmake.org/licensing for details. # Hard-code third-party try_compile checks where we know the answer. @@ -43,8 +43,8 @@ if(WIN32) set(HAVE_CHROOT 0) set(HAVE_COPYFILE_H 0) set(HAVE_CRYPTO_H 0) - set(HAVE__CTIME64_S 1) set(HAVE_CTIME_R 0) + set(HAVE_CTIME_S 1) set(HAVE_CYGWIN_CONV_PATH 0) set(HAVE_DES_H 0) set(HAVE_DIRECT_H 1) @@ -55,6 +55,7 @@ if(WIN32) set(HAVE_EILSEQ 1) set(HAVE_ERR_H 0) set(HAVE_ERRNO_H 1) + set(HAVE_EVENTFD 0) set(HAVE_EXT2FS_EXT2_FS_H 0) set(HAVE_FCHDIR 0) set(HAVE_FCHFLAGS 0) @@ -64,6 +65,8 @@ if(WIN32) set(HAVE_FCNTL_H 1) set(HAVE_FCNTL_O_NONBLOCK 0) set(HAVE_FDOPENDIR 0) + set(HAVE_FNMATCH 0) + set(HAVE_FNMATCH_H 0) set(HAVE_FORK 0) set(HAVE_FREEADDRINFO 1) set(HAVE_FREEIFADDRS 0) @@ -82,6 +85,7 @@ if(WIN32) set(HAVE_GETGRGID_R 0) set(HAVE_GETGRNAM_R 0) set(HAVE_GETHOSTBYNAME 1) + set(HAVE_GETLINE 0) set(HAVE_GETPAGESIZE 0) set(HAVE_GETPEERNAME 1) set(HAVE_GETPID 1) @@ -94,8 +98,8 @@ if(WIN32) set(HAVE_GETSOCKNAME 1) set(HAVE_GETVFSBYNAME 0) set(HAVE_GLIBC_STRERROR_R 0) - set(HAVE__GMTIME64_S 1) set(HAVE_GMTIME_R 0) + set(HAVE_GMTIME_S 1) set(HAVE_GRP_H 0) set(HAVE_IDN2_H 0) set(HAVE_IFADDRS_H 0) @@ -126,8 +130,8 @@ if(WIN32) set(HAVE_LINUX_FS_H 0) set(HAVE_LINUX_MAGIC_H 0) set(HAVE_LINUX_TYPES_H 0) - set(HAVE__LOCALTIME64_S 1) set(HAVE_LOCALTIME_R 0) + set(HAVE_LOCALTIME_S 0) set(HAVE_LSTAT 0) set(HAVE_LUTIMES 0) set(HAVE_MACH_ABSOLUTE_TIME 0) @@ -136,7 +140,7 @@ if(WIN32) set(HAVE_MEMORY_H 1) set(HAVE_MKDIR 1) set(HAVE_MKFIFO 0) - set(HAVE__MKGMTIME64 1) + set(HAVE__MKGMTIME 1) set(HAVE_MKNOD 0) set(HAVE_MMAP 0) set(HAVE_MSG_NOSIGNAL 0) @@ -173,6 +177,7 @@ if(WIN32) set(HAVE_SIGNAL_H 1) set(HAVE_SIZEOF_ADDRESS_FAMILY 0) set(HAVE_SIZEOF_SA_FAMILY_T 0) + set(HAVE_SIZEOF_SUSECONDS_T 0) set(HAVE_SOCKETPAIR 0) set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 0) set(HAVE_SPAWN_H 0) @@ -207,14 +212,17 @@ if(WIN32) set(HAVE_STRUCT_VFSCONF 0) set(HAVE_STRUCT_XVFSCONF 0) set(HAVE_SYMLINK 0) + set(HAVE_SYSCONF 0) set(HAVE_SYS_ACL_H 0) set(HAVE_SYSCALL_GETRANDOM 0) + set(HAVE_SYS_EVENTFD_H 0) set(HAVE_SYS_EXTATTR_H 0) set(HAVE_SYS_FILIO_H 0) set(HAVE_SYS_IOCTL_H 0) set(HAVE_SYS_MKDEV_H 0) set(HAVE_SYS_MOUNT_H 0) set(HAVE_SYS_POLL_H 0) + set(HAVE_SYS_QUEUE_H 0) set(HAVE_SYS_RESOURCE_H 0) set(HAVE_SYS_RICHACL_H 0) set(HAVE_SYS_SELECT_H 0) @@ -229,6 +237,8 @@ if(WIN32) set(HAVE_SYS_VFS_H 0) set(HAVE_SYS_WAIT_H 0) set(HAVE_SYS_XATTR_H 0) + set(HAVE_TCGETATTR 0) + set(HAVE_TCSETATTR 0) set(HAVE_TIMEGM 0) set(HAVE_TZSET 1) set(HAVE_UNLINKAT 0) @@ -265,7 +275,6 @@ if(WIN32) set(HAVE_WINDOWS_H 1) set(HAVE_WINIOCTL_H 1) set(HAVE_WINSOCK2_H 1) - set(HAVE_WINSOCK_H 1) set(HAVE_WS2TCPIP_H 1) set(USE_WINCRYPT 1) # We do not need to build as a Windows App. diff --git a/Utilities/cmbzip2/CMakeLists.txt b/Utilities/cmbzip2/CMakeLists.txt index 1d7b265d4cf..b52358e2d4a 100644 --- a/Utilities/cmbzip2/CMakeLists.txt +++ b/Utilities/cmbzip2/CMakeLists.txt @@ -19,3 +19,7 @@ endif() add_definitions(-D_FILE_OFFSET_BITS=64) add_library(cmbzip2 blocksort.c huffman.c crctable.c randtable.c compress.c decompress.c bzlib.c) + +if(WIN32 AND CMake_BUILD_PCH) + target_precompile_headers(cmbzip2 PRIVATE "bzlib.h") +endif() diff --git a/Utilities/cmcppdap/CMakeLists.txt b/Utilities/cmcppdap/CMakeLists.txt index b6841f11886..fe48132bbc4 100644 --- a/Utilities/cmcppdap/CMakeLists.txt +++ b/Utilities/cmcppdap/CMakeLists.txt @@ -37,4 +37,8 @@ if(CMake_HAVE_CXX_ATOMIC_LIB) target_link_libraries(cmcppdap PRIVATE atomic) endif() +if(CMake_BUILD_PCH) + target_precompile_headers(cmcppdap PRIVATE "include/dap/protocol.h") +endif() + install(FILES NOTICE DESTINATION ${CMAKE_DOC_DIR}/cmcppdap) diff --git a/Utilities/cmcppdap/include/dap/any.h b/Utilities/cmcppdap/include/dap/any.h index b05f03d4574..6060546284b 100644 --- a/Utilities/cmcppdap/include/dap/any.h +++ b/Utilities/cmcppdap/include/dap/any.h @@ -159,7 +159,7 @@ any& any::operator=(const std::nullptr_t&) { template T& any::get() const { - static_assert(!std::is_same(), + static_assert(!std::is_same::value, "Cannot get nullptr from 'any'."); assert(is()); return *reinterpret_cast(value); diff --git a/Utilities/cmcppdap/include/dap/network.h b/Utilities/cmcppdap/include/dap/network.h index 9d14f6b70fa..03b940925ba 100644 --- a/Utilities/cmcppdap/include/dap/network.h +++ b/Utilities/cmcppdap/include/dap/network.h @@ -17,6 +17,7 @@ #include #include +#include namespace dap { class ReaderWriter; @@ -44,13 +45,21 @@ class Server { // create() constructs and returns a new Server. static std::unique_ptr create(); - // start() begins listening for connections on the given port. + // start() begins listening for connections on localhost and the given port. // callback will be called for each connection. // onError will be called for any connection errors. virtual bool start(int port, const OnConnect& callback, const OnError& onError = ignoreErrors) = 0; + // start() begins listening for connections on the given specific address and port. + // callback will be called for each connection. + // onError will be called for any connection errors. + virtual bool start(const char* address, + int port, + const OnConnect& callback, + const OnError& onError = ignoreErrors) = 0; + // stop() stops listening for connections. // stop() is implicitly called on destruction. virtual void stop() = 0; diff --git a/Utilities/cmcppdap/include/dap/protocol.h b/Utilities/cmcppdap/include/dap/protocol.h index e4c479e51c3..96a18c9701a 100644 --- a/Utilities/cmcppdap/include/dap/protocol.h +++ b/Utilities/cmcppdap/include/dap/protocol.h @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.59.0 +// DAP version 1.65.0 #ifndef dap_protocol_h #define dap_protocol_h @@ -141,6 +141,18 @@ struct Breakpoint { // The offset from the instruction reference. // This can be negative. optional offset; + // A machine-readable explanation of why a breakpoint may not be verified. If + // a breakpoint is verified or a specific reason is not known, the adapter + // should omit this property. Possible values include: + // + // - `pending`: Indicates a breakpoint might be verified in the future, but + // the adapter cannot verify it in the current state. + // - `failed`: Indicates a breakpoint was not able to be verified, and the + // adapter does not believe it can be verified without intervention. + // + // Must be one of the following enumeration values: + // 'pending', 'failed' + optional reason; // The source where the breakpoint is located. optional source; // If true, the breakpoint could be set (but not necessarily at the desired @@ -214,7 +226,7 @@ struct BreakpointLocationsRequest : public Request { // line is specified, the request returns all possible locations in that line. integer line; // The source location of the breakpoints; either `source.path` or - // `source.reference` must be specified. + // `source.sourceReference` must be specified. Source source; }; @@ -229,18 +241,19 @@ DAP_DECLARE_STRUCT_TYPEINFO(CancelResponse); // The `cancel` request is used by the client in two situations: // - to indicate that it is no longer interested in the result produced by a // specific request issued earlier -// - to cancel a progress sequence. Clients should only call this request if the -// corresponding capability `supportsCancelRequest` is true. This request has a -// hint characteristic: a debug adapter can only be expected to make a 'best -// effort' in honoring this request but there are no guarantees. The `cancel` -// request may return an error if it could not cancel an operation but a client -// should refrain from presenting this error to end users. The request that got -// cancelled still needs to send a response back. This can either be a normal -// result (`success` attribute true) or an error response (`success` attribute -// false and the `message` set to `cancelled`). Returning partial results from a -// cancelled request is possible but please note that a client has no generic -// way for detecting that a response is partial or not. The progress that got -// cancelled still needs to send a `progressEnd` event back. +// - to cancel a progress sequence. +// Clients should only call this request if the corresponding capability +// `supportsCancelRequest` is true. This request has a hint characteristic: a +// debug adapter can only be expected to make a 'best effort' in honoring this +// request but there are no guarantees. The `cancel` request may return an error +// if it could not cancel an operation but a client should refrain from +// presenting this error to end users. The request that got cancelled still +// needs to send a response back. This can either be a normal result (`success` +// attribute true) or an error response (`success` attribute false and the +// `message` set to `cancelled`). Returning partial results from a cancelled +// request is possible but please note that a client has no generic way for +// detecting that a response is partial or not. The progress that got cancelled +// still needs to send a `progressEnd` event back. // A client should not assume that progress just got cancelled after sending // the `cancel` request. struct CancelRequest : public Request { @@ -280,6 +293,28 @@ struct ColumnDescriptor { DAP_DECLARE_STRUCT_TYPEINFO(ColumnDescriptor); +// Describes one or more type of breakpoint a `BreakpointMode` applies to. This +// is a non-exhaustive enumeration and may expand as future breakpoint types are +// added. +using BreakpointModeApplicability = string; + +// A `BreakpointMode` is provided as a option when setting breakpoints on +// sources or instructions. +struct BreakpointMode { + // Describes one or more type of breakpoint this mode applies to. + array appliesTo; + // A help text providing additional information about the breakpoint mode. + // This string is typically shown as a hover and can be translated. + optional description; + // The name of the breakpoint mode. This is shown in the UI. + string label; + // The internal ID of the mode. This value is passed to the `setBreakpoints` + // request. + string mode; +}; + +DAP_DECLARE_STRUCT_TYPEINFO(BreakpointMode); + // An `ExceptionBreakpointsFilter` is shown in the UI as an filter option for // configuring how exceptions are dealt with. struct ExceptionBreakpointsFilter { @@ -308,6 +343,13 @@ DAP_DECLARE_STRUCT_TYPEINFO(ExceptionBreakpointsFilter); struct Capabilities { // The set of additional module information exposed by the debug adapter. optional> additionalModuleColumns; + // Modes of breakpoints supported by the debug adapter, such as 'hardware' or + // 'software'. If present, the client may allow the user to select a mode and + // include it in its `setBreakpoints` request. + // + // Clients may present the first applicable mode in this array as the + // 'default' mode in gestures that set breakpoints. + optional> breakpointModes; // The set of characters that should trigger completion in a REPL. If not // specified, the UI should assume the `.` character. optional> completionTriggerCharacters; @@ -579,7 +621,11 @@ struct DataBreakpointInfoResponse : public Response { optional canPersist; // An identifier for the data on which a data breakpoint can be registered // with the `setDataBreakpoints` request or null if no data breakpoint is - // available. + // available. If a `variablesReference` or `frameId` is passed, the `dataId` + // is valid in the current suspended state, otherwise it's valid indefinitely. + // See 'Lifetime of Object References' in the Overview section for details. + // Breakpoints set using the `dataId` in the `setDataBreakpoints` request may + // outlive the lifetime of the associated `dataId`. variant dataId; // UI string that describes on what data the breakpoint is set on or why a // data breakpoint is not available. @@ -597,6 +643,9 @@ struct DataBreakpointInfoRequest : public Request { // If not specified, the expression is evaluated in the global scope. When // `variablesReference` is specified, this property has no effect. optional frameId; + // The mode of the desired breakpoint. If defined, this must be one of the + // `breakpointModes` the debug adapter advertised in its `Capabilities`. + optional mode; // The name of the variable's child to obtain data breakpoint information for. // If `variablesReference` isn't specified, this can be an expression. string name; @@ -634,6 +683,15 @@ struct DisassembledInstruction { // but can be omitted afterwards if this instruction maps to the same source // file as the previous instruction. optional location; + // A hint for how to present the instruction in the UI. + // + // A value of `invalid` may be used to indicate this instruction is 'filler' + // and cannot be reached by the program. For example, unreadable memory + // addresses may be presented is 'invalid.' + // + // Must be one of the following enumeration values: + // 'normal', 'invalid' + optional presentationHint; // Name of the symbol that corresponds with the location of this instruction, // if any. optional symbol; @@ -785,9 +843,8 @@ struct EvaluateResponse : public Response { optional indexedVariables; // A memory reference to a location appropriate for this result. // For pointer type eval results, this is generally a reference to the memory - // address contained in the pointer. This attribute should be returned by a - // debug adapter if corresponding capability `supportsMemoryReferences` is - // true. + // address contained in the pointer. This attribute may be returned by a debug + // adapter if corresponding capability `supportsMemoryReferences` is true. optional memoryReference; // The number of named child variables. // The client can use this information to present the variables in a paged UI @@ -979,6 +1036,13 @@ DAP_DECLARE_STRUCT_TYPEINFO(GotoTargetsRequest); struct InitializeResponse : public Response { // The set of additional module information exposed by the debug adapter. optional> additionalModuleColumns; + // Modes of breakpoints supported by the debug adapter, such as 'hardware' or + // 'software'. If present, the client may allow the user to select a mode and + // include it in its `setBreakpoints` request. + // + // Clients may present the first applicable mode in this array as the + // 'default' mode in gestures that set breakpoints. + optional> breakpointModes; // The set of characters that should trigger completion in a REPL. If not // specified, the UI should assume the `.` character. optional> completionTriggerCharacters; @@ -1774,6 +1838,9 @@ struct SourceBreakpoint { // `hitCondition` or `condition` is specified, then the message should only be // logged if those conditions are met. optional logMessage; + // The mode of this breakpoint. If defined, this must be one of the + // `breakpointModes` the debug adapter advertised in its `Capabilities`. + optional mode; }; DAP_DECLARE_STRUCT_TYPEINFO(SourceBreakpoint); @@ -1846,13 +1913,13 @@ DAP_DECLARE_STRUCT_TYPEINFO(SetDataBreakpointsRequest); // the returned array must start with `filters` information first, followed by // `filterOptions` information. The `verified` property of a `Breakpoint` object // signals whether the exception breakpoint or filter could be successfully -// created and whether the condition or hit count expressions are valid. In case -// of an error the `message` property explains the problem. The `id` property -// can be used to introduce a unique ID for the exception breakpoint or filter -// so that it can be updated subsequently by sending breakpoint events. For -// backward compatibility both the `breakpoints` array and the enclosing `body` -// are optional. If these elements are missing a client is not able to show -// problems for individual exception breakpoints or filters. +// created and whether the condition is valid. In case of an error the `message` +// property explains the problem. The `id` property can be used to introduce a +// unique ID for the exception breakpoint or filter so that it can be updated +// subsequently by sending breakpoint events. For backward compatibility both +// the `breakpoints` array and the enclosing `body` are optional. If these +// elements are missing a client is not able to show problems for individual +// exception breakpoints or filters. struct SetExceptionBreakpointsResponse : public Response { // Information about the exception breakpoints or filters. // The breakpoints returned are in the same order as the elements of the @@ -1901,15 +1968,20 @@ struct ExceptionFilterOptions { // ID of an exception filter returned by the `exceptionBreakpointFilters` // capability. string filterId; + // The mode of this exception breakpoint. If defined, this must be one of the + // `breakpointModes` the debug adapter advertised in its `Capabilities`. + optional mode; }; DAP_DECLARE_STRUCT_TYPEINFO(ExceptionFilterOptions); -// The request configures the debugger's response to thrown exceptions. -// If an exception is configured to break, a `stopped` event is fired (with -// reason `exception`). Clients should only call this request if the -// corresponding capability `exceptionBreakpointFilters` returns one or more -// filters. +// The request configures the debugger's response to thrown exceptions. Each of +// the `filters`, `filterOptions`, and `exceptionOptions` in the request are +// independent configurations to a debug adapter indicating a kind of exception +// to catch. An exception thrown in a program should result in a `stopped` event +// from the debug adapter (with reason `exception`) if any of the configured +// filters match. Clients should only call this request if the corresponding +// capability `exceptionBreakpointFilters` returns one or more filters. struct SetExceptionBreakpointsRequest : public Request { using Response = SetExceptionBreakpointsResponse; // Configuration options for selected exceptions. @@ -1937,6 +2009,11 @@ struct SetExpressionResponse : public Response { // and fetch them in chunks. The value should be less than or equal to // 2147483647 (2^31-1). optional indexedVariables; + // A memory reference to a location appropriate for this result. + // For pointer type eval results, this is generally a reference to the memory + // address contained in the pointer. This attribute may be returned by a debug + // adapter if corresponding capability `supportsMemoryReferences` is true. + optional memoryReference; // The number of named child variables. // The client can use this information to present the variables in a paged UI // and fetch them in chunks. The value should be less than or equal to @@ -2047,7 +2124,10 @@ struct InstructionBreakpoint { // `EvaluateResponse`, `Variable`, `StackFrame`, `GotoTarget`, or // `Breakpoint`. string instructionReference; - // The offset from the instruction reference. + // The mode of this breakpoint. If defined, this must be one of the + // `breakpointModes` the debug adapter advertised in its `Capabilities`. + optional mode; + // The offset from the instruction reference in bytes. // This can be negative. optional offset; }; @@ -2075,6 +2155,11 @@ struct SetVariableResponse : public Response { // and fetch them in chunks. The value should be less than or equal to // 2147483647 (2^31-1). optional indexedVariables; + // A memory reference to a location appropriate for this result. + // For pointer type eval results, this is generally a reference to the memory + // address contained in the pointer. This attribute may be returned by a debug + // adapter if corresponding capability `supportsMemoryReferences` is true. + optional memoryReference; // The number of named child variables. // The client can use this information to present the variables in a paged UI // and fetch them in chunks. The value should be less than or equal to @@ -2568,9 +2653,12 @@ struct Variable { // The client can use this information to present the children in a paged UI // and fetch them in chunks. optional indexedVariables; - // The memory reference for the variable if the variable represents executable - // code, such as a function pointer. This attribute is only required if the - // corresponding capability `supportsMemoryReferences` is true. + // A memory reference associated with this variable. + // For pointer type variables, this is generally a reference to the memory + // address contained in the pointer. For executable data, this reference may + // later be used in a `disassemble` request. This attribute may be returned by + // a debug adapter if corresponding capability `supportsMemoryReferences` is + // true. optional memoryReference; // The variable's name. string name; @@ -2616,7 +2704,8 @@ DAP_DECLARE_STRUCT_TYPEINFO(VariablesResponse); struct VariablesRequest : public Request { using Response = VariablesResponse; // The number of variables to return. If count is missing or 0, all variables - // are returned. + // are returned. The attribute is only honored by a debug adapter if the + // corresponding capability `supportsVariablePaging` is true. optional count; // Filter to limit the child variables to either named or indexed. If omitted, // both types are fetched. @@ -2629,6 +2718,8 @@ struct VariablesRequest : public Request { // capability `supportsValueFormattingOptions` is true. optional format; // The index of the first variable to return; if omitted children start at 0. + // The attribute is only honored by a debug adapter if the corresponding + // capability `supportsVariablePaging` is true. optional start; // The variable for which to retrieve its children. The `variablesReference` // must have been obtained in the current suspended state. See 'Lifetime of diff --git a/Utilities/cmcppdap/include/dap/typeof.h b/Utilities/cmcppdap/include/dap/typeof.h index 803bb8dcc1f..43c9289c923 100644 --- a/Utilities/cmcppdap/include/dap/typeof.h +++ b/Utilities/cmcppdap/include/dap/typeof.h @@ -164,7 +164,7 @@ M member_type(M T::*); bool TypeOf::deserializeFields(const Deserializer* fd, void* obj) { \ using StructTy = STRUCT; \ (void)sizeof(StructTy); /* avoid unused 'using' warning */ \ - for (auto field : std::initializer_list{__VA_ARGS__}) { \ + for (auto& field : std::initializer_list{__VA_ARGS__}) { \ if (!fd->field(field.name, [&](Deserializer* d) { \ auto ptr = reinterpret_cast(obj) + field.offset; \ return field.type->deserialize(d, ptr); \ @@ -177,7 +177,7 @@ M member_type(M T::*); bool TypeOf::serializeFields(FieldSerializer* fs, const void* obj) {\ using StructTy = STRUCT; \ (void)sizeof(StructTy); /* avoid unused 'using' warning */ \ - for (auto field : std::initializer_list{__VA_ARGS__}) { \ + for (auto& field : std::initializer_list{__VA_ARGS__}) { \ if (!fs->field(field.name, [&](Serializer* s) { \ auto ptr = reinterpret_cast(obj) + field.offset; \ return field.type->serialize(s, ptr); \ diff --git a/Utilities/cmcppdap/src/network.cpp b/Utilities/cmcppdap/src/network.cpp index 613c2343f95..7d1da7a1cf1 100644 --- a/Utilities/cmcppdap/src/network.cpp +++ b/Utilities/cmcppdap/src/network.cpp @@ -32,10 +32,17 @@ class Impl : public dap::net::Server { bool start(int port, const OnConnect& onConnect, const OnError& onError) override { + return start("localhost", port, onConnect, onError); + } + + bool start(const char* address, + int port, + const OnConnect& onConnect, + const OnError& onError) override { std::unique_lock lock(mutex); stopWithLock(); socket = std::unique_ptr( - new dap::Socket("localhost", std::to_string(port).c_str())); + new dap::Socket(address, std::to_string(port).c_str())); if (!socket->isOpen()) { onError("Failed to open socket"); diff --git a/Utilities/cmcppdap/src/protocol_events.cpp b/Utilities/cmcppdap/src/protocol_events.cpp index 9deb85f3444..0cd6b9b88e6 100644 --- a/Utilities/cmcppdap/src/protocol_events.cpp +++ b/Utilities/cmcppdap/src/protocol_events.cpp @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.59.0 +// DAP version 1.65.0 #include "dap/protocol.h" diff --git a/Utilities/cmcppdap/src/protocol_requests.cpp b/Utilities/cmcppdap/src/protocol_requests.cpp index a3b33ec2dd6..dd5d3e3670d 100644 --- a/Utilities/cmcppdap/src/protocol_requests.cpp +++ b/Utilities/cmcppdap/src/protocol_requests.cpp @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.59.0 +// DAP version 1.65.0 #include "dap/protocol.h" @@ -55,6 +55,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(ContinueRequest, DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointInfoRequest, "dataBreakpointInfo", DAP_FIELD(frameId, "frameId"), + DAP_FIELD(mode, "mode"), DAP_FIELD(name, "name"), DAP_FIELD(variablesReference, "variablesReference")); diff --git a/Utilities/cmcppdap/src/protocol_response.cpp b/Utilities/cmcppdap/src/protocol_response.cpp index bab8ebbb26f..1810c8aa733 100644 --- a/Utilities/cmcppdap/src/protocol_response.cpp +++ b/Utilities/cmcppdap/src/protocol_response.cpp @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.59.0 +// DAP version 1.65.0 #include "dap/protocol.h" @@ -83,6 +83,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO( InitializeResponse, "", DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"), + DAP_FIELD(breakpointModes, "breakpointModes"), DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"), DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"), DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"), @@ -177,6 +178,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExceptionBreakpointsResponse, DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExpressionResponse, "", DAP_FIELD(indexedVariables, "indexedVariables"), + DAP_FIELD(memoryReference, "memoryReference"), DAP_FIELD(namedVariables, "namedVariables"), DAP_FIELD(presentationHint, "presentationHint"), DAP_FIELD(type, "type"), @@ -195,6 +197,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(SetInstructionBreakpointsResponse, DAP_IMPLEMENT_STRUCT_TYPEINFO(SetVariableResponse, "", DAP_FIELD(indexedVariables, "indexedVariables"), + DAP_FIELD(memoryReference, "memoryReference"), DAP_FIELD(namedVariables, "namedVariables"), DAP_FIELD(type, "type"), DAP_FIELD(value, "value"), diff --git a/Utilities/cmcppdap/src/protocol_types.cpp b/Utilities/cmcppdap/src/protocol_types.cpp index d9a9e36f798..2ae91000bb0 100644 --- a/Utilities/cmcppdap/src/protocol_types.cpp +++ b/Utilities/cmcppdap/src/protocol_types.cpp @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.59.0 +// DAP version 1.65.0 #include "dap/protocol.h" @@ -48,6 +48,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(Breakpoint, DAP_FIELD(line, "line"), DAP_FIELD(message, "message"), DAP_FIELD(offset, "offset"), + DAP_FIELD(reason, "reason"), DAP_FIELD(source, "source"), DAP_FIELD(verified, "verified")); @@ -66,6 +67,13 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(ColumnDescriptor, DAP_FIELD(type, "type"), DAP_FIELD(width, "width")); +DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointMode, + "", + DAP_FIELD(appliesTo, "appliesTo"), + DAP_FIELD(description, "description"), + DAP_FIELD(label, "label"), + DAP_FIELD(mode, "mode")); + DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakpointsFilter, "", DAP_FIELD(conditionDescription, @@ -81,6 +89,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO( Capabilities, "", DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"), + DAP_FIELD(breakpointModes, "breakpointModes"), DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"), DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"), DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"), @@ -148,6 +157,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembledInstruction, DAP_FIELD(instructionBytes, "instructionBytes"), DAP_FIELD(line, "line"), DAP_FIELD(location, "location"), + DAP_FIELD(presentationHint, "presentationHint"), DAP_FIELD(symbol, "symbol")); DAP_IMPLEMENT_STRUCT_TYPEINFO(Message, @@ -223,7 +233,8 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(SourceBreakpoint, DAP_FIELD(condition, "condition"), DAP_FIELD(hitCondition, "hitCondition"), DAP_FIELD(line, "line"), - DAP_FIELD(logMessage, "logMessage")); + DAP_FIELD(logMessage, "logMessage"), + DAP_FIELD(mode, "mode")); DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpoint, "", @@ -245,7 +256,8 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionOptions, DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionFilterOptions, "", DAP_FIELD(condition, "condition"), - DAP_FIELD(filterId, "filterId")); + DAP_FIELD(filterId, "filterId"), + DAP_FIELD(mode, "mode")); DAP_IMPLEMENT_STRUCT_TYPEINFO(FunctionBreakpoint, "", @@ -259,6 +271,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(InstructionBreakpoint, DAP_FIELD(hitCondition, "hitCondition"), DAP_FIELD(instructionReference, "instructionReference"), + DAP_FIELD(mode, "mode"), DAP_FIELD(offset, "offset")); DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrame, diff --git a/Utilities/cmcppdap/src/session.cpp b/Utilities/cmcppdap/src/session.cpp index 5bf22c9cc8a..ffc77751f83 100644 --- a/Utilities/cmcppdap/src/session.cpp +++ b/Utilities/cmcppdap/src/session.cpp @@ -138,7 +138,7 @@ class Impl : public dap::Session { return send(s.dump()); } - ~Impl() { + ~Impl() override { inbox.close(); reader.close(); writer.close(); diff --git a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake index 142e919449e..217c8832c9a 100644 --- a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake +++ b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake @@ -21,58 +21,48 @@ # SPDX-License-Identifier: curl # ########################################################################### -include(CheckCSourceCompiles) - -option(CURL_HIDDEN_SYMBOLS "Set to ON to hide libcurl internal symbols (=hide all symbols that aren't officially external)." ON) +option(CURL_HIDDEN_SYMBOLS "Hide libcurl internal symbols (=hide all symbols that are not officially external)" ON) mark_as_advanced(CURL_HIDDEN_SYMBOLS) -if(CURL_HIDDEN_SYMBOLS) - set(SUPPORTS_SYMBOL_HIDING FALSE) +if(WIN32 AND (ENABLE_DEBUG OR ENABLE_CURLDEBUG)) + # We need to export internal debug functions, + # e.g. curl_easy_perform_ev() or curl_dbg_*(), + # so disable symbol hiding for debug builds and for memory tracking. + set(CURL_HIDDEN_SYMBOLS OFF) +elseif(DOS OR AMIGA OR MINGW32CE) + set(CURL_HIDDEN_SYMBOLS OFF) +endif() +set(CURL_HIDES_PRIVATE_SYMBOLS FALSE) +set(CURL_EXTERN_SYMBOL "") +set(CURL_CFLAG_SYMBOLS_HIDE "") + +if(CURL_HIDDEN_SYMBOLS) if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT MSVC) - set(SUPPORTS_SYMBOL_HIDING TRUE) - set(_SYMBOL_EXTERN "__attribute__ ((__visibility__ (\"default\")))") - set(_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") - elseif(CMAKE_COMPILER_IS_GNUCC) - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.4) - # note: this is considered buggy prior to 4.0 but the autotools don't care, so let's ignore that fact - set(SUPPORTS_SYMBOL_HIDING TRUE) - set(_SYMBOL_EXTERN "__attribute__ ((__visibility__ (\"default\")))") - set(_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") - endif() - elseif(CMAKE_C_COMPILER_ID MATCHES "SunPro" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.0) - set(SUPPORTS_SYMBOL_HIDING TRUE) - set(_SYMBOL_EXTERN "__global") - set(_CFLAG_SYMBOLS_HIDE "-xldscope=hidden") - elseif(CMAKE_C_COMPILER_ID MATCHES "Intel" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 9.0) - # note: this should probably just check for version 9.1.045 but I'm not 100% sure - # so let's do it the same way autotools do. - set(SUPPORTS_SYMBOL_HIDING TRUE) - set(_SYMBOL_EXTERN "__attribute__ ((__visibility__ (\"default\")))") - set(_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") - check_c_source_compiles("#include - int main (void) { printf(\"icc fvisibility bug test\"); return 0; }" _no_bug) - if(NOT _no_bug) - set(SUPPORTS_SYMBOL_HIDING FALSE) - set(_SYMBOL_EXTERN "") - set(_CFLAG_SYMBOLS_HIDE "") + set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) + set(CURL_EXTERN_SYMBOL "__attribute__((__visibility__(\"default\")))") + set(CURL_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.4) + # Note: This is considered buggy prior to 4.0 but the autotools do not care, so let us ignore that fact + set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) + set(CURL_EXTERN_SYMBOL "__attribute__((__visibility__(\"default\")))") + set(CURL_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") endif() + elseif(CMAKE_C_COMPILER_ID MATCHES "SunPro" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) + set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) + set(CURL_EXTERN_SYMBOL "__global") + set(CURL_CFLAG_SYMBOLS_HIDE "-xldscope=hidden") + elseif(CMAKE_C_COMPILER_ID MATCHES "Intel" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 9.0) # Requires 9.1.045 + set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) + set(CURL_EXTERN_SYMBOL "__attribute__((__visibility__(\"default\")))") + set(CURL_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") elseif(MSVC) - set(SUPPORTS_SYMBOL_HIDING TRUE) - endif() - - set(HIDES_CURL_PRIVATE_SYMBOLS ${SUPPORTS_SYMBOL_HIDING}) -elseif(MSVC) - if(NOT CMAKE_VERSION VERSION_LESS 3.7) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) #present since 3.4.3 but broken - set(HIDES_CURL_PRIVATE_SYMBOLS FALSE) - else() - message(WARNING "Hiding private symbols regardless CURL_HIDDEN_SYMBOLS being disabled.") - set(HIDES_CURL_PRIVATE_SYMBOLS TRUE) + set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) endif() else() - set(HIDES_CURL_PRIVATE_SYMBOLS FALSE) + if(MSVC) + # Note: This option is prone to export non-curl extra symbols. + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + endif() endif() - -set(CURL_CFLAG_SYMBOLS_HIDE ${_CFLAG_SYMBOLS_HIDE}) -set(CURL_EXTERN_SYMBOL ${_SYMBOL_EXTERN}) diff --git a/Utilities/cmcurl/CMake/CurlTests.c b/Utilities/cmcurl/CMake/CurlTests.c index 3dbba3cbb23..de2313080dd 100644 --- a/Utilities/cmcurl/CMake/CurlTests.c +++ b/Utilities/cmcurl/CMake/CurlTests.c @@ -21,26 +21,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#ifdef TIME_WITH_SYS_TIME -/* Time with sys/time test */ - -#include -#include -#include - -int -main () -{ -if ((struct tm *) 0) -return 0; - ; - return 0; -} - -#endif #ifdef HAVE_FCNTL_O_NONBLOCK - /* headers for FCNTL_O_NONBLOCK test */ #include #include @@ -48,118 +30,77 @@ return 0; /* */ #if defined(sun) || defined(__sun__) || \ defined(__SUNPRO_C) || defined(__SUNPRO_CC) -# if defined(__SVR4) || defined(__srv4__) -# define PLATFORM_SOLARIS -# else -# define PLATFORM_SUNOS4 -# endif +# if defined(__SVR4) || defined(__srv4__) +# define PLATFORM_SOLARIS +# else +# define PLATFORM_SUNOS4 +# endif #endif #if (defined(_AIX) || defined(__xlC__)) && !defined(_AIX41) -# define PLATFORM_AIX_V3 +# define PLATFORM_AIX_V3 #endif /* */ #if defined(PLATFORM_SUNOS4) || defined(PLATFORM_AIX_V3) #error "O_NONBLOCK does not work on this platform" #endif -int -main () +int main(void) { - /* O_NONBLOCK source test */ - int flags = 0; - if(0 != fcntl(0, F_SETFL, flags | O_NONBLOCK)) - return 1; - return 0; + /* O_NONBLOCK source test */ + int flags = 0; + if(0 != fcntl(0, F_SETFL, flags | O_NONBLOCK)) + return 1; + return 0; } #endif /* tests for gethostbyname_r */ -#if defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) || \ - defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) || \ - defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) -# define _REENTRANT - /* no idea whether _REENTRANT is always set, just invent a new flag */ -# define TEST_GETHOSTBYFOO_REENTRANT -#endif #if defined(HAVE_GETHOSTBYNAME_R_3) || \ + defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) || \ defined(HAVE_GETHOSTBYNAME_R_5) || \ + defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) || \ defined(HAVE_GETHOSTBYNAME_R_6) || \ - defined(TEST_GETHOSTBYFOO_REENTRANT) + defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) #include #include int main(void) { - char *address = "example.com"; - int length = 0; - int type = 0; + const char *address = "example.com"; struct hostent h; int rc = 0; -#if defined(HAVE_GETHOSTBYNAME_R_3) || \ - defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) +#if defined(HAVE_GETHOSTBYNAME_R_3) || \ + defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) struct hostent_data hdata; #elif defined(HAVE_GETHOSTBYNAME_R_5) || \ defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) || \ defined(HAVE_GETHOSTBYNAME_R_6) || \ defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) char buffer[8192]; - int h_errnop; struct hostent *hp; + int h_errnop; #endif #if defined(HAVE_GETHOSTBYNAME_R_3) || \ defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) rc = gethostbyname_r(address, &h, &hdata); + (void)hdata; #elif defined(HAVE_GETHOSTBYNAME_R_5) || \ defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) rc = gethostbyname_r(address, &h, buffer, 8192, &h_errnop); (void)hp; /* not used for test */ + (void)h_errnop; #elif defined(HAVE_GETHOSTBYNAME_R_6) || \ defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) rc = gethostbyname_r(address, &h, buffer, 8192, &hp, &h_errnop); + (void)hp; + (void)h_errnop; #endif - - (void)length; - (void)type; + (void)h; (void)rc; return 0; } #endif -#ifdef HAVE_SOCKLEN_T -#ifdef _WIN32 -#include -#else -#include -#include -#endif -int -main () -{ -if ((socklen_t *) 0) - return 0; -if (sizeof (socklen_t)) - return 0; - ; - return 0; -} -#endif -#ifdef HAVE_IN_ADDR_T -#include -#include -#include - -int -main () -{ -if ((in_addr_t *) 0) - return 0; -if (sizeof (in_addr_t)) - return 0; - ; - return 0; -} -#endif - #ifdef HAVE_BOOL_T #ifdef HAVE_SYS_TYPES_H #include @@ -167,13 +108,9 @@ if (sizeof (in_addr_t)) #ifdef HAVE_STDBOOL_H #include #endif -int -main () +int main(void) { -if (sizeof (bool *) ) - return 0; - ; - return 0; + return (int)sizeof(bool *); } #endif @@ -182,123 +119,84 @@ if (sizeof (bool *) ) #include #include #include -int main() { return 0; } +int main(void) { return 0; } #endif + #ifdef HAVE_FILE_OFFSET_BITS -#ifdef _FILE_OFFSET_BITS -#undef _FILE_OFFSET_BITS -#endif -#define _FILE_OFFSET_BITS 64 #include - /* Check that off_t can represent 2**63 - 1 correctly. - We can't simply define LARGE_OFF_T to be 9223372036854775807, - since some C++ compilers masquerading as C compilers - incorrectly reject 9223372036854775807. */ +/* Check that off_t can represent 2**63 - 1 correctly. + We cannot simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) - int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 - && LARGE_OFF_T % 2147483647 == 1) - ? 1 : -1]; -int main () { ; return 0; } +static int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && + LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int main(void) +{ + (void)off_t_is_large; + return 0; +} #endif + #ifdef HAVE_IOCTLSOCKET -/* includes start */ -#ifdef HAVE_WINDOWS_H -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# ifdef HAVE_WINSOCK2_H -# include -# endif +#ifdef _WIN32 +# include #endif - -int -main () +int main(void) { - -/* ioctlsocket source code */ - int socket; - unsigned long flags = ioctlsocket(socket, FIONBIO, &flags); - - ; + /* ioctlsocket source code */ + int socket = -1; + unsigned long flags = ioctlsocket(socket, FIONBIO, &flags); + (void)flags; return 0; } -#endif -#ifdef HAVE_IOCTLSOCKET_CAMEL -/* includes start */ -#ifdef HAVE_WINDOWS_H -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# ifdef HAVE_WINSOCK2_H -# include -# endif #endif -int -main () +#ifdef HAVE_IOCTLSOCKET_CAMEL +#include +int main(void) { - -/* IoctlSocket source code */ - if(0 != IoctlSocket(0, 0, 0)) - return 1; - ; + /* IoctlSocket source code */ + if(0 != IoctlSocket(0, 0, 0)) + return 1; return 0; } #endif + #ifdef HAVE_IOCTLSOCKET_CAMEL_FIONBIO -/* includes start */ -#ifdef HAVE_WINDOWS_H -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# ifdef HAVE_WINSOCK2_H -# include -# endif +#include +#ifdef HAVE_SYS_IOCTL_H +# include #endif - -int -main () +int main(void) { - -/* IoctlSocket source code */ - long flags = 0; - if(0 != IoctlSocket(0, FIONBIO, &flags)) - return 1; - ; + /* IoctlSocket source code */ + long flags = 0; + if(0 != IoctlSocket(0, FIONBIO, &flags)) + return 1; + (void)flags; return 0; } #endif + #ifdef HAVE_IOCTLSOCKET_FIONBIO -/* includes start */ -#ifdef HAVE_WINDOWS_H -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# ifdef HAVE_WINSOCK2_H -# include -# endif +#ifdef _WIN32 +# include #endif - -int -main () +int main(void) { - - int flags = 0; - if(0 != ioctlsocket(0, FIONBIO, &flags)) - return 1; - - ; + unsigned long flags = 0; + if(0 != ioctlsocket(0, FIONBIO, &flags)) + return 1; + (void)flags; return 0; } #endif + #ifdef HAVE_IOCTL_FIONBIO /* headers for FIONBIO test */ -/* includes start */ #ifdef HAVE_SYS_TYPES_H # include #endif @@ -314,22 +212,18 @@ main () #ifdef HAVE_STROPTS_H # include #endif - -int -main () +int main(void) { - - int flags = 0; - if(0 != ioctl(0, FIONBIO, &flags)) - return 1; - - ; + int flags = 0; + if(0 != ioctl(0, FIONBIO, &flags)) + return 1; + (void)flags; return 0; } #endif + #ifdef HAVE_IOCTL_SIOCGIFADDR /* headers for FIONBIO test */ -/* includes start */ #ifdef HAVE_SYS_TYPES_H # include #endif @@ -346,156 +240,105 @@ main () # include #endif #include - -int -main () +int main(void) { - struct ifreq ifr; - if(0 != ioctl(0, SIOCGIFADDR, &ifr)) - return 1; - - ; + struct ifreq ifr; + if(0 != ioctl(0, SIOCGIFADDR, &ifr)) + return 1; + (void)ifr; return 0; } #endif + #ifdef HAVE_SETSOCKOPT_SO_NONBLOCK -/* includes start */ -#ifdef HAVE_WINDOWS_H -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# ifdef HAVE_WINSOCK2_H -# include -# endif +#ifdef _WIN32 +# include #endif -/* includes start */ #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_SOCKET_H # include #endif -/* includes end */ - -int -main () +int main(void) { - if(0 != setsockopt(0, SOL_SOCKET, SO_NONBLOCK, 0, 0)) - return 1; - ; + if(0 != setsockopt(0, SOL_SOCKET, SO_NONBLOCK, 0, 0)) + return 1; return 0; } #endif + #ifdef HAVE_GLIBC_STRERROR_R #include #include -void check(char c) {} +static void check(char c) { (void)c; } -int -main () { +int main(void) +{ char buffer[1024]; /* This will not compile if strerror_r does not return a char* */ + /* !checksrc! disable ERRNOVAR 1 */ check(strerror_r(EACCES, buffer, sizeof(buffer))[0]); return 0; } #endif + #ifdef HAVE_POSIX_STRERROR_R #include #include -/* float, because a pointer can't be implicitly cast to float */ -void check(float f) {} +/* Float, because a pointer cannot be implicitly cast to float */ +static void check(float f) { (void)f; } -int -main () { +int main(void) +{ char buffer[1024]; /* This will not compile if strerror_r does not return an int */ + /* !checksrc! disable ERRNOVAR 1 */ check(strerror_r(EACCES, buffer, sizeof(buffer))); return 0; } #endif + #ifdef HAVE_FSETXATTR_6 #include /* header from libc, not from libattr */ -int -main() { +int main(void) +{ fsetxattr(0, 0, 0, 0, 0, 0); return 0; } #endif + #ifdef HAVE_FSETXATTR_5 #include /* header from libc, not from libattr */ -int -main() { - fsetxattr(0, 0, 0, 0, 0); +int main(void) +{ + fsetxattr(0, "", 0, 0, 0); return 0; } #endif + #ifdef HAVE_CLOCK_GETTIME_MONOTONIC #include -int -main() { - struct timespec ts = {0, 0}; - clock_gettime(CLOCK_MONOTONIC, &ts); - return 0; -} -#endif -#ifdef HAVE_BUILTIN_AVAILABLE -int -main() { - if(__builtin_available(macOS 10.12, *)) {} +int main(void) +{ + struct timespec ts; + (void)clock_gettime(CLOCK_MONOTONIC, &ts); + (void)ts; return 0; } #endif -#ifdef HAVE_VARIADIC_MACROS_C99 -#define c99_vmacro3(first, ...) fun3(first, __VA_ARGS__) -#define c99_vmacro2(first, ...) fun2(first, __VA_ARGS__) - -int fun3(int arg1, int arg2, int arg3); -int fun2(int arg1, int arg2); -int fun3(int arg1, int arg2, int arg3) { - return arg1 + arg2 + arg3; -} -int fun2(int arg1, int arg2) { - return arg1 + arg2; -} - -int -main() { - int res3 = c99_vmacro3(1, 2, 3); - int res2 = c99_vmacro2(1, 2); - (void)res3; - (void)res2; +#ifdef HAVE_BUILTIN_AVAILABLE +int main(void) +{ + if(__builtin_available(macOS 10.12, iOS 5.0, *)) {} return 0; } #endif -#ifdef HAVE_VARIADIC_MACROS_GCC -#define gcc_vmacro3(first, args...) fun3(first, args) -#define gcc_vmacro2(first, args...) fun2(first, args) - -int fun3(int arg1, int arg2, int arg3); -int fun2(int arg1, int arg2); - -int fun3(int arg1, int arg2, int arg3) { - return arg1 + arg2 + arg3; -} -int fun2(int arg1, int arg2) { - return arg1 + arg2; -} -int -main() { - int res3 = gcc_vmacro3(1, 2, 3); - int res2 = gcc_vmacro2(1, 2); - (void)res3; - (void)res2; - return 0; -} -#endif #ifdef HAVE_ATOMIC -/* includes start */ #ifdef HAVE_SYS_TYPES_H # include #endif @@ -505,28 +348,45 @@ main() { #ifdef HAVE_STDATOMIC_H # include #endif -/* includes end */ - -int -main() { +int main(void) +{ _Atomic int i = 1; - i = 0; // Force an atomic-write operation. + i = 0; /* Force an atomic-write operation. */ return i; } #endif + #ifdef HAVE_WIN32_WINNT -/* includes start */ -#ifdef WIN32 -# include "../lib/setup-win32.h" +#ifdef _WIN32 +# ifndef NOGDI +# define NOGDI +# endif +# include #endif -/* includes end */ #define enquote(x) #x #define expand(x) enquote(x) #pragma message("_WIN32_WINNT=" expand(_WIN32_WINNT)) -int -main() { +int main(void) +{ + return 0; +} +#endif + +#ifdef MINGW64_VERSION +#ifdef __MINGW32__ +# include <_mingw.h> +#endif + +#define enquote(x) #x +#define expand(x) enquote(x) +#pragma message("MINGW64_VERSION=" \ + expand(__MINGW64_VERSION_MAJOR) "." \ + expand(__MINGW64_VERSION_MINOR)) + +int main(void) +{ return 0; } #endif diff --git a/Utilities/cmcurl/CMake/FindBearSSL.cmake b/Utilities/cmcurl/CMake/FindBearSSL.cmake index 56a064eacd7..ff55be0f22e 100644 --- a/Utilities/cmcurl/CMake/FindBearSSL.cmake +++ b/Utilities/cmcurl/CMake/FindBearSSL.cmake @@ -21,12 +21,38 @@ # SPDX-License-Identifier: curl # ########################################################################### -find_path(BEARSSL_INCLUDE_DIRS bearssl.h) +# Find the BearSSL library +# +# Input variables: +# +# - `BEARSSL_INCLUDE_DIR`: The BearSSL include directory. +# - `BEARSSL_LIBRARY`: Path to `bearssl` library. +# +# Result variables: +# +# - `BEARSSL_FOUND`: System has BearSSL. +# - `BEARSSL_INCLUDE_DIRS`: The BearSSL include directories. +# - `BEARSSL_LIBRARIES`: The BearSSL library names. + +if(DEFINED BEARSSL_INCLUDE_DIRS AND NOT DEFINED BEARSSL_INCLUDE_DIR) + message(WARNING "BEARSSL_INCLUDE_DIRS is deprecated, use BEARSSL_INCLUDE_DIR instead.") + set(BEARSSL_INCLUDE_DIR "${BEARSSL_INCLUDE_DIRS}") + unset(BEARSSL_INCLUDE_DIRS) +endif() -find_library(BEARSSL_LIBRARY bearssl) +find_path(BEARSSL_INCLUDE_DIR NAMES "bearssl.h") +find_library(BEARSSL_LIBRARY NAMES "bearssl") include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(BEARSSL DEFAULT_MSG - BEARSSL_INCLUDE_DIRS BEARSSL_LIBRARY) +find_package_handle_standard_args(BearSSL + REQUIRED_VARS + BEARSSL_INCLUDE_DIR + BEARSSL_LIBRARY +) + +if(BEARSSL_FOUND) + set(BEARSSL_INCLUDE_DIRS ${BEARSSL_INCLUDE_DIR}) + set(BEARSSL_LIBRARIES ${BEARSSL_LIBRARY}) +endif() -mark_as_advanced(BEARSSL_INCLUDE_DIRS BEARSSL_LIBRARY) +mark_as_advanced(BEARSSL_INCLUDE_DIR BEARSSL_LIBRARY) diff --git a/Utilities/cmcurl/CMake/FindBrotli.cmake b/Utilities/cmcurl/CMake/FindBrotli.cmake index 11ab7f82545..690b5a9c27c 100644 --- a/Utilities/cmcurl/CMake/FindBrotli.cmake +++ b/Utilities/cmcurl/CMake/FindBrotli.cmake @@ -21,23 +21,56 @@ # SPDX-License-Identifier: curl # ########################################################################### -include(FindPackageHandleStandardArgs) +# Find the brotli library +# +# Input variables: +# +# - `BROTLI_INCLUDE_DIR`: The brotli include directory. +# - `BROTLICOMMON_LIBRARY`: Path to `brotlicommon` library. +# - `BROTLIDEC_LIBRARY`: Path to `brotlidec` library. +# +# Result variables: +# +# - `BROTLI_FOUND`: System has brotli. +# - `BROTLI_INCLUDE_DIRS`: The brotli include directories. +# - `BROTLI_LIBRARIES`: The brotli library names. +# - `BROTLI_LIBRARY_DIRS`: The brotli library directories. +# - `BROTLI_PC_REQUIRES`: The brotli pkg-config packages. +# - `BROTLI_CFLAGS`: Required compiler flags. +# - `BROTLI_VERSION`: Version of brotli. -find_path(BROTLI_INCLUDE_DIR "brotli/decode.h") +set(BROTLI_PC_REQUIRES "libbrotlidec" "libbrotlicommon") # order is significant: brotlidec then brotlicommon -find_library(BROTLICOMMON_LIBRARY NAMES brotlicommon) -find_library(BROTLIDEC_LIBRARY NAMES brotlidec) +if(CURL_USE_PKGCONFIG AND + NOT DEFINED BROTLI_INCLUDE_DIR AND + NOT DEFINED BROTLICOMMON_LIBRARY AND + NOT DEFINED BROTLIDEC_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(BROTLI ${BROTLI_PC_REQUIRES}) +endif() -find_package_handle_standard_args(Brotli - FOUND_VAR - BROTLI_FOUND +if(BROTLI_FOUND) + set(Brotli_FOUND TRUE) + set(BROTLI_VERSION "${BROTLI_libbrotlicommon_VERSION}") + string(REPLACE ";" " " BROTLI_CFLAGS "${BROTLI_CFLAGS}") + message(STATUS "Found Brotli (via pkg-config): ${BROTLI_INCLUDE_DIRS} (found version \"${BROTLI_VERSION}\")") +else() + find_path(BROTLI_INCLUDE_DIR "brotli/decode.h") + find_library(BROTLICOMMON_LIBRARY NAMES "brotlicommon") + find_library(BROTLIDEC_LIBRARY NAMES "brotlidec") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Brotli REQUIRED_VARS + BROTLI_INCLUDE_DIR BROTLIDEC_LIBRARY BROTLICOMMON_LIBRARY - BROTLI_INCLUDE_DIR - FAIL_MESSAGE - "Could NOT find Brotli" -) + ) + + if(BROTLI_FOUND) + set(BROTLI_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR}) + set(BROTLI_LIBRARIES ${BROTLIDEC_LIBRARY} ${BROTLICOMMON_LIBRARY}) + endif() -set(BROTLI_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR}) -set(BROTLI_LIBRARIES ${BROTLICOMMON_LIBRARY} ${BROTLIDEC_LIBRARY}) + mark_as_advanced(BROTLI_INCLUDE_DIR BROTLIDEC_LIBRARY BROTLICOMMON_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindCARES.cmake b/Utilities/cmcurl/CMake/FindCARES.cmake deleted file mode 100644 index fa758911898..00000000000 --- a/Utilities/cmcurl/CMake/FindCARES.cmake +++ /dev/null @@ -1,47 +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 -# -########################################################################### -# - Find c-ares -# Find the c-ares includes and library -# This module defines -# CARES_INCLUDE_DIR, where to find ares.h, etc. -# CARES_LIBRARIES, the libraries needed to use c-ares. -# CARES_FOUND, If false, do not try to use c-ares. -# also defined, but not for general use are -# CARES_LIBRARY, where to find the c-ares library. - -find_path(CARES_INCLUDE_DIR ares.h) - -set(CARES_NAMES ${CARES_NAMES} cares) -find_library(CARES_LIBRARY - NAMES ${CARES_NAMES} - ) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CARES - REQUIRED_VARS CARES_LIBRARY CARES_INCLUDE_DIR) - -mark_as_advanced( - CARES_LIBRARY - CARES_INCLUDE_DIR - ) diff --git a/Utilities/cmcurl/CMake/FindCares.cmake b/Utilities/cmcurl/CMake/FindCares.cmake new file mode 100644 index 00000000000..cc47e2d3310 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindCares.cmake @@ -0,0 +1,97 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the c-ares library +# +# Input variables: +# +# - `CARES_INCLUDE_DIR`: The c-ares include directory. +# - `CARES_LIBRARY`: Path to `cares` library. +# +# Result variables: +# +# - `CARES_FOUND`: System has c-ares. +# - `CARES_INCLUDE_DIRS`: The c-ares include directories. +# - `CARES_LIBRARIES`: The c-ares library names. +# - `CARES_LIBRARY_DIRS`: The c-ares library directories. +# - `CARES_PC_REQUIRES`: The c-ares pkg-config packages. +# - `CARES_CFLAGS`: Required compiler flags. +# - `CARES_VERSION`: Version of c-ares. + +set(CARES_PC_REQUIRES "libcares") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED CARES_INCLUDE_DIR AND + NOT DEFINED CARES_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(CARES ${CARES_PC_REQUIRES}) +endif() + +if(CARES_FOUND) + set(Cares_FOUND TRUE) + string(REPLACE ";" " " CARES_CFLAGS "${CARES_CFLAGS}") + message(STATUS "Found Cares (via pkg-config): ${CARES_INCLUDE_DIRS} (found version \"${CARES_VERSION}\")") +else() + find_path(CARES_INCLUDE_DIR NAMES "ares.h") + find_library(CARES_LIBRARY NAMES ${CARES_NAMES} "cares") + + unset(CARES_VERSION CACHE) + if(CARES_INCLUDE_DIR AND EXISTS "${CARES_INCLUDE_DIR}/ares_version.h") + set(_version_regex1 "#[\t ]*define[\t ]+ARES_VERSION_MAJOR[\t ]+([0-9]+).*") + set(_version_regex2 "#[\t ]*define[\t ]+ARES_VERSION_MINOR[\t ]+([0-9]+).*") + set(_version_regex3 "#[\t ]*define[\t ]+ARES_VERSION_PATCH[\t ]+([0-9]+).*") + file(STRINGS "${CARES_INCLUDE_DIR}/ares_version.h" _version_str1 REGEX "${_version_regex1}") + file(STRINGS "${CARES_INCLUDE_DIR}/ares_version.h" _version_str2 REGEX "${_version_regex2}") + file(STRINGS "${CARES_INCLUDE_DIR}/ares_version.h" _version_str3 REGEX "${_version_regex3}") + string(REGEX REPLACE "${_version_regex1}" "\\1" _version_str1 "${_version_str1}") + string(REGEX REPLACE "${_version_regex2}" "\\1" _version_str2 "${_version_str2}") + string(REGEX REPLACE "${_version_regex3}" "\\1" _version_str3 "${_version_str3}") + set(CARES_VERSION "${_version_str1}.${_version_str2}.${_version_str3}") + unset(_version_regex1) + unset(_version_regex2) + unset(_version_regex3) + unset(_version_str1) + unset(_version_str2) + unset(_version_str3) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Cares + REQUIRED_VARS + CARES_INCLUDE_DIR + CARES_LIBRARY + VERSION_VAR + CARES_VERSION + ) + + if(CARES_FOUND) + set(CARES_INCLUDE_DIRS ${CARES_INCLUDE_DIR}) + set(CARES_LIBRARIES ${CARES_LIBRARY}) + endif() + + mark_as_advanced(CARES_INCLUDE_DIR CARES_LIBRARY) +endif() + +if(CARES_FOUND AND WIN32) + list(APPEND CARES_LIBRARIES "iphlpapi") # for if_indextoname and others +endif() diff --git a/Utilities/cmcurl/CMake/FindGSS.cmake b/Utilities/cmcurl/CMake/FindGSS.cmake index b244e610ec6..c1802ee740f 100644 --- a/Utilities/cmcurl/CMake/FindGSS.cmake +++ b/Utilities/cmcurl/CMake/FindGSS.cmake @@ -21,292 +21,337 @@ # SPDX-License-Identifier: curl # ########################################################################### -# - Try to find the GSS Kerberos library -# Once done this will define +# Find the GSS Kerberos library # -# GSS_ROOT_DIR - Set this variable to the root installation of GSS +# Input variables: # -# Read-Only variables: -# GSS_FOUND - system has the Heimdal library -# GSS_FLAVOUR - "MIT" or "Heimdal" if anything found. -# GSS_INCLUDE_DIR - the Heimdal include directory -# GSS_LIBRARIES - The libraries needed to use GSS -# GSS_LINK_DIRECTORIES - Directories to add to linker search path -# GSS_LINKER_FLAGS - Additional linker flags -# GSS_COMPILER_FLAGS - Additional compiler flags -# GSS_VERSION - This is set to version advertised by pkg-config or read from manifest. -# In case the library is found but no version info available it'll be set to "unknown" - -set(_MIT_MODNAME mit-krb5-gssapi) -set(_HEIMDAL_MODNAME heimdal-gssapi) +# - `GSS_ROOT_DIR`: Set this variable to the root installation of GSS. (also supported as environment) +# +# Result variables: +# +# - `GSS_FOUND`: System has the Heimdal library. +# - `GSS_FLAVOUR`: "GNU", "MIT" or "Heimdal" if anything found. +# - `GSS_INCLUDE_DIRS`: The GSS include directories. +# - `GSS_LIBRARIES`: The GSS library names. +# - `GSS_LIBRARY_DIRS`: The GSS library directories. +# - `GSS_PC_REQUIRES`: The GSS pkg-config packages. +# - `GSS_CFLAGS`: Required compiler flags. +# - `GSS_VERSION`: This is set to version advertised by pkg-config or read from manifest. +# In case the library is found but no version info available it is set to "unknown" + +set(_gnu_modname "gss") +set(_mit_modname "mit-krb5-gssapi") +set(_heimdal_modname "heimdal-gssapi") include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckTypeSize) -set(_GSS_ROOT_HINTS - "${GSS_ROOT_DIR}" - "$ENV{GSS_ROOT_DIR}" +set(_gss_root_hints + "${GSS_ROOT_DIR}" + "$ENV{GSS_ROOT_DIR}" ) -# try to find library using system pkg-config if user didn't specify root dir +# Try to find library using system pkg-config if user did not specify root dir if(NOT GSS_ROOT_DIR AND NOT "$ENV{GSS_ROOT_DIR}") - if(UNIX) + if(CURL_USE_PKGCONFIG) find_package(PkgConfig QUIET) - pkg_search_module(_GSS_PKG ${_MIT_MODNAME} ${_HEIMDAL_MODNAME}) - list(APPEND _GSS_ROOT_HINTS "${_GSS_PKG_PREFIX}") - elseif(WIN32) - list(APPEND _GSS_ROOT_HINTS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MIT\\Kerberos;InstallDir]") + pkg_search_module(_GSS ${_gnu_modname} ${_mit_modname} ${_heimdal_modname}) + list(APPEND _gss_root_hints "${_GSS_PREFIX}") + endif() + if(WIN32) + list(APPEND _gss_root_hints "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MIT\\Kerberos;InstallDir]") endif() endif() -if(NOT _GSS_FOUND) #not found by pkg-config. Let's take more traditional approach. - find_file(_GSS_CONFIGURE_SCRIPT - NAMES - "krb5-config" - HINTS - ${_GSS_ROOT_HINTS} - PATH_SUFFIXES - bin - NO_CMAKE_PATH - NO_CMAKE_ENVIRONMENT_PATH +if(NOT _GSS_FOUND) # Not found by pkg-config. Let us take more traditional approach. + find_file(_gss_configure_script + NAMES + "krb5-config" + HINTS + ${_gss_root_hints} + PATH_SUFFIXES + "bin" + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH ) - # if not found in user-supplied directories, maybe system knows better - find_file(_GSS_CONFIGURE_SCRIPT - NAMES - "krb5-config" - PATH_SUFFIXES - bin + # If not found in user-supplied directories, maybe system knows better + find_file(_gss_configure_script + NAMES + "krb5-config" + PATH_SUFFIXES + "bin" ) - if(_GSS_CONFIGURE_SCRIPT) + if(_gss_configure_script) execute_process( - COMMAND ${_GSS_CONFIGURE_SCRIPT} "--cflags" "gssapi" - OUTPUT_VARIABLE _GSS_CFLAGS - RESULT_VARIABLE _GSS_CONFIGURE_FAILED - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - message(STATUS "CFLAGS: ${_GSS_CFLAGS}") - if(NOT _GSS_CONFIGURE_FAILED) # 0 means success - # should also work in an odd case when multiple directories are given + COMMAND ${_gss_configure_script} "--cflags" "gssapi" + OUTPUT_VARIABLE _GSS_CFLAGS + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + message(STATUS "FindGSS krb5-config --cflags: ${_GSS_CFLAGS}") + if(NOT _gss_configure_failed) # 0 means success + # Should also work in an odd case when multiple directories are given string(STRIP "${_GSS_CFLAGS}" _GSS_CFLAGS) string(REGEX REPLACE " +-I" ";" _GSS_CFLAGS "${_GSS_CFLAGS}") string(REGEX REPLACE " +-([^I][^ \\t;]*)" ";-\\1" _GSS_CFLAGS "${_GSS_CFLAGS}") - foreach(_flag ${_GSS_CFLAGS}) - if(_flag MATCHES "^-I.*") + foreach(_flag IN LISTS _GSS_CFLAGS) + if(_flag MATCHES "^-I") string(REGEX REPLACE "^-I" "" _val "${_flag}") - list(APPEND _GSS_INCLUDE_DIR "${_val}") + list(APPEND _GSS_INCLUDE_DIRS "${_val}") else() - list(APPEND _GSS_COMPILER_FLAGS "${_flag}") + list(APPEND _GSS_CFLAGS "${_flag}") endif() endforeach() endif() execute_process( - COMMAND ${_GSS_CONFIGURE_SCRIPT} "--libs" "gssapi" - OUTPUT_VARIABLE _GSS_LIB_FLAGS - RESULT_VARIABLE _GSS_CONFIGURE_FAILED - OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND ${_gss_configure_script} "--libs" "gssapi" + OUTPUT_VARIABLE _gss_lib_flags + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE ) - message(STATUS "LDFLAGS: ${_GSS_LIB_FLAGS}") + message(STATUS "FindGSS krb5-config --libs: ${_gss_lib_flags}") - if(NOT _GSS_CONFIGURE_FAILED) # 0 means success - # this script gives us libraries and link directories. Blah. We have to deal with it. - string(STRIP "${_GSS_LIB_FLAGS}" _GSS_LIB_FLAGS) - string(REGEX REPLACE " +-(L|l)" ";-\\1" _GSS_LIB_FLAGS "${_GSS_LIB_FLAGS}") - string(REGEX REPLACE " +-([^Ll][^ \\t;]*)" ";-\\1" _GSS_LIB_FLAGS "${_GSS_LIB_FLAGS}") + if(NOT _gss_configure_failed) # 0 means success + # This script gives us libraries and link directories. Blah. We have to deal with it. + string(STRIP "${_gss_lib_flags}" _gss_lib_flags) + string(REGEX REPLACE " +-(L|l)" ";-\\1" _gss_lib_flags "${_gss_lib_flags}") + string(REGEX REPLACE " +-([^Ll][^ \\t;]*)" ";-\\1" _gss_lib_flags "${_gss_lib_flags}") - foreach(_flag ${_GSS_LIB_FLAGS}) - if(_flag MATCHES "^-l.*") + foreach(_flag IN LISTS _gss_lib_flags) + if(_flag MATCHES "^-l") string(REGEX REPLACE "^-l" "" _val "${_flag}") list(APPEND _GSS_LIBRARIES "${_val}") - elseif(_flag MATCHES "^-L.*") + elseif(_flag MATCHES "^-L") string(REGEX REPLACE "^-L" "" _val "${_flag}") - list(APPEND _GSS_LINK_DIRECTORIES "${_val}") - else() - list(APPEND _GSS_LINKER_FLAGS "${_flag}") + list(APPEND _GSS_LIBRARY_DIRS "${_val}") endif() endforeach() endif() execute_process( - COMMAND ${_GSS_CONFIGURE_SCRIPT} "--version" - OUTPUT_VARIABLE _GSS_VERSION - RESULT_VARIABLE _GSS_CONFIGURE_FAILED - OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND ${_gss_configure_script} "--version" + OUTPUT_VARIABLE _GSS_VERSION + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE ) - # older versions may not have the "--version" parameter. In this case we just don't care. - if(_GSS_CONFIGURE_FAILED) + # Older versions may not have the "--version" parameter. In this case we just do not care. + if(_gss_configure_failed) set(_GSS_VERSION 0) endif() execute_process( - COMMAND ${_GSS_CONFIGURE_SCRIPT} "--vendor" - OUTPUT_VARIABLE _GSS_VENDOR - RESULT_VARIABLE _GSS_CONFIGURE_FAILED - OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND ${_gss_configure_script} "--vendor" + OUTPUT_VARIABLE _gss_vendor + RESULT_VARIABLE _gss_configure_failed + OUTPUT_STRIP_TRAILING_WHITESPACE ) - # older versions may not have the "--vendor" parameter. In this case we just don't care. - if(_GSS_CONFIGURE_FAILED) - set(GSS_FLAVOUR "Heimdal") # most probably, shouldn't really matter + # Older versions may not have the "--vendor" parameter. In this case we just do not care. + if(_gss_configure_failed) + set(GSS_FLAVOUR "Heimdal") # most probably, should not really matter else() - if(_GSS_VENDOR MATCHES ".*H|heimdal.*") + if(_gss_vendor MATCHES "H|heimdal") set(GSS_FLAVOUR "Heimdal") else() set(GSS_FLAVOUR "MIT") endif() endif() - else() # either there is no config script or we are on a platform that doesn't provide one (Windows?) + else() # Either there is no config script or we are on a platform that does not provide one (Windows?) - find_path(_GSS_INCLUDE_DIR - NAMES - "gssapi/gssapi.h" - HINTS - ${_GSS_ROOT_HINTS} - PATH_SUFFIXES - include - inc + find_path(_GSS_INCLUDE_DIRS NAMES "gssapi/gssapi.h" + HINTS + ${_gss_root_hints} + PATH_SUFFIXES + "include" + "inc" ) - if(_GSS_INCLUDE_DIR) #jay, we've found something - set(CMAKE_REQUIRED_INCLUDES "${_GSS_INCLUDE_DIR}") - check_include_files( "gssapi/gssapi_generic.h;gssapi/gssapi_krb5.h" _GSS_HAVE_MIT_HEADERS) + if(_GSS_INCLUDE_DIRS) # jay, we have found something + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_INCLUDES "${_GSS_INCLUDE_DIRS}") + check_include_files("gssapi/gssapi_generic.h;gssapi/gssapi_krb5.h" _gss_have_mit_headers) - if(_GSS_HAVE_MIT_HEADERS) + if(_gss_have_mit_headers) set(GSS_FLAVOUR "MIT") else() - # prevent compiling the header - just check if we can include it - list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__) - check_include_file( "roken.h" _GSS_HAVE_ROKEN_H) + # Prevent compiling the header - just check if we can include it + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D__ROKEN_H__") + check_include_file("roken.h" _gss_have_roken_h) - check_include_file( "heimdal/roken.h" _GSS_HAVE_HEIMDAL_ROKEN_H) - if(_GSS_HAVE_ROKEN_H OR _GSS_HAVE_HEIMDAL_ROKEN_H) + check_include_file("heimdal/roken.h" _gss_have_heimdal_roken_h) + if(_gss_have_roken_h OR _gss_have_heimdal_roken_h) set(GSS_FLAVOUR "Heimdal") endif() - list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__) endif() + cmake_pop_check_state() else() - # I'm not convinced if this is the right way but this is what autotools do at the moment - find_path(_GSS_INCLUDE_DIR - NAMES - "gssapi.h" - HINTS - ${_GSS_ROOT_HINTS} - PATH_SUFFIXES - include - inc + # I am not convinced if this is the right way but this is what autotools do at the moment + find_path(_GSS_INCLUDE_DIRS NAMES "gssapi.h" + HINTS + ${_gss_root_hints} + PATH_SUFFIXES + "include" + "inc" ) - if(_GSS_INCLUDE_DIR) + if(_GSS_INCLUDE_DIRS) set(GSS_FLAVOUR "Heimdal") + else() + find_path(_GSS_INCLUDE_DIRS NAMES "gss.h" + HINTS + ${_gss_root_hints} + PATH_SUFFIXES + "include" + ) + + if(_GSS_INCLUDE_DIRS) + set(GSS_FLAVOUR "GNU") + set(GSS_PC_REQUIRES "gss") + endif() endif() endif() - # if we have headers, check if we can link libraries + # If we have headers, check if we can link libraries if(GSS_FLAVOUR) - set(_GSS_LIBDIR_SUFFIXES "") - set(_GSS_LIBDIR_HINTS ${_GSS_ROOT_HINTS}) - get_filename_component(_GSS_CALCULATED_POTENTIAL_ROOT "${_GSS_INCLUDE_DIR}" PATH) - list(APPEND _GSS_LIBDIR_HINTS ${_GSS_CALCULATED_POTENTIAL_ROOT}) + set(_gss_libdir_suffixes "") + set(_gss_libdir_hints ${_gss_root_hints}) + get_filename_component(_gss_calculated_potential_root "${_GSS_INCLUDE_DIRS}" DIRECTORY) + list(APPEND _gss_libdir_hints ${_gss_calculated_potential_root}) if(WIN32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) - list(APPEND _GSS_LIBDIR_SUFFIXES "lib/AMD64") - if(GSS_FLAVOUR STREQUAL "MIT") - set(_GSS_LIBNAME "gssapi64") + list(APPEND _gss_libdir_suffixes "lib/AMD64") + if(GSS_FLAVOUR STREQUAL "GNU") + set(_gss_libname "gss") + elseif(GSS_FLAVOUR STREQUAL "MIT") + set(_gss_libname "gssapi64") else() - set(_GSS_LIBNAME "libgssapi") + set(_gss_libname "libgssapi") endif() else() - list(APPEND _GSS_LIBDIR_SUFFIXES "lib/i386") - if(GSS_FLAVOUR STREQUAL "MIT") - set(_GSS_LIBNAME "gssapi32") + list(APPEND _gss_libdir_suffixes "lib/i386") + if(GSS_FLAVOUR STREQUAL "GNU") + set(_gss_libname "gss") + elseif(GSS_FLAVOUR STREQUAL "MIT") + set(_gss_libname "gssapi32") else() - set(_GSS_LIBNAME "libgssapi") + set(_gss_libname "libgssapi") endif() endif() else() - list(APPEND _GSS_LIBDIR_SUFFIXES "lib;lib64") # those suffixes are not checked for HINTS - if(GSS_FLAVOUR STREQUAL "MIT") - set(_GSS_LIBNAME "gssapi_krb5") + list(APPEND _gss_libdir_suffixes "lib;lib64") # those suffixes are not checked for HINTS + if(GSS_FLAVOUR STREQUAL "GNU") + set(_gss_libname "gss") + elseif(GSS_FLAVOUR STREQUAL "MIT") + set(_gss_libname "gssapi_krb5") else() - set(_GSS_LIBNAME "gssapi") + set(_gss_libname "gssapi") endif() endif() - find_library(_GSS_LIBRARIES - NAMES - ${_GSS_LIBNAME} - HINTS - ${_GSS_LIBDIR_HINTS} - PATH_SUFFIXES - ${_GSS_LIBDIR_SUFFIXES} + find_library(_GSS_LIBRARIES NAMES ${_gss_libname} + HINTS + ${_gss_libdir_hints} + PATH_SUFFIXES + ${_gss_libdir_suffixes} ) - endif() endif() else() - if(_GSS_PKG_${_MIT_MODNAME}_VERSION) + # _GSS_MODULE_NAME set since CMake 3.16 + if(_GSS_MODULE_NAME STREQUAL _gnu_modname OR _GSS_${_gnu_modname}_VERSION) + set(GSS_FLAVOUR "GNU") + set(GSS_PC_REQUIRES "gss") + if(NOT _GSS_VERSION) # for old CMake versions? + set(_GSS_VERSION ${_GSS_${_gnu_modname}_VERSION}) + endif() + elseif(_GSS_MODULE_NAME STREQUAL _mit_modname OR _GSS_${_mit_modname}_VERSION) set(GSS_FLAVOUR "MIT") - set(_GSS_VERSION _GSS_PKG_${_MIT_MODNAME}_VERSION) + set(GSS_PC_REQUIRES "mit-krb5-gssapi") + if(NOT _GSS_VERSION) # for old CMake versions? + set(_GSS_VERSION ${_GSS_${_mit_modname}_VERSION}) + endif() else() set(GSS_FLAVOUR "Heimdal") - set(_GSS_VERSION _GSS_PKG_${_MIT_HEIMDAL}_VERSION) + set(GSS_PC_REQUIRES "heimdal-gssapi") + if(NOT _GSS_VERSION) # for old CMake versions? + set(_GSS_VERSION ${_GSS_${_heimdal_modname}_VERSION}) + endif() endif() + message(STATUS "Found GSS/${GSS_FLAVOUR} (via pkg-config): ${_GSS_INCLUDE_DIRS} (found version \"${_GSS_VERSION}\")") endif() -set(GSS_INCLUDE_DIR ${_GSS_INCLUDE_DIR}) +string(REPLACE ";" " " _GSS_CFLAGS "${_GSS_CFLAGS}") + +set(GSS_INCLUDE_DIRS ${_GSS_INCLUDE_DIRS}) set(GSS_LIBRARIES ${_GSS_LIBRARIES}) -set(GSS_LINK_DIRECTORIES ${_GSS_LINK_DIRECTORIES}) -set(GSS_LINKER_FLAGS ${_GSS_LINKER_FLAGS}) -set(GSS_COMPILER_FLAGS ${_GSS_COMPILER_FLAGS}) +set(GSS_LIBRARY_DIRS ${_GSS_LIBRARY_DIRS}) +set(GSS_CFLAGS ${_GSS_CFLAGS}) set(GSS_VERSION ${_GSS_VERSION}) if(GSS_FLAVOUR) if(NOT GSS_VERSION AND GSS_FLAVOUR STREQUAL "Heimdal") if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(HEIMDAL_MANIFEST_FILE "Heimdal.Application.amd64.manifest") + set(_heimdal_manifest_file "Heimdal.Application.amd64.manifest") else() - set(HEIMDAL_MANIFEST_FILE "Heimdal.Application.x86.manifest") + set(_heimdal_manifest_file "Heimdal.Application.x86.manifest") endif() - if(EXISTS "${GSS_INCLUDE_DIR}/${HEIMDAL_MANIFEST_FILE}") - file(STRINGS "${GSS_INCLUDE_DIR}/${HEIMDAL_MANIFEST_FILE}" heimdal_version_str - REGEX "^.*version=\"[0-9]\\.[^\"]+\".*$") + if(EXISTS "${GSS_INCLUDE_DIRS}/${_heimdal_manifest_file}") + file(STRINGS "${GSS_INCLUDE_DIRS}/${_heimdal_manifest_file}" _heimdal_version_str + REGEX "^.*version=\"[0-9]\\.[^\"]+\".*$") - string(REGEX MATCH "[0-9]\\.[^\"]+" - GSS_VERSION "${heimdal_version_str}") + string(REGEX MATCH "[0-9]\\.[^\"]+" GSS_VERSION "${_heimdal_version_str}") endif() if(NOT GSS_VERSION) set(GSS_VERSION "Heimdal Unknown") endif() elseif(NOT GSS_VERSION AND GSS_FLAVOUR STREQUAL "MIT") - get_filename_component(_MIT_VERSION "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MIT\\Kerberos\\SDK\\CurrentVersion;VersionString]" NAME CACHE) - if(WIN32 AND _MIT_VERSION) - set(GSS_VERSION "${_MIT_VERSION}") + get_filename_component(_mit_version "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MIT\\Kerberos\\SDK\\CurrentVersion;VersionString]" NAME + CACHE) + if(WIN32 AND _mit_version) + set(GSS_VERSION "${_mit_version}") else() set(GSS_VERSION "MIT Unknown") endif() + elseif(NOT GSS_VERSION AND GSS_FLAVOUR STREQUAL "GNU") + if(GSS_INCLUDE_DIRS AND EXISTS "${GSS_INCLUDE_DIRS}/gss.h") + set(_version_regex "#[\t ]*define[\t ]+GSS_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${GSS_INCLUDE_DIRS}/gss.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(GSS_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() endif() endif() include(FindPackageHandleStandardArgs) - -set(_GSS_REQUIRED_VARS GSS_LIBRARIES GSS_FLAVOUR) - find_package_handle_standard_args(GSS - REQUIRED_VARS - ${_GSS_REQUIRED_VARS} - VERSION_VAR - GSS_VERSION - FAIL_MESSAGE - "Could NOT find GSS, try to set the path to GSS root folder in the system variable GSS_ROOT_DIR" + REQUIRED_VARS + GSS_FLAVOUR + GSS_LIBRARIES + VERSION_VAR + GSS_VERSION + FAIL_MESSAGE + "Could NOT find GSS, try to set the path to GSS root folder in the system variable GSS_ROOT_DIR" ) -mark_as_advanced(GSS_INCLUDE_DIR GSS_LIBRARIES) +mark_as_advanced( + _GSS_CFLAGS + _GSS_FOUND + _GSS_INCLUDE_DIRS + _GSS_LIBRARIES + _GSS_LIBRARY_DIRS + _GSS_MODULE_NAME + _GSS_PREFIX + _GSS_VERSION +) diff --git a/Utilities/cmcurl/CMake/FindLDAP.cmake b/Utilities/cmcurl/CMake/FindLDAP.cmake new file mode 100644 index 00000000000..fdc6d7be943 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLDAP.cmake @@ -0,0 +1,107 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the ldap library +# +# Input variables: +# +# - `LDAP_INCLUDE_DIR`: The ldap include directory. +# - `LDAP_LIBRARY`: Path to `ldap` library. +# - `LDAP_LBER_LIBRARY`: Path to `lber` library. +# +# Result variables: +# +# - `LDAP_FOUND`: System has ldap. +# - `LDAP_INCLUDE_DIRS`: The ldap include directories. +# - `LDAP_LIBRARIES`: The ldap library names. +# - `LDAP_LIBRARY_DIRS`: The ldap library directories. +# - `LDAP_PC_REQUIRES`: The ldap pkg-config packages. +# - `LDAP_CFLAGS`: Required compiler flags. +# - `LDAP_VERSION`: Version of ldap. + +set(LDAP_PC_REQUIRES "ldap" "lber") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LDAP_INCLUDE_DIR AND + NOT DEFINED LDAP_LIBRARY AND + NOT DEFINED LDAP_LBER_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LDAP ${LDAP_PC_REQUIRES}) +endif() + +if(LDAP_FOUND) + set(LDAP_VERSION "${LDAP_ldap_VERSION}") + string(REPLACE ";" " " LDAP_CFLAGS "${LDAP_CFLAGS}") + message(STATUS "Found LDAP (via pkg-config): ${LDAP_INCLUDE_DIRS} (found version \"${LDAP_VERSION}\")") +else() + set(LDAP_PC_REQUIRES "") # Depend on pkg-config only when found via pkg-config + + # On Apple the SDK LDAP gets picked up from + # 'MacOSX.sdk/System/Library/Frameworks/LDAP.framework/Headers', which contains + # ldap.h and lber.h both being stubs to include and . + # This causes an infinite inclusion loop in compile. Also do this for libraries + # to avoid picking up the 'ldap.framework' with a full path. + set(_save_cmake_system_framework_path ${CMAKE_SYSTEM_FRAMEWORK_PATH}) + set(CMAKE_SYSTEM_FRAMEWORK_PATH "") + find_path(LDAP_INCLUDE_DIR NAMES "ldap.h") + find_library(LDAP_LIBRARY NAMES "ldap") + find_library(LDAP_LBER_LIBRARY NAMES "lber") + set(CMAKE_SYSTEM_FRAMEWORK_PATH ${_save_cmake_system_framework_path}) + + unset(LDAP_VERSION CACHE) + if(LDAP_INCLUDE_DIR AND EXISTS "${LDAP_INCLUDE_DIR}/ldap_features.h") + set(_version_regex1 "#[\t ]*define[\t ]+LDAP_VENDOR_VERSION_MAJOR[\t ]+([0-9]+).*") + set(_version_regex2 "#[\t ]*define[\t ]+LDAP_VENDOR_VERSION_MINOR[\t ]+([0-9]+).*") + set(_version_regex3 "#[\t ]*define[\t ]+LDAP_VENDOR_VERSION_PATCH[\t ]+([0-9]+).*") + file(STRINGS "${LDAP_INCLUDE_DIR}/ldap_features.h" _version_str1 REGEX "${_version_regex1}") + file(STRINGS "${LDAP_INCLUDE_DIR}/ldap_features.h" _version_str2 REGEX "${_version_regex2}") + file(STRINGS "${LDAP_INCLUDE_DIR}/ldap_features.h" _version_str3 REGEX "${_version_regex3}") + string(REGEX REPLACE "${_version_regex1}" "\\1" _version_str1 "${_version_str1}") + string(REGEX REPLACE "${_version_regex2}" "\\1" _version_str2 "${_version_str2}") + string(REGEX REPLACE "${_version_regex3}" "\\1" _version_str3 "${_version_str3}") + set(LDAP_VERSION "${_version_str1}.${_version_str2}.${_version_str3}") + unset(_version_regex1) + unset(_version_regex2) + unset(_version_regex3) + unset(_version_str1) + unset(_version_str2) + unset(_version_str3) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LDAP + REQUIRED_VARS + LDAP_INCLUDE_DIR + LDAP_LIBRARY + LDAP_LBER_LIBRARY + VERSION_VAR + LDAP_VERSION + ) + + if(LDAP_FOUND) + set(LDAP_INCLUDE_DIRS ${LDAP_INCLUDE_DIR}) + set(LDAP_LIBRARIES ${LDAP_LIBRARY} ${LDAP_LBER_LIBRARY}) + endif() + + mark_as_advanced(LDAP_INCLUDE_DIR LDAP_LIBRARY LDAP_LBER_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindLibPSL.cmake b/Utilities/cmcurl/CMake/FindLibPSL.cmake deleted file mode 100644 index e3bd68d1d77..00000000000 --- a/Utilities/cmcurl/CMake/FindLibPSL.cmake +++ /dev/null @@ -1,45 +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 -# -########################################################################### -# - Try to find the libpsl library -# Once done this will define -# -# LIBPSL_FOUND - system has the libpsl library -# LIBPSL_INCLUDE_DIR - the libpsl include directory -# LIBPSL_LIBRARY - the libpsl library name - -find_path(LIBPSL_INCLUDE_DIR libpsl.h) - -find_library(LIBPSL_LIBRARY NAMES psl libpsl) - -if(LIBPSL_INCLUDE_DIR) - file(STRINGS "${LIBPSL_INCLUDE_DIR}/libpsl.h" libpsl_version_str REGEX "^#define[\t ]+PSL_VERSION[\t ]+\"(.*)\"") - string(REGEX REPLACE "^.*\"([^\"]+)\"" "\\1" LIBPSL_VERSION "${libpsl_version_str}") -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibPSL - REQUIRED_VARS LIBPSL_LIBRARY LIBPSL_INCLUDE_DIR - VERSION_VAR LIBPSL_VERSION) - -mark_as_advanced(LIBPSL_INCLUDE_DIR LIBPSL_LIBRARY) diff --git a/Utilities/cmcurl/CMake/FindLibSSH2.cmake b/Utilities/cmcurl/CMake/FindLibSSH2.cmake deleted file mode 100644 index a0c251ae395..00000000000 --- a/Utilities/cmcurl/CMake/FindLibSSH2.cmake +++ /dev/null @@ -1,45 +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 -# -########################################################################### -# - Try to find the libssh2 library -# Once done this will define -# -# LIBSSH2_FOUND - system has the libssh2 library -# LIBSSH2_INCLUDE_DIR - the libssh2 include directory -# LIBSSH2_LIBRARY - the libssh2 library name - -find_path(LIBSSH2_INCLUDE_DIR libssh2.h) - -find_library(LIBSSH2_LIBRARY NAMES ssh2 libssh2) - -if(LIBSSH2_INCLUDE_DIR) - file(STRINGS "${LIBSSH2_INCLUDE_DIR}/libssh2.h" libssh2_version_str REGEX "^#define[\t ]+LIBSSH2_VERSION[\t ]+\"(.*)\"") - string(REGEX REPLACE "^.*\"([^\"]+)\"" "\\1" LIBSSH2_VERSION "${libssh2_version_str}") -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibSSH2 - REQUIRED_VARS LIBSSH2_LIBRARY LIBSSH2_INCLUDE_DIR - VERSION_VAR LIBSSH2_VERSION) - -mark_as_advanced(LIBSSH2_INCLUDE_DIR LIBSSH2_LIBRARY) diff --git a/Utilities/cmcurl/CMake/FindLibgsasl.cmake b/Utilities/cmcurl/CMake/FindLibgsasl.cmake new file mode 100644 index 00000000000..c726ce1a49d --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLibgsasl.cmake @@ -0,0 +1,83 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the libgsasl library +# +# Input variables: +# +# - `LIBGSASL_INCLUDE_DIR`: The libgsasl include directory. +# - `LIBGSASL_LIBRARY`: Path to `libgsasl` library. +# +# Result variables: +# +# - `LIBGSASL_FOUND`: System has libgsasl. +# - `LIBGSASL_INCLUDE_DIRS`: The libgsasl include directories. +# - `LIBGSASL_LIBRARIES`: The libgsasl library names. +# - `LIBGSASL_LIBRARY_DIRS`: The libgsasl library directories. +# - `LIBGSASL_PC_REQUIRES`: The libgsasl pkg-config packages. +# - `LIBGSASL_CFLAGS`: Required compiler flags. +# - `LIBGSASL_VERSION`: Version of libgsasl. + +set(LIBGSASL_PC_REQUIRES "libgsasl") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LIBGSASL_INCLUDE_DIR AND + NOT DEFINED LIBGSASL_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LIBGSASL ${LIBGSASL_PC_REQUIRES}) +endif() + +if(LIBGSASL_FOUND) + set(Libgsasl_FOUND TRUE) + string(REPLACE ";" " " LIBGSASL_CFLAGS "${LIBGSASL_CFLAGS}") + message(STATUS "Found Libgsasl (via pkg-config): ${LIBGSASL_INCLUDE_DIRS} (found version \"${LIBGSASL_VERSION}\")") +else() + find_path(LIBGSASL_INCLUDE_DIR NAMES "gsasl.h") + find_library(LIBGSASL_LIBRARY NAMES "gsasl" "libgsasl") + + unset(LIBGSASL_VERSION CACHE) + if(LIBGSASL_INCLUDE_DIR AND EXISTS "${LIBGSASL_INCLUDE_DIR}/gsasl-version.h") + set(_version_regex "#[\t ]*define[\t ]+GSASL_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${LIBGSASL_INCLUDE_DIR}/gsasl-version.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(LIBGSASL_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libgsasl + REQUIRED_VARS + LIBGSASL_INCLUDE_DIR + LIBGSASL_LIBRARY + VERSION_VAR + LIBGSASL_VERSION + ) + + if(LIBGSASL_FOUND) + set(LIBGSASL_INCLUDE_DIRS ${LIBGSASL_INCLUDE_DIR}) + set(LIBGSASL_LIBRARIES ${LIBGSASL_LIBRARY}) + endif() + + mark_as_advanced(LIBGSASL_INCLUDE_DIR LIBGSASL_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindLibidn2.cmake b/Utilities/cmcurl/CMake/FindLibidn2.cmake new file mode 100644 index 00000000000..f8f00f0c791 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLibidn2.cmake @@ -0,0 +1,83 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the libidn2 library +# +# Input variables: +# +# - `LIBIDN2_INCLUDE_DIR`: The libidn2 include directory. +# - `LIBIDN2_LIBRARY`: Path to `libidn2` library. +# +# Result variables: +# +# - `LIBIDN2_FOUND`: System has libidn2. +# - `LIBIDN2_INCLUDE_DIRS`: The libidn2 include directories. +# - `LIBIDN2_LIBRARIES`: The libidn2 library names. +# - `LIBIDN2_LIBRARY_DIRS`: The libidn2 library directories. +# - `LIBIDN2_PC_REQUIRES`: The libidn2 pkg-config packages. +# - `LIBIDN2_CFLAGS`: Required compiler flags. +# - `LIBIDN2_VERSION`: Version of libidn2. + +set(LIBIDN2_PC_REQUIRES "libidn2") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LIBIDN2_INCLUDE_DIR AND + NOT DEFINED LIBIDN2_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LIBIDN2 ${LIBIDN2_PC_REQUIRES}) +endif() + +if(LIBIDN2_FOUND) + set(Libidn2_FOUND TRUE) + string(REPLACE ";" " " LIBIDN2_CFLAGS "${LIBIDN2_CFLAGS}") + message(STATUS "Found Libidn2 (via pkg-config): ${LIBIDN2_INCLUDE_DIRS} (found version \"${LIBIDN2_VERSION}\")") +else() + find_path(LIBIDN2_INCLUDE_DIR NAMES "idn2.h") + find_library(LIBIDN2_LIBRARY NAMES "idn2" "libidn2") + + unset(LIBIDN2_VERSION CACHE) + if(LIBIDN2_INCLUDE_DIR AND EXISTS "${LIBIDN2_INCLUDE_DIR}/idn2.h") + set(_version_regex "#[\t ]*define[\t ]+IDN2_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${LIBIDN2_INCLUDE_DIR}/idn2.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(LIBIDN2_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libidn2 + REQUIRED_VARS + LIBIDN2_INCLUDE_DIR + LIBIDN2_LIBRARY + VERSION_VAR + LIBIDN2_VERSION + ) + + if(LIBIDN2_FOUND) + set(LIBIDN2_INCLUDE_DIRS ${LIBIDN2_INCLUDE_DIR}) + set(LIBIDN2_LIBRARIES ${LIBIDN2_LIBRARY}) + endif() + + mark_as_advanced(LIBIDN2_INCLUDE_DIR LIBIDN2_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindLibpsl.cmake b/Utilities/cmcurl/CMake/FindLibpsl.cmake new file mode 100644 index 00000000000..d6fde4b2d03 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLibpsl.cmake @@ -0,0 +1,83 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the libpsl library +# +# Input variables: +# +# - `LIBPSL_INCLUDE_DIR`: The libpsl include directory. +# - `LIBPSL_LIBRARY`: Path to `libpsl` library. +# +# Result variables: +# +# - `LIBPSL_FOUND`: System has libpsl. +# - `LIBPSL_INCLUDE_DIRS`: The libpsl include directories. +# - `LIBPSL_LIBRARIES`: The libpsl library names. +# - `LIBPSL_LIBRARY_DIRS`: The libpsl library directories. +# - `LIBPSL_PC_REQUIRES`: The libpsl pkg-config packages. +# - `LIBPSL_CFLAGS`: Required compiler flags. +# - `LIBPSL_VERSION`: Version of libpsl. + +set(LIBPSL_PC_REQUIRES "libpsl") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LIBPSL_INCLUDE_DIR AND + NOT DEFINED LIBPSL_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LIBPSL ${LIBPSL_PC_REQUIRES}) +endif() + +if(LIBPSL_FOUND AND LIBPSL_INCLUDE_DIRS) + set(Libpsl_FOUND TRUE) + string(REPLACE ";" " " LIBPSL_CFLAGS "${LIBPSL_CFLAGS}") + message(STATUS "Found Libpsl (via pkg-config): ${LIBPSL_INCLUDE_DIRS} (found version \"${LIBPSL_VERSION}\")") +else() + find_path(LIBPSL_INCLUDE_DIR NAMES "libpsl.h") + find_library(LIBPSL_LIBRARY NAMES "psl" "libpsl") + + unset(LIBPSL_VERSION CACHE) + if(LIBPSL_INCLUDE_DIR AND EXISTS "${LIBPSL_INCLUDE_DIR}/libpsl.h") + set(_version_regex "#[\t ]*define[\t ]+PSL_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${LIBPSL_INCLUDE_DIR}/libpsl.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(LIBPSL_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libpsl + REQUIRED_VARS + LIBPSL_INCLUDE_DIR + LIBPSL_LIBRARY + VERSION_VAR + LIBPSL_VERSION + ) + + if(LIBPSL_FOUND) + set(LIBPSL_INCLUDE_DIRS ${LIBPSL_INCLUDE_DIR}) + set(LIBPSL_LIBRARIES ${LIBPSL_LIBRARY}) + endif() + + mark_as_advanced(LIBPSL_INCLUDE_DIR LIBPSL_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindLibrtmp.cmake b/Utilities/cmcurl/CMake/FindLibrtmp.cmake new file mode 100644 index 00000000000..50fc9692bd2 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLibrtmp.cmake @@ -0,0 +1,103 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the librtmp library +# +# Input variables: +# +# - `LIBRTMP_INCLUDE_DIR`: The librtmp include directory. +# - `LIBRTMP_LIBRARY`: Path to `librtmp` library. +# +# Result variables: +# +# - `LIBRTMP_FOUND`: System has librtmp. +# - `LIBRTMP_INCLUDE_DIRS`: The librtmp include directories. +# - `LIBRTMP_LIBRARIES`: The librtmp library names. +# - `LIBRTMP_LIBRARY_DIRS`: The librtmp library directories. +# - `LIBRTMP_PC_REQUIRES`: The librtmp pkg-config packages. +# - `LIBRTMP_CFLAGS`: Required compiler flags. +# - `LIBRTMP_VERSION`: Version of librtmp. + +set(LIBRTMP_PC_REQUIRES "librtmp") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LIBRTMP_INCLUDE_DIR AND + NOT DEFINED LIBRTMP_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LIBRTMP ${LIBRTMP_PC_REQUIRES}) +endif() + +if(LIBRTMP_FOUND AND LIBRTMP_INCLUDE_DIRS) + set(Librtmp_FOUND TRUE) + string(REPLACE ";" " " LIBRTMP_CFLAGS "${LIBRTMP_CFLAGS}") + message(STATUS "Found Librtmp (via pkg-config): ${LIBRTMP_INCLUDE_DIRS} (found version \"${LIBRTMP_VERSION}\")") +else() + find_path(LIBRTMP_INCLUDE_DIR NAMES "librtmp/rtmp.h") + find_library(LIBRTMP_LIBRARY NAMES "rtmp") + + unset(LIBRTMP_VERSION CACHE) + if(LIBRTMP_INCLUDE_DIR AND EXISTS "${LIBRTMP_INCLUDE_DIR}/librtmp/rtmp.h") + set(_version_regex "#[\t ]*define[\t ]+RTMP_LIB_VERSION[\t ]+0x([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F]).*") + file(STRINGS "${LIBRTMP_INCLUDE_DIR}/librtmp/rtmp.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str1 "${_version_str}") + string(REGEX REPLACE "${_version_regex}" "\\2" _version_str2 "${_version_str}") + if(CMAKE_VERSION VERSION_LESS 3.13) + # No support for hex version numbers, just strip leading zeroes + string(REGEX REPLACE "^0" "" _version_str1 "${_version_str1}") + string(REGEX REPLACE "^0" "" _version_str2 "${_version_str2}") + else() + math(EXPR _version_str1 "0x${_version_str1}" OUTPUT_FORMAT DECIMAL) + math(EXPR _version_str2 "0x${_version_str2}" OUTPUT_FORMAT DECIMAL) + endif() + set(LIBRTMP_VERSION "${_version_str1}.${_version_str2}") + unset(_version_regex) + unset(_version_str1) + unset(_version_str2) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Librtmp + REQUIRED_VARS + LIBRTMP_INCLUDE_DIR + LIBRTMP_LIBRARY + VERSION_VAR + LIBRTMP_VERSION + ) + + if(LIBRTMP_FOUND) + set(LIBRTMP_INCLUDE_DIRS ${LIBRTMP_INCLUDE_DIR}) + set(LIBRTMP_LIBRARIES ${LIBRTMP_LIBRARY}) + endif() + + mark_as_advanced(LIBRTMP_INCLUDE_DIR LIBRTMP_LIBRARY) + + # Necessary when linking a static librtmp + find_package(OpenSSL) + if(OPENSSL_FOUND) + list(APPEND LIBRTMP_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) + endif() +endif() + +if(LIBRTMP_FOUND AND WIN32) + list(APPEND LIBRTMP_LIBRARIES "winmm") +endif() diff --git a/Utilities/cmcurl/CMake/FindLibssh.cmake b/Utilities/cmcurl/CMake/FindLibssh.cmake new file mode 100644 index 00000000000..e2b27d975c3 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLibssh.cmake @@ -0,0 +1,97 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the libssh library +# +# Input variables: +# +# - `LIBSSH_INCLUDE_DIR`: The libssh include directory. +# - `LIBSSH_LIBRARY`: Path to libssh library. +# +# Result variables: +# +# - `LIBSSH_FOUND`: System has libssh. +# - `LIBSSH_INCLUDE_DIRS`: The libssh include directories. +# - `LIBSSH_LIBRARIES`: The libssh library names. +# - `LIBSSH_LIBRARY_DIRS`: The libssh library directories. +# - `LIBSSH_PC_REQUIRES`: The libssh pkg-config packages. +# - `LIBSSH_CFLAGS`: Required compiler flags. +# - `LIBSSH_VERSION`: Version of libssh. + +set(LIBSSH_PC_REQUIRES "libssh") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LIBSSH_INCLUDE_DIR AND + NOT DEFINED LIBSSH_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LIBSSH ${LIBSSH_PC_REQUIRES}) +endif() + +if(LIBSSH_FOUND) + set(Libssh_FOUND TRUE) + string(REPLACE ";" " " LIBSSH_CFLAGS "${LIBSSH_CFLAGS}") + message(STATUS "Found Libssh (via pkg-config): ${LIBSSH_INCLUDE_DIRS} (found version \"${LIBSSH_VERSION}\")") +else() + find_path(LIBSSH_INCLUDE_DIR NAMES "libssh/libssh.h") + find_library(LIBSSH_LIBRARY NAMES "ssh" "libssh") + + unset(LIBSSH_VERSION CACHE) + if(LIBSSH_INCLUDE_DIR AND EXISTS "${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h") + set(_version_regex1 "#[\t ]*define[\t ]+LIBSSH_VERSION_MAJOR[\t ]+([0-9]+).*") + set(_version_regex2 "#[\t ]*define[\t ]+LIBSSH_VERSION_MINOR[\t ]+([0-9]+).*") + set(_version_regex3 "#[\t ]*define[\t ]+LIBSSH_VERSION_MICRO[\t ]+([0-9]+).*") + file(STRINGS "${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h" _version_str1 REGEX "${_version_regex1}") + file(STRINGS "${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h" _version_str2 REGEX "${_version_regex2}") + file(STRINGS "${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h" _version_str3 REGEX "${_version_regex3}") + string(REGEX REPLACE "${_version_regex1}" "\\1" _version_str1 "${_version_str1}") + string(REGEX REPLACE "${_version_regex2}" "\\1" _version_str2 "${_version_str2}") + string(REGEX REPLACE "${_version_regex3}" "\\1" _version_str3 "${_version_str3}") + set(LIBSSH_VERSION "${_version_str1}.${_version_str2}.${_version_str3}") + unset(_version_regex1) + unset(_version_regex2) + unset(_version_regex3) + unset(_version_str1) + unset(_version_str2) + unset(_version_str3) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libssh + REQUIRED_VARS + LIBSSH_INCLUDE_DIR + LIBSSH_LIBRARY + VERSION_VAR + LIBSSH_VERSION + ) + + if(LIBSSH_FOUND) + set(LIBSSH_INCLUDE_DIRS ${LIBSSH_INCLUDE_DIR}) + set(LIBSSH_LIBRARIES ${LIBSSH_LIBRARY}) + endif() + + mark_as_advanced(LIBSSH_INCLUDE_DIR LIBSSH_LIBRARY) +endif() + +if(LIBSSH_FOUND AND WIN32) + list(APPEND LIBSSH_LIBRARIES "iphlpapi") # for if_nametoindex +endif() diff --git a/Utilities/cmcurl/CMake/FindLibssh2.cmake b/Utilities/cmcurl/CMake/FindLibssh2.cmake new file mode 100644 index 00000000000..0b81ecb3cfc --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLibssh2.cmake @@ -0,0 +1,83 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the libssh2 library +# +# Input variables: +# +# - `LIBSSH2_INCLUDE_DIR`: The libssh2 include directory. +# - `LIBSSH2_LIBRARY`: Path to `libssh2` library. +# +# Result variables: +# +# - `LIBSSH2_FOUND`: System has libssh2. +# - `LIBSSH2_INCLUDE_DIRS`: The libssh2 include directories. +# - `LIBSSH2_LIBRARIES`: The libssh2 library names. +# - `LIBSSH2_LIBRARY_DIRS`: The libssh2 library directories. +# - `LIBSSH2_PC_REQUIRES`: The libssh2 pkg-config packages. +# - `LIBSSH2_CFLAGS`: Required compiler flags. +# - `LIBSSH2_VERSION`: Version of libssh2. + +set(LIBSSH2_PC_REQUIRES "libssh2") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LIBSSH2_INCLUDE_DIR AND + NOT DEFINED LIBSSH2_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LIBSSH2 ${LIBSSH2_PC_REQUIRES}) +endif() + +if(LIBSSH2_FOUND AND LIBSSH2_INCLUDE_DIRS) + set(Libssh2_FOUND TRUE) + string(REPLACE ";" " " LIBSSH2_CFLAGS "${LIBSSH2_CFLAGS}") + message(STATUS "Found Libssh2 (via pkg-config): ${LIBSSH2_INCLUDE_DIRS} (found version \"${LIBSSH2_VERSION}\")") +else() + find_path(LIBSSH2_INCLUDE_DIR NAMES "libssh2.h") + find_library(LIBSSH2_LIBRARY NAMES "ssh2" "libssh2") + + unset(LIBSSH2_VERSION CACHE) + if(LIBSSH2_INCLUDE_DIR AND EXISTS "${LIBSSH2_INCLUDE_DIR}/libssh2.h") + set(_version_regex "#[\t ]*define[\t ]+LIBSSH2_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${LIBSSH2_INCLUDE_DIR}/libssh2.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(LIBSSH2_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libssh2 + REQUIRED_VARS + LIBSSH2_INCLUDE_DIR + LIBSSH2_LIBRARY + VERSION_VAR + LIBSSH2_VERSION + ) + + if(LIBSSH2_FOUND) + set(LIBSSH2_INCLUDE_DIRS ${LIBSSH2_INCLUDE_DIR}) + set(LIBSSH2_LIBRARIES ${LIBSSH2_LIBRARY}) + endif() + + mark_as_advanced(LIBSSH2_INCLUDE_DIR LIBSSH2_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindLibuv.cmake b/Utilities/cmcurl/CMake/FindLibuv.cmake new file mode 100644 index 00000000000..b16b3554f65 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindLibuv.cmake @@ -0,0 +1,93 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the libuv library +# +# Input variables: +# +# - `LIBUV_INCLUDE_DIR`: The libuv include directory. +# - `LIBUV_LIBRARY`: Path to `libuv` library. +# +# Result variables: +# +# - `LIBUV_FOUND`: System has libuv. +# - `LIBUV_INCLUDE_DIRS`: The libuv include directories. +# - `LIBUV_LIBRARIES`: The libuv library names. +# - `LIBUV_LIBRARY_DIRS`: The libuv library directories. +# - `LIBUV_PC_REQUIRES`: The libuv pkg-config packages. +# - `LIBUV_CFLAGS`: Required compiler flags. +# - `LIBUV_VERSION`: Version of libuv. + +set(LIBUV_PC_REQUIRES "libuv") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED LIBUV_INCLUDE_DIR AND + NOT DEFINED LIBUV_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(LIBUV ${LIBUV_PC_REQUIRES}) +endif() + +if(LIBUV_FOUND) + set(Libuv_FOUND TRUE) + string(REPLACE ";" " " LIBUV_CFLAGS "${LIBUV_CFLAGS}") + message(STATUS "Found Libuv (via pkg-config): ${LIBUV_INCLUDE_DIRS} (found version \"${LIBUV_VERSION}\")") +else() + find_path(LIBUV_INCLUDE_DIR NAMES "uv.h") + find_library(LIBUV_LIBRARY NAMES "uv" "libuv") + + unset(LIBUV_VERSION CACHE) + if(LIBUV_INCLUDE_DIR AND EXISTS "${LIBUV_INCLUDE_DIR}/uv/version.h") + set(_version_regex1 "#[\t ]*define[\t ]+UV_VERSION_MAJOR[\t ]+([0-9]+).*") + set(_version_regex2 "#[\t ]*define[\t ]+UV_VERSION_MINOR[\t ]+([0-9]+).*") + set(_version_regex3 "#[\t ]*define[\t ]+UV_VERSION_PATCH[\t ]+([0-9]+).*") + file(STRINGS "${LIBUV_INCLUDE_DIR}/uv/version.h" _version_str1 REGEX "${_version_regex1}") + file(STRINGS "${LIBUV_INCLUDE_DIR}/uv/version.h" _version_str2 REGEX "${_version_regex2}") + file(STRINGS "${LIBUV_INCLUDE_DIR}/uv/version.h" _version_str3 REGEX "${_version_regex3}") + string(REGEX REPLACE "${_version_regex1}" "\\1" _version_str1 "${_version_str1}") + string(REGEX REPLACE "${_version_regex2}" "\\1" _version_str2 "${_version_str2}") + string(REGEX REPLACE "${_version_regex3}" "\\1" _version_str3 "${_version_str3}") + set(LIBUV_VERSION "${_version_str1}.${_version_str2}.${_version_str3}") + unset(_version_regex1) + unset(_version_regex2) + unset(_version_regex3) + unset(_version_str1) + unset(_version_str2) + unset(_version_str3) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libuv + REQUIRED_VARS + LIBUV_INCLUDE_DIR + LIBUV_LIBRARY + VERSION_VAR + LIBUV_VERSION + ) + + if(LIBUV_FOUND) + set(LIBUV_INCLUDE_DIRS ${LIBUV_INCLUDE_DIR}) + set(LIBUV_LIBRARIES ${LIBUV_LIBRARY}) + endif() + + mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindMSH3.cmake b/Utilities/cmcurl/CMake/FindMSH3.cmake index 7d9c6b6544f..da38d458236 100644 --- a/Utilities/cmcurl/CMake/FindMSH3.cmake +++ b/Utilities/cmcurl/CMake/FindMSH3.cmake @@ -21,50 +21,52 @@ # SPDX-License-Identifier: curl # ########################################################################### +# Find the msh3 library +# +# Input variables: +# +# - `MSH3_INCLUDE_DIR`: The msh3 include directory. +# - `MSH3_LIBRARY`: Path to `msh3` library. +# +# Result variables: +# +# - `MSH3_FOUND`: System has msh3. +# - `MSH3_INCLUDE_DIRS`: The msh3 include directories. +# - `MSH3_LIBRARIES`: The msh3 library names. +# - `MSH3_LIBRARY_DIRS`: The msh3 library directories. +# - `MSH3_PC_REQUIRES`: The msh3 pkg-config packages. +# - `MSH3_CFLAGS`: Required compiler flags. +# - `MSH3_VERSION`: Version of msh3. -#[=======================================================================[.rst: -FindMSH3 ----------- - -Find the msh3 library - -Result Variables -^^^^^^^^^^^^^^^^ +set(MSH3_PC_REQUIRES "libmsh3") -``MSH3_FOUND`` - System has msh3 -``MSH3_INCLUDE_DIRS`` - The msh3 include directories. -``MSH3_LIBRARIES`` - The libraries needed to use msh3 -#]=======================================================================] -if(UNIX) +if(CURL_USE_PKGCONFIG AND + NOT DEFINED MSH3_INCLUDE_DIR AND + NOT DEFINED MSH3_LIBRARY) find_package(PkgConfig QUIET) - pkg_search_module(PC_MSH3 libmsh3) + pkg_check_modules(MSH3 ${MSH3_PC_REQUIRES}) endif() -find_path(MSH3_INCLUDE_DIR msh3.h - HINTS - ${PC_MSH3_INCLUDEDIR} - ${PC_MSH3_INCLUDE_DIRS} -) +if(MSH3_FOUND) + string(REPLACE ";" " " MSH3_CFLAGS "${MSH3_CFLAGS}") + message(STATUS "Found MSH3 (via pkg-config): ${MSH3_INCLUDE_DIRS} (found version \"${MSH3_VERSION}\")") +else() + set(MSH3_PC_REQUIRES "") # Depend on pkg-config only when found via pkg-config -find_library(MSH3_LIBRARY NAMES msh3 - HINTS - ${PC_MSH3_LIBDIR} - ${PC_MSH3_LIBRARY_DIRS} -) + find_path(MSH3_INCLUDE_DIR NAMES "msh3.h") + find_library(MSH3_LIBRARY NAMES "msh3") -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MSH3 - REQUIRED_VARS - MSH3_LIBRARY - MSH3_INCLUDE_DIR -) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(MSH3 + REQUIRED_VARS + MSH3_INCLUDE_DIR + MSH3_LIBRARY + ) -if(MSH3_FOUND) - set(MSH3_LIBRARIES ${MSH3_LIBRARY}) - set(MSH3_INCLUDE_DIRS ${MSH3_INCLUDE_DIR}) -endif() + if(MSH3_FOUND) + set(MSH3_INCLUDE_DIRS ${MSH3_INCLUDE_DIR}) + set(MSH3_LIBRARIES ${MSH3_LIBRARY}) + endif() -mark_as_advanced(MSH3_INCLUDE_DIRS MSH3_LIBRARIES) + mark_as_advanced(MSH3_INCLUDE_DIR MSH3_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindMbedTLS.cmake b/Utilities/cmcurl/CMake/FindMbedTLS.cmake index 814bd97da14..fcd6afb3ab6 100644 --- a/Utilities/cmcurl/CMake/FindMbedTLS.cmake +++ b/Utilities/cmcurl/CMake/FindMbedTLS.cmake @@ -21,16 +21,90 @@ # SPDX-License-Identifier: curl # ########################################################################### -find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h) +# Find the mbedTLS library +# +# Input variables: +# +# - `MBEDTLS_INCLUDE_DIR`: The mbedTLS include directory. +# - `MBEDTLS_LIBRARY`: Path to `mbedtls` library. +# - `MBEDX509_LIBRARY`: Path to `mbedx509` library. +# - `MBEDCRYPTO_LIBRARY`: Path to `mbedcrypto` library. +# +# Result variables: +# +# - `MBEDTLS_FOUND`: System has mbedTLS. +# - `MBEDTLS_INCLUDE_DIRS`: The mbedTLS include directories. +# - `MBEDTLS_LIBRARIES`: The mbedTLS library names. +# - `MBEDTLS_LIBRARY_DIRS`: The mbedTLS library directories. +# - `MBEDTLS_PC_REQUIRES`: The mbedTLS pkg-config packages. +# - `MBEDTLS_CFLAGS`: Required compiler flags. +# - `MBEDTLS_VERSION`: Version of mbedTLS. + +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(CURL_USE_PKGCONFIG AND + NOT DEFINED MBEDTLS_INCLUDE_DIR AND + NOT DEFINED MBEDTLS_LIBRARY AND + NOT DEFINED MBEDX509_LIBRARY AND + NOT DEFINED MBEDCRYPTO_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(MBEDTLS ${MBEDTLS_PC_REQUIRES}) +endif() + +if(MBEDTLS_FOUND) + set(MbedTLS_FOUND TRUE) + set(MBEDTLS_VERSION "${MBEDTLS_mbedtls_VERSION}") + string(REPLACE ";" " " MBEDTLS_CFLAGS "${MBEDTLS_CFLAGS}") + message(STATUS "Found MbedTLS (via pkg-config): ${MBEDTLS_INCLUDE_DIRS} (found version \"${MBEDTLS_VERSION}\")") +else() + set(MBEDTLS_PC_REQUIRES "") # Depend on pkg-config only when found via pkg-config + + find_path(MBEDTLS_INCLUDE_DIR NAMES "mbedtls/ssl.h") + find_library(MBEDTLS_LIBRARY NAMES "mbedtls" "libmbedtls") + find_library(MBEDX509_LIBRARY NAMES "mbedx509" "libmbedx509") + find_library(MBEDCRYPTO_LIBRARY NAMES "mbedcrypto" "libmbedcrypto") -find_library(MBEDTLS_LIBRARY mbedtls) -find_library(MBEDX509_LIBRARY mbedx509) -find_library(MBEDCRYPTO_LIBRARY mbedcrypto) + unset(MBEDTLS_VERSION CACHE) + if(MBEDTLS_INCLUDE_DIR) + if(EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h") # 3.x + set(_version_header "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h") + elseif(EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") # 2.x + set(_version_header "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") + else() + unset(_version_header) + endif() + if(_version_header) + set(_version_regex "#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"([0-9.]+)\"") + file(STRINGS "${_version_header}" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(MBEDTLS_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + unset(_version_header) + endif() + endif() -set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(MbedTLS + REQUIRED_VARS + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARY + MBEDX509_LIBRARY + MBEDCRYPTO_LIBRARY + VERSION_VAR + MBEDTLS_VERSION + ) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MbedTLS DEFAULT_MSG - MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) + if(MBEDTLS_FOUND) + set(MBEDTLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) + set(MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARY} ${MBEDX509_LIBRARY} ${MBEDCRYPTO_LIBRARY}) + endif() -mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) + mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake index 3957646c439..b8f37fdaebf 100644 --- a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake +++ b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake @@ -21,21 +21,62 @@ # SPDX-License-Identifier: curl # ########################################################################### -include(FindPackageHandleStandardArgs) +# Find the nghttp2 library +# +# Input variables: +# +# - `NGHTTP2_INCLUDE_DIR`: The nghttp2 include directory. +# - `NGHTTP2_LIBRARY`: Path to `nghttp2` library. +# +# Result variables: +# +# - `NGHTTP2_FOUND`: System has nghttp2. +# - `NGHTTP2_INCLUDE_DIRS`: The nghttp2 include directories. +# - `NGHTTP2_LIBRARIES`: The nghttp2 library names. +# - `NGHTTP2_LIBRARY_DIRS`: The nghttp2 library directories. +# - `NGHTTP2_PC_REQUIRES`: The nghttp2 pkg-config packages. +# - `NGHTTP2_CFLAGS`: Required compiler flags. +# - `NGHTTP2_VERSION`: Version of nghttp2. -find_path(NGHTTP2_INCLUDE_DIR "nghttp2/nghttp2.h") +set(NGHTTP2_PC_REQUIRES "libnghttp2") -find_library(NGHTTP2_LIBRARY NAMES nghttp2) +if(CURL_USE_PKGCONFIG AND + NOT DEFINED NGHTTP2_INCLUDE_DIR AND + NOT DEFINED NGHTTP2_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(NGHTTP2 ${NGHTTP2_PC_REQUIRES}) +endif() -find_package_handle_standard_args(NGHTTP2 - FOUND_VAR - NGHTTP2_FOUND +if(NGHTTP2_FOUND) + string(REPLACE ";" " " NGHTTP2_CFLAGS "${NGHTTP2_CFLAGS}") + message(STATUS "Found NGHTTP2 (via pkg-config): ${NGHTTP2_INCLUDE_DIRS} (found version \"${NGHTTP2_VERSION}\")") +else() + find_path(NGHTTP2_INCLUDE_DIR NAMES "nghttp2/nghttp2.h") + find_library(NGHTTP2_LIBRARY NAMES "nghttp2" "nghttp2_static") + + unset(NGHTTP2_VERSION CACHE) + if(NGHTTP2_INCLUDE_DIR AND EXISTS "${NGHTTP2_INCLUDE_DIR}/nghttp2/nghttp2ver.h") + set(_version_regex "#[\t ]*define[\t ]+NGHTTP2_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${NGHTTP2_INCLUDE_DIR}/nghttp2/nghttp2ver.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(NGHTTP2_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NGHTTP2 REQUIRED_VARS - NGHTTP2_LIBRARY NGHTTP2_INCLUDE_DIR -) + NGHTTP2_LIBRARY + VERSION_VAR + NGHTTP2_VERSION + ) -set(NGHTTP2_INCLUDE_DIRS ${NGHTTP2_INCLUDE_DIR}) -set(NGHTTP2_LIBRARIES ${NGHTTP2_LIBRARY}) + if(NGHTTP2_FOUND) + set(NGHTTP2_INCLUDE_DIRS ${NGHTTP2_INCLUDE_DIR}) + set(NGHTTP2_LIBRARIES ${NGHTTP2_LIBRARY}) + endif() -mark_as_advanced(NGHTTP2_INCLUDE_DIRS NGHTTP2_LIBRARIES) + mark_as_advanced(NGHTTP2_INCLUDE_DIR NGHTTP2_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake index 9b13e6c6ffe..99edd19955e 100644 --- a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake +++ b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake @@ -21,58 +21,62 @@ # SPDX-License-Identifier: curl # ########################################################################### +# Find the nghttp3 library +# +# Input variables: +# +# - `NGHTTP3_INCLUDE_DIR`: The nghttp3 include directory. +# - `NGHTTP3_LIBRARY`: Path to `nghttp3` library. +# +# Result variables: +# +# - `NGHTTP3_FOUND`: System has nghttp3. +# - `NGHTTP3_INCLUDE_DIRS`: The nghttp3 include directories. +# - `NGHTTP3_LIBRARIES`: The nghttp3 library names. +# - `NGHTTP3_LIBRARY_DIRS`: The nghttp3 library directories. +# - `NGHTTP3_PC_REQUIRES`: The nghttp3 pkg-config packages. +# - `NGHTTP3_CFLAGS`: Required compiler flags. +# - `NGHTTP3_VERSION`: Version of nghttp3. -#[=======================================================================[.rst: -FindNGHTTP3 ----------- - -Find the nghttp3 library - -Result Variables -^^^^^^^^^^^^^^^^ - -``NGHTTP3_FOUND`` - System has nghttp3 -``NGHTTP3_INCLUDE_DIRS`` - The nghttp3 include directories. -``NGHTTP3_LIBRARIES`` - The libraries needed to use nghttp3 -``NGHTTP3_VERSION`` - version of nghttp3. -#]=======================================================================] +set(NGHTTP3_PC_REQUIRES "libnghttp3") -if(UNIX) +if(CURL_USE_PKGCONFIG AND + NOT DEFINED NGHTTP3_INCLUDE_DIR AND + NOT DEFINED NGHTTP3_LIBRARY) find_package(PkgConfig QUIET) - pkg_search_module(PC_NGHTTP3 libnghttp3) + pkg_check_modules(NGHTTP3 ${NGHTTP3_PC_REQUIRES}) endif() -find_path(NGHTTP3_INCLUDE_DIR nghttp3/nghttp3.h - HINTS - ${PC_NGHTTP3_INCLUDEDIR} - ${PC_NGHTTP3_INCLUDE_DIRS} -) +if(NGHTTP3_FOUND) + string(REPLACE ";" " " NGHTTP3_CFLAGS "${NGHTTP3_CFLAGS}") + message(STATUS "Found NGHTTP3 (via pkg-config): ${NGHTTP3_INCLUDE_DIRS} (found version \"${NGHTTP3_VERSION}\")") +else() + find_path(NGHTTP3_INCLUDE_DIR NAMES "nghttp3/nghttp3.h") + find_library(NGHTTP3_LIBRARY NAMES "nghttp3") -find_library(NGHTTP3_LIBRARY NAMES nghttp3 - HINTS - ${PC_NGHTTP3_LIBDIR} - ${PC_NGHTTP3_LIBRARY_DIRS} -) + unset(NGHTTP3_VERSION CACHE) + if(NGHTTP3_INCLUDE_DIR AND EXISTS "${NGHTTP3_INCLUDE_DIR}/nghttp3/version.h") + set(_version_regex "#[\t ]*define[\t ]+NGHTTP3_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${NGHTTP3_INCLUDE_DIR}/nghttp3/version.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(NGHTTP3_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() -if(PC_NGHTTP3_VERSION) - set(NGHTTP3_VERSION ${PC_NGHTTP3_VERSION}) -endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NGHTTP3 + REQUIRED_VARS + NGHTTP3_INCLUDE_DIR + NGHTTP3_LIBRARY + VERSION_VAR + NGHTTP3_VERSION + ) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(NGHTTP3 - REQUIRED_VARS - NGHTTP3_LIBRARY - NGHTTP3_INCLUDE_DIR - VERSION_VAR NGHTTP3_VERSION -) + if(NGHTTP3_FOUND) + set(NGHTTP3_INCLUDE_DIRS ${NGHTTP3_INCLUDE_DIR}) + set(NGHTTP3_LIBRARIES ${NGHTTP3_LIBRARY}) + endif() -if(NGHTTP3_FOUND) - set(NGHTTP3_LIBRARIES ${NGHTTP3_LIBRARY}) - set(NGHTTP3_INCLUDE_DIRS ${NGHTTP3_INCLUDE_DIR}) + mark_as_advanced(NGHTTP3_INCLUDE_DIR NGHTTP3_LIBRARY) endif() - -mark_as_advanced(NGHTTP3_INCLUDE_DIRS NGHTTP3_LIBRARIES) diff --git a/Utilities/cmcurl/CMake/FindNGTCP2.cmake b/Utilities/cmcurl/CMake/FindNGTCP2.cmake index ff0d49e3387..3cbd408c22c 100644 --- a/Utilities/cmcurl/CMake/FindNGTCP2.cmake +++ b/Utilities/cmcurl/CMake/FindNGTCP2.cmake @@ -21,95 +21,103 @@ # SPDX-License-Identifier: curl # ########################################################################### +# Find the ngtcp2 library +# +# This module accepts optional COMPONENTS to control the crypto library (these are +# mutually exclusive): +# +# - quictls: Use `libngtcp2_crypto_quictls`. (choose this for LibreSSL) +# - BoringSSL: Use `libngtcp2_crypto_boringssl`. (choose this for AWS-LC) +# - wolfSSL: Use `libngtcp2_crypto_wolfssl`. +# - GnuTLS: Use `libngtcp2_crypto_gnutls`. +# - ossl: Use `libngtcp2_crypto_ossl`. +# +# Input variables: +# +# - `NGTCP2_INCLUDE_DIR`: The ngtcp2 include directory. +# - `NGTCP2_LIBRARY`: Path to `ngtcp2` library. +# +# Result variables: +# +# - `NGTCP2_FOUND`: System has ngtcp2. +# - `NGTCP2_INCLUDE_DIRS`: The ngtcp2 include directories. +# - `NGTCP2_LIBRARIES`: The ngtcp2 library names. +# - `NGTCP2_LIBRARY_DIRS`: The ngtcp2 library directories. +# - `NGTCP2_PC_REQUIRES`: The ngtcp2 pkg-config packages. +# - `NGTCP2_CFLAGS`: Required compiler flags. +# - `NGTCP2_VERSION`: Version of ngtcp2. -#[=======================================================================[.rst: -FindNGTCP2 ----------- - -Find the ngtcp2 library - -This module accepts optional COMPONENTS to control the crypto library (these are -mutually exclusive):: - - OpenSSL: Use libngtcp2_crypto_openssl - GnuTLS: Use libngtcp2_crypto_gnutls +if(NGTCP2_FIND_COMPONENTS) + set(_ngtcp2_crypto_backend "") + foreach(_component IN LISTS NGTCP2_FIND_COMPONENTS) + if(_component MATCHES "^(BoringSSL|quictls|wolfSSL|GnuTLS|ossl)") + if(_ngtcp2_crypto_backend) + message(FATAL_ERROR "NGTCP2: Only one crypto library can be selected") + endif() + set(_ngtcp2_crypto_backend ${_component}) + endif() + endforeach() -Result Variables -^^^^^^^^^^^^^^^^ + if(_ngtcp2_crypto_backend) + string(TOLOWER "ngtcp2_crypto_${_ngtcp2_crypto_backend}" _crypto_library_lower) + string(TOUPPER "ngtcp2_crypto_${_ngtcp2_crypto_backend}" _crypto_library_upper) + endif() +endif() -``NGTCP2_FOUND`` - System has ngtcp2 -``NGTCP2_INCLUDE_DIRS`` - The ngtcp2 include directories. -``NGTCP2_LIBRARIES`` - The libraries needed to use ngtcp2 -``NGTCP2_VERSION`` - version of ngtcp2. -#]=======================================================================] +set(NGTCP2_PC_REQUIRES "libngtcp2") +if(_ngtcp2_crypto_backend) + list(APPEND NGTCP2_PC_REQUIRES "lib${_crypto_library_lower}") +endif() -if(UNIX) +if(CURL_USE_PKGCONFIG AND + NOT DEFINED NGTCP2_INCLUDE_DIR AND + NOT DEFINED NGTCP2_LIBRARY) find_package(PkgConfig QUIET) - pkg_search_module(PC_NGTCP2 libngtcp2) + pkg_check_modules(NGTCP2 ${NGTCP2_PC_REQUIRES}) endif() -find_path(NGTCP2_INCLUDE_DIR ngtcp2/ngtcp2.h - HINTS - ${PC_NGTCP2_INCLUDEDIR} - ${PC_NGTCP2_INCLUDE_DIRS} -) - -find_library(NGTCP2_LIBRARY NAMES ngtcp2 - HINTS - ${PC_NGTCP2_LIBDIR} - ${PC_NGTCP2_LIBRARY_DIRS} -) +if(NGTCP2_FOUND) + set(NGTCP2_VERSION "${NGTCP2_libngtcp2_VERSION}") + string(REPLACE ";" " " NGTCP2_CFLAGS "${NGTCP2_CFLAGS}") + message(STATUS "Found NGTCP2 (via pkg-config): ${NGTCP2_INCLUDE_DIRS} (found version \"${NGTCP2_VERSION}\")") +else() + find_path(NGTCP2_INCLUDE_DIR NAMES "ngtcp2/ngtcp2.h") + find_library(NGTCP2_LIBRARY NAMES "ngtcp2") -if(PC_NGTCP2_VERSION) - set(NGTCP2_VERSION ${PC_NGTCP2_VERSION}) -endif() + unset(NGTCP2_VERSION CACHE) + if(NGTCP2_INCLUDE_DIR AND EXISTS "${NGTCP2_INCLUDE_DIR}/ngtcp2/version.h") + set(_version_regex "#[\t ]*define[\t ]+NGTCP2_VERSION[\t ]+\"([^\"]*)\"") + file(STRINGS "${NGTCP2_INCLUDE_DIR}/ngtcp2/version.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(NGTCP2_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() -if(NGTCP2_FIND_COMPONENTS) - set(NGTCP2_CRYPTO_BACKEND "") - foreach(component IN LISTS NGTCP2_FIND_COMPONENTS) - if(component MATCHES "^(BoringSSL|OpenSSL|wolfSSL|GnuTLS)") - if(NGTCP2_CRYPTO_BACKEND) - message(FATAL_ERROR "NGTCP2: Only one crypto library can be selected") - endif() - set(NGTCP2_CRYPTO_BACKEND ${component}) - endif() - endforeach() + if(_ngtcp2_crypto_backend) + get_filename_component(_ngtcp2_library_dir "${NGTCP2_LIBRARY}" DIRECTORY) + find_library(${_crypto_library_upper}_LIBRARY NAMES ${_crypto_library_lower} HINTS ${_ngtcp2_library_dir}) - if(NGTCP2_CRYPTO_BACKEND) - string(TOLOWER "ngtcp2_crypto_${NGTCP2_CRYPTO_BACKEND}" _crypto_library) - if(UNIX) - pkg_search_module(PC_${_crypto_library} lib${_crypto_library}) - endif() - find_library(${_crypto_library}_LIBRARY - NAMES - ${_crypto_library} - HINTS - ${PC_${_crypto_library}_LIBDIR} - ${PC_${_crypto_library}_LIBRARY_DIRS} - ) - if(${_crypto_library}_LIBRARY) - set(NGTCP2_${NGTCP2_CRYPTO_BACKEND}_FOUND TRUE) - set(NGTCP2_CRYPTO_LIBRARY ${${_crypto_library}_LIBRARY}) + if(${_crypto_library_upper}_LIBRARY) + set(NGTCP2_${_ngtcp2_crypto_backend}_FOUND TRUE) + set(NGTCP2_CRYPTO_LIBRARY ${${_crypto_library_upper}_LIBRARY}) endif() endif() -endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(NGTCP2 - REQUIRED_VARS - NGTCP2_LIBRARY - NGTCP2_INCLUDE_DIR - VERSION_VAR NGTCP2_VERSION - HANDLE_COMPONENTS -) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NGTCP2 + REQUIRED_VARS + NGTCP2_INCLUDE_DIR + NGTCP2_LIBRARY + VERSION_VAR + NGTCP2_VERSION + HANDLE_COMPONENTS + ) -if(NGTCP2_FOUND) - set(NGTCP2_LIBRARIES ${NGTCP2_LIBRARY} ${NGTCP2_CRYPTO_LIBRARY}) - set(NGTCP2_INCLUDE_DIRS ${NGTCP2_INCLUDE_DIR}) -endif() + if(NGTCP2_FOUND) + set(NGTCP2_INCLUDE_DIRS ${NGTCP2_INCLUDE_DIR}) + set(NGTCP2_LIBRARIES ${NGTCP2_LIBRARY} ${NGTCP2_CRYPTO_LIBRARY}) + endif() -mark_as_advanced(NGTCP2_INCLUDE_DIRS NGTCP2_LIBRARIES) + mark_as_advanced(NGTCP2_INCLUDE_DIR NGTCP2_LIBRARY NGTCP2_CRYPTO_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindNSS.cmake b/Utilities/cmcurl/CMake/FindNSS.cmake deleted file mode 100644 index ccddf422a5a..00000000000 --- a/Utilities/cmcurl/CMake/FindNSS.cmake +++ /dev/null @@ -1,40 +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 -# -########################################################################### -if(UNIX) - find_package(PkgConfig QUIET) - pkg_search_module(PC_NSS nss) -endif() -if(NOT PC_NSS_FOUND) - return() -endif() - -set(NSS_LIBRARIES ${PC_NSS_LINK_LIBRARIES}) -set(NSS_INCLUDE_DIRS ${PC_NSS_INCLUDE_DIRS}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(NSS - REQUIRED_VARS NSS_LIBRARIES NSS_INCLUDE_DIRS - VERSION_VAR PC_NSS_VERSION) - -mark_as_advanced(NSS_INCLUDE_DIRS NSS_LIBRARIES) diff --git a/Utilities/cmcurl/CMake/FindNettle.cmake b/Utilities/cmcurl/CMake/FindNettle.cmake new file mode 100644 index 00000000000..c2decf6e7b0 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindNettle.cmake @@ -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 +# +########################################################################### +# Find the nettle library +# +# Input variables: +# +# - `NETTLE_INCLUDE_DIR`: The nettle include directory. +# - `NETTLE_LIBRARY`: Path to `nettle` library. +# +# Result variables: +# +# - `NETTLE_FOUND`: System has nettle. +# - `NETTLE_INCLUDE_DIRS`: The nettle include directories. +# - `NETTLE_LIBRARIES`: The nettle library names. +# - `NETTLE_LIBRARY_DIRS`: The nettle library directories. +# - `NETTLE_PC_REQUIRES`: The nettle pkg-config packages. +# - `NETTLE_CFLAGS`: Required compiler flags. +# - `NETTLE_VERSION`: Version of nettle. + +set(NETTLE_PC_REQUIRES "nettle") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED NETTLE_INCLUDE_DIR AND + NOT DEFINED NETTLE_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(NETTLE ${NETTLE_PC_REQUIRES}) +endif() + +if(NETTLE_FOUND) + set(Nettle_FOUND TRUE) + string(REPLACE ";" " " NETTLE_CFLAGS "${NETTLE_CFLAGS}") + message(STATUS "Found Nettle (via pkg-config): ${NETTLE_INCLUDE_DIRS} (found version \"${NETTLE_VERSION}\")") +else() + find_path(NETTLE_INCLUDE_DIR NAMES "nettle/sha2.h") + find_library(NETTLE_LIBRARY NAMES "nettle") + + unset(NETTLE_VERSION CACHE) + if(NETTLE_INCLUDE_DIR AND EXISTS "${NETTLE_INCLUDE_DIR}/nettle/version.h") + set(_version_regex1 "#[\t ]*define[ \t]+NETTLE_VERSION_MAJOR[ \t]+([0-9]+).*") + set(_version_regex2 "#[\t ]*define[ \t]+NETTLE_VERSION_MINOR[ \t]+([0-9]+).*") + file(STRINGS "${NETTLE_INCLUDE_DIR}/nettle/version.h" _version_str1 REGEX "${_version_regex1}") + file(STRINGS "${NETTLE_INCLUDE_DIR}/nettle/version.h" _version_str2 REGEX "${_version_regex2}") + string(REGEX REPLACE "${_version_regex1}" "\\1" _version_str1 "${_version_str1}") + string(REGEX REPLACE "${_version_regex2}" "\\1" _version_str2 "${_version_str2}") + set(NETTLE_VERSION "${_version_str1}.${_version_str2}") + unset(_version_regex1) + unset(_version_regex2) + unset(_version_str1) + unset(_version_str2) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Nettle + REQUIRED_VARS + NETTLE_INCLUDE_DIR + NETTLE_LIBRARY + VERSION_VAR + NETTLE_VERSION + ) + + if(NETTLE_FOUND) + set(NETTLE_INCLUDE_DIRS ${NETTLE_INCLUDE_DIR}) + set(NETTLE_LIBRARIES ${NETTLE_LIBRARY}) + endif() + + mark_as_advanced(NETTLE_INCLUDE_DIR NETTLE_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindQUICHE.cmake b/Utilities/cmcurl/CMake/FindQUICHE.cmake deleted file mode 100644 index 0488463d553..00000000000 --- a/Utilities/cmcurl/CMake/FindQUICHE.cmake +++ /dev/null @@ -1,70 +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 -# -########################################################################### - -#[=======================================================================[.rst: -FindQUICHE ----------- - -Find the quiche library - -Result Variables -^^^^^^^^^^^^^^^^ - -``QUICHE_FOUND`` - System has quiche -``QUICHE_INCLUDE_DIRS`` - The quiche include directories. -``QUICHE_LIBRARIES`` - The libraries needed to use quiche -#]=======================================================================] -if(UNIX) - find_package(PkgConfig QUIET) - pkg_search_module(PC_QUICHE quiche) -endif() - -find_path(QUICHE_INCLUDE_DIR quiche.h - HINTS - ${PC_QUICHE_INCLUDEDIR} - ${PC_QUICHE_INCLUDE_DIRS} -) - -find_library(QUICHE_LIBRARY NAMES quiche - HINTS - ${PC_QUICHE_LIBDIR} - ${PC_QUICHE_LIBRARY_DIRS} -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QUICHE - REQUIRED_VARS - QUICHE_LIBRARY - QUICHE_INCLUDE_DIR -) - -if(QUICHE_FOUND) - set(QUICHE_LIBRARIES ${QUICHE_LIBRARY}) - set(QUICHE_INCLUDE_DIRS ${QUICHE_INCLUDE_DIR}) -endif() - -mark_as_advanced(QUICHE_INCLUDE_DIRS QUICHE_LIBRARIES) diff --git a/Utilities/cmcurl/CMake/FindQuiche.cmake b/Utilities/cmcurl/CMake/FindQuiche.cmake new file mode 100644 index 00000000000..6939c64e0ff --- /dev/null +++ b/Utilities/cmcurl/CMake/FindQuiche.cmake @@ -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 +# +########################################################################### +# Find the quiche library +# +# Input variables: +# +# - `QUICHE_INCLUDE_DIR`: The quiche include directory. +# - `QUICHE_LIBRARY`: Path to `quiche` library. +# +# Result variables: +# +# - `QUICHE_FOUND`: System has quiche. +# - `QUICHE_INCLUDE_DIRS`: The quiche include directories. +# - `QUICHE_LIBRARIES`: The quiche library names. +# - `QUICHE_LIBRARY_DIRS`: The quiche library directories. +# - `QUICHE_PC_REQUIRES`: The quiche pkg-config packages. +# - `QUICHE_CFLAGS`: Required compiler flags. +# - `QUICHE_VERSION`: Version of quiche. + +set(QUICHE_PC_REQUIRES "quiche") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED QUICHE_INCLUDE_DIR AND + NOT DEFINED QUICHE_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(QUICHE ${QUICHE_PC_REQUIRES}) +endif() + +if(QUICHE_FOUND) + set(Quiche_FOUND TRUE) + string(REPLACE ";" " " QUICHE_CFLAGS "${QUICHE_CFLAGS}") + message(STATUS "Found Quiche (via pkg-config): ${QUICHE_INCLUDE_DIRS} (found version \"${QUICHE_VERSION}\")") +else() + find_path(QUICHE_INCLUDE_DIR NAMES "quiche.h") + find_library(QUICHE_LIBRARY NAMES "quiche") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Quiche + REQUIRED_VARS + QUICHE_INCLUDE_DIR + QUICHE_LIBRARY + ) + + if(QUICHE_FOUND) + set(QUICHE_INCLUDE_DIRS ${QUICHE_INCLUDE_DIR}) + set(QUICHE_LIBRARIES ${QUICHE_LIBRARY}) + endif() + + mark_as_advanced(QUICHE_INCLUDE_DIR QUICHE_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/FindRustls.cmake b/Utilities/cmcurl/CMake/FindRustls.cmake new file mode 100644 index 00000000000..564b08ce15b --- /dev/null +++ b/Utilities/cmcurl/CMake/FindRustls.cmake @@ -0,0 +1,109 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Find the Rustls library +# +# Input variables: +# +# - `RUSTLS_INCLUDE_DIR`: The Rustls include directory. +# - `RUSTLS_LIBRARY`: Path to `rustls` library. +# +# Result variables: +# +# - `RUSTLS_FOUND`: System has Rustls. +# - `RUSTLS_INCLUDE_DIRS`: The Rustls include directories. +# - `RUSTLS_LIBRARIES`: The Rustls library names. +# - `RUSTLS_LIBRARY_DIRS`: The Rustls library directories. +# - `RUSTLS_PC_REQUIRES`: The Rustls pkg-config packages. +# - `RUSTLS_CFLAGS`: Required compiler flags. +# - `RUSTLS_VERSION`: Version of Rustls. + +set(RUSTLS_PC_REQUIRES "rustls") + +if(CURL_USE_PKGCONFIG AND + NOT DEFINED RUSTLS_INCLUDE_DIR AND + NOT DEFINED RUSTLS_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(RUSTLS ${RUSTLS_PC_REQUIRES}) +endif() + +if(RUSTLS_FOUND) + set(Rustls_FOUND TRUE) + string(REPLACE ";" " " RUSTLS_CFLAGS "${RUSTLS_CFLAGS}") + message(STATUS "Found Rustls (via pkg-config): ${RUSTLS_INCLUDE_DIRS} (found version \"${RUSTLS_VERSION}\")") +else() + set(RUSTLS_PC_REQUIRES "") # Depend on pkg-config only when found via pkg-config + + find_path(RUSTLS_INCLUDE_DIR NAMES "rustls.h") + find_library(RUSTLS_LIBRARY NAMES "rustls") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Rustls + REQUIRED_VARS + RUSTLS_INCLUDE_DIR + RUSTLS_LIBRARY + ) + + if(RUSTLS_FOUND) + set(RUSTLS_INCLUDE_DIRS ${RUSTLS_INCLUDE_DIR}) + set(RUSTLS_LIBRARIES ${RUSTLS_LIBRARY}) + endif() + + mark_as_advanced(RUSTLS_INCLUDE_DIR RUSTLS_LIBRARY) +endif() + +if(RUSTLS_FOUND) + if(APPLE) + find_library(SECURITY_FRAMEWORK NAMES "Security") + mark_as_advanced(SECURITY_FRAMEWORK) + if(NOT SECURITY_FRAMEWORK) + message(FATAL_ERROR "Security framework not found") + endif() + list(APPEND RUSTLS_LIBRARIES "-framework Security") + + find_library(FOUNDATION_FRAMEWORK NAMES "Foundation") + mark_as_advanced(FOUNDATION_FRAMEWORK) + if(NOT FOUNDATION_FRAMEWORK) + message(FATAL_ERROR "Foundation framework not found") + endif() + list(APPEND RUSTLS_LIBRARIES "-framework Foundation") + elseif(NOT WIN32) + find_library(PTHREAD_LIBRARY NAMES "pthread") + if(PTHREAD_LIBRARY) + list(APPEND RUSTLS_LIBRARIES ${PTHREAD_LIBRARY}) + endif() + mark_as_advanced(PTHREAD_LIBRARY) + + find_library(DL_LIBRARY NAMES "dl") + if(DL_LIBRARY) + list(APPEND RUSTLS_LIBRARIES ${DL_LIBRARY}) + endif() + mark_as_advanced(DL_LIBRARY) + + find_library(MATH_LIBRARY NAMES "m") + if(MATH_LIBRARY) + list(APPEND RUSTLS_LIBRARIES ${MATH_LIBRARY}) + endif() + mark_as_advanced(MATH_LIBRARY) + endif() +endif() diff --git a/Utilities/cmcurl/CMake/FindWolfSSH.cmake b/Utilities/cmcurl/CMake/FindWolfSSH.cmake new file mode 100644 index 00000000000..98de656b923 --- /dev/null +++ b/Utilities/cmcurl/CMake/FindWolfSSH.cmake @@ -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 +# +########################################################################### +# Find the wolfSSH library +# +# Input variables: +# +# - `WOLFSSH_INCLUDE_DIR`: The wolfSSH include directory. +# - `WOLFSSH_LIBRARY`: Path to `wolfssh` library. +# +# Result variables: +# +# - `WOLFSSH_FOUND`: System has wolfSSH. +# - `WOLFSSH_INCLUDE_DIRS`: The wolfSSH include directories. +# - `WOLFSSH_LIBRARIES`: The wolfSSH library names. +# - `WOLFSSH_VERSION`: Version of wolfSSH. + +find_path(WOLFSSH_INCLUDE_DIR NAMES "wolfssh/ssh.h") +find_library(WOLFSSH_LIBRARY NAMES "wolfssh" "libwolfssh") + +unset(WOLFSSH_VERSION CACHE) +if(WOLFSSH_INCLUDE_DIR AND EXISTS "${WOLFSSH_INCLUDE_DIR}/wolfssh/version.h") + set(_version_regex "#[\t ]*define[\t ]+LIBWOLFSSH_VERSION_STRING[\t ]+\"([^\"]*)\"") + file(STRINGS "${WOLFSSH_INCLUDE_DIR}/wolfssh/version.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(WOLFSSH_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(WolfSSH + REQUIRED_VARS + WOLFSSH_INCLUDE_DIR + WOLFSSH_LIBRARY + VERSION_VAR + WOLFSSH_VERSION +) + +if(WOLFSSH_FOUND) + set(WOLFSSH_INCLUDE_DIRS ${WOLFSSH_INCLUDE_DIR}) + set(WOLFSSH_LIBRARIES ${WOLFSSH_LIBRARY}) +endif() + +mark_as_advanced(WOLFSSH_INCLUDE_DIR WOLFSSH_LIBRARY) diff --git a/Utilities/cmcurl/CMake/FindWolfSSL.cmake b/Utilities/cmcurl/CMake/FindWolfSSL.cmake index d67c0eb24d4..e33fef87119 100644 --- a/Utilities/cmcurl/CMake/FindWolfSSL.cmake +++ b/Utilities/cmcurl/CMake/FindWolfSSL.cmake @@ -21,16 +21,80 @@ # SPDX-License-Identifier: curl # ########################################################################### -find_path(WolfSSL_INCLUDE_DIR NAMES wolfssl/ssl.h) -find_library(WolfSSL_LIBRARY NAMES wolfssl) -mark_as_advanced(WolfSSL_INCLUDE_DIR WolfSSL_LIBRARY) +# Find the wolfSSL library +# +# Input variables: +# +# - `WOLFSSL_INCLUDE_DIR`: The wolfSSL include directory. +# - `WOLFSSL_LIBRARY`: Path to `wolfssl` library. +# +# Result variables: +# +# - `WOLFSSL_FOUND`: System has wolfSSL. +# - `WOLFSSL_INCLUDE_DIRS`: The wolfSSL include directories. +# - `WOLFSSL_LIBRARIES`: The wolfSSL library names. +# - `WOLFSSL_LIBRARY_DIRS`: The wolfSSL library directories. +# - `WOLFSSL_PC_REQUIRES`: The wolfSSL pkg-config packages. +# - `WOLFSSL_CFLAGS`: Required compiler flags. +# - `WOLFSSL_VERSION`: Version of wolfSSL. + +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") -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(WolfSSL - REQUIRED_VARS WolfSSL_INCLUDE_DIR WolfSSL_LIBRARY +if(CURL_USE_PKGCONFIG AND + NOT DEFINED WOLFSSL_INCLUDE_DIR AND + NOT DEFINED WOLFSSL_LIBRARY) + find_package(PkgConfig QUIET) + pkg_check_modules(WOLFSSL ${WOLFSSL_PC_REQUIRES}) +endif() + +if(WOLFSSL_FOUND) + set(WolfSSL_FOUND TRUE) + string(REPLACE ";" " " WOLFSSL_CFLAGS "${WOLFSSL_CFLAGS}") + message(STATUS "Found WolfSSL (via pkg-config): ${WOLFSSL_INCLUDE_DIRS} (found version \"${WOLFSSL_VERSION}\")") +else() + find_path(WOLFSSL_INCLUDE_DIR NAMES "wolfssl/ssl.h") + find_library(WOLFSSL_LIBRARY NAMES "wolfssl") + + unset(WOLFSSL_VERSION CACHE) + if(WOLFSSL_INCLUDE_DIR AND EXISTS "${WOLFSSL_INCLUDE_DIR}/wolfssl/version.h") + set(_version_regex "#[\t ]*define[\t ]+LIBWOLFSSL_VERSION_STRING[\t ]+\"([^\"]*)\"") + file(STRINGS "${WOLFSSL_INCLUDE_DIR}/wolfssl/version.h" _version_str REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" _version_str "${_version_str}") + set(WOLFSSL_VERSION "${_version_str}") + unset(_version_regex) + unset(_version_str) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(WolfSSL + REQUIRED_VARS + WOLFSSL_INCLUDE_DIR + WOLFSSL_LIBRARY + VERSION_VAR + WOLFSSL_VERSION ) -if(WolfSSL_FOUND) - set(WolfSSL_INCLUDE_DIRS ${WolfSSL_INCLUDE_DIR}) - set(WolfSSL_LIBRARIES ${WolfSSL_LIBRARY}) + if(WOLFSSL_FOUND) + set(WOLFSSL_INCLUDE_DIRS ${WOLFSSL_INCLUDE_DIR}) + set(WOLFSSL_LIBRARIES ${WOLFSSL_LIBRARY}) + endif() + + mark_as_advanced(WOLFSSL_INCLUDE_DIR WOLFSSL_LIBRARY) +endif() + +if(WOLFSSL_FOUND AND NOT WIN32) + find_library(MATH_LIBRARY NAMES "m") + if(MATH_LIBRARY) + list(APPEND WOLFSSL_LIBRARIES ${MATH_LIBRARY}) # for log and pow + endif() + mark_as_advanced(MATH_LIBRARY) endif() diff --git a/Utilities/cmcurl/CMake/FindZstd.cmake b/Utilities/cmcurl/CMake/FindZstd.cmake index 973e6ad4a90..0ea1fef4672 100644 --- a/Utilities/cmcurl/CMake/FindZstd.cmake +++ b/Utilities/cmcurl/CMake/FindZstd.cmake @@ -21,51 +21,82 @@ # SPDX-License-Identifier: curl # ########################################################################### +# Find the zstd library +# +# Input variables: +# +# - `ZSTD_INCLUDE_DIR`: The zstd include directory. +# - `ZSTD_LIBRARY`: Path to `zstd` library. +# +# Result variables: +# +# - `ZSTD_FOUND`: System has zstd. +# - `ZSTD_INCLUDE_DIRS`: The zstd include directories. +# - `ZSTD_LIBRARIES`: The zstd library names. +# - `ZSTD_LIBRARY_DIRS`: The zstd library directories. +# - `ZSTD_PC_REQUIRES`: The zstd pkg-config packages. +# - `ZSTD_CFLAGS`: Required compiler flags. +# - `ZSTD_VERSION`: Version of zstd. -#[=======================================================================[.rst: -FindZstd ----------- - -Find the zstd library - -Result Variables -^^^^^^^^^^^^^^^^ +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() -``Zstd_FOUND`` - System has zstd -``Zstd_INCLUDE_DIRS`` - The zstd include directories. -``Zstd_LIBRARIES`` - The libraries needed to use zstd -#]=======================================================================] +set(ZSTD_PC_REQUIRES "libzstd") -if(UNIX) +if(CURL_USE_PKGCONFIG AND + NOT DEFINED ZSTD_INCLUDE_DIR AND + NOT DEFINED ZSTD_LIBRARY) find_package(PkgConfig QUIET) - pkg_search_module(PC_Zstd libzstd) + pkg_check_modules(ZSTD ${ZSTD_PC_REQUIRES}) endif() -find_path(Zstd_INCLUDE_DIR zstd.h - HINTS - ${PC_Zstd_INCLUDEDIR} - ${PC_Zstd_INCLUDE_DIRS} -) +if(ZSTD_FOUND) + set(Zstd_FOUND TRUE) + string(REPLACE ";" " " ZSTD_CFLAGS "${ZSTD_CFLAGS}") + message(STATUS "Found Zstd (via pkg-config): ${ZSTD_INCLUDE_DIRS} (found version \"${ZSTD_VERSION}\")") +else() + find_path(ZSTD_INCLUDE_DIR NAMES "zstd.h") + find_library(ZSTD_LIBRARY NAMES "zstd") -find_library(Zstd_LIBRARY NAMES zstd - HINTS - ${PC_Zstd_LIBDIR} - ${PC_Zstd_LIBRARY_DIRS} -) + unset(ZSTD_VERSION CACHE) + if(ZSTD_INCLUDE_DIR AND EXISTS "${ZSTD_INCLUDE_DIR}/zstd.h") + set(_version_regex1 "#[\t ]*define[ \t]+ZSTD_VERSION_MAJOR[ \t]+([0-9]+).*") + set(_version_regex2 "#[\t ]*define[ \t]+ZSTD_VERSION_MINOR[ \t]+([0-9]+).*") + set(_version_regex3 "#[\t ]*define[ \t]+ZSTD_VERSION_RELEASE[ \t]+([0-9]+).*") + file(STRINGS "${ZSTD_INCLUDE_DIR}/zstd.h" _version_str1 REGEX "${_version_regex1}") + file(STRINGS "${ZSTD_INCLUDE_DIR}/zstd.h" _version_str2 REGEX "${_version_regex2}") + file(STRINGS "${ZSTD_INCLUDE_DIR}/zstd.h" _version_str3 REGEX "${_version_regex3}") + string(REGEX REPLACE "${_version_regex1}" "\\1" _version_str1 "${_version_str1}") + string(REGEX REPLACE "${_version_regex2}" "\\1" _version_str2 "${_version_str2}") + string(REGEX REPLACE "${_version_regex3}" "\\1" _version_str3 "${_version_str3}") + set(ZSTD_VERSION "${_version_str1}.${_version_str2}.${_version_str3}") + unset(_version_regex1) + unset(_version_regex2) + unset(_version_regex3) + unset(_version_str1) + unset(_version_str2) + unset(_version_str3) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Zstd - REQUIRED_VARS - Zstd_LIBRARY - Zstd_INCLUDE_DIR -) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Zstd + REQUIRED_VARS + ZSTD_INCLUDE_DIR + ZSTD_LIBRARY + VERSION_VAR + ZSTD_VERSION + ) -if(Zstd_FOUND) - set(Zstd_LIBRARIES ${Zstd_LIBRARY}) - set(Zstd_INCLUDE_DIRS ${Zstd_INCLUDE_DIR}) -endif() + if(ZSTD_FOUND) + set(ZSTD_INCLUDE_DIRS ${ZSTD_INCLUDE_DIR}) + set(ZSTD_LIBRARIES ${ZSTD_LIBRARY}) + endif() -mark_as_advanced(Zstd_INCLUDE_DIRS Zstd_LIBRARIES) + mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY) +endif() diff --git a/Utilities/cmcurl/CMake/Macros.cmake b/Utilities/cmcurl/CMake/Macros.cmake index e12bf303f48..e3a654b5f36 100644 --- a/Utilities/cmcurl/CMake/Macros.cmake +++ b/Utilities/cmcurl/CMake/Macros.cmake @@ -21,102 +21,76 @@ # SPDX-License-Identifier: curl # ########################################################################### -#File defines convenience macros for available feature testing - -# This macro checks if the symbol exists in the library and if it -# does, it prepends library to the list. It is intended to be called -# multiple times with a sequence of possibly dependent libraries in -# order of least-to-most-dependent. Some libraries depend on others -# to link correctly. -macro(check_library_exists_concat LIBRARY SYMBOL VARIABLE) - check_library_exists("${LIBRARY};${CURL_LIBS}" ${SYMBOL} "${CMAKE_LIBRARY_PATH}" - ${VARIABLE}) - if(${VARIABLE}) - set(CURL_LIBS ${LIBRARY} ${CURL_LIBS}) - endif() -endmacro() +# File defines convenience macros for available feature testing # Check if header file exists and add it to the list. # This macro is intended to be called multiple times with a sequence of # possibly dependent header files. Some headers depend on others to be # compiled correctly. -macro(check_include_file_concat FILE VARIABLE) - check_include_files("${CURL_INCLUDES};${FILE}" ${VARIABLE}) - if(${VARIABLE}) - set(CURL_INCLUDES ${CURL_INCLUDES} ${FILE}) - set(CURL_TEST_DEFINES "${CURL_TEST_DEFINES} -D${VARIABLE}") +macro(check_include_file_concat_curl _file _variable) + check_include_files("${CURL_INCLUDES};${_file}" ${_variable}) + if(${_variable}) + list(APPEND CURL_INCLUDES ${_file}) endif() endmacro() +set(CURL_TEST_DEFINES "") # Initialize global variable + # For other curl specific tests, use this macro. -macro(curl_internal_test CURL_TEST) - if(NOT DEFINED "${CURL_TEST}") - set(MACRO_CHECK_FUNCTION_DEFINITIONS - "-D${CURL_TEST} ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS}") +# Return result in variable: CURL_TEST_OUTPUT +macro(curl_internal_test _curl_test) + if(NOT DEFINED "${_curl_test}") + string(REPLACE ";" " " _cmake_required_definitions "${CMAKE_REQUIRED_DEFINITIONS}") + set(_curl_test_add_libraries "") if(CMAKE_REQUIRED_LIBRARIES) - set(CURL_TEST_ADD_LIBRARIES + set(_curl_test_add_libraries "-DLINK_LIBRARIES:STRING=${CMAKE_REQUIRED_LIBRARIES}") endif() - message(STATUS "Performing Curl Test ${CURL_TEST}") - try_compile(${CURL_TEST} - ${CMAKE_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c - CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS} - "${CURL_TEST_ADD_LIBRARIES}" - OUTPUT_VARIABLE OUTPUT) - if(${CURL_TEST}) - set(${CURL_TEST} 1 CACHE INTERNAL "Curl test ${FUNCTION}") - message(STATUS "Performing Curl Test ${CURL_TEST} - Success") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Performing Curl Test ${CURL_TEST} passed with the following output:\n" - "${OUTPUT}\n") + message(STATUS "Performing Test ${_curl_test}") + try_compile(${_curl_test} + ${PROJECT_BINARY_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c" + CMAKE_FLAGS + "-DCOMPILE_DEFINITIONS:STRING=-D${_curl_test} ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS} ${_cmake_required_definitions}" + "${_curl_test_add_libraries}" + OUTPUT_VARIABLE CURL_TEST_OUTPUT) + if(${_curl_test}) + set(${_curl_test} 1 CACHE INTERNAL "Curl test") + message(STATUS "Performing Test ${_curl_test} - Success") else() - message(STATUS "Performing Curl Test ${CURL_TEST} - Failed") - set(${CURL_TEST} "" CACHE INTERNAL "Curl test ${FUNCTION}") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Performing Curl Test ${CURL_TEST} failed with the following output:\n" - "${OUTPUT}\n") + set(${_curl_test} "" CACHE INTERNAL "Curl test") + message(STATUS "Performing Test ${_curl_test} - Failed") endif() endif() endmacro() -macro(curl_nroff_check) - find_program(NROFF NAMES gnroff nroff) - if(NROFF) - # Need a way to write to stdin, this will do - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/nroff-input.txt" "test") - # Tests for a valid nroff option to generate a manpage - foreach(_MANOPT "-man" "-mandoc") - execute_process(COMMAND "${NROFF}" ${_MANOPT} - OUTPUT_VARIABLE NROFF_MANOPT_OUTPUT - INPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/nroff-input.txt" - ERROR_QUIET) - # Save the option if it was valid - if(NROFF_MANOPT_OUTPUT) - message("Found *nroff option: -- ${_MANOPT}") - set(NROFF_MANOPT ${_MANOPT}) - set(NROFF_USEFUL ON) - break() - endif() +macro(curl_dependency_option _option_name _find_name _desc_name) + set(${_option_name} "AUTO" CACHE STRING "Build curl with ${_desc_name} support (AUTO, ON or OFF)") + set_property(CACHE ${_option_name} PROPERTY STRINGS "AUTO" "ON" "OFF") + + if(${_option_name} STREQUAL "AUTO") + find_package(${_find_name}) + elseif(${_option_name}) + find_package(${_find_name} REQUIRED) + endif() +endmacro() + +# Convert the passed paths to libpath linker options and add them to CMAKE_REQUIRED_*. +macro(curl_required_libpaths _libpaths_arg) + if(CMAKE_VERSION VERSION_LESS 3.31) + set(_libpaths "${_libpaths_arg}") + foreach(_libpath IN LISTS _libpaths) + list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "${CMAKE_LIBRARY_PATH_FLAG}${_libpath}") endforeach() - # No need for the temporary file - file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/nroff-input.txt") - if(NOT NROFF_USEFUL) - message(WARNING "Found no *nroff option to get plaintext from man pages") - endif() else() - message(WARNING "Found no *nroff program") + list(APPEND CMAKE_REQUIRED_LINK_DIRECTORIES "${_libpaths_arg}") endif() endmacro() -macro(optional_dependency DEPENDENCY) - set(CURL_${DEPENDENCY} AUTO CACHE STRING "Build curl with ${DEPENDENCY} support (AUTO, ON or OFF)") - set_property(CACHE CURL_${DEPENDENCY} PROPERTY STRINGS AUTO ON OFF) - - if(CURL_${DEPENDENCY} STREQUAL AUTO) - find_package(${DEPENDENCY}) - elseif(CURL_${DEPENDENCY}) - find_package(${DEPENDENCY} REQUIRED) - endif() +# Pre-fill variables set by a check_type_size() call. +macro(curl_prefill_type_size _type _size) + set(HAVE_SIZEOF_${_type} TRUE) + set(SIZEOF_${_type} ${_size}) + set(SIZEOF_${_type}_CODE "#define SIZEOF_${_type} ${_size}") endmacro() diff --git a/Utilities/cmcurl/CMake/OtherTests.cmake b/Utilities/cmcurl/CMake/OtherTests.cmake index fa1e458e611..26e9d821c2b 100644 --- a/Utilities/cmcurl/CMake/OtherTests.cmake +++ b/Utilities/cmcurl/CMake/OtherTests.cmake @@ -22,115 +22,136 @@ # ########################################################################### include(CheckCSourceCompiles) -# The begin of the sources (macros and includes) -set(_source_epilogue "#undef inline") +include(CheckCSourceRuns) +include(CheckTypeSize) -macro(add_header_include check header) - if(${check}) - set(_source_epilogue "${_source_epilogue}\n#include <${header}>") +macro(curl_add_header_include _check _header) + if(${_check}) + set(_source_epilogue "${_source_epilogue} + #include <${_header}>") endif() endmacro() -set(signature_call_conv) -if(HAVE_WINDOWS_H) - add_header_include(HAVE_WINSOCK2_H "winsock2.h") - add_header_include(HAVE_WINDOWS_H "windows.h") - set(_source_epilogue - "${_source_epilogue}\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif") - set(signature_call_conv "PASCAL") - if(HAVE_LIBWS2_32) - set(CMAKE_REQUIRED_LIBRARIES ws2_32) +set(_cmake_try_compile_target_type_save ${CMAKE_TRY_COMPILE_TARGET_TYPE}) +set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + +if(NOT DEFINED HAVE_STRUCT_SOCKADDR_STORAGE) + cmake_push_check_state() + set(CMAKE_EXTRA_INCLUDE_FILES "") + if(WIN32) + set(CMAKE_EXTRA_INCLUDE_FILES "winsock2.h") + list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32") + elseif(HAVE_SYS_SOCKET_H) + set(CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h") endif() -else() - add_header_include(HAVE_SYS_TYPES_H "sys/types.h") - add_header_include(HAVE_SYS_SOCKET_H "sys/socket.h") + check_type_size("struct sockaddr_storage" SIZEOF_STRUCT_SOCKADDR_STORAGE) + set(HAVE_STRUCT_SOCKADDR_STORAGE ${HAVE_SIZEOF_STRUCT_SOCKADDR_STORAGE}) + cmake_pop_check_state() endif() -set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +if(NOT WIN32) + set(_source_epilogue "#undef inline") + curl_add_header_include(HAVE_SYS_TYPES_H "sys/types.h") + curl_add_header_include(HAVE_SYS_SOCKET_H "sys/socket.h") + check_c_source_compiles("${_source_epilogue} + int main(void) + { + int flag = MSG_NOSIGNAL; + (void)flag; + return 0; + }" HAVE_MSG_NOSIGNAL) +endif() +set(_source_epilogue "#undef inline") +curl_add_header_include(HAVE_SYS_TIME_H "sys/time.h") check_c_source_compiles("${_source_epilogue} - int main(void) { - int flag = MSG_NOSIGNAL; - (void)flag; + #ifdef _MSC_VER + #include + #endif + #include + int main(void) + { + struct timeval ts; + ts.tv_sec = 0; + ts.tv_usec = 0; + (void)ts; return 0; - }" HAVE_MSG_NOSIGNAL) - -if(NOT HAVE_WINDOWS_H) - add_header_include(HAVE_SYS_TIME_H "sys/time.h") - add_header_include(TIME_WITH_SYS_TIME "time.h") - add_header_include(HAVE_TIME_H "time.h") -endif() -check_c_source_compiles("${_source_epilogue} -int main(void) { - struct timeval ts; - ts.tv_sec = 0; - ts.tv_usec = 0; - (void)ts; - return 0; -}" HAVE_STRUCT_TIMEVAL) - -if(HAVE_WINDOWS_H) - set(CMAKE_EXTRA_INCLUDE_FILES winsock2.h) -else() - set(CMAKE_EXTRA_INCLUDE_FILES) - if(HAVE_SYS_SOCKET_H) - set(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h) - endif() -endif() - -check_type_size("struct sockaddr_storage" SIZEOF_STRUCT_SOCKADDR_STORAGE) -if(HAVE_SIZEOF_STRUCT_SOCKADDR_STORAGE) - set(HAVE_STRUCT_SOCKADDR_STORAGE 1) + }" HAVE_STRUCT_TIMEVAL) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE ${_cmake_try_compile_target_type_save}) +unset(_cmake_try_compile_target_type_save) + +# Detect HAVE_GETADDRINFO_THREADSAFE + +if(WIN32) + set(HAVE_GETADDRINFO_THREADSAFE ${HAVE_GETADDRINFO}) +elseif(NOT HAVE_GETADDRINFO) + set(HAVE_GETADDRINFO_THREADSAFE FALSE) +elseif(APPLE OR + CMAKE_SYSTEM_NAME STREQUAL "AIX" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "HP-UX" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "SunOS") + set(HAVE_GETADDRINFO_THREADSAFE TRUE) +elseif(BSD OR CMAKE_SYSTEM_NAME MATCHES "BSD") + set(HAVE_GETADDRINFO_THREADSAFE FALSE) endif() -unset(CMAKE_TRY_COMPILE_TARGET_TYPE) - -if(NOT CMAKE_CROSSCOMPILING) - if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "iOS") - # only try this on non-apple platforms - - # if not cross-compilation... - include(CheckCSourceRuns) - set(CMAKE_REQUIRED_FLAGS "") - if(HAVE_SYS_POLL_H) - set(CMAKE_REQUIRED_FLAGS "-DHAVE_SYS_POLL_H") - elseif(HAVE_POLL_H) - set(CMAKE_REQUIRED_FLAGS "-DHAVE_POLL_H") - endif() - check_c_source_runs(" - #include - #include - - #ifdef HAVE_SYS_POLL_H - # include - #elif HAVE_POLL_H - # include - #endif - +if(NOT DEFINED HAVE_GETADDRINFO_THREADSAFE) + set(_source_epilogue "#undef inline") + curl_add_header_include(HAVE_SYS_SOCKET_H "sys/socket.h") + curl_add_header_include(HAVE_SYS_TIME_H "sys/time.h") + curl_add_header_include(HAVE_NETDB_H "netdb.h") + check_c_source_compiles("${_source_epilogue} + int main(void) + { + #ifndef h_errno + #error force compilation error + #endif + return 0; + }" HAVE_H_ERRNO) + + if(NOT HAVE_H_ERRNO) + check_c_source_compiles("${_source_epilogue} int main(void) { - if(0 != poll(0, 0, 10)) { - return 1; /* fail */ - } - else { - /* detect the 10.12 poll() breakage */ - struct timeval before, after; - int rc; - size_t us; - - gettimeofday(&before, NULL); - rc = poll(NULL, 0, 500); - gettimeofday(&after, NULL); - - us = (after.tv_sec - before.tv_sec) * 1000000 + - (after.tv_usec - before.tv_usec); - - if(us < 400000) { - return 1; - } - } + h_errno = 2; + return h_errno != 0 ? 1 : 0; + }" HAVE_H_ERRNO_ASSIGNABLE) + + if(NOT HAVE_H_ERRNO_ASSIGNABLE) + check_c_source_compiles("${_source_epilogue} + int main(void) + { + #if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200809L) + #elif defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 700) + #else + #error force compilation error + #endif return 0; - }" HAVE_POLL_FINE) + }" HAVE_H_ERRNO_SBS_ISSUE_7) + endif() + endif() + + if(HAVE_H_ERRNO OR HAVE_H_ERRNO_ASSIGNABLE OR HAVE_H_ERRNO_SBS_ISSUE_7) + set(HAVE_GETADDRINFO_THREADSAFE TRUE) endif() endif() +if(NOT WIN32 AND NOT DEFINED HAVE_CLOCK_GETTIME_MONOTONIC_RAW) + set(_source_epilogue "#undef inline") + curl_add_header_include(HAVE_SYS_TYPES_H "sys/types.h") + curl_add_header_include(HAVE_SYS_TIME_H "sys/time.h") + check_c_source_compiles("${_source_epilogue} + #include + int main(void) + { + struct timespec ts; + (void)clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + return 0; + }" HAVE_CLOCK_GETTIME_MONOTONIC_RAW) +endif() + +unset(_source_epilogue) diff --git a/Utilities/cmcurl/CMake/PickyWarnings.cmake b/Utilities/cmcurl/CMake/PickyWarnings.cmake index 1310cb4fbe2..be26d05cb8b 100644 --- a/Utilities/cmcurl/CMake/PickyWarnings.cmake +++ b/Utilities/cmcurl/CMake/PickyWarnings.cmake @@ -23,38 +23,75 @@ ########################################################################### include(CheckCCompilerFlag) +set(_picky "") +set(_picky_nocheck "") # not to pass to feature checks + +if(CURL_WERROR) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + set(CMAKE_COMPILE_WARNING_AS_ERROR ON) + else() + if(MSVC) + list(APPEND _picky_nocheck "-WX") + else() # llvm/clang and gcc style options + list(APPEND _picky_nocheck "-Werror") + endif() + endif() + + if((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND + NOT DOS AND # Watt-32 headers use the '#include_next' GCC extension + CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 5.0) OR + CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND _picky_nocheck "-pedantic-errors") + endif() +endif() + +if(APPLE AND + (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.6) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 6.3)) + list(APPEND _picky "-Werror=partial-availability") # clang 3.6 appleclang 6.3 +endif() + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND _picky "-Werror-implicit-function-declaration") # clang 1.0 gcc 2.95 +endif() + +if(MSVC) + list(APPEND _picky "-W4") # Use the highest warning level for Visual Studio. +elseif(BORLAND) + list(APPEND _picky "-w-") # Disable warnings on Borland to avoid changing 3rd party code. +endif() + if(PICKY_COMPILER) - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") # https://clang.llvm.org/docs/DiagnosticsReference.html # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html - # WPICKY_ENABLE = Options we want to enable as-is. - # WPICKY_DETECT = Options we want to test first and enable if available. + # _picky_enable = Options we want to enable as-is. + # _picky_detect = Options we want to test first and enable if available. # Prefer the -Wextra alias with clang. if(CMAKE_C_COMPILER_ID MATCHES "Clang") - set(WPICKY_ENABLE "-Wextra") + set(_picky_enable "-Wextra") else() - set(WPICKY_ENABLE "-W") + set(_picky_enable "-W") endif() - list(APPEND WPICKY_ENABLE + list(APPEND _picky_enable -Wall -pedantic ) # ---------------------------------- # Add new options here, if in doubt: # ---------------------------------- - set(WPICKY_DETECT + set(_picky_detect ) # Assume these options always exist with both clang and gcc. # Require clang 3.0 / gcc 2.95 or later. - list(APPEND WPICKY_ENABLE - -Wbad-function-cast # clang 3.0 gcc 2.95 - -Wconversion # clang 3.0 gcc 2.95 - -Winline # clang 1.0 gcc 1.0 + list(APPEND _picky_enable + -Wbad-function-cast # clang 2.7 gcc 2.95 + -Wconversion # clang 2.7 gcc 2.95 -Wmissing-declarations # clang 1.0 gcc 2.7 -Wmissing-prototypes # clang 1.0 gcc 1.0 -Wnested-externs # clang 1.0 gcc 2.7 @@ -69,129 +106,255 @@ if(PICKY_COMPILER) ) # Always enable with clang, version dependent with gcc - set(WPICKY_COMMON_OLD + set(_picky_common_old + -Waddress # clang 2.7 gcc 4.3 + -Wattributes # clang 2.7 gcc 4.1 -Wcast-align # clang 1.0 gcc 4.2 + -Wcast-qual # clang 3.0 gcc 3.4.6 -Wdeclaration-after-statement # clang 1.0 gcc 3.4 - -Wempty-body # clang 3.0 gcc 4.3 + -Wdiv-by-zero # clang 2.7 gcc 4.1 + -Wempty-body # clang 2.7 gcc 4.3 -Wendif-labels # clang 1.0 gcc 3.3 -Wfloat-equal # clang 1.0 gcc 2.96 (3.0) - -Wignored-qualifiers # clang 3.0 gcc 4.3 + -Wformat-security # clang 2.7 gcc 4.1 + -Wignored-qualifiers # clang 2.8 gcc 4.3 + -Wmissing-field-initializers # clang 2.7 gcc 4.1 + -Wmissing-noreturn # clang 2.7 gcc 4.1 -Wno-format-nonliteral # clang 1.0 gcc 2.96 (3.0) - -Wno-sign-conversion # clang 3.0 gcc 4.3 + -Wno-sign-conversion # clang 2.9 gcc 4.3 -Wno-system-headers # clang 1.0 gcc 3.0 + # -Wpadded # clang 2.9 gcc 4.1 # Not used: We cannot change public structs + -Wold-style-definition # clang 2.7 gcc 3.4 + -Wredundant-decls # clang 2.7 gcc 4.1 -Wstrict-prototypes # clang 1.0 gcc 3.3 - -Wtype-limits # clang 3.0 gcc 4.3 + # -Wswitch-enum # clang 2.7 gcc 4.1 # Not used: It basically disallows default case + -Wtype-limits # clang 2.7 gcc 4.3 + -Wunreachable-code # clang 2.7 gcc 4.1 + # -Wunused-macros # clang 2.7 gcc 4.1 # Not practical + # -Wno-error=unused-macros # clang 2.7 gcc 4.1 + -Wunused-parameter # clang 2.7 gcc 4.1 -Wvla # clang 2.8 gcc 4.3 ) - set(WPICKY_COMMON - -Wdouble-promotion # clang 3.6 gcc 4.6 appleclang 6.3 - -Wenum-conversion # clang 3.2 gcc 10.0 appleclang 4.6 g++ 11.0 - -Wunused-const-variable # clang 3.4 gcc 6.0 appleclang 5.1 - ) - if(CMAKE_C_COMPILER_ID MATCHES "Clang") - list(APPEND WPICKY_ENABLE - ${WPICKY_COMMON_OLD} + list(APPEND _picky_enable + ${_picky_common_old} -Wshift-sign-overflow # clang 2.9 -Wshorten-64-to-32 # clang 1.0 + -Wformat=2 # clang 3.0 gcc 4.8 ) + if(NOT MSVC) + list(APPEND _picky_enable + -Wlanguage-extension-token # clang 3.0 + ) + endif() # Enable based on compiler version - if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.3)) - list(APPEND WPICKY_ENABLE - ${WPICKY_COMMON} + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.6) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 6.3)) + list(APPEND _picky_enable + -Wdouble-promotion # clang 3.6 gcc 4.6 appleclang 6.3 + -Wenum-conversion # clang 3.2 gcc 10.0 appleclang 4.6 g++ 11.0 + -Wheader-guard # clang 3.4 appleclang 5.1 + -Wpragmas # clang 3.5 gcc 4.1 appleclang 6.0 + -Wsometimes-uninitialized # clang 3.2 appleclang 4.6 + # -Wunreachable-code-break # clang 3.5 appleclang 6.0 # Not used: Silent in "unity" builds + -Wunused-const-variable # clang 3.4 gcc 6.0 appleclang 5.1 ) endif() - if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.3)) - list(APPEND WPICKY_ENABLE + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.9) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8.3)) + list(APPEND _picky_enable -Wcomma # clang 3.9 appleclang 8.3 -Wmissing-variable-declarations # clang 3.2 appleclang 4.6 ) endif() - if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.3)) - list(APPEND WPICKY_ENABLE + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 7.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10.3)) + list(APPEND _picky_enable -Wassign-enum # clang 7.0 appleclang 10.3 -Wextra-semi-stmt # clang 7.0 appleclang 10.3 ) endif() + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.4)) + list(APPEND _picky_enable + -Wimplicit-fallthrough # clang 4.0 gcc 7.0 appleclang 12.4 # We do silencing for clang 10.0 and above only + -Wxor-used-as-pow # clang 10.0 gcc 13.0 + ) + endif() else() # gcc - list(APPEND WPICKY_DETECT - ${WPICKY_COMMON} - ) # Enable based on compiler version - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.3) - list(APPEND WPICKY_ENABLE - ${WPICKY_COMMON_OLD} + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.3) + list(APPEND _picky_enable + ${_picky_common_old} + -Wclobbered # gcc 4.3 -Wmissing-parameter-type # gcc 4.3 -Wold-style-declaration # gcc 4.3 + -Wpragmas # clang 3.5 gcc 4.1 appleclang 6.0 -Wstrict-aliasing=3 # gcc 4.0 + -ftree-vrp # gcc 4.3 (required for -Warray-bounds, included in -Wall) ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5 AND MINGW) - list(APPEND WPICKY_ENABLE - -Wno-pedantic-ms-format # gcc 4.5 (mingw-only) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.5) + list(APPEND _picky_enable + -Wjump-misses-init # gcc 4.5 ) + if(MINGW) + list(APPEND _picky_enable + -Wno-pedantic-ms-format # gcc 4.5 (MinGW-only) + ) + endif() endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8) - list(APPEND WPICKY_ENABLE - -Wformat=2 # clang 3.0 gcc 4.8 (clang part-default, enabling it fully causes -Wformat-nonliteral warnings) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.8) + list(APPEND _picky_enable + -Wdouble-promotion # clang 3.6 gcc 4.6 appleclang 6.3 + -Wformat=2 # clang 3.0 gcc 4.8 + -Wtrampolines # gcc 4.6 ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0) - list(APPEND WPICKY_ENABLE - -Warray-bounds=2 -ftree-vrp # clang 3.0 gcc 5.0 (clang default: -Warray-bounds) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 5.0) + list(APPEND _picky_enable + -Warray-bounds=2 # clang 3.0 gcc 5.0 (clang default: -Warray-bounds) ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.0) - list(APPEND WPICKY_ENABLE + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 6.0) + list(APPEND _picky_enable -Wduplicated-cond # gcc 6.0 -Wnull-dereference # clang 3.0 gcc 6.0 (clang default) -fdelete-null-pointer-checks -Wshift-negative-value # clang 3.7 gcc 6.0 (clang default) -Wshift-overflow=2 # clang 3.0 gcc 6.0 (clang default: -Wshift-overflow) + -Wunused-const-variable # clang 3.4 gcc 6.0 appleclang 5.1 ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) - list(APPEND WPICKY_ENABLE + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 7.0) + list(APPEND _picky_enable -Walloc-zero # gcc 7.0 -Wduplicated-branches # gcc 7.0 - -Wformat-overflow=2 # gcc 7.0 - -Wformat-truncation=1 # gcc 7.0 + -Wformat-truncation=2 # gcc 7.0 + -Wimplicit-fallthrough # clang 4.0 gcc 7.0 -Wrestrict # gcc 7.0 ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.0) - list(APPEND WPICKY_ENABLE + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) + list(APPEND _picky_enable -Warith-conversion # gcc 10.0 + -Wenum-conversion # clang 3.2 gcc 10.0 appleclang 4.6 g++ 11.0 + ) + endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0) + list(APPEND _picky_enable + -Warray-compare # clang 20.0 gcc 12.0 + -Wenum-int-mismatch # gcc 13.0 + -Wxor-used-as-pow # clang 10.0 gcc 13.0 + ) + endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0) + list(APPEND _picky_enable + -Wleading-whitespace=spaces # gcc 15.0 + -Wtrailing-whitespace=any # gcc 15.0 + -Wunterminated-string-initialization # gcc 15.0 ) endif() endif() # - unset(WPICKY) - - foreach(_CCOPT ${WPICKY_ENABLE}) - set(WPICKY "${WPICKY} ${_CCOPT}") + set(_picky_skipped "") + foreach(_ccopt IN LISTS _picky_enable) + string(REGEX MATCH "-W([a-z0-9-]+)" _ccmatch "${_ccopt}") + if(_ccmatch AND CMAKE_C_FLAGS MATCHES "-Wno-${CMAKE_MATCH_1}" AND NOT _ccopt STREQUAL "-Wall" AND NOT _ccopt MATCHES "^-Wno-") + string(APPEND _picky_skipped " ${_ccopt}") + else() + list(APPEND _picky "${_ccopt}") + endif() endforeach() + if(_picky_skipped) + message(STATUS "Picky compiler options skipped due to CMAKE_C_FLAGS override:${_picky_skipped}") + endif() - foreach(_CCOPT ${WPICKY_DETECT}) - # surprisingly, CHECK_C_COMPILER_FLAG needs a new variable to store each new - # test result in. - string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname) + foreach(_ccopt IN LISTS _picky_detect) + # Use a unique variable name 1. for meaningful log output 2. to have a fresh, undefined variable for each detection + string(MAKE_C_IDENTIFIER "OPT${_ccopt}" _optvarname) # GCC only warns about unknown -Wno- options if there are also other diagnostic messages, # so test for the positive form instead - string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}") - check_c_compiler_flag(${_CCOPT_ON} ${_optvarname}) + string(REPLACE "-Wno-" "-W" _ccopt_on "${_ccopt}") + check_c_compiler_flag(${_ccopt_on} ${_optvarname}) if(${_optvarname}) - set(WPICKY "${WPICKY} ${_CCOPT}") + list(APPEND _picky "${_ccopt}") endif() endforeach() - message(STATUS "Picky compiler options:${WPICKY}") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WPICKY}") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + if(CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5) + # Avoid false positives + list(APPEND _picky "-Wno-shadow") + list(APPEND _picky "-Wno-unreachable-code") + endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.2 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.6) + # GCC <4.6 do not support #pragma to suppress warnings locally. Disable them globally instead. + list(APPEND _picky "-Wno-overlength-strings") + endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.0 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.7) + list(APPEND _picky "-Wno-missing-field-initializers") # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36750 + endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.3 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8) + list(APPEND _picky "-Wno-type-limits") # Avoid false positives + endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 5.1 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5.5) + list(APPEND _picky "-Wno-conversion") # Avoid false positives + endif() + endif() + elseif(MSVC AND MSVC_VERSION LESS_EQUAL 1943) # Skip for untested/unreleased newer versions + list(APPEND _picky "-Wall") + list(APPEND _picky "-wd4061") # enumerator 'A' in switch of enum 'B' is not explicitly handled by a case label + list(APPEND _picky "-wd4191") # 'type cast': unsafe conversion from 'FARPROC' to 'void (__cdecl *)(void)' + list(APPEND _picky "-wd4255") # no function prototype given: converting '()' to '(void)' (in winuser.h) + list(APPEND _picky "-wd4464") # relative include path contains '..' + list(APPEND _picky "-wd4548") # expression before comma has no effect; expected expression with side-effect (in FD_SET()) + list(APPEND _picky "-wd4574") # 'M' is defined to be '0': did you mean to use '#if M'? (in ws2tcpip.h) + list(APPEND _picky "-wd4668") # 'M' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' (in winbase.h) + list(APPEND _picky "-wd4710") # 'snprintf': function not inlined + list(APPEND _picky "-wd4711") # function 'A' selected for automatic inline expansion + list(APPEND _picky "-wd4746") # volatile access of '' is subject to /volatile: setting; + # consider using __iso_volatile_load/store intrinsic functions (ARM64) + list(APPEND _picky "-wd4774") # 'snprintf': format string expected in argument 3 is not a string literal + list(APPEND _picky "-wd4820") # 'A': 'N' bytes padding added after data member 'B' + if(MSVC_VERSION GREATER_EQUAL 1900) + list(APPEND _picky "-wd5045") # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified + endif() endif() endif() + +# clang-cl +if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND MSVC) + list(APPEND _picky "-Wno-language-extension-token") # Allow __int64 + + foreach(_wlist IN ITEMS _picky_nocheck _picky) + set(_picky_tmp "") + foreach(_ccopt IN LISTS "${_wlist}") + # Prefix -Wall, otherwise clang-cl interprets it as an MSVC option and translates it to -Weverything + if(_ccopt MATCHES "^-W" AND NOT _ccopt STREQUAL "-Wall") + list(APPEND _picky_tmp ${_ccopt}) + else() + list(APPEND _picky_tmp "-clang:${_ccopt}") + endif() + endforeach() + set("${_wlist}" ${_picky_tmp}) + endforeach() +endif() + +if(_picky_nocheck OR _picky) + set(_picky_tmp "${_picky_nocheck}" "${_picky}") + string(REPLACE ";" " " _picky_tmp "${_picky_tmp}") + string(STRIP "${_picky_tmp}" _picky_tmp) + message(STATUS "Picky compiler options: ${_picky_tmp}") + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "${_picky_nocheck}" "${_picky}") + + # Apply to all feature checks + string(REPLACE ";" " " _picky_tmp "${_picky}") + string(APPEND CMAKE_REQUIRED_FLAGS " ${_picky_tmp}") + + unset(_picky) + unset(_picky_tmp) +endif() diff --git a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake b/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake deleted file mode 100644 index 37712377e4c..00000000000 --- a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake +++ /dev/null @@ -1,91 +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 -# -########################################################################### -if(NOT UNIX) - if(WIN32) - set(HAVE_LIBSOCKET 0) - set(HAVE_GETHOSTNAME 1) - set(HAVE_LIBZ 0) - - set(HAVE_ARPA_INET_H 0) - set(HAVE_FCNTL_H 1) - set(HAVE_IO_H 1) - set(HAVE_NETDB_H 0) - set(HAVE_NETINET_IN_H 0) - set(HAVE_NET_IF_H 0) - set(HAVE_PWD_H 0) - set(HAVE_SETJMP_H 1) - set(HAVE_SIGNAL_H 1) - set(HAVE_STDLIB_H 1) - set(HAVE_STRINGS_H 0) - set(HAVE_STRING_H 1) - set(HAVE_SYS_PARAM_H 0) - set(HAVE_SYS_POLL_H 0) - set(HAVE_SYS_SELECT_H 0) - set(HAVE_SYS_SOCKET_H 0) - set(HAVE_SYS_SOCKIO_H 0) - set(HAVE_SYS_STAT_H 1) - set(HAVE_SYS_TIME_H 0) - set(HAVE_SYS_TYPES_H 1) - set(HAVE_SYS_UTIME_H 1) - set(HAVE_TERMIOS_H 0) - set(HAVE_TERMIO_H 0) - set(HAVE_TIME_H 1) - set(HAVE_UTIME_H 0) - - set(HAVE_SOCKET 1) - set(HAVE_SELECT 1) - set(HAVE_STRDUP 1) - set(HAVE_STRICMP 1) - set(HAVE_STRCMPI 1) - set(HAVE_GETTIMEOFDAY 0) - set(HAVE_CLOSESOCKET 1) - set(HAVE_SIGSETJMP 0) - set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1) - set(HAVE_GETPASS_R 0) - set(HAVE_GETPWUID 0) - set(HAVE_GETEUID 0) - set(HAVE_UTIME 1) - set(HAVE_RAND_EGD 0) - set(HAVE_GMTIME_R 0) - set(HAVE_GETHOSTBYNAME_R 0) - set(HAVE_SIGNAL 1) - - set(HAVE_GETHOSTBYNAME_R_3 0) - set(HAVE_GETHOSTBYNAME_R_3_REENTRANT 0) - set(HAVE_GETHOSTBYNAME_R_5 0) - set(HAVE_GETHOSTBYNAME_R_5_REENTRANT 0) - set(HAVE_GETHOSTBYNAME_R_6 0) - set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 0) - - set(TIME_WITH_SYS_TIME 0) - set(HAVE_O_NONBLOCK 0) - set(HAVE_IN_ADDR_T 0) - set(STDC_HEADERS 1) - - set(HAVE_SIGACTION 0) - set(HAVE_MACRO_SIGSETJMP 0) - else() - message("This file should be included on Windows platform only") - endif() -endif() diff --git a/Utilities/cmcurl/CMake/Utilities.cmake b/Utilities/cmcurl/CMake/Utilities.cmake index 9ff38e3a0f1..ff2173fc08b 100644 --- a/Utilities/cmcurl/CMake/Utilities.cmake +++ b/Utilities/cmcurl/CMake/Utilities.cmake @@ -23,13 +23,31 @@ ########################################################################### # File containing various utilities -# Returns a list of arguments that evaluate to true -function(count_true output_count_var) - set(lst_len 0) - foreach(option_var IN LISTS ARGN) - if(${option_var}) - math(EXPR lst_len "${lst_len} + 1") +# Return number of arguments that evaluate to true +function(curl_count_true _output_count_var) + set(_list_len 0) + foreach(_option_var IN LISTS ARGN) + if(${_option_var}) + math(EXPR _list_len "${_list_len} + 1") endif() endforeach() - set(${output_count_var} ${lst_len} PARENT_SCOPE) + set(${_output_count_var} ${_list_len} PARENT_SCOPE) +endfunction() + +# Dump all defined variables with their values +function(curl_dumpvars) + message("::group::CMake Variable Dump") + get_cmake_property(_vars VARIABLES) + foreach(_var IN ITEMS ${_vars}) + get_property(_var_type CACHE ${_var} PROPERTY TYPE) + get_property(_var_advanced CACHE ${_var} PROPERTY ADVANCED) + if(_var_type) + set(_var_type ":${_var_type}") + endif() + if(_var_advanced) + set(_var_advanced " [adv]") + endif() + message("${_var}${_var_type}${_var_advanced} = '${${_var}}'") + endforeach() + message("::endgroup::") endfunction() diff --git a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in index 3e0742d1e49..e4f3eae34cb 100644 --- a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in +++ b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in @@ -30,20 +30,21 @@ if(NOT DEFINED CMAKE_INSTALL_PREFIX) endif() message(${CMAKE_INSTALL_PREFIX}) -file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) -string(REGEX REPLACE "\n" ";" files "${files}") -foreach(file ${files}) - message(STATUS "Uninstalling $ENV{DESTDIR}${file}") - if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E rm -f \"$ENV{DESTDIR}${file}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" _files) +string(REGEX REPLACE "\n" ";" _files "${_files}") +foreach(_file ${_files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${_file}") + if(IS_SYMLINK "$ENV{DESTDIR}${_file}" OR EXISTS "$ENV{DESTDIR}${_file}") + execute_process( + COMMAND "@CMAKE_COMMAND@" -E rm -f "$ENV{DESTDIR}${_file}" + RESULT_VARIABLE rm_retval + OUTPUT_QUIET + ERROR_QUIET + ) if(NOT "${rm_retval}" STREQUAL 0) - message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${_file}") endif() else() - message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + message(STATUS "File $ENV{DESTDIR}${_file} does not exist.") endif() endforeach() diff --git a/Utilities/cmcurl/CMake/curl-config.cmake.in b/Utilities/cmcurl/CMake/curl-config.cmake.in index dbe4ed21025..d1582b8d41b 100644 --- a/Utilities/cmcurl/CMake/curl-config.cmake.in +++ b/Utilities/cmcurl/CMake/curl-config.cmake.in @@ -24,12 +24,54 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) -if(@USE_OPENSSL@) - find_dependency(OpenSSL @OPENSSL_VERSION_MAJOR@) +if("@USE_OPENSSL@") + if("@OPENSSL_VERSION_MAJOR@") + find_dependency(OpenSSL "@OPENSSL_VERSION_MAJOR@") + else() + find_dependency(OpenSSL) + endif() endif() -if(@USE_ZLIB@) - find_dependency(ZLIB @ZLIB_VERSION_MAJOR@) +if("@HAVE_LIBZ@") + find_dependency(ZLIB "@ZLIB_VERSION_MAJOR@") endif() include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") + +# Alias for either shared or static library +if(NOT TARGET @PROJECT_NAME@::@LIB_NAME@) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.11 AND CMAKE_VERSION VERSION_LESS 3.18) + set_target_properties(@PROJECT_NAME@::@LIB_SELECTED@ PROPERTIES IMPORTED_GLOBAL TRUE) + endif() + add_library(@PROJECT_NAME@::@LIB_NAME@ ALIAS @PROJECT_NAME@::@LIB_SELECTED@) +endif() + +# For compatibility with CMake's FindCURL.cmake +set(CURL_VERSION_STRING "@CURLVERSION@") +set(CURL_LIBRARIES @PROJECT_NAME@::@LIB_NAME@) +set_and_check(CURL_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") + +set(CURL_SUPPORTED_PROTOCOLS "@CURL_SUPPORTED_PROTOCOLS_LIST@") +set(CURL_SUPPORTED_FEATURES "@CURL_SUPPORTED_FEATURES_LIST@") + +foreach(_item IN LISTS CURL_SUPPORTED_PROTOCOLS CURL_SUPPORTED_FEATURES) + set(CURL_SUPPORTS_${_item} TRUE) +endforeach() + +set(_missing_req "") +foreach(_item IN LISTS CURL_FIND_COMPONENTS) + if(CURL_SUPPORTS_${_item}) + set(CURL_${_item}_FOUND TRUE) + elseif(CURL_FIND_REQUIRED_${_item}) + list(APPEND _missing_req ${_item}) + endif() +endforeach() + +if(_missing_req) + string(REPLACE ";" " " _missing_req "${_missing_req}") + if(CURL_FIND_REQUIRED) + message(FATAL_ERROR "CURL: missing required components: ${_missing_req}") + endif() + unset(_missing_req) +endif() + check_required_components("@PROJECT_NAME@") diff --git a/Utilities/cmcurl/CMake/unix-cache.cmake b/Utilities/cmcurl/CMake/unix-cache.cmake new file mode 100644 index 00000000000..f42ac5d09d1 --- /dev/null +++ b/Utilities/cmcurl/CMake/unix-cache.cmake @@ -0,0 +1,320 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Based on CI runs for Cygwin/MSYS2, Linux, macOS, FreeBSD, NetBSD, OpenBSD +if(NOT UNIX) + message(FATAL_ERROR "This file should be included on Unix platforms only") +endif() + +if(APPLE OR + CYGWIN) + set(HAVE_ACCEPT4 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_ACCEPT4 1) +endif() +set(HAVE_ALARM 1) +if(ANDROID) + set(HAVE_ARC4RANDOM 1) +else() + set(HAVE_ARC4RANDOM 0) +endif() +if(0) # XXX(cmake): cache this result for nghttp2 +set(HAVE_ARPA_INET_H 1) +endif() # XXX(cmake): end +set(HAVE_ATOMIC 1) +set(HAVE_BASENAME 1) +set(HAVE_BOOL_T 1) +if(NOT APPLE) + set(HAVE_CLOCK_GETTIME_MONOTONIC 1) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1) + else() + set(HAVE_CLOCK_GETTIME_MONOTONIC_RAW 0) + endif() +endif() +set(HAVE_CLOSESOCKET 0) +set(HAVE_DECL_FSEEKO 1) +set(HAVE_DIRENT_H 1) +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_EVENTFD 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_EVENTFD 1) +endif() +set(HAVE_FCNTL 1) +set(HAVE_FCNTL_H 1) +set(HAVE_FCNTL_O_NONBLOCK 1) +set(HAVE_FILE_OFFSET_BITS 1) +set(HAVE_FNMATCH 1) +set(HAVE_FREEADDRINFO 1) +set(HAVE_FSEEKO 1) +if(APPLE) + set(HAVE_FSETXATTR 1) + set(HAVE_FSETXATTR_5 0) + set(HAVE_FSETXATTR_6 1) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_FSETXATTR 0) + set(HAVE_FSETXATTR_5 0) + set(HAVE_FSETXATTR_6 0) +elseif(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_FSETXATTR 1) + set(HAVE_FSETXATTR_5 1) + set(HAVE_FSETXATTR_6 0) +endif() +set(HAVE_FTRUNCATE 1) +set(HAVE_GETADDRINFO 1) +if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_GETADDRINFO_THREADSAFE 0) +elseif(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_GETADDRINFO_THREADSAFE 1) +endif() +set(HAVE_GETEUID 1) +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_GETHOSTBYNAME_R 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(HAVE_GETHOSTBYNAME_R 1) +endif() +set(HAVE_GETHOSTBYNAME_R_3 0) +set(HAVE_GETHOSTBYNAME_R_3_REENTRANT 0) +set(HAVE_GETHOSTBYNAME_R_5 0) +set(HAVE_GETHOSTBYNAME_R_5_REENTRANT 0) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_GETHOSTBYNAME_R_6 1) + set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 1) +else() + set(HAVE_GETHOSTBYNAME_R_6 0) + set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 0) +endif() +set(HAVE_GETHOSTNAME 1) +if(NOT ANDROID OR ANDROID_PLATFORM_LEVEL GREATER_EQUAL 24) + set(HAVE_GETIFADDRS 1) +else() + set(HAVE_GETIFADDRS 0) +endif() +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_GETPASS_R 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_GETPASS_R 1) +endif() +set(HAVE_GETPEERNAME 1) +set(HAVE_GETPPID 1) +set(HAVE_GETPWUID 1) +set(HAVE_GETPWUID_R 1) +set(HAVE_GETRLIMIT 1) +set(HAVE_GETSOCKNAME 1) +set(HAVE_GETTIMEOFDAY 1) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_GLIBC_STRERROR_R 1) +else() + set(HAVE_GLIBC_STRERROR_R 0) +endif() +set(HAVE_GMTIME_R 1) +set(HAVE_IFADDRS_H 1) +set(HAVE_IF_NAMETOINDEX 1) +set(HAVE_INET_NTOP 1) +set(HAVE_INET_PTON 1) +set(HAVE_IOCTLSOCKET 0) +set(HAVE_IOCTLSOCKET_CAMEL 0) +set(HAVE_IOCTLSOCKET_CAMEL_FIONBIO 0) +set(HAVE_IOCTLSOCKET_FIONBIO 0) +set(HAVE_IOCTL_FIONBIO 1) +set(HAVE_IOCTL_SIOCGIFADDR 1) +if(CYGWIN) + set(HAVE_IO_H 1) +else() + set(HAVE_IO_H 0) +endif() +set(HAVE_LIBGEN_H 1) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_LINUX_TCP_H 1) +else() + set(HAVE_LINUX_TCP_H 0) +endif() +set(HAVE_LOCALE_H 1) +set(HAVE_LONGLONG 1) +if(APPLE) + set(HAVE_MACH_ABSOLUTE_TIME 1) +endif() +if(APPLE OR + CYGWIN) + set(HAVE_MEMRCHR 0) +else() + set(HAVE_MEMRCHR 1) +endif() +set(HAVE_MSG_NOSIGNAL 1) +set(HAVE_NETDB_H 1) +if(ANDROID) + set(HAVE_NETINET_IN6_H 1) +else() + set(HAVE_NETINET_IN6_H 0) +endif() +if(0) # XXX(cmake): cache this result for nghttp2 +set(HAVE_NETINET_IN_H 1) +endif() # XXX(cmake): end +set(HAVE_NETINET_TCP_H 1) +set(HAVE_NETINET_UDP_H 1) +set(HAVE_NET_IF_H 1) +set(HAVE_OPENDIR 1) +set(HAVE_PIPE 1) +if(APPLE OR + CYGWIN) + set(HAVE_PIPE2 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_PIPE2 1) +endif() +set(HAVE_POLL 1) +set(HAVE_POLL_H 1) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_POSIX_STRERROR_R 0) +else() + set(HAVE_POSIX_STRERROR_R 1) +endif() +set(HAVE_PWD_H 1) +set(HAVE_REALPATH 1) +set(HAVE_RECV 1) +set(HAVE_SA_FAMILY_T 1) +set(HAVE_SCHED_YIELD 1) +set(HAVE_SELECT 1) +set(HAVE_SEND 1) +if(APPLE OR + CYGWIN) + set(HAVE_SENDMMSG 0) +else() + set(HAVE_SENDMMSG 1) +endif() +set(HAVE_SENDMSG 1) +set(HAVE_SETLOCALE 1) +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_SETMODE 0) +else() + set(HAVE_SETMODE 1) +endif() +set(HAVE_SETRLIMIT 1) +set(HAVE_SETSOCKOPT_SO_NONBLOCK 0) +set(HAVE_SIGACTION 1) +set(HAVE_SIGINTERRUPT 1) +set(HAVE_SIGNAL 1) +set(HAVE_SIGSETJMP 1) +set(HAVE_SNPRINTF 1) +set(HAVE_SOCKADDR_IN6_SIN6_ADDR 1) +set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1) +set(HAVE_SOCKET 1) +set(HAVE_SOCKETPAIR 1) +set(HAVE_STDATOMIC_H 1) +set(HAVE_STDBOOL_H 1) +set(HAVE_STDDEF_H 1) +set(HAVE_STDINT_H 1) +set(HAVE_STRCASECMP 1) +set(HAVE_STRCMPI 0) +set(HAVE_STRDUP 1) +set(HAVE_STRERROR_R 1) +set(HAVE_STRICMP 0) +set(HAVE_STRINGS_H 1) +if(_CURL_OLD_LINUX) + set(HAVE_STROPTS_H 1) +else() + set(HAVE_STROPTS_H 0) # glibc 2.30 or newer. https://sourceware.org/legacy-ml/libc-alpha/2019-08/msg00029.html +endif() +set(HAVE_STRUCT_SOCKADDR_STORAGE 1) +set(HAVE_STRUCT_TIMEVAL 1) +if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(HAVE_SUSECONDS_T 1) +endif() +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_SYS_EVENTFD_H 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_SYS_EVENTFD_H 1) +endif() +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_SYS_FILIO_H 0) +else() + set(HAVE_SYS_FILIO_H 1) +endif() +set(HAVE_SYS_IOCTL_H 1) +set(HAVE_SYS_PARAM_H 1) +set(HAVE_SYS_POLL_H 1) +set(HAVE_SYS_RESOURCE_H 1) +set(HAVE_SYS_SELECT_H 1) +set(HAVE_SYS_SOCKET_H 1) +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_SYS_SOCKIO_H 0) +else() + set(HAVE_SYS_SOCKIO_H 1) +endif() +set(HAVE_SYS_STAT_H 1) +set(HAVE_SYS_TIME_H 1) +set(HAVE_SYS_TYPES_H 1) +set(HAVE_SYS_UN_H 1) +if(CYGWIN) + set(HAVE_SYS_UTIME_H 1) +else() + set(HAVE_SYS_UTIME_H 0) +endif() +set(HAVE_TERMIOS_H 1) +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_TERMIO_H 1) +else() + set(HAVE_TERMIO_H 0) +endif() +set(HAVE_TIME_T_UNSIGNED 0) +set(HAVE_UNISTD_H 1) +set(HAVE_UTIME 1) +set(HAVE_UTIMES 1) +set(HAVE_UTIME_H 1) +set(HAVE_WRITABLE_ARGV 1) +if(CYGWIN) + set(HAVE__SETMODE 1) +endif() +set(STDC_HEADERS 1) +set(USE_UNIX_SOCKETS 1) diff --git a/Utilities/cmcurl/CMake/win32-cache.cmake b/Utilities/cmcurl/CMake/win32-cache.cmake new file mode 100644 index 00000000000..163a310fdad --- /dev/null +++ b/Utilities/cmcurl/CMake/win32-cache.cmake @@ -0,0 +1,245 @@ +#*************************************************************************** +# _ _ ____ _ +# 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(NOT WIN32) + message(FATAL_ERROR "This file should be included on Windows platform only") +endif() + +if(MINGW) + set(HAVE_BASENAME 1) + set(HAVE_BOOL_T 1) # = HAVE_STDBOOL_H + set(HAVE_DIRENT_H 1) + set(HAVE_FTRUNCATE 1) + set(HAVE_GETTIMEOFDAY 1) + set(HAVE_LIBGEN_H 1) + set(HAVE_OPENDIR 1) + set(HAVE_SNPRINTF 1) + set(HAVE_STDBOOL_H 1) + set(HAVE_STDDEF_H 1) # detected by CMake internally in check_type_size() + set(HAVE_STDINT_H 1) # detected by CMake internally in check_type_size() + set(HAVE_STRINGS_H 1) # wrapper to string.h + set(HAVE_SYS_PARAM_H 1) + set(HAVE_SYS_TIME_H 1) + set(HAVE_UNISTD_H 1) + set(HAVE_UTIME_H 1) # wrapper to sys/utime.h +else() + set(HAVE_DIRENT_H 0) + set(HAVE_FTRUNCATE 0) + set(HAVE_GETTIMEOFDAY 0) + set(HAVE_LIBGEN_H 0) + set(HAVE_OPENDIR 0) + set(HAVE_STRINGS_H 0) + set(HAVE_SYS_PARAM_H 0) + set(HAVE_SYS_TIME_H 0) + set(HAVE_UTIME_H 0) + if(MSVC) + set(HAVE_UNISTD_H 0) + set(HAVE_STDDEF_H 1) # detected by CMake internally in check_type_size() + if(MSVC_VERSION GREATER_EQUAL 1600) + set(HAVE_STDINT_H 1) # detected by CMake internally in check_type_size() + else() + set(HAVE_STDINT_H 0) # detected by CMake internally in check_type_size() + endif() + if(MSVC_VERSION GREATER_EQUAL 1800) + set(HAVE_STDBOOL_H 1) + else() + set(HAVE_STDBOOL_H 0) + endif() + set(HAVE_BOOL_T "${HAVE_STDBOOL_H}") + if(MSVC_VERSION GREATER_EQUAL 1900) + set(HAVE_SNPRINTF 1) + else() + set(HAVE_SNPRINTF 0) + endif() + set(HAVE_BASENAME 0) + endif() +endif() + +if((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.9) OR + (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.6)) + # MinGW or clang-cl + set(HAVE_STDATOMIC_H 1) + set(HAVE_ATOMIC 1) +else() + set(HAVE_STDATOMIC_H 0) + set(HAVE_ATOMIC 0) +endif() + +set(HAVE_ACCEPT4 0) +set(HAVE_ALARM 0) +set(HAVE_ARC4RANDOM 0) +set(HAVE_ARPA_INET_H 0) +set(HAVE_CLOSESOCKET 1) +set(HAVE_EVENTFD 0) +set(HAVE_FCNTL 0) +set(HAVE_FCNTL_H 1) +set(HAVE_FCNTL_O_NONBLOCK 0) +set(HAVE_FNMATCH 0) +set(HAVE_FREEADDRINFO 1) # Available in Windows XP and newer +set(HAVE_FSETXATTR 0) +set(HAVE_GETADDRINFO 1) # Available in Windows XP and newer +set(HAVE_GETEUID 0) +set(HAVE_GETHOSTBYNAME_R 0) +set(HAVE_GETHOSTBYNAME_R_3 0) +set(HAVE_GETHOSTBYNAME_R_3_REENTRANT 0) +set(HAVE_GETHOSTBYNAME_R_5 0) +set(HAVE_GETHOSTBYNAME_R_5_REENTRANT 0) +set(HAVE_GETHOSTBYNAME_R_6 0) +set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 0) +set(HAVE_GETHOSTNAME 1) +set(HAVE_GETIFADDRS 0) +set(HAVE_GETPASS_R 0) +set(HAVE_GETPEERNAME 1) +set(HAVE_GETPPID 0) +set(HAVE_GETPWUID 0) +set(HAVE_GETPWUID_R 0) +set(HAVE_GETRLIMIT 0) +set(HAVE_GETSOCKNAME 1) +set(HAVE_GLIBC_STRERROR_R 0) +set(HAVE_GMTIME_R 0) +set(HAVE_IFADDRS_H 0) +set(HAVE_IF_NAMETOINDEX 0) +set(HAVE_INET_NTOP 0) +set(HAVE_INET_PTON 0) +set(HAVE_IOCTLSOCKET 1) +set(HAVE_IOCTLSOCKET_CAMEL 0) +set(HAVE_IOCTLSOCKET_CAMEL_FIONBIO 0) +set(HAVE_IOCTLSOCKET_FIONBIO 1) +set(HAVE_IOCTL_FIONBIO 0) +set(HAVE_IOCTL_SIOCGIFADDR 0) +set(HAVE_IO_H 1) +set(HAVE_LINUX_TCP_H 0) +set(HAVE_LOCALE_H 1) +set(HAVE_MEMRCHR 0) +set(HAVE_MSG_NOSIGNAL 0) +set(HAVE_NETDB_H 0) +set(HAVE_NETINET_IN6_H 0) +set(HAVE_NETINET_IN_H 0) +set(HAVE_NETINET_TCP_H 0) +set(HAVE_NETINET_UDP_H 0) +set(HAVE_NET_IF_H 0) +set(HAVE_PIPE 0) +set(HAVE_PIPE2 0) +set(HAVE_POLL 0) +set(HAVE_POLL_H 0) +set(HAVE_POSIX_STRERROR_R 0) +set(HAVE_PWD_H 0) +set(HAVE_RECV 1) +set(HAVE_SELECT 1) +set(HAVE_SEND 1) +set(HAVE_SENDMMSG 0) +set(HAVE_SENDMSG 0) +set(HAVE_SETLOCALE 1) +set(HAVE_SETMODE 1) +set(HAVE_SETRLIMIT 0) +set(HAVE_SETSOCKOPT_SO_NONBLOCK 0) +set(HAVE_SIGACTION 0) +set(HAVE_SIGINTERRUPT 0) +set(HAVE_SIGNAL 1) +set(HAVE_SIGSETJMP 0) +set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1) +set(HAVE_SOCKET 1) +set(HAVE_SOCKETPAIR 0) +set(HAVE_STRDUP 1) +set(HAVE_STRERROR_R 0) +set(HAVE_STROPTS_H 0) +set(HAVE_STRUCT_SOCKADDR_STORAGE 1) +set(HAVE_STRUCT_TIMEVAL 1) +set(HAVE_SYS_EVENTFD_H 0) +set(HAVE_SYS_FILIO_H 0) +set(HAVE_SYS_IOCTL_H 0) +set(HAVE_SYS_POLL_H 0) +set(HAVE_SYS_RESOURCE_H 0) +set(HAVE_SYS_SELECT_H 0) +set(HAVE_SYS_SOCKET_H 0) +set(HAVE_SYS_SOCKIO_H 0) +set(HAVE_SYS_STAT_H 1) +set(HAVE_SYS_TYPES_H 1) +set(HAVE_SYS_UN_H 0) +set(HAVE_SYS_UTIME_H 1) +set(HAVE_TERMIOS_H 0) +set(HAVE_TERMIO_H 0) +set(HAVE_TIME_T_UNSIGNED 0) +set(HAVE_UTIME 1) +set(HAVE_UTIMES 0) +set(HAVE__SETMODE 1) +set(STDC_HEADERS 1) + +# Types and sizes + +set(HAVE_SIZEOF_SA_FAMILY_T 0) +set(HAVE_SIZEOF_SUSECONDS_T 0) + +if(MINGW OR MSVC) + curl_prefill_type_size("INT" 4) + curl_prefill_type_size("LONG" 4) + curl_prefill_type_size("LONG_LONG" 8) + curl_prefill_type_size("__INT64" 8) + curl_prefill_type_size("CURL_OFF_T" 8) + # CURL_SOCKET_T, SIZE_T: 8 for _WIN64, 4 otherwise + # TIME_T: 8 for _WIN64 or UCRT or MSVC and not Windows CE, 4 otherwise + # Also 4 for non-UCRT 32-bit when _USE_32BIT_TIME_T is set. + # mingw-w64 sets _USE_32BIT_TIME_T unless __MINGW_USE_VC2005_COMPAT is explicit defined. + if(MSVC) + set(HAVE_SIZEOF_SSIZE_T 0) + set(HAVE_FILE_OFFSET_BITS 0) + curl_prefill_type_size("OFF_T" 4) + curl_prefill_type_size("ADDRESS_FAMILY" 2) + else() + # SSIZE_T: 8 for _WIN64, 4 otherwise + if(MINGW64_VERSION) + if(MINGW64_VERSION VERSION_GREATER_EQUAL 3.0) + set(HAVE_FILE_OFFSET_BITS 1) + curl_prefill_type_size("OFF_T" 8) + endif() + if(MINGW64_VERSION VERSION_GREATER_EQUAL 2.0) + curl_prefill_type_size("ADDRESS_FAMILY" 2) + else() + set(HAVE_SIZEOF_ADDRESS_FAMILY 0) + endif() + endif() + endif() +endif() + +# Windows CE exceptions + +if(WINCE) + set(HAVE_FREEADDRINFO 0) + set(HAVE_GETADDRINFO 0) + set(HAVE_LOCALE_H 0) + set(HAVE_SETLOCALE 0) + set(HAVE_SETMODE 0) + set(HAVE_SIGNAL 0) + set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 0) + curl_prefill_type_size("CURL_SOCKET_T" 4) + curl_prefill_type_size("TIME_T" 4) + curl_prefill_type_size("SIZE_T" 4) + if(MINGW32CE) + set(HAVE_STRTOK_R 0) + set(HAVE__SETMODE 0) + set(HAVE_FILE_OFFSET_BITS 0) + set(HAVE_SIZEOF_ADDRESS_FAMILY 0) + curl_prefill_type_size("SSIZE_T" 4) + curl_prefill_type_size("OFF_T" 4) + endif() +endif() diff --git a/Utilities/cmcurl/CMakeLists.txt b/Utilities/cmcurl/CMakeLists.txt index 82f9b0b70ec..ddc975c202d 100644 --- a/Utilities/cmcurl/CMakeLists.txt +++ b/Utilities/cmcurl/CMakeLists.txt @@ -1,39 +1,62 @@ # Set curl options as needed for CMake build +if(WIN32) + set(_CURL_PREFILL ON) +else() + set(_CURL_PREFILL OFF) +endif() set(BUILD_CURL_EXE OFF CACHE INTERNAL "No curl exe") set(BUILD_DASHBOARD_REPORTS OFF CACHE INTERNAL "No curl dashboard reports") set(BUILD_RELEASE_DEBUG_DIRS OFF CACHE INTERNAL "No curl release/debug dirs") -set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Build shared libraries") +set(BUILD_SHARED_LIBS OFF) +set(BUILD_STATIC_LIBS ON) +set(BUILD_STATIC_CURL OFF) +set(CURL_CA_SEARCH_SAFE OFF) set(CURL_USE_BEARSSL OFF) +set(CURL_USE_GSASL OFF) set(CURL_USE_GSSAPI OFF) set(CURL_USE_LIBPSL OFF) set(CURL_USE_LIBSSH2 OFF) set(CURL_USE_LIBSSH OFF) +set(CURL_USE_LIBUV OFF) set(CURL_USE_MBEDTLS OFF) set(CURL_USE_NSS OFF) set(CURL_USE_OPENLDAP OFF) set(CURL_USE_OPENSSL "${CMAKE_USE_OPENSSL}") +set(CURL_USE_PKGCONFIG OFF) set(CURL_USE_SCHANNEL OFF) set(CURL_USE_SECTRANSP OFF) +set(CURL_USE_WOLFSSH OFF) set(CURL_USE_WOLFSSL OFF) -set(CURL_BROTLI OFF) +set(CURL_BROTLI "OFF" CACHE INTERNAL "Build curl with BROTLI support (AUTO, ON or OFF)") set(CURL_DISABLE_ALTSVC ON) +set(CURL_DISABLE_AWS OFF) +set(CURL_DISABLE_BASIC_AUTH OFF) +set(CURL_DISABLE_BEARER_AUTH OFF) +set(CURL_DISABLE_BINDLOCAL OFF) +set(CURL_DISABLE_CA_SEARCH OFF) set(CURL_DISABLE_COOKIES OFF CACHE INTERNAL "Do not disable curl cookie support") -set(CURL_DISABLE_CRYPTO_AUTH OFF CACHE INTERNAL "Do not disable curl crypto auth") set(CURL_DISABLE_DICT ON CACHE INTERNAL "Disable curl dict protocol?") +set(CURL_DISABLE_DIGEST_AUTH OFF) set(CURL_DISABLE_DOH OFF) set(CURL_DISABLE_FILE OFF CACHE INTERNAL "Disable curl file protocol?") +set(CURL_DISABLE_FORM_API OFF) set(CURL_DISABLE_FTP OFF CACHE INTERNAL "Disable curl ftp protocol?") set(CURL_DISABLE_GETOPTIONS OFF) set(CURL_DISABLE_GOPHER ON CACHE INTERNAL "Disable curl gopher protocol?") -set(CURL_DISABLE_HSTS ON) +set(CURL_DISABLE_HEADERS_API OFF) +set(CURL_DISABLE_HSTS OFF) set(CURL_DISABLE_HTTP_AUTH OFF) set(CURL_DISABLE_HTTP OFF CACHE INTERNAL "Disable curl http protocol?") set(CURL_DISABLE_IMAP ON CACHE INTERNAL "Disable curl imap protocol?") +set(CURL_DISABLE_INSTALL ON) +set(CURL_DISABLE_IPFS OFF) +set(CURL_DISABLE_KERBEROS_AUTH OFF) set(CURL_DISABLE_LDAP ON CACHE INTERNAL "Disable curl ldap protocol?") set(CURL_DISABLE_LDAPS ON CACHE INTERNAL "Disable curl ldaps protocol?") set(CURL_DISABLE_LIBCURL_OPTION OFF) set(CURL_DISABLE_MIME OFF) set(CURL_DISABLE_MQTT ON) +set(CURL_DISABLE_NEGOTIATE_AUTH OFF) set(CURL_DISABLE_NETRC OFF) set(CURL_DISABLE_NTLM OFF) set(CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG OFF) @@ -42,19 +65,23 @@ set(CURL_DISABLE_POP3 ON CACHE INTERNAL "Disable curl pop3 protocol?") set(CURL_DISABLE_PROGRESS_METER OFF) set(CURL_DISABLE_PROXY OFF CACHE INTERNAL "Do not disable curl proxy") set(CURL_DISABLE_RTSP ON CACHE INTERNAL "Disable curl rtsp protocol?") +set(CURL_DISABLE_SHA512_256 OFF) set(CURL_DISABLE_SHUFFLE_DNS OFF) set(CURL_DISABLE_SMB OFF) set(CURL_DISABLE_SMTP ON CACHE INTERNAL "Disable curl smtp protocol?") set(CURL_DISABLE_SOCKETPAIR OFF) +set(CURL_DISABLE_SRP OFF) set(CURL_DISABLE_TELNET ON CACHE INTERNAL "Disable curl telnet protocol?") set(CURL_DISABLE_TFTP ON CACHE INTERNAL "Disable curl tftp protocol?") set(CURL_DISABLE_VERBOSE_STRINGS OFF CACHE INTERNAL "Do not disable curl verbosity") +set(CURL_DISABLE_WEBSOCKETS OFF) set(CURL_ENABLE_EXPORT_TARGET OFF) set(CURL_HIDDEN_SYMBOLS OFF CACHE INTERNAL "No curl hidden symbols") set(CURL_LTO OFF CACHE INTERNAL "Turn on compiler Link Time Optimizations") set(CURL_STATIC_CRT OFF CACHE INTERNAL "Set to ON to build libcurl with static CRT on Windows (/MT).") set(CURL_WERROR OFF CACHE INTERNAL "Turn compiler warnings into errors") -set(CURL_ZSTD OFF) +set(CURL_ZLIB "AUTO" CACHE INTERNAL "Build curl with ZLIB support (AUTO, ON or OFF)") +set(CURL_ZSTD "OFF" CACHE INTERNAL "Build curl with ZSTD support (AUTO, ON or OFF)") set(DISABLED_THREADSAFE OFF CACHE INTERNAL "Curl can use thread-safe functions") set(ENABLE_ARES OFF CACHE INTERNAL "No curl c-ares support") set(ENABLE_ALT_SVC OFF) @@ -63,6 +90,7 @@ set(ENABLE_DEBUG OFF CACHE INTERNAL "No curl debug features") set(ENABLE_INET_PTON OFF CACHE INTERNAL "Set to OFF to prevent usage of inet_pton when building against modern SDKs while still requiring compatibility with older Windows versions, such as Windows XP, Windows Server 2003 etc.") set(ENABLE_IPV6 ON CACHE INTERNAL "Enable curl IPv6 support detection") set(ENABLE_MANUAL OFF CACHE INTERNAL "No curl built-in manual") +set(ENABLE_SERVER_DEBUG OFF) set(ENABLE_THREADED_RESOLVER OFF CACHE INTERNAL "No curl POSIX threaded DNS lookup") set(ENABLE_UNICODE OFF) set(ENABLE_UNIX_SOCKETS OFF CACHE INTERNAL "No curl Unix domain sockets support") @@ -76,11 +104,17 @@ set(HAVE_STDATOMIC_H 0) set(HAVE_STRCASECMP 0) # we do not vendor the code that uses this set(HAVE_WIN32_WINNT 0) # we do not need this info set(HTTP_ONLY OFF CACHE INTERNAL "Curl is not http-only") -set(PICKY_COMPILER OFF CACHE INTERNAL "Enable picky compiler options") +set(PICKY_COMPILER OFF) +set(SHARE_LIB_OBJECT OFF) +set(USE_ECH OFF) +set(USE_HTTPSRR OFF) set(USE_LIBIDN2 ON) +set(USE_LIBRTMP OFF) set(USE_NGHTTP2 ON) set(USE_NGTCP2 OFF) +set(USE_OPENSSL_QUIC OFF) set(USE_QUICHE OFF) +set(USE_SSLS_EXPORT OFF) set(USE_WIN32_IDN OFF) set(USE_WIN32_LDAP OFF CACHE INTERNAL "No curl Windows LDAP") if(CURL_USE_OPENSSL) @@ -166,218 +200,495 @@ endif() # SPDX-License-Identifier: curl # ########################################################################### -# curl/libcurl CMake script # by Tetetest and Sukender (Benoit Neil) -# TODO: -# The output .so file lacks the soname number which we currently have within the lib/Makefile.am file -# Add full (4 or 5 libs) SSL support -# Add INSTALL target (EXTRA_DIST variables in Makefile.am may be moved to Makefile.inc so that CMake/CPack is aware of what's to include). -# Check on all possible platforms -# Test with as many configurations possible (With or without any option) -# Create scripts that help keeping the CMake build system up to date (to reduce maintenance). According to Tetetest: -# - lists of headers that 'configure' checks for; -# - curl-specific tests (the ones that are in m4/curl-*.m4 files); -# - (most obvious thing:) curl version numbers. -# Add documentation subproject -# -# To check: -# (From Daniel Stenberg) The cmake build selected to run gcc with -fPIC on my box while the plain configure script did not. -# (From Daniel Stenberg) The gcc command line use neither -g nor any -O options. As a developer, I also treasure our configure scripts's --enable-debug option that sets a long range of "picky" compiler options. - -# Note: By default this CMake build script detects the version of some -# dependencies using `check_symbol_exists`. Those checks do not work -# in the case that both CURL and its dependency are included as -# sub-projects in a larger build using `FetchContent`. To support -# that case, additional variables may be defined by the parent -# project, ideally in the "extra" find package redirect file: -# https://cmake.org/cmake/help/latest/module/FetchContent.html#integrating-with-find-package -# -# The following variables are available: -# HAVE_RAND_EGD: `RAND_egd` present in OpenSSL -# HAVE_AWSLC: OpenSSL is AWS-LC -# HAVE_BORINGSSL: OpenSSL is BoringSSL -# HAVE_PK11_CREATEMANAGEDGENERICOBJECTL: `PK11_CreateManagedGenericObject` present in NSS -# HAVE_SSL_CTX_SET_QUIC_METHOD: `SSL_CTX_set_quic_method` present in OpenSSL/wolfSSL -# HAVE_QUICHE_CONN_SET_QLOG_FD: `quiche_conn_set_qlog_fd` present in QUICHE -# HAVE_ZSTD_CREATEDSTREAM: `ZSTD_createDStream` present in Zstd -# -# For each of the above variables, if the variable is DEFINED (either -# to ON or OFF), the symbol detection will be skipped. If the -# variable is NOT DEFINED, the symbol detection will be performed. +if(0) # XXX(cmake): not needed for build within cmake +cmake_minimum_required(VERSION 3.7...3.16 FATAL_ERROR) +message(STATUS "Using CMake version ${CMAKE_VERSION}") + +# Collect command-line arguments for buildinfo.txt. +# Must reside at the top of the script to work as expected. +set(_cmake_args "") +if(NOT "$ENV{CURL_BUILDINFO}$ENV{CURL_CI}$ENV{CI}" STREQUAL "") + get_cmake_property(_cache_vars CACHE_VARIABLES) + foreach(_cache_var IN ITEMS ${_cache_vars}) + get_property(_cache_var_helpstring CACHE ${_cache_var} PROPERTY HELPSTRING) + if(_cache_var_helpstring STREQUAL "No help, variable specified on the command line.") + get_property(_cache_var_type CACHE ${_cache_var} PROPERTY TYPE) + get_property(_cache_var_value CACHE ${_cache_var} PROPERTY VALUE) + if(_cache_var_type STREQUAL "UNINITIALIZED") + set(_cache_var_type) + else() + set(_cache_var_type ":${_cache_var_type}") + endif() + string(APPEND _cmake_args " -D${_cache_var}${_cache_var_type}=\"${_cache_var_value}\"") + endif() + endforeach() +endif() +endif() # XXX(cmake): end -set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}") +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH}) include(Utilities) include(Macros) include(CMakeDependentOption) include(CheckCCompilerFlag) -project(CURL C) +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/curl/curlver.h" _curl_version_h_contents REGEX "#define LIBCURL_VERSION( |_NUM )") +string(REGEX MATCH "#define LIBCURL_VERSION \"[^\"]*" _curl_version ${_curl_version_h_contents}) +string(REGEX REPLACE "[^\"]+\"" "" _curl_version ${_curl_version}) +string(REGEX MATCH "#define LIBCURL_VERSION_NUM 0x[0-9a-fA-F]+" _curl_version_num ${_curl_version_h_contents}) +string(REGEX REPLACE "[^0]+0x" "" _curl_version_num ${_curl_version_num}) +unset(_curl_version_h_contents) + +if(0) # XXX(cmake): not needed for build within cmake +message(STATUS "curl version=[${_curl_version}]") +endif() # XXX(cmake): end + +# XXX(cmake): Set these as normal variables to suppress cache entries. +set(CMAKE_PROJECT_VERSION 0) +set(CMAKE_PROJECT_VERSION_MAJOR 0) +set(CMAKE_PROJECT_VERSION_MINOR 0) +set(CMAKE_PROJECT_VERSION_PATCH 0) +set(CMAKE_PROJECT_VERSION_TWEAK 0) + +string(REGEX REPLACE "([0-9]+\.[0-9]+\.[0-9]+).+" "\\1" _curl_version_sem "${_curl_version}") +project(CURL + VERSION "${_curl_version_sem}" + LANGUAGES C) + +# CMake does not recognize some targets accurately. Touch up configuration manually as a workaround. +if(WINDOWS_STORE AND MINGW) # mingw UWP build + # CMake (as of v3.31.2) gets confused and applies the MSVC rc.exe command-line + # template to windres. Reset it to the windres template via 'Modules/Platform/Windows-windres.cmake': + set(CMAKE_RC_COMPILE_OBJECT " -O coff ") +elseif(WIN32 AND WINCE AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # mingw32ce build + if(NOT MINGW32CE_LIBRARY_DIR) + message(FATAL_ERROR "Set MINGW32CE_LIBRARY_DIR variable to the mingw32ce platform library directory.") + endif() + + set(MINGW 1) + set(MINGW32CE 1) + + # Build implib with libcurl DLL. Copied from CMake's 'Modules/Platform/Windows-GNU.cmake'. + set(CMAKE_C_CREATE_SHARED_LIBRARY " ") + string(APPEND CMAKE_C_CREATE_SHARED_LIBRARY " -o -Wl,--out-implib,") + string(APPEND CMAKE_C_CREATE_SHARED_LIBRARY " ${CMAKE_GNULD_IMAGE_VERSION} ") + + # Build resources. Copied from CMake's 'Modules/Platform/Windows-windres.cmake'. + set(CMAKE_RC_COMPILE_OBJECT " -O coff ") + enable_language(RC) + + # To compile long long integer literals + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-std=gnu99") + string(APPEND CMAKE_REQUIRED_FLAGS " -std=gnu99") -file(STRINGS ${CURL_SOURCE_DIR}/include/curl/curlver.h CURL_VERSION_H_CONTENTS REGEX "#define LIBCURL_VERSION( |_NUM )") -string(REGEX MATCH "#define LIBCURL_VERSION \"[^\"]*" - CURL_VERSION ${CURL_VERSION_H_CONTENTS}) -string(REGEX REPLACE "[^\"]+\"" "" CURL_VERSION ${CURL_VERSION}) -string(REGEX MATCH "#define LIBCURL_VERSION_NUM 0x[0-9a-fA-F]+" - CURL_VERSION_NUM ${CURL_VERSION_H_CONTENTS}) -string(REGEX REPLACE "[^0]+0x" "" CURL_VERSION_NUM ${CURL_VERSION_NUM}) + set(CMAKE_C_COMPILE_OPTIONS_PIC "") # CMake sets it to '-fPIC', confusing the toolchain and breaking builds. Zap it. + set(CMAKE_STATIC_LIBRARY_PREFIX "lib") + set(CMAKE_STATIC_LIBRARY_SUFFIX ".a") + set(CMAKE_SHARED_LIBRARY_PREFIX "lib") + set(CMAKE_SHARED_LIBRARY_SUFFIX ".dll") + set(CMAKE_IMPORT_LIBRARY_PREFIX "lib") + set(CMAKE_IMPORT_LIBRARY_SUFFIX ".dll.a") + set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "") + set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll.a" ".a" ".lib") +elseif(DOS AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # DJGPP + set(CMAKE_STATIC_LIBRARY_PREFIX "lib") + set(CMAKE_STATIC_LIBRARY_SUFFIX ".a") + set(CMAKE_FIND_LIBRARY_PREFIXES "lib") + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +endif() + +# Fill platform level variable when using CMake's built-in Android configuration +if(ANDROID AND NOT DEFINED ANDROID_PLATFORM_LEVEL AND NOT CMAKE_SYSTEM_VERSION EQUAL 1) + set(ANDROID_PLATFORM_LEVEL "${CMAKE_SYSTEM_VERSION}") +endif() + +set(_target_flags "") +if(APPLE) + string(APPEND _target_flags " APPLE") +endif() +if(UNIX) + string(APPEND _target_flags " UNIX") +endif() +if(BSD) + string(APPEND _target_flags " BSD") +endif() +if(ANDROID) + string(APPEND _target_flags " ANDROID-${ANDROID_PLATFORM_LEVEL}") +endif() +if(WIN32) + string(APPEND _target_flags " WIN32") +endif() +if(WINCE) + string(APPEND _target_flags " WINCE") +endif() +if(WINDOWS_STORE) + string(APPEND _target_flags " UWP") +endif() +if(CYGWIN) + string(APPEND _target_flags " CYGWIN") +endif() +if(DOS) + string(APPEND _target_flags " DOS") +endif() +if(AMIGA) + string(APPEND _target_flags " AMIGA") +endif() +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + string(APPEND _target_flags " GCC") +endif() +if(MINGW) + string(APPEND _target_flags " MINGW") +endif() +if(MSVC) + string(APPEND _target_flags " MSVC-${MSVC_VERSION}") +endif() +if(VCPKG_TOOLCHAIN) + string(APPEND _target_flags " VCPKG") +endif() +if(CMAKE_CROSSCOMPILING) + string(APPEND _target_flags " CROSS") +endif() +if(0) # XXX(cmake): not needed for build within cmake +message(STATUS "CMake platform flags:${_target_flags}") +endif() # XXX(cmake): end -# Setup package meta-data -# SET(PACKAGE "curl") -if(0) # This code not needed for building within CMake. -message(STATUS "curl version=[${CURL_VERSION}]") +if(CMAKE_CROSSCOMPILING) + message(STATUS "Cross-compiling: " + "${CMAKE_HOST_SYSTEM_NAME}/${CMAKE_HOST_SYSTEM_PROCESSOR} -> " + "${CMAKE_SYSTEM_NAME}/${CMAKE_SYSTEM_PROCESSOR}") endif() -# SET(PACKAGE_TARNAME "curl") -# SET(PACKAGE_NAME "curl") -# SET(PACKAGE_VERSION "-") -# SET(PACKAGE_STRING "curl-") -# SET(PACKAGE_BUGREPORT "a suitable curl mailing list => https://curl.se/mail/") -set(OPERATING_SYSTEM "${CMAKE_SYSTEM_NAME}") + if(CMAKE_C_COMPILER_TARGET) - set(OS "\"${CMAKE_C_COMPILER_TARGET}\"") + set(CURL_OS "\"${CMAKE_C_COMPILER_TARGET}\"") else() - set(OS "\"${CMAKE_SYSTEM_NAME}\"") + set(CURL_OS "\"${CMAKE_SYSTEM_NAME}\"") +endif() + +set(LIB_NAME "libcurl") + +set_property(DIRECTORY APPEND PROPERTY INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/include") + +if(NOT DEFINED CMAKE_UNITY_BUILD_BATCH_SIZE) + set(CMAKE_UNITY_BUILD_BATCH_SIZE 0) endif() -include_directories(${CURL_SOURCE_DIR}/include) +# Having CMAKE_TRY_COMPILE_TARGET_TYPE set to STATIC_LIBRARY breaks certain +# 'check_function_exists()' detections (possibly more), by detecting +# non-existing features. This happens by default when using 'ios.toolchain.cmake'. +# Work it around by setting this value to `EXECUTABLE`. +if(CMAKE_TRY_COMPILE_TARGET_TYPE STREQUAL "STATIC_LIBRARY") + message(STATUS "CMAKE_TRY_COMPILE_TARGET_TYPE was found set to STATIC_LIBRARY. " + "Overriding with EXECUTABLE for feature detections to work.") + set(_cmake_try_compile_target_type_save ${CMAKE_TRY_COMPILE_TARGET_TYPE}) + set(CMAKE_TRY_COMPILE_TARGET_TYPE "EXECUTABLE") +endif() option(CURL_WERROR "Turn compiler warnings into errors" OFF) option(PICKY_COMPILER "Enable picky compiler options" ON) -option(BUILD_CURL_EXE "Set to ON to build curl executable." ON) +option(BUILD_CURL_EXE "Build curl executable" ON) option(BUILD_SHARED_LIBS "Build shared libraries" ON) -option(ENABLE_ARES "Set to ON to enable c-ares support" OFF) +option(BUILD_STATIC_LIBS "Build static libraries" OFF) +option(BUILD_STATIC_CURL "Build curl executable with static libcurl" OFF) +option(ENABLE_ARES "Enable c-ares support" OFF) +option(CURL_DISABLE_INSTALL "Disable installation targets" OFF) + if(WIN32) - option(CURL_STATIC_CRT "Set to ON to build libcurl with static CRT on Windows (/MT)." OFF) - option(ENABLE_UNICODE "Set to ON to use the Unicode version of the Windows API functions" OFF) - if(0) # This code not needed for building within CMake. + option(ENABLE_UNICODE "Use the Unicode version of the Windows API functions" OFF) + if(WINDOWS_STORE OR WINCE) + set(ENABLE_UNICODE ON) + endif() + if(ENABLE_UNICODE) + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "UNICODE" "_UNICODE") + if(MINGW AND NOT MINGW32CE) + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-municode") + endif() + endif() + + if(0) # XXX(cmake): not needed for build within cmake + # Apply to all feature checks + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DWIN32_LEAN_AND_MEAN") + if(MSVC) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_CRT_NONSTDC_NO_DEPRECATE") # for strdup() detection + endif() + set(CURL_TARGET_WINDOWS_VERSION "" CACHE STRING "Minimum target Windows version as hex string") if(CURL_TARGET_WINDOWS_VERSION) - add_definitions(-D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}) - list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}) - set(CURL_TEST_DEFINES "${CURL_TEST_DEFINES} -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") # Apply to all feature checks endif() + endif() # XXX(cmake): end + + # Detect actual value of _WIN32_WINNT and store as HAVE_WIN32_WINNT + curl_internal_test(HAVE_WIN32_WINNT) + if(HAVE_WIN32_WINNT) + string(REGEX MATCH "_WIN32_WINNT=0x[0-9a-fA-F]+" CURL_TEST_OUTPUT "${CURL_TEST_OUTPUT}") + string(REGEX REPLACE "_WIN32_WINNT=" "" CURL_TEST_OUTPUT "${CURL_TEST_OUTPUT}") + string(REGEX REPLACE "0x([0-9a-f][0-9a-f][0-9a-f])$" "0x0\\1" CURL_TEST_OUTPUT "${CURL_TEST_OUTPUT}") # pad to 4 digits + string(TOLOWER "${CURL_TEST_OUTPUT}" HAVE_WIN32_WINNT) + message(STATUS "Found _WIN32_WINNT=${HAVE_WIN32_WINNT}") endif() - if(ENABLE_UNICODE) - add_definitions(-DUNICODE -D_UNICODE) - if(MINGW) - add_compile_options(-municode) + unset(HAVE_WIN32_WINNT CACHE) # Avoid storing in CMake cache + + if(MINGW) + # Detect __MINGW64_VERSION_MAJOR, __MINGW64_VERSION_MINOR and store as MINGW64_VERSION + curl_internal_test(MINGW64_VERSION) + if(MINGW64_VERSION) + string(REGEX MATCH "MINGW64_VERSION=[0-9]+\.[0-9]+" CURL_TEST_OUTPUT "${CURL_TEST_OUTPUT}") + string(REGEX REPLACE "MINGW64_VERSION=" "" MINGW64_VERSION "${CURL_TEST_OUTPUT}") + if(MINGW64_VERSION) + message(STATUS "Found MINGW64_VERSION=${MINGW64_VERSION}") + endif() endif() + unset(MINGW64_VERSION CACHE) # Avoid storing in CMake cache endif() +elseif(DOS OR AMIGA) + set(BUILD_SHARED_LIBS OFF) + set(BUILD_STATIC_LIBS ON) endif() -option(CURL_LTO "Turn on compiler Link Time Optimizations" OFF) +option(CURL_LTO "Enable compiler Link Time Optimizations" OFF) -if(0) # This code not needed for building within CMake. -cmake_dependent_option(ENABLE_THREADED_RESOLVER "Set to ON to enable threaded DNS lookup" - ON "NOT ENABLE_ARES" - OFF) +if(0) # XXX(cmake): not needed for build within cmake +if(NOT DOS AND NOT AMIGA) + # if c-ares is used, default the threaded resolver to OFF + if(ENABLE_ARES) + set(_enable_threaded_resolver_default OFF) + else() + set(_enable_threaded_resolver_default ON) + endif() + option(ENABLE_THREADED_RESOLVER "Enable threaded DNS lookup" ${_enable_threaded_resolver_default}) endif() -option(ENABLE_DEBUG "Set to ON to enable curl debug features" OFF) -option(ENABLE_CURLDEBUG "Set to ON to build with TrackMemory feature enabled" OFF) - include(PickyWarnings) +endif() # XXX(cmake): end + +if(CYGWIN OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "_GNU_SOURCE") # Required for accept4(), pipe2(), sendmmsg() + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE") # Apply to all feature checks +endif() +option(ENABLE_DEBUG "Enable curl debug features (for developing curl itself)" OFF) if(ENABLE_DEBUG) - # DEBUGBUILD will be defined only for Debug builds - set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$:DEBUGBUILD>) - set(ENABLE_CURLDEBUG ON) + message(WARNING "This curl build is Debug-enabled and insecure, do not use in production.") endif() +option(ENABLE_CURLDEBUG "Enable TrackMemory debug feature" ${ENABLE_DEBUG}) +option(ENABLE_SERVER_DEBUG "Apply curl debug options to test servers" OFF) +set(CURL_DEBUG_MACROS "") +if(ENABLE_DEBUG) + list(APPEND CURL_DEBUG_MACROS "DEBUGBUILD") +endif() if(ENABLE_CURLDEBUG) - set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS CURLDEBUG) + list(APPEND CURL_DEBUG_MACROS "CURLDEBUG") +endif() + +if(0) # XXX(cmake): not needed for build within cmake +option(CURL_TEST_BUNDLES "Build tests into single-binary bundles" OFF) + +option(CURL_CLANG_TIDY "Run the build through clang-tidy" OFF) +if(CURL_CLANG_TIDY) + # clang-tidy is not looking into #included sources, thus not compatible with + # unity builds and test bundles. + set(CMAKE_UNITY_BUILD OFF) + set(CURL_TEST_BUNDLES OFF) + set(_tidy_checks "") + list(APPEND _tidy_checks "-clang-analyzer-security.insecureAPI.strcpy") + list(APPEND _tidy_checks "-clang-analyzer-optin.performance.Padding") + list(APPEND _tidy_checks "-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling") + string(REPLACE ";" "," _tidy_checks "${_tidy_checks}") + find_program(CLANG_TIDY NAMES "clang-tidy" REQUIRED) + set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY}" "-checks=${_tidy_checks}" "-quiet") + unset(_tidy_checks) + if(CURL_WERROR) + list(APPEND CMAKE_C_CLANG_TIDY "--warnings-as-errors=*") + endif() + if(CURL_CLANG_TIDYFLAGS) + list(APPEND CMAKE_C_CLANG_TIDY ${CURL_CLANG_TIDYFLAGS}) + endif() endif() -if(0) # This code not needed for building within CMake. # For debug libs and exes, add "-d" postfix if(NOT DEFINED CMAKE_DEBUG_POSTFIX) set(CMAKE_DEBUG_POSTFIX "-d") endif() +endif() # XXX(cmake): end + +set(LIB_STATIC "libcurl_static") +set(LIB_SHARED "libcurl_shared") + +if(NOT BUILD_SHARED_LIBS AND NOT BUILD_STATIC_LIBS) + set(BUILD_STATIC_LIBS ON) +endif() +if(NOT BUILD_STATIC_CURL AND NOT BUILD_SHARED_LIBS) + set(BUILD_STATIC_CURL ON) +elseif(BUILD_STATIC_CURL AND NOT BUILD_STATIC_LIBS) + set(BUILD_STATIC_CURL OFF) +endif() + +# Lib flavour selected for curl tool +if(BUILD_STATIC_CURL) + set(LIB_SELECTED_FOR_EXE ${LIB_STATIC}) +else() + set(LIB_SELECTED_FOR_EXE ${LIB_SHARED}) +endif() + +# Lib flavour selected for example and test programs. +if(BUILD_SHARED_LIBS) + set(LIB_SELECTED ${LIB_SHARED}) +else() + set(LIB_SELECTED ${LIB_STATIC}) +endif() + +if(WIN32) + option(CURL_STATIC_CRT "Build libcurl with static CRT with MSVC (/MT)" OFF) + if(CURL_STATIC_CRT AND MSVC) + if(MSVC_VERSION GREATER_EQUAL 1900 OR BUILD_STATIC_CURL OR NOT BUILD_CURL_EXE) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "$<$:-MT>") + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "$<$:-MTd>") + else() + message(WARNING "Static CRT requires UCRT, static libcurl or no curl executable.") + endif() + endif() +endif() + +# Override to force-disable or force-enable the use of pkg-config. +if((UNIX AND NOT ANDROID AND (NOT APPLE OR CMAKE_SYSTEM_NAME MATCHES "Darwin")) OR + VCPKG_TOOLCHAIN OR + (MINGW AND NOT CMAKE_CROSSCOMPILING)) + set(_curl_use_pkgconfig_default ON) +else() + set(_curl_use_pkgconfig_default OFF) endif() +option(CURL_USE_PKGCONFIG "Enable pkg-config to detect dependencies" ${_curl_use_pkgconfig_default}) -# initialize CURL_LIBS +# Initialize variables collecting dependency libs, paths, pkg-config names. set(CURL_LIBS "") +set(CURL_LIBDIRS "") +set(LIBCURL_PC_REQUIRES_PRIVATE "") if(ENABLE_ARES) set(USE_ARES 1) - find_package(CARES REQUIRED) - list(APPEND CURL_LIBS ${CARES_LIBRARY}) + find_package(Cares REQUIRED) + list(APPEND CURL_LIBS ${CARES_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${CARES_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${CARES_PC_REQUIRES}) + link_directories(${CARES_LIBRARY_DIRS}) + if(CARES_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${CARES_CFLAGS}") + endif() endif() -if(0) # This code not needed for building within CMake. +if(0) # XXX(cmake): not needed for build within cmake include(CurlSymbolHiding) -endif() +endif() # XXX(cmake): end -option(CURL_ENABLE_EXPORT_TARGET "to enable cmake export target" ON) +option(CURL_ENABLE_EXPORT_TARGET "Enable CMake export target" ON) mark_as_advanced(CURL_ENABLE_EXPORT_TARGET) -option(CURL_DISABLE_ALTSVC "disables alt-svc support" OFF) +option(CURL_DISABLE_ALTSVC "Disable alt-svc support" OFF) mark_as_advanced(CURL_DISABLE_ALTSVC) -option(CURL_DISABLE_COOKIES "disables cookies support" OFF) +option(CURL_DISABLE_SRP "Disable TLS-SRP support" OFF) +mark_as_advanced(CURL_DISABLE_SRP) +option(CURL_DISABLE_COOKIES "Disable cookies support" OFF) mark_as_advanced(CURL_DISABLE_COOKIES) -option(CURL_DISABLE_CRYPTO_AUTH "disables cryptographic authentication" OFF) -mark_as_advanced(CURL_DISABLE_CRYPTO_AUTH) -option(CURL_DISABLE_DICT "disables DICT" OFF) +option(CURL_DISABLE_BASIC_AUTH "Disable Basic authentication" OFF) +mark_as_advanced(CURL_DISABLE_BASIC_AUTH) +option(CURL_DISABLE_BEARER_AUTH "Disable Bearer authentication" OFF) +mark_as_advanced(CURL_DISABLE_BEARER_AUTH) +option(CURL_DISABLE_DIGEST_AUTH "Disable Digest authentication" OFF) +mark_as_advanced(CURL_DISABLE_DIGEST_AUTH) +option(CURL_DISABLE_KERBEROS_AUTH "Disable Kerberos authentication" OFF) +mark_as_advanced(CURL_DISABLE_KERBEROS_AUTH) +option(CURL_DISABLE_NEGOTIATE_AUTH "Disable negotiate authentication" OFF) +mark_as_advanced(CURL_DISABLE_NEGOTIATE_AUTH) +option(CURL_DISABLE_AWS "Disable aws-sigv4" OFF) +mark_as_advanced(CURL_DISABLE_AWS) +option(CURL_DISABLE_DICT "Disable DICT" OFF) mark_as_advanced(CURL_DISABLE_DICT) -option(CURL_DISABLE_DOH "disables DNS-over-HTTPS" OFF) +option(CURL_DISABLE_DOH "Disable DNS-over-HTTPS" OFF) mark_as_advanced(CURL_DISABLE_DOH) -option(CURL_DISABLE_FILE "disables FILE" OFF) +option(CURL_DISABLE_FILE "Disable FILE" OFF) mark_as_advanced(CURL_DISABLE_FILE) -option(CURL_DISABLE_FTP "disables FTP" OFF) +option(CURL_DISABLE_FTP "Disable FTP" OFF) mark_as_advanced(CURL_DISABLE_FTP) -option(CURL_DISABLE_GETOPTIONS "disables curl_easy_options API for existing options to curl_easy_setopt" OFF) +option(CURL_DISABLE_GETOPTIONS "Disable curl_easy_options API for existing options to curl_easy_setopt" OFF) mark_as_advanced(CURL_DISABLE_GETOPTIONS) -option(CURL_DISABLE_GOPHER "disables Gopher" OFF) +option(CURL_DISABLE_GOPHER "Disable Gopher" OFF) mark_as_advanced(CURL_DISABLE_GOPHER) -option(CURL_DISABLE_HSTS "disables HSTS support" OFF) +option(CURL_DISABLE_HEADERS_API "Disable headers-api support" OFF) +mark_as_advanced(CURL_DISABLE_HEADERS_API) +option(CURL_DISABLE_HSTS "Disable HSTS support" OFF) mark_as_advanced(CURL_DISABLE_HSTS) -option(CURL_DISABLE_HTTP "disables HTTP" OFF) +option(CURL_DISABLE_HTTP "Disable HTTP" OFF) mark_as_advanced(CURL_DISABLE_HTTP) -option(CURL_DISABLE_HTTP_AUTH "disables all HTTP authentication methods" OFF) +option(CURL_DISABLE_HTTP_AUTH "Disable all HTTP authentication methods" OFF) mark_as_advanced(CURL_DISABLE_HTTP_AUTH) -option(CURL_DISABLE_IMAP "disables IMAP" OFF) +option(CURL_DISABLE_IMAP "Disable IMAP" OFF) mark_as_advanced(CURL_DISABLE_IMAP) -option(CURL_DISABLE_LDAP "disables LDAP" OFF) +option(CURL_DISABLE_LDAP "Disable LDAP" OFF) mark_as_advanced(CURL_DISABLE_LDAP) -option(CURL_DISABLE_LDAPS "disables LDAPS" OFF) +option(CURL_DISABLE_LDAPS "Disable LDAPS" ${CURL_DISABLE_LDAP}) mark_as_advanced(CURL_DISABLE_LDAPS) -option(CURL_DISABLE_LIBCURL_OPTION "disables --libcurl option from the curl tool" OFF) +option(CURL_DISABLE_LIBCURL_OPTION "Disable --libcurl option from the curl tool" OFF) mark_as_advanced(CURL_DISABLE_LIBCURL_OPTION) -option(CURL_DISABLE_MIME "disables MIME support" OFF) +option(CURL_DISABLE_MIME "Disable MIME support" OFF) mark_as_advanced(CURL_DISABLE_MIME) -option(CURL_DISABLE_MQTT "disables MQTT" OFF) +if(0) # XXX(cmake): not needed for build within cmake +cmake_dependent_option(CURL_DISABLE_FORM_API "Disable form-api" + OFF "NOT CURL_DISABLE_MIME" + ON) +mark_as_advanced(CURL_DISABLE_FORM_API) +endif() # XXX(cmake): end +option(CURL_DISABLE_MQTT "Disable MQTT" OFF) mark_as_advanced(CURL_DISABLE_MQTT) -option(CURL_DISABLE_NETRC "disables netrc parser" OFF) +option(CURL_DISABLE_BINDLOCAL "Disable local binding support" OFF) +mark_as_advanced(CURL_DISABLE_BINDLOCAL) +option(CURL_DISABLE_NETRC "Disable netrc parser" OFF) mark_as_advanced(CURL_DISABLE_NETRC) -option(CURL_DISABLE_NTLM "disables NTLM support" OFF) +option(CURL_DISABLE_NTLM "Disable NTLM support" OFF) mark_as_advanced(CURL_DISABLE_NTLM) -option(CURL_DISABLE_PARSEDATE "disables date parsing" OFF) +option(CURL_DISABLE_PARSEDATE "Disable date parsing" OFF) mark_as_advanced(CURL_DISABLE_PARSEDATE) -option(CURL_DISABLE_POP3 "disables POP3" OFF) +option(CURL_DISABLE_POP3 "Disable POP3" OFF) mark_as_advanced(CURL_DISABLE_POP3) -option(CURL_DISABLE_PROGRESS_METER "disables built-in progress meter" OFF) +option(CURL_DISABLE_PROGRESS_METER "Disable built-in progress meter" OFF) mark_as_advanced(CURL_DISABLE_PROGRESS_METER) -option(CURL_DISABLE_PROXY "disables proxy support" OFF) +option(CURL_DISABLE_PROXY "Disable proxy support" OFF) mark_as_advanced(CURL_DISABLE_PROXY) -option(CURL_DISABLE_RTSP "disables RTSP" OFF) +option(CURL_DISABLE_IPFS "Disable IPFS" OFF) +mark_as_advanced(CURL_DISABLE_IPFS) +option(CURL_DISABLE_RTSP "Disable RTSP" OFF) mark_as_advanced(CURL_DISABLE_RTSP) -option(CURL_DISABLE_SHUFFLE_DNS "disables shuffle DNS feature" OFF) +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_DISABLE_SMB "disables SMB" OFF) +option(CURL_DISABLE_SMB "Disable SMB" OFF) mark_as_advanced(CURL_DISABLE_SMB) -option(CURL_DISABLE_SMTP "disables SMTP" OFF) +option(CURL_DISABLE_SMTP "Disable SMTP" OFF) mark_as_advanced(CURL_DISABLE_SMTP) -option(CURL_DISABLE_SOCKETPAIR "disables use of socketpair for curl_multi_poll" OFF) +option(CURL_DISABLE_SOCKETPAIR "Disable use of socketpair for curl_multi_poll" OFF) mark_as_advanced(CURL_DISABLE_SOCKETPAIR) -option(CURL_DISABLE_TELNET "disables Telnet" OFF) +option(CURL_DISABLE_WEBSOCKETS "Disable WebSocket" OFF) +mark_as_advanced(CURL_DISABLE_WEBSOCKETS) +option(CURL_DISABLE_TELNET "Disable Telnet" OFF) mark_as_advanced(CURL_DISABLE_TELNET) -option(CURL_DISABLE_TFTP "disables TFTP" OFF) +option(CURL_DISABLE_TFTP "Disable TFTP" OFF) mark_as_advanced(CURL_DISABLE_TFTP) -option(CURL_DISABLE_VERBOSE_STRINGS "disables verbose strings" OFF) +option(CURL_DISABLE_VERBOSE_STRINGS "Disable verbose strings" OFF) mark_as_advanced(CURL_DISABLE_VERBOSE_STRINGS) +if(CURL_DISABLE_HTTP) + set(CURL_DISABLE_IPFS ON) + set(CURL_DISABLE_RTSP ON) + set(CURL_DISABLE_ALTSVC ON) + set(CURL_DISABLE_HSTS ON) +endif() + # Corresponds to HTTP_ONLY in lib/curl_setup.h -option(HTTP_ONLY "disables all protocols except HTTP (This overrides all CURL_DISABLE_* options)" OFF) +option(HTTP_ONLY "Disable all protocols except HTTP (This overrides all CURL_DISABLE_* options)" OFF) mark_as_advanced(HTTP_ONLY) if(HTTP_ONLY) @@ -390,6 +701,7 @@ if(HTTP_ONLY) set(CURL_DISABLE_LDAPS ON) set(CURL_DISABLE_MQTT ON) set(CURL_DISABLE_POP3 ON) + set(CURL_DISABLE_IPFS ON) set(CURL_DISABLE_RTSP ON) set(CURL_DISABLE_SMB ON) set(CURL_DISABLE_SMTP ON) @@ -397,68 +709,51 @@ if(HTTP_ONLY) set(CURL_DISABLE_TFTP ON) endif() -option(ENABLE_IPV6 "Define if you want to enable IPv6 support" ON) -mark_as_advanced(ENABLE_IPV6) -if(ENABLE_IPV6 AND NOT WIN32) - include(CheckStructHasMember) - check_struct_has_member("struct sockaddr_in6" sin6_addr "netinet/in.h" - HAVE_SOCKADDR_IN6_SIN6_ADDR) - check_struct_has_member("struct sockaddr_in6" sin6_scope_id "netinet/in.h" - HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) - if(NOT HAVE_SOCKADDR_IN6_SIN6_ADDR) - message(WARNING "struct sockaddr_in6 not available, disabling IPv6 support") - # Force the feature off as this name is used as guard macro... - set(ENABLE_IPV6 OFF - CACHE BOOL "Define if you want to enable IPv6 support" FORCE) - endif() - - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT ENABLE_ARES) - set(use_core_foundation ON) - - find_library(SYSTEMCONFIGURATION_FRAMEWORK "SystemConfiguration") - if(NOT SYSTEMCONFIGURATION_FRAMEWORK) - message(FATAL_ERROR "SystemConfiguration framework not found") - endif() - - list(APPEND CURL_LIBS "-framework SystemConfiguration") - endif() +if(WINDOWS_STORE OR WINCE) + set(CURL_DISABLE_TELNET ON) # telnet code needs fixing to compile for UWP. endif() -if(0) # This code not needed for building within CMake. -if(USE_MANUAL) - #nroff is currently only used when USE_MANUAL is set, so we can prevent the warning of no *NROFF if USE_MANUAL is OFF (or not defined), by not even looking for NROFF.. - curl_nroff_check() -endif() +if(0) # XXX(cmake): not needed for build within cmake find_package(Perl) -cmake_dependent_option(ENABLE_MANUAL "to provide the built-in manual" - ON "NROFF_USEFUL;PERL_FOUND" - OFF) - -if(ENABLE_MANUAL) - set(USE_MANUAL ON) -endif() +if(PERL_EXECUTABLE) + add_custom_target(curl-ca-bundle + COMMENT "Generating a fresh ca-bundle.crt" VERBATIM USES_TERMINAL + COMMAND "${PERL_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/mk-ca-bundle.pl" -b -l -u "lib/ca-bundle.crt" + DEPENDS "${PROJECT_SOURCE_DIR}/scripts/mk-ca-bundle.pl" + ) + add_custom_target(curl-ca-firefox + COMMENT "generating a fresh ca-bundle.crt" VERBATIM USES_TERMINAL + COMMAND "${PERL_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/firefox-db2pem.sh" "lib/ca-bundle.crt" + DEPENDS "${PROJECT_SOURCE_DIR}/scripts/firefox-db2pem.sh" + ) endif() -if(CURL_STATIC_CRT) - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT") - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd") -endif() +option(BUILD_LIBCURL_DOCS "Build libcurl man pages" ON) +option(BUILD_MISC_DOCS "Build misc man pages (e.g. curl-config and mk-ca-bundle)" ON) +option(ENABLE_CURL_MANUAL "Build the man page for curl and enable its -M/--manual option" ON) -# Disable warnings on Borland to avoid changing 3rd party code. -if(BORLAND) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w-") +if(ENABLE_CURL_MANUAL OR BUILD_LIBCURL_DOCS) + if(PERL_FOUND) + set(HAVE_MANUAL_TOOLS ON) + endif() + if(NOT HAVE_MANUAL_TOOLS) + message(WARNING "Perl not found. Will not build manuals.") + endif() endif() +endif() # XXX(cmake): end # If we are on AIX, do the _ALL_SOURCE magic -if(${CMAKE_SYSTEM_NAME} MATCHES AIX) - set(_ALL_SOURCE 1) +if(CMAKE_SYSTEM_NAME STREQUAL "AIX") + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "_ALL_SOURCE") endif() # If we are on Haiku, make sure that the network library is brought in. -if(${CMAKE_SYSTEM_NAME} MATCHES Haiku) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lnetwork") +if(CMAKE_SYSTEM_NAME STREQUAL "Haiku") + list(APPEND CURL_LIBS "network") +elseif(AMIGA) + list(APPEND CURL_LIBS "net" "m" "atomic") + list(APPEND CMAKE_REQUIRED_LIBRARIES "net" "m" "atomic") endif() # Include all the necessary files for macros @@ -471,114 +766,225 @@ include(CheckSymbolExists) include(CheckTypeSize) include(CheckCSourceCompiles) -# On windows preload settings -if(WIN32) - list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WINSOCKAPI_=) - include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/Platforms/WindowsCache.cmake) +option(_CURL_PREFILL "Fast-track known feature detection results (Windows, some Apple)" "${WIN32}") +if(_CURL_PREFILL) + if(WIN32) + include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/win32-cache.cmake") + elseif(UNIX) + include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/unix-cache.cmake") + if(0) # XXX(cmake): not needed for build within cmake + message(STATUS "Pre-filling feature detection results for UNIX") + endif() # XXX(cmake): end + endif() +elseif(WIN32) + if(0) # XXX(cmake): not needed for build within cmake + message(STATUS "Pre-filling feature detection results disabled.") + endif() # XXX(cmake): end +elseif(APPLE) + set(HAVE_EVENTFD 0) + set(HAVE_GETPASS_R 0) + set(HAVE_WRITABLE_ARGV 1) + set(HAVE_SENDMMSG 0) +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(ENABLE_THREADED_RESOLVER) - find_package(Threads REQUIRED) if(WIN32) set(USE_THREADS_WIN32 ON) else() + find_package(Threads REQUIRED) set(USE_THREADS_POSIX ${CMAKE_USE_PTHREADS_INIT}) set(HAVE_PTHREAD_H ${CMAKE_USE_PTHREADS_INIT}) + list(APPEND CURL_LIBS ${CMAKE_THREAD_LIBS_INIT}) endif() - set(CURL_LIBS ${CURL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) endif() # Check for all needed libraries -check_library_exists_concat("socket" connect HAVE_LIBSOCKET) +if(WIN32) + if(WINCE) + set(_win32_winsock "ws2") + else() + set(_win32_winsock "ws2_32") + endif() + set(_win32_crypt32 "crypt32") -check_function_exists(gethostname HAVE_GETHOSTNAME) + if(MINGW32CE) # FIXME upstream: must specify the full path to avoid CMake converting "ws2" to "ws2.lib" + set(_win32_winsock "${MINGW32CE_LIBRARY_DIR}/lib${_win32_winsock}.a") + set(_win32_crypt32 "${MINGW32CE_LIBRARY_DIR}/lib${_win32_crypt32}.a") + endif() +elseif(DOS) + if(WATT_ROOT) + set(USE_WATT32 ON) + # FIXME upstream: must specify the full path to avoid CMake converting "watt" to "watt.lib" + list(APPEND CURL_LIBS "${WATT_ROOT}/lib/libwatt.a") + include_directories(SYSTEM "${WATT_ROOT}/inc") + list(APPEND CMAKE_REQUIRED_INCLUDES "${WATT_ROOT}/inc") + else() + message(FATAL_ERROR "Set WATT_ROOT variable to the root installation of Watt-32.") + endif() +elseif(AMIGA) + if(AMISSL_INCLUDE_DIR AND AMISSL_STUBS_LIBRARY AND AMISSL_AUTO_LIBRARY) + set(USE_AMISSL ON) + list(APPEND CMAKE_REQUIRED_INCLUDES "${AMISSL_INCLUDE_DIR}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${AMISSL_STUBS_LIBRARY}" "${AMISSL_AUTO_LIBRARY}") + set(OPENSSL_INCLUDE_DIR "${AMISSL_INCLUDE_DIR}") + set(OPENSSL_SSL_LIBRARY "${AMISSL_STUBS_LIBRARY}") + set(OPENSSL_CRYPTO_LIBRARY "${AMISSL_AUTO_LIBRARY}") + set(CURL_USE_OPENSSL ON) + set(CURL_CA_FALLBACK ON CACHE BOOL "") + endif() +elseif(NOT APPLE) + check_library_exists("socket" "connect" "" HAVE_LIBSOCKET) + if(HAVE_LIBSOCKET) + set(CURL_LIBS "socket" ${CURL_LIBS}) + endif() +endif() -if(WIN32) - check_library_exists_concat("ws2_32" getch HAVE_LIBWS2_32) - check_library_exists_concat("winmm" getch HAVE_LIBWINMM) +option(ENABLE_IPV6 "Enable IPv6 support" ON) +mark_as_advanced(ENABLE_IPV6) +if(ENABLE_IPV6) + include(CheckStructHasMember) + if(WIN32) + check_struct_has_member("struct sockaddr_in6" "sin6_scope_id" "winsock2.h;ws2tcpip.h" HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + else() + check_struct_has_member("struct sockaddr_in6" "sin6_scope_id" "netinet/in.h" HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + check_struct_has_member("struct sockaddr_in6" "sin6_addr" "netinet/in.h" HAVE_SOCKADDR_IN6_SIN6_ADDR) + if(NOT HAVE_SOCKADDR_IN6_SIN6_ADDR) + if(NOT DOS AND NOT AMIGA) + message(WARNING "struct sockaddr_in6 not available, disabling IPv6 support") + endif() + set(ENABLE_IPV6 OFF CACHE BOOL "Enable IPv6 support" FORCE) # Force the feature off as we use this name as guard macro + endif() - # Matching logic used for Curl_win32_random() - if(MINGW) - check_c_source_compiles(" - #include <_mingw.h> - #if defined(__MINGW64_VERSION_MAJOR) - #error - #endif - int main(void) { - return 0; - }" - HAVE_MINGW_ORIGINAL) + if(APPLE AND NOT ENABLE_ARES) + set(_use_core_foundation_and_core_services ON) + + find_library(SYSTEMCONFIGURATION_FRAMEWORK NAMES "SystemConfiguration") + mark_as_advanced(SYSTEMCONFIGURATION_FRAMEWORK) + if(NOT SYSTEMCONFIGURATION_FRAMEWORK) + message(FATAL_ERROR "SystemConfiguration framework not found") + endif() + list(APPEND CURL_LIBS "-framework SystemConfiguration") + endif() endif() endif() +if(ENABLE_IPV6 AND NOT WINCE) + set(USE_IPV6 ON) +endif() -if(0) # This code not needed for building within CMake. -# check SSL libraries -# TODO support GnuTLS +if(0) # XXX(cmake): not needed for build within cmake +# Check SSL libraries option(CURL_ENABLE_SSL "Enable SSL support" ON) +if(CURL_DEFAULT_SSL_BACKEND) + set(_valid_default_ssl_backend FALSE) +endif() + if(APPLE) - cmake_dependent_option(CURL_USE_SECTRANSP "enable Apple OS native SSL/TLS" OFF CURL_ENABLE_SSL OFF) + cmake_dependent_option(CURL_USE_SECTRANSP "Enable Apple OS native SSL/TLS (Secure Transport)" OFF CURL_ENABLE_SSL OFF) endif() if(WIN32) - cmake_dependent_option(CURL_USE_SCHANNEL "enable Windows native SSL/TLS" OFF CURL_ENABLE_SSL OFF) - cmake_dependent_option(CURL_WINDOWS_SSPI "Use windows libraries to allow NTLM authentication without OpenSSL" ON - CURL_USE_SCHANNEL OFF) + cmake_dependent_option(CURL_USE_SCHANNEL "Enable Windows native SSL/TLS (Schannel)" OFF CURL_ENABLE_SSL OFF) + option(CURL_WINDOWS_SSPI "Enable SSPI on Windows" ${CURL_USE_SCHANNEL}) endif() cmake_dependent_option(CURL_USE_MBEDTLS "Enable mbedTLS for SSL/TLS" OFF CURL_ENABLE_SSL OFF) cmake_dependent_option(CURL_USE_BEARSSL "Enable BearSSL for SSL/TLS" OFF CURL_ENABLE_SSL OFF) -cmake_dependent_option(CURL_USE_NSS "Enable NSS for SSL/TLS" OFF CURL_ENABLE_SSL OFF) -cmake_dependent_option(CURL_USE_WOLFSSL "enable wolfSSL for SSL/TLS" OFF CURL_ENABLE_SSL OFF) - -set(openssl_default ON) -if(WIN32 OR CURL_USE_SECTRANSP OR CURL_USE_SCHANNEL OR CURL_USE_MBEDTLS OR CURL_USE_NSS OR CURL_USE_WOLFSSL) - set(openssl_default OFF) +cmake_dependent_option(CURL_USE_WOLFSSL "Enable wolfSSL for SSL/TLS" OFF CURL_ENABLE_SSL OFF) +cmake_dependent_option(CURL_USE_GNUTLS "Enable GnuTLS for SSL/TLS" OFF CURL_ENABLE_SSL OFF) +cmake_dependent_option(CURL_USE_RUSTLS "Enable Rustls for SSL/TLS" OFF CURL_ENABLE_SSL OFF) + +if(WIN32 OR + CURL_USE_SECTRANSP OR + CURL_USE_SCHANNEL OR + CURL_USE_MBEDTLS OR + CURL_USE_BEARSSL OR + CURL_USE_WOLFSSL OR + CURL_USE_GNUTLS OR + CURL_USE_RUSTLS) + set(_openssl_default OFF) +else() + set(_openssl_default ON) endif() -cmake_dependent_option(CURL_USE_OPENSSL "Use OpenSSL code. Experimental" ${openssl_default} CURL_ENABLE_SSL OFF) -option(CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG "Disable automatic loading of OpenSSL configuration" OFF) +cmake_dependent_option(CURL_USE_OPENSSL "Enable OpenSSL for SSL/TLS" ${_openssl_default} CURL_ENABLE_SSL OFF) +option(USE_OPENSSL_QUIC "Use OpenSSL and nghttp3 libraries for HTTP/3 support" OFF) +if(USE_OPENSSL_QUIC AND NOT CURL_USE_OPENSSL) + message(WARNING "OpenSSL QUIC has been requested, but without enabling OpenSSL. Will not enable QUIC.") + set(USE_OPENSSL_QUIC OFF) endif() +option(CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG "Disable automatic loading of OpenSSL configuration" OFF) +endif() # XXX(cmake): end -count_true(enabled_ssl_options_count +curl_count_true(_enabled_ssl_options_count CURL_USE_SCHANNEL CURL_USE_SECTRANSP CURL_USE_OPENSSL CURL_USE_MBEDTLS CURL_USE_BEARSSL - CURL_USE_NSS CURL_USE_WOLFSSL + CURL_USE_GNUTLS + CURL_USE_RUSTLS ) -if(enabled_ssl_options_count GREATER "1") +if(_enabled_ssl_options_count GREATER 1) set(CURL_WITH_MULTI_SSL ON) +elseif(_enabled_ssl_options_count EQUAL 0) + set(CURL_DISABLE_HSTS ON) endif() if(CURL_USE_SCHANNEL) - set(SSL_ENABLED ON) - set(USE_SCHANNEL ON) # Windows native SSL/TLS support - set(USE_WINDOWS_SSPI ON) # CURL_USE_SCHANNEL implies CURL_WINDOWS_SSPI + set(_ssl_enabled ON) + set(USE_SCHANNEL ON) # Windows native SSL/TLS support + set(USE_WINDOWS_SSPI ON) # CURL_USE_SCHANNEL requires CURL_WINDOWS_SSPI + + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "schannel") + set(_valid_default_ssl_backend TRUE) + endif() endif() if(CURL_WINDOWS_SSPI) set(USE_WINDOWS_SSPI ON) endif() if(CURL_USE_SECTRANSP) - set(use_core_foundation ON) + set(_use_core_foundation_and_core_services ON) - find_library(SECURITY_FRAMEWORK "Security") + find_library(SECURITY_FRAMEWORK NAMES "Security") + mark_as_advanced(SECURITY_FRAMEWORK) if(NOT SECURITY_FRAMEWORK) - message(FATAL_ERROR "Security framework not found") + message(FATAL_ERROR "Security framework not found") endif() + list(APPEND CURL_LIBS "-framework Security") - set(SSL_ENABLED ON) + set(_ssl_enabled ON) set(USE_SECTRANSP ON) - list(APPEND CURL_LIBS "-framework Security") + + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "secure-transport") + set(_valid_default_ssl_backend TRUE) + endif() + + if(0) # XXX(cmake): not needed for build within cmake + message(WARNING "Secure Transport does not support TLS 1.3.") + endif() # XXX(cmake): end endif() -if(use_core_foundation) - find_library(COREFOUNDATION_FRAMEWORK "CoreFoundation") +if(_use_core_foundation_and_core_services) + find_library(COREFOUNDATION_FRAMEWORK NAMES "CoreFoundation") + mark_as_advanced(COREFOUNDATION_FRAMEWORK) if(NOT COREFOUNDATION_FRAMEWORK) - message(FATAL_ERROR "CoreFoundation framework not found") + message(FATAL_ERROR "CoreFoundation framework not found") endif() - list(APPEND CURL_LIBS "-framework CoreFoundation") + + find_library(CORESERVICES_FRAMEWORK NAMES "CoreServices") + mark_as_advanced(CORESERVICES_FRAMEWORK) + if(NOT CORESERVICES_FRAMEWORK) + message(FATAL_ERROR "CoreServices framework not found") + endif() + list(APPEND CURL_LIBS "-framework CoreServices") endif() if(CURL_USE_OPENSSL) @@ -588,28 +994,31 @@ if(CURL_USE_OPENSSL) "Could not find OpenSSL. Install an OpenSSL development package or " "configure CMake with -DCMAKE_USE_OPENSSL=OFF to build without OpenSSL.") endif() - set(SSL_ENABLED ON) + set(_ssl_enabled ON) set(USE_OPENSSL ON) - list(APPEND CURL_LIBS ${OPENSSL_LIBRARIES}) - include_directories(${OPENSSL_INCLUDE_DIR}) - if(WIN32) - list(APPEND CURL_LIBS "ws2_32") - if(NOT HAVE_MINGW_ORIGINAL) - list(APPEND CURL_LIBS "bcrypt") # for OpenSSL/LibreSSL - endif() - endif() + # Depend on OpenSSL via imported targets. This allows our dependents to + # get our dependencies transitively. + list(APPEND CURL_LIBS OpenSSL::SSL OpenSSL::Crypto) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE "openssl") - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - if(NOT DEFINED HAVE_RAND_EGD) - check_symbol_exists(RAND_egd "${CURL_INCLUDES}" HAVE_RAND_EGD) + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "openssl") + set(_valid_default_ssl_backend TRUE) endif() + set(_curl_ca_bundle_supported TRUE) + + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) if(NOT DEFINED HAVE_BORINGSSL) - check_symbol_exists(OPENSSL_IS_BORINGSSL "openssl/base.h" HAVE_BORINGSSL) + check_symbol_exists("OPENSSL_IS_BORINGSSL" "openssl/base.h" HAVE_BORINGSSL) endif() if(NOT DEFINED HAVE_AWSLC) - check_symbol_exists(OPENSSL_IS_AWSLC "openssl/base.h" HAVE_AWSLC) + check_symbol_exists("OPENSSL_IS_AWSLC" "openssl/base.h" HAVE_AWSLC) + endif() + if(NOT DEFINED HAVE_LIBRESSL) + check_symbol_exists("LIBRESSL_VERSION_NUMBER" "openssl/opensslv.h" HAVE_LIBRESSL) endif() + cmake_pop_check_state() # Optionally build with a specific CA cert bundle. if(CURL_CA_BUNDLE) @@ -619,297 +1028,518 @@ if(CURL_USE_OPENSSL) if(CURL_CA_PATH) add_definitions(-DCURL_CA_PATH="${CURL_CA_PATH}") endif() + + if(HAVE_BORINGSSL OR HAVE_AWSLC) + if(OPENSSL_USE_STATIC_LIBS AND CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND CURL_LIBS "stdc++") + list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++") + endif() + endif() + + if(HAVE_BORINGSSL) + set(_openssl "BoringSSL") + elseif(HAVE_AWSLC) + set(_openssl "AWS-LC") + elseif(HAVE_LIBRESSL) + set(_openssl "LibreSSL") + elseif(USE_AMISSL) + set(_openssl "AmiSSL") + else() + set(_openssl "OpenSSL") + endif() endif() if(CURL_USE_MBEDTLS) find_package(MbedTLS REQUIRED) - set(SSL_ENABLED ON) + set(_ssl_enabled ON) set(USE_MBEDTLS ON) list(APPEND CURL_LIBS ${MBEDTLS_LIBRARIES}) - include_directories(${MBEDTLS_INCLUDE_DIRS}) + list(APPEND CURL_LIBDIRS ${MBEDTLS_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${MBEDTLS_PC_REQUIRES}) + include_directories(SYSTEM ${MBEDTLS_INCLUDE_DIRS}) + link_directories(${MBEDTLS_LIBRARY_DIRS}) + if(MBEDTLS_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${MBEDTLS_CFLAGS}") + endif() + + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "mbedtls") + set(_valid_default_ssl_backend TRUE) + endif() + set(_curl_ca_bundle_supported TRUE) endif() if(CURL_USE_BEARSSL) find_package(BearSSL REQUIRED) - set(SSL_ENABLED ON) + set(_ssl_enabled ON) set(USE_BEARSSL ON) - list(APPEND CURL_LIBS ${BEARSSL_LIBRARY}) - include_directories(${BEARSSL_INCLUDE_DIRS}) + list(APPEND CURL_LIBS ${BEARSSL_LIBRARIES}) + include_directories(SYSTEM ${BEARSSL_INCLUDE_DIRS}) + + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "bearssl") + set(_valid_default_ssl_backend TRUE) + endif() + set(_curl_ca_bundle_supported TRUE) + + if(0) # XXX(cmake): not needed for build within cmake + message(WARNING "BearSSL does not support TLS 1.3.") + endif() # XXX(cmake): end endif() if(CURL_USE_WOLFSSL) find_package(WolfSSL REQUIRED) - set(SSL_ENABLED ON) + set(_ssl_enabled ON) set(USE_WOLFSSL ON) - list(APPEND CURL_LIBS ${WolfSSL_LIBRARIES}) - include_directories(${WolfSSL_INCLUDE_DIRS}) -endif() + list(APPEND CURL_LIBS ${WOLFSSL_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${WOLFSSL_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${WOLFSSL_PC_REQUIRES}) + include_directories(SYSTEM ${WOLFSSL_INCLUDE_DIRS}) + link_directories(${WOLFSSL_LIBRARY_DIRS}) + if(WOLFSSL_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${WOLFSSL_CFLAGS}") + endif() + + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "wolfssl") + set(_valid_default_ssl_backend TRUE) + endif() + set(_curl_ca_bundle_supported TRUE) +endif() + +if(CURL_USE_GNUTLS) + if(CURL_USE_PKGCONFIG) + find_package(PkgConfig QUIET) + pkg_check_modules(GNUTLS "gnutls") + if(GNUTLS_FOUND) + set(GNUTLS_LIBRARIES ${GNUTLS_LINK_LIBRARIES}) + string(REPLACE ";" " " GNUTLS_CFLAGS "${GNUTLS_CFLAGS}") + if(GNUTLS_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${GNUTLS_CFLAGS}") + endif() + endif() + endif() + if(NOT GNUTLS_FOUND) + find_package(GnuTLS REQUIRED) + endif() + find_package(Nettle REQUIRED) + set(_ssl_enabled ON) + set(USE_GNUTLS ON) + list(APPEND CURL_LIBS ${GNUTLS_LIBRARIES} ${NETTLE_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${GNUTLS_LIBRARY_DIRS} ${NETTLE_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE "gnutls" ${NETTLE_PC_REQUIRES}) + include_directories(SYSTEM ${GNUTLS_INCLUDE_DIRS} ${NETTLE_INCLUDE_DIRS}) + link_directories(${NETTLE_LIBRARY_DIRS}) + if(NETTLE_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${NETTLE_CFLAGS}") + endif() + + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "gnutls") + set(_valid_default_ssl_backend TRUE) + endif() + set(_curl_ca_bundle_supported TRUE) -if(CURL_USE_NSS) - find_package(NSS REQUIRED) - include_directories(${NSS_INCLUDE_DIRS}) - list(APPEND CURL_LIBS ${NSS_LIBRARIES}) - set(SSL_ENABLED ON) - set(USE_NSS ON) - if(NOT DEFINED HAVE_PK11_CREATEMANAGEDGENERICOBJECT) + if(NOT DEFINED HAVE_GNUTLS_SRP AND NOT CURL_DISABLE_SRP) cmake_push_check_state() - set(CMAKE_REQUIRED_INCLUDES ${NSS_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_LIBRARIES ${NSS_LIBRARIES}) - check_symbol_exists(PK11_CreateManagedGenericObject "pk11pub.h" HAVE_PK11_CREATEMANAGEDGENERICOBJECT) + list(APPEND CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") + check_symbol_exists("gnutls_srp_verifier" "gnutls/gnutls.h" HAVE_GNUTLS_SRP) cmake_pop_check_state() endif() endif() +if(CURL_USE_RUSTLS) + find_package(Rustls REQUIRED) + set(_ssl_enabled ON) + set(USE_RUSTLS ON) + list(APPEND CURL_LIBS ${RUSTLS_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${RUSTLS_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${RUSTLS_PC_REQUIRES}) + include_directories(SYSTEM ${RUSTLS_INCLUDE_DIRS}) + link_directories(${RUSTLS_LIBRARY_DIRS}) + if(RUSTLS_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${RUSTLS_CFLAGS}") + endif() + + if(NOT DEFINED HAVE_RUSTLS_SUPPORTED_HPKE) + if(RUSTLS_VERSION AND RUSTLS_VERSION VERSION_GREATER_EQUAL 0.15) + set(HAVE_RUSTLS_SUPPORTED_HPKE TRUE) + elseif(NOT RUSTLS_VERSION) + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_INCLUDES "${RUSTLS_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${RUSTLS_LIBRARIES}") + curl_required_libpaths("${RUSTLS_LIBRARY_DIRS}") + check_symbol_exists("rustls_supported_hpke" "rustls.h" HAVE_RUSTLS_SUPPORTED_HPKE) + cmake_pop_check_state() + endif() + endif() + if(NOT HAVE_RUSTLS_SUPPORTED_HPKE) + message(FATAL_ERROR "rustls-ffi library does not provide rustls_supported_hpke function. Required version is 0.15 or newer.") + endif() + + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "rustls") + set(_valid_default_ssl_backend TRUE) + endif() + set(_curl_ca_bundle_supported TRUE) +endif() + +if(CURL_DEFAULT_SSL_BACKEND AND NOT _valid_default_ssl_backend) + message(FATAL_ERROR "CURL_DEFAULT_SSL_BACKEND '${CURL_DEFAULT_SSL_BACKEND}' not enabled.") +endif() + # Keep ZLIB detection after TLS detection, -# and before calling CheckQuicSupportInOpenSSL. +# and before calling curl_openssl_check_exists(). set(HAVE_LIBZ OFF) -set(USE_ZLIB OFF) -find_package(ZLIB) +curl_dependency_option(CURL_ZLIB ZLIB "ZLIB") if(ZLIB_FOUND) set(HAVE_LIBZ ON) - set(USE_ZLIB ON) - - # Depend on ZLIB via imported targets if supported by the running - # version of CMake. This allows our dependents to get our dependencies - # transitively. - if(NOT CMAKE_VERSION VERSION_LESS 3.4) - if(CMAKE_USE_SYSTEM_ZLIB) - list(APPEND CURL_LIBS ZLIB::ZLIB) - else() - list(APPEND CURL_LIBS cmzlib) - endif() + # Depend on ZLIB via imported targets. This allows our dependents to + # get our dependencies transitively. + if(CMAKE_USE_SYSTEM_ZLIB) + list(APPEND CURL_LIBS ZLIB::ZLIB) else() - list(APPEND CURL_LIBS ${ZLIB_LIBRARIES}) - include_directories(${ZLIB_INCLUDE_DIRS}) - list(APPEND CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIRS}) + list(APPEND CURL_LIBS cmzlib) endif() + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE "zlib") endif() -option(CURL_BROTLI "Set to ON to enable building curl with brotli support." OFF) set(HAVE_BROTLI OFF) -if(CURL_BROTLI) - find_package(Brotli QUIET) - if(BROTLI_FOUND) - set(HAVE_BROTLI ON) - list(APPEND CURL_LIBS ${BROTLI_LIBRARIES}) - include_directories(${BROTLI_INCLUDE_DIRS}) - list(APPEND CMAKE_REQUIRED_INCLUDES ${BROTLI_INCLUDE_DIRS}) +curl_dependency_option(CURL_BROTLI Brotli "brotli") +if(BROTLI_FOUND) + set(HAVE_BROTLI ON) + list(APPEND CURL_LIBS ${BROTLI_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${BROTLI_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${BROTLI_PC_REQUIRES}) + include_directories(SYSTEM ${BROTLI_INCLUDE_DIRS}) + link_directories(${BROTLI_LIBRARY_DIRS}) + if(BROTLI_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${BROTLI_CFLAGS}") endif() endif() -option(CURL_ZSTD "Set to ON to enable building curl with zstd support." OFF) set(HAVE_ZSTD OFF) -if(CURL_ZSTD) - find_package(Zstd REQUIRED) - if (NOT DEFINED HAVE_ZSTD_CREATEDSTREAM) - cmake_push_check_state() - set(CMAKE_REQUIRED_INCLUDES ${Zstd_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_LIBRARIES ${Zstd_LIBRARIES}) - check_symbol_exists(ZSTD_createDStream "zstd.h" HAVE_ZSTD_CREATEDSTREAM) - cmake_pop_check_state() - endif() - if(Zstd_FOUND AND HAVE_ZSTD_CREATEDSTREAM) +curl_dependency_option(CURL_ZSTD Zstd "zstd") +if(ZSTD_FOUND) + if(ZSTD_VERSION VERSION_GREATER_EQUAL 1.0.0) set(HAVE_ZSTD ON) - list(APPEND CURL_LIBS ${Zstd_LIBRARIES}) - include_directories(${Zstd_INCLUDE_DIRS}) + list(APPEND CURL_LIBS ${ZSTD_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${ZSTD_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${ZSTD_PC_REQUIRES}) + include_directories(SYSTEM ${ZSTD_INCLUDE_DIRS}) + link_directories(${ZSTD_LIBRARY_DIRS}) + if(ZSTD_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${ZSTD_CFLAGS}") + endif() + else() + message(WARNING "zstd v1.0.0 or newer is required, disabling zstd support.") endif() endif() -option(USE_NGHTTP2 "Use Nghttp2 library" OFF) -if(USE_NGHTTP2) - find_package(NGHTTP2 REQUIRED) - include_directories(${NGHTTP2_INCLUDE_DIRS}) - list(APPEND CURL_LIBS ${NGHTTP2_LIBRARIES}) -endif() - -function(CheckQuicSupportInOpenSSL) - # Be sure that the OpenSSL/wolfSSL library actually supports QUIC. - if(NOT DEFINED HAVE_SSL_CTX_SET_QUIC_METHOD) - cmake_push_check_state() - if(USE_WOLFSSL) - set(CMAKE_REQUIRED_INCLUDES "${WolfSSL_INCLUDE_DIRS}") - set(CMAKE_REQUIRED_LIBRARIES "${WolfSSL_LIBRARIES}") - if(HAVE_LIBZ) - list(APPEND CMAKE_REQUIRED_INCLUDES "${ZLIB_INCLUDE_DIRS}") # Public wolfSSL headers require zlib headers - list(APPEND CMAKE_REQUIRED_LIBRARIES "${ZLIB_LIBRARIES}") +# Check function in an OpenSSL-like TLS backend. +macro(curl_openssl_check_exists) + cmake_push_check_state() + if(USE_OPENSSL) + list(APPEND CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DOPENSSL_SUPPRESS_DEPRECATED") # for SSL_CTX_set_srp_username deprecated since 3.0.0 + if(HAVE_LIBZ) + if(CMAKE_USE_SYSTEM_ZLIB) + list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB) + else() + list(APPEND CMAKE_REQUIRED_LIBRARIES cmzlib) endif() - if(WIN32) - list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32" "crypt32") + endif() + if(WIN32 AND NOT WINCE) + list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL + endif() + endif() + if(USE_WOLFSSL) + list(APPEND CMAKE_REQUIRED_INCLUDES "${WOLFSSL_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${WOLFSSL_LIBRARIES}") + curl_required_libpaths("${WOLFSSL_LIBRARY_DIRS}") + if(HAVE_LIBZ) + list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB) # Public wolfSSL headers also require zlib headers + endif() + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DHAVE_UINTPTR_T") # to pull in stdint.h (as of wolfSSL v5.5.4) + endif() + if(WIN32) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${_win32_winsock}" "${_win32_crypt32}") # for OpenSSL/wolfSSL + endif() + if(${ARGC} EQUAL 2) + check_function_exists(${ARGN}) + else() + check_symbol_exists(${ARGN}) # Uses CMAKE_REQUIRED_INCLUDES and CMAKE_REQUIRED_DEFINITIONS + endif() + cmake_pop_check_state() +endmacro() + +# Ensure that OpenSSL (or fork) or wolfSSL actually supports QUICTLS API. +macro(curl_openssl_check_quic) + if(USE_OPENSSL AND NOT USE_OPENSSL_QUIC) + if(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0) + if(NOT DEFINED HAVE_SSL_SET_QUIC_TLS_CBS) + curl_openssl_check_exists("SSL_set_quic_tls_cbs" HAVE_SSL_SET_QUIC_TLS_CBS) endif() - list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_UINTPTR_T) # to pull in stdint.h (as of wolfSSL v5.5.4) - check_symbol_exists(wolfSSL_set_quic_method "wolfssl/options.h;wolfssl/openssl/ssl.h" HAVE_SSL_CTX_SET_QUIC_METHOD) else() - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") - if(HAVE_LIBZ) - list(APPEND CMAKE_REQUIRED_LIBRARIES "${ZLIB_LIBRARIES}") - endif() - if(WIN32) - list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32") - if(NOT HAVE_MINGW_ORIGINAL) - list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL - endif() + if(NOT DEFINED HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) + curl_openssl_check_exists("SSL_set_quic_use_legacy_codepoint" HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) endif() - check_symbol_exists(SSL_CTX_set_quic_method "openssl/ssl.h" HAVE_SSL_CTX_SET_QUIC_METHOD) endif() - cmake_pop_check_state() endif() - if(NOT HAVE_SSL_CTX_SET_QUIC_METHOD) - message(FATAL_ERROR "QUIC support is missing in OpenSSL/LibreSSL/BoringSSL/wolfSSL. Try setting -DOPENSSL_ROOT_DIR") + if(USE_WOLFSSL AND NOT DEFINED HAVE_WOLFSSL_SET_QUIC_USE_LEGACY_CODEPOINT) + curl_openssl_check_exists("wolfSSL_set_quic_use_legacy_codepoint" HAVE_WOLFSSL_SET_QUIC_USE_LEGACY_CODEPOINT) endif() -endfunction() + if(NOT HAVE_SSL_SET_QUIC_TLS_CBS AND + NOT HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT AND + NOT HAVE_WOLFSSL_SET_QUIC_USE_LEGACY_CODEPOINT) + message(FATAL_ERROR "QUICTLS API support is missing from OpenSSL/fork/wolfSSL. Try setting -DOPENSSL_ROOT_DIR") + endif() +endmacro() + +if(USE_WOLFSSL) + curl_openssl_check_exists("wolfSSL_get_peer_certificate" HAVE_WOLFSSL_GET_PEER_CERTIFICATE) + curl_openssl_check_exists("wolfSSL_UseALPN" HAVE_WOLFSSL_USEALPN) + curl_openssl_check_exists("wolfSSL_DES_ecb_encrypt" HAVE_WOLFSSL_DES_ECB_ENCRYPT) + curl_openssl_check_exists("wolfSSL_BIO_new" HAVE_WOLFSSL_BIO_NEW) + curl_openssl_check_exists("wolfSSL_BIO_set_shutdown" HAVE_WOLFSSL_BIO_SET_SHUTDOWN) +endif() + +if(USE_OPENSSL) + if(NOT DEFINED HAVE_SSL_SET0_WBIO) + curl_openssl_check_exists("SSL_set0_wbio" HAVE_SSL_SET0_WBIO) + endif() + if(NOT DEFINED HAVE_OPENSSL_SRP AND NOT CURL_DISABLE_SRP) + curl_openssl_check_exists("SSL_CTX_set_srp_username" "openssl/ssl.h" HAVE_OPENSSL_SRP) + endif() +endif() + +option(USE_HTTPSRR "Enable HTTPS RR support" OFF) +option(USE_ECH "Enable ECH support" OFF) +if(USE_ECH) + if(USE_OPENSSL OR USE_WOLFSSL OR USE_RUSTLS) + # Be sure that the TLS library actually supports ECH. + if(USE_WOLFSSL) + curl_openssl_check_exists("wolfSSL_CTX_GenerateEchConfig" HAVE_WOLFSSL_CTX_GENERATEECHCONFIG) + endif() + if(USE_OPENSSL) + curl_openssl_check_exists("SSL_set1_ech_config_list" HAVE_SSL_SET1_ECH_CONFIG_LIST) + endif() + if(HAVE_WOLFSSL_CTX_GENERATEECHCONFIG OR + HAVE_SSL_SET1_ECH_CONFIG_LIST OR + USE_RUSTLS) + set(HAVE_ECH 1) + endif() + if(NOT HAVE_ECH) + message(FATAL_ERROR "ECH support missing in OpenSSL/BoringSSL/AWS-LC/wolfSSL/rustls-ffi") + else() + message(STATUS "ECH enabled") + # ECH wants HTTPSRR + set(USE_HTTPSRR ON) + message(STATUS "HTTPSRR enabled") + endif() + else() + message(FATAL_ERROR "ECH requires ECH-enabled OpenSSL, BoringSSL, AWS-LC, wolfSSL or rustls-ffi") + endif() +endif() + +option(USE_SSLS_EXPORT "Enable SSL session export support" OFF) +if(USE_SSLS_EXPORT) + if(_ssl_enabled) + message(STATUS "SSL export enabled.") + else() + message(FATAL_ERROR "SSL session export requires SSL enabled") + endif() +endif() + +option(USE_NGHTTP2 "Use nghttp2 library" ON) +if(USE_NGHTTP2) + find_package(NGHTTP2) + if(NGHTTP2_FOUND) + list(APPEND CURL_LIBS ${NGHTTP2_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${NGHTTP2_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${NGHTTP2_PC_REQUIRES}) + include_directories(SYSTEM ${NGHTTP2_INCLUDE_DIRS}) + link_directories(${NGHTTP2_LIBRARY_DIRS}) + if(NGHTTP2_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${NGHTTP2_CFLAGS}") + endif() + else() + set(USE_NGHTTP2 OFF) + endif() +endif() option(USE_NGTCP2 "Use ngtcp2 and nghttp3 libraries for HTTP/3 support" OFF) if(USE_NGTCP2) if(USE_OPENSSL OR USE_WOLFSSL) if(USE_WOLFSSL) - find_package(NGTCP2 REQUIRED wolfSSL) - elseif(HAVE_BORINGSSL) - find_package(NGTCP2 REQUIRED BoringSSL) + find_package(NGTCP2 REQUIRED "wolfSSL") + elseif(HAVE_BORINGSSL OR HAVE_AWSLC) + find_package(NGTCP2 REQUIRED "BoringSSL") + elseif(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0 AND NOT USE_OPENSSL_QUIC) + find_package(NGTCP2 REQUIRED "ossl") + if(NGTCP2_VERSION VERSION_LESS 1.12.0) + message(FATAL_ERROR "ngtcp2 1.12.0 or upper required for OpenSSL") + endif() + set(OPENSSL_QUIC_API2 1) else() - find_package(NGTCP2 REQUIRED OpenSSL) + find_package(NGTCP2 REQUIRED "quictls") + if(NOT HAVE_LIBRESSL) + set(_openssl "quictls") + endif() endif() - CheckQuicSupportInOpenSSL() + curl_openssl_check_quic() elseif(USE_GNUTLS) - # TODO add GnuTLS support as vtls library. - find_package(NGTCP2 REQUIRED GnuTLS) + find_package(NGTCP2 REQUIRED "GnuTLS") else() - message(FATAL_ERROR "ngtcp2 requires OpenSSL, wolfSSL or GnuTLS") + message(FATAL_ERROR "ngtcp2 requires a supported TLS-backend") endif() - set(USE_NGTCP2 ON) - include_directories(${NGTCP2_INCLUDE_DIRS}) list(APPEND CURL_LIBS ${NGTCP2_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${NGTCP2_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${NGTCP2_PC_REQUIRES}) + include_directories(SYSTEM ${NGTCP2_INCLUDE_DIRS}) + link_directories(${NGTCP2_LIBRARY_DIRS}) + if(NGTCP2_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${NGTCP2_CFLAGS}") + endif() find_package(NGHTTP3 REQUIRED) set(USE_NGHTTP3 ON) - include_directories(${NGHTTP3_INCLUDE_DIRS}) list(APPEND CURL_LIBS ${NGHTTP3_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${NGHTTP3_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${NGHTTP3_PC_REQUIRES}) + include_directories(SYSTEM ${NGHTTP3_INCLUDE_DIRS}) + link_directories(${NGHTTP3_LIBRARY_DIRS}) + if(NGHTTP3_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${NGHTTP3_CFLAGS}") + endif() endif() option(USE_QUICHE "Use quiche library for HTTP/3 support" OFF) if(USE_QUICHE) if(USE_NGTCP2) - message(FATAL_ERROR "Only one HTTP/3 backend can be selected!") + message(FATAL_ERROR "Only one HTTP/3 backend can be selected") endif() - find_package(QUICHE REQUIRED) - CheckQuicSupportInOpenSSL() - set(USE_QUICHE ON) - include_directories(${QUICHE_INCLUDE_DIRS}) + find_package(Quiche REQUIRED) + if(NOT HAVE_BORINGSSL) + message(FATAL_ERROR "quiche requires BoringSSL") + endif() + curl_openssl_check_quic() list(APPEND CURL_LIBS ${QUICHE_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${QUICHE_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${QUICHE_PC_REQUIRES}) + include_directories(SYSTEM ${QUICHE_INCLUDE_DIRS}) + link_directories(${QUICHE_LIBRARY_DIRS}) + if(QUICHE_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${QUICHE_CFLAGS}") + endif() if(NOT DEFINED HAVE_QUICHE_CONN_SET_QLOG_FD) cmake_push_check_state() - set(CMAKE_REQUIRED_INCLUDES "${QUICHE_INCLUDE_DIRS}") - set(CMAKE_REQUIRED_LIBRARIES "${QUICHE_LIBRARIES}") - check_symbol_exists(quiche_conn_set_qlog_fd "quiche.h" HAVE_QUICHE_CONN_SET_QLOG_FD) + list(APPEND CMAKE_REQUIRED_INCLUDES "${QUICHE_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${QUICHE_LIBRARIES}") + check_symbol_exists("quiche_conn_set_qlog_fd" "quiche.h" HAVE_QUICHE_CONN_SET_QLOG_FD) cmake_pop_check_state() endif() endif() -option(USE_MSH3 "Use msquic library for HTTP/3 support" OFF) +option(USE_MSH3 "Use msh3/msquic library for HTTP/3 support" OFF) if(USE_MSH3) if(USE_NGTCP2 OR USE_QUICHE) - message(FATAL_ERROR "Only one HTTP/3 backend can be selected!") + message(FATAL_ERROR "Only one HTTP/3 backend can be selected") endif() - set(USE_MSH3 ON) - include_directories(${MSH3_INCLUDE_DIRS}) + if(NOT WIN32) + if(NOT USE_OPENSSL) + message(FATAL_ERROR "msh3/msquic requires OpenSSL fork with QUIC API") + endif() + curl_openssl_check_quic() + endif() + find_package(MSH3 REQUIRED) list(APPEND CURL_LIBS ${MSH3_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${MSH3_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${MSH3_PC_REQUIRES}) + include_directories(SYSTEM ${MSH3_INCLUDE_DIRS}) + link_directories(${MSH3_LIBRARY_DIRS}) + if(MSH3_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${MSH3_CFLAGS}") + endif() endif() -if(NOT CURL_DISABLE_LDAP) - if(WIN32) +if(USE_OPENSSL_QUIC) + if(USE_NGTCP2 OR USE_QUICHE OR USE_MSH3) + message(FATAL_ERROR "Only one HTTP/3 backend can be selected") + endif() + find_package(OpenSSL 3.3.0 REQUIRED) + + find_package(NGHTTP3 REQUIRED) + set(USE_NGHTTP3 ON) + list(APPEND CURL_LIBS ${NGHTTP3_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${NGHTTP3_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${NGHTTP3_PC_REQUIRES}) + include_directories(SYSTEM ${NGHTTP3_INCLUDE_DIRS}) + link_directories(${NGHTTP3_LIBRARY_DIRS}) + if(NGHTTP3_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${NGHTTP3_CFLAGS}") + endif() +endif() + +if(CURL_WITH_MULTI_SSL AND (USE_NGTCP2 OR USE_QUICHE OR USE_MSH3 OR USE_OPENSSL_QUIC)) + message(FATAL_ERROR "MultiSSL cannot be enabled with HTTP/3 and vice versa.") +endif() + +if(NOT CURL_DISABLE_SRP AND (HAVE_GNUTLS_SRP OR HAVE_OPENSSL_SRP)) + set(USE_TLS_SRP 1) +endif() + +if(NOT CURL_DISABLE_LDAP) + if(WIN32 AND NOT WINDOWS_STORE AND NOT WINCE) option(USE_WIN32_LDAP "Use Windows LDAP implementation" ON) if(USE_WIN32_LDAP) - check_library_exists_concat("wldap32" cldap_open HAVE_WLDAP32) - if(NOT HAVE_WLDAP32) - set(USE_WIN32_LDAP OFF) - elseif(NOT CURL_DISABLE_LDAPS) + list(APPEND CURL_LIBS "wldap32") + if(NOT CURL_DISABLE_LDAPS) set(HAVE_LDAP_SSL ON) endif() endif() endif() - option(CURL_USE_OPENLDAP "Use OpenLDAP code." OFF) - mark_as_advanced(CURL_USE_OPENLDAP) - set(CMAKE_LDAP_LIB "ldap" CACHE STRING "Name or full path to ldap library") - set(CMAKE_LBER_LIB "lber" CACHE STRING "Name or full path to lber library") - - if(CURL_USE_OPENLDAP AND USE_WIN32_LDAP) - message(FATAL_ERROR "Cannot use USE_WIN32_LDAP and CURL_USE_OPENLDAP at the same time") - endif() - - # Now that we know, we're not using windows LDAP... - if(USE_WIN32_LDAP) - check_include_file_concat("winldap.h" HAVE_WINLDAP_H) - else() + # Now that we know, we are not using Windows LDAP... + if(NOT USE_WIN32_LDAP) # Check for LDAP - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES}) - check_library_exists_concat(${CMAKE_LDAP_LIB} ldap_init HAVE_LIBLDAP) - check_library_exists_concat(${CMAKE_LBER_LIB} ber_init HAVE_LIBLBER) - - set(CMAKE_REQUIRED_INCLUDES_BAK ${CMAKE_REQUIRED_INCLUDES}) - set(CMAKE_LDAP_INCLUDE_DIR "" CACHE STRING "Path to LDAP include directory") - if(CMAKE_LDAP_INCLUDE_DIR) - list(APPEND CMAKE_REQUIRED_INCLUDES ${CMAKE_LDAP_INCLUDE_DIR}) + cmake_push_check_state() + if(USE_OPENSSL) + list(APPEND CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) endif() - check_include_file_concat("ldap.h" HAVE_LDAP_H) - check_include_file_concat("lber.h" HAVE_LBER_H) - - if(NOT HAVE_LDAP_H) - message(STATUS "LDAP_H not found CURL_DISABLE_LDAP set ON") - set(CURL_DISABLE_LDAP ON CACHE BOOL "" FORCE) - set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_BAK}) #LDAP includes won't be used - elseif(NOT HAVE_LIBLDAP) - message(STATUS "LDAP library '${CMAKE_LDAP_LIB}' not found CURL_DISABLE_LDAP set ON") - set(CURL_DISABLE_LDAP ON CACHE BOOL "" FORCE) - set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_BAK}) #LDAP includes won't be used - else() - if(CURL_USE_OPENLDAP) - set(USE_OPENLDAP ON) - endif() - if(CMAKE_LDAP_INCLUDE_DIR) - include_directories(${CMAKE_LDAP_INCLUDE_DIR}) + find_package(LDAP) + if(LDAP_FOUND) + set(HAVE_LBER_H 1) + set(CURL_LIBS ${LDAP_LIBRARIES} ${CURL_LIBS}) + list(APPEND CURL_LIBDIRS ${LDAP_LIBRARY_DIRS}) + if(LDAP_PC_REQUIRES) + set(LIBCURL_PC_REQUIRES_PRIVATE ${LDAP_PC_REQUIRES} ${LIBCURL_PC_REQUIRES_PRIVATE}) endif() - set(NEED_LBER_H ON) - set(_HEADER_LIST) - if(HAVE_WINDOWS_H) - list(APPEND _HEADER_LIST "windows.h") + include_directories(SYSTEM ${LDAP_INCLUDE_DIRS}) + link_directories(${LDAP_LIBRARY_DIRS}) + if(LDAP_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LDAP_CFLAGS}") endif() - if(HAVE_SYS_TYPES_H) - list(APPEND _HEADER_LIST "sys/types.h") - endif() - list(APPEND _HEADER_LIST "ldap.h") - set(_SRC_STRING "") - foreach(_HEADER ${_HEADER_LIST}) - set(_INCLUDE_STRING "${_INCLUDE_STRING}#include <${_HEADER}>\n") - endforeach() + # LDAP feature checks - set(_SRC_STRING - " - ${_INCLUDE_STRING} - int main(int argc, char ** argv) - { - BerValue *bvp = NULL; - BerElement *bep = ber_init(bvp); - ber_free(bep, 1); - return 0; - }" - ) - list(APPEND CMAKE_REQUIRED_DEFINITIONS -DLDAP_DEPRECATED=1) - list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LDAP_LIB}) - if(HAVE_LIBLBER) - list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LBER_LIB}) - endif() - check_c_source_compiles("${_SRC_STRING}" NOT_NEED_LBER_H) - unset(CMAKE_REQUIRED_LIBRARIES) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DLDAP_DEPRECATED=1") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${LDAP_LIBRARIES}") + curl_required_libpaths("${LDAP_LIBRARY_DIRS}") - if(NOT_NEED_LBER_H) - set(NEED_LBER_H OFF) - else() - set(CURL_TEST_DEFINES "${CURL_TEST_DEFINES} -DNEED_LBER_H") + check_function_exists("ldap_url_parse" HAVE_LDAP_URL_PARSE) + check_function_exists("ldap_init_fd" HAVE_LDAP_INIT_FD) + + check_include_file("ldap_ssl.h" HAVE_LDAP_SSL_H) + + if(HAVE_LDAP_INIT_FD) + set(USE_OPENLDAP ON) + endif() + if(NOT CURL_DISABLE_LDAPS) + set(HAVE_LDAP_SSL ON) endif() + else() + message(STATUS "LDAP not found. CURL_DISABLE_LDAP set ON") + set(CURL_DISABLE_LDAP ON CACHE BOOL "" FORCE) endif() + cmake_pop_check_state() endif() endif() @@ -921,69 +1551,139 @@ if(CURL_DISABLE_LDAP) endif() endif() -if(NOT CURL_DISABLE_LDAPS) - check_include_file_concat("ldap_ssl.h" HAVE_LDAP_SSL_H) +if(WIN32) + option(USE_WIN32_IDN "Use WinIDN for IDN support" OFF) + if(USE_WIN32_IDN) + list(APPEND CURL_LIBS "normaliz") + endif() +else() + set(USE_WIN32_IDN OFF) endif() -# Check for idn2 -option(USE_LIBIDN2 "Use libidn2 for IDN support" ON) -if(USE_LIBIDN2) - check_library_exists_concat("idn2" idn2_lookup_ul HAVE_LIBIDN2) +if(APPLE) + option(USE_APPLE_IDN "Use Apple built-in IDN support" OFF) + if(USE_APPLE_IDN) + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_LIBRARIES "icucore") + check_symbol_exists("uidna_openUTS46" "unicode/uidna.h" HAVE_APPLE_IDN) + cmake_pop_check_state() + if(HAVE_APPLE_IDN) + list(APPEND CURL_LIBS "icucore" "iconv") + else() + set(USE_APPLE_IDN OFF) + endif() + endif() else() - set(HAVE_LIBIDN2 OFF) + set(USE_APPLE_IDN OFF) endif() -if(WIN32) - option(USE_WIN32_IDN "Use WinIDN for IDN support" OFF) - if(USE_WIN32_IDN) - list(APPEND CURL_LIBS "normaliz") +# Check for libidn2 +option(USE_LIBIDN2 "Use libidn2 for IDN support" ON) +set(HAVE_IDN2_H OFF) +set(HAVE_LIBIDN2 OFF) +if(USE_LIBIDN2 AND NOT USE_APPLE_IDN AND NOT USE_WIN32_IDN) + find_package(Libidn2 QUIET) + if(LIBIDN2_FOUND) + set(CURL_LIBS ${LIBIDN2_LIBRARIES} ${CURL_LIBS}) + list(APPEND CURL_LIBDIRS ${LIBIDN2_LIBRARY_DIRS}) + set(LIBCURL_PC_REQUIRES_PRIVATE ${LIBIDN2_PC_REQUIRES} ${LIBCURL_PC_REQUIRES_PRIVATE}) + include_directories(SYSTEM ${LIBIDN2_INCLUDE_DIRS}) + link_directories(${LIBIDN2_LIBRARY_DIRS}) + if(LIBIDN2_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LIBIDN2_CFLAGS}") + endif() + set(HAVE_IDN2_H 1) + set(HAVE_LIBIDN2 1) endif() endif() -#libpsl -option(CURL_USE_LIBPSL "Use libPSL" ON) +# libpsl +option(CURL_USE_LIBPSL "Use libpsl" ON) mark_as_advanced(CURL_USE_LIBPSL) set(USE_LIBPSL OFF) if(CURL_USE_LIBPSL) - find_package(LibPSL) - if(LIBPSL_FOUND) - list(APPEND CURL_LIBS ${LIBPSL_LIBRARY}) - list(APPEND CMAKE_REQUIRED_INCLUDES "${LIBPSL_INCLUDE_DIR}") - include_directories("${LIBPSL_INCLUDE_DIR}") - set(USE_LIBPSL ON) + find_package(Libpsl REQUIRED) + list(APPEND CURL_LIBS ${LIBPSL_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${LIBPSL_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${LIBPSL_PC_REQUIRES}) + include_directories(SYSTEM ${LIBPSL_INCLUDE_DIRS}) + link_directories(${LIBPSL_LIBRARY_DIRS}) + if(LIBPSL_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LIBPSL_CFLAGS}") endif() + set(USE_LIBPSL ON) endif() -#libSSH2 -option(CURL_USE_LIBSSH2 "Use libSSH2" ON) +# libssh2 +option(CURL_USE_LIBSSH2 "Use libssh2" ON) mark_as_advanced(CURL_USE_LIBSSH2) set(USE_LIBSSH2 OFF) if(CURL_USE_LIBSSH2) - find_package(LibSSH2) + find_package(Libssh2) if(LIBSSH2_FOUND) - list(APPEND CURL_LIBS ${LIBSSH2_LIBRARY}) - list(APPEND CMAKE_REQUIRED_INCLUDES "${LIBSSH2_INCLUDE_DIR}") - include_directories("${LIBSSH2_INCLUDE_DIR}") + set(CURL_LIBS ${LIBSSH2_LIBRARIES} ${CURL_LIBS}) # keep it before TLS-crypto, compression + list(APPEND CURL_LIBDIRS ${LIBSSH2_LIBRARY_DIRS}) + set(LIBCURL_PC_REQUIRES_PRIVATE ${LIBSSH2_PC_REQUIRES} ${LIBCURL_PC_REQUIRES_PRIVATE}) + include_directories(SYSTEM ${LIBSSH2_INCLUDE_DIRS}) + link_directories(${LIBSSH2_LIBRARY_DIRS}) + if(LIBSSH2_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LIBSSH2_CFLAGS}") + endif() set(USE_LIBSSH2 ON) endif() endif() # libssh -option(CURL_USE_LIBSSH "Use libSSH" OFF) +option(CURL_USE_LIBSSH "Use libssh" OFF) mark_as_advanced(CURL_USE_LIBSSH) if(NOT USE_LIBSSH2 AND CURL_USE_LIBSSH) - find_package(libssh CONFIG) - if(libssh_FOUND) - message(STATUS "Found libssh ${libssh_VERSION}") - # Use imported target for include and library paths. - list(APPEND CURL_LIBS ssh) - set(USE_LIBSSH ON) + find_package(Libssh REQUIRED) + set(CURL_LIBS ${LIBSSH_LIBRARIES} ${CURL_LIBS}) # keep it before TLS-crypto, compression + list(APPEND CURL_LIBDIRS ${LIBSSH_LIBRARY_DIRS}) + set(LIBCURL_PC_REQUIRES_PRIVATE ${LIBSSH_PC_REQUIRES} ${LIBCURL_PC_REQUIRES_PRIVATE}) + include_directories(SYSTEM ${LIBSSH_INCLUDE_DIRS}) + link_directories(${LIBSSH_LIBRARY_DIRS}) + if(LIBSSH_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LIBSSH_CFLAGS}") + endif() + set(USE_LIBSSH ON) +endif() + +# wolfSSH +option(CURL_USE_WOLFSSH "Use wolfSSH" OFF) +mark_as_advanced(CURL_USE_WOLFSSH) +set(USE_WOLFSSH OFF) +if(NOT USE_LIBSSH2 AND NOT USE_LIBSSH AND CURL_USE_WOLFSSH) + if(USE_WOLFSSL) + find_package(WolfSSH) + if(WOLFSSH_FOUND) + set(CURL_LIBS ${WOLFSSH_LIBRARIES} ${CURL_LIBS}) # keep it before TLS-crypto, compression + include_directories(SYSTEM ${WOLFSSH_INCLUDE_DIRS}) + set(USE_WOLFSSH ON) + endif() + else() + message(WARNING "wolfSSH requires wolfSSL. Skipping.") + endif() +endif() + +option(CURL_USE_GSASL "Use libgsasl" OFF) +mark_as_advanced(CURL_USE_GSASL) +if(CURL_USE_GSASL) + find_package(Libgsasl REQUIRED) + list(APPEND CURL_LIBS ${LIBGSASL_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${LIBGSASL_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${LIBGSASL_PC_REQUIRES}) + include_directories(SYSTEM ${LIBGSASL_INCLUDE_DIRS}) + link_directories(${LIBGSASL_LIBRARY_DIRS}) + if(LIBGSASL_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LIBGSASL_CFLAGS}") endif() + set(USE_GSASL ON) endif() -option(CURL_USE_GSSAPI "Use GSSAPI implementation (right now only Heimdal is supported with CMake build)" OFF) +option(CURL_USE_GSSAPI "Use GSSAPI implementation" OFF) mark_as_advanced(CURL_USE_GSSAPI) if(CURL_USE_GSSAPI) @@ -991,332 +1691,400 @@ if(CURL_USE_GSSAPI) set(HAVE_GSSAPI ${GSS_FOUND}) if(GSS_FOUND) + list(APPEND CURL_LIBS ${GSS_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${GSS_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${GSS_PC_REQUIRES}) + include_directories(SYSTEM ${GSS_INCLUDE_DIRS}) + link_directories(${GSS_LIBRARY_DIRS}) + if(GSS_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${GSS_CFLAGS}") + endif() - message(STATUS "Found ${GSS_FLAVOUR} GSSAPI version: \"${GSS_VERSION}\"") - - list(APPEND CMAKE_REQUIRED_INCLUDES ${GSS_INCLUDE_DIR}) - check_include_file_concat("gssapi/gssapi.h" HAVE_GSSAPI_GSSAPI_H) - check_include_file_concat("gssapi/gssapi_generic.h" HAVE_GSSAPI_GSSAPI_GENERIC_H) - check_include_file_concat("gssapi/gssapi_krb5.h" HAVE_GSSAPI_GSSAPI_KRB5_H) + if(GSS_FLAVOUR STREQUAL "GNU") + set(HAVE_GSSGNU 1) + else() + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_INCLUDES "${GSS_INCLUDE_DIRS}") - if(GSS_FLAVOUR STREQUAL "Heimdal") - set(HAVE_GSSHEIMDAL ON) - else() # MIT - set(HAVE_GSSMIT ON) - set(_INCLUDE_LIST "") + set(_include_list "") + check_include_file("gssapi/gssapi.h" HAVE_GSSAPI_GSSAPI_H) if(HAVE_GSSAPI_GSSAPI_H) - list(APPEND _INCLUDE_LIST "gssapi/gssapi.h") - endif() - if(HAVE_GSSAPI_GSSAPI_GENERIC_H) - list(APPEND _INCLUDE_LIST "gssapi/gssapi_generic.h") - endif() - if(HAVE_GSSAPI_GSSAPI_KRB5_H) - list(APPEND _INCLUDE_LIST "gssapi/gssapi_krb5.h") + list(APPEND _include_list "gssapi/gssapi.h") endif() + check_include_files("${_include_list};gssapi/gssapi_generic.h" HAVE_GSSAPI_GSSAPI_GENERIC_H) - string(REPLACE ";" " " _COMPILER_FLAGS_STR "${GSS_COMPILER_FLAGS}") - string(REPLACE ";" " " _LINKER_FLAGS_STR "${GSS_LINKER_FLAGS}") - - foreach(_dir ${GSS_LINK_DIRECTORIES}) - set(_LINKER_FLAGS_STR "${_LINKER_FLAGS_STR} -L\"${_dir}\"") - endforeach() + if(GSS_FLAVOUR STREQUAL "MIT") + check_include_files("${_include_list};gssapi/gssapi_krb5.h" _have_gssapi_gssapi_krb5_h) + if(HAVE_GSSAPI_GSSAPI_GENERIC_H) + list(APPEND _include_list "gssapi/gssapi_generic.h") + endif() + if(_have_gssapi_gssapi_krb5_h) + list(APPEND _include_list "gssapi/gssapi_krb5.h") + endif() - if(NOT DEFINED HAVE_GSS_C_NT_HOSTBASED_SERVICE) - set(CMAKE_REQUIRED_FLAGS "${_COMPILER_FLAGS_STR} ${_LINKER_FLAGS_STR}") - set(CMAKE_REQUIRED_LIBRARIES ${GSS_LIBRARIES}) - check_symbol_exists("GSS_C_NT_HOSTBASED_SERVICE" ${_INCLUDE_LIST} HAVE_GSS_C_NT_HOSTBASED_SERVICE) - unset(CMAKE_REQUIRED_LIBRARIES) - endif() - if(NOT HAVE_GSS_C_NT_HOSTBASED_SERVICE) - set(HAVE_OLD_GSSMIT ON) + if(NOT DEFINED HAVE_GSS_C_NT_HOSTBASED_SERVICE) + string(APPEND CMAKE_REQUIRED_FLAGS " ${GSS_CFLAGS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${GSS_LIBRARIES}") + curl_required_libpaths("${GSS_LIBRARY_DIRS}") + check_symbol_exists("GSS_C_NT_HOSTBASED_SERVICE" "${_include_list}" HAVE_GSS_C_NT_HOSTBASED_SERVICE) + endif() + if(NOT HAVE_GSS_C_NT_HOSTBASED_SERVICE) + set(HAVE_OLD_GSSMIT ON) + endif() endif() + unset(_include_list) + cmake_pop_check_state() endif() - - include_directories(${GSS_INCLUDE_DIR}) - link_directories(${GSS_LINK_DIRECTORIES}) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GSS_COMPILER_FLAGS}") - string(REPLACE ";" " " GSS_LINKER_FLAGS "${GSS_LINKER_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GSS_LINKER_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GSS_LINKER_FLAGS}") - set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} ${GSS_LINKER_FLAGS}") - list(APPEND CURL_LIBS ${GSS_LIBRARIES}) - else() - message(WARNING "GSSAPI support has been requested but no supporting libraries found. Skipping.") + message(WARNING "GSSAPI has been requested, but no supporting libraries found. Skipping.") endif() endif() -option(ENABLE_UNIX_SOCKETS "Define if you want Unix domain sockets support" ON) -if(ENABLE_UNIX_SOCKETS) - include(CheckStructHasMember) - if(WIN32) +# libuv +option(CURL_USE_LIBUV "Use libuv for event-based tests" OFF) +if(CURL_USE_LIBUV) + if(NOT ENABLE_DEBUG) + message(FATAL_ERROR "Using libuv without debug support enabled is useless") + endif() + find_package(Libuv REQUIRED) + list(APPEND CURL_LIBS ${LIBUV_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${LIBUV_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${LIBUV_PC_REQUIRES}) + include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS}) + link_directories(${LIBUV_LIBRARY_DIRS}) + if(LIBUV_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LIBUV_CFLAGS}") + endif() + set(USE_LIBUV ON) + set(HAVE_UV_H ON) +endif() + +option(USE_LIBRTMP "Enable librtmp from rtmpdump" OFF) +if(USE_LIBRTMP) + find_package(Librtmp REQUIRED) + list(APPEND CURL_LIBS ${LIBRTMP_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${LIBRTMP_LIBRARY_DIRS}) + list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${LIBRTMP_PC_REQUIRES}) + include_directories(SYSTEM ${LIBRTMP_INCLUDE_DIRS}) + link_directories(${LIBRTMP_LIBRARY_DIRS}) + if(LIBRTMP_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${LIBRTMP_CFLAGS}") + endif() +endif() + +option(ENABLE_UNIX_SOCKETS "Enable Unix domain sockets support" ON) +if(ENABLE_UNIX_SOCKETS AND NOT WINCE) + if(WIN32 OR DOS) set(USE_UNIX_SOCKETS ON) else() - check_struct_has_member("struct sockaddr_un" sun_path "sys/un.h" USE_UNIX_SOCKETS) + include(CheckStructHasMember) + check_struct_has_member("struct sockaddr_un" "sun_path" "sys/un.h" USE_UNIX_SOCKETS) endif() else() unset(USE_UNIX_SOCKETS CACHE) endif() - -if(0) # This code not needed for building within CMake. +if(0) # XXX(cmake): not needed for build within cmake # # CA handling # -set(CURL_CA_BUNDLE "auto" CACHE STRING - "Path to the CA bundle. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") -set(CURL_CA_FALLBACK OFF CACHE BOOL - "Set ON to use built-in CA store of TLS backend. Defaults to OFF") -set(CURL_CA_PATH "auto" CACHE STRING - "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") - -if("${CURL_CA_BUNDLE}" STREQUAL "") - message(FATAL_ERROR "Invalid value of CURL_CA_BUNDLE. Use 'none', 'auto' or file path.") -elseif("${CURL_CA_BUNDLE}" STREQUAL "none") - unset(CURL_CA_BUNDLE CACHE) -elseif("${CURL_CA_BUNDLE}" STREQUAL "auto") - unset(CURL_CA_BUNDLE CACHE) - if(NOT CMAKE_CROSSCOMPILING) - set(CURL_CA_BUNDLE_AUTODETECT TRUE) +if(_curl_ca_bundle_supported) + set(CURL_CA_BUNDLE "auto" CACHE + STRING "Path to the CA bundle. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") + set(CURL_CA_FALLBACK OFF CACHE + BOOL "Use built-in CA store of TLS backend. Defaults to OFF") + set(CURL_CA_PATH "auto" CACHE + STRING "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") + set(CURL_CA_EMBED "" CACHE + STRING "Path to the CA bundle to embed in the curl tool.") + + if(CURL_CA_BUNDLE STREQUAL "") + message(FATAL_ERROR "Invalid value of CURL_CA_BUNDLE. Use 'none', 'auto' or file path.") + elseif(CURL_CA_BUNDLE STREQUAL "none") + unset(CURL_CA_BUNDLE CACHE) + elseif(CURL_CA_BUNDLE STREQUAL "auto") + unset(CURL_CA_BUNDLE CACHE) + if(NOT CMAKE_CROSSCOMPILING AND NOT WIN32) + set(_curl_ca_bundle_autodetect TRUE) + endif() + else() + set(CURL_CA_BUNDLE_SET TRUE) endif() -else() - set(CURL_CA_BUNDLE_SET TRUE) -endif() - -if("${CURL_CA_PATH}" STREQUAL "") - message(FATAL_ERROR "Invalid value of CURL_CA_PATH. Use 'none', 'auto' or directory path.") -elseif("${CURL_CA_PATH}" STREQUAL "none") - unset(CURL_CA_PATH CACHE) -elseif("${CURL_CA_PATH}" STREQUAL "auto") - unset(CURL_CA_PATH CACHE) - if(NOT CMAKE_CROSSCOMPILING AND NOT USE_NSS) - set(CURL_CA_PATH_AUTODETECT TRUE) + mark_as_advanced(CURL_CA_BUNDLE_SET) + + if(CURL_CA_PATH STREQUAL "") + message(FATAL_ERROR "Invalid value of CURL_CA_PATH. Use 'none', 'auto' or directory path.") + elseif(CURL_CA_PATH STREQUAL "none") + unset(CURL_CA_PATH CACHE) + elseif(CURL_CA_PATH STREQUAL "auto") + unset(CURL_CA_PATH CACHE) + if(NOT CMAKE_CROSSCOMPILING AND NOT WIN32) + set(_curl_ca_path_autodetect TRUE) + endif() + else() + set(CURL_CA_PATH_SET TRUE) endif() -else() - set(CURL_CA_PATH_SET TRUE) -endif() - -if(CURL_CA_BUNDLE_SET AND CURL_CA_PATH_AUTODETECT) - # Skip autodetection of unset CA path because CA bundle is set explicitly -elseif(CURL_CA_PATH_SET AND CURL_CA_BUNDLE_AUTODETECT) - # Skip autodetection of unset CA bundle because CA path is set explicitly -elseif(CURL_CA_PATH_AUTODETECT OR CURL_CA_BUNDLE_AUTODETECT) - # first try autodetecting a CA bundle, then a CA path - - if(CURL_CA_BUNDLE_AUTODETECT) - set(SEARCH_CA_BUNDLE_PATHS - /etc/ssl/certs/ca-certificates.crt - /etc/pki/tls/certs/ca-bundle.crt - /usr/share/ssl/certs/ca-bundle.crt - /usr/local/share/certs/ca-root-nss.crt - /etc/ssl/cert.pem) - - foreach(SEARCH_CA_BUNDLE_PATH ${SEARCH_CA_BUNDLE_PATHS}) - if(EXISTS "${SEARCH_CA_BUNDLE_PATH}") - message(STATUS "Found CA bundle: ${SEARCH_CA_BUNDLE_PATH}") - set(CURL_CA_BUNDLE "${SEARCH_CA_BUNDLE_PATH}" CACHE STRING - "Path to the CA bundle. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") - set(CURL_CA_BUNDLE_SET TRUE CACHE BOOL "Path to the CA bundle has been set") - break() + mark_as_advanced(CURL_CA_PATH_SET) + + if(CURL_CA_BUNDLE_SET AND _curl_ca_path_autodetect) + # Skip auto-detection of unset CA path because CA bundle is set explicitly + elseif(CURL_CA_PATH_SET AND _curl_ca_bundle_autodetect) + # Skip auto-detection of unset CA bundle because CA path is set explicitly + elseif(_curl_ca_bundle_autodetect OR _curl_ca_path_autodetect) + # First try auto-detecting a CA bundle, then a CA path + + if(_curl_ca_bundle_autodetect) + foreach(_search_ca_bundle_path IN ITEMS + "/etc/ssl/certs/ca-certificates.crt" + "/etc/pki/tls/certs/ca-bundle.crt" + "/usr/share/ssl/certs/ca-bundle.crt" + "/usr/local/share/certs/ca-root-nss.crt" + "/etc/ssl/cert.pem") + if(EXISTS "${_search_ca_bundle_path}") + message(STATUS "Found CA bundle: ${_search_ca_bundle_path}") + set(CURL_CA_BUNDLE "${_search_ca_bundle_path}" CACHE + STRING "Path to the CA bundle. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") + set(CURL_CA_BUNDLE_SET TRUE CACHE BOOL "Path to the CA bundle has been set") + break() + endif() + endforeach() + endif() + + if(_curl_ca_path_autodetect AND NOT CURL_CA_PATH_SET) + set(_search_ca_path "/etc/ssl/certs") + file(GLOB _curl_ca_files_found "${_search_ca_path}/[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].0") + if(_curl_ca_files_found) + unset(_curl_ca_files_found) + message(STATUS "Found CA path: ${_search_ca_path}") + set(CURL_CA_PATH "${_search_ca_path}" CACHE + STRING "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") + set(CURL_CA_PATH_SET TRUE CACHE BOOL "Path to the CA bundle has been set") endif() - endforeach() + endif() endif() - if(CURL_CA_PATH_AUTODETECT AND (NOT CURL_CA_PATH_SET)) - if(EXISTS "/etc/ssl/certs") - set(CURL_CA_PATH "/etc/ssl/certs" CACHE STRING - "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") - set(CURL_CA_PATH_SET TRUE CACHE BOOL "Path to the CA bundle has been set") + set(CURL_CA_EMBED_SET FALSE) + if(BUILD_CURL_EXE AND NOT CURL_CA_EMBED STREQUAL "") + if(EXISTS "${CURL_CA_EMBED}") + set(CURL_CA_EMBED_SET TRUE) + message(STATUS "Found CA bundle to embed: ${CURL_CA_EMBED}") + else() + message(FATAL_ERROR "CA bundle to embed is missing: '${CURL_CA_EMBED}'") endif() endif() endif() +endif() # XXX(cmake): end -if(CURL_CA_PATH_SET AND NOT USE_OPENSSL AND NOT USE_MBEDTLS) - message(STATUS - "CA path only supported by OpenSSL, GnuTLS or mbed TLS. " - "Set CURL_CA_PATH=none or enable one of those TLS backends.") -endif() +if(WIN32) + option(CURL_DISABLE_CA_SEARCH "Disable unsafe CA bundle search in PATH on Windows" OFF) + option(CURL_CA_SEARCH_SAFE "Enable safe CA bundle search (within the curl tool directory) on Windows" OFF) endif() # Check for header files -if(NOT UNIX) - check_include_file_concat("windows.h" HAVE_WINDOWS_H) - check_include_file_concat("ws2tcpip.h" HAVE_WS2TCPIP_H) - check_include_file_concat("winsock2.h" HAVE_WINSOCK2_H) - check_include_file_concat("wincrypt.h" HAVE_WINCRYPT_H) -else() - set(HAVE_WINDOWS_H 0) - set(HAVE_WINSOCK_H 0) - set(HAVE_WS2TCPIP_H 0) - set(HAVE_WINSOCK2_H 0) -endif() - -check_include_file_concat("inttypes.h" HAVE_INTTYPES_H) -check_include_file_concat("sys/filio.h" HAVE_SYS_FILIO_H) -check_include_file_concat("sys/ioctl.h" HAVE_SYS_IOCTL_H) -check_include_file_concat("sys/param.h" HAVE_SYS_PARAM_H) -check_include_file_concat("sys/poll.h" HAVE_SYS_POLL_H) -check_include_file_concat("sys/resource.h" HAVE_SYS_RESOURCE_H) -check_include_file_concat("sys/select.h" HAVE_SYS_SELECT_H) -check_include_file_concat("sys/socket.h" HAVE_SYS_SOCKET_H) -check_include_file_concat("sys/sockio.h" HAVE_SYS_SOCKIO_H) -check_include_file_concat("sys/stat.h" HAVE_SYS_STAT_H) -check_include_file_concat("sys/time.h" HAVE_SYS_TIME_H) -check_include_file_concat("sys/types.h" HAVE_SYS_TYPES_H) -check_include_file_concat("sys/un.h" HAVE_SYS_UN_H) -check_include_file_concat("sys/utime.h" HAVE_SYS_UTIME_H) -check_include_file_concat("sys/xattr.h" HAVE_SYS_XATTR_H) -check_include_file_concat("arpa/inet.h" HAVE_ARPA_INET_H) -check_include_file_concat("arpa/tftp.h" HAVE_ARPA_TFTP_H) -check_include_file_concat("fcntl.h" HAVE_FCNTL_H) -check_include_file_concat("idn2.h" HAVE_IDN2_H) -check_include_file_concat("ifaddrs.h" HAVE_IFADDRS_H) -check_include_file_concat("io.h" HAVE_IO_H) -check_include_file_concat("libgen.h" HAVE_LIBGEN_H) -check_include_file_concat("locale.h" HAVE_LOCALE_H) -check_include_file_concat("net/if.h" HAVE_NET_IF_H) -check_include_file_concat("netdb.h" HAVE_NETDB_H) -check_include_file_concat("netinet/in.h" HAVE_NETINET_IN_H) -check_include_file_concat("netinet/tcp.h" HAVE_NETINET_TCP_H) +if(WIN32) + list(APPEND CURL_INCLUDES "winsock2.h") + list(APPEND CURL_INCLUDES "ws2tcpip.h") + + if(HAVE_WIN32_WINNT AND HAVE_WIN32_WINNT LESS 0x0501 AND NOT WINCE) + # Windows XP is required for freeaddrinfo, getaddrinfo + message(FATAL_ERROR "Building for Windows XP or newer is required.") + endif() +endif() + +# Detect headers + +# Use check_include_file_concat_curl() for headers required by subsequent +# check_include_file_concat_curl() or check_symbol_exists() detections. +# Order for these is significant. +check_include_file("sys/eventfd.h" HAVE_SYS_EVENTFD_H) +check_include_file("sys/filio.h" HAVE_SYS_FILIO_H) +check_include_file("sys/ioctl.h" HAVE_SYS_IOCTL_H) +check_include_file("sys/param.h" HAVE_SYS_PARAM_H) +check_include_file("sys/poll.h" HAVE_SYS_POLL_H) +check_include_file("sys/resource.h" HAVE_SYS_RESOURCE_H) +check_include_file_concat_curl("sys/select.h" HAVE_SYS_SELECT_H) +check_include_file_concat_curl("sys/socket.h" HAVE_SYS_SOCKET_H) +check_include_file("sys/sockio.h" HAVE_SYS_SOCKIO_H) +check_include_file("sys/stat.h" HAVE_SYS_STAT_H) +check_include_file_concat_curl("sys/time.h" HAVE_SYS_TIME_H) +check_include_file_concat_curl("sys/types.h" HAVE_SYS_TYPES_H) +check_include_file("sys/un.h" HAVE_SYS_UN_H) +check_include_file_concat_curl("sys/utime.h" HAVE_SYS_UTIME_H) # sys/types.h (AmigaOS) + +check_include_file_concat_curl("arpa/inet.h" HAVE_ARPA_INET_H) +check_include_file("dirent.h" HAVE_DIRENT_H) +check_include_file("fcntl.h" HAVE_FCNTL_H) +check_include_file_concat_curl("ifaddrs.h" HAVE_IFADDRS_H) +check_include_file("io.h" HAVE_IO_H) +check_include_file_concat_curl("libgen.h" HAVE_LIBGEN_H) check_include_file("linux/tcp.h" HAVE_LINUX_TCP_H) +check_include_file("locale.h" HAVE_LOCALE_H) +check_include_file_concat_curl("net/if.h" HAVE_NET_IF_H) # sys/select.h (e.g. MS-DOS/Watt-32) +check_include_file_concat_curl("netdb.h" HAVE_NETDB_H) +check_include_file_concat_curl("netinet/in.h" HAVE_NETINET_IN_H) +check_include_file("netinet/in6.h" HAVE_NETINET_IN6_H) +check_include_file_concat_curl("netinet/tcp.h" HAVE_NETINET_TCP_H) # sys/types.h (e.g. Cygwin) netinet/in.h +check_include_file_concat_curl("netinet/udp.h" HAVE_NETINET_UDP_H) # sys/types.h (e.g. Cygwin) +check_include_file("poll.h" HAVE_POLL_H) +check_include_file("pwd.h" HAVE_PWD_H) +check_include_file("stdatomic.h" HAVE_STDATOMIC_H) +check_include_file("stdbool.h" HAVE_STDBOOL_H) +check_include_file("stdint.h" HAVE_STDINT_H) +check_include_file("strings.h" HAVE_STRINGS_H) +check_include_file("stropts.h" HAVE_STROPTS_H) +check_include_file("termio.h" HAVE_TERMIO_H) +check_include_file("termios.h" HAVE_TERMIOS_H) +check_include_file_concat_curl("unistd.h" HAVE_UNISTD_H) +check_include_file("utime.h" HAVE_UTIME_H) + +if(AMIGA) + check_include_file_concat_curl("proto/bsdsocket.h" HAVE_PROTO_BSDSOCKET_H) +endif() + +# Pass these detection results to curl_internal_test() for use in CurlTests.c +# Add here all feature flags referenced from CurlTests.c +foreach(_variable IN ITEMS + HAVE_STDATOMIC_H + HAVE_STDBOOL_H + HAVE_STROPTS_H + HAVE_SYS_IOCTL_H + HAVE_SYS_SOCKET_H + HAVE_SYS_TYPES_H + HAVE_UNISTD_H + ) + if(${_variable}) + string(APPEND CURL_TEST_DEFINES " -D${_variable}") + endif() +endforeach() -check_include_file_concat("poll.h" HAVE_POLL_H) -check_include_file_concat("pwd.h" HAVE_PWD_H) -check_include_file_concat("setjmp.h" HAVE_SETJMP_H) -check_include_file_concat("signal.h" HAVE_SIGNAL_H) -check_include_file_concat("ssl.h" HAVE_SSL_H) -check_include_file_concat("stdatomic.h" HAVE_STDATOMIC_H) -check_include_file_concat("stdbool.h" HAVE_STDBOOL_H) -check_include_file_concat("stdint.h" HAVE_STDINT_H) -check_include_file_concat("stdlib.h" HAVE_STDLIB_H) -check_include_file_concat("string.h" HAVE_STRING_H) -check_include_file_concat("strings.h" HAVE_STRINGS_H) -check_include_file_concat("stropts.h" HAVE_STROPTS_H) -check_include_file_concat("termio.h" HAVE_TERMIO_H) -check_include_file_concat("termios.h" HAVE_TERMIOS_H) -check_include_file_concat("time.h" HAVE_TIME_H) -check_include_file_concat("unistd.h" HAVE_UNISTD_H) -check_include_file_concat("utime.h" HAVE_UTIME_H) - -check_include_file_concat("stddef.h" HAVE_STDDEF_H) -check_include_file_concat("sys/utsname.h" HAVE_SYS_UTSNAME_H) - -check_type_size(size_t SIZEOF_SIZE_T) -check_type_size(ssize_t SIZEOF_SSIZE_T) -check_type_size("time_t" SIZEOF_TIME_T) +check_type_size("size_t" SIZEOF_SIZE_T) +check_type_size("ssize_t" SIZEOF_SSIZE_T) +check_type_size("time_t" SIZEOF_TIME_T) +check_type_size("suseconds_t" SIZEOF_SUSECONDS_T) -if(NOT CMAKE_CROSSCOMPILING) - find_file(RANDOM_FILE urandom /dev) - mark_as_advanced(RANDOM_FILE) +if(SIZEOF_SUSECONDS_T) + set(HAVE_SUSECONDS_T 1) endif() # Check for some functions that are used -if(HAVE_LIBWS2_32) - set(CMAKE_REQUIRED_LIBRARIES ws2_32) + +# Apply to all feature checks +if(WIN32) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${_win32_winsock}") elseif(HAVE_LIBSOCKET) - set(CMAKE_REQUIRED_LIBRARIES socket) + list(APPEND CMAKE_REQUIRED_LIBRARIES "socket") elseif(HAVE_LIBNETWORK) - set(CMAKE_REQUIRED_LIBRARIES network) -endif() - -check_symbol_exists(fchmod "${CURL_INCLUDES}" HAVE_FCHMOD) -check_symbol_exists(basename "${CURL_INCLUDES}" HAVE_BASENAME) -check_symbol_exists(socket "${CURL_INCLUDES}" HAVE_SOCKET) -check_symbol_exists(socketpair "${CURL_INCLUDES}" HAVE_SOCKETPAIR) -check_symbol_exists(recv "${CURL_INCLUDES}" HAVE_RECV) -check_symbol_exists(send "${CURL_INCLUDES}" HAVE_SEND) -check_symbol_exists(sendmsg "${CURL_INCLUDES}" HAVE_SENDMSG) -check_symbol_exists(select "${CURL_INCLUDES}" HAVE_SELECT) -check_symbol_exists(strdup "${CURL_INCLUDES}" HAVE_STRDUP) -check_symbol_exists(strtok_r "${CURL_INCLUDES}" HAVE_STRTOK_R) -check_symbol_exists(strcasecmp "${CURL_INCLUDES}" HAVE_STRCASECMP) -check_symbol_exists(stricmp "${CURL_INCLUDES}" HAVE_STRICMP) -check_symbol_exists(strcmpi "${CURL_INCLUDES}" HAVE_STRCMPI) -check_symbol_exists(alarm "${CURL_INCLUDES}" HAVE_ALARM) -check_symbol_exists(getppid "${CURL_INCLUDES}" HAVE_GETPPID) -check_symbol_exists(utimes "${CURL_INCLUDES}" HAVE_UTIMES) - -check_symbol_exists(gettimeofday "${CURL_INCLUDES}" HAVE_GETTIMEOFDAY) -check_symbol_exists(closesocket "${CURL_INCLUDES}" HAVE_CLOSESOCKET) -check_symbol_exists(sigsetjmp "${CURL_INCLUDES}" HAVE_SIGSETJMP) -check_symbol_exists(getpass_r "${CURL_INCLUDES}" HAVE_GETPASS_R) -check_symbol_exists(getpwuid "${CURL_INCLUDES}" HAVE_GETPWUID) -check_symbol_exists(getpwuid_r "${CURL_INCLUDES}" HAVE_GETPWUID_R) -check_symbol_exists(geteuid "${CURL_INCLUDES}" HAVE_GETEUID) -check_symbol_exists(utime "${CURL_INCLUDES}" HAVE_UTIME) -check_symbol_exists(gmtime_r "${CURL_INCLUDES}" HAVE_GMTIME_R) - -check_symbol_exists(gethostbyname_r "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME_R) - -check_symbol_exists(signal "${CURL_INCLUDES}" HAVE_SIGNAL) -check_symbol_exists(strtoll "${CURL_INCLUDES}" HAVE_STRTOLL) -check_symbol_exists(strerror_r "${CURL_INCLUDES}" HAVE_STRERROR_R) -check_symbol_exists(siginterrupt "${CURL_INCLUDES}" HAVE_SIGINTERRUPT) -check_symbol_exists(getaddrinfo "${CURL_INCLUDES}" HAVE_GETADDRINFO) -if(WIN32) - set(HAVE_GETADDRINFO_THREADSAFE ${HAVE_GETADDRINFO}) -endif() -check_symbol_exists(freeaddrinfo "${CURL_INCLUDES}" HAVE_FREEADDRINFO) -check_symbol_exists(pipe "${CURL_INCLUDES}" HAVE_PIPE) -check_symbol_exists(ftruncate "${CURL_INCLUDES}" HAVE_FTRUNCATE) -check_symbol_exists(getpeername "${CURL_INCLUDES}" HAVE_GETPEERNAME) -check_symbol_exists(getsockname "${CURL_INCLUDES}" HAVE_GETSOCKNAME) -check_symbol_exists(if_nametoindex "${CURL_INCLUDES}" HAVE_IF_NAMETOINDEX) -check_symbol_exists(getrlimit "${CURL_INCLUDES}" HAVE_GETRLIMIT) -check_symbol_exists(setlocale "${CURL_INCLUDES}" HAVE_SETLOCALE) -check_symbol_exists(setmode "${CURL_INCLUDES}" HAVE_SETMODE) -check_symbol_exists(setrlimit "${CURL_INCLUDES}" HAVE_SETRLIMIT) - -if(NOT MSVC OR (MSVC_VERSION GREATER_EQUAL 1900)) - # earlier MSVC compilers had faulty snprintf implementations - check_symbol_exists(snprintf "stdio.h" HAVE_SNPRINTF) -endif() -check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME) -check_symbol_exists(inet_ntop "${CURL_INCLUDES}" HAVE_INET_NTOP) -if(MSVC AND (MSVC_VERSION LESS_EQUAL 1600)) - set(HAVE_INET_NTOP OFF) -endif() -check_symbol_exists(inet_pton "${CURL_INCLUDES}" HAVE_INET_PTON) - -check_symbol_exists(fsetxattr "${CURL_INCLUDES}" HAVE_FSETXATTR) -if(HAVE_FSETXATTR) - foreach(CURL_TEST HAVE_FSETXATTR_5 HAVE_FSETXATTR_6) - curl_internal_test(${CURL_TEST}) - endforeach() + list(APPEND CMAKE_REQUIRED_LIBRARIES "network") +elseif(DOS) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${WATT_ROOT}/lib/libwatt.a") +endif() + +check_function_exists("accept4" HAVE_ACCEPT4) +check_function_exists("fnmatch" HAVE_FNMATCH) +check_symbol_exists("basename" "${CURL_INCLUDES};string.h" HAVE_BASENAME) # libgen.h unistd.h +check_symbol_exists("opendir" "dirent.h" HAVE_OPENDIR) +check_function_exists("poll" HAVE_POLL) # poll.h +check_symbol_exists("socket" "${CURL_INCLUDES}" HAVE_SOCKET) # winsock2.h sys/socket.h +check_symbol_exists("socketpair" "${CURL_INCLUDES}" HAVE_SOCKETPAIR) # sys/socket.h +check_symbol_exists("recv" "${CURL_INCLUDES}" HAVE_RECV) # proto/bsdsocket.h sys/types.h sys/socket.h +check_symbol_exists("send" "${CURL_INCLUDES}" HAVE_SEND) # proto/bsdsocket.h sys/types.h sys/socket.h +check_function_exists("sendmsg" HAVE_SENDMSG) +check_function_exists("sendmmsg" HAVE_SENDMMSG) +check_symbol_exists("select" "${CURL_INCLUDES}" HAVE_SELECT) # proto/bsdsocket.h sys/select.h sys/socket.h +check_symbol_exists("strdup" "string.h" HAVE_STRDUP) +check_symbol_exists("memrchr" "string.h" HAVE_MEMRCHR) +check_symbol_exists("alarm" "unistd.h" HAVE_ALARM) +check_symbol_exists("fcntl" "fcntl.h" HAVE_FCNTL) +check_function_exists("getppid" HAVE_GETPPID) +check_function_exists("utimes" HAVE_UTIMES) + +check_function_exists("gettimeofday" HAVE_GETTIMEOFDAY) # sys/time.h +check_symbol_exists("closesocket" "${CURL_INCLUDES}" HAVE_CLOSESOCKET) # winsock2.h +check_symbol_exists("sigsetjmp" "setjmp.h" HAVE_SIGSETJMP) +check_function_exists("getpass_r" HAVE_GETPASS_R) +check_function_exists("getpwuid" HAVE_GETPWUID) +check_function_exists("getpwuid_r" HAVE_GETPWUID_R) +check_function_exists("geteuid" HAVE_GETEUID) +check_function_exists("utime" HAVE_UTIME) +check_symbol_exists("gmtime_r" "stdlib.h;time.h" HAVE_GMTIME_R) + +check_symbol_exists("gethostbyname_r" "netdb.h" HAVE_GETHOSTBYNAME_R) +check_symbol_exists("gethostname" "${CURL_INCLUDES}" HAVE_GETHOSTNAME) # winsock2.h unistd.h proto/bsdsocket.h + +check_symbol_exists("signal" "signal.h" HAVE_SIGNAL) +check_symbol_exists("strerror_r" "stdlib.h;string.h" HAVE_STRERROR_R) +check_symbol_exists("sigaction" "signal.h" HAVE_SIGACTION) +check_symbol_exists("siginterrupt" "signal.h" HAVE_SIGINTERRUPT) +check_symbol_exists("getaddrinfo" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_GETADDRINFO) # ws2tcpip.h sys/socket.h netdb.h +check_symbol_exists("getifaddrs" "${CURL_INCLUDES};stdlib.h" HAVE_GETIFADDRS) # ifaddrs.h +check_symbol_exists("freeaddrinfo" "${CURL_INCLUDES}" HAVE_FREEADDRINFO) # ws2tcpip.h sys/socket.h netdb.h +check_function_exists("pipe" HAVE_PIPE) +check_function_exists("pipe2" HAVE_PIPE2) +check_function_exists("eventfd" HAVE_EVENTFD) +check_symbol_exists("ftruncate" "unistd.h" HAVE_FTRUNCATE) +check_symbol_exists("getpeername" "${CURL_INCLUDES}" HAVE_GETPEERNAME) # winsock2.h unistd.h proto/bsdsocket.h +check_symbol_exists("getsockname" "${CURL_INCLUDES}" HAVE_GETSOCKNAME) # winsock2.h unistd.h proto/bsdsocket.h +check_function_exists("getrlimit" HAVE_GETRLIMIT) +check_function_exists("setlocale" HAVE_SETLOCALE) +check_function_exists("setrlimit" HAVE_SETRLIMIT) + +if(NOT WIN32) + check_function_exists("if_nametoindex" HAVE_IF_NAMETOINDEX) # iphlpapi.h (Windows non-UWP), net/if.h + check_function_exists("realpath" HAVE_REALPATH) + check_function_exists("sched_yield" HAVE_SCHED_YIELD) + check_symbol_exists("strcasecmp" "string.h" HAVE_STRCASECMP) + check_symbol_exists("stricmp" "string.h" HAVE_STRICMP) + check_symbol_exists("strcmpi" "string.h" HAVE_STRCMPI) +endif() + +if(NOT MINGW32CE) # Avoid false detections + check_function_exists("setmode" HAVE_SETMODE) + if(WIN32 OR CYGWIN) + check_function_exists("_setmode" HAVE__SETMODE) + endif() endif() -set(CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h") -check_type_size("sa_family_t" SIZEOF_SA_FAMILY_T) -set(HAVE_SA_FAMILY_T ${HAVE_SIZEOF_SA_FAMILY_T}) -set(CMAKE_EXTRA_INCLUDE_FILES "") +if(AMIGA) + check_symbol_exists("CloseSocket" "${CURL_INCLUDES}" HAVE_CLOSESOCKET_CAMEL) # sys/socket.h proto/bsdsocket.h +endif() -set(CMAKE_EXTRA_INCLUDE_FILES "ws2def.h") -check_type_size("ADDRESS_FAMILY" SIZEOF_ADDRESS_FAMILY) -set(HAVE_ADDRESS_FAMILY ${HAVE_SIZEOF_ADDRESS_FAMILY}) -set(CMAKE_EXTRA_INCLUDE_FILES "") +if(NOT _ssl_enabled) + check_symbol_exists("arc4random" "${CURL_INCLUDES};stdlib.h" HAVE_ARC4RANDOM) +endif() -# sigaction and sigsetjmp are special. Use special mechanism for -# detecting those, but only if previous attempt failed. -if(HAVE_SIGNAL_H) - check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION) +if(NOT MSVC) + check_function_exists("snprintf" HAVE_SNPRINTF) # to match detection method in ./configure +elseif(MSVC_VERSION GREATER_EQUAL 1900) # Earlier MSVC compilers had faulty snprintf implementations + check_symbol_exists("snprintf" "stdio.h" HAVE_SNPRINTF) # snprintf may be a compatibility macro, not an exported function +endif() +if(APPLE) + check_function_exists("mach_absolute_time" HAVE_MACH_ABSOLUTE_TIME) +endif() +if(NOT WIN32) + check_symbol_exists("inet_ntop" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_NTOP) # arpa/inet.h netinet/in.h sys/socket.h + check_symbol_exists("inet_pton" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_PTON) # arpa/inet.h netinet/in.h sys/socket.h endif() -if(NOT HAVE_SIGSETJMP) - if(HAVE_SETJMP_H) - check_symbol_exists(sigsetjmp "setjmp.h" HAVE_MACRO_SIGSETJMP) - if(HAVE_MACRO_SIGSETJMP) - set(HAVE_SIGSETJMP 1) - endif() - endif() +check_symbol_exists("fsetxattr" "sys/xattr.h" HAVE_FSETXATTR) +if(HAVE_FSETXATTR) + curl_internal_test(HAVE_FSETXATTR_5) + curl_internal_test(HAVE_FSETXATTR_6) endif() -# If there is no stricmp(), do not allow LDAP to parse URLs -if(NOT HAVE_STRICMP) - set(HAVE_LDAP_URL_PARSE 1) +cmake_push_check_state() +if(WIN32) + list(APPEND CMAKE_EXTRA_INCLUDE_FILES "winsock2.h") + check_type_size("ADDRESS_FAMILY" SIZEOF_ADDRESS_FAMILY) + set(HAVE_ADDRESS_FAMILY ${HAVE_SIZEOF_ADDRESS_FAMILY}) +elseif(HAVE_SYS_SOCKET_H) + list(APPEND CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h") + check_type_size("sa_family_t" SIZEOF_SA_FAMILY_T) + set(HAVE_SA_FAMILY_T ${HAVE_SIZEOF_SA_FAMILY_T}) endif() +cmake_pop_check_state() # Do curl specific tests -foreach(CURL_TEST +foreach(_curl_test IN ITEMS HAVE_FCNTL_O_NONBLOCK HAVE_IOCTLSOCKET HAVE_IOCTLSOCKET_CAMEL @@ -1325,520 +2093,689 @@ foreach(CURL_TEST HAVE_IOCTL_FIONBIO HAVE_IOCTL_SIOCGIFADDR HAVE_SETSOCKOPT_SO_NONBLOCK - TIME_WITH_SYS_TIME - HAVE_O_NONBLOCK HAVE_GETHOSTBYNAME_R_3 HAVE_GETHOSTBYNAME_R_5 HAVE_GETHOSTBYNAME_R_6 - HAVE_GETHOSTBYNAME_R_3_REENTRANT - HAVE_GETHOSTBYNAME_R_5_REENTRANT - HAVE_GETHOSTBYNAME_R_6_REENTRANT - HAVE_IN_ADDR_T HAVE_BOOL_T STDC_HEADERS - HAVE_FILE_OFFSET_BITS - HAVE_VARIADIC_MACROS_C99 - HAVE_VARIADIC_MACROS_GCC HAVE_ATOMIC ) - curl_internal_test(${CURL_TEST}) -endforeach() - -if(HAVE_FILE_OFFSET_BITS) - set(_FILE_OFFSET_BITS 64) - set(CMAKE_REQUIRED_FLAGS "-D_FILE_OFFSET_BITS=64") -endif() -check_type_size("off_t" SIZEOF_OFF_T) - -# include this header to get the type -set(CMAKE_REQUIRED_INCLUDES "${CURL_SOURCE_DIR}/include") -set(CMAKE_EXTRA_INCLUDE_FILES "curl/system.h") -check_type_size("curl_off_t" SIZEOF_CURL_OFF_T) -set(CMAKE_EXTRA_INCLUDE_FILES "curl/curl.h") -check_type_size("curl_socket_t" SIZEOF_CURL_SOCKET_T) -set(CMAKE_EXTRA_INCLUDE_FILES "") - -if(WIN32) - # detect actual value of _WIN32_WINNT and store as HAVE_WIN32_WINNT - curl_internal_test(HAVE_WIN32_WINNT) - if(HAVE_WIN32_WINNT) - string(REGEX MATCH ".*_WIN32_WINNT=0x[0-9a-fA-F]+" OUTPUT "${OUTPUT}") - string(REGEX REPLACE ".*_WIN32_WINNT=" "" HAVE_WIN32_WINNT "${OUTPUT}") - message(STATUS "Found _WIN32_WINNT=${HAVE_WIN32_WINNT}") - endif() - # avoid storing HAVE_WIN32_WINNT in CMake cache - unset(HAVE_WIN32_WINNT CACHE) -endif() - -set(CMAKE_REQUIRED_FLAGS) - -option(ENABLE_WEBSOCKETS "Set to ON to enable EXPERIMENTAL websockets" OFF) - -if(ENABLE_WEBSOCKETS) - if(${SIZEOF_CURL_OFF_T} GREATER "4") - set(USE_WEBSOCKETS ON) - else() - message(WARNING "curl_off_t is too small to enable WebSockets") - endif() -endif() - -foreach(CURL_TEST - HAVE_GLIBC_STRERROR_R - HAVE_POSIX_STRERROR_R - ) - curl_internal_test(${CURL_TEST}) + curl_internal_test(${_curl_test}) endforeach() # Check for reentrant -foreach(CURL_TEST +cmake_push_check_state() +list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_REENTRANT") +foreach(_curl_test IN ITEMS HAVE_GETHOSTBYNAME_R_3 HAVE_GETHOSTBYNAME_R_5 HAVE_GETHOSTBYNAME_R_6) - if(NOT ${CURL_TEST}) - if(${CURL_TEST}_REENTRANT) - set(NEED_REENTRANT 1) - endif() + curl_internal_test(${_curl_test}_REENTRANT) + if(NOT ${_curl_test} AND ${_curl_test}_REENTRANT) + set(NEED_REENTRANT 1) endif() endforeach() +cmake_pop_check_state() if(NEED_REENTRANT) - foreach(CURL_TEST + foreach(_curl_test IN ITEMS HAVE_GETHOSTBYNAME_R_3 HAVE_GETHOSTBYNAME_R_5 HAVE_GETHOSTBYNAME_R_6) - set(${CURL_TEST} 0) - if(${CURL_TEST}_REENTRANT) - set(${CURL_TEST} 1) + set(${_curl_test} 0) + if(${_curl_test}_REENTRANT) + set(${_curl_test} 1) endif() endforeach() endif() -# Check clock_gettime(CLOCK_MONOTONIC, x) support -curl_internal_test(HAVE_CLOCK_GETTIME_MONOTONIC) +cmake_push_check_state() +list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_FILE_OFFSET_BITS=64") +curl_internal_test(HAVE_FILE_OFFSET_BITS) +cmake_pop_check_state() -# Check compiler support of __builtin_available() -curl_internal_test(HAVE_BUILTIN_AVAILABLE) +cmake_push_check_state() +if(HAVE_FILE_OFFSET_BITS) + set(_FILE_OFFSET_BITS 64) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_FILE_OFFSET_BITS=64") +endif() +check_type_size("off_t" SIZEOF_OFF_T) -# Some other minor tests +if(NOT WIN32) + # fseeko may not exist with _FILE_OFFSET_BITS=64 but can exist with + # _FILE_OFFSET_BITS unset or 32 (e.g. Android ARMv7 with NDK 26b and API level < 24) + # so we need to test fseeko after testing for _FILE_OFFSET_BITS + check_symbol_exists("fseeko" "${CURL_INCLUDES};stdio.h" HAVE_FSEEKO) -if(NOT HAVE_IN_ADDR_T) - set(in_addr_t "unsigned long") + if(HAVE_FSEEKO) + set(HAVE_DECL_FSEEKO 1) + endif() endif() -# Check for nonblocking -set(HAVE_DISABLED_NONBLOCKING 1) -if(HAVE_FIONBIO OR - HAVE_IOCTLSOCKET OR - HAVE_IOCTLSOCKET_CASE OR - HAVE_O_NONBLOCK) - set(HAVE_DISABLED_NONBLOCKING) +# Include this header to get the type +cmake_push_check_state() +list(APPEND CMAKE_REQUIRED_INCLUDES "${PROJECT_SOURCE_DIR}/include") +list(APPEND CMAKE_EXTRA_INCLUDE_FILES "curl/system.h") +check_type_size("curl_off_t" SIZEOF_CURL_OFF_T) +list(APPEND CMAKE_EXTRA_INCLUDE_FILES "curl/curl.h") +check_type_size("curl_socket_t" SIZEOF_CURL_SOCKET_T) +cmake_pop_check_state() # pop curl system headers +cmake_pop_check_state() # pop -D_FILE_OFFSET_BITS=64 + +if(0) # XXX(cmake): not needed for build within cmake +if(NOT WIN32 AND NOT CMAKE_CROSSCOMPILING) + # On non-Windows and not cross-compiling, check for writable argv[] + include(CheckCSourceRuns) + check_c_source_runs(" + int main(int argc, char **argv) + { + (void)argc; + argv[0][0] = ' '; + return (argv[0][0] == ' ')?0:1; + }" HAVE_WRITABLE_ARGV) +endif() +endif() # XXX(cmake): end + +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() -if(CMAKE_COMPILER_IS_GNUCC AND APPLE) - include(CheckCCompilerFlag) - check_c_compiler_flag(-Wno-long-double HAVE_C_FLAG_Wno_long_double) - if(HAVE_C_FLAG_Wno_long_double) - # The Mac version of GCC warns about use of long double. Disable it. - get_source_file_property(MPRINTF_COMPILE_FLAGS mprintf.c COMPILE_FLAGS) - if(MPRINTF_COMPILE_FLAGS) - set(MPRINTF_COMPILE_FLAGS "${MPRINTF_COMPILE_FLAGS} -Wno-long-double") - else() - set(MPRINTF_COMPILE_FLAGS "-Wno-long-double") - endif() - set_source_files_properties(mprintf.c PROPERTIES - COMPILE_FLAGS ${MPRINTF_COMPILE_FLAGS}) - endif() +curl_internal_test(HAVE_GLIBC_STRERROR_R) +curl_internal_test(HAVE_POSIX_STRERROR_R) + +if(NOT WIN32) + curl_internal_test(HAVE_CLOCK_GETTIME_MONOTONIC) # Check clock_gettime(CLOCK_MONOTONIC, x) support endif() -# TODO test which of these headers are required -if(WIN32) - set(CURL_PULL_WS2TCPIP_H ${HAVE_WS2TCPIP_H}) -else() - set(CURL_PULL_SYS_TYPES_H ${HAVE_SYS_TYPES_H}) - set(CURL_PULL_SYS_SOCKET_H ${HAVE_SYS_SOCKET_H}) - set(CURL_PULL_SYS_POLL_H ${HAVE_SYS_POLL_H}) +if(APPLE) + curl_internal_test(HAVE_BUILTIN_AVAILABLE) # Check compiler support of __builtin_available() +endif() + +# Some other minor tests + +if(_cmake_try_compile_target_type_save) + set(CMAKE_TRY_COMPILE_TARGET_TYPE ${_cmake_try_compile_target_type_save}) + unset(_cmake_try_compile_target_type_save) endif() include(CMake/OtherTests.cmake) -add_definitions(-DHAVE_CONFIG_H) +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H") -# For Windows, all compilers used by CMake should support large files if(WIN32) - set(USE_WIN32_LARGE_FILES ON) - - # Use the manifest embedded in the Windows Resource - set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -DCURL_EMBED_MANIFEST") + list(APPEND CURL_LIBS "${_win32_winsock}") + if(NOT WINCE) + list(APPEND CURL_LIBS "bcrypt") + endif() - # Check if crypto functions in wincrypt.h are actually available - if(HAVE_WINCRYPT_H) - check_symbol_exists(CryptAcquireContext "windows.h;wincrypt.h" USE_WINCRYPT) + if(NOT WINCE) + set(USE_WIN32_LARGE_FILES ON) endif() - if(USE_WINCRYPT) + + # We use crypto functions that are not available for UWP apps + if(NOT WINDOWS_STORE) set(USE_WIN32_CRYPTO ON) endif() # Link required libraries for USE_WIN32_CRYPTO or USE_SCHANNEL if(USE_WIN32_CRYPTO OR USE_SCHANNEL) - list(APPEND CURL_LIBS "advapi32" "crypt32") - endif() - - if(NOT HAVE_MINGW_ORIGINAL) - list(APPEND CURL_LIBS "bcrypt") - else() - set(HAVE_FTRUNCATE OFF) - endif() -endif() - -if(MSVC) - # Disable default manifest added by CMake - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") - - add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) - if(CMAKE_C_FLAGS MATCHES "/W[0-4]") - string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - elseif(CMAKE_C_FLAGS MATCHES "-W[0-4]") - string(REGEX REPLACE "-W[0-4]" "-W4" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - else() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") - endif() - - # Use multithreaded compilation on VS 2008+ - if(MSVC_VERSION GREATER_EQUAL 1500) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") + if(NOT WINCE) + list(APPEND CURL_LIBS "advapi32") + endif() + list(APPEND CURL_LIBS "${_win32_crypt32}") endif() endif() -if(CURL_WERROR) - if(MSVC_VERSION) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /WX") - else() - # this assumes clang or gcc style options - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") - endif() +if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") # MSVC but exclude clang-cl + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-MP") # Parallel compilation endif() if(CURL_LTO) if(CMAKE_VERSION VERSION_LESS 3.9) - message(FATAL_ERROR "Requested LTO but your cmake version ${CMAKE_VERSION} is to old. You need at least 3.9") + message(FATAL_ERROR "LTO has been requested, but your cmake version ${CMAKE_VERSION} is to old. You need at least 3.9") endif() cmake_policy(SET CMP0069 NEW) include(CheckIPOSupported) - check_ipo_supported(RESULT CURL_HAS_LTO OUTPUT CURL_LTO_ERROR LANGUAGES C) + check_ipo_supported(RESULT CURL_HAS_LTO OUTPUT _lto_error LANGUAGES C) if(CURL_HAS_LTO) message(STATUS "LTO supported and enabled") else() - message(FATAL_ERROR "LTO was requested - but compiler doesn't support it\n${CURL_LTO_ERROR}") + message(FATAL_ERROR "LTO has been requested, but the compiler does not support it\n${_lto_error}") endif() endif() -# Ugly (but functional) way to include "Makefile.inc" by transforming it (= regenerate it). -function(transform_makefile_inc INPUT_FILE OUTPUT_FILE) - file(READ ${INPUT_FILE} MAKEFILE_INC_TEXT) - string(REPLACE "$(top_srcdir)" "\${CURL_SOURCE_DIR}" MAKEFILE_INC_TEXT ${MAKEFILE_INC_TEXT}) - string(REPLACE "$(top_builddir)" "\${CURL_BINARY_DIR}" MAKEFILE_INC_TEXT ${MAKEFILE_INC_TEXT}) +# Ugly (but functional) way to include "Makefile.inc" by transforming it +# (= regenerate it). +function(curl_transform_makefile_inc _input_file _output_file) + file(READ ${_input_file} _makefile_inc_text) + string(REPLACE "$(top_srcdir)" "\${PROJECT_SOURCE_DIR}" _makefile_inc_text ${_makefile_inc_text}) + string(REPLACE "$(top_builddir)" "\${PROJECT_BINARY_DIR}" _makefile_inc_text ${_makefile_inc_text}) + + string(REGEX REPLACE "\\\\\n" "!^!^!" _makefile_inc_text ${_makefile_inc_text}) + string(REGEX REPLACE "([a-zA-Z_][a-zA-Z0-9_]*)[\t ]*=[\t ]*([^\n]*)" "set(\\1 \\2)" _makefile_inc_text ${_makefile_inc_text}) + string(REPLACE "!^!^!" "\n" _makefile_inc_text ${_makefile_inc_text}) - string(REGEX REPLACE "\\\\\n" "!π!α!" MAKEFILE_INC_TEXT ${MAKEFILE_INC_TEXT}) - string(REGEX REPLACE "([a-zA-Z_][a-zA-Z0-9_]*)[\t ]*=[\t ]*([^\n]*)" "SET(\\1 \\2)" MAKEFILE_INC_TEXT ${MAKEFILE_INC_TEXT}) - string(REPLACE "!π!α!" "\n" MAKEFILE_INC_TEXT ${MAKEFILE_INC_TEXT}) + # Replace $() with ${} + string(REGEX REPLACE "\\$\\(([a-zA-Z_][a-zA-Z0-9_]*)\\)" "\${\\1}" _makefile_inc_text ${_makefile_inc_text}) + # Replace @@ with ${}, even if that may not be read by CMake scripts. + string(REGEX REPLACE "@([a-zA-Z_][a-zA-Z0-9_]*)@" "\${\\1}" _makefile_inc_text ${_makefile_inc_text}) - string(REGEX REPLACE "\\$\\(([a-zA-Z_][a-zA-Z0-9_]*)\\)" "\${\\1}" MAKEFILE_INC_TEXT ${MAKEFILE_INC_TEXT}) # Replace $() with ${} - string(REGEX REPLACE "@([a-zA-Z_][a-zA-Z0-9_]*)@" "\${\\1}" MAKEFILE_INC_TEXT ${MAKEFILE_INC_TEXT}) # Replace @@ with ${}, even if that may not be read by CMake scripts. - file(WRITE ${OUTPUT_FILE} ${MAKEFILE_INC_TEXT}) - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${INPUT_FILE}") + file(WRITE ${_output_file} ${_makefile_inc_text}) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_input_file}") endfunction() -if(0) # This code not needed for building within CMake. +#----------------------------------------------------------------------------- +# XXX(cmake): begin cmake-specific curl code +add_subdirectory(lib) + +add_executable(curltest curltest.c) +target_link_libraries(curltest cmcurl) + +if(BUILD_TESTING AND CMAKE_CURL_TEST_URL) + add_test(curl curltest ${CMAKE_CURL_TEST_URL}) +endif() + +install(FILES COPYING DESTINATION ${CMAKE_DOC_DIR}/cmcurl) + +return() # The rest of this file is not needed for building within CMake. +# XXX(cmake): end cmake-specific curl code +#----------------------------------------------------------------------------- + include(GNUInstallDirs) -set(CURL_INSTALL_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) +set(_install_cmake_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") -set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") -set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") -set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") +set(_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") +set(_project_config "${_generated_dir}/${PROJECT_NAME}Config.cmake") +set(_version_config "${_generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") + +option(BUILD_TESTING "Build tests" ON) +if(BUILD_TESTING AND PERL_FOUND) + set(CURL_BUILD_TESTING ON) +else() + set(CURL_BUILD_TESTING OFF) endif() -if(USE_MANUAL) +if(HAVE_MANUAL_TOOLS) + set(CURL_MANPAGE "${PROJECT_BINARY_DIR}/docs/cmdline-opts/curl.1") + set(CURL_ASCIIPAGE "${PROJECT_BINARY_DIR}/docs/cmdline-opts/curl.txt") add_subdirectory(docs) endif() +add_subdirectory(scripts) # for shell completions + +list(REMOVE_DUPLICATES CURL_LIBDIRS) + add_subdirectory(lib) if(BUILD_CURL_EXE) add_subdirectory(src) endif() -#----------------------------------------------------------------------------- -# CMake-specific curl code. -add_executable(curltest curltest.c) -target_link_libraries(curltest cmcurl) - -if(BUILD_TESTING AND CMAKE_CURL_TEST_URL) - add_test(curl curltest ${CMAKE_CURL_TEST_URL}) +option(BUILD_EXAMPLES "Build libcurl examples" ON) +if(BUILD_EXAMPLES) + add_subdirectory(docs/examples) endif() -install(FILES COPYING DESTINATION ${CMAKE_DOC_DIR}/cmcurl) -#----------------------------------------------------------------------------- - -if(0) # This code not needed for building within CMake. -cmake_dependent_option(BUILD_TESTING "Build tests" - ON "PERL_FOUND;NOT CURL_DISABLE_TESTS" - OFF) -if(BUILD_TESTING) +if(CURL_BUILD_TESTING) add_subdirectory(tests) endif() -# Helper to populate a list (_items) with a label when conditions (the remaining -# args) are satisfied -macro(_add_if label) - # needs to be a macro to allow this indirection +# Helper to populate a list (_items) with a label when conditions +# (the remaining args) are satisfied +macro(curl_add_if _label) + # Needs to be a macro to allow this indirection if(${ARGN}) - set(_items ${_items} "${label}") + set(_items ${_items} "${_label}") endif() endmacro() -# NTLM support requires crypto function adaptions from various SSL libs -# TODO alternative SSL libs tests for SSP1, GNUTLS, NSS -if(NOT (CURL_DISABLE_CRYPTO_AUTH OR CURL_DISABLE_NTLM) AND - (USE_OPENSSL OR USE_MBEDTLS OR USE_DARWINSSL OR USE_WIN32_CRYPTO)) - set(use_curl_ntlm_core ON) +# NTLM support requires crypto functions from various SSL libs. +# These conditions must match those in lib/curl_setup.h. +if(NOT CURL_DISABLE_NTLM AND + (USE_OPENSSL OR + USE_MBEDTLS OR + USE_GNUTLS OR + USE_SECTRANSP OR + USE_WIN32_CRYPTO OR + (USE_WOLFSSL AND HAVE_WOLFSSL_DES_ECB_ENCRYPT))) + set(_use_curl_ntlm_core ON) endif() -# Clear list and try to detect available features -set(_items) -_add_if("SSL" SSL_ENABLED) -_add_if("IPv6" ENABLE_IPV6) -_add_if("unixsockets" USE_UNIX_SOCKETS) -_add_if("libz" HAVE_LIBZ) -_add_if("brotli" HAVE_BROTLI) -_add_if("zstd" HAVE_ZSTD) -_add_if("AsynchDNS" USE_ARES OR USE_THREADS_POSIX OR USE_THREADS_WIN32) -_add_if("IDN" HAVE_LIBIDN2 OR USE_WIN32_IDN) -_add_if("Largefile" (SIZEOF_CURL_OFF_T GREATER 4) AND - ((SIZEOF_OFF_T GREATER 4) OR USE_WIN32_LARGE_FILES)) -# TODO SSP1 (Schannel) check is missing -_add_if("SSPI" USE_WINDOWS_SSPI) -_add_if("GSS-API" HAVE_GSSAPI) -_add_if("alt-svc" NOT CURL_DISABLE_ALTSVC) -_add_if("HSTS" NOT CURL_DISABLE_HSTS) -# TODO SSP1 missing for SPNEGO -_add_if("SPNEGO" NOT CURL_DISABLE_CRYPTO_AUTH AND - (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) -_add_if("Kerberos" NOT CURL_DISABLE_CRYPTO_AUTH AND - (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) -# NTLM support requires crypto function adaptions from various SSL libs -# TODO alternative SSL libs tests for SSP1, GNUTLS, NSS -_add_if("NTLM" NOT (CURL_DISABLE_CRYPTO_AUTH OR CURL_DISABLE_NTLM) AND - (use_curl_ntlm_core OR USE_WINDOWS_SSPI)) -# TODO missing option (autoconf: --enable-ntlm-wb) -_add_if("NTLM_WB" NOT (CURL_DISABLE_CRYPTO_AUTH OR CURL_DISABLE_NTLM) AND - (use_curl_ntlm_core OR USE_WINDOWS_SSPI) AND - NOT CURL_DISABLE_HTTP AND NTLM_WB_ENABLED) -# TODO missing option (--enable-tls-srp), depends on GNUTLS_SRP/OPENSSL_SRP -_add_if("TLS-SRP" USE_TLS_SRP) -# TODO option --with-nghttp2 tests for nghttp2 lib and nghttp2/nghttp2.h header -_add_if("HTTP2" USE_NGHTTP2) -_add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE) -_add_if("MultiSSL" CURL_WITH_MULTI_SSL) -# TODO wolfSSL only support this from v5.0.0 onwards -_add_if("HTTPS-proxy" SSL_ENABLED AND (USE_OPENSSL OR USE_GNUTLS OR USE_NSS - OR USE_SCHANNEL OR USE_RUSTLS OR USE_BEARSSL OR - USE_MBEDTLS OR USE_SECTRANSP)) -_add_if("unicode" ENABLE_UNICODE) -_add_if("threadsafe" HAVE_ATOMIC OR (WIN32 AND - HAVE_WIN32_WINNT GREATER_EQUAL 0x600)) -_add_if("PSL" USE_LIBPSL) -string(REPLACE ";" " " SUPPORT_FEATURES "${_items}") -message(STATUS "Enabled features: ${SUPPORT_FEATURES}") - # Clear list and try to detect available protocols -set(_items) -_add_if("HTTP" NOT CURL_DISABLE_HTTP) -_add_if("HTTPS" NOT CURL_DISABLE_HTTP AND SSL_ENABLED) -_add_if("FTP" NOT CURL_DISABLE_FTP) -_add_if("FTPS" NOT CURL_DISABLE_FTP AND SSL_ENABLED) -_add_if("FILE" NOT CURL_DISABLE_FILE) -_add_if("TELNET" NOT CURL_DISABLE_TELNET) -_add_if("LDAP" NOT CURL_DISABLE_LDAP) +set(_items "") +curl_add_if("HTTP" NOT CURL_DISABLE_HTTP) +curl_add_if("HTTPS" NOT CURL_DISABLE_HTTP AND _ssl_enabled) +curl_add_if("FTP" NOT CURL_DISABLE_FTP) +curl_add_if("FTPS" NOT CURL_DISABLE_FTP AND _ssl_enabled) +curl_add_if("FILE" NOT CURL_DISABLE_FILE) +curl_add_if("TELNET" NOT CURL_DISABLE_TELNET) +curl_add_if("LDAP" NOT CURL_DISABLE_LDAP) # CURL_DISABLE_LDAP implies CURL_DISABLE_LDAPS -# TODO check HAVE_LDAP_SSL (in autoconf this is enabled with --enable-ldaps) -_add_if("LDAPS" NOT CURL_DISABLE_LDAPS AND - ((USE_OPENLDAP AND SSL_ENABLED) OR - (NOT USE_OPENLDAP AND HAVE_LDAP_SSL))) -_add_if("DICT" NOT CURL_DISABLE_DICT) -_add_if("TFTP" NOT CURL_DISABLE_TFTP) -_add_if("GOPHER" NOT CURL_DISABLE_GOPHER) -_add_if("GOPHERS" NOT CURL_DISABLE_GOPHER AND SSL_ENABLED) -_add_if("POP3" NOT CURL_DISABLE_POP3) -_add_if("POP3S" NOT CURL_DISABLE_POP3 AND SSL_ENABLED) -_add_if("IMAP" NOT CURL_DISABLE_IMAP) -_add_if("IMAPS" NOT CURL_DISABLE_IMAP AND SSL_ENABLED) -_add_if("SMB" NOT CURL_DISABLE_SMB AND - use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4)) -_add_if("SMBS" NOT CURL_DISABLE_SMB AND SSL_ENABLED AND - use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4)) -_add_if("SMTP" NOT CURL_DISABLE_SMTP) -_add_if("SMTPS" NOT CURL_DISABLE_SMTP AND SSL_ENABLED) -_add_if("SCP" USE_LIBSSH2 OR USE_LIBSSH) -_add_if("SFTP" USE_LIBSSH2 OR USE_LIBSSH) -_add_if("RTSP" NOT CURL_DISABLE_RTSP) -_add_if("RTMP" USE_LIBRTMP) -_add_if("MQTT" NOT CURL_DISABLE_MQTT) -_add_if("WS" USE_WEBSOCKETS) -_add_if("WSS" USE_WEBSOCKETS) +curl_add_if("LDAPS" NOT CURL_DISABLE_LDAPS AND + ((USE_OPENLDAP AND _ssl_enabled) OR + (NOT USE_OPENLDAP AND HAVE_LDAP_SSL))) +curl_add_if("DICT" NOT CURL_DISABLE_DICT) +curl_add_if("TFTP" NOT CURL_DISABLE_TFTP) +curl_add_if("GOPHER" NOT CURL_DISABLE_GOPHER) +curl_add_if("GOPHERS" NOT CURL_DISABLE_GOPHER AND _ssl_enabled) +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" NOT CURL_DISABLE_SMB AND + _use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4)) +curl_add_if("SMBS" NOT CURL_DISABLE_SMB AND _ssl_enabled AND + _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 OR USE_WOLFSSH) +curl_add_if("SFTP" USE_LIBSSH2 OR USE_LIBSSH OR USE_WOLFSSH) +curl_add_if("IPFS" NOT CURL_DISABLE_IPFS) +curl_add_if("IPNS" NOT CURL_DISABLE_IPFS) +curl_add_if("RTSP" NOT CURL_DISABLE_RTSP) +curl_add_if("RTMP" USE_LIBRTMP) +curl_add_if("MQTT" NOT CURL_DISABLE_MQTT) +curl_add_if("WS" NOT CURL_DISABLE_WEBSOCKETS) +curl_add_if("WSS" NOT CURL_DISABLE_WEBSOCKETS AND _ssl_enabled) if(_items) list(SORT _items) endif() +set(CURL_SUPPORTED_PROTOCOLS_LIST "${_items}") string(REPLACE ";" " " SUPPORT_PROTOCOLS "${_items}") -message(STATUS "Enabled protocols: ${SUPPORT_PROTOCOLS}") +string(TOLOWER "${SUPPORT_PROTOCOLS}" _support_protocols_lower) +message(STATUS "Protocols: ${_support_protocols_lower}") + +# Clear list and try to detect available features +set(_items "") +curl_add_if("SSL" _ssl_enabled) +curl_add_if("IPv6" USE_IPV6) +curl_add_if("UnixSockets" USE_UNIX_SOCKETS) +curl_add_if("libz" HAVE_LIBZ) +curl_add_if("brotli" HAVE_BROTLI) +curl_add_if("gsasl" USE_GSASL) +curl_add_if("zstd" HAVE_ZSTD) +curl_add_if("AsynchDNS" USE_ARES OR USE_THREADS_POSIX OR USE_THREADS_WIN32) +curl_add_if("asyn-rr" USE_ARES AND ENABLE_THREADED_RESOLVER AND USE_HTTPSRR) +curl_add_if("IDN" (HAVE_LIBIDN2 AND HAVE_IDN2_H) OR + USE_WIN32_IDN OR + USE_APPLE_IDN) +curl_add_if("Largefile" (SIZEOF_CURL_OFF_T GREATER 4) AND + ((SIZEOF_OFF_T GREATER 4) OR USE_WIN32_LARGE_FILES)) +curl_add_if("SSPI" USE_WINDOWS_SSPI) +curl_add_if("GSS-API" HAVE_GSSAPI) +curl_add_if("alt-svc" NOT CURL_DISABLE_ALTSVC) +curl_add_if("HSTS" NOT CURL_DISABLE_HSTS) +curl_add_if("SPNEGO" NOT CURL_DISABLE_NEGOTIATE_AUTH AND + (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) +curl_add_if("Kerberos" NOT CURL_DISABLE_KERBEROS_AUTH AND + (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) +curl_add_if("NTLM" NOT CURL_DISABLE_NTLM AND + (_use_curl_ntlm_core OR USE_WINDOWS_SSPI)) +curl_add_if("TLS-SRP" USE_TLS_SRP) +curl_add_if("HTTP2" USE_NGHTTP2) +curl_add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE OR USE_MSH3 OR USE_OPENSSL_QUIC) +curl_add_if("MultiSSL" CURL_WITH_MULTI_SSL) +curl_add_if("HTTPS-proxy" NOT CURL_DISABLE_PROXY AND _ssl_enabled AND (USE_OPENSSL OR USE_GNUTLS + OR USE_SCHANNEL OR USE_RUSTLS OR USE_BEARSSL OR + USE_MBEDTLS OR USE_SECTRANSP OR + (USE_WOLFSSL AND HAVE_WOLFSSL_BIO_NEW))) +curl_add_if("Unicode" ENABLE_UNICODE) +curl_add_if("threadsafe" HAVE_ATOMIC OR + (USE_THREADS_POSIX AND HAVE_PTHREAD_H) OR + (WIN32 AND HAVE_WIN32_WINNT GREATER_EQUAL 0x0600)) +curl_add_if("Debug" ENABLE_DEBUG) +curl_add_if("TrackMemory" ENABLE_CURLDEBUG) +curl_add_if("ECH" _ssl_enabled AND HAVE_ECH) +curl_add_if("HTTPSRR" _ssl_enabled AND USE_HTTPSRR) +curl_add_if("PSL" USE_LIBPSL) +curl_add_if("CAcert" CURL_CA_EMBED_SET) +curl_add_if("SSLS-EXPORT" _ssl_enabled AND USE_SSLS_EXPORT) +if(_items) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) + list(SORT _items CASE INSENSITIVE) + else() + list(SORT _items) + endif() +endif() +set(CURL_SUPPORTED_FEATURES_LIST "${_items}") +string(REPLACE ";" " " SUPPORT_FEATURES "${_items}") +message(STATUS "Features: ${SUPPORT_FEATURES}") # Clear list and collect SSL backends -set(_items) -_add_if("Schannel" SSL_ENABLED AND USE_SCHANNEL) -_add_if("OpenSSL" SSL_ENABLED AND USE_OPENSSL) -_add_if("Secure Transport" SSL_ENABLED AND USE_SECTRANSP) -_add_if("mbedTLS" SSL_ENABLED AND USE_MBEDTLS) -_add_if("BearSSL" SSL_ENABLED AND USE_BEARSSL) -_add_if("NSS" SSL_ENABLED AND USE_NSS) -_add_if("wolfSSL" SSL_ENABLED AND USE_WOLFSSL) +set(_items "") +curl_add_if("Schannel" _ssl_enabled AND USE_SCHANNEL) +curl_add_if("${_openssl}" _ssl_enabled AND USE_OPENSSL AND OPENSSL_VERSION VERSION_LESS 3.0.0) +curl_add_if("${_openssl} v3+" _ssl_enabled AND USE_OPENSSL AND OPENSSL_VERSION VERSION_GREATER_EQUAL 3.0.0) +curl_add_if("Secure Transport" _ssl_enabled AND USE_SECTRANSP) +curl_add_if("mbedTLS" _ssl_enabled AND USE_MBEDTLS) +curl_add_if("BearSSL" _ssl_enabled AND USE_BEARSSL) +curl_add_if("wolfSSL" _ssl_enabled AND USE_WOLFSSL) +curl_add_if("GnuTLS" _ssl_enabled AND USE_GNUTLS) +curl_add_if("rustls" _ssl_enabled AND USE_RUSTLS) + if(_items) - list(SORT _items) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) + list(SORT _items CASE INSENSITIVE) + else() + list(SORT _items) + endif() endif() string(REPLACE ";" " " SSL_BACKENDS "${_items}") message(STATUS "Enabled SSL backends: ${SSL_BACKENDS}") +if(CURL_DEFAULT_SSL_BACKEND) + message(STATUS "Default SSL backend: ${CURL_DEFAULT_SSL_BACKEND}") +endif() + +if(NOT CURL_DISABLE_INSTALL) + + # curl-config needs the following options to be set. + set(CC "${CMAKE_C_COMPILER}") + set(CONFIGURE_OPTIONS "") + set(CURLVERSION "${_curl_version}") + set(VERSIONNUM "${_curl_version_num}") + set(prefix "${CMAKE_INSTALL_PREFIX}") + set(exec_prefix "\${prefix}") + if(IS_ABSOLUTE ${CMAKE_INSTALL_INCLUDEDIR}) + set(includedir "${CMAKE_INSTALL_INCLUDEDIR}") + else() + set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") + endif() + if(IS_ABSOLUTE ${CMAKE_INSTALL_LIBDIR}) + set(libdir "${CMAKE_INSTALL_LIBDIR}") + else() + set(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") + endif() + # "a" (Linux) or "lib" (Windows) + string(REPLACE "." "" libext "${CMAKE_STATIC_LIBRARY_SUFFIX}") + + set(_ldflags "") + set(LIBCURL_PC_LIBS_PRIVATE "") + + # Filter CMAKE_SHARED_LINKER_FLAGS for libs and libpaths + string(STRIP "${CMAKE_SHARED_LINKER_FLAGS}" _custom_ldflags) + string(REGEX REPLACE " +-([^ \\t;]*)" ";-\\1" _custom_ldflags "${_custom_ldflags}") + + set(_custom_libs "") + set(_custom_libdirs "") + foreach(_flag IN LISTS _custom_ldflags) + if(_flag MATCHES "^-l") + string(REGEX REPLACE "^-l" "" _flag "${_flag}") + list(APPEND _custom_libs "${_flag}") + elseif(_flag MATCHES "^-framework|^-F") + list(APPEND _custom_libs "${_flag}") + elseif(_flag MATCHES "^-L") + string(REGEX REPLACE "^-L" "" _flag "${_flag}") + list(APPEND _custom_libdirs "${_flag}") + elseif(_flag MATCHES "^--library-path=") + string(REGEX REPLACE "^--library-path=" "" _flag "${_flag}") + list(APPEND _custom_libdirs "${_flag}") + endif() + endforeach() + + # Avoid getting unnecessary -L options for known system directories. + set(_sys_libdirs "${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}") + foreach(_libdir IN LISTS CMAKE_SYSTEM_PREFIX_PATH) + if(_libdir MATCHES "/$") + string(APPEND _libdir "lib") + else() + string(APPEND _libdir "/lib") + endif() + if(IS_DIRECTORY "${_libdir}") + list(APPEND _sys_libdirs "${_libdir}") + endif() + if(DEFINED CMAKE_LIBRARY_ARCHITECTURE) + string(APPEND _libdir "/${CMAKE_LIBRARY_ARCHITECTURE}") + if(IS_DIRECTORY "${_libdir}") + list(APPEND _sys_libdirs "${_libdir}") + endif() + endif() + endforeach() -# curl-config needs the following options to be set. -set(CC "${CMAKE_C_COMPILER}") -# TODO probably put a -D... options here? -set(CONFIGURE_OPTIONS "") -# TODO when to set "-DCURL_STATICLIB" for CPPFLAG_CURL_STATICLIB? -set(CPPFLAG_CURL_STATICLIB "") -set(CURLVERSION "${CURL_VERSION}") -set(exec_prefix "\${prefix}") -set(includedir "\${prefix}/include") -set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS}") -set(LIBCURL_LIBS "") -set(libdir "${CMAKE_INSTALL_PREFIX}/lib") -foreach(_lib ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${CURL_LIBS}) - if(TARGET "${_lib}") - set(_libname "${_lib}") - get_target_property(_imported "${_libname}" IMPORTED) - if(NOT _imported) - # Reading the LOCATION property on non-imported target will error out. - # Assume the user won't need this information in the .pc file. - continue() + foreach(_libdir IN LISTS _custom_libdirs CURL_LIBDIRS) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) + cmake_path(SET _libdir NORMALIZE "${_libdir}") endif() - get_target_property(_lib "${_libname}" LOCATION) - if(NOT _lib) - message(WARNING "Bad lib in library list: ${_libname}") - continue() + list(FIND _sys_libdirs "${_libdir}" _libdir_index) + if(_libdir_index LESS 0) + list(APPEND _ldflags "-L${_libdir}") endif() + endforeach() + + set(_implicit_libs "") + if(NOT MINGW AND NOT UNIX) + set(_implicit_libs "${CMAKE_C_IMPLICIT_LINK_LIBRARIES}") endif() - if(_lib MATCHES ".*/.*" OR _lib MATCHES "^-") - set(LIBCURL_LIBS "${LIBCURL_LIBS} ${_lib}") + + foreach(_lib IN LISTS _implicit_libs _custom_libs CURL_LIBS) + if(TARGET "${_lib}") + set(_libname "${_lib}") + get_target_property(_imported "${_libname}" IMPORTED) + if(NOT _imported) + # Reading the LOCATION property on non-imported target will error out. + # Assume the user will not need this information in the .pc file. + continue() + endif() + get_target_property(_lib "${_libname}" LOCATION) + if(NOT _lib) + message(WARNING "Bad lib in library list: ${_libname}") + continue() + endif() + endif() + if(_lib MATCHES "^-") # '-framework ' + list(APPEND _ldflags "${_lib}") + elseif(_lib MATCHES "/") + # This gets a bit more complex, because we want to specify the + # directory separately, and only once per directory + get_filename_component(_libdir ${_lib} DIRECTORY) + get_filename_component(_libname ${_lib} NAME_WE) + if(_libname MATCHES "^lib") + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) + cmake_path(SET _libdir NORMALIZE "${_libdir}") + endif() + list(FIND _sys_libdirs "${_libdir}" _libdir_index) + if(_libdir_index LESS 0) + list(APPEND _ldflags "-L${_libdir}") + endif() + string(REGEX REPLACE "^lib" "" _libname "${_libname}") + list(APPEND LIBCURL_PC_LIBS_PRIVATE "-l${_libname}") + else() + list(APPEND LIBCURL_PC_LIBS_PRIVATE "${_lib}") + endif() + else() + list(APPEND LIBCURL_PC_LIBS_PRIVATE "-l${_lib}") + endif() + endforeach() + + if(LIBCURL_PC_REQUIRES_PRIVATE) + string(REPLACE ";" "," LIBCURL_PC_REQUIRES_PRIVATE "${LIBCURL_PC_REQUIRES_PRIVATE}") + endif() + if(LIBCURL_PC_LIBS_PRIVATE) + string(REPLACE ";" " " LIBCURL_PC_LIBS_PRIVATE "${LIBCURL_PC_LIBS_PRIVATE}") + endif() + if(_ldflags) + list(REMOVE_DUPLICATES _ldflags) + string(REPLACE ";" " " _ldflags "${_ldflags}") + set(LIBCURL_PC_LDFLAGS_PRIVATE "${_ldflags}") + string(STRIP "${LIBCURL_PC_LDFLAGS_PRIVATE}" LIBCURL_PC_LDFLAGS_PRIVATE) else() - set(LIBCURL_LIBS "${LIBCURL_LIBS} -l${_lib}") + set(LIBCURL_PC_LDFLAGS_PRIVATE "") endif() -endforeach() -if(BUILD_SHARED_LIBS) - set(ENABLE_SHARED "yes") - set(ENABLE_STATIC "no") - set(LIBCURL_NO_SHARED "") -else() - set(ENABLE_SHARED "no") - set(ENABLE_STATIC "yes") - set(LIBCURL_NO_SHARED "${LIBCURL_LIBS}") -endif() -# "a" (Linux) or "lib" (Windows) -string(REPLACE "." "" libext "${CMAKE_STATIC_LIBRARY_SUFFIX}") -set(prefix "${CMAKE_INSTALL_PREFIX}") -# Set this to "yes" to append all libraries on which -lcurl is dependent -set(REQUIRE_LIB_DEPS "no") -# SUPPORT_FEATURES -# SUPPORT_PROTOCOLS -set(VERSIONNUM "${CURL_VERSION_NUM}") - -# Finally generate a "curl-config" matching this config -# Use: -# * ENABLE_SHARED -# * ENABLE_STATIC -configure_file("${CURL_SOURCE_DIR}/curl-config.in" - "${CURL_BINARY_DIR}/curl-config" @ONLY) -install(FILES "${CURL_BINARY_DIR}/curl-config" - DESTINATION ${CMAKE_INSTALL_BINDIR} - PERMISSIONS - OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) - -# Finally generate a pkg-config file matching this config -configure_file("${CURL_SOURCE_DIR}/libcurl.pc.in" - "${CURL_BINARY_DIR}/libcurl.pc" @ONLY) -install(FILES "${CURL_BINARY_DIR}/libcurl.pc" - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) - -# install headers -install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/curl" + set(LIBCURL_PC_CFLAGS_PRIVATE "-DCURL_STATICLIB") + + # Merge pkg-config private fields into public ones when static-only + if(BUILD_SHARED_LIBS) + set(ENABLE_SHARED "yes") + set(LIBCURL_PC_REQUIRES "") + set(LIBCURL_PC_LIBS "") + set(LIBCURL_PC_CFLAGS "") + else() + set(ENABLE_SHARED "no") + set(LIBCURL_PC_REQUIRES "${LIBCURL_PC_REQUIRES_PRIVATE}") + set(LIBCURL_PC_LIBS "${LIBCURL_PC_LIBS_PRIVATE}") + set(LIBCURL_PC_CFLAGS "${LIBCURL_PC_CFLAGS_PRIVATE}") + endif() + if(BUILD_STATIC_LIBS) + set(ENABLE_STATIC "yes") + else() + set(ENABLE_STATIC "no") + endif() + + # Generate a "curl-config" matching this config. + # Consumed variables: + # CC + # CONFIGURE_OPTIONS + # CURLVERSION + # CURL_CA_BUNDLE + # ENABLE_SHARED + # ENABLE_STATIC + # exec_prefix + # includedir + # LIBCURL_PC_CFLAGS + # LIBCURL_PC_LDFLAGS_PRIVATE + # LIBCURL_PC_LIBS_PRIVATE + # libdir + # libext + # prefix + # SSL_BACKENDS + # SUPPORT_FEATURES + # SUPPORT_PROTOCOLS + # VERSIONNUM + configure_file( + "${PROJECT_SOURCE_DIR}/curl-config.in" + "${PROJECT_BINARY_DIR}/curl-config" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/curl-config" + DESTINATION ${CMAKE_INSTALL_BINDIR} + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + + # Generate a pkg-config file matching this config. + # Consumed variables: + # CURLVERSION + # exec_prefix + # includedir + # LIBCURL_PC_CFLAGS + # LIBCURL_PC_CFLAGS_PRIVATE + # LIBCURL_PC_LDFLAGS_PRIVATE + # LIBCURL_PC_LIBS + # LIBCURL_PC_LIBS_PRIVATE + # LIBCURL_PC_REQUIRES + # LIBCURL_PC_REQUIRES_PRIVATE + # libdir + # prefix + # SUPPORT_FEATURES + # SUPPORT_PROTOCOLS + # Documentation: + # https://people.freedesktop.org/~dbn/pkg-config-guide.html + # https://manpages.debian.org/unstable/pkgconf/pkg-config.1.en.html + # https://manpages.debian.org/unstable/pkg-config/pkg-config.1.en.html + # https://www.msys2.org/docs/pkgconfig/ + configure_file( + "${PROJECT_SOURCE_DIR}/libcurl.pc.in" + "${PROJECT_BINARY_DIR}/libcurl.pc" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/libcurl.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + + # Install headers + install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/curl" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h") -include(CMakePackageConfigHelpers) -write_basic_package_version_file( - "${version_config}" - VERSION ${CURL_VERSION} - COMPATIBILITY SameMajorVersion -) -file(READ "${version_config}" generated_version_config) -file(WRITE "${version_config}" -"if(NOT PACKAGE_FIND_VERSION_RANGE AND PACKAGE_FIND_VERSION_MAJOR STREQUAL \"7\") - # Version 8 satisfies version 7... requirements - set(PACKAGE_FIND_VERSION_MAJOR 8) - set(PACKAGE_FIND_VERSION_COUNT 1) -endif() -${generated_version_config}" -) - -# Use: -# * TARGETS_EXPORT_NAME -# * PROJECT_NAME -configure_package_config_file(CMake/curl-config.cmake.in - "${project_config}" - INSTALL_DESTINATION ${CURL_INSTALL_CMAKE_DIR} -) + include(CMakePackageConfigHelpers) + write_basic_package_version_file( + "${_version_config}" + VERSION ${_curl_version} + COMPATIBILITY SameMajorVersion) + file(READ "${_version_config}" _generated_version_config) + file(WRITE "${_version_config}" " + if(NOT PACKAGE_FIND_VERSION_RANGE AND PACKAGE_FIND_VERSION_MAJOR STREQUAL \"7\") + # Version 8 satisfies version 7... requirements + set(PACKAGE_FIND_VERSION_MAJOR 8) + set(PACKAGE_FIND_VERSION_COUNT 1) + endif() + ${_generated_version_config}") + + # Consumed custom variables: + # CURLVERSION + # LIB_NAME + # LIB_SELECTED + # TARGETS_EXPORT_NAME + # USE_OPENSSL OPENSSL_VERSION_MAJOR + # HAVE_LIBZ ZLIB_VERSION_MAJOR + # CURL_SUPPORTED_FEATURES_LIST + # CURL_SUPPORTED_PROTOCOLS_LIST + configure_package_config_file("CMake/curl-config.cmake.in" + "${_project_config}" + INSTALL_DESTINATION ${_install_cmake_dir} + PATH_VARS CMAKE_INSTALL_INCLUDEDIR) + + if(CURL_ENABLE_EXPORT_TARGET) + install(EXPORT "${TARGETS_EXPORT_NAME}" + NAMESPACE "${PROJECT_NAME}::" + DESTINATION ${_install_cmake_dir}) + endif() -if(CURL_ENABLE_EXPORT_TARGET) - install( - EXPORT "${TARGETS_EXPORT_NAME}" - NAMESPACE "${PROJECT_NAME}::" - DESTINATION ${CURL_INSTALL_CMAKE_DIR} - ) -endif() + install(FILES ${_version_config} ${_project_config} + DESTINATION ${_install_cmake_dir}) -install( - FILES ${version_config} ${project_config} - DESTINATION ${CURL_INSTALL_CMAKE_DIR} -) + if(NOT TARGET curl_uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/CMake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake" + @ONLY) -# Workaround for MSVS10 to avoid the Dialog Hell -# FIXME: This could be removed with future version of CMake. -if(MSVC_VERSION EQUAL 1600) - set(CURL_SLN_FILENAME "${CMAKE_CURRENT_BINARY_DIR}/CURL.sln") - if(EXISTS "${CURL_SLN_FILENAME}") - file(APPEND "${CURL_SLN_FILENAME}" "\n# This should be regenerated!\n") + add_custom_target(curl_uninstall + COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake") endif() -endif() - -if(NOT TARGET curl_uninstall) - configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/CMake/cmake_uninstall.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake - IMMEDIATE @ONLY) - add_custom_target(curl_uninstall - COMMAND ${CMAKE_COMMAND} -P - ${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake) -endif() + install(FILES "${PROJECT_SOURCE_DIR}/scripts/wcurl" + DESTINATION ${CMAKE_INSTALL_BINDIR} + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + + # The `-DEV` part is important + string(REGEX REPLACE "([0-9]+\.[0-9]+)\.([0-9]+.*)" "\\2" CPACK_PACKAGE_VERSION_PATCH "${_curl_version}") + set(CPACK_GENERATOR "TGZ") + include(CPack) +endif() + +# Save build info for test runner to pick up and log +set(_cmake_sysroot "") +if(CMAKE_OSX_SYSROOT) + set(_cmake_sysroot ${CMAKE_OSX_SYSROOT}) +elseif(CMAKE_SYSROOT) + set(_cmake_sysroot ${CMAKE_SYSROOT}) +endif() +set(_buildinfo "\ +buildinfo.configure.tool: cmake +buildinfo.configure.command: ${CMAKE_COMMAND} +buildinfo.configure.version: ${CMAKE_VERSION} +buildinfo.configure.args:${_cmake_args} +buildinfo.configure.generator: ${CMAKE_GENERATOR} +buildinfo.configure.make: ${CMAKE_MAKE_PROGRAM} +buildinfo.host.cpu: ${CMAKE_HOST_SYSTEM_PROCESSOR} +buildinfo.host.os: ${CMAKE_HOST_SYSTEM_NAME} +buildinfo.target.cpu: ${CMAKE_SYSTEM_PROCESSOR} +buildinfo.target.os: ${CMAKE_SYSTEM_NAME} +buildinfo.target.flags:${_target_flags} +buildinfo.compiler: ${CMAKE_C_COMPILER_ID} +buildinfo.compiler.version: ${CMAKE_C_COMPILER_VERSION} +buildinfo.sysroot: ${_cmake_sysroot} +") +file(WRITE "${PROJECT_BINARY_DIR}/buildinfo.txt" "# This is a generated file. Do not edit.\n${_buildinfo}") +if(NOT "$ENV{CURL_BUILDINFO}$ENV{CURL_CI}$ENV{CI}" STREQUAL "") + message(STATUS "\n${_buildinfo}") endif() diff --git a/Utilities/cmcurl/COPYING b/Utilities/cmcurl/COPYING index d1eab3eb932..3fa85ebb641 100644 --- a/Utilities/cmcurl/COPYING +++ b/Utilities/cmcurl/COPYING @@ -1,6 +1,6 @@ COPYRIGHT AND PERMISSION NOTICE -Copyright (c) 1996 - 2023, Daniel Stenberg, , and many +Copyright (c) 1996 - 2025, Daniel Stenberg, , and many contributors, see the THANKS file. All rights reserved. diff --git a/Utilities/cmcurl/curltest.c b/Utilities/cmcurl/curltest.c index cb87fce7fc1..d972f144533 100644 --- a/Utilities/cmcurl/curltest.c +++ b/Utilities/cmcurl/curltest.c @@ -4,17 +4,17 @@ #include #include -int test_curl(const char* url) +int test_curl(char const* url) { CURL* curl; CURLcode r; char proxy[1024]; int proxy_type = 0; - const char* env_HTTP_PROXY = getenv("HTTP_PROXY"); + char const* env_HTTP_PROXY = getenv("HTTP_PROXY"); if (env_HTTP_PROXY) { - const char* env_HTTP_PROXY_PORT = getenv("HTTP_PROXY_PORT"); - const char* env_HTTP_PROXY_TYPE = getenv("HTTP_PROXY_TYPE"); + char const* env_HTTP_PROXY_PORT = getenv("HTTP_PROXY_PORT"); + char const* env_HTTP_PROXY_TYPE = getenv("HTTP_PROXY_TYPE"); proxy_type = 1; if (env_HTTP_PROXY_PORT) { sprintf(proxy, "%s:%s", env_HTTP_PROXY, env_HTTP_PROXY_PORT); @@ -69,7 +69,7 @@ int test_curl(const char* url) return 0; } -int main(int argc, const char* argv[]) +int main(int argc, char const* argv[]) { int r; curl_global_init(CURL_GLOBAL_DEFAULT); diff --git a/Utilities/cmcurl/include/curl/curl.h b/Utilities/cmcurl/include/curl/curl.h index cae9b1cf1c8..d769108f912 100644 --- a/Utilities/cmcurl/include/curl/curl.h +++ b/Utilities/cmcurl/include/curl/curl.h @@ -30,55 +30,55 @@ */ #ifdef CURL_NO_OLDIES -#define CURL_STRICTER +#define CURL_STRICTER /* not used since 8.11.0 */ #endif /* Compile-time deprecation macros. */ -#if defined(__GNUC__) && \ - ((__GNUC__ > 12) || ((__GNUC__ == 12) && (__GNUC_MINOR__ >= 1 ))) && \ - !defined(__INTEL_COMPILER) && \ +#if (defined(__GNUC__) && \ + ((__GNUC__ > 12) || ((__GNUC__ == 12) && (__GNUC_MINOR__ >= 1))) || \ + (defined(__clang__) && __clang_major__ >= 3) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + !defined(__INTEL_COMPILER) && \ !defined(CURL_DISABLE_DEPRECATION) && !defined(BUILDING_LIBCURL) #define CURL_DEPRECATED(version, message) \ __attribute__((deprecated("since " # version ". " message))) +#ifdef __IAR_SYSTEMS_ICC__ +#define CURL_IGNORE_DEPRECATION(statements) \ + _Pragma("diag_suppress=Pe1444") \ + statements \ + _Pragma("diag_default=Pe1444") +#else #define CURL_IGNORE_DEPRECATION(statements) \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \ statements \ _Pragma("GCC diagnostic pop") +#endif #else #define CURL_DEPRECATED(version, message) #define CURL_IGNORE_DEPRECATION(statements) statements #endif #include "curlver.h" /* libcurl version defines */ -#include "system.h" /* determine things run-time */ - -/* - * Define CURL_WIN32 when build target is Win32 API - */ - -#if (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) && \ - !defined(__SYMBIAN32__) -#define CURL_WIN32 -#endif +#include "system.h" /* determine things runtime */ #include #include -#if (defined(__FreeBSD__) && (__FreeBSD__ >= 2)) || defined(__MidnightBSD__) +#if defined(__FreeBSD__) || defined(__MidnightBSD__) /* Needed for __FreeBSD_version or __MidnightBSD_version symbol definition */ -#include +#include #endif /* The include stuff here below is mainly for time_t! */ #include #include -#if defined(CURL_WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__) +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__) #if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H) || \ defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H)) -/* The check above prevents the winsock2 inclusion if winsock.h already was - included, since they can't co-exist without problems */ +/* The check above prevents the winsock2.h inclusion if winsock.h already was + included, since they cannot co-exist without problems */ #include #include #endif @@ -88,49 +88,42 @@ libc5-based Linux systems. Only include it on systems that are known to require it! */ #if defined(_AIX) || defined(__NOVELL_LIBC__) || defined(__NetBSD__) || \ - defined(__minix) || defined(__SYMBIAN32__) || defined(__INTEGRITY) || \ + defined(__minix) || defined(__INTEGRITY) || \ defined(ANDROID) || defined(__ANDROID__) || defined(__OpenBSD__) || \ defined(__CYGWIN__) || defined(AMIGA) || defined(__NuttX__) || \ (defined(__FreeBSD_version) && (__FreeBSD_version < 800000)) || \ (defined(__MidnightBSD_version) && (__MidnightBSD_version < 100000)) || \ - defined(__sun__) || defined(__serenity__) + defined(__sun__) || defined(__serenity__) || defined(__vxworks__) #include #endif -#if !defined(CURL_WIN32) && !defined(_WIN32_WCE) +#ifndef _WIN32 #include -#endif - -#if !defined(CURL_WIN32) #include #endif -/* Compatibility for non-Clang compilers */ -#ifndef __has_declspec_attribute -# define __has_declspec_attribute(x) 0 -#endif - #ifdef __cplusplus extern "C" { #endif -#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER) -typedef struct Curl_easy CURL; -typedef struct Curl_share CURLSH; -#else typedef void CURL; typedef void CURLSH; -#endif /* * libcurl external API function linkage decorations. */ +#ifdef __has_declspec_attribute +#define CURL_HAS_DECLSPEC_ATTRIBUTE(x) __has_declspec_attribute(x) +#else +#define CURL_HAS_DECLSPEC_ATTRIBUTE(x) 0 +#endif + #ifdef CURL_STATICLIB # define CURL_EXTERN -#elif defined(CURL_WIN32) || defined(__SYMBIAN32__) || \ - (__has_declspec_attribute(dllexport) && \ - __has_declspec_attribute(dllimport)) +#elif defined(_WIN32) || \ + (CURL_HAS_DECLSPEC_ATTRIBUTE(dllexport) && \ + CURL_HAS_DECLSPEC_ATTRIBUTE(dllimport)) # if defined(BUILDING_LIBCURL) # define CURL_EXTERN __declspec(dllexport) # else @@ -144,7 +137,7 @@ typedef void CURLSH; #ifndef curl_socket_typedef /* socket typedef */ -#if defined(CURL_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) +#if defined(_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) typedef SOCKET curl_socket_t; #define CURL_SOCKET_BAD INVALID_SOCKET #else @@ -159,9 +152,9 @@ typedef enum { CURLSSLBACKEND_NONE = 0, CURLSSLBACKEND_OPENSSL = 1, CURLSSLBACKEND_GNUTLS = 2, - CURLSSLBACKEND_NSS = 3, + CURLSSLBACKEND_NSS CURL_DEPRECATED(8.3.0, "") = 3, CURLSSLBACKEND_OBSOLETE4 = 4, /* Was QSOSSL. */ - CURLSSLBACKEND_GSKIT = 5, + CURLSSLBACKEND_GSKIT CURL_DEPRECATED(8.3.0, "") = 5, CURLSSLBACKEND_POLARSSL CURL_DEPRECATED(7.69.0, "") = 6, CURLSSLBACKEND_WOLFSSL = 7, CURLSSLBACKEND_SCHANNEL = 8, @@ -182,6 +175,16 @@ typedef enum { #define CURLSSLBACKEND_CYASSL CURLSSLBACKEND_WOLFSSL #define CURLSSLBACKEND_DARWINSSL CURLSSLBACKEND_SECURETRANSPORT +/* bits for the CURLOPT_FOLLOWLOCATION option */ +#define CURLFOLLOW_ALL 1L /* generic follow redirects */ + +/* Do not use the custom method in the follow-up request if the HTTP code + instructs so (301, 302, 303). */ +#define CURLFOLLOW_OBEYCODE 2L + +/* Only use the custom method in the first request, always reset in the next */ +#define CURLFOLLOW_FIRSTONLY 3L + struct curl_httppost { struct curl_httppost *next; /* next entry in the list */ char *name; /* pointer to allocated name */ @@ -198,9 +201,9 @@ struct curl_httppost { files */ long flags; /* as defined below */ -/* specified content is a file name */ +/* specified content is a filename */ #define CURL_HTTPPOST_FILENAME (1<<0) -/* specified content is a file name */ +/* specified content is a filename */ #define CURL_HTTPPOST_READFILE (1<<1) /* name is only stored pointer do not free in formfree */ #define CURL_HTTPPOST_PTRNAME (1<<2) @@ -216,8 +219,8 @@ struct curl_httppost { /* use size in 'contentlen', added in 7.46.0 */ #define CURL_HTTPPOST_LARGE (1<<7) - char *showfilename; /* The file name to show. If not set, the - actual file name will be used (if this + char *showfilename; /* The filename to show. If not set, the + actual filename will be used (if this is a file part) */ void *userp; /* custom pointer used for HTTPPOST_CALLBACK posts */ @@ -254,12 +257,12 @@ typedef int (*curl_xferinfo_callback)(void *clientp, #endif #ifndef CURL_MAX_WRITE_SIZE - /* Tests have proven that 20K is a very bad buffer size for uploads on - Windows, while 16K for some odd reason performed a lot better. - We do the ifndef check to allow this value to easier be changed at build - time for those who feel adventurous. The practical minimum is about - 400 bytes since libcurl uses a buffer of this size as a scratch area - (unrelated to network send operations). */ + /* Tests have proven that 20K is a bad buffer size for uploads on Windows, + while 16K for some odd reason performed a lot better. We do the ifndef + check to allow this value to easier be changed at build time for those + who feel adventurous. The practical minimum is about 400 bytes since + libcurl uses a buffer of this size as a scratch area (unrelated to + network send operations). */ #define CURL_MAX_WRITE_SIZE 16384 #endif @@ -359,13 +362,13 @@ typedef long (*curl_chunk_bgn_callback)(const void *transfer_info, download of an individual chunk finished. Note! After this callback was set then it have to be called FOR ALL chunks. Even if downloading of this chunk was skipped in CHUNK_BGN_FUNC. - This is the reason why we don't need "transfer_info" parameter in this + This is the reason why we do not need "transfer_info" parameter in this callback and we are not interested in "remains" parameter too. */ typedef long (*curl_chunk_end_callback)(void *ptr); /* return codes for FNMATCHFUNCTION */ #define CURL_FNMATCHFUNC_MATCH 0 /* string corresponds to the pattern */ -#define CURL_FNMATCHFUNC_NOMATCH 1 /* pattern doesn't match the string */ +#define CURL_FNMATCHFUNC_NOMATCH 1 /* pattern does not match the string */ #define CURL_FNMATCHFUNC_FAIL 2 /* an error occurred */ /* callback type for wildcard downloading pattern matching. If the @@ -377,7 +380,7 @@ typedef int (*curl_fnmatch_callback)(void *ptr, /* These are the return codes for the seek callbacks */ #define CURL_SEEKFUNC_OK 0 #define CURL_SEEKFUNC_FAIL 1 /* fail the entire transfer */ -#define CURL_SEEKFUNC_CANTSEEK 2 /* tell libcurl seeking can't be done, so +#define CURL_SEEKFUNC_CANTSEEK 2 /* tell libcurl seeking cannot be done, so libcurl might try other means instead */ typedef int (*curl_seek_callback)(void *instream, curl_off_t offset, @@ -460,7 +463,7 @@ typedef curlioerr (*curl_ioctl_callback)(CURL *handle, #ifndef CURL_DID_MEMORY_FUNC_TYPEDEFS /* * The following typedef's are signatures of malloc, free, realloc, strdup and - * calloc respectively. Function pointers of these types can be passed to the + * calloc respectively. Function pointers of these types can be passed to the * curl_global_init_mem() function to set user defined memory management * callback routines. */ @@ -548,22 +551,22 @@ typedef enum { CURLE_WRITE_ERROR, /* 23 */ CURLE_OBSOLETE24, /* 24 - NOT USED */ CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ - CURLE_READ_ERROR, /* 26 - couldn't open/read from file */ + CURLE_READ_ERROR, /* 26 - could not open/read from file */ CURLE_OUT_OF_MEMORY, /* 27 */ CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ CURLE_OBSOLETE29, /* 29 - NOT USED */ CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ CURLE_OBSOLETE32, /* 32 - NOT USED */ - CURLE_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ - CURLE_HTTP_POST_ERROR, /* 34 */ + CURLE_RANGE_ERROR, /* 33 - RANGE "command" did not work */ + CURLE_OBSOLETE34, /* 34 */ CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ - CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - could not resume download */ CURLE_FILE_COULDNT_READ_FILE, /* 37 */ CURLE_LDAP_CANNOT_BIND, /* 38 */ CURLE_LDAP_SEARCH_FAILED, /* 39 */ CURLE_OBSOLETE40, /* 40 - NOT USED */ - CURLE_FUNCTION_NOT_FOUND, /* 41 - NOT USED starting with 7.53.0 */ + CURLE_OBSOLETE41, /* 41 - NOT USED starting with 7.53.0 */ CURLE_ABORTED_BY_CALLBACK, /* 42 */ CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ CURLE_OBSOLETE44, /* 44 - NOT USED */ @@ -582,9 +585,9 @@ typedef enum { CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ CURLE_OBSOLETE57, /* 57 - NOT IN USE */ CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ - CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ + CURLE_SSL_CIPHER, /* 59 - could not use specified cipher */ CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint - wasn't verified fine */ + was not verified fine */ CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ CURLE_OBSOLETE62, /* 62 - NOT IN USE since 7.82.0 */ CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ @@ -613,7 +616,7 @@ typedef enum { CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL connection */ CURLE_AGAIN, /* 81 - socket is not ready for send/recv, - wait till it's ready and try again (Added + wait till it is ready and try again (Added in 7.18.2) */ CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or wrong format (Added in 7.19.0) */ @@ -640,16 +643,37 @@ typedef enum { CURLE_PROXY, /* 97 - proxy handshake error */ CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */ CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */ - CURL_LAST /* never use! */ + CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */ + CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */ + CURL_LAST, /* never use! */ + + CURLE_RESERVED115 = 115, /* 115-126 - used in tests */ + CURLE_RESERVED116 = 116, + CURLE_RESERVED117 = 117, + CURLE_RESERVED118 = 118, + CURLE_RESERVED119 = 119, + CURLE_RESERVED120 = 120, + CURLE_RESERVED121 = 121, + CURLE_RESERVED122 = 122, + CURLE_RESERVED123 = 123, + CURLE_RESERVED124 = 124, + CURLE_RESERVED125 = 125, + CURLE_RESERVED126 = 126 } CURLcode; #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all the obsolete stuff removed! */ -/* Previously obsolete error code re-used in 7.38.0 */ +/* removed in 7.53.0 */ +#define CURLE_FUNCTION_NOT_FOUND CURLE_OBSOLETE41 + +/* removed in 7.56.0 */ +#define CURLE_HTTP_POST_ERROR CURLE_OBSOLETE34 + +/* Previously obsolete error code reused in 7.38.0 */ #define CURLE_OBSOLETE16 CURLE_HTTP2 -/* Previously obsolete error codes re-used in 7.24.0 */ +/* Previously obsolete error codes reused in 7.24.0 */ #define CURLE_OBSOLETE10 CURLE_FTP_ACCEPT_FAILED #define CURLE_OBSOLETE12 CURLE_FTP_ACCEPT_TIMEOUT @@ -720,6 +744,8 @@ typedef enum { with them. */ #define CURLOPT_WRITEINFO CURLOPT_OBSOLETE40 #define CURLOPT_CLOSEPOLICY CURLOPT_OBSOLETE72 +#define CURLOPT_OBSOLETE72 9999 +#define CURLOPT_OBSOLETE40 9999 #endif /* !CURL_NO_OLDIES */ @@ -770,7 +796,7 @@ typedef CURLcode (*curl_conv_callback)(char *buffer, size_t length); typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, /* easy handle */ void *ssl_ctx, /* actually an OpenSSL - or WolfSSL SSL_CTX, + or wolfSSL SSL_CTX, or an mbedTLS mbedtls_ssl_config */ void *userptr); @@ -781,13 +807,13 @@ typedef enum { CURLPROXY_HTTP_1_0 = 1, /* added in 7.19.4, force to use CONNECT HTTP/1.0 */ CURLPROXY_HTTPS = 2, /* HTTPS but stick to HTTP/1 added in 7.52.0 */ - CURLPROXY_HTTPS2 = 3, /* HTTPS and attempt HTTP/2 added in 8.1.0 */ + CURLPROXY_HTTPS2 = 3, /* HTTPS and attempt HTTP/2 added in 8.2.0 */ CURLPROXY_SOCKS4 = 4, /* support added in 7.15.2, enum existed already in 7.10 */ CURLPROXY_SOCKS5 = 5, /* added in 7.10 */ CURLPROXY_SOCKS4A = 6, /* added in 7.18.0 */ CURLPROXY_SOCKS5_HOSTNAME = 7 /* Use the SOCKS5 protocol but pass along the - host name rather than the IP address. added + hostname rather than the IP address. added in 7.18.0 */ } curl_proxytype; /* this enum was added in 7.10 */ @@ -819,7 +845,10 @@ typedef enum { #define CURLAUTH_GSSAPI CURLAUTH_NEGOTIATE #define CURLAUTH_NTLM (((unsigned long)1)<<3) #define CURLAUTH_DIGEST_IE (((unsigned long)1)<<4) +#ifndef CURL_NO_OLDIES + /* functionality removed since 8.8.0 */ #define CURLAUTH_NTLM_WB (((unsigned long)1)<<5) +#endif #define CURLAUTH_BEARER (((unsigned long)1)<<6) #define CURLAUTH_AWS_SIGV4 (((unsigned long)1)<<7) #define CURLAUTH_ONLY (((unsigned long)1)<<31) @@ -864,7 +893,7 @@ enum curl_khstat { CURLKHSTAT_FINE_ADD_TO_FILE, CURLKHSTAT_FINE, CURLKHSTAT_REJECT, /* reject the connection, return an error */ - CURLKHSTAT_DEFER, /* do not accept it, but we can't answer right now. + CURLKHSTAT_DEFER, /* do not accept it, but we cannot answer right now. Causes a CURLE_PEER_FAILED_VERIFICATION error but the connection will be left intact etc */ CURLKHSTAT_FINE_REPLACE, /* accept and replace the wrong key */ @@ -898,12 +927,13 @@ typedef int /* parameter for the CURLOPT_USE_SSL option */ +#define CURLUSESSL_NONE 0L /* do not attempt to use SSL */ +#define CURLUSESSL_TRY 1L /* try using SSL, proceed anyway otherwise */ +#define CURLUSESSL_CONTROL 2L /* SSL for the control connection or fail */ +#define CURLUSESSL_ALL 3L /* SSL for all communication or fail */ + typedef enum { - CURLUSESSL_NONE, /* do not attempt to use SSL */ - CURLUSESSL_TRY, /* try using SSL, proceed anyway otherwise */ - CURLUSESSL_CONTROL, /* SSL for the control connection or fail */ - CURLUSESSL_ALL, /* SSL for all communication or fail */ - CURLUSESSL_LAST /* not an option, never use */ + CURLUSESSL_LAST = 4 /* not an option, never use */ } curl_usessl; /* Definition of bits for the CURLOPT_SSL_OPTIONS argument: */ @@ -936,6 +966,9 @@ typedef enum { a client certificate for authentication. (Schannel) */ #define CURLSSLOPT_AUTO_CLIENT_CERT (1<<5) +/* If possible, send data using TLS 1.3 early data */ +#define CURLSSLOPT_EARLYDATA (1<<6) + /* The default connection attempt delay in milliseconds for happy eyeballs. CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 and happy-eyeballs-timeout-ms.d document this value, keep them in sync. */ @@ -1004,6 +1037,12 @@ typedef enum { #define CURLALTSVC_H2 (1<<4) #define CURLALTSVC_H3 (1<<5) +/* bitmask values for CURLOPT_UPLOAD_FLAGS */ +#define CURLULFLAG_ANSWERED (1L<<0) +#define CURLULFLAG_DELETED (1L<<1) +#define CURLULFLAG_DRAFT (1L<<2) +#define CURLULFLAG_FLAGGED (1L<<3) +#define CURLULFLAG_SEEN (1L<<4) struct curl_hstsentry { char *name; @@ -1084,7 +1123,7 @@ typedef CURLSTScode (*curl_hstswrite_callback)(CURL *easy, #define CURLOPT(na,t,nu) na = t + nu #define CURLOPTDEPRECATED(na,t,nu,v,m) na CURL_DEPRECATED(v,m) = t + nu -/* CURLOPT aliases that make no run-time difference */ +/* CURLOPT aliases that make no runtime difference */ /* 'char *' argument to a string with a trailing zero */ #define CURLOPTTYPE_STRINGPOINT CURLOPTTYPE_OBJECTPOINT @@ -1151,7 +1190,7 @@ typedef enum { * * For large file support, there is also a _LARGE version of the key * which takes an off_t type, allowing platforms with larger off_t - * sizes to handle larger files. See below for INFILESIZE_LARGE. + * sizes to handle larger files. See below for INFILESIZE_LARGE. */ CURLOPT(CURLOPT_INFILESIZE, CURLOPTTYPE_LONG, 14), @@ -1184,7 +1223,7 @@ typedef enum { * * Note there is also a _LARGE version of this key which uses * off_t types, allowing for large file offsets on platforms which - * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. + * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. */ CURLOPT(CURLOPT_RESUME_FROM, CURLOPTTYPE_LONG, 21), @@ -1246,8 +1285,7 @@ typedef enum { /* send linked-list of post-transfer QUOTE commands */ CURLOPT(CURLOPT_POSTQUOTE, CURLOPTTYPE_SLISTPOINT, 39), - /* OBSOLETE, do not use! */ - CURLOPT(CURLOPT_OBSOLETE40, CURLOPTTYPE_OBJECTPOINT, 40), + /* 40 is not used */ /* talk a lot */ CURLOPT(CURLOPT_VERBOSE, CURLOPTTYPE_LONG, 41), @@ -1320,9 +1358,9 @@ typedef enum { /* Set the interface string to use as outgoing network interface */ CURLOPT(CURLOPT_INTERFACE, CURLOPTTYPE_STRINGPOINT, 62), - /* Set the krb4/5 security level, this also enables krb4/5 awareness. This - * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string - * is set but doesn't match one of these, 'private' will be used. */ + /* Set the krb4/5 security level, this also enables krb4/5 awareness. This + * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string + * is set but does not match one of these, 'private' will be used. */ CURLOPT(CURLOPT_KRBLEVEL, CURLOPTTYPE_STRINGPOINT, 63), /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ @@ -1348,22 +1386,20 @@ typedef enum { /* Max amount of cached alive connections */ CURLOPT(CURLOPT_MAXCONNECTS, CURLOPTTYPE_LONG, 71), - /* OBSOLETE, do not use! */ - CURLOPT(CURLOPT_OBSOLETE72, CURLOPTTYPE_LONG, 72), - + /* 72 = OBSOLETE */ /* 73 = OBSOLETE */ /* Set to explicitly use a new connection for the upcoming transfer. - Do not use this unless you're absolutely sure of this, as it makes the + Do not use this unless you are absolutely sure of this, as it makes the operation slower and is less friendly for the network. */ CURLOPT(CURLOPT_FRESH_CONNECT, CURLOPTTYPE_LONG, 74), - /* Set to explicitly forbid the upcoming transfer's connection to be re-used - when done. Do not use this unless you're absolutely sure of this, as it + /* Set to explicitly forbid the upcoming transfer's connection to be reused + when done. Do not use this unless you are absolutely sure of this, as it makes the operation slower and is less friendly for the network. */ CURLOPT(CURLOPT_FORBID_REUSE, CURLOPTTYPE_LONG, 75), - /* Set to a file name that contains random data for libcurl to use to + /* Set to a filename that contains random data for libcurl to use to seed the random engine when doing SSL connects. */ CURLOPTDEPRECATED(CURLOPT_RANDOM_FILE, CURLOPTTYPE_STRINGPOINT, 76, 7.84.0, "Serves no purpose anymore"), @@ -1390,11 +1426,11 @@ typedef enum { * provided hostname. */ CURLOPT(CURLOPT_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 81), - /* Specify which file name to write all known cookies in after completed - operation. Set file name to "-" (dash) to make it go to stdout. */ + /* Specify which filename to write all known cookies in after completed + operation. Set filename to "-" (dash) to make it go to stdout. */ CURLOPT(CURLOPT_COOKIEJAR, CURLOPTTYPE_STRINGPOINT, 82), - /* Specify which SSL ciphers to use */ + /* Specify which TLS 1.2 (1.1, 1.0) ciphers to use */ CURLOPT(CURLOPT_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 83), /* Specify which HTTP version to use! This must be set to one of the @@ -1490,7 +1526,7 @@ typedef enum { CURLOPT(CURLOPT_HTTPAUTH, CURLOPTTYPE_VALUES, 107), /* Set the ssl context callback function, currently only for OpenSSL or - WolfSSL ssl_ctx, or mbedTLS mbedtls_ssl_config in the second argument. + wolfSSL ssl_ctx, or mbedTLS mbedtls_ssl_config in the second argument. The function must match the curl_ssl_ctx_callback prototype. */ CURLOPT(CURLOPT_SSL_CTX_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 108), @@ -1510,7 +1546,7 @@ typedef enum { CURLOPT(CURLOPT_PROXYAUTH, CURLOPTTYPE_VALUES, 111), /* Option that changes the timeout, in seconds, associated with getting a - response. This is different from transfer timeout time and essentially + response. This is different from transfer timeout time and essentially places a demand on the server to acknowledge commands in a timely manner. For FTP, SMTP, IMAP and POP3. */ CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT, CURLOPTTYPE_LONG, 112), @@ -1524,7 +1560,7 @@ typedef enum { an HTTP or FTP server. Note there is also _LARGE version which adds large file support for - platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ + platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ CURLOPT(CURLOPT_MAXFILESIZE, CURLOPTTYPE_LONG, 114), /* See the comment for INFILESIZE above, but in short, specifies @@ -1532,17 +1568,17 @@ typedef enum { */ CURLOPT(CURLOPT_INFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 115), - /* Sets the continuation offset. There is also a CURLOPTTYPE_LONG version + /* Sets the continuation offset. There is also a CURLOPTTYPE_LONG version * of this; look above for RESUME_FROM. */ CURLOPT(CURLOPT_RESUME_FROM_LARGE, CURLOPTTYPE_OFF_T, 116), /* Sets the maximum size of data that will be downloaded from - * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. + * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. */ CURLOPT(CURLOPT_MAXFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 117), - /* Set this option to the file name of your .netrc file you want libcurl + /* Set this option to the filename of your .netrc file you want libcurl to parse (using the CURLOPT_NETRC option). If not set, libcurl will do a poor attempt to find the user's home directory and check for a .netrc file in there. */ @@ -1652,7 +1688,7 @@ typedef enum { CURLOPT(CURLOPT_SOCKOPTFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 148), CURLOPT(CURLOPT_SOCKOPTDATA, CURLOPTTYPE_CBPOINT, 149), - /* set to 0 to disable session ID re-use for this transfer, default is + /* set to 0 to disable session ID reuse for this transfer, default is enabled (== 1) */ CURLOPT(CURLOPT_SSL_SESSIONID_CACHE, CURLOPTTYPE_LONG, 150), @@ -1689,7 +1725,7 @@ typedef enum { /* Callback function for opening socket (instead of socket(2)). Optionally, callback is able change the address or refuse to connect returning - CURL_SOCKET_BAD. The callback should have type + CURL_SOCKET_BAD. The callback should have type curl_opensocket_callback */ CURLOPT(CURLOPT_OPENSOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 163), CURLOPT(CURLOPT_OPENSOCKETDATA, CURLOPTTYPE_CBPOINT, 164), @@ -1759,7 +1795,7 @@ typedef enum { CURLOPTDEPRECATED(CURLOPT_REDIR_PROTOCOLS, CURLOPTTYPE_LONG, 182, 7.85.0, "Use CURLOPT_REDIR_PROTOCOLS_STR"), - /* set the SSH knownhost file name to use */ + /* set the SSH knownhost filename to use */ CURLOPT(CURLOPT_SSH_KNOWNHOSTS, CURLOPTTYPE_STRINGPOINT, 183), /* set the SSH host key callback, must point to a curl_sshkeycallback @@ -1840,7 +1876,7 @@ typedef enum { future libcurl release. libcurl will ask for the compressed methods it knows of, and if that - isn't any, it will not ask for transfer-encoding at all even if this + is not any, it will not ask for transfer-encoding at all even if this option is set to 1. */ @@ -1854,7 +1890,8 @@ typedef enum { /* allow GSSAPI credential delegation */ CURLOPT(CURLOPT_GSSAPI_DELEGATION, CURLOPTTYPE_VALUES, 210), - /* Set the name servers to use for DNS resolution */ + /* Set the name servers to use for DNS resolution. + * Only supported by the c-ares DNS backend */ CURLOPT(CURLOPT_DNS_SERVERS, CURLOPTTYPE_STRINGPOINT, 211), /* Time-out accept operations (currently for FTP only) after this amount @@ -1941,7 +1978,7 @@ typedef enum { /* Service Name */ CURLOPT(CURLOPT_SERVICE_NAME, CURLOPTTYPE_STRINGPOINT, 236), - /* Wait/don't wait for pipe/mutex to clarify */ + /* Wait/do not wait for pipe/mutex to clarify */ CURLOPT(CURLOPT_PIPEWAIT, CURLOPTTYPE_LONG, 237), /* Set the protocol used when curl is given a URL without a protocol */ @@ -1950,10 +1987,10 @@ typedef enum { /* Set stream weight, 1 - 256 (default is 16) */ CURLOPT(CURLOPT_STREAM_WEIGHT, CURLOPTTYPE_LONG, 239), - /* Set stream dependency on another CURL handle */ + /* Set stream dependency on another curl handle */ CURLOPT(CURLOPT_STREAM_DEPENDS, CURLOPTTYPE_OBJECTPOINT, 240), - /* Set E-xclusive stream dependency on another CURL handle */ + /* Set E-xclusive stream dependency on another curl handle */ CURLOPT(CURLOPT_STREAM_DEPENDS_E, CURLOPTTYPE_OBJECTPOINT, 241), /* Do not send any tftp option requests to the server */ @@ -2017,7 +2054,7 @@ typedef enum { /* password for the SSL private key for proxy */ CURLOPT(CURLOPT_PROXY_KEYPASSWD, CURLOPTTYPE_STRINGPOINT, 258), - /* Specify which SSL ciphers to use for proxy */ + /* Specify which TLS 1.2 (1.1, 1.0) ciphers to use for proxy */ CURLOPT(CURLOPT_PROXY_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 259), /* CRL file for proxy */ @@ -2102,7 +2139,7 @@ typedef enum { /* alt-svc control bitmask */ CURLOPT(CURLOPT_ALTSVC_CTRL, CURLOPTTYPE_LONG, 286), - /* alt-svc cache file name to possibly read from/write to */ + /* alt-svc cache filename to possibly read from/write to */ CURLOPT(CURLOPT_ALTSVC, CURLOPTTYPE_STRINGPOINT, 287), /* maximum age (idle time) of a connection to consider it for reuse @@ -2113,7 +2150,7 @@ typedef enum { CURLOPT(CURLOPT_SASL_AUTHZID, CURLOPTTYPE_STRINGPOINT, 289), /* allow RCPT TO command to fail for some recipients */ - CURLOPT(CURLOPT_MAIL_RCPT_ALLLOWFAILS, CURLOPTTYPE_LONG, 290), + CURLOPT(CURLOPT_MAIL_RCPT_ALLOWFAILS, CURLOPTTYPE_LONG, 290), /* the private SSL-certificate as a "blob" */ CURLOPT(CURLOPT_SSLCERT_BLOB, CURLOPTTYPE_BLOB, 291), @@ -2128,13 +2165,13 @@ typedef enum { /* the EC curves requested by the TLS client (RFC 8422, 5.1); * OpenSSL support via 'set_groups'/'set_curves': - * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_groups.html + * https://docs.openssl.org/master/man3/SSL_CTX_set1_curves/ */ CURLOPT(CURLOPT_SSL_EC_CURVES, CURLOPTTYPE_STRINGPOINT, 298), /* HSTS bitmask */ CURLOPT(CURLOPT_HSTS_CTRL, CURLOPTTYPE_LONG, 299), - /* HSTS file name */ + /* HSTS filename */ CURLOPT(CURLOPT_HSTS, CURLOPTTYPE_STRINGPOINT, 300), /* HSTS read callback */ @@ -2198,7 +2235,7 @@ typedef enum { /* specify which protocols that libcurl is allowed to follow directs to */ CURLOPT(CURLOPT_REDIR_PROTOCOLS_STR, CURLOPTTYPE_STRINGPOINT, 319), - /* websockets options */ + /* WebSockets options */ CURLOPT(CURLOPT_WS_OPTIONS, CURLOPTTYPE_LONG, 320), /* CA cache timeout */ @@ -2207,6 +2244,23 @@ typedef enum { /* Can leak things, gonna exit() soon */ CURLOPT(CURLOPT_QUICK_EXIT, CURLOPTTYPE_LONG, 322), + /* set a specific client IP for HAProxy PROXY protocol header? */ + CURLOPT(CURLOPT_HAPROXY_CLIENT_IP, CURLOPTTYPE_STRINGPOINT, 323), + + /* millisecond version */ + CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, CURLOPTTYPE_LONG, 324), + + /* set ECH configuration */ + CURLOPT(CURLOPT_ECH, CURLOPTTYPE_STRINGPOINT, 325), + + /* maximum number of keepalive probes (Linux, *BSD, macOS, etc.) */ + CURLOPT(CURLOPT_TCP_KEEPCNT, CURLOPTTYPE_LONG, 326), + + CURLOPT(CURLOPT_UPLOAD_FLAGS, CURLOPTTYPE_LONG, 327), + + /* set TLS supported signature algorithms */ + CURLOPT(CURLOPT_SSL_SIGNATURE_ALGORITHMS, CURLOPTTYPE_STRINGPOINT, 328), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -2235,6 +2289,9 @@ typedef enum { /* */ #define CURLOPT_FTP_RESPONSE_TIMEOUT CURLOPT_SERVER_RESPONSE_TIMEOUT +/* Added in 8.2.0 */ +#define CURLOPT_MAIL_RCPT_ALLLOWFAILS CURLOPT_MAIL_RCPT_ALLOWFAILS + #else /* This is set if CURL_NO_OLDIES is defined at compile-time */ #undef CURLOPT_DNS_USE_GLOBAL_CACHE /* soon obsolete */ @@ -2252,26 +2309,25 @@ typedef enum { /* Convenient "aliases" */ #define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER - /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ -enum { - CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd - like the library to choose the best possible - for us! */ - CURL_HTTP_VERSION_1_0, /* please use HTTP 1.0 in the request */ - CURL_HTTP_VERSION_1_1, /* please use HTTP 1.1 in the request */ - CURL_HTTP_VERSION_2_0, /* please use HTTP 2 in the request */ - CURL_HTTP_VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */ - CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE, /* please use HTTP 2 without HTTP/1.1 - Upgrade */ - CURL_HTTP_VERSION_3 = 30, /* Use HTTP/3, fallback to HTTP/2 or HTTP/1 if - needed. For HTTPS only. For HTTP, this option - makes libcurl return error. */ - CURL_HTTP_VERSION_3ONLY = 31, /* Use HTTP/3 without fallback. For HTTPS - only. For HTTP, this makes libcurl - return error. */ - - CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ -}; +/* These constants are for use with the CURLOPT_HTTP_VERSION option. */ +#define CURL_HTTP_VERSION_NONE 0L /* setting this means we do not care, and + that we would like the library to choose + the best possible for us! */ +#define CURL_HTTP_VERSION_1_0 1L /* please use HTTP 1.0 in the request */ +#define CURL_HTTP_VERSION_1_1 2L /* please use HTTP 1.1 in the request */ +#define CURL_HTTP_VERSION_2_0 3L /* please use HTTP 2 in the request */ +#define CURL_HTTP_VERSION_2TLS 4L /* use version 2 for HTTPS, version 1.1 for + HTTP */ +#define CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE 5L /* please use HTTP 2 without + HTTP/1.1 Upgrade */ +#define CURL_HTTP_VERSION_3 30L /* Use HTTP/3, fallback to HTTP/2 or + HTTP/1 if needed. For HTTPS only. For + HTTP, this option makes libcurl + return error. */ +#define CURL_HTTP_VERSION_3ONLY 31L /* Use HTTP/3 without fallback. For + HTTPS only. For HTTP, this makes + libcurl return error. */ +#define CURL_HTTP_VERSION_LAST 32L /* *ILLEGAL* http version */ /* Convenience definition simple because the name of the version is HTTP/2 and not 2.0. The 2_0 version of the enum name was set while the version was @@ -2281,63 +2337,63 @@ enum { /* * Public API enums for RTSP requests */ -enum { - CURL_RTSPREQ_NONE, /* first in list */ - CURL_RTSPREQ_OPTIONS, - CURL_RTSPREQ_DESCRIBE, - CURL_RTSPREQ_ANNOUNCE, - CURL_RTSPREQ_SETUP, - CURL_RTSPREQ_PLAY, - CURL_RTSPREQ_PAUSE, - CURL_RTSPREQ_TEARDOWN, - CURL_RTSPREQ_GET_PARAMETER, - CURL_RTSPREQ_SET_PARAMETER, - CURL_RTSPREQ_RECORD, - CURL_RTSPREQ_RECEIVE, - CURL_RTSPREQ_LAST /* last in list */ -}; + +#define CURL_RTSPREQ_NONE 0L +#define CURL_RTSPREQ_OPTIONS 1L +#define CURL_RTSPREQ_DESCRIBE 2L +#define CURL_RTSPREQ_ANNOUNCE 3L +#define CURL_RTSPREQ_SETUP 4L +#define CURL_RTSPREQ_PLAY 5L +#define CURL_RTSPREQ_PAUSE 6L +#define CURL_RTSPREQ_TEARDOWN 7L +#define CURL_RTSPREQ_GET_PARAMETER 8L +#define CURL_RTSPREQ_SET_PARAMETER 9L +#define CURL_RTSPREQ_RECORD 10L +#define CURL_RTSPREQ_RECEIVE 11L +#define CURL_RTSPREQ_LAST 12L /* not used */ /* These enums are for use with the CURLOPT_NETRC option. */ +#define CURL_NETRC_IGNORED 0L /* The .netrc will never be read. + This is the default. */ +#define CURL_NETRC_OPTIONAL 1L /* A user:password in the URL will be preferred + to one in the .netrc. */ +#define CURL_NETRC_REQUIRED 2L /* A user:password in the URL will be ignored. + Unless one is set programmatically, the + .netrc will be queried. */ enum CURL_NETRC_OPTION { - CURL_NETRC_IGNORED, /* The .netrc will never be read. - * This is the default. */ - CURL_NETRC_OPTIONAL, /* A user:password in the URL will be preferred - * to one in the .netrc. */ - CURL_NETRC_REQUIRED, /* A user:password in the URL will be ignored. - * Unless one is set programmatically, the .netrc - * will be queried. */ - CURL_NETRC_LAST + /* we set a single member here, just to make sure we still provide the enum, + but the values to use are defined above with L suffixes */ + CURL_NETRC_LAST = 3 }; -enum { - CURL_SSLVERSION_DEFAULT, - CURL_SSLVERSION_TLSv1, /* TLS 1.x */ - CURL_SSLVERSION_SSLv2, - CURL_SSLVERSION_SSLv3, - CURL_SSLVERSION_TLSv1_0, - CURL_SSLVERSION_TLSv1_1, - CURL_SSLVERSION_TLSv1_2, - CURL_SSLVERSION_TLSv1_3, - - CURL_SSLVERSION_LAST /* never use, keep last */ -}; +#define CURL_SSLVERSION_DEFAULT 0 +#define CURL_SSLVERSION_TLSv1 1 /* TLS 1.x */ +#define CURL_SSLVERSION_SSLv2 2 +#define CURL_SSLVERSION_SSLv3 3 +#define CURL_SSLVERSION_TLSv1_0 4 +#define CURL_SSLVERSION_TLSv1_1 5 +#define CURL_SSLVERSION_TLSv1_2 6 +#define CURL_SSLVERSION_TLSv1_3 7 -enum { - CURL_SSLVERSION_MAX_NONE = 0, - CURL_SSLVERSION_MAX_DEFAULT = (CURL_SSLVERSION_TLSv1 << 16), - CURL_SSLVERSION_MAX_TLSv1_0 = (CURL_SSLVERSION_TLSv1_0 << 16), - CURL_SSLVERSION_MAX_TLSv1_1 = (CURL_SSLVERSION_TLSv1_1 << 16), - CURL_SSLVERSION_MAX_TLSv1_2 = (CURL_SSLVERSION_TLSv1_2 << 16), - CURL_SSLVERSION_MAX_TLSv1_3 = (CURL_SSLVERSION_TLSv1_3 << 16), +#define CURL_SSLVERSION_LAST 8 /* never use, keep last */ + +#define CURL_SSLVERSION_MAX_NONE 0 +#define CURL_SSLVERSION_MAX_DEFAULT (CURL_SSLVERSION_TLSv1 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_0 (CURL_SSLVERSION_TLSv1_0 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_1 (CURL_SSLVERSION_TLSv1_1 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_2 (CURL_SSLVERSION_TLSv1_2 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_3 (CURL_SSLVERSION_TLSv1_3 << 16) /* never use, keep last */ - CURL_SSLVERSION_MAX_LAST = (CURL_SSLVERSION_LAST << 16) -}; +#define CURL_SSLVERSION_MAX_LAST (CURL_SSLVERSION_LAST << 16) + +#define CURL_TLSAUTH_NONE 0L +#define CURL_TLSAUTH_SRP 1L enum CURL_TLSAUTH { - CURL_TLSAUTH_NONE, - CURL_TLSAUTH_SRP, - CURL_TLSAUTH_LAST /* never use, keep last */ + /* we set a single member here, just to make sure we still provide the enum, + but the values to use are defined above with L suffixes */ + CURL_TLSAUTH_LAST = 2 }; /* symbols to use with CURLOPT_POSTREDIR. @@ -2352,14 +2408,16 @@ enum CURL_TLSAUTH { #define CURL_REDIR_POST_ALL \ (CURL_REDIR_POST_301|CURL_REDIR_POST_302|CURL_REDIR_POST_303) -typedef enum { - CURL_TIMECOND_NONE, - - CURL_TIMECOND_IFMODSINCE, - CURL_TIMECOND_IFUNMODSINCE, - CURL_TIMECOND_LASTMOD, +#define CURL_TIMECOND_NONE 0L +#define CURL_TIMECOND_IFMODSINCE 1L +#define CURL_TIMECOND_IFUNMODSINCE 2L +#define CURL_TIMECOND_LASTMOD 3L - CURL_TIMECOND_LAST +typedef enum { + /* we set a single member here, just to make sure we still provide + the enum typedef, but the values to use are defined above with L + suffixes */ + CURL_TIMECOND_LAST = 4 } curl_TimeCond; /* Special size_t value signaling a null-terminated string. */ @@ -2420,7 +2478,7 @@ CURL_EXTERN CURLcode curl_mime_name(curl_mimepart *part, const char *name); * * DESCRIPTION * - * Set mime part remote file name. + * Set mime part remote filename. */ CURL_EXTERN CURLcode curl_mime_filename(curl_mimepart *part, const char *filename); @@ -2629,7 +2687,7 @@ CURL_EXTERN char *curl_getenv(const char *variable); * * DESCRIPTION * - * Returns a static ascii string of the libcurl version. + * Returns a static ASCII string of the libcurl version. */ CURL_EXTERN char *curl_version(void); @@ -2701,10 +2759,10 @@ CURL_EXTERN CURLcode curl_global_init(long flags); * DESCRIPTION * * curl_global_init() or curl_global_init_mem() should be invoked exactly once - * for each application that uses libcurl. This function can be used to + * for each application that uses libcurl. This function can be used to * initialize libcurl and set user defined memory management callback - * functions. Users can implement memory management routines to check for - * memory leaks, check for mis-use of the curl library etc. User registered + * functions. Users can implement memory management routines to check for + * memory leaks, check for mis-use of the curl library etc. User registered * callback routines will be invoked by this library instead of the system * memory management routines like malloc, free etc. */ @@ -2725,6 +2783,20 @@ CURL_EXTERN CURLcode curl_global_init_mem(long flags, */ CURL_EXTERN void curl_global_cleanup(void); +/* + * NAME curl_global_trace() + * + * DESCRIPTION + * + * curl_global_trace() can be invoked at application start to + * configure which components in curl should participate in tracing. + + * This function is thread-safe if CURL_VERSION_THREADSAFE is set in the + * curl_version_info_data.features flag (fetch by curl_version_info()). + + */ +CURL_EXTERN CURLcode curl_global_trace(const char *config); + /* linked-list structure for the CURLOPT_QUOTE option (and other) */ struct curl_slist { char *data; @@ -2741,17 +2813,17 @@ struct curl_slist { * *before* curl_global_init(). * * The backend can be identified by the id (e.g. CURLSSLBACKEND_OPENSSL). The - * backend can also be specified via the name parameter (passing -1 as id). - * If both id and name are specified, the name will be ignored. If neither id - * nor name are specified, the function will fail with - * CURLSSLSET_UNKNOWN_BACKEND and set the "avail" pointer to the - * NULL-terminated list of available backends. + * backend can also be specified via the name parameter (passing -1 as id). If + * both id and name are specified, the name will be ignored. If neither id nor + * name are specified, the function will fail with CURLSSLSET_UNKNOWN_BACKEND + * and set the "avail" pointer to the NULL-terminated list of available + * backends. * * Upon success, the function returns CURLSSLSET_OK. * * If the specified SSL backend is not available, the function returns - * CURLSSLSET_UNKNOWN_BACKEND and sets the "avail" pointer to a NULL-terminated - * list of available SSL backends. + * CURLSSLSET_UNKNOWN_BACKEND and sets the "avail" pointer to a + * NULL-terminated list of available SSL backends. * * The SSL backend can be set only once. If it has already been set, a * subsequent attempt to change it will result in a CURLSSLSET_TOO_LATE. @@ -2804,13 +2876,14 @@ CURL_EXTERN void curl_slist_free_all(struct curl_slist *list); */ CURL_EXTERN time_t curl_getdate(const char *p, const time_t *unused); -/* info about the certificate chain, only for OpenSSL, GnuTLS, Schannel, NSS - and GSKit builds. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ +/* info about the certificate chain, for SSL backends that support it. Asked + for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ struct curl_certinfo { int num_of_certs; /* number of certificates with information */ - struct curl_slist **certinfo; /* for each index in this array, there's a - linked list with textual information in the - format "name: value" */ + struct curl_slist **certinfo; /* for each index in this array, there is a + linked list with textual information for a + certificate in the format "name:content". + eg "Subject:foo", "Issuer:bar", etc. */ }; /* Information about the SSL library used and the respective internal SSL @@ -2918,7 +2991,15 @@ typedef enum { CURLINFO_REFERER = CURLINFO_STRING + 60, CURLINFO_CAINFO = CURLINFO_STRING + 61, CURLINFO_CAPATH = CURLINFO_STRING + 62, - CURLINFO_LASTONE = 62 + CURLINFO_XFER_ID = CURLINFO_OFF_T + 63, + CURLINFO_CONN_ID = CURLINFO_OFF_T + 64, + CURLINFO_QUEUE_TIME_T = CURLINFO_OFF_T + 65, + CURLINFO_USED_PROXY = CURLINFO_LONG + 66, + CURLINFO_POSTTRANSFER_TIME_T = CURLINFO_OFF_T + 67, + CURLINFO_EARLYDATA_SENT_T = CURLINFO_OFF_T + 68, + CURLINFO_HTTPAUTH_USED = CURLINFO_LONG + 69, + CURLINFO_PROXYAUTH_USED = CURLINFO_LONG + 70, + CURLINFO_LASTONE = 70 } CURLINFO; /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as @@ -2994,7 +3075,7 @@ typedef enum { } CURLSHcode; typedef enum { - CURLSHOPT_NONE, /* don't use */ + CURLSHOPT_NONE, /* do not use */ CURLSHOPT_SHARE, /* specify a data type to share */ CURLSHOPT_UNSHARE, /* specify which data type to stop sharing */ CURLSHOPT_LOCKFUNC, /* pass in a 'curl_lock_function' pointer */ @@ -3014,17 +3095,18 @@ CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *share); */ typedef enum { - CURLVERSION_FIRST, - CURLVERSION_SECOND, - CURLVERSION_THIRD, - CURLVERSION_FOURTH, - CURLVERSION_FIFTH, - CURLVERSION_SIXTH, - CURLVERSION_SEVENTH, - CURLVERSION_EIGHTH, - CURLVERSION_NINTH, - CURLVERSION_TENTH, - CURLVERSION_ELEVENTH, + CURLVERSION_FIRST, /* 7.10 */ + CURLVERSION_SECOND, /* 7.11.1 */ + CURLVERSION_THIRD, /* 7.12.0 */ + CURLVERSION_FOURTH, /* 7.16.1 */ + CURLVERSION_FIFTH, /* 7.57.0 */ + CURLVERSION_SIXTH, /* 7.66.0 */ + CURLVERSION_SEVENTH, /* 7.70.0 */ + CURLVERSION_EIGHTH, /* 7.72.0 */ + CURLVERSION_NINTH, /* 7.75.0 */ + CURLVERSION_TENTH, /* 7.77.0 */ + CURLVERSION_ELEVENTH, /* 7.87.0 */ + CURLVERSION_TWELFTH, /* 8.8.0 */ CURLVERSION_LAST /* never actually use this */ } CURLversion; @@ -3033,7 +3115,7 @@ typedef enum { meant to be a built-in version number for what kind of struct the caller expects. If the struct ever changes, we redefine the NOW to another enum from above. */ -#define CURLVERSION_NOW CURLVERSION_ELEVENTH +#define CURLVERSION_NOW CURLVERSION_TWELFTH struct curl_version_info_data { CURLversion age; /* age of the returned struct */ @@ -3093,6 +3175,9 @@ struct curl_version_info_data { /* These fields were added in CURLVERSION_ELEVENTH */ /* feature_names is terminated by an entry with a NULL feature name */ const char * const *feature_names; + + /* These fields were added in CURLVERSION_TWELFTH */ + const char *rtmp_version; /* human readable string. */ }; typedef struct curl_version_info_data curl_version_info_data; @@ -3133,7 +3218,7 @@ typedef struct curl_version_info_data curl_version_info_data; #define CURL_VERSION_GSASL (1<<29) /* libgsasl is supported */ #define CURL_VERSION_THREADSAFE (1<<30) /* libcurl API is thread-safe */ - /* +/* * NAME curl_version_info() * * DESCRIPTION @@ -3149,7 +3234,7 @@ CURL_EXTERN curl_version_info_data *curl_version_info(CURLversion); * DESCRIPTION * * The curl_easy_strerror function may be used to turn a CURLcode value - * into the equivalent human readable error string. This is useful + * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ CURL_EXTERN const char *curl_easy_strerror(CURLcode); @@ -3160,7 +3245,7 @@ CURL_EXTERN const char *curl_easy_strerror(CURLcode); * DESCRIPTION * * The curl_share_strerror function may be used to turn a CURLSHcode value - * into the equivalent human readable error string. This is useful + * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ CURL_EXTERN const char *curl_share_strerror(CURLSHcode); @@ -3185,6 +3270,50 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask); #define CURLPAUSE_ALL (CURLPAUSE_RECV|CURLPAUSE_SEND) #define CURLPAUSE_CONT (CURLPAUSE_RECV_CONT|CURLPAUSE_SEND_CONT) +/* + * NAME curl_easy_ssls_import() + * + * DESCRIPTION + * + * The curl_easy_ssls_import function adds a previously exported SSL session + * to the SSL session cache of the easy handle (or the underlying share). + */ +CURL_EXTERN CURLcode curl_easy_ssls_import(CURL *handle, + const char *session_key, + const unsigned char *shmac, + size_t shmac_len, + const unsigned char *sdata, + size_t sdata_len); + +/* This is the curl_ssls_export_cb callback prototype. It + * is passed to curl_easy_ssls_export() to extract SSL sessions/tickets. */ +typedef CURLcode curl_ssls_export_cb(CURL *handle, + void *userptr, + const char *session_key, + const unsigned char *shmac, + size_t shmac_len, + const unsigned char *sdata, + size_t sdata_len, + curl_off_t valid_until, + int ietf_tls_id, + const char *alpn, + size_t earlydata_max); + +/* + * NAME curl_easy_ssls_export() + * + * DESCRIPTION + * + * The curl_easy_ssls_export function iterates over all SSL sessions stored + * in the easy handle (or underlying share) and invokes the passed + * callback. + * + */ +CURL_EXTERN CURLcode curl_easy_ssls_export(CURL *handle, + curl_ssls_export_cb *export_fn, + void *userptr); + + #ifdef __cplusplus } /* end of extern "C" */ #endif @@ -3197,8 +3326,9 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask); #include "options.h" #include "header.h" #include "websockets.h" +#include "mprintf.h" -/* the typechecker doesn't work in C++ (yet) */ +/* the typechecker does not work in C++ (yet) */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) && \ !defined(__cplusplus) && !defined(CURL_DISABLE_TYPECHECK) diff --git a/Utilities/cmcurl/include/curl/curlver.h b/Utilities/cmcurl/include/curl/curlver.h index 5588fa54deb..cce4b2027ba 100644 --- a/Utilities/cmcurl/include/curl/curlver.h +++ b/Utilities/cmcurl/include/curl/curlver.h @@ -32,13 +32,13 @@ /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "8.1.2" +#define LIBCURL_VERSION "8.14.1" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 1 -#define LIBCURL_VERSION_PATCH 2 +#define LIBCURL_VERSION_MINOR 14 +#define LIBCURL_VERSION_PATCH 1 /* This is the numeric version of the libcurl version number, meant for easier parsing and comparisons by programs. The LIBCURL_VERSION_NUM define will @@ -48,7 +48,7 @@ Where XX, YY and ZZ are the main version, release and patch numbers in hexadecimal (using 8 bits each). All three numbers are always represented - using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 appears as "0x090b07". This 6-digit (24 bits) hexadecimal number does not show pre-release number, @@ -59,7 +59,7 @@ CURL_VERSION_BITS() macro since curl's own configure script greps for it and needs it to contain the full number. */ -#define LIBCURL_VERSION_NUM 0x080102 +#define LIBCURL_VERSION_NUM 0x080e01 /* * This is the date and time when the full source package was created. The diff --git a/Utilities/cmcurl/include/curl/easy.h b/Utilities/cmcurl/include/curl/easy.h index 1285101c587..56f8060e045 100644 --- a/Utilities/cmcurl/include/curl/easy.h +++ b/Utilities/cmcurl/include/curl/easy.h @@ -50,7 +50,7 @@ CURL_EXTERN void curl_easy_cleanup(CURL *curl); * * Request internal information from the curl session with this function. * The third argument MUST be pointing to the specific type of the used option - * which is documented in each man page of the option. The data pointed to + * which is documented in each manpage of the option. The data pointed to * will be filled in accordingly and can be relied upon only if the function * returns CURLE_OK. This function is intended to get used *AFTER* a performed * transfer, all results from this function are undefined until the transfer @@ -78,7 +78,7 @@ CURL_EXTERN CURL *curl_easy_duphandle(CURL *curl); * * DESCRIPTION * - * Re-initializes a CURL handle to the default values. This puts back the + * Re-initializes a curl handle to the default values. This puts back the * handle to the same state as it was in when it was just created. * * It does keep: live connections, the Session ID cache, the DNS cache and the diff --git a/Utilities/cmcurl/include/curl/mprintf.h b/Utilities/cmcurl/include/curl/mprintf.h index e652a6520e6..88059c851fb 100644 --- a/Utilities/cmcurl/include/curl/mprintf.h +++ b/Utilities/cmcurl/include/curl/mprintf.h @@ -32,18 +32,51 @@ extern "C" { #endif -CURL_EXTERN int curl_mprintf(const char *format, ...); -CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...); -CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...); +#ifndef CURL_TEMP_PRINTF +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#if defined(__MINGW_PRINTF_FORMAT) /* mingw-w64 3.0.0+. Needs stdio.h. */ +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(__MINGW_PRINTF_FORMAT, fmt, arg))) +#else +#define CURL_TEMP_PRINTF(fmt, arg) +#endif +#else +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(printf, fmt, arg))) +#endif +#else +#define CURL_TEMP_PRINTF(fmt, arg) +#endif +#endif + +CURL_EXTERN int curl_mprintf(const char *format, ...) + CURL_TEMP_PRINTF(1, 2); +CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...) + CURL_TEMP_PRINTF(2, 3); +CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...) + CURL_TEMP_PRINTF(2, 3); CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, - const char *format, ...); -CURL_EXTERN int curl_mvprintf(const char *format, va_list args); -CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args); -CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args); + const char *format, ...) + CURL_TEMP_PRINTF(3, 4); +CURL_EXTERN int curl_mvprintf(const char *format, va_list args) + CURL_TEMP_PRINTF(1, 0); +CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args) + CURL_TEMP_PRINTF(2, 0); +CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args) + CURL_TEMP_PRINTF(2, 0); CURL_EXTERN int curl_mvsnprintf(char *buffer, size_t maxlength, - const char *format, va_list args); -CURL_EXTERN char *curl_maprintf(const char *format, ...); -CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args); + const char *format, va_list args) + CURL_TEMP_PRINTF(3, 0); +CURL_EXTERN char *curl_maprintf(const char *format, ...) + CURL_TEMP_PRINTF(1, 2); +CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args) + CURL_TEMP_PRINTF(1, 0); + +#undef CURL_TEMP_PRINTF #ifdef __cplusplus } /* end of extern "C" */ diff --git a/Utilities/cmcurl/include/curl/multi.h b/Utilities/cmcurl/include/curl/multi.h index 30a3d930177..42469bb5657 100644 --- a/Utilities/cmcurl/include/curl/multi.h +++ b/Utilities/cmcurl/include/curl/multi.h @@ -24,7 +24,7 @@ * ***************************************************************************/ /* - This is an "external" header file. Don't give away any internals here! + This is an "external" header file. Do not give away any internals here! GOALS @@ -54,11 +54,7 @@ extern "C" { #endif -#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER) -typedef struct Curl_multi CURLM; -#else typedef void CURLM; -#endif typedef enum { CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or @@ -66,7 +62,7 @@ typedef enum { CURLM_OK, CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ - CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you are in deep sh*t */ CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ @@ -109,7 +105,7 @@ struct CURLMsg { typedef struct CURLMsg CURLMsg; /* Based on poll(2) structure and values. - * We don't use pollfd and POLL* constants explicitly + * We do not use pollfd and POLL* constants explicitly * to cover platforms without poll(). */ #define CURL_WAIT_POLLIN 0x0001 #define CURL_WAIT_POLLPRI 0x0002 @@ -118,7 +114,7 @@ typedef struct CURLMsg CURLMsg; struct curl_waitfd { curl_socket_t fd; short events; - short revents; /* not supported yet */ + short revents; }; /* @@ -205,7 +201,7 @@ CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle); /* * Name: curl_multi_perform() * - * Desc: When the app thinks there's data available for curl it calls this + * Desc: When the app thinks there is data available for curl it calls this * function to read/write whatever there is right now. This returns * as soon as the reads and writes are done. This function does not * require that there actually is data available for reading or that @@ -236,7 +232,7 @@ CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); /* * Name: curl_multi_info_read() * - * Desc: Ask the multi handle if there's any messages/informationals from + * Desc: Ask the multi handle if there is any messages/informationals from * the individual transfers. Messages include informationals such as * error code from the transfer or just the fact that a transfer is * completed. More details on these should be written down as well. @@ -248,13 +244,13 @@ CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); * The data the returned pointer points to will not survive calling * curl_multi_cleanup(). * - * The 'CURLMsg' struct is meant to be very simple and only contain - * very basic information. If more involved information is wanted, - * we will provide the particular "transfer handle" in that struct - * and that should/could/would be used in subsequent - * curl_easy_getinfo() calls (or similar). The point being that we - * must never expose complex structs to applications, as then we'll - * undoubtably get backwards compatibility problems in the future. + * The 'CURLMsg' struct is meant to be simple and only contain basic + * information. If more involved information is wanted, we will + * provide the particular "transfer handle" in that struct and that + * should/could/would be used in subsequent curl_easy_getinfo() calls + * (or similar). The point being that we must never expose complex + * structs to applications, as then we will undoubtably get backwards + * compatibility problems in the future. * * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out * of structs. It also writes the number of messages left in the @@ -268,7 +264,7 @@ CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle, * Name: curl_multi_strerror() * * Desc: The curl_multi_strerror function may be used to turn a CURLMcode - * value into the equivalent human readable error string. This is + * value into the equivalent human readable error string. This is * useful for printing meaningful error messages. * * Returns: A pointer to a null-terminated error message. @@ -282,7 +278,7 @@ CURL_EXTERN const char *curl_multi_strerror(CURLMcode); * Desc: An alternative version of curl_multi_perform() that allows the * application to pass in one of the file descriptors that have been * detected to have "action" on them and let libcurl perform. - * See man page for details. + * See manpage for details. */ #define CURL_POLL_NONE 0 #define CURL_POLL_IN 1 @@ -426,6 +422,17 @@ CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle, CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle, curl_socket_t sockfd, void *sockp); +/* + * Name: curl_multi_get_handles() + * + * Desc: Returns an allocated array holding all handles currently added to + * the multi handle. Marks the final entry with a NULL pointer. If + * there is no easy handle added to the multi handle, this function + * returns an array with the first entry as a NULL pointer. + * + * Returns: NULL on failure, otherwise a CURL **array pointer + */ +CURL_EXTERN CURL **curl_multi_get_handles(CURLM *multi_handle); /* * Name: curl_push_callback @@ -453,6 +460,20 @@ typedef int (*curl_push_callback)(CURL *parent, struct curl_pushheaders *headers, void *userp); +/* + * Name: curl_multi_waitfds() + * + * Desc: Ask curl for fds for polling. The app can use these to poll on. + * We want curl_multi_perform() called as soon as one of them are + * ready. Passing zero size allows to get just a number of fds. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count); + #ifdef __cplusplus } /* end of extern "C" */ #endif diff --git a/Utilities/cmcurl/include/curl/system.h b/Utilities/cmcurl/include/curl/system.h index def7739242d..f1c2719cfe5 100644 --- a/Utilities/cmcurl/include/curl/system.h +++ b/Utilities/cmcurl/include/curl/system.h @@ -31,83 +31,42 @@ * changed. * * In order to differentiate between platforms/compilers/architectures use - * only compiler built in predefined preprocessor symbols. + * only compiler built-in predefined preprocessor symbols. * * curl_off_t * ---------- * - * For any given platform/compiler curl_off_t must be typedef'ed to a 64-bit + * For any given platform/compiler curl_off_t MUST be typedef'ed to a 64-bit * wide signed integral data type. The width of this data type must remain * constant and independent of any possible large file support settings. * - * As an exception to the above, curl_off_t shall be typedef'ed to a 32-bit - * wide signed integral data type if there is no 64-bit type. - * * As a general rule, curl_off_t shall not be mapped to off_t. This rule shall * only be violated if off_t is the only 64-bit data type available and the * size of off_t is independent of large file support settings. Keep your - * build on the safe side avoiding an off_t gating. If you have a 64-bit + * build on the safe side avoiding an off_t gating. If you have a 64-bit * off_t then take for sure that another 64-bit data type exists, dig deeper * and you will find it. * */ -#if defined(__DJGPP__) || defined(__GO32__) -# if defined(__DJGPP__) && (__DJGPP__ > 1) -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# else -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T int - -#elif defined(__SALFORDC__) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL +#ifdef __DJGPP__ +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL # define CURL_TYPEOF_CURL_SOCKLEN_T int #elif defined(__BORLANDC__) -# if (__BORLANDC__ < 0x520) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# else -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T int - -#elif defined(__TURBOC__) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 # define CURL_TYPEOF_CURL_SOCKLEN_T int #elif defined(__POCC__) -# if (__POCC__ < 280) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# elif defined(_MSC_VER) +# if defined(_MSC_VER) # define CURL_TYPEOF_CURL_OFF_T __int64 # define CURL_FORMAT_CURL_OFF_T "I64d" # define CURL_FORMAT_CURL_OFF_TU "I64u" @@ -141,74 +100,66 @@ # define CURL_TYPEOF_CURL_SOCKLEN_T int # endif -#elif defined(__SYMBIAN32__) -# if defined(__EABI__) /* Treat all ARM compilers equally */ -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(__CW32__) -# pragma longlong on +#elif defined(macintosh) +# include +# if TYPE_LONGLONG # define CURL_TYPEOF_CURL_OFF_T long long # define CURL_FORMAT_CURL_OFF_T "lld" # define CURL_FORMAT_CURL_OFF_TU "llu" # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(__VC32__) -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL # endif # define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int -#elif defined(macintosh) -# include -# if TYPE_LONGLONG +#elif defined(__TANDEM) +# if !defined(__LP64) # define CURL_TYPEOF_CURL_OFF_T long long # define CURL_FORMAT_CURL_OFF_T "lld" # define CURL_FORMAT_CURL_OFF_TU "llu" # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T int # else # define CURL_TYPEOF_CURL_OFF_T long # define CURL_FORMAT_CURL_OFF_T "ld" # define CURL_FORMAT_CURL_OFF_TU "lu" # define CURL_SUFFIX_CURL_OFF_T L # define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int # endif -# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int - -#elif defined(__TANDEM) -# if ! defined(__LP64) - /* Required for 32-bit NonStop builds only. */ -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# endif -#elif defined(_WIN32_WCE) -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# define CURL_TYPEOF_CURL_SOCKLEN_T int +#elif defined(UNDER_CE) +# if defined(__MINGW32CE__) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# else +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# endif #elif defined(__MINGW32__) +# include # define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_CURL_OFF_T PRId64 +# define CURL_FORMAT_CURL_OFF_TU PRIu64 # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_TYPEOF_CURL_SOCKLEN_T int # define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_WS2TCPIP_H 1 #elif defined(__VMS) # if defined(__VAX) @@ -237,52 +188,32 @@ # define CURL_PULL_SYS_SOCKET_H 1 #elif defined(__MVS__) -# if defined(__IBMC__) || defined(__IBMCPP__) -# if defined(_ILP32) -# elif defined(_LP64) -# endif -# if defined(_LONG_LONG) -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(_LP64) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# else -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t -# define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_SYS_SOCKET_H 1 +# if defined(_LONG_LONG) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# else /* _LP64 and default */ +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL # endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 #elif defined(__370__) # if defined(__IBMC__) || defined(__IBMCPP__) -# if defined(_ILP32) -# elif defined(_LP64) -# endif # if defined(_LONG_LONG) # define CURL_TYPEOF_CURL_OFF_T long long # define CURL_FORMAT_CURL_OFF_T "lld" # define CURL_FORMAT_CURL_OFF_TU "llu" # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(_LP64) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# else +# else /* _LP64 and default */ # define CURL_TYPEOF_CURL_OFF_T long # define CURL_FORMAT_CURL_OFF_T "ld" # define CURL_FORMAT_CURL_OFF_TU "lu" @@ -352,24 +283,40 @@ # define CURL_PULL_SYS_TYPES_H 1 # define CURL_PULL_SYS_SOCKET_H 1 +#elif defined(__hpux) /* HP aCC compiler */ +# if !defined(_LP64) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + /* ===================================== */ /* KEEP MSVC THE PENULTIMATE ENTRY */ /* ===================================== */ #elif defined(_MSC_VER) -# if (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) -# define CURL_TYPEOF_CURL_OFF_T __int64 +# if (_MSC_VER >= 1800) +# include +# define CURL_FORMAT_CURL_OFF_T PRId64 +# define CURL_FORMAT_CURL_OFF_TU PRIu64 +# else # define CURL_FORMAT_CURL_OFF_T "I64d" # define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# else -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL # endif +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 # define CURL_TYPEOF_CURL_SOCKLEN_T int /* ===================================== */ @@ -389,6 +336,8 @@ # define CURL_FORMAT_CURL_OFF_TU "llu" # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_POPCOUNT64(x) __builtin_popcountll(x) +# define CURL_CTZ64(x) __builtin_ctzll(x) # elif defined(__LP64__) || \ defined(__x86_64__) || defined(__ppc64__) || defined(__sparc64__) || \ defined(__e2k__) || \ @@ -399,19 +348,21 @@ # define CURL_FORMAT_CURL_OFF_TU "lu" # define CURL_SUFFIX_CURL_OFF_T L # define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_POPCOUNT64(x) __builtin_popcountl(x) +# define CURL_CTZ64(x) __builtin_ctzl(x) # endif # define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t # define CURL_PULL_SYS_TYPES_H 1 # define CURL_PULL_SYS_SOCKET_H 1 #else -/* generic "safe guess" on old 32 bit style */ -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# define CURL_TYPEOF_CURL_SOCKLEN_T int +/* generic "safe guess" on old 32-bit style */ +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int #endif #ifdef _AIX @@ -419,15 +370,6 @@ #define CURL_PULL_SYS_POLL_H #endif - -/* CURL_PULL_WS2TCPIP_H is defined above when inclusion of header file */ -/* ws2tcpip.h is required here to properly make type definitions below. */ -#ifdef CURL_PULL_WS2TCPIP_H -# include -# include -# include -#endif - /* CURL_PULL_SYS_TYPES_H is defined above when inclusion of header file */ /* sys/types.h is required here to properly make type definitions below. */ #ifdef CURL_PULL_SYS_TYPES_H @@ -474,7 +416,7 @@ #if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) || \ defined(__HP_aCC) || defined(__BORLANDC__) || defined(__LCC__) || \ - defined(__POCC__) || defined(__SALFORDC__) || defined(__HIGHC__) || \ + defined(__POCC__) || defined(__HIGHC__) || \ defined(__ILEC400__) /* This compiler is believed to have an ISO compatible preprocessor */ #define CURL_ISOCPP diff --git a/Utilities/cmcurl/include/curl/typecheck-gcc.h b/Utilities/cmcurl/include/curl/typecheck-gcc.h index bc8d7a78ce9..ca0c0ef9d69 100644 --- a/Utilities/cmcurl/include/curl/typecheck-gcc.h +++ b/Utilities/cmcurl/include/curl/typecheck-gcc.h @@ -34,121 +34,163 @@ * _curl_easy_setopt_err_sometype below * * NOTE: We use two nested 'if' statements here instead of the && operator, in - * order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x + * order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x * when compiling with -Wlogical-op. * - * To add an option that uses the same type as an existing option, you'll just - * need to extend the appropriate _curl_*_option macro + * To add an option that uses the same type as an existing option, you will + * just need to extend the appropriate _curl_*_option macro */ + #define curl_easy_setopt(handle, option, value) \ __extension__({ \ - CURLoption _curl_opt = (option); \ - if(__builtin_constant_p(_curl_opt)) { \ + if(__builtin_constant_p(option)) { \ CURL_IGNORE_DEPRECATION( \ - if(curlcheck_long_option(_curl_opt)) \ + if(curlcheck_long_option(option)) \ if(!curlcheck_long(value)) \ _curl_easy_setopt_err_long(); \ - if(curlcheck_off_t_option(_curl_opt)) \ + if(curlcheck_off_t_option(option)) \ if(!curlcheck_off_t(value)) \ _curl_easy_setopt_err_curl_off_t(); \ - if(curlcheck_string_option(_curl_opt)) \ + if(curlcheck_string_option(option)) \ if(!curlcheck_string(value)) \ _curl_easy_setopt_err_string(); \ - if(curlcheck_write_cb_option(_curl_opt)) \ + if((option) == CURLOPT_PRIVATE) { } \ + if(curlcheck_write_cb_option(option)) \ if(!curlcheck_write_cb(value)) \ _curl_easy_setopt_err_write_callback(); \ - if((_curl_opt) == CURLOPT_RESOLVER_START_FUNCTION) \ + if(curlcheck_curl_option(option)) \ + if(!curlcheck_curl(value)) \ + _curl_easy_setopt_err_curl(); \ + if((option) == CURLOPT_RESOLVER_START_FUNCTION) \ if(!curlcheck_resolver_start_callback(value)) \ _curl_easy_setopt_err_resolver_start_callback(); \ - if((_curl_opt) == CURLOPT_READFUNCTION) \ + if((option) == CURLOPT_READFUNCTION) \ if(!curlcheck_read_cb(value)) \ _curl_easy_setopt_err_read_cb(); \ - if((_curl_opt) == CURLOPT_IOCTLFUNCTION) \ + if((option) == CURLOPT_IOCTLFUNCTION) \ if(!curlcheck_ioctl_cb(value)) \ _curl_easy_setopt_err_ioctl_cb(); \ - if((_curl_opt) == CURLOPT_SOCKOPTFUNCTION) \ + if((option) == CURLOPT_SOCKOPTFUNCTION) \ if(!curlcheck_sockopt_cb(value)) \ _curl_easy_setopt_err_sockopt_cb(); \ - if((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION) \ + if((option) == CURLOPT_OPENSOCKETFUNCTION) \ if(!curlcheck_opensocket_cb(value)) \ _curl_easy_setopt_err_opensocket_cb(); \ - if((_curl_opt) == CURLOPT_PROGRESSFUNCTION) \ + if((option) == CURLOPT_PROGRESSFUNCTION) \ if(!curlcheck_progress_cb(value)) \ _curl_easy_setopt_err_progress_cb(); \ - if((_curl_opt) == CURLOPT_DEBUGFUNCTION) \ + if((option) == CURLOPT_XFERINFOFUNCTION) \ + if(!curlcheck_xferinfo_cb(value)) \ + _curl_easy_setopt_err_xferinfo_cb(); \ + if((option) == CURLOPT_DEBUGFUNCTION) \ if(!curlcheck_debug_cb(value)) \ _curl_easy_setopt_err_debug_cb(); \ - if((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION) \ + if((option) == CURLOPT_SSL_CTX_FUNCTION) \ if(!curlcheck_ssl_ctx_cb(value)) \ _curl_easy_setopt_err_ssl_ctx_cb(); \ - if(curlcheck_conv_cb_option(_curl_opt)) \ + if(curlcheck_conv_cb_option(option)) \ if(!curlcheck_conv_cb(value)) \ _curl_easy_setopt_err_conv_cb(); \ - if((_curl_opt) == CURLOPT_SEEKFUNCTION) \ + if((option) == CURLOPT_SEEKFUNCTION) \ if(!curlcheck_seek_cb(value)) \ _curl_easy_setopt_err_seek_cb(); \ - if(curlcheck_cb_data_option(_curl_opt)) \ + if((option) == CURLOPT_CHUNK_BGN_FUNCTION) \ + if(!curlcheck_chunk_bgn_cb(value)) \ + _curl_easy_setopt_err_chunk_bgn_cb(); \ + if((option) == CURLOPT_CHUNK_END_FUNCTION) \ + if(!curlcheck_chunk_end_cb(value)) \ + _curl_easy_setopt_err_chunk_end_cb(); \ + if((option) == CURLOPT_CLOSESOCKETFUNCTION) \ + if(!curlcheck_close_socket_cb(value)) \ + _curl_easy_setopt_err_close_socket_cb(); \ + if((option) == CURLOPT_FNMATCH_FUNCTION) \ + if(!curlcheck_fnmatch_cb(value)) \ + _curl_easy_setopt_err_fnmatch_cb(); \ + if((option) == CURLOPT_HSTSREADFUNCTION) \ + if(!curlcheck_hstsread_cb(value)) \ + _curl_easy_setopt_err_hstsread_cb(); \ + if((option) == CURLOPT_HSTSWRITEFUNCTION) \ + if(!curlcheck_hstswrite_cb(value)) \ + _curl_easy_setopt_err_hstswrite_cb(); \ + if((option) == CURLOPT_SSH_HOSTKEYFUNCTION) \ + if(!curlcheck_ssh_hostkey_cb(value)) \ + _curl_easy_setopt_err_ssh_hostkey_cb(); \ + if((option) == CURLOPT_SSH_KEYFUNCTION) \ + if(!curlcheck_ssh_key_cb(value)) \ + _curl_easy_setopt_err_ssh_key_cb(); \ + if((option) == CURLOPT_INTERLEAVEFUNCTION) \ + if(!curlcheck_interleave_cb(value)) \ + _curl_easy_setopt_err_interleave_cb(); \ + if((option) == CURLOPT_PREREQFUNCTION) \ + if(!curlcheck_prereq_cb(value)) \ + _curl_easy_setopt_err_prereq_cb(); \ + if((option) == CURLOPT_TRAILERFUNCTION) \ + if(!curlcheck_trailer_cb(value)) \ + _curl_easy_setopt_err_trailer_cb(); \ + if(curlcheck_cb_data_option(option)) \ if(!curlcheck_cb_data(value)) \ _curl_easy_setopt_err_cb_data(); \ - if((_curl_opt) == CURLOPT_ERRORBUFFER) \ + if((option) == CURLOPT_ERRORBUFFER) \ if(!curlcheck_error_buffer(value)) \ _curl_easy_setopt_err_error_buffer(); \ - if((_curl_opt) == CURLOPT_STDERR) \ + if((option) == CURLOPT_CURLU) \ + if(!curlcheck_ptr((value), CURLU)) \ + _curl_easy_setopt_err_curlu(); \ + if((option) == CURLOPT_STDERR) \ if(!curlcheck_FILE(value)) \ _curl_easy_setopt_err_FILE(); \ - if(curlcheck_postfields_option(_curl_opt)) \ + if(curlcheck_postfields_option(option)) \ if(!curlcheck_postfields(value)) \ _curl_easy_setopt_err_postfields(); \ - if((_curl_opt) == CURLOPT_HTTPPOST) \ + if((option) == CURLOPT_HTTPPOST) \ if(!curlcheck_arr((value), struct curl_httppost)) \ _curl_easy_setopt_err_curl_httpost(); \ - if((_curl_opt) == CURLOPT_MIMEPOST) \ + if((option) == CURLOPT_MIMEPOST) \ if(!curlcheck_ptr((value), curl_mime)) \ _curl_easy_setopt_err_curl_mimepost(); \ - if(curlcheck_slist_option(_curl_opt)) \ + if(curlcheck_slist_option(option)) \ if(!curlcheck_arr((value), struct curl_slist)) \ _curl_easy_setopt_err_curl_slist(); \ - if((_curl_opt) == CURLOPT_SHARE) \ + if((option) == CURLOPT_SHARE) \ if(!curlcheck_ptr((value), CURLSH)) \ _curl_easy_setopt_err_CURLSH(); \ - ) \ - } \ - curl_easy_setopt(handle, _curl_opt, value); \ + ) \ + } \ + curl_easy_setopt(handle, option, value); \ }) /* wraps curl_easy_getinfo() with typechecking */ #define curl_easy_getinfo(handle, info, arg) \ __extension__({ \ - CURLINFO _curl_info = (info); \ - if(__builtin_constant_p(_curl_info)) { \ + if(__builtin_constant_p(info)) { \ CURL_IGNORE_DEPRECATION( \ - if(curlcheck_string_info(_curl_info)) \ + if(curlcheck_string_info(info)) \ if(!curlcheck_arr((arg), char *)) \ _curl_easy_getinfo_err_string(); \ - if(curlcheck_long_info(_curl_info)) \ + if(curlcheck_long_info(info)) \ if(!curlcheck_arr((arg), long)) \ _curl_easy_getinfo_err_long(); \ - if(curlcheck_double_info(_curl_info)) \ + if(curlcheck_double_info(info)) \ if(!curlcheck_arr((arg), double)) \ _curl_easy_getinfo_err_double(); \ - if(curlcheck_slist_info(_curl_info)) \ + if(curlcheck_slist_info(info)) \ if(!curlcheck_arr((arg), struct curl_slist *)) \ _curl_easy_getinfo_err_curl_slist(); \ - if(curlcheck_tlssessioninfo_info(_curl_info)) \ + if(curlcheck_tlssessioninfo_info(info)) \ if(!curlcheck_arr((arg), struct curl_tlssessioninfo *)) \ - _curl_easy_getinfo_err_curl_tlssesssioninfo(); \ - if(curlcheck_certinfo_info(_curl_info)) \ + _curl_easy_getinfo_err_curl_tlssessioninfo(); \ + if(curlcheck_certinfo_info(info)) \ if(!curlcheck_arr((arg), struct curl_certinfo *)) \ _curl_easy_getinfo_err_curl_certinfo(); \ - if(curlcheck_socket_info(_curl_info)) \ + if(curlcheck_socket_info(info)) \ if(!curlcheck_arr((arg), curl_socket_t)) \ _curl_easy_getinfo_err_curl_socket(); \ - if(curlcheck_off_t_info(_curl_info)) \ + if(curlcheck_off_t_info(info)) \ if(!curlcheck_arr((arg), curl_off_t)) \ _curl_easy_getinfo_err_curl_off_t(); \ - ) \ - } \ - curl_easy_getinfo(handle, _curl_info, arg); \ + ) \ + } \ + curl_easy_getinfo(handle, info, arg); \ }) /* @@ -157,7 +199,6 @@ #define curl_share_setopt(share,opt,param) curl_share_setopt(share,opt,param) #define curl_multi_setopt(handle,opt,param) curl_multi_setopt(handle,opt,param) - /* the actual warnings, triggered by calling the _curl_easy_setopt_err* * functions */ @@ -168,185 +209,212 @@ id(void) { __asm__(""); } CURLWARNING(_curl_easy_setopt_err_long, - "curl_easy_setopt expects a long argument for this option") + "curl_easy_setopt expects a long argument") CURLWARNING(_curl_easy_setopt_err_curl_off_t, - "curl_easy_setopt expects a curl_off_t argument for this option") + "curl_easy_setopt expects a curl_off_t argument") CURLWARNING(_curl_easy_setopt_err_string, - "curl_easy_setopt expects a " - "string ('char *' or char[]) argument for this option" - ) + "curl_easy_setopt expects a " + "string ('char *' or char[]) argument") CURLWARNING(_curl_easy_setopt_err_write_callback, - "curl_easy_setopt expects a curl_write_callback argument for this option") + "curl_easy_setopt expects a curl_write_callback argument") CURLWARNING(_curl_easy_setopt_err_resolver_start_callback, - "curl_easy_setopt expects a " - "curl_resolver_start_callback argument for this option" - ) + "curl_easy_setopt expects a " + "curl_resolver_start_callback argument") CURLWARNING(_curl_easy_setopt_err_read_cb, - "curl_easy_setopt expects a curl_read_callback argument for this option") + "curl_easy_setopt expects a curl_read_callback argument") CURLWARNING(_curl_easy_setopt_err_ioctl_cb, - "curl_easy_setopt expects a curl_ioctl_callback argument for this option") + "curl_easy_setopt expects a curl_ioctl_callback argument") CURLWARNING(_curl_easy_setopt_err_sockopt_cb, - "curl_easy_setopt expects a curl_sockopt_callback argument for this option") + "curl_easy_setopt expects a curl_sockopt_callback argument") CURLWARNING(_curl_easy_setopt_err_opensocket_cb, - "curl_easy_setopt expects a " - "curl_opensocket_callback argument for this option" - ) + "curl_easy_setopt expects a " + "curl_opensocket_callback argument") CURLWARNING(_curl_easy_setopt_err_progress_cb, - "curl_easy_setopt expects a curl_progress_callback argument for this option") + "curl_easy_setopt expects a curl_progress_callback argument") +CURLWARNING(_curl_easy_setopt_err_xferinfo_cb, + "curl_easy_setopt expects a curl_xferinfo_callback argument") CURLWARNING(_curl_easy_setopt_err_debug_cb, - "curl_easy_setopt expects a curl_debug_callback argument for this option") + "curl_easy_setopt expects a curl_debug_callback argument") CURLWARNING(_curl_easy_setopt_err_ssl_ctx_cb, - "curl_easy_setopt expects a curl_ssl_ctx_callback argument for this option") + "curl_easy_setopt expects a curl_ssl_ctx_callback argument") CURLWARNING(_curl_easy_setopt_err_conv_cb, - "curl_easy_setopt expects a curl_conv_callback argument for this option") + "curl_easy_setopt expects a curl_conv_callback argument") CURLWARNING(_curl_easy_setopt_err_seek_cb, - "curl_easy_setopt expects a curl_seek_callback argument for this option") + "curl_easy_setopt expects a curl_seek_callback argument") CURLWARNING(_curl_easy_setopt_err_cb_data, - "curl_easy_setopt expects a " - "private data pointer as argument for this option") + "curl_easy_setopt expects a " + "private data pointer as argument") +CURLWARNING(_curl_easy_setopt_err_chunk_bgn_cb, + "curl_easy_setopt expects a curl_chunk_bgn_callback argument") +CURLWARNING(_curl_easy_setopt_err_chunk_end_cb, + "curl_easy_setopt expects a curl_chunk_end_callback argument") +CURLWARNING(_curl_easy_setopt_err_close_socket_cb, + "curl_easy_setopt expects a curl_closesocket_callback argument") +CURLWARNING(_curl_easy_setopt_err_fnmatch_cb, + "curl_easy_setopt expects a curl_fnmatch_callback argument") +CURLWARNING(_curl_easy_setopt_err_hstsread_cb, + "curl_easy_setopt expects a curl_hstsread_callback argument") +CURLWARNING(_curl_easy_setopt_err_hstswrite_cb, + "curl_easy_setopt expects a curl_hstswrite_callback argument") +CURLWARNING(_curl_easy_setopt_err_ssh_key_cb, + "curl_easy_setopt expects a curl_sshkeycallback argument") +CURLWARNING(_curl_easy_setopt_err_ssh_hostkey_cb, + "curl_easy_setopt expects a curl_sshhostkeycallback argument") +CURLWARNING(_curl_easy_setopt_err_interleave_cb, + "curl_easy_setopt expects a curl_interleave_callback argument") +CURLWARNING(_curl_easy_setopt_err_prereq_cb, + "curl_easy_setopt expects a curl_prereq_callback argument") +CURLWARNING(_curl_easy_setopt_err_trailer_cb, + "curl_easy_setopt expects a curl_trailerfunc_ok argument") CURLWARNING(_curl_easy_setopt_err_error_buffer, - "curl_easy_setopt expects a " - "char buffer of CURL_ERROR_SIZE as argument for this option") + "curl_easy_setopt expects a " + "char buffer of CURL_ERROR_SIZE as argument") +CURLWARNING(_curl_easy_setopt_err_curlu, + "curl_easy_setopt expects a 'CURLU *' argument") +CURLWARNING(_curl_easy_setopt_err_curl, + "curl_easy_setopt expects a 'CURL *' argument") CURLWARNING(_curl_easy_setopt_err_FILE, - "curl_easy_setopt expects a 'FILE *' argument for this option") + "curl_easy_setopt expects a 'FILE *' argument") CURLWARNING(_curl_easy_setopt_err_postfields, - "curl_easy_setopt expects a 'void *' or 'char *' argument for this option") + "curl_easy_setopt expects a 'void *' or 'char *' argument") CURLWARNING(_curl_easy_setopt_err_curl_httpost, - "curl_easy_setopt expects a 'struct curl_httppost *' " - "argument for this option") + "curl_easy_setopt expects a 'struct curl_httppost *' " + "argument") CURLWARNING(_curl_easy_setopt_err_curl_mimepost, - "curl_easy_setopt expects a 'curl_mime *' " - "argument for this option") + "curl_easy_setopt expects a 'curl_mime *' " + "argument") CURLWARNING(_curl_easy_setopt_err_curl_slist, - "curl_easy_setopt expects a 'struct curl_slist *' argument for this option") + "curl_easy_setopt expects a 'struct curl_slist *' argument") CURLWARNING(_curl_easy_setopt_err_CURLSH, - "curl_easy_setopt expects a CURLSH* argument for this option") - + "curl_easy_setopt expects a CURLSH* argument") CURLWARNING(_curl_easy_getinfo_err_string, - "curl_easy_getinfo expects a pointer to 'char *' for this info") + "curl_easy_getinfo expects a pointer to 'char *'") CURLWARNING(_curl_easy_getinfo_err_long, - "curl_easy_getinfo expects a pointer to long for this info") + "curl_easy_getinfo expects a pointer to long") CURLWARNING(_curl_easy_getinfo_err_double, - "curl_easy_getinfo expects a pointer to double for this info") + "curl_easy_getinfo expects a pointer to double") CURLWARNING(_curl_easy_getinfo_err_curl_slist, - "curl_easy_getinfo expects a pointer to 'struct curl_slist *' for this info") -CURLWARNING(_curl_easy_getinfo_err_curl_tlssesssioninfo, - "curl_easy_getinfo expects a pointer to " - "'struct curl_tlssessioninfo *' for this info") + "curl_easy_getinfo expects a pointer to 'struct curl_slist *'") +CURLWARNING(_curl_easy_getinfo_err_curl_tlssessioninfo, + "curl_easy_getinfo expects a pointer to " + "'struct curl_tlssessioninfo *'") CURLWARNING(_curl_easy_getinfo_err_curl_certinfo, - "curl_easy_getinfo expects a pointer to " - "'struct curl_certinfo *' for this info") + "curl_easy_getinfo expects a pointer to " + "'struct curl_certinfo *'") CURLWARNING(_curl_easy_getinfo_err_curl_socket, - "curl_easy_getinfo expects a pointer to curl_socket_t for this info") + "curl_easy_getinfo expects a pointer to curl_socket_t") CURLWARNING(_curl_easy_getinfo_err_curl_off_t, - "curl_easy_getinfo expects a pointer to curl_off_t for this info") + "curl_easy_getinfo expects a pointer to curl_off_t") /* groups of curl_easy_setops options that take the same type of argument */ -/* To add a new option to one of the groups, just add - * (option) == CURLOPT_SOMETHING - * to the or-expression. If the option takes a long or curl_off_t, you don't - * have to do anything - */ - /* evaluates to true if option takes a long argument */ #define curlcheck_long_option(option) \ (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) -#define curlcheck_off_t_option(option) \ +#define curlcheck_off_t_option(option) \ (((option) > CURLOPTTYPE_OFF_T) && ((option) < CURLOPTTYPE_BLOB)) +/* option takes a CURL * argument */ +#define curlcheck_curl_option(option) \ + ((option) == CURLOPT_STREAM_DEPENDS || \ + (option) == CURLOPT_STREAM_DEPENDS_E || \ + 0) + /* evaluates to true if option takes a char* argument */ -#define curlcheck_string_option(option) \ - ((option) == CURLOPT_ABSTRACT_UNIX_SOCKET || \ - (option) == CURLOPT_ACCEPT_ENCODING || \ - (option) == CURLOPT_ALTSVC || \ - (option) == CURLOPT_CAINFO || \ - (option) == CURLOPT_CAPATH || \ - (option) == CURLOPT_COOKIE || \ - (option) == CURLOPT_COOKIEFILE || \ - (option) == CURLOPT_COOKIEJAR || \ - (option) == CURLOPT_COOKIELIST || \ - (option) == CURLOPT_CRLFILE || \ - (option) == CURLOPT_CUSTOMREQUEST || \ - (option) == CURLOPT_DEFAULT_PROTOCOL || \ - (option) == CURLOPT_DNS_INTERFACE || \ - (option) == CURLOPT_DNS_LOCAL_IP4 || \ - (option) == CURLOPT_DNS_LOCAL_IP6 || \ - (option) == CURLOPT_DNS_SERVERS || \ - (option) == CURLOPT_DOH_URL || \ - (option) == CURLOPT_EGDSOCKET || \ - (option) == CURLOPT_FTP_ACCOUNT || \ - (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \ - (option) == CURLOPT_FTPPORT || \ - (option) == CURLOPT_HSTS || \ - (option) == CURLOPT_INTERFACE || \ - (option) == CURLOPT_ISSUERCERT || \ - (option) == CURLOPT_KEYPASSWD || \ - (option) == CURLOPT_KRBLEVEL || \ - (option) == CURLOPT_LOGIN_OPTIONS || \ - (option) == CURLOPT_MAIL_AUTH || \ - (option) == CURLOPT_MAIL_FROM || \ - (option) == CURLOPT_NETRC_FILE || \ - (option) == CURLOPT_NOPROXY || \ - (option) == CURLOPT_PASSWORD || \ - (option) == CURLOPT_PINNEDPUBLICKEY || \ - (option) == CURLOPT_PRE_PROXY || \ - (option) == CURLOPT_PROTOCOLS_STR || \ - (option) == CURLOPT_PROXY || \ - (option) == CURLOPT_PROXY_CAINFO || \ - (option) == CURLOPT_PROXY_CAPATH || \ - (option) == CURLOPT_PROXY_CRLFILE || \ - (option) == CURLOPT_PROXY_ISSUERCERT || \ - (option) == CURLOPT_PROXY_KEYPASSWD || \ - (option) == CURLOPT_PROXY_PINNEDPUBLICKEY || \ - (option) == CURLOPT_PROXY_SERVICE_NAME || \ - (option) == CURLOPT_PROXY_SSL_CIPHER_LIST || \ - (option) == CURLOPT_PROXY_SSLCERT || \ - (option) == CURLOPT_PROXY_SSLCERTTYPE || \ - (option) == CURLOPT_PROXY_SSLKEY || \ - (option) == CURLOPT_PROXY_SSLKEYTYPE || \ - (option) == CURLOPT_PROXY_TLS13_CIPHERS || \ - (option) == CURLOPT_PROXY_TLSAUTH_PASSWORD || \ - (option) == CURLOPT_PROXY_TLSAUTH_TYPE || \ - (option) == CURLOPT_PROXY_TLSAUTH_USERNAME || \ - (option) == CURLOPT_PROXYPASSWORD || \ - (option) == CURLOPT_PROXYUSERNAME || \ - (option) == CURLOPT_PROXYUSERPWD || \ - (option) == CURLOPT_RANDOM_FILE || \ - (option) == CURLOPT_RANGE || \ - (option) == CURLOPT_REDIR_PROTOCOLS_STR || \ - (option) == CURLOPT_REFERER || \ - (option) == CURLOPT_REQUEST_TARGET || \ - (option) == CURLOPT_RTSP_SESSION_ID || \ - (option) == CURLOPT_RTSP_STREAM_URI || \ - (option) == CURLOPT_RTSP_TRANSPORT || \ - (option) == CURLOPT_SASL_AUTHZID || \ - (option) == CURLOPT_SERVICE_NAME || \ - (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE || \ - (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 || \ - (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 || \ - (option) == CURLOPT_SSH_KNOWNHOSTS || \ - (option) == CURLOPT_SSH_PRIVATE_KEYFILE || \ - (option) == CURLOPT_SSH_PUBLIC_KEYFILE || \ - (option) == CURLOPT_SSLCERT || \ - (option) == CURLOPT_SSLCERTTYPE || \ - (option) == CURLOPT_SSLENGINE || \ - (option) == CURLOPT_SSLKEY || \ - (option) == CURLOPT_SSLKEYTYPE || \ - (option) == CURLOPT_SSL_CIPHER_LIST || \ - (option) == CURLOPT_TLS13_CIPHERS || \ - (option) == CURLOPT_TLSAUTH_PASSWORD || \ - (option) == CURLOPT_TLSAUTH_TYPE || \ - (option) == CURLOPT_TLSAUTH_USERNAME || \ - (option) == CURLOPT_UNIX_SOCKET_PATH || \ - (option) == CURLOPT_URL || \ - (option) == CURLOPT_USERAGENT || \ - (option) == CURLOPT_USERNAME || \ - (option) == CURLOPT_AWS_SIGV4 || \ - (option) == CURLOPT_USERPWD || \ - (option) == CURLOPT_XOAUTH2_BEARER || \ - (option) == CURLOPT_SSL_EC_CURVES || \ +#define curlcheck_string_option(option) \ + ((option) == CURLOPT_ABSTRACT_UNIX_SOCKET || \ + (option) == CURLOPT_ACCEPT_ENCODING || \ + (option) == CURLOPT_ALTSVC || \ + (option) == CURLOPT_CAINFO || \ + (option) == CURLOPT_CAPATH || \ + (option) == CURLOPT_COOKIE || \ + (option) == CURLOPT_COOKIEFILE || \ + (option) == CURLOPT_COOKIEJAR || \ + (option) == CURLOPT_COOKIELIST || \ + (option) == CURLOPT_CRLFILE || \ + (option) == CURLOPT_CUSTOMREQUEST || \ + (option) == CURLOPT_DEFAULT_PROTOCOL || \ + (option) == CURLOPT_DNS_INTERFACE || \ + (option) == CURLOPT_DNS_LOCAL_IP4 || \ + (option) == CURLOPT_DNS_LOCAL_IP6 || \ + (option) == CURLOPT_DNS_SERVERS || \ + (option) == CURLOPT_DOH_URL || \ + (option) == CURLOPT_ECH || \ + (option) == CURLOPT_EGDSOCKET || \ + (option) == CURLOPT_FTP_ACCOUNT || \ + (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \ + (option) == CURLOPT_FTPPORT || \ + (option) == CURLOPT_HAPROXY_CLIENT_IP || \ + (option) == CURLOPT_HSTS || \ + (option) == CURLOPT_INTERFACE || \ + (option) == CURLOPT_ISSUERCERT || \ + (option) == CURLOPT_KEYPASSWD || \ + (option) == CURLOPT_KRBLEVEL || \ + (option) == CURLOPT_LOGIN_OPTIONS || \ + (option) == CURLOPT_MAIL_AUTH || \ + (option) == CURLOPT_MAIL_FROM || \ + (option) == CURLOPT_NETRC_FILE || \ + (option) == CURLOPT_NOPROXY || \ + (option) == CURLOPT_PASSWORD || \ + (option) == CURLOPT_PINNEDPUBLICKEY || \ + (option) == CURLOPT_PRE_PROXY || \ + (option) == CURLOPT_PROTOCOLS_STR || \ + (option) == CURLOPT_PROXY || \ + (option) == CURLOPT_PROXY_CAINFO || \ + (option) == CURLOPT_PROXY_CAPATH || \ + (option) == CURLOPT_PROXY_CRLFILE || \ + (option) == CURLOPT_PROXY_ISSUERCERT || \ + (option) == CURLOPT_PROXY_KEYPASSWD || \ + (option) == CURLOPT_PROXY_PINNEDPUBLICKEY || \ + (option) == CURLOPT_PROXY_SERVICE_NAME || \ + (option) == CURLOPT_PROXY_SSL_CIPHER_LIST || \ + (option) == CURLOPT_PROXY_SSLCERT || \ + (option) == CURLOPT_PROXY_SSLCERTTYPE || \ + (option) == CURLOPT_PROXY_SSLKEY || \ + (option) == CURLOPT_PROXY_SSLKEYTYPE || \ + (option) == CURLOPT_PROXY_TLS13_CIPHERS || \ + (option) == CURLOPT_PROXY_TLSAUTH_PASSWORD || \ + (option) == CURLOPT_PROXY_TLSAUTH_TYPE || \ + (option) == CURLOPT_PROXY_TLSAUTH_USERNAME || \ + (option) == CURLOPT_PROXYPASSWORD || \ + (option) == CURLOPT_PROXYUSERNAME || \ + (option) == CURLOPT_PROXYUSERPWD || \ + (option) == CURLOPT_RANDOM_FILE || \ + (option) == CURLOPT_RANGE || \ + (option) == CURLOPT_REDIR_PROTOCOLS_STR || \ + (option) == CURLOPT_REFERER || \ + (option) == CURLOPT_REQUEST_TARGET || \ + (option) == CURLOPT_RTSP_SESSION_ID || \ + (option) == CURLOPT_RTSP_STREAM_URI || \ + (option) == CURLOPT_RTSP_TRANSPORT || \ + (option) == CURLOPT_SASL_AUTHZID || \ + (option) == CURLOPT_SERVICE_NAME || \ + (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE || \ + (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 || \ + (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 || \ + (option) == CURLOPT_SSH_KNOWNHOSTS || \ + (option) == CURLOPT_SSH_PRIVATE_KEYFILE || \ + (option) == CURLOPT_SSH_PUBLIC_KEYFILE || \ + (option) == CURLOPT_SSLCERT || \ + (option) == CURLOPT_SSLCERTTYPE || \ + (option) == CURLOPT_SSLENGINE || \ + (option) == CURLOPT_SSLKEY || \ + (option) == CURLOPT_SSLKEYTYPE || \ + (option) == CURLOPT_SSL_CIPHER_LIST || \ + (option) == CURLOPT_SSL_EC_CURVES || \ + (option) == CURLOPT_SSL_SIGNATURE_ALGORITHMS || \ + (option) == CURLOPT_TLS13_CIPHERS || \ + (option) == CURLOPT_TLSAUTH_PASSWORD || \ + (option) == CURLOPT_TLSAUTH_TYPE || \ + (option) == CURLOPT_TLSAUTH_USERNAME || \ + (option) == CURLOPT_UNIX_SOCKET_PATH || \ + (option) == CURLOPT_URL || \ + (option) == CURLOPT_USERAGENT || \ + (option) == CURLOPT_USERNAME || \ + (option) == CURLOPT_AWS_SIGV4 || \ + (option) == CURLOPT_USERPWD || \ + (option) == CURLOPT_XOAUTH2_BEARER || \ 0) /* evaluates to true if option takes a curl_write_callback argument */ @@ -373,7 +441,7 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_IOCTLDATA || \ (option) == CURLOPT_OPENSOCKETDATA || \ (option) == CURLOPT_PREREQDATA || \ - (option) == CURLOPT_PROGRESSDATA || \ + (option) == CURLOPT_XFERINFODATA || \ (option) == CURLOPT_READDATA || \ (option) == CURLOPT_SEEKDATA || \ (option) == CURLOPT_SOCKOPTDATA || \ @@ -477,22 +545,36 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, curlcheck_arr((expr), signed char) || \ curlcheck_arr((expr), unsigned char)) +/* evaluates to true if expr is a CURL * */ +#define curlcheck_curl(expr) \ + (curlcheck_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), CURL *)) + + /* evaluates to true if expr is a long (no matter the signedness) * XXX: for now, int is also accepted (and therefore short and char, which * are promoted to int when passed to a variadic function) */ -#define curlcheck_long(expr) \ - (__builtin_types_compatible_p(__typeof__(expr), long) || \ - __builtin_types_compatible_p(__typeof__(expr), signed long) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned long) || \ - __builtin_types_compatible_p(__typeof__(expr), int) || \ - __builtin_types_compatible_p(__typeof__(expr), signed int) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned int) || \ - __builtin_types_compatible_p(__typeof__(expr), short) || \ - __builtin_types_compatible_p(__typeof__(expr), signed short) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned short) || \ - __builtin_types_compatible_p(__typeof__(expr), char) || \ - __builtin_types_compatible_p(__typeof__(expr), signed char) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned char)) +#define curlcheck_long(expr) \ + ( \ + ((sizeof(long) != sizeof(int)) && \ + (__builtin_types_compatible_p(__typeof__(expr), long) || \ + __builtin_types_compatible_p(__typeof__(expr), signed long) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned long))) \ + || \ + ((sizeof(long) == sizeof(int)) && \ + (__builtin_types_compatible_p(__typeof__(expr), long) || \ + __builtin_types_compatible_p(__typeof__(expr), signed long) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned long) || \ + __builtin_types_compatible_p(__typeof__(expr), int) || \ + __builtin_types_compatible_p(__typeof__(expr), signed int) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned int) || \ + __builtin_types_compatible_p(__typeof__(expr), short) || \ + __builtin_types_compatible_p(__typeof__(expr), signed short) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned short) || \ + __builtin_types_compatible_p(__typeof__(expr), char) || \ + __builtin_types_compatible_p(__typeof__(expr), signed char) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned char))) \ + ) /* evaluates to true if expr is of type curl_off_t */ #define curlcheck_off_t(expr) \ @@ -627,6 +709,11 @@ typedef int (*_curl_progress_callback1)(void *, typedef int (*_curl_progress_callback2)(const void *, double, double, double, double); +/* evaluates to true if expr is of type curl_xferinfo_callback */ +#define curlcheck_xferinfo_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_xferinfo_callback)) + /* evaluates to true if expr is of type curl_debug_callback or "similar" */ #define curlcheck_debug_cb(expr) \ (curlcheck_NULL(expr) || \ @@ -676,7 +763,7 @@ typedef CURLcode (*_curl_ssl_ctx_callback4)(CURL *, const void *, const void *); #ifdef HEADER_SSL_H /* hack: if we included OpenSSL's ssl.h, we know about SSL_CTX - * this will of course break if we're included before OpenSSL headers... + * this will of course break if we are included before OpenSSL headers... */ typedef CURLcode (*_curl_ssl_ctx_callback5)(CURL *, SSL_CTX *, void *); typedef CURLcode (*_curl_ssl_ctx_callback6)(CURL *, SSL_CTX *, const void *); @@ -712,5 +799,69 @@ typedef CURLcode (*_curl_conv_callback4)(const void *, size_t length); typedef CURLcode (*_curl_seek_callback1)(void *, curl_off_t, int); typedef CURLcode (*_curl_seek_callback2)(const void *, curl_off_t, int); +/* evaluates to true if expr is of type curl_chunk_bgn_callback */ +#define curlcheck_chunk_bgn_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_chunk_bgn_callback) || \ + curlcheck_cb_compatible((expr), _curl_chunk_bgn_callback1) || \ + curlcheck_cb_compatible((expr), _curl_chunk_bgn_callback2)) +typedef long (*_curl_chunk_bgn_callback1)(struct curl_fileinfo *, + void *, int); +typedef long (*_curl_chunk_bgn_callback2)(void *, void *, int); + +/* evaluates to true if expr is of type curl_chunk_end_callback */ +#define curlcheck_chunk_end_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_chunk_end_callback)) + +/* evaluates to true if expr is of type curl_closesocket_callback */ +#define curlcheck_close_socket_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_closesocket_callback)) + +/* evaluates to true if expr is of type curl_fnmatch_callback */ +#define curlcheck_fnmatch_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_fnmatch_callback)) + +/* evaluates to true if expr is of type curl_hstsread_callback */ +#define curlcheck_hstsread_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_hstsread_callback)) + +/* evaluates to true if expr is of type curl_hstswrite_callback */ +#define curlcheck_hstswrite_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_hstswrite_callback)) + +/* evaluates to true if expr is of type curl_sshhostkeycallback */ +#define curlcheck_ssh_hostkey_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_sshhostkeycallback)) + +/* evaluates to true if expr is of type curl_sshkeycallback */ +#define curlcheck_ssh_key_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_sshkeycallback)) + +/* evaluates to true if expr is of type curl_interleave_callback */ +#define curlcheck_interleave_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), _curl_interleave_callback1) || \ + curlcheck_cb_compatible((expr), _curl_interleave_callback2)) +typedef size_t (*_curl_interleave_callback1)(void *p, size_t s, + size_t n, void *u); +typedef size_t (*_curl_interleave_callback2)(char *p, size_t s, + size_t n, void *u); + +/* evaluates to true if expr is of type curl_prereq_callback */ +#define curlcheck_prereq_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_prereq_callback)) + +/* evaluates to true if expr is of type curl_trailer_callback */ +#define curlcheck_trailer_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_trailer_callback)) #endif /* CURLINC_TYPECHECK_GCC_H */ diff --git a/Utilities/cmcurl/include/curl/urlapi.h b/Utilities/cmcurl/include/curl/urlapi.h index b3504b683af..b4a6e5d5670 100644 --- a/Utilities/cmcurl/include/curl/urlapi.h +++ b/Utilities/cmcurl/include/curl/urlapi.h @@ -63,6 +63,7 @@ typedef enum { CURLUE_BAD_SLASHES, /* 28 */ CURLUE_BAD_USER, /* 29 */ CURLUE_LACKS_IDN, /* 30 */ + CURLUE_TOO_LARGE, /* 31 */ CURLUE_LAST } CURLUcode; @@ -96,7 +97,12 @@ typedef enum { #define CURLU_NO_AUTHORITY (1<<10) /* Allow empty authority when the scheme is unknown. */ #define CURLU_ALLOW_SPACE (1<<11) /* Allow spaces in the URL */ -#define CURLU_PUNYCODE (1<<12) /* get the host name in pynycode */ +#define CURLU_PUNYCODE (1<<12) /* get the hostname in punycode */ +#define CURLU_PUNY2IDN (1<<13) /* punycode => IDN conversion */ +#define CURLU_GET_EMPTY (1<<14) /* allow empty queries and fragments + when extracting the URL or the + components */ +#define CURLU_NO_GUESS_SCHEME (1<<15) /* for get, do not accept a guess */ typedef struct Curl_URL CURLU; @@ -137,7 +143,7 @@ CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what, /* * curl_url_strerror() turns a CURLUcode value into the equivalent human - * readable error string. This is useful for printing meaningful error + * readable error string. This is useful for printing meaningful error * messages. */ CURL_EXTERN const char *curl_url_strerror(CURLUcode); diff --git a/Utilities/cmcurl/include/curl/websockets.h b/Utilities/cmcurl/include/curl/websockets.h index fd6a916547d..afb86b4ebcf 100644 --- a/Utilities/cmcurl/include/curl/websockets.h +++ b/Utilities/cmcurl/include/curl/websockets.h @@ -54,13 +54,13 @@ struct curl_ws_frame { */ CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, size_t *recv, - struct curl_ws_frame **metap); + const struct curl_ws_frame **metap); -/* sendflags for curl_ws_send() */ +/* flags for curl_ws_send() */ #define CURLWS_PONG (1<<6) /* - * NAME curl_easy_send() + * NAME curl_ws_send() * * DESCRIPTION * @@ -69,13 +69,14 @@ CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, */ CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, size_t buflen, size_t *sent, - curl_off_t framesize, - unsigned int sendflags); + curl_off_t fragsize, + unsigned int flags); /* bits for the CURLOPT_WS_OPTIONS bitmask: */ -#define CURLWS_RAW_MODE (1<<0) +#define CURLWS_RAW_MODE (1<<0) +#define CURLWS_NOAUTOPONG (1<<1) -CURL_EXTERN struct curl_ws_frame *curl_ws_meta(CURL *curl); +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *curl); #ifdef __cplusplus } diff --git a/Utilities/cmcurl/lib/CMakeLists.txt b/Utilities/cmcurl/lib/CMakeLists.txt index f1d0f768201..6a0798de70b 100644 --- a/Utilities/cmcurl/lib/CMakeLists.txt +++ b/Utilities/cmcurl/lib/CMakeLists.txt @@ -21,42 +21,39 @@ # SPDX-License-Identifier: curl # ########################################################################### -set(LIB_NAME cmcurl) -set(LIBCURL_OUTPUT_NAME cmcurl) -add_definitions(-DBUILDING_LIBCURL) -if(BUILD_SHARED_LIBS) - set(CURL_STATICLIB NO) -else() - set(CURL_STATICLIB YES) -endif() +set(LIBCURL_OUTPUT_NAME "${LIB_NAME}" CACHE STRING "Basename of the curl library") -# Use: -# * CURL_STATICLIB -configure_file(curl_config.h.cmake - ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h) +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "BUILDING_LIBCURL") +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "${CURL_DEBUG_MACROS}") -transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") -include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake) +configure_file("curl_config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/curl_config.h") -list(APPEND HHEADERS - ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h - ) +# Get 'CSOURCES', 'HHEADERS' variables +curl_transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") +include("${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") -if(WIN32 AND NOT CURL_STATICLIB) - list(APPEND CSOURCES libcurl.rc) -endif() +list(APPEND HHEADERS "${CMAKE_CURRENT_BINARY_DIR}/curl_config.h") # The rest of the build -include_directories(${CMAKE_CURRENT_BINARY_DIR}/../include) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) -include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +set_property(DIRECTORY APPEND PROPERTY INCLUDE_DIRECTORIES + "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h" +) + if(USE_ARES) - include_directories(${CARES_INCLUDE_DIR}) + include_directories(SYSTEM ${CARES_INCLUDE_DIRS}) +endif() + +#----------------------------------------------------------------------------- +# XXX(cmake): begin cmake-specific curl code +unset(LIBCURL_OUTPUT_NAME CACHE) + +add_library(cmcurl ${HHEADERS} ${CSOURCES}) +target_compile_definitions(cmcurl INTERFACE CURL_STATICLIB) +target_link_libraries(cmcurl PRIVATE ${CURL_LIBS}) +if(WIN32 AND CMake_BUILD_PCH) + target_precompile_headers(cmcurl PRIVATE "curl_setup.h" "curl_sspi.h" "${CURL_SOURCE_DIR}/include/curl/curl.h") endif() # For windows we want to install OPENSSL_LIBRARIES dlls @@ -83,111 +80,275 @@ if(CURL_USE_OPENSSL AND OPENSSL_FOUND AND WIN32) endif() endif() -add_library( - ${LIB_NAME} - ${HHEADERS} ${CSOURCES} - ${CMAKE_CURL_SSL_DLLS} - ) +return() # The rest of this file is not needed for building within CMake. +# XXX(cmake): end cmake-specific curl code +#----------------------------------------------------------------------------- -add_library( - ${PROJECT_NAME}::${LIB_NAME} - ALIAS ${LIB_NAME} +if(CURL_BUILD_TESTING) + add_library( + curlu # special libcurlu library just for unittests + STATIC + EXCLUDE_FROM_ALL + ${HHEADERS} ${CSOURCES} ) + target_compile_definitions(curlu PUBLIC "CURL_STATICLIB" "UNITTESTS") + target_link_libraries(curlu PRIVATE ${CURL_LIBS}) + # There is plenty of parallelism when building the testdeps target. + # Override the curlu batch size with the maximum to optimize performance. + set_target_properties(curlu PROPERTIES UNITY_BUILD_BATCH_SIZE 0) +endif() -if(NOT BUILD_SHARED_LIBS) - set_target_properties(${LIB_NAME} PROPERTIES INTERFACE_COMPILE_DEFINITIONS CURL_STATICLIB) +if(ENABLE_CURLDEBUG) + # We must compile this source separately to avoid memdebug.h redefinitions + # applying to it. + set_source_files_properties("memdebug.c" PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) endif() -target_link_libraries(${LIB_NAME} PRIVATE ${CURL_LIBS}) +## Library definition -if(0) # This code not needed for building within CMake. -transform_makefile_inc("Makefile.soname" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") -include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake) +if(NOT DEFINED IMPORT_LIB_SUFFIX) + # Suffix implib name with "_imp" by default, to avoid conflicting with + # the generated static "libcurl.lib" (typically with MSVC). + if(WIN32 AND BUILD_SHARED_LIBS AND + CMAKE_IMPORT_LIBRARY_SUFFIX STREQUAL CMAKE_STATIC_LIBRARY_SUFFIX) + set(IMPORT_LIB_SUFFIX "_imp") + else() + set(IMPORT_LIB_SUFFIX "") + endif() +endif() +if(NOT DEFINED STATIC_LIB_SUFFIX) + set(STATIC_LIB_SUFFIX "") endif() -set_target_properties(${LIB_NAME} PROPERTIES - COMPILE_DEFINITIONS BUILDING_LIBCURL - OUTPUT_NAME ${LIBCURL_OUTPUT_NAME} - ) +# Detect implib static lib filename collision +if(WIN32 AND BUILD_STATIC_LIBS AND BUILD_SHARED_LIBS AND + "${IMPORT_LIB_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}" STREQUAL + "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}") + message(FATAL_ERROR "Library suffix is the same ('${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}') " + "for the import and static '${LIBCURL_OUTPUT_NAME}' library. " + "Set IMPORT_LIB_SUFFIX and/or STATIC_LIB_SUFFIX to different values, " + "or disable building either the shared or static library to avoid the filename collision.") +endif() -if(0) # This code not needed for building within CMake. -if(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR - CMAKE_SYSTEM_NAME STREQUAL "Linux" OR - CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR - CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR - CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR +# Whether to do a single compilation pass for libcurl sources and reuse these +# objects to generate both static and shared target. +if(NOT DEFINED SHARE_LIB_OBJECT) + # Enable it by default on platforms where PIC is the default for both shared + # and static and there is a way to tell the linker which libcurl symbols it + # should export (vs. marking these symbols exportable at compile-time). + if(WIN32) + set(SHARE_LIB_OBJECT ON) + else() + # On other platforms, make it an option disabled by default + set(SHARE_LIB_OBJECT OFF) + endif() +endif() - # FreeBSD comes with the a.out and elf flavours - # but a.out was supported up to version 3.x and - # elf from 3.x. I cannot imagine someone running - # CMake on those ancient systems - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR +if(SHARE_LIB_OBJECT AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) + set(LIB_OBJECT "libcurl_object") + add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES}) # Requires CMake 3.12 + if(WIN32) + # Define CURL_STATICLIB always, to disable __declspec(dllexport) for + # exported libcurl symbols. We handle exports via libcurl.def instead. + # Except with symbol hiding disabled or debug mode enabled, when we export + # _all_ symbols from libcurl DLL, without using libcurl.def. + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_STATICLIB") + endif() + target_link_libraries(${LIB_OBJECT} PRIVATE ${CURL_LIBS}) + set_target_properties(${LIB_OBJECT} PROPERTIES + POSITION_INDEPENDENT_CODE ON) + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") + endif() + if(CURL_HAS_LTO) + if(CMAKE_CONFIGURATION_TYPES) + set_target_properties(${LIB_OBJECT} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + else() + set_target_properties(${LIB_OBJECT} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + endif() - CMAKE_SYSTEM_NAME STREQUAL "Haiku") + target_include_directories(${LIB_OBJECT} INTERFACE + "$" + "$") - math(EXPR CMAKESONAME "${VERSIONCHANGE} - ${VERSIONDEL}") - set(CMAKEVERSION "${CMAKESONAME}.${VERSIONDEL}.${VERSIONADD}") + set(LIB_SOURCE $) +else() + set(LIB_SOURCE ${HHEADERS} ${CSOURCES}) +endif() - set_target_properties(${LIB_NAME} PROPERTIES - VERSION ${CMAKEVERSION} - SOVERSION ${CMAKESONAME} - ) +# We want it to be called libcurl on all platforms +if(BUILD_STATIC_LIBS) + list(APPEND libcurl_export ${LIB_STATIC}) + add_library(${LIB_STATIC} STATIC ${LIB_SOURCE}) + add_library(${PROJECT_NAME}::${LIB_STATIC} ALIAS ${LIB_STATIC}) + if(WIN32) + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_STATICLIB") + endif() + target_link_libraries(${LIB_STATIC} PRIVATE ${CURL_LIBS}) + # Remove the "lib" prefix since the library is already named "libcurl". + set_target_properties(${LIB_STATIC} PROPERTIES + PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}" + SUFFIX "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}" + INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB" + INTERFACE_LINK_DIRECTORIES "${CURL_LIBDIRS}") + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") + endif() + if(CURL_HAS_LTO) + if(CMAKE_CONFIGURATION_TYPES) + set_target_properties(${LIB_OBJECT} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + else() + set_target_properties(${LIB_OBJECT} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + endif() + target_include_directories(${LIB_STATIC} INTERFACE + "$" + "$") endif() +if(BUILD_SHARED_LIBS) + list(APPEND libcurl_export ${LIB_SHARED}) + add_library(${LIB_SHARED} SHARED ${LIB_SOURCE}) + add_library(${PROJECT_NAME}::${LIB_SHARED} ALIAS ${LIB_SHARED}) + if(WIN32) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES "dllmain.c") + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES "libcurl.rc") + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES "${PROJECT_SOURCE_DIR}/lib/libcurl.def") + endif() + endif() + target_link_libraries(${LIB_SHARED} PRIVATE ${CURL_LIBS}) + # Remove the "lib" prefix since the library is already named "libcurl". + set_target_properties(${LIB_SHARED} PROPERTIES + PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}" + IMPORT_PREFIX "" IMPORT_SUFFIX "${IMPORT_LIB_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}" + POSITION_INDEPENDENT_CODE ON) + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") + endif() + if(CURL_HAS_LTO) + if(CMAKE_CONFIGURATION_TYPES) + set_target_properties(${LIB_OBJECT} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + else() + set_target_properties(${LIB_OBJECT} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + endif() -if(HIDES_CURL_PRIVATE_SYMBOLS) - set_property(TARGET ${LIB_NAME} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") - set_property(TARGET ${LIB_NAME} APPEND PROPERTY COMPILE_FLAGS ${CURL_CFLAG_SYMBOLS_HIDE}) -endif() + target_include_directories(${LIB_SHARED} INTERFACE + "$" + "$") + + if(CMAKE_DLL_NAME_WITH_SOVERSION OR + CYGWIN OR + APPLE OR + CMAKE_SYSTEM_NAME STREQUAL "AIX" OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR + CMAKE_SYSTEM_NAME STREQUAL "Haiku" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(_soversion_default TRUE) + else() + set(_soversion_default FALSE) + endif() -# Remove the "lib" prefix since the library is already named "libcurl". -set_target_properties(${LIB_NAME} PROPERTIES PREFIX "") -set_target_properties(${LIB_NAME} PROPERTIES IMPORT_PREFIX "") + option(CURL_LIBCURL_SOVERSION "Enable libcurl SOVERSION" ${_soversion_default}) + option(CURL_LIBCURL_VERSIONED_SYMBOLS "Enable libcurl versioned symbols" OFF) -if(CURL_HAS_LTO) - set_target_properties(${LIB_NAME} PROPERTIES - INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE - INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) -endif() + if(CURL_LIBCURL_SOVERSION OR CURL_LIBCURL_VERSIONED_SYMBOLS) + # Get 'VERSIONCHANGE', 'VERSIONADD', 'VERSIONDEL', 'VERSIONINFO' variables + curl_transform_makefile_inc("Makefile.soname" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") + include("${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") -if(WIN32) - if(BUILD_SHARED_LIBS) - if(MSVC) - # Add "_imp" as a suffix before the extension to avoid conflicting with - # the statically linked "libcurl.lib" - set_target_properties(${LIB_NAME} PROPERTIES IMPORT_SUFFIX "_imp.lib") + math(EXPR _cmakesoname "${VERSIONCHANGE} - ${VERSIONDEL}") + set(_cmakeversion "${_cmakesoname}.${VERSIONDEL}.${VERSIONADD}") + endif() + + if(CURL_LIBCURL_SOVERSION) + set_target_properties(${LIB_SHARED} PROPERTIES + VERSION "${_cmakeversion}" SOVERSION "${_cmakesoname}") + endif() + + ## Versioned symbols + + if(CURL_LIBCURL_VERSIONED_SYMBOLS) + if(NOT DEFINED CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX) + # Default to prefixes used by autotools + if(CURL_WITH_MULTI_SSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "MULTISSL_") + elseif(CURL_USE_OPENSSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "OPENSSL_") + elseif(CURL_USE_MBEDTLS) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "MBEDTLS_") + elseif(CURL_USE_BEARSSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "BEARSSL_") + elseif(CURL_USE_WOLFSSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "WOLFSSL_") + elseif(CURL_USE_GNUTLS) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "GNUTLS_") + elseif(CURL_USE_RUSTLS) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "RUSTLS_") + endif() + endif() + # Generate version script for the linker, for versioned symbols. + # Consumed variables: + # CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX + # CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME + set(CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME ${_cmakesoname}) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/libcurl.vers.in" + "${CMAKE_CURRENT_BINARY_DIR}/libcurl.vers" @ONLY) + include(CMakePushCheckState) + include(CheckCSourceCompiles) + cmake_push_check_state() + set(CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/libcurl.vers") + check_c_source_compiles("int main(void) { return 0; }" HAVE_VERSIONED_SYMBOLS) + if(HAVE_VERSIONED_SYMBOLS) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) + set_target_properties(${LIB_SHARED} PROPERTIES LINK_OPTIONS "${CMAKE_REQUIRED_LINK_OPTIONS}") + else() + set_target_properties(${LIB_SHARED} PROPERTIES LINK_FLAGS "${CMAKE_REQUIRED_LINK_OPTIONS}") + endif() + else() + message(WARNING "Versioned symbols requested, but not supported by the toolchain.") endif() + cmake_pop_check_state() endif() -elseif(NOT CMAKE_CROSSCOMPILING) - # on not-Windows and not-crosscompiling, check for writable argv[] - include(CheckCSourceRuns) - check_c_source_runs(" -int main(int argc, char **argv) -{ - (void)argc; - argv[0][0] = ' '; - return (argv[0][0] == ' ')?0:1; -}" - HAVE_WRITABLE_ARGV) endif() -target_include_directories(${LIB_NAME} INTERFACE - $ - $) +add_library(${LIB_NAME} ALIAS ${LIB_SELECTED}) +add_library(${PROJECT_NAME}::${LIB_NAME} ALIAS ${LIB_SELECTED}) if(CURL_ENABLE_EXPORT_TARGET) - install(TARGETS ${LIB_NAME} - EXPORT ${TARGETS_EXPORT_NAME} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ) + if(BUILD_STATIC_LIBS) + install(TARGETS ${LIB_STATIC} + EXPORT ${TARGETS_EXPORT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + endif() + if(BUILD_SHARED_LIBS) + install(TARGETS ${LIB_SHARED} + EXPORT ${TARGETS_EXPORT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + endif() - export(TARGETS ${LIB_NAME} - FILE ${PROJECT_BINARY_DIR}/libcurl-target.cmake - NAMESPACE ${PROJECT_NAME}:: + export(TARGETS ${libcurl_export} + FILE "${PROJECT_BINARY_DIR}/libcurl-target.cmake" + NAMESPACE ${PROJECT_NAME}:: ) endif() - -endif() diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc index f815170a706..b384e09623d 100644 --- a/Utilities/cmcurl/lib/Makefile.inc +++ b/Utilities/cmcurl/lib/Makefile.inc @@ -22,6 +22,33 @@ # ########################################################################### +LIB_CURLX_CFILES = \ + curlx/base64.c \ + curlx/dynbuf.c \ + curlx/inet_pton.c \ + curlx/multibyte.c \ + curlx/nonblock.c \ + curlx/strparse.c \ + curlx/timediff.c \ + curlx/timeval.c \ + curlx/version_win32.c \ + curlx/warnless.c \ + curlx/winapi.c + +LIB_CURLX_HFILES = \ + curlx/base64.h \ + curlx/curlx.h \ + curlx/dynbuf.h \ + curlx/inet_pton.h \ + curlx/multibyte.h \ + curlx/nonblock.h \ + curlx/strparse.h \ + curlx/timediff.h \ + curlx/timeval.h \ + curlx/version_win32.h \ + curlx/warnless.h \ + curlx/winapi.h + LIB_VAUTH_CFILES = \ vauth/cleartext.c \ vauth/cram.c \ @@ -39,75 +66,82 @@ LIB_VAUTH_CFILES = \ LIB_VAUTH_HFILES = \ vauth/digest.h \ - vauth/ntlm.h \ vauth/vauth.h LIB_VTLS_CFILES = \ vtls/bearssl.c \ - vtls/gskit.c \ + vtls/cipher_suite.c \ vtls/gtls.c \ vtls/hostcheck.c \ vtls/keylog.c \ vtls/mbedtls.c \ vtls/mbedtls_threadlock.c \ - vtls/nss.c \ vtls/openssl.c \ vtls/rustls.c \ vtls/schannel.c \ vtls/schannel_verify.c \ vtls/sectransp.c \ vtls/vtls.c \ + vtls/vtls_scache.c \ + vtls/vtls_spack.c \ vtls/wolfssl.c \ vtls/x509asn1.c LIB_VTLS_HFILES = \ vtls/bearssl.h \ - vtls/gskit.h \ + vtls/cipher_suite.h \ vtls/gtls.h \ vtls/hostcheck.h \ vtls/keylog.h \ vtls/mbedtls.h \ vtls/mbedtls_threadlock.h \ - vtls/nssg.h \ vtls/openssl.h \ vtls/rustls.h \ vtls/schannel.h \ + vtls/schannel_int.h \ vtls/sectransp.h \ vtls/vtls.h \ vtls/vtls_int.h \ + vtls/vtls_scache.h \ + vtls/vtls_spack.h \ vtls/wolfssl.h \ vtls/x509asn1.h LIB_VQUIC_CFILES = \ vquic/curl_msh3.c \ vquic/curl_ngtcp2.c \ + vquic/curl_osslq.c \ vquic/curl_quiche.c \ - vquic/vquic.c + vquic/vquic.c \ + vquic/vquic-tls.c LIB_VQUIC_HFILES = \ vquic/curl_msh3.h \ vquic/curl_ngtcp2.h \ + vquic/curl_osslq.h \ vquic/curl_quiche.h \ vquic/vquic.h \ - vquic/vquic_int.h + vquic/vquic_int.h \ + vquic/vquic-tls.h LIB_VSSH_CFILES = \ vssh/libssh.c \ vssh/libssh2.c \ + vssh/curl_path.c \ vssh/wolfssh.c -LIB_VSSH_HFILES = \ +LIB_VSSH_HFILES = \ + vssh/curl_path.h \ vssh/ssh.h LIB_CFILES = \ altsvc.c \ amigaos.c \ asyn-ares.c \ - asyn-thread.c \ - base64.c \ + asyn-base.c \ + asyn-thrdd.c \ bufq.c \ bufref.c \ - c-hyper.c \ cf-h1-proxy.c \ cf-h2-proxy.c \ cf-haproxy.c \ @@ -118,6 +152,7 @@ LIB_CFILES = \ connect.c \ content_encoding.c \ cookie.c \ + cshutdn.c \ curl_addrinfo.c \ curl_des.c \ curl_endian.c \ @@ -125,25 +160,25 @@ LIB_CFILES = \ curl_get_line.c \ curl_gethostname.c \ curl_gssapi.c \ - curl_log.c \ curl_memrchr.c \ - curl_multibyte.c \ curl_ntlm_core.c \ - curl_ntlm_wb.c \ - curl_path.c \ curl_range.c \ curl_rtmp.c \ curl_sasl.c \ + curl_sha512_256.c \ curl_sspi.c \ curl_threads.c \ + curl_trc.c \ + cw-out.c \ + cw-pause.c \ dict.c \ doh.c \ - dynbuf.c \ dynhds.c \ easy.c \ easygetopt.c \ easyoptions.c \ escape.c \ + fake_addrinfo.c \ file.c \ fileinfo.c \ fopen.c \ @@ -156,29 +191,28 @@ LIB_CFILES = \ hash.c \ headers.c \ hmac.c \ - hostasyn.c \ hostip.c \ hostip4.c \ hostip6.c \ - hostsyn.c \ hsts.c \ http.c \ http1.c \ http2.c \ + http_aws_sigv4.c \ http_chunks.c \ http_digest.c \ http_negotiate.c \ http_ntlm.c \ http_proxy.c \ - http_aws_sigv4.c \ + httpsrr.c \ idn.c \ if2ip.c \ imap.c \ inet_ntop.c \ - inet_pton.c \ krb5.c \ ldap.c \ llist.c \ + macos.c \ md4.c \ md5.c \ memdebug.c \ @@ -186,8 +220,8 @@ LIB_CFILES = \ mprintf.c \ mqtt.c \ multi.c \ + multi_ev.c \ netrc.c \ - nonblock.c \ noproxy.c \ openldap.c \ parsedate.c \ @@ -197,6 +231,7 @@ LIB_CFILES = \ psl.c \ rand.c \ rename.c \ + request.c \ rtsp.c \ select.c \ sendf.c \ @@ -214,20 +249,19 @@ LIB_CFILES = \ splay.c \ strcase.c \ strdup.c \ + strequal.c \ strerror.c \ - strtok.c \ - strtoofft.c \ system_win32.c \ telnet.c \ tftp.c \ - timediff.c \ - timeval.c \ transfer.c \ + uint-bset.c \ + uint-hash.c \ + uint-spbset.c \ + uint-table.c \ url.c \ urlapi.c \ version.c \ - version_win32.c \ - warnless.c \ ws.c LIB_HFILES = \ @@ -237,7 +271,6 @@ LIB_HFILES = \ asyn.h \ bufq.h \ bufref.h \ - c-hyper.h \ cf-h1-proxy.h \ cf-h2-proxy.h \ cf-haproxy.h \ @@ -245,11 +278,11 @@ LIB_HFILES = \ cf-socket.h \ cfilters.h \ conncache.h \ + cshutdn.h \ connect.h \ content_encoding.h \ cookie.h \ curl_addrinfo.h \ - curl_base64.h \ curl_ctype.h \ curl_des.h \ curl_endian.h \ @@ -260,15 +293,11 @@ LIB_HFILES = \ curl_hmac.h \ curl_krb5.h \ curl_ldap.h \ - curl_log.h \ curl_md4.h \ curl_md5.h \ curl_memory.h \ curl_memrchr.h \ - curl_multibyte.h \ curl_ntlm_core.h \ - curl_ntlm_wb.h \ - curl_path.h \ curl_printf.h \ curl_range.h \ curl_rtmp.h \ @@ -276,24 +305,27 @@ LIB_HFILES = \ curl_setup.h \ curl_setup_once.h \ curl_sha256.h \ + curl_sha512_256.h \ curl_sspi.h \ curl_threads.h \ - curlx.h \ + curl_trc.h \ + cw-out.h \ + cw-pause.h \ dict.h \ doh.h \ - dynbuf.h \ dynhds.h \ easy_lock.h \ easyif.h \ easyoptions.h \ escape.h \ + fake_addrinfo.h \ file.h \ fileinfo.h \ fopen.h \ formdata.h \ - functypes.h \ ftp.h \ ftplistparser.h \ + functypes.h \ getinfo.h \ gopher.h \ hash.h \ @@ -303,25 +335,26 @@ LIB_HFILES = \ http.h \ http1.h \ http2.h \ + http_aws_sigv4.h \ http_chunks.h \ http_digest.h \ http_negotiate.h \ http_ntlm.h \ http_proxy.h \ - http_aws_sigv4.h \ + httpsrr.h \ idn.h \ if2ip.h \ imap.h \ inet_ntop.h \ - inet_pton.h \ llist.h \ + macos.h \ memdebug.h \ mime.h \ mqtt.h \ multihandle.h \ + multi_ev.h \ multiif.h \ netrc.h \ - nonblock.h \ noproxy.h \ parsedate.h \ pingpong.h \ @@ -330,11 +363,14 @@ LIB_HFILES = \ psl.h \ rand.h \ rename.h \ + request.h \ rtsp.h \ select.h \ sendf.h \ setopt.h \ + setup-os400.h \ setup-vms.h \ + setup-win32.h \ share.h \ sigpipe.h \ slist.h \ @@ -348,24 +384,22 @@ LIB_HFILES = \ strcase.h \ strdup.h \ strerror.h \ - strtok.h \ - strtoofft.h \ system_win32.h \ telnet.h \ tftp.h \ - timediff.h \ - timeval.h \ transfer.h \ + uint-bset.h \ + uint-hash.h \ + uint-spbset.h \ + uint-table.h \ url.h \ urlapi-int.h \ urldata.h \ - version_win32.h \ - warnless.h \ ws.h LIB_RCFILES = libcurl.rc CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) \ - $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) + $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) $(LIB_CURLX_CFILES) HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) \ - $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) + $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) $(LIB_CURLX_HFILES) diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c index f812bafc1bd..602ef61deff 100644 --- a/Utilities/cmcurl/lib/altsvc.c +++ b/Utilities/cmcurl/lib/altsvc.c @@ -35,9 +35,13 @@ #include "strcase.h" #include "parsedate.h" #include "sendf.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "fopen.h" #include "rename.h" +#include "strdup.h" +#include "curlx/inet_pton.h" +#include "curlx/strparse.h" +#include "connect.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -45,26 +49,12 @@ #include "memdebug.h" #define MAX_ALTSVC_LINE 4095 -#define MAX_ALTSVC_DATELENSTR "64" -#define MAX_ALTSVC_DATELEN 64 -#define MAX_ALTSVC_HOSTLENSTR "512" -#define MAX_ALTSVC_HOSTLEN 512 -#define MAX_ALTSVC_ALPNLENSTR "10" +#define MAX_ALTSVC_DATELEN 256 +#define MAX_ALTSVC_HOSTLEN 2048 #define MAX_ALTSVC_ALPNLEN 10 #define H3VERSION "h3" -static enum alpnid alpn2alpnid(char *name) -{ - if(strcasecompare(name, "h1")) - return ALPN_h1; - if(strcasecompare(name, "h2")) - return ALPN_h2; - if(strcasecompare(name, H3VERSION)) - return ALPN_h3; - return ALPN_none; /* unknown, probably rubbish input */ -} - /* Given the ALPN ID, return the name */ const char *Curl_alpnid2str(enum alpnid id) { @@ -89,32 +79,51 @@ static void altsvc_free(struct altsvc *as) } static struct altsvc *altsvc_createid(const char *srchost, + size_t hlen, const char *dsthost, + size_t dlen, /* dsthost length */ enum alpnid srcalpnid, enum alpnid dstalpnid, - unsigned int srcport, - unsigned int dstport) + size_t srcport, + size_t dstport) { - struct altsvc *as = calloc(sizeof(struct altsvc), 1); - size_t hlen; + struct altsvc *as = calloc(1, sizeof(struct altsvc)); if(!as) return NULL; - hlen = strlen(srchost); DEBUGASSERT(hlen); - as->src.host = strdup(srchost); + DEBUGASSERT(dlen); + if(!hlen || !dlen) + /* bad input */ + goto error; + if((hlen > 2) && srchost[0] == '[') { + /* IPv6 address, strip off brackets */ + srchost++; + hlen -= 2; + } + else if(srchost[hlen - 1] == '.') { + /* strip off trailing dot */ + hlen--; + if(!hlen) + goto error; + } + if((dlen > 2) && dsthost[0] == '[') { + /* IPv6 address, strip off brackets */ + dsthost++; + dlen -= 2; + } + + as->src.host = Curl_memdup0(srchost, hlen); if(!as->src.host) goto error; - if(hlen && (srchost[hlen - 1] == '.')) - /* strip off trailing any dot */ - as->src.host[--hlen] = 0; - as->dst.host = strdup(dsthost); + + as->dst.host = Curl_memdup0(dsthost, dlen); if(!as->dst.host) goto error; as->src.alpnid = srcalpnid; as->dst.alpnid = dstalpnid; - as->src.port = curlx_ultous(srcport); - as->dst.port = curlx_ultous(dstport); + as->src.port = (unsigned short)srcport; + as->dst.port = (unsigned short)dstport; return as; error: @@ -122,54 +131,77 @@ static struct altsvc *altsvc_createid(const char *srchost, return NULL; } -static struct altsvc *altsvc_create(char *srchost, - char *dsthost, - char *srcalpn, - char *dstalpn, - unsigned int srcport, - unsigned int dstport) +static struct altsvc *altsvc_create(struct Curl_str *srchost, + struct Curl_str *dsthost, + struct Curl_str *srcalpn, + struct Curl_str *dstalpn, + size_t srcport, + size_t dstport) { - enum alpnid dstalpnid = alpn2alpnid(dstalpn); - enum alpnid srcalpnid = alpn2alpnid(srcalpn); + enum alpnid dstalpnid = + Curl_alpn2alpnid(curlx_str(dstalpn), curlx_strlen(dstalpn)); + enum alpnid srcalpnid = + Curl_alpn2alpnid(curlx_str(srcalpn), curlx_strlen(srcalpn)); if(!srcalpnid || !dstalpnid) return NULL; - return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid, + return altsvc_createid(curlx_str(srchost), curlx_strlen(srchost), + curlx_str(dsthost), curlx_strlen(dsthost), + srcalpnid, dstalpnid, srcport, dstport); } /* only returns SERIOUS errors */ -static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) +static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line) { /* Example line: h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1 */ - char srchost[MAX_ALTSVC_HOSTLEN + 1]; - char dsthost[MAX_ALTSVC_HOSTLEN + 1]; - char srcalpn[MAX_ALTSVC_ALPNLEN + 1]; - char dstalpn[MAX_ALTSVC_ALPNLEN + 1]; - char date[MAX_ALTSVC_DATELEN + 1]; - unsigned int srcport; - unsigned int dstport; - unsigned int prio; - unsigned int persist; - int rc; - - rc = sscanf(line, - "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " - "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " - "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u", - srcalpn, srchost, &srcport, - dstalpn, dsthost, &dstport, - date, &persist, &prio); - if(9 == rc) { + struct Curl_str srchost; + struct Curl_str dsthost; + struct Curl_str srcalpn; + struct Curl_str dstalpn; + struct Curl_str date; + curl_off_t srcport; + curl_off_t dstport; + curl_off_t persist; + curl_off_t prio; + + if(curlx_str_word(&line, &srcalpn, MAX_ALTSVC_ALPNLEN) || + curlx_str_singlespace(&line) || + curlx_str_word(&line, &srchost, MAX_ALTSVC_HOSTLEN) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &srcport, 65535) || + curlx_str_singlespace(&line) || + curlx_str_word(&line, &dstalpn, MAX_ALTSVC_ALPNLEN) || + curlx_str_singlespace(&line) || + curlx_str_word(&line, &dsthost, MAX_ALTSVC_HOSTLEN) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &dstport, 65535) || + curlx_str_singlespace(&line) || + curlx_str_quotedword(&line, &date, MAX_ALTSVC_DATELEN) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &persist, 1) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &prio, 0) || + curlx_str_newline(&line)) + ; + else { struct altsvc *as; - time_t expires = Curl_getdate_capped(date); - as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport); + char dbuf[MAX_ALTSVC_DATELEN + 1]; + time_t expires; + + /* The date parser works on a null-terminated string. The maximum length + is upheld by curlx_str_quotedword(). */ + memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); + dbuf[curlx_strlen(&date)] = 0; + expires = Curl_getdate_capped(dbuf); + as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn, + (size_t)srcport, (size_t)dstport); if(as) { as->expires = expires; - as->prio = prio; + as->prio = 0; /* not supported to just set zero */ as->persist = persist ? 1 : 0; - Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); + Curl_llist_append(&asi->list, as, &as->node); } } @@ -187,10 +219,9 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) { CURLcode result = CURLE_OK; - char *line = NULL; FILE *fp; - /* we need a private copy of the file name so that the altsvc cache file + /* we need a private copy of the filename so that the altsvc cache file name survives an easy handle reset */ free(asi->filename); asi->filename = strdup(file); @@ -199,29 +230,18 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) fp = fopen(file, FOPEN_READTEXT); if(fp) { - line = malloc(MAX_ALTSVC_LINE); - if(!line) - goto fail; - while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) { - char *lineptr = line; - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - if(*lineptr == '#') - /* skip commented lines */ - continue; - - altsvc_add(asi, lineptr); + struct dynbuf buf; + curlx_dyn_init(&buf, MAX_ALTSVC_LINE); + while(Curl_get_line(&buf, fp)) { + const char *lineptr = curlx_dyn_ptr(&buf); + curlx_str_passblanks(&lineptr); + if(curlx_str_single(&lineptr, '#')) + altsvc_add(asi, lineptr); } - free(line); /* free the line buffer */ + curlx_dyn_free(&buf); /* free the line buffer */ fclose(fp); } return result; - -fail: - Curl_safefree(asi->filename); - free(line); - fclose(fp); - return CURLE_OUT_OF_MEMORY; } /* @@ -231,18 +251,40 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) static CURLcode altsvc_out(struct altsvc *as, FILE *fp) { struct tm stamp; + const char *dst6_pre = ""; + const char *dst6_post = ""; + const char *src6_pre = ""; + const char *src6_post = ""; CURLcode result = Curl_gmtime(as->expires, &stamp); if(result) return result; - +#ifdef USE_IPV6 + else { + char ipv6_unused[16]; + if(1 == curlx_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { + dst6_pre = "["; + dst6_post = "]"; + } + if(1 == curlx_inet_pton(AF_INET6, as->src.host, ipv6_unused)) { + src6_pre = "["; + src6_post = "]"; + } + } +#endif fprintf(fp, - "%s %s %u " - "%s %s %u " + "%s %s%s%s %u " + "%s %s%s%s %u " "\"%d%02d%02d " "%02d:%02d:%02d\" " - "%u %d\n", - Curl_alpnid2str(as->src.alpnid), as->src.host, as->src.port, - Curl_alpnid2str(as->dst.alpnid), as->dst.host, as->dst.port, + "%u %u\n", + Curl_alpnid2str(as->src.alpnid), + src6_pre, as->src.host, src6_post, + as->src.port, + + Curl_alpnid2str(as->dst.alpnid), + dst6_pre, as->dst.host, dst6_post, + as->dst.port, + stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, stamp.tm_hour, stamp.tm_min, stamp.tm_sec, as->persist, as->prio); @@ -257,7 +299,7 @@ static CURLcode altsvc_out(struct altsvc *as, FILE *fp) */ struct altsvcinfo *Curl_altsvc_init(void) { - struct altsvcinfo *asi = calloc(sizeof(struct altsvcinfo), 1); + struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo)); if(!asi) return NULL; Curl_llist_init(&asi->list, NULL); @@ -267,7 +309,7 @@ struct altsvcinfo *Curl_altsvc_init(void) #ifdef USE_HTTP2 | CURLALTSVC_H2 #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 | CURLALTSVC_H3 #endif ; @@ -279,10 +321,8 @@ struct altsvcinfo *Curl_altsvc_init(void) */ CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) { - CURLcode result; DEBUGASSERT(asi); - result = altsvc_load(asi, file); - return result; + return altsvc_load(asi, file); } /* @@ -291,9 +331,6 @@ CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) { DEBUGASSERT(asi); - if(!ctrl) - /* unexpected */ - return CURLE_BAD_FUNCTION_ARGUMENT; asi->flags = ctrl; return CURLE_OK; } @@ -304,13 +341,13 @@ CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) */ void Curl_altsvc_cleanup(struct altsvcinfo **altsvcp) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; if(*altsvcp) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; struct altsvcinfo *altsvc = *altsvcp; - for(e = altsvc->list.head; e; e = n) { - struct altsvc *as = e->ptr; - n = e->next; + for(e = Curl_llist_head(&altsvc->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); altsvc_free(as); } free(altsvc->filename); @@ -325,8 +362,6 @@ void Curl_altsvc_cleanup(struct altsvcinfo **altsvcp) CURLcode Curl_altsvc_save(struct Curl_easy *data, struct altsvcinfo *altsvc, const char *file) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; CURLcode result = CURLE_OK; FILE *out; char *tempstore = NULL; @@ -340,17 +375,19 @@ CURLcode Curl_altsvc_save(struct Curl_easy *data, file = altsvc->filename; if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0]) - /* marked as read-only, no file or zero length file name */ + /* marked as read-only, no file or zero length filename */ return CURLE_OK; result = Curl_fopen(data, file, &out, &tempstore); if(!result) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n" "# This file was generated by libcurl! Edit at your own risk.\n", out); - for(e = altsvc->list.head; e; e = n) { - struct altsvc *as = e->ptr; - n = e->next; + for(e = Curl_llist_head(&altsvc->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); result = altsvc_out(as, out); if(result) break; @@ -366,26 +403,6 @@ CURLcode Curl_altsvc_save(struct Curl_easy *data, return result; } -static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen) -{ - size_t len; - const char *protop; - const char *p = *ptr; - while(*p && ISBLANK(*p)) - p++; - protop = p; - while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '=')) - p++; - len = p - protop; - *ptr = p; - - if(!len || (len >= buflen)) - return CURLE_BAD_FUNCTION_ARGUMENT; - memcpy(alpnbuf, protop, len); - alpnbuf[len] = 0; - return CURLE_OK; -} - /* hostcompare() returns true if 'host' matches 'check'. The first host * argument may have a trailing dot present that will be ignored. */ @@ -397,7 +414,7 @@ static bool hostcompare(const char *host, const char *check) if(hlen && (host[hlen - 1] == '.')) hlen--; if(hlen != clen) - /* they can't match if they have different lengths */ + /* they cannot match if they have different lengths */ return FALSE; return strncasecompare(host, check, hlen); } @@ -407,47 +424,47 @@ static bool hostcompare(const char *host, const char *check) static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, const char *srchost, unsigned short srcport) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; - for(e = asi->list.head; e; e = n) { - struct altsvc *as = e->ptr; - n = e->next; + struct Curl_llist_node *e; + struct Curl_llist_node *n; + for(e = Curl_llist_head(&asi->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); if((srcalpnid == as->src.alpnid) && (srcport == as->src.port) && hostcompare(srchost, as->src.host)) { - Curl_llist_remove(&asi->list, e, NULL); + Curl_node_remove(e); altsvc_free(as); } } } -#ifdef DEBUGBUILD +#if defined(DEBUGBUILD) || defined(UNITTESTS) /* to play well with debug builds, we can *set* a fixed time this will return */ -static time_t debugtime(void *unused) +static time_t altsvc_debugtime(void *unused) { - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); (void)unused; if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, TIME_T_MAX); return (time_t)val; } return time(NULL); } -#define time(x) debugtime(x) +#undef time +#define time(x) altsvc_debugtime(x) #endif -#define ISNEWLINE(x) (((x) == '\n') || (x) == '\r') - /* * Curl_altsvc_parse() takes an incoming alt-svc response header and stores * the data correctly in the cache. * - * 'value' points to the header *value*. That's contents to the right of the + * 'value' points to the header *value*. That is contents to the right of the * header name. * * Currently this function rejects invalid data without returning an error. - * Invalid host name, port number will result in the specific alternative + * Invalid hostname, port number will result in the specific alternative * being rejected. Unknown protocols are skipped. */ CURLcode Curl_altsvc_parse(struct Curl_easy *data, @@ -456,168 +473,158 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, unsigned short srcport) { const char *p = value; - size_t len; - char namebuf[MAX_ALTSVC_HOSTLEN] = ""; - char alpnbuf[MAX_ALTSVC_ALPNLEN] = ""; struct altsvc *as; unsigned short dstport = srcport; /* the same by default */ - CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); size_t entries = 0; + struct Curl_str alpn; + const char *sp; + time_t maxage = 24 * 3600; /* default is 24 hours */ + bool persist = FALSE; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - if(result) { - infof(data, "Excessive alt-svc header, ignoring."); - return CURLE_OK; - } DEBUGASSERT(asi); - /* "clear" is a magic keyword */ - if(strcasecompare(alpnbuf, "clear")) { - /* Flush cached alternatives for this source origin */ - altsvc_flush(asi, srcalpnid, srchost, srcport); - return CURLE_OK; + /* initial check for "clear" */ + if(!curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, ';') && + !curlx_str_single(&p, ';')) { + curlx_str_trimblanks(&alpn); + /* "clear" is a magic keyword */ + if(curlx_str_casecompare(&alpn, "clear")) { + /* Flush cached alternatives for this source origin */ + altsvc_flush(asi, srcalpnid, srchost, srcport); + return CURLE_OK; + } + } + + p = value; + + if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) + return CURLE_OK; /* strange line */ + + curlx_str_trimblanks(&alpn); + + /* Handle the optional 'ma' and 'persist' flags once first, as they need to + be known for each alternative service. Unknown flags are skipped. */ + sp = strchr(p, ';'); + if(sp) { + sp++; /* pass the semicolon */ + for(;;) { + struct Curl_str name; + struct Curl_str val; + const char *vp; + curl_off_t num; + bool quoted; + /* allow some extra whitespaces around name and value */ + if(curlx_str_until(&sp, &name, 20, '=') || + curlx_str_single(&sp, '=') || + curlx_str_until(&sp, &val, 80, ';')) + break; + curlx_str_trimblanks(&name); + curlx_str_trimblanks(&val); + /* the value might be quoted */ + vp = curlx_str(&val); + quoted = (*vp == '\"'); + if(quoted) + vp++; + if(!curlx_str_number(&vp, &num, TIME_T_MAX)) { + if(curlx_str_casecompare(&name, "ma")) + maxage = (time_t)num; + else if(curlx_str_casecompare(&name, "persist") && (num == 1)) + persist = TRUE; + } + if(quoted && curlx_str_single(&sp, '\"')) + break; + if(curlx_str_single(&sp, ';')) + break; + } } do { - if(*p == '=') { - /* [protocol]="[host][:port]" */ - enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */ - p++; - if(*p == '\"') { - const char *dsthost = ""; - const char *value_ptr; - char option[32]; - unsigned long num; - char *end_ptr; - bool quoted = FALSE; - time_t maxage = 24 * 3600; /* default is 24 hours */ - bool persist = FALSE; - bool valid = TRUE; - p++; - if(*p != ':') { - /* host name starts here */ - const char *hostp = p; - while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-'))) - p++; - len = p - hostp; - if(!len || (len >= MAX_ALTSVC_HOSTLEN)) { - infof(data, "Excessive alt-svc host name, ignoring."); - valid = FALSE; + if(!curlx_str_single(&p, '=')) { + /* [protocol]="[host][:port], [protocol]="[host][:port]" */ + enum alpnid dstalpnid = + Curl_alpn2alpnid(curlx_str(&alpn), curlx_strlen(&alpn)); + if(!curlx_str_single(&p, '\"')) { + struct Curl_str dsthost; + curl_off_t port = 0; + if(curlx_str_single(&p, ':')) { + /* hostname starts here */ + if(curlx_str_single(&p, '[')) { + if(curlx_str_until(&p, &dsthost, MAX_ALTSVC_HOSTLEN, ':')) { + infof(data, "Bad alt-svc hostname, ignoring."); + break; + } } else { - memcpy(namebuf, hostp, len); - namebuf[len] = 0; - dsthost = namebuf; + /* IPv6 host name */ + if(curlx_str_until(&p, &dsthost, MAX_IPADR_LEN, ']') || + curlx_str_single(&p, ']')) { + infof(data, "Bad alt-svc IPv6 hostname, ignoring."); + break; + } } + if(curlx_str_single(&p, ':')) + break; } - else { + else /* no destination name, use source host */ - dsthost = srchost; - } - if(*p == ':') { - unsigned long port = 0; - p++; - if(ISDIGIT(*p)) - /* a port number */ - port = strtoul(p, &end_ptr, 10); - else - end_ptr = (char *)p; /* not left uninitialized */ - if(!port || port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') { - infof(data, "Unknown alt-svc port number, ignoring."); - valid = FALSE; - } - else { - dstport = curlx_ultous(port); - p = end_ptr; - } - } - if(*p++ != '\"') + curlx_str_assign(&dsthost, srchost, strlen(srchost)); + + if(curlx_str_number(&p, &port, 0xffff)) { + infof(data, "Unknown alt-svc port number, ignoring."); break; - /* Handle the optional 'ma' and 'persist' flags. Unknown flags - are skipped. */ - for(;;) { - while(ISBLANK(*p)) - p++; - if(*p != ';') - break; - p++; /* pass the semicolon */ - if(!*p || ISNEWLINE(*p)) - break; - result = getalnum(&p, option, sizeof(option)); - if(result) { - /* skip option if name is too long */ - option[0] = '\0'; - } - while(*p && ISBLANK(*p)) - p++; - if(*p != '=') - return CURLE_OK; - p++; - while(*p && ISBLANK(*p)) - p++; - if(!*p) - return CURLE_OK; - if(*p == '\"') { - /* quoted value */ - p++; - quoted = TRUE; - } - value_ptr = p; - if(quoted) { - while(*p && *p != '\"') - p++; - if(!*p++) - return CURLE_OK; - } - else { - while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',') - p++; - } - num = strtoul(value_ptr, &end_ptr, 10); - if((end_ptr != value_ptr) && (num < ULONG_MAX)) { - if(strcasecompare("ma", option)) - maxage = num; - else if(strcasecompare("persist", option) && (num == 1)) - persist = TRUE; - } } - if(dstalpnid && valid) { + + dstport = (unsigned short)port; + + if(curlx_str_single(&p, '\"')) + break; + + if(dstalpnid) { if(!entries++) /* Flush cached alternatives for this source origin, if any - when this is the first entry of the line. */ altsvc_flush(asi, srcalpnid, srchost, srcport); - as = altsvc_createid(srchost, dsthost, + as = altsvc_createid(srchost, strlen(srchost), + curlx_str(&dsthost), + curlx_strlen(&dsthost), srcalpnid, dstalpnid, srcport, dstport); if(as) { - /* The expires time also needs to take the Age: value (if any) into - account. [See RFC 7838 section 3.1] */ - as->expires = maxage + time(NULL); + time_t secs = time(NULL); + /* The expires time also needs to take the Age: value (if any) + into account. [See RFC 7838 section 3.1] */ + if(maxage > (TIME_T_MAX - secs)) + as->expires = TIME_T_MAX; + else + as->expires = maxage + secs; as->persist = persist; - Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); - infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport, - Curl_alpnid2str(dstalpnid)); + Curl_llist_append(&asi->list, as, &as->node); + infof(data, "Added alt-svc: %.*s:%d over %s", + (int)curlx_strlen(&dsthost), curlx_str(&dsthost), + dstport, Curl_alpnid2str(dstalpnid)); } } } else break; - /* after the double quote there can be a comma if there's another + + /* after the double quote there can be a comma if there is another string or a semicolon if no more */ - if(*p == ',') { - /* comma means another alternative is presented */ - p++; - result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); - if(result) - break; - } + if(curlx_str_single(&p, ',')) + break; + + /* comma means another alternative is present */ + if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) + break; + curlx_str_trimblanks(&alpn); } else break; - } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r')); + } while(1); return CURLE_OK; } @@ -631,26 +638,26 @@ bool Curl_altsvc_lookup(struct altsvcinfo *asi, struct altsvc **dstentry, const int versions) /* one or more bits */ { - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; time_t now = time(NULL); DEBUGASSERT(asi); DEBUGASSERT(srchost); DEBUGASSERT(dstentry); - for(e = asi->list.head; e; e = n) { - struct altsvc *as = e->ptr; - n = e->next; + for(e = Curl_llist_head(&asi->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); if(as->expires < now) { /* an expired entry, remove */ - Curl_llist_remove(&asi->list, e, NULL); + Curl_node_remove(e); altsvc_free(as); continue; } if((as->src.alpnid == srcalpnid) && hostcompare(srchost, as->src.host) && (as->src.port == srcport) && - (versions & as->dst.alpnid)) { + (versions & (int)as->dst.alpnid)) { /* match */ *dstentry = as; return TRUE; diff --git a/Utilities/cmcurl/lib/altsvc.h b/Utilities/cmcurl/lib/altsvc.h index 7fea1434a54..831cd097436 100644 --- a/Utilities/cmcurl/lib/altsvc.h +++ b/Utilities/cmcurl/lib/altsvc.h @@ -29,13 +29,6 @@ #include #include "llist.h" -enum alpnid { - ALPN_none = 0, - ALPN_h1 = CURLALTSVC_H1, - ALPN_h2 = CURLALTSVC_H2, - ALPN_h3 = CURLALTSVC_H3 -}; - struct althost { char *host; unsigned short port; @@ -46,9 +39,9 @@ struct altsvc { struct althost src; struct althost dst; time_t expires; - bool persist; - int prio; - struct Curl_llist_element node; + struct Curl_llist_node node; + unsigned int prio; + BIT(persist); }; struct altsvcinfo { diff --git a/Utilities/cmcurl/lib/amigaos.c b/Utilities/cmcurl/lib/amigaos.c index b0a95002672..ac6d6b41937 100644 --- a/Utilities/cmcurl/lib/amigaos.c +++ b/Utilities/cmcurl/lib/amigaos.c @@ -117,7 +117,7 @@ void Curl_amiga_cleanup(void) #ifdef CURLRES_AMIGA /* - * Because we need to handle the different cases in hostip4.c at run-time, + * Because we need to handle the different cases in hostip4.c at runtime, * not at compile-time, based on what was detected in Curl_amiga_init(), * we replace it completely with our own as to not complicate the baseline * code. Assumes malloc/calloc/free are thread safe because Curl_he2ai() @@ -178,12 +178,13 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, #endif /* CURLRES_AMIGA */ #ifdef USE_AMISSL +#include int Curl_amiga_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) { int r = WaitSelect(nfds, readfds, writefds, errorfds, timeout, 0); /* Ensure Ctrl-C signal is actioned */ - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) raise(SIGINT); return r; } @@ -195,12 +196,11 @@ int Curl_amiga_select(int nfds, fd_set *readfds, fd_set *writefds, */ struct Library *SocketBase = NULL; -extern int errno, h_errno; #ifdef __libnix__ void __request(const char *msg); #else -# define __request(msg) Printf(msg "\n\a") +# define __request(msg) Printf((const unsigned char *)(msg "\n\a"), 0) #endif void Curl_amiga_cleanup(void) @@ -214,7 +214,7 @@ void Curl_amiga_cleanup(void) CURLcode Curl_amiga_init(void) { if(!SocketBase) - SocketBase = OpenLibrary("bsdsocket.library", 4); + SocketBase = OpenLibrary((const unsigned char *)"bsdsocket.library", 4); if(!SocketBase) { __request("No TCP/IP Stack running!"); diff --git a/Utilities/cmcurl/lib/amigaos.h b/Utilities/cmcurl/lib/amigaos.h index 7997ede80e8..c99d963ecec 100644 --- a/Utilities/cmcurl/lib/amigaos.h +++ b/Utilities/cmcurl/lib/amigaos.h @@ -39,4 +39,3 @@ void Curl_amiga_cleanup(void); #endif #endif /* HEADER_CURL_AMIGAOS_H */ - diff --git a/Utilities/cmcurl/lib/arpa_telnet.h b/Utilities/cmcurl/lib/arpa_telnet.h index de1373800ce..d641a01da81 100644 --- a/Utilities/cmcurl/lib/arpa_telnet.h +++ b/Utilities/cmcurl/lib/arpa_telnet.h @@ -56,12 +56,14 @@ static const char * const telnetoptions[]= "TERM SPEED", "LFLOW", "LINEMODE", "XDISPLOC", "OLD-ENVIRON", "AUTHENTICATION", "ENCRYPT", "NEW-ENVIRON" }; +#define CURL_TELOPT(x) telnetoptions[x] +#else +#define CURL_TELOPT(x) "" #endif #define CURL_TELOPT_MAXIMUM CURL_TELOPT_NEW_ENVIRON #define CURL_TELOPT_OK(x) ((x) <= CURL_TELOPT_MAXIMUM) -#define CURL_TELOPT(x) telnetoptions[x] #define CURL_NTELOPTS 40 @@ -75,7 +77,7 @@ static const char * const telnetoptions[]= #define CURL_GA 249 /* Go Ahead, reverse the line */ #define CURL_SB 250 /* SuBnegotiation */ #define CURL_WILL 251 /* Our side WILL use this option */ -#define CURL_WONT 252 /* Our side WON'T use this option */ +#define CURL_WONT 252 /* Our side will not use this option */ #define CURL_DO 253 /* DO use this option! */ #define CURL_DONT 254 /* DON'T use this option! */ #define CURL_IAC 255 /* Interpret As Command */ @@ -103,7 +105,12 @@ static const char * const telnetcmds[]= #define CURL_TELCMD_OK(x) ( ((unsigned int)(x) >= CURL_TELCMD_MINIMUM) && \ ((unsigned int)(x) <= CURL_TELCMD_MAXIMUM) ) + +#ifndef CURL_DISABLE_VERBOSE_STRINGS #define CURL_TELCMD(x) telnetcmds[(x)-CURL_TELCMD_MINIMUM] +#else +#define CURL_TELCMD(x) "" +#endif #endif /* CURL_DISABLE_TELNET */ diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/asyn-ares.c index 19fe8536b02..10f870a15af 100644 --- a/Utilities/cmcurl/lib/asyn-ares.c +++ b/Utilities/cmcurl/lib/asyn-ares.c @@ -24,14 +24,14 @@ #include "curl_setup.h" +#ifdef CURLRES_ARES + /*********************************************************************** * Only for ares-enabled builds * And only for functions that fulfill the asynch resolver backend API * as defined in asyn.h, nothing else belongs in this file! **********************************************************************/ -#ifdef CURLRES_ARES - #include #ifdef HAVE_NETINET_IN_H #include @@ -54,24 +54,17 @@ #include "share.h" #include "url.h" #include "multiif.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "connect.h" #include "select.h" #include "progress.h" -#include "timediff.h" - -# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ - defined(WIN32) -# define CARES_STATICLIB -# endif -# include -# include /* really old c-ares didn't include this by - itself */ - -#if ARES_VERSION >= 0x010500 -/* c-ares 1.5.0 or later, the callback proto is modified */ -#define HAVE_CARES_CALLBACK_TIMEOUTS 1 -#endif +#include "curlx/timediff.h" +#include "httpsrr.h" +#include "strdup.h" + +#include +#include /* really old c-ares did not include this by + itself */ #if ARES_VERSION >= 0x010601 /* IPv6 supported since 1.6.1 */ @@ -93,55 +86,59 @@ #define HAVE_CARES_GETADDRINFO 1 #endif +#ifdef USE_HTTPSRR +#if ARES_VERSION < 0x011c00 +#error "requires c-ares 1.28.0 or newer for HTTPSRR" +#endif +#define HTTPSRR_WORKS +#endif + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -struct thread_data { - int num_pending; /* number of outstanding c-ares requests */ - struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares - parts */ - int last_status; -#ifndef HAVE_CARES_GETADDRINFO - struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ -#endif - char hostname[1]; -}; - /* How long we are willing to wait for additional parallel responses after - obtaining a "definitive" one. + obtaining a "definitive" one. For old c-ares without getaddrinfo. - This is intended to equal the c-ares default timeout. cURL always uses that - default value. Unfortunately, c-ares doesn't expose its default timeout in + This is intended to equal the c-ares default timeout. cURL always uses that + default value. Unfortunately, c-ares does not expose its default timeout in its API, but it is officially documented as 5 seconds. See query_completed_cb() for an explanation of how this is used. */ #define HAPPY_EYEBALLS_DNS_TIMEOUT 5000 +#define CARES_TIMEOUT_PER_ATTEMPT 2000 + +static int ares_ver = 0; + +static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, + bool reset_on_null); + /* - * Curl_resolver_global_init() - the generic low-level asynchronous name - * resolve API. Called from curl_global_init() to initialize global resolver - * environment. Initializes ares library. + * Curl_async_global_init() - the generic low-level asynchronous name + * resolve API. Called from curl_global_init() to initialize global resolver + * environment. Initializes ares library. */ -int Curl_resolver_global_init(void) +int Curl_async_global_init(void) { #ifdef CARES_HAVE_ARES_LIBRARY_INIT if(ares_library_init(ARES_LIB_INIT_ALL)) { return CURLE_FAILED_INIT; } #endif + ares_version(&ares_ver); return CURLE_OK; } /* - * Curl_resolver_global_cleanup() + * Curl_async_global_cleanup() * * Called from curl_global_cleanup() to destroy global resolver environment. * Deinitializes ares library. */ -void Curl_resolver_global_cleanup(void) +void Curl_async_global_cleanup(void) { #ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP ares_library_cleanup(); @@ -155,267 +152,224 @@ static void sock_state_cb(void *data, ares_socket_t socket_fd, struct Curl_easy *easy = data; if(!readable && !writable) { DEBUGASSERT(easy); - Curl_multi_closed(easy, socket_fd); + Curl_multi_will_close(easy, socket_fd); } } -/* - * Curl_resolver_init() - * - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Fills the passed pointer by the initialized ares_channel. - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) +static CURLcode async_ares_init(struct Curl_easy *data) { + struct async_ares_ctx *ares = &data->state.async.ares; int status; struct ares_options options; int optmask = ARES_OPT_SOCK_STATE_CB; + CURLcode rc = CURLE_OK; + options.sock_state_cb = sock_state_cb; - options.sock_state_cb_data = easy; - status = ares_init_options((ares_channel*)resolver, &options, optmask); + options.sock_state_cb_data = data; + + DEBUGASSERT(!ares->channel); + /* + if c ares < 1.20.0: curl set timeout to CARES_TIMEOUT_PER_ATTEMPT (2s) + + if c-ares >= 1.20.0 it already has the timeout to 2s, curl does not need + to set the timeout value; + + if c-ares >= 1.24.0, user can set the timeout via /etc/resolv.conf to + overwrite c-ares' timeout. + */ + DEBUGASSERT(ares_ver); + if(ares_ver < 0x011400) { + options.timeout = CARES_TIMEOUT_PER_ATTEMPT; + optmask |= ARES_OPT_TIMEOUTMS; + } + + status = ares_init_options(&ares->channel, &options, optmask); if(status != ARES_SUCCESS) { - if(status == ARES_ENOMEM) - return CURLE_OUT_OF_MEMORY; - else - return CURLE_FAILED_INIT; + ares->channel = NULL; + rc = (status == ARES_ENOMEM) ? + CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT; + goto out; } - return CURLE_OK; - /* make sure that all other returns from this function should destroy the - ares channel before returning error! */ -} -/* - * Curl_resolver_cleanup() - * - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Destroys the ares channel. - */ -void Curl_resolver_cleanup(void *resolver) -{ - ares_destroy((ares_channel)resolver); + rc = async_ares_set_dns_servers(data, FALSE); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = Curl_async_ares_set_dns_interface(data); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = Curl_async_ares_set_dns_local_ip4(data); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = Curl_async_ares_set_dns_local_ip6(data); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = CURLE_OK; + +out: + if(rc && ares->channel) { + ares_destroy(ares->channel); + ares->channel = NULL; + } + return rc; } -/* - * Curl_resolver_duphandle() - * - * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * environment ('resolver' member of the UrlState structure). Duplicates the - * 'from' ares channel and passes the resulting channel to the 'to' pointer. - */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) +static CURLcode async_ares_init_lazy(struct Curl_easy *data) { - (void)from; - /* - * it would be better to call ares_dup instead, but right now - * it is not possible to set 'sock_state_cb_data' outside of - * ares_init_options - */ - return Curl_resolver_init(easy, to); + struct async_ares_ctx *ares = &data->state.async.ares; + if(!ares->channel) + return async_ares_init(data); + return CURLE_OK; } -static void destroy_async_data(struct Curl_async *async); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct Curl_easy *data) +CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) { - DEBUGASSERT(data); - if(data->state.async.resolver) - ares_cancel((ares_channel)data->state.async.resolver); - destroy_async_data(&data->state.async); + struct async_ares_ctx *ares = &data->state.async.ares; + CURLcode result = CURLE_OK; + if(!ares->channel) { + result = async_ares_init(data); + } + *impl = ares->channel; + return result; } -/* - * We're equivalent to Curl_resolver_cancel() for the c-ares resolver. We - * never block. - */ -void Curl_resolver_kill(struct Curl_easy *data) +static void async_ares_cleanup(struct Curl_easy *data); + +void Curl_async_ares_shutdown(struct Curl_easy *data) { - /* We don't need to check the resolver state because we can be called safely - at any time and we always do the same thing. */ - Curl_resolver_cancel(data); + struct async_ares_ctx *ares = &data->state.async.ares; + if(ares->channel) + ares_cancel(ares->channel); + async_ares_cleanup(data); } -/* - * destroy_async_data() cleans up async resolver data. - */ -static void destroy_async_data(struct Curl_async *async) +void Curl_async_ares_destroy(struct Curl_easy *data) { - if(async->tdata) { - struct thread_data *res = async->tdata; - if(res) { - if(res->temp_ai) { - Curl_freeaddrinfo(res->temp_ai); - res->temp_ai = NULL; - } - free(res); - } - async->tdata = NULL; + struct async_ares_ctx *ares = &data->state.async.ares; + Curl_async_ares_shutdown(data); + if(ares->channel) { + ares_destroy(ares->channel); + ares->channel = NULL; } } /* - * Curl_resolver_getsock() is called when someone from the outside world - * (using curl_multi_fdset()) wants to get our fd_set setup and we're talking - * with ares. The caller must make sure that this function is only called when - * we have a working ares channel. - * - * Returns: sockets-in-use-bitmap + * async_ares_cleanup() cleans up async resolver data. */ - -int Curl_resolver_getsock(struct Curl_easy *data, - curl_socket_t *socks) +static void async_ares_cleanup(struct Curl_easy *data) { - struct timeval maxtime; - struct timeval timebuf; - struct timeval *timeout; - long milli; - int max = ares_getsock((ares_channel)data->state.async.resolver, - (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); - - maxtime.tv_sec = CURL_TIMEOUT_RESOLVE; - maxtime.tv_usec = 0; - - timeout = ares_timeout((ares_channel)data->state.async.resolver, &maxtime, - &timebuf); - milli = (long)curlx_tvtoms(timeout); - if(milli == 0) - milli += 10; - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); - - return max; + struct async_ares_ctx *ares = &data->state.async.ares; + if(ares->temp_ai) { + Curl_freeaddrinfo(ares->temp_ai); + ares->temp_ai = NULL; + } +#ifdef USE_HTTPSRR + Curl_httpsrr_cleanup(&ares->hinfo); +#endif } /* - * waitperform() - * - * 1) Ask ares what sockets it currently plays with, then - * 2) wait for the timeout period to check for action on ares' sockets. - * 3) tell ares to act on all the sockets marked as "with action" - * - * return number of sockets it worked on, or -1 on error + * Curl_async_getsock() is called when someone from the outside world + * (using curl_multi_fdset()) wants to get our fd_set setup. */ -static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *socks) { - int nfds; - int bitmask; - ares_socket_t socks[ARES_GETSOCK_MAXNUM]; - struct pollfd pfd[ARES_GETSOCK_MAXNUM]; - int i; - int num = 0; - - bitmask = ares_getsock((ares_channel)data->state.async.resolver, socks, - ARES_GETSOCK_MAXNUM); - - for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { - pfd[i].events = 0; - pfd[i].revents = 0; - if(ARES_GETSOCK_READABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLRDNORM|POLLIN; - } - if(ARES_GETSOCK_WRITABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLWRNORM|POLLOUT; - } - if(pfd[i].events) - num++; - else - break; - } - - if(num) { - nfds = Curl_poll(pfd, num, timeout_ms); - if(nfds < 0) - return -1; - } - else - nfds = 0; - - if(!nfds) - /* Call ares_process() unconditionally here, even if we simply timed out - above, as otherwise the ares name resolve won't timeout! */ - ares_process_fd((ares_channel)data->state.async.resolver, ARES_SOCKET_BAD, - ARES_SOCKET_BAD); - else { - /* move through the descriptors and ask for processing on them */ - for(i = 0; i < num; i++) - ares_process_fd((ares_channel)data->state.async.resolver, - (pfd[i].revents & (POLLRDNORM|POLLIN))? - pfd[i].fd:ARES_SOCKET_BAD, - (pfd[i].revents & (POLLWRNORM|POLLOUT))? - pfd[i].fd:ARES_SOCKET_BAD); - } - return nfds; + struct async_ares_ctx *ares = &data->state.async.ares; + DEBUGASSERT(ares->channel); + return Curl_ares_getsock(data, ares->channel, socks); } /* - * Curl_resolver_is_resolved() is called repeatedly to check if a previous + * Curl_async_is_resolved() is called repeatedly to check if a previous * name resolve request has completed. It should also make sure to time-out if * the operation seems to take too long. * * Returns normal CURLcode errors. */ -CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **dns) +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns) { - struct thread_data *res = data->state.async.tdata; + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_OK; DEBUGASSERT(dns); *dns = NULL; - if(waitperform(data, 0) < 0) + if(data->state.async.done) { + *dns = data->state.async.dns; + return CURLE_OK; + } + + if(Curl_ares_perform(ares->channel, 0) < 0) return CURLE_UNRECOVERABLE_POLL; #ifndef HAVE_CARES_GETADDRINFO - /* Now that we've checked for any last minute results above, see if there are - any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer + /* Now that we have checked for any last minute results above, see if there + are any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer expires. */ - if(res - && res->num_pending + if(ares->num_pending /* This is only set to non-zero if the timer was started. */ - && (res->happy_eyeballs_dns_time.tv_sec - || res->happy_eyeballs_dns_time.tv_usec) - && (Curl_timediff(Curl_now(), res->happy_eyeballs_dns_time) + && (ares->happy_eyeballs_dns_time.tv_sec + || ares->happy_eyeballs_dns_time.tv_usec) + && (curlx_timediff(curlx_now(), ares->happy_eyeballs_dns_time) >= HAPPY_EYEBALLS_DNS_TIMEOUT)) { /* Remember that the EXPIRE_HAPPY_EYEBALLS_DNS timer is no longer running. */ - memset( - &res->happy_eyeballs_dns_time, 0, sizeof(res->happy_eyeballs_dns_time)); + memset(&ares->happy_eyeballs_dns_time, 0, + sizeof(ares->happy_eyeballs_dns_time)); /* Cancel the raw c-ares request, which will fire query_completed_cb() with - ARES_ECANCELLED synchronously for all pending responses. This will + ARES_ECANCELLED synchronously for all pending responses. This will leave us with res->num_pending == 0, which is perfect for the next block. */ - ares_cancel((ares_channel)data->state.async.resolver); - DEBUGASSERT(res->num_pending == 0); + ares_cancel(ares->channel); + DEBUGASSERT(ares->num_pending == 0); } #endif - if(res && !res->num_pending) { - (void)Curl_addrinfo_callback(data, res->last_status, res->temp_ai); - /* temp_ai ownership is moved to the connection, so we need not free-up - them */ - res->temp_ai = NULL; - - if(!data->state.async.dns) + if(!ares->num_pending) { + /* all c-ares operations done, what is the result to report? */ + Curl_resolv_unlink(data, &data->state.async.dns); + data->state.async.done = TRUE; + result = ares->result; + if(ares->last_status == CURL_ASYNC_SUCCESS && !result) { + data->state.async.dns = + Curl_dnscache_mk_entry(data, ares->temp_ai, + data->state.async.hostname, 0, + data->state.async.port, FALSE); + ares->temp_ai = NULL; /* temp_ai now owned by entry */ +#ifdef HTTPSRR_WORKS + if(data->state.async.dns) { + struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); + if(!lhrr) + result = CURLE_OUT_OF_MEMORY; + else + data->state.async.dns->hinfo = lhrr; + } +#endif + if(!result && data->state.async.dns) + result = Curl_dnscache_add(data, data->state.async.dns); + } + /* if we have not found anything, report the proper + * CURLE_COULDNT_RESOLVE_* code */ + if(!result && !data->state.async.dns) result = Curl_resolver_error(data); - else - *dns = data->state.async.dns; - - destroy_async_data(&data->state.async); + if(result) + Curl_resolv_unlink(data, &data->state.async.dns); + *dns = data->state.async.dns; + CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", + result, *dns ? "" : "not "); + async_ares_cleanup(data); } - return result; } /* - * Curl_resolver_wait_resolv() + * Curl_async_await() * * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". @@ -425,12 +379,13 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry) +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **entry) { + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_OK; timediff_t timeout; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); DEBUGASSERT(entry); *entry = NULL; /* clear on entry */ @@ -459,7 +414,7 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, store.tv_sec = itimeout/1000; store.tv_usec = (itimeout%1000)*1000; - tvp = ares_timeout((ares_channel)data->state.async.resolver, &store, &tv); + tvp = ares_timeout(ares->channel, &store, &tv); /* use the timeout period ares returned to us above if less than one second is left, otherwise just use 1000ms to make sure the progress @@ -469,18 +424,18 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, else timeout_ms = 1000; - if(waitperform(data, timeout_ms) < 0) + if(Curl_ares_perform(ares->channel, timeout_ms) < 0) return CURLE_UNRECOVERABLE_POLL; - result = Curl_resolver_is_resolved(data, entry); + result = Curl_async_is_resolved(data, entry); if(result || data->state.async.done) break; if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else { - struct curltime now2 = Curl_now(); - timediff_t timediff = Curl_timediff(now2, now); /* spent time */ + struct curltime now2 = curlx_now(); + timediff_t timediff = curlx_timediff(now2, now); /* spent time */ if(timediff <= 0) timeout -= 1; /* always deduct at least 1 */ else if(timediff > timeout) @@ -492,163 +447,155 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, if(timeout < 0) result = CURLE_OPERATION_TIMEDOUT; } - if(result) - /* failure, so we cancel the ares operation */ - ares_cancel((ares_channel)data->state.async.resolver); /* Operation complete, if the lookup was successful we now have the entry in the cache. */ + data->state.async.done = TRUE; if(entry) *entry = data->state.async.dns; if(result) - /* close the connection, since we can't return failure here without - cleaning up this connection properly. */ - connclose(data->conn, "c-ares resolve failed"); - + ares_cancel(ares->channel); return result; } #ifndef HAVE_CARES_GETADDRINFO /* Connects results to the list */ -static void compound_results(struct thread_data *res, - struct Curl_addrinfo *ai) +static void async_addr_concat(struct Curl_addrinfo **pbase, + struct Curl_addrinfo *ai) { if(!ai) return; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ - if(res->temp_ai && res->temp_ai->ai_family == PF_INET6) { - /* We have results already, put the new IPv6 entries at the head of the - list. */ - struct Curl_addrinfo *temp_ai_tail = res->temp_ai; - - while(temp_ai_tail->ai_next) - temp_ai_tail = temp_ai_tail->ai_next; - - temp_ai_tail->ai_next = ai; + /* When adding `ai` to an existing address list, we prefer ipv6 + * to be in front. */ +#ifdef USE_IPV6 /* CURLRES_IPV6 */ + if(*pbase && (*pbase)->ai_family == PF_INET6) { + /* ipv6 already in front, append `ai` */ + struct Curl_addrinfo *tail = *pbase; + while(tail->ai_next) + tail = tail->ai_next; + tail->ai_next = ai; } else #endif /* CURLRES_IPV6 */ { - /* Add the new results to the list of old results. */ - struct Curl_addrinfo *ai_tail = ai; - while(ai_tail->ai_next) - ai_tail = ai_tail->ai_next; - - ai_tail->ai_next = res->temp_ai; - res->temp_ai = ai; + /* prepend to the (possibly) existing list. */ + struct Curl_addrinfo *tail = ai; + while(tail->ai_next) + tail = tail->ai_next; + tail->ai_next = *pbase; + *pbase = ai; } } /* * ares_query_completed_cb() is the callback that ares will call when - * the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(), - * when using ares, is completed either successfully or with failure. + * the host query initiated by ares_gethostbyname() from + * Curl_async_getaddrinfo(), when using ares, is completed either + * successfully or with failure. */ -static void query_completed_cb(void *arg, /* (struct connectdata *) */ - int status, -#ifdef HAVE_CARES_CALLBACK_TIMEOUTS - int timeouts, -#endif - struct hostent *hostent) +static void async_ares_hostbyname_cb(void *user_data, + int status, + int timeouts, + struct hostent *hostent) { - struct Curl_easy *data = (struct Curl_easy *)arg; - struct thread_data *res; + struct Curl_easy *data = (struct Curl_easy *)user_data; + struct async_ares_ctx *ares = &data->state.async.ares; -#ifdef HAVE_CARES_CALLBACK_TIMEOUTS (void)timeouts; /* ignored */ -#endif if(ARES_EDESTRUCTION == status) /* when this ares handle is getting destroyed, the 'arg' pointer may not be valid so only defer it when we know the 'status' says its fine! */ return; - res = data->state.async.tdata; - if(res) { - res->num_pending--; + if(CURL_ASYNC_SUCCESS == status) { + ares->last_status = status; /* one success overrules any error */ + async_addr_concat(&ares->temp_ai, + Curl_he2ai(hostent, data->state.async.port)); + } + else if(ares->last_status != ARES_SUCCESS) { + /* no success so far, remember error */ + ares->last_status = status; + } - if(CURL_ASYNC_SUCCESS == status) { - struct Curl_addrinfo *ai = Curl_he2ai(hostent, data->state.async.port); - if(ai) { - compound_results(res, ai); - } - } - /* A successful result overwrites any previous error */ - if(res->last_status != ARES_SUCCESS) - res->last_status = status; - - /* If there are responses still pending, we presume they must be the - complementary IPv4 or IPv6 lookups that we started in parallel in - Curl_resolver_getaddrinfo() (for Happy Eyeballs). If we've got a - "definitive" response from one of a set of parallel queries, we need to - think about how long we're willing to wait for more responses. */ - if(res->num_pending - /* Only these c-ares status values count as "definitive" for these - purposes. For example, ARES_ENODATA is what we expect when there is - no IPv6 entry for a domain name, and that's not a reason to get more - aggressive in our timeouts for the other response. Other errors are - either a result of bad input (which should affect all parallel - requests), local or network conditions, non-definitive server - responses, or us cancelling the request. */ - && (status == ARES_SUCCESS || status == ARES_ENOTFOUND)) { - /* Right now, there can only be up to two parallel queries, so don't - bother handling any other cases. */ - DEBUGASSERT(res->num_pending == 1); - - /* It's possible that one of these parallel queries could succeed - quickly, but the other could always fail or timeout (when we're - talking to a pool of DNS servers that can only successfully resolve - IPv4 address, for example). - - It's also possible that the other request could always just take - longer because it needs more time or only the second DNS server can - fulfill it successfully. But, to align with the philosophy of Happy - Eyeballs, we don't want to wait _too_ long or users will think - requests are slow when IPv6 lookups don't actually work (but IPv4 ones - do). - - So, now that we have a usable answer (some IPv4 addresses, some IPv6 - addresses, or "no such domain"), we start a timeout for the remaining - pending responses. Even though it is typical that this resolved - request came back quickly, that needn't be the case. It might be that - this completing request didn't get a result from the first DNS server - or even the first round of the whole DNS server pool. So it could - already be quite some time after we issued the DNS queries in the - first place. Without modifying c-ares, we can't know exactly where in - its retry cycle we are. We could guess based on how much time has - gone by, but it doesn't really matter. Happy Eyeballs tells us that, - given usable information in hand, we simply don't want to wait "too - much longer" after we get a result. - - We simply wait an additional amount of time equal to the default - c-ares query timeout. That is enough time for a typical parallel - response to arrive without being "too long". Even on a network - where one of the two types of queries is failing or timing out - constantly, this will usually mean we wait a total of the default - c-ares timeout (5 seconds) plus the round trip time for the successful - request, which seems bearable. The downside is that c-ares might race - with us to issue one more retry just before we give up, but it seems - better to "waste" that request instead of trying to guess the perfect - timeout to prevent it. After all, we don't even know where in the - c-ares retry cycle each request is. - */ - res->happy_eyeballs_dns_time = Curl_now(); - Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, - EXPIRE_HAPPY_EYEBALLS_DNS); - } + ares->num_pending--; + + CURL_TRC_DNS(data, "ares: hostbyname done, status=%d, pending=%d, " + "addr=%sfound", + status, ares->num_pending, ares->temp_ai ? "" : "not "); + /* If there are responses still pending, we presume they must be the + complementary IPv4 or IPv6 lookups that we started in parallel in + Curl_async_getaddrinfo() (for Happy Eyeballs). If we have got a + "definitive" response from one of a set of parallel queries, we need to + think about how long we are willing to wait for more responses. */ + if(ares->num_pending + /* Only these c-ares status values count as "definitive" for these + purposes. For example, ARES_ENODATA is what we expect when there is + no IPv6 entry for a domain name, and that is not a reason to get more + aggressive in our timeouts for the other response. Other errors are + either a result of bad input (which should affect all parallel + requests), local or network conditions, non-definitive server + responses, or us cancelling the request. */ + && (status == ARES_SUCCESS || status == ARES_ENOTFOUND)) { + /* Right now, there can only be up to two parallel queries, so do not + bother handling any other cases. */ + DEBUGASSERT(ares->num_pending == 1); + + /* it is possible that one of these parallel queries could succeed + quickly, but the other could always fail or timeout (when we are + talking to a pool of DNS servers that can only successfully resolve + IPv4 address, for example). + + it is also possible that the other request could always just take + longer because it needs more time or only the second DNS server can + fulfill it successfully. But, to align with the philosophy of Happy + Eyeballs, we do not want to wait _too_ long or users will think + requests are slow when IPv6 lookups do not actually work (but IPv4 + ones do). + + So, now that we have a usable answer (some IPv4 addresses, some IPv6 + addresses, or "no such domain"), we start a timeout for the remaining + pending responses. Even though it is typical that this resolved + request came back quickly, that needn't be the case. It might be that + this completing request did not get a result from the first DNS + server or even the first round of the whole DNS server pool. So it + could already be quite some time after we issued the DNS queries in + the first place. Without modifying c-ares, we cannot know exactly + where in its retry cycle we are. We could guess based on how much + time has gone by, but it does not really matter. Happy Eyeballs tells + us that, given usable information in hand, we simply do not want to + wait "too much longer" after we get a result. + + We simply wait an additional amount of time equal to the default + c-ares query timeout. That is enough time for a typical parallel + response to arrive without being "too long". Even on a network + where one of the two types of queries is failing or timing out + constantly, this will usually mean we wait a total of the default + c-ares timeout (5 seconds) plus the round trip time for the successful + request, which seems bearable. The downside is that c-ares might race + with us to issue one more retry just before we give up, but it seems + better to "waste" that request instead of trying to guess the perfect + timeout to prevent it. After all, we do not even know where in the + c-ares retry cycle each request is. + */ + ares->happy_eyeballs_dns_time = curlx_now(); + Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, + EXPIRE_HAPPY_EYEBALLS_DNS); } } + #else /* c-ares 1.16.0 or later */ /* - * ares2addr() converts an address list provided by c-ares to an internal - * libcurl compatible list + * async_ares_node2addr() converts an address list provided by c-ares + * to an internal libcurl compatible list. */ -static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) +static struct Curl_addrinfo * +async_ares_node2addr(struct ares_addrinfo_node *node) { /* traverse the ares_addrinfo_node list */ struct ares_addrinfo_node *ai; @@ -663,7 +610,7 @@ static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) /* settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -718,124 +665,183 @@ static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) return cafirst; } -static void addrinfo_cb(void *arg, int status, int timeouts, - struct ares_addrinfo *result) +static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts, + struct ares_addrinfo *result) { - struct Curl_easy *data = (struct Curl_easy *)arg; - struct thread_data *res = data->state.async.tdata; + struct Curl_easy *data = (struct Curl_easy *)user_data; + struct async_ares_ctx *ares = &data->state.async.ares; (void)timeouts; + CURL_TRC_DNS(data, "asyn-ares: addrinfo callback, status=%d", status); if(ARES_SUCCESS == status) { - res->temp_ai = ares2addr(result->nodes); - res->last_status = CURL_ASYNC_SUCCESS; + ares->temp_ai = async_ares_node2addr(result->nodes); + ares->last_status = CURL_ASYNC_SUCCESS; ares_freeaddrinfo(result); } - res->num_pending--; + ares->num_pending--; + CURL_TRC_DNS(data, "ares: addrinfo done, status=%d, pending=%d, " + "addr=%sfound", + status, ares->num_pending, ares->temp_ai ? "" : "not "); } #endif + +#ifdef USE_HTTPSRR +static void async_ares_rr_done(void *user_data, ares_status_t status, + size_t timeouts, + const ares_dns_record_t *dnsrec) +{ + struct Curl_easy *data = user_data; + struct async_ares_ctx *ares = &data->state.async.ares; + + (void)timeouts; + --ares->num_pending; + CURL_TRC_DNS(data, "ares: httpsrr done, status=%d, pending=%d, " + "dnsres=%sfound", + status, ares->num_pending, + (dnsrec && + ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER)) ? + "" : "not "); + if((ARES_SUCCESS != status) || !dnsrec) + return; + ares->result = Curl_httpsrr_from_ares(data, dnsrec, &ares->hinfo); +} +#endif /* USE_HTTPSRR */ + /* - * Curl_resolver_getaddrinfo() - when using ares + * Curl_async_getaddrinfo() - when using ares * * Returns name information about the given hostname and port number. If * successful, the 'hostent' is returned and the fourth argument will point to * memory we need to free after use. That memory *MUST* be freed with * Curl_freeaddrinfo(), nothing else. */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) { - struct thread_data *res = NULL; - size_t namelen = strlen(hostname); + struct async_ares_ctx *ares = &data->state.async.ares; *waitp = 0; /* default to synchronous response */ - res = calloc(sizeof(struct thread_data) + namelen, 1); - if(res) { - strcpy(res->hostname, hostname); - data->state.async.hostname = res->hostname; - data->state.async.port = port; - data->state.async.done = FALSE; /* not done */ - data->state.async.status = 0; /* clear */ - data->state.async.dns = NULL; /* clear */ - data->state.async.tdata = res; + if(async_ares_init_lazy(data)) + return NULL; - /* initial status - failed */ - res->last_status = ARES_ENOTFOUND; + data->state.async.done = FALSE; /* not done */ + data->state.async.dns = NULL; /* clear */ + data->state.async.port = port; + data->state.async.ip_version = ip_version; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + return NULL; + + /* initial status - failed */ + ares->last_status = ARES_ENOTFOUND; #ifdef HAVE_CARES_GETADDRINFO - { - struct ares_addrinfo_hints hints; - char service[12]; - int pf = PF_INET; - memset(&hints, 0, sizeof(hints)); + { + struct ares_addrinfo_hints hints; + char service[12]; + int pf = PF_INET; + memset(&hints, 0, sizeof(hints)); #ifdef CURLRES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) - /* The stack seems to be IPv6-enabled */ + if((ip_version != CURL_IPRESOLVE_V4) && + Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + if(ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; + else pf = PF_UNSPEC; -#endif /* CURLRES_IPV6 */ - hints.ai_family = pf; - hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP)? - SOCK_STREAM : SOCK_DGRAM; - /* Since the service is a numerical one, set the hint flags - * accordingly to save a call to getservbyname in inside C-Ares - */ - hints.ai_flags = ARES_AI_NUMERICSERV; - msnprintf(service, sizeof(service), "%d", port); - res->num_pending = 1; - ares_getaddrinfo((ares_channel)data->state.async.resolver, hostname, - service, &hints, addrinfo_cb, data); } +#endif /* CURLRES_IPV6 */ + CURL_TRC_DNS(data, "asyn-ares: fire off getaddrinfo for %s", + (pf == PF_UNSPEC) ? "A+AAAA" : + ((pf == PF_INET) ? "A" : "AAAA")); + hints.ai_family = pf; + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? + SOCK_STREAM : SOCK_DGRAM; + /* Since the service is a numerical one, set the hint flags + * accordingly to save a call to getservbyname in inside C-Ares + */ + hints.ai_flags = ARES_AI_NUMERICSERV; + msnprintf(service, sizeof(service), "%d", port); + ares->num_pending = 1; + ares_getaddrinfo(ares->channel, data->state.async.hostname, + service, &hints, async_ares_addrinfo_cb, data); + } #else #ifdef HAVE_CARES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { - /* The stack seems to be IPv6-enabled */ - res->num_pending = 2; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET, query_completed_cb, data); - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET6, query_completed_cb, data); - } - else + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + /* areschannel is already setup in the Curl_open() function */ + CURL_TRC_DNS(data, "asyn-ares: fire off query for A"); + ares_gethostbyname(ares->channel, hostname, PF_INET, + async_ares_hostbyname_cb, data); + CURL_TRC_DNS(data, "asyn-ares: fire off query for AAAA"); + ares->num_pending = 2; + ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET6, + async_ares_hostbyname_cb, data); + } + else #endif - { - res->num_pending = 1; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, - hostname, PF_INET, - query_completed_cb, data); - } + { + /* areschannel is already setup in the Curl_open() function */ + CURL_TRC_DNS(data, "asyn-ares: fire off query for A"); + ares->num_pending = 1; + ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET, + async_ares_hostbyname_cb, data); + } #endif - *waitp = 1; /* expect asynchronous response */ +#ifdef USE_HTTPSRR + { + CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR"); + memset(&ares->hinfo, 0, sizeof(ares->hinfo)); + ares->hinfo.port = -1; + ares->num_pending++; /* one more */ + ares_query_dnsrec(ares->channel, data->state.async.hostname, + ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, + async_ares_rr_done, data, NULL); } +#endif + *waitp = 1; /* expect asynchronous response */ + return NULL; /* no struct yet */ } -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) +/* Set what DNS server are is to use. This is called in 2 situations: + * 1. when the application does 'CURLOPT_DNS_SERVERS' and passing NULL + * means any previous set value should be unset. Which means + * we need to destroy and create the are channel anew, if there is one. + * 2. When we lazy init the ares channel and NULL means that there + * are no preferences and we do not reset any existing channel. */ +static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, + bool reset_on_null) { + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_NOT_BUILT_IN; - int ares_result; - - /* If server is NULL or empty, this would purge all DNS servers - * from ares library, which will cause any and all queries to fail. - * So, just return OK if none are configured and don't actually make - * any changes to c-ares. This lets c-ares use it's defaults, which - * it gets from the OS (for instance from /etc/resolv.conf on Linux). - */ - if(!(servers && servers[0])) + const char *servers = data->set.str[STRING_DNS_SERVERS]; + int ares_result = ARES_SUCCESS; + +#if defined(CURLDEBUG) && defined(HAVE_CARES_SERVERS_CSV) + if(getenv("CURL_DNS_SERVER")) + servers = getenv("CURL_DNS_SERVER"); +#endif + + if(!servers) { + if(reset_on_null) { + Curl_async_destroy(data); + } return CURLE_OK; + } #ifdef HAVE_CARES_SERVERS_CSV + /* if channel is not there, this is just a parameter check */ + if(ares->channel) #ifdef HAVE_CARES_PORTS_CSV - ares_result = ares_set_servers_ports_csv(data->state.async.resolver, - servers); + ares_result = ares_set_servers_ports_csv(ares->channel, servers); #else - ares_result = ares_set_servers_csv(data->state.async.resolver, servers); + ares_result = ares_set_servers_csv(ares->channel, servers); #endif switch(ares_result) { case ARES_SUCCESS: @@ -848,6 +854,7 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, case ARES_ENODATA: case ARES_EBADSTR: default: + DEBUGF(infof(data, "bad servers set")); result = CURLE_BAD_FUNCTION_ARGUMENT; break; } @@ -858,14 +865,23 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, return result; } -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) +CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data) +{ + return async_ares_set_dns_servers(data, TRUE); +} + +CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data) { #ifdef HAVE_CARES_LOCAL_DEV + struct async_ares_ctx *ares = &data->state.async.ares; + const char *interf = data->set.str[STRING_DNS_INTERFACE]; + if(!interf) interf = ""; - ares_set_local_dev((ares_channel)data->state.async.resolver, interf); + /* if channel is not there, this is just a parameter check */ + if(ares->channel) + ares_set_local_dev(ares->channel, interf); return CURLE_OK; #else /* c-ares version too old! */ @@ -875,23 +891,26 @@ CURLcode Curl_set_dns_interface(struct Curl_easy *data, #endif } -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) +CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data) { #ifdef HAVE_CARES_SET_LOCAL + struct async_ares_ctx *ares = &data->state.async.ares; struct in_addr a4; + const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4]; if((!local_ip4) || (local_ip4[0] == 0)) { a4.s_addr = 0; /* disabled: do not bind to a specific address */ } else { - if(Curl_inet_pton(AF_INET, local_ip4, &a4) != 1) { + if(curlx_inet_pton(AF_INET, local_ip4, &a4) != 1) { + DEBUGF(infof(data, "bad DNS IPv4 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ares_set_local_ip4((ares_channel)data->state.async.resolver, - ntohl(a4.s_addr)); + /* if channel is not there yet, this is just a parameter check */ + if(ares->channel) + ares_set_local_ip4(ares->channel, ntohl(a4.s_addr)); return CURLE_OK; #else /* c-ares version too old! */ @@ -901,29 +920,33 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, #endif } -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) +CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data) { -#if defined(HAVE_CARES_SET_LOCAL) && defined(ENABLE_IPV6) +#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) + struct async_ares_ctx *ares = &data->state.async.ares; unsigned char a6[INET6_ADDRSTRLEN]; + const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6]; if((!local_ip6) || (local_ip6[0] == 0)) { /* disabled: do not bind to a specific address */ memset(a6, 0, sizeof(a6)); } else { - if(Curl_inet_pton(AF_INET6, local_ip6, a6) != 1) { + if(curlx_inet_pton(AF_INET6, local_ip6, a6) != 1) { + DEBUGF(infof(data, "bad DNS IPv6 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ares_set_local_ip6((ares_channel)data->state.async.resolver, a6); + /* if channel is not there, this is just a parameter check */ + if(ares->channel) + ares_set_local_ip6(ares->channel, a6); return CURLE_OK; #else /* c-ares version too old! */ (void)data; - (void)local_ip6; return CURLE_NOT_BUILT_IN; #endif } + #endif /* CURLRES_ARES */ diff --git a/Utilities/cmcurl/lib/asyn-base.c b/Utilities/cmcurl/lib/asyn-base.c new file mode 100644 index 00000000000..ea89fba15c3 --- /dev/null +++ b/Utilities/cmcurl/lib/asyn-base.c @@ -0,0 +1,196 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef USE_ARES +#include +#include /* really old c-ares did not include this by + itself */ +#endif + +#include "urldata.h" +#include "asyn.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "multiif.h" +#include "select.h" +#include "share.h" +#include "url.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/*********************************************************************** + * Only for builds using asynchronous name resolves + **********************************************************************/ +#ifdef CURLRES_ASYNCH + + +#ifdef USE_ARES + +#if ARES_VERSION < 0x010600 +#error "requires c-ares 1.6.0 or newer" +#endif + +/* + * Curl_ares_getsock() is called when the outside world (using + * curl_multi_fdset()) wants to get our fd_set setup and we are talking with + * ares. The caller must make sure that this function is only called when we + * have a working ares channel. + * + * Returns: sockets-in-use-bitmap + */ + +int Curl_ares_getsock(struct Curl_easy *data, + ares_channel channel, + curl_socket_t *socks) +{ + struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 }; + struct timeval timebuf; + int max = ares_getsock(channel, + (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); + struct timeval *timeout = ares_timeout(channel, &maxtime, &timebuf); + timediff_t milli = curlx_tvtoms(timeout); + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + return max; +} + +/* + * Curl_ares_perform() + * + * 1) Ask ares what sockets it currently plays with, then + * 2) wait for the timeout period to check for action on ares' sockets. + * 3) tell ares to act on all the sockets marked as "with action" + * + * return number of sockets it worked on, or -1 on error + */ +int Curl_ares_perform(ares_channel channel, + timediff_t timeout_ms) +{ + int nfds; + int bitmask; + ares_socket_t socks[ARES_GETSOCK_MAXNUM]; + struct pollfd pfd[ARES_GETSOCK_MAXNUM]; + int i; + int num = 0; + + if(!channel) + return 0; + + bitmask = ares_getsock(channel, socks, ARES_GETSOCK_MAXNUM); + + for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { + pfd[i].events = 0; + pfd[i].revents = 0; + if(ARES_GETSOCK_READABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLRDNORM|POLLIN; + } + if(ARES_GETSOCK_WRITABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLWRNORM|POLLOUT; + } + if(pfd[i].events) + num++; + else + break; + } + + if(num) { + nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms); + if(nfds < 0) + return -1; + } + else + nfds = 0; + + if(!nfds) + /* Call ares_process() unconditionally here, even if we simply timed out + above, as otherwise the ares name resolve will not timeout! */ + ares_process_fd(channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); + else { + /* move through the descriptors and ask for processing on them */ + for(i = 0; i < num; i++) + ares_process_fd(channel, + (pfd[i].revents & (POLLRDNORM|POLLIN)) ? + pfd[i].fd : ARES_SOCKET_BAD, + (pfd[i].revents & (POLLWRNORM|POLLOUT)) ? + pfd[i].fd : ARES_SOCKET_BAD); + } + return nfds; +} + +#endif + +#endif /* CURLRES_ASYNCH */ + +#ifdef USE_CURL_ASYNC + +#include "doh.h" + +void Curl_async_shutdown(struct Curl_easy *data) +{ +#ifdef CURLRES_ARES + Curl_async_ares_shutdown(data); +#endif +#ifdef CURLRES_THREADED + Curl_async_thrdd_shutdown(data); +#endif +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif + Curl_safefree(data->state.async.hostname); +} + +void Curl_async_destroy(struct Curl_easy *data) +{ +#ifdef CURLRES_ARES + Curl_async_ares_destroy(data); +#endif +#ifdef CURLRES_THREADED + Curl_async_thrdd_destroy(data); +#endif +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif + Curl_safefree(data->state.async.hostname); +} + +#endif /* USE_CURL_ASYNC */ diff --git a/Utilities/cmcurl/lib/asyn-thrdd.c b/Utilities/cmcurl/lib/asyn-thrdd.c new file mode 100644 index 00000000000..9cd25dcd209 --- /dev/null +++ b/Utilities/cmcurl/lib/asyn-thrdd.c @@ -0,0 +1,760 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "socketpair.h" + +/*********************************************************************** + * Only for threaded name resolves builds + **********************************************************************/ +#ifdef CURLRES_THREADED + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include +#endif + +#ifdef HAVE_GETADDRINFO +# define RESOLVER_ENOMEM EAI_MEMORY /* = WSA_NOT_ENOUGH_MEMORY on Windows */ +#else +# define RESOLVER_ENOMEM SOCKENOMEM +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "multiif.h" +#include "inet_ntop.h" +#include "curl_threads.h" +#include "strdup.h" + +#ifdef USE_ARES +#include +#ifdef USE_HTTPSRR +#define USE_HTTPSRR_ARES /* the combo */ +#endif +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/* + * Curl_async_global_init() + * Called from curl_global_init() to initialize global resolver environment. + * Does nothing here. + */ +int Curl_async_global_init(void) +{ +#if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) + if(ares_library_init(ARES_LIB_INIT_ALL)) { + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* + * Curl_async_global_cleanup() + * Called from curl_global_cleanup() to destroy global resolver environment. + * Does nothing here. + */ +void Curl_async_global_cleanup(void) +{ +#if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) + ares_library_cleanup(); +#endif +} + +static void async_thrdd_destroy(struct Curl_easy *); + +CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) +{ + (void)data; + *impl = NULL; + return CURLE_OK; +} + +/* Destroy context of threaded resolver */ +static void addr_ctx_destroy(struct async_thrdd_addr_ctx *addr_ctx) +{ + if(addr_ctx) { + DEBUGASSERT(!addr_ctx->ref_count); + Curl_mutex_destroy(&addr_ctx->mutx); + free(addr_ctx->hostname); + if(addr_ctx->res) + Curl_freeaddrinfo(addr_ctx->res); +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * close one end of the socket pair (may be done in resolver thread); + * the other end (for reading) is always closed in the parent thread. + */ +#ifndef USE_EVENTFD + if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) { + wakeup_close(addr_ctx->sock_pair[1]); + } +#endif +#endif + free(addr_ctx); + } +} + +/* Initialize context for threaded resolver */ +static struct async_thrdd_addr_ctx * +addr_ctx_create(const char *hostname, int port, + const struct addrinfo *hints) +{ + struct async_thrdd_addr_ctx *addr_ctx = calloc(1, sizeof(*addr_ctx)); + if(!addr_ctx) + return NULL; + + addr_ctx->thread_hnd = curl_thread_t_null; + addr_ctx->port = port; +#ifndef CURL_DISABLE_SOCKETPAIR + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + addr_ctx->sock_pair[1] = CURL_SOCKET_BAD; +#endif + addr_ctx->ref_count = 0; + +#ifdef HAVE_GETADDRINFO + DEBUGASSERT(hints); + addr_ctx->hints = *hints; +#else + (void) hints; +#endif + + Curl_mutex_init(&addr_ctx->mutx); + +#ifndef CURL_DISABLE_SOCKETPAIR + /* create socket pair or pipe */ + if(wakeup_create(addr_ctx->sock_pair, FALSE) < 0) { + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + addr_ctx->sock_pair[1] = CURL_SOCKET_BAD; + goto err_exit; + } +#endif + addr_ctx->sock_error = CURL_ASYNC_SUCCESS; + + /* Copying hostname string because original can be destroyed by parent + * thread during gethostbyname execution. + */ + addr_ctx->hostname = strdup(hostname); + if(!addr_ctx->hostname) + goto err_exit; + + addr_ctx->ref_count = 1; + return addr_ctx; + +err_exit: +#ifndef CURL_DISABLE_SOCKETPAIR + if(addr_ctx->sock_pair[0] != CURL_SOCKET_BAD) { + wakeup_close(addr_ctx->sock_pair[0]); + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + } +#endif + addr_ctx_destroy(addr_ctx); + return NULL; +} + +#ifdef HAVE_GETADDRINFO + +/* + * getaddrinfo_thread() resolves a name and then exits. + * + * For builds without ARES, but with USE_IPV6, create a resolver thread + * and wait on it. + */ +static +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) +DWORD +#else +unsigned int +#endif +CURL_STDCALL getaddrinfo_thread(void *arg) +{ + struct async_thrdd_addr_ctx *addr_ctx = arg; + char service[12]; + int rc; + bool all_gone; + + msnprintf(service, sizeof(service), "%d", addr_ctx->port); + + rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service, + &addr_ctx->hints, &addr_ctx->res); + + if(rc) { + addr_ctx->sock_error = SOCKERRNO ? SOCKERRNO : rc; + if(addr_ctx->sock_error == 0) + addr_ctx->sock_error = RESOLVER_ENOMEM; + } + else { + Curl_addrinfo_set_port(addr_ctx->res, addr_ctx->port); + } + + Curl_mutex_acquire(&addr_ctx->mutx); + if(addr_ctx->ref_count > 1) { + /* Someone still waiting on our results. */ +#ifndef CURL_DISABLE_SOCKETPAIR + if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) { +#ifdef USE_EVENTFD + const uint64_t buf[1] = { 1 }; +#else + const char buf[1] = { 1 }; +#endif + /* DNS has been resolved, signal client task */ + if(wakeup_write(addr_ctx->sock_pair[1], buf, sizeof(buf)) < 0) { + /* update sock_erro to errno */ + addr_ctx->sock_error = SOCKERRNO; + } + } +#endif + } + /* thread gives up its reference to the shared data now. */ + --addr_ctx->ref_count; + all_gone = !addr_ctx->ref_count; + Curl_mutex_release(&addr_ctx->mutx); + if(all_gone) + addr_ctx_destroy(addr_ctx); + + return 0; +} + +#else /* HAVE_GETADDRINFO */ + +/* + * gethostbyname_thread() resolves a name and then exits. + */ +static +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) +DWORD +#else +unsigned int +#endif +CURL_STDCALL gethostbyname_thread(void *arg) +{ + struct async_thrdd_addr_ctx *addr_ctx = arg; + bool all_gone; + + addr_ctx->res = Curl_ipv4_resolve_r(addr_ctx->hostname, addr_ctx->port); + + if(!addr_ctx->res) { + addr_ctx->sock_error = SOCKERRNO; + if(addr_ctx->sock_error == 0) + addr_ctx->sock_error = RESOLVER_ENOMEM; + } + + Curl_mutex_acquire(&addr_ctx->mutx); + /* thread gives up its reference to the shared data now. */ + --addr_ctx->ref_count; + all_gone = !addr_ctx->ref_count;; + Curl_mutex_release(&addr_ctx->mutx); + if(all_gone) + addr_ctx_destroy(addr_ctx); + + return 0; +} + +#endif /* HAVE_GETADDRINFO */ + +/* + * async_thrdd_destroy() cleans up async resolver data and thread handle. + */ +static void async_thrdd_destroy(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + struct async_thrdd_addr_ctx *addr = thrdd->addr; +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + ares_destroy(thrdd->rr.channel); + thrdd->rr.channel = NULL; + } + Curl_httpsrr_cleanup(&thrdd->rr.hinfo); +#endif + + if(addr) { +#ifndef CURL_DISABLE_SOCKETPAIR + curl_socket_t sock_rd = addr->sock_pair[0]; +#endif + bool done; + + /* Release our reference to the data shared with the thread. */ + Curl_mutex_acquire(&addr->mutx); + --addr->ref_count; + CURL_TRC_DNS(data, "resolve, destroy async data, shared ref=%d", + addr->ref_count); + done = !addr->ref_count; + /* we give up our reference to `addr`, so NULL our pointer. + * coverity analyses this as being a potential unsynched write, + * assuming two calls to this function could be invoked concurrently. + * Which they never are, as the transfer's side runs single-threaded. */ + thrdd->addr = NULL; + if(!done) { + /* thread is still running. Detach the thread while mutexed, it will + * trigger the cleanup when it releases its reference. */ + Curl_thread_destroy(&addr->thread_hnd); + } + Curl_mutex_release(&addr->mutx); + + if(done) { + /* thread has released its reference, join it and + * release the memory we shared with it. */ + if(addr->thread_hnd != curl_thread_t_null) + Curl_thread_join(&addr->thread_hnd); + addr_ctx_destroy(addr); + } +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE + * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL + */ + Curl_multi_will_close(data, sock_rd); + wakeup_close(sock_rd); +#endif + } +} + +#ifdef USE_HTTPSRR_ARES + +static void async_thrdd_rr_done(void *user_data, ares_status_t status, + size_t timeouts, + const ares_dns_record_t *dnsrec) +{ + struct Curl_easy *data = user_data; + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + + (void)timeouts; + thrdd->rr.done = TRUE; + if((ARES_SUCCESS != status) || !dnsrec) + return; + thrdd->rr.result = Curl_httpsrr_from_ares(data, dnsrec, &thrdd->rr.hinfo); +} + +static CURLcode async_rr_start(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + int status; + + DEBUGASSERT(!thrdd->rr.channel); + status = ares_init_options(&thrdd->rr.channel, NULL, 0); + if(status != ARES_SUCCESS) { + thrdd->rr.channel = NULL; + return CURLE_FAILED_INIT; + } + + memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo)); + thrdd->rr.hinfo.port = -1; + ares_query_dnsrec(thrdd->rr.channel, + data->conn->host.name, ARES_CLASS_IN, + ARES_REC_TYPE_HTTPS, + async_thrdd_rr_done, data, NULL); + return CURLE_OK; +} +#endif + +/* + * async_thrdd_init() starts a new thread that performs the actual + * resolve. This function returns before the resolve is done. + * + * Returns FALSE in case of failure, otherwise TRUE. + */ +static bool async_thrdd_init(struct Curl_easy *data, + const char *hostname, int port, int ip_version, + const struct addrinfo *hints) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + struct async_thrdd_addr_ctx *addr_ctx; + + /* !checksrc! disable ERRNOVAR 1 */ + int err = ENOMEM; + + if(thrdd->addr +#ifdef USE_HTTPSRR_ARES + || thrdd->rr.channel +#endif + ) { + CURL_TRC_DNS(data, "starting new resolve, with previous not cleaned up"); + async_thrdd_destroy(data); + DEBUGASSERT(!thrdd->addr); +#ifdef USE_HTTPSRR_ARES + DEBUGASSERT(!thrdd->rr.channel); +#endif + } + + data->state.async.dns = NULL; + data->state.async.done = FALSE; + data->state.async.port = port; + data->state.async.ip_version = ip_version; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + goto err_exit; + + addr_ctx = addr_ctx_create(hostname, port, hints); + if(!addr_ctx) + goto err_exit; + thrdd->addr = addr_ctx; + + Curl_mutex_acquire(&addr_ctx->mutx); + DEBUGASSERT(addr_ctx->ref_count == 1); + /* passing addr_ctx to the thread adds a reference */ + addr_ctx->start = curlx_now(); + ++addr_ctx->ref_count; +#ifdef HAVE_GETADDRINFO + addr_ctx->thread_hnd = Curl_thread_create(getaddrinfo_thread, addr_ctx); +#else + addr_ctx->thread_hnd = Curl_thread_create(gethostbyname_thread, addr_ctx); +#endif + if(addr_ctx->thread_hnd == curl_thread_t_null) { + /* The thread never started, remove its reference that never happened. */ + --addr_ctx->ref_count; + err = errno; + Curl_mutex_release(&addr_ctx->mutx); + goto err_exit; + } + Curl_mutex_release(&addr_ctx->mutx); + +#ifdef USE_HTTPSRR_ARES + if(async_rr_start(data)) + infof(data, "Failed HTTPS RR operation"); +#endif + CURL_TRC_DNS(data, "resolve thread started for of %s:%d", hostname, port); + return TRUE; + +err_exit: + CURL_TRC_DNS(data, "resolve thread failed init: %d", err); + async_thrdd_destroy(data); + CURL_SETERRNO(err); + return FALSE; +} + +/* + * 'entry' may be NULL and then no data is returned + */ +static CURLcode asyn_thrdd_await(struct Curl_easy *data, + struct async_thrdd_addr_ctx *addr_ctx, + struct Curl_dns_entry **entry) +{ + CURLcode result = CURLE_OK; + + DEBUGASSERT(addr_ctx->thread_hnd != curl_thread_t_null); + + CURL_TRC_DNS(data, "resolve, wait for thread to finish"); + /* wait for the thread to resolve the name */ + if(Curl_thread_join(&addr_ctx->thread_hnd)) { + if(entry) + result = Curl_async_is_resolved(data, entry); + } + else + DEBUGASSERT(0); + + data->state.async.done = TRUE; + if(entry) + *entry = data->state.async.dns; + + async_thrdd_destroy(data); + return result; +} + + +/* + * Until we gain a way to signal the resolver threads to stop early, we must + * simply wait for them and ignore their results. + */ +void Curl_async_thrdd_shutdown(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + + /* If we are still resolving, we must wait for the threads to fully clean up, + unfortunately. Otherwise, we can simply cancel to clean up any resolver + data. */ + if(thrdd->addr && (thrdd->addr->thread_hnd != curl_thread_t_null) && + !data->set.quick_exit) + (void)asyn_thrdd_await(data, thrdd->addr, NULL); + else + async_thrdd_destroy(data); +} + +void Curl_async_thrdd_destroy(struct Curl_easy *data) +{ + Curl_async_thrdd_shutdown(data); +} + +/* + * Curl_async_await() + * + * Waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, + * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. + * + * This is the version for resolves-in-a-thread. + */ +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **entry) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + if(thrdd->addr) + return asyn_thrdd_await(data, thrdd->addr, entry); + return CURLE_FAILED_INIT; +} + +/* + * Curl_async_is_resolved() is called repeatedly to check if a previous + * name resolve request has completed. It should also make sure to time-out if + * the operation seems to take too long. + */ +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + bool done = FALSE; + + DEBUGASSERT(dns); + *dns = NULL; + + if(data->state.async.done) { + *dns = data->state.async.dns; + CURL_TRC_DNS(data, "threaded: is_resolved(), already done, dns=%sfound", + *dns ? "" : "not "); + return CURLE_OK; + } + +#ifdef USE_HTTPSRR_ARES + /* best effort, ignore errors */ + if(thrdd->rr.channel) + (void)Curl_ares_perform(thrdd->rr.channel, 0); +#endif + + DEBUGASSERT(thrdd->addr); + if(!thrdd->addr) + return CURLE_FAILED_INIT; + + Curl_mutex_acquire(&thrdd->addr->mutx); + done = (thrdd->addr->ref_count == 1); + Curl_mutex_release(&thrdd->addr->mutx); + + if(done) { + CURLcode result = CURLE_OK; + + data->state.async.done = TRUE; + Curl_resolv_unlink(data, &data->state.async.dns); + + if(thrdd->addr->res) { + data->state.async.dns = + Curl_dnscache_mk_entry(data, thrdd->addr->res, + data->state.async.hostname, 0, + data->state.async.port, FALSE); + thrdd->addr->res = NULL; + if(!data->state.async.dns) + result = CURLE_OUT_OF_MEMORY; + +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + result = thrdd->rr.result; + if(!result) { + struct Curl_https_rrinfo *lhrr; + lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); + if(!lhrr) + result = CURLE_OUT_OF_MEMORY; + else + data->state.async.dns->hinfo = lhrr; + } + } +#endif + if(!result && data->state.async.dns) + result = Curl_dnscache_add(data, data->state.async.dns); + } + + if(!result && !data->state.async.dns) + result = Curl_resolver_error(data); + if(result) + Curl_resolv_unlink(data, &data->state.async.dns); + *dns = data->state.async.dns; + CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", + result, *dns ? "" : "not "); + async_thrdd_destroy(data); + return result; + } + else { + /* poll for name lookup done with exponential backoff up to 250ms */ + /* should be fine even if this converts to 32-bit */ + timediff_t elapsed = curlx_timediff(curlx_now(), + data->progress.t_startsingle); + if(elapsed < 0) + elapsed = 0; + + if(thrdd->addr->poll_interval == 0) + /* Start at 1ms poll interval */ + thrdd->addr->poll_interval = 1; + else if(elapsed >= thrdd->addr->interval_end) + /* Back-off exponentially if last interval expired */ + thrdd->addr->poll_interval *= 2; + + if(thrdd->addr->poll_interval > 250) + thrdd->addr->poll_interval = 250; + + thrdd->addr->interval_end = elapsed + thrdd->addr->poll_interval; + Curl_expire(data, thrdd->addr->poll_interval, EXPIRE_ASYNC_NAME); + return CURLE_OK; + } +} + +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + int ret_val = 0; +#if !defined(CURL_DISABLE_SOCKETPAIR) || defined(USE_HTTPSRR_ARES) + int socketi = 0; +#else + (void)socks; +#endif + +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + ret_val = Curl_ares_getsock(data, thrdd->rr.channel, socks); + for(socketi = 0; socketi < (MAX_SOCKSPEREASYHANDLE - 1); socketi++) + if(!ARES_GETSOCK_READABLE(ret_val, socketi) && + !ARES_GETSOCK_WRITABLE(ret_val, socketi)) + break; + } +#endif + if(!thrdd->addr) + return ret_val; + +#ifndef CURL_DISABLE_SOCKETPAIR + if(thrdd->addr) { + /* return read fd to client for polling the DNS resolution status */ + socks[socketi] = thrdd->addr->sock_pair[0]; + ret_val |= GETSOCK_READSOCK(socketi); + } + else +#endif + { + timediff_t milli; + timediff_t ms = curlx_timediff(curlx_now(), thrdd->addr->start); + if(ms < 3) + milli = 0; + else if(ms <= 50) + milli = ms/3; + else if(ms <= 250) + milli = 50; + else + milli = 200; + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + } + + return ret_val; +} + +#ifndef HAVE_GETADDRINFO +/* + * Curl_async_getaddrinfo() - for platforms without getaddrinfo + */ +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) +{ + (void)ip_version; + *waitp = 0; /* default to synchronous response */ + + /* fire up a new resolver thread! */ + if(async_thrdd_init(data, hostname, port, ip_version, NULL)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed"); + + return NULL; +} + +#else /* !HAVE_GETADDRINFO */ + +/* + * Curl_async_getaddrinfo() - for getaddrinfo + */ +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) +{ + struct addrinfo hints; + int pf = PF_INET; + *waitp = 0; /* default to synchronous response */ + + CURL_TRC_DNS(data, "init threaded resolve of %s:%d", hostname, port); +#ifdef CURLRES_IPV6 + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + if(ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; + else + pf = PF_UNSPEC; + } +#else + (void)ip_version; +#endif /* CURLRES_IPV6 */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? + SOCK_STREAM : SOCK_DGRAM; + + /* fire up a new resolver thread! */ + if(async_thrdd_init(data, hostname, port, ip_version, &hints)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed to start"); + return NULL; + +} + +#endif /* !HAVE_GETADDRINFO */ + +#endif /* CURLRES_THREADED */ diff --git a/Utilities/cmcurl/lib/asyn-thread.c b/Utilities/cmcurl/lib/asyn-thread.c deleted file mode 100644 index 6f0a2126ab6..00000000000 --- a/Utilities/cmcurl/lib/asyn-thread.c +++ /dev/null @@ -1,756 +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 "socketpair.h" - -/*********************************************************************** - * Only for threaded name resolves builds - **********************************************************************/ -#ifdef CURLRES_THREADED - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) -# include -#endif - -#ifdef HAVE_GETADDRINFO -# define RESOLVER_ENOMEM EAI_MEMORY -#else -# define RESOLVER_ENOMEM ENOMEM -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "multiif.h" -#include "inet_ntop.h" -#include "curl_threads.h" -#include "connect.h" -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -struct resdata { - struct curltime start; -}; - -/* - * Curl_resolver_global_init() - * Called from curl_global_init() to initialize global resolver environment. - * Does nothing here. - */ -int Curl_resolver_global_init(void) -{ - return CURLE_OK; -} - -/* - * Curl_resolver_global_cleanup() - * Called from curl_global_cleanup() to destroy global resolver environment. - * Does nothing here. - */ -void Curl_resolver_global_cleanup(void) -{ -} - -/* - * Curl_resolver_init() - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) -{ - (void)easy; - *resolver = calloc(1, sizeof(struct resdata)); - if(!*resolver) - return CURLE_OUT_OF_MEMORY; - return CURLE_OK; -} - -/* - * Curl_resolver_cleanup() - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). - */ -void Curl_resolver_cleanup(void *resolver) -{ - free(resolver); -} - -/* - * Curl_resolver_duphandle() - * Called from curl_easy_duphandle() to duplicate resolver URL state-specific - * environment ('resolver' member of the UrlState structure). - */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) -{ - (void)from; - return Curl_resolver_init(easy, to); -} - -static void destroy_async_data(struct Curl_async *); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct Curl_easy *data) -{ - destroy_async_data(&data->state.async); -} - -/* This function is used to init a threaded resolve */ -static bool init_resolve_thread(struct Curl_easy *data, - const char *hostname, int port, - const struct addrinfo *hints); - - -/* Data for synchronization between resolver thread and its parent */ -struct thread_sync_data { - curl_mutex_t *mtx; - int done; - int port; - char *hostname; /* hostname to resolve, Curl_async.hostname - duplicate */ -#ifndef CURL_DISABLE_SOCKETPAIR - struct Curl_easy *data; - curl_socket_t sock_pair[2]; /* socket pair */ -#endif - int sock_error; - struct Curl_addrinfo *res; -#ifdef HAVE_GETADDRINFO - struct addrinfo hints; -#endif - struct thread_data *td; /* for thread-self cleanup */ -}; - -struct thread_data { - curl_thread_t thread_hnd; - unsigned int poll_interval; - timediff_t interval_end; - struct thread_sync_data tsd; -}; - -static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data) -{ - return &(data->state.async.tdata->tsd); -} - -/* Destroy resolver thread synchronization data */ -static -void destroy_thread_sync_data(struct thread_sync_data *tsd) -{ - if(tsd->mtx) { - Curl_mutex_destroy(tsd->mtx); - free(tsd->mtx); - } - - free(tsd->hostname); - - if(tsd->res) - Curl_freeaddrinfo(tsd->res); - -#ifndef CURL_DISABLE_SOCKETPAIR - /* - * close one end of the socket pair (may be done in resolver thread); - * the other end (for reading) is always closed in the parent thread. - */ - if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { - sclose(tsd->sock_pair[1]); - } -#endif - memset(tsd, 0, sizeof(*tsd)); -} - -/* Initialize resolver thread synchronization data */ -static -int init_thread_sync_data(struct thread_data *td, - const char *hostname, - int port, - const struct addrinfo *hints) -{ - struct thread_sync_data *tsd = &td->tsd; - - memset(tsd, 0, sizeof(*tsd)); - - tsd->td = td; - tsd->port = port; - /* Treat the request as done until the thread actually starts so any early - * cleanup gets done properly. - */ - tsd->done = 1; -#ifdef HAVE_GETADDRINFO - DEBUGASSERT(hints); - tsd->hints = *hints; -#else - (void) hints; -#endif - - tsd->mtx = malloc(sizeof(curl_mutex_t)); - if(!tsd->mtx) - goto err_exit; - - Curl_mutex_init(tsd->mtx); - -#ifndef CURL_DISABLE_SOCKETPAIR - /* create socket pair, avoid AF_LOCAL since it doesn't build on Solaris */ - if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &tsd->sock_pair[0]) < 0) { - tsd->sock_pair[0] = CURL_SOCKET_BAD; - tsd->sock_pair[1] = CURL_SOCKET_BAD; - goto err_exit; - } -#endif - tsd->sock_error = CURL_ASYNC_SUCCESS; - - /* Copying hostname string because original can be destroyed by parent - * thread during gethostbyname execution. - */ - tsd->hostname = strdup(hostname); - if(!tsd->hostname) - goto err_exit; - - return 1; - -err_exit: -#ifndef CURL_DISABLE_SOCKETPAIR - if(tsd->sock_pair[0] != CURL_SOCKET_BAD) { - sclose(tsd->sock_pair[0]); - tsd->sock_pair[0] = CURL_SOCKET_BAD; - } -#endif - destroy_thread_sync_data(tsd); - return 0; -} - -static CURLcode getaddrinfo_complete(struct Curl_easy *data) -{ - struct thread_sync_data *tsd = conn_thread_sync_data(data); - CURLcode result; - - result = Curl_addrinfo_callback(data, tsd->sock_error, tsd->res); - /* The tsd->res structure has been copied to async.dns and perhaps the DNS - cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it. - */ - tsd->res = NULL; - - return result; -} - - -#ifdef HAVE_GETADDRINFO - -/* - * getaddrinfo_thread() resolves a name and then exits. - * - * For builds without ARES, but with ENABLE_IPV6, create a resolver thread - * and wait on it. - */ -static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) -{ - struct thread_sync_data *tsd = (struct thread_sync_data *)arg; - struct thread_data *td = tsd->td; - char service[12]; - int rc; -#ifndef CURL_DISABLE_SOCKETPAIR - char buf[1]; -#endif - - msnprintf(service, sizeof(service), "%d", tsd->port); - - rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); - - if(rc) { - tsd->sock_error = SOCKERRNO?SOCKERRNO:rc; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - else { - Curl_addrinfo_set_port(tsd->res, tsd->port); - } - - Curl_mutex_acquire(tsd->mtx); - if(tsd->done) { - /* too late, gotta clean up the mess */ - Curl_mutex_release(tsd->mtx); - destroy_thread_sync_data(tsd); - free(td); - } - else { -#ifndef CURL_DISABLE_SOCKETPAIR - if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { - /* DNS has been resolved, signal client task */ - buf[0] = 1; - if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) { - /* update sock_erro to errno */ - tsd->sock_error = SOCKERRNO; - } - } -#endif - tsd->done = 1; - Curl_mutex_release(tsd->mtx); - } - - return 0; -} - -#else /* HAVE_GETADDRINFO */ - -/* - * gethostbyname_thread() resolves a name and then exits. - */ -static unsigned int CURL_STDCALL gethostbyname_thread(void *arg) -{ - struct thread_sync_data *tsd = (struct thread_sync_data *)arg; - struct thread_data *td = tsd->td; - - tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port); - - if(!tsd->res) { - tsd->sock_error = SOCKERRNO; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - - Curl_mutex_acquire(tsd->mtx); - if(tsd->done) { - /* too late, gotta clean up the mess */ - Curl_mutex_release(tsd->mtx); - destroy_thread_sync_data(tsd); - free(td); - } - else { - tsd->done = 1; - Curl_mutex_release(tsd->mtx); - } - - return 0; -} - -#endif /* HAVE_GETADDRINFO */ - -/* - * destroy_async_data() cleans up async resolver data and thread handle. - */ -static void destroy_async_data(struct Curl_async *async) -{ - if(async->tdata) { - struct thread_data *td = async->tdata; - int done; -#ifndef CURL_DISABLE_SOCKETPAIR - curl_socket_t sock_rd = td->tsd.sock_pair[0]; - struct Curl_easy *data = td->tsd.data; -#endif - - /* - * if the thread is still blocking in the resolve syscall, detach it and - * let the thread do the cleanup... - */ - Curl_mutex_acquire(td->tsd.mtx); - done = td->tsd.done; - td->tsd.done = 1; - Curl_mutex_release(td->tsd.mtx); - - if(!done) { - Curl_thread_destroy(td->thread_hnd); - } - else { - if(td->thread_hnd != curl_thread_t_null) - Curl_thread_join(&td->thread_hnd); - - destroy_thread_sync_data(&td->tsd); - - free(async->tdata); - } -#ifndef CURL_DISABLE_SOCKETPAIR - /* - * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE - * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL - */ - Curl_multi_closed(data, sock_rd); - sclose(sock_rd); -#endif - } - async->tdata = NULL; - - free(async->hostname); - async->hostname = NULL; -} - -/* - * init_resolve_thread() starts a new thread that performs the actual - * resolve. This function returns before the resolve is done. - * - * Returns FALSE in case of failure, otherwise TRUE. - */ -static bool init_resolve_thread(struct Curl_easy *data, - const char *hostname, int port, - const struct addrinfo *hints) -{ - struct thread_data *td = calloc(1, sizeof(struct thread_data)); - int err = ENOMEM; - struct Curl_async *asp = &data->state.async; - - data->state.async.tdata = td; - if(!td) - goto errno_exit; - - asp->port = port; - asp->done = FALSE; - asp->status = 0; - asp->dns = NULL; - td->thread_hnd = curl_thread_t_null; - - if(!init_thread_sync_data(td, hostname, port, hints)) { - asp->tdata = NULL; - free(td); - goto errno_exit; - } - - free(asp->hostname); - asp->hostname = strdup(hostname); - if(!asp->hostname) - goto err_exit; - - /* The thread will set this to 1 when complete. */ - td->tsd.done = 0; - -#ifdef HAVE_GETADDRINFO - td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd); -#else - td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd); -#endif - - if(!td->thread_hnd) { - /* The thread never started, so mark it as done here for proper cleanup. */ - td->tsd.done = 1; - err = errno; - goto err_exit; - } - - return TRUE; - -err_exit: - destroy_async_data(asp); - -errno_exit: - errno = err; - return FALSE; -} - -/* - * 'entry' may be NULL and then no data is returned - */ -static CURLcode thread_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry, - bool report) -{ - struct thread_data *td; - CURLcode result = CURLE_OK; - - DEBUGASSERT(data); - td = data->state.async.tdata; - DEBUGASSERT(td); - DEBUGASSERT(td->thread_hnd != curl_thread_t_null); - - /* wait for the thread to resolve the name */ - if(Curl_thread_join(&td->thread_hnd)) { - if(entry) - result = getaddrinfo_complete(data); - } - else - DEBUGASSERT(0); - - data->state.async.done = TRUE; - - if(entry) - *entry = data->state.async.dns; - - if(!data->state.async.dns && report) - /* a name was not resolved, report error */ - result = Curl_resolver_error(data); - - destroy_async_data(&data->state.async); - - if(!data->state.async.dns && report) - connclose(data->conn, "asynch resolve failed"); - - return result; -} - - -/* - * Until we gain a way to signal the resolver threads to stop early, we must - * simply wait for them and ignore their results. - */ -void Curl_resolver_kill(struct Curl_easy *data) -{ - struct thread_data *td = data->state.async.tdata; - - /* If we're still resolving, we must wait for the threads to fully clean up, - unfortunately. Otherwise, we can simply cancel to clean up any resolver - data. */ - if(td && td->thread_hnd != curl_thread_t_null - && (data->set.quick_exit != 1L)) - (void)thread_wait_resolv(data, NULL, FALSE); - else - Curl_resolver_cancel(data); -} - -/* - * Curl_resolver_wait_resolv() - * - * Waits for a resolve to finish. This function should be avoided since using - * this risk getting the multi interface to "hang". - * - * If 'entry' is non-NULL, make it point to the resolved dns entry - * - * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, - * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. - * - * This is the version for resolves-in-a-thread. - */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry) -{ - return thread_wait_resolv(data, entry, TRUE); -} - -/* - * Curl_resolver_is_resolved() is called repeatedly to check if a previous - * name resolve request has completed. It should also make sure to time-out if - * the operation seems to take too long. - */ -CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **entry) -{ - struct thread_data *td = data->state.async.tdata; - int done = 0; - - DEBUGASSERT(entry); - *entry = NULL; - - if(!td) { - DEBUGASSERT(td); - return CURLE_COULDNT_RESOLVE_HOST; - } - - Curl_mutex_acquire(td->tsd.mtx); - done = td->tsd.done; - Curl_mutex_release(td->tsd.mtx); - - if(done) { - getaddrinfo_complete(data); - - if(!data->state.async.dns) { - CURLcode result = Curl_resolver_error(data); - destroy_async_data(&data->state.async); - return result; - } - destroy_async_data(&data->state.async); - *entry = data->state.async.dns; - } - else { - /* poll for name lookup done with exponential backoff up to 250ms */ - /* should be fine even if this converts to 32 bit */ - timediff_t elapsed = Curl_timediff(Curl_now(), - data->progress.t_startsingle); - if(elapsed < 0) - elapsed = 0; - - if(td->poll_interval == 0) - /* Start at 1ms poll interval */ - td->poll_interval = 1; - else if(elapsed >= td->interval_end) - /* Back-off exponentially if last interval expired */ - td->poll_interval *= 2; - - if(td->poll_interval > 250) - td->poll_interval = 250; - - td->interval_end = elapsed + td->poll_interval; - Curl_expire(data, td->poll_interval, EXPIRE_ASYNC_NAME); - } - - return CURLE_OK; -} - -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) -{ - int ret_val = 0; - timediff_t milli; - timediff_t ms; - struct resdata *reslv = (struct resdata *)data->state.async.resolver; -#ifndef CURL_DISABLE_SOCKETPAIR - struct thread_data *td = data->state.async.tdata; -#else - (void)socks; -#endif - -#ifndef CURL_DISABLE_SOCKETPAIR - if(td) { - /* return read fd to client for polling the DNS resolution status */ - socks[0] = td->tsd.sock_pair[0]; - td->tsd.data = data; - ret_val = GETSOCK_READSOCK(0); - } - else { -#endif - ms = Curl_timediff(Curl_now(), reslv->start); - if(ms < 3) - milli = 0; - else if(ms <= 50) - milli = ms/3; - else if(ms <= 250) - milli = 50; - else - milli = 200; - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); -#ifndef CURL_DISABLE_SOCKETPAIR - } -#endif - - - return ret_val; -} - -#ifndef HAVE_GETADDRINFO -/* - * Curl_getaddrinfo() - for platforms without getaddrinfo - */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - struct resdata *reslv = (struct resdata *)data->state.async.resolver; - - *waitp = 0; /* default to synchronous response */ - - reslv->start = Curl_now(); - - /* fire up a new resolver thread! */ - if(init_resolve_thread(data, hostname, port, NULL)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - failf(data, "getaddrinfo() thread failed"); - - return NULL; -} - -#else /* !HAVE_GETADDRINFO */ - -/* - * Curl_resolver_getaddrinfo() - for getaddrinfo - */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - struct addrinfo hints; - int pf = PF_INET; - struct resdata *reslv = (struct resdata *)data->state.async.resolver; - - *waitp = 0; /* default to synchronous response */ - -#ifdef CURLRES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) - /* The stack seems to be IPv6-enabled */ - pf = PF_UNSPEC; -#endif /* CURLRES_IPV6 */ - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = pf; - hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP)? - SOCK_STREAM : SOCK_DGRAM; - - reslv->start = Curl_now(); - /* fire up a new resolver thread! */ - if(init_resolve_thread(data, hostname, port, &hints)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - failf(data, "getaddrinfo() thread failed to start"); - return NULL; - -} - -#endif /* !HAVE_GETADDRINFO */ - -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) -{ - (void)data; - (void)interf; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) -{ - (void)data; - (void)local_ip4; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) -{ - (void)data; - (void)local_ip6; - return CURLE_NOT_BUILT_IN; -} - -#endif /* CURLRES_THREADED */ diff --git a/Utilities/cmcurl/lib/asyn.h b/Utilities/cmcurl/lib/asyn.h index 7e207c4f564..1cc7175bebc 100644 --- a/Utilities/cmcurl/lib/asyn.h +++ b/Utilities/cmcurl/lib/asyn.h @@ -25,13 +25,22 @@ ***************************************************************************/ #include "curl_setup.h" + +struct Curl_easy; +struct Curl_dns_entry; + +#ifdef CURLRES_ASYNCH + #include "curl_addrinfo.h" +#include "httpsrr.h" struct addrinfo; struct hostent; -struct Curl_easy; struct connectdata; -struct Curl_dns_entry; + +#if defined(CURLRES_ARES) && defined(CURLRES_THREADED) +#error cannot have both CURLRES_ARES and CURLRES_THREADED defined +#endif /* * This header defines all functions in the internal asynch resolver interface. @@ -41,85 +50,38 @@ struct Curl_dns_entry; */ /* - * Curl_resolver_global_init() + * Curl_async_global_init() * * Called from curl_global_init() to initialize global resolver environment. * Returning anything else than CURLE_OK fails curl_global_init(). */ -int Curl_resolver_global_init(void); +int Curl_async_global_init(void); /* - * Curl_resolver_global_cleanup() + * Curl_async_global_cleanup() * Called from curl_global_cleanup() to destroy global resolver environment. */ -void Curl_resolver_global_cleanup(void); - -/* - * Curl_resolver_init() - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Should fill the passed pointer by the initialized handler. - * Returning anything else than CURLE_OK fails curl_easy_init() with the - * correspondent code. - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver); - -/* - * Curl_resolver_cleanup() - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Should destroy the handler and free all resources connected to - * it. - */ -void Curl_resolver_cleanup(void *resolver); +void Curl_async_global_cleanup(void); /* - * Curl_resolver_duphandle() - * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * environment ('resolver' member of the UrlState structure). Should - * duplicate the 'from' handle and pass the resulting handle to the 'to' - * pointer. Returning anything else than CURLE_OK causes failed - * curl_easy_duphandle() call. + * Curl_async_get_impl() + * Get the resolver implementation instance (c-ares channel) or NULL + * for passing to application callback. */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, - void *from); +CURLcode Curl_async_get_impl(struct Curl_easy *easy, void **impl); -/* - * Curl_resolver_cancel(). - * - * It is called from inside other functions to cancel currently performing - * resolver request. Should also free any temporary resources allocated to - * perform a request. This never waits for resolver threads to complete. - * - * It is safe to call this when conn is in any state. - */ -void Curl_resolver_cancel(struct Curl_easy *data); - -/* - * Curl_resolver_kill(). +/* Curl_async_getsock() * - * This acts like Curl_resolver_cancel() except it will block until any threads - * associated with the resolver are complete. This never blocks for resolvers - * that do not use threads. This is intended to be the "last chance" function - * that cleans up an in-progress resolver completely (before its owner is about - * to die). - * - * It is safe to call this when conn is in any state. - */ -void Curl_resolver_kill(struct Curl_easy *data); - -/* Curl_resolver_getsock() - * - * This function is called from the multi_getsock() function. 'sock' is a + * This function is called from the Curl_multi_getsock() function. 'sock' is a * pointer to an array to hold the file descriptors, with 'numsock' being the * size of that array (in number of entries). This function is supposed to * return bitmask indicating what file descriptors (referring to array indexes * in the 'sock' array) to wait for, read/write. */ -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *sock); /* - * Curl_resolver_is_resolved() + * Curl_async_is_resolved() * * Called repeatedly to check if a previous name resolve request has * completed. It should also make sure to time-out if the operation seems to @@ -127,25 +89,25 @@ int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); * * Returns normal CURLcode errors. */ -CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **dns); +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns); /* - * Curl_resolver_wait_resolv() + * Curl_async_await() * * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". * - * If 'entry' is non-NULL, make it point to the resolved dns entry + * On return 'entry' is assigned the resolved dns (CURLE_OK or NULL otherwise. * * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **dnsentry); +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **dnsentry); /* - * Curl_resolver_getaddrinfo() - when using this resolver + * Curl_async_getaddrinfo() - when using this resolver * * Returns name information about the given hostname and port number. If * successful, the 'hostent' is returned and the fourth argument will point to @@ -155,29 +117,157 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, * Each resolver backend must of course make sure to return data in the * correct format to comply with this. */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp); - -#ifndef CURLRES_ASYNCH -/* convert these functions if an asynch resolver isn't used */ -#define Curl_resolver_cancel(x) Curl_nop_stmt -#define Curl_resolver_kill(x) Curl_nop_stmt -#define Curl_resolver_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST -#define Curl_resolver_wait_resolv(x,y) CURLE_COULDNT_RESOLVE_HOST -#define Curl_resolver_duphandle(x,y,z) CURLE_OK -#define Curl_resolver_init(x,y) CURLE_OK -#define Curl_resolver_global_init() CURLE_OK -#define Curl_resolver_global_cleanup() Curl_nop_stmt -#define Curl_resolver_cleanup(x) Curl_nop_stmt +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp); + +#ifdef USE_ARES +/* common functions for c-ares and threaded resolver with HTTPSRR */ +#include + +int Curl_ares_getsock(struct Curl_easy *data, + ares_channel channel, + curl_socket_t *socks); +int Curl_ares_perform(ares_channel channel, + timediff_t timeout_ms); #endif -#ifdef CURLRES_ASYNCH -#define Curl_resolver_asynch() 1 -#else -#define Curl_resolver_asynch() 0 +#ifdef CURLRES_ARES +/* async resolving implementation using c-ares alone */ +struct async_ares_ctx { + ares_channel channel; + int num_pending; /* number of outstanding c-ares requests */ + struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares + parts */ + int last_status; + CURLcode result; /* CURLE_OK or error handling response */ +#ifndef HAVE_CARES_GETADDRINFO + struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ +#endif +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo hinfo; +#endif +}; + +void Curl_async_ares_shutdown(struct Curl_easy *data); +void Curl_async_ares_destroy(struct Curl_easy *data); + +/* Set the DNS server to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data); + +/* Set the DNS interfacer to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data); + +/* Set the local ipv4 address to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data); + +/* Set the local ipv6 address to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data); + +#endif /* CURLRES_ARES */ + +#ifdef CURLRES_THREADED +/* async resolving implementation using POSIX threads */ +#include "curl_threads.h" + +/* Context for threaded address resolver */ +struct async_thrdd_addr_ctx { + curl_thread_t thread_hnd; + char *hostname; /* hostname to resolve, Curl_async.hostname + duplicate */ + curl_mutex_t mutx; +#ifndef CURL_DISABLE_SOCKETPAIR + curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ #endif + struct Curl_addrinfo *res; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; +#endif + struct curltime start; + timediff_t interval_end; + unsigned int poll_interval; + int port; + int sock_error; + int ref_count; +}; + +/* Context for threaded resolver */ +struct async_thrdd_ctx { + /* `addr` is a pointer since this memory is shared with a started + * thread. Since threads cannot be killed, we use reference counting + * so that we can "release" our pointer to this memory while the + * thread is still running. */ + struct async_thrdd_addr_ctx *addr; +#if defined(USE_HTTPSRR) && defined(USE_ARES) + struct { + ares_channel channel; + struct Curl_https_rrinfo hinfo; + CURLcode result; + BIT(done); + } rr; +#endif +}; + +void Curl_async_thrdd_shutdown(struct Curl_easy *data); +void Curl_async_thrdd_destroy(struct Curl_easy *data); + +#endif /* CURLRES_THREADED */ + +#ifndef CURL_DISABLE_DOH +struct doh_probes; +#endif + +#else /* CURLRES_ASYNCH */ + +/* convert these functions if an asynch resolver is not used */ +#define Curl_async_get_impl(x,y) (*(y) = NULL, CURLE_OK) +#define Curl_async_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST +#define Curl_async_await(x,y) CURLE_COULDNT_RESOLVE_HOST +#define Curl_async_global_init() CURLE_OK +#define Curl_async_global_cleanup() Curl_nop_stmt + +#endif /* !CURLRES_ASYNCH */ + +#if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) +#define USE_CURL_ASYNC +#endif + +#ifdef USE_CURL_ASYNC +struct Curl_async { +#ifdef CURLRES_ARES /* */ + struct async_ares_ctx ares; +#elif defined(CURLRES_THREADED) + struct async_thrdd_ctx thrdd; +#endif +#ifndef CURL_DISABLE_DOH + struct doh_probes *doh; /* DoH specific data for this request */ +#endif + struct Curl_dns_entry *dns; /* result of resolving on success */ + char *hostname; /* copy of the params resolv started with */ + int port; + int ip_version; + BIT(done); +}; + +/* + * Curl_async_shutdown(). + * + * This shuts down all ongoing operations. + */ +void Curl_async_shutdown(struct Curl_easy *data); + +/* + * Curl_async_destroy(). + * + * This frees the resources of any async resolve. + */ +void Curl_async_destroy(struct Curl_easy *data); +#else /* !USE_CURL_ASYNC */ +#define Curl_async_shutdown(x) Curl_nop_stmt +#define Curl_async_destroy(x) Curl_nop_stmt +#endif /* USE_CURL_ASYNC */ /********** end of generic resolver interface functions *****************/ diff --git a/Utilities/cmcurl/lib/bufq.c b/Utilities/cmcurl/lib/bufq.c index 30598cf5864..724d62f31cd 100644 --- a/Utilities/cmcurl/lib/bufq.c +++ b/Utilities/cmcurl/lib/bufq.c @@ -45,11 +45,6 @@ static size_t chunk_len(const struct buf_chunk *chunk) return chunk->w_offset - chunk->r_offset; } -static size_t chunk_space(const struct buf_chunk *chunk) -{ - return chunk->dlen - chunk->w_offset; -} - static void chunk_reset(struct buf_chunk *chunk) { chunk->next = NULL; @@ -91,6 +86,23 @@ static size_t chunk_read(struct buf_chunk *chunk, } } +static size_t chunk_unwrite(struct buf_chunk *chunk, size_t len) +{ + size_t n = chunk->w_offset - chunk->r_offset; + DEBUGASSERT(chunk->w_offset >= chunk->r_offset); + if(!n) { + return 0; + } + else if(n <= len) { + chunk->r_offset = chunk->w_offset = 0; + return n; + } + else { + chunk->w_offset -= len; + return len; + } +} + static ssize_t chunk_slurpn(struct buf_chunk *chunk, size_t max_len, Curl_bufq_reader *reader, void *reader_ctx, CURLcode *err) @@ -144,21 +156,6 @@ static size_t chunk_skip(struct buf_chunk *chunk, size_t amount) return n; } -static void chunk_shift(struct buf_chunk *chunk) -{ - if(chunk->r_offset) { - if(!chunk_is_empty(chunk)) { - size_t n = chunk->w_offset - chunk->r_offset; - memmove(chunk->x.data, chunk->x.data + chunk->r_offset, n); - chunk->w_offset -= chunk->r_offset; - chunk->r_offset = 0; - } - else { - chunk->r_offset = chunk->w_offset = 0; - } - } -} - static void chunk_list_free(struct buf_chunk **anchor) { struct buf_chunk *chunk; @@ -285,24 +282,6 @@ size_t Curl_bufq_len(const struct bufq *q) return len; } -size_t Curl_bufq_space(const struct bufq *q) -{ - size_t space = 0; - if(q->tail) - space += chunk_space(q->tail); - if(q->spare) { - struct buf_chunk *chunk = q->spare; - while(chunk) { - space += chunk->dlen; - chunk = chunk->next; - } - } - if(q->chunk_count < q->max_chunks) { - space += (q->max_chunks - q->chunk_count) * q->chunk_size; - } - return space; -} - bool Curl_bufq_is_empty(const struct bufq *q) { return !q->head || chunk_is_empty(q->head); @@ -378,6 +357,49 @@ static void prune_head(struct bufq *q) } } +static struct buf_chunk *chunk_prev(struct buf_chunk *head, + struct buf_chunk *chunk) +{ + while(head) { + if(head == chunk) + return NULL; + if(head->next == chunk) + return head; + head = head->next; + } + return NULL; +} + +static void prune_tail(struct bufq *q) +{ + struct buf_chunk *chunk; + + while(q->tail && chunk_is_empty(q->tail)) { + chunk = q->tail; + q->tail = chunk_prev(q->head, chunk); + if(q->tail) + q->tail->next = NULL; + if(q->head == chunk) + q->head = q->tail; + if(q->pool) { + bufcp_put(q->pool, chunk); + --q->chunk_count; + } + else if((q->chunk_count > q->max_chunks) || + (q->opts & BUFQ_OPT_NO_SPARES)) { + /* SOFT_LIMIT allowed us more than max. free spares until + * we are at max again. Or free them if we are configured + * to not use spares. */ + free(chunk); + --q->chunk_count; + } + else { + chunk->next = q->spare; + q->spare = chunk; + } + } +} + static struct buf_chunk *get_non_full_tail(struct bufq *q) { struct buf_chunk *chunk; @@ -411,14 +433,15 @@ ssize_t Curl_bufq_write(struct bufq *q, while(len) { tail = get_non_full_tail(q); if(!tail) { - if(q->chunk_count < q->max_chunks) { + if((q->chunk_count < q->max_chunks) || (q->opts & BUFQ_OPT_SOFT_LIMIT)) { *err = CURLE_OUT_OF_MEMORY; return -1; } break; } n = chunk_append(tail, buf, len); - DEBUGASSERT(n); + if(!n) + break; nwritten += n; buf += n; len -= n; @@ -431,6 +454,26 @@ ssize_t Curl_bufq_write(struct bufq *q, return nwritten; } +CURLcode Curl_bufq_cwrite(struct bufq *q, + const char *buf, size_t len, + size_t *pnwritten) +{ + ssize_t n; + CURLcode result; + n = Curl_bufq_write(q, (const unsigned char *)buf, len, &result); + *pnwritten = (n < 0) ? 0 : (size_t)n; + return result; +} + +CURLcode Curl_bufq_unwrite(struct bufq *q, size_t len) +{ + while(len && q->tail) { + len -= chunk_unwrite(q->tail, len); + prune_tail(q); + } + return len ? CURLE_AGAIN : CURLE_OK; +} + ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, CURLcode *err) { @@ -454,6 +497,16 @@ ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, return nread; } +CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len, + size_t *pnread) +{ + ssize_t n; + CURLcode result; + n = Curl_bufq_read(q, (unsigned char *)buf, len, &result); + *pnread = (n < 0) ? 0 : (size_t)n; + return result; +} + bool Curl_bufq_peek(struct bufq *q, const unsigned char **pbuf, size_t *plen) { @@ -503,13 +556,6 @@ void Curl_bufq_skip(struct bufq *q, size_t amount) } } -void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount) -{ - Curl_bufq_skip(q, amount); - if(q->tail) - chunk_shift(q->tail); -} - ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, void *writer_ctx, CURLcode *err) { @@ -528,6 +574,14 @@ ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, } break; } + if(!chunk_written) { + if(!nwritten) { + /* treat as blocked */ + *err = CURLE_AGAIN; + nwritten = -1; + } + break; + } Curl_bufq_skip(q, (size_t)chunk_written); nwritten += chunk_written; } @@ -551,7 +605,8 @@ ssize_t Curl_bufq_write_pass(struct bufq *q, /* real error, fail */ return -1; } - /* would block */ + /* would block, bufq is full, give up */ + break; } } @@ -562,16 +617,25 @@ ssize_t Curl_bufq_write_pass(struct bufq *q, /* real error, fail */ return -1; } - /* no room in bufq, bail out */ - goto out; + /* no room in bufq */ + break; } + /* edge case of writer returning 0 (and len is >0) + * break or we might enter an infinite loop here */ + if(n == 0) + break; + /* Maybe only part of `data` has been added, continue to loop */ buf += (size_t)n; len -= (size_t)n; nwritten += (size_t)n; } -out: + if(!nwritten && len) { + *err = CURLE_AGAIN; + return -1; + } + *err = CURLE_OK; return nwritten; } diff --git a/Utilities/cmcurl/lib/bufq.h b/Utilities/cmcurl/lib/bufq.h index 89b5c84efe9..60059deb30f 100644 --- a/Utilities/cmcurl/lib/bufq.h +++ b/Utilities/cmcurl/lib/bufq.h @@ -85,7 +85,7 @@ void Curl_bufcp_free(struct bufc_pool *pool); * preferably never fail (except for memory exhaustion). * * By default and without a pool, a bufq will keep chunks that read - * read empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will + * empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will * disable that and free chunks once they become empty. * * When providing a pool to a bufq, all chunk creation and spare handling @@ -150,14 +150,6 @@ void Curl_bufq_free(struct bufq *q); */ size_t Curl_bufq_len(const struct bufq *q); -/** - * Return the total amount of free space in the queue. - * The returned length is the number of bytes that can - * be expected to be written successfully to the bufq, - * providing no memory allocations fail. - */ -size_t Curl_bufq_space(const struct bufq *q); - /** * Returns TRUE iff there is no data in the buffer queue. */ @@ -178,6 +170,16 @@ ssize_t Curl_bufq_write(struct bufq *q, const unsigned char *buf, size_t len, CURLcode *err); +CURLcode Curl_bufq_cwrite(struct bufq *q, + const char *buf, size_t len, + size_t *pnwritten); + +/** + * Remove `len` bytes from the end of the buffer queue again. + * Returns CURLE_AGAIN if less than `len` bytes were in the queue. + */ +CURLcode Curl_bufq_unwrite(struct bufq *q, size_t len); + /** * Read buf from the start of the buffer queue. The buf is copied * and the amount of copied bytes is returned. @@ -187,6 +189,9 @@ ssize_t Curl_bufq_write(struct bufq *q, ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, CURLcode *err); +CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len, + size_t *pnread); + /** * Peek at the head chunk in the buffer queue. Returns a pointer to * the chunk buf (at the current offset) and its length. Does not @@ -209,12 +214,6 @@ bool Curl_bufq_peek_at(struct bufq *q, size_t offset, */ void Curl_bufq_skip(struct bufq *q, size_t amount); -/** - * Same as `skip` but shift tail data to the start afterwards, - * so that further writes will find room in tail. - */ -void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount); - typedef ssize_t Curl_bufq_writer(void *writer_ctx, const unsigned char *buf, size_t len, CURLcode *err); diff --git a/Utilities/cmcurl/lib/bufref.c b/Utilities/cmcurl/lib/bufref.c index ce686b6f377..ac0612071d6 100644 --- a/Utilities/cmcurl/lib/bufref.c +++ b/Utilities/cmcurl/lib/bufref.c @@ -25,11 +25,14 @@ #include "curl_setup.h" #include "urldata.h" #include "bufref.h" +#include "strdup.h" #include "curl_memory.h" #include "memdebug.h" +#ifdef DEBUGBUILD #define SIGNATURE 0x5c48e9b2 /* Random pattern. */ +#endif /* * Init a bufref struct. @@ -47,7 +50,7 @@ void Curl_bufref_init(struct bufref *br) } /* - * Free the buffer and re-init the necessary fields. It doesn't touch the + * Free the buffer and re-init the necessary fields. It does not touch the * 'signature' field and thus this buffer reference can be reused. */ @@ -58,7 +61,7 @@ void Curl_bufref_free(struct bufref *br) DEBUGASSERT(br->ptr || !br->len); if(br->ptr && br->dtor) - br->dtor((void *) br->ptr); + br->dtor(CURL_UNCONST(br->ptr)); br->dtor = NULL; br->ptr = NULL; @@ -116,12 +119,9 @@ CURLcode Curl_bufref_memdup(struct bufref *br, const void *ptr, size_t len) DEBUGASSERT(len <= CURL_MAX_INPUT_LENGTH); if(ptr) { - cpy = malloc(len + 1); + cpy = Curl_memdup0(ptr, len); if(!cpy) return CURLE_OUT_OF_MEMORY; - if(len) - memcpy(cpy, ptr, len); - cpy[len] = '\0'; } Curl_bufref_set(br, cpy, len, curl_free); diff --git a/Utilities/cmcurl/lib/c-hyper.c b/Utilities/cmcurl/lib/c-hyper.c deleted file mode 100644 index 756aebee21f..00000000000 --- a/Utilities/cmcurl/lib/c-hyper.c +++ /dev/null @@ -1,1250 +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.haxx.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) - -#ifdef HAVE_NETINET_IN_H -#include -#endif - -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#include -#include "urldata.h" -#include "sendf.h" -#include "transfer.h" -#include "multiif.h" -#include "progress.h" -#include "content_encoding.h" -#include "ws.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -size_t Curl_hyper_recv(void *userp, hyper_context *ctx, - uint8_t *buf, size_t buflen) -{ - struct Curl_easy *data = userp; - struct connectdata *conn = data->conn; - CURLcode result; - ssize_t nread; - DEBUGASSERT(conn); - (void)ctx; - - result = Curl_read(data, conn->sockfd, (char *)buf, buflen, &nread); - if(result == CURLE_AGAIN) { - /* would block, register interest */ - if(data->hyp.read_waker) - hyper_waker_free(data->hyp.read_waker); - data->hyp.read_waker = hyper_context_waker(ctx); - if(!data->hyp.read_waker) { - failf(data, "Couldn't make the read hyper_context_waker"); - return HYPER_IO_ERROR; - } - return HYPER_IO_PENDING; - } - else if(result) { - failf(data, "Curl_read failed"); - return HYPER_IO_ERROR; - } - return (size_t)nread; -} - -size_t Curl_hyper_send(void *userp, hyper_context *ctx, - const uint8_t *buf, size_t buflen) -{ - struct Curl_easy *data = userp; - struct connectdata *conn = data->conn; - CURLcode result; - ssize_t nwrote; - - result = Curl_write(data, conn->sockfd, (void *)buf, buflen, &nwrote); - if(result == CURLE_AGAIN) { - /* would block, register interest */ - if(data->hyp.write_waker) - hyper_waker_free(data->hyp.write_waker); - data->hyp.write_waker = hyper_context_waker(ctx); - if(!data->hyp.write_waker) { - failf(data, "Couldn't make the write hyper_context_waker"); - return HYPER_IO_ERROR; - } - return HYPER_IO_PENDING; - } - else if(result) { - failf(data, "Curl_write failed"); - return HYPER_IO_ERROR; - } - return (size_t)nwrote; -} - -static int hyper_each_header(void *userdata, - const uint8_t *name, - size_t name_len, - const uint8_t *value, - size_t value_len) -{ - struct Curl_easy *data = (struct Curl_easy *)userdata; - size_t len; - char *headp; - CURLcode result; - int writetype; - - if(name_len + value_len + 2 > CURL_MAX_HTTP_HEADER) { - failf(data, "Too long response header"); - data->state.hresult = CURLE_OUT_OF_MEMORY; - return HYPER_ITER_BREAK; - } - - if(!data->req.bytecount) - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - Curl_dyn_reset(&data->state.headerb); - if(name_len) { - if(Curl_dyn_addf(&data->state.headerb, "%.*s: %.*s\r\n", - (int) name_len, name, (int) value_len, value)) - return HYPER_ITER_BREAK; - } - else { - if(Curl_dyn_addn(&data->state.headerb, STRCONST("\r\n"))) - return HYPER_ITER_BREAK; - } - len = Curl_dyn_len(&data->state.headerb); - headp = Curl_dyn_ptr(&data->state.headerb); - - result = Curl_http_header(data, data->conn, headp); - if(result) { - data->state.hresult = result; - return HYPER_ITER_BREAK; - } - - Curl_debug(data, CURLINFO_HEADER_IN, headp, len); - - if(!data->state.hconnect || !data->set.suppress_connect_headers) { - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - if(data->state.hconnect) - writetype |= CLIENTWRITE_CONNECT; - if(data->req.httpcode/100 == 1) - writetype |= CLIENTWRITE_1XX; - result = Curl_client_write(data, writetype, headp, len); - if(result) { - data->state.hresult = CURLE_ABORTED_BY_CALLBACK; - return HYPER_ITER_BREAK; - } - } - - data->info.header_size += (curl_off_t)len; - data->req.headerbytecount += (curl_off_t)len; - return HYPER_ITER_CONTINUE; -} - -static int hyper_body_chunk(void *userdata, const hyper_buf *chunk) -{ - char *buf = (char *)hyper_buf_bytes(chunk); - size_t len = hyper_buf_len(chunk); - struct Curl_easy *data = (struct Curl_easy *)userdata; - struct SingleRequest *k = &data->req; - CURLcode result = CURLE_OK; - - if(0 == k->bodywrites++) { - bool done = FALSE; -#if defined(USE_NTLM) - struct connectdata *conn = data->conn; - if(conn->bits.close && - (((data->req.httpcode == 401) && - (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || - ((data->req.httpcode == 407) && - (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { - infof(data, "Connection closed while negotiating NTLM"); - data->state.authproblem = TRUE; - Curl_safefree(data->req.newurl); - } -#endif - if(data->state.expect100header) { - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - if(data->req.httpcode < 400) { - k->exp100 = EXP100_SEND_DATA; - if(data->hyp.exp100_waker) { - hyper_waker_wake(data->hyp.exp100_waker); - data->hyp.exp100_waker = NULL; - } - } - else { /* >= 4xx */ - k->exp100 = EXP100_FAILED; - } - } - if(data->state.hconnect && (data->req.httpcode/100 != 2) && - data->state.authproxy.done) { - done = TRUE; - result = CURLE_OK; - } - else - result = Curl_http_firstwrite(data, data->conn, &done); - if(result || done) { - infof(data, "Return early from hyper_body_chunk"); - data->state.hresult = result; - return HYPER_ITER_BREAK; - } - } - if(k->ignorebody) - return HYPER_ITER_CONTINUE; - if(0 == len) - return HYPER_ITER_CONTINUE; - Curl_debug(data, CURLINFO_DATA_IN, buf, len); - if(!data->set.http_ce_skip && k->writer_stack) - /* content-encoded data */ - result = Curl_unencode_write(data, k->writer_stack, buf, len); - else - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, len); - - if(result) { - data->state.hresult = result; - return HYPER_ITER_BREAK; - } - - data->req.bytecount += len; - Curl_pgrsSetDownloadCounter(data, data->req.bytecount); - return HYPER_ITER_CONTINUE; -} - -/* - * Hyper does not consider the status line, the first line in an HTTP/1 - * response, to be a header. The libcurl API does. This function sends the - * status line in the header callback. */ -static CURLcode status_line(struct Curl_easy *data, - struct connectdata *conn, - uint16_t http_status, - int http_version, - const uint8_t *reason, size_t rlen) -{ - CURLcode result; - size_t len; - const char *vstr; - int writetype; - vstr = http_version == HYPER_HTTP_VERSION_1_1 ? "1.1" : - (http_version == HYPER_HTTP_VERSION_2 ? "2" : "1.0"); - - /* We need to set 'httpcodeq' for functions that check the response code in - a single place. */ - data->req.httpcode = http_status; - - if(data->state.hconnect) - /* CONNECT */ - data->info.httpproxycode = http_status; - else { - conn->httpversion = - http_version == HYPER_HTTP_VERSION_1_1 ? 11 : - (http_version == HYPER_HTTP_VERSION_2 ? 20 : 10); - if(http_version == HYPER_HTTP_VERSION_1_0) - data->state.httpwant = CURL_HTTP_VERSION_1_0; - - result = Curl_http_statusline(data, conn); - if(result) - return result; - } - - Curl_dyn_reset(&data->state.headerb); - - result = Curl_dyn_addf(&data->state.headerb, "HTTP/%s %03d %.*s\r\n", - vstr, - (int)http_status, - (int)rlen, reason); - if(result) - return result; - len = Curl_dyn_len(&data->state.headerb); - Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&data->state.headerb), - len); - - if(!data->state.hconnect || !data->set.suppress_connect_headers) { - writetype = CLIENTWRITE_HEADER|CLIENTWRITE_STATUS; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - result = Curl_client_write(data, writetype, - Curl_dyn_ptr(&data->state.headerb), len); - if(result) - return result; - } - data->info.header_size += (curl_off_t)len; - data->req.headerbytecount += (curl_off_t)len; - return CURLE_OK; -} - -/* - * Hyper does not pass on the last empty response header. The libcurl API - * does. This function sends an empty header in the header callback. - */ -static CURLcode empty_header(struct Curl_easy *data) -{ - CURLcode result = Curl_http_size(data); - if(!result) { - result = hyper_each_header(data, NULL, 0, NULL, 0) ? - CURLE_WRITE_ERROR : CURLE_OK; - if(result) - failf(data, "hyperstream: couldn't pass blank header"); - } - return result; -} - -CURLcode Curl_hyper_stream(struct Curl_easy *data, - struct connectdata *conn, - int *didwhat, - bool *done, - int select_res) -{ - hyper_response *resp = NULL; - uint16_t http_status; - int http_version; - hyper_headers *headers = NULL; - hyper_body *resp_body = NULL; - struct hyptransfer *h = &data->hyp; - hyper_task *task; - hyper_task *foreach; - hyper_error *hypererr = NULL; - const uint8_t *reasonp; - size_t reason_len; - CURLcode result = CURLE_OK; - struct SingleRequest *k = &data->req; - (void)conn; - - if(k->exp100 > EXP100_SEND_DATA) { - struct curltime now = Curl_now(); - timediff_t ms = Curl_timediff(now, k->start100); - if(ms >= data->set.expect_100_timeout) { - /* we've waited long enough, continue anyway */ - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - infof(data, "Done waiting for 100-continue"); - if(data->hyp.exp100_waker) { - hyper_waker_wake(data->hyp.exp100_waker); - data->hyp.exp100_waker = NULL; - } - } - } - - if(select_res & CURL_CSELECT_IN) { - if(h->read_waker) - hyper_waker_wake(h->read_waker); - h->read_waker = NULL; - } - if(select_res & CURL_CSELECT_OUT) { - if(h->write_waker) - hyper_waker_wake(h->write_waker); - h->write_waker = NULL; - } - - *done = FALSE; - do { - hyper_task_return_type t; - task = hyper_executor_poll(h->exec); - if(!task) { - *didwhat = KEEP_RECV; - break; - } - t = hyper_task_type(task); - switch(t) { - case HYPER_TASK_ERROR: - hypererr = hyper_task_value(task); - break; - case HYPER_TASK_RESPONSE: - resp = hyper_task_value(task); - break; - default: - break; - } - hyper_task_free(task); - - if(t == HYPER_TASK_ERROR) { - if(data->state.hresult) { - /* override Hyper's view, might not even be an error */ - result = data->state.hresult; - infof(data, "hyperstream is done (by early callback)"); - } - else { - uint8_t errbuf[256]; - size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf)); - hyper_code code = hyper_error_code(hypererr); - failf(data, "Hyper: [%d] %.*s", (int)code, (int)errlen, errbuf); - if(code == HYPERE_ABORTED_BY_CALLBACK) - result = CURLE_OK; - else if((code == HYPERE_UNEXPECTED_EOF) && !data->req.bytecount) - result = CURLE_GOT_NOTHING; - else if(code == HYPERE_INVALID_PEER_MESSAGE) - result = CURLE_UNSUPPORTED_PROTOCOL; /* maybe */ - else - result = CURLE_RECV_ERROR; - } - *done = TRUE; - hyper_error_free(hypererr); - break; - } - else if(h->endtask == task) { - /* end of transfer, forget the task handled, we might get a - * new one with the same address in the future. */ - *done = TRUE; - h->endtask = NULL; - infof(data, "hyperstream is done"); - if(!k->bodywrites) { - /* hyper doesn't always call the body write callback */ - bool stilldone; - result = Curl_http_firstwrite(data, data->conn, &stilldone); - } - break; - } - else if(t != HYPER_TASK_RESPONSE) { - *didwhat = KEEP_RECV; - break; - } - /* HYPER_TASK_RESPONSE */ - - *didwhat = KEEP_RECV; - if(!resp) { - failf(data, "hyperstream: couldn't get response"); - return CURLE_RECV_ERROR; - } - - http_status = hyper_response_status(resp); - http_version = hyper_response_version(resp); - reasonp = hyper_response_reason_phrase(resp); - reason_len = hyper_response_reason_phrase_len(resp); - - if(http_status == 417 && data->state.expect100header) { - infof(data, "Got 417 while waiting for a 100"); - data->state.disableexpect = TRUE; - data->req.newurl = strdup(data->state.url); - Curl_done_sending(data, k); - } - - result = status_line(data, conn, - http_status, http_version, reasonp, reason_len); - if(result) - break; - - headers = hyper_response_headers(resp); - if(!headers) { - failf(data, "hyperstream: couldn't get response headers"); - result = CURLE_RECV_ERROR; - break; - } - - /* the headers are already received */ - hyper_headers_foreach(headers, hyper_each_header, data); - if(data->state.hresult) { - result = data->state.hresult; - break; - } - - result = empty_header(data); - if(result) - break; - - k->deductheadercount = - (100 <= http_status && 199 >= http_status)?k->headerbytecount:0; -#ifdef USE_WEBSOCKETS - if(k->upgr101 == UPGR101_WS) { - if(http_status == 101) { - /* verify the response */ - result = Curl_ws_accept(data, NULL, 0); - if(result) - return result; - } - else { - failf(data, "Expected 101, got %u", k->httpcode); - result = CURLE_HTTP_RETURNED_ERROR; - break; - } - } -#endif - - /* Curl_http_auth_act() checks what authentication methods that are - * available and decides which one (if any) to use. It will set 'newurl' - * if an auth method was picked. */ - result = Curl_http_auth_act(data); - if(result) - break; - - resp_body = hyper_response_body(resp); - if(!resp_body) { - failf(data, "hyperstream: couldn't get response body"); - result = CURLE_RECV_ERROR; - break; - } - foreach = hyper_body_foreach(resp_body, hyper_body_chunk, data); - if(!foreach) { - failf(data, "hyperstream: body foreach failed"); - result = CURLE_OUT_OF_MEMORY; - break; - } - DEBUGASSERT(hyper_task_type(foreach) == HYPER_TASK_EMPTY); - if(HYPERE_OK != hyper_executor_push(h->exec, foreach)) { - failf(data, "Couldn't hyper_executor_push the body-foreach"); - result = CURLE_OUT_OF_MEMORY; - break; - } - h->endtask = foreach; - - hyper_response_free(resp); - resp = NULL; - } while(1); - if(resp) - hyper_response_free(resp); - return result; -} - -static CURLcode debug_request(struct Curl_easy *data, - const char *method, - const char *path, - bool h2) -{ - char *req = aprintf("%s %s HTTP/%s\r\n", method, path, - h2?"2":"1.1"); - if(!req) - return CURLE_OUT_OF_MEMORY; - Curl_debug(data, CURLINFO_HEADER_OUT, req, strlen(req)); - free(req); - return CURLE_OK; -} - -/* - * Given a full header line "name: value" (optional CRLF in the input, should - * be in the output), add to Hyper and send to the debug callback. - * - * Supports multiple headers. - */ - -CURLcode Curl_hyper_header(struct Curl_easy *data, hyper_headers *headers, - const char *line) -{ - const char *p; - const char *n; - size_t nlen; - const char *v; - size_t vlen; - bool newline = TRUE; - int numh = 0; - - if(!line) - return CURLE_OK; - n = line; - do { - size_t linelen = 0; - - p = strchr(n, ':'); - if(!p) - /* this is fine if we already added at least one header */ - return numh ? CURLE_OK : CURLE_BAD_FUNCTION_ARGUMENT; - nlen = p - n; - p++; /* move past the colon */ - while(*p == ' ') - p++; - v = p; - p = strchr(v, '\r'); - if(!p) { - p = strchr(v, '\n'); - if(p) - linelen = 1; /* LF only */ - else { - p = strchr(v, '\0'); - newline = FALSE; /* no newline */ - } - } - else - linelen = 2; /* CRLF ending */ - linelen += (p - n); - vlen = p - v; - - if(HYPERE_OK != hyper_headers_add(headers, (uint8_t *)n, nlen, - (uint8_t *)v, vlen)) { - failf(data, "hyper refused to add header '%s'", line); - return CURLE_OUT_OF_MEMORY; - } - if(data->set.verbose) { - char *ptr = NULL; - if(!newline) { - ptr = aprintf("%.*s\r\n", (int)linelen, line); - if(!ptr) - return CURLE_OUT_OF_MEMORY; - Curl_debug(data, CURLINFO_HEADER_OUT, ptr, linelen + 2); - free(ptr); - } - else - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)n, linelen); - } - numh++; - n += linelen; - } while(newline); - return CURLE_OK; -} - -static CURLcode request_target(struct Curl_easy *data, - struct connectdata *conn, - const char *method, - bool h2, - hyper_request *req) -{ - CURLcode result; - struct dynbuf r; - - Curl_dyn_init(&r, DYN_HTTP_REQUEST); - - result = Curl_http_target(data, conn, &r); - if(result) - return result; - - if(h2 && hyper_request_set_uri_parts(req, - /* scheme */ - (uint8_t *)data->state.up.scheme, - strlen(data->state.up.scheme), - /* authority */ - (uint8_t *)conn->host.name, - strlen(conn->host.name), - /* path_and_query */ - (uint8_t *)Curl_dyn_uptr(&r), - Curl_dyn_len(&r))) { - failf(data, "error setting uri parts to hyper"); - result = CURLE_OUT_OF_MEMORY; - } - else if(!h2 && hyper_request_set_uri(req, (uint8_t *)Curl_dyn_uptr(&r), - Curl_dyn_len(&r))) { - failf(data, "error setting uri to hyper"); - result = CURLE_OUT_OF_MEMORY; - } - else - result = debug_request(data, method, Curl_dyn_ptr(&r), h2); - - Curl_dyn_free(&r); - - return result; -} - -static int uploadpostfields(void *userdata, hyper_context *ctx, - hyper_buf **chunk) -{ - struct Curl_easy *data = (struct Curl_easy *)userdata; - (void)ctx; - if(data->req.exp100 > EXP100_SEND_DATA) { - if(data->req.exp100 == EXP100_FAILED) - return HYPER_POLL_ERROR; - - /* still waiting confirmation */ - if(data->hyp.exp100_waker) - hyper_waker_free(data->hyp.exp100_waker); - data->hyp.exp100_waker = hyper_context_waker(ctx); - return HYPER_POLL_PENDING; - } - if(data->req.upload_done) - *chunk = NULL; /* nothing more to deliver */ - else { - /* send everything off in a single go */ - hyper_buf *copy = hyper_buf_copy(data->set.postfields, - (size_t)data->req.p.http->postsize); - if(copy) - *chunk = copy; - else { - data->state.hresult = CURLE_OUT_OF_MEMORY; - return HYPER_POLL_ERROR; - } - /* increasing the writebytecount here is a little premature but we - don't know exactly when the body is sent */ - data->req.writebytecount += (size_t)data->req.p.http->postsize; - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - data->req.upload_done = TRUE; - } - return HYPER_POLL_READY; -} - -static int uploadstreamed(void *userdata, hyper_context *ctx, - hyper_buf **chunk) -{ - size_t fillcount; - struct Curl_easy *data = (struct Curl_easy *)userdata; - struct connectdata *conn = (struct connectdata *)data->conn; - CURLcode result; - (void)ctx; - - if(data->req.exp100 > EXP100_SEND_DATA) { - if(data->req.exp100 == EXP100_FAILED) - return HYPER_POLL_ERROR; - - /* still waiting confirmation */ - if(data->hyp.exp100_waker) - hyper_waker_free(data->hyp.exp100_waker); - data->hyp.exp100_waker = hyper_context_waker(ctx); - return HYPER_POLL_PENDING; - } - - if(data->req.upload_chunky && conn->bits.authneg) { - fillcount = 0; - data->req.upload_chunky = FALSE; - result = CURLE_OK; - } - else { - result = Curl_fillreadbuffer(data, data->set.upload_buffer_size, - &fillcount); - } - if(result) { - data->state.hresult = result; - return HYPER_POLL_ERROR; - } - if(!fillcount) { - if((data->req.keepon & KEEP_SEND_PAUSE) != KEEP_SEND_PAUSE) - /* done! */ - *chunk = NULL; - else { - /* paused, save a waker */ - if(data->hyp.send_body_waker) - hyper_waker_free(data->hyp.send_body_waker); - data->hyp.send_body_waker = hyper_context_waker(ctx); - return HYPER_POLL_PENDING; - } - } - else { - hyper_buf *copy = hyper_buf_copy((uint8_t *)data->state.ulbuf, fillcount); - if(copy) - *chunk = copy; - else { - data->state.hresult = CURLE_OUT_OF_MEMORY; - return HYPER_POLL_ERROR; - } - /* increasing the writebytecount here is a little premature but we - don't know exactly when the body is sent */ - data->req.writebytecount += fillcount; - Curl_pgrsSetUploadCounter(data, fillcount); - } - return HYPER_POLL_READY; -} - -/* - * bodysend() sets up headers in the outgoing request for an HTTP transfer that - * sends a body - */ - -static CURLcode bodysend(struct Curl_easy *data, - struct connectdata *conn, - hyper_headers *headers, - hyper_request *hyperreq, - Curl_HttpReq httpreq) -{ - struct HTTP *http = data->req.p.http; - CURLcode result = CURLE_OK; - struct dynbuf req; - if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) - Curl_pgrsSetUploadSize(data, 0); /* no request body */ - else { - hyper_body *body; - Curl_dyn_init(&req, DYN_HTTP_REQUEST); - result = Curl_http_bodysend(data, conn, &req, httpreq); - - if(!result) - result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&req)); - - Curl_dyn_free(&req); - - body = hyper_body_new(); - hyper_body_set_userdata(body, data); - if(data->set.postfields) - hyper_body_set_data_func(body, uploadpostfields); - else { - result = Curl_get_upload_buffer(data); - if(result) - return result; - /* init the "upload from here" pointer */ - data->req.upload_fromhere = data->state.ulbuf; - hyper_body_set_data_func(body, uploadstreamed); - } - if(HYPERE_OK != hyper_request_set_body(hyperreq, body)) { - /* fail */ - hyper_body_free(body); - result = CURLE_OUT_OF_MEMORY; - } - } - http->sending = HTTPSEND_BODY; - return result; -} - -static CURLcode cookies(struct Curl_easy *data, - struct connectdata *conn, - hyper_headers *headers) -{ - struct dynbuf req; - CURLcode result; - Curl_dyn_init(&req, DYN_HTTP_REQUEST); - - result = Curl_http_cookies(data, conn, &req); - if(!result) - result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&req)); - Curl_dyn_free(&req); - return result; -} - -/* called on 1xx responses */ -static void http1xx_cb(void *arg, struct hyper_response *resp) -{ - struct Curl_easy *data = (struct Curl_easy *)arg; - hyper_headers *headers = NULL; - CURLcode result = CURLE_OK; - uint16_t http_status; - int http_version; - const uint8_t *reasonp; - size_t reason_len; - - infof(data, "Got HTTP 1xx informational"); - - http_status = hyper_response_status(resp); - http_version = hyper_response_version(resp); - reasonp = hyper_response_reason_phrase(resp); - reason_len = hyper_response_reason_phrase_len(resp); - - result = status_line(data, data->conn, - http_status, http_version, reasonp, reason_len); - if(!result) { - headers = hyper_response_headers(resp); - if(!headers) { - failf(data, "hyperstream: couldn't get 1xx response headers"); - result = CURLE_RECV_ERROR; - } - } - data->state.hresult = result; - - if(!result) { - /* the headers are already received */ - hyper_headers_foreach(headers, hyper_each_header, data); - /* this callback also sets data->state.hresult on error */ - - if(empty_header(data)) - result = CURLE_OUT_OF_MEMORY; - } - - if(data->state.hresult) - infof(data, "ERROR in 1xx, bail out"); -} - -/* - * Curl_http() gets called from the generic multi_do() function when an HTTP - * request is to be performed. This creates and sends a properly constructed - * HTTP request. - */ -CURLcode Curl_http(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct hyptransfer *h = &data->hyp; - hyper_io *io = NULL; - hyper_clientconn_options *options = NULL; - hyper_task *task = NULL; /* for the handshake */ - hyper_task *sendtask = NULL; /* for the send */ - hyper_clientconn *client = NULL; - hyper_request *req = NULL; - hyper_headers *headers = NULL; - hyper_task *handshake = NULL; - CURLcode result; - const char *p_accept; /* Accept: string */ - const char *method; - Curl_HttpReq httpreq; - bool h2 = FALSE; - const char *te = NULL; /* transfer-encoding */ - hyper_code rc; - - /* Always consider the DO phase done after this function call, even if there - may be parts of the request that is not yet sent, since we can deal with - the rest of the request in the PERFORM phase. */ - *done = TRUE; - - infof(data, "Time for the Hyper dance"); - memset(h, 0, sizeof(struct hyptransfer)); - - result = Curl_http_host(data, conn); - if(result) - return result; - - Curl_http_method(data, conn, &method, &httpreq); - - /* setup the authentication headers */ - { - char *pq = NULL; - if(data->state.up.query) { - pq = aprintf("%s?%s", data->state.up.path, data->state.up.query); - if(!pq) - return CURLE_OUT_OF_MEMORY; - } - result = Curl_http_output_auth(data, conn, method, httpreq, - (pq ? pq : data->state.up.path), FALSE); - free(pq); - if(result) - return result; - } - - result = Curl_http_resume(data, conn, httpreq); - if(result) - return result; - - result = Curl_http_range(data, httpreq); - if(result) - return result; - - result = Curl_http_useragent(data); - if(result) - return result; - - io = hyper_io_new(); - if(!io) { - failf(data, "Couldn't create hyper IO"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - /* tell Hyper how to read/write network data */ - hyper_io_set_userdata(io, data); - hyper_io_set_read(io, Curl_hyper_recv); - hyper_io_set_write(io, Curl_hyper_send); - - /* create an executor to poll futures */ - if(!h->exec) { - h->exec = hyper_executor_new(); - if(!h->exec) { - failf(data, "Couldn't create hyper executor"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - } - - options = hyper_clientconn_options_new(); - if(!options) { - failf(data, "Couldn't create hyper client options"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - if(conn->alpn == CURL_HTTP_VERSION_2) { - hyper_clientconn_options_http2(options, 1); - h2 = TRUE; - } - hyper_clientconn_options_set_preserve_header_case(options, 1); - hyper_clientconn_options_set_preserve_header_order(options, 1); - hyper_clientconn_options_http1_allow_multiline_headers(options, 1); - - hyper_clientconn_options_exec(options, h->exec); - - /* "Both the `io` and the `options` are consumed in this function call" */ - handshake = hyper_clientconn_handshake(io, options); - if(!handshake) { - failf(data, "Couldn't create hyper client handshake"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - io = NULL; - options = NULL; - - if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) { - failf(data, "Couldn't hyper_executor_push the handshake"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - handshake = NULL; /* ownership passed on */ - - task = hyper_executor_poll(h->exec); - if(!task) { - failf(data, "Couldn't hyper_executor_poll the handshake"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - client = hyper_task_value(task); - hyper_task_free(task); - - req = hyper_request_new(); - if(!req) { - failf(data, "Couldn't hyper_request_new"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - if(!Curl_use_http_1_1plus(data, conn)) { - if(HYPERE_OK != hyper_request_set_version(req, - HYPER_HTTP_VERSION_1_0)) { - failf(data, "error setting HTTP version"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - } - else { - if(!h2 && !data->state.disableexpect) { - data->state.expect100header = TRUE; - } - } - - if(hyper_request_set_method(req, (uint8_t *)method, strlen(method))) { - failf(data, "error setting method"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - result = request_target(data, conn, method, h2, req); - if(result) - goto error; - - headers = hyper_request_headers(req); - if(!headers) { - failf(data, "hyper_request_headers"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - rc = hyper_request_on_informational(req, http1xx_cb, data); - if(rc) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - result = Curl_http_body(data, conn, httpreq, &te); - if(result) - goto error; - - if(!h2) { - if(data->state.aptr.host) { - result = Curl_hyper_header(data, headers, data->state.aptr.host); - if(result) - goto error; - } - } - else { - /* For HTTP/2, we show the Host: header as if we sent it, to make it look - like for HTTP/1 but it isn't actually sent since :authority is then - used. */ - Curl_debug(data, CURLINFO_HEADER_OUT, data->state.aptr.host, - strlen(data->state.aptr.host)); - } - - if(data->state.aptr.proxyuserpwd) { - result = Curl_hyper_header(data, headers, data->state.aptr.proxyuserpwd); - if(result) - goto error; - } - - if(data->state.aptr.userpwd) { - result = Curl_hyper_header(data, headers, data->state.aptr.userpwd); - if(result) - goto error; - } - - if((data->state.use_range && data->state.aptr.rangeline)) { - result = Curl_hyper_header(data, headers, data->state.aptr.rangeline); - if(result) - goto error; - } - - if(data->set.str[STRING_USERAGENT] && - *data->set.str[STRING_USERAGENT] && - data->state.aptr.uagent) { - result = Curl_hyper_header(data, headers, data->state.aptr.uagent); - if(result) - goto error; - } - - p_accept = Curl_checkheaders(data, - STRCONST("Accept"))?NULL:"Accept: */*\r\n"; - if(p_accept) { - result = Curl_hyper_header(data, headers, p_accept); - if(result) - goto error; - } - if(te) { - result = Curl_hyper_header(data, headers, te); - if(result) - goto error; - } - -#ifndef CURL_DISABLE_ALTSVC - if(conn->bits.altused && !Curl_checkheaders(data, STRCONST("Alt-Used"))) { - char *altused = aprintf("Alt-Used: %s:%d\r\n", - conn->conn_to_host.name, conn->conn_to_port); - if(!altused) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - result = Curl_hyper_header(data, headers, altused); - if(result) - goto error; - free(altused); - } -#endif - -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy && - !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && - !Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) { - result = Curl_hyper_header(data, headers, "Proxy-Connection: Keep-Alive"); - if(result) - goto error; - } -#endif - - Curl_safefree(data->state.aptr.ref); - if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer"))) { - data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer); - if(!data->state.aptr.ref) - result = CURLE_OUT_OF_MEMORY; - else - result = Curl_hyper_header(data, headers, data->state.aptr.ref); - if(result) - goto error; - } - -#ifdef HAVE_LIBZ - /* we only consider transfer-encoding magic if libz support is built-in */ - result = Curl_transferencode(data); - if(result) - goto error; - result = Curl_hyper_header(data, headers, data->state.aptr.te); - if(result) - goto error; -#endif - - if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && - data->set.str[STRING_ENCODING]) { - Curl_safefree(data->state.aptr.accept_encoding); - data->state.aptr.accept_encoding = - aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - if(!data->state.aptr.accept_encoding) - result = CURLE_OUT_OF_MEMORY; - else - result = Curl_hyper_header(data, headers, - data->state.aptr.accept_encoding); - if(result) - goto error; - } - else - Curl_safefree(data->state.aptr.accept_encoding); - - result = cookies(data, conn, headers); - if(result) - goto error; - - if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) - result = Curl_ws_request(data, headers); - - result = Curl_add_timecondition(data, headers); - if(result) - goto error; - - result = Curl_add_custom_headers(data, FALSE, headers); - if(result) - goto error; - - result = bodysend(data, conn, headers, req, httpreq); - if(result) - goto error; - - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"\r\n", 2); - - if(data->req.upload_chunky && conn->bits.authneg) { - data->req.upload_chunky = TRUE; - } - else { - data->req.upload_chunky = FALSE; - } - sendtask = hyper_clientconn_send(client, req); - if(!sendtask) { - failf(data, "hyper_clientconn_send"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) { - failf(data, "Couldn't hyper_executor_push the send"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - hyper_clientconn_free(client); - - if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) { - /* HTTP GET/HEAD download */ - Curl_pgrsSetUploadSize(data, 0); /* nothing */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); - } - conn->datastream = Curl_hyper_stream; - if(data->state.expect100header) - /* Timeout count starts now since with Hyper we don't know exactly when - the full request has been sent. */ - data->req.start100 = Curl_now(); - - /* clear userpwd and proxyuserpwd to avoid re-using old credentials - * from re-used connections */ - Curl_safefree(data->state.aptr.userpwd); - Curl_safefree(data->state.aptr.proxyuserpwd); - return CURLE_OK; -error: - DEBUGASSERT(result); - if(io) - hyper_io_free(io); - - if(options) - hyper_clientconn_options_free(options); - - if(handshake) - hyper_task_free(handshake); - - return result; -} - -void Curl_hyper_done(struct Curl_easy *data) -{ - struct hyptransfer *h = &data->hyp; - if(h->exec) { - hyper_executor_free(h->exec); - h->exec = NULL; - } - if(h->read_waker) { - hyper_waker_free(h->read_waker); - h->read_waker = NULL; - } - if(h->write_waker) { - hyper_waker_free(h->write_waker); - h->write_waker = NULL; - } - if(h->exp100_waker) { - hyper_waker_free(h->exp100_waker); - h->exp100_waker = NULL; - } -} - -#endif /* !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) */ diff --git a/Utilities/cmcurl/lib/c-hyper.h b/Utilities/cmcurl/lib/c-hyper.h deleted file mode 100644 index 4218cda75fe..00000000000 --- a/Utilities/cmcurl/lib/c-hyper.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef HEADER_CURL_HYPER_H -#define HEADER_CURL_HYPER_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.haxx.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) - -#include - -/* per-transfer data for the Hyper backend */ -struct hyptransfer { - hyper_waker *write_waker; - hyper_waker *read_waker; - const hyper_executor *exec; - hyper_task *endtask; - hyper_waker *exp100_waker; - hyper_waker *send_body_waker; -}; - -size_t Curl_hyper_recv(void *userp, hyper_context *ctx, - uint8_t *buf, size_t buflen); -size_t Curl_hyper_send(void *userp, hyper_context *ctx, - const uint8_t *buf, size_t buflen); -CURLcode Curl_hyper_stream(struct Curl_easy *data, - struct connectdata *conn, - int *didwhat, - bool *done, - int select_res); - -CURLcode Curl_hyper_header(struct Curl_easy *data, hyper_headers *headers, - const char *line); -void Curl_hyper_done(struct Curl_easy *); - -#else -#define Curl_hyper_done(x) - -#endif /* !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) */ -#endif /* HEADER_CURL_HYPER_H */ diff --git a/Utilities/cmcurl/lib/cf-h1-proxy.c b/Utilities/cmcurl/lib/cf-h1-proxy.c index b42c4e60558..df6f575454a 100644 --- a/Utilities/cmcurl/lib/cf-h1-proxy.c +++ b/Utilities/cmcurl/lib/cf-h1-proxy.c @@ -27,13 +27,11 @@ #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) #include -#ifdef USE_HYPER -#include -#endif #include "urldata.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "sendf.h" #include "http.h" +#include "http1.h" #include "http_proxy.h" #include "url.h" #include "select.h" @@ -41,11 +39,12 @@ #include "cfilters.h" #include "cf-h1-proxy.h" #include "connect.h" -#include "curl_log.h" -#include "curlx.h" +#include "curl_trc.h" +#include "strcase.h" #include "vtls/vtls.h" #include "transfer.h" #include "multiif.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -54,170 +53,135 @@ typedef enum { - TUNNEL_INIT, /* init/default/no tunnel state */ - TUNNEL_CONNECT, /* CONNECT request is being send */ - TUNNEL_RECEIVE, /* CONNECT answer is being received */ - TUNNEL_RESPONSE, /* CONNECT response received completely */ - TUNNEL_ESTABLISHED, - TUNNEL_FAILED -} tunnel_state; + H1_TUNNEL_INIT, /* init/default/no tunnel state */ + H1_TUNNEL_CONNECT, /* CONNECT request is being send */ + H1_TUNNEL_RECEIVE, /* CONNECT answer is being received */ + H1_TUNNEL_RESPONSE, /* CONNECT response received completely */ + H1_TUNNEL_ESTABLISHED, + H1_TUNNEL_FAILED +} h1_tunnel_state; /* struct for HTTP CONNECT tunneling */ -struct tunnel_state { - int sockindex; - const char *hostname; - int remote_port; - struct HTTP CONNECT; +struct h1_tunnel_state { struct dynbuf rcvbuf; - struct dynbuf req; - size_t nsend; + struct dynbuf request_data; + size_t nsent; size_t headerlines; + struct Curl_chunker ch; enum keeponval { KEEPON_DONE, KEEPON_CONNECT, KEEPON_IGNORE } keepon; curl_off_t cl; /* size of content to read and ignore */ - tunnel_state tunnel_state; + h1_tunnel_state tunnel_state; BIT(chunked_encoding); BIT(close_connection); }; -static bool tunnel_is_established(struct tunnel_state *ts) +static bool tunnel_is_established(struct h1_tunnel_state *ts) { - return ts && (ts->tunnel_state == TUNNEL_ESTABLISHED); + return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED); } -static bool tunnel_is_failed(struct tunnel_state *ts) +static bool tunnel_is_failed(struct h1_tunnel_state *ts) { - return ts && (ts->tunnel_state == TUNNEL_FAILED); + return ts && (ts->tunnel_state == H1_TUNNEL_FAILED); } -static CURLcode tunnel_reinit(struct tunnel_state *ts, - struct connectdata *conn, - struct Curl_easy *data) +static CURLcode tunnel_reinit(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) { (void)data; + (void)cf; DEBUGASSERT(ts); - Curl_dyn_reset(&ts->rcvbuf); - Curl_dyn_reset(&ts->req); - ts->tunnel_state = TUNNEL_INIT; + curlx_dyn_reset(&ts->rcvbuf); + curlx_dyn_reset(&ts->request_data); + ts->tunnel_state = H1_TUNNEL_INIT; ts->keepon = KEEPON_CONNECT; ts->cl = 0; ts->close_connection = FALSE; - - if(conn->bits.conn_to_host) - ts->hostname = conn->conn_to_host.name; - else if(ts->sockindex == SECONDARYSOCKET) - ts->hostname = conn->secondaryhostname; - else - ts->hostname = conn->host.name; - - if(ts->sockindex == SECONDARYSOCKET) - ts->remote_port = conn->secondary_port; - else if(conn->bits.conn_to_port) - ts->remote_port = conn->conn_to_port; - else - ts->remote_port = conn->remote_port; - return CURLE_OK; } -static CURLcode tunnel_init(struct tunnel_state **pts, +static CURLcode tunnel_init(struct Curl_cfilter *cf, struct Curl_easy *data, - struct connectdata *conn, - int sockindex) + struct h1_tunnel_state **pts) { - struct tunnel_state *ts; - CURLcode result; + struct h1_tunnel_state *ts; - if(conn->handler->flags & PROTOPT_NOTCPPROXY) { - failf(data, "%s cannot be done over CONNECT", conn->handler->scheme); + if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) { + failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme); return CURLE_UNSUPPORTED_PROTOCOL; } - /* we might need the upload buffer for streaming a partial request */ - result = Curl_get_upload_buffer(data); - if(result) - return result; - ts = calloc(1, sizeof(*ts)); if(!ts) return CURLE_OUT_OF_MEMORY; - ts->sockindex = sockindex; infof(data, "allocate connect buffer"); - Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); - Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST); + curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); + curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); + Curl_httpchunk_init(data, &ts->ch, TRUE); *pts = ts; - connkeep(conn, "HTTP proxy CONNECT"); - return tunnel_reinit(ts, conn, data); + connkeep(cf->conn, "HTTP proxy CONNECT"); + return tunnel_reinit(cf, data, ts); } -static void tunnel_go_state(struct Curl_cfilter *cf, - struct tunnel_state *ts, - tunnel_state new_state, - struct Curl_easy *data) +static void h1_tunnel_go_state(struct Curl_cfilter *cf, + struct h1_tunnel_state *ts, + h1_tunnel_state new_state, + struct Curl_easy *data) { if(ts->tunnel_state == new_state) return; - /* leaving this one */ - switch(ts->tunnel_state) { - case TUNNEL_CONNECT: - data->req.ignorebody = FALSE; - break; - default: - break; - } /* entering this one */ switch(new_state) { - case TUNNEL_INIT: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'init'")); - tunnel_reinit(ts, cf->conn, data); + case H1_TUNNEL_INIT: + CURL_TRC_CF(data, cf, "new tunnel state 'init'"); + tunnel_reinit(cf, data, ts); break; - case TUNNEL_CONNECT: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'connect'")); - ts->tunnel_state = TUNNEL_CONNECT; + case H1_TUNNEL_CONNECT: + CURL_TRC_CF(data, cf, "new tunnel state 'connect'"); + ts->tunnel_state = H1_TUNNEL_CONNECT; ts->keepon = KEEPON_CONNECT; - Curl_dyn_reset(&ts->rcvbuf); + curlx_dyn_reset(&ts->rcvbuf); break; - case TUNNEL_RECEIVE: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'receive'")); - ts->tunnel_state = TUNNEL_RECEIVE; + case H1_TUNNEL_RECEIVE: + CURL_TRC_CF(data, cf, "new tunnel state 'receive'"); + ts->tunnel_state = H1_TUNNEL_RECEIVE; break; - case TUNNEL_RESPONSE: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'response'")); - ts->tunnel_state = TUNNEL_RESPONSE; + case H1_TUNNEL_RESPONSE: + CURL_TRC_CF(data, cf, "new tunnel state 'response'"); + ts->tunnel_state = H1_TUNNEL_RESPONSE; break; - case TUNNEL_ESTABLISHED: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'established'")); + case H1_TUNNEL_ESTABLISHED: + CURL_TRC_CF(data, cf, "new tunnel state 'established'"); infof(data, "CONNECT phase completed"); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; - /* FALLTHROUGH */ - case TUNNEL_FAILED: - if(new_state == TUNNEL_FAILED) - DEBUGF(LOG_CF(data, cf, "new tunnel state 'failed'")); + FALLTHROUGH(); + case H1_TUNNEL_FAILED: + if(new_state == H1_TUNNEL_FAILED) + CURL_TRC_CF(data, cf, "new tunnel state 'failed'"); ts->tunnel_state = new_state; - Curl_dyn_reset(&ts->rcvbuf); - Curl_dyn_reset(&ts->req); + curlx_dyn_reset(&ts->rcvbuf); + curlx_dyn_reset(&ts->request_data); /* restore the protocol pointer */ data->info.httpcode = 0; /* clear it as it might've been used for the proxy */ /* If a proxy-authorization header was used for the proxy, then we should - make sure that it isn't accidentally used for the document request - after we've connected. So let's free and clear it here. */ + make sure that it is not accidentally used for the document request + after we have connected. So let's free and clear it here. */ Curl_safefree(data->state.aptr.proxyuserpwd); -#ifdef USE_HYPER - data->state.hconnect = FALSE; -#endif break; } } @@ -225,181 +189,99 @@ static void tunnel_go_state(struct Curl_cfilter *cf, static void tunnel_free(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct tunnel_state *ts = cf->ctx; - if(ts) { - tunnel_go_state(cf, ts, TUNNEL_FAILED, data); - Curl_dyn_free(&ts->rcvbuf); - Curl_dyn_free(&ts->req); - free(ts); - cf->ctx = NULL; + if(cf) { + struct h1_tunnel_state *ts = cf->ctx; + if(ts) { + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + curlx_dyn_free(&ts->rcvbuf); + curlx_dyn_free(&ts->request_data); + Curl_httpchunk_free(data, &ts->ch); + free(ts); + cf->ctx = NULL; + } } } -static CURLcode CONNECT_host(struct Curl_easy *data, - struct connectdata *conn, - const char *hostname, - int remote_port, - char **connecthostp, - char **hostp) +static bool tunnel_want_send(struct h1_tunnel_state *ts) { - char *hostheader; /* for CONNECT */ - char *host = NULL; /* Host: */ - bool ipv6_ip = conn->bits.ipv6_ip; - - /* the hostname may be different */ - if(hostname != conn->host.name) - ipv6_ip = (strchr(hostname, ':') != NULL); - hostheader = /* host:port with IPv6 support */ - aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", - remote_port); - if(!hostheader) - return CURLE_OUT_OF_MEMORY; - - if(!Curl_checkProxyheaders(data, conn, STRCONST("Host"))) { - host = aprintf("Host: %s\r\n", hostheader); - if(!host) { - free(hostheader); - return CURLE_OUT_OF_MEMORY; - } - } - *connecthostp = hostheader; - *hostp = host; - return CURLE_OK; + return ts->tunnel_state == H1_TUNNEL_CONNECT; } -#ifndef USE_HYPER static CURLcode start_CONNECT(struct Curl_cfilter *cf, struct Curl_easy *data, - struct tunnel_state *ts) + struct h1_tunnel_state *ts) { - struct connectdata *conn = cf->conn; - char *hostheader = NULL; - char *host = NULL; - const char *httpv; + struct httpreq *req = NULL; + int http_minor; CURLcode result; - infof(data, "Establish HTTP proxy tunnel to %s:%d", - ts->hostname, ts->remote_port); - - /* This only happens if we've looped here due to authentication - reasons, and we don't really use the newly cloned URL here + /* This only happens if we have looped here due to authentication + reasons, and we do not really use the newly cloned URL here then. Just free() it. */ Curl_safefree(data->req.newurl); - result = CONNECT_host(data, conn, - ts->hostname, ts->remote_port, - &hostheader, &host); - if(result) - goto out; - - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET, - hostheader, TRUE); + result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1); if(result) goto out; - httpv = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1"; - - result = - Curl_dyn_addf(&ts->req, - "CONNECT %s HTTP/%s\r\n" - "%s" /* Host: */ - "%s", /* Proxy-Authorization */ - hostheader, - httpv, - host?host:"", - data->state.aptr.proxyuserpwd? - data->state.aptr.proxyuserpwd:""); - if(result) - goto out; + infof(data, "Establish HTTP proxy tunnel to %s", req->authority); - if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) - && data->set.str[STRING_USERAGENT]) - result = Curl_dyn_addf(&ts->req, "User-Agent: %s\r\n", - data->set.str[STRING_USERAGENT]); - if(result) - goto out; - - if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) - result = Curl_dyn_addn(&ts->req, - STRCONST("Proxy-Connection: Keep-Alive\r\n")); - if(result) - goto out; - - result = Curl_add_custom_headers(data, TRUE, &ts->req); - if(result) - goto out; - - /* CRLF terminate the request */ - result = Curl_dyn_addn(&ts->req, STRCONST("\r\n")); - if(result) - goto out; - - /* Send the connect request to the proxy */ - result = Curl_buffer_send(&ts->req, data, &ts->CONNECT, - &data->info.request_size, 0, - ts->sockindex); + curlx_dyn_reset(&ts->request_data); + ts->nsent = 0; ts->headerlines = 0; + http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1; + + result = Curl_h1_req_write_head(req, http_minor, &ts->request_data); + if(!result) + result = Curl_creader_set_null(data); out: if(result) failf(data, "Failed sending CONNECT to proxy"); - free(host); - free(hostheader); + if(req) + Curl_http_req_free(req); return result; } -static CURLcode send_CONNECT(struct Curl_easy *data, - struct connectdata *conn, - struct tunnel_state *ts, +static CURLcode send_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, bool *done) { - struct SingleRequest *k = &data->req; - struct HTTP *http = &ts->CONNECT; + char *buf = curlx_dyn_ptr(&ts->request_data); + size_t request_len = curlx_dyn_len(&ts->request_data); + size_t blen = request_len; CURLcode result = CURLE_OK; + ssize_t nwritten; - if(http->sending != HTTPSEND_REQUEST) - goto out; + if(blen <= ts->nsent) + goto out; /* we are done */ - if(!ts->nsend) { - size_t fillcount; - k->upload_fromhere = data->state.ulbuf; - result = Curl_fillreadbuffer(data, data->set.upload_buffer_size, - &fillcount); - if(result) - goto out; - ts->nsend = fillcount; - } - if(ts->nsend) { - ssize_t bytes_written; - /* write to socket (send away data) */ - result = Curl_write(data, - conn->writesockfd, /* socket to send to */ - k->upload_fromhere, /* buffer pointer */ - ts->nsend, /* buffer size */ - &bytes_written); /* actually sent */ - if(result) - goto out; - /* send to debug callback! */ - Curl_debug(data, CURLINFO_HEADER_OUT, - k->upload_fromhere, bytes_written); + blen -= ts->nsent; + buf += ts->nsent; - ts->nsend -= bytes_written; - k->upload_fromhere += bytes_written; + nwritten = cf->next->cft->do_send(cf->next, data, buf, blen, FALSE, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + result = CURLE_OK; + } + goto out; } - if(!ts->nsend) - http->sending = HTTPSEND_NADA; + + DEBUGASSERT(blen >= (size_t)nwritten); + ts->nsent += (size_t)nwritten; + Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)nwritten); out: if(result) failf(data, "Failed sending CONNECT to proxy"); - *done = (http->sending != HTTPSEND_REQUEST); + *done = (!result && (ts->nsent >= request_len)); return result; } static CURLcode on_resp_header(struct Curl_cfilter *cf, struct Curl_easy *data, - struct tunnel_state *ts, + struct h1_tunnel_state *ts, const char *header) { CURLcode result = CURLE_OK; @@ -411,12 +293,12 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) { - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; + bool proxy = (k->httpcode == 407); char *auth = Curl_copy_header_value(header); if(!auth) return CURLE_OUT_OF_MEMORY; - DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'", header)); + CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header); result = Curl_http_input_auth(data, proxy, auth); free(auth); @@ -433,8 +315,11 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, k->httpcode); } else { - (void)curlx_strtoofft(header + strlen("Content-Length:"), - NULL, 10, &ts->cl); + const char *p = header + strlen("Content-Length:"); + if(curlx_str_numblanks(&p, &ts->cl)) { + failf(data, "Unsupported Content-Length value"); + return CURLE_WEIRD_SERVER_REPLY; + } } } else if(Curl_compareheader(header, @@ -453,8 +338,8 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, STRCONST("chunked"))) { infof(data, "CONNECT responded chunked"); ts->chunked_encoding = TRUE; - /* init our chunky engine */ - Curl_httpchunk_init(data); + /* reset our chunky engine */ + Curl_httpchunk_reset(data, &ts->ch, TRUE); } } else if(Curl_compareheader(header, @@ -475,15 +360,14 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, struct Curl_easy *data, - struct tunnel_state *ts, + struct h1_tunnel_state *ts, bool *done) { CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); char *linep; - size_t perline; - int error; + size_t line_len; + int error, writetype; #define SELECT_OK 0 #define SELECT_ERROR 1 @@ -491,16 +375,16 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, error = SELECT_OK; *done = FALSE; - if(!Curl_conn_data_pending(data, ts->sockindex)) + if(!Curl_conn_data_pending(data, cf->sockindex)) return CURLE_OK; while(ts->keepon) { - ssize_t gotbytes; + ssize_t nread; char byte; /* Read one byte at a time to avoid a race condition. Wait at most one second before looping to ensure continuous pgrsUpdates. */ - result = Curl_read(data, tunnelsocket, &byte, 1, &gotbytes); + result = Curl_conn_recv(data, cf->sockindex, &byte, 1, &nread); if(result == CURLE_AGAIN) /* socket buffer drained, return */ return CURLE_OK; @@ -513,7 +397,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, break; } - if(gotbytes <= 0) { + if(nread <= 0) { if(data->set.proxyauth && data->state.authproxy.avail && data->state.aptr.proxyuserpwd) { /* proxy auth was requested and there was proxy auth available, @@ -534,25 +418,25 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, if(ts->cl) { /* A Content-Length based body: simply count down the counter - and make sure to break out of the loop when we're done! */ + and make sure to break out of the loop when we are done! */ ts->cl--; if(ts->cl <= 0) { ts->keepon = KEEPON_DONE; break; } } - else { + else if(ts->chunked_encoding) { /* chunked-encoded body, so we need to do the chunked dance properly to know when the end of the body is reached */ - CHUNKcode r; - CURLcode extra; - ssize_t tookcareof = 0; + size_t consumed = 0; /* now parse the chunked piece of data so that we can properly tell when the stream ends */ - r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &extra); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ + result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed); + if(result) + return result; + if(Curl_httpchunk_is_done(data, &ts->ch)) { + /* we are done reading chunks! */ infof(data, "chunk reading DONE"); ts->keepon = KEEPON_DONE; } @@ -560,7 +444,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, continue; } - if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) { + if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) { failf(data, "CONNECT response too large"); return CURLE_RECV_ERROR; } @@ -570,26 +454,24 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, continue; ts->headerlines++; - linep = Curl_dyn_ptr(&ts->rcvbuf); - perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ + linep = curlx_dyn_ptr(&ts->rcvbuf); + line_len = curlx_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ /* output debug if that is requested */ - Curl_debug(data, CURLINFO_HEADER_IN, linep, perline); - - if(!data->set.suppress_connect_headers) { - /* send the header to the callback */ - int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | - (data->set.include_header ? CLIENTWRITE_BODY : 0) | - (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); - result = Curl_client_write(data, writetype, linep, perline); - if(result) - return result; - } + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | + (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + result = Curl_client_write(data, writetype, linep, line_len); + if(result) + return result; - data->info.header_size += (long)perline; + result = Curl_bump_headersize(data, line_len, TRUE); + if(result) + return result; - /* Newlines are CRLF, so the CR is ignored as the line isn't + /* Newlines are CRLF, so the CR is ignored as the line is not really terminated until the LF comes. Treat a following CR as end-of-headers as well.*/ @@ -604,39 +486,16 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, ts->keepon = KEEPON_IGNORE; if(ts->cl) { - infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T - " bytes of response-body", ts->cl); + infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl); } else if(ts->chunked_encoding) { - CHUNKcode r; - CURLcode extra; - infof(data, "Ignore chunked response-body"); - - /* We set ignorebody true here since the chunked decoder - function will acknowledge that. Pay attention so that this is - cleared again when this function returns! */ - k->ignorebody = TRUE; - - if(linep[1] == '\n') - /* this can only be a LF if the letter at index 0 was a CR */ - linep++; - - /* now parse the chunked piece of data so that we can properly - tell when the stream ends */ - r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes, - &extra); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ - infof(data, "chunk reading DONE"); - ts->keepon = KEEPON_DONE; - } } else { /* without content-length or chunked encoding, we - can't keep the connection alive since the close is + cannot keep the connection alive since the close is the end signal so we bail out at once instead */ - DEBUGF(LOG_CF(data, cf, "CONNECT: no content-length or chunked")); + CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked"); ts->keepon = KEEPON_DONE; } } @@ -653,8 +512,8 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, if(result) return result; - Curl_dyn_reset(&ts->rcvbuf); - } /* while there's buffer left and loop is requested */ + curlx_dyn_reset(&ts->rcvbuf); + } /* while there is buffer left and loop is requested */ if(error) result = CURLE_RECV_ERROR; @@ -667,291 +526,9 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, return result; } -#else /* USE_HYPER */ -/* The Hyper version of CONNECT */ -static CURLcode start_CONNECT(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct tunnel_state *ts) -{ - struct connectdata *conn = cf->conn; - struct hyptransfer *h = &data->hyp; - curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); - hyper_io *io = NULL; - hyper_request *req = NULL; - hyper_headers *headers = NULL; - hyper_clientconn_options *options = NULL; - hyper_task *handshake = NULL; - hyper_task *task = NULL; /* for the handshake */ - hyper_clientconn *client = NULL; - hyper_task *sendtask = NULL; /* for the send */ - char *hostheader = NULL; /* for CONNECT */ - char *host = NULL; /* Host: */ - CURLcode result = CURLE_OUT_OF_MEMORY; - - io = hyper_io_new(); - if(!io) { - failf(data, "Couldn't create hyper IO"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - /* tell Hyper how to read/write network data */ - hyper_io_set_userdata(io, data); - hyper_io_set_read(io, Curl_hyper_recv); - hyper_io_set_write(io, Curl_hyper_send); - conn->sockfd = tunnelsocket; - - data->state.hconnect = TRUE; - - /* create an executor to poll futures */ - if(!h->exec) { - h->exec = hyper_executor_new(); - if(!h->exec) { - failf(data, "Couldn't create hyper executor"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - } - - options = hyper_clientconn_options_new(); - hyper_clientconn_options_set_preserve_header_case(options, 1); - hyper_clientconn_options_set_preserve_header_order(options, 1); - - if(!options) { - failf(data, "Couldn't create hyper client options"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - hyper_clientconn_options_exec(options, h->exec); - - /* "Both the `io` and the `options` are consumed in this function - call" */ - handshake = hyper_clientconn_handshake(io, options); - if(!handshake) { - failf(data, "Couldn't create hyper client handshake"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - io = NULL; - options = NULL; - - if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) { - failf(data, "Couldn't hyper_executor_push the handshake"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - handshake = NULL; /* ownership passed on */ - - task = hyper_executor_poll(h->exec); - if(!task) { - failf(data, "Couldn't hyper_executor_poll the handshake"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - client = hyper_task_value(task); - hyper_task_free(task); - req = hyper_request_new(); - if(!req) { - failf(data, "Couldn't hyper_request_new"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - if(hyper_request_set_method(req, (uint8_t *)"CONNECT", - strlen("CONNECT"))) { - failf(data, "error setting method"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - infof(data, "Establish HTTP proxy tunnel to %s:%d", - ts->hostname, ts->remote_port); - - /* This only happens if we've looped here due to authentication - reasons, and we don't really use the newly cloned URL here - then. Just free() it. */ - Curl_safefree(data->req.newurl); - - result = CONNECT_host(data, conn, ts->hostname, ts->remote_port, - &hostheader, &host); - if(result) - goto error; - - if(hyper_request_set_uri(req, (uint8_t *)hostheader, - strlen(hostheader))) { - failf(data, "error setting path"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - if(data->set.verbose) { - char *se = aprintf("CONNECT %s HTTP/1.1\r\n", hostheader); - if(!se) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se)); - free(se); - } - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET, - hostheader, TRUE); - if(result) - goto error; - Curl_safefree(hostheader); - - /* default is 1.1 */ - if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) && - (HYPERE_OK != hyper_request_set_version(req, - HYPER_HTTP_VERSION_1_0))) { - failf(data, "error setting HTTP version"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - headers = hyper_request_headers(req); - if(!headers) { - failf(data, "hyper_request_headers"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - if(host) { - result = Curl_hyper_header(data, headers, host); - if(result) - goto error; - Curl_safefree(host); - } - - if(data->state.aptr.proxyuserpwd) { - result = Curl_hyper_header(data, headers, - data->state.aptr.proxyuserpwd); - if(result) - goto error; - } - - if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) && - data->set.str[STRING_USERAGENT]) { - struct dynbuf ua; - Curl_dyn_init(&ua, DYN_HTTP_REQUEST); - result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n", - data->set.str[STRING_USERAGENT]); - if(result) - goto error; - result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua)); - if(result) - goto error; - Curl_dyn_free(&ua); - } - - if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) { - result = Curl_hyper_header(data, headers, - "Proxy-Connection: Keep-Alive"); - if(result) - goto error; - } - - result = Curl_add_custom_headers(data, TRUE, headers); - if(result) - goto error; - - sendtask = hyper_clientconn_send(client, req); - if(!sendtask) { - failf(data, "hyper_clientconn_send"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) { - failf(data, "Couldn't hyper_executor_push the send"); - result = CURLE_OUT_OF_MEMORY; - goto error; - } - -error: - free(host); - free(hostheader); - if(io) - hyper_io_free(io); - if(options) - hyper_clientconn_options_free(options); - if(handshake) - hyper_task_free(handshake); - if(client) - hyper_clientconn_free(client); - return result; -} - -static CURLcode send_CONNECT(struct Curl_easy *data, - struct connectdata *conn, - struct tunnel_state *ts, - bool *done) -{ - struct hyptransfer *h = &data->hyp; - hyper_task *task = NULL; - hyper_error *hypererr = NULL; - CURLcode result = CURLE_OK; - - (void)ts; - (void)conn; - do { - task = hyper_executor_poll(h->exec); - if(task) { - bool error = hyper_task_type(task) == HYPER_TASK_ERROR; - if(error) - hypererr = hyper_task_value(task); - hyper_task_free(task); - if(error) { - /* this could probably use a better error code? */ - result = CURLE_OUT_OF_MEMORY; - goto error; - } - } - } while(task); -error: - *done = (result == CURLE_OK); - if(hypererr) { - uint8_t errbuf[256]; - size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf)); - failf(data, "Hyper: %.*s", (int)errlen, errbuf); - hyper_error_free(hypererr); - } - return result; -} - -static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct tunnel_state *ts, - bool *done) -{ - struct hyptransfer *h = &data->hyp; - CURLcode result; - int didwhat; - - (void)ts; - *done = FALSE; - result = Curl_hyper_stream(data, cf->conn, &didwhat, done, - CURL_CSELECT_IN | CURL_CSELECT_OUT); - if(result || !*done) - return result; - if(h->exec) { - hyper_executor_free(h->exec); - h->exec = NULL; - } - if(h->read_waker) { - hyper_waker_free(h->read_waker); - h->read_waker = NULL; - } - if(h->write_waker) { - hyper_waker_free(h->write_waker); - h->write_waker = NULL; - } - return result; -} - -#endif /* USE_HYPER */ - -static CURLcode CONNECT(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct tunnel_state *ts) +static CURLcode H1_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) { struct connectdata *conn = cf->conn; CURLcode result; @@ -973,27 +550,27 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, } switch(ts->tunnel_state) { - case TUNNEL_INIT: + case H1_TUNNEL_INIT: /* Prepare the CONNECT request and make a first attempt to send. */ - DEBUGF(LOG_CF(data, cf, "CONNECT start")); + CURL_TRC_CF(data, cf, "CONNECT start"); result = start_CONNECT(cf, data, ts); if(result) goto out; - tunnel_go_state(cf, ts, TUNNEL_CONNECT, data); - /* FALLTHROUGH */ + h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data); + FALLTHROUGH(); - case TUNNEL_CONNECT: + case H1_TUNNEL_CONNECT: /* see that the request is completely sent */ - DEBUGF(LOG_CF(data, cf, "CONNECT send")); - result = send_CONNECT(data, cf->conn, ts, &done); + CURL_TRC_CF(data, cf, "CONNECT send"); + result = send_CONNECT(cf, data, ts, &done); if(result || !done) goto out; - tunnel_go_state(cf, ts, TUNNEL_RECEIVE, data); - /* FALLTHROUGH */ + h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data); + FALLTHROUGH(); - case TUNNEL_RECEIVE: + case H1_TUNNEL_RECEIVE: /* read what is there */ - DEBUGF(LOG_CF(data, cf, "CONNECT receive")); + CURL_TRC_CF(data, cf, "CONNECT receive"); result = recv_CONNECT_resp(cf, data, ts, &done); if(Curl_pgrsUpdate(data)) { result = CURLE_ABORTED_BY_CALLBACK; @@ -1003,32 +580,33 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, if(result || !done) goto out; /* got it */ - tunnel_go_state(cf, ts, TUNNEL_RESPONSE, data); - /* FALLTHROUGH */ + h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data); + FALLTHROUGH(); - case TUNNEL_RESPONSE: - DEBUGF(LOG_CF(data, cf, "CONNECT response")); + case H1_TUNNEL_RESPONSE: + CURL_TRC_CF(data, cf, "CONNECT response"); if(data->req.newurl) { /* not the "final" response, we need to do a follow up request. * If the other side indicated a connection close, or if someone * else told us to close this connection, do so now. */ + Curl_req_soft_reset(&data->req, data); if(ts->close_connection || conn->bits.close) { /* Close this filter and the sub-chain, re-connect the * sub-chain and continue. Closing this filter will * reset our tunnel state. To avoid recursion, we return * and expect to be called again. */ - DEBUGF(LOG_CF(data, cf, "CONNECT need to close+open")); + CURL_TRC_CF(data, cf, "CONNECT need to close+open"); infof(data, "Connect me again please"); Curl_conn_cf_close(cf, data); connkeep(conn, "HTTP proxy CONNECT"); - result = Curl_conn_cf_connect(cf->next, data, FALSE, &done); + result = Curl_conn_cf_connect(cf->next, data, &done); goto out; } else { /* staying on this connection, reset state */ - tunnel_go_state(cf, ts, TUNNEL_INIT, data); + h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data); } } break; @@ -1039,57 +617,56 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, } while(data->req.newurl); - DEBUGASSERT(ts->tunnel_state == TUNNEL_RESPONSE); + DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE); if(data->info.httpproxycode/100 != 2) { - /* a non-2xx response and we have no next url to try. */ + /* a non-2xx response and we have no next URL to try. */ Curl_safefree(data->req.newurl); - /* failure, close this connection to avoid re-use */ + /* failure, close this connection to avoid reuse */ streamclose(conn, "proxy CONNECT failure"); - tunnel_go_state(cf, ts, TUNNEL_FAILED, data); + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode); return CURLE_RECV_ERROR; } /* 2xx response, SUCCESS! */ - tunnel_go_state(cf, ts, TUNNEL_ESTABLISHED, data); + h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data); infof(data, "CONNECT tunnel established, response %d", data->info.httpproxycode); result = CURLE_OK; out: if(result) - tunnel_go_state(cf, ts, TUNNEL_FAILED, data); + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); return result; } static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { CURLcode result; - struct tunnel_state *ts = cf->ctx; + struct h1_tunnel_state *ts = cf->ctx; if(cf->connected) { *done = TRUE; return CURLE_OK; } - DEBUGF(LOG_CF(data, cf, "connect")); - result = cf->next->cft->connect(cf->next, data, blocking, done); + CURL_TRC_CF(data, cf, "connect"); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; *done = FALSE; if(!ts) { - result = tunnel_init(&ts, data, cf->conn, cf->sockindex); + result = tunnel_init(cf, data, &ts); if(result) return result; cf->ctx = ts; } - /* TODO: can we do blocking? */ /* We want "seamless" operations through HTTP proxy tunnel */ - result = CONNECT(cf, data, ts); + result = H1_CONNECT(cf, data, ts); if(result) goto out; Curl_safefree(data->state.aptr.proxyuserpwd); @@ -1098,67 +675,74 @@ static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx); if(*done) { cf->connected = TRUE; + /* The real request will follow the CONNECT, reset request partially */ + Curl_req_soft_reset(&data->req, data); + Curl_client_reset(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + tunnel_free(cf, data); } return result; } -static int cf_h1_proxy_get_select_socks(struct Curl_cfilter *cf, +static void cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { - struct tunnel_state *ts = cf->ctx; - int fds; + struct h1_tunnel_state *ts = cf->ctx; - fds = cf->next->cft->get_select_socks(cf->next, data, socks); - if(!fds && cf->next->connected && !cf->connected) { + if(!cf->connected) { /* If we are not connected, but the filter "below" is * and not waiting on something, we are tunneling. */ - socks[0] = Curl_conn_cf_get_socket(cf, data); + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); if(ts) { - /* when we've sent a CONNECT to a proxy, we should rather either + /* when we have sent a CONNECT to a proxy, we should rather either wait for the socket to become readable to be able to get the - response headers or if we're still sending the request, wait + response headers or if we are still sending the request, wait for write. */ - if(ts->CONNECT.sending == HTTPSEND_REQUEST) { - return GETSOCK_WRITESOCK(0); - } - return GETSOCK_READSOCK(0); + if(tunnel_want_send(ts)) + Curl_pollset_set_out_only(data, ps, sock); + else + Curl_pollset_set_in_only(data, ps, sock); } - return GETSOCK_WRITESOCK(0); + else + Curl_pollset_set_out_only(data, ps, sock); } - return fds; } static void cf_h1_proxy_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - DEBUGF(LOG_CF(data, cf, "destroy")); + CURL_TRC_CF(data, cf, "destroy"); tunnel_free(cf, data); } static void cf_h1_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - DEBUGF(LOG_CF(data, cf, "close")); - cf->connected = FALSE; - if(cf->ctx) { - tunnel_go_state(cf, cf->ctx, TUNNEL_INIT, data); + CURL_TRC_CF(data, cf, "close"); + if(cf) { + cf->connected = FALSE; + if(cf->ctx) { + h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data); + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); } - if(cf->next) - cf->next->cft->close(cf->next, data); } struct Curl_cftype Curl_cft_h1_proxy = { "H1-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, cf_h1_proxy_destroy, cf_h1_proxy_connect, cf_h1_proxy_close, + Curl_cf_def_shutdown, Curl_cf_http_proxy_get_host, - cf_h1_proxy_get_select_socks, + cf_h1_proxy_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, diff --git a/Utilities/cmcurl/lib/cf-h2-proxy.c b/Utilities/cmcurl/lib/cf-h2-proxy.c index 8e76ff84abd..d3bc4b07680 100644 --- a/Utilities/cmcurl/lib/cf-h2-proxy.c +++ b/Utilities/cmcurl/lib/cf-h2-proxy.c @@ -30,13 +30,15 @@ #include "urldata.h" #include "cfilters.h" #include "connect.h" -#include "curl_log.h" +#include "curl_trc.h" #include "bufq.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "dynhds.h" #include "http1.h" +#include "http2.h" #include "http_proxy.h" #include "multiif.h" +#include "sendf.h" #include "cf-h2-proxy.h" /* The last 3 #include files should be in this order */ @@ -44,26 +46,25 @@ #include "curl_memory.h" #include "memdebug.h" -#define H2_NW_CHUNK_SIZE (128*1024) -#define H2_NW_RECV_CHUNKS 1 -#define H2_NW_SEND_CHUNKS 1 +#define PROXY_H2_CHUNK_SIZE (16*1024) -#define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */ +#define PROXY_HTTP2_HUGE_WINDOW_SIZE (100 * 1024 * 1024) +#define H2_TUNNEL_WINDOW_SIZE (10 * 1024 * 1024) + +#define PROXY_H2_NW_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE) +#define PROXY_H2_NW_SEND_CHUNKS 1 + +#define H2_TUNNEL_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE) +#define H2_TUNNEL_SEND_CHUNKS ((128 * 1024) / PROXY_H2_CHUNK_SIZE) -#define H2_TUNNEL_WINDOW_SIZE (1024 * 1024) -#define H2_TUNNEL_CHUNK_SIZE (32 * 1024) -#define H2_TUNNEL_RECV_CHUNKS \ - (H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE) -#define H2_TUNNEL_SEND_CHUNKS \ - (H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE) typedef enum { - TUNNEL_INIT, /* init/default/no tunnel state */ - TUNNEL_CONNECT, /* CONNECT request is being send */ - TUNNEL_RESPONSE, /* CONNECT response received completely */ - TUNNEL_ESTABLISHED, - TUNNEL_FAILED -} tunnel_state; + H2_TUNNEL_INIT, /* init/default/no tunnel state */ + H2_TUNNEL_CONNECT, /* CONNECT request is being send */ + H2_TUNNEL_RESPONSE, /* CONNECT response received completely */ + H2_TUNNEL_ESTABLISHED, + H2_TUNNEL_FAILED +} h2_tunnel_state; struct tunnel_stream { struct http_resp *resp; @@ -72,10 +73,10 @@ struct tunnel_stream { char *authority; int32_t stream_id; uint32_t error; - tunnel_state state; - bool has_final_response; - bool closed; - bool reset; + h2_tunnel_state state; + BIT(has_final_response); + BIT(closed); + BIT(reset); }; static CURLcode tunnel_stream_init(struct Curl_cfilter *cf, @@ -83,33 +84,22 @@ static CURLcode tunnel_stream_init(struct Curl_cfilter *cf, { const char *hostname; int port; - bool ipv6_ip = cf->conn->bits.ipv6_ip; + bool ipv6_ip; + CURLcode result; - ts->state = TUNNEL_INIT; + ts->state = H2_TUNNEL_INIT; ts->stream_id = -1; - Curl_bufq_init2(&ts->recvbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS, + Curl_bufq_init2(&ts->recvbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - Curl_bufq_init(&ts->sendbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS); - - if(cf->conn->bits.conn_to_host) - hostname = cf->conn->conn_to_host.name; - else if(cf->sockindex == SECONDARYSOCKET) - hostname = cf->conn->secondaryhostname; - else - hostname = cf->conn->host.name; - - if(cf->sockindex == SECONDARYSOCKET) - port = cf->conn->secondary_port; - else if(cf->conn->bits.conn_to_port) - port = cf->conn->conn_to_port; - else - port = cf->conn->remote_port; + Curl_bufq_init(&ts->sendbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS); - if(hostname != cf->conn->host.name) - ipv6_ip = (strchr(hostname, ':') != NULL); + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + return result; ts->authority = /* host:port with IPv6 support */ - aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", port); + aprintf("%s%s%s:%d", ipv6_ip ? "[":"", hostname, + ipv6_ip ? "]" : "", port); if(!ts->authority) return CURLE_OUT_OF_MEMORY; @@ -123,13 +113,13 @@ static void tunnel_stream_clear(struct tunnel_stream *ts) Curl_bufq_free(&ts->sendbuf); Curl_safefree(ts->authority); memset(ts, 0, sizeof(*ts)); - ts->state = TUNNEL_INIT; + ts->state = H2_TUNNEL_INIT; } -static void tunnel_go_state(struct Curl_cfilter *cf, - struct tunnel_stream *ts, - tunnel_state new_state, - struct Curl_easy *data) +static void h2_tunnel_go_state(struct Curl_cfilter *cf, + struct tunnel_stream *ts, + h2_tunnel_state new_state, + struct Curl_easy *data) { (void)cf; @@ -137,7 +127,7 @@ static void tunnel_go_state(struct Curl_cfilter *cf, return; /* leaving this one */ switch(ts->state) { - case TUNNEL_CONNECT: + case H2_TUNNEL_CONNECT: data->req.ignorebody = FALSE; break; default: @@ -145,34 +135,35 @@ static void tunnel_go_state(struct Curl_cfilter *cf, } /* entering this one */ switch(new_state) { - case TUNNEL_INIT: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'init'")); + case H2_TUNNEL_INIT: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'init'", ts->stream_id); tunnel_stream_clear(ts); break; - case TUNNEL_CONNECT: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'connect'")); - ts->state = TUNNEL_CONNECT; + case H2_TUNNEL_CONNECT: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'connect'", ts->stream_id); + ts->state = H2_TUNNEL_CONNECT; break; - case TUNNEL_RESPONSE: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'response'")); - ts->state = TUNNEL_RESPONSE; + case H2_TUNNEL_RESPONSE: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'response'", ts->stream_id); + ts->state = H2_TUNNEL_RESPONSE; break; - case TUNNEL_ESTABLISHED: - DEBUGF(LOG_CF(data, cf, "new tunnel state 'established'")); + case H2_TUNNEL_ESTABLISHED: + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'established'", + ts->stream_id); infof(data, "CONNECT phase completed"); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; - /* FALLTHROUGH */ - case TUNNEL_FAILED: - if(new_state == TUNNEL_FAILED) - DEBUGF(LOG_CF(data, cf, "new tunnel state 'failed'")); + FALLTHROUGH(); + case H2_TUNNEL_FAILED: + if(new_state == H2_TUNNEL_FAILED) + CURL_TRC_CF(data, cf, "[%d] new tunnel state 'failed'", ts->stream_id); ts->state = new_state; /* If a proxy-authorization header was used for the proxy, then we should - make sure that it isn't accidentally used for the document request - after we've connected. So let's free and clear it here. */ + make sure that it is not accidentally used for the document request + after we have connected. So let's free and clear it here. */ Curl_safefree(data->state.aptr.proxyuserpwd); break; } @@ -190,10 +181,13 @@ struct cf_h2_proxy_ctx { int32_t goaway_error; int32_t last_stream_id; BIT(conn_closed); - BIT(goaway); + BIT(rcvd_goaway); + BIT(sent_goaway); + BIT(nw_out_blocked); }; /* How to access `call_data` from a cf_h2 filter */ +#undef CF_CTX_CALL_DATA #define CF_CTX_CALL_DATA(cf) \ ((struct cf_h2_proxy_ctx *)(cf)->ctx)->call_data @@ -219,38 +213,72 @@ static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx) } } -static ssize_t nw_in_reader(void *reader_ctx, - unsigned char *buf, size_t buflen, - CURLcode *err) +static void drain_tunnel(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct tunnel_stream *tunnel) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(!tunnel->closed && !tunnel->reset && + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", + tunnel->stream_id, bits); + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static ssize_t proxy_nw_in_reader(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) { struct Curl_cfilter *cf = reader_ctx; - struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; - nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err); - DEBUGF(LOG_CF(data, cf, "nw_in recv(len=%zu) -> %zd, %d", - buflen, nread, *err)); + if(cf) { + struct Curl_easy *data = CF_DATA_CURRENT(cf); + nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err); + CURL_TRC_CF(data, cf, "[0] nw_in_reader(len=%zu) -> %zd, %d", + buflen, nread, *err); + } + else { + nread = 0; + } return nread; } -static ssize_t nw_out_writer(void *writer_ctx, - const unsigned char *buf, size_t buflen, - CURLcode *err) +static ssize_t proxy_h2_nw_out_writer(void *writer_ctx, + const unsigned char *buf, size_t buflen, + CURLcode *err) { struct Curl_cfilter *cf = writer_ctx; - struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; - nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, err); - DEBUGF(LOG_CF(data, cf, "nw_out send(len=%zu) -> %zd", buflen, nwritten)); + if(cf) { + struct Curl_easy *data = CF_DATA_CURRENT(cf); + nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, + FALSE, err); + CURL_TRC_CF(data, cf, "[0] nw_out_writer(len=%zu) -> %zd, %d", + buflen, nwritten, *err); + } + else { + nwritten = 0; + } return nwritten; } -static int h2_client_new(struct Curl_cfilter *cf, - nghttp2_session_callbacks *cbs) +static int proxy_h2_client_new(struct Curl_cfilter *cf, + nghttp2_session_callbacks *cbs) { struct cf_h2_proxy_ctx *ctx = cf->ctx; nghttp2_option *o; + nghttp2_mem mem = {NULL, Curl_nghttp2_malloc, Curl_nghttp2_free, + Curl_nghttp2_calloc, Curl_nghttp2_realloc}; int rc = nghttp2_option_new(&o); if(rc) @@ -263,7 +291,7 @@ static int h2_client_new(struct Curl_cfilter *cf, HTTP field value. */ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); #endif - rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o); + rc = nghttp2_session_client_new3(&ctx->h2, cbs, cf, o, &mem); nghttp2_option_del(o); return rc; } @@ -271,15 +299,23 @@ static int h2_client_new(struct Curl_cfilter *cf, static ssize_t on_session_send(nghttp2_session *h2, const uint8_t *buf, size_t blen, int flags, void *userp); -static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, - void *userp); -static int on_stream_close(nghttp2_session *session, int32_t stream_id, - uint32_t error_code, void *userp); -static int on_header(nghttp2_session *session, const nghttp2_frame *frame, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - uint8_t flags, - void *userp); +static int proxy_h2_on_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp); +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int proxy_h2_on_frame_send(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp); +#endif +static int proxy_h2_on_stream_close(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, void *userp); +static int proxy_h2_on_header(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, + void *userp); static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *mem, size_t len, void *userp); @@ -298,8 +334,8 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, DEBUGASSERT(!ctx->h2); memset(&ctx->tunnel, 0, sizeof(ctx->tunnel)); - Curl_bufq_init(&ctx->inbufq, H2_NW_CHUNK_SIZE, H2_NW_RECV_CHUNKS); - Curl_bufq_init(&ctx->outbufq, H2_NW_CHUNK_SIZE, H2_NW_SEND_CHUNKS); + Curl_bufq_init(&ctx->inbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_RECV_CHUNKS); + Curl_bufq_init(&ctx->outbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_SEND_CHUNKS); if(tunnel_stream_init(cf, &ctx->tunnel)) goto out; @@ -311,14 +347,20 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, } nghttp2_session_callbacks_set_send_callback(cbs, on_session_send); - nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); + nghttp2_session_callbacks_set_on_frame_recv_callback( + cbs, proxy_h2_on_frame_recv); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + nghttp2_session_callbacks_set_on_frame_send_callback(cbs, + proxy_h2_on_frame_send); +#endif nghttp2_session_callbacks_set_on_data_chunk_recv_callback( cbs, tunnel_recv_callback); - nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); - nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); + nghttp2_session_callbacks_set_on_stream_close_callback( + cbs, proxy_h2_on_stream_close); + nghttp2_session_callbacks_set_on_header_callback(cbs, proxy_h2_on_header); /* The nghttp2 session is not yet setup, do it */ - rc = h2_client_new(cf, cbs); + rc = proxy_h2_client_new(cf, cbs); if(rc) { failf(data, "Couldn't initialize nghttp2"); goto out; @@ -343,7 +385,7 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, } rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0, - HTTP2_HUGE_WINDOW_SIZE); + PROXY_HTTP2_HUGE_WINDOW_SIZE); if(rc) { failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", nghttp2_strerror(rc), rc); @@ -358,31 +400,39 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, out: if(cbs) nghttp2_session_callbacks_del(cbs); - DEBUGF(LOG_CF(data, cf, "init proxy ctx -> %d", result)); + CURL_TRC_CF(data, cf, "[0] init proxy ctx -> %d", result); return result; } -static CURLcode nw_out_flush(struct Curl_cfilter *cf, - struct Curl_easy *data) +static int proxy_h2_should_close_session(struct cf_h2_proxy_ctx *ctx) +{ + return !nghttp2_session_want_read(ctx->h2) && + !nghttp2_session_want_write(ctx->h2); +} + +static CURLcode proxy_h2_nw_out_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_h2_proxy_ctx *ctx = cf->ctx; - size_t buflen = Curl_bufq_len(&ctx->outbufq); ssize_t nwritten; CURLcode result; (void)data; - if(!buflen) + if(Curl_bufq_is_empty(&ctx->outbufq)) return CURLE_OK; - DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", buflen)); - nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result); + nwritten = Curl_bufq_pass(&ctx->outbufq, proxy_h2_nw_out_writer, cf, + &result); if(nwritten < 0) { + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "[0] flush nw send buffer(%zu) -> EAGAIN", + Curl_bufq_len(&ctx->outbufq)); + ctx->nw_out_blocked = 1; + } return result; } - if((size_t)nwritten < buflen) { - return CURLE_AGAIN; - } - return CURLE_OK; + CURL_TRC_CF(data, cf, "[0] nw send buffer flushed"); + return Curl_bufq_is_empty(&ctx->outbufq) ? CURLE_OK : CURLE_AGAIN; } /* @@ -390,9 +440,9 @@ static CURLcode nw_out_flush(struct Curl_cfilter *cf, * This function returns 0 if it succeeds, or -1 and error code will * be assigned to *err. */ -static int h2_process_pending_input(struct Curl_cfilter *cf, - struct Curl_easy *data, - CURLcode *err) +static int proxy_h2_process_pending_input(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) { struct cf_h2_proxy_ctx *ctx = cf->ctx; const unsigned char *buf; @@ -402,8 +452,7 @@ static int h2_process_pending_input(struct Curl_cfilter *cf, while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) { rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); - DEBUGF(LOG_CF(data, cf, - "fed %zu bytes from nw to nghttp2 -> %zd", blen, rv)); + CURL_TRC_CF(data, cf, "[0] %zu bytes to nghttp2 -> %zd", blen, rv); if(rv < 0) { failf(data, "process_pending_input: nghttp2_session_mem_recv() returned " @@ -413,28 +462,20 @@ static int h2_process_pending_input(struct Curl_cfilter *cf, } Curl_bufq_skip(&ctx->inbufq, (size_t)rv); if(Curl_bufq_is_empty(&ctx->inbufq)) { - DEBUGF(LOG_CF(data, cf, "all data in connection buffer processed")); + CURL_TRC_CF(data, cf, "[0] all data in connection buffer processed"); break; } else { - DEBUGF(LOG_CF(data, cf, "process_pending_input: %zu bytes left " - "in connection buffer", Curl_bufq_len(&ctx->inbufq))); + CURL_TRC_CF(data, cf, "[0] process_pending_input: %zu bytes left " + "in connection buffer", Curl_bufq_len(&ctx->inbufq)); } } - if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { - /* No more requests are allowed in the current session, so - the connection may not be reused. This is set when a - GOAWAY frame has been received or when the limit of stream - identifiers has been reached. */ - connclose(cf->conn, "http/2: No new requests allowed"); - } - return 0; } -static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_h2_proxy_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -442,9 +483,9 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, /* Process network input buffer fist */ if(!Curl_bufq_is_empty(&ctx->inbufq)) { - DEBUGF(LOG_CF(data, cf, "Process %zd bytes in connection buffer", - Curl_bufq_len(&ctx->inbufq))); - if(h2_process_pending_input(cf, data, &result) < 0) + CURL_TRC_CF(data, cf, "[0] process %zu bytes in connection buffer", + Curl_bufq_len(&ctx->inbufq)); + if(proxy_h2_process_pending_input(cf, data, &result) < 0) return result; } @@ -455,9 +496,9 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */ !Curl_bufq_is_full(&ctx->tunnel.recvbuf)) { - nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); - DEBUGF(LOG_CF(data, cf, "read %zd bytes nw data -> %zd, %d", - Curl_bufq_len(&ctx->inbufq), nread, result)); + nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result); + CURL_TRC_CF(data, cf, "[0] read %zu bytes nw data -> %zd, %d", + Curl_bufq_len(&ctx->inbufq), nread, result); if(nread < 0) { if(result != CURLE_AGAIN) { failf(data, "Failed receiving HTTP2 data"); @@ -470,7 +511,7 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, break; } - if(h2_process_pending_input(cf, data, &result)) + if(proxy_h2_process_pending_input(cf, data, &result)) return result; } @@ -481,25 +522,22 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, return CURLE_OK; } -/* - * Check if there's been an update in the priority / - * dependency settings and if so it submits a PRIORITY frame with the updated - * info. - * Flush any out data pending in the network buffer. - */ -static CURLcode h2_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode proxy_h2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_h2_proxy_ctx *ctx = cf->ctx; int rv = 0; - rv = nghttp2_session_send(ctx->h2); + ctx->nw_out_blocked = 0; + while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2)) + rv = nghttp2_session_send(ctx->h2); + if(nghttp2_is_fatal(rv)) { - DEBUGF(LOG_CF(data, cf, "nghttp2_session_send error (%s)%d", - nghttp2_strerror(rv), rv)); + CURL_TRC_CF(data, cf, "[0] nghttp2_session_send error (%s)%d", + nghttp2_strerror(rv), rv); return CURLE_SEND_ERROR; } - return nw_out_flush(cf, data); + return proxy_h2_nw_out_flush(cf, data); } static ssize_t on_session_send(nghttp2_session *h2, @@ -517,7 +555,7 @@ static ssize_t on_session_send(nghttp2_session *h2, DEBUGASSERT(data); nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, - nw_out_writer, cf, &result); + proxy_h2_nw_out_writer, cf, &result); if(nwritten < 0) { if(result == CURLE_AGAIN) { return NGHTTP2_ERR_WOULDBLOCK; @@ -532,8 +570,100 @@ static ssize_t on_session_send(nghttp2_session *h2, return nwritten; } -static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, - void *userp) +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int proxy_h2_fr_print(const nghttp2_frame *frame, + char *buffer, size_t blen) +{ + switch(frame->hd.type) { + case NGHTTP2_DATA: { + return msnprintf(buffer, blen, + "FRAME[DATA, len=%d, eos=%d, padlen=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM), + (int)frame->data.padlen); + } + case NGHTTP2_HEADERS: { + return msnprintf(buffer, blen, + "FRAME[HEADERS, len=%d, hend=%d, eos=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); + } + case NGHTTP2_PRIORITY: { + return msnprintf(buffer, blen, + "FRAME[PRIORITY, len=%d, flags=%d]", + (int)frame->hd.length, frame->hd.flags); + } + case NGHTTP2_RST_STREAM: { + return msnprintf(buffer, blen, + "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]", + (int)frame->hd.length, frame->hd.flags, + frame->rst_stream.error_code); + } + case NGHTTP2_SETTINGS: { + if(frame->hd.flags & NGHTTP2_FLAG_ACK) { + return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]"); + } + return msnprintf(buffer, blen, + "FRAME[SETTINGS, len=%d]", (int)frame->hd.length); + } + case NGHTTP2_PUSH_PROMISE: + return msnprintf(buffer, blen, + "FRAME[PUSH_PROMISE, len=%d, hend=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)); + case NGHTTP2_PING: + return msnprintf(buffer, blen, + "FRAME[PING, len=%d, ack=%d]", + (int)frame->hd.length, + frame->hd.flags & NGHTTP2_FLAG_ACK); + case NGHTTP2_GOAWAY: { + char scratch[128]; + size_t s_len = CURL_ARRAYSIZE(scratch); + size_t len = (frame->goaway.opaque_data_len < s_len) ? + frame->goaway.opaque_data_len : s_len-1; + if(len) + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len] = '\0'; + return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " + "last_stream=%d]", frame->goaway.error_code, + scratch, frame->goaway.last_stream_id); + } + case NGHTTP2_WINDOW_UPDATE: { + return msnprintf(buffer, blen, + "FRAME[WINDOW_UPDATE, incr=%d]", + frame->window_update.window_size_increment); + } + default: + return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]", + frame->hd.type, (int)frame->hd.length, + frame->hd.flags); + } +} + +static int proxy_h2_on_frame_send(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)session; + DEBUGASSERT(data); + if(data && Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer); + } + return 0; +} +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +static int proxy_h2_on_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp) { struct Curl_cfilter *cf = userp; struct cf_h2_proxy_ctx *ctx = cf->ctx; @@ -542,85 +672,77 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, (void)session; DEBUGASSERT(data); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer); + } +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + if(!stream_id) { /* stream ID zero is for connection-oriented stuff */ DEBUGASSERT(data); switch(frame->hd.type) { case NGHTTP2_SETTINGS: - /* we do not do anything with this for now */ + /* Since the initial stream window is 64K, a request might be on HOLD, + * due to exhaustion. The (initial) SETTINGS may announce a much larger + * window and *assume* that we treat this like a WINDOW_UPDATE. Some + * servers send an explicit WINDOW_UPDATE, but not all seem to do that. + * To be safe, we UNHOLD a stream in order not to stall. */ + if(CURL_WANT_SEND(data)) { + drain_tunnel(cf, data, &ctx->tunnel); + } break; case NGHTTP2_GOAWAY: - infof(data, "recveived GOAWAY, error=%d, last_stream=%u", - frame->goaway.error_code, frame->goaway.last_stream_id); - ctx->goaway = TRUE; - break; - case NGHTTP2_WINDOW_UPDATE: - DEBUGF(LOG_CF(data, cf, "recv frame WINDOW_UPDATE")); + ctx->rcvd_goaway = TRUE; break; default: - DEBUGF(LOG_CF(data, cf, "recv frame %x on 0", frame->hd.type)); + break; } return 0; } if(stream_id != ctx->tunnel.stream_id) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] rcvd FRAME not for tunnel", - stream_id)); + CURL_TRC_CF(data, cf, "[%d] rcvd FRAME not for tunnel", stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } switch(frame->hd.type) { - case NGHTTP2_DATA: - /* If body started on this stream, then receiving DATA is illegal. */ - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame DATA", stream_id)); - break; case NGHTTP2_HEADERS: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame HEADERS", stream_id)); - /* nghttp2 guarantees that :status is received, and we store it to stream->status_code. Fuzzing has proven this can still be reached without status code having been set. */ if(!ctx->tunnel.resp) return NGHTTP2_ERR_CALLBACK_FAILURE; /* Only final status code signals the end of header */ - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] got http status: %d", - stream_id, ctx->tunnel.resp->status)); + 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; } } break; - case NGHTTP2_PUSH_PROMISE: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv PUSH_PROMISE", stream_id)); - return NGHTTP2_ERR_CALLBACK_FAILURE; - case NGHTTP2_RST_STREAM: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv RST", stream_id)); - ctx->tunnel.reset = TRUE; - break; case NGHTTP2_WINDOW_UPDATE: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv WINDOW_UPDATE", stream_id)); - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - Curl_expire(data, 0, EXPIRE_RUN_NOW); - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] unpausing after win update", - stream_id)); + if(CURL_WANT_SEND(data)) { + drain_tunnel(cf, data, &ctx->tunnel); } break; default: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame %x", - stream_id, frame->hd.type)); break; } return 0; } -static int on_header(nghttp2_session *session, const nghttp2_frame *frame, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - uint8_t flags, - void *userp) +static int proxy_h2_on_header(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, + void *userp) { struct Curl_cfilter *cf = userp; struct cf_h2_proxy_ctx *ctx = cf->ctx; @@ -633,10 +755,9 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, (void)session; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ if(stream_id != ctx->tunnel.stream_id) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] header for non-tunnel stream: " - "%.*s: %.*s", stream_id, - (int)namelen, name, - (int)valuelen, value)); + CURL_TRC_CF(data, cf, "[%d] header for non-tunnel stream: " + "%.*s: %.*s", stream_id, + (int)namelen, name, (int)valuelen, value); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -664,8 +785,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, return NGHTTP2_ERR_CALLBACK_FAILURE; resp->prev = ctx->tunnel.resp; ctx->tunnel.resp = resp; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] status: HTTP/2 %03d", - stream_id, ctx->tunnel.resp->status)); + CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d", + stream_id, ctx->tunnel.resp->status); return 0; } @@ -678,10 +799,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] header: %.*s: %.*s", - stream_id, - (int)namelen, name, - (int)valuelen, value)); + CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s", + stream_id, (int)namelen, name, (int)valuelen, value); return 0; /* 0 is successful */ } @@ -721,8 +840,8 @@ static ssize_t tunnel_send_callback(nghttp2_session *session, if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf)) *data_flags = NGHTTP2_DATA_FLAG_EOF; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] tunnel_send_callback -> %zd", - ts->stream_id, nread)); + CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd", + ts->stream_id, nread); return nread; } @@ -746,14 +865,17 @@ static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags, if(nwritten < 0) { if(result != CURLE_AGAIN) return NGHTTP2_ERR_CALLBACK_FAILURE; +#ifdef DEBUGBUILD nwritten = 0; +#endif } DEBUGASSERT((size_t)nwritten == len); return 0; } -static int on_stream_close(nghttp2_session *session, int32_t stream_id, - uint32_t error_code, void *userp) +static int proxy_h2_on_stream_close(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, void *userp) { struct Curl_cfilter *cf = userp; struct cf_h2_proxy_ctx *ctx = cf->ctx; @@ -765,27 +887,26 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, if(stream_id != ctx->tunnel.stream_id) return 0; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] on_stream_close, %s (err %d)", - stream_id, nghttp2_http2_strerror(error_code), error_code)); + CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)", + stream_id, nghttp2_http2_strerror(error_code), error_code); ctx->tunnel.closed = TRUE; ctx->tunnel.error = error_code; return 0; } -static CURLcode h2_submit(int32_t *pstream_id, - struct Curl_cfilter *cf, - struct Curl_easy *data, - nghttp2_session *h2, - struct httpreq *req, - const nghttp2_priority_spec *pri_spec, - void *stream_user_data, - nghttp2_data_source_read_callback read_callback, - void *read_ctx) +static CURLcode proxy_h2_submit(int32_t *pstream_id, + struct Curl_cfilter *cf, + struct Curl_easy *data, + nghttp2_session *h2, + struct httpreq *req, + const nghttp2_priority_spec *pri_spec, + void *stream_user_data, + nghttp2_data_source_read_callback read_callback, + void *read_ctx) { struct dynhds h2_headers; nghttp2_nv *nva = NULL; - unsigned int i; int32_t stream_id = -1; size_t nheader; CURLcode result; @@ -796,22 +917,12 @@ static CURLcode h2_submit(int32_t *pstream_id, if(result) goto out; - nheader = Curl_dynhds_count(&h2_headers); - nva = malloc(sizeof(nghttp2_nv) * nheader); + nva = Curl_dynhds_to_nva(&h2_headers, &nheader); if(!nva) { result = CURLE_OUT_OF_MEMORY; goto out; } - for(i = 0; i < nheader; ++i) { - struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); - nva[i].name = (unsigned char *)e->name; - nva[i].namelen = e->namelen; - nva[i].value = (unsigned char *)e->value; - nva[i].valuelen = e->valuelen; - nva[i].flags = NGHTTP2_NV_FLAG_NONE; - } - if(read_callback) { nghttp2_data_provider data_prd; @@ -848,44 +959,20 @@ static CURLcode submit_CONNECT(struct Curl_cfilter *cf, CURLcode result; struct httpreq *req = NULL; - infof(data, "Establish HTTP/2 proxy tunnel to %s", ts->authority); - - result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1, - NULL, 0, ts->authority, strlen(ts->authority), - NULL, 0); + result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2); if(result) goto out; - - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, - req->authority, TRUE); + result = Curl_creader_set_null(data); if(result) goto out; - if(data->state.aptr.proxyuserpwd) { - result = Curl_dynhds_h1_cadd_line(&req->headers, - data->state.aptr.proxyuserpwd); - if(result) - goto out; - } - - if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) - && data->set.str[STRING_USERAGENT]) { - result = Curl_dynhds_cadd(&req->headers, "User-Agent", - data->set.str[STRING_USERAGENT]); - if(result) - goto out; - } - - result = Curl_dynhds_add_custom(data, TRUE, &req->headers); - if(result) - goto out; + infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority); - result = h2_submit(&ts->stream_id, cf, data, ctx->h2, req, - NULL, ts, tunnel_send_callback, cf); + result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req, + NULL, ts, tunnel_send_callback, cf); if(result) { - DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u", - nghttp2_strerror(ts->stream_id), ts->stream_id)); + CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s", + ts->stream_id, nghttp2_strerror(ts->stream_id)); } out: @@ -907,7 +994,7 @@ static CURLcode inspect_response(struct Curl_cfilter *cf, DEBUGASSERT(ts->resp); if(ts->resp->status/100 == 2) { infof(data, "CONNECT tunnel established, response %d", ts->resp->status); - tunnel_go_state(cf, ts, TUNNEL_ESTABLISHED, data); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data); return CURLE_OK; } @@ -919,16 +1006,16 @@ static CURLcode inspect_response(struct Curl_cfilter *cf, } if(auth_reply) { - DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'", - auth_reply->value)); + CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'", + auth_reply->value); result = Curl_http_input_auth(data, ts->resp->status == 407, auth_reply->value); if(result) return result; if(data->req.newurl) { - /* Inidicator that we should try again */ + /* Indicator that we should try again */ Curl_safefree(data->req.newurl); - tunnel_go_state(cf, ts, TUNNEL_INIT, data); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data); return CURLE_OK; } } @@ -937,9 +1024,9 @@ static CURLcode inspect_response(struct Curl_cfilter *cf, return CURLE_RECV_ERROR; } -static CURLcode CONNECT(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct tunnel_stream *ts) +static CURLcode H2_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct tunnel_stream *ts) { struct cf_h2_proxy_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -948,62 +1035,62 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, DEBUGASSERT(ts->authority); do { switch(ts->state) { - case TUNNEL_INIT: + case H2_TUNNEL_INIT: /* Prepare the CONNECT request and make a first attempt to send. */ - DEBUGF(LOG_CF(data, cf, "CONNECT start for %s", ts->authority)); + CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority); result = submit_CONNECT(cf, data, ts); if(result) goto out; - tunnel_go_state(cf, ts, TUNNEL_CONNECT, data); - /* FALLTHROUGH */ + h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data); + FALLTHROUGH(); - case TUNNEL_CONNECT: + case H2_TUNNEL_CONNECT: /* see that the request is completely sent */ - result = h2_progress_ingress(cf, data); + result = proxy_h2_progress_ingress(cf, data); if(!result) - result = h2_progress_egress(cf, data); - if(result) { - tunnel_go_state(cf, ts, TUNNEL_FAILED, data); + result = proxy_h2_progress_egress(cf, data); + if(result && result != CURLE_AGAIN) { + h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data); break; } if(ts->has_final_response) { - tunnel_go_state(cf, ts, TUNNEL_RESPONSE, data); + h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data); } else { result = CURLE_OK; goto out; } - /* FALLTHROUGH */ + FALLTHROUGH(); - case TUNNEL_RESPONSE: + case H2_TUNNEL_RESPONSE: DEBUGASSERT(ts->has_final_response); result = inspect_response(cf, data, ts); if(result) goto out; break; - case TUNNEL_ESTABLISHED: + case H2_TUNNEL_ESTABLISHED: return CURLE_OK; - case TUNNEL_FAILED: + case H2_TUNNEL_FAILED: return CURLE_RECV_ERROR; default: break; } - } while(ts->state == TUNNEL_INIT); + } while(ts->state == H2_TUNNEL_INIT); out: - if(result || ctx->tunnel.closed) - tunnel_go_state(cf, ts, TUNNEL_FAILED, data); + if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed) + h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data); return result; } static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_h2_proxy_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -1018,7 +1105,7 @@ static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, /* Connect the lower filters first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } @@ -1043,11 +1130,16 @@ static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, /* for the secondary socket (FTP), use the "connect to host" * but ignore the "connect to port" (use the secondary port) */ - result = CONNECT(cf, data, ts); + result = H2_CONNECT(cf, data, ts); out: - *done = (result == CURLE_OK) && (ts->state == TUNNEL_ESTABLISHED); - cf->connected = *done; + *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED); + if(*done) { + cf->connected = TRUE; + /* The real request will follow the CONNECT, reset request partially */ + Curl_req_soft_reset(&data->req, data); + Curl_client_reset(data); + } CF_DATA_RESTORE(cf, save); return result; } @@ -1063,6 +1155,8 @@ static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) cf_h2_proxy_ctx_clear(ctx); CF_DATA_RESTORE(cf, save); } + if(cf->next) + cf->next->cft->do_close(cf->next, data); } static void cf_h2_proxy_destroy(struct Curl_cfilter *cf, @@ -1077,36 +1171,110 @@ static void cf_h2_proxy_destroy(struct Curl_cfilter *cf, } } +static CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result; + int rv; + + if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + + if(!ctx->sent_goaway) { + rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, + 0, 0, + (const uint8_t *)"shutdown", + sizeof("shutdown")); + if(rv) { + failf(data, "nghttp2_submit_goaway() failed: %s(%d)", + nghttp2_strerror(rv), rv); + result = CURLE_SEND_ERROR; + goto out; + } + ctx->sent_goaway = TRUE; + } + /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ + result = CURLE_OK; + if(nghttp2_session_want_write(ctx->h2)) + result = proxy_h2_progress_egress(cf, data); + if(!result && nghttp2_session_want_read(ctx->h2)) + result = proxy_h2_progress_ingress(cf, data); + + *done = (ctx->conn_closed || + (!result && !nghttp2_session_want_write(ctx->h2) && + !nghttp2_session_want_read(ctx->h2))); +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_h2_proxy_ctx *ctx = cf->ctx; if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) || - (ctx && ctx->tunnel.state == TUNNEL_ESTABLISHED && + (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED && !Curl_bufq_is_empty(&ctx->tunnel.recvbuf))) return TRUE; - return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } -static int cf_h2_proxy_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *sock) +static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { struct cf_h2_proxy_ctx *ctx = cf->ctx; - int bitmap = GETSOCK_BLANK; struct cf_call_data save; + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + bool want_recv, want_send; + + if(!cf->connected && ctx->h2) { + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + want_recv = nghttp2_session_want_read(ctx->h2); + } + else + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); - CF_DATA_SAVE(save, cf, data); - sock[0] = Curl_conn_cf_get_socket(cf, data); - bitmap |= GETSOCK_READSOCK(0); - - /* HTTP/2 layer wants to send data) AND there's a window to send data in */ - if(nghttp2_session_want_write(ctx->h2) && - nghttp2_session_get_remote_window_size(ctx->h2)) - bitmap |= GETSOCK_WRITESOCK(0); + if(ctx->h2 && (want_recv || want_send)) { + bool c_exhaust, s_exhaust; - CF_DATA_RESTORE(cf, save); - return bitmap; + CF_DATA_SAVE(save, cf, data); + c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = ctx->tunnel.stream_id >= 0 && + !nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + (!c_exhaust && nghttp2_session_want_write(ctx->h2)) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d", + want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } + else if(ctx->sent_goaway && !cf->shutdown) { + /* shutdown in progress */ + CF_DATA_SAVE(save, cf, data); + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + want_recv = nghttp2_session_want_read(ctx->h2); + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d", + want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } } static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf, @@ -1117,9 +1285,9 @@ static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf, ssize_t rv = 0; if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new " - "connection", ctx->tunnel.stream_id)); - connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ + CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new " + "connection", ctx->tunnel.stream_id); + connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ return -1; } @@ -1138,7 +1306,8 @@ static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf, *err = CURLE_OK; rv = 0; - DEBUGF(LOG_CF(data, cf, "handle_tunnel_close -> %zd, %d", rv, *err)); + CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> %zd, %d", + ctx->tunnel.stream_id, rv, *err); return rv; } @@ -1163,7 +1332,8 @@ static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(ctx->tunnel.reset || (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || - (ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) { + (ctx->rcvd_goaway && + ctx->last_stream_id < ctx->tunnel.stream_id)) { *err = CURLE_RECV_ERROR; nread = -1; } @@ -1174,8 +1344,8 @@ static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } out: - DEBUGF(LOG_CF(data, cf, "tunnel_recv(len=%zu) -> %zd, %d", - len, nread, *err)); + CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %zd, %d", + ctx->tunnel.stream_id, len, nread, *err); return nread; } @@ -1188,14 +1358,14 @@ static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf, struct cf_call_data save; CURLcode result; - if(ctx->tunnel.state != TUNNEL_ESTABLISHED) { + if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) { *err = CURLE_RECV_ERROR; return -1; } CF_DATA_SAVE(save, cf, data); if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) { - *err = h2_progress_ingress(cf, data); + *err = proxy_h2_progress_ingress(cf, data); if(*err) goto out; } @@ -1203,129 +1373,266 @@ static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf, nread = tunnel_recv(cf, data, buf, len, err); if(nread > 0) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] increase window by %zd", - ctx->tunnel.stream_id, nread)); + CURL_TRC_CF(data, cf, "[%d] increase window by %zd", + ctx->tunnel.stream_id, nread); nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread); } - result = h2_progress_egress(cf, data); - if(result) { + result = proxy_h2_progress_egress(cf, data); + if(result && (result != CURLE_AGAIN)) { *err = result; nread = -1; } out: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv(len=%zu) -> %zd %d", - ctx->tunnel.stream_id, len, nread, *err)); + if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) && + (nread >= 0 || *err == CURLE_AGAIN)) { + /* data pending and no fatal error to report. Need to trigger + * draining to avoid stalling when no socket events happen. */ + drain_tunnel(cf, data, &ctx->tunnel); + } + CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d", + ctx->tunnel.stream_id, len, nread, *err); CF_DATA_RESTORE(cf, save); return nread; } static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *mem, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_h2_proxy_ctx *ctx = cf->ctx; struct cf_call_data save; - ssize_t nwritten = -1; - const unsigned char *buf = mem; - size_t start_len = len; int rv; + ssize_t nwritten; + CURLcode result; - if(ctx->tunnel.state != TUNNEL_ESTABLISHED) { + (void)eos; + if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) { *err = CURLE_SEND_ERROR; return -1; } CF_DATA_SAVE(save, cf, data); - while(len) { + if(ctx->tunnel.closed) { + nwritten = -1; + *err = CURLE_SEND_ERROR; + goto out; + } + else { nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err); - if(nwritten <= 0) { - if(*err && *err != CURLE_AGAIN) { - DEBUGF(LOG_CF(data, cf, "error adding data to tunnel sendbuf: %d", - *err)); - nwritten = -1; - goto out; - } - /* blocked */ - nwritten = 0; - } - else { - DEBUGASSERT((size_t)nwritten <= len); - buf += (size_t)nwritten; - len -= (size_t)nwritten; - } + if(nwritten < 0 && (*err != CURLE_AGAIN)) + goto out; + } - /* resume the tunnel stream and let the h2 session send, which - * triggers reading from tunnel.sendbuf */ + if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + /* req body data is buffered, resume the potentially suspended stream */ rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id); if(nghttp2_is_fatal(rv)) { *err = CURLE_SEND_ERROR; nwritten = -1; goto out; } - *err = h2_progress_egress(cf, data); - if(*err) { + } + + result = proxy_h2_progress_ingress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + /* Call the nghttp2 send loop and flush to write ALL buffered data, + * headers and/or request body completely out to the network */ + result = proxy_h2_progress_egress(cf, data); + if(result && (result != CURLE_AGAIN)) { + *err = result; + nwritten = -1; + goto out; + } + + if(proxy_h2_should_close_session(ctx)) { + /* nghttp2 thinks this session is done. If the stream has not been + * closed, this is an error state for out transfer */ + if(ctx->tunnel.closed) { + *err = CURLE_SEND_ERROR; + nwritten = -1; + } + else { + CURL_TRC_CF(data, cf, "[0] send: nothing to do in this session"); + *err = CURLE_HTTP2; nwritten = -1; + } + } + +out: + if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) && + (nwritten >= 0 || *err == CURLE_AGAIN)) { + /* data pending and no fatal error to report. Need to trigger + * draining to avoid stalling when no socket events happen. */ + drain_tunnel(cf, data, &ctx->tunnel); + } + CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, " + "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)", + ctx->tunnel.stream_id, len, nwritten, *err, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->tunnel.sendbuf), + Curl_bufq_len(&ctx->outbufq)); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static CURLcode cf_h2_proxy_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + + CF_DATA_SAVE(save, cf, data); + if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + /* resume the potentially suspended tunnel */ + int rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id); + if(nghttp2_is_fatal(rv)) { + result = CURLE_SEND_ERROR; goto out; } + } + + result = proxy_h2_progress_egress(cf, data); - if(!nwritten && Curl_bufq_is_full(&ctx->tunnel.sendbuf)) { - size_t rwin; - /* we could not add to the buffer and after session processing, - * it is still full. */ - rwin = nghttp2_session_get_stream_remote_window_size( - ctx->h2, ctx->tunnel.stream_id); - DEBUGF(LOG_CF(data, cf, "cf_send: tunnel win %u/%zu", - nghttp2_session_get_remote_window_size(ctx->h2), rwin)); - if(rwin == 0) { - /* We cannot upload more as the stream's remote window size - * is 0. We need to receive WIN_UPDATEs before we can continue. - */ - data->req.keepon |= KEEP_SEND_HOLD; - DEBUGF(LOG_CF(data, cf, "pausing send as remote flow " - "window is exhausted")); +out: + CURL_TRC_CF(data, cf, "[%d] flush -> %d, " + "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)", + ctx->tunnel.stream_id, result, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->tunnel.sendbuf), + Curl_bufq_len(&ctx->outbufq)); + CF_DATA_RESTORE(cf, save); + return result; +} + +static bool proxy_h2_connisalive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + bool alive = TRUE; + + *input_pending = FALSE; + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + return FALSE; + + if(*input_pending) { + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, + only "protocol frames" */ + CURLcode result; + ssize_t nread = -1; + + *input_pending = FALSE; + nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result); + if(nread != -1) { + if(proxy_h2_process_pending_input(cf, data, &result) < 0) + /* immediate error, considered dead */ + alive = FALSE; + else { + alive = !proxy_h2_should_close_session(ctx); } - break; + } + else if(result != CURLE_AGAIN) { + /* the read failed so let's say this is dead anyway */ + alive = FALSE; } } - nwritten = start_len - len; - if(nwritten > 0) { - *err = CURLE_OK; - } - else if(ctx->tunnel.closed) { - nwritten = -1; - *err = CURLE_SEND_ERROR; + return alive; +} + +static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + CURLcode result; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending)); + CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d", + result, *input_pending); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_h2_proxy_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_NEED_FLUSH: { + if(!Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + CURL_TRC_CF(data, cf, "needs flush"); + *pres1 = TRUE; + return CURLE_OK; + } + break; } - else { - nwritten = -1; - *err = CURLE_AGAIN; + default: + break; } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} -out: - DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) -> %zd, %d ", - start_len, nwritten, *err)); - CF_DATA_RESTORE(cf, save); - return nwritten; +static CURLcode cf_h2_proxy_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + struct cf_call_data save; + + (void)arg1; + (void)arg2; + + switch(event) { + case CF_CTRL_FLUSH: + CF_DATA_SAVE(save, cf, data); + result = cf_h2_proxy_flush(cf, data); + CF_DATA_RESTORE(cf, save); + break; + default: + break; + } + return result; } struct Curl_cftype Curl_cft_h2_proxy = { "H2-PROXY", - CF_TYPE_IP_CONNECT, - CURL_LOG_DEFAULT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, + CURL_LOG_LVL_NONE, cf_h2_proxy_destroy, cf_h2_proxy_connect, cf_h2_proxy_close, + cf_h2_proxy_shutdown, Curl_cf_http_proxy_get_host, - cf_h2_proxy_get_select_socks, + cf_h2_proxy_adjust_pollset, cf_h2_proxy_data_pending, cf_h2_proxy_send, cf_h2_proxy_recv, - Curl_cf_def_cntrl, - Curl_cf_def_conn_is_alive, + cf_h2_proxy_cntrl, + cf_h2_proxy_is_alive, Curl_cf_def_conn_keep_alive, - Curl_cf_def_query, + cf_h2_proxy_query, }; CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, @@ -1336,7 +1643,7 @@ CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, CURLcode result = CURLE_OUT_OF_MEMORY; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) goto out; diff --git a/Utilities/cmcurl/lib/cf-haproxy.c b/Utilities/cmcurl/lib/cf-haproxy.c index 86d7fd1837d..7bc12dbbb15 100644 --- a/Utilities/cmcurl/lib/cf-haproxy.c +++ b/Utilities/cmcurl/lib/cf-haproxy.c @@ -30,7 +30,7 @@ #include "urldata.h" #include "cfilters.h" #include "cf-haproxy.h" -#include "curl_log.h" +#include "curl_trc.h" #include "multiif.h" /* The last 3 #include files should be in this order */ @@ -54,13 +54,13 @@ static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx) { DEBUGASSERT(ctx); ctx->state = HAPROXY_INIT; - Curl_dyn_reset(&ctx->data_out); + curlx_dyn_reset(&ctx->data_out); } static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx) { if(ctx) { - Curl_dyn_free(&ctx->data_out); + curlx_dyn_free(&ctx->data_out); free(ctx); } } @@ -70,25 +70,32 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, { struct cf_haproxy_ctx *ctx = cf->ctx; CURLcode result; - const char *tcp_version; + const char *client_ip; + struct ip_quadruple ipquad; + int is_ipv6; DEBUGASSERT(ctx); DEBUGASSERT(ctx->state == HAPROXY_INIT); #ifdef USE_UNIX_SOCKETS if(cf->conn->unix_domain_socket) /* the buffer is large enough to hold this! */ - result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n")); + result = curlx_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n")); else { #endif /* USE_UNIX_SOCKETS */ + result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad); + if(result) + return result; + /* Emit the correct prefix for IPv6 */ - tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4"; + if(data->set.str[STRING_HAPROXY_CLIENT_IP]) + client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP]; + else + client_ip = ipquad.local_ip; - result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", - tcp_version, - data->info.conn_local_ip, - data->info.conn_primary_ip, - data->info.conn_local_port, - data->info.conn_primary_port); + result = curlx_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", + is_ipv6 ? "TCP6" : "TCP4", + client_ip, ipquad.remote_ip, + ipquad.local_port, ipquad.remote_port); #ifdef USE_UNIX_SOCKETS } @@ -98,7 +105,7 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_haproxy_ctx *ctx = cf->ctx; CURLcode result; @@ -110,7 +117,7 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, return CURLE_OK; } - result = cf->next->cft->connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; @@ -120,25 +127,30 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, if(result) goto out; ctx->state = HAPROXY_SEND; - /* FALLTHROUGH */ + FALLTHROUGH(); case HAPROXY_SEND: - len = Curl_dyn_len(&ctx->data_out); + len = curlx_dyn_len(&ctx->data_out); if(len > 0) { - ssize_t written = Curl_conn_send(data, cf->sockindex, - Curl_dyn_ptr(&ctx->data_out), - len, &result); - if(written < 0) - goto out; - Curl_dyn_tail(&ctx->data_out, len - (size_t)written); - if(Curl_dyn_len(&ctx->data_out) > 0) { + ssize_t nwritten; + nwritten = Curl_conn_cf_send(cf->next, data, + curlx_dyn_ptr(&ctx->data_out), len, FALSE, + &result); + if(nwritten < 0) { + if(result != CURLE_AGAIN) + goto out; + result = CURLE_OK; + nwritten = 0; + } + curlx_dyn_tail(&ctx->data_out, len - (size_t)nwritten); + if(curlx_dyn_len(&ctx->data_out) > 0) { result = CURLE_OK; goto out; } } ctx->state = HAPROXY_DONE; - /* FALLTHROUGH */ + FALLTHROUGH(); default: - Curl_dyn_free(&ctx->data_out); + curlx_dyn_free(&ctx->data_out); break; } @@ -152,46 +164,41 @@ static void cf_haproxy_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { (void)data; - DEBUGF(LOG_CF(data, cf, "destroy")); + CURL_TRC_CF(data, cf, "destroy"); cf_haproxy_ctx_free(cf->ctx); } static void cf_haproxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - DEBUGF(LOG_CF(data, cf, "close")); + CURL_TRC_CF(data, cf, "close"); cf->connected = FALSE; cf_haproxy_ctx_reset(cf->ctx); if(cf->next) - cf->next->cft->close(cf->next, data); + cf->next->cft->do_close(cf->next, data); } -static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static void cf_haproxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { - int fds; - - fds = cf->next->cft->get_select_socks(cf->next, data, socks); - if(!fds && cf->next->connected && !cf->connected) { + if(cf->next->connected && !cf->connected) { /* If we are not connected, but the filter "below" is * and not waiting on something, we are sending. */ - socks[0] = Curl_conn_cf_get_socket(cf, data); - return GETSOCK_WRITESOCK(0); + Curl_pollset_set_out_only(data, ps, Curl_conn_cf_get_socket(cf, data)); } - return fds; } - struct Curl_cftype Curl_cft_haproxy = { "HAPROXY", - 0, + CF_TYPE_PROXY, 0, cf_haproxy_destroy, cf_haproxy_connect, cf_haproxy_close, + Curl_cf_def_shutdown, Curl_cf_def_get_host, - cf_haproxy_get_select_socks, + cf_haproxy_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, @@ -209,13 +216,13 @@ static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf, CURLcode result; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } ctx->state = HAPROXY_INIT; - Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY); + curlx_dyn_init(&ctx->data_out, DYN_HAXPROXY); result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx); if(result) @@ -224,7 +231,7 @@ static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf, out: cf_haproxy_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } diff --git a/Utilities/cmcurl/lib/cf-https-connect.c b/Utilities/cmcurl/lib/cf-https-connect.c index d03cd1e0d5d..cd0d226efda 100644 --- a/Utilities/cmcurl/lib/cf-https-connect.c +++ b/Utilities/cmcurl/lib/cf-https-connect.c @@ -24,13 +24,14 @@ #include "curl_setup.h" -#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) +#if !defined(CURL_DISABLE_HTTP) #include "urldata.h" #include -#include "curl_log.h" +#include "curl_trc.h" #include "cfilters.h" #include "connect.h" +#include "hostip.h" #include "multiif.h" #include "cf-https-connect.h" #include "http2.h" @@ -41,7 +42,6 @@ #include "curl_memory.h" #include "memdebug.h" - typedef enum { CF_HC_INIT, CF_HC_CONNECT, @@ -55,7 +55,8 @@ struct cf_hc_baller { CURLcode result; struct curltime started; int reply_ms; - bool enabled; + enum alpnid alpn_id; + BIT(shutdown); }; static void cf_hc_baller_reset(struct cf_hc_baller *b, @@ -72,7 +73,7 @@ static void cf_hc_baller_reset(struct cf_hc_baller *b, static bool cf_hc_baller_is_active(struct cf_hc_baller *b) { - return b->enabled && b->cf && !b->result; + return b->cf && !b->result; } static bool cf_hc_baller_has_started(struct cf_hc_baller *b) @@ -83,7 +84,7 @@ static bool cf_hc_baller_has_started(struct cf_hc_baller *b) static int cf_hc_baller_reply_ms(struct cf_hc_baller *b, struct Curl_easy *data) { - if(b->reply_ms < 0) + if(b->cf && (b->reply_ms < 0)) b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS, &b->reply_ms, NULL); return b->reply_ms; @@ -95,31 +96,71 @@ static bool cf_hc_baller_data_pending(struct cf_hc_baller *b, return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data); } +static bool cf_hc_baller_needs_flush(struct cf_hc_baller *b, + struct Curl_easy *data) +{ + return b->cf && !b->result && Curl_conn_cf_needs_flush(b->cf, data); +} + +static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + if(b->cf && !b->result) + return Curl_conn_cf_cntrl(b->cf, data, FALSE, event, arg1, arg2); + return CURLE_OK; +} + struct cf_hc_ctx { cf_hc_state state; - const struct Curl_dns_entry *remotehost; struct curltime started; /* when connect started */ CURLcode result; /* overall result */ - struct cf_hc_baller h3_baller; - struct cf_hc_baller h21_baller; - int soft_eyeballs_timeout_ms; - int hard_eyeballs_timeout_ms; + struct cf_hc_baller ballers[2]; + size_t baller_count; + unsigned int soft_eyeballs_timeout_ms; + unsigned int hard_eyeballs_timeout_ms; }; +static void cf_hc_baller_assign(struct cf_hc_baller *b, + enum alpnid alpn_id) +{ + b->alpn_id = alpn_id; + switch(b->alpn_id) { + case ALPN_h3: + b->name = "h3"; + break; + case ALPN_h2: + b->name = "h2"; + break; + case ALPN_h1: + b->name = "h1"; + break; + default: + b->result = CURLE_FAILED_INIT; + break; + } +} + static void cf_hc_baller_init(struct cf_hc_baller *b, struct Curl_cfilter *cf, struct Curl_easy *data, - const char *name, int transport) { - struct cf_hc_ctx *ctx = cf->ctx; struct Curl_cfilter *save = cf->next; - b->name = name; cf->next = NULL; - b->started = Curl_now(); - b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost, - transport, CURL_CF_SSL_ENABLE); + b->started = curlx_now(); + switch(b->alpn_id) { + case ALPN_h3: + transport = TRNSPRT_QUIC; + break; + default: + break; + } + + if(!b->result) + b->result = Curl_cf_setup_insert_after(cf, data, transport, + CURL_CF_SSL_ENABLE); b->cf = cf->next; cf->next = save; } @@ -132,7 +173,7 @@ static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b, struct Curl_cfilter *save = cf->next; cf->next = b->cf; - b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done); + b->result = Curl_conn_cf_connect(cf->next, data, done); b->cf = cf->next; /* it might mutate */ cf->next = save; return b->result; @@ -141,14 +182,15 @@ static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b, static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_hc_ctx *ctx = cf->ctx; + size_t i; if(ctx) { - cf_hc_baller_reset(&ctx->h3_baller, data); - cf_hc_baller_reset(&ctx->h21_baller, data); + for(i = 0; i < ctx->baller_count; ++i) + cf_hc_baller_reset(&ctx->ballers[i], data); ctx->state = CF_HC_INIT; ctx->result = CURLE_OK; ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; - ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; + ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 4; } } @@ -158,22 +200,29 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, { struct cf_hc_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + int reply_ms; + size_t i; DEBUGASSERT(winner->cf); - if(winner != &ctx->h3_baller) - cf_hc_baller_reset(&ctx->h3_baller, data); - if(winner != &ctx->h21_baller) - cf_hc_baller_reset(&ctx->h21_baller, data); - - DEBUGF(LOG_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms", - winner->name, (int)Curl_timediff(Curl_now(), winner->started), - cf_hc_baller_reply_ms(winner, data))); + for(i = 0; i < ctx->baller_count; ++i) + if(winner != &ctx->ballers[i]) + cf_hc_baller_reset(&ctx->ballers[i], data); + + reply_ms = cf_hc_baller_reply_ms(winner, data); + if(reply_ms >= 0) + CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms", + winner->name, (int)curlx_timediff(curlx_now(), + winner->started), reply_ms); + else + CURL_TRC_CF(data, cf, "deferred handshake %s: %dms", + winner->name, (int)curlx_timediff(curlx_now(), + winner->started)); + cf->next = winner->cf; winner->cf = NULL; switch(cf->conn->alpn) { case CURL_HTTP_VERSION_3: - infof(data, "using HTTP/3"); break; case CURL_HTTP_VERSION_2: #ifdef USE_NGHTTP2 @@ -186,48 +235,50 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, return result; } #endif - infof(data, "using HTTP/2"); - break; - case CURL_HTTP_VERSION_1_1: - infof(data, "using HTTP/1.1"); break; default: - infof(data, "using HTTP/1.x"); break; } ctx->state = CF_HC_SUCCESS; cf->connected = TRUE; - Curl_conn_cf_cntrl(cf->next, data, TRUE, - CF_CTRL_CONN_INFO_UPDATE, 0, NULL); return result; } -static bool time_to_start_h21(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct curltime now) +static bool time_to_start_next(struct Curl_cfilter *cf, + struct Curl_easy *data, + size_t idx, struct curltime now) { struct cf_hc_ctx *ctx = cf->ctx; timediff_t elapsed_ms; + size_t i; - if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller)) + if(idx >= ctx->baller_count) return FALSE; - - if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller)) + if(cf_hc_baller_has_started(&ctx->ballers[idx])) + return FALSE; + for(i = 0; i < idx; i++) { + if(!ctx->ballers[i].result) + break; + } + if(i == idx) { + CURL_TRC_CF(data, cf, "all previous attempts failed, starting %s", + ctx->ballers[idx].name); return TRUE; - - elapsed_ms = Curl_timediff(now, ctx->started); + } + elapsed_ms = curlx_timediff(now, ctx->started); if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { - DEBUGF(LOG_CF(data, cf, "hard timeout of %dms reached, starting h21", - ctx->hard_eyeballs_timeout_ms)); + CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting %s", + ctx->hard_eyeballs_timeout_ms, ctx->ballers[idx].name); return TRUE; } - if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) { - if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) { - DEBUGF(LOG_CF(data, cf, "soft timeout of %dms reached, h3 has not " - "seen any data, starting h21", - ctx->soft_eyeballs_timeout_ms)); + if((idx > 0) && (elapsed_ms >= ctx->soft_eyeballs_timeout_ms)) { + if(cf_hc_baller_reply_ms(&ctx->ballers[idx - 1], data) < 0) { + CURL_TRC_CF(data, cf, "soft timeout of %dms reached, %s has not " + "seen any data, starting %s", + ctx->soft_eyeballs_timeout_ms, + ctx->ballers[idx - 1].name, ctx->ballers[idx].name); return TRUE; } /* set the effective hard timeout again */ @@ -239,67 +290,73 @@ static bool time_to_start_h21(struct Curl_cfilter *cf, static CURLcode cf_hc_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_hc_ctx *ctx = cf->ctx; struct curltime now; CURLcode result = CURLE_OK; + size_t i, failed_ballers; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; } *done = FALSE; - now = Curl_now(); + now = curlx_now(); switch(ctx->state) { case CF_HC_INIT: - DEBUGASSERT(!ctx->h3_baller.cf); - DEBUGASSERT(!ctx->h21_baller.cf); DEBUGASSERT(!cf->next); - DEBUGF(LOG_CF(data, cf, "connect, init")); + for(i = 0; i < ctx->baller_count; i++) + DEBUGASSERT(!ctx->ballers[i].cf); + CURL_TRC_CF(data, cf, "connect, init"); ctx->started = now; - if(ctx->h3_baller.enabled) { - cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC); - if(ctx->h21_baller.enabled) - Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); + cf_hc_baller_init(&ctx->ballers[0], cf, data, cf->conn->transport); + if(ctx->baller_count > 1) { + Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); + CURL_TRC_CF(data, cf, "set next attempt to start in %ums", + ctx->soft_eyeballs_timeout_ms); } - else if(ctx->h21_baller.enabled) - cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", - cf->conn->transport); ctx->state = CF_HC_CONNECT; - /* FALLTHROUGH */ + FALLTHROUGH(); case CF_HC_CONNECT: - if(cf_hc_baller_is_active(&ctx->h3_baller)) { - result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done); + if(cf_hc_baller_is_active(&ctx->ballers[0])) { + result = cf_hc_baller_connect(&ctx->ballers[0], cf, data, done); if(!result && *done) { - result = baller_connected(cf, data, &ctx->h3_baller); + result = baller_connected(cf, data, &ctx->ballers[0]); goto out; } } - if(time_to_start_h21(cf, data, now)) { - cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", - cf->conn->transport); + if(time_to_start_next(cf, data, 1, now)) { + cf_hc_baller_init(&ctx->ballers[1], cf, data, cf->conn->transport); } - if(cf_hc_baller_is_active(&ctx->h21_baller)) { - DEBUGF(LOG_CF(data, cf, "connect, check h21")); - result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done); + if((ctx->baller_count > 1) && cf_hc_baller_is_active(&ctx->ballers[1])) { + CURL_TRC_CF(data, cf, "connect, check %s", ctx->ballers[1].name); + result = cf_hc_baller_connect(&ctx->ballers[1], cf, data, done); if(!result && *done) { - result = baller_connected(cf, data, &ctx->h21_baller); + result = baller_connected(cf, data, &ctx->ballers[1]); goto out; } } - if((!ctx->h3_baller.enabled || ctx->h3_baller.result) && - (!ctx->h21_baller.enabled || ctx->h21_baller.result)) { - /* both failed or disabled. we give up */ - DEBUGF(LOG_CF(data, cf, "connect, all failed")); - result = ctx->result = ctx->h3_baller.enabled? - ctx->h3_baller.result : ctx->h21_baller.result; + failed_ballers = 0; + for(i = 0; i < ctx->baller_count; i++) { + if(ctx->ballers[i].result) + ++failed_ballers; + } + + if(failed_ballers == ctx->baller_count) { + /* all have failed. we give up */ + CURL_TRC_CF(data, cf, "connect, all attempts failed"); + for(i = 0; i < ctx->baller_count; i++) { + if(ctx->ballers[i].result) { + result = ctx->ballers[i].result; + break; + } + } ctx->state = CF_HC_FAILURE; goto out; } @@ -321,81 +378,99 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, } out: - DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done)); + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); return result; } -static int cf_hc_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_hc_ctx *ctx = cf->ctx; - size_t i, j, s; - int brc, rc = GETSOCK_BLANK; - curl_socket_t bsocks[MAX_SOCKSPEREASYHANDLE]; - struct cf_hc_baller *ballers[2]; + size_t i; + CURLcode result = CURLE_OK; - if(cf->connected) - return cf->next->cft->get_select_socks(cf->next, data, socks); + DEBUGASSERT(data); + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } - ballers[0] = &ctx->h3_baller; - ballers[1] = &ctx->h21_baller; - for(i = s = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { - struct cf_hc_baller *b = ballers[i]; - if(!cf_hc_baller_is_active(b)) + /* shutdown all ballers that have not done so already. If one fails, + * continue shutting down others until all are shutdown. */ + for(i = 0; i < ctx->baller_count; i++) { + struct cf_hc_baller *b = &ctx->ballers[i]; + bool bdone = FALSE; + if(!cf_hc_baller_is_active(b) || b->shutdown) continue; - brc = Curl_conn_cf_get_select_socks(b->cf, data, bsocks); - DEBUGF(LOG_CF(data, cf, "get_selected_socks(%s) -> %x", b->name, brc)); - if(!brc) - continue; - for(j = 0; j < MAX_SOCKSPEREASYHANDLE && s < MAX_SOCKSPEREASYHANDLE; ++j) { - if((brc & GETSOCK_WRITESOCK(j)) || (brc & GETSOCK_READSOCK(j))) { - socks[s] = bsocks[j]; - if(brc & GETSOCK_WRITESOCK(j)) - rc |= GETSOCK_WRITESOCK(s); - if(brc & GETSOCK_READSOCK(j)) - rc |= GETSOCK_READSOCK(s); - s++; - } + b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone); + if(b->result || bdone) + b->shutdown = TRUE; /* treat a failed shutdown as done */ + } + + *done = TRUE; + for(i = 0; i < ctx->baller_count; i++) { + if(!ctx->ballers[i].shutdown) + *done = FALSE; + } + if(*done) { + for(i = 0; i < ctx->baller_count; i++) { + if(ctx->ballers[i].result) + result = ctx->ballers[i].result; } } - DEBUGF(LOG_CF(data, cf, "get_selected_socks -> %x", rc)); - return rc; + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + return result; +} + +static void cf_hc_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(!cf->connected) { + struct cf_hc_ctx *ctx = cf->ctx; + size_t i; + + for(i = 0; i < ctx->baller_count; i++) { + struct cf_hc_baller *b = &ctx->ballers[i]; + if(!cf_hc_baller_is_active(b)) + continue; + Curl_conn_cf_adjust_pollset(b->cf, data, ps); + } + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); + } } static bool cf_hc_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_hc_ctx *ctx = cf->ctx; + size_t i; if(cf->connected) return cf->next->cft->has_data_pending(cf->next, data); - DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending")); - return cf_hc_baller_data_pending(&ctx->h3_baller, data) - || cf_hc_baller_data_pending(&ctx->h21_baller, data); + for(i = 0; i < ctx->baller_count; i++) + if(cf_hc_baller_data_pending(&ctx->ballers[i], data)) + return TRUE; + return FALSE; } -static struct curltime get_max_baller_time(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query) +static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query) { struct cf_hc_ctx *ctx = cf->ctx; - struct Curl_cfilter *cfb; struct curltime t, tmax; + size_t i; memset(&tmax, 0, sizeof(tmax)); - memset(&t, 0, sizeof(t)); - cfb = ctx->h21_baller.enabled? ctx->h21_baller.cf : NULL; - if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) - tmax = t; - } - memset(&t, 0, sizeof(t)); - cfb = ctx->h3_baller.enabled? ctx->h3_baller.cf : NULL; - if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) - tmax = t; + for(i = 0; i < ctx->baller_count; i++) { + struct Curl_cfilter *cfb = ctx->ballers[i].cf; + memset(&t, 0, sizeof(t)); + if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { + if((t.tv_sec || t.tv_usec) && curlx_timediff_us(t, tmax) > 0) + tmax = t; + } } return tmax; } @@ -404,35 +479,66 @@ static CURLcode cf_hc_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2) { + struct cf_hc_ctx *ctx = cf->ctx; + size_t i; + if(!cf->connected) { switch(query) { case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT); + *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT); return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { struct curltime *when = pres2; - *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); + *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); return CURLE_OK; } + case CF_QUERY_NEED_FLUSH: { + for(i = 0; i < ctx->baller_count; i++) + if(cf_hc_baller_needs_flush(&ctx->ballers[i], data)) { + *pres1 = TRUE; + return CURLE_OK; + } + break; + } default: break; } } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } +static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_hc_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + size_t i; + + if(!cf->connected) { + for(i = 0; i < ctx->baller_count; i++) { + result = cf_hc_baller_cntrl(&ctx->ballers[i], data, event, arg1, arg2); + if(result && (result != CURLE_AGAIN)) + goto out; + } + result = CURLE_OK; + } +out: + return result; +} + static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - DEBUGF(LOG_CF(data, cf, "close")); + CURL_TRC_CF(data, cf, "close"); cf_hc_reset(cf, data); cf->connected = FALSE; if(cf->next) { - cf->next->cft->close(cf->next, data); + cf->next->cft->do_close(cf->next, data); Curl_conn_cf_discard_chain(&cf->next, data); } } @@ -442,7 +548,7 @@ static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_hc_ctx *ctx = cf->ctx; (void)data; - DEBUGF(LOG_CF(data, cf, "destroy")); + CURL_TRC_CF(data, cf, "destroy"); cf_hc_reset(cf, data); Curl_safefree(ctx); } @@ -450,16 +556,17 @@ static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cftype Curl_cft_http_connect = { "HTTPS-CONNECT", 0, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, cf_hc_destroy, cf_hc_connect, cf_hc_close, + cf_hc_shutdown, Curl_cf_def_get_host, - cf_hc_get_select_socks, + cf_hc_adjust_pollset, cf_hc_data_pending, Curl_cf_def_send, Curl_cf_def_recv, - Curl_cf_def_cntrl, + cf_hc_cntrl, Curl_cf_def_conn_is_alive, Curl_cf_def_conn_keep_alive, cf_hc_query, @@ -467,22 +574,32 @@ struct Curl_cftype Curl_cft_http_connect = { static CURLcode cf_hc_create(struct Curl_cfilter **pcf, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, - bool try_h3, bool try_h21) + enum alpnid *alpnids, size_t alpn_count) { struct Curl_cfilter *cf = NULL; struct cf_hc_ctx *ctx; CURLcode result = CURLE_OK; + size_t i; + + DEBUGASSERT(alpnids); + DEBUGASSERT(alpn_count); + DEBUGASSERT(alpn_count <= CURL_ARRAYSIZE(ctx->ballers)); + if(!alpn_count || (alpn_count > CURL_ARRAYSIZE(ctx->ballers))) { + failf(data, "https-connect filter create with unsupported %zu ALPN ids", + alpn_count); + return CURLE_FAILED_INIT; + } - (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - ctx->remotehost = remotehost; - ctx->h3_baller.enabled = try_h3; - ctx->h21_baller.enabled = try_h21; + for(i = 0; i < alpn_count; ++i) + cf_hc_baller_assign(&ctx->ballers[i], alpnids[i]); + for(; i < CURL_ARRAYSIZE(ctx->ballers); ++i) + ctx->ballers[i].alpn_id = ALPN_none; + ctx->baller_count = alpn_count; result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx); if(result) @@ -491,7 +608,7 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, cf_hc_reset(cf, data); out: - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; free(ctx); return result; } @@ -499,14 +616,13 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, static CURLcode cf_http_connect_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, - bool try_h3, bool try_h21) + enum alpnid *alpn_ids, size_t alpn_count) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21); + result = cf_hc_create(&cf, data, alpn_ids, alpn_count); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); @@ -514,38 +630,114 @@ static CURLcode cf_http_connect_add(struct Curl_easy *data, return result; } +static bool cf_https_alpns_contain(enum alpnid id, + enum alpnid *list, size_t len) +{ + size_t i; + for(i = 0; i < len; ++i) { + if(id == list[i]) + return TRUE; + } + return FALSE; +} + CURLcode Curl_cf_https_setup(struct Curl_easy *data, struct connectdata *conn, - int sockindex, - const struct Curl_dns_entry *remotehost) + int sockindex) { - bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */ + enum alpnid alpn_ids[2]; + size_t alpn_count = 0; CURLcode result = CURLE_OK; + struct Curl_cfilter cf_fake, *cf = NULL; (void)sockindex; - (void)remotehost; - - if(!conn->bits.tls_enable_alpn) - goto out; + /* we want to log for the filter before we create it, fake it. */ + memset(&cf_fake, 0, sizeof(cf_fake)); + cf_fake.cft = &Curl_cft_http_connect; + cf = &cf_fake; + + if(conn->bits.tls_enable_alpn) { +#ifdef USE_HTTPSRR + /* Is there an HTTPSRR use its ALPNs here. + * We are here after having selected a connection to a host+port and + * can no longer change that. Any HTTPSRR advice for other hosts and ports + * we need to ignore. */ + struct Curl_dns_entry *dns = data->state.dns[sockindex]; + struct Curl_https_rrinfo *rr = dns ? dns->hinfo : NULL; + if(rr && !rr->no_def_alpn && /* ALPNs are defaults */ + (!rr->target || /* for same host */ + !rr->target[0] || + (rr->target[0] == '.' && + !rr->target[1])) && + (rr->port < 0 || /* for same port */ + rr->port == conn->remote_port)) { + size_t i; + for(i = 0; i < CURL_ARRAYSIZE(rr->alpns) && + alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) { + enum alpnid alpn = rr->alpns[i]; + if(cf_https_alpns_contain(alpn, alpn_ids, alpn_count)) + continue; + switch(alpn) { + case ALPN_h3: + if(Curl_conn_may_http3(data, conn)) + break; /* not possible */ + if(data->state.http_neg.allowed & CURL_HTTP_V3x) { + CURL_TRC_CF(data, cf, "adding h3 via HTTPS-RR"); + alpn_ids[alpn_count++] = alpn; + } + break; + case ALPN_h2: + if(data->state.http_neg.allowed & CURL_HTTP_V2x) { + CURL_TRC_CF(data, cf, "adding h2 via HTTPS-RR"); + alpn_ids[alpn_count++] = alpn; + } + break; + case ALPN_h1: + if(data->state.http_neg.allowed & CURL_HTTP_V1x) { + CURL_TRC_CF(data, cf, "adding h1 via HTTPS-RR"); + alpn_ids[alpn_count++] = alpn; + } + break; + default: /* ignore */ + break; + } + } + } +#endif - if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { - result = Curl_conn_may_http3(data, conn); - if(result) /* can't do it */ - goto out; - try_h3 = TRUE; - try_h21 = FALSE; + if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) && + (data->state.http_neg.wanted & CURL_HTTP_V3x) && + !cf_https_alpns_contain(ALPN_h3, alpn_ids, alpn_count)) { + result = Curl_conn_may_http3(data, conn); + if(!result) { + CURL_TRC_CF(data, cf, "adding wanted h3"); + alpn_ids[alpn_count++] = ALPN_h3; + } + else if(data->state.http_neg.wanted == CURL_HTTP_V3x) + goto out; /* only h3 allowed, not possible, error out */ + } + if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) && + (data->state.http_neg.wanted & CURL_HTTP_V2x) && + !cf_https_alpns_contain(ALPN_h2, alpn_ids, alpn_count)) { + CURL_TRC_CF(data, cf, "adding wanted h2"); + alpn_ids[alpn_count++] = ALPN_h2; + } + else if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) && + (data->state.http_neg.wanted & CURL_HTTP_V1x) && + !cf_https_alpns_contain(ALPN_h1, alpn_ids, alpn_count)) { + CURL_TRC_CF(data, cf, "adding wanted h1"); + alpn_ids[alpn_count++] = ALPN_h1; + } } - else if(data->state.httpwant >= CURL_HTTP_VERSION_3) { - /* We assume that silently not even trying H3 is ok here */ - /* TODO: should we fail instead? */ - try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK); - try_h21 = TRUE; + + /* If we identified ALPNs to use, install our filter. Otherwise, + * install nothing, so our call will use a default connect setup. */ + if(alpn_count) { + result = cf_http_connect_add(data, conn, sockindex, alpn_ids, alpn_count); } - result = cf_http_connect_add(data, conn, sockindex, remotehost, - try_h3, try_h21); out: return result; } -#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ +#endif /* !defined(CURL_DISABLE_HTTP) */ diff --git a/Utilities/cmcurl/lib/cf-https-connect.h b/Utilities/cmcurl/lib/cf-https-connect.h index 6a39527317c..c36726f0a22 100644 --- a/Utilities/cmcurl/lib/cf-https-connect.h +++ b/Utilities/cmcurl/lib/cf-https-connect.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) +#if !defined(CURL_DISABLE_HTTP) struct Curl_cfilter; struct Curl_easy; @@ -38,21 +38,18 @@ extern struct Curl_cftype Curl_cft_http_connect; CURLcode Curl_cf_http_connect_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, bool try_h3, bool try_h21); CURLcode Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, bool try_h3, bool try_h21); CURLcode Curl_cf_https_setup(struct Curl_easy *data, struct connectdata *conn, - int sockindex, - const struct Curl_dns_entry *remotehost); + int sockindex); -#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ +#endif /* !defined(CURL_DISABLE_HTTP) */ #endif /* HEADER_CURL_CF_HTTP_H */ diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c index 960979b025b..e31977201c6 100644 --- a/Utilities/cmcurl/lib/cf-socket.c +++ b/Utilities/cmcurl/lib/cf-socket.c @@ -35,6 +35,9 @@ #elif defined(HAVE_NETINET_TCP_H) #include #endif +#ifdef HAVE_NETINET_UDP_H +#include +#endif #ifdef HAVE_SYS_IOCTL_H #include #endif @@ -53,6 +56,11 @@ #include #endif +#ifdef __DragonFly__ +/* Required for __DragonFly_version */ +#include +#endif + #include "urldata.h" #include "bufq.h" #include "sendf.h" @@ -66,13 +74,17 @@ #include "multiif.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "progress.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "conncache.h" #include "multihandle.h" +#include "rand.h" #include "share.h" -#include "version_win32.h" +#include "strdup.h" +#include "system_win32.h" +#include "curlx/version_win32.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -80,7 +92,7 @@ #include "memdebug.h" -#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) && defined(WIN32) +#if defined(USE_IPV6) && defined(IPV6_V6ONLY) && defined(_WIN32) /* It makes support for IPv4-mapped IPv6 addresses. * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; * Windows Vista and later: default is on; @@ -101,14 +113,10 @@ static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) #if defined(TCP_NODELAY) curl_socklen_t onoff = (curl_socklen_t) 1; int level = IPPROTO_TCP; -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; -#else - (void) data; -#endif - if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, - sizeof(onoff)) < 0) + if(setsockopt(sockfd, level, TCP_NODELAY, + (void *)&onoff, sizeof(onoff)) < 0) infof(data, "Could not set TCP_NODELAY: %s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); #else @@ -118,7 +126,7 @@ static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) } #ifdef SO_NOSIGPIPE -/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when +/* The preferred method on macOS (10.2 and later) to prevent SIGPIPEs when sending data to a dead peer (instead of relying on the 4th argument to send being MSG_NOSIGNAL). Possibly also existing and in use on other BSD systems? */ @@ -126,8 +134,9 @@ static void nosigpipe(struct Curl_easy *data, curl_socket_t sockfd) { int onoff = 1; - if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, - sizeof(onoff)) < 0) { + (void)data; + if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, + (void *)&onoff, sizeof(onoff)) < 0) { #if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; infof(data, "Could not set SO_NOSIGPIPE: %s", @@ -139,14 +148,24 @@ static void nosigpipe(struct Curl_easy *data, #define nosigpipe(x,y) Curl_nop_stmt #endif -#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H) -/* DragonFlyBSD and Windows use millisecond units */ +#if defined(USE_WINSOCK) && \ + defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT) +/* Win 10, v 1709 (10.0.16299) and later can use SetSockOpt TCP_KEEP____ + * so should use seconds */ +#define CURL_WINSOCK_KEEP_SSO +#define KEEPALIVE_FACTOR(x) +#elif defined(USE_WINSOCK) || \ + (defined(__sun) && !defined(TCP_KEEPIDLE)) || \ + (defined(__DragonFly__) && __DragonFly_version < 500702) || \ + (defined(_WIN32) && !defined(TCP_KEEPIDLE)) +/* Solaris < 11.4, DragonFlyBSD < 500702 and Windows < 10.0.16299 + * use millisecond units. */ #define KEEPALIVE_FACTOR(x) (x *= 1000) #else #define KEEPALIVE_FACTOR(x) #endif -#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS) +#if defined(USE_WINSOCK) && !defined(SIO_KEEPALIVE_VALS) #define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) struct tcp_keepalive { @@ -160,52 +179,125 @@ static void tcpkeepalive(struct Curl_easy *data, curl_socket_t sockfd) { - int optval = data->set.tcp_keepalive?1:0; + int optval = data->set.tcp_keepalive ? 1 : 0; /* only set IDLE and INTVL if setting KEEPALIVE is successful */ if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd); + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set SO_KEEPALIVE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } else { -#if defined(SIO_KEEPALIVE_VALS) +#if defined(SIO_KEEPALIVE_VALS) /* Windows */ +/* Windows 10, version 1709 (10.0.16299) and later versions */ +#if defined(CURL_WINSOCK_KEEP_SSO) + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPIDLE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPINTVL on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } + optval = curlx_sltosi(data->set.tcp_keepcnt); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPCNT on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#else /* Windows < 10.0.16299 */ struct tcp_keepalive vals; DWORD dummy; vals.onoff = 1; optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); - vals.keepalivetime = optval; + vals.keepalivetime = (u_long)optval; optval = curlx_sltosi(data->set.tcp_keepintvl); KEEPALIVE_FACTOR(optval); - vals.keepaliveinterval = optval; + vals.keepaliveinterval = (u_long)optval; if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals), NULL, 0, &dummy, NULL, NULL) != 0) { - infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd %d: %d", - (int)sockfd, WSAGetLastError()); + infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); } -#else +#endif +#else /* !Windows */ #ifdef TCP_KEEPIDLE optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd); + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPIDLE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #elif defined(TCP_KEEPALIVE) - /* Mac OS X style */ + /* macOS style */ optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd); + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#elif defined(TCP_KEEPALIVE_THRESHOLD) + /* Solaris <11.4 style */ + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #endif #ifdef TCP_KEEPINTVL optval = curlx_sltosi(data->set.tcp_keepintvl); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd); + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPINTVL on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#elif defined(TCP_KEEPALIVE_ABORT_THRESHOLD) + /* Solaris <11.4 style */ + /* TCP_KEEPALIVE_ABORT_THRESHOLD should equal to + * TCP_KEEPCNT * TCP_KEEPINTVL on other platforms. + * The default value of TCP_KEEPCNT is 9 on Linux, + * 8 on *BSD/macOS, 5 or 10 on Windows. We use the + * default config for Solaris <11.4 because there is + * no default value for TCP_KEEPCNT on Solaris 11.4. + * + * Note that the consequent probes will not be sent + * at equal intervals on Solaris, but will be sent + * using the exponential backoff algorithm. */ + optval = curlx_sltosi(data->set.tcp_keepcnt) * + curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); + } +#endif +#ifdef TCP_KEEPCNT + optval = curlx_sltosi(data->set.tcp_keepcnt); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPCNT on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); } #endif #endif @@ -216,9 +308,9 @@ tcpkeepalive(struct Curl_easy *data, * Assign the address `ai` to the Curl_sockaddr_ex `dest` and * set the transport used. */ -void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, - const struct Curl_addrinfo *ai, - int transport) +CURLcode Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, + const struct Curl_addrinfo *ai, + int transport) { /* * The Curl_sockaddr_ex structure is basically libcurl's external API @@ -242,11 +334,15 @@ void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, dest->protocol = IPPROTO_UDP; break; } - dest->addrlen = ai->ai_addrlen; + dest->addrlen = (unsigned int)ai->ai_addrlen; + + if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) { + DEBUGASSERT(0); + return CURLE_TOO_LARGE; + } - if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) - dest->addrlen = sizeof(struct Curl_sockaddr_storage); - memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen); + memcpy(&dest->curl_sa_addr, ai->ai_addr, dest->addrlen); + return CURLE_OK; } static CURLcode socket_open(struct Curl_easy *data, @@ -265,11 +361,11 @@ static CURLcode socket_open(struct Curl_easy *data, * might have been changed and this 'new' address will actually be used * here to connect. */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); *sockfd = data->set.fopensocket(data->set.opensocket_client, CURLSOCKTYPE_IPCXN, (struct curl_sockaddr *)addr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } else { /* opensocket callback not set, so simply create the socket now */ @@ -280,9 +376,9 @@ static CURLcode socket_open(struct Curl_easy *data, /* no socket, no connection */ return CURLE_COULDNT_CONNECT; -#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) if(data->conn->scope_id && (addr->family == AF_INET6)) { - struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; + struct sockaddr_in6 * const sa6 = (void *)&addr->curl_sa_addr; sa6->sin6_scope_id = data->conn->scope_id; } #endif @@ -305,30 +401,37 @@ CURLcode Curl_socket_open(struct Curl_easy *data, curl_socket_t *sockfd) { struct Curl_sockaddr_ex dummy; + CURLcode result; if(!addr) - /* if the caller doesn't want info back, use a local temp copy */ + /* if the caller does not want info back, use a local temp copy */ addr = &dummy; - Curl_sock_assign_addr(addr, ai, transport); + result = Curl_sock_assign_addr(addr, ai, transport); + if(result) + return result; + return socket_open(data, addr, sockfd); } static int socket_close(struct Curl_easy *data, struct connectdata *conn, int use_callback, curl_socket_t sock) { + if(CURL_SOCKET_BAD == sock) + return 0; + if(use_callback && conn && conn->fclosesocket) { int rc; - Curl_multi_closed(data, sock); - Curl_set_in_callback(data, true); + Curl_multi_will_close(data, sock); + Curl_set_in_callback(data, TRUE); rc = conn->fclosesocket(conn->closesocket_client, sock); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); return rc; } if(conn) /* tell the multi-socket code about this */ - Curl_multi_closed(data, sock); + Curl_multi_will_close(data, sock); sclose(sock); @@ -356,30 +459,17 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, Buffer Size The problem described in this knowledge-base is applied only to pre-Vista - Windows. Following function trying to detect OS version and skips + Windows. Following function trying to detect OS version and skips SO_SNDBUF adjustment for Windows Vista and above. */ -#define DETECT_OS_NONE 0 -#define DETECT_OS_PREVISTA 1 -#define DETECT_OS_VISTA_OR_LATER 2 -void Curl_sndbufset(curl_socket_t sockfd) +void Curl_sndbuf_init(curl_socket_t sockfd) { int val = CURL_MAX_WRITE_SIZE + 32; int curval = 0; int curlen = sizeof(curval); - static int detectOsState = DETECT_OS_NONE; - - if(detectOsState == DETECT_OS_NONE) { - if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) - detectOsState = DETECT_OS_VISTA_OR_LATER; - else - detectOsState = DETECT_OS_PREVISTA; - } - - if(detectOsState == DETECT_OS_VISTA_OR_LATER) + if(Curl_isVistaOrGreater) return; if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) @@ -388,8 +478,90 @@ void Curl_sndbufset(curl_socket_t sockfd) setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); } -#endif +#endif /* USE_WINSOCK */ +/* + * Curl_parse_interface() + * + * This is used to parse interface argument in the following formats. + * In all the examples, `host` can be an IP address or a hostname. + * + * - can be either an interface name or a host. + * if! - interface name. + * host! - hostname. + * ifhost!! - interface name and hostname. + * + * Parameters: + * + * input [in] - input string. + * 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. + * iface [in/out] - address where a pointer to newly allocated memory + * holding the interface will be stored upon completion. + * host [in/out] - address where a pointer to newly allocated memory + * holding the host will be stored upon completion. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_parse_interface(const char *input, + char **dev, char **iface, char **host) +{ + static const char if_prefix[] = "if!"; + static const char host_prefix[] = "host!"; + 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(!strncmp(if_prefix, input, strlen(if_prefix))) { + input += strlen(if_prefix); + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *iface = Curl_memdup0(input, len - strlen(if_prefix)); + return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + else if(!strncmp(host_prefix, input, strlen(host_prefix))) { + input += strlen(host_prefix); + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *host = Curl_memdup0(input, len - strlen(host_prefix)); + return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) { + const char *host_part; + input += strlen(if_host_prefix); + len -= strlen(if_host_prefix); + host_part = memchr(input, '!', len); + if(!host_part || !*(host_part + 1)) + return CURLE_BAD_FUNCTION_ARGUMENT; + *iface = Curl_memdup0(input, host_part - input); + if(!*iface) + return CURLE_OUT_OF_MEMORY; + ++host_part; + *host = Curl_memdup0(host_part, len - (host_part - input)); + if(!*host) { + free(*iface); + *iface = NULL; + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; + } + + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *dev = Curl_memdup0(input, len); + return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} + +#ifndef CURL_DISABLE_BINDLOCAL static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, curl_socket_t sockfd, int af, unsigned int scope) { @@ -397,7 +569,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; #endif @@ -407,127 +579,117 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* how many port numbers to try to bind to, increasing one at a time */ int portnum = data->set.localportrange; const char *dev = data->set.str[STRING_DEVICE]; + const char *iface_input = data->set.str[STRING_INTERFACE]; + const char *host_input = data->set.str[STRING_BINDHOST]; + const char *iface = iface_input ? iface_input : dev; + const char *host = host_input ? host_input : dev; int error; #ifdef IP_BIND_ADDRESS_NO_PORT int on = 1; #endif -#ifndef ENABLE_IPV6 +#ifndef USE_IPV6 (void)scope; #endif /************************************************************* * Select device to bind socket to *************************************************************/ - if(!dev && !port) + 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; memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); - if(dev && (strlen(dev)<255) ) { + if(iface || host) { char myhost[256] = ""; int done = 0; /* -1 for error, 1 for address found */ - bool is_interface = FALSE; - bool is_host = FALSE; - static const char *if_prefix = "if!"; - static const char *host_prefix = "host!"; - - if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) { - dev += strlen(if_prefix); - is_interface = TRUE; - } - else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) { - dev += strlen(host_prefix); - is_host = TRUE; - } + if2ip_result_t if2ip_result = IF2IP_NOT_FOUND; - /* interface */ - if(!is_host) { #ifdef SO_BINDTODEVICE - /* I am not sure any other OSs than Linux that provide this feature, - * and at the least I cannot test. --Ben - * - * This feature allows one to tightly bind the local socket to a - * particular interface. This will force even requests to other - * local interfaces to go out the external interface. - * - * - * Only bind to the interface when specified as interface, not just - * as a hostname or ip address. + 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 just as a hostname or ip address. * - * interface might be a VRF, eg: vrf-blue, which means it cannot be - * converted to an IP address and would fail Curl_if2ip. Simply try - * to use it straight away. + * The interface might be a VRF, eg: vrf-blue, which means it cannot be + * converted to an IP address and would fail Curl_if2ip. Simply try to + * use it straight away. */ if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - dev, (curl_socklen_t)strlen(dev) + 1) == 0) { - /* This is typically "errno 1, error: Operation not permitted" if - * you're not running as root or another suitable privileged - * user. - * If it succeeds it means the parameter was a valid interface and - * not an IP address. Return immediately. + 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. */ - return CURLE_OK; + if(!host_input) { + infof(data, "socket successfully bound to interface '%s'", iface); + return CURLE_OK; + } } + } #endif - - switch(Curl_if2ip(af, -#ifdef ENABLE_IPV6 - scope, conn->scope_id, + if(!host_input) { + /* Discover IP from input device, then bind to it */ + if2ip_result = Curl_if2ip(af, +#ifdef USE_IPV6 + scope, conn->scope_id, #endif - dev, myhost, sizeof(myhost))) { - case IF2IP_NOT_FOUND: - if(is_interface) { - /* Do not fall back to treating it as a host name */ - failf(data, "Couldn't bind to interface '%s'", dev); - return CURLE_INTERFACE_FAILED; - } - break; - case IF2IP_AF_NOT_SUPPORTED: - /* Signal the caller to try another address family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; - case IF2IP_FOUND: - is_interface = TRUE; - /* - * We now have the numerical IP address in the 'myhost' buffer - */ - infof(data, "Local Interface %s is ip %s using address family %i", - dev, myhost, af); - done = 1; - break; - } + iface, myhost, sizeof(myhost)); } - if(!is_interface) { + switch(if2ip_result) { + case IF2IP_NOT_FOUND: + if(iface_input && !host_input) { + /* Do not fall back to treating it as a hostname */ + char buffer[STRERROR_LEN]; + data->state.os_errno = error = SOCKERRNO; + failf(data, "Couldn't bind to interface '%s' with errno %d: %s", + iface, error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_INTERFACE_FAILED; + } + break; + case IF2IP_AF_NOT_SUPPORTED: + /* Signal the caller to try another address family if available */ + return CURLE_UNSUPPORTED_PROTOCOL; + case IF2IP_FOUND: + /* + * We now have the numerical IP address in the 'myhost' buffer + */ + host = myhost; + infof(data, "Local Interface %s is ip %s using address family %i", + iface, host, af); + done = 1; + break; + } + if(!iface_input || host_input) { /* - * This was not an interface, resolve the name as a host name + * This was not an interface, resolve the name as a hostname * or IP number * * Temporarily force name resolution to use only the address type * of the connection. The resolve functions should really be changed * to take a type parameter instead. */ - unsigned char ipver = conn->ip_version; - int rc; - - if(af == AF_INET) - conn->ip_version = CURL_IPRESOLVE_V4; -#ifdef ENABLE_IPV6 - else if(af == AF_INET6) - conn->ip_version = CURL_IPRESOLVE_V6; + int ip_version = (af == AF_INET) ? + CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER; +#ifdef USE_IPV6 + if(af == AF_INET6) + ip_version = CURL_IPRESOLVE_V6; #endif - rc = Curl_resolv(data, dev, 80, FALSE, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &h); - conn->ip_version = ipver; - + (void)Curl_resolv_blocking(data, host, 80, ip_version, &h); if(h) { + int h_af = h->addr->ai_family; /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ Curl_printable_address(h->addr, myhost, sizeof(myhost)); infof(data, "Name '%s' family %i resolved to '%s' family %i", - dev, af, myhost, h->addr->ai_family); - Curl_resolv_unlock(data, h); - if(af != h->addr->ai_family) { + host, af, myhost, h_af); + Curl_resolv_unlink(data, &h); /* this will NULL, potential free h */ + if(af != h_af) { /* bad IP version combo, signal the caller to try another address family if available */ return CURLE_UNSUPPORTED_PROTOCOL; @@ -537,14 +699,14 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, else { /* * provided dev was no interface (or interfaces are not supported - * e.g. solaris) no ip address and no domain we fail here + * e.g. Solaris) no ip address and no domain we fail here */ done = -1; } } if(done > 0) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* IPv6 address */ if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID @@ -552,19 +714,19 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if(scope_ptr) *(scope_ptr++) = '\0'; #endif - if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { + if(curlx_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { si6->sin6_family = AF_INET6; si6->sin6_port = htons(port); #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID if(scope_ptr) { /* The "myhost" string either comes from Curl_if2ip or from Curl_printable_address. The latter returns only numeric scope - IDs and the former returns none at all. So the scope ID, if + IDs and the former returns none at all. So the scope ID, if present, is known to be numeric */ - unsigned long scope_id = strtoul(scope_ptr, NULL, 10); - if(scope_id > UINT_MAX) + curl_off_t scope_id; + if(curlx_str_number((const char **)CURL_UNCONST(&scope_ptr), + &scope_id, UINT_MAX)) return CURLE_UNSUPPORTED_PROTOCOL; - si6->sin6_scope_id = (unsigned int)scope_id; } #endif @@ -575,7 +737,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, #endif /* IPv4 address */ if((af == AF_INET) && - (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { + (curlx_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { si4->sin_family = AF_INET; si4->sin_port = htons(port); sizeof_sa = sizeof(struct sockaddr_in); @@ -586,14 +748,17 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* errorbuf is set false so failf will overwrite any message already in the error buffer, so the user receives this error message instead of a generic resolve error. */ + char buffer[STRERROR_LEN]; data->state.errorbuf = FALSE; - failf(data, "Couldn't bind to '%s'", dev); + data->state.os_errno = error = SOCKERRNO; + failf(data, "Couldn't bind to '%s' with errno %d: %s", + host, error, Curl_strerror(error, buffer, sizeof(buffer))); return CURLE_INTERFACE_FAILED; } } else { /* no device was given, prepare sa to match af's needs */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(af == AF_INET6) { si6->sin6_family = AF_INET6; si6->sin6_port = htons(port); @@ -613,16 +778,6 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, for(;;) { if(bind(sockfd, sock, sizeof_sa) >= 0) { /* we succeeded to bind */ - struct Curl_sockaddr_storage add; - curl_socklen_t size = sizeof(add); - memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); - if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { - char buffer[STRERROR_LEN]; - data->state.os_errno = error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; - } infof(data, "Local port: %hu", port); conn->bits.bound = TRUE; return CURLE_OK; @@ -632,11 +787,11 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, port++; /* try next port */ if(port == 0) break; - infof(data, "Bind to local port %hu failed, trying next", port - 1); - /* We re-use/clobber the port variable here below */ + infof(data, "Bind to local port %d failed, trying next", port - 1); + /* We reuse/clobber the port variable here below */ if(sock->sa_family == AF_INET) si4->sin_port = ntohs(port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else si6->sin6_port = ntohs(port); #endif @@ -653,6 +808,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, return CURLE_INTERFACE_FAILED; } +#endif /* * verifyconnect() returns TRUE if the connect really has happened. @@ -664,7 +820,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) int err = 0; curl_socklen_t errSize = sizeof(err); -#ifdef WIN32 +#ifdef _WIN32 /* * In October 2003 we effectively nullified this function on Windows due to * problems with it using all CPU in multi-threaded cases. @@ -673,15 +829,15 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) * Gisle Vanem could reproduce the former problems with this function, but * could avoid them by adding this SleepEx() call below: * - * "I don't have Rational Quantify, but the hint from his post was - * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe + * "I do not have Rational Quantify, but the hint from his post was + * ntdll::NtRemoveIoCompletion(). I would assume the SleepEx (or maybe * just Sleep(0) would be enough?) would release whatever * mutex/critical-section the ntdll call is waiting on. * * Someone got to verify this on Win-NT 4.0, 2000." */ -#ifdef _WIN32_WCE +#ifdef UNDER_CE Sleep(0); #else SleepEx(0, FALSE); @@ -691,25 +847,25 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) err = SOCKERRNO; -#ifdef _WIN32_WCE - /* Old WinCE versions don't support SO_ERROR */ +#ifdef UNDER_CE + /* Old Windows CE versions do not support SO_ERROR */ if(WSAENOPROTOOPT == err) { SET_SOCKERRNO(0); err = 0; } #endif #if defined(EBADIOCTL) && defined(__minix) - /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ + /* Minix 3.1.x does not support getsockopt on UDP sockets */ if(EBADIOCTL == err) { SET_SOCKERRNO(0); err = 0; } #endif - if((0 == err) || (EISCONN == err)) + if((0 == err) || (SOCKEISCONN == err)) /* we are connected, awesome! */ rc = TRUE; else - /* This wasn't a successful connect */ + /* This was not a successful connect */ rc = FALSE; if(error) *error = err; @@ -727,13 +883,11 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) static CURLcode socket_connect_result(struct Curl_easy *data, const char *ipaddress, int error) { - char buffer[STRERROR_LEN]; - switch(error) { - case EINPROGRESS: - case EWOULDBLOCK: + case SOCKEINPROGRESS: + case SOCKEWOULDBLOCK: #if defined(EAGAIN) -#if (EAGAIN) != (EWOULDBLOCK) +#if (EAGAIN) != (SOCKEWOULDBLOCK) /* On some platforms EAGAIN and EWOULDBLOCK are the * same value, and on others they are different, hence * the odd #if @@ -745,100 +899,91 @@ static CURLcode socket_connect_result(struct Curl_easy *data, default: /* unknown error, fallthrough and try another address! */ - infof(data, "Immediate connect fail for %s: %s", - ipaddress, Curl_strerror(error, buffer, sizeof(buffer))); +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)ipaddress; +#else + { + char buffer[STRERROR_LEN]; + infof(data, "Immediate connect fail for %s: %s", + ipaddress, Curl_strerror(error, buffer, sizeof(buffer))); + } +#endif data->state.os_errno = error; /* connect failed */ return CURLE_COULDNT_CONNECT; } } -/* We have a recv buffer to enhance reads with len < NW_SMALL_READS. - * This happens often on TLS connections where the TLS implementation - * tries to read the head of a TLS record, determine the length of the - * full record and then make a subsequent read for that. - * On large reads, we will not fill the buffer to avoid the double copy. */ -#define NW_RECV_CHUNK_SIZE (64 * 1024) -#define NW_RECV_CHUNKS 1 -#define NW_SMALL_READS (1024) - struct cf_socket_ctx { int transport; struct Curl_sockaddr_ex addr; /* address to connect to */ curl_socket_t sock; /* current attempt socket */ - struct bufq recvbuf; /* used when `buffer_recv` is set */ - char r_ip[MAX_IPADR_LEN]; /* remote IP as string */ - int r_port; /* remote port number */ - char l_ip[MAX_IPADR_LEN]; /* local IP as string */ - int l_port; /* local port number */ + struct ip_quadruple ip; /* The IP quadruple 2x(addr+port) */ struct curltime started_at; /* when socket was created */ struct curltime connected_at; /* when socket connected/got first byte */ struct curltime first_byte_at; /* when first byte was recvd */ +#ifdef USE_WINSOCK + struct curltime last_sndbuf_query_at; /* when SO_SNDBUF last queried */ + ULONG sndbuf_size; /* the last set SO_SNDBUF size */ +#endif int error; /* errno of last failure or 0 */ +#ifdef DEBUGBUILD + int wblock_percent; /* percent of writes doing EAGAIN */ + int wpartial_percent; /* percent of bytes written in send */ + int rblock_percent; /* percent of reads doing EAGAIN */ + size_t recv_max; /* max enforced read size */ +#endif BIT(got_first_byte); /* if first byte was received */ + BIT(listening); /* socket is listening */ BIT(accepted); /* socket was accepted, not connected */ + BIT(sock_connected); /* socket is "connected", e.g. in UDP */ BIT(active); - BIT(buffer_recv); }; -static void cf_socket_ctx_init(struct cf_socket_ctx *ctx, - const struct Curl_addrinfo *ai, - int transport) +static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx, + const struct Curl_addrinfo *ai, + int transport) { + CURLcode result; + memset(ctx, 0, sizeof(*ctx)); ctx->sock = CURL_SOCKET_BAD; ctx->transport = transport; - Curl_sock_assign_addr(&ctx->addr, ai, transport); - Curl_bufq_init(&ctx->recvbuf, NW_RECV_CHUNK_SIZE, NW_RECV_CHUNKS); -} - -struct reader_ctx { - struct Curl_cfilter *cf; - struct Curl_easy *data; -}; - -static ssize_t nw_in_read(void *reader_ctx, - unsigned char *buf, size_t len, - CURLcode *err) -{ - struct reader_ctx *rctx = reader_ctx; - struct cf_socket_ctx *ctx = rctx->cf->ctx; - ssize_t nread; - *err = CURLE_OK; - nread = sread(ctx->sock, buf, len); - - if(-1 == nread) { - int sockerr = SOCKERRNO; + result = Curl_sock_assign_addr(&ctx->addr, ai, transport); + if(result) + return result; - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == sockerr) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefore - treat both error codes the same here */ - (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - *err = CURLE_AGAIN; - nread = -1; +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_DBG_SOCK_WBLOCK"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) + ctx->wblock_percent = (int)l; } - else { - char buffer[STRERROR_LEN]; - - failf(rctx->data, "Recv failure: %s", - Curl_strerror(sockerr, buffer, sizeof(buffer))); - rctx->data->state.os_errno = sockerr; - *err = CURLE_RECV_ERROR; - nread = -1; + p = getenv("CURL_DBG_SOCK_WPARTIAL"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) + ctx->wpartial_percent = (int)l; + } + p = getenv("CURL_DBG_SOCK_RBLOCK"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) + ctx->rblock_percent = (int)l; + } + p = getenv("CURL_DBG_SOCK_RMAX"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, CURL_OFF_T_MAX)) + ctx->recv_max = (size_t)l; } } - DEBUGF(LOG_CF(rctx->data, rctx->cf, "nw_in_read(len=%zu) -> %d, err=%d", - len, (int)nread, *err)); - return nread; +#endif + + return result; } static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -846,37 +991,14 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_socket_ctx *ctx = cf->ctx; if(ctx && CURL_SOCKET_BAD != ctx->sock) { - if(ctx->active) { - /* We share our socket at cf->conn->sock[cf->sockindex] when active. - * If it is no longer there, someone has stolen (and hopefully - * closed it) and we just forget about it. - */ - if(ctx->sock == cf->conn->sock[cf->sockindex]) { - DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T - ", active)", ctx->sock)); - socket_close(data, cf->conn, !ctx->accepted, ctx->sock); - cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; - } - else { - DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T - ") no longer at conn->sock[], discarding", ctx->sock)); - /* TODO: we do not want this to happen. Need to check which - * code is messing with conn->sock[cf->sockindex] */ - } - ctx->sock = CURL_SOCKET_BAD; - if(cf->sockindex == FIRSTSOCKET) - cf->conn->remote_addr = NULL; - } - else { - /* this is our local socket, we did never publish it */ - DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T - ", not active)", ctx->sock)); - sclose(ctx->sock); - ctx->sock = CURL_SOCKET_BAD; - } - Curl_bufq_reset(&ctx->recvbuf); + CURL_TRC_CF(data, cf, "cf_socket_close, fd=%" FMT_SOCKET_T, ctx->sock); + if(ctx->sock == cf->conn->sock[cf->sockindex]) + cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; + socket_close(data, cf->conn, !ctx->accepted, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + if(ctx->active && cf->sockindex == FIRSTSOCKET) + cf->conn->remote_addr = NULL; ctx->active = FALSE; - ctx->buffer_recv = FALSE; memset(&ctx->started_at, 0, sizeof(ctx->started_at)); memset(&ctx->connected_at, 0, sizeof(ctx->connected_at)); } @@ -884,13 +1006,34 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) cf->connected = FALSE; } +static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + if(cf->connected) { + struct cf_socket_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "cf_socket_shutdown, fd=%" FMT_SOCKET_T, ctx->sock); + /* On TCP, and when the socket looks well and non-blocking mode + * can be enabled, receive dangling bytes before close to avoid + * entering RST states unnecessarily. */ + if(ctx->sock != CURL_SOCKET_BAD && + ctx->transport == TRNSPRT_TCP && + (curlx_nonblock(ctx->sock, TRUE) >= 0)) { + unsigned char buf[1024]; + (void)sread(ctx->sock, buf, sizeof(buf)); + } + } + *done = TRUE; + return CURLE_OK; +} + static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_socket_ctx *ctx = cf->ctx; cf_socket_close(cf, data); - DEBUGF(LOG_CF(data, cf, "destroy")); - Curl_bufq_free(&ctx->recvbuf); + CURL_TRC_CF(data, cf, "destroy"); free(ctx); cf->ctx = NULL; } @@ -901,27 +1044,32 @@ static CURLcode set_local_ip(struct Curl_cfilter *cf, struct cf_socket_ctx *ctx = cf->ctx; #ifdef HAVE_GETSOCKNAME - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssloc; - curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage); + if((ctx->sock != CURL_SOCKET_BAD) && + !(data->conn->handler->protocol & CURLPROTO_TFTP)) { + /* TFTP does not connect, so it cannot get the IP like this */ - memset(&ssloc, 0, sizeof(ssloc)); - if(getsockname(ctx->sock, (struct sockaddr*) &ssloc, &slen)) { - int error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return CURLE_FAILED_INIT; - } - if(!Curl_addr2string((struct sockaddr*)&ssloc, slen, - ctx->l_ip, &ctx->l_port)) { - failf(data, "ssloc inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return CURLE_FAILED_INIT; + char buffer[STRERROR_LEN]; + struct Curl_sockaddr_storage ssloc; + curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage); + + memset(&ssloc, 0, sizeof(ssloc)); + if(getsockname(ctx->sock, (struct sockaddr*) &ssloc, &slen)) { + int error = SOCKERRNO; + failf(data, "getsockname() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } + if(!Curl_addr2string((struct sockaddr*)&ssloc, slen, + ctx->ip.local_ip, &ctx->ip.local_port)) { + failf(data, "ssloc inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } } #else (void)data; - ctx->l_ip[0] = 0; - ctx->l_port = -1; + ctx->ip.local_ip[0] = 0; + ctx->ip.local_port = -1; #endif return CURLE_OK; } @@ -932,13 +1080,14 @@ static CURLcode set_remote_ip(struct Curl_cfilter *cf, struct cf_socket_ctx *ctx = cf->ctx; /* store remote address and port used in this connection attempt */ - if(!Curl_addr2string(&ctx->addr.sa_addr, ctx->addr.addrlen, - ctx->r_ip, &ctx->r_port)) { + if(!Curl_addr2string(&ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen, + ctx->ip.remote_ip, &ctx->ip.remote_port)) { char buffer[STRERROR_LEN]; ctx->error = errno; /* malformed address or bug in inet_ntop, try next address */ - failf(data, "sa_addr inet_ntop() failed with errno %d: %s", + failf(data, "curl_sa_addr inet_ntop() failed with errno %d: %s", errno, Curl_strerror(errno, buffer, sizeof(buffer))); return CURLE_FAILED_INIT; } @@ -953,12 +1102,24 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, bool isconnected = FALSE; CURLcode result = CURLE_COULDNT_CONNECT; bool is_tcp; - const char *ipmsg; (void)data; DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD); - ctx->started_at = Curl_now(); + ctx->started_at = curlx_now(); +#ifdef SOCK_NONBLOCK + /* Do not tuck SOCK_NONBLOCK into socktype when opensocket callback is set + * because we would not know how socketype is about to be used in the + * callback, SOCK_NONBLOCK might get factored out before calling socket(). + */ + if(!data->set.fopensocket) + ctx->addr.socktype |= SOCK_NONBLOCK; +#endif result = socket_open(data, &ctx->addr, &ctx->sock); +#ifdef SOCK_NONBLOCK + /* Restore the socktype after the socket is created. */ + if(!data->set.fopensocket) + ctx->addr.socktype &= ~SOCK_NONBLOCK; +#endif if(result) goto out; @@ -966,17 +1127,16 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, if(result) goto out; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(ctx->addr.family == AF_INET6) { set_ipv6_v6only(ctx->sock, 0); - ipmsg = " Trying [%s]:%d..."; + infof(data, " Trying [%s]:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); } else #endif - ipmsg = " Trying %s:%d..."; - infof(data, ipmsg, ctx->r_ip, ctx->r_port); + infof(data, " Trying %s:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 is_tcp = (ctx->addr.family == AF_INET || ctx->addr.family == AF_INET6) && ctx->addr.socktype == SOCK_STREAM; @@ -989,18 +1149,18 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, nosigpipe(data, ctx->sock); - Curl_sndbufset(ctx->sock); + Curl_sndbuf_init(ctx->sock); if(is_tcp && data->set.tcp_keepalive) tcpkeepalive(data, ctx->sock); if(data->set.fsockopt) { /* activate callback for setting socket options */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); error = data->set.fsockopt(data->set.sockopt_client, ctx->sock, CURLSOCKTYPE_IPCXN); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(error == CURL_SOCKOPT_ALREADY_CONNECTED) isconnected = TRUE; @@ -1010,14 +1170,15 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, } } +#ifndef CURL_DISABLE_BINDLOCAL /* possibly bind the local end to an IP, interface or port */ if(ctx->addr.family == AF_INET -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 || ctx->addr.family == AF_INET6 #endif ) { result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family, - Curl_ipv6_scope(&ctx->addr.sa_addr)); + Curl_ipv6_scope(&ctx->addr.curl_sa_addr)); if(result) { if(result == CURLE_UNSUPPORTED_PROTOCOL) { /* The address family is not supported on this interface. @@ -1027,10 +1188,30 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, goto out; } } +#endif - /* set socket non-blocking */ - (void)curlx_nonblock(ctx->sock, TRUE); - +#ifndef SOCK_NONBLOCK + /* Set socket non-blocking, must be a non-blocking socket for + * a non-blocking connect. */ + error = curlx_nonblock(ctx->sock, TRUE); + if(error < 0) { + result = CURLE_UNSUPPORTED_PROTOCOL; + ctx->error = SOCKERRNO; + goto out; + } +#else + if(data->set.fopensocket) { + /* Set socket non-blocking, must be a non-blocking socket for + * a non-blocking connect. */ + error = curlx_nonblock(ctx->sock, TRUE); + if(error < 0) { + result = CURLE_UNSUPPORTED_PROTOCOL; + ctx->error = SOCKERRNO; + goto out; + } + } +#endif + ctx->sock_connected = (ctx->addr.socktype != SOCK_DGRAM); out: if(result) { if(ctx->sock != CURL_SOCKET_BAD) { @@ -1040,11 +1221,11 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, } else if(isconnected) { set_local_ip(cf, data); - ctx->connected_at = Curl_now(); + ctx->connected_at = curlx_now(); cf->connected = TRUE; } - DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%" CURL_FORMAT_SOCKET_T, - result, ctx->sock)); + CURL_TRC_CF(data, cf, "cf_socket_open() -> %d, fd=%" FMT_SOCKET_T, + result, ctx->sock); return result; } @@ -1069,7 +1250,7 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, endpoints.sae_srcif = 0; endpoints.sae_srcaddr = NULL; endpoints.sae_srcaddrlen = 0; - endpoints.sae_dstaddr = &ctx->addr.sa_addr; + endpoints.sae_dstaddr = &ctx->addr.curl_sa_addr; endpoints.sae_dstaddrlen = ctx->addr.addrlen; rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY, @@ -1077,34 +1258,35 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, NULL, 0, NULL, NULL); } else { - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); } # else - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); # endif /* HAVE_BUILTIN_AVAILABLE */ #elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */ if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, (void *)&optval, sizeof(optval)) < 0) - infof(data, "Failed to enable TCP Fast Open on fd %" - CURL_FORMAT_SOCKET_T, ctx->sock); + infof(data, "Failed to enable TCP Fast Open on fd %" FMT_SOCKET_T, + ctx->sock); - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); #elif defined(MSG_FASTOPEN) /* old Linux */ - if(cf->conn->given->flags & PROTOPT_SSL) - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); else rc = 0; /* Do nothing */ #endif } else { - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen); } return rc; } static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_socket_ctx *ctx = cf->ctx; CURLcode result = CURLE_COULDNT_CONNECT; @@ -1116,12 +1298,9 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, return CURLE_OK; } - /* TODO: need to support blocking connect? */ - if(blocking) - return CURLE_UNSUPPORTED_PROTOCOL; - - *done = FALSE; /* a very negative world view is best */ + *done = FALSE; /* a negative world view is best */ if(ctx->sock == CURL_SOCKET_BAD) { + int error; result = cf_socket_open(cf, data); if(result) @@ -1134,8 +1313,12 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, /* Connect TCP socket */ rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen); + error = SOCKERRNO; + set_local_ip(cf, data); + CURL_TRC_CF(data, cf, "local address %s port %d...", + ctx->ip.local_ip, ctx->ip.local_port); if(-1 == rc) { - result = socket_connect_result(data, ctx->r_ip, SOCKERRNO); + result = socket_connect_result(data, ctx->ip.remote_ip, error); goto out; } } @@ -1151,17 +1334,17 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, rc = SOCKET_WRITABLE(ctx->sock, 0); if(rc == 0) { /* no connection yet */ - DEBUGF(LOG_CF(data, cf, "not connected yet")); + CURL_TRC_CF(data, cf, "not connected yet"); return CURLE_OK; } else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) { if(verifyconnect(ctx->sock, &ctx->error)) { /* we are connected with TCP, awesome! */ - ctx->connected_at = Curl_now(); + ctx->connected_at = curlx_now(); set_local_ip(cf, data); *done = TRUE; cf->connected = TRUE; - DEBUGF(LOG_CF(data, cf, "connected")); + CURL_TRC_CF(data, cf, "connected"); return CURLE_OK; } } @@ -1173,13 +1356,15 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, out: if(result) { if(ctx->error) { + set_local_ip(cf, data); data->state.os_errno = ctx->error; SET_SOCKERRNO(ctx->error); #ifndef CURL_DISABLE_VERBOSE_STRINGS { char buffer[STRERROR_LEN]; - infof(data, "connect to %s port %u failed: %s", - ctx->r_ip, ctx->r_port, + infof(data, "connect to %s port %u from %s port %d failed: %s", + ctx->ip.remote_ip, ctx->ip.remote_port, + ctx->ip.local_ip, ctx->ip.local_port, Curl_strerror(ctx->error, buffer, sizeof(buffer))); } #endif @@ -1199,26 +1384,40 @@ static void cf_socket_get_host(struct Curl_cfilter *cf, const char **pdisplay_host, int *pport) { + struct cf_socket_ctx *ctx = cf->ctx; (void)data; *phost = cf->conn->host.name; *pdisplay_host = cf->conn->host.dispname; - *pport = cf->conn->port; + *pport = ctx->ip.remote_port; } -static int cf_socket_get_select_socks(struct Curl_cfilter *cf, +static void cf_socket_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { struct cf_socket_ctx *ctx = cf->ctx; - int rc = GETSOCK_BLANK; - (void)data; - if(!cf->connected && ctx->sock != CURL_SOCKET_BAD) { - socks[0] = ctx->sock; - rc |= GETSOCK_WRITESOCK(0); + if(ctx->sock != CURL_SOCKET_BAD) { + /* A listening socket filter needs to be connected before the accept + * for some weird FTP interaction. This should be rewritten, so that + * FTP no longer does the socket checks and accept calls and delegates + * all that to the filter. */ + if(ctx->listening) { + Curl_pollset_set_in_only(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, listening, POLLIN fd=%" + FMT_SOCKET_T, ctx->sock); + } + else if(!cf->connected) { + Curl_pollset_set_out_only(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, !connected, POLLOUT fd=%" + FMT_SOCKET_T, ctx->sock); + } + else if(!ctx->active) { + Curl_pollset_add_in(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, !active, POLLIN fd=%" + FMT_SOCKET_T, ctx->sock); + } } - - return rc; } static bool cf_socket_data_pending(struct Curl_cfilter *cf, @@ -1228,28 +1427,76 @@ static bool cf_socket_data_pending(struct Curl_cfilter *cf, int readable; (void)data; - if(!Curl_bufq_is_empty(&ctx->recvbuf)) - return TRUE; - readable = SOCKET_READABLE(ctx->sock, 0); - return (readable > 0 && (readable & CURL_CSELECT_IN)); + return readable > 0 && (readable & CURL_CSELECT_IN); } +#ifdef USE_WINSOCK + +#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY +#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B +#endif + +static void win_update_sndbuf_size(struct cf_socket_ctx *ctx) +{ + ULONG ideal; + DWORD ideallen; + struct curltime n = curlx_now(); + + if(curlx_timediff(n, ctx->last_sndbuf_query_at) > 1000) { + if(!WSAIoctl(ctx->sock, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0, + &ideal, sizeof(ideal), &ideallen, 0, 0) && + ideal != ctx->sndbuf_size && + !setsockopt(ctx->sock, SOL_SOCKET, SO_SNDBUF, + (const char *)&ideal, sizeof(ideal))) { + ctx->sndbuf_size = ideal; + } + ctx->last_sndbuf_query_at = n; + } +} + +#endif /* USE_WINSOCK */ + static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_socket_ctx *ctx = cf->ctx; curl_socket_t fdsave; ssize_t nwritten; + size_t orig_len = len; + (void)eos; /* unused */ *err = CURLE_OK; fdsave = cf->conn->sock[cf->sockindex]; cf->conn->sock[cf->sockindex] = ctx->sock; +#ifdef DEBUGBUILD + /* simulate network blocking/partial writes */ + if(ctx->wblock_percent > 0) { + unsigned char c = 0; + Curl_rand_bytes(data, FALSE, &c, 1); + if(c >= ((100-ctx->wblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE EWOULDBLOCK", orig_len); + *err = CURLE_AGAIN; + nwritten = -1; + cf->conn->sock[cf->sockindex] = fdsave; + return nwritten; + } + } + if(cf->cft != &Curl_cft_udp && ctx->wpartial_percent > 0 && len > 8) { + len = len * ctx->wpartial_percent / 100; + if(!len) + len = 1; + CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE partial write of %zu bytes", + orig_len, len); + } +#endif + #if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */ if(cf->conn->bits.tcp_fastopen) { nwritten = sendto(ctx->sock, buf, len, MSG_FASTOPEN, - &cf->conn->remote_addr->sa_addr, + &cf->conn->remote_addr->curl_sa_addr, cf->conn->remote_addr->addrlen); cf->conn->bits.tcp_fastopen = FALSE; } @@ -1261,15 +1508,16 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, int sockerr = SOCKERRNO; if( -#ifdef WSAEWOULDBLOCK +#ifdef USE_WINSOCK /* This is how Windows does it */ - (WSAEWOULDBLOCK == sockerr) + (SOCKEWOULDBLOCK == sockerr) #else /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned due to its inability to send off data without blocking. We therefore treat both error codes the same here */ - (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) || - (EINPROGRESS == sockerr) + (SOCKEWOULDBLOCK == sockerr) || + (EAGAIN == sockerr) || (SOCKEINTR == sockerr) || + (SOCKEINPROGRESS == sockerr) #endif ) { /* this is just a case of EWOULDBLOCK */ @@ -1284,8 +1532,13 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, } } - DEBUGF(LOG_CF(data, cf, "send(len=%zu) -> %d, err=%d", - len, (int)nwritten, *err)); +#if defined(USE_WINSOCK) + if(!*err) + win_update_sndbuf_size(ctx); +#endif + + CURL_TRC_CF(data, cf, "send(len=%zu) -> %d, err=%d", + orig_len, (int)nwritten, *err); cf->conn->sock[cf->sockindex] = fdsave; return nwritten; } @@ -1294,93 +1547,79 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_socket_ctx *ctx = cf->ctx; - curl_socket_t fdsave; ssize_t nread; *err = CURLE_OK; - fdsave = cf->conn->sock[cf->sockindex]; - cf->conn->sock[cf->sockindex] = ctx->sock; - - if(ctx->buffer_recv && !Curl_bufq_is_empty(&ctx->recvbuf)) { - DEBUGF(LOG_CF(data, cf, "recv from buffer")); - nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); +#ifdef DEBUGBUILD + /* simulate network blocking/partial reads */ + if(cf->cft != &Curl_cft_udp && ctx->rblock_percent > 0) { + unsigned char c = 0; + Curl_rand(data, &c, 1); + if(c >= ((100-ctx->rblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE EWOULDBLOCK", len); + *err = CURLE_AGAIN; + return -1; + } } - else { - struct reader_ctx rctx; - - rctx.cf = cf; - rctx.data = data; - - /* "small" reads may trigger filling our buffer, "large" reads - * are probably not worth the additional copy */ - if(ctx->buffer_recv && len < NW_SMALL_READS) { - ssize_t nwritten; - nwritten = Curl_bufq_slurp(&ctx->recvbuf, nw_in_read, &rctx, err); - if(nwritten < 0 && !Curl_bufq_is_empty(&ctx->recvbuf)) { - /* we have a partial read with an error. need to deliver - * what we got, return the error later. */ - DEBUGF(LOG_CF(data, cf, "partial read: empty buffer first")); - nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); - } - else if(nwritten < 0) { - nread = -1; - goto out; - } - else if(nwritten == 0) { - /* eof */ - *err = CURLE_OK; - nread = 0; - } - else { - DEBUGF(LOG_CF(data, cf, "buffered %zd additional bytes", nwritten)); - nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); - } + if(cf->cft != &Curl_cft_udp && ctx->recv_max && ctx->recv_max < len) { + size_t orig_len = len; + len = ctx->recv_max; + CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE max read of %zu bytes", + orig_len, len); + } +#endif + + *err = CURLE_OK; + nread = sread(ctx->sock, buf, len); + + if(-1 == nread) { + int sockerr = SOCKERRNO; + + if( +#ifdef USE_WINSOCK + /* This is how Windows does it */ + (SOCKEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefore + treat both error codes the same here */ + (SOCKEWOULDBLOCK == sockerr) || + (EAGAIN == sockerr) || (SOCKEINTR == sockerr) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + *err = CURLE_AGAIN; } else { - nread = nw_in_read(&rctx, (unsigned char *)buf, len, err); + char buffer[STRERROR_LEN]; + + failf(data, "Recv failure: %s", + Curl_strerror(sockerr, buffer, sizeof(buffer))); + data->state.os_errno = sockerr; + *err = CURLE_RECV_ERROR; } } -out: - DEBUGF(LOG_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread, - *err)); + CURL_TRC_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread, + *err); if(nread > 0 && !ctx->got_first_byte) { - ctx->first_byte_at = Curl_now(); + ctx->first_byte_at = curlx_now(); ctx->got_first_byte = TRUE; } - cf->conn->sock[cf->sockindex] = fdsave; return nread; } -static void conn_set_primary_ip(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void cf_socket_update_data(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct cf_socket_ctx *ctx = cf->ctx; -#ifdef HAVE_GETPEERNAME - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssrem; - curl_socklen_t plen; - int port; - - plen = sizeof(ssrem); - memset(&ssrem, 0, plen); - if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { - int error = SOCKERRNO; - failf(data, "getpeername() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return; - } - if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, - cf->conn->primary_ip, &port)) { - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return; + /* Update the IP info held in the transfer, if we have that. */ + if(cf->connected && (cf->sockindex == FIRSTSOCKET)) { + struct cf_socket_ctx *ctx = cf->ctx; + data->info.primary = ctx->ip; + /* not sure if this is redundant... */ + data->info.conn_remote_port = cf->conn->remote_port; } -#else - cf->conn->primary_ip[0] = 0; - (void)data; -#endif } static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -1389,20 +1628,16 @@ static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) /* use this socket from now on */ cf->conn->sock[cf->sockindex] = ctx->sock; - /* the first socket info gets set at conn and data */ + set_local_ip(cf, data); if(cf->sockindex == FIRSTSOCKET) { + cf->conn->primary = ctx->ip; cf->conn->remote_addr = &ctx->addr; - #ifdef ENABLE_IPV6 - cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; + #ifdef USE_IPV6 + cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6); #endif - conn_set_primary_ip(cf, data); - set_local_ip(cf, data); - Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); - /* buffering is currently disabled by default because we have stalls - * in parallel transfers where not all buffered data is consumed and no - * socket events happen. - */ - ctx->buffer_recv = FALSE; + } + else { + cf->conn->secondary = ctx->ip; } ctx->active = TRUE; } @@ -1418,9 +1653,13 @@ static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf, switch(event) { case CF_CTRL_CONN_INFO_UPDATE: cf_socket_active(cf, data); + cf_socket_update_data(cf, data); break; case CF_CTRL_DATA_SETUP: - Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + cf_socket_update_data(cf, data); + break; + case CF_CTRL_FORGET_SOCKET: + ctx->sock = CURL_SOCKET_BAD; break; } return CURLE_OK; @@ -1446,19 +1685,19 @@ static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf, r = Curl_poll(pfd, 1, 0); if(r < 0) { - DEBUGF(LOG_CF(data, cf, "is_alive: poll error, assume dead")); + CURL_TRC_CF(data, cf, "is_alive: poll error, assume dead"); return FALSE; } else if(r == 0) { - DEBUGF(LOG_CF(data, cf, "is_alive: poll timeout, assume alive")); + CURL_TRC_CF(data, cf, "is_alive: poll timeout, assume alive"); return TRUE; } else if(pfd[0].revents & (POLLERR|POLLHUP|POLLPRI|POLLNVAL)) { - DEBUGF(LOG_CF(data, cf, "is_alive: err/hup/etc events, assume dead")); + CURL_TRC_CF(data, cf, "is_alive: err/hup/etc events, assume dead"); return FALSE; } - DEBUGF(LOG_CF(data, cf, "is_alive: valid events, looks alive")); + CURL_TRC_CF(data, cf, "is_alive: valid events, looks alive"); *input_pending = TRUE; return TRUE; } @@ -1476,8 +1715,8 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, return CURLE_OK; case CF_QUERY_CONNECT_REPLY_MS: if(ctx->got_first_byte) { - timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); - *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + timediff_t ms = curlx_timediff(ctx->first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else *pres1 = -1; @@ -1493,17 +1732,25 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, *when = ctx->first_byte_at; break; } - /* FALLTHROUGH */ + FALLTHROUGH(); default: *when = ctx->connected_at; break; } return CURLE_OK; } + case CF_QUERY_IP_INFO: +#ifdef USE_IPV6 + *pres1 = (ctx->addr.family == AF_INET6); +#else + *pres1 = FALSE; +#endif + *(struct ip_quadruple *)pres2 = ctx->ip; + return CURLE_OK; default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1511,12 +1758,13 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_tcp = { "TCP", CF_TYPE_IP_CONNECT, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, cf_socket_destroy, cf_tcp_connect, cf_socket_close, + cf_socket_shutdown, cf_socket_get_host, - cf_socket_get_select_socks, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1539,17 +1787,20 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, (void)data; (void)conn; DEBUGASSERT(transport == TRNSPRT_TCP); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - cf_socket_ctx_init(ctx, ai, transport); + + result = cf_socket_ctx_init(ctx, ai, transport); + if(result) + goto out; result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); Curl_safefree(ctx); @@ -1559,27 +1810,40 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, } static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data) { struct cf_socket_ctx *ctx = cf->ctx; int rc; + int one = 1; + + (void)one; /* QUIC needs a connected socket, nonblocking */ DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD); - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + /* error: The 1st argument to 'connect' is -1 but should be >= 0 + NOLINTNEXTLINE(clang-analyzer-unix.StdCLibraryFunctions) */ + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen); if(-1 == rc) { - return socket_connect_result(data, ctx->r_ip, SOCKERRNO); + return socket_connect_result(data, ctx->ip.remote_ip, SOCKERRNO); } + ctx->sock_connected = TRUE; set_local_ip(cf, data); - DEBUGF(LOG_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T - " connected: [%s:%d] -> [%s:%d]", - (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP", - ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port)); - - (void)curlx_nonblock(ctx->sock, TRUE); + CURL_TRC_CF(data, cf, "%s socket %" FMT_SOCKET_T + " connected: [%s:%d] -> [%s:%d]", + (ctx->transport == TRNSPRT_QUIC) ? "QUIC" : "UDP", + ctx->sock, ctx->ip.local_ip, ctx->ip.local_port, + ctx->ip.remote_ip, ctx->ip.remote_port); + + /* Currently, cf->ctx->sock is always non-blocking because the only + * caller to cf_udp_setup_quic() is cf_udp_connect() that passes the + * non-blocking socket created by cf_socket_open() to it. Thus, we + * do not need to call curlx_nonblock() in cf_udp_setup_quic() anymore. + */ +#ifdef __linux__ switch(ctx->addr.family) { -#if defined(__linux__) && defined(IP_MTU_DISCOVER) +#ifdef IP_MTU_DISCOVER case AF_INET: { int val = IP_PMTUDISC_DO; (void)setsockopt(ctx->sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, @@ -1587,7 +1851,7 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, break; } #endif -#if defined(__linux__) && defined(IPV6_MTU_DISCOVER) +#ifdef IPV6_MTU_DISCOVER case AF_INET6: { int val = IPV6_PMTUDISC_DO; (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, @@ -1596,17 +1860,25 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, } #endif } + +#if defined(UDP_GRO) && \ + (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \ + ((defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_QUICHE)) + (void)setsockopt(ctx->sock, IPPROTO_UDP, UDP_GRO, &one, + (socklen_t)sizeof(one)); +#endif +#endif + return CURLE_OK; } static CURLcode cf_udp_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_socket_ctx *ctx = cf->ctx; CURLcode result = CURLE_COULDNT_CONNECT; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; @@ -1615,7 +1887,7 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf, if(ctx->sock == CURL_SOCKET_BAD) { result = cf_socket_open(cf, data); if(result) { - DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), open failed -> %d", result)); + CURL_TRC_CF(data, cf, "cf_udp_connect(), open failed -> %d", result); goto out; } @@ -1623,13 +1895,13 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf, result = cf_udp_setup_quic(cf, data); if(result) goto out; - DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%" - CURL_FORMAT_SOCKET_T " (%s:%d)", - ctx->sock, ctx->l_ip, ctx->l_port)); + CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%" + FMT_SOCKET_T " (%s:%d)", + ctx->sock, ctx->ip.local_ip, ctx->ip.local_port); } else { - DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%" - CURL_FORMAT_SOCKET_T " (unconnected)", ctx->sock)); + CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%" + FMT_SOCKET_T " (unconnected)", ctx->sock); } *done = TRUE; cf->connected = TRUE; @@ -1641,12 +1913,13 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_udp = { "UDP", CF_TYPE_IP_CONNECT, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, cf_socket_destroy, cf_udp_connect, cf_socket_close, + cf_socket_shutdown, cf_socket_get_host, - cf_socket_get_select_socks, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1669,17 +1942,20 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, (void)data; (void)conn; DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - cf_socket_ctx_init(ctx, ai, transport); + + result = cf_socket_ctx_init(ctx, ai, transport); + if(result) + goto out; result = Curl_cf_create(&cf, &Curl_cft_udp, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); Curl_safefree(ctx); @@ -1692,12 +1968,13 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, struct Curl_cftype Curl_cft_unix = { "UNIX", CF_TYPE_IP_CONNECT, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, cf_socket_destroy, cf_tcp_connect, cf_socket_close, + cf_socket_shutdown, cf_socket_get_host, - cf_socket_get_select_socks, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1720,17 +1997,20 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, (void)data; (void)conn; DEBUGASSERT(transport == TRNSPRT_UNIX); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - cf_socket_ctx_init(ctx, ai, transport); + + result = cf_socket_ctx_init(ctx, ai, transport); + if(result) + goto out; result = Curl_cf_create(&cf, &Curl_cft_unix, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); Curl_safefree(ctx); @@ -1739,29 +2019,183 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, return result; } +static timediff_t cf_tcp_accept_timeleft(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT; + timediff_t other; + struct curltime now; + +#ifndef CURL_DISABLE_FTP + if(data->set.accepttimeout > 0) + timeout_ms = data->set.accepttimeout; +#endif + + now = curlx_now(); + /* check if the generic timeout possibly is set shorter */ + other = Curl_timeleft(data, &now, FALSE); + if(other && (other < timeout_ms)) + /* note that this also works fine for when other happens to be negative + due to it already having elapsed */ + timeout_ms = other; + else { + /* subtract elapsed time */ + timeout_ms -= curlx_timediff(now, ctx->started_at); + if(!timeout_ms) + /* avoid returning 0 as that means no timeout! */ + timeout_ms = -1; + } + return timeout_ms; +} + +static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef HAVE_GETPEERNAME + char buffer[STRERROR_LEN]; + struct Curl_sockaddr_storage ssrem; + curl_socklen_t plen; + + ctx->ip.remote_ip[0] = 0; + ctx->ip.remote_port = 0; + plen = sizeof(ssrem); + memset(&ssrem, 0, plen); + if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { + int error = SOCKERRNO; + failf(data, "getpeername() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return; + } + if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, + ctx->ip.remote_ip, &ctx->ip.remote_port)) { + failf(data, "ssrem inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return; + } +#else + ctx->ip.remote_ip[0] = 0; + ctx->ip.remote_port = 0; + (void)data; +#endif +} + static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef USE_IPV6 + struct Curl_sockaddr_storage add; +#else + struct sockaddr_in add; +#endif + curl_socklen_t size = (curl_socklen_t) sizeof(add); + curl_socket_t s_accepted = CURL_SOCKET_BAD; + timediff_t timeout_ms; + int socketstate = 0; + bool incoming = FALSE; + /* we start accepted, if we ever close, we cannot go on */ (void)data; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; } - return CURLE_FAILED_INIT; + + timeout_ms = cf_tcp_accept_timeleft(cf, data); + if(timeout_ms < 0) { + /* if a timeout was already reached, bail out */ + failf(data, "Accept timeout occurred while waiting server connect"); + return CURLE_FTP_ACCEPT_TIMEOUT; + } + + CURL_TRC_CF(data, cf, "Checking for incoming on fd=%" FMT_SOCKET_T + " ip=%s:%d", ctx->sock, ctx->ip.local_ip, ctx->ip.local_port); + socketstate = Curl_socket_check(ctx->sock, CURL_SOCKET_BAD, + CURL_SOCKET_BAD, 0); + CURL_TRC_CF(data, cf, "socket_check -> %x", socketstate); + switch(socketstate) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + default: + if(socketstate & CURL_CSELECT_IN) { + infof(data, "Ready to accept data connection from server"); + incoming = TRUE; + } + break; + } + + if(!incoming) { + CURL_TRC_CF(data, cf, "nothing heard from the server yet"); + *done = FALSE; + return CURLE_OK; + } + + if(0 == getsockname(ctx->sock, (struct sockaddr *) &add, &size)) { + size = sizeof(add); +#ifdef HAVE_ACCEPT4 + s_accepted = accept4(ctx->sock, (struct sockaddr *) &add, &size, + SOCK_NONBLOCK | SOCK_CLOEXEC); +#else + s_accepted = accept(ctx->sock, (struct sockaddr *) &add, &size); +#endif + } + + if(CURL_SOCKET_BAD == s_accepted) { + failf(data, "Error accept()ing server connect"); + return CURLE_FTP_PORT_FAILED; + } + + infof(data, "Connection accepted from server"); +#ifndef HAVE_ACCEPT4 + (void)curlx_nonblock(s_accepted, TRUE); /* enable non-blocking */ +#endif + /* Replace any filter on SECONDARY with one listening on this socket */ + ctx->listening = FALSE; + ctx->accepted = TRUE; + socket_close(data, cf->conn, TRUE, ctx->sock); + ctx->sock = s_accepted; + + cf->conn->sock[cf->sockindex] = ctx->sock; + cf_tcp_set_accepted_remote_ip(cf, data); + set_local_ip(cf, data); + ctx->active = TRUE; + ctx->connected_at = curlx_now(); + cf->connected = TRUE; + CURL_TRC_CF(data, cf, "accepted_set(sock=%" FMT_SOCKET_T + ", remote=%s port=%d)", + ctx->sock, ctx->ip.remote_ip, ctx->ip.remote_port); + + if(data->set.fsockopt) { + int error = 0; + + /* activate callback for setting socket options */ + Curl_set_in_callback(data, true); + error = data->set.fsockopt(data->set.sockopt_client, + ctx->sock, CURLSOCKTYPE_ACCEPT); + Curl_set_in_callback(data, false); + + if(error) + return CURLE_ABORTED_BY_CALLBACK; + } + *done = TRUE; + return CURLE_OK; } struct Curl_cftype Curl_cft_tcp_accept = { "TCP-ACCEPT", CF_TYPE_IP_CONNECT, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, cf_socket_destroy, cf_tcp_accept_connect, cf_socket_close, - cf_socket_get_host, /* TODO: not accurate */ - cf_socket_get_select_socks, + cf_socket_shutdown, + cf_socket_get_host, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1783,26 +2217,26 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, Curl_conn_cf_discard_all(data, conn, sockindex); DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } ctx->transport = conn->transport; ctx->sock = *s; + ctx->listening = TRUE; ctx->accepted = FALSE; result = Curl_cf_create(&cf, &Curl_cft_tcp_accept, ctx); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); + ctx->started_at = curlx_now(); conn->sock[sockindex] = ctx->sock; set_local_ip(cf, data); - ctx->active = TRUE; - ctx->connected_at = Curl_now(); - cf->connected = TRUE; - DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_listen_set(%" - CURL_FORMAT_SOCKET_T ")", ctx->sock)); + CURL_TRC_CF(data, cf, "set filter for listen socket fd=%" FMT_SOCKET_T + " ip=%s:%d", ctx->sock, + ctx->ip.local_ip, ctx->ip.local_port); out: if(result) { @@ -1812,65 +2246,16 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, return result; } -static void set_accepted_remote_ip(struct Curl_cfilter *cf, - struct Curl_easy *data) +bool Curl_conn_is_tcp_listen(struct Curl_easy *data, + int sockindex) { - struct cf_socket_ctx *ctx = cf->ctx; -#ifdef HAVE_GETPEERNAME - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssrem; - curl_socklen_t plen; - - ctx->r_ip[0] = 0; - ctx->r_port = 0; - plen = sizeof(ssrem); - memset(&ssrem, 0, plen); - if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { - int error = SOCKERRNO; - failf(data, "getpeername() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return; + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + while(cf) { + if(cf->cft == &Curl_cft_tcp_accept) + return TRUE; + cf = cf->next; } - if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, - ctx->r_ip, &ctx->r_port)) { - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return; - } -#else - ctx->r_ip[0] = 0; - ctx->r_port = 0; - (void)data; -#endif -} - -CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, curl_socket_t *s) -{ - struct Curl_cfilter *cf = NULL; - struct cf_socket_ctx *ctx = NULL; - - cf = conn->cfilter[sockindex]; - if(!cf || cf->cft != &Curl_cft_tcp_accept) - return CURLE_FAILED_INIT; - - ctx = cf->ctx; - /* discard the listen socket */ - socket_close(data, conn, TRUE, ctx->sock); - ctx->sock = *s; - conn->sock[sockindex] = ctx->sock; - set_accepted_remote_ip(cf, data); - set_local_ip(cf, data); - ctx->active = TRUE; - ctx->accepted = TRUE; - ctx->connected_at = Curl_now(); - cf->connected = TRUE; - DEBUGF(LOG_CF(data, cf, "accepted_set(sock=%" CURL_FORMAT_SOCKET_T - ", remote=%s port=%d)", - ctx->sock, ctx->r_ip, ctx->r_port)); - - return CURLE_OK; + return FALSE; } /** @@ -1888,9 +2273,9 @@ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *psock, const struct Curl_sockaddr_ex **paddr, - const char **pr_ip_str, int *pr_port, - const char **pl_ip_str, int *pl_port) + struct ip_quadruple *pip) { + (void)data; if(cf_is_socket(cf) && cf->ctx) { struct cf_socket_ctx *ctx = cf->ctx; @@ -1898,19 +2283,9 @@ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, *psock = ctx->sock; if(paddr) *paddr = &ctx->addr; - if(pr_ip_str) - *pr_ip_str = ctx->r_ip; - if(pr_port) - *pr_port = ctx->r_port; - if(pl_port ||pl_ip_str) { - set_local_ip(cf, data); - if(pl_ip_str) - *pl_ip_str = ctx->l_ip; - if(pl_port) - *pl_port = ctx->l_port; - } + if(pip) + *pip = ctx->ip; return CURLE_OK; } return CURLE_FAILED_INIT; } - diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h index 1d40df737fc..d3e35098421 100644 --- a/Utilities/cmcurl/lib/cf-socket.h +++ b/Utilities/cmcurl/lib/cf-socket.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */ +#include "curlx/nonblock.h" /* for curlx_nonblock() */ #include "sockaddr.h" struct Curl_addrinfo; @@ -33,23 +33,7 @@ struct Curl_cfilter; struct Curl_easy; struct connectdata; struct Curl_sockaddr_ex; - -#ifndef SIZEOF_CURL_SOCKET_T -/* configure and cmake check and set the define */ -# ifdef _WIN64 -# define SIZEOF_CURL_SOCKET_T 8 -# else -/* default guess */ -# define SIZEOF_CURL_SOCKET_T 4 -# endif -#endif - -#if SIZEOF_CURL_SOCKET_T < 8 -# define CURL_FORMAT_SOCKET_T "d" -#else -# define CURL_FORMAT_SOCKET_T "qd" -#endif - +struct ip_quadruple; /* * The Curl_sockaddr_ex structure is basically libcurl's external API @@ -68,8 +52,13 @@ struct Curl_sockaddr_ex { struct Curl_sockaddr_storage buff; } _sa_ex_u; }; -#define sa_addr _sa_ex_u.addr +#define curl_sa_addr _sa_ex_u.addr +/* + * Parse interface option, and return the interface name and the host part. +*/ +CURLcode Curl_parse_interface(const char *input, + char **dev, char **iface, char **host); /* * Create a socket based on info from 'conn' and 'ai'. @@ -97,18 +86,18 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, Buffer Size */ -void Curl_sndbufset(curl_socket_t sockfd); +void Curl_sndbuf_init(curl_socket_t sockfd); #else -#define Curl_sndbufset(y) Curl_nop_stmt +#define Curl_sndbuf_init(y) Curl_nop_stmt #endif /** * Assign the address `ai` to the Curl_sockaddr_ex `dest` and * set the transport used. */ -void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, - const struct Curl_addrinfo *ai, - int transport); +CURLcode Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, + const struct Curl_addrinfo *ai, + int transport); /** * Creates a cfilter that opens a TCP socket to the given address @@ -158,30 +147,25 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, curl_socket_t *s); /** - * Replace the listen socket with the accept()ed one. + * Return TRUE iff the last filter at `sockindex` was set via + * Curl_conn_tcp_listen_set(). */ -CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - curl_socket_t *s); +bool Curl_conn_is_tcp_listen(struct Curl_easy *data, + int sockindex); /** * Peek at the socket and remote ip/port the socket filter is using. * The filter owns all returned values. * @param psock pointer to hold socket descriptor or NULL * @param paddr pointer to hold addr reference or NULL - * @param pr_ip_str pointer to hold remote addr as string or NULL - * @param pr_port pointer to hold remote port number or NULL - * @param pl_ip_str pointer to hold local addr as string or NULL - * @param pl_port pointer to hold local port number or NULL + * @param pip pointer to get IP quadruple or NULL * Returns error if the filter is of invalid type. */ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *psock, const struct Curl_sockaddr_ex **paddr, - const char **pr_ip_str, int *pr_port, - const char **pl_ip_str, int *pl_port); + struct ip_quadruple *pip); extern struct Curl_cftype Curl_cft_tcp; extern struct Curl_cftype Curl_cft_udp; diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c index 291c823f3d2..00090f0c719 100644 --- a/Utilities/cmcurl/lib/cfilters.c +++ b/Utilities/cmcurl/lib/cfilters.c @@ -28,32 +28,42 @@ #include "strerror.h" #include "cfilters.h" #include "connect.h" -#include "url.h" /* for Curl_safefree() */ +#include "url.h" #include "sendf.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "multiif.h" #include "progress.h" -#include "warnless.h" +#include "select.h" +#include "curlx/warnless.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif +static void cf_cntrl_update_info(struct Curl_easy *data, + struct connectdata *conn); -#ifdef DEBUGBUILD +#ifdef UNITTESTS /* used by unit2600.c */ void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data) { cf->connected = FALSE; if(cf->next) - cf->next->cft->close(cf->next, data); + cf->next->cft->do_close(cf->next, data); } #endif +CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + (void)cf; + (void)data; + *done = TRUE; + return CURLE_OK; +} + static void conn_report_connect_stats(struct Curl_easy *data, struct connectdata *conn); @@ -66,37 +76,40 @@ void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, else { *phost = cf->conn->host.name; *pdisplay_host = cf->conn->host.dispname; - *pport = cf->conn->port; + *pport = cf->conn->primary.remote_port; } } -int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf, +void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { - return cf->next? - cf->next->cft->get_select_socks(cf->next, data, socks) : 0; + /* NOP */ + (void)cf; + (void)data; + (void)ps; } bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - return cf->next? + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { - return cf->next? - cf->next->cft->do_send(cf->next, data, buf, len, err) : + return cf->next ? + cf->next->cft->do_send(cf->next, data, buf, len, eos, err) : CURLE_RECV_ERROR; } ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { - return cf->next? + return cf->next ? cf->next->cft->do_recv(cf->next, data, buf, len, err) : CURLE_SEND_ERROR; } @@ -105,7 +118,7 @@ bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { - return cf->next? + return cf->next ? cf->next->cft->is_alive(cf->next, data, input_pending) : FALSE; /* pessimistic in absence of data */ } @@ -113,7 +126,7 @@ bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, struct Curl_easy *data) { - return cf->next? + return cf->next ? cf->next->cft->keep_alive(cf->next, data) : CURLE_OK; } @@ -122,7 +135,7 @@ CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2) { - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -161,44 +174,109 @@ void Curl_conn_close(struct Curl_easy *data, int index) /* it is valid to call that without filters being present */ cf = data->conn->cfilter[index]; if(cf) { - cf->cft->close(cf, data); + cf->cft->do_close(cf, data); } + Curl_shutdown_clear(data, index); } -ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf, - size_t len, CURLcode *code) +CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + struct curltime now; + + DEBUGASSERT(data->conn); + /* 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; + now = curlx_now(); + if(!Curl_shutdown_started(data, sockindex)) { + CURL_TRC_M(data, "shutdown start on%s connection", + sockindex ? " secondary" : ""); + Curl_shutdown_start(data, sockindex, 0, &now); + } + else { + timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, &now); + 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", 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; +} + +ssize_t Curl_cf_recv(struct Curl_easy *data, int num, char *buf, + size_t len, CURLcode *code) { struct Curl_cfilter *cf; DEBUGASSERT(data); DEBUGASSERT(data->conn); + *code = CURLE_OK; cf = data->conn->cfilter[num]; while(cf && !cf->connected) { cf = cf->next; } if(cf) { - return cf->cft->do_recv(cf, data, buf, len, code); + ssize_t nread = cf->cft->do_recv(cf, data, buf, len, code); + DEBUGASSERT(nread >= 0 || *code); + DEBUGASSERT(nread < 0 || !*code); + return nread; } - failf(data, CMSGI(data->conn, num, "recv: no filter connected")); + failf(data, "recv: no filter connected"); *code = CURLE_FAILED_INIT; return -1; } -ssize_t Curl_conn_send(struct Curl_easy *data, int num, - const void *mem, size_t len, CURLcode *code) +ssize_t Curl_cf_send(struct Curl_easy *data, int num, + const void *mem, size_t len, bool eos, + CURLcode *code) { struct Curl_cfilter *cf; DEBUGASSERT(data); DEBUGASSERT(data->conn); + *code = CURLE_OK; cf = data->conn->cfilter[num]; while(cf && !cf->connected) { cf = cf->next; } if(cf) { - return cf->cft->do_send(cf, data, mem, len, code); + ssize_t nwritten = cf->cft->do_send(cf, data, mem, len, eos, code); + DEBUGASSERT(nwritten >= 0 || *code); + DEBUGASSERT(nwritten < 0 || !*code || !len); + return nwritten; } - failf(data, CMSGI(data->conn, num, "send: no filter connected")); + failf(data, "send: no filter connected"); DEBUGASSERT(0); *code = CURLE_FAILED_INIT; return -1; @@ -212,7 +290,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf, CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(cft); - cf = calloc(sizeof(*cf), 1); + cf = calloc(1, sizeof(*cf)); if(!cf) goto out; @@ -238,7 +316,7 @@ void Curl_conn_cf_add(struct Curl_easy *data, cf->conn = conn; cf->sockindex = index; conn->cfilter[index] = cf; - DEBUGF(LOG_CF(data, cf, "added")); + CURL_TRC_CF(data, cf, "added"); } void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at, @@ -290,33 +368,25 @@ bool Curl_conn_cf_discard_sub(struct Curl_cfilter *cf, CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { if(cf) - return cf->cft->connect(cf, data, blocking, done); + return cf->cft->do_connect(cf, data, done); return CURLE_FAILED_INIT; } void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) { if(cf) - cf->cft->close(cf, data); -} - -int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) -{ - if(cf) - return cf->cft->get_select_socks(cf, data, socks); - return 0; + cf->cft->do_close(cf, data); } ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { if(cf) - return cf->cft->do_send(cf, data, buf, len, err); + return cf->cft->do_send(cf, data, buf, len, eos, err); *err = CURLE_SEND_ERROR; return -1; } @@ -335,6 +405,9 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, bool blocking, bool *done) { +#define CF_CONN_NUM_POLLS_ON_STACK 5 + struct pollfd a_few_on_stack[CF_CONN_NUM_POLLS_ON_STACK]; + struct curl_pollfds cpfds; struct Curl_cfilter *cf; CURLcode result = CURLE_OK; @@ -342,26 +415,93 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, DEBUGASSERT(data->conn); cf = data->conn->cfilter[sockindex]; - DEBUGASSERT(cf); - if(!cf) + if(!cf) { + *done = FALSE; return CURLE_FAILED_INIT; + } *done = cf->connected; - if(!*done) { - result = cf->cft->connect(cf, data, blocking, done); + if(*done) + return CURLE_OK; + + Curl_pollfds_init(&cpfds, a_few_on_stack, CF_CONN_NUM_POLLS_ON_STACK); + while(!*done) { + if(Curl_conn_needs_flush(data, sockindex)) { + DEBUGF(infof(data, "Curl_conn_connect(index=%d), flush", sockindex)); + result = Curl_conn_flush(data, sockindex); + if(result && (result != CURLE_AGAIN)) + return result; + } + + result = cf->cft->do_connect(cf, data, done); + CURL_TRC_CF(data, cf, "Curl_conn_connect(block=%d) -> %d, done=%d", + blocking, result, *done); if(!result && *done) { - Curl_conn_ev_update_info(data, data->conn); + /* Now that the complete filter chain is connected, let all filters + * persist information at the connection. E.g. cf-socket sets the + * socket and ip related information. */ + cf_cntrl_update_info(data, data->conn); conn_report_connect_stats(data, data->conn); - data->conn->keepalive = Curl_now(); + data->conn->keepalive = curlx_now(); + Curl_verboseconnect(data, data->conn, sockindex); + goto out; } else if(result) { + CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", + result); conn_report_connect_stats(data, data->conn); + goto out; + } + + if(!blocking) + goto out; + else { + /* check allowed time left */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + struct easy_pollset ps; + int rc; + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "connect timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + + CURL_TRC_CF(data, cf, "Curl_conn_connect(block=1), do poll"); + Curl_pollfds_reset(&cpfds); + memset(&ps, 0, sizeof(ps)); + /* In general, we want to send after connect, wait on that. */ + if(sockfd != CURL_SOCKET_BAD) + Curl_pollset_set_out_only(data, &ps, sockfd); + Curl_conn_adjust_pollset(data, data->conn, &ps); + result = Curl_pollfds_add_ps(&cpfds, &ps); + if(result) + goto out; + + rc = Curl_poll(cpfds.pfds, cpfds.n, + CURLMIN(timeout_ms, (cpfds.n ? 1000 : 10))); + CURL_TRC_CF(data, cf, "Curl_conn_connect(block=1), Curl_poll() -> %d", + rc); + if(rc < 0) { + result = CURLE_COULDNT_CONNECT; + goto out; + } + /* continue iterating */ } } +out: + Curl_pollfds_cleanup(&cpfds); return result; } +bool Curl_conn_is_setup(struct connectdata *conn, int sockindex) +{ + return (conn->cfilter[sockindex] != NULL); +} + bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) { struct Curl_cfilter *cf; @@ -398,23 +538,46 @@ bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf) bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) { - return conn? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE; + return conn ? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE; } bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; for(; cf; cf = cf->next) { if(cf->cft->flags & CF_TYPE_MULTIPLEX) return TRUE; - if(cf->cft->flags & CF_TYPE_IP_CONNECT - || cf->cft->flags & CF_TYPE_SSL) + if(cf->cft->flags & (CF_TYPE_IP_CONNECT|CF_TYPE_SSL)) return FALSE; } return FALSE; } +unsigned char Curl_conn_http_version(struct Curl_easy *data, + struct connectdata *conn) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_UNKNOWN_OPTION; + unsigned char v = 0; + + cf = conn->cfilter[FIRSTSOCKET]; + for(; cf; cf = cf->next) { + if(cf->cft->flags & CF_TYPE_HTTP) { + int value = 0; + result = cf->cft->query(cf, data, CF_QUERY_HTTP_VERSION, &value, NULL); + if(!result && ((value < 0) || (value > 255))) + result = CURLE_FAILED_INIT; + else + v = (unsigned char)value; + break; + } + if(cf->cft->flags & (CF_TYPE_IP_CONNECT|CF_TYPE_SSL)) + break; + } + return (unsigned char)(result ? 0 : v); +} + bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) { struct Curl_cfilter *cf; @@ -433,22 +596,86 @@ bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) return FALSE; } -int Curl_conn_get_select_socks(struct Curl_easy *data, int sockindex, - curl_socket_t *socks) +bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_cfilter *cf; + CURLcode result; + int pending = 0; + result = cf ? cf->cft->query(cf, data, CF_QUERY_NEED_FLUSH, + &pending, NULL) : CURLE_UNKNOWN_OPTION; + return (result || !pending) ? FALSE : TRUE; +} - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - cf = data->conn->cfilter[sockindex]; +bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex) +{ + return Curl_conn_cf_needs_flush(data->conn->cfilter[sockindex], data); +} - /* if the next one is not yet connected, that's the one we want */ - while(cf && cf->next && !cf->next->connected) +void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + /* Get the lowest not-connected filter, if there are any */ + while(cf && !cf->connected && cf->next && !cf->next->connected) cf = cf->next; - if(cf) { - return cf->cft->get_select_socks(cf, data, socks); + /* Skip all filters that have already shut down */ + while(cf && cf->shutdown) + cf = cf->next; + /* From there on, give all filters a chance to adjust the pollset. + * Lower filters are called later, so they may override */ + while(cf) { + cf->cft->adjust_pollset(cf, data, ps); + cf = cf->next; + } +} + +void Curl_conn_adjust_pollset(struct Curl_easy *data, + struct connectdata *conn, + struct easy_pollset *ps) +{ + int i; + + DEBUGASSERT(data); + DEBUGASSERT(conn); + for(i = 0; i < 2; ++i) { + Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); + } +} + +int Curl_conn_cf_poll(struct Curl_cfilter *cf, + struct Curl_easy *data, + timediff_t timeout_ms) +{ + struct easy_pollset ps; + struct pollfd pfds[MAX_SOCKSPEREASYHANDLE]; + unsigned int i, npfds = 0; + + DEBUGASSERT(cf); + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + memset(&ps, 0, sizeof(ps)); + memset(pfds, 0, sizeof(pfds)); + + Curl_conn_cf_adjust_pollset(cf, data, &ps); + DEBUGASSERT(ps.num <= MAX_SOCKSPEREASYHANDLE); + for(i = 0; i < ps.num; ++i) { + short events = 0; + if(ps.actions[i] & CURL_POLL_IN) { + events |= POLLIN; + } + if(ps.actions[i] & CURL_POLL_OUT) { + events |= POLLOUT; + } + if(events) { + pfds[npfds].fd = ps.sockets[i]; + pfds[npfds].events = events; + ++npfds; + } } - return GETSOCK_BLANK; + + if(!npfds) + DEBUGF(infof(data, "no sockets to poll!")); + return Curl_poll(pfds, npfds, timeout_ms); } void Curl_conn_get_host(struct Curl_easy *data, int sockindex, @@ -511,17 +738,38 @@ curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, return CURL_SOCKET_BAD; } +CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, + struct Curl_easy *data, + int *is_ipv6, struct ip_quadruple *ipquad) +{ + if(cf) + return cf->cft->query(cf, data, CF_QUERY_IP_INFO, is_ipv6, ipquad); + return CURLE_UNKNOWN_OPTION; +} + curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex) { struct Curl_cfilter *cf; - cf = data->conn? data->conn->cfilter[sockindex] : NULL; + cf = data->conn ? data->conn->cfilter[sockindex] : NULL; /* if the top filter has not connected, ask it (and its sub-filters) * for the socket. Otherwise conn->sock[sockindex] should have it. */ if(cf && !cf->connected) return Curl_conn_cf_get_socket(cf, data); - return data->conn? data->conn->sock[sockindex] : CURL_SOCKET_BAD; + return data->conn ? data->conn->sock[sockindex] : CURL_SOCKET_BAD; +} + +void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex) +{ + if(data->conn) { + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + if(cf) + (void)Curl_conn_cf_cntrl(cf, data, TRUE, + CF_CTRL_FORGET_SOCKET, 0, NULL); + fake_sclose(data->conn->sock[sockindex]); + data->conn->sock[sockindex] = CURL_SOCKET_BAD; + } } static CURLcode cf_cntrl_all(struct connectdata *conn, @@ -532,7 +780,7 @@ static CURLcode cf_cntrl_all(struct connectdata *conn, CURLcode result = CURLE_OK; size_t i; - for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { + for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) { result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result, event, arg1, arg2); if(!ignore_result && result) @@ -541,18 +789,6 @@ static CURLcode cf_cntrl_all(struct connectdata *conn, return result; } -void Curl_conn_ev_data_attach(struct connectdata *conn, - struct Curl_easy *data) -{ - cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_ATTACH, 0, NULL); -} - -void Curl_conn_ev_data_detach(struct connectdata *conn, - struct Curl_easy *data) -{ - cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_DETACH, 0, NULL); -} - CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data) { return cf_cntrl_all(data->conn, data, FALSE, @@ -565,9 +801,16 @@ CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data) CF_CTRL_DATA_IDLE, 0, NULL); } + +CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex) +{ + return Curl_conn_cf_cntrl(data->conn->cfilter[sockindex], data, FALSE, + CF_CTRL_FLUSH, 0, NULL); +} + /** * Notify connection filters that the transfer represented by `data` - * is donw with sending data (e.g. has uploaded everything). + * is done with sending data (e.g. has uploaded everything). */ void Curl_conn_ev_data_done_send(struct Curl_easy *data) { @@ -589,8 +832,8 @@ CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause) CF_CTRL_DATA_PAUSE, do_pause, NULL); } -void Curl_conn_ev_update_info(struct Curl_easy *data, - struct connectdata *conn) +static void cf_cntrl_update_info(struct Curl_easy *data, + struct connectdata *conn) { cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL); } @@ -631,7 +874,7 @@ CURLcode Curl_conn_keep_alive(struct Curl_easy *data, int sockindex) { struct Curl_cfilter *cf = conn->cfilter[sockindex]; - return cf? cf->cft->keep_alive(cf, data) : CURLE_OK; + return cf ? cf->cft->keep_alive(cf, data) : CURLE_OK; } size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, @@ -642,8 +885,203 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, int n = 0; struct Curl_cfilter *cf = conn->cfilter[sockindex]; - result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT, - &n, NULL) : CURLE_UNKNOWN_OPTION; - return (result || n <= 0)? 1 : (size_t)n; + result = cf ? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n <= 0) ? 1 : (size_t)n; +} + +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + CURLcode result; + int n = 0; + + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + result = cf ? cf->cft->query(cf, data, CF_QUERY_STREAM_ERROR, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n < 0) ? 0 : n; +} + +int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) +{ + if(data && data->conn && + sockfd != CURL_SOCKET_BAD && sockfd == data->conn->sock[SECONDARYSOCKET]) + return SECONDARYSOCKET; + return FIRSTSOCKET; } +CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, + char *buf, size_t blen, ssize_t *n) +{ + CURLcode result = CURLE_OK; + ssize_t nread; + + DEBUGASSERT(data->conn); + nread = data->conn->recv[sockindex](data, sockindex, buf, blen, &result); + DEBUGASSERT(nread >= 0 || result); + DEBUGASSERT(nread < 0 || !result); + *n = (nread >= 0) ? (size_t)nread : 0; + return result; +} + +CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t blen, bool eos, + size_t *pnwritten) +{ + size_t write_len = blen; + ssize_t nwritten; + CURLcode result = CURLE_OK; + struct connectdata *conn; + + DEBUGASSERT(sockindex >= 0 && sockindex < 2); + DEBUGASSERT(pnwritten); + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + conn = data->conn; +#ifdef DEBUGBUILD + if(write_len) { + /* Allow debug builds to override this logic to force short sends + */ + const char *p = getenv("CURL_SMALLSENDS"); + if(p) { + curl_off_t altsize; + if(!curlx_str_number(&p, &altsize, write_len)) + write_len = (size_t)altsize; + } + } +#endif + if(write_len != blen) + eos = FALSE; + nwritten = conn->send[sockindex](data, sockindex, buf, write_len, eos, + &result); + DEBUGASSERT((nwritten >= 0) || result); + *pnwritten = (nwritten < 0) ? 0 : (size_t)nwritten; + return result; +} + +void Curl_pollset_reset(struct Curl_easy *data, + struct easy_pollset *ps) +{ + size_t i; + (void)data; + memset(ps, 0, sizeof(*ps)); + for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) + ps->sockets[i] = CURL_SOCKET_BAD; +} + +/** + * + */ +void Curl_pollset_change(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + int add_flags, int remove_flags) +{ + unsigned int i; + + (void)data; + DEBUGASSERT(VALID_SOCK(sock)); + if(!VALID_SOCK(sock)) + return; + + DEBUGASSERT(add_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); + DEBUGASSERT(remove_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); + DEBUGASSERT((add_flags&remove_flags) == 0); /* no overlap */ + for(i = 0; i < ps->num; ++i) { + if(ps->sockets[i] == sock) { + ps->actions[i] &= (unsigned char)(~remove_flags); + ps->actions[i] |= (unsigned char)add_flags; + /* all gone? remove socket */ + if(!ps->actions[i]) { + if((i + 1) < ps->num) { + memmove(&ps->sockets[i], &ps->sockets[i + 1], + (ps->num - (i + 1)) * sizeof(ps->sockets[0])); + memmove(&ps->actions[i], &ps->actions[i + 1], + (ps->num - (i + 1)) * sizeof(ps->actions[0])); + } + --ps->num; + } + return; + } + } + /* not present */ + if(add_flags) { + /* Having more SOCKETS per easy handle than what is defined + * is a programming error. This indicates that we need + * to raise this limit, making easy_pollset larger. + * Since we use this in tight loops, we do not want to make + * the pollset dynamic unnecessarily. + * The current maximum in practise is HTTP/3 eyeballing where + * we have up to 4 sockets involved in connection setup. + */ + DEBUGASSERT(i < MAX_SOCKSPEREASYHANDLE); + if(i < MAX_SOCKSPEREASYHANDLE) { + ps->sockets[i] = sock; + ps->actions[i] = (unsigned char)add_flags; + ps->num = i + 1; + } + } +} + +void Curl_pollset_set(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool do_in, bool do_out) +{ + Curl_pollset_change(data, ps, sock, + (do_in ? CURL_POLL_IN : 0)| + (do_out ? CURL_POLL_OUT : 0), + (!do_in ? CURL_POLL_IN : 0)| + (!do_out ? CURL_POLL_OUT : 0)); +} + +static void ps_add(struct Curl_easy *data, struct easy_pollset *ps, + int bitmap, curl_socket_t *socks) +{ + if(bitmap) { + int i; + for(i = 0; i < MAX_SOCKSPEREASYHANDLE; ++i) { + if(!(bitmap & GETSOCK_MASK_RW(i)) || !VALID_SOCK((socks[i]))) { + break; + } + if(bitmap & GETSOCK_READSOCK(i)) { + if(bitmap & GETSOCK_WRITESOCK(i)) + Curl_pollset_add_inout(data, ps, socks[i]); + else + /* is READ, since we checked MASK_RW above */ + Curl_pollset_add_in(data, ps, socks[i]); + } + else + Curl_pollset_add_out(data, ps, socks[i]); + } + } +} + +void Curl_pollset_add_socks(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + curl_socket_t *socks)) +{ + curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; + int bitmap; + + bitmap = get_socks_cb(data, socks); + ps_add(data, ps, bitmap, socks); +} + +void Curl_pollset_check(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool *pwant_read, bool *pwant_write) +{ + unsigned int i; + + (void)data; + DEBUGASSERT(VALID_SOCK(sock)); + for(i = 0; i < ps->num; ++i) { + if(ps->sockets[i] == sock) { + *pwant_read = !!(ps->actions[i] & CURL_POLL_IN); + *pwant_write = !!(ps->actions[i] & CURL_POLL_OUT); + return; + } + } + *pwant_read = *pwant_write = FALSE; +} diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h index 70dcbe758e0..4c604db38f7 100644 --- a/Utilities/cmcurl/lib/cfilters.h +++ b/Utilities/cmcurl/lib/cfilters.h @@ -24,11 +24,13 @@ * ***************************************************************************/ +#include "curlx/timediff.h" struct Curl_cfilter; struct Curl_easy; struct Curl_dns_entry; struct connectdata; +struct ip_quadruple; /* Callback to destroy resources held by this filter instance. * Implementations MUST NOT chain calls to cf->next. @@ -36,12 +38,20 @@ struct connectdata; typedef void Curl_cft_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data); +/* Callback to close the connection immediately. */ typedef void Curl_cft_close(struct Curl_cfilter *cf, struct Curl_easy *data); +/* Callback to close the connection filter gracefully, non-blocking. + * Implementations MUST NOT chain calls to cf->next. + */ +typedef CURLcode Curl_cft_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); + typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done); + bool *done); /* Return the hostname and port the connection goes to. * This may change with the connection state of filters when tunneling @@ -55,19 +65,39 @@ typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf, * @param pport on return, contains the port number */ typedef void Curl_cft_get_host(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char **phost, - const char **pdisplay_host, - int *pport); + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport); + +struct easy_pollset; -/* Filters may return sockets and fdset flags they are waiting for. - * The passes array has room for up to MAX_SOCKSPEREASYHANDLE sockets. - * @return read/write fdset for index in socks - * or GETSOCK_BLANK when nothing to wait on +/* Passing in an easy_pollset for monitoring of sockets, let + * filters add or remove sockets actions (CURL_POLL_OUT, CURL_POLL_IN). + * This may add a socket or, in case no actions remain, remove + * a socket from the set. + * + * Filter implementations need to call filters "below" *after* they have + * made their adjustments. This allows lower filters to override "upper" + * actions. If a "lower" filter is unable to write, it needs to be able + * to disallow POLL_OUT. + * + * A filter without own restrictions/preferences should not modify + * the pollset. Filters, whose filter "below" is not connected, should + * also do no adjustments. + * + * Examples: a TLS handshake, while ongoing, might remove POLL_IN when it + * needs to write, or vice versa. An HTTP/2 filter might remove POLL_OUT when + * a stream window is exhausted and a WINDOW_UPDATE needs to be received first + * and add instead POLL_IN. + * + * @param cf the filter to ask + * @param data the easy handle the pollset is about + * @param ps the pollset (inout) for the easy handle */ -typedef int Curl_cft_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks); +typedef void Curl_cft_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); typedef bool Curl_cft_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data); @@ -76,6 +106,7 @@ typedef ssize_t Curl_cft_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* transfer */ const void *buf, /* data to write */ size_t len, /* amount to write */ + bool eos, /* last chunk */ CURLcode *err); /* error to return */ typedef ssize_t Curl_cft_recv(struct Curl_cfilter *cf, @@ -101,8 +132,6 @@ typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf, * to all filters in the chain. Overall result is always CURLE_OK. */ /* data event arg1 arg2 return */ -#define CF_CTRL_DATA_ATTACH 1 /* 0 NULL ignored */ -#define CF_CTRL_DATA_DETACH 2 /* 0 NULL ignored */ #define CF_CTRL_DATA_SETUP 4 /* 0 NULL first fail */ #define CF_CTRL_DATA_IDLE 5 /* 0 NULL first fail */ #define CF_CTRL_DATA_PAUSE 6 /* on/off NULL first fail */ @@ -110,6 +139,8 @@ typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf, #define CF_CTRL_DATA_DONE_SEND 8 /* 0 NULL ignored */ /* update conn info at connection and data */ #define CF_CTRL_CONN_INFO_UPDATE (256+0) /* 0 NULL ignored */ +#define CF_CTRL_FORGET_SOCKET (256+1) /* 0 NULL ignored */ +#define CF_CTRL_FLUSH (256+2) /* 0 NULL first fail */ /** * Handle event/control for the filter. @@ -132,6 +163,9 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, * were received. * -1 if not determined yet. * - CF_QUERY_SOCKET: the socket used by the filter chain + * - CF_QUERY_NEED_FLUSH: TRUE iff any of the filters have unsent data + * - CF_QUERY_IP_INFO: res1 says if connection used IPv6, res2 is the + * ip quadruple */ /* query res1 res2 */ #define CF_QUERY_MAX_CONCURRENT 1 /* number - */ @@ -139,6 +173,10 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, #define CF_QUERY_SOCKET 3 /* - curl_socket_t */ #define CF_QUERY_TIMER_CONNECT 4 /* - struct curltime */ #define CF_QUERY_TIMER_APPCONNECT 5 /* - struct curltime */ +#define CF_QUERY_STREAM_ERROR 6 /* error code - */ +#define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */ +#define CF_QUERY_IP_INFO 8 /* TRUE/FALSE struct ip_quadruple */ +#define CF_QUERY_HTTP_VERSION 9 /* number (10/11/20/30) - */ /** * Query the cfilter for properties. Filters ignorant of a query will @@ -157,10 +195,14 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, * connection, etc. * CF_TYPE_SSL: provide SSL/TLS * CF_TYPE_MULTIPLEX: provides multiplexing of easy handles + * CF_TYPE_PROXY provides proxying + * CF_TYPE_HTTP implement a version of the HTTP protocol */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) #define CF_TYPE_MULTIPLEX (1 << 2) +#define CF_TYPE_PROXY (1 << 3) +#define CF_TYPE_HTTP (1 << 4) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { @@ -168,10 +210,11 @@ struct Curl_cftype { int flags; /* flags of filter type */ int log_level; /* log level for such filters */ Curl_cft_destroy_this *destroy; /* destroy resources of this cf */ - Curl_cft_connect *connect; /* establish connection */ - Curl_cft_close *close; /* close conn */ + Curl_cft_connect *do_connect; /* establish connection */ + Curl_cft_close *do_close; /* close conn */ + Curl_cft_shutdown *do_shutdown; /* shutdown conn */ Curl_cft_get_host *get_host; /* host filter talks to */ - Curl_cft_get_select_socks *get_select_socks;/* sockets to select on */ + Curl_cft_adjust_pollset *adjust_pollset; /* adjust transfer poll set */ Curl_cft_data_pending *has_data_pending;/* conn has data pending */ Curl_cft_send *do_send; /* send data */ Curl_cft_recv *do_recv; /* receive data */ @@ -189,6 +232,7 @@ struct Curl_cfilter { struct connectdata *conn; /* the connection this filter belongs to */ int sockindex; /* the index the filter is installed at */ BIT(connected); /* != 0 iff this filter is connected */ + BIT(shutdown); /* != 0 iff this filter has shut down */ }; /* Default implementations for the type functions, implementing nop. */ @@ -200,18 +244,19 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, const char **phost, const char **pdisplay_host, int *pport); -int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks); +void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data); ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err); + const void *buf, size_t len, bool eos, + CURLcode *err); ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err); CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf, - struct Curl_easy *data, - int event, int arg1, void *arg2); + struct Curl_easy *data, + int event, int arg1, void *arg2); bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending); @@ -220,6 +265,8 @@ CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2); +CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done); /** * Create a new filter instance, unattached to the filter chain. @@ -277,13 +324,11 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data, CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done); + bool *done); void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data); -int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks); ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err); + const void *buf, size_t len, bool eos, + CURLcode *err); ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err); CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf, @@ -304,6 +349,12 @@ bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf); curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, struct Curl_easy *data); +CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, + struct Curl_easy *data, + int *is_ipv6, struct ip_quadruple *ipquad); + +bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, + struct Curl_easy *data); #define CURL_CF_SSL_DEFAULT -1 #define CURL_CF_SSL_DISABLE 0 @@ -319,6 +370,11 @@ curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, CURLcode Curl_conn_connect(struct Curl_easy *data, int sockindex, bool blocking, bool *done); +/** + * Check if a filter chain at `sockindex` for connection `conn` exists. + */ +bool Curl_conn_is_setup(struct connectdata *conn, int sockindex); + /** * Check if the filter chain at `sockindex` for connection `conn` is * completely connected. @@ -344,12 +400,26 @@ bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex); */ bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex); +/** + * Return the HTTP version used on the FIRSTSOCKET connection filters + * or 0 if unknown. Value otherwise is 09, 10, 11, etc. + */ +unsigned char Curl_conn_http_version(struct Curl_easy *data, + struct connectdata *conn); + /** * Close the filter chain at `sockindex` for connection `data->conn`. * Filters remain in place and may be connected again afterwards. */ void Curl_conn_close(struct Curl_easy *data, int sockindex); +/** + * Shutdown the connection at `sockindex` non-blocking, using timeout + * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS. + * Will return CURLE_OK and *done == FALSE if not finished. + */ +CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done); + /** * Return if data is pending in some connection filter at chain * `sockindex` for connection `data->conn`. @@ -357,6 +427,17 @@ void Curl_conn_close(struct Curl_easy *data, int sockindex); bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex); +/** + * Return TRUE if any of the connection filters at chain `sockindex` + * have data still to send. + */ +bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex); + +/** + * Flush any pending data on the connection filters at chain `sockindex`. + */ +CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex); + /** * Return the socket used on data's connection for the index. * Returns CURL_SOCKET_BAD if not available. @@ -364,20 +445,41 @@ bool Curl_conn_data_pending(struct Curl_easy *data, curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex); /** - * Get any select fd flags and the socket filters at chain `sockindex` - * at connection `conn` might be waiting for. + * Tell filters to forget about the socket at sockindex. */ -int Curl_conn_get_select_socks(struct Curl_easy *data, int sockindex, - curl_socket_t *socks); +void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex); + +/** + * Adjust the pollset for the filter chain startgin at `cf`. + */ +void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); + +/** + * Adjust pollset from filters installed at transfer's connection. + */ +void Curl_conn_adjust_pollset(struct Curl_easy *data, + struct connectdata *conn, + struct easy_pollset *ps); + +/** + * Curl_poll() the filter chain at `cf` with timeout `timeout_ms`. + * Returns 0 on timeout, negative on error or number of sockets + * with requested poll events. + */ +int Curl_conn_cf_poll(struct Curl_cfilter *cf, + struct Curl_easy *data, + timediff_t timeout_ms); /** * Receive data through the filter chain at `sockindex` for connection * `data->conn`. Copy at most `len` bytes into `buf`. Return the - * actuel number of bytes copied or a negative value on error. + * actual number of bytes copied or a negative value on error. * The error code is placed into `*code`. */ -ssize_t Curl_conn_recv(struct Curl_easy *data, int sockindex, char *buf, - size_t len, CURLcode *code); +ssize_t Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, + size_t len, CURLcode *code); /** * Send `len` bytes of data from `buf` through the filter chain `sockindex` @@ -385,26 +487,8 @@ ssize_t Curl_conn_recv(struct Curl_easy *data, int sockindex, char *buf, * or a negative value on error. * The error code is placed into `*code`. */ -ssize_t Curl_conn_send(struct Curl_easy *data, int sockindex, - const void *buf, size_t len, CURLcode *code); - -/** - * The easy handle `data` is being attached to `conn`. This does - * not mean that data will actually do a transfer. Attachment is - * also used for temporary actions on the connection. - */ -void Curl_conn_ev_data_attach(struct connectdata *conn, - struct Curl_easy *data); - -/** - * The easy handle `data` is being detached (no longer served) - * by connection `conn`. All filters are informed to release any resources - * related to `data`. - * Note: there may be several `data` attached to a connection at the same - * time. - */ -void Curl_conn_ev_data_detach(struct connectdata *conn, - struct Curl_easy *data); +ssize_t Curl_cf_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t len, bool eos, CURLcode *code); /** * Notify connection filters that they need to setup data for @@ -420,7 +504,7 @@ CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data); /** * Notify connection filters that the transfer represented by `data` - * is donw with sending data (e.g. has uploaded everything). + * is done with sending data (e.g. has uploaded everything). */ void Curl_conn_ev_data_done_send(struct Curl_easy *data); @@ -435,12 +519,6 @@ void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature); */ CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause); -/** - * Inform connection filters to update their info in `conn`. - */ -void Curl_conn_ev_update_info(struct Curl_easy *data, - struct connectdata *conn); - /** * Check if FIRSTSOCKET's cfilter chain deems connection alive. */ @@ -454,7 +532,9 @@ CURLcode Curl_conn_keep_alive(struct Curl_easy *data, struct connectdata *conn, int sockindex); +#ifdef UNITTESTS void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data); +#endif void Curl_conn_get_host(struct Curl_easy *data, int sockindex, const char **phost, const char **pdisplay_host, int *pport); @@ -467,6 +547,79 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, struct connectdata *conn, int sockindex); +/** + * Get the underlying error code for a transfer stream or 0 if not known. + */ +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +/** + * Get the index of the given socket in the connection's sockets. + * Useful in calling `Curl_conn_send()/Curl_conn_recv()` with the + * correct socket index. + */ +int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd); + +/* + * Receive data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. + * Will return CURLE_AGAIN iff blocked on receiving. + */ +CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, + char *buf, size_t buffersize, + ssize_t *pnread); + +/* + * Send data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. + * Will return CURLE_AGAIN iff blocked on sending. + */ +CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t blen, bool eos, + size_t *pnwritten); + + +void Curl_pollset_reset(struct Curl_easy *data, + struct easy_pollset *ps); + +/* Change the poll flags (CURL_POLL_IN/CURL_POLL_OUT) to the poll set for + * socket `sock`. If the socket is not already part of the poll set, it + * will be added. + * If the socket is present and all poll flags are cleared, it will be removed. + */ +void Curl_pollset_change(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + int add_flags, int remove_flags); + +void Curl_pollset_set(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool do_in, bool do_out); + +#define Curl_pollset_add_in(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), CURL_POLL_IN, 0) +#define Curl_pollset_add_out(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), CURL_POLL_OUT, 0) +#define Curl_pollset_add_inout(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_IN|CURL_POLL_OUT, 0) +#define Curl_pollset_set_in_only(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_IN, CURL_POLL_OUT) +#define Curl_pollset_set_out_only(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_OUT, CURL_POLL_IN) + +void Curl_pollset_add_socks(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + curl_socket_t *socks)); + +/** + * Check if the pollset, as is, wants to read and/or write regarding + * the given socket. + */ +void Curl_pollset_check(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool *pwant_read, bool *pwant_write); /** * Types and macros used to keep the current easy handle in filter calls, @@ -508,7 +661,7 @@ struct cf_call_data { (save) = CF_CTX_CALL_DATA(cf); \ DEBUGASSERT((save).data == NULL || (save).depth > 0); \ CF_CTX_CALL_DATA(cf).depth++; \ - CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)CURL_UNCONST(data); \ } while(0) #define CF_DATA_RESTORE(cf, save) \ @@ -523,7 +676,7 @@ struct cf_call_data { #define CF_DATA_SAVE(save, cf, data) \ do { \ (save) = CF_CTX_CALL_DATA(cf); \ - CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)CURL_UNCONST(data); \ } while(0) #define CF_DATA_RESTORE(cf, save) \ diff --git a/Utilities/cmcurl/lib/conncache.c b/Utilities/cmcurl/lib/conncache.c index a21409c13f1..f5e2ea87252 100644 --- a/Utilities/cmcurl/lib/conncache.c +++ b/Utilities/cmcurl/lib/conncache.c @@ -29,550 +29,856 @@ #include "urldata.h" #include "url.h" +#include "cfilters.h" #include "progress.h" #include "multiif.h" +#include "multi_ev.h" #include "sendf.h" +#include "cshutdn.h" #include "conncache.h" +#include "http_negotiate.h" +#include "http_ntlm.h" #include "share.h" #include "sigpipe.h" #include "connect.h" +#include "select.h" #include "strcase.h" +#include "curlx/strparse.h" +#include "uint-table.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#define HASHKEY_SIZE 128 -static CURLcode bundle_create(struct connectbundle **bundlep) +#define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) + +#define CPOOL_LOCK(c,d) \ + do { \ + if((c)) { \ + if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ + Curl_share_lock((d), CURL_LOCK_DATA_CONNECT, \ + CURL_LOCK_ACCESS_SINGLE); \ + DEBUGASSERT(!(c)->locked); \ + (c)->locked = TRUE; \ + } \ + } while(0) + +#define CPOOL_UNLOCK(c,d) \ + do { \ + if((c)) { \ + DEBUGASSERT((c)->locked); \ + (c)->locked = FALSE; \ + if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ + Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT); \ + } \ + } while(0) + + +/* A list of connections 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 */ +}; + + +static void cpool_discard_conn(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted); + +static struct cpool_bundle *cpool_bundle_create(const char *dest) { - DEBUGASSERT(*bundlep == NULL); - *bundlep = malloc(sizeof(struct connectbundle)); - if(!*bundlep) - return CURLE_OUT_OF_MEMORY; - - (*bundlep)->num_connections = 0; - (*bundlep)->multiuse = BUNDLE_UNKNOWN; - - Curl_llist_init(&(*bundlep)->conn_list, NULL); - return CURLE_OK; + struct cpool_bundle *bundle; + size_t dest_len = strlen(dest); + + bundle = calloc(1, sizeof(*bundle) + dest_len); + if(!bundle) + return NULL; + Curl_llist_init(&bundle->conns, NULL); + bundle->dest_len = dest_len + 1; + memcpy(bundle->dest, dest, bundle->dest_len); + return bundle; } -static void bundle_destroy(struct connectbundle *bundle) +static void cpool_bundle_destroy(struct cpool_bundle *bundle) { + DEBUGASSERT(!Curl_llist_count(&bundle->conns)); free(bundle); } /* Add a connection to a bundle */ -static void bundle_add_conn(struct connectbundle *bundle, - struct connectdata *conn) +static void cpool_bundle_add(struct cpool_bundle *bundle, + struct connectdata *conn) { - Curl_llist_insert_next(&bundle->conn_list, bundle->conn_list.tail, conn, - &conn->bundle_node); - conn->bundle = bundle; - bundle->num_connections++; + DEBUGASSERT(!Curl_node_llist(&conn->cpool_node)); + Curl_llist_append(&bundle->conns, conn, &conn->cpool_node); + conn->bits.in_cpool = TRUE; } /* Remove a connection from a bundle */ -static int bundle_remove_conn(struct connectbundle *bundle, - struct connectdata *conn) +static void cpool_bundle_remove(struct cpool_bundle *bundle, + struct connectdata *conn) { - struct Curl_llist_element *curr; + (void)bundle; + DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns); + Curl_node_remove(&conn->cpool_node); + conn->bits.in_cpool = FALSE; +} - curr = bundle->conn_list.head; - while(curr) { - if(curr->ptr == conn) { - Curl_llist_remove(&bundle->conn_list, curr, NULL); - bundle->num_connections--; - conn->bundle = NULL; - return 1; /* we removed a handle */ - } - curr = curr->next; - } - DEBUGASSERT(0); - return 0; +static void cpool_bundle_free_entry(void *freethis) +{ + cpool_bundle_destroy((struct cpool_bundle *)freethis); } -static void free_bundle_hash_entry(void *freethis) +void Curl_cpool_init(struct cpool *cpool, + struct Curl_easy *idata, + struct Curl_share *share, + size_t size) { - struct connectbundle *b = (struct connectbundle *) freethis; + Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str, + curlx_str_key_compare, cpool_bundle_free_entry); + + DEBUGASSERT(idata); - bundle_destroy(b); + cpool->idata = idata; + cpool->share = share; + cpool->initialised = TRUE; } -int Curl_conncache_init(struct conncache *connc, int size) +/* Return the "first" connection in the pool or NULL. */ +static struct connectdata *cpool_get_first(struct cpool *cpool) { - /* allocate a new easy handle to use when closing cached connections */ - connc->closure_handle = curl_easy_init(); - if(!connc->closure_handle) - return 1; /* bad */ + 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); + } + return NULL; +} - Curl_hash_init(&connc->hash, size, Curl_hash_str, - Curl_str_key_compare, free_bundle_hash_entry); - connc->closure_handle->state.conn_cache = connc; - return 0; /* good */ +static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, + struct connectdata *conn) +{ + return Curl_hash_pick(&cpool->dest2bundle, + conn->destination, strlen(conn->destination) + 1); } -void Curl_conncache_destroy(struct conncache *connc) + +static void cpool_remove_bundle(struct cpool *cpool, + struct cpool_bundle *bundle) { - if(connc) - Curl_hash_destroy(&connc->hash); + if(!cpool) + return; + Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len); } -/* creates a key to find a bundle for this connection */ -static void hashkey(struct connectdata *conn, char *buf, size_t len) + +static void cpool_remove_conn(struct cpool *cpool, + struct connectdata *conn) { - const char *hostname; - long port = conn->remote_port; - DEBUGASSERT(len >= HASHKEY_SIZE); -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - hostname = conn->http_proxy.host.name; - port = conn->port; + struct Curl_llist *list = Curl_node_llist(&conn->cpool_node); + 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); + } } - else -#endif - if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; - - /* put the numbers first so that the hostname gets cut off if too long */ -#ifdef ENABLE_IPV6 - msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname); -#else - msnprintf(buf, len, "%ld/%s", port, hostname); -#endif - Curl_strntolower(buf, buf, len); } -/* Returns number of connections currently held in the connection cache. - Locks/unlocks the cache itself! -*/ -size_t Curl_conncache_size(struct Curl_easy *data) +void Curl_cpool_destroy(struct cpool *cpool) { - size_t num; - CONNCACHE_LOCK(data); - num = data->state.conn_cache->num_conn; - CONNCACHE_UNLOCK(data); - return num; + if(cpool && cpool->initialised && cpool->idata) { + struct connectdata *conn; + SIGPIPE_VARIABLE(pipe_st); + + CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections", + cpool->share ? "[SHARE] " : "", cpool->num_conn); + /* Move all connections to the shutdown list */ + sigpipe_init(&pipe_st); + CPOOL_LOCK(cpool, cpool->idata); + conn = cpool_get_first(cpool); + while(conn) { + cpool_remove_conn(cpool, conn); + sigpipe_apply(cpool->idata, &pipe_st); + connclose(conn, "kill all"); + cpool_discard_conn(cpool, cpool->idata, conn, FALSE); + conn = cpool_get_first(cpool); + } + CPOOL_UNLOCK(cpool, cpool->idata); + sigpipe_restore(&pipe_st); + Curl_hash_destroy(&cpool->dest2bundle); + } } -/* Look up the bundle with all the connections to the same host this - connectdata struct is setup to use. +static struct cpool *cpool_get_instance(struct Curl_easy *data) +{ + if(data) { + if(CURL_SHARE_KEEP_CONNECT(data->share)) + return &data->share->cpool; + else if(data->multi_easy) + return &data->multi_easy->cpool; + else if(data->multi) + return &data->multi->cpool; + } + return NULL; +} - **NOTE**: When it returns, it holds the connection cache lock! */ -struct connectbundle * -Curl_conncache_find_bundle(struct Curl_easy *data, - struct connectdata *conn, - struct conncache *connc) +void Curl_cpool_xfer_init(struct Curl_easy *data) { - struct connectbundle *bundle = NULL; - CONNCACHE_LOCK(data); - if(connc) { - char key[HASHKEY_SIZE]; - hashkey(conn, key, sizeof(key)); - bundle = Curl_hash_pick(&connc->hash, key, strlen(key)); + struct cpool *cpool = cpool_get_instance(data); + + DEBUGASSERT(cpool); + if(cpool) { + CPOOL_LOCK(cpool, data); + /* the identifier inside the connection cache */ + data->id = cpool->next_easy_id++; + if(cpool->next_easy_id <= 0) + cpool->next_easy_id = 0; + data->state.lastconnect_id = -1; + + CPOOL_UNLOCK(cpool, data); + } + else { + /* We should not get here, but in a non-debug build, do something */ + data->id = 0; + data->state.lastconnect_id = -1; } +} + +static struct cpool_bundle * +cpool_add_bundle(struct cpool *cpool, 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; } -static void *conncache_add_bundle(struct conncache *connc, - char *key, - struct connectbundle *bundle) +static struct connectdata * +cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle) { - return Curl_hash_add(&connc->hash, key, strlen(key), bundle); + struct Curl_llist_node *curr; + timediff_t highscore = -1; + timediff_t score; + struct curltime now; + struct connectdata *oldest_idle = NULL; + struct connectdata *conn; + + now = curlx_now(); + 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_timediff(now, conn->lastused); + + if(score > highscore) { + highscore = score; + oldest_idle = conn; + } + } + curr = Curl_node_next(curr); + } + return oldest_idle; } -static void conncache_remove_bundle(struct conncache *connc, - struct connectbundle *bundle) +static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool) { struct Curl_hash_iterator iter; + struct Curl_llist_node *curr; struct Curl_hash_element *he; + struct connectdata *oldest_idle = NULL; + struct cpool_bundle *bundle; + struct curltime now; + timediff_t highscore =- 1; + timediff_t score; - if(!connc) - return; + now = curlx_now(); + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - Curl_hash_start_iterate(&connc->hash, &iter); + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { + struct connectdata *conn; + bundle = he->ptr; - he = Curl_hash_next_element(&iter); - while(he) { - if(he->ptr == bundle) { - /* The bundle is destroyed by the hash destructor function, - free_bundle_hash_entry() */ - Curl_hash_delete(&connc->hash, he->key, he->key_len); - return; + for(curr = Curl_llist_head(&bundle->conns); curr; + curr = Curl_node_next(curr)) { + conn = Curl_node_elem(curr); + if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only) + continue; + /* Set higher score for the age passed since the connection was used */ + score = curlx_timediff(now, conn->lastused); + if(score > highscore) { + highscore = score; + oldest_idle = conn; + } } - - he = Curl_hash_next_element(&iter); } + return oldest_idle; } -CURLcode Curl_conncache_add_conn(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct connectbundle *bundle = NULL; - struct connectdata *conn = data->conn; - struct conncache *connc = data->state.conn_cache; - DEBUGASSERT(conn); - /* *find_bundle() locks the connection cache */ - bundle = Curl_conncache_find_bundle(data, conn, data->state.conn_cache); - if(!bundle) { - char key[HASHKEY_SIZE]; +int Curl_cpool_check_limits(struct Curl_easy *data, + struct connectdata *conn) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_bundle *bundle; + size_t dest_limit = 0; + size_t total_limit = 0; + size_t shutdowns; + int result = CPOOL_LIMIT_OK; + + if(!cpool) + return CPOOL_LIMIT_OK; + + if(cpool->idata->multi) { + dest_limit = cpool->idata->multi->max_host_connections; + total_limit = cpool->idata->multi->max_total_connections; + } - result = bundle_create(&bundle); - if(result) { - goto unlock; + if(!dest_limit && !total_limit) + return CPOOL_LIMIT_OK; + + CPOOL_LOCK(cpool, cpool->idata); + 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(data, conn->destination); + while((live + shutdowns) >= dest_limit) { + if(shutdowns) { + /* close one connection in shutdown right away, if we can */ + if(!Curl_cshutdn_close_oldest(data, 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); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + CURL_TRC_M(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach destination " + "limit of %zu", oldest_idle->connection_id, + Curl_llist_count(&bundle->conns), dest_limit); + Curl_conn_terminate(cpool->idata, oldest_idle, FALSE); + + /* 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; + } + shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination); } - - hashkey(conn, key, sizeof(key)); - - if(!conncache_add_bundle(data->state.conn_cache, key, bundle)) { - bundle_destroy(bundle); - result = CURLE_OUT_OF_MEMORY; - goto unlock; + if((live + shutdowns) >= dest_limit) { + result = CPOOL_LIMIT_DEST; + goto out; } } - bundle_add_conn(bundle, conn); - conn->connection_id = connc->next_connection_id++; - connc->num_conn++; - - DEBUGF(infof(data, "Added connection %ld. " - "The cache now contains %zu members", - conn->connection_id, connc->num_conn)); - -unlock: - CONNCACHE_UNLOCK(data); + if(total_limit) { + shutdowns = Curl_cshutdn_count(cpool->idata); + while((cpool->num_conn + shutdowns) >= total_limit) { + if(shutdowns) { + /* close one connection in shutdown right away, if we can */ + if(!Curl_cshutdn_close_oldest(data, NULL)) + break; + } + else { + struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + CURL_TRC_M(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach total " + "limit of %zu", + oldest_idle->connection_id, cpool->num_conn, total_limit); + Curl_conn_terminate(cpool->idata, oldest_idle, FALSE); + } + shutdowns = Curl_cshutdn_count(cpool->idata); + } + if((cpool->num_conn + shutdowns) >= total_limit) { + result = CPOOL_LIMIT_TOTAL; + goto out; + } + } +out: + CPOOL_UNLOCK(cpool, cpool->idata); return result; } -/* - * Removes the connectdata object from the connection cache, but the transfer - * still owns this connection. - * - * Pass TRUE/FALSE in the 'lock' argument depending on if the parent function - * already holds the lock or not. - */ -void Curl_conncache_remove_conn(struct Curl_easy *data, - struct connectdata *conn, bool lock) +CURLcode Curl_cpool_add(struct Curl_easy *data, + struct connectdata *conn) { - struct connectbundle *bundle = conn->bundle; - struct conncache *connc = data->state.conn_cache; + CURLcode result = CURLE_OK; + struct cpool_bundle *bundle = NULL; + struct cpool *cpool = cpool_get_instance(data); + DEBUGASSERT(conn); - /* The bundle pointer can be NULL, since this function can be called - due to a failed connection attempt, before being added to a bundle */ - if(bundle) { - if(lock) { - CONNCACHE_LOCK(data); - } - bundle_remove_conn(bundle, conn); - if(bundle->num_connections == 0) - conncache_remove_bundle(connc, bundle); - conn->bundle = NULL; /* removed from it */ - if(connc) { - connc->num_conn--; - DEBUGF(infof(data, "The cache now contains %zu members", - connc->num_conn)); - } - if(lock) { - CONNCACHE_UNLOCK(data); + DEBUGASSERT(cpool); + if(!cpool) + return CURLE_FAILED_INIT; + + CPOOL_LOCK(cpool, data); + bundle = cpool_find_bundle(cpool, conn); + if(!bundle) { + bundle = cpool_add_bundle(cpool, conn); + if(!bundle) { + result = CURLE_OUT_OF_MEMORY; + goto out; } } + + cpool_bundle_add(bundle, conn); + 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); +out: + CPOOL_UNLOCK(cpool, data); + + return result; } -/* This function iterates the entire connection cache and calls the function +/* This function iterates the entire connection pool and calls the function func() with the connection pointer as the first argument and the supplied 'param' argument as the other. - The conncache lock is still held when the callback is called. It needs it, + The cpool lock is still held when the callback is called. It needs it, so that it can safely continue traversing the lists once the callback returns. - Returns 1 if the loop was aborted due to the callback's return code. + Returns TRUE if the loop was aborted due to the callback's return code. Return 0 from func() to continue the loop, return 1 to abort it. */ -bool Curl_conncache_foreach(struct Curl_easy *data, - struct conncache *connc, - void *param, - int (*func)(struct Curl_easy *data, - struct connectdata *conn, void *param)) +static bool cpool_foreach(struct Curl_easy *data, + struct cpool *cpool, + void *param, + int (*func)(struct Curl_easy *data, + struct connectdata *conn, void *param)) { struct Curl_hash_iterator iter; - struct Curl_llist_element *curr; struct Curl_hash_element *he; - if(!connc) + if(!cpool) return FALSE; - CONNCACHE_LOCK(data); - Curl_hash_start_iterate(&connc->hash, &iter); + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); he = Curl_hash_next_element(&iter); while(he) { - struct connectbundle *bundle; - - bundle = he->ptr; + struct Curl_llist_node *curr; + struct cpool_bundle *bundle = he->ptr; he = Curl_hash_next_element(&iter); - curr = bundle->conn_list.head; + 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 = curr->ptr; - curr = curr->next; + struct connectdata *conn = Curl_node_elem(curr); + curr = Curl_node_next(curr); if(1 == func(data, conn, param)) { - CONNCACHE_UNLOCK(data); return TRUE; } } } - CONNCACHE_UNLOCK(data); return FALSE; } -/* Return the first connection found in the cache. Used when closing all - connections. +/* + * A connection (already in the pool) has become 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) +{ + unsigned int maxconnects = !data->multi->maxconnects ? + (Curl_multi_xfers_running(data->multi) * 4) : data->multi->maxconnects; + struct connectdata *oldest_idle = NULL; + struct cpool *cpool = cpool_get_instance(data); + bool kept = TRUE; + + conn->lastused = curlx_now(); /* it was used up until now */ + if(cpool && maxconnects) { + /* may be called form a callback already under lock */ + bool do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool, data); + 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); + kept = (oldest_idle != conn); + if(oldest_idle) { + Curl_conn_terminate(data, oldest_idle, FALSE); + } + } + if(do_lock) + CPOOL_UNLOCK(cpool, data); + } + + return kept; +} - NOTE: no locking is done here as this is presumably only done when cleaning - up a cache! -*/ -static struct connectdata * -conncache_find_first_connection(struct conncache *connc) +bool Curl_cpool_find(struct Curl_easy *data, + const char *destination, + Curl_cpool_conn_match_cb *conn_cb, + Curl_cpool_done_match_cb *done_cb, + void *userdata) { - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - struct connectbundle *bundle; + struct cpool *cpool = cpool_get_instance(data); + struct cpool_bundle *bundle; + bool result = FALSE; - Curl_hash_start_iterate(&connc->hash, &iter); + DEBUGASSERT(cpool); + DEBUGASSERT(conn_cb); + if(!cpool) + return FALSE; - he = Curl_hash_next_element(&iter); - while(he) { - struct Curl_llist_element *curr; - bundle = he->ptr; + CPOOL_LOCK(cpool, data); + bundle = Curl_hash_pick(&cpool->dest2bundle, + CURL_UNCONST(destination), + strlen(destination) + 1); + 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); - curr = bundle->conn_list.head; - if(curr) { - return curr->ptr; + if(conn_cb(conn, userdata)) { + result = TRUE; + break; + } } - - he = Curl_hash_next_element(&iter); } - return NULL; + if(done_cb) { + result = done_cb(result, userdata); + } + CPOOL_UNLOCK(cpool, data); + return result; } -/* - * Give ownership of a connection back to the connection cache. Might - * disconnect the oldest existing in there to make space. - * - * Return TRUE if stored, FALSE if closed. - */ -bool Curl_conncache_return_conn(struct Curl_easy *data, - struct connectdata *conn) +static void cpool_discard_conn(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted) { - /* data->multi->maxconnects can be negative, deal with it. */ - size_t maxconnects = - (data->multi->maxconnects < 0) ? data->multi->num_easy * 4: - data->multi->maxconnects; - struct connectdata *conn_candidate = NULL; - - conn->lastused = Curl_now(); /* it was used up until now */ - if(maxconnects > 0 && - Curl_conncache_size(data) > maxconnects) { - infof(data, "Connection cache is full, closing the oldest one"); - - conn_candidate = Curl_conncache_extract_oldest(data); - if(conn_candidate) { - /* the winner gets the honour of being disconnected */ - Curl_disconnect(data, conn_candidate, /* dead_connection */ FALSE); - } + bool done = FALSE; + + DEBUGASSERT(data); + DEBUGASSERT(!data->conn); + DEBUGASSERT(cpool); + DEBUGASSERT(!conn->bits.in_cpool); + + /* + * If this connection is not marked to force-close, leave it open if there + * are other users of it + */ + if(CONN_INUSE(conn) && !aborted) { + CURL_TRC_M(data, "[CPOOL] not discarding #%" FMT_OFF_T + " still in use by %u transfers", conn->connection_id, + CONN_ATTACHED(conn)); + return; } - return (conn_candidate == conn) ? FALSE : TRUE; + /* treat the connection as aborted in CONNECT_ONLY situations, we do + * not know what the APP did with it. */ + if(conn->connect_only) + aborted = TRUE; + conn->bits.aborted = aborted; + + /* We do not shutdown dead connections. The term 'dead' can be misleading + * here, as we also mark errored connections/transfers as 'dead'. + * If we do a shutdown for an aborted transfer, the server might think + * it was successful otherwise (for example an ftps: upload). This is + * not what we want. */ + if(aborted) + done = TRUE; + if(!done) { + /* Attempt to shutdown the connection right away. */ + Curl_cshutdn_run_once(cpool->idata, conn, &done); + } + if(done || !data->multi) + Curl_cshutdn_terminate(cpool->idata, conn, FALSE); + else + Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn); } -/* - * This function finds the connection in the connection bundle that has been - * unused for the longest time. - * - * Does not lock the connection cache! - * - * Returns the pointer to the oldest idle connection, or NULL if none was - * found. - */ -struct connectdata * -Curl_conncache_extract_bundle(struct Curl_easy *data, - struct connectbundle *bundle) +void Curl_conn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool aborted) { - struct Curl_llist_element *curr; - timediff_t highscore = -1; - timediff_t score; - struct curltime now; - struct connectdata *conn_candidate = NULL; - struct connectdata *conn; + struct cpool *cpool = cpool_get_instance(data); + bool do_lock; - (void)data; + DEBUGASSERT(cpool); + DEBUGASSERT(data && !data->conn); + if(!cpool) + return; - now = Curl_now(); + /* If this connection is not marked to force-close, leave it open if there + * are other users of it */ + if(CONN_INUSE(conn) && !aborted) { + DEBUGASSERT(0); /* does this ever happen? */ + DEBUGF(infof(data, "Curl_disconnect when inuse: %u", CONN_ATTACHED(conn))); + return; + } - curr = bundle->conn_list.head; - while(curr) { - conn = curr->ptr; + /* This method may be called while we are under lock, e.g. from a + * user callback in find. */ + do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool, data); - if(!CONN_INUSE(conn)) { - /* Set higher score for the age passed since the connection was used */ - score = Curl_timediff(now, conn->lastused); + if(conn->bits.in_cpool) { + cpool_remove_conn(cpool, conn); + DEBUGASSERT(!conn->bits.in_cpool); + } - if(score > highscore) { - highscore = score; - conn_candidate = conn; - } - } - curr = curr->next; + /* treat the connection as aborted in CONNECT_ONLY situations, + * so no graceful shutdown is attempted. */ + if(conn->connect_only) + aborted = TRUE; + + if(data->multi) { + /* Add it to the multi's cpool for shutdown handling */ + infof(data, "%s connection #%" FMT_OFF_T, + aborted ? "closing" : "shutting down", conn->connection_id); + cpool_discard_conn(&data->multi->cpool, data, conn, aborted); } - if(conn_candidate) { - /* remove it to prevent another thread from nicking it */ - bundle_remove_conn(bundle, conn_candidate); - data->state.conn_cache->num_conn--; - DEBUGF(infof(data, "The cache now contains %zu members", - data->state.conn_cache->num_conn)); + else { + /* No multi available, terminate */ + infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); + Curl_cshutdn_terminate(cpool->idata, conn, !aborted); } - return conn_candidate; + if(do_lock) + CPOOL_UNLOCK(cpool, data); +} + + +struct cpool_reaper_ctx { + struct curltime now; +}; + +static int cpool_reap_dead_cb(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_reaper_ctx *rctx = param; + if(Curl_conn_seems_dead(conn, data, &rctx->now)) { + /* stop the iteration here, pass back the connection that was pruned */ + Curl_conn_terminate(data, conn, FALSE); + return 1; + } + return 0; /* continue iteration */ } /* - * This function finds the connection in the connection cache that has been - * unused for the longest time and extracts that from the bundle. + * 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. * - * Returns the pointer to the connection, or NULL if none was found. + * When called, this transfer has no connection attached. */ -struct connectdata * -Curl_conncache_extract_oldest(struct Curl_easy *data) +void Curl_cpool_prune_dead(struct Curl_easy *data) { - struct conncache *connc = data->state.conn_cache; - struct Curl_hash_iterator iter; - struct Curl_llist_element *curr; - struct Curl_hash_element *he; - timediff_t highscore =- 1; - timediff_t score; - struct curltime now; - struct connectdata *conn_candidate = NULL; - struct connectbundle *bundle; - struct connectbundle *bundle_candidate = NULL; + struct cpool *cpool = cpool_get_instance(data); + struct cpool_reaper_ctx rctx; + timediff_t elapsed; - now = Curl_now(); + if(!cpool) + return; - CONNCACHE_LOCK(data); - Curl_hash_start_iterate(&connc->hash, &iter); + rctx.now = curlx_now(); + CPOOL_LOCK(cpool, data); + elapsed = curlx_timediff(rctx.now, cpool->last_cleanup); - he = Curl_hash_next_element(&iter); - while(he) { - struct connectdata *conn; + if(elapsed >= 1000L) { + while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb)) + ; + cpool->last_cleanup = rctx.now; + } + CPOOL_UNLOCK(cpool, data); +} - bundle = he->ptr; +static int conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + void *param) +{ + struct curltime *now = param; + Curl_conn_upkeep(data, conn, now); + return 0; /* continue iteration */ +} - curr = bundle->conn_list.head; - while(curr) { - conn = curr->ptr; - - if(!CONN_INUSE(conn) && !conn->bits.close && - !conn->connect_only) { - /* Set higher score for the age passed since the connection was used */ - score = Curl_timediff(now, conn->lastused); - - if(score > highscore) { - highscore = score; - conn_candidate = conn; - bundle_candidate = bundle; - } - } - curr = curr->next; - } +CURLcode Curl_cpool_upkeep(void *data) +{ + struct cpool *cpool = cpool_get_instance(data); + struct curltime now = curlx_now(); - he = Curl_hash_next_element(&iter); - } - if(conn_candidate) { - /* remove it to prevent another thread from nicking it */ - bundle_remove_conn(bundle_candidate, conn_candidate); - connc->num_conn--; - DEBUGF(infof(data, "The cache now contains %zu members", - connc->num_conn)); - } - CONNCACHE_UNLOCK(data); + if(!cpool) + return CURLE_OK; - return conn_candidate; + CPOOL_LOCK(cpool, data); + cpool_foreach(data, cpool, &now, conn_upkeep); + CPOOL_UNLOCK(cpool, data); + return CURLE_OK; } -void Curl_conncache_close_all_connections(struct conncache *connc) -{ +struct cpool_find_ctx { + curl_off_t id; struct connectdata *conn; - char buffer[READBUFFER_MIN + 1]; - SIGPIPE_VARIABLE(pipe_st); - if(!connc->closure_handle) - return; - connc->closure_handle->state.buffer = buffer; - connc->closure_handle->set.buffer_size = READBUFFER_MIN; - - conn = conncache_find_first_connection(connc); - while(conn) { - sigpipe_ignore(connc->closure_handle, &pipe_st); - /* This will remove the connection from the cache */ - connclose(conn, "kill all"); - Curl_conncache_remove_conn(connc->closure_handle, conn, TRUE); - Curl_disconnect(connc->closure_handle, conn, FALSE); - sigpipe_restore(&pipe_st); +}; - conn = conncache_find_first_connection(connc); +static int cpool_find_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_find_ctx *fctx = param; + (void)data; + if(conn->connection_id == fctx->id) { + fctx->conn = conn; + return 1; } + return 0; +} - connc->closure_handle->state.buffer = NULL; - sigpipe_ignore(connc->closure_handle, &pipe_st); +struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, + curl_off_t conn_id) +{ + 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; +} + +struct cpool_do_conn_ctx { + curl_off_t id; + Curl_cpool_conn_do_cb *cb; + void *cbdata; +}; - Curl_hostcache_clean(connc->closure_handle, - connc->closure_handle->dns.hostcache); - Curl_close(&connc->closure_handle); - sigpipe_restore(&pipe_st); +static int cpool_do_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_do_conn_ctx *dctx = param; + (void)data; + if(conn->connection_id == dctx->id) { + dctx->cb(conn, data, dctx->cbdata); + return 1; + } + return 0; +} + +void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id, + Curl_cpool_conn_do_cb *cb, void *cbdata) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_do_conn_ctx dctx; + + if(!cpool) + return; + dctx.id = conn_id; + dctx.cb = cb; + dctx.cbdata = cbdata; + CPOOL_LOCK(cpool, data); + cpool_foreach(data, cpool, &dctx, cpool_do_conn); + CPOOL_UNLOCK(cpool, data); +} + +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); + } + else + cb(conn, data, cbdata); } #if 0 -/* Useful for debugging the connection cache */ -void Curl_conncache_print(struct conncache *connc) +/* Useful for debugging the connection pool */ +void Curl_cpool_print(struct cpool *cpool) { struct Curl_hash_iterator iter; - struct Curl_llist_element *curr; + struct Curl_llist_node *curr; struct Curl_hash_element *he; - if(!connc) + if(!cpool) return; fprintf(stderr, "=Bundle cache=\n"); - Curl_hash_start_iterate(connc->hash, &iter); + Curl_hash_start_iterate(cpool->dest2bundle, &iter); he = Curl_hash_next_element(&iter); while(he) { - struct connectbundle *bundle; + struct cpool_bundle *bundle; struct connectdata *conn; bundle = he->ptr; fprintf(stderr, "%s -", he->key); - curr = bundle->conn_list->head; + curr = Curl_llist_head(bundle->conns); while(curr) { - conn = curr->ptr; + conn = Curl_node_elem(curr); - fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse); - curr = curr->next; + fprintf(stderr, " [%p %d]", (void *)conn, conn->refcount); + curr = Curl_node_next(curr); } fprintf(stderr, "\n"); diff --git a/Utilities/cmcurl/lib/conncache.h b/Utilities/cmcurl/lib/conncache.h index 959767d596f..1314b65c607 100644 --- a/Utilities/cmcurl/lib/conncache.h +++ b/Utilities/cmcurl/lib/conncache.h @@ -25,97 +25,142 @@ * ***************************************************************************/ -/* - * All accesses to struct fields and changing of data in the connection cache - * and connectbundles must be done with the conncache LOCKED. The cache might - * be shared. - */ - #include -#include "timeval.h" +#include "curlx/timeval.h" struct connectdata; +struct Curl_easy; +struct curl_pollfds; +struct Curl_waitfds; +struct Curl_multi; +struct Curl_share; + +/** + * Terminate the connection, e.g. close and destroy. + * If the connection is in a cpool, remove it. + * If a `cshutdn` is available (e.g. data has a multi handle), + * pass the connection to that for controlled shutdown. + * Otherwise terminate it right away. + * Takes ownership of `conn`. + * `data` should not be attached to a connection. + */ +void Curl_conn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); -struct conncache { - struct Curl_hash hash; +struct cpool { + /* the pooled connections, bundled per destination */ + struct Curl_hash dest2bundle; size_t num_conn; - long next_connection_id; + curl_off_t next_connection_id; + curl_off_t next_easy_id; struct curltime last_cleanup; - /* handle used for closing cached connections */ - struct Curl_easy *closure_handle; + struct Curl_easy *idata; /* internal handle for maintenance */ + struct Curl_share *share; /* != NULL if pool belongs to share */ + BIT(locked); + BIT(initialised); }; -#define BUNDLE_NO_MULTIUSE -1 -#define BUNDLE_UNKNOWN 0 /* initial value */ -#define BUNDLE_MULTIPLEX 2 - -#ifdef CURLDEBUG -/* the debug versions of these macros make extra certain that the lock is - never doubly locked or unlocked */ -#define CONNCACHE_LOCK(x) \ - do { \ - if((x)->share) { \ - Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, \ - CURL_LOCK_ACCESS_SINGLE); \ - DEBUGASSERT(!(x)->state.conncache_lock); \ - (x)->state.conncache_lock = TRUE; \ - } \ - } while(0) - -#define CONNCACHE_UNLOCK(x) \ - do { \ - if((x)->share) { \ - DEBUGASSERT((x)->state.conncache_lock); \ - (x)->state.conncache_lock = FALSE; \ - Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT); \ - } \ - } while(0) -#else -#define CONNCACHE_LOCK(x) if((x)->share) \ - Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE) -#define CONNCACHE_UNLOCK(x) if((x)->share) \ - Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT) -#endif - -struct connectbundle { - int multiuse; /* supports multi-use */ - size_t num_connections; /* Number of connections in the bundle */ - struct Curl_llist conn_list; /* The connectdata members of the bundle */ -}; +/* Init the pool, pass multi only if pool is owned by it. + * Cannot fail. + */ +void Curl_cpool_init(struct cpool *cpool, + struct Curl_easy *idata, + struct Curl_share *share, + size_t size); + +/* Destroy all connections and free all members */ +void Curl_cpool_destroy(struct cpool *connc); + +/* Init the transfer to be used within its connection pool. + * 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); + +/* 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. Will 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); + +/* Return of conn is suitable. If so, stops iteration. */ +typedef bool 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(bool result, void *userdata); -/* returns 1 on error, 0 is fine */ -int Curl_conncache_init(struct conncache *, int size); -void Curl_conncache_destroy(struct conncache *connc); - -/* return the correct bundle, to a host or a proxy */ -struct connectbundle *Curl_conncache_find_bundle(struct Curl_easy *data, - struct connectdata *conn, - struct conncache *connc); -/* returns number of connections currently held in the connection cache */ -size_t Curl_conncache_size(struct Curl_easy *data); - -bool Curl_conncache_return_conn(struct Curl_easy *data, - struct connectdata *conn); -CURLcode Curl_conncache_add_conn(struct Curl_easy *data) WARN_UNUSED_RESULT; -void Curl_conncache_remove_conn(struct Curl_easy *data, - struct connectdata *conn, - bool lock); -bool Curl_conncache_foreach(struct Curl_easy *data, - struct conncache *connc, - void *param, - int (*func)(struct Curl_easy *data, - struct connectdata *conn, - void *param)); - -struct connectdata * -Curl_conncache_find_first_connection(struct conncache *connc); - -struct connectdata * -Curl_conncache_extract_bundle(struct Curl_easy *data, - struct connectbundle *bundle); -struct connectdata * -Curl_conncache_extract_oldest(struct Curl_easy *data); -void Curl_conncache_close_all_connections(struct conncache *connc); -void Curl_conncache_print(struct conncache *connc); +/** + * Find a connection in the pool matching `destination`. + * All callbacks are invoked while the pool's lock is held. + * @param data current transfer + * @param destination match agaonst `conn->destination` in pool + * @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 + connections were present. + */ +bool Curl_cpool_find(struct Curl_easy *data, + const char *destination, + 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); + +/** + * 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 Curl_easy *data); + +/** + * Perform upkeep actions on connections in the transfer's pool. + */ +CURLcode Curl_cpool_upkeep(void *data); + +typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, + struct Curl_easy *data, + void *cbdata); + +/** + * Invoke the callback on the pool's connection with the + * given connection id (if it exists). + */ +void Curl_cpool_do_by_id(struct Curl_easy *data, + curl_off_t conn_id, + Curl_cpool_conn_do_cb *cb, void *cbdata); + +/** + * Invoked the callback for the given data + connection under the + * connection pool's lock. + * 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); #endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c index ed5512138eb..1dcdde3fc5a 100644 --- a/Utilities/cmcurl/lib/connect.c +++ b/Utilities/cmcurl/lib/connect.c @@ -67,48 +67,61 @@ #include "multiif.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "vtls/vtls.h" /* for vtsl cfilters */ #include "progress.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "conncache.h" #include "multihandle.h" #include "share.h" -#include "version_win32.h" +#include "curlx/version_win32.h" #include "vquic/vquic.h" /* for quic cfilters */ #include "http_proxy.h" #include "socks.h" +#include "strcase.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +#if !defined(CURL_DISABLE_ALTSVC) || defined(USE_HTTPSRR) + +enum alpnid Curl_alpn2alpnid(const char *name, size_t len) +{ + if(len == 2) { + if(strncasecompare(name, "h1", 2)) + return ALPN_h1; + if(strncasecompare(name, "h2", 2)) + return ALPN_h2; + if(strncasecompare(name, "h3", 2)) + return ALPN_h3; + } + else if(len == 8) { + if(strncasecompare(name, "http/1.1", 8)) + return ALPN_h1; + } + return ALPN_none; /* unknown, probably rubbish input */ +} + +#endif /* * Curl_timeleft() returns the amount of milliseconds left allowed for the - * transfer/connection. If the value is 0, there's no timeout (ie there's + * transfer/connection. If the value is 0, there is no timeout (ie there is * infinite time left). If the value is negative, the timeout time has already * elapsed. - * - * If 'nowp' is non-NULL, it points to the current time. - * 'duringconnect' is FALSE if not during a connect, as then of course the - * connect timeout is not taken into account! - * + * @param data the transfer to check on + * @param nowp timestamp to use for calculation, NULL to use curlx_now() + * @param duringconnect TRUE iff connect timeout is also taken into account. * @unittest: 1303 */ - -#define TIMEOUT_CONNECT 1 -#define TIMEOUT_MAXTIME 2 - timediff_t Curl_timeleft(struct Curl_easy *data, struct curltime *nowp, bool duringconnect) { - unsigned int timeout_set = 0; - timediff_t connect_timeout_ms = 0; - timediff_t maxtime_timeout_ms = 0; - timediff_t timeout_ms = 0; + timediff_t timeleft_ms = 0; + timediff_t ctimeleft_ms = 0; struct curltime now; /* The duration of a connect and the total transfer are calculated from two @@ -116,61 +129,107 @@ timediff_t Curl_timeleft(struct Curl_easy *data, before the connect timeout expires and we must acknowledge whichever timeout that is reached first. The total timeout is set per entire operation, while the connect timeout is set per connect. */ + if(data->set.timeout <= 0 && !duringconnect) + return 0; /* no timeout in place or checked, return "no limit" */ + + if(!nowp) { + now = curlx_now(); + nowp = &now; + } if(data->set.timeout > 0) { - timeout_set = TIMEOUT_MAXTIME; - maxtime_timeout_ms = data->set.timeout; + timeleft_ms = data->set.timeout - + curlx_timediff(*nowp, data->progress.t_startop); + if(!timeleft_ms) + timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!duringconnect) + return timeleft_ms; /* no connect check, this is it */ } + if(duringconnect) { - timeout_set |= TIMEOUT_CONNECT; - connect_timeout_ms = (data->set.connecttimeout > 0) ? + timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; + ctimeleft_ms = ctimeout_ms - + curlx_timediff(*nowp, data->progress.t_startsingle); + if(!ctimeleft_ms) + ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!timeleft_ms) + return ctimeleft_ms; /* no general timeout, this is it */ } - if(!timeout_set) - /* no timeout */ - return 0; + /* return minimal time left or max amount already expired */ + return (ctimeleft_ms < timeleft_ms) ? ctimeleft_ms : timeleft_ms; +} + +void Curl_shutdown_start(struct Curl_easy *data, int sockindex, + int timeout_ms, struct curltime *nowp) +{ + struct curltime now; + DEBUGASSERT(data->conn); if(!nowp) { - now = Curl_now(); + now = curlx_now(); nowp = &now; } + data->conn->shutdown.start[sockindex] = *nowp; + data->conn->shutdown.timeout_ms = (timeout_ms > 0) ? + (unsigned int)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 && data->conn->shutdown.timeout_ms) + Curl_expire_ex(data, nowp, data->conn->shutdown.timeout_ms, + EXPIRE_SHUTDOWN); +} - if(timeout_set & TIMEOUT_MAXTIME) { - maxtime_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startop); - timeout_ms = maxtime_timeout_ms; - } +timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, + struct curltime *nowp) +{ + struct curltime now; + timediff_t left_ms; - if(timeout_set & TIMEOUT_CONNECT) { - connect_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startsingle); + if(!conn->shutdown.start[sockindex].tv_sec || !conn->shutdown.timeout_ms) + return 0; /* not started or no limits */ - if(!(timeout_set & TIMEOUT_MAXTIME) || - (connect_timeout_ms < maxtime_timeout_ms)) - timeout_ms = connect_timeout_ms; + if(!nowp) { + now = curlx_now(); + nowp = &now; } + left_ms = conn->shutdown.timeout_ms - + curlx_timediff(*nowp, conn->shutdown.start[sockindex]); + return left_ms ? left_ms : -1; +} + +timediff_t Curl_conn_shutdown_timeleft(struct connectdata *conn, + struct curltime *nowp) +{ + timediff_t left_ms = 0, ms; + struct curltime now; + int i; - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; + for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { + if(!conn->shutdown.start[i].tv_sec) + continue; + if(!nowp) { + now = curlx_now(); + nowp = &now; + } + ms = Curl_shutdown_timeleft(conn, i, nowp); + if(ms && (!left_ms || ms < left_ms)) + left_ms = ms; + } + return left_ms; +} - return timeout_ms; +void Curl_shutdown_clear(struct Curl_easy *data, int sockindex) +{ + struct curltime *pt = &data->conn->shutdown.start[sockindex]; + memset(pt, 0, sizeof(*pt)); } -/* Copies connection info into the transfer handle to make it available when - the transfer handle is no longer associated with the connection. */ -void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, - char *local_ip, int local_port) +bool Curl_shutdown_started(struct Curl_easy *data, int sockindex) { - memcpy(data->info.conn_primary_ip, conn->primary_ip, MAX_IPADR_LEN); - if(local_ip && local_ip[0]) - memcpy(data->info.conn_local_ip, local_ip, MAX_IPADR_LEN); - else - data->info.conn_local_ip[0] = 0; - data->info.conn_scheme = conn->handler->scheme; - /* conn_protocol can only provide "old" protocols */ - data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; - data->info.conn_primary_port = conn->port; - data->info.conn_remote_port = conn->remote_port; - data->info.conn_local_port = local_port; + struct curltime *pt = &data->conn->shutdown.start[sockindex]; + return (pt->tv_sec > 0) || (pt->tv_usec > 0); } static const struct Curl_addrinfo * @@ -201,7 +260,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, char *addr, int *port) { struct sockaddr_in *si = NULL; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *si6 = NULL; #endif #if (defined(HAVE_SYS_UN_H) || defined(WIN32_SOCKADDR_UN)) && defined(AF_UNIX) @@ -213,18 +272,16 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, switch(sa->sa_family) { case AF_INET: si = (struct sockaddr_in *)(void *) sa; - if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, - addr, MAX_IPADR_LEN)) { + if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) { unsigned short us_port = ntohs(si->sin_port); *port = us_port; return TRUE; } break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: si6 = (struct sockaddr_in6 *)(void *) sa; - if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, - addr, MAX_IPADR_LEN)) { + if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) { unsigned short us_port = ntohs(si6->sin6_port); *port = us_port; return TRUE; @@ -248,27 +305,10 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, addr[0] = '\0'; *port = 0; - errno = EAFNOSUPPORT; + CURL_SETERRNO(SOCKEAFNOSUPPORT); return FALSE; } -struct connfind { - long id_tofind; - struct connectdata *found; -}; - -static int conn_is_conn(struct Curl_easy *data, - struct connectdata *conn, void *param) -{ - struct connfind *f = (struct connfind *)param; - (void)data; - if(conn->connection_id == f->id_tofind) { - f->found = conn; - return 1; - } - return 0; -} - /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. @@ -285,30 +325,19 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, * - that is associated with a multi handle, and whose connection * was detached with CURLOPT_CONNECT_ONLY */ - if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) { - struct connectdata *c; - struct connfind find; - find.id_tofind = data->state.lastconnect_id; - find.found = NULL; - - Curl_conncache_foreach(data, - data->share && (data->share->specifier - & (1<< CURL_LOCK_DATA_CONNECT))? - &data->share->conn_cache: - data->multi_easy? - &data->multi_easy->conn_cache: - &data->multi->conn_cache, &find, conn_is_conn); - - if(!find.found) { + 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; return CURL_SOCKET_BAD; } - c = find.found; if(connp) /* only store this if the caller cares for it */ - *connp = c; - return c->sock[FIRSTSOCKET]; + *connp = conn; + return conn->sock[FIRSTSOCKET]; } return CURL_SOCKET_BAD; } @@ -323,7 +352,7 @@ void Curl_conncontrol(struct connectdata *conn, #endif ) { - /* close if a connection, or a stream that isn't multiplexed. */ + /* close if a connection, or a stream that is not multiplexed. */ /* This function will be called both before and after this connection is associated with a transfer. */ bool closeit, is_multiplex; @@ -348,6 +377,7 @@ void Curl_conncontrol(struct connectdata *conn, */ struct eyeballer { const char *name; + const struct Curl_addrinfo *first; /* complete address list, not owned */ const struct Curl_addrinfo *addr; /* List of addresses to try, not owned */ int ai_family; /* matching address family only */ cf_ip_connect_create *cf_create; /* for creating cf */ @@ -359,9 +389,13 @@ struct eyeballer { expire_id timeout_id; /* ID for Curl_expire() */ CURLcode result; int error; + BIT(rewinded); /* if we rewinded the addr list */ BIT(has_started); /* attempts have started */ BIT(is_done); /* out of addresses/time */ BIT(connected); /* cf has connected */ + BIT(shutdown); /* cf has shutdown */ + BIT(inconclusive); /* connect was not a hard failure, we + * might talk to a restarting server */ }; @@ -374,13 +408,17 @@ typedef enum { struct cf_he_ctx { int transport; cf_ip_connect_create *cf_create; - const struct Curl_dns_entry *remotehost; cf_connect_state state; struct eyeballer *baller[2]; struct eyeballer *winner; struct curltime started; }; +/* when there are more than one IP address left to use, this macro returns how + much of the given timeout to spend on *this* attempt */ +#define TIMEOUT_LARGE 600 +#define USETIME(ms) ((ms > TIMEOUT_LARGE) ? (ms / 2) : ms) + static CURLcode eyeballer_new(struct eyeballer **pballer, cf_ip_connect_create *cf_create, const struct Curl_addrinfo *addr, @@ -393,22 +431,22 @@ static CURLcode eyeballer_new(struct eyeballer **pballer, struct eyeballer *baller; *pballer = NULL; - baller = calloc(1, sizeof(*baller) + 1000); + baller = calloc(1, sizeof(*baller)); if(!baller) return CURLE_OUT_OF_MEMORY; - baller->name = ((ai_family == AF_INET)? "ipv4" : ( -#ifdef ENABLE_IPV6 - (ai_family == AF_INET6)? "ipv6" : + baller->name = ((ai_family == AF_INET) ? "ipv4" : ( +#ifdef USE_IPV6 + (ai_family == AF_INET6) ? "ipv6" : #endif "ip")); baller->cf_create = cf_create; - baller->addr = addr; + baller->first = baller->addr = addr; baller->ai_family = ai_family; baller->primary = primary; baller->delay_ms = delay_ms; - baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)? - timeout_ms / 2 : timeout_ms; + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ? + USETIME(timeout_ms) : timeout_ms; baller->timeout_id = timeout_id; baller->result = CURLE_COULDNT_CONNECT; @@ -433,6 +471,13 @@ static void baller_free(struct eyeballer *baller, } } +static void baller_rewind(struct eyeballer *baller) +{ + baller->rewinded = TRUE; + baller->addr = baller->first; + baller->inconclusive = FALSE; +} + static void baller_next_addr(struct eyeballer *baller) { baller->addr = addr_next_match(baller->addr, baller->ai_family); @@ -454,7 +499,7 @@ static void baller_initiate(struct Curl_cfilter *cf, CURLcode result; - /* Don't close a previous cfilter yet to ensure that the next IP's + /* Do not close a previous cfilter yet to ensure that the next IP's socket gets a different file descriptor, which can prevent bugs when the curl_multi_socket_action interface is used with certain select() replacements such as kqueue. */ @@ -475,7 +520,7 @@ static void baller_initiate(struct Curl_cfilter *cf, out: if(result) { - DEBUGF(LOG_CF(data, cf, "%s failed", baller->name)); + CURL_TRC_CF(data, cf, "%s failed", baller->name); baller_close(baller, data); } if(cf_prev) @@ -499,9 +544,9 @@ static CURLcode baller_start(struct Curl_cfilter *cf, baller->has_started = TRUE; while(baller->addr) { - baller->started = Curl_now(); + baller->started = curlx_now(); baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ? - timeoutms / 2 : timeoutms; + USETIME(timeoutms) : timeoutms; baller_initiate(cf, data, baller); if(!baller->result) break; @@ -523,6 +568,12 @@ static CURLcode baller_start_next(struct Curl_cfilter *cf, { if(cf->sockindex == FIRSTSOCKET) { baller_next_addr(baller); + /* If we get inconclusive answers from the server(s), we start + * again until this whole thing times out. This allows us to + * connect to servers that are gracefully restarting and the + * packet routing to the new instance has not happened yet (e.g. QUIC). */ + if(!baller->addr && baller->inconclusive) + baller_rewind(baller); baller_start(cf, data, baller, timeoutms); } else { @@ -545,22 +596,24 @@ static CURLcode baller_connect(struct Curl_cfilter *cf, *connected = baller->connected; if(!baller->result && !*connected) { /* evaluate again */ - baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected); + baller->result = Curl_conn_cf_connect(baller->cf, data, connected); if(!baller->result) { if(*connected) { baller->connected = TRUE; baller->is_done = TRUE; } - else if(Curl_timediff(*now, baller->started) >= baller->timeoutms) { - infof(data, "%s connect timeout after %" CURL_FORMAT_TIMEDIFF_T + else if(curlx_timediff(*now, baller->started) >= baller->timeoutms) { + infof(data, "%s connect timeout after %" FMT_TIMEDIFF_T "ms, move on!", baller->name, baller->timeoutms); -#if defined(ETIMEDOUT) - baller->error = ETIMEDOUT; +#ifdef SOCKETIMEDOUT + baller->error = SOCKETIMEDOUT; #endif baller->result = CURLE_OPERATION_TIMEDOUT; } } + else if(baller->result == CURLE_WEIRD_SERVER_REPLY) + baller->inconclusive = TRUE; } return baller->result; } @@ -587,10 +640,10 @@ static CURLcode is_connected(struct Curl_cfilter *cf, * If transport is QUIC, we need to shutdown the ongoing 'other' * cot ballers in a QUIC appropriate way. */ evaluate: - *connected = FALSE; /* a very negative world view is best */ - now = Curl_now(); + *connected = FALSE; /* a negative world view is best */ + now = curlx_now(); ongoing = not_started = 0; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; if(!baller || baller->is_done) @@ -601,8 +654,8 @@ static CURLcode is_connected(struct Curl_cfilter *cf, continue; } baller->result = baller_connect(cf, data, baller, &now, connected); - DEBUGF(LOG_CF(data, cf, "%s connect -> %d, connected=%d", - baller->name, baller->result, *connected)); + CURL_TRC_CF(data, cf, "%s connect -> %d, connected=%d", + baller->name, baller->result, *connected); if(!baller->result) { if(*connected) { @@ -623,12 +676,13 @@ static CURLcode is_connected(struct Curl_cfilter *cf, } baller_start_next(cf, data, baller, Curl_timeleft(data, &now, TRUE)); if(baller->is_done) { - DEBUGF(LOG_CF(data, cf, "%s done", baller->name)); + CURL_TRC_CF(data, cf, "%s done", baller->name); } else { /* next attempt was started */ - DEBUGF(LOG_CF(data, cf, "%s trying next", baller->name)); + CURL_TRC_CF(data, cf, "%s trying next", baller->name); ++ongoing; + Curl_expire(data, 0, EXPIRE_RUN_NOW); } } } @@ -641,8 +695,8 @@ static CURLcode is_connected(struct Curl_cfilter *cf, /* Nothing connected, check the time before we might * start new ballers or return ok. */ if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) { - failf(data, "Connection timeout after %ld ms", - Curl_timediff(now, data->progress.t_startsingle)); + failf(data, "Connection timeout after %" FMT_OFF_T " ms", + curlx_timediff(now, data->progress.t_startsingle)); return CURLE_OPERATION_TIMEDOUT; } @@ -650,7 +704,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, if(not_started > 0) { int added = 0; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; if(!baller || baller->has_started) @@ -658,15 +712,14 @@ static CURLcode is_connected(struct Curl_cfilter *cf, /* We start its primary baller has failed to connect or if * its start delay_ms have expired */ if((baller->primary && baller->primary->is_done) || - Curl_timediff(now, ctx->started) >= baller->delay_ms) { + curlx_timediff(now, ctx->started) >= baller->delay_ms) { baller_start(cf, data, baller, Curl_timeleft(data, &now, TRUE)); if(baller->is_done) { - DEBUGF(LOG_CF(data, cf, "%s done", baller->name)); + CURL_TRC_CF(data, cf, "%s done", baller->name); } else { - DEBUGF(LOG_CF(data, cf, "%s starting (timeout=%" - CURL_FORMAT_TIMEDIFF_T "ms)", - baller->name, baller->timeoutms)); + CURL_TRC_CF(data, cf, "%s starting (timeout=%" FMT_TIMEDIFF_T "ms)", + baller->name, baller->timeoutms); ++ongoing; ++added; } @@ -683,15 +736,15 @@ static CURLcode is_connected(struct Curl_cfilter *cf, } /* all ballers have failed to connect. */ - DEBUGF(LOG_CF(data, cf, "all eyeballers failed")); + CURL_TRC_CF(data, cf, "all eyeballers failed"); result = CURLE_COULDNT_CONNECT; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; - DEBUGF(LOG_CF(data, cf, "%s assess started=%d, result=%d", - baller?baller->name:NULL, - baller?baller->has_started:0, - baller?baller->result:0)); - if(baller && baller->has_started && baller->result) { + if(!baller) + continue; + CURL_TRC_CF(data, cf, "%s assess started=%d, result=%d", + baller->name, baller->has_started, baller->result); + if(baller->has_started && baller->result) { result = baller->result; break; } @@ -710,16 +763,13 @@ static CURLcode is_connected(struct Curl_cfilter *cf, hostname = conn->host.name; failf(data, "Failed to connect to %s port %u after " - "%" CURL_FORMAT_TIMEDIFF_T " ms: %s", - hostname, conn->port, - Curl_timediff(now, data->progress.t_startsingle), + "%" FMT_TIMEDIFF_T " ms: %s", + hostname, conn->primary.remote_port, + curlx_timediff(now, data->progress.t_startsingle), curl_easy_strerror(result)); -#ifdef WSAETIMEDOUT - if(WSAETIMEDOUT == data->state.os_errno) - result = CURLE_OPERATION_TIMEDOUT; -#elif defined(ETIMEDOUT) - if(ETIMEDOUT == data->state.os_errno) +#ifdef SOCKETIMEDOUT + if(SOCKETIMEDOUT == data->state.os_errno) result = CURLE_OPERATION_TIMEDOUT; #endif @@ -727,19 +777,22 @@ static CURLcode is_connected(struct Curl_cfilter *cf, } /* - * Connect to the given host with timeout, proxy or remote doesn't matter. + * Connect to the given host with timeout, proxy or remote does not matter. * There might be more than one IP address to try out. */ static CURLcode start_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - const struct Curl_dns_entry *remotehost) + struct Curl_easy *data) { struct cf_he_ctx *ctx = cf->ctx; struct connectdata *conn = cf->conn; CURLcode result = CURLE_COULDNT_CONNECT; - int ai_family0, ai_family1; + int ai_family0 = 0, ai_family1 = 0; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - const struct Curl_addrinfo *addr0, *addr1; + const struct Curl_addrinfo *addr0 = NULL, *addr1 = NULL; + struct Curl_dns_entry *dns = data->state.dns[cf->sockindex]; + + if(!dns) + return CURLE_FAILED_INIT; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ @@ -747,9 +800,9 @@ static CURLcode start_connect(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } - ctx->started = Curl_now(); + ctx->started = curlx_now(); - /* remotehost->addr is the list of addresses from the resolver, each + /* dns->addr is the list of addresses from the resolver, each * with an address family. The list has at least one entry, possibly * many more. * We try at most 2 at a time, until we either get a connection or @@ -758,33 +811,33 @@ static CURLcode start_connect(struct Curl_cfilter *cf, * the 2 connect attempt ballers to try different families, if possible. * */ - if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) { - /* any IP version is allowed */ - ai_family0 = remotehost->addr? - remotehost->addr->ai_family : 0; -#ifdef ENABLE_IPV6 - ai_family1 = ai_family0 == AF_INET6 ? - AF_INET : AF_INET6; -#else - ai_family1 = AF_UNSPEC; + if(conn->ip_version == CURL_IPRESOLVE_V6) { +#ifdef USE_IPV6 + ai_family0 = AF_INET6; + addr0 = addr_first_match(dns->addr, ai_family0); #endif } + else if(conn->ip_version == CURL_IPRESOLVE_V4) { + ai_family0 = AF_INET; + addr0 = addr_first_match(dns->addr, ai_family0); + } else { - /* only one IP version is allowed */ - ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ? - AF_INET : -#ifdef ENABLE_IPV6 - AF_INET6; -#else - AF_UNSPEC; + /* no user preference, we try ipv6 always first when available */ +#ifdef USE_IPV6 + ai_family0 = AF_INET6; + addr0 = addr_first_match(dns->addr, ai_family0); #endif - ai_family1 = AF_UNSPEC; + /* next candidate is ipv4 */ + ai_family1 = AF_INET; + addr1 = addr_first_match(dns->addr, ai_family1); + /* no ip address families, probably AF_UNIX or something, use the + * address family given to us */ + if(!addr1 && !addr0 && dns->addr) { + ai_family0 = dns->addr->ai_family; + addr0 = addr_first_match(dns->addr, ai_family0); + } } - /* Get the first address in the list that matches the family, - * this might give NULL, if we do not have any matches. */ - addr0 = addr_first_match(remotehost->addr, ai_family0); - addr1 = addr_first_match(remotehost->addr, ai_family1); if(!addr0 && addr1) { /* switch around, so a single baller always uses addr0 */ addr0 = addr1; @@ -803,9 +856,8 @@ static CURLcode start_connect(struct Curl_cfilter *cf, timeout_ms, EXPIRE_DNS_PER_NAME); if(result) return result; - DEBUGF(LOG_CF(data, cf, "created %s (timeout %" - CURL_FORMAT_TIMEDIFF_T "ms)", - ctx->baller[0]->name, ctx->baller[0]->timeoutms)); + CURL_TRC_CF(data, cf, "created %s (timeout %" FMT_TIMEDIFF_T "ms)", + ctx->baller[0]->name, ctx->baller[0]->timeoutms); if(addr1) { /* second one gets a delayed start */ result = eyeballer_new(&ctx->baller[1], ctx->cf_create, addr1, ai_family1, @@ -815,14 +867,12 @@ static CURLcode start_connect(struct Curl_cfilter *cf, timeout_ms, EXPIRE_DNS_PER_NAME2); if(result) return result; - DEBUGF(LOG_CF(data, cf, "created %s (timeout %" - CURL_FORMAT_TIMEDIFF_T "ms)", - ctx->baller[1]->name, ctx->baller[1]->timeoutms)); + CURL_TRC_CF(data, cf, "created %s (timeout %" FMT_TIMEDIFF_T "ms)", + ctx->baller[1]->name, ctx->baller[1]->timeoutms); + Curl_expire(data, data->set.happy_eyeballs_timeout, + EXPIRE_HAPPY_EYEBALLS); } - Curl_expire(data, data->set.happy_eyeballs_timeout, - EXPIRE_HAPPY_EYEBALLS); - return CURLE_OK; } @@ -833,7 +883,7 @@ static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(ctx); DEBUGASSERT(data); - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { baller_free(ctx->baller[i], data); ctx->baller[i] = NULL; } @@ -841,40 +891,67 @@ static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) ctx->winner = NULL; } -static int cf_he_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static CURLcode cf_he_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_he_ctx *ctx = cf->ctx; - size_t i, s; - int wrc, rc = GETSOCK_BLANK; - curl_socket_t wsocks[MAX_SOCKSPEREASYHANDLE]; + size_t i; + CURLcode result = CURLE_OK; - if(cf->connected) - return cf->next->cft->get_select_socks(cf->next, data, socks); + DEBUGASSERT(data); + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } - for(i = s = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + /* shutdown all ballers that have not done so already. If one fails, + * continue shutting down others until all are shutdown. */ + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; - if(!baller || !baller->cf) + bool bdone = FALSE; + if(!baller || !baller->cf || baller->shutdown) continue; + baller->result = baller->cf->cft->do_shutdown(baller->cf, data, &bdone); + if(baller->result || bdone) + baller->shutdown = TRUE; /* treat a failed shutdown as done */ + } - wrc = Curl_conn_cf_get_select_socks(baller->cf, data, wsocks); - if(wrc) { - /* TODO: we assume we get at most one socket back */ - socks[s] = wsocks[0]; - if(wrc & GETSOCK_WRITESOCK(0)) - rc |= GETSOCK_WRITESOCK(s); - if(wrc & GETSOCK_READSOCK(0)) - rc |= GETSOCK_READSOCK(s); - s++; + *done = TRUE; + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { + if(ctx->baller[i] && !ctx->baller[i]->shutdown) + *done = FALSE; + } + if(*done) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { + if(ctx->baller[i] && ctx->baller[i]->result) + result = ctx->baller[i]->result; } } - return rc; + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + return result; +} + +static void cf_he_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + + if(!cf->connected) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; + Curl_conn_cf_adjust_pollset(baller->cf, data, ps); + } + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); + } } static CURLcode cf_he_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_he_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -884,7 +961,6 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, return CURLE_OK; } - (void)blocking; /* TODO: do we want to support this? */ DEBUGASSERT(ctx); *done = FALSE; @@ -892,11 +968,11 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, case SCFST_INIT: DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data)); DEBUGASSERT(!cf->connected); - result = start_connect(cf, data, ctx->remotehost); + result = start_connect(cf, data); if(result) return result; ctx->state = SCFST_WAITING; - /* FALLTHROUGH */ + FALLTHROUGH(); case SCFST_WAITING: result = is_connected(cf, data, done); if(!result && *done) { @@ -910,12 +986,20 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, cf->next = ctx->winner->cf; ctx->winner->cf = NULL; cf_he_ctx_clear(cf, data); - Curl_conn_cf_cntrl(cf->next, data, TRUE, - CF_CTRL_CONN_INFO_UPDATE, 0, NULL); if(cf->conn->handler->protocol & PROTO_FAMILY_SSH) - Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ - Curl_verboseconnect(data, cf->conn); + Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */ + if(Curl_trc_cf_is_verbose(cf, data)) { + struct ip_quadruple ipquad; + int is_ipv6; + if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) { + const char *host, *disphost; + int port; + cf->next->cft->get_host(cf->next, data, &host, &disphost, &port); + CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u", + disphost, ipquad.remote_ip, ipquad.remote_port); + } + } data->info.numconnects++; /* to track the # of connections made */ } break; @@ -931,13 +1015,13 @@ static void cf_he_close(struct Curl_cfilter *cf, { struct cf_he_ctx *ctx = cf->ctx; - DEBUGF(LOG_CF(data, cf, "close")); + CURL_TRC_CF(data, cf, "close"); cf_he_ctx_clear(cf, data); cf->connected = FALSE; ctx->state = SCFST_INIT; if(cf->next) { - cf->next->cft->close(cf->next, data); + cf->next->cft->do_close(cf->next, data); Curl_conn_cf_discard_chain(&cf->next, data); } } @@ -951,7 +1035,7 @@ static bool cf_he_data_pending(struct Curl_cfilter *cf, if(cf->connected) return cf->next->cft->has_data_pending(cf->next, data); - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; if(!baller || !baller->cf) continue; @@ -970,13 +1054,13 @@ static struct curltime get_max_baller_time(struct Curl_cfilter *cf, size_t i; memset(&tmax, 0, sizeof(tmax)); - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; memset(&t, 0, sizeof(t)); if(baller && baller->cf && !baller->cf->cft->query(baller->cf, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) + if((t.tv_sec || t.tv_usec) && curlx_timediff_us(t, tmax) > 0) tmax = t; } } @@ -995,7 +1079,7 @@ static CURLcode cf_he_query(struct Curl_cfilter *cf, int reply_ms = -1; size_t i; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; int breply_ms; @@ -1007,7 +1091,7 @@ static CURLcode cf_he_query(struct Curl_cfilter *cf, } } *pres1 = reply_ms; - DEBUGF(LOG_CF(data, cf, "query connect reply: %dms", *pres1)); + CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1); return CURLE_OK; } case CF_QUERY_TIMER_CONNECT: { @@ -1025,7 +1109,7 @@ static CURLcode cf_he_query(struct Curl_cfilter *cf, } } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1034,7 +1118,7 @@ static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_he_ctx *ctx = cf->ctx; - DEBUGF(LOG_CF(data, cf, "destroy")); + CURL_TRC_CF(data, cf, "destroy"); if(ctx) { cf_he_ctx_clear(cf, data); } @@ -1045,12 +1129,13 @@ static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cftype Curl_cft_happy_eyeballs = { "HAPPY-EYEBALLS", 0, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, cf_he_destroy, cf_he_connect, cf_he_close, + cf_he_shutdown, Curl_cf_def_get_host, - cf_he_get_select_socks, + cf_he_adjust_pollset, cf_he_data_pending, Curl_cf_def_send, Curl_cf_def_recv, @@ -1075,7 +1160,6 @@ cf_happy_eyeballs_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, cf_ip_connect_create *cf_create, - const struct Curl_dns_entry *remotehost, int transport) { struct cf_he_ctx *ctx = NULL; @@ -1084,21 +1168,20 @@ cf_happy_eyeballs_create(struct Curl_cfilter **pcf, (void)data; (void)conn; *pcf = NULL; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } ctx->transport = transport; ctx->cf_create = cf_create; - ctx->remotehost = remotehost; result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx); out: if(result) { Curl_safefree(*pcf); - Curl_safefree(ctx); + free(ctx); } return result; } @@ -1109,26 +1192,26 @@ struct transport_provider { }; static -#ifndef DEBUGBUILD +#ifndef UNITTESTS const #endif struct transport_provider transport_providers[] = { { TRNSPRT_TCP, Curl_cf_tcp_create }, -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 { TRNSPRT_QUIC, Curl_cf_quic_create }, #endif +#ifndef CURL_DISABLE_TFTP { TRNSPRT_UDP, Curl_cf_udp_create }, +#endif +#ifdef USE_UNIX_SOCKETS { TRNSPRT_UNIX, Curl_cf_unix_create }, -}; - -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif +}; static cf_ip_connect_create *get_cf_create(int transport) { size_t i; - for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) { if(transport == transport_providers[i].transport) return transport_providers[i].cf_create; } @@ -1137,7 +1220,6 @@ static cf_ip_connect_create *get_cf_create(int transport) static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport) { cf_ip_connect_create *cf_create; @@ -1148,12 +1230,11 @@ static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at, DEBUGASSERT(cf_at); cf_create = get_cf_create(transport); if(!cf_create) { - DEBUGF(LOG_CF(data, cf_at, "unsupported transport type %d", transport)); + CURL_TRC_CF(data, cf_at, "unsupported transport type %d", transport); return CURLE_UNSUPPORTED_PROTOCOL; } result = cf_happy_eyeballs_create(&cf, data, cf_at->conn, - cf_create, remotehost, - transport); + cf_create, transport); if(result) return result; @@ -1173,17 +1254,17 @@ typedef enum { struct cf_setup_ctx { cf_setup_state state; - const struct Curl_dns_entry *remotehost; int ssl_mode; int transport; }; static CURLcode cf_setup_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + struct Curl_dns_entry *dns = data->state.dns[cf->sockindex]; if(cf->connected) { *done = TRUE; @@ -1192,14 +1273,17 @@ static CURLcode cf_setup_connect(struct Curl_cfilter *cf, /* connect current sub-chain */ connect_sub_chain: + if(!dns) + return CURLE_FAILED_INIT; + if(cf->next && !cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { - result = cf_he_insert_after(cf, data, ctx->remotehost, ctx->transport); + result = cf_he_insert_after(cf, data, ctx->transport); if(result) return result; ctx->state = CF_SETUP_CNNCT_EYEBALLS; @@ -1286,12 +1370,12 @@ static void cf_setup_close(struct Curl_cfilter *cf, { struct cf_setup_ctx *ctx = cf->ctx; - DEBUGF(LOG_CF(data, cf, "close")); + CURL_TRC_CF(data, cf, "close"); cf->connected = FALSE; ctx->state = CF_SETUP_INIT; if(cf->next) { - cf->next->cft->close(cf->next, data); + cf->next->cft->do_close(cf->next, data); Curl_conn_cf_discard_chain(&cf->next, data); } } @@ -1301,7 +1385,7 @@ static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_setup_ctx *ctx = cf->ctx; (void)data; - DEBUGF(LOG_CF(data, cf, "destroy")); + CURL_TRC_CF(data, cf, "destroy"); Curl_safefree(ctx); } @@ -1309,12 +1393,13 @@ static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cftype Curl_cft_setup = { "SETUP", 0, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, cf_setup_destroy, cf_setup_connect, cf_setup_close, + Curl_cf_def_shutdown, Curl_cf_def_get_host, - Curl_cf_def_get_select_socks, + Curl_cf_def_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, @@ -1326,7 +1411,6 @@ struct Curl_cftype Curl_cft_setup = { static CURLcode cf_setup_create(struct Curl_cfilter **pcf, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode) { @@ -1335,13 +1419,12 @@ static CURLcode cf_setup_create(struct Curl_cfilter **pcf, CURLcode result = CURLE_OK; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } ctx->state = CF_SETUP_INIT; - ctx->remotehost = remotehost; ctx->ssl_mode = ssl_mode; ctx->transport = transport; @@ -1351,15 +1434,16 @@ static CURLcode cf_setup_create(struct Curl_cfilter **pcf, ctx = NULL; out: - *pcf = result? NULL : cf; - free(ctx); + *pcf = result ? NULL : cf; + if(ctx) { + free(ctx); + } return result; } static CURLcode cf_setup_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode) { @@ -1367,7 +1451,7 @@ static CURLcode cf_setup_add(struct Curl_easy *data, CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + result = cf_setup_create(&cf, data, transport, ssl_mode); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); @@ -1375,24 +1459,23 @@ static CURLcode cf_setup_add(struct Curl_easy *data, return result; } -#ifdef DEBUGBUILD +#ifdef UNITTESTS /* used by unit2600.c */ void Curl_debug_set_transport_provider(int transport, cf_ip_connect_create *cf_create) { size_t i; - for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) { if(transport == transport_providers[i].transport) { transport_providers[i].cf_create = cf_create; return; } } } -#endif /* DEBUGBUILD */ +#endif /* UNITTESTS */ CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode) { @@ -1400,7 +1483,7 @@ CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, CURLcode result; DEBUGASSERT(data); - result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + result = cf_setup_create(&cf, data, transport, ssl_mode); if(result) goto out; Curl_conn_cf_insert_after(cf_at, cf); @@ -1411,34 +1494,38 @@ CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, CURLcode Curl_conn_setup(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, + struct Curl_dns_entry *dns, int ssl_mode) { CURLcode result = CURLE_OK; DEBUGASSERT(data); DEBUGASSERT(conn->handler); + DEBUGASSERT(dns); + + Curl_resolv_unlink(data, &data->state.dns[sockindex]); + data->state.dns[sockindex] = dns; -#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) +#if !defined(CURL_DISABLE_HTTP) if(!conn->cfilter[sockindex] && conn->handler->protocol == CURLPROTO_HTTPS) { DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); - result = Curl_cf_https_setup(data, conn, sockindex, remotehost); + result = Curl_cf_https_setup(data, conn, sockindex); if(result) goto out; } -#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ +#endif /* !defined(CURL_DISABLE_HTTP) */ /* Still no cfilter set, apply default. */ if(!conn->cfilter[sockindex]) { - result = cf_setup_add(data, conn, sockindex, remotehost, - conn->transport, ssl_mode); + result = cf_setup_add(data, conn, sockindex, conn->transport, ssl_mode); if(result) goto out; } DEBUGASSERT(conn->cfilter[sockindex]); out: + if(result) + Curl_resolv_unlink(data, &data->state.dns[sockindex]); return result; } - diff --git a/Utilities/cmcurl/lib/connect.h b/Utilities/cmcurl/lib/connect.h index 58264bdba48..120338eb99f 100644 --- a/Utilities/cmcurl/lib/connect.h +++ b/Utilities/cmcurl/lib/connect.h @@ -25,13 +25,16 @@ ***************************************************************************/ #include "curl_setup.h" -#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */ +#include "curlx/nonblock.h" /* for curlx_nonblock() */ #include "sockaddr.h" -#include "timeval.h" +#include "curlx/timeval.h" struct Curl_dns_entry; +struct ip_quadruple; -/* generic function that returns how much time there's left to run, according +enum alpnid Curl_alpn2alpnid(const char *name, size_t len); + +/* generic function that returns how much time there is left to run, according to the timeouts set */ timediff_t Curl_timeleft(struct Curl_easy *data, struct curltime *nowp, @@ -39,6 +42,26 @@ timediff_t Curl_timeleft(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, int sockindex, + int timeout_ms, struct curltime *nowp); + +/* 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 connectdata *conn, int sockindex, + struct curltime *nowp); + +/* 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 connectdata *conn, + struct curltime *nowp); + +void Curl_shutdown_clear(struct Curl_easy *data, int sockindex); + +/* TRUE iff shutdown has been started */ +bool Curl_shutdown_started(struct Curl_easy *data, int sockindex); + /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. @@ -51,9 +74,6 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, char *addr, int *port); -void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, - char *local_ip, int local_port); - /* * Curl_conncontrol() marks the end of a connection/stream. The 'closeit' * argument specifies if it is the end of a connection or a stream. @@ -106,7 +126,6 @@ typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode); @@ -118,13 +137,13 @@ CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, CURLcode Curl_conn_setup(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, + struct Curl_dns_entry *dns, int ssl_mode); extern struct Curl_cftype Curl_cft_happy_eyeballs; extern struct Curl_cftype Curl_cft_setup; -#ifdef DEBUGBUILD +#ifdef UNITTESTS void Curl_debug_set_transport_provider(int transport, cf_ip_connect_create *cf_create); #endif diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c index 5b2fc6f8737..ac80478f0a6 100644 --- a/Utilities/cmcurl/lib/content_encoding.c +++ b/Utilities/cmcurl/lib/content_encoding.c @@ -33,13 +33,13 @@ #endif #ifdef HAVE_BROTLI -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) /* Ignore -Wvla warnings in brotli headers */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvla" #endif #include -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif @@ -63,40 +63,32 @@ #ifndef CURL_DISABLE_HTTP -#define DSIZ CURL_MAX_WRITE_SIZE /* buffer size for decompressed data */ +/* allow no more than 5 "chained" compression steps */ +#define MAX_ENCODE_STACK 5 +#if defined(HAVE_LIBZ) || defined(HAVE_BROTLI) || defined(HAVE_ZSTD) +#define DECOMPRESS_BUFFER_SIZE 16384 /* buffer size for decompressed data */ +#endif #ifdef HAVE_LIBZ -/* Comment this out if zlib is always going to be at least ver. 1.2.0.4 - (doing so will reduce code size slightly). */ -#define OLD_ZLIB_SUPPORT 1 - -#define GZIP_MAGIC_0 0x1f -#define GZIP_MAGIC_1 0x8b - -/* gzip flag byte */ -#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ -#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ -#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ -#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ -#define COMMENT 0x10 /* bit 4 set: file comment present */ -#define RESERVED 0xE0 /* bits 5..7: reserved */ +#if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1252) +#error "requires zlib 1.2.5.2 or newer" +#endif typedef enum { ZLIB_UNINIT, /* uninitialized */ ZLIB_INIT, /* initialized */ ZLIB_INFLATING, /* inflating started. */ ZLIB_EXTERNAL_TRAILER, /* reading external trailer */ - ZLIB_GZIP_HEADER, /* reading gzip header */ - ZLIB_GZIP_INFLATING, /* inflating gzip stream */ ZLIB_INIT_GZIP /* initialized in transparent gzip mode */ } zlibInitState; /* Deflate and gzip writer. */ struct zlib_writer { - struct contenc_writer super; + struct Curl_cwriter super; zlibInitState zlib_init; /* zlib init state */ + char buffer[DECOMPRESS_BUFFER_SIZE]; /* Put the decompressed data here. */ uInt trailerlen; /* Remaining trailer byte count. */ z_stream z; /* State structure for zlib. */ }; @@ -134,9 +126,6 @@ static CURLcode exit_zlib(struct Curl_easy *data, z_stream *z, zlibInitState *zlib_init, CURLcode result) { - if(*zlib_init == ZLIB_GZIP_HEADER) - Curl_safefree(z->next_in); - if(*zlib_init != ZLIB_UNINIT) { if(inflateEnd(z) != Z_OK && result == CURLE_OK) result = process_zlib_error(data, z); @@ -151,7 +140,7 @@ static CURLcode process_trailer(struct Curl_easy *data, { z_stream *z = &zp->z; CURLcode result = CURLE_OK; - uInt len = z->avail_in < zp->trailerlen? z->avail_in: zp->trailerlen; + uInt len = z->avail_in < zp->trailerlen ? z->avail_in : zp->trailerlen; /* Consume expected trailer bytes. Terminate stream if exhausted. Issue an error if unexpected bytes follow. */ @@ -171,54 +160,40 @@ static CURLcode process_trailer(struct Curl_easy *data, } static CURLcode inflate_stream(struct Curl_easy *data, - struct contenc_writer *writer, + struct Curl_cwriter *writer, int type, zlibInitState started) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ uInt nread = z->avail_in; - Bytef *orig_in = z->next_in; + z_const Bytef *orig_in = z->next_in; bool done = FALSE; CURLcode result = CURLE_OK; /* Curl_client_write status */ - char *decomp; /* Put the decompressed data here. */ /* Check state. */ if(zp->zlib_init != ZLIB_INIT && zp->zlib_init != ZLIB_INFLATING && - zp->zlib_init != ZLIB_INIT_GZIP && - zp->zlib_init != ZLIB_GZIP_INFLATING) + zp->zlib_init != ZLIB_INIT_GZIP) return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); - /* Dynamically allocate a buffer for decompression because it's uncommonly - large to hold on the stack */ - decomp = malloc(DSIZ); - if(!decomp) - return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); - /* because the buffer size is fixed, iteratively decompress and transfer to - the client via downstream_write function. */ + the client via next_write function. */ while(!done) { int status; /* zlib status */ done = TRUE; /* (re)set buffer for decompressed output for every iteration */ - z->next_out = (Bytef *) decomp; - z->avail_out = DSIZ; + z->next_out = (Bytef *) zp->buffer; + z->avail_out = DECOMPRESS_BUFFER_SIZE; -#ifdef Z_BLOCK - /* Z_BLOCK is only available in zlib ver. >= 1.2.0.5 */ status = inflate(z, Z_BLOCK); -#else - /* fallback for zlib ver. < 1.2.0.5 */ - status = inflate(z, Z_SYNC_FLUSH); -#endif /* Flush output data if some. */ - if(z->avail_out != DSIZ) { + if(z->avail_out != DECOMPRESS_BUFFER_SIZE) { if(status == Z_OK || status == Z_STREAM_END) { zp->zlib_init = started; /* Data started. */ - result = Curl_unencode_write(data, writer->downstream, decomp, - DSIZ - z->avail_out); + result = Curl_cwriter_write(data, writer->next, type, zp->buffer, + DECOMPRESS_BUFFER_SIZE - z->avail_out); if(result) { exit_zlib(data, z, &zp->zlib_init, result); break; @@ -242,9 +217,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* some servers seem to not generate zlib headers, so this is an attempt to fix and continue anyway */ if(zp->zlib_init == ZLIB_INIT) { - /* Do not use inflateReset2(): only available since zlib 1.2.3.4. */ - (void) inflateEnd(z); /* don't care about the return code */ - if(inflateInit2(z, -MAX_WBITS) == Z_OK) { + if(inflateReset2(z, -MAX_WBITS) == Z_OK) { z->next_in = orig_in; z->avail_in = nread; zp->zlib_init = ZLIB_INFLATING; @@ -261,9 +234,8 @@ static CURLcode inflate_stream(struct Curl_easy *data, break; } } - free(decomp); - /* We're about to leave this call so the `nread' data bytes won't be seen + /* We are about to leave this call so the `nread' data bytes will not be seen again. If we are in a state that would wrongly allow restart in raw mode at the next call, assume output has already started. */ if(nread && zp->zlib_init == ZLIB_INIT) @@ -274,15 +246,12 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* Deflate handler. */ -static CURLcode deflate_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode deflate_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ - if(!writer->downstream) - return CURLE_WRITE_ERROR; - /* Initialize zlib */ z->zalloc = (alloc_func) zalloc_cb; z->zfree = (free_func) zfree_cb; @@ -293,26 +262,29 @@ static CURLcode deflate_init_writer(struct Curl_easy *data, return CURLE_OK; } -static CURLcode deflate_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) +static CURLcode deflate_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + /* Set the compressed input when this function is called */ - z->next_in = (Bytef *) buf; - z->avail_in = (uInt) nbytes; + z->next_in = (z_const Bytef *)buf; + z->avail_in = (uInt)nbytes; if(zp->zlib_init == ZLIB_EXTERNAL_TRAILER) return process_trailer(data, zp); /* Now uncompress the data */ - return inflate_stream(data, writer, ZLIB_INFLATING); + return inflate_stream(data, writer, type, ZLIB_INFLATING); } -static void deflate_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void deflate_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ @@ -320,263 +292,58 @@ static void deflate_close_writer(struct Curl_easy *data, exit_zlib(data, z, &zp->zlib_init, CURLE_OK); } -static const struct content_encoding deflate_encoding = { +static const struct Curl_cwtype deflate_encoding = { "deflate", NULL, - deflate_init_writer, - deflate_unencode_write, - deflate_close_writer, + deflate_do_init, + deflate_do_write, + deflate_do_close, sizeof(struct zlib_writer) }; /* Gzip handler. */ -static CURLcode gzip_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode gzip_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ - if(!writer->downstream) - return CURLE_WRITE_ERROR; - /* Initialize zlib */ z->zalloc = (alloc_func) zalloc_cb; z->zfree = (free_func) zfree_cb; - if(strcmp(zlibVersion(), "1.2.0.4") >= 0) { - /* zlib ver. >= 1.2.0.4 supports transparent gzip decompressing */ - if(inflateInit2(z, MAX_WBITS + 32) != Z_OK) { - return process_zlib_error(data, z); - } - zp->zlib_init = ZLIB_INIT_GZIP; /* Transparent gzip decompress state */ - } - else { - /* we must parse the gzip header and trailer ourselves */ - if(inflateInit2(z, -MAX_WBITS) != Z_OK) { - return process_zlib_error(data, z); - } - zp->trailerlen = 8; /* A CRC-32 and a 32-bit input size (RFC 1952, 2.2) */ - zp->zlib_init = ZLIB_INIT; /* Initial call state */ - } + if(inflateInit2(z, MAX_WBITS + 32) != Z_OK) + return process_zlib_error(data, z); + zp->zlib_init = ZLIB_INIT_GZIP; /* Transparent gzip decompress state */ return CURLE_OK; } -#ifdef OLD_ZLIB_SUPPORT -/* Skip over the gzip header */ -static enum { - GZIP_OK, - GZIP_BAD, - GZIP_UNDERFLOW -} check_gzip_header(unsigned char const *data, ssize_t len, ssize_t *headerlen) -{ - int method, flags; - const ssize_t totallen = len; - - /* The shortest header is 10 bytes */ - if(len < 10) - return GZIP_UNDERFLOW; - - if((data[0] != GZIP_MAGIC_0) || (data[1] != GZIP_MAGIC_1)) - return GZIP_BAD; - - method = data[2]; - flags = data[3]; - - if(method != Z_DEFLATED || (flags & RESERVED) != 0) { - /* Can't handle this compression method or unknown flag */ - return GZIP_BAD; - } - - /* Skip over time, xflags, OS code and all previous bytes */ - len -= 10; - data += 10; - - if(flags & EXTRA_FIELD) { - ssize_t extra_len; - - if(len < 2) - return GZIP_UNDERFLOW; - - extra_len = (data[1] << 8) | data[0]; - - if(len < (extra_len + 2)) - return GZIP_UNDERFLOW; - - len -= (extra_len + 2); - data += (extra_len + 2); - } - - if(flags & ORIG_NAME) { - /* Skip over NUL-terminated file name */ - while(len && *data) { - --len; - ++data; - } - if(!len || *data) - return GZIP_UNDERFLOW; - - /* Skip over the NUL */ - --len; - ++data; - } - - if(flags & COMMENT) { - /* Skip over NUL-terminated comment */ - while(len && *data) { - --len; - ++data; - } - if(!len || *data) - return GZIP_UNDERFLOW; - - /* Skip over the NUL */ - --len; - } - - if(flags & HEAD_CRC) { - if(len < 2) - return GZIP_UNDERFLOW; - - len -= 2; - } - - *headerlen = totallen - len; - return GZIP_OK; -} -#endif - -static CURLcode gzip_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) +static CURLcode gzip_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + if(zp->zlib_init == ZLIB_INIT_GZIP) { /* Let zlib handle the gzip decompression entirely */ - z->next_in = (Bytef *) buf; - z->avail_in = (uInt) nbytes; + z->next_in = (z_const Bytef *)buf; + z->avail_in = (uInt)nbytes; /* Now uncompress the data */ - return inflate_stream(data, writer, ZLIB_INIT_GZIP); + return inflate_stream(data, writer, type, ZLIB_INIT_GZIP); } -#ifndef OLD_ZLIB_SUPPORT - /* Support for old zlib versions is compiled away and we are running with - an old version, so return an error. */ + /* We are running with an old version: return error. */ return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); - -#else - /* This next mess is to get around the potential case where there isn't - * enough data passed in to skip over the gzip header. If that happens, we - * malloc a block and copy what we have then wait for the next call. If - * there still isn't enough (this is definitely a worst-case scenario), we - * make the block bigger, copy the next part in and keep waiting. - * - * This is only required with zlib versions < 1.2.0.4 as newer versions - * can handle the gzip header themselves. - */ - - switch(zp->zlib_init) { - /* Skip over gzip header? */ - case ZLIB_INIT: - { - /* Initial call state */ - ssize_t hlen; - - switch(check_gzip_header((unsigned char *) buf, nbytes, &hlen)) { - case GZIP_OK: - z->next_in = (Bytef *) buf + hlen; - z->avail_in = (uInt) (nbytes - hlen); - zp->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ - break; - - case GZIP_UNDERFLOW: - /* We need more data so we can find the end of the gzip header. It's - * possible that the memory block we malloc here will never be freed if - * the transfer abruptly aborts after this point. Since it's unlikely - * that circumstances will be right for this code path to be followed in - * the first place, and it's even more unlikely for a transfer to fail - * immediately afterwards, it should seldom be a problem. - */ - z->avail_in = (uInt) nbytes; - z->next_in = malloc(z->avail_in); - if(!z->next_in) { - return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); - } - memcpy(z->next_in, buf, z->avail_in); - zp->zlib_init = ZLIB_GZIP_HEADER; /* Need more gzip header data state */ - /* We don't have any data to inflate yet */ - return CURLE_OK; - - case GZIP_BAD: - default: - return exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); - } - - } - break; - - case ZLIB_GZIP_HEADER: - { - /* Need more gzip header data state */ - ssize_t hlen; - z->avail_in += (uInt) nbytes; - z->next_in = Curl_saferealloc(z->next_in, z->avail_in); - if(!z->next_in) { - return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); - } - /* Append the new block of data to the previous one */ - memcpy(z->next_in + z->avail_in - nbytes, buf, nbytes); - - switch(check_gzip_header(z->next_in, z->avail_in, &hlen)) { - case GZIP_OK: - /* This is the zlib stream data */ - free(z->next_in); - /* Don't point into the malloced block since we just freed it */ - z->next_in = (Bytef *) buf + hlen + nbytes - z->avail_in; - z->avail_in = (uInt) (z->avail_in - hlen); - zp->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ - break; - - case GZIP_UNDERFLOW: - /* We still don't have any data to inflate! */ - return CURLE_OK; - - case GZIP_BAD: - default: - return exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); - } - - } - break; - - case ZLIB_EXTERNAL_TRAILER: - z->next_in = (Bytef *) buf; - z->avail_in = (uInt) nbytes; - return process_trailer(data, zp); - - case ZLIB_GZIP_INFLATING: - default: - /* Inflating stream state */ - z->next_in = (Bytef *) buf; - z->avail_in = (uInt) nbytes; - break; - } - - if(z->avail_in == 0) { - /* We don't have any data to inflate; wait until next time */ - return CURLE_OK; - } - - /* We've parsed the header, now uncompress the data */ - return inflate_stream(data, writer, ZLIB_GZIP_INFLATING); -#endif } -static void gzip_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void gzip_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ @@ -584,22 +351,22 @@ static void gzip_close_writer(struct Curl_easy *data, exit_zlib(data, z, &zp->zlib_init, CURLE_OK); } -static const struct content_encoding gzip_encoding = { +static const struct Curl_cwtype gzip_encoding = { "gzip", "x-gzip", - gzip_init_writer, - gzip_unencode_write, - gzip_close_writer, + gzip_do_init, + gzip_do_write, + gzip_do_close, sizeof(struct zlib_writer) }; #endif /* HAVE_LIBZ */ - #ifdef HAVE_BROTLI /* Brotli writer. */ struct brotli_writer { - struct contenc_writer super; + struct Curl_cwriter super; + char buffer[DECOMPRESS_BUFFER_SIZE]; BrotliDecoderState *br; /* State structure for brotli. */ }; @@ -641,46 +408,41 @@ static CURLcode brotli_map_error(BrotliDecoderErrorCode be) return CURLE_WRITE_ERROR; } -static CURLcode brotli_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode brotli_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct brotli_writer *bp = (struct brotli_writer *) writer; (void) data; - if(!writer->downstream) - return CURLE_WRITE_ERROR; - bp->br = BrotliDecoderCreateInstance(NULL, NULL, NULL); - return bp->br? CURLE_OK: CURLE_OUT_OF_MEMORY; + return bp->br ? CURLE_OK : CURLE_OUT_OF_MEMORY; } -static CURLcode brotli_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) +static CURLcode brotli_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { struct brotli_writer *bp = (struct brotli_writer *) writer; const uint8_t *src = (const uint8_t *) buf; - char *decomp; uint8_t *dst; size_t dstleft; CURLcode result = CURLE_OK; BrotliDecoderResult r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + if(!bp->br) return CURLE_WRITE_ERROR; /* Stream already ended. */ - decomp = malloc(DSIZ); - if(!decomp) - return CURLE_OUT_OF_MEMORY; - while((nbytes || r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) && result == CURLE_OK) { - dst = (uint8_t *) decomp; - dstleft = DSIZ; + dst = (uint8_t *) bp->buffer; + dstleft = DECOMPRESS_BUFFER_SIZE; r = BrotliDecoderDecompressStream(bp->br, &nbytes, &src, &dstleft, &dst, NULL); - result = Curl_unencode_write(data, writer->downstream, - decomp, DSIZ - dstleft); + result = Curl_cwriter_write(data, writer->next, type, + bp->buffer, DECOMPRESS_BUFFER_SIZE - dstleft); if(result) break; switch(r) { @@ -698,15 +460,13 @@ static CURLcode brotli_unencode_write(struct Curl_easy *data, break; } } - free(decomp); return result; } -static void brotli_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void brotli_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct brotli_writer *bp = (struct brotli_writer *) writer; - (void) data; if(bp->br) { @@ -715,43 +475,61 @@ static void brotli_close_writer(struct Curl_easy *data, } } -static const struct content_encoding brotli_encoding = { +static const struct Curl_cwtype brotli_encoding = { "br", NULL, - brotli_init_writer, - brotli_unencode_write, - brotli_close_writer, + brotli_do_init, + brotli_do_write, + brotli_do_close, sizeof(struct brotli_writer) }; #endif - #ifdef HAVE_ZSTD /* Zstd writer. */ struct zstd_writer { - struct contenc_writer super; + struct Curl_cwriter super; ZSTD_DStream *zds; /* State structure for zstd. */ - void *decomp; + char buffer[DECOMPRESS_BUFFER_SIZE]; }; -static CURLcode zstd_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +#ifdef ZSTD_STATIC_LINKING_ONLY +static void *Curl_zstd_alloc(void *opaque, size_t size) +{ + (void)opaque; + return Curl_cmalloc(size); +} + +static void Curl_zstd_free(void *opaque, void *address) +{ + (void)opaque; + Curl_cfree(address); +} +#endif + +static CURLcode zstd_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zstd_writer *zp = (struct zstd_writer *) writer; (void)data; - if(!writer->downstream) - return CURLE_WRITE_ERROR; - +#ifdef ZSTD_STATIC_LINKING_ONLY + zp->zds = ZSTD_createDStream_advanced((ZSTD_customMem) { + .customAlloc = Curl_zstd_alloc, + .customFree = Curl_zstd_free, + .opaque = NULL + }); +#else zp->zds = ZSTD_createDStream(); - zp->decomp = NULL; +#endif + return zp->zds ? CURLE_OK : CURLE_OUT_OF_MEMORY; } -static CURLcode zstd_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) +static CURLcode zstd_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { CURLcode result = CURLE_OK; struct zstd_writer *zp = (struct zstd_writer *) writer; @@ -759,27 +537,25 @@ static CURLcode zstd_unencode_write(struct Curl_easy *data, ZSTD_outBuffer out; size_t errorCode; - if(!zp->decomp) { - zp->decomp = malloc(DSIZ); - if(!zp->decomp) - return CURLE_OUT_OF_MEMORY; - } + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + in.pos = 0; in.src = buf; in.size = nbytes; for(;;) { out.pos = 0; - out.dst = zp->decomp; - out.size = DSIZ; + out.dst = zp->buffer; + out.size = DECOMPRESS_BUFFER_SIZE; errorCode = ZSTD_decompressStream(zp->zds, &out, &in); if(ZSTD_isError(errorCode)) { return CURLE_BAD_CONTENT_ENCODING; } if(out.pos > 0) { - result = Curl_unencode_write(data, writer->downstream, - zp->decomp, out.pos); + result = Curl_cwriter_write(data, writer->next, type, + zp->buffer, out.pos); if(result) break; } @@ -790,68 +566,40 @@ static CURLcode zstd_unencode_write(struct Curl_easy *data, return result; } -static void zstd_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void zstd_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zstd_writer *zp = (struct zstd_writer *) writer; - (void)data; - if(zp->decomp) { - free(zp->decomp); - zp->decomp = NULL; - } if(zp->zds) { ZSTD_freeDStream(zp->zds); zp->zds = NULL; } } -static const struct content_encoding zstd_encoding = { +static const struct Curl_cwtype zstd_encoding = { "zstd", NULL, - zstd_init_writer, - zstd_unencode_write, - zstd_close_writer, + zstd_do_init, + zstd_do_write, + zstd_do_close, sizeof(struct zstd_writer) }; #endif - /* Identity handler. */ -static CURLcode identity_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - return writer->downstream? CURLE_OK: CURLE_WRITE_ERROR; -} - -static CURLcode identity_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) -{ - return Curl_unencode_write(data, writer->downstream, buf, nbytes); -} - -static void identity_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - (void) writer; -} - -static const struct content_encoding identity_encoding = { +static const struct Curl_cwtype identity_encoding = { "identity", "none", - identity_init_writer, - identity_unencode_write, - identity_close_writer, - sizeof(struct contenc_writer) + Curl_cwriter_def_init, + Curl_cwriter_def_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) }; - -/* supported content encodings table. */ -static const struct content_encoding * const encodings[] = { +/* supported general content decoders. */ +static const struct Curl_cwtype * const general_unencoders[] = { &identity_encoding, #ifdef HAVE_LIBZ &deflate_encoding, @@ -866,28 +614,39 @@ static const struct content_encoding * const encodings[] = { NULL }; +/* supported content decoders only for transfer encodings */ +static const struct Curl_cwtype * const transfer_unencoders[] = { +#ifndef CURL_DISABLE_HTTP + &Curl_httpchunk_unencoder, +#endif + NULL +}; -/* Return a list of comma-separated names of supported encodings. */ -char *Curl_all_content_encodings(void) +/* Provide a list of comma-separated names of supported encodings. +*/ +void Curl_all_content_encodings(char *buf, size_t blen) { size_t len = 0; - const struct content_encoding * const *cep; - const struct content_encoding *ce; - char *ace; + const struct Curl_cwtype * const *cep; + const struct Curl_cwtype *ce; + + DEBUGASSERT(buf); + DEBUGASSERT(blen); + buf[0] = 0; - for(cep = encodings; *cep; cep++) { + for(cep = general_unencoders; *cep; cep++) { ce = *cep; if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) len += strlen(ce->name) + 2; } - if(!len) - return strdup(CONTENT_ENCODING_DEFAULT); - - ace = malloc(len); - if(ace) { - char *p = ace; - for(cep = encodings; *cep; cep++) { + if(!len) { + if(blen >= sizeof(CONTENT_ENCODING_DEFAULT)) + strcpy(buf, CONTENT_ENCODING_DEFAULT); + } + else if(blen > len) { + char *p = buf; + for(cep = general_unencoders; *cep; cep++) { ce = *cep; if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) { strcpy(p, ce->name); @@ -898,150 +657,71 @@ char *Curl_all_content_encodings(void) } p[-2] = '\0'; } - - return ace; } - -/* Real client writer: no downstream. */ -static CURLcode client_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - return writer->downstream? CURLE_WRITE_ERROR: CURLE_OK; -} - -static CURLcode client_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) -{ - struct SingleRequest *k = &data->req; - - (void) writer; - - if(!nbytes || k->ignorebody) - return CURLE_OK; - - return Curl_client_write(data, CLIENTWRITE_BODY, (char *) buf, nbytes); -} - -static void client_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - (void) writer; -} - -static const struct content_encoding client_encoding = { - NULL, - NULL, - client_init_writer, - client_unencode_write, - client_close_writer, - sizeof(struct contenc_writer) -}; - - /* Deferred error dummy writer. */ -static CURLcode error_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode error_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { - (void) data; - return writer->downstream? CURLE_OK: CURLE_WRITE_ERROR; + (void)data; + (void)writer; + return CURLE_OK; } -static CURLcode error_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) +static CURLcode error_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { - char *all = Curl_all_content_encodings(); - (void) writer; (void) buf; (void) nbytes; - if(!all) - return CURLE_OUT_OF_MEMORY; - failf(data, "Unrecognized content encoding type. " - "libcurl understands %s content encodings.", all); - free(all); + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + else { + char all[256]; + (void)Curl_all_content_encodings(all, sizeof(all)); + failf(data, "Unrecognized content encoding type. " + "libcurl understands %s content encodings.", all); + } return CURLE_BAD_CONTENT_ENCODING; } -static void error_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void error_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { (void) data; (void) writer; } -static const struct content_encoding error_encoding = { - NULL, +static const struct Curl_cwtype error_writer = { + "ce-error", NULL, - error_init_writer, - error_unencode_write, - error_close_writer, - sizeof(struct contenc_writer) + error_do_init, + error_do_write, + error_do_close, + sizeof(struct Curl_cwriter) }; -/* Create an unencoding writer stage using the given handler. */ -static struct contenc_writer * -new_unencoding_writer(struct Curl_easy *data, - const struct content_encoding *handler, - struct contenc_writer *downstream, - int order) -{ - struct contenc_writer *writer; - - DEBUGASSERT(handler->writersize >= sizeof(struct contenc_writer)); - writer = (struct contenc_writer *) calloc(1, handler->writersize); - - if(writer) { - writer->handler = handler; - writer->downstream = downstream; - writer->order = order; - if(handler->init_writer(data, writer)) { - free(writer); - writer = NULL; +/* Find the content encoding by name. */ +static const struct Curl_cwtype *find_unencode_writer(const char *name, + size_t len, + Curl_cwriter_phase phase) +{ + const struct Curl_cwtype * const *cep; + + if(phase == CURL_CW_TRANSFER_DECODE) { + for(cep = transfer_unencoders; *cep; cep++) { + const struct Curl_cwtype *ce = *cep; + if((strncasecompare(name, ce->name, len) && !ce->name[len]) || + (ce->alias && strncasecompare(name, ce->alias, len) + && !ce->alias[len])) + return ce; } } - - return writer; -} - -/* Write data using an unencoding writer stack. "nbytes" is not - allowed to be 0. */ -CURLcode Curl_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) -{ - if(!nbytes) - return CURLE_OK; - return writer->handler->unencode_write(data, writer, buf, nbytes); -} - -/* Close and clean-up the connection's writer stack. */ -void Curl_unencode_cleanup(struct Curl_easy *data) -{ - struct SingleRequest *k = &data->req; - struct contenc_writer *writer = k->writer_stack; - - while(writer) { - k->writer_stack = writer->downstream; - writer->handler->close_writer(data, writer); - free(writer); - writer = k->writer_stack; - } -} - -/* Find the content encoding by name. */ -static const struct content_encoding *find_encoding(const char *name, - size_t len) -{ - const struct content_encoding * const *cep; - - for(cep = encodings; *cep; cep++) { - const struct content_encoding *ce = *cep; + /* look among the general decoders */ + for(cep = general_unencoders; *cep; cep++) { + const struct Curl_cwtype *ce = *cep; if((strncasecompare(name, ce->name, len) && !ce->name[len]) || (ce->alias && strncasecompare(name, ce->alias, len) && !ce->alias[len])) return ce; @@ -1049,20 +729,20 @@ static const struct content_encoding *find_encoding(const char *name, return NULL; } -/* allow no more than 5 "chained" compression steps */ -#define MAX_ENCODE_STACK 5 - -/* Set-up the unencoding stack from the Content-Encoding header value. +/* Setup the unencoding stack from the Content-Encoding header value. * See RFC 7231 section 3.1.2.2. */ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, const char *enclist, int is_transfer) { - struct SingleRequest *k = &data->req; - unsigned int order = is_transfer? 2: 1; + Curl_cwriter_phase phase = is_transfer ? + CURL_CW_TRANSFER_DECODE : CURL_CW_CONTENT_DECODE; + CURLcode result; + bool has_chunked = FALSE; do { const char *name; size_t namelen; + bool is_chunked = FALSE; /* Parse a single encoding name. */ while(ISBLANK(*enclist) || *enclist == ',') @@ -1071,56 +751,88 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, name = enclist; for(namelen = 0; *enclist && *enclist != ','; enclist++) - if(!ISSPACE(*enclist)) + if(*enclist > ' ') namelen = enclist - name + 1; - /* Special case: chunked encoding is handled at the reader level. */ - if(is_transfer && namelen == 7 && strncasecompare(name, "chunked", 7)) { - k->chunk = TRUE; /* chunks coming our way. */ - Curl_httpchunk_init(data); /* init our chunky engine. */ - } - else if(namelen) { - const struct content_encoding *encoding; - struct contenc_writer *writer; - if(is_transfer && !data->set.http_transfer_encoding) + if(namelen) { + const struct Curl_cwtype *cwt; + struct Curl_cwriter *writer; + + CURL_TRC_WRITE(data, "looking for %s decoder: %.*s", + is_transfer ? "transfer" : "content", (int)namelen, name); + is_chunked = (is_transfer && (namelen == 7) && + strncasecompare(name, "chunked", 7)); + /* if we skip the decoding in this phase, do not look further. + * Exception is "chunked" transfer-encoding which always must happen */ + if((is_transfer && !data->set.http_transfer_encoding && !is_chunked) || + (!is_transfer && data->set.http_ce_skip)) { + bool is_identity = strncasecompare(name, "identity", 8); /* not requested, ignore */ + CURL_TRC_WRITE(data, "decoder not requested, ignored: %.*s", + (int)namelen, name); + if(is_transfer && !data->set.http_te_skip) { + if(has_chunked) + failf(data, "A Transfer-Encoding (%.*s) was listed after chunked", + (int)namelen, name); + else if(is_identity) + continue; + else + failf(data, "Unsolicited Transfer-Encoding (%.*s) found", + (int)namelen, name); + return CURLE_BAD_CONTENT_ENCODING; + } return CURLE_OK; - encoding = find_encoding(name, namelen); - - if(!k->writer_stack) { - k->writer_stack = new_unencoding_writer(data, &client_encoding, - NULL, 0); - - if(!k->writer_stack) - return CURLE_OUT_OF_MEMORY; } - if(!encoding) - encoding = &error_encoding; /* Defer error at stack use. */ - - if(k->writer_stack_depth++ >= MAX_ENCODE_STACK) { + if(Curl_cwriter_count(data, phase) + 1 >= MAX_ENCODE_STACK) { failf(data, "Reject response due to more than %u content encodings", MAX_ENCODE_STACK); return CURLE_BAD_CONTENT_ENCODING; } - /* Stack the unencoding stage. */ - if(order >= k->writer_stack->order) { - writer = new_unencoding_writer(data, encoding, - k->writer_stack, order); - if(!writer) - return CURLE_OUT_OF_MEMORY; - k->writer_stack = writer; + + cwt = find_unencode_writer(name, namelen, phase); + if(cwt && is_chunked && Curl_cwriter_get_by_type(data, cwt)) { + /* A 'chunked' transfer encoding has already been added. + * Ignore duplicates. See #13451. + * Also RFC 9112, ch. 6.1: + * "A sender MUST NOT apply the chunked transfer coding more than + * once to a message body." + */ + CURL_TRC_WRITE(data, "ignoring duplicate 'chunked' decoder"); + return CURLE_OK; + } + + if(is_transfer && !is_chunked && + Curl_cwriter_get_by_name(data, "chunked")) { + /* RFC 9112, ch. 6.1: + * "If any transfer coding other than chunked is applied to a + * response's content, the sender MUST either apply chunked as the + * final transfer coding or terminate the message by closing the + * connection." + * "chunked" must be the last added to be the first in its phase, + * reject this. + */ + failf(data, "Reject response due to 'chunked' not being the last " + "Transfer-Encoding"); + return CURLE_BAD_CONTENT_ENCODING; } - else { - struct contenc_writer *w = k->writer_stack; - while(w->downstream && order < w->downstream->order) - w = w->downstream; - writer = new_unencoding_writer(data, encoding, - w->downstream, order); - if(!writer) - return CURLE_OUT_OF_MEMORY; - w->downstream = writer; + + if(!cwt) + cwt = &error_writer; /* Defer error at use. */ + + result = Curl_cwriter_create(&writer, data, cwt, phase); + CURL_TRC_WRITE(data, "added %s decoder %s -> %d", + is_transfer ? "transfer" : "content", cwt->name, result); + if(result) + return result; + + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + return result; } + if(is_chunked) + has_chunked = TRUE; } } while(*enclist); @@ -1138,25 +850,14 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, return CURLE_NOT_BUILT_IN; } -CURLcode Curl_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) -{ - (void) data; - (void) writer; - (void) buf; - (void) nbytes; - return CURLE_NOT_BUILT_IN; -} - -void Curl_unencode_cleanup(struct Curl_easy *data) -{ - (void) data; -} - -char *Curl_all_content_encodings(void) +void Curl_all_content_encodings(char *buf, size_t blen) { - return strdup(CONTENT_ENCODING_DEFAULT); /* Satisfy caller. */ + DEBUGASSERT(buf); + DEBUGASSERT(blen); + if(blen < sizeof(CONTENT_ENCODING_DEFAULT)) + buf[0] = 0; + else + strcpy(buf, CONTENT_ENCODING_DEFAULT); } #endif /* CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/content_encoding.h b/Utilities/cmcurl/lib/content_encoding.h index 56e7f97f704..1addf230bbf 100644 --- a/Utilities/cmcurl/lib/content_encoding.h +++ b/Utilities/cmcurl/lib/content_encoding.h @@ -25,33 +25,10 @@ ***************************************************************************/ #include "curl_setup.h" -struct contenc_writer { - const struct content_encoding *handler; /* Encoding handler. */ - struct contenc_writer *downstream; /* Downstream writer. */ - unsigned int order; /* Ordering within writer stack. */ -}; - -/* Content encoding writer. */ -struct content_encoding { - const char *name; /* Encoding name. */ - const char *alias; /* Encoding name alias. */ - CURLcode (*init_writer)(struct Curl_easy *data, - struct contenc_writer *writer); - CURLcode (*unencode_write)(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes); - void (*close_writer)(struct Curl_easy *data, - struct contenc_writer *writer); - size_t writersize; -}; +struct Curl_cwriter; +void Curl_all_content_encodings(char *buf, size_t blen); CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, const char *enclist, int is_transfer); -CURLcode Curl_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes); -void Curl_unencode_cleanup(struct Curl_easy *data); -char *Curl_all_content_encodings(void); - #endif /* HEADER_CURL_CONTENT_ENCODING_H */ diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c index 0303efbb02e..1a8426ca27f 100644 --- a/Utilities/cmcurl/lib/cookie.c +++ b/Utilities/cmcurl/lib/cookie.c @@ -28,41 +28,27 @@ RECEIVING COOKIE INFORMATION ============================ -struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, - const char *file, struct CookieInfo *inc, bool newsession); +Curl_cookie_init() Inits a cookie struct to store data in a local file. This is always called before any cookies are set. -struct Cookie *Curl_cookie_add(struct Curl_easy *data, - struct CookieInfo *c, bool httpheader, bool noexpire, - char *lineptr, const char *domain, const char *path, - bool secure); - - The 'lineptr' parameter is a full "Set-cookie:" line as - received from a server. - - The function need to replace previously stored lines that this new - line supersedes. - - It may remove lines that are expired. +Curl_cookie_add() - It should return an indication of success/error. + Adds a cookie to the in-memory cookie jar. SENDING COOKIE INFORMATION ========================== -struct Cookies *Curl_cookie_getlist(struct CookieInfo *cookie, - char *host, char *path, bool secure); +Curl_cookie_getlist() For a given host and path, return a linked list of cookies that the client should send to the server if used now. The secure boolean informs the cookie if a secure connection is achieved or not. - It shall only return cookies that haven't expired. - + It shall only return cookies that have not expired. Example set of cookies: @@ -90,11 +76,9 @@ Example set of cookies: #include "urldata.h" #include "cookie.h" #include "psl.h" -#include "strtok.h" #include "sendf.h" #include "slist.h" #include "share.h" -#include "strtoofft.h" #include "strcase.h" #include "curl_get_line.h" #include "curl_memrchr.h" @@ -102,6 +86,8 @@ Example set of cookies: #include "rename.h" #include "fopen.h" #include "strdup.h" +#include "llist.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -110,21 +96,39 @@ Example set of cookies: static void strstore(char **str, const char *newstr, size_t len); +/* number of seconds in 400 days */ +#define COOKIES_MAXAGE (400*24*3600) + +/* Make sure cookies never expire further away in time than 400 days into the + future. (from RFC6265bis draft-19) + + For the sake of easier testing, align the capped time to an even 60 second + boundary. +*/ +static void cap_expires(time_t now, struct Cookie *co) +{ + if((TIME_T_MAX - COOKIES_MAXAGE - 30) > now) { + timediff_t cap = now + COOKIES_MAXAGE; + if(co->expires > cap) { + cap += 30; + co->expires = (cap/60)*60; + } + } +} + static void freecookie(struct Cookie *co) { - free(co->expirestr); free(co->domain); free(co->path); free(co->spath); free(co->name); free(co->value); - free(co->maxage); - free(co->version); free(co); } -static bool tailmatch(const char *cookie_domain, size_t cookie_domain_len, - const char *hostname) +static bool cookie_tailmatch(const char *cookie_domain, + size_t cookie_domain_len, + const char *hostname) { size_t hostname_len = strlen(hostname); @@ -152,15 +156,13 @@ static bool tailmatch(const char *cookie_domain, size_t cookie_domain_len, } /* - * matching cookie path and url path + * matching cookie path and URL path * RFC6265 5.1.4 Paths and Path-Match */ -static bool pathmatch(const char *cookie_path, const char *request_uri) +static bool pathmatch(const char *cookie_path, const char *uri_path) { size_t cookie_path_len; size_t uri_path_len; - char *uri_path = NULL; - char *pos; bool ret = FALSE; /* cookie_path must not have last '/' separator. ex: /sample */ @@ -170,19 +172,9 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) return TRUE; } - uri_path = strdup(request_uri); - if(!uri_path) - return FALSE; - pos = strchr(uri_path, '?'); - if(pos) - *pos = 0x0; - /* #-fragments are already cut off! */ - if(0 == strlen(uri_path) || uri_path[0] != '/') { - strstore(&uri_path, "/", 1); - if(!uri_path) - return FALSE; - } + if(0 == strlen(uri_path) || uri_path[0] != '/') + uri_path = "/"; /* * here, RFC6265 5.1.4 says @@ -196,16 +188,12 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) uri_path_len = strlen(uri_path); - if(uri_path_len < cookie_path_len) { - ret = FALSE; + if(uri_path_len < cookie_path_len) goto pathmatched; - } /* not using checkprefix() because matching should be case-sensitive */ - if(strncmp(cookie_path, uri_path, cookie_path_len)) { - ret = FALSE; + if(strncmp(cookie_path, uri_path, cookie_path_len)) goto pathmatched; - } /* The cookie-path and the uri-path are identical. */ if(cookie_path_len == uri_path_len) { @@ -219,10 +207,7 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) goto pathmatched; } - ret = FALSE; - pathmatched: - free(uri_path); return ret; } @@ -247,7 +232,7 @@ static const char *get_top_domain(const char * const domain, size_t *outlen) if(outlen) *outlen = len; - return first? first: domain; + return first ? first : domain; } /* Avoid C1001, an "internal error" with MSVC14 */ @@ -264,8 +249,9 @@ static size_t cookie_hash_domain(const char *domain, const size_t len) size_t h = 5381; while(domain < end) { + size_t j = (size_t)Curl_raw_toupper(*domain++); h += h << 5; - h ^= Curl_raw_toupper(*domain++); + h ^= j; } return (h % COOKIE_HASH_SIZE); @@ -295,34 +281,27 @@ static size_t cookiehash(const char * const domain) */ static char *sanitize_cookie_path(const char *cookie_path) { - size_t len; - char *new_path = strdup(cookie_path); - if(!new_path) - return NULL; + size_t len = strlen(cookie_path); - /* some stupid site sends path attribute with '"'. */ - len = strlen(new_path); - if(new_path[0] == '\"') { - memmove(new_path, new_path + 1, len); + /* some sites send path attribute within '"'. */ + if(cookie_path[0] == '\"') { + cookie_path++; len--; } - if(len && (new_path[len - 1] == '\"')) { - new_path[--len] = 0x0; - } + if(len && (cookie_path[len - 1] == '\"')) + len--; /* RFC6265 5.2.4 The Path Attribute */ - if(new_path[0] != '/') { + if(cookie_path[0] != '/') /* Let cookie-path be the default-path. */ - strstore(&new_path, "/", 1); - return new_path; - } + return strdup("/"); + /* remove trailing slash */ /* convert /hoge/ to /hoge */ - if(len && new_path[len - 1] == '/') { - new_path[len - 1] = 0x0; - } + if(len && cookie_path[len - 1] == '/') + len--; - return new_path; + return Curl_memdup0(cookie_path, len); } /* @@ -332,21 +311,21 @@ static char *sanitize_cookie_path(const char *cookie_path) */ void Curl_cookie_loadfiles(struct Curl_easy *data) { - struct curl_slist *list = data->set.cookielist; + struct curl_slist *list = data->state.cookielist; if(list) { Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); while(list) { - struct CookieInfo *newcookies = + struct CookieInfo *ci = Curl_cookie_init(data, list->data, data->cookies, data->set.cookiesession); - if(!newcookies) + if(!ci) /* * Failure may be due to OOM or a bad cookie; both are ignored * but only the first should be */ infof(data, "ignoring failed cookie_init for %s", list->data); else - data->cookies = newcookies; + data->cookies = ci; list = list->next; } Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); @@ -364,12 +343,13 @@ void Curl_cookie_loadfiles(struct Curl_easy *data) */ static void strstore(char **str, const char *newstr, size_t len) { - DEBUGASSERT(newstr); DEBUGASSERT(str); free(*str); - *str = Curl_memdup(newstr, len + 1); - if(*str) - (*str)[len] = 0; + if(!len) { + len++; + newstr = ""; + } + *str = Curl_memdup0(newstr, len); } /* @@ -377,59 +357,55 @@ static void strstore(char **str, const char *newstr, size_t len) * * Remove expired cookies from the hash by inspecting the expires timestamp on * each cookie in the hash, freeing and deleting any where the timestamp is in - * the past. If the cookiejar has recorded the next timestamp at which one or + * the past. If the cookiejar has recorded the next timestamp at which one or * more cookies expire, then processing will exit early in case this timestamp * is in the future. */ -static void remove_expired(struct CookieInfo *cookies) +static void remove_expired(struct CookieInfo *ci) { - struct Cookie *co, *nx; + struct Cookie *co; curl_off_t now = (curl_off_t)time(NULL); unsigned int i; /* * If the earliest expiration timestamp in the jar is in the future we can - * skip scanning the whole jar and instead exit early as there won't be any - * cookies to evict. If we need to evict however, reset the next_expiration - * counter in order to track the next one. In case the recorded first - * expiration is the max offset, then perform the safe fallback of checking - * all cookies. + * skip scanning the whole jar and instead exit early as there will not be + * any cookies to evict. If we need to evict however, reset the + * next_expiration counter in order to track the next one. In case the + * recorded first expiration is the max offset, then perform the safe + * fallback of checking all cookies. */ - if(now < cookies->next_expiration && - cookies->next_expiration != CURL_OFF_T_MAX) + if(now < ci->next_expiration && + ci->next_expiration != CURL_OFF_T_MAX) return; else - cookies->next_expiration = CURL_OFF_T_MAX; + ci->next_expiration = CURL_OFF_T_MAX; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - struct Cookie *pv = NULL; - co = cookies->cookies[i]; - while(co) { - nx = co->next; + struct Curl_llist_node *n; + struct Curl_llist_node *e = NULL; + + for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) { + co = Curl_node_elem(n); + e = Curl_node_next(n); if(co->expires && co->expires < now) { - if(!pv) { - cookies->cookies[i] = co->next; - } - else { - pv->next = co->next; - } - cookies->numcookies--; + Curl_node_remove(n); freecookie(co); + ci->numcookies--; } else { /* - * If this cookie has an expiration timestamp earlier than what we've - * seen so far then record it for the next round of expirations. + * If this cookie has an expiration timestamp earlier than what we + * have seen so far then record it for the next round of expirations. */ - if(co->expires && co->expires < cookies->next_expiration) - cookies->next_expiration = co->expires; - pv = co; + if(co->expires && co->expires < ci->next_expiration) + ci->next_expiration = co->expires; } - co = nx; } } } +#ifndef USE_LIBPSL /* Make sure domain contains a dot or is localhost. */ static bool bad_domain(const char *domain, size_t len) { @@ -447,6 +423,7 @@ static bool bad_domain(const char *domain, size_t len) } return TRUE; } +#endif /* RFC 6265 section 4.1.1 says a server should accept this range: @@ -457,613 +434,522 @@ static bool bad_domain(const char *domain, size_t len) fine. The prime reason for filtering out control bytes is that some HTTP servers return 400 for requests that contain such. */ -static int invalid_octets(const char *p) +static bool invalid_octets(const char *ptr) { + const unsigned char *p = (const unsigned char *)ptr; /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */ - static const char badoctets[] = { - "\x01\x02\x03\x04\x05\x06\x07\x08\x0a" - "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" - "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f" - }; - size_t len; - /* scan for all the octets that are *not* in cookie-octet */ - len = strcspn(p, badoctets); - return (p[len] != '\0'); + while(*p) { + if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f)) + return TRUE; + p++; + } + return FALSE; } -/* - * Curl_cookie_add - * - * Add a single cookie line to the cookie keeping object. Be aware that - * sometimes we get an IP-only host name, and that might also be a numerical - * IPv6 address. - * - * Returns NULL on out of memory or invalid cookie. This is suboptimal, - * as they should be treated separately. - */ -struct Cookie * -Curl_cookie_add(struct Curl_easy *data, - struct CookieInfo *c, - bool httpheader, /* TRUE if HTTP header-style line */ - bool noexpire, /* if TRUE, skip remove_expired() */ - char *lineptr, /* first character of the line */ - const char *domain, /* default domain */ - const char *path, /* full path used when this cookie is set, - used to get default path for the cookie - unless set */ - bool secure) /* TRUE if connection is over secure origin */ -{ - struct Cookie *clist; - struct Cookie *co; - struct Cookie *lastc = NULL; - struct Cookie *replace_co = NULL; - struct Cookie *replace_clist = NULL; - time_t now = time(NULL); - bool replace_old = FALSE; - bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */ - size_t myhash; - - DEBUGASSERT(data); - DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */ - if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT) - return NULL; - - /* First, alloc and init a new struct for it */ - co = calloc(1, sizeof(struct Cookie)); - if(!co) - return NULL; /* bail out if we're this low on memory */ - - if(httpheader) { - /* This line was read off an HTTP-header */ - const char *ptr; - - size_t linelength = strlen(lineptr); - if(linelength > MAX_COOKIE_LINE) { - /* discard overly long lines at once */ - free(co); - return NULL; - } - - ptr = lineptr; - do { - size_t vlen; - size_t nlen; - - while(*ptr && ISBLANK(*ptr)) - ptr++; +#define CERR_OK 0 +#define CERR_TOO_LONG 1 /* input line too long */ +#define CERR_TAB 2 /* in a wrong place */ +#define CERR_TOO_BIG 3 /* name/value too large */ +#define CERR_BAD 4 /* deemed incorrect */ +#define CERR_NO_SEP 5 /* semicolon problem */ +#define CERR_NO_NAME_VALUE 6 /* name or value problem */ +#define CERR_INVALID_OCTET 7 /* bad content */ +#define CERR_BAD_SECURE 8 /* secure in a bad place */ +#define CERR_OUT_OF_MEMORY 9 +#define CERR_NO_TAILMATCH 10 +#define CERR_COMMENT 11 /* a commented line */ +#define CERR_RANGE 12 /* expire range problem */ +#define CERR_FIELDS 13 /* incomplete netscape line */ +#ifdef USE_LIBPSL +#define CERR_PSL 14 /* a public suffix */ +#endif +#define CERR_LIVE_WINS 15 - /* we have a = pair or a stand-alone word here */ - nlen = strcspn(ptr, ";\t\r\n="); - if(nlen) { - bool done = FALSE; - bool sep = FALSE; - const char *namep = ptr; - const char *valuep; - - ptr += nlen; - - /* trim trailing spaces and tabs after name */ - while(nlen && ISBLANK(namep[nlen - 1])) - nlen--; - - if(*ptr == '=') { - vlen = strcspn(++ptr, ";\r\n"); - valuep = ptr; - sep = TRUE; - ptr = &valuep[vlen]; - - /* Strip off trailing whitespace from the value */ - while(vlen && ISBLANK(valuep[vlen-1])) - vlen--; - - /* Skip leading whitespace from the value */ - while(vlen && ISBLANK(*valuep)) { - valuep++; - vlen--; - } +/* The maximum length we accept a date string for the 'expire' keyword. The + standard date formats are within the 30 bytes range. This adds an extra + margin just to make sure it realistically works with what is used out + there. +*/ +#define MAX_DATE_LENGTH 80 + +static int +parse_cookie_header(struct Curl_easy *data, + struct Cookie *co, + struct CookieInfo *ci, + const char *ptr, + const char *domain, /* default domain */ + const char *path, /* full path used when this cookie is + set, used to get default path for + the cookie unless set */ + bool secure) /* TRUE if connection is over secure + origin */ +{ + /* This line was read off an HTTP-header */ + time_t now; + size_t linelength = strlen(ptr); + if(linelength > MAX_COOKIE_LINE) + /* discard overly long lines at once */ + return CERR_TOO_LONG; + + now = time(NULL); + do { + struct Curl_str name; + struct Curl_str val; + + /* we have a = pair or a stand-alone word here */ + if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) { + bool done = FALSE; + bool sep = FALSE; + curlx_str_trimblanks(&name); + + if(!curlx_str_single(&ptr, '=')) { + sep = TRUE; /* a '=' was used */ + if(!curlx_str_cspn(&ptr, &val, ";\r\n")) { + curlx_str_trimblanks(&val); /* Reject cookies with a TAB inside the value */ - if(memchr(valuep, '\t', vlen)) { - freecookie(co); + if(memchr(curlx_str(&val), '\t', curlx_strlen(&val))) { infof(data, "cookie contains TAB, dropping"); - return NULL; + return CERR_TAB; } } - else { - valuep = NULL; - vlen = 0; - } + } + else { + curlx_str_init(&val); + } - /* - * Check for too long individual name or contents, or too long - * combination of name + contents. Chrome and Firefox support 4095 or - * 4096 bytes combo - */ - if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) || - ((nlen + vlen) > MAX_NAME)) { - freecookie(co); - infof(data, "oversized cookie dropped, name/val %zu + %zu bytes", - nlen, vlen); - return NULL; - } + /* + * Check for too long individual name or contents, or too long + * combination of name + contents. Chrome and Firefox support 4095 or + * 4096 bytes combo + */ + if(curlx_strlen(&name) >= (MAX_NAME-1) || + curlx_strlen(&val) >= (MAX_NAME-1) || + ((curlx_strlen(&name) + curlx_strlen(&val)) > MAX_NAME)) { + infof(data, "oversized cookie dropped, name/val %zu + %zu bytes", + curlx_strlen(&name), curlx_strlen(&val)); + return CERR_TOO_BIG; + } + + /* + * Check if we have a reserved prefix set before anything else, as we + * otherwise have to test for the prefix in both the cookie name and + * "the rest". Prefixes must start with '__' and end with a '-', so + * only test for names where that can possibly be true. + */ + if(!strncmp("__Secure-", curlx_str(&name), 9)) + co->prefix_secure = TRUE; + else if(!strncmp("__Host-", curlx_str(&name), 7)) + co->prefix_host = TRUE; + + /* + * Use strstore() below to properly deal with received cookie + * headers that have the same string property set more than once, + * and then we use the last one. + */ + if(!co->name) { + /* The very first name/value pair is the actual cookie name */ + if(!sep) + /* Bad name/value pair. */ + return CERR_NO_SEP; + + strstore(&co->name, curlx_str(&name), curlx_strlen(&name)); + strstore(&co->value, curlx_str(&val), curlx_strlen(&val)); + done = TRUE; + if(!co->name || !co->value) + return CERR_NO_NAME_VALUE; + + if(invalid_octets(co->value) || invalid_octets(co->name)) { + infof(data, "invalid octets in name/value, cookie dropped"); + return CERR_INVALID_OCTET; + } + } + else if(!curlx_strlen(&val)) { /* - * Check if we have a reserved prefix set before anything else, as we - * otherwise have to test for the prefix in both the cookie name and - * "the rest". Prefixes must start with '__' and end with a '-', so - * only test for names where that can possibly be true. + * this was a "=" with no content, and we must allow + * 'secure' and 'httponly' specified this weirdly */ - if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') { - if(strncasecompare("__Secure-", namep, 9)) - co->prefix |= COOKIE_PREFIX__SECURE; - else if(strncasecompare("__Host-", namep, 7)) - co->prefix |= COOKIE_PREFIX__HOST; - } - + done = TRUE; /* - * Use strstore() below to properly deal with received cookie - * headers that have the same string property set more than once, - * and then we use the last one. + * 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(!co->name) { - /* The very first name/value pair is the actual cookie name */ - if(!sep) { - /* Bad name/value pair. */ - badcookie = TRUE; - break; - } - strstore(&co->name, namep, nlen); - strstore(&co->value, valuep, vlen); - done = TRUE; - if(!co->name || !co->value) { - badcookie = TRUE; - break; - } - if(invalid_octets(co->value) || invalid_octets(co->name)) { - infof(data, "invalid octets in name/value, cookie dropped"); - badcookie = TRUE; - break; - } - } - else if(!vlen) { - /* - * this was a "=" with no content, and we must allow - * 'secure' and 'httponly' specified this weirdly - */ - done = TRUE; - /* - * 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((nlen == 6) && strncasecompare("secure", namep, 6)) { - if(secure || !c->running) { - co->secure = TRUE; - } - else { - badcookie = TRUE; - break; - } - } - else if((nlen == 8) && strncasecompare("httponly", namep, 8)) - co->httponly = TRUE; - else if(sep) - /* there was a '=' so we're not done parsing this field */ - done = FALSE; - } - if(done) - ; - else if((nlen == 4) && strncasecompare("path", namep, 4)) { - strstore(&co->path, valuep, vlen); - if(!co->path) { - badcookie = TRUE; /* out of memory bad */ - break; + if(curlx_str_casecompare(&name, "secure")) { + if(secure || !ci->running) { + co->secure = TRUE; } - free(co->spath); /* if this is set again */ - co->spath = sanitize_cookie_path(co->path); - if(!co->spath) { - badcookie = TRUE; /* out of memory bad */ - break; + else { + return CERR_BAD_SECURE; } } - else if((nlen == 6) && - strncasecompare("domain", namep, 6) && vlen) { - bool is_ip; - - /* - * Now, we make sure that our host is within the given domain, or - * the given domain is not valid and thus cannot be set. - */ + else if(curlx_str_casecompare(&name, "httponly")) + co->httponly = TRUE; + else if(sep) + /* there was a '=' so we are not done parsing this field */ + done = FALSE; + } + if(done) + ; + else if(curlx_str_casecompare(&name, "path")) { + strstore(&co->path, curlx_str(&val), curlx_strlen(&val)); + if(!co->path) + return CERR_OUT_OF_MEMORY; + free(co->spath); /* if this is set again */ + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) + return CERR_OUT_OF_MEMORY; + } + else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) { + bool is_ip; + const char *v = curlx_str(&val); + /* + * Now, we make sure that our host is within the given domain, or + * the given domain is not valid and thus cannot be set. + */ - if('.' == valuep[0]) { - valuep++; /* ignore preceding dot */ - vlen--; - } + if('.' == *v) + curlx_str_nudge(&val, 1); #ifndef USE_LIBPSL - /* - * Without PSL we don't know when the incoming cookie is set on a - * TLD or otherwise "protected" suffix. To reduce risk, we require a - * dot OR the exact host name being "localhost". - */ - if(bad_domain(valuep, vlen)) - domain = ":"; + /* + * Without PSL we do not know when the incoming cookie is set on a + * TLD or otherwise "protected" suffix. To reduce risk, we require a + * dot OR the exact hostname being "localhost". + */ + if(bad_domain(curlx_str(&val), curlx_strlen(&val))) + domain = ":"; #endif - is_ip = Curl_host_is_ipnum(domain ? domain : valuep); - - if(!domain - || (is_ip && !strncmp(valuep, domain, vlen) && - (vlen == strlen(domain))) - || (!is_ip && tailmatch(valuep, vlen, domain))) { - strstore(&co->domain, valuep, vlen); - if(!co->domain) { - badcookie = TRUE; - break; - } - if(!is_ip) - co->tailmatch = TRUE; /* we always do that if the domain name was - given */ - } - else { - /* - * We did not get a tailmatch and then the attempted set domain is - * not a domain to which the current host belongs. Mark as bad. - */ - badcookie = TRUE; - infof(data, "skipped cookie with bad tailmatch domain: %s", - valuep); - } + is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(&val)); + + if(!domain + || (is_ip && !strncmp(curlx_str(&val), domain, + curlx_strlen(&val)) && + (curlx_strlen(&val) == strlen(domain))) + || (!is_ip && cookie_tailmatch(curlx_str(&val), + curlx_strlen(&val), domain))) { + strstore(&co->domain, curlx_str(&val), curlx_strlen(&val)); + if(!co->domain) + return CERR_OUT_OF_MEMORY; + + if(!is_ip) + co->tailmatch = TRUE; /* we always do that if the domain name was + given */ } - else if((nlen == 7) && strncasecompare("version", namep, 7)) { - strstore(&co->version, valuep, vlen); - if(!co->version) { - badcookie = TRUE; - break; - } - } - else if((nlen == 7) && strncasecompare("max-age", namep, 7)) { + else { /* - * Defined in RFC2109: - * - * Optional. The Max-Age attribute defines the lifetime of the - * cookie, in seconds. The delta-seconds value is a decimal non- - * negative integer. After delta-seconds seconds elapse, the - * client should discard the cookie. A value of zero means the - * cookie should be discarded immediately. + * We did not get a tailmatch and then the attempted set domain is + * not a domain to which the current host belongs. Mark as bad. */ - strstore(&co->maxage, valuep, vlen); - if(!co->maxage) { - badcookie = TRUE; - break; - } - } - else if((nlen == 7) && strncasecompare("expires", namep, 7)) { - strstore(&co->expirestr, valuep, vlen); - if(!co->expirestr) { - badcookie = TRUE; - break; - } + infof(data, "skipped cookie with bad tailmatch domain: %s", + curlx_str(&val)); + return CERR_NO_TAILMATCH; } - + } + else if(curlx_str_casecompare(&name, "version")) { + /* just ignore */ + } + else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) { /* - * Else, this is the second (or more) name we don't know about! + * Defined in RFC2109: + * + * Optional. The Max-Age attribute defines the lifetime of the + * cookie, in seconds. The delta-seconds value is a decimal non- + * negative integer. After delta-seconds seconds elapse, the + * client should discard the cookie. A value of zero means the + * cookie should be discarded immediately. */ + int rc; + const char *maxage = curlx_str(&val); + if(*maxage == '\"') + maxage++; + rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX); + + switch(rc) { + case STRE_OVERFLOW: + /* overflow, used max value */ + co->expires = CURL_OFF_T_MAX; + break; + default: + /* negative or otherwise bad, expire */ + co->expires = 1; + break; + case STRE_OK: + if(!co->expires) + /* already expired */ + co->expires = 1; + else if(CURL_OFF_T_MAX - now < co->expires) + /* would overflow */ + co->expires = CURL_OFF_T_MAX; + else + co->expires += now; + break; + } + cap_expires(now, co); } - else { - /* this is an "illegal" = pair */ - } + else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) { + if(!co->expires && (curlx_strlen(&val) < MAX_DATE_LENGTH)) { + /* + * Let max-age have priority. + * + * If the date cannot get parsed for whatever reason, the cookie + * will be treated as a session cookie + */ + char dbuf[MAX_DATE_LENGTH + 1]; + memcpy(dbuf, curlx_str(&val), curlx_strlen(&val)); + dbuf[curlx_strlen(&val)] = 0; + co->expires = Curl_getdate_capped(dbuf); - while(*ptr && ISBLANK(*ptr)) - ptr++; - if(*ptr == ';') - ptr++; - else - break; - } while(1); - - if(co->maxage) { - CURLofft offt; - offt = curlx_strtoofft((*co->maxage == '\"')? - &co->maxage[1]:&co->maxage[0], NULL, 10, - &co->expires); - switch(offt) { - case CURL_OFFT_FLOW: - /* overflow, used max value */ - co->expires = CURL_OFF_T_MAX; - break; - case CURL_OFFT_INVAL: - /* negative or otherwise bad, expire */ - co->expires = 1; - break; - case CURL_OFFT_OK: - if(!co->expires) - /* already expired */ - co->expires = 1; - else if(CURL_OFF_T_MAX - now < co->expires) - /* would overflow */ - co->expires = CURL_OFF_T_MAX; - else - co->expires += now; - break; + /* + * Session cookies have expires set to 0 so if we get that back + * from the date parser let's add a second to make it a + * non-session cookie + */ + if(co->expires == 0) + co->expires = 1; + else if(co->expires < 0) + co->expires = 0; + cap_expires(now, co); + } } - } - else if(co->expirestr) { - /* - * Note that if the date couldn't get parsed for whatever reason, the - * cookie will be treated as a session cookie - */ - co->expires = Curl_getdate_capped(co->expirestr); /* - * Session cookies have expires set to 0 so if we get that back from the - * date parser let's add a second to make it a non-session cookie + * Else, this is the second (or more) name we do not know about! */ - if(co->expires == 0) - co->expires = 1; - else if(co->expires < 0) - co->expires = 0; } - if(!badcookie && !co->domain) { - if(domain) { - /* no domain was given in the header line, set the default */ - co->domain = strdup(domain); - if(!co->domain) - badcookie = TRUE; - } - } + if(curlx_str_single(&ptr, ';')) + break; + } while(1); - if(!badcookie && !co->path && path) { - /* - * No path was given in the header line, set the default. Note that the - * passed-in path to this function MAY have a '?' and following part that - * MUST NOT be stored as part of the path. - */ - char *queryp = strchr(path, '?'); - - /* - * queryp is where the interesting part of the path ends, so now we - * want to the find the last - */ - char *endslash; - if(!queryp) - endslash = strrchr(path, '/'); - else - endslash = memrchr(path, '/', (queryp - path)); - if(endslash) { - size_t pathlen = (endslash-path + 1); /* include end slash */ - co->path = malloc(pathlen + 1); /* one extra for the zero byte */ - if(co->path) { - memcpy(co->path, path, pathlen); - co->path[pathlen] = 0; /* null-terminate */ - co->spath = sanitize_cookie_path(co->path); - if(!co->spath) - badcookie = TRUE; /* out of memory bad */ - } - else - badcookie = TRUE; - } - } + if(!co->domain && domain) { + /* no domain was given in the header line, set the default */ + co->domain = strdup(domain); + if(!co->domain) + return CERR_OUT_OF_MEMORY; + } + if(!co->path && path) { /* - * If we didn't get a cookie name, or a bad one, the this is an illegal - * line so bail out. + * No path was given in the header line, set the default. */ - if(badcookie || !co->name) { - freecookie(co); - return NULL; + const char *endslash = strrchr(path, '/'); + if(endslash) { + size_t pathlen = (endslash - path + 1); /* include end slash */ + co->path = Curl_memdup0(path, pathlen); + if(co->path) { + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) + return CERR_OUT_OF_MEMORY; + } + else + return CERR_OUT_OF_MEMORY; } - data->req.setcookies++; } - else { - /* - * This line is NOT an HTTP header style line, we do offer support for - * reading the odd netscape cookies-file format here - */ - char *ptr; - char *firstptr; - char *tok_buf = NULL; - int fields; - /* - * IE introduced HTTP-only cookies to prevent XSS attacks. Cookies marked - * with httpOnly after the domain name are not accessible from javascripts, - * but since curl does not operate at javascript level, we include them - * anyway. In Firefox's cookie files, these lines are preceded with - * #HttpOnly_ and then everything is as usual, so we skip 10 characters of - * the line.. - */ - if(strncmp(lineptr, "#HttpOnly_", 10) == 0) { - lineptr += 10; - co->httponly = TRUE; - } + /* + * If we did not get a cookie name, or a bad one, the this is an illegal + * line so bail out. + */ + if(!co->name) + return CERR_BAD; - if(lineptr[0]=='#') { - /* don't even try the comments */ - free(co); - return NULL; - } - /* strip off the possible end-of-line characters */ - ptr = strchr(lineptr, '\r'); - if(ptr) - *ptr = 0; /* clear it */ - ptr = strchr(lineptr, '\n'); - if(ptr) - *ptr = 0; /* clear it */ + data->req.setcookies++; + return CERR_OK; +} - firstptr = strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */ +static int +parse_netscape(struct Cookie *co, + struct CookieInfo *ci, + const char *lineptr, + bool secure) /* TRUE if connection is over secure + origin */ +{ + /* + * This line is NOT an HTTP header style line, we do offer support for + * reading the odd netscape cookies-file format here + */ + const char *ptr, *next; + int fields; + size_t len; - /* - * Now loop through the fields and init the struct we already have - * allocated - */ - for(ptr = firstptr, fields = 0; ptr && !badcookie; - ptr = strtok_r(NULL, "\t", &tok_buf), fields++) { - switch(fields) { - case 0: - if(ptr[0]=='.') /* skip preceding dots */ - ptr++; - co->domain = strdup(ptr); - if(!co->domain) - badcookie = TRUE; - break; - case 1: - /* - * flag: A TRUE/FALSE value indicating if all machines within a given - * domain can access the variable. Set TRUE when the cookie says - * .domain.com and to false when the domain is complete www.domain.com - */ - co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE; - break; - case 2: - /* The file format allows the path field to remain not filled in */ - if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) { - /* only if the path doesn't look like a boolean option! */ - co->path = strdup(ptr); - if(!co->path) - badcookie = TRUE; - else { - co->spath = sanitize_cookie_path(co->path); - if(!co->spath) { - badcookie = TRUE; /* out of memory bad */ - } - } - break; - } - /* this doesn't look like a path, make one up! */ - co->path = strdup("/"); + /* + * In 2008, Internet Explorer introduced HTTP-only cookies to prevent XSS + * attacks. Cookies marked httpOnly are not accessible to JavaScript. In + * Firefox's cookie files, they are prefixed #HttpOnly_ and the rest + * remains as usual, so we skip 10 characters of the line. + */ + if(strncmp(lineptr, "#HttpOnly_", 10) == 0) { + lineptr += 10; + co->httponly = TRUE; + } + + if(lineptr[0]=='#') + /* do not even try the comments */ + return CERR_COMMENT; + + /* + * Now loop through the fields and init the struct we already have + * allocated + */ + fields = 0; + for(next = lineptr; next; fields++) { + ptr = next; + len = strcspn(ptr, "\t\r\n"); + next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL); + switch(fields) { + case 0: + if(ptr[0]=='.') { /* skip preceding dots */ + ptr++; + len--; + } + co->domain = Curl_memdup0(ptr, len); + if(!co->domain) + return CERR_OUT_OF_MEMORY; + break; + case 1: + /* + * flag: A TRUE/FALSE value indicating if all machines within a given + * domain can access the variable. Set TRUE when the cookie says + * .example.com and to false when the domain is complete www.example.com + */ + co->tailmatch = !!strncasecompare(ptr, "TRUE", len); + break; + case 2: + /* The file format allows the path field to remain not filled in */ + if(strncmp("TRUE", ptr, len) && strncmp("FALSE", ptr, len)) { + /* only if the path does not look like a boolean option! */ + co->path = Curl_memdup0(ptr, len); if(!co->path) - badcookie = TRUE; - co->spath = strdup("/"); - if(!co->spath) - badcookie = TRUE; - fields++; /* add a field and fall down to secure */ - /* FALLTHROUGH */ - case 3: - co->secure = FALSE; - if(strcasecompare(ptr, "TRUE")) { - if(secure || c->running) - co->secure = TRUE; - else - badcookie = TRUE; - } - break; - case 4: - if(curlx_strtoofft(ptr, NULL, 10, &co->expires)) - badcookie = TRUE; - break; - case 5: - co->name = strdup(ptr); - if(!co->name) - badcookie = TRUE; + return CERR_OUT_OF_MEMORY; else { - /* For Netscape file format cookies we check prefix on the name */ - if(strncasecompare("__Secure-", co->name, 9)) - co->prefix |= COOKIE_PREFIX__SECURE; - else if(strncasecompare("__Host-", co->name, 7)) - co->prefix |= COOKIE_PREFIX__HOST; + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) + return CERR_OUT_OF_MEMORY; } break; - case 6: - co->value = strdup(ptr); - if(!co->value) - badcookie = TRUE; - break; } - } - if(6 == fields) { - /* we got a cookie with blank contents, fix it */ - co->value = strdup(""); + /* this does not look like a path, make one up! */ + co->path = strdup("/"); + if(!co->path) + return CERR_OUT_OF_MEMORY; + co->spath = strdup("/"); + if(!co->spath) + return CERR_OUT_OF_MEMORY; + fields++; /* add a field and fall down to secure */ + FALLTHROUGH(); + case 3: + co->secure = FALSE; + if(strncasecompare(ptr, "TRUE", len)) { + if(secure || ci->running) + co->secure = TRUE; + else + return CERR_BAD_SECURE; + } + break; + case 4: + if(curlx_str_number(&ptr, &co->expires, CURL_OFF_T_MAX)) + return CERR_RANGE; + break; + case 5: + co->name = Curl_memdup0(ptr, len); + if(!co->name) + return CERR_OUT_OF_MEMORY; + else { + /* For Netscape file format cookies we check prefix on the name */ + if(strncasecompare("__Secure-", co->name, 9)) + co->prefix_secure = TRUE; + else if(strncasecompare("__Host-", co->name, 7)) + co->prefix_host = TRUE; + } + break; + case 6: + co->value = Curl_memdup0(ptr, len); if(!co->value) - badcookie = TRUE; - else - fields++; - } - - if(!badcookie && (7 != fields)) - /* we did not find the sufficient number of fields */ - badcookie = TRUE; - - if(badcookie) { - freecookie(co); - return NULL; - } - - } - - if(co->prefix & COOKIE_PREFIX__SECURE) { - /* The __Secure- prefix only requires that the cookie be set secure */ - if(!co->secure) { - freecookie(co); - return NULL; - } - } - if(co->prefix & COOKIE_PREFIX__HOST) { - /* - * The __Host- prefix requires the cookie to be secure, have a "/" path - * and not have a domain set. - */ - if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch) - ; - else { - freecookie(co); - return NULL; + return CERR_OUT_OF_MEMORY; + break; } } - - if(!c->running && /* read from a file */ - c->newsession && /* clean session cookies */ - !co->expires) { /* this is a session cookie since it doesn't expire! */ - freecookie(co); - return NULL; + if(6 == fields) { + /* we got a cookie with blank contents, fix it */ + co->value = strdup(""); + if(!co->value) + return CERR_OUT_OF_MEMORY; + else + fields++; } - co->livecookie = c->running; - co->creationtime = ++c->lastct; - - /* - * Now we have parsed the incoming line, we must now check if this supersedes - * an already existing cookie, which it may if the previous have the same - * domain and path as this. - */ + if(7 != fields) + /* we did not find the sufficient number of fields */ + return CERR_FIELDS; - /* at first, remove expired cookies */ - if(!noexpire) - remove_expired(c); + return CERR_OK; +} +static int +is_public_suffix(struct Curl_easy *data, + struct Cookie *co, + const char *domain) +{ #ifdef USE_LIBPSL /* * Check if the domain is a Public Suffix and if yes, ignore the cookie. We - * must also check that the data handle isn't NULL since the psl code will + * must also check that the data handle is not NULL since the psl code will * dereference it. */ + DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s", + co->name, co->domain, domain)); if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) { - const psl_ctx_t *psl = Curl_psl_use(data); - int acceptable; - - if(psl) { - acceptable = psl_is_cookie_domain_acceptable(psl, domain, co->domain); - Curl_psl_release(data); + bool acceptable = FALSE; + char lcase[256]; + char lcookie[256]; + size_t dlen = strlen(domain); + size_t clen = strlen(co->domain); + if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) { + const psl_ctx_t *psl = Curl_psl_use(data); + if(psl) { + /* the PSL check requires lowercase domain name and pattern */ + Curl_strntolower(lcase, domain, dlen + 1); + Curl_strntolower(lcookie, co->domain, clen + 1); + acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie); + Curl_psl_release(data); + } + else + infof(data, "libpsl problem, rejecting cookie for satety"); } - else - acceptable = !bad_domain(domain, strlen(domain)); if(!acceptable) { infof(data, "cookie '%s' dropped, domain '%s' must not " - "set cookies for '%s'", co->name, domain, co->domain); - freecookie(co); - return NULL; + "set cookies for '%s'", co->name, domain, co->domain); + return CERR_PSL; } } +#else + (void)data; + (void)co; + (void)domain; + DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s", + co->name, co->domain, domain)); #endif + return CERR_OK; +} - /* A non-secure cookie may not overlay an existing secure cookie. */ - myhash = cookiehash(co->domain); - clist = c->cookies[myhash]; - while(clist) { - if(strcasecompare(clist->name, co->name)) { +static int +replace_existing(struct Curl_easy *data, + struct Cookie *co, + struct CookieInfo *ci, + bool secure, + bool *replacep) +{ + bool replace_old = FALSE; + struct Curl_llist_node *replace_n = NULL; + struct Curl_llist_node *n; + size_t myhash = cookiehash(co->domain); + for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) { + struct Cookie *clist = Curl_node_elem(n); + if(!strcmp(clist->name, co->name)) { /* the names are identical */ bool matching_domains = FALSE; @@ -1098,13 +984,12 @@ Curl_cookie_add(struct Curl_easy *data, if(strncasecompare(clist->spath, co->spath, cllen)) { infof(data, "cookie '%s' for domain '%s' dropped, would " "overlay an existing cookie", co->name, co->domain); - freecookie(co); - return NULL; + return CERR_BAD_SECURE; } } } - if(!replace_co && strcasecompare(clist->name, co->name)) { + if(!replace_n && !strcmp(clist->name, co->name)) { /* the names are identical */ if(clist->domain && co->domain) { @@ -1128,70 +1013,142 @@ Curl_cookie_add(struct Curl_easy *data, if(replace_old && !co->livecookie && clist->livecookie) { /* - * Both cookies matched fine, except that the already present cookie is - * "live", which means it was set from a header, while the new one was - * read from a file and thus isn't "live". "live" cookies are preferred - * so the new cookie is freed. + * Both cookies matched fine, except that the already present cookie + * is "live", which means it was set from a header, while the new one + * was read from a file and thus is not "live". "live" cookies are + * preferred so the new cookie is freed. */ - freecookie(co); - return NULL; - } - if(replace_old) { - replace_co = co; - replace_clist = clist; + return CERR_LIVE_WINS; } + if(replace_old) + replace_n = n; } - lastc = clist; - clist = clist->next; } - if(replace_co) { - co = replace_co; - clist = replace_clist; - co->next = clist->next; /* get the next-pointer first */ + if(replace_n) { + struct Cookie *repl = Curl_node_elem(replace_n); /* when replacing, creationtime is kept from old */ - co->creationtime = clist->creationtime; - - /* then free all the old pointers */ - free(clist->name); - free(clist->value); - free(clist->domain); - free(clist->path); - free(clist->spath); - free(clist->expirestr); - free(clist->version); - free(clist->maxage); - - *clist = *co; /* then store all the new data */ - - free(co); /* free the newly allocated memory */ - co = clist; + co->creationtime = repl->creationtime; + + /* unlink the old */ + Curl_node_remove(replace_n); + + /* free the old cookie */ + freecookie(repl); + } + *replacep = replace_old; + return CERR_OK; +} + +/* + * Curl_cookie_add + * + * Add a single cookie line to the cookie keeping object. Be aware that + * sometimes we get an IP-only hostname, and that might also be a numerical + * IPv6 address. + * + * Returns NULL on out of memory or invalid cookie. This is suboptimal, + * as they should be treated separately. + */ +struct Cookie * +Curl_cookie_add(struct Curl_easy *data, + struct CookieInfo *ci, + bool httpheader, /* TRUE if HTTP header-style line */ + bool noexpire, /* if TRUE, skip remove_expired() */ + const char *lineptr, /* first character of the line */ + const char *domain, /* default domain */ + const char *path, /* full path used when this cookie is set, + used to get default path for the cookie + unless set */ + bool secure) /* TRUE if connection is over secure origin */ +{ + struct Cookie *co; + size_t myhash; + int rc; + bool replaces = FALSE; + + DEBUGASSERT(data); + DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */ + if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT) + return NULL; + + /* First, alloc and init a new struct for it */ + co = calloc(1, sizeof(struct Cookie)); + if(!co) + return NULL; /* bail out if we are this low on memory */ + + if(httpheader) + rc = parse_cookie_header(data, co, ci, lineptr, domain, path, secure); + else + rc = parse_netscape(co, ci, lineptr, secure); + + if(rc) + goto fail; + + if(co->prefix_secure && !co->secure) + /* The __Secure- prefix only requires that the cookie be set secure */ + goto fail; + + if(co->prefix_host) { + /* + * The __Host- prefix requires the cookie to be secure, have a "/" path + * and not have a domain set. + */ + if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch) + ; + else + goto fail; } - if(c->running) + if(!ci->running && /* read from a file */ + ci->newsession && /* clean session cookies */ + !co->expires) /* this is a session cookie since it does not expire */ + goto fail; + + co->livecookie = ci->running; + co->creationtime = ++ci->lastct; + + /* + * Now we have parsed the incoming line, we must now check if this supersedes + * an already existing cookie, which it may if the previous have the same + * domain and path as this. + */ + + /* remove expired cookies */ + if(!noexpire) + remove_expired(ci); + + if(is_public_suffix(data, co, domain)) + goto fail; + + if(replace_existing(data, co, ci, secure, &replaces)) + goto fail; + + /* add this cookie to the list */ + myhash = cookiehash(co->domain); + Curl_llist_append(&ci->cookielist[myhash], co, &co->node); + + if(ci->running) /* Only show this when NOT reading the cookies from a file */ infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, " - "expire %" CURL_FORMAT_CURL_OFF_T, - replace_old?"Replaced":"Added", co->name, co->value, + "expire %" FMT_OFF_T, + replaces ? "Replaced":"Added", co->name, co->value, co->domain, co->path, co->expires); - if(!replace_old) { - /* then make the last item point on this new one */ - if(lastc) - lastc->next = co; - else - c->cookies[myhash] = co; - c->numcookies++; /* one more cookie in the jar */ - } + if(!replaces) + ci->numcookies++; /* one more cookie in the jar */ /* - * Now that we've added a new cookie to the jar, update the expiration + * Now that we have added a new cookie to the jar, update the expiration * tracker in case it is the next one to expire. */ - if(co->expires && (co->expires < c->next_expiration)) - c->next_expiration = co->expires; + if(co->expires && (co->expires < ci->next_expiration)) + ci->next_expiration = co->expires; return co; +fail: + freecookie(co); + return NULL; } @@ -1211,36 +1168,34 @@ Curl_cookie_add(struct Curl_easy *data, */ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, const char *file, - struct CookieInfo *inc, + struct CookieInfo *ci, bool newsession) { - struct CookieInfo *c; - char *line = NULL; FILE *handle = NULL; - if(!inc) { - /* we didn't get a struct, create one */ - c = calloc(1, sizeof(struct CookieInfo)); - if(!c) + if(!ci) { + int i; + + /* we did not get a struct, create one */ + ci = calloc(1, sizeof(struct CookieInfo)); + if(!ci) return NULL; /* failed to get memory */ - c->filename = strdup(file?file:"none"); /* copy the name just in case */ - if(!c->filename) - goto fail; /* failed to get memory */ + + /* This does not use the destructor callback since we want to add + and remove to lists while keeping the cookie struct intact */ + for(i = 0; i < COOKIE_HASH_SIZE; i++) + Curl_llist_init(&ci->cookielist[i], NULL); /* - * Initialize the next_expiration time to signal that we don't have enough + * Initialize the next_expiration time to signal that we do not have enough * information yet. */ - c->next_expiration = CURL_OFF_T_MAX; - } - else { - /* we got an already existing one, use that */ - c = inc; + ci->next_expiration = CURL_OFF_T_MAX; } - c->newsession = newsession; /* new session? */ + ci->newsession = newsession; /* new session? */ if(data) { FILE *fp = NULL; - if(file) { + if(file && *file) { if(!strcmp(file, "-")) fp = stdin; else { @@ -1252,57 +1207,38 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, } } - c->running = FALSE; /* this is not running, this is init */ + ci->running = FALSE; /* this is not running, this is init */ if(fp) { - char *lineptr; - bool headerline; - - line = malloc(MAX_COOKIE_LINE); - if(!line) - goto fail; - while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) { - if(checkprefix("Set-Cookie:", line)) { + struct dynbuf buf; + curlx_dyn_init(&buf, MAX_COOKIE_LINE); + while(Curl_get_line(&buf, fp)) { + const char *lineptr = curlx_dyn_ptr(&buf); + bool headerline = FALSE; + if(checkprefix("Set-Cookie:", lineptr)) { /* This is a cookie line, get it! */ - lineptr = &line[11]; + lineptr += 11; headerline = TRUE; + curlx_str_passblanks(&lineptr); } - else { - lineptr = line; - headerline = FALSE; - } - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE); + Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, NULL, TRUE); } - free(line); /* free the line buffer */ + curlx_dyn_free(&buf); /* free the line buffer */ /* * Remove expired cookies from the hash. We must make sure to run this * after reading the file, and not on every cookie. */ - remove_expired(c); + remove_expired(ci); if(handle) fclose(handle); } data->state.cookie_engine = TRUE; - c->running = TRUE; /* now, we're running */ } + ci->running = TRUE; /* now, we are running */ - return c; - -fail: - free(line); - /* - * Only clean up if we allocated it here, as the original could still be in - * use by a share handle. - */ - if(!inc) - Curl_cookie_cleanup(c); - if(handle) - fclose(handle); - return NULL; /* out of memory */ + return ci; } /* @@ -1315,8 +1251,8 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, */ static int cookie_sort(const void *p1, const void *p2) { - struct Cookie *c1 = *(struct Cookie **)p1; - struct Cookie *c2 = *(struct Cookie **)p2; + const struct Cookie *c1 = *(const struct Cookie * const *)p1; + const struct Cookie *c2 = *(const struct Cookie * const *)p2; size_t l1, l2; /* 1 - compare cookie path lengths */ @@ -1324,14 +1260,14 @@ static int cookie_sort(const void *p1, const void *p2) l2 = c2->path ? strlen(c2->path) : 0; if(l1 != l2) - return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */ + return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */ /* 2 - compare cookie domain lengths */ l1 = c1->domain ? strlen(c1->domain) : 0; l2 = c2->domain ? strlen(c2->domain) : 0; if(l1 != l2) - return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */ + return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */ /* 3 - compare cookie name lengths */ l1 = c1->name ? strlen(c1->name) : 0; @@ -1351,47 +1287,12 @@ static int cookie_sort(const void *p1, const void *p2) */ static int cookie_sort_ct(const void *p1, const void *p2) { - struct Cookie *c1 = *(struct Cookie **)p1; - struct Cookie *c2 = *(struct Cookie **)p2; + const struct Cookie *c1 = *(const struct Cookie * const *)p1; + const struct Cookie *c2 = *(const struct Cookie * const *)p2; return (c2->creationtime > c1->creationtime) ? 1 : -1; } -#define CLONE(field) \ - do { \ - if(src->field) { \ - d->field = strdup(src->field); \ - if(!d->field) \ - goto fail; \ - } \ - } while(0) - -static struct Cookie *dup_cookie(struct Cookie *src) -{ - struct Cookie *d = calloc(sizeof(struct Cookie), 1); - if(d) { - CLONE(expirestr); - CLONE(domain); - CLONE(path); - CLONE(spath); - CLONE(name); - CLONE(value); - CLONE(maxage); - CLONE(version); - d->expires = src->expires; - d->tailmatch = src->tailmatch; - d->secure = src->secure; - d->livecookie = src->livecookie; - d->httponly = src->httponly; - d->creationtime = src->creationtime; - } - return d; - -fail: - freecookie(d); - return NULL; -} - /* * Curl_cookie_getlist * @@ -1399,39 +1300,43 @@ static struct Cookie *dup_cookie(struct Cookie *src) * should send to the server if used now. The secure boolean informs the cookie * if a secure connection is achieved or not. * - * It shall only return cookies that haven't expired. + * It shall only return cookies that have not expired. + * + * Returns 0 when there is a list returned. Otherwise non-zero. */ -struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, - struct CookieInfo *c, - const char *host, const char *path, - bool secure) +int Curl_cookie_getlist(struct Curl_easy *data, + struct CookieInfo *ci, + const char *host, const char *path, + bool secure, + struct Curl_llist *list) { - struct Cookie *newco; - struct Cookie *co; - struct Cookie *mainco = NULL; size_t matches = 0; bool is_ip; const size_t myhash = cookiehash(host); + struct Curl_llist_node *n; + + Curl_llist_init(list, NULL); - if(!c || !c->cookies[myhash]) - return NULL; /* no cookie struct or no cookies in the struct */ + if(!ci || !Curl_llist_count(&ci->cookielist[myhash])) + return 1; /* no cookie struct or no cookies in the struct */ /* at first, remove expired cookies */ - remove_expired(c); + remove_expired(ci); /* check if host is an IP(v4|v6) address */ is_ip = Curl_host_is_ipnum(host); - co = c->cookies[myhash]; + for(n = Curl_llist_head(&ci->cookielist[myhash]); + n; n = Curl_node_next(n)) { + struct Cookie *co = Curl_node_elem(n); - while(co) { - /* if the cookie requires we're secure we must only continue if we are! */ - if(co->secure?secure:TRUE) { + /* if the cookie requires we are secure we must only continue if we are! */ + if(co->secure ? secure : TRUE) { /* now check if the domain is correct */ if(!co->domain || (co->tailmatch && !is_ip && - tailmatch(co->domain, strlen(co->domain), host)) || + cookie_tailmatch(co->domain, strlen(co->domain), host)) || ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) { /* * the right part of the host matches the domain stuff in the @@ -1445,31 +1350,18 @@ struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, if(!co->spath || pathmatch(co->spath, path) ) { /* - * and now, we know this is a match and we should create an - * entry for the return-linked-list + * This is a match and we add it to the return-linked-list */ - - newco = dup_cookie(co); - if(newco) { - /* then modify our next */ - newco->next = mainco; - - /* point the main to us */ - mainco = newco; - - matches++; - if(matches >= MAX_COOKIE_SEND_AMOUNT) { - infof(data, "Included max number of cookies (%zu) in request!", - matches); - break; - } + Curl_llist_append(list, co, &co->getnode); + matches++; + if(matches >= MAX_COOKIE_SEND_AMOUNT) { + infof(data, "Included max number of cookies (%zu) in request!", + matches); + break; } - else - goto fail; } } } - co = co->next; } if(matches) { @@ -1486,30 +1378,29 @@ struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, if(!array) goto fail; - co = mainco; + n = Curl_llist_head(list); - for(i = 0; co; co = co->next) - array[i++] = co; + for(i = 0; n; n = Curl_node_next(n)) + array[i++] = Curl_node_elem(n); /* now sort the cookie pointers in path length order */ qsort(array, matches, sizeof(struct Cookie *), cookie_sort); /* remake the linked list order according to the new order */ + Curl_llist_destroy(list, NULL); - mainco = array[0]; /* start here */ - for(i = 0; inext = array[i + 1]; - array[matches-1]->next = NULL; /* terminate the list */ + for(i = 0; i < matches; i++) + Curl_llist_append(list, array[i], &array[i]->getnode); free(array); /* remove the temporary data again */ } - return mainco; /* return the new list */ + return 0; /* success */ fail: /* failure, clear up the allocated chain and return NULL */ - Curl_cookie_freelist(mainco); - return NULL; + Curl_llist_destroy(list, NULL); + return 2; /* error */ } /* @@ -1517,30 +1408,21 @@ struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, * * Clear all existing cookies and reset the counter. */ -void Curl_cookie_clearall(struct CookieInfo *cookies) +void Curl_cookie_clearall(struct CookieInfo *ci) { - if(cookies) { + if(ci) { unsigned int i; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - Curl_cookie_freelist(cookies->cookies[i]); - cookies->cookies[i] = NULL; + struct Curl_llist_node *n; + for(n = Curl_llist_head(&ci->cookielist[i]); n;) { + struct Cookie *c = Curl_node_elem(n); + struct Curl_llist_node *e = Curl_node_next(n); + Curl_node_remove(n); + freecookie(c); + n = e; + } } - cookies->numcookies = 0; - } -} - -/* - * Curl_cookie_freelist - * - * Free a list of cookies previously returned by Curl_cookie_getlist(); - */ -void Curl_cookie_freelist(struct Cookie *co) -{ - struct Cookie *next; - while(co) { - next = co->next; - freecookie(co); - co = next; + ci->numcookies = 0; } } @@ -1549,39 +1431,26 @@ void Curl_cookie_freelist(struct Cookie *co) * * Free all session cookies in the cookies list. */ -void Curl_cookie_clearsess(struct CookieInfo *cookies) +void Curl_cookie_clearsess(struct CookieInfo *ci) { - struct Cookie *first, *curr, *next, *prev = NULL; unsigned int i; - if(!cookies) + if(!ci) return; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - if(!cookies->cookies[i]) - continue; + struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]); + struct Curl_llist_node *e = NULL; - first = curr = prev = cookies->cookies[i]; - - for(; curr; curr = next) { - next = curr->next; + for(; n; n = e) { + struct Cookie *curr = Curl_node_elem(n); + e = Curl_node_next(n); /* in case the node is removed, get it early */ if(!curr->expires) { - if(first == curr) - first = next; - - if(prev == curr) - prev = next; - else - prev->next = next; - + Curl_node_remove(n); freecookie(curr); - cookies->numcookies--; + ci->numcookies--; } - else - prev = curr; } - - cookies->cookies[i] = first; } } @@ -1590,14 +1459,11 @@ void Curl_cookie_clearsess(struct CookieInfo *cookies) * * Free a "cookie object" previous created with Curl_cookie_init(). */ -void Curl_cookie_cleanup(struct CookieInfo *c) +void Curl_cookie_cleanup(struct CookieInfo *ci) { - if(c) { - unsigned int i; - free(c->filename); - for(i = 0; i < COOKIE_HASH_SIZE; i++) - Curl_cookie_freelist(c->cookies[i]); - free(c); /* free the base struct as well */ + if(ci) { + Curl_cookie_clearall(ci); + free(ci); /* free the base struct as well */ } } @@ -1616,47 +1482,47 @@ static char *get_netscape_format(const struct Cookie *co) "%s\t" /* tailmatch */ "%s\t" /* path */ "%s\t" /* secure */ - "%" CURL_FORMAT_CURL_OFF_T "\t" /* expires */ + "%" FMT_OFF_T "\t" /* expires */ "%s\t" /* name */ "%s", /* value */ - co->httponly?"#HttpOnly_":"", + co->httponly ? "#HttpOnly_" : "", /* * Make sure all domains are prefixed with a dot if they allow * tailmatching. This is Mozilla-style. */ - (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"", - co->domain?co->domain:"unknown", - co->tailmatch?"TRUE":"FALSE", - co->path?co->path:"/", - co->secure?"TRUE":"FALSE", + (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "", + co->domain ? co->domain : "unknown", + co->tailmatch ? "TRUE" : "FALSE", + co->path ? co->path : "/", + co->secure ? "TRUE" : "FALSE", co->expires, co->name, - co->value?co->value:""); + co->value ? co->value : ""); } /* * cookie_output() * * Writes all internally known cookies to the specified file. Specify - * "-" as file name to write to stdout. + * "-" as filename to write to stdout. * * The function returns non-zero on write failure. */ static CURLcode cookie_output(struct Curl_easy *data, - struct CookieInfo *c, const char *filename) + struct CookieInfo *ci, + const char *filename) { - struct Cookie *co; FILE *out = NULL; bool use_stdout = FALSE; char *tempstore = NULL; CURLcode error = CURLE_OK; - if(!c) + if(!ci) /* no cookie engine alive */ return CURLE_OK; /* at first, remove expired cookies */ - remove_expired(c); + remove_expired(ci); if(!strcmp("-", filename)) { /* use stdout */ @@ -1674,12 +1540,13 @@ static CURLcode cookie_output(struct Curl_easy *data, "# This file was generated by libcurl! Edit at your own risk.\n\n", out); - if(c->numcookies) { + if(ci->numcookies) { unsigned int i; size_t nvalid = 0; struct Cookie **array; + struct Curl_llist_node *n; - array = calloc(1, sizeof(struct Cookie *) * c->numcookies); + array = calloc(1, sizeof(struct Cookie *) * ci->numcookies); if(!array) { error = CURLE_OUT_OF_MEMORY; goto error; @@ -1687,7 +1554,9 @@ static CURLcode cookie_output(struct Curl_easy *data, /* only sort the cookies with a domain property */ for(i = 0; i < COOKIE_HASH_SIZE; i++) { - for(co = c->cookies[i]; co; co = co->next) { + for(n = Curl_llist_head(&ci->cookielist[i]); n; + n = Curl_node_next(n)) { + struct Cookie *co = Curl_node_elem(n); if(!co->domain) continue; array[nvalid++] = co; @@ -1739,15 +1608,17 @@ static struct curl_slist *cookie_list(struct Curl_easy *data) { struct curl_slist *list = NULL; struct curl_slist *beg; - struct Cookie *c; - char *line; unsigned int i; + struct Curl_llist_node *n; if(!data->cookies || (data->cookies->numcookies == 0)) return NULL; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - for(c = data->cookies->cookies[i]; c; c = c->next) { + for(n = Curl_llist_head(&data->cookies->cookielist[i]); n; + n = Curl_node_next(n)) { + struct Cookie *c = Curl_node_elem(n); + char *line; if(!c->domain) continue; line = get_netscape_format(c); diff --git a/Utilities/cmcurl/lib/cookie.h b/Utilities/cmcurl/lib/cookie.h index b3c0063b2cf..7af65073cd9 100644 --- a/Utilities/cmcurl/lib/cookie.h +++ b/Utilities/cmcurl/lib/cookie.h @@ -27,26 +27,24 @@ #include +#include "llist.h" + struct Cookie { - struct Cookie *next; /* next in the chain */ - char *name; /* = value */ - char *value; /* name = */ + struct Curl_llist_node node; /* for the main cookie list */ + struct Curl_llist_node getnode; /* for getlist */ + char *name; /* = value */ + char *value; /* name = */ char *path; /* path = which is in Set-Cookie: */ char *spath; /* sanitized cookie path */ - char *domain; /* domain = */ - curl_off_t expires; /* expires = */ - char *expirestr; /* the plain text version */ - - /* RFC 2109 keywords. Version=1 means 2109-compliant cookie sending */ - char *version; /* Version = */ - char *maxage; /* Max-Age = */ - - bool tailmatch; /* whether we do tail-matching of the domain name */ - bool secure; /* whether the 'secure' keyword was used */ - bool livecookie; /* updated from a server, not a stored file */ - bool httponly; /* true if the httponly directive is present */ - int creationtime; /* time when the cookie was written */ - unsigned char prefix; /* bitmap fields indicating which prefix are set */ + char *domain; /* domain = */ + curl_off_t expires; /* expires = */ + unsigned int creationtime; /* time when the cookie was written */ + BIT(tailmatch); /* tail-match the domain name */ + BIT(secure); /* the 'secure' keyword was used */ + BIT(livecookie); /* updated from a server, not a stored file */ + BIT(httponly); /* the httponly directive is present */ + BIT(prefix_secure); /* secure prefix is set */ + BIT(prefix_host); /* host prefix is set */ }; /* @@ -56,17 +54,16 @@ struct Cookie { #define COOKIE_PREFIX__SECURE (1<<0) #define COOKIE_PREFIX__HOST (1<<1) -#define COOKIE_HASH_SIZE 256 +#define COOKIE_HASH_SIZE 63 struct CookieInfo { - /* linked list of cookies we know of */ - struct Cookie *cookies[COOKIE_HASH_SIZE]; - char *filename; /* file we read from/write to */ - long numcookies; /* number of cookies in the "jar" */ - bool running; /* state info, for cookie adding information */ - bool newsession; /* new session, discard session cookies on load */ - int lastct; /* last creation-time used in the jar */ + /* linked lists of cookies we know of */ + struct Curl_llist cookielist[COOKIE_HASH_SIZE]; curl_off_t next_expiration; /* the next time at which expiration happens */ + unsigned int numcookies; /* number of cookies in the "jar" */ + unsigned int lastct; /* last creation-time used in the jar */ + BIT(running); /* state info, for cookie adding information */ + BIT(newsession); /* new session, discard session cookies on load */ }; /* The maximum sizes we accept for cookies. RFC 6265 section 6.1 says @@ -75,7 +72,6 @@ struct CookieInfo { - At least 4096 bytes per cookie (as measured by the sum of the length of the cookie's name, value, and attributes). - In the 6265bis draft document section 5.4 it is phrased even stronger: "If the sum of the lengths of the name string and the value string is more than 4096 octets, abort these steps and ignore the set-cookie-string entirely." @@ -83,7 +79,7 @@ struct CookieInfo { /** Limits for INCOMING cookies **/ -/* The longest we allow a line to be when reading a cookie from a HTTP header +/* The longest we allow a line to be when reading a cookie from an HTTP header or from a cookie jar */ #define MAX_COOKIE_LINE 5000 @@ -116,14 +112,14 @@ struct Curl_easy; struct Cookie *Curl_cookie_add(struct Curl_easy *data, struct CookieInfo *c, bool header, - bool noexpiry, char *lineptr, + bool noexpiry, const char *lineptr, const char *domain, const char *path, bool secure); -struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, - struct CookieInfo *c, const char *host, - const char *path, bool secure); -void Curl_cookie_freelist(struct Cookie *cookies); +int Curl_cookie_getlist(struct Curl_easy *data, + struct CookieInfo *c, const char *host, + const char *path, bool secure, + struct Curl_llist *list); void Curl_cookie_clearall(struct CookieInfo *cookies); void Curl_cookie_clearsess(struct CookieInfo *cookies); diff --git a/Utilities/cmcurl/lib/cshutdn.c b/Utilities/cmcurl/lib/cshutdn.c new file mode 100644 index 00000000000..f05b87d277a --- /dev/null +++ b/Utilities/cmcurl/lib/cshutdn.c @@ -0,0 +1,581 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Linus Nielsen Feltzing, + * 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 + +#include "urldata.h" +#include "url.h" +#include "cfilters.h" +#include "progress.h" +#include "multiif.h" +#include "multi_ev.h" +#include "sendf.h" +#include "cshutdn.h" +#include "http_negotiate.h" +#include "http_ntlm.h" +#include "sigpipe.h" +#include "connect.h" +#include "select.h" +#include "strcase.h" +#include "curlx/strparse.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +static void cshutdn_run_conn_handler(struct Curl_easy *data, + struct connectdata *conn) +{ + if(!conn->bits.shutdown_handler) { + + /* Cleanup NTLM connection-related data */ + Curl_http_auth_cleanup_ntlm(conn); + + /* Cleanup NEGOTIATE connection-related data */ + Curl_http_auth_cleanup_negotiate(conn); + + if(conn->handler && conn->handler->disconnect) { + /* Some disconnect handlers do a blocking wait on server responses. + * FTP/IMAP/SMTP and SFTP are among them. When using the internal + * handle, set an overall short timeout so we do not hang for the + * default 120 seconds. */ + if(data->state.internal) { + data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS; + (void)Curl_pgrsTime(data, TIMER_STARTOP); + } + + /* This is set if protocol-specific cleanups should be made */ + DEBUGF(infof(data, "connection #%" FMT_OFF_T + ", shutdown protocol handler (aborted=%d)", + conn->connection_id, conn->bits.aborted)); + /* There are protocol handlers that block on retrieving + * server responses here (FTP). Set a short timeout. */ + conn->handler->disconnect(data, conn, conn->bits.aborted); + } + + conn->bits.shutdown_handler = TRUE; + } +} + +static void cshutdn_run_once(struct Curl_easy *data, + struct connectdata *conn, + bool *done) +{ + CURLcode r1, r2; + bool done1, done2; + + /* We expect to be attached when called */ + DEBUGASSERT(data->conn == conn); + + cshutdn_run_conn_handler(data, conn); + + if(conn->bits.shutdown_filters) { + *done = TRUE; + return; + } + + if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) + r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); + else { + r1 = CURLE_OK; + done1 = TRUE; + } + + if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) + r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); + else { + r2 = CURLE_OK; + done2 = TRUE; + } + + /* we are done when any failed or both report success */ + *done = (r1 || r2 || (done1 && done2)); + if(*done) + conn->bits.shutdown_filters = TRUE; +} + +void Curl_cshutdn_run_once(struct Curl_easy *data, + struct connectdata *conn, + bool *done) +{ + DEBUGASSERT(!data->conn); + Curl_attach_connection(data, conn); + cshutdn_run_once(data, conn, done); + CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done); + Curl_detach_connection(data); +} + + +void Curl_cshutdn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool do_shutdown) +{ + struct Curl_easy *admin = data; + bool done; + + /* there must be a connection to close */ + DEBUGASSERT(conn); + /* it must be removed from the connection pool */ + DEBUGASSERT(!conn->bits.in_cpool); + /* the transfer must be detached from the connection */ + DEBUGASSERT(data && !data->conn); + + /* If we can obtain an internal admin handle, use that to attach + * and terminate the connection. Some protocol will try to mess with + * `data` during shutdown and we do not want that with a `data` from + * the application. */ + if(data->multi && data->multi->admin) + admin = data->multi->admin; + + Curl_attach_connection(admin, conn); + + cshutdn_run_conn_handler(admin, conn); + if(do_shutdown) { + /* Make a last attempt to shutdown handlers and filters, if + * not done so already. */ + cshutdn_run_once(admin, conn, &done); + } + CURL_TRC_M(admin, "[SHUTDOWN] %sclosing connection #%" FMT_OFF_T, + conn->bits.shutdown_filters ? "" : "force ", + conn->connection_id); + Curl_conn_close(admin, SECONDARYSOCKET); + Curl_conn_close(admin, FIRSTSOCKET); + Curl_detach_connection(admin); + + if(data->multi) + Curl_multi_ev_conn_done(data->multi, data, conn); + Curl_conn_free(admin, conn); + + if(data->multi) { + CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged"); + Curl_multi_connchanged(data->multi); + } +} + +static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, + struct Curl_easy *data, + const char *destination) +{ + 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); + } + + if(e) { + SIGPIPE_VARIABLE(pipe_st); + conn = Curl_node_elem(e); + Curl_node_remove(e); + sigpipe_init(&pipe_st); + sigpipe_apply(data, &pipe_st); + Curl_cshutdn_terminate(data, conn, FALSE); + sigpipe_restore(&pipe_st); + return TRUE; + } + return FALSE; +} + +bool Curl_cshutdn_close_oldest(struct Curl_easy *data, + const char *destination) +{ + if(data && data->multi) { + struct cshutdn *csd = &data->multi->cshutdn; + return cshutdn_destroy_oldest(csd, data, destination); + } + return FALSE; +} + +#define NUM_POLLS_ON_STACK 10 + +static CURLcode cshutdn_wait(struct cshutdn *cshutdn, + struct Curl_easy *data, + int timeout_ms) +{ + struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; + struct curl_pollfds cpfds; + CURLcode result; + + Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); + + result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds); + if(result) + goto out; + + Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000)); + +out: + Curl_pollfds_cleanup(&cpfds); + return result; +} + + +static void cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *data) +{ + struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); + struct Curl_llist_node *enext; + struct connectdata *conn; + struct curltime *nowp = NULL; + struct curltime now; + timediff_t next_expire_ms = 0, ms; + bool done; + + if(!e) + return; + + CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections", + Curl_llist_count(&cshutdn->list)); + while(e) { + enext = Curl_node_next(e); + conn = Curl_node_elem(e); + Curl_cshutdn_run_once(data, conn, &done); + if(done) { + Curl_node_remove(e); + Curl_cshutdn_terminate(data, conn, FALSE); + } + else { + /* idata has one timer list, but maybe more than one connection. + * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ + if(!nowp) { + now = curlx_now(); + nowp = &now; + } + ms = Curl_conn_shutdown_timeleft(conn, nowp); + if(ms && ms < next_expire_ms) + next_expire_ms = ms; + } + e = enext; + } + + if(next_expire_ms) + Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN); +} + + +static void cshutdn_terminate_all(struct cshutdn *cshutdn, + struct Curl_easy *data, + int timeout_ms) +{ + struct curltime started = curlx_now(); + struct Curl_llist_node *e; + SIGPIPE_VARIABLE(pipe_st); + + DEBUGASSERT(cshutdn); + DEBUGASSERT(data); + + CURL_TRC_M(data, "[SHUTDOWN] shutdown all"); + sigpipe_init(&pipe_st); + sigpipe_apply(data, &pipe_st); + + while(Curl_llist_head(&cshutdn->list)) { + timediff_t timespent; + int remain_ms; + + cshutdn_perform(cshutdn, data); + + if(!Curl_llist_head(&cshutdn->list)) { + CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly"); + break; + } + + /* wait for activity, timeout or "nothing" */ + timespent = curlx_timediff(curlx_now(), started); + if(timespent >= (timediff_t)timeout_ms) { + CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s", + (timeout_ms > 0) ? "timeout" : "best effort done"); + break; + } + + remain_ms = timeout_ms - (int)timespent; + if(cshutdn_wait(cshutdn, data, remain_ms)) { + CURL_TRC_M(data, "[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_cshutdn_terminate(data, conn, FALSE); + e = Curl_llist_head(&cshutdn->list); + } + DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); + + sigpipe_restore(&pipe_st); +} + + +int Curl_cshutdn_init(struct cshutdn *cshutdn, + struct Curl_multi *multi) +{ + DEBUGASSERT(multi); + cshutdn->multi = multi; + Curl_llist_init(&cshutdn->list, NULL); + cshutdn->initialised = TRUE; + return 0; /* good */ +} + + +void Curl_cshutdn_destroy(struct cshutdn *cshutdn, + struct Curl_easy *data) +{ + if(cshutdn->initialised && data) { + int timeout_ms = 0; + /* Just for testing, run graceful shutdown */ +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, INT_MAX)) + timeout_ms = (int)l; + } + } +#endif + + CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", + Curl_llist_count(&cshutdn->list), timeout_ms); + cshutdn_terminate_all(cshutdn, data, timeout_ms); + } + cshutdn->multi = NULL; +} + +size_t Curl_cshutdn_count(struct Curl_easy *data) +{ + if(data && data->multi) { + struct cshutdn *csd = &data->multi->cshutdn; + return Curl_llist_count(&csd->list); + } + return 0; +} + +size_t Curl_cshutdn_dest_count(struct Curl_easy *data, + const char *destination) +{ + if(data && data->multi) { + struct cshutdn *csd = &data->multi->cshutdn; + size_t n = 0; + struct Curl_llist_node *e = Curl_llist_head(&csd->list); + while(e) { + struct connectdata *conn = Curl_node_elem(e); + if(!strcmp(destination, conn->destination)) + ++n; + e = Curl_node_next(e); + } + return n; + } + return 0; +} + + +static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct connectdata *conn) +{ + CURLMcode mresult; + + DEBUGASSERT(cshutdn); + DEBUGASSERT(cshutdn->multi->socket_cb); + + Curl_attach_connection(data, conn); + mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn); + Curl_detach_connection(data); + return mresult; +} + + +void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct connectdata *conn, + size_t conns_in_pool) +{ + struct Curl_easy *data = cshutdn->multi->admin; + size_t max_total = (cshutdn->multi->max_total_connections > 0) ? + (size_t)cshutdn->multi->max_total_connections : 0; + + /* 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)))) { + CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection " + "due to connection limit of %zu", max_total); + cshutdn_destroy_oldest(cshutdn, data, NULL); + } + + if(cshutdn->multi->socket_cb) { + if(cshutdn_update_ev(cshutdn, data, conn)) { + CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%" + FMT_OFF_T, conn->connection_id); + Curl_cshutdn_terminate(data, conn, FALSE); + return; + } + } + + Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); + CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T + " to shutdowns, now %zu conns in shutdown", + conn->connection_id, Curl_llist_count(&cshutdn->list)); +} + + +static void cshutdn_multi_socket(struct cshutdn *cshutdn, + struct Curl_easy *data, + curl_socket_t s) +{ + struct Curl_llist_node *e; + struct connectdata *conn; + bool done; + + DEBUGASSERT(cshutdn->multi->socket_cb); + e = Curl_llist_head(&cshutdn->list); + while(e) { + conn = Curl_node_elem(e); + if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) { + Curl_cshutdn_run_once(data, conn, &done); + if(done || cshutdn_update_ev(cshutdn, data, conn)) { + Curl_node_remove(e); + Curl_cshutdn_terminate(data, conn, FALSE); + } + break; + } + e = Curl_node_next(e); + } +} + + +void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *data, + curl_socket_t s) +{ + if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb)) + cshutdn_perform(cshutdn, data); + else + cshutdn_multi_socket(cshutdn, data, s); +} + +/* return fd_set info about the shutdown connections */ +void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + fd_set *read_fd_set, fd_set *write_fd_set, + int *maxfd) +{ + if(Curl_llist_head(&cshutdn->list)) { + struct Curl_llist_node *e; + + for(e = Curl_llist_head(&cshutdn->list); e; + e = Curl_node_next(e)) { + struct easy_pollset ps; + unsigned int i; + struct connectdata *conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, conn, &ps); + Curl_detach_connection(data); + + for(i = 0; i < ps.num; i++) { +#if defined(__DJGPP__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warith-conversion" +#endif + if(ps.actions[i] & CURL_POLL_IN) + FD_SET(ps.sockets[i], read_fd_set); + if(ps.actions[i] & CURL_POLL_OUT) + FD_SET(ps.sockets[i], write_fd_set); +#if defined(__DJGPP__) +#pragma GCC diagnostic pop +#endif + if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && + ((int)ps.sockets[i] > *maxfd)) + *maxfd = (int)ps.sockets[i]; + } + } + } +} + +/* return information about the shutdown connections */ +unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct Curl_waitfds *cwfds) +{ + unsigned int need = 0; + + if(Curl_llist_head(&cshutdn->list)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; + + for(e = Curl_llist_head(&cshutdn->list); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, conn, &ps); + Curl_detach_connection(data); + + need += Curl_waitfds_add_ps(cwfds, &ps); + } + } + return need; +} + +CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct curl_pollfds *cpfds) +{ + CURLcode result = CURLE_OK; + + if(Curl_llist_head(&cshutdn->list)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; + + for(e = Curl_llist_head(&cshutdn->list); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, conn, &ps); + Curl_detach_connection(data); + + result = Curl_pollfds_add_ps(cpfds, &ps); + if(result) { + Curl_pollfds_cleanup(cpfds); + goto out; + } + } + } +out: + return result; +} diff --git a/Utilities/cmcurl/lib/cshutdn.h b/Utilities/cmcurl/lib/cshutdn.h new file mode 100644 index 00000000000..510d5bf5061 --- /dev/null +++ b/Utilities/cmcurl/lib/cshutdn.h @@ -0,0 +1,110 @@ +#ifndef HEADER_CURL_CSHUTDN_H +#define HEADER_CURL_CSHUTDN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Linus Nielsen Feltzing, + * + * 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 +#include "curlx/timeval.h" + +struct connectdata; +struct Curl_easy; +struct curl_pollfds; +struct Curl_waitfds; +struct Curl_multi; +struct Curl_share; + +/* Run the shutdown of the connection once. + * Will shortly attach/detach `data` to `conn` while doing so. + * `done` will be set TRUE if any error was encountered or if + * the connection was shut down completely. */ +void Curl_cshutdn_run_once(struct Curl_easy *data, + struct connectdata *conn, + bool *done); + +/* Terminates the connection, e.g. closes and destroys it. + * If `run_shutdown` is TRUE, the shutdown will be run once before + * terminating it. + * Takes ownership of `conn`. */ +void Curl_cshutdn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool run_shutdown); + +/* 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(initialised); +}; + +/* Init as part of the given multi handle. */ +int Curl_cshutdn_init(struct cshutdn *cshutdn, + struct Curl_multi *multi); + +/* Terminate all remaining connections and free resources. */ +void Curl_cshutdn_destroy(struct cshutdn *cshutdn, + struct Curl_easy *data); + +/* Number of connections being shut down. */ +size_t Curl_cshutdn_count(struct Curl_easy *data); + +/* Number of connections to the destination being shut down. */ +size_t Curl_cshutdn_dest_count(struct Curl_easy *data, + const char *destination); + +/* Close the oldest connection 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 Curl_easy *data, + const char *destination); + +/* Add a connection to have it shut down. Will terminate the oldest + * connection when total connection limit of multi is being reached. */ +void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct connectdata *conn, + size_t conns_in_pool); + +/* Add sockets and POLLIN/OUT flags for connections being shut down. */ +CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct curl_pollfds *cpfds); + +unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct Curl_waitfds *cwfds); + +void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + fd_set *read_fd_set, fd_set *write_fd_set, + int *maxfd); + +/* Run shut down connections using socket. If socket is CURL_SOCKET_TIMEOUT, + * run maintenance on all connections. */ +void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *data, + curl_socket_t s); + +#endif /* HEADER_CURL_CSHUTDN_H */ diff --git a/Utilities/cmcurl/lib/curl_addrinfo.c b/Utilities/cmcurl/lib/curl_addrinfo.c index f9211d3f576..b131c747b10 100644 --- a/Utilities/cmcurl/lib/curl_addrinfo.c +++ b/Utilities/cmcurl/lib/curl_addrinfo.c @@ -50,8 +50,9 @@ #include #include "curl_addrinfo.h" -#include "inet_pton.h" -#include "warnless.h" +#include "fake_addrinfo.h" +#include "curlx/inet_pton.h" +#include "curlx/warnless.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -95,7 +96,7 @@ Curl_freeaddrinfo(struct Curl_addrinfo *cahead) * the only difference that instead of returning a linked list of * addrinfo structs this one returns a linked list of Curl_addrinfo * ones. The memory allocated by this function *MUST* be free'd with - * Curl_freeaddrinfo(). For each successful call to this function + * Curl_freeaddrinfo(). For each successful call to this function * there must be an associated call later to Curl_freeaddrinfo(). * * There should be no single call to system's getaddrinfo() in the @@ -118,7 +119,7 @@ Curl_getaddrinfo_ex(const char *nodename, *result = NULL; /* assume failure */ - error = getaddrinfo(nodename, servname, hints, &aihead); + error = CURL_GETADDRINFO(nodename, servname, hints, &aihead); if(error) return error; @@ -130,7 +131,7 @@ Curl_getaddrinfo_ex(const char *nodename, /* settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -184,7 +185,7 @@ Curl_getaddrinfo_ex(const char *nodename, /* destroy the addrinfo list */ if(aihead) - freeaddrinfo(aihead); + CURL_FREEADDRINFO(aihead); /* if we failed, also destroy the Curl_addrinfo list */ if(error) { @@ -217,11 +218,11 @@ Curl_getaddrinfo_ex(const char *nodename, * * This function returns a pointer to the first element of a newly allocated * Curl_addrinfo struct linked list filled with the data of a given hostent. - * Curl_addrinfo is meant to work like the addrinfo struct does for a IPv6 + * Curl_addrinfo is meant to work like the addrinfo struct does for an IPv6 * stack, but usable also for IPv4, all hosts and environments. * * The memory allocated by this function *MUST* be free'd later on calling - * Curl_freeaddrinfo(). For each successful call to this function there + * Curl_freeaddrinfo(). For each successful call to this function there * must be an associated call later to Curl_freeaddrinfo(). * * Curl_addrinfo defined in "lib/curl_addrinfo.h" @@ -252,6 +253,7 @@ Curl_getaddrinfo_ex(const char *nodename, * #define h_addr h_addr_list[0] */ +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) struct Curl_addrinfo * Curl_he2ai(const struct hostent *he, int port) { @@ -259,7 +261,7 @@ Curl_he2ai(const struct hostent *he, int port) struct Curl_addrinfo *prevai = NULL; struct Curl_addrinfo *firstai = NULL; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif CURLcode result = CURLE_OK; @@ -275,7 +277,7 @@ Curl_he2ai(const struct hostent *he, int port) for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { size_t ss_size; size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(he->h_addrtype == AF_INET6) ss_size = sizeof(struct sockaddr_in6); else @@ -321,7 +323,7 @@ Curl_he2ai(const struct hostent *he, int port) addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ai->ai_addr; /* storage area for this info */ @@ -342,24 +344,12 @@ Curl_he2ai(const struct hostent *he, int port) return firstai; } - - -struct namebuff { - struct hostent hostentry; - union { - struct in_addr ina4; -#ifdef ENABLE_IPV6 - struct in6_addr ina6; #endif - } addrentry; - char *h_addr_list[2]; -}; - /* * Curl_ip2addr() * - * This function takes an internet address, in binary form, as input parameter + * This function takes an Internet address, in binary form, as input parameter * along with its address family and the string version of the address, and it * returns a Curl_addrinfo chain filled in correctly with information for the * given address/host @@ -369,71 +359,68 @@ struct Curl_addrinfo * Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) { struct Curl_addrinfo *ai; - -#if defined(__VMS) && \ - defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) -#pragma pointer_size save -#pragma pointer_size short -#pragma message disable PTRMISMATCH -#endif - - struct hostent *h; - struct namebuff *buf; - char *addrentry; - char *hoststr; size_t addrsize; + size_t namelen; + struct sockaddr_in *addr; +#ifdef USE_IPV6 + struct sockaddr_in6 *addr6; +#endif DEBUGASSERT(inaddr && hostname); - buf = malloc(sizeof(struct namebuff)); - if(!buf) + namelen = strlen(hostname) + 1; + + if(af == AF_INET) + addrsize = sizeof(struct sockaddr_in); +#ifdef USE_IPV6 + else if(af == AF_INET6) + addrsize = sizeof(struct sockaddr_in6); +#endif + else return NULL; - hoststr = strdup(hostname); - if(!hoststr) { - free(buf); + /* allocate memory to hold the struct, the address and the name */ + ai = calloc(1, sizeof(struct Curl_addrinfo) + addrsize + namelen); + if(!ai) return NULL; - } + /* put the address after the struct */ + ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo)); + /* then put the name after the address */ + ai->ai_canonname = (char *)ai->ai_addr + addrsize; + memcpy(ai->ai_canonname, hostname, namelen); + ai->ai_family = af; + ai->ai_socktype = SOCK_STREAM; + ai->ai_addrlen = (curl_socklen_t)addrsize; + /* leave the rest of the struct filled with zero */ switch(af) { case AF_INET: - addrsize = sizeof(struct in_addr); - addrentry = (void *)&buf->addrentry.ina4; - memcpy(addrentry, inaddr, sizeof(struct in_addr)); + addr = (void *)ai->ai_addr; /* storage area for this info */ + + memcpy(&addr->sin_addr, inaddr, sizeof(struct in_addr)); +#ifdef __MINGW32__ + addr->sin_family = (short)af; +#else + addr->sin_family = (CURL_SA_FAMILY_T)af; +#endif + addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 + +#ifdef USE_IPV6 case AF_INET6: - addrsize = sizeof(struct in6_addr); - addrentry = (void *)&buf->addrentry.ina6; - memcpy(addrentry, inaddr, sizeof(struct in6_addr)); + addr6 = (void *)ai->ai_addr; /* storage area for this info */ + + memcpy(&addr6->sin6_addr, inaddr, sizeof(struct in6_addr)); +#ifdef __MINGW32__ + addr6->sin6_family = (short)af; +#else + addr6->sin6_family = (CURL_SA_FAMILY_T)af; +#endif + addr6->sin6_port = htons((unsigned short)port); break; #endif - default: - free(hoststr); - free(buf); - return NULL; } - h = &buf->hostentry; - h->h_name = hoststr; - h->h_aliases = NULL; - h->h_addrtype = (short)af; - h->h_length = (short)addrsize; - h->h_addr_list = &buf->h_addr_list[0]; - h->h_addr_list[0] = addrentry; - h->h_addr_list[1] = NULL; /* terminate list of entries */ - -#if defined(__VMS) && \ - defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) -#pragma pointer_size restore -#pragma message enable PTRMISMATCH -#endif - - ai = Curl_he2ai(h, port); - - free(hoststr); - free(buf); - return ai; } @@ -444,13 +431,13 @@ Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) struct Curl_addrinfo *Curl_str2addr(char *address, int port) { struct in_addr in; - if(Curl_inet_pton(AF_INET, address, &in) > 0) + if(curlx_inet_pton(AF_INET, address, &in) > 0) /* This is a dotted IP address 123.123.123.123-style */ return Curl_ip2addr(AF_INET, &in, address, port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 { struct in6_addr in6; - if(Curl_inet_pton(AF_INET6, address, &in6) > 0) + if(curlx_inet_pton(AF_INET6, address, &in6) > 0) /* This is a dotted IPv6 address ::1-style */ return Curl_ip2addr(AF_INET6, &in6, address, port); } @@ -481,7 +468,7 @@ struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, sa_un = (void *) ai->ai_addr; sa_un->sun_family = AF_UNIX; - /* sun_path must be able to store the NUL-terminated path */ + /* sun_path must be able to store the null-terminated path */ path_len = strlen(path) + 1; if(path_len > sizeof(sa_un->sun_path)) { free(ai); @@ -511,7 +498,7 @@ struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, * * This is strictly for memory tracing and are using the same style as the * family otherwise present in memdebug.c. I put these ones here since they - * require a bunch of structs I didn't want to include in memdebug.c + * require a bunch of structs I did not want to include in memdebug.c */ void @@ -522,8 +509,16 @@ curl_dbg_freeaddrinfo(struct addrinfo *freethis, source, line, (void *)freethis); #ifdef USE_LWIPSOCK lwip_freeaddrinfo(freethis); +#elif defined(USE_FAKE_GETADDRINFO) + { + const char *env = getenv("CURL_DNS_SERVER"); + if(env) + r_freeaddrinfo(freethis); + else + freeaddrinfo(freethis); + } #else - (freeaddrinfo)(freethis); + freeaddrinfo(freethis); #endif } #endif /* defined(CURLDEBUG) && defined(HAVE_FREEADDRINFO) */ @@ -535,20 +530,27 @@ curl_dbg_freeaddrinfo(struct addrinfo *freethis, * * This is strictly for memory tracing and are using the same style as the * family otherwise present in memdebug.c. I put these ones here since they - * require a bunch of structs I didn't want to include in memdebug.c + * require a bunch of structs I did not want to include in memdebug.c */ int curl_dbg_getaddrinfo(const char *hostname, - const char *service, - const struct addrinfo *hints, - struct addrinfo **result, - int line, const char *source) + const char *service, + const struct addrinfo *hints, + struct addrinfo **result, + int line, const char *source) { #ifdef USE_LWIPSOCK int res = lwip_getaddrinfo(hostname, service, hints, result); +#elif defined(USE_FAKE_GETADDRINFO) + int res; + const char *env = getenv("CURL_DNS_SERVER"); + if(env) + res = r_getaddrinfo(hostname, service, hints, result); + else + res = getaddrinfo(hostname, service, hints, result); #else - int res = (getaddrinfo)(hostname, service, hints, result); + int res = getaddrinfo(hostname, service, hints, result); #endif if(0 == res) /* success */ @@ -563,14 +565,14 @@ curl_dbg_getaddrinfo(const char *hostname, #if defined(HAVE_GETADDRINFO) && defined(USE_RESOLVE_ON_IPS) /* - * Work-arounds the sin6_port is always zero bug on iOS 9.3.2 and Mac OS X + * Work-arounds the sin6_port is always zero bug on iOS 9.3.2 and macOS * 10.11.5. */ void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) { struct Curl_addrinfo *ca; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif for(ca = addrinfo; ca != NULL; ca = ca->ai_next) { @@ -580,7 +582,7 @@ void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ca->ai_addr; /* storage area for this info */ addr6->sin6_port = htons((unsigned short)port); diff --git a/Utilities/cmcurl/lib/curl_addrinfo.h b/Utilities/cmcurl/lib/curl_addrinfo.h index c757c49c5cb..2303e95e314 100644 --- a/Utilities/cmcurl/lib/curl_addrinfo.h +++ b/Utilities/cmcurl/lib/curl_addrinfo.h @@ -44,9 +44,9 @@ /* * Curl_addrinfo is our internal struct definition that we use to allow - * consistent internal handling of this data. We use this even when the - * system provides an addrinfo structure definition. And we use this for - * all sorts of IPv4 and IPV6 builds. + * consistent internal handling of this data. We use this even when the system + * provides an addrinfo structure definition. We use this for all sorts of + * IPv4 and IPV6 builds. */ struct Curl_addrinfo { @@ -71,8 +71,10 @@ Curl_getaddrinfo_ex(const char *nodename, struct Curl_addrinfo **result); #endif +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) struct Curl_addrinfo * Curl_he2ai(const struct hostent *he, int port); +#endif struct Curl_addrinfo * Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port); diff --git a/Utilities/cmcurl/lib/curl_base64.h b/Utilities/cmcurl/lib/curl_base64.h deleted file mode 100644 index 806d4431cfc..00000000000 --- a/Utilities/cmcurl/lib/curl_base64.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef HEADER_CURL_BASE64_H -#define HEADER_CURL_BASE64_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 - * - ***************************************************************************/ - -CURLcode Curl_base64_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen); -CURLcode Curl_base64url_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen); -CURLcode Curl_base64_decode(const char *src, - unsigned char **outptr, size_t *outlen); - -#endif /* HEADER_CURL_BASE64_H */ diff --git a/Utilities/cmcurl/lib/curl_config.h.cmake b/Utilities/cmcurl/lib/curl_config.h.cmake index 6e2458dc8ba..5e942a8bcb8 100644 --- a/Utilities/cmcurl/lib/curl_config.h.cmake +++ b/Utilities/cmcurl/lib/curl_config.h.cmake @@ -21,18 +21,35 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -/* lib/curl_config.h.in. Generated somehow by cmake. */ #include +/* Default SSL backend */ +#cmakedefine CURL_DEFAULT_SSL_BACKEND "${CURL_DEFAULT_SSL_BACKEND}" + /* disables alt-svc */ #cmakedefine CURL_DISABLE_ALTSVC 1 /* disables cookies support */ #cmakedefine CURL_DISABLE_COOKIES 1 -/* disables cryptographic authentication */ -#cmakedefine CURL_DISABLE_CRYPTO_AUTH 1 +/* disables Basic authentication */ +#cmakedefine CURL_DISABLE_BASIC_AUTH 1 + +/* disables Bearer authentication */ +#cmakedefine CURL_DISABLE_BEARER_AUTH 1 + +/* disables Digest authentication */ +#cmakedefine CURL_DISABLE_DIGEST_AUTH 1 + +/* disables Kerberos authentication */ +#cmakedefine CURL_DISABLE_KERBEROS_AUTH 1 + +/* disables negotiate authentication */ +#cmakedefine CURL_DISABLE_NEGOTIATE_AUTH 1 + +/* disables aws-sigv4 */ +#cmakedefine CURL_DISABLE_AWS 1 /* disables DICT */ #cmakedefine CURL_DISABLE_DICT 1 @@ -43,18 +60,30 @@ /* disables FILE */ #cmakedefine CURL_DISABLE_FILE 1 +/* disables form api */ +#cmakedefine CURL_DISABLE_FORM_API 1 + /* disables FTP */ #cmakedefine CURL_DISABLE_FTP 1 +/* disables curl_easy_options API for existing options to curl_easy_setopt */ +#cmakedefine CURL_DISABLE_GETOPTIONS 1 + /* disables GOPHER */ #cmakedefine CURL_DISABLE_GOPHER 1 +/* disables headers-api support */ +#cmakedefine CURL_DISABLE_HEADERS_API 1 + /* disables HSTS support */ #cmakedefine CURL_DISABLE_HSTS 1 /* disables HTTP */ #cmakedefine CURL_DISABLE_HTTP 1 +/* disabled all HTTP authentication methods */ +#cmakedefine CURL_DISABLE_HTTP_AUTH 1 + /* disables IMAP */ #cmakedefine CURL_DISABLE_IMAP 1 @@ -70,6 +99,9 @@ /* disables MIME support */ #cmakedefine CURL_DISABLE_MIME 1 +/* disables local binding support */ +#cmakedefine CURL_DISABLE_BINDLOCAL 1 + /* disables MQTT */ #cmakedefine CURL_DISABLE_MQTT 1 @@ -91,15 +123,27 @@ /* disables proxies */ #cmakedefine CURL_DISABLE_PROXY 1 +/* disables IPFS from the curl tool */ +#cmakedefine CURL_DISABLE_IPFS 1 + /* disables RTSP */ #cmakedefine CURL_DISABLE_RTSP 1 +/* disables SHA-512/256 hash algorithm */ +#cmakedefine CURL_DISABLE_SHA512_256 1 + +/* disabled shuffle DNS feature */ +#cmakedefine CURL_DISABLE_SHUFFLE_DNS 1 + /* disables SMB */ #cmakedefine CURL_DISABLE_SMB 1 /* disables SMTP */ #cmakedefine CURL_DISABLE_SMTP 1 +/* disabled WebSockets */ +#cmakedefine CURL_DISABLE_WEBSOCKETS 1 + /* disables use of socketpair for curl_multi_poll */ #cmakedefine CURL_DISABLE_SOCKETPAIR 1 @@ -112,6 +156,12 @@ /* disables verbose strings */ #cmakedefine CURL_DISABLE_VERBOSE_STRINGS 1 +/* disables unsafe CA bundle search on Windows from the curl tool */ +#cmakedefine CURL_DISABLE_CA_SEARCH 1 + +/* safe CA bundle search (within the curl tool directory) on Windows */ +#cmakedefine CURL_CA_SEARCH_SAFE 1 + /* to make a symbol visible */ #cmakedefine CURL_EXTERN_SYMBOL ${CURL_EXTERN_SYMBOL} /* Ensure using CURL_EXTERN_SYMBOL is possible */ @@ -125,29 +175,26 @@ /* Use Windows LDAP implementation */ #cmakedefine USE_WIN32_LDAP 1 -/* when not building a shared library */ -#cmakedefine CURL_STATICLIB 1 - -/* your Entropy Gathering Daemon socket pathname */ -#cmakedefine EGD_SOCKET ${EGD_SOCKET} - /* Define if you want to enable IPv6 support */ -#cmakedefine ENABLE_IPV6 1 +#cmakedefine USE_IPV6 1 /* Define to 1 if you have the alarm function. */ #cmakedefine HAVE_ALARM 1 +/* Define to 1 if you have the arc4random function. */ +#cmakedefine HAVE_ARC4RANDOM 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ARPA_INET_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_ARPA_TFTP_H 1 - /* Define to 1 if you have _Atomic support. */ #cmakedefine HAVE_ATOMIC 1 -/* Define to 1 if you have the `fchmod' function. */ -#cmakedefine HAVE_FCHMOD 1 +/* Define to 1 if you have the `accept4' function. */ +#cmakedefine HAVE_ACCEPT4 1 + +/* Define to 1 if you have the `fnmatch' function. */ +#cmakedefine HAVE_FNMATCH 1 /* Define to 1 if you have the `basename' function. */ #cmakedefine HAVE_BASENAME 1 @@ -161,9 +208,22 @@ /* Define to 1 if you have the clock_gettime function and monotonic timer. */ #cmakedefine HAVE_CLOCK_GETTIME_MONOTONIC 1 +/* Define to 1 if you have the clock_gettime function and raw monotonic timer. + */ +#cmakedefine HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1 + /* Define to 1 if you have the `closesocket' function. */ #cmakedefine HAVE_CLOSESOCKET 1 +/* Define to 1 if you have the `CloseSocket' function. */ +#cmakedefine HAVE_CLOSESOCKET_CAMEL 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DIRENT_H 1 + +/* Define to 1 if you have the `opendir' function. */ +#cmakedefine HAVE_OPENDIR 1 + /* Define to 1 if you have the fcntl function. */ #cmakedefine HAVE_FCNTL 1 @@ -176,6 +236,12 @@ /* Define to 1 if you have the freeaddrinfo function. */ #cmakedefine HAVE_FREEADDRINFO 1 +/* Define to 1 if you have the fseeko function. */ +#cmakedefine HAVE_FSEEKO 1 + +/* Define to 1 if you have the fseeko declaration. */ +#cmakedefine HAVE_DECL_FSEEKO 1 + /* Define to 1 if you have the ftruncate function. */ #cmakedefine HAVE_FTRUNCATE 1 @@ -212,9 +278,6 @@ /* Define to 1 if you have the `getpass_r' function. */ #cmakedefine HAVE_GETPASS_R 1 -/* Define to 1 if you have the `getppid' function. */ -#cmakedefine HAVE_GETPPID 1 - /* Define to 1 if you have the `getpeername' function. */ #cmakedefine HAVE_GETPEERNAME 1 @@ -251,28 +314,16 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_GSSAPI_GSSAPI_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_GSSAPI_GSSAPI_KRB5_H 1 - /* if you have the GNU gssapi libraries */ #cmakedefine HAVE_GSSGNU 1 -/* if you have the Heimdal gssapi libraries */ -#cmakedefine HAVE_GSSHEIMDAL 1 - -/* if you have the MIT gssapi libraries */ -#cmakedefine HAVE_GSSMIT 1 - -/* Define to 1 if you have the `idna_strerror' function. */ -#cmakedefine HAVE_IDNA_STRERROR 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IFADDRS_H 1 -/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ +/* Define to 1 if you have an IPv6 capable working inet_ntop function. */ #cmakedefine HAVE_INET_NTOP 1 -/* Define to 1 if you have a IPv6 capable working inet_pton function. */ +/* Define to 1 if you have an IPv6 capable working inet_pton function. */ #cmakedefine HAVE_INET_PTON 1 /* Define to 1 if symbol `sa_family_t' exists */ @@ -281,9 +332,6 @@ /* Define to 1 if symbol `ADDRESS_FAMILY' exists */ #cmakedefine HAVE_ADDRESS_FAMILY 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_INTTYPES_H 1 - /* Define to 1 if you have the ioctlsocket function. */ #cmakedefine HAVE_IOCTLSOCKET 1 @@ -309,9 +357,6 @@ /* Define to 1 if you have the lber.h header file. */ #cmakedefine HAVE_LBER_H 1 -/* Define to 1 if you have the ldap.h header file. */ -#cmakedefine HAVE_LDAP_H 1 - /* Use LDAPS implementation */ #cmakedefine HAVE_LDAP_SSL 1 @@ -330,12 +375,6 @@ /* Define to 1 if you have the idn2.h header file. */ #cmakedefine HAVE_IDN2_H 1 -/* Define to 1 if you have the `socket' library (-lsocket). */ -#cmakedefine HAVE_LIBSOCKET 1 - -/* Define to 1 if you have the `ssh2' library (-lssh2). */ -#cmakedefine HAVE_LIBSSH2 1 - /* if zlib is available */ #cmakedefine HAVE_LIBZ 1 @@ -353,6 +392,9 @@ # define HAVE_LONGLONG 1 #endif +/* Define to 1 if you have the 'suseconds_t' data type. */ +#cmakedefine HAVE_SUSECONDS_T 1 + /* Define to 1 if you have the MSG_NOSIGNAL flag. */ #cmakedefine HAVE_MSG_NOSIGNAL 1 @@ -362,9 +404,15 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_IN_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IN6_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_TCP_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_UDP_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LINUX_TCP_H 1 @@ -377,8 +425,17 @@ /* Define to 1 if you have the `pipe' function. */ #cmakedefine HAVE_PIPE 1 -/* If you have a fine poll */ -#cmakedefine HAVE_POLL_FINE 1 +/* Define to 1 if you have the `pipe2' function. */ +#cmakedefine HAVE_PIPE2 1 + +/* Define to 1 if you have the `eventfd' function. */ +#cmakedefine HAVE_EVENTFD 1 + +/* If you have poll */ +#cmakedefine HAVE_POLL 1 + +/* If you have realpath */ +#cmakedefine HAVE_REALPATH 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_POLL_H 1 @@ -392,8 +449,8 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PWD_H 1 -/* Define to 1 if you have the `RAND_egd' function. */ -#cmakedefine HAVE_RAND_EGD 1 +/* Define to 1 if OpenSSL has the `SSL_set0_wbio` function. */ +#cmakedefine HAVE_SSL_SET0_WBIO 1 /* Define to 1 if you have the recv function. */ #cmakedefine HAVE_RECV 1 @@ -401,9 +458,21 @@ /* Define to 1 if you have the select function. */ #cmakedefine HAVE_SELECT 1 +/* Define to 1 if you have the sched_yield function. */ +#cmakedefine HAVE_SCHED_YIELD 1 + /* Define to 1 if you have the send function. */ #cmakedefine HAVE_SEND 1 +/* Define to 1 if you have the sendmsg function. */ +#cmakedefine HAVE_SENDMSG 1 + +/* Define to 1 if you have the sendmmsg function. */ +#cmakedefine HAVE_SENDMMSG 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + /* Define to 1 if you have the 'fsetxattr' function. */ #cmakedefine HAVE_FSETXATTR 1 @@ -413,15 +482,15 @@ /* fsetxattr() takes 6 args */ #cmakedefine HAVE_FSETXATTR_6 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SETJMP_H 1 - /* Define to 1 if you have the `setlocale' function. */ #cmakedefine HAVE_SETLOCALE 1 /* Define to 1 if you have the `setmode' function. */ #cmakedefine HAVE_SETMODE 1 +/* Define to 1 if you have the `_setmode' function. */ +#cmakedefine HAVE__SETMODE 1 + /* Define to 1 if you have the `setrlimit' function. */ #cmakedefine HAVE_SETRLIMIT 1 @@ -437,36 +506,30 @@ /* Define to 1 if you have the signal function. */ #cmakedefine HAVE_SIGNAL 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SIGNAL_H 1 - /* Define to 1 if you have the sigsetjmp function or macro. */ #cmakedefine HAVE_SIGSETJMP 1 +/* Define to 1 if you have the `snprintf' function. */ +#cmakedefine HAVE_SNPRINTF 1 + /* Define to 1 if struct sockaddr_in6 has the sin6_scope_id member */ #cmakedefine HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 /* Define to 1 if you have the `socket' function. */ #cmakedefine HAVE_SOCKET 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PROTO_BSDSOCKET_H 1 + /* Define to 1 if you have the socketpair function. */ #cmakedefine HAVE_SOCKETPAIR 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SSL_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDATOMIC_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDBOOL_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STDLIB_H 1 - /* Define to 1 if you have the strcasecmp function. */ #cmakedefine HAVE_STRCASECMP 1 @@ -485,17 +548,11 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STRINGS_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STRING_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STROPTS_H 1 -/* Define to 1 if you have the strtok_r function. */ -#cmakedefine HAVE_STRTOK_R 1 - -/* Define to 1 if you have the strtoll function. */ -#cmakedefine HAVE_STRTOLL 1 +/* Define to 1 if you have the memrchr function. */ +#cmakedefine HAVE_MEMRCHR 1 /* if struct sockaddr_storage is defined */ #cmakedefine HAVE_STRUCT_SOCKADDR_STORAGE 1 @@ -503,6 +560,9 @@ /* Define to 1 if you have the timeval struct. */ #cmakedefine HAVE_STRUCT_TIMEVAL 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_EVENTFD_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_FILIO_H 1 @@ -548,9 +608,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_TERMIO_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_TIME_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H 1 @@ -563,59 +620,17 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UTIME_H 1 -/* Define to 1 if compiler supports C99 variadic macro style. */ -#cmakedefine HAVE_VARIADIC_MACROS_C99 1 - -/* Define to 1 if compiler supports old gcc variadic macro style. */ -#cmakedefine HAVE_VARIADIC_MACROS_GCC 1 - -/* Define to 1 if you have the windows.h header file. */ -#cmakedefine HAVE_WINDOWS_H 1 - -/* Define to 1 if you have the winldap.h header file. */ -#cmakedefine HAVE_WINLDAP_H 1 - -/* Define to 1 if you have the winsock2.h header file. */ -#cmakedefine HAVE_WINSOCK2_H 1 - /* Define this symbol if your OS supports changing the contents of argv */ #cmakedefine HAVE_WRITABLE_ARGV 1 -/* Define to 1 if you have the ws2tcpip.h header file. */ -#cmakedefine HAVE_WS2TCPIP_H 1 - -/* Define to 1 if you need the lber.h header file even with ldap.h */ -#cmakedefine NEED_LBER_H 1 - -/* Define to 1 if you need the malloc.h header file even with stdlib.h */ -#cmakedefine NEED_MALLOC_H 1 +/* Define this if time_t is unsigned */ +#cmakedefine HAVE_TIME_T_UNSIGNED 1 /* Define to 1 if _REENTRANT preprocessor symbol must be defined. */ #cmakedefine NEED_REENTRANT 1 /* cpu-machine-OS */ -#cmakedefine OS ${OS} - -/* Name of package */ -#cmakedefine PACKAGE ${PACKAGE} - -/* Define to the address where bug reports for this package should be sent. */ -#cmakedefine PACKAGE_BUGREPORT ${PACKAGE_BUGREPORT} - -/* Define to the full name of this package. */ -#cmakedefine PACKAGE_NAME ${PACKAGE_NAME} - -/* Define to the full name and version of this package. */ -#cmakedefine PACKAGE_STRING ${PACKAGE_STRING} - -/* Define to the one symbol short name of this package. */ -#cmakedefine PACKAGE_TARNAME ${PACKAGE_TARNAME} - -/* Define to the version of this package. */ -#cmakedefine PACKAGE_VERSION ${PACKAGE_VERSION} - -/* a suitable file to read random data from */ -#cmakedefine RANDOM_FILE "${RANDOM_FILE}" +#cmakedefine CURL_OS ${CURL_OS} /* Note: SIZEOF_* variables are fetched with CMake through check_type_size(). @@ -643,12 +658,18 @@ # define SIZEOF___INT64 KWIML_ABI_SIZEOF___INT64 #endif +/* The size of `long long', as computed by sizeof. */ +${SIZEOF_LONG_LONG_CODE} + /* The size of `off_t', as computed by sizeof. */ ${SIZEOF_OFF_T_CODE} /* The size of `curl_off_t', as computed by sizeof. */ ${SIZEOF_CURL_OFF_T_CODE} +/* The size of `curl_socket_t', as computed by sizeof. */ +${SIZEOF_CURL_SOCKET_T_CODE} + /* The size of `size_t', as computed by sizeof. */ ${SIZEOF_SIZE_T_CODE} @@ -661,16 +682,13 @@ ${SIZEOF_TIME_T_CODE} /* Define to 1 if you have the ANSI C header files. */ #cmakedefine STDC_HEADERS 1 -/* Define to 1 if you can safely include both and . */ -#cmakedefine TIME_WITH_SYS_TIME 1 - /* Define if you want to enable c-ares support */ #cmakedefine USE_ARES 1 /* Define if you want to enable POSIX threaded DNS lookup */ #cmakedefine USE_THREADS_POSIX 1 -/* Define if you want to enable WIN32 threaded DNS lookup */ +/* Define if you want to enable Win32 threaded DNS lookup */ #cmakedefine USE_THREADS_WIN32 1 /* if GnuTLS is enabled */ @@ -679,32 +697,47 @@ ${SIZEOF_TIME_T_CODE} /* if Secure Transport is enabled */ #cmakedefine USE_SECTRANSP 1 +/* if SSL session export support is available */ +#cmakedefine USE_SSLS_EXPORT 1 + /* if mbedTLS is enabled */ #cmakedefine USE_MBEDTLS 1 /* if BearSSL is enabled */ #cmakedefine USE_BEARSSL 1 -/* if WolfSSL is enabled */ +/* if Rustls is enabled */ +#cmakedefine USE_RUSTLS 1 + +/* if wolfSSL is enabled */ #cmakedefine USE_WOLFSSL 1 -/* if libSSH is in use */ -#cmakedefine USE_LIBSSH 1 +/* if wolfSSL has the wolfSSL_get_peer_certificate function. */ +#cmakedefine HAVE_WOLFSSL_GET_PEER_CERTIFICATE 1 -/* if libSSH2 is in use */ -#cmakedefine USE_LIBSSH2 1 +/* if wolfSSL has the wolfSSL_UseALPN function. */ +#cmakedefine HAVE_WOLFSSL_USEALPN 1 -/* if libPSL is in use */ -#cmakedefine USE_LIBPSL 1 +/* if wolfSSL has the wolfSSL_DES_ecb_encrypt function. */ +#cmakedefine HAVE_WOLFSSL_DES_ECB_ENCRYPT 1 + +/* if wolfSSL has the wolfSSL_BIO_new function. */ +#cmakedefine HAVE_WOLFSSL_BIO_NEW 1 + +/* if wolfSSL has the wolfSSL_BIO_set_shutdown function. */ +#cmakedefine HAVE_WOLFSSL_BIO_SET_SHUTDOWN 1 -/* If you want to build curl with the built-in manual */ -#cmakedefine USE_MANUAL 1 +/* if libssh is in use */ +#cmakedefine USE_LIBSSH 1 + +/* if libssh2 is in use */ +#cmakedefine USE_LIBSSH2 1 -/* if NSS is enabled */ -#cmakedefine USE_NSS 1 +/* if wolfssh is in use */ +#cmakedefine USE_WOLFSSH 1 -/* if you have the PK11_CreateManagedGenericObject function */ -#cmakedefine HAVE_PK11_CREATEMANAGEDGENERICOBJECT 1 +/* if libpsl is in use */ +#cmakedefine USE_LIBPSL 1 /* if you want to use OpenLDAP code instead of legacy ldap implementation */ #cmakedefine USE_OPENLDAP 1 @@ -712,7 +745,22 @@ ${SIZEOF_TIME_T_CODE} /* if OpenSSL is in use */ #cmakedefine USE_OPENSSL 1 -/* Define to 1 if you don't want the OpenSSL configuration to be loaded +/* if AmiSSL is in use */ +#cmakedefine USE_AMISSL 1 + +/* if librtmp/rtmpdump is in use */ +#cmakedefine USE_LIBRTMP 1 + +/* if GSASL is in use */ +#cmakedefine USE_GSASL 1 + +/* if libuv is in use */ +#cmakedefine USE_LIBUV 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UV_H 1 + +/* Define to 1 if you do not want the OpenSSL configuration to be loaded automatically */ #cmakedefine CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG 1 @@ -728,6 +776,12 @@ ${SIZEOF_TIME_T_CODE} /* to enable quiche */ #cmakedefine USE_QUICHE 1 +/* to enable openssl + nghttp3 */ +#cmakedefine USE_OPENSSL_QUIC 1 + +/* to enable openssl + ngtcp2 + nghttp3 */ +#cmakedefine OPENSSL_QUIC_API2 1 + /* Define to 1 if you have the quiche_conn_set_qlog_fd function. */ #cmakedefine HAVE_QUICHE_CONN_SET_QLOG_FD 1 @@ -735,7 +789,7 @@ ${SIZEOF_TIME_T_CODE} #cmakedefine USE_MSH3 1 /* if Unix domain sockets are enabled */ -#cmakedefine USE_UNIX_SOCKETS +#cmakedefine USE_UNIX_SOCKETS 1 /* to enable SSPI support */ #cmakedefine USE_WINDOWS_SSPI 1 @@ -743,17 +797,12 @@ ${SIZEOF_TIME_T_CODE} /* to enable Windows SSL */ #cmakedefine USE_SCHANNEL 1 +/* if Watt-32 is in use */ +#cmakedefine USE_WATT32 1 + /* enable multiple SSL backends */ #cmakedefine CURL_WITH_MULTI_SSL 1 -/* Version number of package */ -#cmakedefine VERSION ${VERSION} - -/* Define to 1 if OS is AIX. */ -#ifndef _ALL_SOURCE -# undef _ALL_SOURCE -#endif - /* Number of bits in a file offset, on hosts where this is settable. */ #cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} @@ -766,15 +815,6 @@ ${SIZEOF_TIME_T_CODE} /* Define to empty if `const' does not conform to ANSI C. */ #cmakedefine const ${const} -/* Type to use in place of in_addr_t when system does not provide it. */ -#cmakedefine in_addr_t ${in_addr_t} - -/* Define to `__inline__' or `__inline' if that's what the C compiler - calls it, or to nothing if 'inline' is not supported under any name. */ -#ifndef __cplusplus -#undef inline -#endif - /* Define to `unsigned int' if does not define. */ #cmakedefine size_t ${size_t} @@ -797,5 +837,26 @@ ${SIZEOF_TIME_T_CODE} /* to enable Windows IDN */ #cmakedefine USE_WIN32_IDN 1 -/* Define to 1 to enable websocket support. */ -#cmakedefine USE_WEBSOCKETS 1 +/* to enable Apple IDN */ +#cmakedefine USE_APPLE_IDN 1 + +/* Define to 1 if OpenSSL has the SSL_CTX_set_srp_username function. */ +#cmakedefine HAVE_OPENSSL_SRP 1 + +/* Define to 1 if GnuTLS has the gnutls_srp_verifier function. */ +#cmakedefine HAVE_GNUTLS_SRP 1 + +/* Define to 1 to enable TLS-SRP support. */ +#cmakedefine USE_TLS_SRP 1 + +/* Define to 1 to query for HTTPSRR when using DoH */ +#cmakedefine USE_HTTPSRR 1 + +/* if ECH support is available */ +#cmakedefine USE_ECH 1 + +/* Define to 1 if you have the wolfSSL_CTX_GenerateEchConfig function. */ +#cmakedefine HAVE_WOLFSSL_CTX_GENERATEECHCONFIG + +/* Define to 1 if you have the SSL_set1_ech_config_list function. */ +#cmakedefine HAVE_SSL_SET1_ECH_CONFIG_LIST diff --git a/Utilities/cmcurl/lib/curl_ctype.h b/Utilities/cmcurl/lib/curl_ctype.h index 1d1d60c28d1..48c3c37c359 100644 --- a/Utilities/cmcurl/lib/curl_ctype.h +++ b/Utilities/cmcurl/lib/curl_ctype.h @@ -37,11 +37,16 @@ #define ISCNTRL(x) (ISLOWCNTRL(x) || IS7F(x)) #define ISALPHA(x) (ISLOWER(x) || ISUPPER(x)) #define ISXDIGIT(x) (ISDIGIT(x) || ISLOWHEXALHA(x) || ISUPHEXALHA(x)) +#define ISODIGIT(x) (((x) >= '0') && ((x) <= '7')) #define ISALNUM(x) (ISDIGIT(x) || ISLOWER(x) || ISUPPER(x)) #define ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z')) #define ISLOWER(x) (((x) >= 'a') && ((x) <= 'z')) #define ISDIGIT(x) (((x) >= '0') && ((x) <= '9')) #define ISBLANK(x) (((x) == ' ') || ((x) == '\t')) #define ISSPACE(x) (ISBLANK(x) || (((x) >= 0xa) && ((x) <= 0x0d))) +#define ISURLPUNTCS(x) (((x) == '-') || ((x) == '.') || ((x) == '_') || \ + ((x) == '~')) +#define ISUNRESERVED(x) (ISALNUM(x) || ISURLPUNTCS(x)) +#define ISNEWLINE(x) (((x) == '\n') || (x) == '\r') #endif /* HEADER_CURL_CTYPE_H */ diff --git a/Utilities/cmcurl/lib/curl_des.c b/Utilities/cmcurl/lib/curl_des.c index 5c623b35bcf..15836f58b97 100644 --- a/Utilities/cmcurl/lib/curl_des.c +++ b/Utilities/cmcurl/lib/curl_des.c @@ -24,12 +24,11 @@ #include "curl_setup.h" -#if defined(USE_CURL_NTLM_CORE) && !defined(USE_WOLFSSL) && \ - (defined(USE_GNUTLS) || \ - defined(USE_NSS) || \ - defined(USE_SECTRANSP) || \ - defined(USE_OS400CRYPTO) || \ - defined(USE_WIN32_CRYPTO)) +#if defined(USE_CURL_NTLM_CORE) && \ + (defined(USE_GNUTLS) || \ + defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || \ + defined(USE_WIN32_CRYPTO)) #include "curl_des.h" @@ -37,7 +36,7 @@ * Curl_des_set_odd_parity() * * This is used to apply odd parity to the given byte array. It is typically - * used by when a cryptography engines doesn't have it's own version. + * used by when a cryptography engine does not have its own version. * * The function is a port of the Java based oddParity() function over at: * diff --git a/Utilities/cmcurl/lib/curl_des.h b/Utilities/cmcurl/lib/curl_des.h index 6ec450accce..2dd498da245 100644 --- a/Utilities/cmcurl/lib/curl_des.h +++ b/Utilities/cmcurl/lib/curl_des.h @@ -26,12 +26,11 @@ #include "curl_setup.h" -#if defined(USE_CURL_NTLM_CORE) && !defined(USE_WOLFSSL) && \ - (defined(USE_GNUTLS) || \ - defined(USE_NSS) || \ - defined(USE_SECTRANSP) || \ - defined(USE_OS400CRYPTO) || \ - defined(USE_WIN32_CRYPTO)) +#if defined(USE_CURL_NTLM_CORE) && \ + (defined(USE_GNUTLS) || \ + defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || \ + defined(USE_WIN32_CRYPTO)) /* Applies odd parity to the given byte array */ void Curl_des_set_odd_parity(unsigned char *bytes, size_t length); diff --git a/Utilities/cmcurl/lib/curl_endian.c b/Utilities/cmcurl/lib/curl_endian.c index 11c662a4c7e..d982e31269f 100644 --- a/Utilities/cmcurl/lib/curl_endian.c +++ b/Utilities/cmcurl/lib/curl_endian.c @@ -30,7 +30,7 @@ * Curl_read16_le() * * This function converts a 16-bit integer from the little endian format, as - * used in the incoming package to whatever endian format we're using + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: @@ -49,7 +49,7 @@ unsigned short Curl_read16_le(const unsigned char *buf) * Curl_read32_le() * * This function converts a 32-bit integer from the little endian format, as - * used in the incoming package to whatever endian format we're using + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: @@ -68,7 +68,7 @@ unsigned int Curl_read32_le(const unsigned char *buf) * Curl_read16_be() * * This function converts a 16-bit integer from the big endian format, as - * used in the incoming package to whatever endian format we're using + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: diff --git a/Utilities/cmcurl/lib/curl_fnmatch.c b/Utilities/cmcurl/lib/curl_fnmatch.c index 5f9ca4f1be3..21eca4ceda5 100644 --- a/Utilities/cmcurl/lib/curl_fnmatch.c +++ b/Utilities/cmcurl/lib/curl_fnmatch.c @@ -71,16 +71,16 @@ typedef enum { #define SETCHARSET_OK 1 #define SETCHARSET_FAIL 0 -static int parsekeyword(unsigned char **pattern, unsigned char *charset) +static int parsekeyword(const unsigned char **pattern, unsigned char *charset) { parsekey_state state = CURLFNM_PKW_INIT; #define KEYLEN 10 char keyword[KEYLEN] = { 0 }; int i; - unsigned char *p = *pattern; + const unsigned char *p = *pattern; bool found = FALSE; for(i = 0; !found; i++) { - char c = *p++; + char c = (char)*p++; if(i >= KEYLEN) return SETCHARSET_FAIL; switch(state) { @@ -140,9 +140,9 @@ static char_class charclass(unsigned char c) } /* Include a character or a range in set. */ -static void setcharorrange(unsigned char **pp, unsigned char *charset) +static void setcharorrange(const unsigned char **pp, unsigned char *charset) { - unsigned char *p = (*pp)++; + const unsigned char *p = (*pp)++; unsigned char c = *p++; charset[c] = 1; @@ -161,8 +161,8 @@ static void setcharorrange(unsigned char **pp, unsigned char *charset) } } -/* returns 1 (true) if pattern is OK, 0 if is bad ("p" is pattern pointer) */ -static int setcharset(unsigned char **p, unsigned char *charset) +/* returns 1 (TRUE) if pattern is OK, 0 if is bad ("p" is pattern pointer) */ +static int setcharset(const unsigned char **p, unsigned char *charset) { setcharset_state state = CURLFNM_SCHS_DEFAULT; bool something_found = FALSE; @@ -185,7 +185,7 @@ static int setcharset(unsigned char **p, unsigned char *charset) (*p)++; } else if(c == '[') { - unsigned char *pp = *p + 1; + const unsigned char *pp = *p + 1; if(*pp++ == ':' && parsekeyword(&pp, charset)) *p = pp; @@ -257,12 +257,12 @@ static int setcharset(unsigned char **p, unsigned char *charset) static int loop(const unsigned char *pattern, const unsigned char *string, int maxstars) { - unsigned char *p = (unsigned char *)pattern; - unsigned char *s = (unsigned char *)string; + const unsigned char *p = (const unsigned char *)pattern; + const unsigned char *s = (const unsigned char *)string; unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; for(;;) { - unsigned char *pp; + const unsigned char *pp; switch(*p) { case '*': @@ -293,7 +293,7 @@ static int loop(const unsigned char *pattern, const unsigned char *string, p++; break; case '\0': - return *s? CURL_FNMATCH_NOMATCH: CURL_FNMATCH_MATCH; + return *s ? CURL_FNMATCH_NOMATCH : CURL_FNMATCH_MATCH; case '\\': if(p[1]) p++; @@ -303,7 +303,7 @@ static int loop(const unsigned char *pattern, const unsigned char *string, case '[': pp = p + 1; /* Copy in case of syntax error in set. */ if(setcharset(&pp, charset)) { - int found = FALSE; + bool found = FALSE; if(!*s) return CURL_FNMATCH_NOMATCH; if(charset[(unsigned int)*s]) @@ -319,7 +319,7 @@ static int loop(const unsigned char *pattern, const unsigned char *string, else if(charset[CURLFNM_PRINT]) found = ISPRINT(*s); else if(charset[CURLFNM_SPACE]) - found = ISSPACE(*s); + found = ISBLANK(*s); else if(charset[CURLFNM_UPPER]) found = ISUPPER(*s); else if(charset[CURLFNM_LOWER]) @@ -359,7 +359,8 @@ int Curl_fnmatch(void *ptr, const char *pattern, const char *string) if(!pattern || !string) { return CURL_FNMATCH_FAIL; } - return loop((unsigned char *)pattern, (unsigned char *)string, 2); + return loop((const unsigned char *)pattern, + (const unsigned char *)string, 2); } #else #include diff --git a/Utilities/cmcurl/lib/curl_fnmatch.h b/Utilities/cmcurl/lib/curl_fnmatch.h index 595646ff0d2..b8c2a4353c6 100644 --- a/Utilities/cmcurl/lib/curl_fnmatch.h +++ b/Utilities/cmcurl/lib/curl_fnmatch.h @@ -31,7 +31,7 @@ /* default pattern matching function * ================================= * Implemented with recursive backtracking, if you want to use Curl_fnmatch, - * please note that there is not implemented UTF/UNICODE support. + * please note that there is not implemented UTF/Unicode support. * * Implemented features: * '?' notation, does not match UTF characters diff --git a/Utilities/cmcurl/lib/curl_get_line.c b/Utilities/cmcurl/lib/curl_get_line.c index 686abe7511b..2bb57492b30 100644 --- a/Utilities/cmcurl/lib/curl_get_line.c +++ b/Utilities/cmcurl/lib/curl_get_line.c @@ -33,14 +33,16 @@ #include "memdebug.h" /* - * Curl_get_line() makes sure to only return complete whole lines that fit in - * 'len' bytes and end with a newline. + * Curl_get_line() makes sure to only return complete whole lines that end + * newlines. */ -char *Curl_get_line(char *buf, int len, FILE *input) +int Curl_get_line(struct dynbuf *buf, FILE *input) { - bool partial = FALSE; + CURLcode result; + char buffer[128]; + curlx_dyn_reset(buf); while(1) { - char *b = fgets(buf, len, input); + char *b = fgets(buffer, sizeof(buffer), input); if(b) { size_t rlen = strlen(b); @@ -48,39 +50,30 @@ char *Curl_get_line(char *buf, int len, FILE *input) if(!rlen) break; - if(b[rlen-1] == '\n') { - /* b is \n terminated */ - if(partial) { - partial = FALSE; - continue; - } - return b; - } - else if(feof(input)) { - if(partial) - /* Line is already too large to return, ignore rest */ - break; + result = curlx_dyn_addn(buf, b, rlen); + if(result) + /* too long line or out of memory */ + return 0; /* error */ - if(rlen + 1 < (size_t) len) { - /* b is EOF terminated, insert missing \n */ - b[rlen] = '\n'; - b[rlen + 1] = '\0'; - return b; - } - else - /* Maximum buffersize reached + EOF - * This line is impossible to add a \n to so we'll ignore it - */ - break; + else if(b[rlen-1] == '\n') + /* end of the line */ + return 1; /* all good */ + + else if(feof(input)) { + /* append a newline */ + result = curlx_dyn_addn(buf, "\n", 1); + if(result) + /* too long line or out of memory */ + return 0; /* error */ + return 1; /* all good */ } - else - /* Maximum buffersize reached */ - partial = TRUE; } + else if(curlx_dyn_len(buf)) + return 1; /* all good */ else break; } - return NULL; + return 0; } #endif /* if not disabled */ diff --git a/Utilities/cmcurl/lib/curl_get_line.h b/Utilities/cmcurl/lib/curl_get_line.h index 0ff32c5c2cb..d4877123f26 100644 --- a/Utilities/cmcurl/lib/curl_get_line.h +++ b/Utilities/cmcurl/lib/curl_get_line.h @@ -24,8 +24,9 @@ * ***************************************************************************/ -/* get_line() makes sure to only return complete whole lines that fit in 'len' - * bytes and end with a newline. */ -char *Curl_get_line(char *buf, int len, FILE *input); +#include "curlx/dynbuf.h" + +/* Curl_get_line() returns complete lines that end with a newline. */ +int Curl_get_line(struct dynbuf *buf, FILE *input); #endif /* HEADER_CURL_GET_LINE_H */ diff --git a/Utilities/cmcurl/lib/curl_gethostname.c b/Utilities/cmcurl/lib/curl_gethostname.c index 706b2e68928..fb418b400d8 100644 --- a/Utilities/cmcurl/lib/curl_gethostname.c +++ b/Utilities/cmcurl/lib/curl_gethostname.c @@ -28,26 +28,17 @@ /* * Curl_gethostname() is a wrapper around gethostname() which allows - * overriding the host name that the function would normally return. + * overriding the hostname that the function would normally return. * This capability is used by the test suite to verify exact matching * of NTLM authentication, which exercises libcurl's MD4 and DES code * as well as by the SMTP module when a hostname is not provided. * - * For libcurl debug enabled builds host name overriding takes place + * For libcurl debug enabled builds hostname overriding takes place * when environment variable CURL_GETHOSTNAME is set, using the value - * held by the variable to override returned host name. + * held by the variable to override returned hostname. * * Note: The function always returns the un-qualified hostname rather * than being provider dependent. - * - * For libcurl shared library release builds the test suite preloads - * another shared library named libhostname using the LD_PRELOAD - * mechanism which intercepts, and might override, the gethostname() - * function call. In this case a given platform must support the - * LD_PRELOAD mechanism and additionally have environment variable - * CURL_GETHOSTNAME set in order to override the returned host name. - * - * For libcurl static library release builds no overriding takes place. */ int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) @@ -65,10 +56,13 @@ int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) #ifdef DEBUGBUILD - /* Override host name when environment variable CURL_GETHOSTNAME is set */ + /* Override hostname when environment variable CURL_GETHOSTNAME is set */ const char *force_hostname = getenv("CURL_GETHOSTNAME"); if(force_hostname) { - strncpy(name, force_hostname, namelen); + if(strlen(force_hostname) < (size_t)namelen) + strcpy(name, force_hostname); + else + return 1; /* can't do it */ err = 0; } else { @@ -78,11 +72,12 @@ int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) #else /* DEBUGBUILD */ - /* The call to system's gethostname() might get intercepted by the - libhostname library when libcurl is built as a non-debug shared - library when running the test suite. */ name[0] = '\0'; +#ifdef __AMIGA__ + err = gethostname((unsigned char *)name, namelen); +#else err = gethostname(name, namelen); +#endif #endif diff --git a/Utilities/cmcurl/lib/curl_gssapi.c b/Utilities/cmcurl/lib/curl_gssapi.c index c6fe1256b2c..f83701ad645 100644 --- a/Utilities/cmcurl/lib/curl_gssapi.c +++ b/Utilities/cmcurl/lib/curl_gssapi.c @@ -35,16 +35,21 @@ #include "memdebug.h" #if defined(__GNUC__) -#define CURL_ALIGN8 __attribute__ ((aligned(8))) +#define CURL_ALIGN8 __attribute__((aligned(8))) #else #define CURL_ALIGN8 #endif +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + gss_OID_desc Curl_spnego_mech_oid CURL_ALIGN8 = { - 6, (char *)"\x2b\x06\x01\x05\x05\x02" + 6, CURL_UNCONST("\x2b\x06\x01\x05\x05\x02") }; gss_OID_desc Curl_krb5_mech_oid CURL_ALIGN8 = { - 9, (char *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" + 9, CURL_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") }; OM_uint32 Curl_gss_init_sec_context( @@ -149,4 +154,8 @@ void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, #endif } +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + #endif /* HAVE_GSSAPI */ diff --git a/Utilities/cmcurl/lib/curl_hmac.h b/Utilities/cmcurl/lib/curl_hmac.h index 11625c0cb42..f54edeb49ed 100644 --- a/Utilities/cmcurl/lib/curl_hmac.h +++ b/Utilities/cmcurl/lib/curl_hmac.h @@ -24,36 +24,36 @@ * ***************************************************************************/ -#ifndef CURL_DISABLE_CRYPTO_AUTH +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) || \ + !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \ + defined(USE_SSL) #include #define HMAC_MD5_LENGTH 16 -typedef CURLcode (* HMAC_hinit_func)(void *context); -typedef void (* HMAC_hupdate_func)(void *context, - const unsigned char *data, - unsigned int len); -typedef void (* HMAC_hfinal_func)(unsigned char *result, void *context); - +typedef CURLcode (*HMAC_hinit)(void *context); +typedef void (*HMAC_hupdate)(void *context, + const unsigned char *data, + unsigned int len); +typedef void (*HMAC_hfinal)(unsigned char *result, void *context); /* Per-hash function HMAC parameters. */ struct HMAC_params { - HMAC_hinit_func - hmac_hinit; /* Initialize context procedure. */ - HMAC_hupdate_func hmac_hupdate; /* Update context with data. */ - HMAC_hfinal_func hmac_hfinal; /* Get final result procedure. */ - unsigned int hmac_ctxtsize; /* Context structure size. */ - unsigned int hmac_maxkeylen; /* Maximum key length (bytes). */ - unsigned int hmac_resultlen; /* Result length (bytes). */ + HMAC_hinit hinit; /* Initialize context procedure. */ + HMAC_hupdate hupdate; /* Update context with data. */ + HMAC_hfinal hfinal; /* Get final result procedure. */ + unsigned int ctxtsize; /* Context structure size. */ + unsigned int maxkeylen; /* Maximum key length (bytes). */ + unsigned int resultlen; /* Result length (bytes). */ }; /* HMAC computation context. */ struct HMAC_context { - const struct HMAC_params *hmac_hash; /* Hash function definition. */ - void *hmac_hashctxt1; /* Hash function context 1. */ - void *hmac_hashctxt2; /* Hash function context 2. */ + const struct HMAC_params *hash; /* Hash function definition. */ + void *hashctxt1; /* Hash function context 1. */ + void *hashctxt2; /* Hash function context 2. */ }; diff --git a/Utilities/cmcurl/lib/curl_krb5.h b/Utilities/cmcurl/lib/curl_krb5.h index ccf6b96a875..574340fd3c5 100644 --- a/Utilities/cmcurl/lib/curl_krb5.h +++ b/Utilities/cmcurl/lib/curl_krb5.h @@ -39,14 +39,16 @@ struct Curl_sec_client_mech { #define AUTH_CONTINUE 1 #define AUTH_ERROR 2 -#ifdef HAVE_GSSAPI +#if defined(HAVE_GSSAPI) && !defined(CURL_DISABLE_FTP) +void Curl_sec_conn_init(struct connectdata *); +void Curl_sec_conn_destroy(struct connectdata *); int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, char *, enum protection_level); -void Curl_sec_end(struct connectdata *); CURLcode Curl_sec_login(struct Curl_easy *, struct connectdata *); int Curl_sec_request_prot(struct connectdata *conn, const char *level); #else -#define Curl_sec_end(x) +#define Curl_sec_conn_init(x) Curl_nop_stmt +#define Curl_sec_conn_destroy(x) Curl_nop_stmt #endif #endif /* HEADER_CURL_KRB5_H */ diff --git a/Utilities/cmcurl/lib/curl_log.c b/Utilities/cmcurl/lib/curl_log.c deleted file mode 100644 index 71024cfc626..00000000000 --- a/Utilities/cmcurl/lib/curl_log.c +++ /dev/null @@ -1,230 +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 - -#include "curl_log.h" -#include "urldata.h" -#include "easyif.h" -#include "cfilters.h" -#include "timeval.h" -#include "multiif.h" -#include "strcase.h" - -#include "cf-socket.h" -#include "connect.h" -#include "http2.h" -#include "http_proxy.h" -#include "cf-h1-proxy.h" -#include "cf-h2-proxy.h" -#include "cf-haproxy.h" -#include "cf-https-connect.h" -#include "socks.h" -#include "strtok.h" -#include "vtls/vtls.h" -#include "vquic/vquic.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - - -void Curl_debug(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size) -{ - if(data->set.verbose) { - static const char s_infotype[CURLINFO_END][3] = { - "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; - if(data->set.fdebug) { - bool inCallback = Curl_is_in_callback(data); - Curl_set_in_callback(data, true); - (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata); - Curl_set_in_callback(data, inCallback); - } - else { - switch(type) { - case CURLINFO_TEXT: - case CURLINFO_HEADER_OUT: - case CURLINFO_HEADER_IN: - fwrite(s_infotype[type], 2, 1, data->set.err); - fwrite(ptr, size, 1, data->set.err); - break; - default: /* nada */ - break; - } - } - } -} - - -/* Curl_failf() is for messages stating why we failed. - * The message SHALL NOT include any LF or CR. - */ -void Curl_failf(struct Curl_easy *data, const char *fmt, ...) -{ - DEBUGASSERT(!strchr(fmt, '\n')); - if(data->set.verbose || data->set.errorbuffer) { - va_list ap; - int len; - char error[CURL_ERROR_SIZE + 2]; - va_start(ap, fmt); - len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap); - - if(data->set.errorbuffer && !data->state.errorbuf) { - strcpy(data->set.errorbuffer, error); - data->state.errorbuf = TRUE; /* wrote error string */ - } - error[len++] = '\n'; - error[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, error, len); - va_end(ap); - } -} - -/* Curl_infof() is for info message along the way */ -#define MAXINFO 2048 - -void Curl_infof(struct Curl_easy *data, const char *fmt, ...) -{ - DEBUGASSERT(!strchr(fmt, '\n')); - if(data && data->set.verbose) { - va_list ap; - int len; - char buffer[MAXINFO + 2]; - va_start(ap, fmt); - len = mvsnprintf(buffer, MAXINFO, fmt, ap); - va_end(ap); - buffer[len++] = '\n'; - buffer[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, buffer, len); - } -} - -#ifdef DEBUGBUILD - -void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, - const char *fmt, ...) -{ - DEBUGASSERT(cf); - if(data && Curl_log_cf_is_debug(cf)) { - va_list ap; - int len; - char buffer[MAXINFO + 2]; - len = msnprintf(buffer, MAXINFO, "[CONN-%ld%s-%s] ", - cf->conn->connection_id, cf->sockindex? "/2" : "", - cf->cft->name); - va_start(ap, fmt); - len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap); - va_end(ap); - buffer[len++] = '\n'; - buffer[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, buffer, len); - } -} - - -static struct Curl_cftype *cf_types[] = { - &Curl_cft_tcp, - &Curl_cft_udp, - &Curl_cft_unix, - &Curl_cft_tcp_accept, - &Curl_cft_happy_eyeballs, - &Curl_cft_setup, -#ifdef USE_NGHTTP2 - &Curl_cft_nghttp2, -#endif -#ifdef USE_SSL - &Curl_cft_ssl, - &Curl_cft_ssl_proxy, -#endif -#if !defined(CURL_DISABLE_PROXY) -#if !defined(CURL_DISABLE_HTTP) - &Curl_cft_h1_proxy, -#ifdef USE_NGHTTP2 - &Curl_cft_h2_proxy, -#endif - &Curl_cft_http_proxy, -#endif /* !CURL_DISABLE_HTTP */ - &Curl_cft_haproxy, - &Curl_cft_socks_proxy, -#endif /* !CURL_DISABLE_PROXY */ -#ifdef ENABLE_QUIC - &Curl_cft_http3, -#endif -#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) - &Curl_cft_http_connect, -#endif - NULL, -}; - -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif - -CURLcode Curl_log_init(void) -{ - const char *setting = getenv("CURL_DEBUG"); - if(setting) { - char *token, *tok_buf, *tmp; - size_t i; - - tmp = strdup(setting); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - - token = strtok_r(tmp, ", ", &tok_buf); - while(token) { - for(i = 0; cf_types[i]; ++i) { - if(strcasecompare(token, cf_types[i]->name)) { - cf_types[i]->log_level = CURL_LOG_DEBUG; - break; - } - } - token = strtok_r(NULL, ", ", &tok_buf); - } - free(tmp); - } - return CURLE_OK; -} -#else /* DEBUGBUILD */ - -CURLcode Curl_log_init(void) -{ - return CURLE_OK; -} - -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, - const char *fmt, ...) -{ - (void)data; - (void)cf; - (void)fmt; -} -#endif - -#endif /* !DEBUGBUILD */ diff --git a/Utilities/cmcurl/lib/curl_log.h b/Utilities/cmcurl/lib/curl_log.h deleted file mode 100644 index ad6143fa991..00000000000 --- a/Utilities/cmcurl/lib/curl_log.h +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef HEADER_CURL_LOG_H -#define HEADER_CURL_LOG_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 - * - ***************************************************************************/ - -struct Curl_easy; -struct Curl_cfilter; - -/** - * Init logging, return != 0 on failure. - */ -CURLcode Curl_log_init(void); - - -void Curl_infof(struct Curl_easy *, const char *fmt, ...); -void Curl_failf(struct Curl_easy *, const char *fmt, ...); - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - -#if defined(HAVE_VARIADIC_MACROS_C99) -#define infof(...) Curl_nop_stmt -#elif defined(HAVE_VARIADIC_MACROS_GCC) -#define infof(x...) Curl_nop_stmt -#else -#error "missing VARIADIC macro define, fix and rebuild!" -#endif - -#else /* CURL_DISABLE_VERBOSE_STRINGS */ - -#define infof Curl_infof - -#endif /* CURL_DISABLE_VERBOSE_STRINGS */ - -#define failf Curl_failf - - -#define CURL_LOG_DEFAULT 0 -#define CURL_LOG_DEBUG 1 -#define CURL_LOG_TRACE 2 - - -/* the function used to output verbose information */ -void Curl_debug(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size); - -#ifdef DEBUGBUILD - -/* explainer: we have some mix configuration and werror settings - * that define HAVE_VARIADIC_MACROS_C99 even though C89 is enforced - * on gnuc and some other compiler. Need to treat carefully. - */ -#if defined(HAVE_VARIADIC_MACROS_C99) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) - -#define LOG_CF(data, cf, ...) \ - do { if(Curl_log_cf_is_debug(cf)) \ - Curl_log_cf_debug(data, cf, __VA_ARGS__); } while(0) -#else -#define LOG_CF Curl_log_cf_debug -#endif - -void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, -#if defined(__GNUC__) && !defined(printf) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__MINGW32__) - const char *fmt, ...) - __attribute__((format(printf, 3, 4))); -#else - const char *fmt, ...); -#endif - -#define Curl_log_cf_is_debug(cf) \ - ((cf) && (cf)->cft->log_level >= CURL_LOG_DEBUG) - -#else /* !DEBUGBUILD */ - -#if defined(HAVE_VARIADIC_MACROS_C99) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -#define LOG_CF(...) Curl_nop_stmt -#define Curl_log_cf_debug(...) Curl_nop_stmt -#elif defined(HAVE_VARIADIC_MACROS_GCC) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -#define LOG_CF(x...) Curl_nop_stmt -#define Curl_log_cf_debug(x...) Curl_nop_stmt -#else -#define LOG_CF Curl_log_cf_debug -/* without c99, we seem unable to completely define away this function. */ -void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, - const char *fmt, ...); -#endif - -#define Curl_log_cf_is_debug(x) ((void)(x), FALSE) - -#endif /* !DEBUGBUILD */ - -#define LOG_CF_IS_DEBUG(x) Curl_log_cf_is_debug(x) - -/* Macros intended for DEBUGF logging, use like: - * DEBUGF(infof(data, CFMSG(cf, "this filter %s rocks"), "very much")); - * and it will output: - * [CONN-1-0][CF-SSL] this filter very much rocks - * on connection #1 with sockindex 0 for filter of type "SSL". */ -#define DMSG(d,msg) \ - "[CONN-%ld] "msg, (d)->conn->connection_id -#define DMSGI(d,i,msg) \ - "[CONN-%ld-%d] "msg, (d)->conn->connection_id, (i) -#define CMSG(c,msg) \ - "[CONN-%ld] "msg, (c)->connection_id -#define CMSGI(c,i,msg) \ - "[CONN-%ld-%d] "msg, (c)->connection_id, (i) -#define CFMSG(cf,msg) \ - "[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \ - (cf)->sockindex, (cf)->cft->name - - - -#endif /* HEADER_CURL_LOG_H */ diff --git a/Utilities/cmcurl/lib/curl_md4.h b/Utilities/cmcurl/lib/curl_md4.h index 03567b9916f..4706e49578b 100644 --- a/Utilities/cmcurl/lib/curl_md4.h +++ b/Utilities/cmcurl/lib/curl_md4.h @@ -25,14 +25,15 @@ ***************************************************************************/ #include "curl_setup.h" +#include -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#if defined(USE_CURL_NTLM_CORE) #define MD4_DIGEST_LENGTH 16 -void Curl_md4it(unsigned char *output, const unsigned char *input, - const size_t len); +CURLcode Curl_md4it(unsigned char *output, const unsigned char *input, + const size_t len); -#endif /* !defined(CURL_DISABLE_CRYPTO_AUTH) */ +#endif /* defined(USE_CURL_NTLM_CORE) */ #endif /* HEADER_CURL_MD4_H */ diff --git a/Utilities/cmcurl/lib/curl_md5.h b/Utilities/cmcurl/lib/curl_md5.h index ec2512f0028..ec27503b144 100644 --- a/Utilities/cmcurl/lib/curl_md5.h +++ b/Utilities/cmcurl/lib/curl_md5.h @@ -24,16 +24,18 @@ * ***************************************************************************/ -#ifndef CURL_DISABLE_CRYPTO_AUTH +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_DIGEST_AUTH) + #include "curl_hmac.h" #define MD5_DIGEST_LEN 16 -typedef CURLcode (* Curl_MD5_init_func)(void *context); -typedef void (* Curl_MD5_update_func)(void *context, - const unsigned char *data, - unsigned int len); -typedef void (* Curl_MD5_final_func)(unsigned char *result, void *context); +typedef CURLcode (*Curl_MD5_init_func)(void *context); +typedef void (*Curl_MD5_update_func)(void *context, + const unsigned char *data, + unsigned int len); +typedef void (*Curl_MD5_final_func)(unsigned char *result, void *context); struct MD5_params { Curl_MD5_init_func md5_init_func; /* Initialize context procedure */ @@ -48,8 +50,8 @@ struct MD5_context { void *md5_hashctx; /* Hash function context */ }; -extern const struct MD5_params Curl_DIGEST_MD5[1]; -extern const struct HMAC_params Curl_HMAC_MD5[1]; +extern const struct MD5_params Curl_DIGEST_MD5; +extern const struct HMAC_params Curl_HMAC_MD5; CURLcode Curl_md5it(unsigned char *output, const unsigned char *input, const size_t len); diff --git a/Utilities/cmcurl/lib/curl_memory.h b/Utilities/cmcurl/lib/curl_memory.h index 1a21c5ad4a0..bc3e944fea2 100644 --- a/Utilities/cmcurl/lib/curl_memory.h +++ b/Utilities/cmcurl/lib/curl_memory.h @@ -55,9 +55,53 @@ */ #ifdef HEADER_CURL_MEMDEBUG_H -#error "Header memdebug.h shall not be included before curl_memory.h" +/* cleanup after memdebug.h */ + +#ifdef MEMDEBUG_NODEFINES +#ifdef CURLDEBUG + +#undef strdup +#undef malloc +#undef calloc +#undef realloc +#undef free +#undef send +#undef recv + +#ifdef _WIN32 +# ifdef UNICODE +# undef wcsdup +# undef _wcsdup +# undef _tcsdup +# else +# undef _tcsdup +# endif #endif +#undef socket +#undef accept +#ifdef HAVE_SOCKETPAIR +#undef socketpair +#endif + +/* sclose is probably already defined, redefine it! */ +#undef sclose +#undef fopen +#undef fdopen +#undef fclose + +#endif /* MEMDEBUG_NODEFINES */ +#endif /* CURLDEBUG */ + +#undef HEADER_CURL_MEMDEBUG_H +#endif /* HEADER_CURL_MEMDEBUG_H */ + +/* +** Following section applies even when CURLDEBUG is not defined. +*/ + +#undef fake_sclose + #ifndef CURL_DID_MEMORY_FUNC_TYPEDEFS /* only if not already done */ /* * The following memory function replacement typedef's are COPIED from @@ -78,7 +122,7 @@ extern curl_free_callback Curl_cfree; extern curl_realloc_callback Curl_crealloc; extern curl_strdup_callback Curl_cstrdup; extern curl_calloc_callback Curl_ccalloc; -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) extern curl_wcsdup_callback Curl_cwcsdup; #endif @@ -104,7 +148,7 @@ extern curl_wcsdup_callback Curl_cwcsdup; #undef free #define free(ptr) Curl_cfree(ptr) -#ifdef WIN32 +#ifdef _WIN32 # ifdef UNICODE # undef wcsdup # define wcsdup(ptr) Curl_cwcsdup(ptr) diff --git a/Utilities/cmcurl/lib/curl_memrchr.c b/Utilities/cmcurl/lib/curl_memrchr.c index 3f3dc6de163..5b6a39c022c 100644 --- a/Utilities/cmcurl/lib/curl_memrchr.c +++ b/Utilities/cmcurl/lib/curl_memrchr.c @@ -33,7 +33,6 @@ #include "memdebug.h" #ifndef HAVE_MEMRCHR - /* * Curl_memrchr() * @@ -54,11 +53,10 @@ Curl_memrchr(const void *s, int c, size_t n) while(p >= q) { if(*p == (unsigned char)c) - return (void *)p; + return CURL_UNCONST(p); p--; } } return NULL; } - #endif /* HAVE_MEMRCHR */ diff --git a/Utilities/cmcurl/lib/curl_memrchr.h b/Utilities/cmcurl/lib/curl_memrchr.h index a1a4ba09273..3c7dda96ac9 100644 --- a/Utilities/cmcurl/lib/curl_memrchr.h +++ b/Utilities/cmcurl/lib/curl_memrchr.h @@ -28,17 +28,13 @@ #ifdef HAVE_MEMRCHR -#ifdef HAVE_STRING_H -# include -#endif +#include #ifdef HAVE_STRINGS_H # include #endif #else /* HAVE_MEMRCHR */ - void *Curl_memrchr(const void *s, int c, size_t n); - #define memrchr(x,y,z) Curl_memrchr((x),(y),(z)) #endif /* HAVE_MEMRCHR */ diff --git a/Utilities/cmcurl/lib/curl_multibyte.c b/Utilities/cmcurl/lib/curl_multibyte.c deleted file mode 100644 index 522ea34e827..00000000000 --- a/Utilities/cmcurl/lib/curl_multibyte.c +++ /dev/null @@ -1,179 +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 - * - ***************************************************************************/ - -/* - * This file is 'mem-include-scan' clean, which means memdebug.h and - * curl_memory.h are purposely not included in this file. See test 1132. - * - * The functions in this file are curlx functions which are not tracked by the - * curl memory tracker memdebug. - */ - -#include "curl_setup.h" - -#if defined(WIN32) - -#include "curl_multibyte.h" - -/* - * MultiByte conversions using Windows kernel32 library. - */ - -wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) -{ - wchar_t *str_w = NULL; - - if(str_utf8) { - int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - str_utf8, -1, NULL, 0); - if(str_w_len > 0) { - str_w = malloc(str_w_len * sizeof(wchar_t)); - if(str_w) { - if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, - str_w_len) == 0) { - free(str_w); - return NULL; - } - } - } - } - - return str_w; -} - -char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w) -{ - char *str_utf8 = NULL; - - if(str_w) { - int bytes = WideCharToMultiByte(CP_UTF8, 0, str_w, -1, - NULL, 0, NULL, NULL); - if(bytes > 0) { - str_utf8 = malloc(bytes); - if(str_utf8) { - if(WideCharToMultiByte(CP_UTF8, 0, str_w, -1, str_utf8, bytes, - NULL, NULL) == 0) { - free(str_utf8); - return NULL; - } - } - } - } - - return str_utf8; -} - -#endif /* WIN32 */ - -#if defined(USE_WIN32_LARGE_FILES) || defined(USE_WIN32_SMALL_FILES) - -int curlx_win32_open(const char *filename, int oflag, ...) -{ - int pmode = 0; - -#ifdef _UNICODE - int result = -1; - wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename); -#endif - - va_list param; - va_start(param, oflag); - if(oflag & O_CREAT) - pmode = va_arg(param, int); - va_end(param); - -#ifdef _UNICODE - if(filename_w) { - result = _wopen(filename_w, oflag, pmode); - curlx_unicodefree(filename_w); - } - else - errno = EINVAL; - return result; -#else - return (_open)(filename, oflag, pmode); -#endif -} - -FILE *curlx_win32_fopen(const char *filename, const char *mode) -{ -#ifdef _UNICODE - FILE *result = NULL; - wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename); - wchar_t *mode_w = curlx_convert_UTF8_to_wchar(mode); - if(filename_w && mode_w) - result = _wfopen(filename_w, mode_w); - else - errno = EINVAL; - curlx_unicodefree(filename_w); - curlx_unicodefree(mode_w); - return result; -#else - return (fopen)(filename, mode); -#endif -} - -int curlx_win32_stat(const char *path, struct_stat *buffer) -{ -#ifdef _UNICODE - int result = -1; - wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); - if(path_w) { -#if defined(USE_WIN32_SMALL_FILES) - result = _wstat(path_w, buffer); -#else - result = _wstati64(path_w, buffer); -#endif - curlx_unicodefree(path_w); - } - else - errno = EINVAL; - return result; -#else -#if defined(USE_WIN32_SMALL_FILES) - return _stat(path, buffer); -#else - return _stati64(path, buffer); -#endif -#endif -} - -int curlx_win32_access(const char *path, int mode) -{ -#if defined(_UNICODE) - int result = -1; - wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); - if(path_w) { - result = _waccess(path_w, mode); - curlx_unicodefree(path_w); - } - else - errno = EINVAL; - return result; -#else - return _access(path, mode); -#endif -} - -#endif /* USE_WIN32_LARGE_FILES || USE_WIN32_SMALL_FILES */ diff --git a/Utilities/cmcurl/lib/curl_multibyte.h b/Utilities/cmcurl/lib/curl_multibyte.h deleted file mode 100644 index ddac1f63821..00000000000 --- a/Utilities/cmcurl/lib/curl_multibyte.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef HEADER_CURL_MULTIBYTE_H -#define HEADER_CURL_MULTIBYTE_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" - -#if defined(WIN32) - - /* - * MultiByte conversions using Windows kernel32 library. - */ - -wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8); -char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w); -#endif /* WIN32 */ - -/* - * Macros curlx_convert_UTF8_to_tchar(), curlx_convert_tchar_to_UTF8() - * and curlx_unicodefree() main purpose is to minimize the number of - * preprocessor conditional directives needed by code using these - * to differentiate UNICODE from non-UNICODE builds. - * - * In the case of a non-UNICODE build the tchar strings are char strings that - * are duplicated via strdup and remain in whatever the passed in encoding is, - * which is assumed to be UTF-8 but may be other encoding. Therefore the - * significance of the conversion functions is primarily for UNICODE builds. - * - * Allocated memory should be free'd with curlx_unicodefree(). - * - * Note: Because these are curlx functions their memory usage is not tracked - * by the curl memory tracker memdebug. You'll notice that curlx function-like - * macros call free and strdup in parentheses, eg (strdup)(ptr), and that's to - * ensure that the curl memdebug override macros do not replace them. - */ - -#if defined(UNICODE) && defined(WIN32) - -#define curlx_convert_UTF8_to_tchar(ptr) curlx_convert_UTF8_to_wchar((ptr)) -#define curlx_convert_tchar_to_UTF8(ptr) curlx_convert_wchar_to_UTF8((ptr)) - -typedef union { - unsigned short *tchar_ptr; - const unsigned short *const_tchar_ptr; - unsigned short *tbyte_ptr; - const unsigned short *const_tbyte_ptr; -} xcharp_u; - -#else - -#define curlx_convert_UTF8_to_tchar(ptr) (strdup)(ptr) -#define curlx_convert_tchar_to_UTF8(ptr) (strdup)(ptr) - -typedef union { - char *tchar_ptr; - const char *const_tchar_ptr; - unsigned char *tbyte_ptr; - const unsigned char *const_tbyte_ptr; -} xcharp_u; - -#endif /* UNICODE && WIN32 */ - -#define curlx_unicodefree(ptr) \ - do { \ - if(ptr) { \ - (free)(ptr); \ - (ptr) = NULL; \ - } \ - } while(0) - -#endif /* HEADER_CURL_MULTIBYTE_H */ diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.c b/Utilities/cmcurl/lib/curl_ntlm_core.c index ba8457d6ea8..d6cd44d963e 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_core.c +++ b/Utilities/cmcurl/lib/curl_ntlm_core.c @@ -38,7 +38,7 @@ 1. USE_OPENSSL 2. USE_WOLFSSL 3. USE_GNUTLS - 4. USE_NSS + 4. - 5. USE_MBEDTLS 6. USE_SECTRANSP 7. USE_OS400CRYPTO @@ -47,7 +47,7 @@ This ensures that: - the same SSL branch gets activated throughout this source file even if multiple backends are enabled at the same time. - - OpenSSL and NSS have higher priority than Windows Crypt, due + - OpenSSL has higher priority than Windows Crypt, due to issues with the latter supporting NTLM2Session responses in NTLM type-3 messages. */ @@ -57,51 +57,52 @@ #if !defined(OPENSSL_NO_DES) && !defined(OPENSSL_NO_DEPRECATED_3_0) #define USE_OPENSSL_DES #endif +#elif defined(USE_WOLFSSL) + #include + #if !defined(NO_DES3) + #define USE_OPENSSL_DES + #endif #endif -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) #if defined(USE_OPENSSL) # include # include # include # include +# if defined(OPENSSL_IS_AWSLC) +# define DES_set_key_unchecked (void)DES_set_key +# define DESKEYARG(x) *x +# define DESKEY(x) &x +# else +# define DESKEYARG(x) *x +# define DESKEY(x) &x +# endif #else -# include # include # include # include # include -#endif - -# if (defined(OPENSSL_VERSION_NUMBER) && \ - (OPENSSL_VERSION_NUMBER < 0x00907001L)) && !defined(USE_WOLFSSL) -# define DES_key_schedule des_key_schedule -# define DES_cblock des_cblock -# define DES_set_odd_parity des_set_odd_parity -# define DES_set_key des_set_key -# define DES_ecb_encrypt des_ecb_encrypt -# define DESKEY(x) x -# define DESKEYARG(x) x -# elif defined(OPENSSL_IS_AWSLC) -# define DES_set_key_unchecked (void)DES_set_key +# if defined(OPENSSL_COEXIST) +# define DES_key_schedule WOLFSSL_DES_key_schedule +# define DES_cblock WOLFSSL_DES_cblock +# define DES_set_odd_parity wolfSSL_DES_set_odd_parity +# define DES_set_key wolfSSL_DES_set_key +# define DES_set_key_unchecked wolfSSL_DES_set_key_unchecked +# define DES_ecb_encrypt wolfSSL_DES_ecb_encrypt +# define DESKEY(x) ((WOLFSSL_DES_key_schedule *)(x)) # define DESKEYARG(x) *x -# define DESKEY(x) &x # else # define DESKEYARG(x) *x # define DESKEY(x) &x # endif +#endif #elif defined(USE_GNUTLS) # include -#elif defined(USE_NSS) - -# include -# include -# include - #elif defined(USE_MBEDTLS) # include @@ -116,7 +117,8 @@ #elif defined(USE_WIN32_CRYPTO) # include #else -# error "Can't compile NTLM support without a crypto library with DES." +# error "cannot compile NTLM support without a crypto library with DES." +# define CURL_NTLM_NOT_SUPPORTED #endif #include "urldata.h" @@ -124,7 +126,7 @@ #include "curl_ntlm_core.h" #include "curl_md5.h" #include "curl_hmac.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_endian.h" #include "curl_des.h" #include "curl_md4.h" @@ -133,27 +135,26 @@ #include "curl_memory.h" #include "memdebug.h" -#define NTLMv2_BLOB_SIGNATURE "\x01\x01\x00\x00" -#define NTLMv2_BLOB_LEN (44 -16 + ntlm->target_info_len + 4) - +#if !defined(CURL_NTLM_NOT_SUPPORTED) /* * Turns a 56-bit key into being 64-bit wide. */ static void extend_key_56_to_64(const unsigned char *key_56, char *key) { - key[0] = key_56[0]; - key[1] = (unsigned char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); - key[2] = (unsigned char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); - key[3] = (unsigned char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); - key[4] = (unsigned char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); - key[5] = (unsigned char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); - key[6] = (unsigned char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); - key[7] = (unsigned char) ((key_56[6] << 1) & 0xFF); + key[0] = (char)key_56[0]; + key[1] = (char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); + key[2] = (char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); + key[3] = (char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); + key[4] = (char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); + key[5] = (char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); + key[6] = (char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); + key[7] = (char) ((key_56[6] << 1) & 0xFF); } +#endif -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) /* - * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. The + * Turns a 56-bit key into a 64-bit, odd parity key and sets the key. The * key schedule ks is also set. */ static void setup_des_key(const unsigned char *key_56, @@ -161,7 +162,7 @@ static void setup_des_key(const unsigned char *key_56, { DES_cblock key; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, (char *) &key); /* Set the key parity to odd */ @@ -178,7 +179,7 @@ static void setup_des_key(const unsigned char *key_56, { char key[8]; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -188,70 +189,6 @@ static void setup_des_key(const unsigned char *key_56, des_set_key(des, (const uint8_t *) key); } -#elif defined(USE_NSS) - -/* - * encrypt_des() expands a 56 bit key KEY_56 to 64 bit and encrypts 64 bit of - * data, using the expanded key. IN should point to 64 bits of source data, - * OUT to a 64 bit output buffer. - */ -static bool encrypt_des(const unsigned char *in, unsigned char *out, - const unsigned char *key_56) -{ - const CK_MECHANISM_TYPE mech = CKM_DES_ECB; /* DES cipher in ECB mode */ - char key[8]; /* expanded 64 bit key */ - SECItem key_item; - PK11SymKey *symkey = NULL; - SECItem *param = NULL; - PK11Context *ctx = NULL; - int out_len; /* not used, required by NSS */ - bool rv = FALSE; - - /* use internal slot for DES encryption (requires NSS to be initialized) */ - PK11SlotInfo *slot = PK11_GetInternalKeySlot(); - if(!slot) - return FALSE; - - /* Expand the 56-bit key to 64-bits */ - extend_key_56_to_64(key_56, key); - - /* Set the key parity to odd */ - Curl_des_set_odd_parity((unsigned char *) key, sizeof(key)); - - /* Import the key */ - key_item.data = (unsigned char *)key; - key_item.len = sizeof(key); - symkey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap, CKA_ENCRYPT, - &key_item, NULL); - if(!symkey) - goto fail; - - /* Create the DES encryption context */ - param = PK11_ParamFromIV(mech, /* no IV in ECB mode */ NULL); - if(!param) - goto fail; - ctx = PK11_CreateContextBySymKey(mech, CKA_ENCRYPT, symkey, param); - if(!ctx) - goto fail; - - /* Perform the encryption */ - if(SECSuccess == PK11_CipherOp(ctx, out, &out_len, /* outbuflen */ 8, - (unsigned char *)in, /* inbuflen */ 8) - && SECSuccess == PK11_Finalize(ctx)) - rv = /* all OK */ TRUE; - -fail: - /* cleanup */ - if(ctx) - PK11_DestroyContext(ctx, PR_TRUE); - if(symkey) - PK11_FreeSymKey(symkey); - if(param) - SECITEM_FreeItem(param, PR_TRUE); - PK11_FreeSlot(slot); - return rv; -} - #elif defined(USE_MBEDTLS) static bool encrypt_des(const unsigned char *in, unsigned char *out, @@ -260,7 +197,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, mbedtls_des_context ctx; char key[8]; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -281,7 +218,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, size_t out_len; CCCryptorStatus err; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -307,7 +244,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, ctl.Func_ID = ENCRYPT_ONLY; ctl.Data_Len = sizeof(key); - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, ctl.Crypto_Key); /* Set the key parity to odd */ @@ -345,7 +282,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, blob.hdr.aiKeyAlg = CALG_DES; blob.len = sizeof(blob.key); - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, blob.key); /* Set the key parity to odd */ @@ -380,20 +317,20 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, const unsigned char *plaintext, unsigned char *results) { -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) DES_key_schedule ks; setup_des_key(keys, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) results, - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock*)CURL_UNCONST(plaintext), + (DES_cblock*)results, DESKEY(ks), DES_ENCRYPT); setup_des_key(keys + 7, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results + 8), - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock*)CURL_UNCONST(plaintext), + (DES_cblock*)(results + 8), DESKEY(ks), DES_ENCRYPT); setup_des_key(keys + 14, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results + 16), - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock*)CURL_UNCONST(plaintext), + (DES_cblock*)(results + 16), DESKEY(ks), DES_ENCRYPT); #elif defined(USE_GNUTLS) struct des_ctx des; setup_des_key(keys, &des); @@ -402,11 +339,15 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, des_encrypt(&des, 8, results + 8, plaintext); setup_des_key(keys + 14, &des); des_encrypt(&des, 8, results + 16, plaintext); -#elif defined(USE_NSS) || defined(USE_MBEDTLS) || defined(USE_SECTRANSP) \ +#elif defined(USE_MBEDTLS) || defined(USE_SECTRANSP) \ || defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) encrypt_des(plaintext, results, keys); encrypt_des(plaintext, results + 8, keys + 7); encrypt_des(plaintext, results + 16, keys + 14); +#else + (void)keys; + (void)plaintext; + (void)results; #endif } @@ -417,9 +358,11 @@ CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, unsigned char *lmbuffer /* 21 bytes */) { unsigned char pw[14]; +#if !defined(CURL_NTLM_NOT_SUPPORTED) static const unsigned char magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 /* i.e. KGS!@#$% */ }; +#endif size_t len = CURLMIN(strlen(password), 14); Curl_strntoupper((char *)pw, password, len); @@ -428,23 +371,23 @@ CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, { /* Create LanManager hashed password. */ -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) DES_key_schedule ks; setup_des_key(pw, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)lmbuffer, - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(magic), + (DES_cblock *)lmbuffer, DESKEY(ks), DES_ENCRYPT); setup_des_key(pw + 7, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)(lmbuffer + 8), - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(magic), + (DES_cblock *)(lmbuffer + 8), DESKEY(ks), DES_ENCRYPT); #elif defined(USE_GNUTLS) struct des_ctx des; setup_des_key(pw, &des); des_encrypt(&des, 8, lmbuffer, magic); setup_des_key(pw + 7, &des); des_encrypt(&des, 8, lmbuffer + 8, magic); -#elif defined(USE_NSS) || defined(USE_MBEDTLS) || defined(USE_SECTRANSP) \ +#elif defined(USE_MBEDTLS) || defined(USE_SECTRANSP) \ || defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) encrypt_des(magic, lmbuffer, pw); encrypt_des(magic, lmbuffer + 8, pw + 7); @@ -489,6 +432,7 @@ CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, { size_t len = strlen(password); unsigned char *pw; + CURLcode result; if(len > SIZE_T_MAX/2) /* avoid integer overflow */ return CURLE_OUT_OF_MEMORY; pw = len ? malloc(len * 2) : (unsigned char *)strdup(""); @@ -498,16 +442,20 @@ CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, ascii_to_unicode_le(pw, password, len); /* Create NT hashed password. */ - Curl_md4it(ntbuffer, pw, 2 * len); - memset(ntbuffer + 16, 0, 21 - 16); + result = Curl_md4it(ntbuffer, pw, 2 * len); + if(!result) + memset(ntbuffer + 16, 0, 21 - 16); free(pw); - return CURLE_OK; + return result; } #if !defined(USE_WINDOWS_SSPI) +#define NTLMv2_BLOB_SIGNATURE "\x01\x01\x00\x00" +#define NTLMv2_BLOB_LEN (44 -16 + ntlm->target_info_len + 4) + /* Timestamp in tenths of a microsecond since January 1, 1601 00:00:00 UTC. */ struct ms_filetime { unsigned int dwLowDateTime; @@ -525,20 +473,20 @@ static void time2filetime(struct ms_filetime *ft, time_t t) unsigned int r, s; unsigned int i; - ft->dwLowDateTime = t & 0xFFFFFFFF; + ft->dwLowDateTime = (unsigned int)t & 0xFFFFFFFF; ft->dwHighDateTime = 0; # ifndef HAVE_TIME_T_UNSIGNED /* Extend sign if needed. */ if(ft->dwLowDateTime & 0x80000000) - ft->dwHighDateTime = ~0; + ft->dwHighDateTime = ~(unsigned int)0; # endif /* Bias seconds to Jan 1, 1601. 134774 days = 11644473600 seconds = 0x2B6109100 */ r = ft->dwLowDateTime; ft->dwLowDateTime = (ft->dwLowDateTime + 0xB6109100U) & 0xFFFFFFFF; - ft->dwHighDateTime += ft->dwLowDateTime < r? 0x03: 0x02; + ft->dwHighDateTime += ft->dwLowDateTime < r ? 0x03 : 0x02; /* Convert to tenths of microseconds. */ ft->dwHighDateTime *= 10000000; @@ -583,7 +531,7 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, ascii_uppercase_to_unicode_le(identity, user, userlen); ascii_to_unicode_le(identity + (userlen << 1), domain, domlen); - result = Curl_hmacit(Curl_HMAC_MD5, ntlmhash, 16, identity, identity_len, + result = Curl_hmacit(&Curl_HMAC_MD5, ntlmhash, 16, identity, identity_len, ntlmv2hash); free(identity); @@ -593,13 +541,13 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, /* * Curl_ntlm_core_mk_ntlmv2_resp() * - * This creates the NTLMv2 response as set in the ntlm type-3 message. + * This creates the NTLMv2 response as set in the NTLM type-3 message. * * Parameters: * - * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * ntlmv2hash [in] - The NTLMv2 hash (16 bytes) * challenge_client [in] - The client nonce (8 bytes) - * ntlm [in] - The ntlm data struct being used to read TargetInfo + * ntlm [in] - The NTLM data struct being used to read TargetInfo and Server challenge received in the type-2 message * ntresp [out] - The address where a pointer to newly allocated * memory holding the NTLMv2 response. @@ -668,8 +616,8 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, /* Concatenate the Type 2 challenge with the BLOB and do HMAC MD5 */ memcpy(ptr + 8, &ntlm->nonce[0], 8); - result = Curl_hmacit(Curl_HMAC_MD5, ntlmv2hash, HMAC_MD5_LENGTH, ptr + 8, - NTLMv2_BLOB_LEN + 8, hmac_output); + result = Curl_hmacit(&Curl_HMAC_MD5, ntlmv2hash, HMAC_MD5_LENGTH, ptr + 8, + NTLMv2_BLOB_LEN + 8, hmac_output); if(result) { free(ptr); return result; @@ -688,11 +636,11 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, /* * Curl_ntlm_core_mk_lmv2_resp() * - * This creates the LMv2 response as used in the ntlm type-3 message. + * This creates the LMv2 response as used in the NTLM type-3 message. * * Parameters: * - * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * ntlmv2hash [in] - The NTLMv2 hash (16 bytes) * challenge_client [in] - The client nonce (8 bytes) * challenge_client [in] - The server challenge (8 bytes) * lmresp [out] - The LMv2 response (24 bytes) @@ -711,12 +659,12 @@ CURLcode Curl_ntlm_core_mk_lmv2_resp(unsigned char *ntlmv2hash, memcpy(&data[0], challenge_server, 8); memcpy(&data[8], challenge_client, 8); - result = Curl_hmacit(Curl_HMAC_MD5, ntlmv2hash, 16, &data[0], 16, + result = Curl_hmacit(&Curl_HMAC_MD5, ntlmv2hash, 16, &data[0], 16, hmac_output); if(result) return result; - /* Concatenate the HMAC MD5 output with the client nonce */ + /* Concatenate the HMAC MD5 output with the client nonce */ memcpy(lmresp, hmac_output, 16); memcpy(lmresp + 16, challenge_client, 8); diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.h b/Utilities/cmcurl/lib/curl_ntlm_core.h index 33b651f5ea5..e2e4b1bd43a 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_core.h +++ b/Utilities/cmcurl/lib/curl_ntlm_core.h @@ -28,22 +28,6 @@ #if defined(USE_CURL_NTLM_CORE) -/* If NSS is the first available SSL backend (see order in curl_ntlm_core.c) - then it must be initialized to be used by NTLM. */ -#if !defined(USE_OPENSSL) && \ - !defined(USE_WOLFSSL) && \ - !defined(USE_GNUTLS) && \ - defined(USE_NSS) -#define NTLM_NEEDS_NSS_INIT -#endif - -#if defined(USE_OPENSSL) -# include -#elif defined(USE_WOLFSSL) -# include -# include -#endif - /* Helpers to generate function byte arguments in little endian order */ #define SHORTPAIR(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)) #define LONGQUARTET(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)), \ diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.c b/Utilities/cmcurl/lib/curl_ntlm_wb.c deleted file mode 100644 index a10e2a1b090..00000000000 --- a/Utilities/cmcurl/lib/curl_ntlm_wb.c +++ /dev/null @@ -1,500 +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" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - -/* - * NTLM details: - * - * https://davenport.sourceforge.net/ntlm.html - * https://www.innovation.ch/java/ntlm.html - */ - -#define DEBUG_ME 0 - -#ifdef HAVE_SYS_WAIT_H -#include -#endif -#ifdef HAVE_SIGNAL_H -#include -#endif -#ifdef HAVE_PWD_H -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "select.h" -#include "vauth/ntlm.h" -#include "curl_ntlm_core.h" -#include "curl_ntlm_wb.h" -#include "url.h" -#include "strerror.h" -#include "strdup.h" -#include "strcase.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -#if DEBUG_ME -# define DEBUG_OUT(x) x -#else -# define DEBUG_OUT(x) Curl_nop_stmt -#endif - -/* Portable 'sclose_nolog' used only in child process instead of 'sclose' - to avoid fooling the socket leak detector */ -#if defined(HAVE_CLOSESOCKET) -# define sclose_nolog(x) closesocket((x)) -#elif defined(HAVE_CLOSESOCKET_CAMEL) -# define sclose_nolog(x) CloseSocket((x)) -#else -# define sclose_nolog(x) close((x)) -#endif - -static void ntlm_wb_cleanup(struct ntlmdata *ntlm) -{ - if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) { - sclose(ntlm->ntlm_auth_hlpr_socket); - ntlm->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - } - - if(ntlm->ntlm_auth_hlpr_pid) { - int i; - for(i = 0; i < 4; i++) { - pid_t ret = waitpid(ntlm->ntlm_auth_hlpr_pid, NULL, WNOHANG); - if(ret == ntlm->ntlm_auth_hlpr_pid || errno == ECHILD) - break; - switch(i) { - case 0: - kill(ntlm->ntlm_auth_hlpr_pid, SIGTERM); - break; - case 1: - /* Give the process another moment to shut down cleanly before - bringing down the axe */ - Curl_wait_ms(1); - break; - case 2: - kill(ntlm->ntlm_auth_hlpr_pid, SIGKILL); - break; - case 3: - break; - } - } - ntlm->ntlm_auth_hlpr_pid = 0; - } - - Curl_safefree(ntlm->challenge); - Curl_safefree(ntlm->response); -} - -static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm, - const char *userp) -{ - curl_socket_t sockfds[2]; - pid_t child_pid; - const char *username; - char *slash, *domain = NULL; - const char *ntlm_auth = NULL; - char *ntlm_auth_alloc = NULL; -#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) - struct passwd pw, *pw_res; - char pwbuf[1024]; -#endif - char buffer[STRERROR_LEN]; - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) data; -#endif - - /* Return if communication with ntlm_auth already set up */ - if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD || - ntlm->ntlm_auth_hlpr_pid) - return CURLE_OK; - - username = userp; - /* The real ntlm_auth really doesn't like being invoked with an - empty username. It won't make inferences for itself, and expects - the client to do so (mostly because it's really designed for - servers like squid to use for auth, and client support is an - afterthought for it). So try hard to provide a suitable username - if we don't already have one. But if we can't, provide the - empty one anyway. Perhaps they have an implementation of the - ntlm_auth helper which *doesn't* need it so we might as well try */ - if(!username || !username[0]) { - username = getenv("NTLMUSER"); - if(!username || !username[0]) - username = getenv("LOGNAME"); - if(!username || !username[0]) - username = getenv("USER"); -#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) - if((!username || !username[0]) && - !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) && - pw_res) { - username = pw.pw_name; - } -#endif - if(!username || !username[0]) - username = userp; - } - slash = strpbrk(username, "\\/"); - if(slash) { - domain = strdup(username); - if(!domain) - return CURLE_OUT_OF_MEMORY; - slash = domain + (slash - username); - *slash = '\0'; - username = username + (slash - domain) + 1; - } - - /* For testing purposes, when DEBUGBUILD is defined and environment - variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform - NTLM challenge/response which only accepts commands and output - strings pre-written in test case definitions */ -#ifdef DEBUGBUILD - ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE"); - if(ntlm_auth_alloc) - ntlm_auth = ntlm_auth_alloc; - else -#endif - ntlm_auth = NTLM_WB_FILE; - - if(access(ntlm_auth, X_OK) != 0) { - failf(data, "Could not access ntlm_auth: %s errno %d: %s", - ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - - if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) { - failf(data, "Could not open socket pair. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - - child_pid = fork(); - if(child_pid == -1) { - sclose(sockfds[0]); - sclose(sockfds[1]); - failf(data, "Could not fork. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - else if(!child_pid) { - /* - * child process - */ - - /* Don't use sclose in the child since it fools the socket leak detector */ - sclose_nolog(sockfds[0]); - if(dup2(sockfds[1], STDIN_FILENO) == -1) { - failf(data, "Could not redirect child stdin. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - if(dup2(sockfds[1], STDOUT_FILENO) == -1) { - failf(data, "Could not redirect child stdout. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - if(domain) - execl(ntlm_auth, ntlm_auth, - "--helper-protocol", "ntlmssp-client-1", - "--use-cached-creds", - "--username", username, - "--domain", domain, - NULL); - else - execl(ntlm_auth, ntlm_auth, - "--helper-protocol", "ntlmssp-client-1", - "--use-cached-creds", - "--username", username, - NULL); - - sclose_nolog(sockfds[1]); - failf(data, "Could not execl(). errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - sclose(sockfds[1]); - ntlm->ntlm_auth_hlpr_socket = sockfds[0]; - ntlm->ntlm_auth_hlpr_pid = child_pid; - free(domain); - free(ntlm_auth_alloc); - return CURLE_OK; - -done: - free(domain); - free(ntlm_auth_alloc); - return CURLE_REMOTE_ACCESS_DENIED; -} - -/* if larger than this, something is seriously wrong */ -#define MAX_NTLM_WB_RESPONSE 100000 - -static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm, - const char *input, curlntlm state) -{ - size_t len_in = strlen(input), len_out = 0; - struct dynbuf b; - char *ptr = NULL; - unsigned char *buf = (unsigned char *)data->state.buffer; - Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE); - - while(len_in > 0) { - ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in); - if(written == -1) { - /* Interrupted by a signal, retry it */ - if(errno == EINTR) - continue; - /* write failed if other errors happen */ - goto done; - } - input += written; - len_in -= written; - } - /* Read one line */ - while(1) { - ssize_t size = - sread(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size); - if(size == -1) { - if(errno == EINTR) - continue; - goto done; - } - else if(size == 0) - goto done; - - if(Curl_dyn_addn(&b, buf, size)) - goto done; - - len_out = Curl_dyn_len(&b); - ptr = Curl_dyn_ptr(&b); - if(len_out && ptr[len_out - 1] == '\n') { - ptr[len_out - 1] = '\0'; - break; /* done! */ - } - /* loop */ - } - - /* Samba/winbind installed but not configured */ - if(state == NTLMSTATE_TYPE1 && - len_out == 3 && - ptr[0] == 'P' && ptr[1] == 'W') - goto done; - /* invalid response */ - if(len_out < 4) - goto done; - if(state == NTLMSTATE_TYPE1 && - (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' ')) - goto done; - if(state == NTLMSTATE_TYPE2 && - (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') && - (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' ')) - goto done; - - ntlm->response = strdup(ptr + 3); - Curl_dyn_free(&b); - if(!ntlm->response) - return CURLE_OUT_OF_MEMORY; - return CURLE_OK; -done: - Curl_dyn_free(&b); - return CURLE_REMOTE_ACCESS_DENIED; -} - -CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, - struct connectdata *conn, - bool proxy, - const char *header) -{ - struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm; - curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state; - - (void) data; /* In case it gets unused by nop log macros. */ - - if(!checkprefix("NTLM", header)) - return CURLE_BAD_CONTENT_ENCODING; - - header += strlen("NTLM"); - while(*header && ISSPACE(*header)) - header++; - - if(*header) { - ntlm->challenge = strdup(header); - if(!ntlm->challenge) - return CURLE_OUT_OF_MEMORY; - - *state = NTLMSTATE_TYPE2; /* We got a type-2 message */ - } - else { - if(*state == NTLMSTATE_LAST) { - infof(data, "NTLM auth restarted"); - Curl_http_auth_cleanup_ntlm_wb(conn); - } - else if(*state == NTLMSTATE_TYPE3) { - infof(data, "NTLM handshake rejected"); - Curl_http_auth_cleanup_ntlm_wb(conn); - *state = NTLMSTATE_NONE; - return CURLE_REMOTE_ACCESS_DENIED; - } - else if(*state >= NTLMSTATE_TYPE1) { - infof(data, "NTLM handshake failure (internal error)"); - return CURLE_REMOTE_ACCESS_DENIED; - } - - *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */ - } - - return CURLE_OK; -} - -/* - * This is for creating ntlm header output by delegating challenge/response - * to Samba's winbind daemon helper ntlm_auth. - */ -CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, - bool proxy) -{ - /* point to the address of the pointer that holds the string to send to the - server, which is for a plain host or for an HTTP proxy */ - char **allocuserpwd; - /* point to the name and password for this */ - const char *userp; - struct ntlmdata *ntlm; - curlntlm *state; - struct auth *authp; - - CURLcode res = CURLE_OK; - - DEBUGASSERT(conn); - DEBUGASSERT(data); - - if(proxy) { -#ifndef CURL_DISABLE_PROXY - allocuserpwd = &data->state.aptr.proxyuserpwd; - userp = conn->http_proxy.user; - ntlm = &conn->proxyntlm; - state = &conn->proxy_ntlm_state; - authp = &data->state.authproxy; -#else - return CURLE_NOT_BUILT_IN; -#endif - } - else { - allocuserpwd = &data->state.aptr.userpwd; - userp = conn->user; - ntlm = &conn->ntlm; - state = &conn->http_ntlm_state; - authp = &data->state.authhost; - } - authp->done = FALSE; - - /* not set means empty */ - if(!userp) - userp = ""; - - switch(*state) { - case NTLMSTATE_TYPE1: - default: - /* Use Samba's 'winbind' daemon to support NTLM authentication, - * by delegating the NTLM challenge/response protocol to a helper - * in ntlm_auth. - * https://web.archive.org/web/20190925164737 - * /devel.squid-cache.org/ntlm/squid_helper_protocol.html - * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html - * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html - * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this - * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute - * filename of ntlm_auth helper. - * If NTLM authentication using winbind fails, go back to original - * request handling process. - */ - /* Create communication with ntlm_auth */ - res = ntlm_wb_init(data, ntlm, userp); - if(res) - return res; - res = ntlm_wb_response(data, ntlm, "YR\n", *state); - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - ntlm->response); - DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); - Curl_safefree(ntlm->response); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - break; - - case NTLMSTATE_TYPE2: { - char *input = aprintf("TT %s\n", ntlm->challenge); - if(!input) - return CURLE_OUT_OF_MEMORY; - res = ntlm_wb_response(data, ntlm, input, *state); - free(input); - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - ntlm->response); - DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); - *state = NTLMSTATE_TYPE3; /* we sent a type-3 */ - authp->done = TRUE; - Curl_http_auth_cleanup_ntlm_wb(conn); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - break; - } - case NTLMSTATE_TYPE3: - /* connection is already authenticated, - * don't send a header in future requests */ - *state = NTLMSTATE_LAST; - /* FALLTHROUGH */ - case NTLMSTATE_LAST: - Curl_safefree(*allocuserpwd); - authp->done = TRUE; - break; - } - - return CURLE_OK; -} - -void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn) -{ - ntlm_wb_cleanup(&conn->ntlm); - ntlm_wb_cleanup(&conn->proxyntlm); -} - -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.h b/Utilities/cmcurl/lib/curl_ntlm_wb.h deleted file mode 100644 index 37704c0fe0b..00000000000 --- a/Utilities/cmcurl/lib/curl_ntlm_wb.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef HEADER_CURL_NTLM_WB_H -#define HEADER_CURL_NTLM_WB_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" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - -/* this is for ntlm header input */ -CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, - struct connectdata *conn, bool proxy, - const char *header); - -/* this is for creating ntlm header output */ -CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, - bool proxy); - -void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn); - -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ - -#endif /* HEADER_CURL_NTLM_WB_H */ diff --git a/Utilities/cmcurl/lib/curl_printf.h b/Utilities/cmcurl/lib/curl_printf.h index 6d3d492cc46..6e0fa1fa8d8 100644 --- a/Utilities/cmcurl/lib/curl_printf.h +++ b/Utilities/cmcurl/lib/curl_printf.h @@ -24,19 +24,31 @@ * ***************************************************************************/ +#include + +#define MERR_OK 0 +#define MERR_MEM 1 +#define MERR_TOO_LARGE 2 + +/* Lower-case digits. */ +extern const unsigned char Curl_ldigits[]; + +/* Upper-case digits. */ +extern const unsigned char Curl_udigits[]; + +#ifdef BUILDING_LIBCURL + /* * This header should be included by ALL code in libcurl that uses any * *rintf() functions. */ -#include - # undef printf # undef fprintf # undef msnprintf # undef vprintf # undef vfprintf -# undef vsnprintf +# undef mvsnprintf # undef aprintf # undef vaprintf # define printf curl_mprintf @@ -47,4 +59,6 @@ # define mvsnprintf curl_mvsnprintf # define aprintf curl_maprintf # define vaprintf curl_mvaprintf + +#endif /* BUILDING_LIBCURL */ #endif /* HEADER_CURL_PRINTF_H */ diff --git a/Utilities/cmcurl/lib/curl_range.c b/Utilities/cmcurl/lib/curl_range.c index d499953c9ed..e9620a29b53 100644 --- a/Utilities/cmcurl/lib/curl_range.c +++ b/Utilities/cmcurl/lib/curl_range.c @@ -26,7 +26,7 @@ #include #include "curl_range.h" #include "sendf.h" -#include "strtoofft.h" +#include "curlx/strparse.h" /* Only include this function if one or more of FTP, FILE are enabled. */ #if !defined(CURL_DISABLE_FTP) || !defined(CURL_DISABLE_FILE) @@ -37,33 +37,32 @@ */ CURLcode Curl_range(struct Curl_easy *data) { - curl_off_t from, to; - char *ptr; - char *ptr2; - if(data->state.use_range && data->state.range) { - CURLofft from_t; - CURLofft to_t; - from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); - if(from_t == CURL_OFFT_FLOW) - return CURLE_RANGE_ERROR; - while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) - ptr++; - to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); - if(to_t == CURL_OFFT_FLOW) + curl_off_t from, to; + bool first_num = TRUE; + const char *p = data->state.range; + if(curlx_str_number(&p, &from, CURL_OFF_T_MAX)) + first_num = FALSE; + + if(curlx_str_single(&p, '-')) + /* no leading dash or after the first number is an error */ return CURLE_RANGE_ERROR; - if((to_t == CURL_OFFT_INVAL) && !from_t) { + + if(curlx_str_number(&p, &to, CURL_OFF_T_MAX)) { + /* no second number */ /* X - */ data->state.resume_from = from; - DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file", - from)); + DEBUGF(infof(data, "RANGE %" FMT_OFF_T " to end of file", from)); } - else if((from_t == CURL_OFFT_INVAL) && !to_t) { + else if(!first_num) { /* -Y */ + if(!to) + /* "-0" is just wrong */ + return CURLE_RANGE_ERROR; + data->req.maxdownload = to; data->state.resume_from = -to; - DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes", - to)); + DEBUGF(infof(data, "RANGE the last %" FMT_OFF_T " bytes", to)); } else { /* X-Y */ @@ -79,13 +78,12 @@ CURLcode Curl_range(struct Curl_easy *data) data->req.maxdownload = totalsize + 1; /* include last byte */ data->state.resume_from = from; - DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T - " getting %" CURL_FORMAT_CURL_OFF_T " bytes", + DEBUGF(infof(data, "RANGE from %" FMT_OFF_T + " getting %" FMT_OFF_T " bytes", from, data->req.maxdownload)); } - DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T - " to %" CURL_FORMAT_CURL_OFF_T ", totally %" - CURL_FORMAT_CURL_OFF_T " bytes", + DEBUGF(infof(data, "range-download from %" FMT_OFF_T + " to %" FMT_OFF_T ", totally %" FMT_OFF_T " bytes", from, to, data->req.maxdownload)); } else diff --git a/Utilities/cmcurl/lib/curl_rtmp.c b/Utilities/cmcurl/lib/curl_rtmp.c index 406fb42ac0f..62632c1e9cb 100644 --- a/Utilities/cmcurl/lib/curl_rtmp.c +++ b/Utilities/cmcurl/lib/curl_rtmp.c @@ -29,17 +29,20 @@ #include "curl_rtmp.h" #include "urldata.h" -#include "nonblock.h" /* for curlx_nonblock */ +#include "url.h" +#include "curlx/nonblock.h" /* for curlx_nonblock */ #include "progress.h" /* for Curl_pgrsSetUploadSize */ #include "transfer.h" -#include "warnless.h" +#include "curlx/warnless.h" #include #include + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" -/* The last #include file should be: */ #include "memdebug.h" -#if defined(WIN32) && !defined(USE_LWIPSOCK) +#if defined(_WIN32) && !defined(USE_LWIPSOCK) #define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e) #define SET_RCVTIMEO(tv,s) int tv = s*1000 #elif defined(LWIP_SO_SNDRCVTIMEO_NONSTANDARD) @@ -50,6 +53,10 @@ #define DEF_BUFTIME (2*60*60*1000) /* 2 hours */ +/* meta key for storing RTMP* at connection */ +#define CURL_META_RTMP_CONN "meta:proto:rtmp:conn" + + static CURLcode rtmp_setup_connection(struct Curl_easy *data, struct connectdata *conn); static CURLcode rtmp_do(struct Curl_easy *data, bool *done); @@ -66,7 +73,7 @@ static Curl_send rtmp_send; */ const struct Curl_handler Curl_handler_rtmp = { - "RTMP", /* scheme */ + "rtmp", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -79,9 +86,11 @@ const struct Curl_handler Curl_handler_rtmp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMP, /* defport */ CURLPROTO_RTMP, /* protocol */ CURLPROTO_RTMP, /* family */ @@ -89,7 +98,7 @@ const struct Curl_handler Curl_handler_rtmp = { }; const struct Curl_handler Curl_handler_rtmpt = { - "RTMPT", /* scheme */ + "rtmpt", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -102,9 +111,11 @@ const struct Curl_handler Curl_handler_rtmpt = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPT, /* defport */ CURLPROTO_RTMPT, /* protocol */ CURLPROTO_RTMPT, /* family */ @@ -112,7 +123,7 @@ const struct Curl_handler Curl_handler_rtmpt = { }; const struct Curl_handler Curl_handler_rtmpe = { - "RTMPE", /* scheme */ + "rtmpe", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -125,9 +136,11 @@ const struct Curl_handler Curl_handler_rtmpe = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMP, /* defport */ CURLPROTO_RTMPE, /* protocol */ CURLPROTO_RTMPE, /* family */ @@ -135,7 +148,7 @@ const struct Curl_handler Curl_handler_rtmpe = { }; const struct Curl_handler Curl_handler_rtmpte = { - "RTMPTE", /* scheme */ + "rtmpte", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -148,9 +161,11 @@ const struct Curl_handler Curl_handler_rtmpte = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPT, /* defport */ CURLPROTO_RTMPTE, /* protocol */ CURLPROTO_RTMPTE, /* family */ @@ -158,7 +173,7 @@ const struct Curl_handler Curl_handler_rtmpte = { }; const struct Curl_handler Curl_handler_rtmps = { - "RTMPS", /* scheme */ + "rtmps", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -171,9 +186,11 @@ const struct Curl_handler Curl_handler_rtmps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPS, /* defport */ CURLPROTO_RTMPS, /* protocol */ CURLPROTO_RTMP, /* family */ @@ -181,7 +198,7 @@ const struct Curl_handler Curl_handler_rtmps = { }; const struct Curl_handler Curl_handler_rtmpts = { - "RTMPTS", /* scheme */ + "rtmpts", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -194,20 +211,32 @@ const struct Curl_handler Curl_handler_rtmpts = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPS, /* defport */ CURLPROTO_RTMPTS, /* protocol */ CURLPROTO_RTMPT, /* family */ PROTOPT_NONE /* flags */ }; +static void rtmp_conn_dtor(void *key, size_t klen, void *entry) +{ + RTMP *r = entry; + (void)key; + (void)klen; + RTMP_Close(r); + RTMP_Free(r); +} + static CURLcode rtmp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { RTMP *r = RTMP_Alloc(); - if(!r) + if(!r || + Curl_conn_meta_set(conn, CURL_META_RTMP_CONN, r, rtmp_conn_dtor)) return CURLE_OUT_OF_MEMORY; RTMP_Init(r); @@ -216,19 +245,21 @@ static CURLcode rtmp_setup_connection(struct Curl_easy *data, RTMP_Free(r); return CURLE_URL_MALFORMAT; } - conn->proto.rtmp = r; return CURLE_OK; } static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); SET_RCVTIMEO(tv, 10); + if(!r) + return CURLE_FAILED_INIT; + r->m_sb.sb_socket = (int)conn->sock[FIRSTSOCKET]; - /* We have to know if it's a write before we send the + /* We have to know if it is a write before we send the * connect request packet */ if(data->state.upload) @@ -247,7 +278,7 @@ static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; /* Clients must send a periodic BytesReceived report to the server */ - r->m_bSendCounter = true; + r->m_bSendCounter = TRUE; *done = TRUE; conn->recv[FIRSTSOCKET] = rtmp_recv; @@ -258,17 +289,17 @@ static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) static CURLcode rtmp_do(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); - if(!RTMP_ConnectStream(r, 0)) + if(!r || !RTMP_ConnectStream(r, 0)) return CURLE_FAILED_INIT; if(data->state.upload) { Curl_pgrsSetUploadSize(data, data->state.infilesize); - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); } else - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); *done = TRUE; return CURLE_OK; } @@ -287,14 +318,11 @@ static CURLcode rtmp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); (void)data; (void)dead_connection; - if(r) { - conn->proto.rtmp = NULL; - RTMP_Close(r); - RTMP_Free(r); - } + if(r) + Curl_conn_meta_remove(conn, CURL_META_RTMP_CONN); return CURLE_OK; } @@ -302,10 +330,14 @@ static ssize_t rtmp_recv(struct Curl_easy *data, int sockindex, char *buf, size_t len, CURLcode *err) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); ssize_t nread; (void)sockindex; /* unused */ + if(!r) { + *err = CURLE_FAILED_INIT; + return -1; + } nread = RTMP_Read(r, buf, curlx_uztosi(len)); if(nread < 0) { @@ -321,18 +353,39 @@ static ssize_t rtmp_recv(struct Curl_easy *data, int sockindex, char *buf, } static ssize_t rtmp_send(struct Curl_easy *data, int sockindex, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, CURLcode *err) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); ssize_t num; (void)sockindex; /* unused */ + (void)eos; /* unused */ + if(!r) { + *err = CURLE_FAILED_INIT; + return -1; + } - num = RTMP_Write(r, (char *)buf, curlx_uztosi(len)); + num = RTMP_Write(r, (const char *)buf, curlx_uztosi(len)); if(num < 0) *err = CURLE_SEND_ERROR; return num; } + +void Curl_rtmp_version(char *version, size_t len) +{ + char suff[2]; + if(RTMP_LIB_VERSION & 0xff) { + suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; + suff[1] = '\0'; + } + else + suff[0] = '\0'; + + msnprintf(version, len, "librtmp/%d.%d%s", + RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, + suff); +} + #endif /* USE_LIBRTMP */ diff --git a/Utilities/cmcurl/lib/curl_rtmp.h b/Utilities/cmcurl/lib/curl_rtmp.h index 9b93ee060b7..339d3a4384d 100644 --- a/Utilities/cmcurl/lib/curl_rtmp.h +++ b/Utilities/cmcurl/lib/curl_rtmp.h @@ -30,6 +30,8 @@ extern const struct Curl_handler Curl_handler_rtmpe; extern const struct Curl_handler Curl_handler_rtmpte; extern const struct Curl_handler Curl_handler_rtmps; extern const struct Curl_handler Curl_handler_rtmpts; + +void Curl_rtmp_version(char *version, size_t len); #endif #endif /* HEADER_CURL_RTMP_H */ diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c index 119fb9b25a2..4fcbaac263b 100644 --- a/Utilities/cmcurl/lib/curl_sasl.c +++ b/Utilities/cmcurl/lib/curl_sasl.c @@ -42,15 +42,14 @@ #include #include "urldata.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "curl_md5.h" #include "vauth/vauth.h" #include "cfilters.h" #include "vtls/vtls.h" #include "curl_hmac.h" #include "curl_sasl.h" -#include "warnless.h" -#include "strtok.h" +#include "curlx/warnless.h" #include "sendf.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -205,28 +204,33 @@ void Curl_sasl_init(struct SASL *sasl, struct Curl_easy *data, sasl->force_ir = FALSE; /* Respect external option */ if(auth != CURLAUTH_BASIC) { - sasl->resetprefs = FALSE; - sasl->prefmech = SASL_AUTH_NONE; + unsigned short mechs = SASL_AUTH_NONE; + + /* If some usable http authentication options have been set, determine + new defaults from them. */ if(auth & CURLAUTH_BASIC) - sasl->prefmech |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; + mechs |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; if(auth & CURLAUTH_DIGEST) - sasl->prefmech |= SASL_MECH_DIGEST_MD5; + mechs |= SASL_MECH_DIGEST_MD5; if(auth & CURLAUTH_NTLM) - sasl->prefmech |= SASL_MECH_NTLM; + mechs |= SASL_MECH_NTLM; if(auth & CURLAUTH_BEARER) - sasl->prefmech |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; + mechs |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; if(auth & CURLAUTH_GSSAPI) - sasl->prefmech |= SASL_MECH_GSSAPI; + mechs |= SASL_MECH_GSSAPI; + + if(mechs != SASL_AUTH_NONE) + sasl->prefmech = mechs; } } /* - * state() + * sasl_state() * * This is the ONLY way to change SASL state! */ -static void state(struct SASL *sasl, struct Curl_easy *data, - saslstate newstate) +static void sasl_state(struct SASL *sasl, struct Curl_easy *data, + saslstate newstate) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ @@ -262,6 +266,8 @@ static void state(struct SASL *sasl, struct Curl_easy *data, sasl->state = newstate; } +#if defined(USE_NTLM) || defined(USE_GSASL) || defined(USE_KERBEROS5) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) /* Get the SASL server message and convert it to binary. */ static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, struct bufref *out) @@ -277,13 +283,14 @@ static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, if(!*serverdata || *serverdata == '=') Curl_bufref_set(out, NULL, 0, NULL); else { - result = Curl_base64_decode(serverdata, &msg, &msglen); + result = curlx_base64_decode(serverdata, &msg, &msglen); if(!result) Curl_bufref_set(out, msg, msglen, curl_free); } } return result; } +#endif /* Encode the outgoing SASL message. */ static CURLcode build_message(struct SASL *sasl, struct bufref *msg) @@ -299,8 +306,8 @@ static CURLcode build_message(struct SASL *sasl, struct bufref *msg) char *base64; size_t base64len; - result = Curl_base64_encode((const char *) Curl_bufref_ptr(msg), - Curl_bufref_len(msg), &base64, &base64len); + result = curlx_base64_encode((const char *) Curl_bufref_ptr(msg), + Curl_bufref_len(msg), &base64, &base64len); if(!result) Curl_bufref_set(msg, base64, base64len, curl_free); } @@ -320,7 +327,7 @@ bool Curl_sasl_can_authenticate(struct SASL *sasl, struct Curl_easy *data) if(data->state.aptr.user) return TRUE; - /* EXTERNAL can authenticate without a user name and/or password */ + /* EXTERNAL can authenticate without a username and/or password */ if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL) return TRUE; @@ -368,7 +375,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, sasl->authused = SASL_MECH_EXTERNAL; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_external_message(conn->user, &resp); + Curl_auth_create_external_message(conn->user, &resp); } else if(data->state.aptr.user) { #if defined(USE_KERBEROS5) @@ -420,7 +427,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, } else #endif -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH if((enabledmechs & SASL_MECH_DIGEST_MD5) && Curl_auth_is_digest_supported()) { mech = SASL_MECH_STRING_DIGEST_MD5; @@ -490,7 +497,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, sasl->authused = SASL_MECH_LOGIN; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_login_message(conn->user, &resp); + Curl_auth_create_login_message(conn->user, &resp); } } @@ -508,7 +515,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, if(!result) { *progress = SASL_INPROGRESS; - state(sasl, data, Curl_bufref_ptr(&resp) ? state2 : state1); + sasl_state(sasl, data, Curl_bufref_ptr(&resp) ? state2 : state1); } } @@ -530,8 +537,8 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, struct bufref resp; const char *hostname, *disp_hostname; int port; -#if !defined(CURL_DISABLE_CRYPTO_AUTH) || defined(USE_KERBEROS5) || \ - defined(USE_NTLM) +#if defined(USE_KERBEROS5) || defined(USE_NTLM) \ + || !defined(CURL_DISABLE_DIGEST_AUTH) const char *service = data->set.str[STRING_SERVICE_NAME] ? data->set.str[STRING_SERVICE_NAME] : sasl->params->service; @@ -548,14 +555,14 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, if(code != sasl->params->finalcode) result = CURLE_LOGIN_DENIED; *progress = SASL_DONE; - state(sasl, data, SASL_STOP); + sasl_state(sasl, data, SASL_STOP); return result; } if(sasl->state != SASL_CANCEL && sasl->state != SASL_OAUTH2_RESP && code != sasl->params->contcode) { *progress = SASL_DONE; - state(sasl, data, SASL_STOP); + sasl_state(sasl, data, SASL_STOP); return CURLE_LOGIN_DENIED; } @@ -568,16 +575,15 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, conn->user, conn->passwd, &resp); break; case SASL_LOGIN: - result = Curl_auth_create_login_message(conn->user, &resp); + Curl_auth_create_login_message(conn->user, &resp); newstate = SASL_LOGIN_PASSWD; break; case SASL_LOGIN_PASSWD: - result = Curl_auth_create_login_message(conn->passwd, &resp); + Curl_auth_create_login_message(conn->passwd, &resp); break; case SASL_EXTERNAL: - result = Curl_auth_create_external_message(conn->user, &resp); + Curl_auth_create_external_message(conn->user, &resp); break; -#ifndef CURL_DISABLE_CRYPTO_AUTH #ifdef USE_GSASL case SASL_GSASL: result = get_server_message(sasl, data, &serverdata); @@ -587,6 +593,7 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, newstate = SASL_GSASL; break; #endif +#ifndef CURL_DISABLE_DIGEST_AUTH case SASL_CRAMMD5: result = get_server_message(sasl, data, &serverdata); if(!result) @@ -698,7 +705,7 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, if(code == sasl->params->finalcode) { /* Final response was received so we are done */ *progress = SASL_DONE; - state(sasl, data, SASL_STOP); + sasl_state(sasl, data, SASL_STOP); return result; } else if(code == sasl->params->contcode) { @@ -708,7 +715,7 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, } else { *progress = SASL_DONE; - state(sasl, data, SASL_STOP); + sasl_state(sasl, data, SASL_STOP); return CURLE_LOGIN_DENIED; } @@ -745,8 +752,105 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, Curl_bufref_free(&resp); - state(sasl, data, newstate); + sasl_state(sasl, data, newstate); return result; } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void sasl_unchosen(struct Curl_easy *data, unsigned short mech, + unsigned short enabledmechs, + bool built_in, bool platform, + const char *param_missing) +{ + const char *mname = NULL; + size_t i; + + if(!(enabledmechs & mech)) + return; + + for(i = 0; mechtable[i].name; ++i) { + if(mechtable[i].bit == mech) { + mname = mechtable[i].name; + break; + } + } + if(!mname) /* should not happen */ + return; + if(!built_in) + infof(data, "SASL: %s not builtin", mname); + else if(!platform) + infof(data, "SASL: %s not supported by the platform/libraries", mname); + else { + if(param_missing) + infof(data, "SASL: %s is missing %s", mname, param_missing); + if(!data->state.aptr.user) + infof(data, "SASL: %s is missing username", mname); + } +} +#endif /* CURL_DISABLE_VERBOSE_STRINGS */ + +CURLcode Curl_sasl_is_blocked(struct SASL *sasl, struct Curl_easy *data) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS +#ifdef USE_KERBEROS5 +#define CURL_SASL_KERBEROS5 TRUE +#else +#define CURL_SASL_KERBEROS5 FALSE +#endif +#ifdef USE_GSASL +#define CURL_SASL_GASL TRUE +#else +#define CURL_SASL_GASL FALSE +#endif +#ifdef CURL_DISABLE_DIGEST_AUTH +#define CURL_SASL_DIGEST TRUE +#else +#define CURL_SASL_DIGEST FALSE +#endif +#ifndef USE_NTLM +#define CURL_SASL_NTLM TRUE +#else +#define CURL_SASL_NTLM FALSE +#endif + /* Failing SASL authentication is a pain. Give a helping hand if + * we were unable to select an AUTH mechanism. + * `sasl->authmechs` are mechanisms offered by the peer + * `sasl->prefmech` are mechanisms preferred by us */ + unsigned short enabledmechs = sasl->authmechs & sasl->prefmech; + + if(!sasl->authmechs) + infof(data, "SASL: no auth mechanism was offered or recognized"); + else if(!enabledmechs) + infof(data, "SASL: no overlap between offered and configured " + "auth mechanisms"); + else { + infof(data, "SASL: no auth mechanism offered could be selected"); + if((enabledmechs & SASL_MECH_EXTERNAL) && data->conn->passwd[0]) + infof(data, "SASL: auth EXTERNAL not chosen with password"); + sasl_unchosen(data, SASL_MECH_GSSAPI, enabledmechs, + CURL_SASL_KERBEROS5, Curl_auth_is_gssapi_supported(), NULL); + sasl_unchosen(data, SASL_MECH_SCRAM_SHA_256, enabledmechs, + CURL_SASL_GASL, FALSE, NULL); + sasl_unchosen(data, SASL_MECH_SCRAM_SHA_1, enabledmechs, + CURL_SASL_GASL, FALSE, NULL); + sasl_unchosen(data, SASL_MECH_DIGEST_MD5, enabledmechs, + CURL_SASL_DIGEST, Curl_auth_is_digest_supported(), NULL); + sasl_unchosen(data, SASL_MECH_CRAM_MD5, enabledmechs, + CURL_SASL_DIGEST, TRUE, NULL); + sasl_unchosen(data, SASL_MECH_NTLM, enabledmechs, + CURL_SASL_NTLM, Curl_auth_is_ntlm_supported(), NULL); + sasl_unchosen(data, SASL_MECH_OAUTHBEARER, enabledmechs, TRUE, TRUE, + data->set.str[STRING_BEARER] ? + NULL : "CURLOPT_XOAUTH2_BEARER"); + sasl_unchosen(data, SASL_MECH_XOAUTH2, enabledmechs, TRUE, TRUE, + data->set.str[STRING_BEARER] ? + NULL : "CURLOPT_XOAUTH2_BEARER"); + } +#endif /* CURL_DISABLE_VERBOSE_STRINGS */ + (void)sasl; + (void)data; + return CURLE_LOGIN_DENIED; +} + #endif /* protocols are enabled that use SASL */ diff --git a/Utilities/cmcurl/lib/curl_sasl.h b/Utilities/cmcurl/lib/curl_sasl.h index e94e6431a24..59983f7c669 100644 --- a/Utilities/cmcurl/lib/curl_sasl.h +++ b/Utilities/cmcurl/lib/curl_sasl.h @@ -162,4 +162,6 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, int code, saslprogress *progress); +CURLcode Curl_sasl_is_blocked(struct SASL *sasl, struct Curl_easy *data); + #endif /* HEADER_CURL_SASL_H */ diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h index 9043d97e702..2ffebf06008 100644 --- a/Utilities/cmcurl/lib/curl_setup.h +++ b/Utilities/cmcurl/lib/curl_setup.h @@ -28,49 +28,106 @@ #define CURL_NO_OLDIES #endif -/* define mingw version macros, eg __MINGW{32,64}_{MINOR,MAJOR}_VERSION */ +/* Set default _WIN32_WINNT */ #ifdef __MINGW32__ #include <_mingw.h> #endif -/* - * Disable Visual Studio warnings: - * 4127 "conditional expression is constant" - */ -#ifdef _MSC_VER -#pragma warning(disable:4127) +/* Workaround for Homebrew gcc 12.4.0, 13.3.0, 14.1.0, 14.2.0 (initial build) + that started advertising the `availability` attribute, which then gets used + by Apple SDK, but, in a way incompatible with gcc, resulting in misc errors + inside SDK headers, e.g.: + error: attributes should be specified before the declarator in a function + definition + error: expected ',' or '}' before + Followed by missing declarations. + Work it around by overriding the built-in feature-check macro used by the + headers to enable the problematic attributes. This makes the feature check + fail. Fixed in 14.2.0_1. Disable the workaround if the fix is detected. */ +#if defined(__APPLE__) && !defined(__clang__) && defined(__GNUC__) && \ + defined(__has_attribute) +# if !defined(__has_feature) +# define availability curl_pp_attribute_disabled +# elif !__has_feature(attribute_availability) +# define availability curl_pp_attribute_disabled +# endif #endif -/* - * Define WIN32 when build target is Win32 API - */ +#ifdef __APPLE__ +#include +#include +/* Fixup faulty target macro initialization in macOS SDK since v14.4 (as of + 15.0 beta). The SDK target detection in `TargetConditionals.h` correctly + detects macOS, but fails to set the macro's old name `TARGET_OS_OSX`, then + continues to set it to a default value of 0. Other parts of the SDK still + rely on the old name, and with this inconsistency our builds fail due to + missing declarations. It happens when using mainline llvm older than v18. + Later versions fixed it by predefining these target macros, avoiding the + faulty dynamic detection. gcc is not affected (for now) because it lacks + the necessary dynamic detection features, so the SDK falls back to + a codepath that sets both the old and new macro to 1. */ +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC && \ + defined(TARGET_OS_OSX) && !TARGET_OS_OSX && \ + (!defined(TARGET_OS_IPHONE) || !TARGET_OS_IPHONE) && \ + (!defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR) +#undef TARGET_OS_OSX +#define TARGET_OS_OSX TARGET_OS_MAC +#endif +#endif + +/* Visual Studio 2008 is the minimum Visual Studio version we support. + Workarounds for older versions of Visual Studio have been removed. */ +#if defined(_MSC_VER) && (_MSC_VER < 1500) +#error "Ancient versions of Visual Studio are no longer supported due to bugs." +#endif -#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) -#define WIN32 +#ifdef _MSC_VER +/* Disable Visual Studio warnings: 4127 "conditional expression is constant" */ +#pragma warning(disable:4127) +/* Avoid VS2005 and upper complaining about portable C functions. */ +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE /* for strdup(), write(), etc. */ #endif +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE /* for fopen(), getenv(), etc. */ +#endif +#endif /* _MSC_VER */ -#ifdef WIN32 +#ifdef _WIN32 /* - * Don't include unneeded stuff in Windows headers to avoid compiler + * Do not include unneeded stuff in Windows headers to avoid compiler * warnings and macro clashes. * Make sure to define this macro before including any Windows headers. */ # ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN # endif # ifndef NOGDI -# define NOGDI +# define NOGDI # endif /* Detect Windows App environment which has a restricted access * to the Win32 APIs. */ -# if (defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0602)) || \ - defined(WINAPI_FAMILY) -# include -# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ - !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -# define CURL_WINDOWS_APP +# if (defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0602)) || \ + defined(WINAPI_FAMILY) +# include +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ + !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define CURL_WINDOWS_UWP +# endif # endif -# endif +#endif + +/* Avoid bogus format check warnings with mingw32ce gcc 4.4.0 in + C99 (-std=gnu99) mode */ +#if defined(__MINGW32CE__) && !defined(CURL_NO_FMT_CHECKS) && \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) && \ + (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 4)) +#define CURL_NO_FMT_CHECKS +#endif + +/* Compatibility */ +#ifdef ENABLE_IPV6 +#define USE_IPV6 1 #endif /* @@ -84,12 +141,8 @@ #else /* HAVE_CONFIG_H */ -#ifdef _WIN32_WCE -# include "config-win32ce.h" -#else -# ifdef WIN32 -# include "config-win32.h" -# endif +#ifdef _WIN32 +# include "config-win32.h" #endif #ifdef macintosh @@ -100,10 +153,6 @@ # include "config-riscos.h" #endif -#ifdef __AMIGA__ -# include "config-amigaos.h" -#endif - #ifdef __OS400__ # include "config-os400.h" #endif @@ -112,10 +161,6 @@ # include "config-plan9.h" #endif -#ifdef MSDOS -# include "config-dos.h" -#endif - #endif /* HAVE_CONFIG_H */ #if defined(_MSC_VER) @@ -131,6 +176,12 @@ /* system header files in our config files, avoid this at any cost. */ /* ================================================================ */ +#ifdef HAVE_LIBZ +# ifndef ZLIB_CONST +# define ZLIB_CONST /* Use z_const. Supported by v1.2.5.2 and upper. */ +# endif +#endif + /* * AIX 4.3 and newer needs _THREAD_SAFE defined to build * proper reentrant code. Others may also need it. @@ -138,7 +189,7 @@ #ifdef NEED_THREAD_SAFE # ifndef _THREAD_SAFE -# define _THREAD_SAFE +# define _THREAD_SAFE # endif #endif @@ -150,14 +201,14 @@ #ifdef NEED_REENTRANT # ifndef _REENTRANT -# define _REENTRANT +# define _REENTRANT # endif #endif /* Solaris needs this to get a POSIX-conformant getpwuid_r */ #if defined(sun) || defined(__sun) # ifndef _POSIX_PTHREAD_SEMANTICS -# define _POSIX_PTHREAD_SEMANTICS 1 +# define _POSIX_PTHREAD_SEMANTICS 1 # endif #endif @@ -166,6 +217,11 @@ /* please, do it beyond the point further indicated in this file. */ /* ================================================================ */ +/* Give calloc a chance to be dragging in early, so we do not redefine */ +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include +#endif + /* * Disable other protocols when http is the only one desired. */ @@ -223,6 +279,23 @@ # define CURL_DISABLE_RTSP #endif +/* + * When HTTP is disabled, disable HTTP-only features + */ + +#ifdef CURL_DISABLE_HTTP +# define CURL_DISABLE_ALTSVC 1 +# define CURL_DISABLE_COOKIES 1 +# define CURL_DISABLE_BASIC_AUTH 1 +# define CURL_DISABLE_BEARER_AUTH 1 +# define CURL_DISABLE_AWS 1 +# define CURL_DISABLE_DOH 1 +# define CURL_DISABLE_FORM_API 1 +# define CURL_DISABLE_HEADERS_API 1 +# define CURL_DISABLE_HSTS 1 +# define CURL_DISABLE_HTTP_AUTH 1 +#endif + /* ================================================================ */ /* No system header file shall be included in this file before this */ /* point. */ @@ -248,22 +321,82 @@ * Windows setup file includes some system headers. */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # include "setup-win32.h" #endif #include +/* Helper macro to expand and concatenate two macros. + * Direct macros concatenation does not work because macros + * are not expanded before direct concatenation. + */ +#define CURL_CONC_MACROS_(A,B) A ## B +#define CURL_CONC_MACROS(A,B) CURL_CONC_MACROS_(A,B) + +/* curl uses its own printf() function internally. It understands the GNU + * format. Use this format, so that is matches the GNU format attribute we + * use with the MinGW compiler, allowing it to verify them at compile-time. + */ +#ifdef __MINGW32__ +# undef CURL_FORMAT_CURL_OFF_T +# undef CURL_FORMAT_CURL_OFF_TU +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +#endif + +/* based on logic in "curl/mprintf.h" */ + +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(gnu_printf, fmt, arg))) +#else +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(__printf__, fmt, arg))) +#endif +#else +#define CURL_PRINTF(fmt, arg) +#endif + +/* Override default printf mask check rules in "curl/mprintf.h" */ +#define CURL_TEMP_PRINTF CURL_PRINTF + +/* Workaround for mainline llvm v16 and earlier missing a built-in macro + expected by macOS SDK v14 / Xcode v15 (2023) and newer. + gcc (as of v14) is also missing it. */ +#if defined(__APPLE__) && \ + ((!defined(__apple_build_version__) && \ + defined(__clang__) && __clang_major__ < 17) || \ + (defined(__GNUC__) && __GNUC__ <= 14)) && \ + defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ + !defined(__ENVIRONMENT_OS_VERSION_MIN_REQUIRED__) +#define __ENVIRONMENT_OS_VERSION_MIN_REQUIRED__ \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif + /* * Use getaddrinfo to resolve the IPv4 address literal. If the current network - * interface doesn't support IPv4, but supports IPv6, NAT64, and DNS64, + * interface does not support IPv4, but supports IPv6, NAT64, and DNS64, * performing this task will result in a synthesized IPv6 address. */ #if defined(__APPLE__) && !defined(USE_ARES) -#include #define USE_RESOLVE_ON_IPS 1 -# if defined(TARGET_OS_OSX) && TARGET_OS_OSX -# define CURL_OSX_CALL_COPYPROXIES 1 +# if TARGET_OS_MAC && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && \ + defined(USE_IPV6) +# define CURL_MACOS_CALL_COPYPROXIES 1 +# endif +#endif + +#ifdef USE_ARES +# ifndef CARES_NO_DEPRECATED +# define CARES_NO_DEPRECATED /* for ares_getsock() */ +# endif +# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && defined(_WIN32) +# define CARES_STATICLIB /* define it before including ares.h */ # endif #endif @@ -273,11 +406,8 @@ # include #endif -#ifdef HAVE_EXTRA_STRICMP_H +#ifdef macintosh # include -#endif - -#ifdef HAVE_EXTRA_STRDUP_H # include #endif @@ -302,6 +432,7 @@ # if defined(HAVE_PROTO_BSDSOCKET_H) && \ (!defined(__amigaos4__) || defined(USE_AMISSL)) /* use bsdsocket.library directly, instead of libc networking functions */ +# define _SYS_MBUF_H /* m_len define clashes with curl */ # include # ifdef __amigaos4__ int Curl_amiga_select(int nfds, fd_set *readfds, fd_set *writefds, @@ -325,36 +456,24 @@ # define __NO_NET_API #endif +/* Whether to use eventfd() */ +#if defined(HAVE_EVENTFD) && defined(HAVE_SYS_EVENTFD_H) +#define USE_EVENTFD +#endif + #include #include #ifdef __TANDEM /* for ns*-tandem-nsk systems */ -# if ! defined __LP64 -# include /* FLOSS is only used for 32-bit builds. */ -# endif +# if ! defined __LP64 +# include /* FLOSS is only used for 32-bit builds. */ +# endif #endif #ifndef STDC_HEADERS /* no standard C headers! */ #include #endif -#ifdef __POCC__ -# include -# include -# define sys_nerr EILSEQ -#endif - -/* - * Salford-C kludge section (mostly borrowed from wxWidgets). - */ -#ifdef __SALFORDC__ - #pragma suppress 353 /* Possible nested comments */ - #pragma suppress 593 /* Define not used */ - #pragma suppress 61 /* enum has no name */ - #pragma suppress 106 /* unnamed, unused parameter */ - #include -#endif - /* Default Windows file API selection. */ #ifdef _WIN32 # if defined(_MSC_VER) && (_INTEGRAL_MAX_BITS >= 64) @@ -367,11 +486,13 @@ #endif /* - * Large file (>2Gb) support using WIN32 functions. + * Large file (>2Gb) support using Win32 functions. */ #ifdef USE_WIN32_LARGE_FILES +# ifdef HAVE_IO_H # include +# endif # include # include # undef lseek @@ -384,22 +505,30 @@ # define LSEEK_ERROR (__int64)-1 # define open curlx_win32_open # define fopen(fname,mode) curlx_win32_fopen(fname, mode) -# define access(fname,mode) curlx_win32_access(fname, mode) int curlx_win32_open(const char *filename, int oflag, ...); int curlx_win32_stat(const char *path, struct_stat *buffer); FILE *curlx_win32_fopen(const char *filename, const char *mode); - int curlx_win32_access(const char *path, int mode); +#endif + +#ifdef __DJGPP__ +/* Requires DJGPP 2.04 */ +# include +# undef lseek +# define lseek(fdes,offset,whence) llseek(fdes, offset, whence) +# define LSEEK_ERROR (offset_t)-1 #endif /* - * Small file (<2Gb) support using WIN32 functions. + * Small file (<2Gb) support using Win32 functions. */ -#ifdef USE_WIN32_SMALL_FILES +#if defined(_WIN32) && !defined(USE_WIN32_LARGE_FILES) +# ifdef HAVE_IO_H # include +# endif # include # include -# ifndef _WIN32_WCE +# ifndef UNDER_CE # undef lseek # define lseek(fdes,offset,whence) _lseek(fdes, (long)offset, whence) # define fstat(fdes,stp) _fstat(fdes, stp) @@ -407,30 +536,46 @@ # define struct_stat struct _stat # define open curlx_win32_open # define fopen(fname,mode) curlx_win32_fopen(fname, mode) -# define access(fname,mode) curlx_win32_access(fname, mode) int curlx_win32_stat(const char *path, struct_stat *buffer); int curlx_win32_open(const char *filename, int oflag, ...); FILE *curlx_win32_fopen(const char *filename, const char *mode); - int curlx_win32_access(const char *path, int mode); # endif # define LSEEK_ERROR (long)-1 #endif #ifndef struct_stat -# define struct_stat struct stat +#define struct_stat struct stat #endif #ifndef LSEEK_ERROR -# define LSEEK_ERROR (off_t)-1 +#define LSEEK_ERROR (off_t)-1 #endif #ifndef SIZEOF_TIME_T -/* assume default size of time_t to be 32 bit */ +/* assume default size of time_t to be 32 bits */ #define SIZEOF_TIME_T 4 #endif +#ifndef SIZEOF_CURL_SOCKET_T +/* configure and cmake check and set the define */ +# ifdef _WIN64 +# define SIZEOF_CURL_SOCKET_T 8 +# else +/* default guess */ +# define SIZEOF_CURL_SOCKET_T 4 +# endif +#endif + +#if SIZEOF_CURL_SOCKET_T < 8 +# define FMT_SOCKET_T "d" +#elif defined(__MINGW32__) +# define FMT_SOCKET_T "zd" +#else +# define FMT_SOCKET_T "qd" +#endif + /* - * Default sizeof(off_t) in case it hasn't been defined in config file. + * Default sizeof(off_t) in case it has not been defined in config file. */ #ifndef SIZEOF_OFF_T @@ -464,6 +609,23 @@ #endif #define CURL_OFF_T_MIN (-CURL_OFF_T_MAX - CURL_OFF_T_C(1)) +#if (SIZEOF_CURL_OFF_T != 8) +# error "curl_off_t must be exactly 64 bits" +#else + typedef unsigned CURL_TYPEOF_CURL_OFF_T curl_uint64_t; + typedef CURL_TYPEOF_CURL_OFF_T curl_int64_t; +# ifndef CURL_SUFFIX_CURL_OFF_TU +# error "CURL_SUFFIX_CURL_OFF_TU must be defined" +# endif +# define CURL_UINT64_SUFFIX CURL_SUFFIX_CURL_OFF_TU +# define CURL_UINT64_C(val) CURL_CONC_MACROS(val,CURL_UINT64_SUFFIX) +# define FMT_PRId64 CURL_FORMAT_CURL_OFF_T +# define FMT_PRIu64 CURL_FORMAT_CURL_OFF_TU +#endif + +#define FMT_OFF_T CURL_FORMAT_CURL_OFF_T +#define FMT_OFF_TU CURL_FORMAT_CURL_OFF_TU + #if (SIZEOF_TIME_T == 4) # ifdef HAVE_TIME_T_UNSIGNED # define TIME_T_MAX UINT_MAX @@ -483,7 +645,7 @@ #endif #ifndef SIZE_T_MAX -/* some limits.h headers have this defined, some don't */ +/* some limits.h headers have this defined, some do not */ #if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) #define SIZE_T_MAX 18446744073709551615U #else @@ -492,7 +654,7 @@ #endif #ifndef SSIZE_T_MAX -/* some limits.h headers have this defined, some don't */ +/* some limits.h headers have this defined, some do not */ #if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) #define SSIZE_T_MAX 9223372036854775807 #else @@ -501,7 +663,7 @@ #endif /* - * Arg 2 type for gethostname in case it hasn't been defined in config file. + * Arg 2 type for gethostname in case it has not been defined in config file. */ #ifndef GETHOSTNAME_TYPE_ARG2 @@ -518,11 +680,11 @@ 5. set dir/file naming defines */ -#ifdef WIN32 +#ifdef _WIN32 # define DIR_CHAR "\\" -#else /* WIN32 */ +#else /* _WIN32 */ # ifdef MSDOS /* Watt-32 */ @@ -541,149 +703,96 @@ # ifdef __minix /* Minix 3 versions up to at least 3.1.3 are missing these prototypes */ - extern char *strtok_r(char *s, const char *delim, char **last); extern struct tm *gmtime_r(const time_t * const timep, struct tm *tmp); # endif # define DIR_CHAR "/" -# ifndef fileno /* sunos 4 have this as a macro! */ - int fileno(FILE *stream); -# endif - -#endif /* WIN32 */ - -/* - * msvc 6.0 requires PSDK in order to have INET6_ADDRSTRLEN - * defined in ws2tcpip.h as well as to provide IPv6 support. - * Does not apply if lwIP is used. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) && !defined(USE_LWIPSOCK) -# if !defined(HAVE_WS2TCPIP_H) || \ - ((_MSC_VER < 1300) && !defined(INET6_ADDRSTRLEN)) -# undef HAVE_GETADDRINFO_THREADSAFE -# undef HAVE_FREEADDRINFO -# undef HAVE_GETADDRINFO -# undef ENABLE_IPV6 -# endif -#endif +#endif /* _WIN32 */ /* ---------------------------------------------------------------- */ /* resolver specialty compile-time defines */ /* CURLRES_* defines to use in the host*.c sources */ /* ---------------------------------------------------------------- */ -/* - * lcc-win32 doesn't have _beginthreadex(), lacks threads support. - */ - -#if defined(__LCC__) && defined(WIN32) -# undef USE_THREADS_POSIX -# undef USE_THREADS_WIN32 -#endif - -/* - * MSVC threads support requires a multi-threaded runtime library. - * _beginthreadex() is not available in single-threaded ones. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) && !defined(_MT) -# undef USE_THREADS_POSIX -# undef USE_THREADS_WIN32 -#endif - /* * Mutually exclusive CURLRES_* definitions. */ -#if defined(ENABLE_IPV6) && defined(HAVE_GETADDRINFO) +#if defined(USE_IPV6) && defined(HAVE_GETADDRINFO) # define CURLRES_IPV6 +#elif defined(USE_IPV6) && (defined(_WIN32) || defined(__CYGWIN__)) +/* assume on Windows that IPv6 without getaddrinfo is a broken build */ +# error "Unexpected build: IPv6 is enabled but getaddrinfo was not found." #else # define CURLRES_IPV4 #endif -#ifdef USE_ARES +#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) +# define CURLRES_ASYNCH +# define CURLRES_THREADED +#elif defined(USE_ARES) # define CURLRES_ASYNCH # define CURLRES_ARES /* now undef the stock libc functions just to avoid them being used */ # undef HAVE_GETADDRINFO # undef HAVE_FREEADDRINFO -#elif defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) -# define CURLRES_ASYNCH -# define CURLRES_THREADED #else # define CURLRES_SYNCH #endif /* ---------------------------------------------------------------- */ -/* - * msvc 6.0 does not have struct sockaddr_storage and - * does not define IPPROTO_ESP in winsock2.h. But both - * are available if PSDK is properly installed. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) -# if !defined(HAVE_WINSOCK2_H) || ((_MSC_VER < 1300) && !defined(IPPROTO_ESP)) -# undef HAVE_STRUCT_SOCKADDR_STORAGE -# endif +#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && \ + !defined(USE_WIN32_IDN) && !defined(USE_APPLE_IDN) +/* The lib and header are present */ +#define USE_LIBIDN2 #endif -/* - * Intentionally fail to build when using msvc 6.0 without PSDK installed. - * The brave of heart can circumvent this, defining ALLOW_MSVC6_WITHOUT_PSDK - * in lib/config-win32.h although absolutely discouraged and unsupported. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) -# if !defined(HAVE_WINDOWS_H) || ((_MSC_VER < 1300) && !defined(_FILETIME_)) -# if !defined(ALLOW_MSVC6_WITHOUT_PSDK) -# error MSVC 6.0 requires "February 2003 Platform SDK" a.k.a. \ - "Windows Server 2003 PSDK" -# else -# define CURL_DISABLE_LDAP 1 -# endif -# endif +#if defined(USE_LIBIDN2) && (defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN)) +#error "libidn2 cannot be enabled with WinIDN or AppleIDN, choose one." #endif -#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && !defined(USE_WIN32_IDN) -/* The lib and header are present */ -#define USE_LIBIDN2 +#if defined(USE_GNUTLS) || defined(USE_OPENSSL) || defined(USE_MBEDTLS) || \ + defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#define USE_SSL /* SSL support has been enabled */ #endif -#if defined(USE_LIBIDN2) && defined(USE_WIN32_IDN) -#error "Both libidn2 and WinIDN are enabled, choose one." +#if defined(USE_OPENSSL) && defined(USE_WOLFSSL) +# include +# if LIBWOLFSSL_VERSION_HEX >= 0x05007006 +# ifndef OPENSSL_COEXIST +# define OPENSSL_COEXIST +# endif +# else +# error "OpenSSL can only coexist with wolfSSL v5.7.6 or upper" +# endif #endif -#define LIBIDN_REQUIRED_VERSION "0.4.1" - -#if defined(USE_GNUTLS) || defined(USE_OPENSSL) || defined(USE_NSS) || \ - defined(USE_MBEDTLS) || \ - defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || \ - defined(USE_SECTRANSP) || defined(USE_GSKIT) || \ - defined(USE_BEARSSL) || defined(USE_RUSTLS) -#define USE_SSL /* SSL support has been enabled */ +#if defined(USE_WOLFSSL) && defined(USE_GNUTLS) +/* Avoid defining unprefixed wolfSSL SHA macros colliding with nettle ones */ +#define NO_OLD_WC_NAMES #endif /* Single point where USE_SPNEGO definition might be defined */ -#if !defined(CURL_DISABLE_CRYPTO_AUTH) && \ +#if !defined(CURL_DISABLE_NEGOTIATE_AUTH) && \ (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) #define USE_SPNEGO #endif /* Single point where USE_KERBEROS5 definition might be defined */ -#if !defined(CURL_DISABLE_CRYPTO_AUTH) && \ +#if !defined(CURL_DISABLE_KERBEROS_AUTH) && \ (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) #define USE_KERBEROS5 #endif /* Single point where USE_NTLM definition might be defined */ -#if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(CURL_DISABLE_NTLM) -# if defined(USE_OPENSSL) || defined(USE_MBEDTLS) || \ - defined(USE_GNUTLS) || defined(USE_NSS) || defined(USE_SECTRANSP) || \ - defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) || \ - (defined(USE_WOLFSSL) && defined(HAVE_WOLFSSL_DES_ECB_ENCRYPT)) +#ifndef CURL_DISABLE_NTLM +# if defined(USE_OPENSSL) || defined(USE_MBEDTLS) || \ + defined(USE_GNUTLS) || defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) || \ + (defined(USE_WOLFSSL) && defined(HAVE_WOLFSSL_DES_ECB_ENCRYPT)) # define USE_CURL_NTLM_CORE # endif # if defined(USE_CURL_NTLM_CORE) || defined(USE_WINDOWS_SSPI) @@ -691,10 +800,6 @@ # endif #endif -#ifdef CURL_WANTS_CA_BUNDLE_ENV -#error "No longer supported. Set CURLOPT_CAINFO at runtime instead." -#endif - #if defined(USE_LIBSSH2) || defined(USE_LIBSSH) || defined(USE_WOLFSSH) #define USE_SSH #endif @@ -709,11 +814,42 @@ ((__GNUC__ == 2) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 7))) # define UNUSED_PARAM __attribute__((__unused__)) # define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif defined(__IAR_SYSTEMS_ICC__) +# define UNUSED_PARAM __attribute__((__unused__)) +# if (__VER__ >= 9040001) +# define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define WARN_UNUSED_RESULT +# endif #else # define UNUSED_PARAM /* NOTHING */ # define WARN_UNUSED_RESULT #endif +/* noreturn attribute */ + +#ifndef CURL_NORETURN +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__) +# define CURL_NORETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) +# define CURL_NORETURN __declspec(noreturn) +#else +# define CURL_NORETURN +#endif +#endif + +/* fallthrough attribute */ + +#ifndef FALLTHROUGH +#if (defined(__GNUC__) && __GNUC__ >= 7) || \ + (defined(__clang__) && __clang_major__ >= 10) +# define FALLTHROUGH() __attribute__((fallthrough)) +#else +# define FALLTHROUGH() do {} while (0) +#endif +#endif + /* * Include macros and defines that should only be processed once. */ @@ -722,12 +858,33 @@ #include "curl_setup_once.h" #endif +#ifdef UNDER_CE +#define getenv curl_getenv /* Windows CE does not support getenv() */ +#define raise(s) ((void)(s)) +/* Terrible workarounds to make Windows CE compile */ +#define errno 0 +#define CURL_SETERRNO(x) ((void)(x)) +#define EINTR 4 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EEXIST 17 +#define EISDIR 21 +#define EINVAL 22 +#define ENOSPC 28 +#define strerror(x) "?" +#undef STDIN_FILENO +#define STDIN_FILENO 0 +#else +#define CURL_SETERRNO(x) (errno = (x)) +#endif + /* * Definition of our NOP statement Object-like macro */ #ifndef Curl_nop_stmt -# define Curl_nop_stmt do { } while(0) +#define Curl_nop_stmt do { } while(0) #endif /* @@ -735,16 +892,13 @@ */ #if defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H) -# if defined(SOCKET) || \ - defined(USE_WINSOCK) || \ - defined(HAVE_WINSOCK2_H) || \ - defined(HAVE_WS2TCPIP_H) -# error "WinSock and lwIP TCP/IP stack definitions shall not coexist!" +# if defined(SOCKET) || defined(USE_WINSOCK) +# error "Winsock and lwIP TCP/IP stack definitions shall not coexist!" # endif #endif /* - * shutdown() flags for systems that don't define them + * shutdown() flags for systems that do not define them */ #ifndef SHUT_RD @@ -769,15 +923,34 @@ #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif +/* For MSVC (all versions as of VS2022) */ +#ifndef STDIN_FILENO +#define STDIN_FILENO fileno(stdin) +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO fileno(stdout) +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO fileno(stderr) +#endif + +/* Since O_BINARY is used in bitmasks, setting it to zero makes it usable in + source code but yet it does not ruin anything */ +#ifdef O_BINARY +#define CURL_O_BINARY O_BINARY +#else +#define CURL_O_BINARY 0 +#endif + /* In Windows the default file mode is text but an application can override it. Therefore we specify it explicitly. https://github.com/curl/curl/pull/258 */ -#if defined(WIN32) || defined(MSDOS) +#if defined(_WIN32) || defined(MSDOS) #define FOPEN_READTEXT "rt" #define FOPEN_WRITETEXT "wt" #define FOPEN_APPENDTEXT "at" #elif defined(__CYGWIN__) -/* Cygwin has specific behavior we need to address when WIN32 is not defined. +/* Cygwin has specific behavior we need to address when _WIN32 is not defined. https://cygwin.com/cygwin-ug-net/using-textbinary.html For write we want our output to have line endings of LF and be compatible with other Cygwin utilities. For read we want to handle input that may have line @@ -792,12 +965,14 @@ endings either CRLF or LF so 't' is appropriate. #define FOPEN_APPENDTEXT "a" #endif -/* for systems that don't detect this in configure */ +/* for systems that do not detect this in configure */ #ifndef CURL_SA_FAMILY_T # if defined(HAVE_SA_FAMILY_T) # define CURL_SA_FAMILY_T sa_family_t # elif defined(HAVE_ADDRESS_FAMILY) # define CURL_SA_FAMILY_T ADDRESS_FAMILY +# elif defined(__AMIGA__) +# define CURL_SA_FAMILY_T unsigned char # else /* use a sensible default */ # define CURL_SA_FAMILY_T unsigned short @@ -814,57 +989,100 @@ endings either CRLF or LF so 't' is appropriate. as their argument */ #define STRCONST(x) x,sizeof(x)-1 -/* Some versions of the Android SDK is missing the declaration */ -#if defined(HAVE_GETPWUID_R) && defined(HAVE_DECL_GETPWUID_R_MISSING) +#define CURL_ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) + +#ifdef CURLDEBUG +#define CURL_GETADDRINFO(host,serv,hint,res) \ + curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) +#define CURL_FREEADDRINFO(data) \ + curl_dbg_freeaddrinfo(data, __LINE__, __FILE__) +#else +#define CURL_GETADDRINFO getaddrinfo +#define CURL_FREEADDRINFO freeaddrinfo +#endif + +/* Some versions of the Android NDK is missing the declaration */ +#if defined(HAVE_GETPWUID_R) && \ + defined(__ANDROID_API__) && (__ANDROID_API__ < 21) struct passwd; int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result); #endif -#ifdef DEBUGBUILD +#ifdef UNITTESTS #define UNITTEST #else #define UNITTEST static #endif -#if defined(USE_NGHTTP2) || defined(USE_HYPER) +#ifdef USE_NGHTTP2 #define USE_HTTP2 #endif #if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + (defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)) || \ defined(USE_QUICHE) || defined(USE_MSH3) -#define ENABLE_QUIC + +#ifdef CURL_WITH_MULTI_SSL +#error "MultiSSL combined with QUIC is not supported" +#endif + #define USE_HTTP3 #endif /* Certain Windows implementations are not aligned with what curl expects, so always use the local one on this platform. E.g. the mingw-w64 implementation can return wrong results for non-ASCII inputs. */ -#if defined(HAVE_BASENAME) && defined(WIN32) +#if defined(HAVE_BASENAME) && defined(_WIN32) #undef HAVE_BASENAME #endif -#if defined(USE_UNIX_SOCKETS) && defined(WIN32) -# if defined(__MINGW32__) && !defined(LUP_SECURE) - typedef u_short ADDRESS_FAMILY; /* Classic mingw, 11y+ old mingw-w64 */ -# endif +#if defined(USE_UNIX_SOCKETS) && defined(_WIN32) # if !defined(UNIX_PATH_MAX) /* Replicating logic present in afunix.h (distributed with newer Windows 10 SDK versions only) */ # define UNIX_PATH_MAX 108 /* !checksrc! disable TYPEDEFSTRUCT 1 */ typedef struct sockaddr_un { - ADDRESS_FAMILY sun_family; + CURL_SA_FAMILY_T sun_family; char sun_path[UNIX_PATH_MAX]; } SOCKADDR_UN, *PSOCKADDR_UN; # define WIN32_SOCKADDR_UN # endif #endif +#ifdef USE_OPENSSL /* OpenSSLv3 marks DES, MD5 and ENGINE functions deprecated but we have no replacements (yet) so tell the compiler to not warn for them. */ -#ifdef USE_OPENSSL -#define OPENSSL_SUPPRESS_DEPRECATED +# define OPENSSL_SUPPRESS_DEPRECATED +# ifdef _WIN32 +/* Silence LibreSSL warnings about wincrypt.h collision. Works in 3.8.2+ */ +# ifndef LIBRESSL_DISABLE_OVERRIDE_WINCRYPT_DEFINES_WARNING +# define LIBRESSL_DISABLE_OVERRIDE_WINCRYPT_DEFINES_WARNING +# endif +# endif +#endif + +#ifdef CURL_INLINE +/* 'CURL_INLINE' defined, use as-is */ +#elif defined(inline) +# define CURL_INLINE inline /* 'inline' defined, assumed correct */ +#elif defined(__cplusplus) +/* The code is compiled with C++ compiler. + C++ always supports 'inline'. */ +# define CURL_INLINE inline /* 'inline' keyword supported */ +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901 +/* C99 (and later) supports 'inline' keyword */ +# define CURL_INLINE inline /* 'inline' keyword supported */ +#elif defined(__GNUC__) && __GNUC__ >= 3 +/* GCC supports '__inline__' as an extension */ +# define CURL_INLINE __inline__ +#elif defined(_MSC_VER) +# define CURL_INLINE __inline +#else +/* Probably 'inline' is not supported by compiler. + Define to the empty string to be on the safe side. */ +# define CURL_INLINE /* empty */ #endif #endif /* HEADER_CURL_SETUP_H */ diff --git a/Utilities/cmcurl/lib/curl_setup_once.h b/Utilities/cmcurl/lib/curl_setup_once.h index dde7229d238..c1051e0faef 100644 --- a/Utilities/cmcurl/lib/curl_setup_once.h +++ b/Utilities/cmcurl/lib/curl_setup_once.h @@ -24,7 +24,6 @@ * ***************************************************************************/ - /* * Inclusion of common header files. */ @@ -34,20 +33,14 @@ #include #include #include +#ifndef UNDER_CE #include +#endif #ifdef HAVE_SYS_TYPES_H #include #endif -#ifdef NEED_MALLOC_H -#include -#endif - -#ifdef NEED_MEMORY_H -#include -#endif - #ifdef HAVE_SYS_STAT_H #include #endif @@ -56,8 +49,11 @@ #include #endif -#ifdef WIN32 +#ifdef HAVE_IO_H #include +#endif + +#ifdef HAVE_FCNTL_H #include #endif @@ -69,12 +65,24 @@ #include #endif -#ifdef USE_WOLFSSL -# if defined(HAVE_STDINT_H) -# include -# elif defined(HAVE_INTTYPES_H) -# include -# endif +#if defined(HAVE_STDINT_H) || defined(USE_WOLFSSL) +#include +#endif + +/* Macro to strip 'const' without triggering a compiler warning. + Use it for APIs that do not or cannot support the const qualifier. */ +#ifdef HAVE_STDINT_H +# define CURL_UNCONST(p) ((void *)(uintptr_t)(const void *)(p)) +#elif defined(_WIN32) /* for VS2008 */ +# define CURL_UNCONST(p) ((void *)(ULONG_PTR)(const void *)(p)) +#else +# define CURL_UNCONST(p) ((void *)(p)) /* Fall back to simple cast */ +#endif + +#ifdef USE_SCHANNEL +/* Must set this before is included directly or indirectly by + another Windows header. */ +# define SCHANNEL_USE_BLACKLISTS 1 #endif #ifdef __hpux @@ -104,13 +112,13 @@ #endif /* - * Definition of timeval struct for platforms that don't have it. + * Definition of timeval struct for platforms that do not have it. */ #ifndef HAVE_STRUCT_TIMEVAL struct timeval { - long tv_sec; - long tv_usec; + long tv_sec; + long tv_usec; }; #endif @@ -127,8 +135,8 @@ struct timeval { #endif -#if defined(__minix) -/* Minix doesn't support recv on TCP sockets */ +#ifdef __minix +/* Minix does not support recv on TCP sockets */ #define sread(x,y,z) (ssize_t)read((RECV_TYPE_ARG1)(x), \ (RECV_TYPE_ARG2)(y), \ (RECV_TYPE_ARG3)(z)) @@ -141,7 +149,7 @@ struct timeval { * * HAVE_RECV is defined if you have a function named recv() * which is used to read incoming data from sockets. If your - * function has another name then don't define HAVE_RECV. + * function has another name then do not define HAVE_RECV. * * If HAVE_RECV is defined then RECV_TYPE_ARG1, RECV_TYPE_ARG2, * RECV_TYPE_ARG3, RECV_TYPE_ARG4 and RECV_TYPE_RETV must also @@ -149,7 +157,7 @@ struct timeval { * * HAVE_SEND is defined if you have a function named send() * which is used to write outgoing data on a connected socket. - * If yours has another name then don't define HAVE_SEND. + * If yours has another name then do not define HAVE_SEND. * * If HAVE_SEND is defined then SEND_TYPE_ARG1, SEND_QUAL_ARG2, * SEND_TYPE_ARG2, SEND_TYPE_ARG3, SEND_TYPE_ARG4 and @@ -162,29 +170,24 @@ struct timeval { (RECV_TYPE_ARG4)(0)) #else /* HAVE_RECV */ #ifndef sread - /* */ - Error Missing_definition_of_macro_sread - /* */ +#error "Missing definition of macro sread!" #endif #endif /* HAVE_RECV */ -#if defined(__minix) -/* Minix doesn't support send on TCP sockets */ +#ifdef __minix +/* Minix does not support send on TCP sockets */ #define swrite(x,y,z) (ssize_t)write((SEND_TYPE_ARG1)(x), \ - (SEND_TYPE_ARG2)(y), \ - (SEND_TYPE_ARG3)(z)) - + (SEND_TYPE_ARG2)CURL_UNCONST(y), \ + (SEND_TYPE_ARG3)(z)) #elif defined(HAVE_SEND) #define swrite(x,y,z) (ssize_t)send((SEND_TYPE_ARG1)(x), \ - (SEND_QUAL_ARG2 SEND_TYPE_ARG2)(y), \ + (SEND_QUAL_ARG2 SEND_TYPE_ARG2)CURL_UNCONST(y), \ (SEND_TYPE_ARG3)(z), \ (SEND_TYPE_ARG4)(SEND_4TH_ARG)) #else /* HAVE_SEND */ #ifndef swrite - /* */ - Error Missing_definition_of_macro_swrite - /* */ +#error "Missing definition of macro swrite!" #endif #endif /* HAVE_SEND */ @@ -193,11 +196,11 @@ struct timeval { * Function-like macro definition used to close a socket. */ -#if defined(HAVE_CLOSESOCKET) +#ifdef HAVE_CLOSESOCKET # define sclose(x) closesocket((x)) #elif defined(HAVE_CLOSESOCKET_CAMEL) # define sclose(x) CloseSocket((x)) -#elif defined(HAVE_CLOSE_S) +#elif defined(MSDOS) /* Watt-32 */ # define sclose(x) close_s((x)) #elif defined(USE_LWIPSOCK) # define sclose(x) lwip_close((x)) @@ -208,7 +211,7 @@ struct timeval { /* * Stack-independent version of fcntl() on sockets: */ -#if defined(USE_LWIPSOCK) +#ifdef USE_LWIPSOCK # define sfcntl lwip_fcntl #else # define sfcntl fcntl @@ -228,19 +231,19 @@ struct timeval { /* * 'bool' exists on platforms with , i.e. C99 platforms. - * On non-C99 platforms there's no bool, so define an enum for that. + * On non-C99 platforms there is no bool, so define an enum for that. * On C99 platforms 'false' and 'true' also exist. Enum uses a * global namespace though, so use bool_false and bool_true. */ #ifndef HAVE_BOOL_T typedef enum { - bool_false = 0, - bool_true = 1 + bool_false = 0, + bool_true = 1 } bool; /* - * Use a define to let 'true' and 'false' use those enums. There + * Use a define to let 'true' and 'false' use those enums. There * are currently no use of true and false in libcurl proper, but * there are some in the examples. This will cater for any later * code happening to use true and false. @@ -292,7 +295,7 @@ typedef unsigned int bit; */ #undef DEBUGASSERT -#if defined(DEBUGBUILD) +#ifdef DEBUGBUILD #define DEBUGASSERT(x) assert(x) #else #define DEBUGASSERT(x) do { } while(0) @@ -318,78 +321,39 @@ typedef unsigned int bit; */ #ifdef USE_WINSOCK -#undef EBADF /* override definition in errno.h */ -#define EBADF WSAEBADF -#undef EINTR /* override definition in errno.h */ -#define EINTR WSAEINTR -#undef EINVAL /* override definition in errno.h */ -#define EINVAL WSAEINVAL -#undef EWOULDBLOCK /* override definition in errno.h */ -#define EWOULDBLOCK WSAEWOULDBLOCK -#undef EINPROGRESS /* override definition in errno.h */ -#define EINPROGRESS WSAEINPROGRESS -#undef EALREADY /* override definition in errno.h */ -#define EALREADY WSAEALREADY -#undef ENOTSOCK /* override definition in errno.h */ -#define ENOTSOCK WSAENOTSOCK -#undef EDESTADDRREQ /* override definition in errno.h */ -#define EDESTADDRREQ WSAEDESTADDRREQ -#undef EMSGSIZE /* override definition in errno.h */ -#define EMSGSIZE WSAEMSGSIZE -#undef EPROTOTYPE /* override definition in errno.h */ -#define EPROTOTYPE WSAEPROTOTYPE -#undef ENOPROTOOPT /* override definition in errno.h */ -#define ENOPROTOOPT WSAENOPROTOOPT -#undef EPROTONOSUPPORT /* override definition in errno.h */ -#define EPROTONOSUPPORT WSAEPROTONOSUPPORT -#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT -#undef EOPNOTSUPP /* override definition in errno.h */ -#define EOPNOTSUPP WSAEOPNOTSUPP -#define EPFNOSUPPORT WSAEPFNOSUPPORT -#undef EAFNOSUPPORT /* override definition in errno.h */ -#define EAFNOSUPPORT WSAEAFNOSUPPORT -#undef EADDRINUSE /* override definition in errno.h */ -#define EADDRINUSE WSAEADDRINUSE -#undef EADDRNOTAVAIL /* override definition in errno.h */ -#define EADDRNOTAVAIL WSAEADDRNOTAVAIL -#undef ENETDOWN /* override definition in errno.h */ -#define ENETDOWN WSAENETDOWN -#undef ENETUNREACH /* override definition in errno.h */ -#define ENETUNREACH WSAENETUNREACH -#undef ENETRESET /* override definition in errno.h */ -#define ENETRESET WSAENETRESET -#undef ECONNABORTED /* override definition in errno.h */ -#define ECONNABORTED WSAECONNABORTED -#undef ECONNRESET /* override definition in errno.h */ -#define ECONNRESET WSAECONNRESET -#undef ENOBUFS /* override definition in errno.h */ -#define ENOBUFS WSAENOBUFS -#undef EISCONN /* override definition in errno.h */ -#define EISCONN WSAEISCONN -#undef ENOTCONN /* override definition in errno.h */ -#define ENOTCONN WSAENOTCONN -#define ESHUTDOWN WSAESHUTDOWN -#define ETOOMANYREFS WSAETOOMANYREFS -#undef ETIMEDOUT /* override definition in errno.h */ -#define ETIMEDOUT WSAETIMEDOUT -#undef ECONNREFUSED /* override definition in errno.h */ -#define ECONNREFUSED WSAECONNREFUSED -#undef ELOOP /* override definition in errno.h */ -#define ELOOP WSAELOOP -#ifndef ENAMETOOLONG /* possible previous definition in errno.h */ -#define ENAMETOOLONG WSAENAMETOOLONG -#endif -#define EHOSTDOWN WSAEHOSTDOWN -#undef EHOSTUNREACH /* override definition in errno.h */ -#define EHOSTUNREACH WSAEHOSTUNREACH -#ifndef ENOTEMPTY /* possible previous definition in errno.h */ -#define ENOTEMPTY WSAENOTEMPTY +#define SOCKEACCES WSAEACCES +#define SOCKEADDRINUSE WSAEADDRINUSE +#define SOCKEADDRNOTAVAIL WSAEADDRNOTAVAIL +#define SOCKEAFNOSUPPORT WSAEAFNOSUPPORT +#define SOCKEBADF WSAEBADF +#define SOCKECONNREFUSED WSAECONNREFUSED +#define SOCKECONNRESET WSAECONNRESET +#define SOCKEINPROGRESS WSAEINPROGRESS +#define SOCKEINTR WSAEINTR +#define SOCKEINVAL WSAEINVAL +#define SOCKEISCONN WSAEISCONN +#define SOCKEMSGSIZE WSAEMSGSIZE +#define SOCKENOMEM WSA_NOT_ENOUGH_MEMORY +#define SOCKETIMEDOUT WSAETIMEDOUT +#define SOCKEWOULDBLOCK WSAEWOULDBLOCK +#else +#define SOCKEACCES EACCES +#define SOCKEADDRINUSE EADDRINUSE +#define SOCKEADDRNOTAVAIL EADDRNOTAVAIL +#define SOCKEAFNOSUPPORT EAFNOSUPPORT +#define SOCKEBADF EBADF +#define SOCKECONNREFUSED ECONNREFUSED +#define SOCKECONNRESET ECONNRESET +#define SOCKEINPROGRESS EINPROGRESS +#define SOCKEINTR EINTR +#define SOCKEINVAL EINVAL +#define SOCKEISCONN EISCONN +#define SOCKEMSGSIZE EMSGSIZE +#define SOCKENOMEM ENOMEM +#ifdef ETIMEDOUT +#define SOCKETIMEDOUT ETIMEDOUT #endif -#define EPROCLIM WSAEPROCLIM -#define EUSERS WSAEUSERS -#define EDQUOT WSAEDQUOT -#define ESTALE WSAESTALE -#define EREMOTE WSAEREMOTE +#define SOCKEWOULDBLOCK EWOULDBLOCK #endif /* @@ -398,8 +362,8 @@ typedef unsigned int bit; #ifdef __VMS #define argv_item_t __char_ptr32 -#elif defined(_UNICODE) -#define argv_item_t wchar_t * +#elif defined(_UNICODE) && !defined(UNDER_CE) +#define argv_item_t wchar_t * #else #define argv_item_t char * #endif diff --git a/Utilities/cmcurl/lib/curl_sha256.h b/Utilities/cmcurl/lib/curl_sha256.h index c5e157bee1f..f532939d056 100644 --- a/Utilities/cmcurl/lib/curl_sha256.h +++ b/Utilities/cmcurl/lib/curl_sha256.h @@ -25,19 +25,16 @@ * ***************************************************************************/ -#ifndef CURL_DISABLE_CRYPTO_AUTH +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) || defined(USE_SSL) + #include #include "curl_hmac.h" -extern const struct HMAC_params Curl_HMAC_SHA256[1]; +extern const struct HMAC_params Curl_HMAC_SHA256; -#ifdef USE_WOLFSSL -/* SHA256_DIGEST_LENGTH is an enum value in wolfSSL. Need to import it from - * sha.h */ -#include -#include -#else -#define SHA256_DIGEST_LENGTH 32 +#ifndef CURL_SHA256_DIGEST_LENGTH +#define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ #endif CURLcode Curl_sha256it(unsigned char *outbuffer, const unsigned char *input, diff --git a/Utilities/cmcurl/lib/curl_sha512_256.c b/Utilities/cmcurl/lib/curl_sha512_256.c new file mode 100644 index 00000000000..2d8a23f19a7 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_sha512_256.c @@ -0,0 +1,846 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Evgeny Grin (Karlson2k), . + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_DIGEST_AUTH) && !defined(CURL_DISABLE_SHA512_256) + +#include "curl_sha512_256.h" +#include "curlx/warnless.h" + +/* The recommended order of the TLS backends: + * * OpenSSL + * * GnuTLS + * * wolfSSL + * * Schannel SSPI + * * Secure Transport (Darwin) + * * mbedTLS + * * BearSSL + * * Rustls + * Skip the backend if it does not support the required algorithm */ + +#if defined(USE_OPENSSL) +# include +# if (!defined(LIBRESSL_VERSION_NUMBER) && \ + defined(OPENSSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER >= 0x10101000L)) || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + (LIBRESSL_VERSION_NUMBER >= 0x3080000fL)) +# include +# if !defined(OPENSSL_NO_SHA) && !defined(OPENSSL_NO_SHA512) +# include +# define USE_OPENSSL_SHA512_256 1 +# define HAS_SHA512_256_IMPLEMENTATION 1 +# ifdef __NetBSD__ +/* Some NetBSD versions has a bug in SHA-512/256. + * See https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 + * The problematic versions: + * - NetBSD before 9.4 + * - NetBSD 9 all development versions (9.99.x) + * - NetBSD 10 development versions (10.99.x) before 10.99.11 + * The bug was fixed in NetBSD 9.4 release, NetBSD 10.0 release, + * NetBSD 10.99.11 development. + * It is safe to apply the workaround even if the bug is not present, as + * the workaround just reduces performance slightly. */ +# include +# if __NetBSD_Version__ < 904000000 || \ + (__NetBSD_Version__ >= 999000000 && \ + __NetBSD_Version__ < 1000000000) || \ + (__NetBSD_Version__ >= 1099000000 && \ + __NetBSD_Version__ < 1099001100) +# define NEED_NETBSD_SHA512_256_WORKAROUND 1 +# include +# endif +# endif +# endif +# endif +#endif /* USE_OPENSSL */ + + +#if !defined(HAS_SHA512_256_IMPLEMENTATION) && defined(USE_GNUTLS) +# include +# if defined(SHA512_256_DIGEST_SIZE) +# define USE_GNUTLS_SHA512_256 1 +# endif +#endif /* ! HAS_SHA512_256_IMPLEMENTATION && USE_GNUTLS */ + +#if defined(USE_OPENSSL_SHA512_256) + +/* OpenSSL does not provide macros for SHA-512/256 sizes */ + +/** + * Size of the SHA-512/256 single processing block in bytes. + */ +#define CURL_SHA512_256_BLOCK_SIZE 128 + +/** + * Size of the SHA-512/256 resulting digest in bytes. + * This is the final digest size, not intermediate hash. + */ +#define CURL_SHA512_256_DIGEST_SIZE CURL_SHA512_256_DIGEST_LENGTH + +/** + * Context type used for SHA-512/256 calculations + */ +typedef EVP_MD_CTX *Curl_sha512_256_ctx; + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_init(void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + *ctx = EVP_MD_CTX_create(); + if(!*ctx) + return CURLE_OUT_OF_MEMORY; + + if(EVP_DigestInit_ex(*ctx, EVP_sha512_256(), NULL)) { + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(EVP_MD_CTX_size(*ctx) == CURL_SHA512_256_DIGEST_SIZE); + /* Check whether the block size is correct */ + DEBUGASSERT(EVP_MD_CTX_block_size(*ctx) == CURL_SHA512_256_BLOCK_SIZE); + + return CURLE_OK; /* Success */ + } + + /* Cleanup */ + EVP_MD_CTX_destroy(*ctx); + return CURLE_FAILED_INIT; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + if(!EVP_DigestUpdate(*ctx, data, length)) + return CURLE_SSL_CIPHER; + + return CURLE_OK; +} + + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_finish(unsigned char *digest, + void *context) +{ + CURLcode ret; + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + +#ifdef NEED_NETBSD_SHA512_256_WORKAROUND + /* Use a larger buffer to work around a bug in NetBSD: + https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 */ + unsigned char tmp_digest[CURL_SHA512_256_DIGEST_SIZE * 2]; + ret = EVP_DigestFinal_ex(*ctx, + tmp_digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; + if(ret == CURLE_OK) + memcpy(digest, tmp_digest, CURL_SHA512_256_DIGEST_SIZE); + explicit_memset(tmp_digest, 0, sizeof(tmp_digest)); +#else /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ + ret = EVP_DigestFinal_ex(*ctx, digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; +#endif /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ + + EVP_MD_CTX_destroy(*ctx); + *ctx = NULL; + + return ret; +} + +#elif defined(USE_GNUTLS_SHA512_256) + +#define CURL_SHA512_256_BLOCK_SIZE SHA512_256_BLOCK_SIZE +#define CURL_SHA512_256_DIGEST_SIZE SHA512_256_DIGEST_SIZE + +/** + * Context type used for SHA-512/256 calculations + */ +typedef struct sha512_256_ctx Curl_sha512_256_ctx; + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_init(void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(CURL_SHA512_256_DIGEST_LENGTH == CURL_SHA512_256_DIGEST_SIZE); + + sha512_256_init(ctx); + + return CURLE_OK; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @param length number of bytes in @a data + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + DEBUGASSERT((data != NULL) || (length == 0)); + + sha512_256_update(ctx, length, (const uint8_t *)data); + + return CURLE_OK; +} + + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_finish(unsigned char *digest, + void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + sha512_256_digest(ctx, + (size_t)CURL_SHA512_256_DIGEST_SIZE, (uint8_t *)digest); + + return CURLE_OK; +} + +#else /* No system or TLS backend SHA-512/256 implementation available */ + +/* ** This implementation of SHA-512/256 hash calculation was originally ** * + * ** written by Evgeny Grin (Karlson2k) for GNU libmicrohttpd. ** * + * ** The author ported the code to libcurl. The ported code is provided ** * + * ** under curl license. ** * + * ** This is a minimal version with minimal optimizations. Performance ** * + * ** can be significantly improved. Big-endian store and load macros ** * + * ** are obvious targets for optimization. ** */ + +#ifdef __GNUC__ +# if defined(__has_attribute) && defined(__STDC_VERSION__) +# if __has_attribute(always_inline) && __STDC_VERSION__ >= 199901 +# define CURL_FORCEINLINE CURL_INLINE __attribute__((always_inline)) +# endif +# endif +#endif + +#if !defined(CURL_FORCEINLINE) && \ + defined(_MSC_VER) && !defined(__GNUC__) && !defined(__clang__) +# define CURL_FORCEINLINE __forceinline +#endif + +#if !defined(CURL_FORCEINLINE) + /* Assume that 'CURL_INLINE' keyword works or the + * macro was already defined correctly. */ +# define CURL_FORCEINLINE CURL_INLINE +#endif + +/* Bits manipulation macros and functions. + Can be moved to other headers to reuse. */ + +#define CURL_GET_64BIT_BE(ptr) \ + ( ((curl_uint64_t)(((const unsigned char*)(ptr))[0]) << 56) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[1]) << 48) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[2]) << 40) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[3]) << 32) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[4]) << 24) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[5]) << 16) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[6]) << 8) | \ + (curl_uint64_t)(((const unsigned char*)(ptr))[7]) ) + +#define CURL_PUT_64BIT_BE(ptr,val) do { \ + ((unsigned char*)(ptr))[7]=(unsigned char)((curl_uint64_t)(val)); \ + ((unsigned char*)(ptr))[6]=(unsigned char)(((curl_uint64_t)(val)) >> 8); \ + ((unsigned char*)(ptr))[5]=(unsigned char)(((curl_uint64_t)(val)) >> 16); \ + ((unsigned char*)(ptr))[4]=(unsigned char)(((curl_uint64_t)(val)) >> 24); \ + ((unsigned char*)(ptr))[3]=(unsigned char)(((curl_uint64_t)(val)) >> 32); \ + ((unsigned char*)(ptr))[2]=(unsigned char)(((curl_uint64_t)(val)) >> 40); \ + ((unsigned char*)(ptr))[1]=(unsigned char)(((curl_uint64_t)(val)) >> 48); \ + ((unsigned char*)(ptr))[0]=(unsigned char)(((curl_uint64_t)(val)) >> 56); \ + } while(0) + +/* Defined as a function. The macro version may duplicate the binary code + * size as each argument is used twice, so if any calculation is used + * as an argument, the calculation could be done twice. */ +static CURL_FORCEINLINE curl_uint64_t +Curl_rotr64(curl_uint64_t value, unsigned int bits) +{ + bits %= 64; + if(0 == bits) + return value; + /* Defined in a form which modern compiler could optimize. */ + return (value >> bits) | (value << (64 - bits)); +} + +/* SHA-512/256 specific data */ + +/** + * Number of bits in a single SHA-512/256 word. + */ +#define SHA512_256_WORD_SIZE_BITS 64 + +/** + * Number of bytes in a single SHA-512/256 word. + */ +#define SHA512_256_BYTES_IN_WORD (SHA512_256_WORD_SIZE_BITS / 8) + +/** + * Hash is kept internally as 8 64-bit words. + * This is the intermediate hash size, used during computing the final digest. + */ +#define SHA512_256_HASH_SIZE_WORDS 8 + +/** + * Size of the SHA-512/256 resulting digest in words. + * This is the final digest size, not intermediate hash. + */ +#define SHA512_256_DIGEST_SIZE_WORDS (SHA512_256_HASH_SIZE_WORDS / 2) + +/** + * Size of the SHA-512/256 resulting digest in bytes + * This is the final digest size, not intermediate hash. + */ +#define CURL_SHA512_256_DIGEST_SIZE \ + (SHA512_256_DIGEST_SIZE_WORDS * SHA512_256_BYTES_IN_WORD) + +/** + * Size of the SHA-512/256 single processing block in bits. + */ +#define SHA512_256_BLOCK_SIZE_BITS 1024 + +/** + * Size of the SHA-512/256 single processing block in bytes. + */ +#define CURL_SHA512_256_BLOCK_SIZE (SHA512_256_BLOCK_SIZE_BITS / 8) + +/** + * Size of the SHA-512/256 single processing block in words. + */ +#define SHA512_256_BLOCK_SIZE_WORDS \ + (SHA512_256_BLOCK_SIZE_BITS / SHA512_256_WORD_SIZE_BITS) + +/** + * SHA-512/256 calculation context + */ +struct Curl_sha512_256ctx +{ + /** + * Intermediate hash value. The variable is properly aligned. Smart + * compilers may automatically use fast load/store instruction for big + * endian data on little endian machine. + */ + curl_uint64_t H[SHA512_256_HASH_SIZE_WORDS]; + /** + * SHA-512/256 input data buffer. The buffer is properly aligned. Smart + * compilers may automatically use fast load/store instruction for big + * endian data on little endian machine. + */ + curl_uint64_t buffer[SHA512_256_BLOCK_SIZE_WORDS]; + /** + * The number of bytes, lower part + */ + curl_uint64_t count; + /** + * The number of bits, high part. Unlike lower part, this counts the number + * of bits, not bytes. + */ + curl_uint64_t count_bits_hi; +}; + +/** + * Context type used for SHA-512/256 calculations + */ +typedef struct Curl_sha512_256ctx Curl_sha512_256_ctx; + + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_init(void *context) +{ + struct Curl_sha512_256ctx *const ctx = (struct Curl_sha512_256ctx *)context; + + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(CURL_SHA512_256_DIGEST_LENGTH == CURL_SHA512_256_DIGEST_SIZE); + + DEBUGASSERT(sizeof(curl_uint64_t) == 8); + + /* Initial hash values, see FIPS PUB 180-4 section 5.3.6.2 */ + /* Values generated by "IV Generation Function" as described in + * section 5.3.6 */ + ctx->H[0] = CURL_UINT64_C(0x22312194FC2BF72C); + ctx->H[1] = CURL_UINT64_C(0x9F555FA3C84C64C2); + ctx->H[2] = CURL_UINT64_C(0x2393B86B6F53B151); + ctx->H[3] = CURL_UINT64_C(0x963877195940EABD); + ctx->H[4] = CURL_UINT64_C(0x96283EE2A88EFFE3); + ctx->H[5] = CURL_UINT64_C(0xBE5E1E2553863992); + ctx->H[6] = CURL_UINT64_C(0x2B0199FC2C85B8AA); + ctx->H[7] = CURL_UINT64_C(0x0EB72DDC81C52CA2); + + /* Initialise number of bytes and high part of number of bits. */ + ctx->count = CURL_UINT64_C(0); + ctx->count_bits_hi = CURL_UINT64_C(0); + + return CURLE_OK; +} + + +/** + * Base of the SHA-512/256 transformation. + * Gets a full 128 bytes block of data and updates hash values; + * @param H hash values + * @param data the data buffer with #CURL_SHA512_256_BLOCK_SIZE bytes block + */ +static void +Curl_sha512_256_transform(curl_uint64_t H[SHA512_256_HASH_SIZE_WORDS], + const void *data) +{ + /* Working variables, + see FIPS PUB 180-4 section 6.7, 6.4. */ + curl_uint64_t a = H[0]; + curl_uint64_t b = H[1]; + curl_uint64_t c = H[2]; + curl_uint64_t d = H[3]; + curl_uint64_t e = H[4]; + curl_uint64_t f = H[5]; + curl_uint64_t g = H[6]; + curl_uint64_t h = H[7]; + + /* Data buffer, used as a cyclic buffer. + See FIPS PUB 180-4 section 5.2.2, 6.7, 6.4. */ + curl_uint64_t W[16]; + + /* 'Ch' and 'Maj' macro functions are defined with widely-used optimization. + See FIPS PUB 180-4 formulae 4.8, 4.9. */ +#define Sha512_Ch(x,y,z) ( (z) ^ ((x) & ((y) ^ (z))) ) +#define Sha512_Maj(x,y,z) ( ((x) & (y)) ^ ((z) & ((x) ^ (y))) ) + + /* Four 'Sigma' macro functions. + See FIPS PUB 180-4 formulae 4.10, 4.11, 4.12, 4.13. */ +#define SIG0(x) \ + ( Curl_rotr64((x), 28) ^ Curl_rotr64((x), 34) ^ Curl_rotr64((x), 39) ) +#define SIG1(x) \ + ( Curl_rotr64((x), 14) ^ Curl_rotr64((x), 18) ^ Curl_rotr64((x), 41) ) +#define sig0(x) \ + ( Curl_rotr64((x), 1) ^ Curl_rotr64((x), 8) ^ ((x) >> 7) ) +#define sig1(x) \ + ( Curl_rotr64((x), 19) ^ Curl_rotr64((x), 61) ^ ((x) >> 6) ) + + if(1) { + unsigned int t; + /* K constants array. + See FIPS PUB 180-4 section 4.2.3 for K values. */ + static const curl_uint64_t K[80] = { + CURL_UINT64_C(0x428a2f98d728ae22), CURL_UINT64_C(0x7137449123ef65cd), + CURL_UINT64_C(0xb5c0fbcfec4d3b2f), CURL_UINT64_C(0xe9b5dba58189dbbc), + CURL_UINT64_C(0x3956c25bf348b538), CURL_UINT64_C(0x59f111f1b605d019), + CURL_UINT64_C(0x923f82a4af194f9b), CURL_UINT64_C(0xab1c5ed5da6d8118), + CURL_UINT64_C(0xd807aa98a3030242), CURL_UINT64_C(0x12835b0145706fbe), + CURL_UINT64_C(0x243185be4ee4b28c), CURL_UINT64_C(0x550c7dc3d5ffb4e2), + CURL_UINT64_C(0x72be5d74f27b896f), CURL_UINT64_C(0x80deb1fe3b1696b1), + CURL_UINT64_C(0x9bdc06a725c71235), CURL_UINT64_C(0xc19bf174cf692694), + CURL_UINT64_C(0xe49b69c19ef14ad2), CURL_UINT64_C(0xefbe4786384f25e3), + CURL_UINT64_C(0x0fc19dc68b8cd5b5), CURL_UINT64_C(0x240ca1cc77ac9c65), + CURL_UINT64_C(0x2de92c6f592b0275), CURL_UINT64_C(0x4a7484aa6ea6e483), + CURL_UINT64_C(0x5cb0a9dcbd41fbd4), CURL_UINT64_C(0x76f988da831153b5), + CURL_UINT64_C(0x983e5152ee66dfab), CURL_UINT64_C(0xa831c66d2db43210), + CURL_UINT64_C(0xb00327c898fb213f), CURL_UINT64_C(0xbf597fc7beef0ee4), + CURL_UINT64_C(0xc6e00bf33da88fc2), CURL_UINT64_C(0xd5a79147930aa725), + CURL_UINT64_C(0x06ca6351e003826f), CURL_UINT64_C(0x142929670a0e6e70), + CURL_UINT64_C(0x27b70a8546d22ffc), CURL_UINT64_C(0x2e1b21385c26c926), + CURL_UINT64_C(0x4d2c6dfc5ac42aed), CURL_UINT64_C(0x53380d139d95b3df), + CURL_UINT64_C(0x650a73548baf63de), CURL_UINT64_C(0x766a0abb3c77b2a8), + CURL_UINT64_C(0x81c2c92e47edaee6), CURL_UINT64_C(0x92722c851482353b), + CURL_UINT64_C(0xa2bfe8a14cf10364), CURL_UINT64_C(0xa81a664bbc423001), + CURL_UINT64_C(0xc24b8b70d0f89791), CURL_UINT64_C(0xc76c51a30654be30), + CURL_UINT64_C(0xd192e819d6ef5218), CURL_UINT64_C(0xd69906245565a910), + CURL_UINT64_C(0xf40e35855771202a), CURL_UINT64_C(0x106aa07032bbd1b8), + CURL_UINT64_C(0x19a4c116b8d2d0c8), CURL_UINT64_C(0x1e376c085141ab53), + CURL_UINT64_C(0x2748774cdf8eeb99), CURL_UINT64_C(0x34b0bcb5e19b48a8), + CURL_UINT64_C(0x391c0cb3c5c95a63), CURL_UINT64_C(0x4ed8aa4ae3418acb), + CURL_UINT64_C(0x5b9cca4f7763e373), CURL_UINT64_C(0x682e6ff3d6b2b8a3), + CURL_UINT64_C(0x748f82ee5defb2fc), CURL_UINT64_C(0x78a5636f43172f60), + CURL_UINT64_C(0x84c87814a1f0ab72), CURL_UINT64_C(0x8cc702081a6439ec), + CURL_UINT64_C(0x90befffa23631e28), CURL_UINT64_C(0xa4506cebde82bde9), + CURL_UINT64_C(0xbef9a3f7b2c67915), CURL_UINT64_C(0xc67178f2e372532b), + CURL_UINT64_C(0xca273eceea26619c), CURL_UINT64_C(0xd186b8c721c0c207), + CURL_UINT64_C(0xeada7dd6cde0eb1e), CURL_UINT64_C(0xf57d4f7fee6ed178), + CURL_UINT64_C(0x06f067aa72176fba), CURL_UINT64_C(0x0a637dc5a2c898a6), + CURL_UINT64_C(0x113f9804bef90dae), CURL_UINT64_C(0x1b710b35131c471b), + CURL_UINT64_C(0x28db77f523047d84), CURL_UINT64_C(0x32caab7b40c72493), + CURL_UINT64_C(0x3c9ebe0a15c9bebc), CURL_UINT64_C(0x431d67c49c100d4c), + CURL_UINT64_C(0x4cc5d4becb3e42b6), CURL_UINT64_C(0x597f299cfc657e2a), + CURL_UINT64_C(0x5fcb6fab3ad6faec), CURL_UINT64_C(0x6c44198c4a475817) + }; + + /* One step of SHA-512/256 computation, + see FIPS PUB 180-4 section 6.4.2 step 3. + * Note: this macro updates working variables in-place, without rotation. + * Note: the first (vH += SIG1(vE) + Ch(vE,vF,vG) + kt + wt) equals T1 in + FIPS PUB 180-4 section 6.4.2 step 3. + the second (vH += SIG0(vA) + Maj(vE,vF,vC) equals T1 + T2 in + FIPS PUB 180-4 section 6.4.2 step 3. + * Note: 'wt' must be used exactly one time in this macro as macro for + 'wt' calculation may change other data as well every time when + used. */ +#define SHA2STEP64(vA,vB,vC,vD,vE,vF,vG,vH,kt,wt) do { \ + (vD) += ((vH) += SIG1((vE)) + Sha512_Ch((vE),(vF),(vG)) + (kt) + (wt)); \ + (vH) += SIG0((vA)) + Sha512_Maj((vA),(vB),(vC)); } while (0) + + /* One step of SHA-512/256 computation with working variables rotation, + see FIPS PUB 180-4 section 6.4.2 step 3. This macro version reassigns + all working variables on each step. */ +#define SHA2STEP64RV(vA,vB,vC,vD,vE,vF,vG,vH,kt,wt) do { \ + curl_uint64_t tmp_h_ = (vH); \ + SHA2STEP64((vA),(vB),(vC),(vD),(vE),(vF),(vG),tmp_h_,(kt),(wt)); \ + (vH) = (vG); \ + (vG) = (vF); \ + (vF) = (vE); \ + (vE) = (vD); \ + (vD) = (vC); \ + (vC) = (vB); \ + (vB) = (vA); \ + (vA) = tmp_h_; } while(0) + + /* Get value of W(t) from input data buffer for 0 <= t <= 15, + See FIPS PUB 180-4 section 6.2. + Input data must be read in big-endian bytes order, + see FIPS PUB 180-4 section 3.1.2. */ +#define SHA512_GET_W_FROM_DATA(buf,t) \ + CURL_GET_64BIT_BE( \ + ((const unsigned char*) (buf)) + (t) * SHA512_256_BYTES_IN_WORD) + + /* During first 16 steps, before making any calculation on each step, the + W element is read from the input data buffer as a big-endian value and + stored in the array of W elements. */ + for(t = 0; t < 16; ++t) { + SHA2STEP64RV(a, b, c, d, e, f, g, h, K[t], \ + W[t] = SHA512_GET_W_FROM_DATA(data, t)); + } + + /* 'W' generation and assignment for 16 <= t <= 79. + See FIPS PUB 180-4 section 6.4.2. + As only the last 16 'W' are used in calculations, it is possible to + use 16 elements array of W as a cyclic buffer. + Note: ((t-16) & 15) have same value as (t & 15) */ +#define Wgen(w,t) \ + (curl_uint64_t)( (w)[(t - 16) & 15] + sig1((w)[((t) - 2) & 15]) \ + + (w)[((t) - 7) & 15] + sig0((w)[((t) - 15) & 15]) ) + + /* During the last 64 steps, before making any calculation on each step, + current W element is generated from other W elements of the cyclic + buffer and the generated value is stored back in the cyclic buffer. */ + for(t = 16; t < 80; ++t) { + SHA2STEP64RV(a, b, c, d, e, f, g, h, K[t], \ + W[t & 15] = Wgen(W, t)); + } + } + + /* Compute and store the intermediate hash. + See FIPS PUB 180-4 section 6.4.2 step 4. */ + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @param length number of bytes in @a data + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + unsigned int bytes_have; /**< Number of bytes in the context buffer */ + struct Curl_sha512_256ctx *const ctx = (struct Curl_sha512_256ctx *)context; + /* the void pointer here is required to mute Intel compiler warning */ + void *const ctx_buf = ctx->buffer; + + DEBUGASSERT((data != NULL) || (length == 0)); + + if(0 == length) + return CURLE_OK; /* Shortcut, do nothing */ + + /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + equals (count % CURL_SHA512_256_BLOCK_SIZE) for this block size. */ + bytes_have = (unsigned int) (ctx->count & (CURL_SHA512_256_BLOCK_SIZE - 1)); + ctx->count += length; + if(length > ctx->count) + ctx->count_bits_hi += 1U << 3; /* Value wrap */ + ctx->count_bits_hi += ctx->count >> 61; + ctx->count &= CURL_UINT64_C(0x1FFFFFFFFFFFFFFF); + + if(0 != bytes_have) { + unsigned int bytes_left = CURL_SHA512_256_BLOCK_SIZE - bytes_have; + if(length >= bytes_left) { + /* Combine new data with data in the buffer and process the full + block. */ + memcpy(((unsigned char *) ctx_buf) + bytes_have, + data, + bytes_left); + data += bytes_left; + length -= bytes_left; + Curl_sha512_256_transform(ctx->H, ctx->buffer); + bytes_have = 0; + } + } + + while(CURL_SHA512_256_BLOCK_SIZE <= length) { + /* Process any full blocks of new data directly, + without copying to the buffer. */ + Curl_sha512_256_transform(ctx->H, data); + data += CURL_SHA512_256_BLOCK_SIZE; + length -= CURL_SHA512_256_BLOCK_SIZE; + } + + if(0 != length) { + /* Copy incomplete block of new data (if any) + to the buffer. */ + memcpy(((unsigned char *) ctx_buf) + bytes_have, data, length); + } + + return CURLE_OK; +} + + + +/** + * Size of "length" insertion in bits. + * See FIPS PUB 180-4 section 5.1.2. + */ +#define SHA512_256_SIZE_OF_LEN_ADD_BITS 128 + +/** + * Size of "length" insertion in bytes. + */ +#define SHA512_256_SIZE_OF_LEN_ADD (SHA512_256_SIZE_OF_LEN_ADD_BITS / 8) + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_finish(unsigned char *digest, + void *context) +{ + struct Curl_sha512_256ctx *const ctx = (struct Curl_sha512_256ctx *)context; + curl_uint64_t num_bits; /**< Number of processed bits */ + unsigned int bytes_have; /**< Number of bytes in the context buffer */ + /* the void pointer here is required to mute Intel compiler warning */ + void *const ctx_buf = ctx->buffer; + + /* Memorise the number of processed bits. + The padding and other data added here during the postprocessing must + not change the amount of hashed data. */ + num_bits = ctx->count << 3; + + /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + equals (count % CURL_SHA512_256_BLOCK_SIZE) for this block size. */ + bytes_have = (unsigned int) (ctx->count & (CURL_SHA512_256_BLOCK_SIZE - 1)); + + /* Input data must be padded with a single bit "1", then with zeros and + the finally the length of data in bits must be added as the final bytes + of the last block. + See FIPS PUB 180-4 section 5.1.2. */ + + /* Data is always processed in form of bytes (not by individual bits), + therefore position of the first padding bit in byte is always + predefined (0x80). */ + /* Buffer always have space at least for one byte (as full buffers are + processed when formed). */ + ((unsigned char *) ctx_buf)[bytes_have++] = 0x80U; + + if(CURL_SHA512_256_BLOCK_SIZE - bytes_have < SHA512_256_SIZE_OF_LEN_ADD) { + /* No space in the current block to put the total length of message. + Pad the current block with zeros and process it. */ + if(bytes_have < CURL_SHA512_256_BLOCK_SIZE) + memset(((unsigned char *) ctx_buf) + bytes_have, 0, + CURL_SHA512_256_BLOCK_SIZE - bytes_have); + /* Process the full block. */ + Curl_sha512_256_transform(ctx->H, ctx->buffer); + /* Start the new block. */ + bytes_have = 0; + } + + /* Pad the rest of the buffer with zeros. */ + memset(((unsigned char *) ctx_buf) + bytes_have, 0, + CURL_SHA512_256_BLOCK_SIZE - SHA512_256_SIZE_OF_LEN_ADD - bytes_have); + /* Put high part of number of bits in processed message and then lower + part of number of bits as big-endian values. + See FIPS PUB 180-4 section 5.1.2. */ + /* Note: the target location is predefined and buffer is always aligned */ + CURL_PUT_64BIT_BE(((unsigned char *) ctx_buf) \ + + CURL_SHA512_256_BLOCK_SIZE \ + - SHA512_256_SIZE_OF_LEN_ADD, \ + ctx->count_bits_hi); + CURL_PUT_64BIT_BE(((unsigned char *) ctx_buf) \ + + CURL_SHA512_256_BLOCK_SIZE \ + - SHA512_256_SIZE_OF_LEN_ADD \ + + SHA512_256_BYTES_IN_WORD, \ + num_bits); + /* Process the full final block. */ + Curl_sha512_256_transform(ctx->H, ctx->buffer); + + /* Put in BE mode the leftmost part of the hash as the final digest. + See FIPS PUB 180-4 section 6.7. */ + + CURL_PUT_64BIT_BE((digest + 0 * SHA512_256_BYTES_IN_WORD), ctx->H[0]); + CURL_PUT_64BIT_BE((digest + 1 * SHA512_256_BYTES_IN_WORD), ctx->H[1]); + CURL_PUT_64BIT_BE((digest + 2 * SHA512_256_BYTES_IN_WORD), ctx->H[2]); + CURL_PUT_64BIT_BE((digest + 3 * SHA512_256_BYTES_IN_WORD), ctx->H[3]); + + /* Erase potentially sensitive data. */ + memset(ctx, 0, sizeof(struct Curl_sha512_256ctx)); + + return CURLE_OK; +} + +#endif /* Local SHA-512/256 code */ + + +/** + * Compute SHA-512/256 hash for the given data in one function call + * @param[out] output the pointer to put the hash + * @param[in] input the pointer to the data to process + * @param input_size the size of the data pointed by @a input + * @return always #CURLE_OK + */ +CURLcode +Curl_sha512_256it(unsigned char *output, const unsigned char *input, + size_t input_size) +{ + Curl_sha512_256_ctx ctx; + CURLcode res; + + res = Curl_sha512_256_init(&ctx); + if(res != CURLE_OK) + return res; + + res = Curl_sha512_256_update(&ctx, (const void *) input, input_size); + + if(res != CURLE_OK) { + (void) Curl_sha512_256_finish(output, &ctx); + return res; + } + + return Curl_sha512_256_finish(output, &ctx); +} + +/* Wrapper function, takes 'unsigned int' as length type, returns void */ +static void +Curl_sha512_256_update_i(void *context, + const unsigned char *data, + unsigned int length) +{ + /* Hypothetically the function may fail, but assume it does not */ + (void) Curl_sha512_256_update(context, data, length); +} + +/* Wrapper function, returns void */ +static void +Curl_sha512_256_finish_v(unsigned char *result, + void *context) +{ + /* Hypothetically the function may fail, but assume it does not */ + (void) Curl_sha512_256_finish(result, context); +} + +/* Wrapper function, takes 'unsigned int' as length type, returns void */ + +const struct HMAC_params Curl_HMAC_SHA512_256[] = { + { + /* Initialize context procedure. */ + Curl_sha512_256_init, + /* Update context with data. */ + Curl_sha512_256_update_i, + /* Get final result procedure. */ + Curl_sha512_256_finish_v, + /* Context structure size. */ + sizeof(Curl_sha512_256_ctx), + /* Maximum key length (bytes). */ + CURL_SHA512_256_BLOCK_SIZE, + /* Result length (bytes). */ + CURL_SHA512_256_DIGEST_SIZE + } +}; + +#endif /* !CURL_DISABLE_DIGEST_AUTH && !CURL_DISABLE_SHA512_256 */ diff --git a/Utilities/cmcurl/lib/curl_sha512_256.h b/Utilities/cmcurl/lib/curl_sha512_256.h new file mode 100644 index 00000000000..a84e77bc303 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_sha512_256.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_SHA512_256_H +#define HEADER_CURL_SHA512_256_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Evgeny Grin (Karlson2k), . + * + * 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 !defined(CURL_DISABLE_DIGEST_AUTH) && !defined(CURL_DISABLE_SHA512_256) + +#include +#include "curl_hmac.h" + +#define CURL_HAVE_SHA512_256 + +extern const struct HMAC_params Curl_HMAC_SHA512_256[1]; + +#define CURL_SHA512_256_DIGEST_LENGTH 32 + +CURLcode +Curl_sha512_256it(unsigned char *output, const unsigned char *input, + size_t input_size); + +#endif /* !CURL_DISABLE_DIGEST_AUTH && !CURL_DISABLE_SHA512_256 */ + +#endif /* HEADER_CURL_SHA256_H */ diff --git a/Utilities/cmcurl/lib/curl_sspi.c b/Utilities/cmcurl/lib/curl_sspi.c index eb21e7e2b09..cd577e8a5e2 100644 --- a/Utilities/cmcurl/lib/curl_sspi.c +++ b/Utilities/cmcurl/lib/curl_sspi.c @@ -28,10 +28,10 @@ #include #include "curl_sspi.h" -#include "curl_multibyte.h" +#include "curlx/multibyte.h" #include "system_win32.h" -#include "version_win32.h" -#include "warnless.h" +#include "curlx/version_win32.h" +#include "curlx/warnless.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -42,7 +42,7 @@ typedef PSecurityFunctionTable (APIENTRY *INITSECURITYINTERFACE_FN)(VOID); /* See definition of SECURITY_ENTRYPOINT in sspi.h */ #ifdef UNICODE -# ifdef _WIN32_WCE +# ifdef UNDER_CE # define SECURITYENTRYPOINT L"InitSecurityInterfaceW" # else # define SECURITYENTRYPOINT "InitSecurityInterfaceW" @@ -52,10 +52,10 @@ typedef PSecurityFunctionTable (APIENTRY *INITSECURITYINTERFACE_FN)(VOID); #endif /* Handle of security.dll or secur32.dll, depending on Windows version */ -HMODULE s_hSecDll = NULL; +HMODULE Curl_hSecDll = NULL; /* Pointer to SSPI dispatch table */ -PSecurityFunctionTable s_pSecFn = NULL; +PSecurityFunctionTable Curl_pSecFn = NULL; /* * Curl_sspi_global_init() @@ -79,29 +79,29 @@ CURLcode Curl_sspi_global_init(void) INITSECURITYINTERFACE_FN pInitSecurityInterface; /* If security interface is not yet initialized try to do this */ - if(!s_hSecDll) { + if(!Curl_hSecDll) { /* Security Service Provider Interface (SSPI) functions are located in * security.dll on WinNT 4.0 and in secur32.dll on Win9x. Win2K and XP * have both these DLLs (security.dll forwards calls to secur32.dll) */ /* Load SSPI dll into the address space of the calling process */ if(curlx_verify_windows_version(4, 0, 0, PLATFORM_WINNT, VERSION_EQUAL)) - s_hSecDll = Curl_load_library(TEXT("security.dll")); + Curl_hSecDll = Curl_load_library(TEXT("security.dll")); else - s_hSecDll = Curl_load_library(TEXT("secur32.dll")); - if(!s_hSecDll) + Curl_hSecDll = Curl_load_library(TEXT("secur32.dll")); + if(!Curl_hSecDll) return CURLE_FAILED_INIT; /* Get address of the InitSecurityInterfaceA function from the SSPI dll */ pInitSecurityInterface = CURLX_FUNCTION_CAST(INITSECURITYINTERFACE_FN, - (GetProcAddress(s_hSecDll, SECURITYENTRYPOINT))); + (GetProcAddress(Curl_hSecDll, SECURITYENTRYPOINT))); if(!pInitSecurityInterface) return CURLE_FAILED_INIT; /* Get pointer to Security Service Provider Interface dispatch table */ - s_pSecFn = pInitSecurityInterface(); - if(!s_pSecFn) + Curl_pSecFn = pInitSecurityInterface(); + if(!Curl_pSecFn) return CURLE_FAILED_INIT; } @@ -119,22 +119,22 @@ CURLcode Curl_sspi_global_init(void) */ void Curl_sspi_global_cleanup(void) { - if(s_hSecDll) { - FreeLibrary(s_hSecDll); - s_hSecDll = NULL; - s_pSecFn = NULL; + if(Curl_hSecDll) { + FreeLibrary(Curl_hSecDll); + Curl_hSecDll = NULL; + Curl_pSecFn = NULL; } } /* * Curl_create_sspi_identity() * - * This is used to populate a SSPI identity structure based on the supplied + * This is used to populate an SSPI identity structure based on the supplied * username and password. * * Parameters: * - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * identity [in/out] - The identity structure. * @@ -154,7 +154,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, /* Initialize the identity */ memset(identity, 0, sizeof(*identity)); - useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *)userp); + useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar(userp); if(!useranddomain.tchar_ptr) return CURLE_OUT_OF_MEMORY; @@ -198,7 +198,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, curlx_unicodefree(useranddomain.tchar_ptr); /* Setup the identity's password and length */ - passwd.tchar_ptr = curlx_convert_UTF8_to_tchar((char *)passwdp); + passwd.tchar_ptr = curlx_convert_UTF8_to_tchar(passwdp); if(!passwd.tchar_ptr) return CURLE_OUT_OF_MEMORY; dup_passwd.tchar_ptr = _tcsdup(passwd.tchar_ptr); @@ -221,7 +221,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, /* * Curl_sspi_free_identity() * - * This is used to free the contents of a SSPI identifier structure. + * This is used to free the contents of an SSPI identifier structure. * * Parameters: * diff --git a/Utilities/cmcurl/lib/curl_sspi.h b/Utilities/cmcurl/lib/curl_sspi.h index 9816d59c842..8fdf8ef2496 100644 --- a/Utilities/cmcurl/lib/curl_sspi.h +++ b/Utilities/cmcurl/lib/curl_sspi.h @@ -45,7 +45,7 @@ CURLcode Curl_sspi_global_init(void); void Curl_sspi_global_cleanup(void); -/* This is used to populate the domain in a SSPI identity structure */ +/* This is used to populate the domain in an SSPI identity structure */ CURLcode Curl_override_sspi_http_realm(const char *chlg, SEC_WINNT_AUTH_IDENTITY *identity); @@ -57,8 +57,8 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity); /* Forward-declaration of global variables defined in curl_sspi.c */ -extern HMODULE s_hSecDll; -extern PSecurityFunctionTable s_pSecFn; +extern HMODULE Curl_hSecDll; +extern PSecurityFunctionTable Curl_pSecFn; /* Provide some definitions missing in old headers */ #define SP_NAME_DIGEST "WDigest" @@ -70,227 +70,225 @@ extern PSecurityFunctionTable s_pSecFn; #define ISC_REQ_USE_HTTP_STYLE 0x01000000 #endif +#ifdef __MINGW32CE__ #ifndef ISC_RET_REPLAY_DETECT #define ISC_RET_REPLAY_DETECT 0x00000004 #endif - #ifndef ISC_RET_SEQUENCE_DETECT #define ISC_RET_SEQUENCE_DETECT 0x00000008 #endif - #ifndef ISC_RET_CONFIDENTIALITY #define ISC_RET_CONFIDENTIALITY 0x00000010 #endif - #ifndef ISC_RET_ALLOCATED_MEMORY #define ISC_RET_ALLOCATED_MEMORY 0x00000100 #endif - #ifndef ISC_RET_STREAM #define ISC_RET_STREAM 0x00008000 #endif #ifndef SEC_E_INSUFFICIENT_MEMORY -# define SEC_E_INSUFFICIENT_MEMORY ((HRESULT)0x80090300L) +#define SEC_E_INSUFFICIENT_MEMORY ((HRESULT)0x80090300L) #endif #ifndef SEC_E_INVALID_HANDLE -# define SEC_E_INVALID_HANDLE ((HRESULT)0x80090301L) +#define SEC_E_INVALID_HANDLE ((HRESULT)0x80090301L) #endif #ifndef SEC_E_UNSUPPORTED_FUNCTION -# define SEC_E_UNSUPPORTED_FUNCTION ((HRESULT)0x80090302L) +#define SEC_E_UNSUPPORTED_FUNCTION ((HRESULT)0x80090302L) #endif #ifndef SEC_E_TARGET_UNKNOWN -# define SEC_E_TARGET_UNKNOWN ((HRESULT)0x80090303L) +#define SEC_E_TARGET_UNKNOWN ((HRESULT)0x80090303L) #endif #ifndef SEC_E_INTERNAL_ERROR -# define SEC_E_INTERNAL_ERROR ((HRESULT)0x80090304L) +#define SEC_E_INTERNAL_ERROR ((HRESULT)0x80090304L) #endif #ifndef SEC_E_SECPKG_NOT_FOUND -# define SEC_E_SECPKG_NOT_FOUND ((HRESULT)0x80090305L) +#define SEC_E_SECPKG_NOT_FOUND ((HRESULT)0x80090305L) #endif #ifndef SEC_E_NOT_OWNER -# define SEC_E_NOT_OWNER ((HRESULT)0x80090306L) +#define SEC_E_NOT_OWNER ((HRESULT)0x80090306L) #endif #ifndef SEC_E_CANNOT_INSTALL -# define SEC_E_CANNOT_INSTALL ((HRESULT)0x80090307L) +#define SEC_E_CANNOT_INSTALL ((HRESULT)0x80090307L) #endif #ifndef SEC_E_INVALID_TOKEN -# define SEC_E_INVALID_TOKEN ((HRESULT)0x80090308L) +#define SEC_E_INVALID_TOKEN ((HRESULT)0x80090308L) #endif #ifndef SEC_E_CANNOT_PACK -# define SEC_E_CANNOT_PACK ((HRESULT)0x80090309L) +#define SEC_E_CANNOT_PACK ((HRESULT)0x80090309L) #endif #ifndef SEC_E_QOP_NOT_SUPPORTED -# define SEC_E_QOP_NOT_SUPPORTED ((HRESULT)0x8009030AL) +#define SEC_E_QOP_NOT_SUPPORTED ((HRESULT)0x8009030AL) #endif #ifndef SEC_E_NO_IMPERSONATION -# define SEC_E_NO_IMPERSONATION ((HRESULT)0x8009030BL) +#define SEC_E_NO_IMPERSONATION ((HRESULT)0x8009030BL) #endif #ifndef SEC_E_LOGON_DENIED -# define SEC_E_LOGON_DENIED ((HRESULT)0x8009030CL) +#define SEC_E_LOGON_DENIED ((HRESULT)0x8009030CL) #endif #ifndef SEC_E_UNKNOWN_CREDENTIALS -# define SEC_E_UNKNOWN_CREDENTIALS ((HRESULT)0x8009030DL) +#define SEC_E_UNKNOWN_CREDENTIALS ((HRESULT)0x8009030DL) #endif #ifndef SEC_E_NO_CREDENTIALS -# define SEC_E_NO_CREDENTIALS ((HRESULT)0x8009030EL) +#define SEC_E_NO_CREDENTIALS ((HRESULT)0x8009030EL) #endif #ifndef SEC_E_MESSAGE_ALTERED -# define SEC_E_MESSAGE_ALTERED ((HRESULT)0x8009030FL) +#define SEC_E_MESSAGE_ALTERED ((HRESULT)0x8009030FL) #endif #ifndef SEC_E_OUT_OF_SEQUENCE -# define SEC_E_OUT_OF_SEQUENCE ((HRESULT)0x80090310L) +#define SEC_E_OUT_OF_SEQUENCE ((HRESULT)0x80090310L) #endif #ifndef SEC_E_NO_AUTHENTICATING_AUTHORITY -# define SEC_E_NO_AUTHENTICATING_AUTHORITY ((HRESULT)0x80090311L) +#define SEC_E_NO_AUTHENTICATING_AUTHORITY ((HRESULT)0x80090311L) #endif #ifndef SEC_E_BAD_PKGID -# define SEC_E_BAD_PKGID ((HRESULT)0x80090316L) +#define SEC_E_BAD_PKGID ((HRESULT)0x80090316L) #endif #ifndef SEC_E_CONTEXT_EXPIRED -# define SEC_E_CONTEXT_EXPIRED ((HRESULT)0x80090317L) +#define SEC_E_CONTEXT_EXPIRED ((HRESULT)0x80090317L) #endif #ifndef SEC_E_INCOMPLETE_MESSAGE -# define SEC_E_INCOMPLETE_MESSAGE ((HRESULT)0x80090318L) +#define SEC_E_INCOMPLETE_MESSAGE ((HRESULT)0x80090318L) #endif #ifndef SEC_E_INCOMPLETE_CREDENTIALS -# define SEC_E_INCOMPLETE_CREDENTIALS ((HRESULT)0x80090320L) +#define SEC_E_INCOMPLETE_CREDENTIALS ((HRESULT)0x80090320L) #endif #ifndef SEC_E_BUFFER_TOO_SMALL -# define SEC_E_BUFFER_TOO_SMALL ((HRESULT)0x80090321L) +#define SEC_E_BUFFER_TOO_SMALL ((HRESULT)0x80090321L) #endif #ifndef SEC_E_WRONG_PRINCIPAL -# define SEC_E_WRONG_PRINCIPAL ((HRESULT)0x80090322L) +#define SEC_E_WRONG_PRINCIPAL ((HRESULT)0x80090322L) #endif #ifndef SEC_E_TIME_SKEW -# define SEC_E_TIME_SKEW ((HRESULT)0x80090324L) +#define SEC_E_TIME_SKEW ((HRESULT)0x80090324L) #endif #ifndef SEC_E_UNTRUSTED_ROOT -# define SEC_E_UNTRUSTED_ROOT ((HRESULT)0x80090325L) +#define SEC_E_UNTRUSTED_ROOT ((HRESULT)0x80090325L) #endif #ifndef SEC_E_ILLEGAL_MESSAGE -# define SEC_E_ILLEGAL_MESSAGE ((HRESULT)0x80090326L) +#define SEC_E_ILLEGAL_MESSAGE ((HRESULT)0x80090326L) #endif #ifndef SEC_E_CERT_UNKNOWN -# define SEC_E_CERT_UNKNOWN ((HRESULT)0x80090327L) +#define SEC_E_CERT_UNKNOWN ((HRESULT)0x80090327L) #endif #ifndef SEC_E_CERT_EXPIRED -# define SEC_E_CERT_EXPIRED ((HRESULT)0x80090328L) +#define SEC_E_CERT_EXPIRED ((HRESULT)0x80090328L) #endif #ifndef SEC_E_ENCRYPT_FAILURE -# define SEC_E_ENCRYPT_FAILURE ((HRESULT)0x80090329L) +#define SEC_E_ENCRYPT_FAILURE ((HRESULT)0x80090329L) #endif #ifndef SEC_E_DECRYPT_FAILURE -# define SEC_E_DECRYPT_FAILURE ((HRESULT)0x80090330L) +#define SEC_E_DECRYPT_FAILURE ((HRESULT)0x80090330L) #endif #ifndef SEC_E_ALGORITHM_MISMATCH -# define SEC_E_ALGORITHM_MISMATCH ((HRESULT)0x80090331L) +#define SEC_E_ALGORITHM_MISMATCH ((HRESULT)0x80090331L) #endif #ifndef SEC_E_SECURITY_QOS_FAILED -# define SEC_E_SECURITY_QOS_FAILED ((HRESULT)0x80090332L) +#define SEC_E_SECURITY_QOS_FAILED ((HRESULT)0x80090332L) #endif #ifndef SEC_E_UNFINISHED_CONTEXT_DELETED -# define SEC_E_UNFINISHED_CONTEXT_DELETED ((HRESULT)0x80090333L) +#define SEC_E_UNFINISHED_CONTEXT_DELETED ((HRESULT)0x80090333L) #endif #ifndef SEC_E_NO_TGT_REPLY -# define SEC_E_NO_TGT_REPLY ((HRESULT)0x80090334L) +#define SEC_E_NO_TGT_REPLY ((HRESULT)0x80090334L) #endif #ifndef SEC_E_NO_IP_ADDRESSES -# define SEC_E_NO_IP_ADDRESSES ((HRESULT)0x80090335L) +#define SEC_E_NO_IP_ADDRESSES ((HRESULT)0x80090335L) #endif #ifndef SEC_E_WRONG_CREDENTIAL_HANDLE -# define SEC_E_WRONG_CREDENTIAL_HANDLE ((HRESULT)0x80090336L) +#define SEC_E_WRONG_CREDENTIAL_HANDLE ((HRESULT)0x80090336L) #endif #ifndef SEC_E_CRYPTO_SYSTEM_INVALID -# define SEC_E_CRYPTO_SYSTEM_INVALID ((HRESULT)0x80090337L) +#define SEC_E_CRYPTO_SYSTEM_INVALID ((HRESULT)0x80090337L) #endif #ifndef SEC_E_MAX_REFERRALS_EXCEEDED -# define SEC_E_MAX_REFERRALS_EXCEEDED ((HRESULT)0x80090338L) +#define SEC_E_MAX_REFERRALS_EXCEEDED ((HRESULT)0x80090338L) #endif #ifndef SEC_E_MUST_BE_KDC -# define SEC_E_MUST_BE_KDC ((HRESULT)0x80090339L) +#define SEC_E_MUST_BE_KDC ((HRESULT)0x80090339L) #endif #ifndef SEC_E_STRONG_CRYPTO_NOT_SUPPORTED -# define SEC_E_STRONG_CRYPTO_NOT_SUPPORTED ((HRESULT)0x8009033AL) +#define SEC_E_STRONG_CRYPTO_NOT_SUPPORTED ((HRESULT)0x8009033AL) #endif #ifndef SEC_E_TOO_MANY_PRINCIPALS -# define SEC_E_TOO_MANY_PRINCIPALS ((HRESULT)0x8009033BL) +#define SEC_E_TOO_MANY_PRINCIPALS ((HRESULT)0x8009033BL) #endif #ifndef SEC_E_NO_PA_DATA -# define SEC_E_NO_PA_DATA ((HRESULT)0x8009033CL) +#define SEC_E_NO_PA_DATA ((HRESULT)0x8009033CL) #endif #ifndef SEC_E_PKINIT_NAME_MISMATCH -# define SEC_E_PKINIT_NAME_MISMATCH ((HRESULT)0x8009033DL) +#define SEC_E_PKINIT_NAME_MISMATCH ((HRESULT)0x8009033DL) #endif #ifndef SEC_E_SMARTCARD_LOGON_REQUIRED -# define SEC_E_SMARTCARD_LOGON_REQUIRED ((HRESULT)0x8009033EL) +#define SEC_E_SMARTCARD_LOGON_REQUIRED ((HRESULT)0x8009033EL) #endif #ifndef SEC_E_SHUTDOWN_IN_PROGRESS -# define SEC_E_SHUTDOWN_IN_PROGRESS ((HRESULT)0x8009033FL) +#define SEC_E_SHUTDOWN_IN_PROGRESS ((HRESULT)0x8009033FL) #endif #ifndef SEC_E_KDC_INVALID_REQUEST -# define SEC_E_KDC_INVALID_REQUEST ((HRESULT)0x80090340L) +#define SEC_E_KDC_INVALID_REQUEST ((HRESULT)0x80090340L) #endif #ifndef SEC_E_KDC_UNABLE_TO_REFER -# define SEC_E_KDC_UNABLE_TO_REFER ((HRESULT)0x80090341L) +#define SEC_E_KDC_UNABLE_TO_REFER ((HRESULT)0x80090341L) #endif #ifndef SEC_E_KDC_UNKNOWN_ETYPE -# define SEC_E_KDC_UNKNOWN_ETYPE ((HRESULT)0x80090342L) +#define SEC_E_KDC_UNKNOWN_ETYPE ((HRESULT)0x80090342L) #endif #ifndef SEC_E_UNSUPPORTED_PREAUTH -# define SEC_E_UNSUPPORTED_PREAUTH ((HRESULT)0x80090343L) +#define SEC_E_UNSUPPORTED_PREAUTH ((HRESULT)0x80090343L) #endif #ifndef SEC_E_DELEGATION_REQUIRED -# define SEC_E_DELEGATION_REQUIRED ((HRESULT)0x80090345L) +#define SEC_E_DELEGATION_REQUIRED ((HRESULT)0x80090345L) #endif #ifndef SEC_E_BAD_BINDINGS -# define SEC_E_BAD_BINDINGS ((HRESULT)0x80090346L) +#define SEC_E_BAD_BINDINGS ((HRESULT)0x80090346L) #endif #ifndef SEC_E_MULTIPLE_ACCOUNTS -# define SEC_E_MULTIPLE_ACCOUNTS ((HRESULT)0x80090347L) +#define SEC_E_MULTIPLE_ACCOUNTS ((HRESULT)0x80090347L) #endif #ifndef SEC_E_NO_KERB_KEY -# define SEC_E_NO_KERB_KEY ((HRESULT)0x80090348L) +#define SEC_E_NO_KERB_KEY ((HRESULT)0x80090348L) #endif #ifndef SEC_E_CERT_WRONG_USAGE -# define SEC_E_CERT_WRONG_USAGE ((HRESULT)0x80090349L) +#define SEC_E_CERT_WRONG_USAGE ((HRESULT)0x80090349L) #endif #ifndef SEC_E_DOWNGRADE_DETECTED -# define SEC_E_DOWNGRADE_DETECTED ((HRESULT)0x80090350L) +#define SEC_E_DOWNGRADE_DETECTED ((HRESULT)0x80090350L) #endif #ifndef SEC_E_SMARTCARD_CERT_REVOKED -# define SEC_E_SMARTCARD_CERT_REVOKED ((HRESULT)0x80090351L) +#define SEC_E_SMARTCARD_CERT_REVOKED ((HRESULT)0x80090351L) #endif #ifndef SEC_E_ISSUING_CA_UNTRUSTED -# define SEC_E_ISSUING_CA_UNTRUSTED ((HRESULT)0x80090352L) +#define SEC_E_ISSUING_CA_UNTRUSTED ((HRESULT)0x80090352L) #endif #ifndef SEC_E_REVOCATION_OFFLINE_C -# define SEC_E_REVOCATION_OFFLINE_C ((HRESULT)0x80090353L) +#define SEC_E_REVOCATION_OFFLINE_C ((HRESULT)0x80090353L) #endif #ifndef SEC_E_PKINIT_CLIENT_FAILURE -# define SEC_E_PKINIT_CLIENT_FAILURE ((HRESULT)0x80090354L) +#define SEC_E_PKINIT_CLIENT_FAILURE ((HRESULT)0x80090354L) #endif #ifndef SEC_E_SMARTCARD_CERT_EXPIRED -# define SEC_E_SMARTCARD_CERT_EXPIRED ((HRESULT)0x80090355L) +#define SEC_E_SMARTCARD_CERT_EXPIRED ((HRESULT)0x80090355L) #endif #ifndef SEC_E_NO_S4U_PROT_SUPPORT -# define SEC_E_NO_S4U_PROT_SUPPORT ((HRESULT)0x80090356L) +#define SEC_E_NO_S4U_PROT_SUPPORT ((HRESULT)0x80090356L) #endif #ifndef SEC_E_CROSSREALM_DELEGATION_FAILURE -# define SEC_E_CROSSREALM_DELEGATION_FAILURE ((HRESULT)0x80090357L) +#define SEC_E_CROSSREALM_DELEGATION_FAILURE ((HRESULT)0x80090357L) #endif #ifndef SEC_E_REVOCATION_OFFLINE_KDC -# define SEC_E_REVOCATION_OFFLINE_KDC ((HRESULT)0x80090358L) +#define SEC_E_REVOCATION_OFFLINE_KDC ((HRESULT)0x80090358L) #endif #ifndef SEC_E_ISSUING_CA_UNTRUSTED_KDC -# define SEC_E_ISSUING_CA_UNTRUSTED_KDC ((HRESULT)0x80090359L) +#define SEC_E_ISSUING_CA_UNTRUSTED_KDC ((HRESULT)0x80090359L) #endif #ifndef SEC_E_KDC_CERT_EXPIRED -# define SEC_E_KDC_CERT_EXPIRED ((HRESULT)0x8009035AL) +#define SEC_E_KDC_CERT_EXPIRED ((HRESULT)0x8009035AL) #endif #ifndef SEC_E_KDC_CERT_REVOKED -# define SEC_E_KDC_CERT_REVOKED ((HRESULT)0x8009035BL) +#define SEC_E_KDC_CERT_REVOKED ((HRESULT)0x8009035BL) #endif +#endif /* __MINGW32CE__ */ #ifndef SEC_E_INVALID_PARAMETER # define SEC_E_INVALID_PARAMETER ((HRESULT)0x8009035DL) #endif @@ -301,36 +299,54 @@ extern PSecurityFunctionTable s_pSecFn; # define SEC_E_POLICY_NLTM_ONLY ((HRESULT)0x8009035FL) #endif +#ifdef __MINGW32CE__ #ifndef SEC_I_CONTINUE_NEEDED -# define SEC_I_CONTINUE_NEEDED ((HRESULT)0x00090312L) +#define SEC_I_CONTINUE_NEEDED ((HRESULT)0x00090312L) #endif #ifndef SEC_I_COMPLETE_NEEDED -# define SEC_I_COMPLETE_NEEDED ((HRESULT)0x00090313L) +#define SEC_I_COMPLETE_NEEDED ((HRESULT)0x00090313L) #endif #ifndef SEC_I_COMPLETE_AND_CONTINUE -# define SEC_I_COMPLETE_AND_CONTINUE ((HRESULT)0x00090314L) +#define SEC_I_COMPLETE_AND_CONTINUE ((HRESULT)0x00090314L) #endif #ifndef SEC_I_LOCAL_LOGON -# define SEC_I_LOCAL_LOGON ((HRESULT)0x00090315L) +#define SEC_I_LOCAL_LOGON ((HRESULT)0x00090315L) #endif #ifndef SEC_I_CONTEXT_EXPIRED -# define SEC_I_CONTEXT_EXPIRED ((HRESULT)0x00090317L) +#define SEC_I_CONTEXT_EXPIRED ((HRESULT)0x00090317L) #endif #ifndef SEC_I_INCOMPLETE_CREDENTIALS -# define SEC_I_INCOMPLETE_CREDENTIALS ((HRESULT)0x00090320L) +#define SEC_I_INCOMPLETE_CREDENTIALS ((HRESULT)0x00090320L) #endif #ifndef SEC_I_RENEGOTIATE -# define SEC_I_RENEGOTIATE ((HRESULT)0x00090321L) +#define SEC_I_RENEGOTIATE ((HRESULT)0x00090321L) #endif #ifndef SEC_I_NO_LSA_CONTEXT -# define SEC_I_NO_LSA_CONTEXT ((HRESULT)0x00090323L) +#define SEC_I_NO_LSA_CONTEXT ((HRESULT)0x00090323L) #endif +#endif /* __MINGW32CE__ */ #ifndef SEC_I_SIGNATURE_NEEDED -# define SEC_I_SIGNATURE_NEEDED ((HRESULT)0x0009035CL) +#define SEC_I_SIGNATURE_NEEDED ((HRESULT)0x0009035CL) #endif #ifndef CRYPT_E_REVOKED -# define CRYPT_E_REVOKED ((HRESULT)0x80092010L) +#define CRYPT_E_REVOKED ((HRESULT)0x80092010L) +#endif + +#ifndef CRYPT_E_NO_REVOCATION_DLL +#define CRYPT_E_NO_REVOCATION_DLL ((HRESULT)0x80092011L) +#endif + +#ifndef CRYPT_E_NO_REVOCATION_CHECK +#define CRYPT_E_NO_REVOCATION_CHECK ((HRESULT)0x80092012L) +#endif + +#ifndef CRYPT_E_REVOCATION_OFFLINE +#define CRYPT_E_REVOCATION_OFFLINE ((HRESULT)0x80092013L) +#endif + +#ifndef CRYPT_E_NOT_IN_REVOCATION_DATABASE +#define CRYPT_E_NOT_IN_REVOCATION_DATABASE ((HRESULT)0x80092014L) #endif #ifdef UNICODE diff --git a/Utilities/cmcurl/lib/curl_threads.c b/Utilities/cmcurl/lib/curl_threads.c index e13e2947c2c..eae75440161 100644 --- a/Utilities/cmcurl/lib/curl_threads.c +++ b/Utilities/cmcurl/lib/curl_threads.c @@ -80,11 +80,12 @@ curl_thread_t Curl_thread_create(unsigned int (*func) (void *), void *arg) return curl_thread_t_null; } -void Curl_thread_destroy(curl_thread_t hnd) +void Curl_thread_destroy(curl_thread_t *hnd) { - if(hnd != curl_thread_t_null) { - pthread_detach(*hnd); - free(hnd); + if(*hnd != curl_thread_t_null) { + pthread_detach(**hnd); + free(*hnd); + *hnd = curl_thread_t_null; } } @@ -100,40 +101,48 @@ int Curl_thread_join(curl_thread_t *hnd) #elif defined(USE_THREADS_WIN32) -/* !checksrc! disable SPACEBEFOREPAREN 1 */ -curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), +curl_thread_t Curl_thread_create( +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) + DWORD +#else + unsigned int +#endif + (CURL_STDCALL *func) (void *), void *arg) { -#ifdef _WIN32_WCE +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) typedef HANDLE curl_win_thread_handle_t; -#elif defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) - typedef unsigned long curl_win_thread_handle_t; #else typedef uintptr_t curl_win_thread_handle_t; #endif curl_thread_t t; curl_win_thread_handle_t thread_handle; -#ifdef _WIN32_WCE +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) thread_handle = CreateThread(NULL, 0, func, arg, 0, NULL); #else thread_handle = _beginthreadex(NULL, 0, func, arg, 0, NULL); #endif t = (curl_thread_t)thread_handle; if((t == 0) || (t == LongToHandle(-1L))) { -#ifdef _WIN32_WCE +#ifdef UNDER_CE DWORD gle = GetLastError(); - errno = ((gle == ERROR_ACCESS_DENIED || - gle == ERROR_NOT_ENOUGH_MEMORY) ? - EACCES : EINVAL); + /* !checksrc! disable ERRNOVAR 1 */ + int err = (gle == ERROR_ACCESS_DENIED || + gle == ERROR_NOT_ENOUGH_MEMORY) ? + EACCES : EINVAL; + CURL_SETERRNO(err); #endif return curl_thread_t_null; } return t; } -void Curl_thread_destroy(curl_thread_t hnd) +void Curl_thread_destroy(curl_thread_t *hnd) { - CloseHandle(hnd); + if(*hnd != curl_thread_t_null) { + CloseHandle(*hnd); + *hnd = curl_thread_t_null; + } } int Curl_thread_join(curl_thread_t *hnd) @@ -145,9 +154,7 @@ int Curl_thread_join(curl_thread_t *hnd) int ret = (WaitForSingleObjectEx(*hnd, INFINITE, FALSE) == WAIT_OBJECT_0); #endif - Curl_thread_destroy(*hnd); - - *hnd = curl_thread_t_null; + Curl_thread_destroy(hnd); return ret; } diff --git a/Utilities/cmcurl/lib/curl_threads.h b/Utilities/cmcurl/lib/curl_threads.h index facbc73705f..b060d4acd3e 100644 --- a/Utilities/cmcurl/lib/curl_threads.h +++ b/Utilities/cmcurl/lib/curl_threads.h @@ -40,8 +40,7 @@ # define curl_thread_t HANDLE # define curl_thread_t_null (HANDLE)0 # if !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_VISTA) || \ - (_WIN32_WINNT < _WIN32_WINNT_VISTA) || \ - (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + (_WIN32_WINNT < _WIN32_WINNT_VISTA) # define Curl_mutex_init(m) InitializeCriticalSection(m) # else # define Curl_mutex_init(m) InitializeCriticalSectionEx(m, 0, 1) @@ -53,11 +52,16 @@ #if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) -/* !checksrc! disable SPACEBEFOREPAREN 1 */ -curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), +curl_thread_t Curl_thread_create( +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) + DWORD +#else + unsigned int +#endif + (CURL_STDCALL *func) (void *), void *arg); -void Curl_thread_destroy(curl_thread_t hnd); +void Curl_thread_destroy(curl_thread_t *hnd); int Curl_thread_join(curl_thread_t *hnd); diff --git a/Utilities/cmcurl/lib/curl_trc.c b/Utilities/cmcurl/lib/curl_trc.c new file mode 100644 index 00000000000..566cdc533e2 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_trc.c @@ -0,0 +1,650 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + +#include "curl_trc.h" +#include "urldata.h" +#include "easyif.h" +#include "cfilters.h" +#include "multiif.h" +#include "strcase.h" + +#include "cf-socket.h" +#include "connect.h" +#include "doh.h" +#include "http2.h" +#include "http_proxy.h" +#include "cf-h1-proxy.h" +#include "cf-h2-proxy.h" +#include "cf-haproxy.h" +#include "cf-https-connect.h" +#include "socks.h" +#include "curlx/strparse.h" +#include "vtls/vtls.h" +#include "vquic/vquic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +static void trc_write(struct Curl_easy *data, curl_infotype type, + const char *ptr, size_t size) +{ + if(data->set.verbose) { + if(data->set.fdebug) { + bool inCallback = Curl_is_in_callback(data); + Curl_set_in_callback(data, TRUE); + (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), size, + data->set.debugdata); + Curl_set_in_callback(data, inCallback); + } + else { + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; + switch(type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_OUT: + case CURLINFO_HEADER_IN: + fwrite(s_infotype[type], 2, 1, data->set.err); + fwrite(ptr, size, 1, data->set.err); + break; + default: /* nada */ + break; + } + } + } +} + +/* max length we trace before ending in '...' */ +#define TRC_LINE_MAX 2048 + +#define CURL_TRC_FMT_IDSC "[x-%" CURL_FORMAT_CURL_OFF_T "] " +#define CURL_TRC_FMT_IDSD "[%" CURL_FORMAT_CURL_OFF_T "-x] " +#define CURL_TRC_FMT_IDSDC "[%" CURL_FORMAT_CURL_OFF_T "-%" \ + CURL_FORMAT_CURL_OFF_T "] " + +static struct curl_trc_feat Curl_trc_feat_ids = { + "LIB-IDS", + CURL_LOG_LVL_NONE, +}; +#define CURL_TRC_IDS(data) \ + (Curl_trc_is_verbose(data) && \ + Curl_trc_feat_ids.log_level >= CURL_LOG_LVL_INFO) + +static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) +{ + curl_off_t cid = data->conn ? + data->conn->connection_id : data->state.recent_conn_id; + if(data->id >= 0) { + if(cid >= 0) + return msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid); + else + return msnprintf(buf, maxlen, CURL_TRC_FMT_IDSD, data->id); + } + else if(cid >= 0) + return msnprintf(buf, maxlen, CURL_TRC_FMT_IDSC, cid); + else { + return msnprintf(buf, maxlen, "[x-x] "); + } +} + +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' */ + if(len >= (maxlen - (addnl ? 2 : 1))) { + len = maxlen - 5; + buf[len++] = '.'; + buf[len++] = '.'; + buf[len++] = '.'; + buf[len++] = '\n'; + } + else if(addnl) + buf[len++] = '\n'; + buf[len] = '\0'; + return len; +} + +void Curl_debug(struct Curl_easy *data, curl_infotype type, + const char *ptr, size_t size) +{ + if(data->set.verbose) { + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; + char buf[TRC_LINE_MAX]; + size_t len; + if(data->set.fdebug) { + bool inCallback = Curl_is_in_callback(data); + + if(CURL_TRC_IDS(data) && (size < TRC_LINE_MAX)) { + len = trc_print_ids(data, buf, TRC_LINE_MAX); + len += msnprintf(buf + len, TRC_LINE_MAX - len, "%.*s", + (int)size, ptr); + len = trc_end_buf(buf, len, TRC_LINE_MAX, FALSE); + Curl_set_in_callback(data, TRUE); + (void)(*data->set.fdebug)(data, type, buf, len, data->set.debugdata); + Curl_set_in_callback(data, inCallback); + } + else { + Curl_set_in_callback(data, TRUE); + (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), + size, data->set.debugdata); + Curl_set_in_callback(data, inCallback); + } + } + else { + switch(type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_OUT: + case CURLINFO_HEADER_IN: + if(CURL_TRC_IDS(data)) { + len = trc_print_ids(data, buf, TRC_LINE_MAX); + fwrite(buf, len, 1, data->set.err); + } + fwrite(s_infotype[type], 2, 1, data->set.err); + fwrite(ptr, size, 1, data->set.err); + break; + default: /* nada */ + break; + } + } + } +} + +/* Curl_failf() is for messages stating why we failed. + * The message SHALL NOT include any LF or CR. + */ +void Curl_failf(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(data->set.verbose || data->set.errorbuffer) { + va_list ap; + size_t len; + char error[CURL_ERROR_SIZE + 2]; + va_start(ap, fmt); + len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap); + + if(data->set.errorbuffer && !data->state.errorbuf) { + strcpy(data->set.errorbuffer, error); + data->state.errorbuf = TRUE; /* wrote error string */ + } + error[len++] = '\n'; + error[len] = '\0'; + trc_write(data, CURLINFO_TEXT, error, len); + va_end(ap); + } +} + +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + + +static void trc_infof(struct Curl_easy *data, + struct curl_trc_feat *feat, + const char *opt_id, int opt_id_idx, + const char * const fmt, va_list ap) CURL_PRINTF(5, 0); + +static void trc_infof(struct Curl_easy *data, + struct curl_trc_feat *feat, + const char *opt_id, int opt_id_idx, + const char * const fmt, va_list ap) +{ + size_t len = 0; + char buf[TRC_LINE_MAX]; + + if(CURL_TRC_IDS(data)) + len += trc_print_ids(data, buf + len, TRC_LINE_MAX - len); + if(feat) + len += msnprintf(buf + len, TRC_LINE_MAX - len, "[%s] ", feat->name); + if(opt_id) { + if(opt_id_idx > 0) + len += msnprintf(buf + len, TRC_LINE_MAX - len, "[%s-%d] ", + opt_id, opt_id_idx); + else + len += msnprintf(buf + len, TRC_LINE_MAX - len, "[%s] ", opt_id); + } + len += mvsnprintf(buf + len, TRC_LINE_MAX - len, fmt, ap); + len = trc_end_buf(buf, len, TRC_LINE_MAX, TRUE); + trc_write(data, CURLINFO_TEXT, buf, len); +} + +void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_is_verbose(data)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, data->state.feat, NULL, 0, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_cf_infof(struct Curl_easy *data, const struct Curl_cfilter *cf, + const char *fmt, ...) +{ + DEBUGASSERT(cf); + if(Curl_trc_cf_is_verbose(cf, data)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, data->state.feat, cf->cft->name, cf->sockindex, fmt, ap); + va_end(ap); + } +} + +struct curl_trc_feat Curl_trc_feat_multi = { + "MULTI", + CURL_LOG_LVL_NONE, +}; +struct curl_trc_feat Curl_trc_feat_read = { + "READ", + CURL_LOG_LVL_NONE, +}; +struct curl_trc_feat Curl_trc_feat_write = { + "WRITE", + CURL_LOG_LVL_NONE, +}; +struct curl_trc_feat Curl_trc_feat_dns = { + "DNS", + CURL_LOG_LVL_NONE, +}; + + +static const char * const Curl_trc_mstate_names[]={ + "INIT", + "PENDING", + "SETUP", + "CONNECT", + "RESOLVING", + "CONNECTING", + "TUNNELING", + "PROTOCONNECT", + "PROTOCONNECTING", + "DO", + "DOING", + "DOING_MORE", + "DID", + "PERFORMING", + "RATELIMITING", + "DONE", + "COMPLETED", + "MSGSENT", +}; + +const char *Curl_trc_mstate_name(int state) +{ + if((state >= 0) && ((size_t)state < CURL_ARRAYSIZE(Curl_trc_mstate_names))) + return Curl_trc_mstate_names[(size_t)state]; + return "?"; +} + +void Curl_trc_multi(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_multi)) { + const char *sname = (data->id >= 0) ? + Curl_trc_mstate_name(data->mstate) : NULL; + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_multi, sname, 0, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_read, NULL, 0, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_write, NULL, 0, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_dns(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_dns, NULL, 0, fmt, ap); + va_end(ap); + } +} + +#ifndef CURL_DISABLE_FTP +struct curl_trc_feat Curl_trc_feat_ftp = { + "FTP", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ftp, NULL, 0, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_FTP */ + +#ifndef CURL_DISABLE_SMTP +struct curl_trc_feat Curl_trc_feat_smtp = { + "SMTP", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_smtp)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_smtp, NULL, 0, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_SMTP */ + +#ifdef USE_SSL +struct curl_trc_feat Curl_trc_feat_ssls = { + "SSLS", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_ssls(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ssls)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ssls, NULL, 0, fmt, ap); + va_end(ap); + } +} +#endif /* USE_SSL */ + +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +struct curl_trc_feat Curl_trc_feat_ws = { + "WS", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ws)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ws, NULL, 0, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_WEBSOCKETS && !CURL_DISABLE_HTTP */ + +#define TRC_CT_NONE (0) +#define TRC_CT_PROTOCOL (1<<(0)) +#define TRC_CT_NETWORK (1<<(1)) +#define TRC_CT_PROXY (1<<(2)) +#define TRC_CT_INTERNALS (1<<(3)) + +struct trc_feat_def { + struct curl_trc_feat *feat; + unsigned int category; +}; + +static struct trc_feat_def trc_feats[] = { + { &Curl_trc_feat_ids, TRC_CT_INTERNALS }, + { &Curl_trc_feat_multi, TRC_CT_NETWORK }, + { &Curl_trc_feat_read, TRC_CT_NONE }, + { &Curl_trc_feat_write, TRC_CT_NONE }, + { &Curl_trc_feat_dns, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_FTP + { &Curl_trc_feat_ftp, TRC_CT_PROTOCOL }, +#endif +#ifndef CURL_DISABLE_DOH +#endif +#ifndef CURL_DISABLE_SMTP + { &Curl_trc_feat_smtp, TRC_CT_PROTOCOL }, +#endif +#ifdef USE_SSL + { &Curl_trc_feat_ssls, TRC_CT_NETWORK }, +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + { &Curl_trc_feat_ws, TRC_CT_PROTOCOL }, +#endif +}; + +struct trc_cft_def { + struct Curl_cftype *cft; + unsigned int category; +}; + +static struct trc_cft_def trc_cfts[] = { + { &Curl_cft_tcp, TRC_CT_NETWORK }, + { &Curl_cft_udp, TRC_CT_NETWORK }, + { &Curl_cft_unix, TRC_CT_NETWORK }, + { &Curl_cft_tcp_accept, TRC_CT_NETWORK }, + { &Curl_cft_happy_eyeballs, TRC_CT_NETWORK }, + { &Curl_cft_setup, TRC_CT_PROTOCOL }, +#ifdef USE_NGHTTP2 + { &Curl_cft_nghttp2, TRC_CT_PROTOCOL }, +#endif +#ifdef USE_SSL + { &Curl_cft_ssl, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_PROXY + { &Curl_cft_ssl_proxy, TRC_CT_PROXY }, +#endif +#endif +#if !defined(CURL_DISABLE_PROXY) +#if !defined(CURL_DISABLE_HTTP) + { &Curl_cft_h1_proxy, TRC_CT_PROXY }, +#ifdef USE_NGHTTP2 + { &Curl_cft_h2_proxy, TRC_CT_PROXY }, +#endif + { &Curl_cft_http_proxy, TRC_CT_PROXY }, +#endif /* !CURL_DISABLE_HTTP */ + { &Curl_cft_haproxy, TRC_CT_PROXY }, + { &Curl_cft_socks_proxy, TRC_CT_PROXY }, +#endif /* !CURL_DISABLE_PROXY */ +#ifdef USE_HTTP3 + { &Curl_cft_http3, TRC_CT_PROTOCOL }, +#endif +#if !defined(CURL_DISABLE_HTTP) + { &Curl_cft_http_connect, TRC_CT_PROTOCOL }, +#endif +}; + +static void trc_apply_level_by_name(struct Curl_str *token, int lvl) +{ + size_t i; + + for(i = 0; i < CURL_ARRAYSIZE(trc_cfts); ++i) { + if(curlx_str_casecompare(token, trc_cfts[i].cft->name)) { + trc_cfts[i].cft->log_level = lvl; + break; + } + } + for(i = 0; i < CURL_ARRAYSIZE(trc_feats); ++i) { + if(curlx_str_casecompare(token, trc_feats[i].feat->name)) { + trc_feats[i].feat->log_level = lvl; + break; + } + } +} + +static void trc_apply_level_by_category(int category, int lvl) +{ + size_t i; + + for(i = 0; i < CURL_ARRAYSIZE(trc_cfts); ++i) { + if(!category || (trc_cfts[i].category & category)) + trc_cfts[i].cft->log_level = lvl; + } + for(i = 0; i < CURL_ARRAYSIZE(trc_feats); ++i) { + if(!category || (trc_feats[i].category & category)) + trc_feats[i].feat->log_level = lvl; + } +} + +static CURLcode trc_opt(const char *config) +{ + struct Curl_str out; + while(!curlx_str_until(&config, &out, 32, ',')) { + int lvl = CURL_LOG_LVL_INFO; + const char *token = curlx_str(&out); + + if(*token == '-') { + lvl = CURL_LOG_LVL_NONE; + curlx_str_nudge(&out, 1); + } + else if(*token == '+') + curlx_str_nudge(&out, 1); + + if(curlx_str_casecompare(&out, "all")) + trc_apply_level_by_category(TRC_CT_NONE, lvl); + else if(curlx_str_casecompare(&out, "protocol")) + trc_apply_level_by_category(TRC_CT_PROTOCOL, lvl); + else if(curlx_str_casecompare(&out, "network")) + trc_apply_level_by_category(TRC_CT_NETWORK, lvl); + else if(curlx_str_casecompare(&out, "proxy")) + trc_apply_level_by_category(TRC_CT_PROXY, lvl); + else if(curlx_str_casecompare(&out, "doh")) { + struct Curl_str dns = { "dns", 3 }; + trc_apply_level_by_name(&dns, lvl); + } + else + trc_apply_level_by_name(&out, lvl); + + if(curlx_str_single(&config, ',')) + break; + } + return CURLE_OK; +} + +CURLcode Curl_trc_opt(const char *config) +{ + CURLcode result = config ? trc_opt(config) : CURLE_OK; +#ifdef DEBUGBUILD + /* CURL_DEBUG can override anything */ + if(!result) { + const char *dbg_config = getenv("CURL_DEBUG"); + if(dbg_config) + result = trc_opt(dbg_config); + } +#endif /* DEBUGBUILD */ + return result; +} + +CURLcode Curl_trc_init(void) +{ +#ifdef DEBUGBUILD + return Curl_trc_opt(NULL); +#else + return CURLE_OK; +#endif +} + +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ + +CURLcode Curl_trc_init(void) +{ + return CURLE_OK; +} + +void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +void Curl_trc_cf_infof(struct Curl_easy *data, const struct Curl_cfilter *cf, + const char *fmt, ...) +{ + (void)data; (void)cf; (void)fmt; +} + +struct curl_trc_feat; + +void Curl_trc_multi(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +void Curl_trc_dns(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +#ifndef CURL_DISABLE_FTP +void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif +#ifndef CURL_DISABLE_SMTP +void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) || !defined(CURL_DISABLE_HTTP) +void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif + +void Curl_trc_ssls(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; + (void)fmt; +} + +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ diff --git a/Utilities/cmcurl/lib/curl_trc.h b/Utilities/cmcurl/lib/curl_trc.h new file mode 100644 index 00000000000..ed7e5d89f00 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_trc.h @@ -0,0 +1,216 @@ +#ifndef HEADER_CURL_TRC_H +#define HEADER_CURL_TRC_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 + * + ***************************************************************************/ + +struct Curl_easy; +struct Curl_cfilter; + +/** + * Init logging, return != 0 on failure. + */ +CURLcode Curl_trc_init(void); + +/** + * Configure tracing. May be called several times during global + * initialization. Later calls may not take effect. + * + * Configuration format supported: + * - comma-separated list of component names to enable logging on. + * E.g. 'http/2,ssl'. Unknown names are ignored. Names are compared + * case-insensitive. + * - component 'all' applies to all known log components + * - prefixing a component with '+' or '-' will en-/disable logging for + * that component + * Example: 'all,-ssl' would enable logging for all components but the + * SSL filters. + * + * @param config configuration string + */ +CURLcode Curl_trc_opt(const char *config); + +/* the function used to output verbose information */ +void Curl_debug(struct Curl_easy *data, curl_infotype type, + const char *ptr, size_t size); + +/** + * Output a failure message on registered callbacks for transfer. + */ +void Curl_failf(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +#define failf Curl_failf + +#define CURL_LOG_LVL_NONE 0 +#define CURL_LOG_LVL_INFO 1 + + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define CURL_HAVE_C99 +#endif + +/** + * Output an informational message when transfer's verbose logging is enabled. + */ +void Curl_infof(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +/** + * Output an informational message when both transfer's verbose logging + * and connection filters verbose logging are enabled. + */ +void Curl_trc_cf_infof(struct Curl_easy *data, const struct Curl_cfilter *cf, + const char *fmt, ...) CURL_PRINTF(3, 4); +void Curl_trc_multi(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +const char *Curl_trc_mstate_name(int state); +void Curl_trc_write(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +void Curl_trc_read(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +void Curl_trc_dns(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +#ifndef CURL_DISABLE_FTP +extern struct curl_trc_feat Curl_trc_feat_ftp; +void Curl_trc_ftp(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif +#ifndef CURL_DISABLE_SMTP +extern struct curl_trc_feat Curl_trc_feat_smtp; +void Curl_trc_smtp(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif +#ifdef USE_SSL +extern struct curl_trc_feat Curl_trc_feat_ssls; +void Curl_trc_ssls(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +extern struct curl_trc_feat Curl_trc_feat_ws; +void Curl_trc_ws(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif + +#if defined(CURL_HAVE_C99) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +#define infof(data, ...) \ + do { if(Curl_trc_is_verbose(data)) \ + Curl_infof(data, __VA_ARGS__); } while(0) +#define CURL_TRC_M(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_multi)) \ + Curl_trc_multi(data, __VA_ARGS__); } while(0) +#define CURL_TRC_CF(data, cf, ...) \ + do { if(Curl_trc_cf_is_verbose(cf, data)) \ + Curl_trc_cf_infof(data, cf, __VA_ARGS__); } while(0) +#define CURL_TRC_WRITE(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) \ + Curl_trc_write(data, __VA_ARGS__); } while(0) +#define CURL_TRC_READ(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) \ + Curl_trc_read(data, __VA_ARGS__); } while(0) +#define CURL_TRC_DNS(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) \ + Curl_trc_dns(data, __VA_ARGS__); } while(0) + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) \ + Curl_trc_ftp(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_FTP */ +#ifndef CURL_DISABLE_SMTP +#define CURL_TRC_SMTP(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_smtp)) \ + Curl_trc_smtp(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_SMTP */ +#ifdef USE_SSL +#define CURL_TRC_SSLS(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ssls)) \ + Curl_trc_ssls(data, __VA_ARGS__); } while(0) +#endif /* USE_SSL */ +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +#define CURL_TRC_WS(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ws)) \ + Curl_trc_ws(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_WEBSOCKETS && !CURL_DISABLE_HTTP */ + +#else /* CURL_HAVE_C99 */ + +#define infof Curl_infof +#define CURL_TRC_M Curl_trc_multi +#define CURL_TRC_CF Curl_trc_cf_infof +#define CURL_TRC_WRITE Curl_trc_write +#define CURL_TRC_READ Curl_trc_read +#define CURL_TRC_DNS Curl_trc_dns + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP Curl_trc_ftp +#endif +#ifndef CURL_DISABLE_SMTP +#define CURL_TRC_SMTP Curl_trc_smtp +#endif +#ifdef USE_SSL +#define CURL_TRC_SSLS Curl_trc_ssls +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +#define CURL_TRC_WS Curl_trc_ws +#endif + +#endif /* !CURL_HAVE_C99 */ + +struct curl_trc_feat { + const char *name; + int log_level; +}; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +/* informational messages enabled */ + +extern struct curl_trc_feat Curl_trc_feat_multi; +extern struct curl_trc_feat Curl_trc_feat_read; +extern struct curl_trc_feat Curl_trc_feat_write; +extern struct curl_trc_feat Curl_trc_feat_dns; + +#define Curl_trc_is_verbose(data) \ + ((data) && (data)->set.verbose && \ + (!(data)->state.feat || \ + ((data)->state.feat->log_level >= CURL_LOG_LVL_INFO))) +#define Curl_trc_cf_is_verbose(cf, data) \ + (Curl_trc_is_verbose(data) && \ + (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO) +#define Curl_trc_ft_is_verbose(data, ft) \ + (Curl_trc_is_verbose(data) && \ + (ft)->log_level >= CURL_LOG_LVL_INFO) +#define CURL_MSTATE_NAME(s) Curl_trc_mstate_name((int)(s)) + +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ +/* All informational messages are not compiled in for size savings */ + +#define Curl_trc_is_verbose(d) (FALSE) +#define Curl_trc_cf_is_verbose(x,y) (FALSE) +#define Curl_trc_ft_is_verbose(x,y) (FALSE) +#define CURL_MSTATE_NAME(x) ((void)(x), "-") + +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ + +#endif /* HEADER_CURL_TRC_H */ diff --git a/Utilities/cmcurl/lib/curlx.h b/Utilities/cmcurl/lib/curlx.h deleted file mode 100644 index 7a753d68247..00000000000 --- a/Utilities/cmcurl/lib/curlx.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef HEADER_CURL_CURLX_H -#define HEADER_CURL_CURLX_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 - * - ***************************************************************************/ - -/* - * Defines protos and includes all header files that provide the curlx_* - * functions. The curlx_* functions are not part of the libcurl API, but are - * stand-alone functions whose sources can be built and linked by apps if need - * be. - */ - -#include -/* this is still a public header file that provides the curl_mprintf() - functions while they still are offered publicly. They will be made library- - private one day */ - -#include "strcase.h" -/* "strcase.h" provides the strcasecompare protos */ - -#include "strtoofft.h" -/* "strtoofft.h" provides this function: curlx_strtoofft(), returns a - curl_off_t number from a given string. -*/ - -#include "nonblock.h" -/* "nonblock.h" provides curlx_nonblock() */ - -#include "warnless.h" -/* "warnless.h" provides functions: - - curlx_ultous() - curlx_ultouc() - curlx_uztosi() -*/ - -#include "curl_multibyte.h" -/* "curl_multibyte.h" provides these functions and macros: - - curlx_convert_UTF8_to_wchar() - curlx_convert_wchar_to_UTF8() - curlx_convert_UTF8_to_tchar() - curlx_convert_tchar_to_UTF8() - curlx_unicodefree() -*/ - -#include "version_win32.h" -/* "version_win32.h" provides curlx_verify_windows_version() */ - -/* Now setup curlx_ * names for the functions that are to become curlx_ and - be removed from a future libcurl official API: - curlx_getenv - curlx_mprintf (and its variations) - curlx_strcasecompare - curlx_strncasecompare - -*/ - -#define curlx_getenv curl_getenv -#define curlx_mvsnprintf curl_mvsnprintf -#define curlx_msnprintf curl_msnprintf -#define curlx_maprintf curl_maprintf -#define curlx_mvaprintf curl_mvaprintf -#define curlx_msprintf curl_msprintf -#define curlx_mprintf curl_mprintf -#define curlx_mfprintf curl_mfprintf -#define curlx_mvsprintf curl_mvsprintf -#define curlx_mvprintf curl_mvprintf -#define curlx_mvfprintf curl_mvfprintf - -#ifdef ENABLE_CURLX_PRINTF -/* If this define is set, we define all "standard" printf() functions to use - the curlx_* version instead. It makes the source code transparent and - easier to understand/patch. Undefine them first. */ -# undef printf -# undef fprintf -# undef sprintf -# undef msnprintf -# undef vprintf -# undef vfprintf -# undef vsprintf -# undef mvsnprintf -# undef aprintf -# undef vaprintf - -# define printf curlx_mprintf -# define fprintf curlx_mfprintf -# define sprintf curlx_msprintf -# define msnprintf curlx_msnprintf -# define vprintf curlx_mvprintf -# define vfprintf curlx_mvfprintf -# define mvsnprintf curlx_mvsnprintf -# define aprintf curlx_maprintf -# define vaprintf curlx_mvaprintf -#endif /* ENABLE_CURLX_PRINTF */ - -#endif /* HEADER_CURL_CURLX_H */ diff --git a/Utilities/cmcurl/lib/base64.c b/Utilities/cmcurl/lib/curlx/base64.c similarity index 80% rename from Utilities/cmcurl/lib/base64.c rename to Utilities/cmcurl/lib/curlx/base64.c index 971300e1790..92ebc57e1e6 100644 --- a/Utilities/cmcurl/lib/base64.c +++ b/Utilities/cmcurl/lib/curlx/base64.c @@ -24,27 +24,28 @@ /* Base64 encoding/decoding */ -#include "curl_setup.h" +#include "../curl_setup.h" #if !defined(CURL_DISABLE_HTTP_AUTH) || defined(USE_SSH) || \ !defined(CURL_DISABLE_LDAP) || \ !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_POP3) || \ !defined(CURL_DISABLE_IMAP) || \ - !defined(CURL_DISABLE_DOH) || defined(USE_SSL) - -#include "urldata.h" /* for the Curl_easy definition */ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_DOH) || defined(USE_SSL) || !defined(BUILDING_LIBCURL) +#include #include "warnless.h" -#include "curl_base64.h" +#include "base64.h" /* The last 2 #include files should be in this order */ -#include "curl_memory.h" -#include "memdebug.h" +#ifdef BUILDING_LIBCURL +#include "../curl_memory.h" +#endif +#include "../memdebug.h" /* ---- Base64 Encoding/Decoding Table --- */ -/* Padding character string starts at offset 64. */ -static const char base64[]= - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +const char Curl_base64encdec[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* The Base 64 encoding with a URL and filename safe alphabet, RFC 4648 section 5 */ @@ -58,11 +59,11 @@ static const unsigned char decodetable[] = 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; /* - * Curl_base64_decode() + * curlx_base64_decode() * - * Given a base64 NUL-terminated string at src, decode it and return a - * pointer in *outptr to a newly allocated memory area holding decoded - * data. Size of decoded data is returned in variable pointed by outlen. + * Given a base64 null-terminated string at src, decode it and return a + * pointer in *outptr to a newly allocated memory area holding decoded data. + * Size of decoded data is returned in variable pointed by outlen. * * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. @@ -71,8 +72,8 @@ static const unsigned char decodetable[] = * * @unittest: 1302 */ -CURLcode Curl_base64_decode(const char *src, - unsigned char **outptr, size_t *outlen) +CURLcode curlx_base64_decode(const char *src, + unsigned char **outptr, size_t *outlen) { size_t srclen = 0; size_t padding = 0; @@ -117,14 +118,6 @@ CURLcode Curl_base64_decode(const char *src, memset(lookup, 0xff, sizeof(lookup)); memcpy(&lookup['+'], decodetable, sizeof(decodetable)); - /* replaces - { - unsigned char c; - const unsigned char *p = (const unsigned char *)base64; - for(c = 0; *p; c++, p++) - lookup[*p] = c; - } - */ /* Decode the complete quantums first */ for(i = 0; i < fullQuantums; i++) { @@ -184,13 +177,13 @@ CURLcode Curl_base64_decode(const char *src, } static CURLcode base64_encode(const char *table64, + unsigned char padbyte, const char *inputbuff, size_t insize, char **outptr, size_t *outlen) { char *output; char *base64data; - const unsigned char *in = (unsigned char *)inputbuff; - const char *padstr = &table64[64]; /* Point to padding string. */ + const unsigned char *in = (const unsigned char *)inputbuff; *outptr = NULL; *outlen = 0; @@ -220,17 +213,17 @@ static CURLcode base64_encode(const char *table64, *output++ = table64[ in[0] >> 2 ]; if(insize == 1) { *output++ = table64[ ((in[0] & 0x03) << 4) ]; - if(*padstr) { - *output++ = *padstr; - *output++ = *padstr; + if(padbyte) { + *output++ = padbyte; + *output++ = padbyte; } } else { /* insize == 2 */ *output++ = table64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4) ]; *output++ = table64[ ((in[1] & 0x0F) << 2) ]; - if(*padstr) - *output++ = *padstr; + if(padbyte) + *output++ = padbyte; } } @@ -241,51 +234,52 @@ static CURLcode base64_encode(const char *table64, *outptr = base64data; /* Return the length of the new data */ - *outlen = output - base64data; + *outlen = (size_t)(output - base64data); return CURLE_OK; } /* - * Curl_base64_encode() + * curlx_base64_encode() * * Given a pointer to an input buffer and an input size, encode it and * return a pointer in *outptr to a newly allocated memory area holding * encoded data. Size of encoded data is returned in variable pointed by * outlen. * - * Input length of 0 indicates input buffer holds a NUL-terminated string. + * Input length of 0 indicates input buffer holds a null-terminated string. * * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. * * @unittest: 1302 */ -CURLcode Curl_base64_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen) +CURLcode curlx_base64_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) { - return base64_encode(base64, inputbuff, insize, outptr, outlen); + return base64_encode(Curl_base64encdec, '=', + inputbuff, insize, outptr, outlen); } /* - * Curl_base64url_encode() + * curlx_base64url_encode() * * Given a pointer to an input buffer and an input size, encode it and * return a pointer in *outptr to a newly allocated memory area holding * encoded data. Size of encoded data is returned in variable pointed by * outlen. * - * Input length of 0 indicates input buffer holds a NUL-terminated string. + * Input length of 0 indicates input buffer holds a null-terminated string. * * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. * * @unittest: 1302 */ -CURLcode Curl_base64url_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen) +CURLcode curlx_base64url_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) { - return base64_encode(base64url, inputbuff, insize, outptr, outlen); + return base64_encode(base64url, 0, inputbuff, insize, outptr, outlen); } #endif /* no users so disabled */ diff --git a/Utilities/cmcurl/lib/curlx/base64.h b/Utilities/cmcurl/lib/curlx/base64.h new file mode 100644 index 00000000000..026f80e4d37 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/base64.h @@ -0,0 +1,36 @@ +#ifndef HEADER_CURL_BASE64_H +#define HEADER_CURL_BASE64_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 + * + ***************************************************************************/ + +CURLcode curlx_base64_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen); +CURLcode curlx_base64url_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen); +CURLcode curlx_base64_decode(const char *src, + unsigned char **outptr, size_t *outlen); + +extern const char Curl_base64encdec[]; + +#endif /* HEADER_CURL_BASE64_H */ diff --git a/Utilities/cmcurl/lib/curlx/curlx.h b/Utilities/cmcurl/lib/curlx/curlx.h new file mode 100644 index 00000000000..983c7b5c75b --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/curlx.h @@ -0,0 +1,74 @@ +#ifndef HEADER_CURL_CURLX_H +#define HEADER_CURL_CURLX_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 + * + ***************************************************************************/ + +/* + * Defines protos and includes all header files that provide the curlx_* + * functions. The curlx_* functions are not part of the libcurl API, but are + * stand-alone functions whose sources can be built and linked by apps if need + * be. + */ + +#include "nonblock.h" +/* "nonblock.h" provides curlx_nonblock() */ + +#include "warnless.h" +/* "warnless.h" provides functions: + + curlx_ultous() + curlx_ultouc() + curlx_uztosi() +*/ + +#include "multibyte.h" +/* "multibyte.h" provides these functions and macros: + + curlx_convert_UTF8_to_wchar() + curlx_convert_wchar_to_UTF8() + curlx_convert_UTF8_to_tchar() + curlx_convert_tchar_to_UTF8() + curlx_unicodefree() +*/ + +#include "version_win32.h" +/* provides curlx_verify_windows_version() */ + +#include "strparse.h" +/* The curlx_str_* parsing functions */ + +#include "dynbuf.h" +/* The curlx_dyn_* functions */ + +#include "base64.h" +#include "timeval.h" +#include "timediff.h" + +#include "winapi.h" +/* for curlx_winapi_strerror */ + +#include "inet_pton.h" +/* for curlx_inet_pton */ + +#endif /* HEADER_CURL_CURLX_H */ diff --git a/Utilities/cmcurl/lib/curlx/dynbuf.c b/Utilities/cmcurl/lib/curlx/dynbuf.c new file mode 100644 index 00000000000..0b8dfe8e34f --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/dynbuf.c @@ -0,0 +1,298 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "dynbuf.h" +#include "../curl_printf.h" +#ifdef BUILDING_LIBCURL +#include "../curl_memory.h" +#endif +#include "../memdebug.h" + +#define MIN_FIRST_ALLOC 32 + +#ifdef DEBUGBUILD +#define DYNINIT 0xbee51da /* random pattern */ +#endif + +/* + * Init a dynbuf struct. + */ +void curlx_dyn_init(struct dynbuf *s, size_t toobig) +{ + DEBUGASSERT(s); + DEBUGASSERT(toobig); + DEBUGASSERT(toobig <= MAX_DYNBUF_SIZE); /* catch crazy mistakes */ + s->bufr = NULL; + s->leng = 0; + s->allc = 0; + s->toobig = toobig; +#ifdef DEBUGBUILD + s->init = DYNINIT; +#endif +} + +/* + * free the buffer and re-init the necessary fields. It does not touch the + * 'init' field and thus this buffer can be reused to add data to again. + */ +void curlx_dyn_free(struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + Curl_safefree(s->bufr); + s->leng = s->allc = 0; +} + +/* + * Store/append an chunk of memory to the dynbuf. + */ +static CURLcode dyn_nappend(struct dynbuf *s, + const unsigned char *mem, size_t len) +{ + size_t indx = s->leng; + size_t a = s->allc; + size_t fit = len + indx + 1; /* new string + old string + zero byte */ + + /* try to detect if there is rubbish in the struct */ + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(s->toobig); + DEBUGASSERT(indx < s->toobig); + DEBUGASSERT(!s->leng || s->bufr); + DEBUGASSERT(a <= s->toobig); + DEBUGASSERT(!len || mem); + + if(fit > s->toobig) { + curlx_dyn_free(s); + return CURLE_TOO_LARGE; + } + else if(!a) { + DEBUGASSERT(!indx); + /* first invoke */ + if(MIN_FIRST_ALLOC > s->toobig) + a = s->toobig; + else if(fit < MIN_FIRST_ALLOC) + a = MIN_FIRST_ALLOC; + else + a = fit; + } + else { + while(a < fit) + a *= 2; + if(a > s->toobig) + /* no point in allocating a larger buffer than this is allowed to use */ + a = s->toobig; + } + + if(a != s->allc) { + /* this logic is not using Curl_saferealloc() to make the tool not have to + include that as well when it uses this code */ + void *p = realloc(s->bufr, a); + if(!p) { + curlx_dyn_free(s); + return CURLE_OUT_OF_MEMORY; + } + s->bufr = p; + s->allc = a; + } + + if(len) + memcpy(&s->bufr[indx], mem, len); + s->leng = indx + len; + s->bufr[s->leng] = 0; + return CURLE_OK; +} + +/* + * Clears the string, keeps the allocation. This can also be called on a + * buffer that already was freed. + */ +void curlx_dyn_reset(struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + if(s->leng) + s->bufr[0] = 0; + s->leng = 0; +} + +/* + * Specify the size of the tail to keep (number of bytes from the end of the + * buffer). The rest will be dropped. + */ +CURLcode curlx_dyn_tail(struct dynbuf *s, size_t trail) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + if(trail > s->leng) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(trail == s->leng) + return CURLE_OK; + else if(!trail) { + curlx_dyn_reset(s); + } + else { + memmove(&s->bufr[0], &s->bufr[s->leng - trail], trail); + s->leng = trail; + s->bufr[s->leng] = 0; + } + return CURLE_OK; + +} + +/* + * Appends a buffer with length. + */ +CURLcode curlx_dyn_addn(struct dynbuf *s, const void *mem, size_t len) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return dyn_nappend(s, mem, len); +} + +/* + * Append a null-terminated string at the end. + */ +CURLcode curlx_dyn_add(struct dynbuf *s, const char *str) +{ + size_t n; + DEBUGASSERT(str); + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + n = strlen(str); + return dyn_nappend(s, (const unsigned char *)str, n); +} + +/* + * Append a string vprintf()-style + */ +CURLcode curlx_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) +{ +#ifdef BUILDING_LIBCURL + int rc; + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + DEBUGASSERT(fmt); + rc = curlx_dyn_vprintf(s, fmt, ap); + + if(!rc) + return CURLE_OK; + else if(rc == MERR_TOO_LARGE) + return CURLE_TOO_LARGE; + return CURLE_OUT_OF_MEMORY; +#else + char *str; + str = curl_mvaprintf(fmt, ap); /* this allocs a new string to append */ + + if(str) { + CURLcode result = dyn_nappend(s, (const unsigned char *)str, strlen(str)); + free(str); + return result; + } + /* If we failed, we cleanup the whole buffer and return error */ + curlx_dyn_free(s); + return CURLE_OUT_OF_MEMORY; +#endif +} + +/* + * Append a string printf()-style + */ +CURLcode curlx_dyn_addf(struct dynbuf *s, const char *fmt, ...) +{ + CURLcode result; + va_list ap; + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + va_start(ap, fmt); + result = curlx_dyn_vaddf(s, fmt, ap); + va_end(ap); + return result; +} + +/* + * Returns a pointer to the buffer. + */ +char *curlx_dyn_ptr(const struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return s->bufr; +} + +char *curlx_dyn_take(struct dynbuf *s, size_t *plen) +{ + char *ptr = s->bufr; + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + *plen = s->leng; + s->bufr = NULL; + s->leng = 0; + s->allc = 0; + return ptr; +} + +/* + * Returns an unsigned pointer to the buffer. + */ +unsigned char *curlx_dyn_uptr(const struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return (unsigned char *)s->bufr; +} + +/* + * Returns the length of the buffer. + */ +size_t curlx_dyn_len(const struct dynbuf *s) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + return s->leng; +} + +/* + * Set a new (smaller) length. + */ +CURLcode curlx_dyn_setlen(struct dynbuf *s, size_t set) +{ + DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); + DEBUGASSERT(!s->leng || s->bufr); + if(set > s->leng) + return CURLE_BAD_FUNCTION_ARGUMENT; + s->leng = set; + s->bufr[s->leng] = 0; + return CURLE_OK; +} diff --git a/Utilities/cmcurl/lib/curlx/dynbuf.h b/Utilities/cmcurl/lib/curlx/dynbuf.h new file mode 100644 index 00000000000..27335a6fbf4 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/dynbuf.h @@ -0,0 +1,85 @@ +#ifndef HEADER_CURL_DYNBUF_H +#define HEADER_CURL_DYNBUF_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 + +struct dynbuf { + char *bufr; /* point to a null-terminated allocated buffer */ + size_t leng; /* number of bytes *EXCLUDING* the null-terminator */ + size_t allc; /* size of the current allocation */ + size_t toobig; /* size limit for the buffer */ +#ifdef DEBUGBUILD + int init; /* detect API usage mistakes */ +#endif +}; + +void curlx_dyn_init(struct dynbuf *s, size_t toobig); +void curlx_dyn_free(struct dynbuf *s); +CURLcode curlx_dyn_addn(struct dynbuf *s, const void *mem, size_t len) + WARN_UNUSED_RESULT; +CURLcode curlx_dyn_add(struct dynbuf *s, const char *str) + WARN_UNUSED_RESULT; +CURLcode curlx_dyn_addf(struct dynbuf *s, const char *fmt, ...) + WARN_UNUSED_RESULT CURL_PRINTF(2, 3); +CURLcode curlx_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) + WARN_UNUSED_RESULT CURL_PRINTF(2, 0); +void curlx_dyn_reset(struct dynbuf *s); +CURLcode curlx_dyn_tail(struct dynbuf *s, size_t trail); +CURLcode curlx_dyn_setlen(struct dynbuf *s, size_t set); +char *curlx_dyn_ptr(const struct dynbuf *s); +unsigned char *curlx_dyn_uptr(const struct dynbuf *s); +size_t curlx_dyn_len(const struct dynbuf *s); + +/* returns 0 on success, -1 on error */ +/* The implementation of this function exists in mprintf.c */ +int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); + +/* Take the buffer out of the dynbuf. Caller has ownership and + * dynbuf resets to initial state. */ +char *curlx_dyn_take(struct dynbuf *s, size_t *plen); + +/* Dynamic buffer max sizes */ +#define MAX_DYNBUF_SIZE (SIZE_T_MAX/2) + +#define DYN_DOH_RESPONSE 3000 +#define DYN_DOH_CNAME 256 +#define DYN_PAUSE_BUFFER (64 * 1024 * 1024) +#define DYN_HAXPROXY 2048 +#define DYN_HTTP_REQUEST (1024*1024) +#define DYN_APRINTF 8000000 +#define DYN_RTSP_REQ_HEADER (64*1024) +#define DYN_TRAILERS (64*1024) +#define DYN_PROXY_CONNECT_HEADERS 16384 +#define DYN_QLOG_NAME 1024 +#define DYN_H1_TRAILER 4096 +#define DYN_PINGPPONG_CMD (64*1024) +#define DYN_IMAP_CMD (64*1024) +#define DYN_MQTT_RECV (64*1024) +#define DYN_MQTT_SEND 0xFFFFFFF +#define DYN_CRLFILE_SIZE (400*1024*1024) /* 400mb */ +#define DYN_CERTFILE_SIZE (100*1024) /* 100KiB */ +#define DYN_KEYFILE_SIZE (100*1024) /* 100KiB */ +#endif diff --git a/Utilities/cmcurl/lib/curlx/inet_pton.c b/Utilities/cmcurl/lib/curlx/inet_pton.c new file mode 100644 index 00000000000..d2b39ae9f1a --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/inet_pton.c @@ -0,0 +1,232 @@ +/* This is from the BIND 4.9.4 release, modified to compile by itself */ + +/* Copyright (c) Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * SPDX-License-Identifier: ISC + */ + +#include "../curl_setup.h" +#include "../curl_ctype.h" +#include "strparse.h" + +#ifndef HAVE_INET_PTON + +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#include "inet_pton.h" + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 +#define INT16SZ 2 + +/* + * 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 + * everywhere. + */ +#if !defined(USE_IPV6) && !defined(AF_INET6) +#define AF_INET6 (AF_INET + 1) +#endif + +/* + * WARNING: Do not even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +static int inet_pton6(const char *src, unsigned char *dst); + +/* int + * inet_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address was not valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * notice: + * On Windows we store the error in the thread errno, not + * in the Winsock error code. This is to avoid losing the + * actual last Winsock error. When this function returns + * -1, check errno not SOCKERRNO. + * author: + * Paul Vixie, 1996. + */ +int +curlx_inet_pton(int af, const char *src, void *dst) +{ + switch(af) { + case AF_INET: + return inet_pton4(src, (unsigned char *)dst); + case AF_INET6: + return inet_pton6(src, (unsigned char *)dst); + default: + CURL_SETERRNO(SOCKEAFNOSUPPORT); + return -1; + } + /* NOTREACHED */ +} + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it is returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + int saw_digit, octets, ch; + unsigned char tmp[INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + tp = tmp; + *tp = 0; + while((ch = *src++) != '\0') { + if(ISDIGIT(ch)) { + unsigned int val = (*tp * 10) + (ch - '0'); + + if(saw_digit && *tp == 0) + return 0; + if(val > 255) + return 0; + *tp = (unsigned char)val; + if(!saw_digit) { + if(++octets > 4) + return 0; + saw_digit = 1; + } + } + else if(ch == '.' && saw_digit) { + if(octets == 4) + return 0; + *++tp = 0; + saw_digit = 0; + } + else + return 0; + } + if(octets < 4) + return 0; + memcpy(dst, tmp, INADDRSZ); + return 1; +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it is returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton6(const char *src, unsigned char *dst) +{ + unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *curtok; + int ch, saw_xdigit; + size_t val; + + memset((tp = tmp), 0, IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if(*src == ':') + if(*++src != ':') + return 0; + curtok = src; + saw_xdigit = 0; + val = 0; + while((ch = *src++) != '\0') { + if(ISXDIGIT(ch)) { + val <<= 4; + val |= Curl_hexval(ch); + if(++saw_xdigit > 4) + return 0; + continue; + } + if(ch == ':') { + curtok = src; + if(!saw_xdigit) { + if(colonp) + return 0; + colonp = tp; + continue; + } + if(tp + INT16SZ > endp) + return 0; + *tp++ = (unsigned char) ((val >> 8) & 0xff); + *tp++ = (unsigned char) (val & 0xff); + saw_xdigit = 0; + val = 0; + continue; + } + if(ch == '.' && ((tp + INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return 0; + } + if(saw_xdigit) { + if(tp + INT16SZ > endp) + return 0; + *tp++ = (unsigned char) ((val >> 8) & 0xff); + *tp++ = (unsigned char) (val & 0xff); + } + if(colonp) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we will do the shift by hand. + */ + const ssize_t n = tp - colonp; + ssize_t i; + + if(tp == endp) + return 0; + for(i = 1; i <= n; i++) { + *(endp - i) = *(colonp + n - i); + *(colonp + n - i) = 0; + } + tp = endp; + } + if(tp != endp) + return 0; + memcpy(dst, tmp, IN6ADDRSZ); + return 1; +} + +#endif /* HAVE_INET_PTON */ diff --git a/Utilities/cmcurl/lib/inet_pton.h b/Utilities/cmcurl/lib/curlx/inet_pton.h similarity index 78% rename from Utilities/cmcurl/lib/inet_pton.h rename to Utilities/cmcurl/lib/curlx/inet_pton.h index 82fde7e2eb5..a9dc43085f4 100644 --- a/Utilities/cmcurl/lib/inet_pton.h +++ b/Utilities/cmcurl/lib/curlx/inet_pton.h @@ -24,18 +24,25 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -int Curl_inet_pton(int, const char *, void *); +int curlx_inet_pton(int, const char *, void *); #ifdef HAVE_INET_PTON +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif #ifdef HAVE_ARPA_INET_H #include -#elif defined(HAVE_WS2TCPIP_H) -/* inet_pton() exists in Vista or later */ -#include #endif -#define Curl_inet_pton(x,y,z) inet_pton(x,y,z) +#ifdef __AMIGA__ +#define curlx_inet_pton(x,y,z) inet_pton(x,(unsigned char *)CURL_UNCONST(y),z) +#else +#define curlx_inet_pton(x,y,z) inet_pton(x,y,z) +#endif #endif #endif /* HEADER_CURL_INET_PTON_H */ diff --git a/Utilities/cmcurl/lib/curlx/multibyte.c b/Utilities/cmcurl/lib/curlx/multibyte.c new file mode 100644 index 00000000000..30380275cc7 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/multibyte.c @@ -0,0 +1,360 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + * + ***************************************************************************/ + +/* + * This file is 'mem-include-scan' clean, which means its memory allocations + * are not tracked by the curl memory tracker memdebug, so they must not use + * `CURLDEBUG` macro replacements in memdebug.h for free, malloc, etc. To avoid + * these macro replacements, wrap the names in parentheses to call the original + * versions: `ptr = (malloc)(123)`, `(free)(ptr)`, etc. + */ + +#include "../curl_setup.h" + +#ifdef _WIN32 + +#include "multibyte.h" + +/* + * MultiByte conversions using Windows kernel32 library. + */ + +wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) +{ + wchar_t *str_w = NULL; + + if(str_utf8) { + int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + str_utf8, -1, NULL, 0); + if(str_w_len > 0) { + str_w = (malloc)(str_w_len * sizeof(wchar_t)); + if(str_w) { + if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, + str_w_len) == 0) { + (free)(str_w); + return NULL; + } + } + } + } + + return str_w; +} + +char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w) +{ + char *str_utf8 = NULL; + + if(str_w) { + int bytes = WideCharToMultiByte(CP_UTF8, 0, str_w, -1, + NULL, 0, NULL, NULL); + if(bytes > 0) { + str_utf8 = (malloc)(bytes); + if(str_utf8) { + if(WideCharToMultiByte(CP_UTF8, 0, str_w, -1, str_utf8, bytes, + NULL, NULL) == 0) { + (free)(str_utf8); + return NULL; + } + } + } + } + + return str_utf8; +} + +#ifndef UNDER_CE + +/* declare GetFullPathNameW for mingw-w64 UWP builds targeting old windows */ +#if defined(CURL_WINDOWS_UWP) && defined(__MINGW32__) && \ + (_WIN32_WINNT < _WIN32_WINNT_WIN10) +WINBASEAPI DWORD WINAPI GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR *); +#endif + +/* Fix excessive paths (paths that exceed MAX_PATH length of 260). + * + * This is a helper function to fix paths that would exceed the MAX_PATH + * limitation check done by Windows APIs. It does so by normalizing the passed + * in filename or path 'in' to its full canonical path, and if that path is + * longer than MAX_PATH then setting 'out' to "\\?\" prefix + that full path. + * + * For example 'in' filename255chars in current directory C:\foo\bar is + * fixed as \\?\C:\foo\bar\filename255chars for 'out' which will tell Windows + * it is ok to access that filename even though the actual full path is longer + * than 260 chars. + * + * For non-Unicode builds this function may fail sometimes because only the + * Unicode versions of some Windows API functions can access paths longer than + * MAX_PATH, for example GetFullPathNameW which is used in this function. When + * the full path is then converted from Unicode to multibyte that fails if any + * directories in the path contain characters not in the current codepage. + */ +static bool fix_excessive_path(const TCHAR *in, TCHAR **out) +{ + size_t needed, count; + const wchar_t *in_w; + wchar_t *fbuf = NULL; + + /* MS documented "approximate" limit for the maximum path length */ + const size_t max_path_len = 32767; + +#ifndef _UNICODE + wchar_t *ibuf = NULL; + char *obuf = NULL; +#endif + + *out = NULL; + + /* skip paths already normalized */ + if(!_tcsncmp(in, _T("\\\\?\\"), 4)) + goto cleanup; + +#ifndef _UNICODE + /* convert multibyte input to unicode */ + needed = mbstowcs(NULL, in, 0); + if(needed == (size_t)-1 || needed >= max_path_len) + goto cleanup; + ++needed; /* for NUL */ + ibuf = (malloc)(needed * sizeof(wchar_t)); + if(!ibuf) + goto cleanup; + count = mbstowcs(ibuf, in, needed); + if(count == (size_t)-1 || count >= needed) + goto cleanup; + in_w = ibuf; +#else + in_w = in; +#endif + + /* GetFullPathNameW returns the normalized full path in unicode. It converts + forward slashes to backslashes, processes .. to remove directory segments, + etc. Unlike GetFullPathNameA it can process paths that exceed MAX_PATH. */ + needed = (size_t)GetFullPathNameW(in_w, 0, NULL, NULL); + if(!needed || needed > max_path_len) + goto cleanup; + /* skip paths that are not excessive and do not need modification */ + if(needed <= MAX_PATH) + goto cleanup; + fbuf = (malloc)(needed * sizeof(wchar_t)); + if(!fbuf) + goto cleanup; + count = (size_t)GetFullPathNameW(in_w, (DWORD)needed, fbuf, NULL); + if(!count || count >= needed) + goto cleanup; + + /* prepend \\?\ or \\?\UNC\ to the excessively long path. + * + * c:\longpath ---> \\?\c:\longpath + * \\.\c:\longpath ---> \\?\c:\longpath + * \\?\c:\longpath ---> \\?\c:\longpath (unchanged) + * \\server\c$\longpath ---> \\?\UNC\server\c$\longpath + * + * https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats + */ + if(!wcsncmp(fbuf, L"\\\\?\\", 4)) + ; /* do nothing */ + else if(!wcsncmp(fbuf, L"\\\\.\\", 4)) + fbuf[2] = '?'; + else if(!wcsncmp(fbuf, L"\\\\.", 3) || !wcsncmp(fbuf, L"\\\\?", 3)) { + /* Unexpected, not UNC. The formatting doc doesn't allow this AFAICT. */ + goto cleanup; + } + else { + wchar_t *temp; + + if(!wcsncmp(fbuf, L"\\\\", 2)) { + /* "\\?\UNC\" + full path without "\\" + null */ + needed = 8 + (count - 2) + 1; + if(needed > max_path_len) + goto cleanup; + + temp = (malloc)(needed * sizeof(wchar_t)); + if(!temp) + goto cleanup; + + wcsncpy(temp, L"\\\\?\\UNC\\", 8); + wcscpy(temp + 8, fbuf + 2); + } + else { + /* "\\?\" + full path + null */ + needed = 4 + count + 1; + if(needed > max_path_len) + goto cleanup; + + temp = (malloc)(needed * sizeof(wchar_t)); + if(!temp) + goto cleanup; + + wcsncpy(temp, L"\\\\?\\", 4); + wcscpy(temp + 4, fbuf); + } + + (free)(fbuf); + fbuf = temp; + } + +#ifndef _UNICODE + /* convert unicode full path to multibyte output */ + needed = wcstombs(NULL, fbuf, 0); + if(needed == (size_t)-1 || needed >= max_path_len) + goto cleanup; + ++needed; /* for NUL */ + obuf = (malloc)(needed); + if(!obuf) + goto cleanup; + count = wcstombs(obuf, fbuf, needed); + if(count == (size_t)-1 || count >= needed) + goto cleanup; + *out = obuf; + obuf = NULL; +#else + *out = fbuf; + fbuf = NULL; +#endif + +cleanup: + (free)(fbuf); +#ifndef _UNICODE + (free)(ibuf); + (free)(obuf); +#endif + return *out ? true : false; +} + +int curlx_win32_open(const char *filename, int oflag, ...) +{ + int pmode = 0; + int result = -1; + TCHAR *fixed = NULL; + const TCHAR *target = NULL; + +#ifdef _UNICODE + wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename); +#endif + + va_list param; + va_start(param, oflag); + if(oflag & O_CREAT) + pmode = va_arg(param, int); + va_end(param); + +#ifdef _UNICODE + if(filename_w) { + if(fix_excessive_path(filename_w, &fixed)) + target = fixed; + else + target = filename_w; + result = _wopen(target, oflag, pmode); + curlx_unicodefree(filename_w); + } + else + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(EINVAL); +#else + if(fix_excessive_path(filename, &fixed)) + target = fixed; + else + target = filename; + result = _open(target, oflag, pmode); +#endif + + (free)(fixed); + return result; +} + +FILE *curlx_win32_fopen(const char *filename, const char *mode) +{ + FILE *result = NULL; + TCHAR *fixed = NULL; + const TCHAR *target = NULL; + +#ifdef _UNICODE + wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename); + wchar_t *mode_w = curlx_convert_UTF8_to_wchar(mode); + if(filename_w && mode_w) { + if(fix_excessive_path(filename_w, &fixed)) + target = fixed; + else + target = filename_w; + result = _wfopen(target, mode_w); + } + else + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(EINVAL); + curlx_unicodefree(filename_w); + curlx_unicodefree(mode_w); +#else + if(fix_excessive_path(filename, &fixed)) + target = fixed; + else + target = filename; + result = (fopen)(target, mode); +#endif + + (free)(fixed); + return result; +} + +int curlx_win32_stat(const char *path, struct_stat *buffer) +{ + int result = -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; +#ifndef USE_WIN32_LARGE_FILES + result = _wstat(target, buffer); +#else + result = _wstati64(target, buffer); +#endif + curlx_unicodefree(path_w); + } + else + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(EINVAL); +#else + if(fix_excessive_path(path, &fixed)) + target = fixed; + else + target = path; +#ifndef USE_WIN32_LARGE_FILES + result = _stat(target, buffer); +#else + result = _stati64(target, buffer); +#endif +#endif + + (free)(fixed); + return result; +} + +#endif /* UNDER_CE */ + +#endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/curlx/multibyte.h b/Utilities/cmcurl/lib/curlx/multibyte.h new file mode 100644 index 00000000000..7835fdc3b5e --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/multibyte.h @@ -0,0 +1,87 @@ +#ifndef HEADER_CURL_MULTIBYTE_H +#define HEADER_CURL_MULTIBYTE_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" + +#ifdef _WIN32 + + /* + * MultiByte conversions using Windows kernel32 library. + */ + +wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8); +char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w); +#endif /* _WIN32 */ + +/* + * Macros curlx_convert_UTF8_to_tchar(), curlx_convert_tchar_to_UTF8() + * and curlx_unicodefree() main purpose is to minimize the number of + * preprocessor conditional directives needed by code using these + * to differentiate Unicode from non-Unicode builds. + * + * In the case of a non-Unicode build the tchar strings are char strings that + * are duplicated via strdup and remain in whatever the passed in encoding is, + * which is assumed to be UTF-8 but may be other encoding. Therefore the + * significance of the conversion functions is primarily for Unicode builds. + * + * Allocated memory should be free'd with curlx_unicodefree(). + * + * Note: Because these are curlx functions their memory usage is not tracked + * by the curl memory tracker memdebug. you will notice that curlx + * function-like macros call free and strdup in parentheses, eg (strdup)(ptr), + * and that is to ensure that the curl memdebug override macros do not replace + * them. + */ + +#if defined(UNICODE) && defined(_WIN32) + +#define curlx_convert_UTF8_to_tchar(ptr) curlx_convert_UTF8_to_wchar((ptr)) +#define curlx_convert_tchar_to_UTF8(ptr) curlx_convert_wchar_to_UTF8((ptr)) + +typedef union { + unsigned short *tchar_ptr; + const unsigned short *const_tchar_ptr; + unsigned short *tbyte_ptr; + const unsigned short *const_tbyte_ptr; +} xcharp_u; + +#else + +#define curlx_convert_UTF8_to_tchar(ptr) (strdup)(ptr) +#define curlx_convert_tchar_to_UTF8(ptr) (strdup)(ptr) + +typedef union { + char *tchar_ptr; + const char *const_tchar_ptr; + unsigned char *tbyte_ptr; + const unsigned char *const_tbyte_ptr; +} xcharp_u; + +#endif /* UNICODE && _WIN32 */ + +/* the purpose of this macro is to free() without being traced by memdebug */ +#define curlx_unicodefree(ptr) (free)(CURL_UNCONST(ptr)) + +#endif /* HEADER_CURL_MULTIBYTE_H */ diff --git a/Utilities/cmcurl/lib/nonblock.c b/Utilities/cmcurl/lib/curlx/nonblock.c similarity index 82% rename from Utilities/cmcurl/lib/nonblock.c rename to Utilities/cmcurl/lib/curlx/nonblock.c index f4eb6561280..409b187f1fd 100644 --- a/Utilities/cmcurl/lib/nonblock.c +++ b/Utilities/cmcurl/lib/curlx/nonblock.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef HAVE_SYS_IOCTL_H #include @@ -47,16 +47,31 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ int nonblock /* TRUE or FALSE */) { #if defined(HAVE_FCNTL_O_NONBLOCK) - /* most recent unix versions */ + /* most recent Unix versions */ int flags; flags = sfcntl(sockfd, F_GETFL, 0); + if(flags < 0) + return -1; + /* Check if the current file status flags have already satisfied + * the request, if so, it is no need to call fcntl() to replicate it. + */ + if(!!(flags & O_NONBLOCK) == !!nonblock) + return 0; if(nonblock) - return sfcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - return sfcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + return sfcntl(sockfd, F_SETFL, flags); + +#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) + + /* Amiga */ + long flags = nonblock ? 1L : 0L; + return IoctlSocket(sockfd, FIONBIO, (char *)&flags); #elif defined(HAVE_IOCTL_FIONBIO) - /* older unix versions */ + /* older Unix versions */ int flags = nonblock ? 1 : 0; return ioctl(sockfd, FIONBIO, &flags); @@ -64,13 +79,7 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ /* Windows */ unsigned long flags = nonblock ? 1UL : 0UL; - return ioctlsocket(sockfd, FIONBIO, &flags); - -#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) - - /* Amiga */ - long flags = nonblock ? 1L : 0L; - return IoctlSocket(sockfd, FIONBIO, (char *)&flags); + return ioctlsocket(sockfd, (long)FIONBIO, &flags); #elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK) diff --git a/Utilities/cmcurl/lib/nonblock.h b/Utilities/cmcurl/lib/curlx/nonblock.h similarity index 100% rename from Utilities/cmcurl/lib/nonblock.h rename to Utilities/cmcurl/lib/curlx/nonblock.h diff --git a/Utilities/cmcurl/lib/curlx/strparse.c b/Utilities/cmcurl/lib/curlx/strparse.c new file mode 100644 index 00000000000..b8b2a14d3cb --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/strparse.c @@ -0,0 +1,303 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "strparse.h" +#include "../strcase.h" + +void curlx_str_init(struct Curl_str *out) +{ + out->str = NULL; + out->len = 0; +} + +void curlx_str_assign(struct Curl_str *out, const char *str, size_t len) +{ + out->str = str; + out->len = len; +} + +/* Get a word until the first DELIM or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_until(const char **linep, struct Curl_str *out, + const size_t max, char delim) +{ + const char *s = *linep; + size_t len = 0; + DEBUGASSERT(linep && *linep && out && max && delim); + + curlx_str_init(out); + while(*s && (*s != delim)) { + s++; + if(++len > max) { + return STRE_BIG; + } + } + if(!len) + return STRE_SHORT; + out->str = *linep; + out->len = len; + *linep = s; /* point to the first byte after the word */ + return STRE_OK; +} + +/* Get a word until the first space or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_word(const char **linep, struct Curl_str *out, + const size_t max) +{ + return curlx_str_until(linep, out, max, ' '); +} + +/* Get a word until a newline byte or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_untilnl(const char **linep, struct Curl_str *out, + const size_t max) +{ + const char *s = *linep; + size_t len = 0; + DEBUGASSERT(linep && *linep && out && max); + + curlx_str_init(out); + while(*s && !ISNEWLINE(*s)) { + s++; + if(++len > max) + return STRE_BIG; + } + if(!len) + return STRE_SHORT; + out->str = *linep; + out->len = len; + *linep = s; /* point to the first byte after the word */ + return STRE_OK; +} + + +/* Get a "quoted" word. No escaping possible. + return non-zero on error */ +int curlx_str_quotedword(const char **linep, struct Curl_str *out, + const size_t max) +{ + const char *s = *linep; + size_t len = 0; + DEBUGASSERT(linep && *linep && out && max); + + curlx_str_init(out); + if(*s != '\"') + return STRE_BEGQUOTE; + s++; + while(*s && (*s != '\"')) { + s++; + if(++len > max) + return STRE_BIG; + } + if(*s != '\"') + return STRE_ENDQUOTE; + out->str = (*linep) + 1; + out->len = len; + *linep = s + 1; + return STRE_OK; +} + +/* Advance over a single character. + return non-zero on error */ +int curlx_str_single(const char **linep, char byte) +{ + DEBUGASSERT(linep && *linep); + if(**linep != byte) + return STRE_BYTE; + (*linep)++; /* move over it */ + return STRE_OK; +} + +/* Advance over a single space. + return non-zero on error */ +int curlx_str_singlespace(const char **linep) +{ + return curlx_str_single(linep, ' '); +} + +/* given an ASCII character and max ascii, return TRUE if valid */ +#define valid_digit(x,m) \ + (((x) >= '0') && ((x) <= m) && Curl_hexasciitable[(x)-'0']) + +/* We use 16 for the zero index (and the necessary bitwise AND in the loop) + to be able to have a non-zero value there to make valid_digit() able to + use the info */ +const unsigned char Curl_hexasciitable[] = { + 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, /* 0x30: 0 - 9 */ + 0, 0, 0, 0, 0, 0, 0, + 10, 11, 12, 13, 14, 15, /* 0x41: A - F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 10, 11, 12, 13, 14, 15 /* 0x61: a - f */ +}; + +/* no support for 0x prefix nor leading spaces */ +static int str_num_base(const char **linep, curl_off_t *nump, curl_off_t max, + int base) /* 8, 10 or 16, nothing else */ +{ + curl_off_t num = 0; + const char *p; + int m = (base == 10) ? '9' : /* the largest digit possible */ + (base == 16) ? 'f' : '7'; + DEBUGASSERT(linep && *linep && nump); + DEBUGASSERT((base == 8) || (base == 10) || (base == 16)); + DEBUGASSERT(max >= 0); /* mostly to catch SIZE_T_MAX, which is too large */ + *nump = 0; + p = *linep; + if(!valid_digit(*p, m)) + return STRE_NO_NUM; + if(max < base) { + /* special-case low max scenario because check needs to be different */ + do { + int n = Curl_hexval(*p++); + num = num * base + n; + if(num > max) + return STRE_OVERFLOW; + } while(valid_digit(*p, m)); + } + else { + do { + int n = Curl_hexval(*p++); + if(num > ((max - n) / base)) + return STRE_OVERFLOW; + num = num * base + n; + } while(valid_digit(*p, m)); + } + *nump = num; + *linep = p; + return STRE_OK; +} + +/* Get an unsigned decimal number with no leading space or minus. Leading + zeroes are accepted. return non-zero on error */ +int curlx_str_number(const char **linep, curl_off_t *nump, curl_off_t max) +{ + return str_num_base(linep, nump, max, 10); +} + +/* Get an unsigned hexadecimal number with no leading space or minus and no + "0x" support. Leading zeroes are accepted. return non-zero on error */ +int curlx_str_hex(const char **linep, curl_off_t *nump, curl_off_t max) +{ + return str_num_base(linep, nump, max, 16); +} + +/* Get an unsigned octal number with no leading space or minus and no "0" + prefix support. Leading zeroes are accepted. return non-zero on error */ +int curlx_str_octal(const char **linep, curl_off_t *nump, curl_off_t max) +{ + return str_num_base(linep, nump, max, 8); +} + +/* + * Parse a positive number up to 63-bit number written in ASCII. Skip leading + * blanks. No support for prefixes. + */ +int curlx_str_numblanks(const char **str, curl_off_t *num) +{ + curlx_str_passblanks(str); + return curlx_str_number(str, num, CURL_OFF_T_MAX); +} + +/* CR or LF + return non-zero on error */ +int curlx_str_newline(const char **linep) +{ + DEBUGASSERT(linep && *linep); + if(ISNEWLINE(**linep)) { + (*linep)++; + return STRE_OK; /* yessir */ + } + return STRE_NEWLINE; +} + +#ifndef WITHOUT_LIBCURL +/* case insensitive compare that the parsed string matches the given string. + Returns non-zero on match. */ +int curlx_str_casecompare(struct Curl_str *str, const char *check) +{ + size_t clen = check ? strlen(check) : 0; + return ((str->len == clen) && strncasecompare(str->str, check, clen)); +} +#endif + +/* case sensitive string compare. Returns non-zero on match. */ +int curlx_str_cmp(struct Curl_str *str, const char *check) +{ + if(check) { + size_t clen = strlen(check); + return ((str->len == clen) && !strncmp(str->str, check, clen)); + } + return !!(str->len); +} + +/* Trim off 'num' number of bytes from the beginning (left side) of the + string. If 'num' is larger than the string, return error. */ +int curlx_str_nudge(struct Curl_str *str, size_t num) +{ + if(num <= str->len) { + str->str += num; + str->len -= num; + return STRE_OK; + } + return STRE_OVERFLOW; +} + +/* Get the following character sequence that consists only of bytes not + present in the 'reject' string. Like strcspn(). */ +int curlx_str_cspn(const char **linep, struct Curl_str *out, + const char *reject) +{ + const char *s = *linep; + size_t len; + DEBUGASSERT(linep && *linep); + + len = strcspn(s, reject); + if(len) { + out->str = s; + out->len = len; + *linep = &s[len]; + return STRE_OK; + } + curlx_str_init(out); + return STRE_SHORT; +} + +/* remove ISBLANK()s from both ends of the string */ +void curlx_str_trimblanks(struct Curl_str *out) +{ + while(out->len && ISBLANK(*out->str)) + curlx_str_nudge(out, 1); + + /* trim trailing spaces and tabs */ + while(out->len && ISBLANK(out->str[out->len - 1])) + out->len--; +} + +/* increase the pointer until it has moved over all blanks */ +void curlx_str_passblanks(const char **linep) +{ + while(ISBLANK(**linep)) + (*linep)++; /* move over it */ +} diff --git a/Utilities/cmcurl/lib/curlx/strparse.h b/Utilities/cmcurl/lib/curlx/strparse.h new file mode 100644 index 00000000000..17bfdb80710 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/strparse.h @@ -0,0 +1,112 @@ +#ifndef HEADER_CURL_STRPARSE_H +#define HEADER_CURL_STRPARSE_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" + +#define STRE_OK 0 +#define STRE_BIG 1 +#define STRE_SHORT 2 +#define STRE_BEGQUOTE 3 +#define STRE_ENDQUOTE 4 +#define STRE_BYTE 5 +#define STRE_NEWLINE 6 +#define STRE_OVERFLOW 7 +#define STRE_NO_NUM 8 + +/* public struct, but all accesses should be done using the provided + functions */ +struct Curl_str { + const char *str; + size_t len; +}; + +void curlx_str_init(struct Curl_str *out); +void curlx_str_assign(struct Curl_str *out, const char *str, size_t len); + +#define curlx_str(x) ((x)->str) +#define curlx_strlen(x) ((x)->len) + +/* Get a word until the first space + return non-zero on error */ +int curlx_str_word(const char **linep, struct Curl_str *out, const size_t max); + +/* Get a word until the first DELIM or end of string + return non-zero on error */ +int curlx_str_until(const char **linep, struct Curl_str *out, const size_t max, + char delim); + +/* Get a word until a newline byte or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_untilnl(const char **linep, struct Curl_str *out, + const size_t max); + +/* Get a "quoted" word. No escaping possible. + return non-zero on error */ +int curlx_str_quotedword(const char **linep, struct Curl_str *out, + const size_t max); + +/* Advance over a single character. + return non-zero on error */ +int curlx_str_single(const char **linep, char byte); + +/* Advance over a single space. + return non-zero on error */ +int curlx_str_singlespace(const char **linep); + +/* Get an unsigned decimal number. Return non-zero on error */ +int curlx_str_number(const char **linep, curl_off_t *nump, curl_off_t max); + +/* As above with CURL_OFF_T_MAX but also pass leading blanks */ +int curlx_str_numblanks(const char **str, curl_off_t *num); + +/* Get an unsigned hexadecimal number. Return non-zero on error */ +int curlx_str_hex(const char **linep, curl_off_t *nump, curl_off_t max); + +/* Get an unsigned octal number. Return non-zero on error */ +int curlx_str_octal(const char **linep, curl_off_t *nump, curl_off_t max); + +/* Check for CR or LF + return non-zero on error */ +int curlx_str_newline(const char **linep); + +/* case insensitive compare that the parsed string matches the + given string. */ +int curlx_str_casecompare(struct Curl_str *str, const char *check); +int curlx_str_cmp(struct Curl_str *str, const char *check); + +int curlx_str_nudge(struct Curl_str *str, size_t num); + +int curlx_str_cspn(const char **linep, struct Curl_str *out, const char *cspn); +void curlx_str_trimblanks(struct Curl_str *out); +void curlx_str_passblanks(const char **linep); + +/* given a hexadecimal letter, return the binary value. '0' returns 0, 'a' + returns 10. THIS ONLY WORKS ON VALID HEXADECIMAL LETTER INPUT. Verify + before calling this! +*/ +extern const unsigned char Curl_hexasciitable[]; +#define Curl_hexval(x) (unsigned char)(Curl_hexasciitable[(x) - '0'] & 0x0f) + +#endif /* HEADER_CURL_STRPARSE_H */ diff --git a/Utilities/cmcurl/lib/timediff.c b/Utilities/cmcurl/lib/curlx/timediff.c similarity index 92% rename from Utilities/cmcurl/lib/timediff.c rename to Utilities/cmcurl/lib/curlx/timediff.c index 1b762bbd3ed..a90da961ab8 100644 --- a/Utilities/cmcurl/lib/timediff.c +++ b/Utilities/cmcurl/lib/curlx/timediff.c @@ -44,7 +44,7 @@ struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms) if(ms > 0) { timediff_t tv_sec = ms / 1000; - timediff_t tv_usec = (ms % 1000) * 1000; /* max=999999 */ + timediff_t tv_usec = (ms % 1000) * 1000; /* max=999000 */ #ifdef HAVE_SUSECONDS_T #if TIMEDIFF_T_MAX > TIME_T_MAX /* tv_sec overflow check in case time_t is signed */ @@ -53,7 +53,7 @@ struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms) #endif tv->tv_sec = (time_t)tv_sec; tv->tv_usec = (suseconds_t)tv_usec; -#elif defined(WIN32) /* maybe also others in the future */ +#elif defined(_WIN32) /* maybe also others in the future */ #if TIMEDIFF_T_MAX > LONG_MAX /* tv_sec overflow check on Windows there we know it is long */ if(tv_sec > LONG_MAX) @@ -84,5 +84,5 @@ struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms) */ timediff_t curlx_tvtoms(struct timeval *tv) { - return (tv->tv_sec*1000) + (timediff_t)(((double)tv->tv_usec)/1000.0); + return (tv->tv_sec*1000) + (timediff_t)(tv->tv_usec/1000); } diff --git a/Utilities/cmcurl/lib/timediff.h b/Utilities/cmcurl/lib/curlx/timediff.h similarity index 92% rename from Utilities/cmcurl/lib/timediff.h rename to Utilities/cmcurl/lib/curlx/timediff.h index fb318d4f2b0..aa224381dd9 100644 --- a/Utilities/cmcurl/lib/timediff.h +++ b/Utilities/cmcurl/lib/curlx/timediff.h @@ -24,12 +24,12 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -/* Use a larger type even for 32 bit time_t systems so that we can keep +/* Use a larger type even for 32-bit time_t systems so that we can keep microsecond accuracy in it */ typedef curl_off_t timediff_t; -#define CURL_FORMAT_TIMEDIFF_T CURL_FORMAT_CURL_OFF_T +#define FMT_TIMEDIFF_T FMT_OFF_T #define TIMEDIFF_T_MAX CURL_OFF_T_MAX #define TIMEDIFF_T_MIN CURL_OFF_T_MIN diff --git a/Utilities/cmcurl/lib/curlx/timeval.c b/Utilities/cmcurl/lib/curlx/timeval.c new file mode 100644 index 00000000000..501bf9c3fd9 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/timeval.c @@ -0,0 +1,259 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "timeval.h" + +#ifdef _WIN32 + +#include +#include "version_win32.h" +#include "../system_win32.h" + +LARGE_INTEGER Curl_freq; +bool Curl_isVistaOrGreater; + +/* For tool or tests, we must initialize before calling curlx_now(). + Providing this function here is wrong. */ +void curlx_now_init(void) +{ + if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + Curl_isVistaOrGreater = true; + else + Curl_isVistaOrGreater = false; + + QueryPerformanceFrequency(&Curl_freq); +} + +/* In case of bug fix this function has a counterpart in tool_util.c */ +struct curltime curlx_now(void) +{ + struct curltime now; + bool isVistaOrGreater; + isVistaOrGreater = Curl_isVistaOrGreater; + if(isVistaOrGreater) { /* QPC timer might have issues pre-Vista */ + LARGE_INTEGER count; + LARGE_INTEGER freq; + freq = Curl_freq; + DEBUGASSERT(freq.QuadPart); + QueryPerformanceCounter(&count); + now.tv_sec = (time_t)(count.QuadPart / freq.QuadPart); + now.tv_usec = (int)((count.QuadPart % freq.QuadPart) * 1000000 / + freq.QuadPart); + } + else { + /* Disable /analyze warning that GetTickCount64 is preferred */ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:28159) +#endif + DWORD milliseconds = GetTickCount(); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + now.tv_sec = (time_t)(milliseconds / 1000); + now.tv_usec = (int)((milliseconds % 1000) * 1000); + } + return now; +} + +#elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) || \ + defined(HAVE_CLOCK_GETTIME_MONOTONIC_RAW) + +struct curltime curlx_now(void) +{ + /* + ** clock_gettime() is granted to be increased monotonically when the + ** monotonic clock is queried. Time starting point is unspecified, it + ** could be the system start-up time, the Epoch, or something else, + ** in any case the time starting point does not change once that the + ** system has started up. + */ +#ifdef HAVE_GETTIMEOFDAY + struct timeval now; +#endif + struct curltime cnow; + struct timespec tsnow; + + /* + ** clock_gettime() may be defined by Apple's SDK as weak symbol thus + ** code compiles but fails during runtime if clock_gettime() is + ** called on unsupported OS version. + */ +#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + (HAVE_BUILTIN_AVAILABLE == 1) + bool have_clock_gettime = FALSE; + if(__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) + have_clock_gettime = TRUE; +#endif + +#ifdef HAVE_CLOCK_GETTIME_MONOTONIC_RAW + if( +#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + (HAVE_BUILTIN_AVAILABLE == 1) + have_clock_gettime && +#endif + (0 == clock_gettime(CLOCK_MONOTONIC_RAW, &tsnow))) { + cnow.tv_sec = tsnow.tv_sec; + cnow.tv_usec = (int)(tsnow.tv_nsec / 1000); + } + else +#endif + + if( +#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + (HAVE_BUILTIN_AVAILABLE == 1) + have_clock_gettime && +#endif + (0 == clock_gettime(CLOCK_MONOTONIC, &tsnow))) { + cnow.tv_sec = tsnow.tv_sec; + cnow.tv_usec = (int)(tsnow.tv_nsec / 1000); + } + /* + ** Even when the configure process has truly detected monotonic clock + ** availability, it might happen that it is not actually available at + ** runtime. When this occurs simply fallback to other time source. + */ +#ifdef HAVE_GETTIMEOFDAY + else { + (void)gettimeofday(&now, NULL); + cnow.tv_sec = now.tv_sec; + cnow.tv_usec = (int)now.tv_usec; + } +#else + else { + cnow.tv_sec = time(NULL); + cnow.tv_usec = 0; + } +#endif + return cnow; +} + +#elif defined(HAVE_MACH_ABSOLUTE_TIME) + +#include +#include + +struct curltime curlx_now(void) +{ + /* + ** Monotonic timer on macOS is provided by mach_absolute_time(), which + ** returns time in Mach "absolute time units," which are platform-dependent. + ** To convert to nanoseconds, one must use conversion factors specified by + ** mach_timebase_info(). + */ + static mach_timebase_info_data_t timebase; + struct curltime cnow; + uint64_t usecs; + + if(0 == timebase.denom) + (void) mach_timebase_info(&timebase); + + usecs = mach_absolute_time(); + usecs *= timebase.numer; + usecs /= timebase.denom; + usecs /= 1000; + + cnow.tv_sec = usecs / 1000000; + cnow.tv_usec = (int)(usecs % 1000000); + + return cnow; +} + +#elif defined(HAVE_GETTIMEOFDAY) + +struct curltime curlx_now(void) +{ + /* + ** gettimeofday() is not granted to be increased monotonically, due to + ** clock drifting and external source time synchronization it can jump + ** forward or backward in time. + */ + struct timeval now; + struct curltime ret; + (void)gettimeofday(&now, NULL); + ret.tv_sec = now.tv_sec; + ret.tv_usec = (int)now.tv_usec; + return ret; +} + +#else + +struct curltime curlx_now(void) +{ + /* + ** time() returns the value of time in seconds since the Epoch. + */ + struct curltime now; + now.tv_sec = time(NULL); + now.tv_usec = 0; + return now; +} + +#endif + +/* + * Returns: time difference in number of milliseconds. For too large diffs it + * returns max value. + * + * @unittest: 1323 + */ +timediff_t curlx_timediff(struct curltime newer, struct curltime older) +{ + timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; + if(diff >= (TIMEDIFF_T_MAX/1000)) + return TIMEDIFF_T_MAX; + else if(diff <= (TIMEDIFF_T_MIN/1000)) + return TIMEDIFF_T_MIN; + return diff * 1000 + (newer.tv_usec-older.tv_usec)/1000; +} + +/* + * Returns: time difference in number of milliseconds, rounded up. + * For too large diffs it returns max value. + */ +timediff_t curlx_timediff_ceil(struct curltime newer, struct curltime older) +{ + timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; + if(diff >= (TIMEDIFF_T_MAX/1000)) + return TIMEDIFF_T_MAX; + else if(diff <= (TIMEDIFF_T_MIN/1000)) + return TIMEDIFF_T_MIN; + return diff * 1000 + (newer.tv_usec - older.tv_usec + 999)/1000; +} + +/* + * Returns: time difference in number of microseconds. For too large diffs it + * returns max value. + */ +timediff_t curlx_timediff_us(struct curltime newer, struct curltime older) +{ + timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; + if(diff >= (TIMEDIFF_T_MAX/1000000)) + return TIMEDIFF_T_MAX; + else if(diff <= (TIMEDIFF_T_MIN/1000000)) + return TIMEDIFF_T_MIN; + return diff * 1000000 + newer.tv_usec-older.tv_usec; +} diff --git a/Utilities/cmcurl/lib/curlx/timeval.h b/Utilities/cmcurl/lib/curlx/timeval.h new file mode 100644 index 00000000000..1f8fe5e8ad1 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/timeval.h @@ -0,0 +1,67 @@ +#ifndef HEADER_CURL_TIMEVAL_H +#define HEADER_CURL_TIMEVAL_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" + +#include "timediff.h" + +struct curltime { + time_t tv_sec; /* seconds */ + int tv_usec; /* microseconds */ +}; + +#ifdef _WIN32 +/* For tool or tests, we must initialize before calling curlx_now() */ +void curlx_now_init(void); +#endif + +struct curltime curlx_now(void); + +/* + * Make sure that the first argument (newer) is the more recent time and older + * is the older time, as otherwise you get a weird negative time-diff back... + * + * Returns: the time difference in number of milliseconds. + */ +timediff_t curlx_timediff(struct curltime newer, struct curltime older); + +/* + * Make sure that the first argument (newer) is the more recent time and older + * is the older time, as otherwise you get a weird negative time-diff back... + * + * Returns: the time difference in number of milliseconds, rounded up. + */ +timediff_t curlx_timediff_ceil(struct curltime newer, struct curltime older); + +/* + * Make sure that the first argument (newer) is the more recent time and older + * is the older time, as otherwise you get a weird negative time-diff back... + * + * Returns: the time difference in number of microseconds. + */ +timediff_t curlx_timediff_us(struct curltime newer, struct curltime older); + +#endif /* HEADER_CURL_TIMEVAL_H */ diff --git a/Utilities/cmcurl/lib/curlx/version_win32.c b/Utilities/cmcurl/lib/curlx/version_win32.c new file mode 100644 index 00000000000..8d0af68fcfe --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/version_win32.c @@ -0,0 +1,243 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 +#include "version_win32.h" +#include "warnless.h" + +/* The last 2 #include files should be in this order */ +#include "../curl_memory.h" +#include "../memdebug.h" + +/* This Unicode version struct works for VerifyVersionInfoW (OSVERSIONINFOEXW) + and RtlVerifyVersionInfo (RTLOSVERSIONINFOEXW) */ +struct OUR_OSVERSIONINFOEXW { + ULONG dwOSVersionInfoSize; + ULONG dwMajorVersion; + ULONG dwMinorVersion; + ULONG dwBuildNumber; + ULONG dwPlatformId; + WCHAR szCSDVersion[128]; + USHORT wServicePackMajor; + USHORT wServicePackMinor; + USHORT wSuiteMask; + UCHAR wProductType; + UCHAR wReserved; +}; + +/* + * curlx_verify_windows_version() + * + * This is used to verify if we are running on a specific Windows version. + * + * Parameters: + * + * majorVersion [in] - The major version number. + * minorVersion [in] - The minor version number. + * buildVersion [in] - The build version number. If 0, this parameter is + * ignored. + * platform [in] - The optional platform identifier. + * condition [in] - The test condition used to specifier whether we are + * checking a version less than, equal to or greater than + * what is specified in the major and minor version + * numbers. + * + * Returns TRUE if matched; otherwise FALSE. + */ +bool curlx_verify_windows_version(const unsigned int majorVersion, + const unsigned int minorVersion, + const unsigned int buildVersion, + const PlatformIdentifier platform, + const VersionCondition condition) +{ + bool matched = FALSE; + +#ifdef CURL_WINDOWS_UWP + /* We have no way to determine the Windows version from Windows apps, + so let's assume we are running on the target Windows version. */ + const WORD fullVersion = MAKEWORD(minorVersion, majorVersion); + const WORD targetVersion = (WORD)_WIN32_WINNT; + + (void)buildVersion; + + switch(condition) { + case VERSION_LESS_THAN: + matched = targetVersion < fullVersion; + break; + + case VERSION_LESS_THAN_EQUAL: + matched = targetVersion <= fullVersion; + break; + + case VERSION_EQUAL: + matched = targetVersion == fullVersion; + break; + + case VERSION_GREATER_THAN_EQUAL: + matched = targetVersion >= fullVersion; + break; + + case VERSION_GREATER_THAN: + matched = targetVersion > fullVersion; + break; + } + + if(matched && (platform == PLATFORM_WINDOWS)) { + /* we are always running on PLATFORM_WINNT */ + matched = FALSE; + } +#elif defined(UNDER_CE) + (void)majorVersion; + (void)minorVersion; + (void)buildVersion; + (void)platform; + (void)condition; +#else + ULONGLONG cm = 0; + struct OUR_OSVERSIONINFOEXW osver; + BYTE majorCondition; + BYTE minorCondition; + BYTE buildCondition; + BYTE spMajorCondition; + BYTE spMinorCondition; + DWORD dwTypeMask = VER_MAJORVERSION | VER_MINORVERSION | + VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR; + + typedef LONG (APIENTRY *RTLVERIFYVERSIONINFO_FN) + (struct OUR_OSVERSIONINFOEXW *, ULONG, ULONGLONG); + static RTLVERIFYVERSIONINFO_FN pRtlVerifyVersionInfo; + static bool onetime = TRUE; /* safe because first call is during init */ + + if(onetime) { + pRtlVerifyVersionInfo = CURLX_FUNCTION_CAST(RTLVERIFYVERSIONINFO_FN, + (GetProcAddress(GetModuleHandleA("ntdll"), "RtlVerifyVersionInfo"))); + onetime = FALSE; + } + + switch(condition) { + case VERSION_LESS_THAN: + majorCondition = VER_LESS; + minorCondition = VER_LESS; + buildCondition = VER_LESS; + spMajorCondition = VER_LESS_EQUAL; + spMinorCondition = VER_LESS_EQUAL; + break; + + case VERSION_LESS_THAN_EQUAL: + majorCondition = VER_LESS_EQUAL; + minorCondition = VER_LESS_EQUAL; + buildCondition = VER_LESS_EQUAL; + spMajorCondition = VER_LESS_EQUAL; + spMinorCondition = VER_LESS_EQUAL; + break; + + case VERSION_EQUAL: + majorCondition = VER_EQUAL; + minorCondition = VER_EQUAL; + buildCondition = VER_EQUAL; + spMajorCondition = VER_GREATER_EQUAL; + spMinorCondition = VER_GREATER_EQUAL; + break; + + case VERSION_GREATER_THAN_EQUAL: + majorCondition = VER_GREATER_EQUAL; + minorCondition = VER_GREATER_EQUAL; + buildCondition = VER_GREATER_EQUAL; + spMajorCondition = VER_GREATER_EQUAL; + spMinorCondition = VER_GREATER_EQUAL; + break; + + case VERSION_GREATER_THAN: + majorCondition = VER_GREATER; + minorCondition = VER_GREATER; + buildCondition = VER_GREATER; + spMajorCondition = VER_GREATER_EQUAL; + spMinorCondition = VER_GREATER_EQUAL; + break; + + default: + return FALSE; + } + + memset(&osver, 0, sizeof(osver)); + osver.dwOSVersionInfoSize = sizeof(osver); + osver.dwMajorVersion = majorVersion; + osver.dwMinorVersion = minorVersion; + osver.dwBuildNumber = buildVersion; + if(platform == PLATFORM_WINDOWS) + osver.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; + else if(platform == PLATFORM_WINNT) + osver.dwPlatformId = VER_PLATFORM_WIN32_NT; + + cm = VerSetConditionMask(cm, VER_MAJORVERSION, majorCondition); + cm = VerSetConditionMask(cm, VER_MINORVERSION, minorCondition); + cm = VerSetConditionMask(cm, VER_SERVICEPACKMAJOR, spMajorCondition); + cm = VerSetConditionMask(cm, VER_SERVICEPACKMINOR, spMinorCondition); + + if(platform != PLATFORM_DONT_CARE) { + cm = VerSetConditionMask(cm, VER_PLATFORMID, VER_EQUAL); + dwTypeMask |= VER_PLATFORMID; + } + + /* Later versions of Windows have version functions that may not return the + real version of Windows unless the application is so manifested. We prefer + the real version always, so we use the Rtl variant of the function when + possible. Note though the function signatures have underlying fundamental + types that are the same, the return values are different. */ + if(pRtlVerifyVersionInfo) + matched = !pRtlVerifyVersionInfo(&osver, dwTypeMask, cm); + else + matched = !!VerifyVersionInfoW((OSVERSIONINFOEXW *)&osver, dwTypeMask, cm); + + /* Compare the build number separately. VerifyVersionInfo normally compares + major.minor in hierarchical order (eg 1.9 is less than 2.0) but does not + do the same for build (eg 1.9 build 222 is not less than 2.0 build 111). + Build comparison is only needed when build numbers are equal (eg 1.9 is + always less than 2.0 so build comparison is not needed). */ + if(matched && buildVersion && + (condition == VERSION_EQUAL || + ((condition == VERSION_GREATER_THAN_EQUAL || + condition == VERSION_LESS_THAN_EQUAL) && + curlx_verify_windows_version(majorVersion, minorVersion, 0, + platform, VERSION_EQUAL)))) { + + cm = VerSetConditionMask(0, VER_BUILDNUMBER, buildCondition); + dwTypeMask = VER_BUILDNUMBER; + if(pRtlVerifyVersionInfo) + matched = !pRtlVerifyVersionInfo(&osver, dwTypeMask, cm); + else + matched = !!VerifyVersionInfoW((OSVERSIONINFOEXW *)&osver, + dwTypeMask, cm); + } + +#endif + + return matched; +} + +#endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/version_win32.h b/Utilities/cmcurl/lib/curlx/version_win32.h similarity index 93% rename from Utilities/cmcurl/lib/version_win32.h rename to Utilities/cmcurl/lib/curlx/version_win32.h index 3899174a311..471100a66f5 100644 --- a/Utilities/cmcurl/lib/version_win32.h +++ b/Utilities/cmcurl/lib/curlx/version_win32.h @@ -24,9 +24,9 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(WIN32) +#ifdef _WIN32 /* Version condition */ typedef enum { @@ -44,13 +44,13 @@ typedef enum { PLATFORM_WINNT } PlatformIdentifier; -/* This is used to verify if we are running on a specific windows version */ +/* This is used to verify if we are running on a specific Windows version */ bool curlx_verify_windows_version(const unsigned int majorVersion, const unsigned int minorVersion, const unsigned int buildVersion, const PlatformIdentifier platform, const VersionCondition condition); -#endif /* WIN32 */ +#endif /* _WIN32 */ #endif /* HEADER_CURL_VERSION_WIN32_H */ diff --git a/Utilities/cmcurl/lib/curlx/warnless.c b/Utilities/cmcurl/lib/curlx/warnless.c new file mode 100644 index 00000000000..5ca92450d89 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/warnless.c @@ -0,0 +1,315 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "warnless.h" + +#if defined(__INTEL_COMPILER) && defined(__unix__) + +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#endif /* __INTEL_COMPILER && __unix__ */ + +#ifdef _WIN32 +#undef read +#undef write +#endif + +#include + +#define CURL_MASK_UCHAR ((unsigned char)~0) + +#define CURL_MASK_USHORT ((unsigned short)~0) + +#define CURL_MASK_UINT ((unsigned int)~0) +#define CURL_MASK_SINT (CURL_MASK_UINT >> 1) + +#define CURL_MASK_ULONG ((unsigned long)~0) + +#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 + + DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_UCHAR); + return (unsigned char)(ulnum & (unsigned long) CURL_MASK_UCHAR); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to signed int +*/ + +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 +} + +/* +** unsigned size_t to unsigned long +*/ + +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_T_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 +} + +/* +** unsigned size_t to unsigned int +*/ + +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_T_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 +} + +/* +** signed long to signed int +*/ + +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 +} + +/* +** signed long to unsigned int +*/ + +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 +} + +/* +** signed long to unsigned short +*/ + +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 +} + +/* +** unsigned size_t to signed ssize_t +*/ + +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 +} + +/* +** signed curl_off_t to unsigned size_t +*/ + +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 +} + +/* +** signed ssize_t to signed int +*/ + +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_T_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 +} + +/* +** unsigned int to unsigned short +*/ + +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 +} + +/* +** signed int to unsigned size_t +*/ + +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 +} + +#ifdef _WIN32 + +ssize_t curlx_read(int fd, void *buf, size_t count) +{ + return (ssize_t)read(fd, buf, curlx_uztoui(count)); +} + +ssize_t curlx_write(int fd, const void *buf, size_t count) +{ + return (ssize_t)write(fd, buf, curlx_uztoui(count)); +} + +#endif /* _WIN32 */ + +/* Ensure that warnless.h redefinitions continue to have an effect + in "unity" builds. */ +#undef HEADER_CURL_WARNLESS_H_REDEFS diff --git a/Utilities/cmcurl/lib/curlx/warnless.h b/Utilities/cmcurl/lib/curlx/warnless.h new file mode 100644 index 00000000000..c78b61169bf --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/warnless.h @@ -0,0 +1,80 @@ +#ifndef HEADER_CURL_WARNLESS_H +#define HEADER_CURL_WARNLESS_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" + +#ifdef USE_WINSOCK +#include /* for curl_socket_t */ +#endif + +#define CURLX_FUNCTION_CAST(target_type, func) \ + (target_type)(void (*) (void))(func) + +unsigned char curlx_ultouc(unsigned long ulnum); + +int curlx_uztosi(size_t uznum); + +unsigned long curlx_uztoul(size_t uznum); + +unsigned int curlx_uztoui(size_t uznum); + +int curlx_sltosi(long slnum); + +unsigned int curlx_sltoui(long slnum); + +unsigned short curlx_sltous(long slnum); + +ssize_t curlx_uztosz(size_t uznum); + +size_t curlx_sotouz(curl_off_t sonum); + +int curlx_sztosi(ssize_t sznum); + +unsigned short curlx_uitous(unsigned int uinum); + +size_t curlx_sitouz(int sinum); + +#ifdef _WIN32 + +ssize_t curlx_read(int fd, void *buf, size_t count); + +ssize_t curlx_write(int fd, const void *buf, size_t count); + +#endif /* _WIN32 */ + +#endif /* HEADER_CURL_WARNLESS_H */ + +#ifndef HEADER_CURL_WARNLESS_H_REDEFS +#define HEADER_CURL_WARNLESS_H_REDEFS + +#ifdef _WIN32 +#undef read +#define read(fd, buf, count) curlx_read(fd, buf, count) +#undef write +#define write(fd, buf, count) curlx_write(fd, buf, count) +#endif + +#endif /* HEADER_CURL_WARNLESS_H_REDEFS */ diff --git a/Utilities/cmcurl/lib/curlx/winapi.c b/Utilities/cmcurl/lib/curlx/winapi.c new file mode 100644 index 00000000000..6069424becc --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/winapi.c @@ -0,0 +1,135 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +/* + * curlx_winapi_strerror: + * Variant of Curl_strerror if the error code is definitely Windows API. + */ +#ifdef _WIN32 +#include "winapi.h" + +#ifdef BUILDING_LIBCURL +#include +#define SNPRINTF curl_msnprintf +#else +/* when built for the test servers */ + +/* adjust for old MSVC */ +#if defined(_MSC_VER) && (_MSC_VER < 1900) +# define SNPRINTF _snprintf +#else +#define SNPRINTF snprintf +#endif + +#endif /* !BUILDING_LIBCURL */ + +#ifdef _WIN32 +/* This is a helper function for Curl_strerror that converts Windows API error + * codes (GetLastError) to error messages. + * Returns NULL if no error message was found for error code. + */ +const char *curlx_get_winapi_error(int err, char *buf, size_t buflen) +{ + char *p; + wchar_t wbuf[256]; + + if(!buflen) + return NULL; + + *buf = '\0'; + *wbuf = L'\0'; + + /* We return the local codepage version of the error string because if it is + output to the user's terminal it will likely be with functions which + expect the local codepage (eg fprintf, failf, infof). + FormatMessageW -> wcstombs is used for Windows CE compatibility. */ + if(FormatMessageW((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, (DWORD)err, + LANG_NEUTRAL, wbuf, CURL_ARRAYSIZE(wbuf), NULL)) { + size_t written = wcstombs(buf, wbuf, buflen - 1); + if(written != (size_t)-1) + buf[written] = '\0'; + else + *buf = '\0'; + } + + /* Truncate multiple lines */ + p = strchr(buf, '\n'); + if(p) { + if(p > buf && *(p-1) == '\r') + *(p-1) = '\0'; + else + *p = '\0'; + } + + return *buf ? buf : NULL; +} +#endif /* _WIN32 */ + +const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen) +{ +#ifdef _WIN32 + DWORD old_win_err = GetLastError(); +#endif + int old_errno = errno; + + if(!buflen) + return NULL; + + *buf = '\0'; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(!curlx_get_winapi_error((int)err, buf, buflen)) { +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic warning "-Wformat-truncation=1" +#endif + /* some GCC compilers cause false positive warnings if we allow this + warning */ + SNPRINTF(buf, buflen, "Unknown error %lu (0x%08lX)", err, err); +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic pop +#endif + + } +#else + { + const char *txt = (err == ERROR_SUCCESS) ? "No error" : "Error"; + if(strlen(txt) < buflen) + strcpy(buf, txt); + } +#endif + + if(errno != old_errno) + CURL_SETERRNO(old_errno); + +#ifdef _WIN32 + if(old_win_err != GetLastError()) + SetLastError(old_win_err); +#endif + + return buf; +} +#endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/curlx/winapi.h b/Utilities/cmcurl/lib/curlx/winapi.h new file mode 100644 index 00000000000..76ddcc53b8b --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/winapi.h @@ -0,0 +1,33 @@ +#ifndef HEADER_CURLX_WINAPI_H +#define HEADER_CURLX_WINAPI_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 + * + ***************************************************************************/ + +#ifdef _WIN32 +#define WINAPI_ERROR_LEN 100 +const char *curlx_get_winapi_error(int err, char *buf, size_t buflen); +const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen); +#endif + +#endif /* HEADER_CURLX_WINAPI_H */ diff --git a/Utilities/cmcurl/lib/cw-out.c b/Utilities/cmcurl/lib/cw-out.c new file mode 100644 index 00000000000..097ef85e8b4 --- /dev/null +++ b/Utilities/cmcurl/lib/cw-out.c @@ -0,0 +1,495 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + +#include "urldata.h" +#include "cfilters.h" +#include "headers.h" +#include "multiif.h" +#include "sendf.h" +#include "cw-out.h" +#include "cw-pause.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/** + * OVERALL DESIGN of this client writer + * + * The 'cw-out' writer is supposed to be the last writer in a transfer's + * stack. It is always added when that stack is initialized. Its purpose + * is to pass BODY and HEADER bytes to the client-installed callback + * functions. + * + * These callback may return `CURL_WRITEFUNC_PAUSE` to indicate that the + * data had not been written and the whole transfer should stop receiving + * new data. Or at least, stop calling the functions. When the transfer + * is "unpaused" by the client, the previous data shall be passed as + * if nothing happened. + * + * The `cw-out` writer therefore manages buffers for bytes that could + * not be written. Data that was already in flight from the server also + * needs buffering on paused transfer when it arrives. + * + * In addition, the writer allows buffering of "small" body writes, + * so client functions are called less often. That is only enabled on a + * number of conditions. + * + * HEADER and BODY data may arrive in any order. For paused transfers, + * a list of `struct cw_out_buf` is kept for `cw_out_type` types. The + * list may be: [BODY]->[HEADER]->[BODY]->[HEADER].... + * When unpausing, this list is "played back" to the client callbacks. + * + * The amount of bytes being buffered is limited by `DYN_PAUSE_BUFFER` + * and when that is exceeded `CURLE_TOO_LARGE` is returned as error. + */ +typedef enum { + CW_OUT_NONE, + CW_OUT_BODY, + CW_OUT_HDS +} cw_out_type; + +struct cw_out_buf { + struct cw_out_buf *next; + struct dynbuf b; + cw_out_type type; +}; + +static struct cw_out_buf *cw_out_buf_create(cw_out_type otype) +{ + struct cw_out_buf *cwbuf = calloc(1, sizeof(*cwbuf)); + if(cwbuf) { + cwbuf->type = otype; + curlx_dyn_init(&cwbuf->b, DYN_PAUSE_BUFFER); + } + return cwbuf; +} + +static void cw_out_buf_free(struct cw_out_buf *cwbuf) +{ + if(cwbuf) { + curlx_dyn_free(&cwbuf->b); + free(cwbuf); + } +} + +struct cw_out_ctx { + struct Curl_cwriter super; + struct cw_out_buf *buf; + BIT(paused); + BIT(errored); +}; + +static CURLcode cw_out_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer); +static CURLcode cw_out_init(struct Curl_easy *data, + struct Curl_cwriter *writer); + +const struct Curl_cwtype Curl_cwt_out = { + "cw-out", + NULL, + cw_out_init, + cw_out_write, + cw_out_close, + sizeof(struct cw_out_ctx) +}; + +static CURLcode cw_out_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct cw_out_ctx *ctx = writer->ctx; + (void)data; + ctx->buf = NULL; + return CURLE_OK; +} + +static void cw_out_bufs_free(struct cw_out_ctx *ctx) +{ + while(ctx->buf) { + struct cw_out_buf *next = ctx->buf->next; + 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; +} + +static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct cw_out_ctx *ctx = writer->ctx; + + (void)data; + cw_out_bufs_free(ctx); +} + +/** + * Return the current curl_write_callback and user_data for the buf type + */ +static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, + curl_write_callback *pwcb, void **pwcb_data, + size_t *pmax_write, size_t *pmin_write) +{ + switch(otype) { + case CW_OUT_BODY: + *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 + * to the client as it comes without delay */ + *pmin_write = 0; + break; + case CW_OUT_HDS: + *pwcb = data->set.fwrite_header ? data->set.fwrite_header : + (data->set.writeheader ? data->set.fwrite_func : NULL); + *pwcb_data = data->set.writeheader; + *pmax_write = 0; /* do not chunk-write headers, write them as they are */ + *pmin_write = 0; + break; + default: + *pwcb = NULL; + *pwcb_data = NULL; + *pmax_write = CURL_MAX_WRITE_SIZE; + *pmin_write = 0; + } +} + +static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, + struct Curl_easy *data, + cw_out_type otype, + bool flush_all, + const char *buf, size_t blen, + size_t *pconsumed) +{ + curl_write_callback wcb = NULL; + void *wcb_data; + size_t max_write, min_write; + size_t wlen, nwritten; + + /* If we errored once, we do not invoke the client callback again */ + if(ctx->errored) + return CURLE_WRITE_ERROR; + + /* write callbacks may get NULLed by the client between calls. */ + cw_get_writefunc(data, otype, &wcb, &wcb_data, &max_write, &min_write); + if(!wcb) { + *pconsumed = blen; + return CURLE_OK; + } + + *pconsumed = 0; + while(blen && !ctx->paused) { + if(!flush_all && blen < min_write) + break; + wlen = max_write ? CURLMIN(blen, max_write) : blen; + Curl_set_in_callback(data, TRUE); + nwritten = wcb((char *)CURL_UNCONST(buf), 1, wlen, wcb_data); + Curl_set_in_callback(data, FALSE); + CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes -> %zu", + wlen, (otype == CW_OUT_BODY) ? "body" : "header", + nwritten); + if(CURL_WRITEFUNC_PAUSE == nwritten) { + if(data->conn && data->conn->handler->flags & PROTOPT_NONETWORK) { + /* Protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it cannot pause since the + transfer is not done using the "normal" procedure. */ + failf(data, "Write callback asked for PAUSE when not supported"); + return CURLE_WRITE_ERROR; + } + /* mark the connection as RECV paused */ + data->req.keepon |= KEEP_RECV_PAUSE; + ctx->paused = TRUE; + CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client"); + break; + } + else if(CURL_WRITEFUNC_ERROR == nwritten) { + failf(data, "client returned ERROR on write of %zu bytes", wlen); + return CURLE_WRITE_ERROR; + } + else if(nwritten != wlen) { + failf(data, "Failure writing output to destination, " + "passed %zu returned %zd", wlen, nwritten); + return CURLE_WRITE_ERROR; + } + *pconsumed += nwritten; + blen -= nwritten; + buf += nwritten; + } + return CURLE_OK; +} + +static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx, + struct Curl_easy *data, + struct cw_out_buf *cwbuf, + bool flush_all) +{ + CURLcode result = CURLE_OK; + + if(curlx_dyn_len(&cwbuf->b)) { + size_t consumed; + + result = cw_out_ptr_flush(ctx, data, cwbuf->type, flush_all, + curlx_dyn_ptr(&cwbuf->b), + curlx_dyn_len(&cwbuf->b), + &consumed); + if(result) + return result; + + if(consumed) { + if(consumed == curlx_dyn_len(&cwbuf->b)) { + curlx_dyn_free(&cwbuf->b); + } + else { + DEBUGASSERT(consumed < curlx_dyn_len(&cwbuf->b)); + result = curlx_dyn_tail(&cwbuf->b, + curlx_dyn_len(&cwbuf->b) - consumed); + if(result) + return result; + } + } + } + return result; +} + +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(ctx->paused) + return CURLE_OK; + + /* 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(result) + return result; + if(*plast) { + /* could not write last, paused again? */ + DEBUGASSERT(ctx->paused); + return CURLE_OK; + } + } + + result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); + if(result) + return result; + if(!curlx_dyn_len(&cwbuf->b)) { + cw_out_buf_free(cwbuf); + *pcwbuf = NULL; + } + return CURLE_OK; +} + +static CURLcode cw_out_append(struct cw_out_ctx *ctx, + struct Curl_easy *data, + cw_out_type otype, + const char *buf, size_t blen) +{ + 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) { + 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. + * And for CW_OUT_HDS always make a new one, so we "replay" headers + * exactly as they came in */ + if(!ctx->buf || (ctx->buf->type != otype) || (otype == CW_OUT_HDS)) { + struct cw_out_buf *cwbuf = cw_out_buf_create(otype); + if(!cwbuf) + return CURLE_OUT_OF_MEMORY; + cwbuf->next = ctx->buf; + ctx->buf = cwbuf; + } + DEBUGASSERT(ctx->buf && (ctx->buf->type == otype)); + return curlx_dyn_addn(&ctx->buf->b, buf, blen); +} + +static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, + struct Curl_easy *data, + cw_out_type otype, + bool flush_all, + const char *buf, size_t blen) +{ + 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(result) + goto out; + } + + if(ctx->buf) { + /* still have buffered data, append and flush */ + result = cw_out_append(ctx, data, otype, buf, blen); + if(result) + return result; + result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + if(result) + goto out; + } + else { + /* nothing buffered, try direct write */ + size_t consumed; + result = cw_out_ptr_flush(ctx, data, otype, flush_all, + buf, blen, &consumed); + if(result) + return result; + if(consumed < blen) { + /* did not write all, append the rest */ + result = cw_out_append(ctx, data, otype, + buf + consumed, blen - consumed); + if(result) + goto out; + } + } + +out: + if(result) { + /* We do not want to invoked client callbacks a second time after + * encountering an error. See issue #13337 */ + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + } + return result; +} + +static CURLcode cw_out_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct cw_out_ctx *ctx = writer->ctx; + CURLcode result; + bool flush_all = !!(type & CLIENTWRITE_EOS); + + if((type & CLIENTWRITE_BODY) || + ((type & CLIENTWRITE_HEADER) && data->set.include_header)) { + result = cw_out_do_write(ctx, data, CW_OUT_BODY, flush_all, buf, blen); + if(result) + return result; + } + + if(type & (CLIENTWRITE_HEADER|CLIENTWRITE_INFO)) { + result = cw_out_do_write(ctx, data, CW_OUT_HDS, flush_all, buf, blen); + if(result) + return result; + } + + return CURLE_OK; +} + +bool Curl_cw_out_is_paused(struct Curl_easy *data) +{ + struct Curl_cwriter *cw_out; + struct cw_out_ctx *ctx; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(!cw_out) + return FALSE; + + ctx = (struct cw_out_ctx *)cw_out; + return ctx->paused; +} + +static CURLcode cw_out_flush(struct Curl_easy *data, + struct Curl_cwriter *cw_out, + bool flush_all) +{ + struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out; + CURLcode result = CURLE_OK; + + if(ctx->errored) + return CURLE_WRITE_ERROR; + if(ctx->paused) + return CURLE_OK; /* not doing it */ + + result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + if(result) { + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + return result; + } + return result; +} + +CURLcode Curl_cw_out_unpause(struct Curl_easy *data) +{ + struct Curl_cwriter *cw_out; + CURLcode result = CURLE_OK; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(cw_out) { + struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out; + CURL_TRC_WRITE(data, "[OUT] unpause"); + ctx->paused = FALSE; + result = Curl_cw_pause_flush(data); + if(!result) + result = cw_out_flush(data, cw_out, FALSE); + } + return result; +} + +CURLcode Curl_cw_out_done(struct Curl_easy *data) +{ + struct Curl_cwriter *cw_out; + CURLcode result = CURLE_OK; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(cw_out) { + CURL_TRC_WRITE(data, "[OUT] done"); + result = Curl_cw_pause_flush(data); + if(!result) + result = cw_out_flush(data, cw_out, TRUE); + } + return result; +} diff --git a/Utilities/cmcurl/lib/cw-out.h b/Utilities/cmcurl/lib/cw-out.h new file mode 100644 index 00000000000..89b9985bb54 --- /dev/null +++ b/Utilities/cmcurl/lib/cw-out.h @@ -0,0 +1,53 @@ +#ifndef HEADER_CURL_CW_OUT_H +#define HEADER_CURL_CW_OUT_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" + +#include "sendf.h" + +/** + * The client writer type "cw-out" that does the actual writing to + * the client callbacks. Intended to be the last installed in the + * client writer stack of a transfer. + */ +extern const struct Curl_cwtype Curl_cwt_out; + +/** + * Return TRUE iff 'cw-out' client write has paused data. + */ +bool Curl_cw_out_is_paused(struct Curl_easy *data); + +/** + * Flush any buffered date to the client, chunk collation still applies. + */ +CURLcode Curl_cw_out_unpause(struct Curl_easy *data); + +/** + * Mark EndOfStream reached and flush ALL data to the client. + */ +CURLcode Curl_cw_out_done(struct Curl_easy *data); + +#endif /* HEADER_CURL_CW_OUT_H */ diff --git a/Utilities/cmcurl/lib/cw-pause.c b/Utilities/cmcurl/lib/cw-pause.c new file mode 100644 index 00000000000..9b9554c5515 --- /dev/null +++ b/Utilities/cmcurl/lib/cw-pause.c @@ -0,0 +1,242 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + +#include "urldata.h" +#include "bufq.h" +#include "cfilters.h" +#include "headers.h" +#include "multiif.h" +#include "sendf.h" +#include "cw-pause.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/* body dynbuf sizes */ +#define CW_PAUSE_BUF_CHUNK (16 * 1024) +/* when content decoding, write data in chunks */ +#define CW_PAUSE_DEC_WRITE_CHUNK (4096) + +struct cw_pause_buf { + struct cw_pause_buf *next; + struct bufq b; + int type; +}; + +static struct cw_pause_buf *cw_pause_buf_create(int type, size_t buflen) +{ + struct cw_pause_buf *cwbuf = calloc(1, sizeof(*cwbuf)); + if(cwbuf) { + cwbuf->type = type; + if(type & CLIENTWRITE_BODY) + Curl_bufq_init2(&cwbuf->b, CW_PAUSE_BUF_CHUNK, 1, + (BUFQ_OPT_SOFT_LIMIT|BUFQ_OPT_NO_SPARES)); + else + Curl_bufq_init(&cwbuf->b, buflen, 1); + } + return cwbuf; +} + +static void cw_pause_buf_free(struct cw_pause_buf *cwbuf) +{ + if(cwbuf) { + Curl_bufq_free(&cwbuf->b); + free(cwbuf); + } +} + +struct cw_pause_ctx { + struct Curl_cwriter super; + struct cw_pause_buf *buf; + size_t buf_total; +}; + +static CURLcode cw_pause_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +static void cw_pause_close(struct Curl_easy *data, + struct Curl_cwriter *writer); +static CURLcode cw_pause_init(struct Curl_easy *data, + struct Curl_cwriter *writer); + +const struct Curl_cwtype Curl_cwt_pause = { + "cw-pause", + NULL, + cw_pause_init, + cw_pause_write, + cw_pause_close, + sizeof(struct cw_pause_ctx) +}; + +static CURLcode cw_pause_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct cw_pause_ctx *ctx = writer->ctx; + (void)data; + ctx->buf = NULL; + return CURLE_OK; +} + +static void cw_pause_bufs_free(struct cw_pause_ctx *ctx) +{ + while(ctx->buf) { + struct cw_pause_buf *next = ctx->buf->next; + cw_pause_buf_free(ctx->buf); + ctx->buf = next; + } +} + +static void cw_pause_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct cw_pause_ctx *ctx = writer->ctx; + + (void)data; + cw_pause_bufs_free(ctx); +} + +static CURLcode cw_pause_flush(struct Curl_easy *data, + struct Curl_cwriter *cw_pause) +{ + struct cw_pause_ctx *ctx = (struct cw_pause_ctx *)cw_pause; + bool decoding = Curl_cwriter_is_content_decoding(data); + CURLcode result = CURLE_OK; + + /* write the end of the chain until it blocks or gets empty */ + while(ctx->buf && !Curl_cwriter_is_paused(data)) { + struct cw_pause_buf **plast = &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 = (decoding && ((*plast)->type & CLIENTWRITE_BODY)) ? + CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; + result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, + (const char *)buf, wlen); + CURL_TRC_WRITE(data, "[PAUSE] flushed %zu/%zu bytes, type=%x -> %d", + wlen, ctx->buf_total, (*plast)->type, result); + Curl_bufq_skip(&(*plast)->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, + (const char *)buf, 0); + CURL_TRC_WRITE(data, "[PAUSE] flushed 0/%zu bytes, type=%x -> %d", + ctx->buf_total, (*plast)->type, result); + } + + if(Curl_bufq_is_empty(&(*plast)->b)) { + cw_pause_buf_free(*plast); + *plast = NULL; + } + } + return result; +} + +static CURLcode cw_pause_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct cw_pause_ctx *ctx = writer->ctx; + CURLcode result = CURLE_OK; + size_t wlen = 0; + bool decoding = Curl_cwriter_is_content_decoding(data); + + if(ctx->buf && !Curl_cwriter_is_paused(data)) { + result = cw_pause_flush(data, writer); + if(result) + return result; + } + + while(!ctx->buf && !Curl_cwriter_is_paused(data)) { + int wtype = type; + DEBUGASSERT(!ctx->buf); + /* content decoding might blow up size considerably, write smaller + * chunks to make pausing need buffer less. */ + wlen = (decoding && (type & CLIENTWRITE_BODY)) ? + CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; + if(wlen < blen) + wtype &= ~CLIENTWRITE_EOS; + result = Curl_cwriter_write(data, writer->next, wtype, buf, wlen); + CURL_TRC_WRITE(data, "[PAUSE] writing %zu/%zu bytes of type %x -> %d", + wlen, blen, wtype, result); + if(result) + return result; + buf += wlen; + blen -= wlen; + if(!blen) + return result; + } + + 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 + * limit and should take everything up to OOM. */ + result = Curl_bufq_cwrite(&ctx->buf->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); + } + CURL_TRC_WRITE(data, "[PAUSE] buffer %zu more bytes of type %x, " + "total=%zu -> %d", nwritten, type, ctx->buf_total + wlen, + result); + if(result) + return result; + buf += nwritten; + blen -= nwritten; + ctx->buf_total += nwritten; + } while(blen); + + return result; +} + +CURLcode Curl_cw_pause_flush(struct Curl_easy *data) +{ + struct Curl_cwriter *cw_pause; + CURLcode result = CURLE_OK; + + cw_pause = Curl_cwriter_get_by_type(data, &Curl_cwt_pause); + if(cw_pause) + result = cw_pause_flush(data, cw_pause); + + return result; +} diff --git a/Utilities/cmcurl/lib/cw-pause.h b/Utilities/cmcurl/lib/cw-pause.h new file mode 100644 index 00000000000..2aa1a499cdd --- /dev/null +++ b/Utilities/cmcurl/lib/cw-pause.h @@ -0,0 +1,40 @@ +#ifndef HEADER_CURL_CW_PAUSE_H +#define HEADER_CURL_CW_PAUSE_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" + +#include "sendf.h" + +/** + * The client writer type "cw-pause" that buffers writes for + * paused transfer writes. + */ +extern const struct Curl_cwtype Curl_cwt_pause; + +CURLcode Curl_cw_pause_flush(struct Curl_easy *data); + + +#endif /* HEADER_CURL_CW_PAUSE_H */ diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c index 3172b382909..637f349e700 100644 --- a/Utilities/cmcurl/lib/dict.c +++ b/Utilities/cmcurl/lib/dict.c @@ -65,6 +65,15 @@ /* The last #include file should be: */ #include "memdebug.h" + +#define DICT_MATCH "/MATCH:" +#define DICT_MATCH2 "/M:" +#define DICT_MATCH3 "/FIND:" +#define DICT_DEFINE "/DEFINE:" +#define DICT_DEFINE2 "/D:" +#define DICT_DEFINE3 "/LOOKUP:" + + /* * Forward declarations. */ @@ -76,7 +85,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_dict = { - "DICT", /* scheme */ + "dict", /* scheme */ ZERO_NULL, /* setup_connection */ dict_do, /* do_it */ ZERO_NULL, /* done */ @@ -89,9 +98,11 @@ const struct Curl_handler Curl_handler_dict = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_DICT, /* defport */ CURLPROTO_DICT, /* protocol */ CURLPROTO_DICT, /* family */ @@ -104,7 +115,7 @@ static char *unescape_word(const char *input) struct dynbuf out; const char *ptr; CURLcode result = CURLE_OK; - Curl_dyn_init(&out, DYN_DICT_WORD); + curlx_dyn_init(&out, DYN_DICT_WORD); /* According to RFC2229 section 2.2, these letters need to be escaped with \[letter] */ @@ -112,20 +123,22 @@ static char *unescape_word(const char *input) char ch = *ptr; if((ch <= 32) || (ch == 127) || (ch == '\'') || (ch == '\"') || (ch == '\\')) - result = Curl_dyn_addn(&out, "\\", 1); + result = curlx_dyn_addn(&out, "\\", 1); if(!result) - result = Curl_dyn_addn(&out, ptr, 1); + result = curlx_dyn_addn(&out, ptr, 1); if(result) return NULL; } - return Curl_dyn_ptr(&out); + return curlx_dyn_ptr(&out); } /* sendf() sends formatted data to the server */ -static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, - const char *fmt, ...) +static CURLcode sendf(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +static CURLcode sendf(struct Curl_easy *data, const char *fmt, ...) { - ssize_t bytes_written; + size_t bytes_written; size_t write_len; CURLcode result = CURLE_OK; char *s; @@ -143,7 +156,7 @@ static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, for(;;) { /* Write the buffer to the socket */ - result = Curl_write(data, sockfd, sptr, write_len, &bytes_written); + result = Curl_xfer_send(data, sptr, write_len, FALSE, &bytes_written); if(result) break; @@ -175,8 +188,6 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) char *nthdef = NULL; /* This is not part of the protocol, but required by RFC 2229 */ CURLcode result; - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; char *path; @@ -210,37 +221,29 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) if(!word || (*word == (char)0)) { infof(data, "lookup word is missing"); - word = (char *)"default"; - } - if(!database || (*database == (char)0)) { - database = (char *)"!"; } - if(!strategy || (*strategy == (char)0)) { - strategy = (char *)"."; - } - - eword = unescape_word(word); + eword = unescape_word((!word || (*word == (char)0)) ? "default" : word); if(!eword) { result = CURLE_OUT_OF_MEMORY; goto error; } - result = sendf(sockfd, data, + result = sendf(data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" "MATCH " "%s " /* database */ "%s " /* strategy */ "%s\r\n" /* word */ "QUIT\r\n", - database, - strategy, + (!database || (*database == (char)0)) ? "!" : database, + (!strategy || (*strategy == (char)0)) ? "." : strategy, eword); if(result) { failf(data, "Failed sending DICT request"); goto error; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); /* no upload */ + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); /* no upload */ } else if(strncasecompare(path, DICT_DEFINE, sizeof(DICT_DEFINE)-1) || strncasecompare(path, DICT_DEFINE2, sizeof(DICT_DEFINE2)-1) || @@ -261,32 +264,27 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) if(!word || (*word == (char)0)) { infof(data, "lookup word is missing"); - word = (char *)"default"; } - if(!database || (*database == (char)0)) { - database = (char *)"!"; - } - - eword = unescape_word(word); + eword = unescape_word((!word || (*word == (char)0)) ? "default" : word); if(!eword) { result = CURLE_OUT_OF_MEMORY; goto error; } - result = sendf(sockfd, data, + result = sendf(data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" "DEFINE " "%s " /* database */ "%s\r\n" /* word */ "QUIT\r\n", - database, + (!database || (*database == (char)0)) ? "!" : database, eword); if(result) { failf(data, "Failed sending DICT request"); goto error; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); } else { @@ -299,7 +297,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) if(ppath[i] == ':') ppath[i] = ' '; } - result = sendf(sockfd, data, + result = sendf(data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" "%s\r\n" "QUIT\r\n", ppath); @@ -308,7 +306,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) goto error; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); } } diff --git a/Utilities/cmcurl/lib/dllmain.c b/Utilities/cmcurl/lib/dllmain.c new file mode 100644 index 00000000000..33076e05714 --- /dev/null +++ b/Utilities/cmcurl/lib/dllmain.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 "curl_setup.h" + +#ifdef USE_OPENSSL +#include +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* DllMain() must only be defined for Windows DLL builds. */ +#if defined(_WIN32) && !defined(CURL_STATICLIB) + +#if defined(USE_OPENSSL) && \ + !defined(OPENSSL_IS_AWSLC) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER >= 0x10100000L) +#define PREVENT_OPENSSL_MEMLEAK +#endif + +#ifdef PREVENT_OPENSSL_MEMLEAK +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + (void)hinstDLL; + (void)lpvReserved; + + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + break; + case DLL_PROCESS_DETACH: + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + /* Call OPENSSL_thread_stop to prevent a memory leak in case OpenSSL is + linked statically. + https://github.com/curl/curl/issues/12327#issuecomment-1826405944 */ + OPENSSL_thread_stop(); + break; + } + return TRUE; +} +#endif /* OpenSSL */ + +#endif /* DLL build */ diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/doh.c index 7a38eab01f4..9f408402a03 100644 --- a/Utilities/cmcurl/lib/doh.c +++ b/Utilities/cmcurl/lib/doh.c @@ -34,10 +34,13 @@ #include "multiif.h" #include "url.h" #include "share.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "connect.h" #include "strdup.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" +#include "escape.h" +#include "urlapi-int.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -69,36 +72,37 @@ static const char *doh_strerror(DOHcode code) return errors[code]; return "bad error code"; } -#endif + +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ /* @unittest 1655 */ -UNITTEST DOHcode doh_encode(const char *host, - DNStype dnstype, - unsigned char *dnsp, /* buffer */ - size_t len, /* buffer size */ - size_t *olen) /* output length */ +UNITTEST DOHcode doh_req_encode(const char *host, + DNStype dnstype, + unsigned char *dnsp, /* buffer */ + size_t len, /* buffer size */ + size_t *olen) /* output length */ { const size_t hostlen = strlen(host); unsigned char *orig = dnsp; const char *hostp = host; /* The expected output length is 16 bytes more than the length of - * the QNAME-encoding of the host name. + * the QNAME-encoding of the hostname. * * A valid DNS name may not contain a zero-length label, except at - * the end. For this reason, a name beginning with a dot, or + * the end. For this reason, a name beginning with a dot, or * containing a sequence of two or more consecutive dots, is invalid * and cannot be encoded as a QNAME. * - * If the host name ends with a trailing dot, the corresponding - * QNAME-encoding is one byte longer than the host name. If (as is + * If the hostname ends with a trailing dot, the corresponding + * QNAME-encoding is one byte longer than the hostname. If (as is * also valid) the hostname is shortened by the omission of the * trailing dot, then its QNAME-encoding will be two bytes longer - * than the host name. + * than the hostname. * * Each [ label, dot ] pair is encoded as [ length, label ], - * preserving overall length. A final [ label ] without a dot is + * 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, * representing the zero-length root label, again increasing @@ -111,7 +115,7 @@ UNITTEST DOHcode doh_encode(const char *host, if(host[hostlen-1]!='.') expected_len++; - if(expected_len > (256 + 16)) /* RFCs 1034, 1035 */ + if(expected_len > DOH_MAX_DNSREQ_SIZE) return DOH_DNS_NAME_TOO_LONG; if(len < expected_len) @@ -156,7 +160,7 @@ UNITTEST DOHcode doh_encode(const char *host, *dnsp++ = 0; /* append zero-length label for root */ /* There are assigned TYPE codes beyond 255: use range [1..65535] */ - *dnsp++ = (unsigned char)(255 & (dnstype>>8)); /* upper 8 bit TYPE */ + *dnsp++ = (unsigned char)(255 & (dnstype >> 8)); /* upper 8 bit TYPE */ *dnsp++ = (unsigned char)(255 & dnstype); /* lower 8 bit TYPE */ *dnsp++ = '\0'; /* upper 8 bit CLASS */ @@ -171,185 +175,268 @@ UNITTEST DOHcode doh_encode(const char *host, } static size_t -doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp) +doh_probe_write_cb(char *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; - struct dynbuf *mem = (struct dynbuf *)userp; + struct Curl_easy *data = userp; + struct doh_request *doh_req = Curl_meta_get(data, CURL_EZM_DOH_PROBE); + if(!doh_req) + return CURL_WRITEFUNC_ERROR; - if(Curl_dyn_addn(mem, contents, realsize)) + if(curlx_dyn_addn(&doh_req->resp_body, contents, realsize)) return 0; return realsize; } -/* called from multi.c when this DoH transfer is complete */ -static int doh_done(struct Curl_easy *doh, CURLcode result) +#if defined(USE_HTTPSRR) && defined(DEBUGBUILD) + +/* doh_print_buf truncates if the hex string will be more than this */ +#define LOCAL_PB_HEXMAX 400 + +static void doh_print_buf(struct Curl_easy *data, + const char *prefix, + unsigned char *buf, size_t len) { - struct Curl_easy *data = doh->set.dohfor; - struct dohdata *dohp = data->req.doh; - /* so one of the DoH request done for the 'data' transfer is now complete! */ - dohp->pending--; - infof(data, "a DoH request is completed, %u to go", dohp->pending); - if(result) - infof(data, "DoH request %s", curl_easy_strerror(result)); + unsigned char hexstr[LOCAL_PB_HEXMAX]; + size_t hlen = LOCAL_PB_HEXMAX; + bool truncated = FALSE; + + if(len > (LOCAL_PB_HEXMAX / 2)) + truncated = TRUE; + Curl_hexencode(buf, len, hexstr, hlen); + if(!truncated) + infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr); + else + infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr); + return; +} +#endif + +/* called from multi when a sub transfer, e.g. doh probe, is done. + * This looks up the the probe response at its meta CURL_EZM_DOH_PROBE + * and copies the response body over to the struct at the master's + * meta at CURL_EZM_DOH_MASTER. */ +static void doh_probe_done(struct Curl_easy *data, + struct Curl_easy *doh, CURLcode result) +{ + struct doh_probes *dohp = data->state.async.doh; + DEBUGASSERT(dohp); + if(dohp) { + struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); + int i; + + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + if(dohp->probe_resp[i].probe_mid == doh->mid) + break; + } + if(i >= DOH_SLOT_COUNT) { + failf(data, "unknown sub request done"); + return; + } + + dohp->pending--; + infof(doh, "a DoH request is completed, %u to go", dohp->pending); + dohp->probe_resp[i].result = result; + /* We expect either the meta data still to exist or the sub request + * to have already failed. */ + DEBUGASSERT(doh_req || result); + if(doh_req) { + if(!result) { + dohp->probe_resp[i].dnstype = doh_req->dnstype; + result = curlx_dyn_addn(&dohp->probe_resp[i].body, + curlx_dyn_ptr(&doh_req->resp_body), + curlx_dyn_len(&doh_req->resp_body)); + curlx_dyn_free(&doh_req->resp_body); + } + Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); + } + + if(result) + infof(doh, "DoH request %s", curl_easy_strerror(result)); - if(!dohp->pending) { - /* DoH completed */ - curl_slist_free_all(dohp->headers); - dohp->headers = NULL; - Curl_expire(data, 0, EXPIRE_RUN_NOW); + if(!dohp->pending) { + /* DoH completed, run the transfer picking up the results */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } } - return 0; } -#define ERROR_CHECK_SETOPT(x,y) \ -do { \ - result = curl_easy_setopt(doh, x, y); \ - if(result && \ - result != CURLE_NOT_BUILT_IN && \ - result != CURLE_UNKNOWN_OPTION) \ - goto error; \ -} while(0) - -static CURLcode dohprobe(struct Curl_easy *data, - struct dnsprobe *p, DNStype dnstype, - const char *host, - const char *url, CURLM *multi, - struct curl_slist *headers) +static void doh_probe_dtor(void *key, size_t klen, void *e) +{ + (void)key; + (void)klen; + if(e) { + struct doh_request *doh_req = e; + curl_slist_free_all(doh_req->req_hds); + curlx_dyn_free(&doh_req->resp_body); + free(e); + } +} + +#define ERROR_CHECK_SETOPT(x,y) \ + do { \ + result = curl_easy_setopt((CURL *)doh, x, y); \ + if(result && \ + result != CURLE_NOT_BUILT_IN && \ + result != CURLE_UNKNOWN_OPTION) \ + goto error; \ + } while(0) + +static CURLcode doh_probe_run(struct Curl_easy *data, + DNStype dnstype, + const char *host, + const char *url, CURLM *multi, + unsigned int *pmid) { struct Curl_easy *doh = NULL; - char *nurl = NULL; CURLcode result = CURLE_OK; timediff_t timeout_ms; - DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer), - &p->dohlen); + struct doh_request *doh_req; + DOHcode d; + + *pmid = UINT_MAX; + + doh_req = calloc(1, sizeof(*doh_req)); + if(!doh_req) + return CURLE_OUT_OF_MEMORY; + doh_req->dnstype = dnstype; + curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE); + + d = doh_req_encode(host, dnstype, doh_req->req_body, + sizeof(doh_req->req_body), + &doh_req->req_body_len); if(d) { failf(data, "Failed to encode DoH packet [%d]", d); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } - p->dnstype = dnstype; - Curl_dyn_init(&p->serverdoh, DYN_DOH_RESPONSE); - timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms <= 0) { result = CURLE_OPERATION_TIMEDOUT; goto error; } + + doh_req->req_hds = + curl_slist_append(NULL, "Content-Type: application/dns-message"); + if(!doh_req->req_hds) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* Curl_open() is the internal version of curl_easy_init() */ result = Curl_open(&doh); - if(!result) { - /* pass in the struct pointer via a local variable to please coverity and - the gcc typecheck helpers */ - struct dynbuf *resp = &p->serverdoh; - ERROR_CHECK_SETOPT(CURLOPT_URL, url); - ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); - ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb); - ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, resp); - ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->dohbuffer); - ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->dohlen); - ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers); + if(result) + goto error; + + /* pass in the struct pointer via a local variable to please coverity and + the gcc typecheck helpers */ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + doh->state.feat = &Curl_trc_feat_dns; +#endif + ERROR_CHECK_SETOPT(CURLOPT_URL, url); + ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); + ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb); + ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, doh); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, doh_req->req_body); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)doh_req->req_body_len); + ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, doh_req->req_hds); #ifdef USE_HTTP2 - ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); #endif -#ifndef CURLDEBUG - /* enforce HTTPS if not debug */ - ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); +#ifndef DEBUGBUILD + /* enforce HTTPS if not debug */ + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, (long)CURLPROTO_HTTPS); #else - /* in debug mode, also allow http */ - ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); + /* in debug mode, also allow http */ + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, (long)CURLPROTO_HTTP|CURLPROTO_HTTPS); #endif - ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms); - ERROR_CHECK_SETOPT(CURLOPT_SHARE, data->share); - if(data->set.err && data->set.err != stderr) - ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err); - if(data->set.verbose) - ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L); - if(data->set.no_signal) - ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L); - - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, - data->set.doh_verifyhost ? 2L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, - data->set.doh_verifypeer ? 1L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, - data->set.doh_verifystatus ? 1L : 0L); - - /* Inherit *some* SSL options from the user's transfer. This is a - best-guess as to which options are needed for compatibility. #3661 - - Note DoH does not inherit the user's proxy server so proxy SSL settings - have no effect and are not inherited. If that changes then two new - options should be added to check doh proxy insecure separately, - CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. - */ - if(data->set.ssl.falsestart) - ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L); - if(data->set.str[STRING_SSL_CAFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO, - data->set.str[STRING_SSL_CAFILE]); - } - if(data->set.blobs[BLOB_CAINFO]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, - data->set.blobs[BLOB_CAINFO]); - } - if(data->set.str[STRING_SSL_CAPATH]) { - ERROR_CHECK_SETOPT(CURLOPT_CAPATH, - data->set.str[STRING_SSL_CAPATH]); - } - if(data->set.str[STRING_SSL_CRLFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, - data->set.str[STRING_SSL_CRLFILE]); - } - 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.str[STRING_SSL_EC_CURVES]) { - ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, - data->set.str[STRING_SSL_EC_CURVES]); - } + ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms); + ERROR_CHECK_SETOPT(CURLOPT_SHARE, (CURLSH *)data->share); + if(data->set.err && data->set.err != stderr) + ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) + ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L); + if(data->set.no_signal) + ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L); + + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, + data->set.doh_verifyhost ? 2L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, + data->set.doh_verifypeer ? 1L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, + data->set.doh_verifystatus ? 1L : 0L); + + /* Inherit *some* SSL options from the user's transfer. This is a + best-guess as to which options are needed for compatibility. #3661 + + Note DoH does not inherit the user's proxy server so proxy SSL settings + have no effect and are not inherited. If that changes then two new + options should be added to check doh proxy insecure separately, + CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. + */ + if(data->set.ssl.falsestart) + ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L); + if(data->set.str[STRING_SSL_CAFILE]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO, + data->set.str[STRING_SSL_CAFILE]); + } + if(data->set.blobs[BLOB_CAINFO]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, + data->set.blobs[BLOB_CAINFO]); + } + if(data->set.str[STRING_SSL_CAPATH]) { + ERROR_CHECK_SETOPT(CURLOPT_CAPATH, + data->set.str[STRING_SSL_CAPATH]); + } + if(data->set.str[STRING_SSL_CRLFILE]) { + ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, + data->set.str[STRING_SSL_CRLFILE]); + } + 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.fdebug) + ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug); + if(data->set.debugdata) + ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata); + if(data->set.str[STRING_SSL_EC_CURVES]) { + ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, + data->set.str[STRING_SSL_EC_CURVES]); + } - { - long mask = - (data->set.ssl.enable_beast ? - CURLSSLOPT_ALLOW_BEAST : 0) | - (data->set.ssl.no_revoke ? - CURLSSLOPT_NO_REVOKE : 0) | - (data->set.ssl.no_partialchain ? - CURLSSLOPT_NO_PARTIALCHAIN : 0) | - (data->set.ssl.revoke_best_effort ? - CURLSSLOPT_REVOKE_BEST_EFFORT : 0) | - (data->set.ssl.native_ca_store ? - CURLSSLOPT_NATIVE_CA : 0) | - (data->set.ssl.auto_client_cert ? - CURLSSLOPT_AUTO_CLIENT_CERT : 0); - - (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask); - } + (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, + (long)data->set.ssl.primary.ssl_options); - doh->set.fmultidone = doh_done; - doh->set.dohfor = data; /* identify for which transfer this is done */ - p->easy = doh; + doh->state.internal = TRUE; + doh->master_mid = data->mid; /* master transfer of this one */ - /* DoH private_data must be null because the user must have a way to - distinguish their transfer's handle from DoH handles in user - callbacks (ie SSL CTX callback). */ - DEBUGASSERT(!doh->set.private_data); + result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor); + doh_req = NULL; /* call took ownership */ + if(result) + goto error; - if(curl_multi_add_handle(multi, doh)) - goto error; - } - else + /* DoH handles must not inherit private_data. The handles may be passed to + the user via callbacks and the user will be able to identify them as + internal handles because private data is not set. The user can then set + private_data via CURLOPT_PRIVATE if they so choose. */ + DEBUGASSERT(!doh->set.private_data); + + if(curl_multi_add_handle(multi, doh)) goto error; - free(nurl); + + *pmid = doh->mid; return CURLE_OK; error: - free(nurl); Curl_close(&doh); + if(doh_req) + doh_probe_dtor(NULL, 0, doh_req); return result; } @@ -361,66 +448,94 @@ static CURLcode dohprobe(struct Curl_easy *data, struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, const char *hostname, int port, + int ip_version, int *waitp) { CURLcode result = CURLE_OK; - int slot; - struct dohdata *dohp; + struct doh_probes *dohp = NULL; struct connectdata *conn = data->conn; - *waitp = TRUE; /* this never returns synchronously */ - (void)hostname; - (void)port; + size_t i; - DEBUGASSERT(!data->req.doh); DEBUGASSERT(conn); + DEBUGASSERT(!data->state.async.doh); + if(data->state.async.doh) + Curl_doh_cleanup(data); + + data->state.async.done = FALSE; + data->state.async.port = port; + data->state.async.ip_version = ip_version; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + return NULL; /* start clean, consider allocating this struct on demand */ - dohp = data->req.doh = calloc(sizeof(struct dohdata), 1); + data->state.async.doh = dohp = calloc(1, sizeof(struct doh_probes)); if(!dohp) return NULL; + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + dohp->probe_resp[i].probe_mid = UINT_MAX; + curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE); + } + conn->bits.doh = TRUE; - dohp->host = hostname; - dohp->port = port; - dohp->headers = - curl_slist_append(NULL, - "Content-Type: application/dns-message"); - if(!dohp->headers) - goto error; + dohp->host = data->state.async.hostname; + dohp->port = data->state.async.port; + /* We are making sub easy handles and want to be called back when + * one is done. */ + data->sub_xfer_done = doh_probe_done; /* create IPv4 DoH request */ - result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V4], - DNS_TYPE_A, hostname, data->set.str[STRING_DOH], - data->multi, dohp->headers); + result = doh_probe_run(data, DNS_TYPE_A, + hostname, data->set.str[STRING_DOH], + data->multi, + &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); if(result) goto error; dohp->pending++; -#ifdef ENABLE_IPV6 - if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { +#ifdef USE_IPV6 + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* create IPv6 DoH request */ - result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6], - DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], - data->multi, dohp->headers); + result = doh_probe_run(data, DNS_TYPE_AAAA, + hostname, data->set.str[STRING_DOH], + data->multi, + &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid); + if(result) + goto error; + dohp->pending++; + } +#endif + +#ifdef USE_HTTPSRR + if(conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* Only use HTTPS RR for HTTP(S) transfers */ + char *qname = NULL; + if(port != PORT_HTTPS) { + qname = aprintf("_%d._https.%s", port, hostname); + if(!qname) + goto error; + } + result = doh_probe_run(data, DNS_TYPE_HTTPS, + qname ? qname : hostname, data->set.str[STRING_DOH], + data->multi, + &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid); + free(qname); if(result) goto error; dohp->pending++; } #endif + *waitp = TRUE; /* this never returns synchronously */ return NULL; error: - curl_slist_free_all(dohp->headers); - data->req.doh->headers = NULL; - for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { - Curl_close(&dohp->probe[slot].easy); - } - Curl_safefree(data->req.doh); + Curl_doh_cleanup(data); return NULL; } -static DOHcode skipqname(const unsigned char *doh, size_t dohlen, - unsigned int *indexp) +static DOHcode doh_skipqname(const unsigned char *doh, size_t dohlen, + unsigned int *indexp) { unsigned char length; do { @@ -438,29 +553,32 @@ static DOHcode skipqname(const unsigned char *doh, size_t dohlen, return DOH_DNS_BAD_LABEL; if(dohlen < (*indexp + 1 + length)) return DOH_DNS_OUT_OF_RANGE; - *indexp += 1 + length; + *indexp += (unsigned int)(1 + length); } while(length); return DOH_OK; } -static unsigned short get16bit(const unsigned char *doh, int index) +static unsigned short doh_get16bit(const unsigned char *doh, + unsigned int index) { return (unsigned short)((doh[index] << 8) | doh[index + 1]); } -static unsigned int get32bit(const unsigned char *doh, int index) +static unsigned int doh_get32bit(const unsigned char *doh, unsigned int index) { - /* make clang and gcc optimize this to bswap by incrementing - the pointer first. */ - doh += index; - - /* avoid undefined behavior by casting to unsigned before shifting - 24 bits, possibly into the sign bit. codegen is same, but - ub sanitizer won't be upset */ - return ( (unsigned)doh[0] << 24) | (doh[1] << 16) |(doh[2] << 8) | doh[3]; + /* make clang and gcc optimize this to bswap by incrementing + the pointer first. */ + doh += index; + + /* avoid undefined behavior by casting to unsigned before shifting + 24 bits, possibly into the sign bit. codegen is same, but + ub sanitizer will not be upset */ + return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) | + ((unsigned)doh[2] << 8) | doh[3]; } -static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d) +static void doh_store_a(const unsigned char *doh, int index, + struct dohentry *d) { /* silently ignore addresses over the limit */ if(d->numaddr < DOH_MAX_ADDR) { @@ -469,12 +587,10 @@ static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d) memcpy(&a->ip.v4, &doh[index], 4); d->numaddr++; } - return DOH_OK; } -static DOHcode store_aaaa(const unsigned char *doh, - int index, - struct dohentry *d) +static void doh_store_aaaa(const unsigned char *doh, int index, + struct dohentry *d) { /* silently ignore addresses over the limit */ if(d->numaddr < DOH_MAX_ADDR) { @@ -483,13 +599,27 @@ static DOHcode store_aaaa(const unsigned char *doh, memcpy(&a->ip.v6, &doh[index], 16); d->numaddr++; } +} + +#ifdef USE_HTTPSRR +static DOHcode doh_store_https(const unsigned char *doh, int index, + struct dohentry *d, uint16_t len) +{ + /* silently ignore RRs over the limit */ + if(d->numhttps_rrs < DOH_MAX_HTTPS) { + struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs]; + h->val = Curl_memdup(&doh[index], len); + if(!h->val) + return DOH_OUT_OF_MEM; + h->len = len; + d->numhttps_rrs++; + } return DOH_OK; } +#endif -static DOHcode store_cname(const unsigned char *doh, - size_t dohlen, - unsigned int index, - struct dohentry *d) +static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen, + unsigned int index, struct dohentry *d) { struct dynbuf *c; unsigned int loop = 128; /* a valid DNS name can never loop this much */ @@ -511,7 +641,7 @@ static DOHcode store_cname(const unsigned char *doh, /* move to the new index */ newpos = (length & 0x3f) << 8 | doh[index + 1]; - index = newpos; + index = (unsigned int)newpos; continue; } else if(length & 0xc0) @@ -520,14 +650,14 @@ static DOHcode store_cname(const unsigned char *doh, index++; if(length) { - if(Curl_dyn_len(c)) { - if(Curl_dyn_addn(c, STRCONST("."))) + if(curlx_dyn_len(c)) { + if(curlx_dyn_addn(c, STRCONST("."))) return DOH_OUT_OF_MEM; } if((index + length) > dohlen) return DOH_DNS_BAD_LABEL; - if(Curl_dyn_addn(c, &doh[index], length)) + if(curlx_dyn_addn(c, &doh[index], length)) return DOH_OUT_OF_MEM; index += length; } @@ -538,36 +668,40 @@ static DOHcode store_cname(const unsigned char *doh, return DOH_OK; } -static DOHcode rdata(const unsigned char *doh, - size_t dohlen, - unsigned short rdlength, - unsigned short type, - int index, - struct dohentry *d) +static DOHcode doh_rdata(const unsigned char *doh, + size_t dohlen, + unsigned short rdlength, + unsigned short type, + int index, + struct dohentry *d) { /* RDATA - A (TYPE 1): 4 bytes - AAAA (TYPE 28): 16 bytes - - NS (TYPE 2): N bytes */ + - NS (TYPE 2): N bytes + - HTTPS (TYPE 65): N bytes */ DOHcode rc; switch(type) { case DNS_TYPE_A: if(rdlength != 4) return DOH_DNS_RDATA_LEN; - rc = store_a(doh, index, d); - if(rc) - return rc; + doh_store_a(doh, index, d); break; case DNS_TYPE_AAAA: if(rdlength != 16) return DOH_DNS_RDATA_LEN; - rc = store_aaaa(doh, index, d); + doh_store_aaaa(doh, index, d); + break; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + rc = doh_store_https(doh, index, d, rdlength); if(rc) return rc; break; +#endif case DNS_TYPE_CNAME: - rc = store_cname(doh, dohlen, index, d); + rc = doh_store_cname(doh, dohlen, (unsigned int)index, d); if(rc) return rc; break; @@ -587,14 +721,14 @@ UNITTEST void de_init(struct dohentry *de) memset(de, 0, sizeof(*de)); de->ttl = INT_MAX; for(i = 0; i < DOH_MAX_CNAME; i++) - Curl_dyn_init(&de->cname[i], DYN_DOH_CNAME); + curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME); } -UNITTEST DOHcode doh_decode(const unsigned char *doh, - size_t dohlen, - DNStype dnstype, - struct dohentry *d) +UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, + size_t dohlen, + DNStype dnstype, + struct dohentry *d) { unsigned char rcode; unsigned short qdcount; @@ -614,9 +748,9 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(rcode) return DOH_DNS_BAD_RCODE; /* bad rcode */ - qdcount = get16bit(doh, 4); + qdcount = doh_get16bit(doh, 4); while(qdcount) { - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ if(dohlen < (index + 4)) @@ -625,19 +759,19 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, qdcount--; } - ancount = get16bit(doh, 6); + ancount = doh_get16bit(doh, 6); while(ancount) { unsigned short class; unsigned int ttl; - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - type = get16bit(doh, index); + type = doh_get16bit(doh, index); if((type != DNS_TYPE_CNAME) /* may be synthesized from DNAME */ && (type != DNS_TYPE_DNAME) /* if present, accept and ignore */ && (type != dnstype)) @@ -647,7 +781,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - class = get16bit(doh, index); + class = doh_get16bit(doh, index); if(DNS_CLASS_IN != class) return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */ index += 2; @@ -655,7 +789,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 4)) return DOH_DNS_OUT_OF_RANGE; - ttl = get32bit(doh, index); + ttl = doh_get32bit(doh, index); if(ttl < d->ttl) d->ttl = ttl; index += 4; @@ -663,21 +797,21 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - rdlength = get16bit(doh, index); + rdlength = doh_get16bit(doh, index); index += 2; if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; - rc = rdata(doh, dohlen, rdlength, type, index, d); + rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d); if(rc) - return rc; /* bad rdata */ + return rc; /* bad doh_rdata */ index += rdlength; ancount--; } - nscount = get16bit(doh, 8); + nscount = doh_get16bit(doh, 8); while(nscount) { - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ @@ -689,7 +823,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - rdlength = get16bit(doh, index); + rdlength = doh_get16bit(doh, index); index += 2; if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; @@ -697,9 +831,9 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, nscount--; } - arcount = get16bit(doh, 10); + arcount = doh_get16bit(doh, 10); while(arcount) { - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ @@ -711,7 +845,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - rdlength = get16bit(doh, index); + rdlength = doh_get16bit(doh, index); index += 2; if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; @@ -722,7 +856,11 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(index != dohlen) return DOH_DNS_MALFORMAT; /* something is wrong */ +#ifdef USE_HTTTPS + if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs) +#else if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr) +#endif /* nothing stored! */ return DOH_NO_CONTENT; @@ -730,29 +868,27 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, } #ifndef CURL_DISABLE_VERBOSE_STRINGS -static void showdoh(struct Curl_easy *data, - const struct dohentry *d) +static void doh_show(struct Curl_easy *data, + const struct dohentry *d) { int i; - infof(data, "TTL: %u seconds", d->ttl); + infof(data, "[DoH] TTL: %u seconds", d->ttl); for(i = 0; i < d->numaddr; i++) { const struct dohaddr *a = &d->addr[i]; if(a->type == DNS_TYPE_A) { - infof(data, "DoH A: %u.%u.%u.%u", + infof(data, "[DoH] A: %u.%u.%u.%u", a->ip.v4[0], a->ip.v4[1], a->ip.v4[2], a->ip.v4[3]); } else if(a->type == DNS_TYPE_AAAA) { int j; - char buffer[128]; - char *ptr; - size_t len; - msnprintf(buffer, 128, "DoH AAAA: "); - ptr = &buffer[10]; - len = 118; + char buffer[128] = "[DoH] AAAA: "; + size_t len = strlen(buffer); + char *ptr = &buffer[len]; + len = sizeof(buffer) - len; for(j = 0; j < 16; j += 2) { size_t l; - msnprintf(ptr, len, "%s%02x%02x", j?":":"", d->addr[i].ip.v6[j], + msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "", d->addr[i].ip.v6[j], d->addr[i].ip.v6[j + 1]); l = strlen(ptr); len -= l; @@ -761,12 +897,22 @@ static void showdoh(struct Curl_easy *data, infof(data, "%s", buffer); } } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) { +# ifdef DEBUGBUILD + doh_print_buf(data, "DoH HTTPS", + d->https_rrs[i].val, d->https_rrs[i].len); +# else + infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len); +# endif + } +#endif for(i = 0; i < d->numcname; i++) { - infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i])); + infof(data, "CNAME: %s", curlx_dyn_ptr(&d->cname[i])); } } #else -#define showdoh(x,y) +#define doh_show(x,y) #endif /* @@ -774,38 +920,39 @@ static void showdoh(struct Curl_easy *data, * * This function returns a pointer to the first element of a newly allocated * Curl_addrinfo struct linked list filled with the data from a set of DoH - * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for - * a IPv6 stack, but usable also for IPv4, all hosts and environments. + * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for + * an IPv6 stack, but usable also for IPv4, all hosts and environments. * * The memory allocated by this function *MUST* be free'd later on calling - * Curl_freeaddrinfo(). For each successful call to this function there + * Curl_freeaddrinfo(). For each successful call to this function there * must be an associated call later to Curl_freeaddrinfo(). */ -static struct Curl_addrinfo * -doh2ai(const struct dohentry *de, const char *hostname, int port) +static CURLcode doh2ai(const struct dohentry *de, const char *hostname, + int port, struct Curl_addrinfo **aip) { struct Curl_addrinfo *ai; struct Curl_addrinfo *prevai = NULL; struct Curl_addrinfo *firstai = NULL; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif CURLcode result = CURLE_OK; int i; size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ - if(!de) - /* no input == no output! */ - return NULL; + DEBUGASSERT(de); + + if(!de->numaddr) + return CURLE_COULDNT_RESOLVE_HOST; for(i = 0; i < de->numaddr; i++) { size_t ss_size; CURL_SA_FAMILY_T addrtype; if(de->addr[i].type == DNS_TYPE_AAAA) { -#ifndef ENABLE_IPV6 - /* we can't handle IPv6 addresses */ +#ifndef USE_IPV6 + /* we cannot handle IPv6 addresses */ continue; #else ss_size = sizeof(struct sockaddr_in6); @@ -849,16 +996,16 @@ doh2ai(const struct dohentry *de, const char *hostname, int port) addr = (void *)ai->ai_addr; /* storage area for this info */ DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4)); memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr)); - addr->sin_family = addrtype; + addr->sin_family = (CURL_SA_FAMILY_T)addrtype; addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ai->ai_addr; /* storage area for this info */ DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6)); memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr)); - addr6->sin6_family = addrtype; + addr6->sin6_family = (CURL_SA_FAMILY_T)addrtype; addr6->sin6_port = htons((unsigned short)port); break; #endif @@ -871,14 +1018,26 @@ doh2ai(const struct dohentry *de, const char *hostname, int port) Curl_freeaddrinfo(firstai); firstai = NULL; } + *aip = firstai; - return firstai; + return result; } #ifndef CURL_DISABLE_VERBOSE_STRINGS -static const char *type2name(DNStype dnstype) +static const char *doh_type2name(DNStype dnstype) { - return (dnstype == DNS_TYPE_A)?"A":"AAAA"; + switch(dnstype) { + case DNS_TYPE_A: + return "A"; + case DNS_TYPE_AAAA: + return "AAAA"; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + return "HTTPS"; +#endif + default: + return "unknown"; + } } #endif @@ -886,93 +1045,266 @@ UNITTEST void de_cleanup(struct dohentry *d) { int i = 0; for(i = 0; i < d->numcname; i++) { - Curl_dyn_free(&d->cname[i]); + curlx_dyn_free(&d->cname[i]); + } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) + Curl_safefree(d->https_rrs[i].val); +#endif +} + +#ifdef USE_HTTPSRR + +/* + * @brief decode the DNS name in a binary RRData + * @param buf points to the buffer (in/out) + * @param remaining points to the remaining buffer length (in/out) + * @param dnsname returns the string form name on success + * @return is 1 for success, error otherwise + * + * The encoding here is defined in + * https://tools.ietf.org/html/rfc1035#section-3.1 + * + * The input buffer pointer will be modified so it points to + * just after the end of the DNS name encoding on output. (And + * that is why it is an "unsigned char **" :-) + */ +static CURLcode doh_decode_rdata_name(const unsigned char **buf, + size_t *remaining, char **dnsname) +{ + const unsigned char *cp = NULL; + size_t rem = 0; + unsigned char clen = 0; /* chunk len */ + struct dynbuf thename; + + DEBUGASSERT(buf && remaining && dnsname); + if(!buf || !remaining || !dnsname || !*remaining) + return CURLE_OUT_OF_MEMORY; + curlx_dyn_init(&thename, CURL_MAXLEN_host_name); + rem = *remaining; + cp = *buf; + clen = *cp++; + if(clen == 0) { + /* special case - return "." as name */ + if(curlx_dyn_addn(&thename, ".", 1)) + return CURLE_OUT_OF_MEMORY; + } + while(clen) { + if(clen >= rem) { + curlx_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + if(curlx_dyn_addn(&thename, cp, clen) || + curlx_dyn_addn(&thename, ".", 1)) + return CURLE_TOO_LARGE; + + cp += clen; + rem -= (clen + 1); + if(rem <= 0) { + curlx_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + clen = *cp++; + } + *buf = cp; + *remaining = rem - 1; + *dnsname = curlx_dyn_ptr(&thename); + return CURLE_OK; +} + +UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, + const unsigned char *cp, size_t len, + struct Curl_https_rrinfo **hrr); + +/* @unittest 1658 */ +UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, + const unsigned char *cp, size_t len, + struct Curl_https_rrinfo **hrr) +{ + uint16_t pcode = 0, plen = 0; + uint32_t expected_min_pcode = 0; + struct Curl_https_rrinfo *lhrr = NULL; + char *dnsname = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + size_t olen; + + *hrr = NULL; + if(len <= 2) + return CURLE_BAD_FUNCTION_ARGUMENT; + lhrr = calloc(1, sizeof(struct Curl_https_rrinfo)); + if(!lhrr) + return CURLE_OUT_OF_MEMORY; + lhrr->priority = doh_get16bit(cp, 0); + cp += 2; + len -= 2; + if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK) + goto err; + lhrr->target = dnsname; + if(Curl_junkscan(dnsname, &olen, FALSE)) { + /* unacceptable hostname content */ + result = CURLE_WEIRD_SERVER_REPLY; + goto err; + } + lhrr->port = -1; /* until set */ + while(len >= 4) { + pcode = doh_get16bit(cp, 0); + plen = doh_get16bit(cp, 2); + cp += 4; + len -= 4; + if(pcode < expected_min_pcode || plen > len) { + result = CURLE_WEIRD_SERVER_REPLY; + goto err; + } + result = Curl_httpsrr_set(data, lhrr, pcode, cp, plen); + if(result) + goto err; + cp += plen; + len -= plen; + expected_min_pcode = pcode + 1; + } + DEBUGASSERT(!len); + *hrr = lhrr; + return CURLE_OK; +err: + Curl_httpsrr_cleanup(lhrr); + Curl_safefree(lhrr); + return result; +} + +#ifdef DEBUGBUILD +UNITTEST void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr); + +UNITTEST void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr) +{ + DEBUGASSERT(hrr); + infof(data, "HTTPS RR: priority %d, target: %s", + hrr->priority, hrr->target); + if(hrr->alpns[0] != ALPN_none) + infof(data, "HTTPS RR: alpns %u %u %u %u", + hrr->alpns[0], hrr->alpns[1], hrr->alpns[2], hrr->alpns[3]); + else + infof(data, "HTTPS RR: no alpns"); + if(hrr->no_def_alpn) + infof(data, "HTTPS RR: no_def_alpn set"); + else + infof(data, "HTTPS RR: no_def_alpn not set"); + if(hrr->ipv4hints) { + doh_print_buf(data, "HTTPS RR: ipv4hints", + hrr->ipv4hints, hrr->ipv4hints_len); + } + else + infof(data, "HTTPS RR: no ipv4hints"); + if(hrr->echconfiglist) { + doh_print_buf(data, "HTTPS RR: ECHConfigList", + hrr->echconfiglist, hrr->echconfiglist_len); + } + else + infof(data, "HTTPS RR: no ECHConfigList"); + if(hrr->ipv6hints) { + doh_print_buf(data, "HTTPS RR: ipv6hint", + hrr->ipv6hints, hrr->ipv6hints_len); } + else + infof(data, "HTTPS RR: no ipv6hints"); + return; } +# endif +#endif CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dnsp) { CURLcode result; - struct dohdata *dohp = data->req.doh; + struct doh_probes *dohp = data->state.async.doh; *dnsp = NULL; /* defaults to no response */ if(!dohp) return CURLE_OUT_OF_MEMORY; - if(!dohp->probe[DOH_PROBE_SLOT_IPADDR_V4].easy && - !dohp->probe[DOH_PROBE_SLOT_IPADDR_V6].easy) { - failf(data, "Could not DoH-resolve: %s", data->state.async.hostname); - return CONN_IS_PROXIED(data->conn)?CURLE_COULDNT_RESOLVE_PROXY: + if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT_MAX && + dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT_MAX) { + failf(data, "Could not DoH-resolve: %s", dohp->host); + return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } else if(!dohp->pending) { - DOHcode rc[DOH_PROBE_SLOTS] = { - DOH_OK, DOH_OK - }; + DOHcode rc[DOH_SLOT_COUNT]; struct dohentry de; int slot; + + /* Clear any result the might still be there */ + Curl_resolv_unlink(data, &data->state.async.dns); + + memset(rc, 0, sizeof(rc)); /* remove DoH handles from multi handle and close them */ - for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { - curl_multi_remove_handle(data->multi, dohp->probe[slot].easy); - Curl_close(&dohp->probe[slot].easy); - } + Curl_doh_close(data); /* parse the responses, create the struct and return it! */ de_init(&de); - for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { - struct dnsprobe *p = &dohp->probe[slot]; + for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { + struct doh_response *p = &dohp->probe_resp[slot]; if(!p->dnstype) continue; - rc[slot] = doh_decode(Curl_dyn_uptr(&p->serverdoh), - Curl_dyn_len(&p->serverdoh), - p->dnstype, - &de); - Curl_dyn_free(&p->serverdoh); + rc[slot] = doh_resp_decode(curlx_dyn_uptr(&p->body), + curlx_dyn_len(&p->body), + p->dnstype, &de); +#ifndef CURL_DISABLE_VERBOSE_STRINGS if(rc[slot]) { infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]), - type2name(p->dnstype), dohp->host); + doh_type2name(p->dnstype), dohp->host); } +#endif } /* next slot */ result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */ - if(!rc[DOH_PROBE_SLOT_IPADDR_V4] || !rc[DOH_PROBE_SLOT_IPADDR_V6]) { + if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { /* we have an address, of one kind or other */ struct Curl_dns_entry *dns; struct Curl_addrinfo *ai; - infof(data, "DoH Host name: %s", dohp->host); - showdoh(data, &de); - ai = doh2ai(&de, dohp->host, dohp->port); - if(!ai) { - de_cleanup(&de); - return CURLE_OUT_OF_MEMORY; + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { + CURL_TRC_DNS(data, "hostname: %s", dohp->host); + doh_show(data, &de); } - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - /* we got a response, store it in the cache */ - dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - /* returned failure, bail out nicely */ - Curl_freeaddrinfo(ai); + result = doh2ai(&de, dohp->host, dohp->port, &ai); + if(result) { + de_cleanup(&de); + return result; } - else { + + /* we got a response, create a dns entry. */ + dns = Curl_dnscache_mk_entry(data, ai, dohp->host, 0, dohp->port, FALSE); + if(dns) { + /* Now add and HTTPSRR information if we have */ +#ifdef USE_HTTPSRR + if(de.numhttps_rrs > 0 && result == CURLE_OK) { + struct Curl_https_rrinfo *hrr = NULL; + result = doh_resp_decode_httpsrr(data, de.https_rrs->val, + de.https_rrs->len, &hrr); + if(result) { + infof(data, "Failed to decode HTTPS RR"); + return result; + } + infof(data, "Some HTTPS RR to process"); +# ifdef DEBUGBUILD + doh_print_httpsrr(data, hrr); +# endif + dns->hinfo = hrr; + } +#endif + /* and add the entry to the cache */ data->state.async.dns = dns; - *dnsp = dns; - result = CURLE_OK; /* address resolution OK */ + result = Curl_dnscache_add(data, dns); + *dnsp = data->state.async.dns; } } /* address processing done */ - /* Now process any build-specific attributes retrieved from DNS */ - /* All done */ + data->state.async.done = TRUE; de_cleanup(&de); - Curl_safefree(data->req.doh); + Curl_doh_cleanup(data); return result; } /* !dohp->pending */ @@ -981,4 +1313,46 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, return CURLE_OK; } +void Curl_doh_close(struct Curl_easy *data) +{ + struct doh_probes *doh = data->state.async.doh; + if(doh && data->multi) { + struct Curl_easy *probe_data; + unsigned int mid; + size_t slot; + for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { + mid = doh->probe_resp[slot].probe_mid; + if(mid == UINT_MAX) + continue; + doh->probe_resp[slot].probe_mid = UINT_MAX; + /* should have been called before data is removed from multi handle */ + DEBUGASSERT(data->multi); + probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) : + NULL; + if(!probe_data) { + DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", + doh->probe_resp[slot].probe_mid)); + continue; + } + /* data->multi might already be reset at this time */ + curl_multi_remove_handle(data->multi, probe_data); + Curl_close(&probe_data); + } + data->sub_xfer_done = NULL; + } +} + +void Curl_doh_cleanup(struct Curl_easy *data) +{ + struct doh_probes *dohp = data->state.async.doh; + if(dohp) { + int i; + Curl_doh_close(data); + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + curlx_dyn_free(&dohp->probe_resp[i].body); + } + Curl_safefree(data->state.async.doh); + } +} + #endif /* CURL_DISABLE_DOH */ diff --git a/Utilities/cmcurl/lib/doh.h b/Utilities/cmcurl/lib/doh.h index 7d7b694f33a..9146f53580f 100644 --- a/Utilities/cmcurl/lib/doh.h +++ b/Utilities/cmcurl/lib/doh.h @@ -26,6 +26,10 @@ #include "urldata.h" #include "curl_addrinfo.h" +#ifdef USE_HTTPSRR +# include +# include "httpsrr.h" +#endif #ifndef CURL_DISABLE_DOH @@ -51,22 +55,58 @@ typedef enum { DNS_TYPE_NS = 2, DNS_TYPE_CNAME = 5, DNS_TYPE_AAAA = 28, - DNS_TYPE_DNAME = 39 /* RFC6672 */ + DNS_TYPE_DNAME = 39, /* RFC6672 */ + DNS_TYPE_HTTPS = 65 } DNStype; -/* one of these for each DoH request */ -struct dnsprobe { - CURL *easy; +enum doh_slot_num { + /* Explicit values for first two symbols so as to match hard-coded + * constants in existing code + */ + DOH_SLOT_IPV4 = 0, /* make 'V4' stand out for readability */ + DOH_SLOT_IPV6 = 1, /* 'V6' likewise */ + + /* Space here for (possibly build-specific) additional slot definitions */ +#ifdef USE_HTTPSRR + DOH_SLOT_HTTPS_RR = 2, /* for HTTPS RR */ +#endif + + /* for example */ + /* #ifdef WANT_DOH_FOOBAR_TXT */ + /* DOH_PROBE_SLOT_FOOBAR_TXT, */ + /* #endif */ + + /* AFTER all slot definitions, establish how many we have */ + DOH_SLOT_COUNT +}; + +#define CURL_EZM_DOH_PROBE "ezm:doh-p" + +/* the largest one we can make, based on RFCs 1034, 1035 */ +#define DOH_MAX_DNSREQ_SIZE (256 + 16) + +/* each DoH probe request has this + * as easy meta for CURL_EZM_DOH_PROBE */ +struct doh_request { + unsigned char req_body[DOH_MAX_DNSREQ_SIZE]; + struct curl_slist *req_hds; + struct dynbuf resp_body; + size_t req_body_len; + DNStype dnstype; +}; + +struct doh_response { + unsigned int probe_mid; + struct dynbuf body; DNStype dnstype; - unsigned char dohbuffer[512]; - size_t dohlen; - struct dynbuf serverdoh; + CURLcode result; }; -struct dohdata { - struct curl_slist *headers; - struct dnsprobe probe[DOH_PROBE_SLOTS]; - unsigned int pending; /* still outstanding requests */ +/* each transfer firing off DoH requests has this + * as easy meta for CURL_EZM_DOH_MASTER */ +struct doh_probes { + struct doh_response probe_resp[DOH_SLOT_COUNT]; + unsigned int pending; /* still outstanding probes */ int port; const char *host; }; @@ -79,15 +119,15 @@ struct dohdata { struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, const char *hostname, int port, + int ip_version, int *waitp); CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dns); -int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks); - #define DOH_MAX_ADDR 24 #define DOH_MAX_CNAME 4 +#define DOH_MAX_HTTPS 4 struct dohaddr { int type; @@ -97,31 +137,53 @@ struct dohaddr { } ip; }; +#ifdef USE_HTTPSRR + +/* + * These may need escaping when found within an ALPN string + * value. + */ +#define COMMA_CHAR ',' +#define BACKSLASH_CHAR '\\' + +struct dohhttps_rr { + uint16_t len; /* raw encoded length */ + unsigned char *val; /* raw encoded octets */ +}; +#endif + struct dohentry { struct dynbuf cname[DOH_MAX_CNAME]; struct dohaddr addr[DOH_MAX_ADDR]; int numaddr; unsigned int ttl; int numcname; +#ifdef USE_HTTPSRR + struct dohhttps_rr https_rrs[DOH_MAX_HTTPS]; + int numhttps_rrs; +#endif }; - -#ifdef DEBUGBUILD -DOHcode doh_encode(const char *host, - DNStype dnstype, - unsigned char *dnsp, /* buffer */ - size_t len, /* buffer size */ - size_t *olen); /* output length */ -DOHcode doh_decode(const unsigned char *doh, - size_t dohlen, - DNStype dnstype, - struct dohentry *d); -void de_init(struct dohentry *d); -void de_cleanup(struct dohentry *d); +void Curl_doh_close(struct Curl_easy *data); +void Curl_doh_cleanup(struct Curl_easy *data); + +#ifdef UNITTESTS +UNITTEST DOHcode doh_req_encode(const char *host, + DNStype dnstype, + unsigned char *dnsp, /* buffer */ + size_t len, /* buffer size */ + size_t *olen); /* output length */ +UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, + size_t dohlen, + DNStype dnstype, + struct dohentry *d); + +UNITTEST void de_init(struct dohentry *d); +UNITTEST void de_cleanup(struct dohentry *d); #endif #else /* if DoH is disabled */ -#define Curl_doh(a,b,c,d) NULL +#define Curl_doh(a,b,c,d,e) NULL #define Curl_doh_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST #endif diff --git a/Utilities/cmcurl/lib/dynbuf.c b/Utilities/cmcurl/lib/dynbuf.c deleted file mode 100644 index 0c9c491aebc..00000000000 --- a/Utilities/cmcurl/lib/dynbuf.c +++ /dev/null @@ -1,275 +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 "dynbuf.h" -#include "curl_printf.h" -#ifdef BUILDING_LIBCURL -#include "curl_memory.h" -#endif -#include "memdebug.h" - -#define MIN_FIRST_ALLOC 32 - -#define DYNINIT 0xbee51da /* random pattern */ - -/* - * Init a dynbuf struct. - */ -void Curl_dyn_init(struct dynbuf *s, size_t toobig) -{ - DEBUGASSERT(s); - DEBUGASSERT(toobig); - s->bufr = NULL; - s->leng = 0; - s->allc = 0; - s->toobig = toobig; -#ifdef DEBUGBUILD - s->init = DYNINIT; -#endif -} - -/* - * free the buffer and re-init the necessary fields. It doesn't touch the - * 'init' field and thus this buffer can be reused to add data to again. - */ -void Curl_dyn_free(struct dynbuf *s) -{ - DEBUGASSERT(s); - Curl_safefree(s->bufr); - s->leng = s->allc = 0; -} - -/* - * Store/append an chunk of memory to the dynbuf. - */ -static CURLcode dyn_nappend(struct dynbuf *s, - const unsigned char *mem, size_t len) -{ - size_t indx = s->leng; - size_t a = s->allc; - size_t fit = len + indx + 1; /* new string + old string + zero byte */ - - /* try to detect if there's rubbish in the struct */ - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(s->toobig); - DEBUGASSERT(indx < s->toobig); - DEBUGASSERT(!s->leng || s->bufr); - DEBUGASSERT(a <= s->toobig); - - if(fit > s->toobig) { - Curl_dyn_free(s); - return CURLE_OUT_OF_MEMORY; - } - else if(!a) { - DEBUGASSERT(!indx); - /* first invoke */ - if(MIN_FIRST_ALLOC > s->toobig) - a = s->toobig; - else if(fit < MIN_FIRST_ALLOC) - a = MIN_FIRST_ALLOC; - else - a = fit; - } - else { - while(a < fit) - a *= 2; - if(a > s->toobig) - /* no point in allocating a larger buffer than this is allowed to use */ - a = s->toobig; - } - - if(a != s->allc) { - /* this logic is not using Curl_saferealloc() to make the tool not have to - include that as well when it uses this code */ - void *p = realloc(s->bufr, a); - if(!p) { - Curl_dyn_free(s); - return CURLE_OUT_OF_MEMORY; - } - s->bufr = p; - s->allc = a; - } - - if(len) - memcpy(&s->bufr[indx], mem, len); - s->leng = indx + len; - s->bufr[s->leng] = 0; - return CURLE_OK; -} - -/* - * Clears the string, keeps the allocation. This can also be called on a - * buffer that already was freed. - */ -void Curl_dyn_reset(struct dynbuf *s) -{ - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - if(s->leng) - s->bufr[0] = 0; - s->leng = 0; -} - -/* - * Specify the size of the tail to keep (number of bytes from the end of the - * buffer). The rest will be dropped. - */ -CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail) -{ - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - if(trail > s->leng) - return CURLE_BAD_FUNCTION_ARGUMENT; - else if(trail == s->leng) - return CURLE_OK; - else if(!trail) { - Curl_dyn_reset(s); - } - else { - memmove(&s->bufr[0], &s->bufr[s->leng - trail], trail); - s->leng = trail; - s->bufr[s->leng] = 0; - } - return CURLE_OK; - -} - -/* - * Appends a buffer with length. - */ -CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) -{ - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - return dyn_nappend(s, mem, len); -} - -/* - * Append a null-terminated string at the end. - */ -CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) -{ - size_t n = strlen(str); - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - return dyn_nappend(s, (unsigned char *)str, n); -} - -/* - * Append a string vprintf()-style - */ -CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) -{ -#ifdef BUILDING_LIBCURL - int rc; - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - rc = Curl_dyn_vprintf(s, fmt, ap); - - if(!rc) - return CURLE_OK; -#else - char *str; - str = vaprintf(fmt, ap); /* this allocs a new string to append */ - - if(str) { - CURLcode result = dyn_nappend(s, (unsigned char *)str, strlen(str)); - free(str); - return result; - } - /* If we failed, we cleanup the whole buffer and return error */ - Curl_dyn_free(s); -#endif - return CURLE_OUT_OF_MEMORY; -} - -/* - * Append a string printf()-style - */ -CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) -{ - CURLcode result; - va_list ap; - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - va_start(ap, fmt); - result = Curl_dyn_vaddf(s, fmt, ap); - va_end(ap); - return result; -} - -/* - * Returns a pointer to the buffer. - */ -char *Curl_dyn_ptr(const struct dynbuf *s) -{ - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - return s->bufr; -} - -/* - * Returns an unsigned pointer to the buffer. - */ -unsigned char *Curl_dyn_uptr(const struct dynbuf *s) -{ - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - return (unsigned char *)s->bufr; -} - -/* - * Returns the length of the buffer. - */ -size_t Curl_dyn_len(const struct dynbuf *s) -{ - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - return s->leng; -} - -/* - * Set a new (smaller) length. - */ -CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t set) -{ - DEBUGASSERT(s); - DEBUGASSERT(s->init == DYNINIT); - DEBUGASSERT(!s->leng || s->bufr); - if(set > s->leng) - return CURLE_BAD_FUNCTION_ARGUMENT; - s->leng = set; - s->bufr[s->leng] = 0; - return CURLE_OK; -} diff --git a/Utilities/cmcurl/lib/dynbuf.h b/Utilities/cmcurl/lib/dynbuf.h deleted file mode 100644 index 57ad62b22b3..00000000000 --- a/Utilities/cmcurl/lib/dynbuf.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef HEADER_CURL_DYNBUF_H -#define HEADER_CURL_DYNBUF_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 - -#ifndef BUILDING_LIBCURL -/* this renames the functions so that the tool code can use the same code - without getting symbol collisions */ -#define Curl_dyn_init(a,b) curlx_dyn_init(a,b) -#define Curl_dyn_add(a,b) curlx_dyn_add(a,b) -#define Curl_dyn_addn(a,b,c) curlx_dyn_addn(a,b,c) -#define Curl_dyn_addf curlx_dyn_addf -#define Curl_dyn_vaddf curlx_dyn_vaddf -#define Curl_dyn_free(a) curlx_dyn_free(a) -#define Curl_dyn_ptr(a) curlx_dyn_ptr(a) -#define Curl_dyn_uptr(a) curlx_dyn_uptr(a) -#define Curl_dyn_len(a) curlx_dyn_len(a) -#define Curl_dyn_reset(a) curlx_dyn_reset(a) -#define Curl_dyn_tail(a,b) curlx_dyn_tail(a,b) -#define Curl_dyn_setlen(a,b) curlx_dyn_setlen(a,b) -#define curlx_dynbuf dynbuf /* for the struct name */ -#endif - -struct dynbuf { - char *bufr; /* point to a null-terminated allocated buffer */ - size_t leng; /* number of bytes *EXCLUDING* the null-terminator */ - size_t allc; /* size of the current allocation */ - size_t toobig; /* size limit for the buffer */ -#ifdef DEBUGBUILD - int init; /* detect API usage mistakes */ -#endif -}; - -void Curl_dyn_init(struct dynbuf *s, size_t toobig); -void Curl_dyn_free(struct dynbuf *s); -CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) - WARN_UNUSED_RESULT; -CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) - WARN_UNUSED_RESULT; -CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) - WARN_UNUSED_RESULT; -CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) - WARN_UNUSED_RESULT; -void Curl_dyn_reset(struct dynbuf *s); -CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail); -CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t set); -char *Curl_dyn_ptr(const struct dynbuf *s); -unsigned char *Curl_dyn_uptr(const struct dynbuf *s); -size_t Curl_dyn_len(const struct dynbuf *s); - -/* returns 0 on success, -1 on error */ -/* The implementation of this function exists in mprintf.c */ -int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); - -/* Dynamic buffer max sizes */ -#define DYN_DOH_RESPONSE 3000 -#define DYN_DOH_CNAME 256 -#define DYN_PAUSE_BUFFER (64 * 1024 * 1024) -#define DYN_HAXPROXY 2048 -#define DYN_HTTP_REQUEST (1024*1024) -#define DYN_H2_HEADERS (128*1024) -#define DYN_H2_TRAILERS (128*1024) -#define DYN_APRINTF 8000000 -#define DYN_RTSP_REQ_HEADER (64*1024) -#define DYN_TRAILERS (64*1024) -#define DYN_PROXY_CONNECT_HEADERS 16384 -#define DYN_QLOG_NAME 1024 -#define DYN_H1_TRAILER 4096 -#define DYN_PINGPPONG_CMD (64*1024) -#define DYN_IMAP_CMD (64*1024) -#endif diff --git a/Utilities/cmcurl/lib/dynhds.c b/Utilities/cmcurl/lib/dynhds.c index 007dfc588c8..5c52d7411ac 100644 --- a/Utilities/cmcurl/lib/dynhds.c +++ b/Utilities/cmcurl/lib/dynhds.c @@ -27,6 +27,10 @@ #include "strcase.h" /* The last 3 #include files should be in this order */ +#ifdef USE_NGHTTP2 +#include +#include +#endif /* USE_NGHTTP2 */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" @@ -137,7 +141,7 @@ void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts) struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n) { DEBUGASSERT(dynhds); - return (n < dynhds->hds_len)? dynhds->hds[n] : NULL; + return (n < dynhds->hds_len) ? dynhds->hds[n] : NULL; } struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, const char *name, @@ -268,10 +272,10 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line) { - return Curl_dynhds_h1_add_line(dynhds, line, line? strlen(line) : 0); + return Curl_dynhds_h1_add_line(dynhds, line, line ? strlen(line) : 0); } -#ifdef DEBUGBUILD +#ifdef UNITTESTS /* used by unit2602.c */ bool Curl_dynhds_contains(struct dynhds *dynhds, @@ -344,6 +348,8 @@ size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name) return Curl_dynhds_remove(dynhds, name, strlen(name)); } +#endif + CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) { CURLcode result = CURLE_OK; @@ -353,9 +359,10 @@ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) return result; for(i = 0; i < dynhds->hds_len; ++i) { - result = Curl_dyn_addf(dbuf, "%.*s: %.*s\r\n", - (int)dynhds->hds[i]->namelen, dynhds->hds[i]->name, - (int)dynhds->hds[i]->valuelen, dynhds->hds[i]->value); + 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); if(result) break; } @@ -363,4 +370,28 @@ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) return result; } -#endif +#ifdef USE_NGHTTP2 + +nghttp2_nv *Curl_dynhds_to_nva(struct dynhds *dynhds, size_t *pcount) +{ + nghttp2_nv *nva = calloc(1, sizeof(nghttp2_nv) * dynhds->hds_len); + size_t i; + + *pcount = 0; + if(!nva) + return NULL; + + for(i = 0; i < dynhds->hds_len; ++i) { + struct dynhds_entry *e = dynhds->hds[i]; + DEBUGASSERT(e); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP2_NV_FLAG_NONE; + } + *pcount = dynhds->hds_len; + return nva; +} + +#endif /* USE_NGHTTP2 */ diff --git a/Utilities/cmcurl/lib/dynhds.h b/Utilities/cmcurl/lib/dynhds.h index 8a053480e99..e533dcc3699 100644 --- a/Utilities/cmcurl/lib/dynhds.h +++ b/Utilities/cmcurl/lib/dynhds.h @@ -26,13 +26,13 @@ #include "curl_setup.h" #include -#include "dynbuf.h" +#include "curlx/dynbuf.h" struct dynbuf; /** * A single header entry. - * `name` and `value` are non-NULL and always NUL terminated. + * `name` and `value` are non-NULL and always null-terminated. */ struct dynhds_entry { char *name; @@ -95,6 +95,9 @@ struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, const char *name, size_t namelen); struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name); +#ifdef UNITTESTS +/* used by unit2602.c */ + /** * Return TRUE iff one or more headers with the given name exist. */ @@ -110,25 +113,11 @@ size_t Curl_dynhds_count_name(struct dynhds *dynhds, const char *name, size_t namelen); /** - * Return how often the given 0-terminated name appears in `dynhds`. + * Return how often the given null-terminated name appears in `dynhds`. * Names are case-insensitive. */ size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name); -/** - * Add a header, name + value, to `dynhds` at the end. Does *not* - * check for duplicate names. - */ -CURLcode Curl_dynhds_add(struct dynhds *dynhds, - const char *name, size_t namelen, - const char *value, size_t valuelen); - -/** - * Add a header, c-string name + value, to `dynhds` at the end. - */ -CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, - const char *name, const char *value); - /** * Remove all entries with the given name. * Returns number of entries removed. @@ -146,20 +135,35 @@ size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name); CURLcode Curl_dynhds_set(struct dynhds *dynhds, const char *name, size_t namelen, const char *value, size_t valuelen); +#endif CURLcode Curl_dynhds_cset(struct dynhds *dynhds, const char *name, const char *value); /** - * Add a single header from a HTTP/1.1 formatted line at the end. Line - * may contain a delimiting \r\n or just \n. Any characters after + * Add a header, name + value, to `dynhds` at the end. Does *not* + * check for duplicate names. + */ +CURLcode Curl_dynhds_add(struct dynhds *dynhds, + const char *name, size_t namelen, + const char *value, size_t valuelen); + +/** + * Add a header, c-string name + value, to `dynhds` at the end. + */ +CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, + const char *name, const char *value); + +/** + * Add a single header from an HTTP/1.1 formatted line at the end. Line + * may contain a delimiting CRLF or just LF. Any characters after * that will be ignored. */ CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line); /** - * Add a single header from a HTTP/1.1 formatted line at the end. Line - * may contain a delimiting \r\n or just \n. Any characters after + * Add a single header from an HTTP/1.1 formatted line at the end. Line + * may contain a delimiting CRLF or just LF. Any characters after * that will be ignored. */ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, @@ -171,4 +175,13 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, */ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf); +#ifdef USE_NGHTTP2 + +#include +#include + +nghttp2_nv *Curl_dynhds_to_nva(struct dynhds *dynhds, size_t *pcount); + +#endif /* USE_NGHTTP2 */ + #endif /* HEADER_CURL_DYNHDS_H */ diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c index d36cc03d1f0..3f867862523 100644 --- a/Utilities/cmcurl/lib/easy.c +++ b/Utilities/cmcurl/lib/easy.c @@ -48,6 +48,8 @@ #include #include "transfer.h" #include "vtls/vtls.h" +#include "vtls/vtls_scache.h" +#include "vquic/vquic.h" #include "url.h" #include "getinfo.h" #include "hostip.h" @@ -63,14 +65,15 @@ #include "slist.h" #include "mime.h" #include "amigaos.h" -#include "warnless.h" +#include "macos.h" +#include "curlx/warnless.h" #include "sigpipe.h" #include "vssh/ssh.h" #include "setopt.h" #include "http_digest.h" #include "system_win32.h" #include "http2.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "altsvc.h" #include "hsts.h" @@ -83,7 +86,7 @@ /* true globals -- for curl_global_init() and curl_global_cleanup() */ static unsigned int initialized; -static long init_flags; +static long easy_init_flags; #ifdef GLOBAL_INIT_IS_THREADSAFE @@ -103,7 +106,7 @@ static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT; * ways, but at this point it must be defined as the system-supplied strdup * so the callback pointer is initialized correctly. */ -#if defined(_WIN32_WCE) +#if defined(UNDER_CE) #define system_strdup _strdup #elif !defined(HAVE_STRDUP) #define system_strdup Curl_strdup @@ -111,7 +114,8 @@ static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT; #define system_strdup strdup #endif -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(push) # pragma warning(disable:4232) /* MSVC extension, dllimport identity */ #endif @@ -124,12 +128,12 @@ curl_free_callback Curl_cfree = (curl_free_callback)free; curl_realloc_callback Curl_crealloc = (curl_realloc_callback)realloc; curl_strdup_callback Curl_cstrdup = (curl_strdup_callback)system_strdup; curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc; -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) curl_wcsdup_callback Curl_cwcsdup = Curl_wcsdup; #endif -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) -# pragma warning(default:4232) /* MSVC extension, dllimport identity */ +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(pop) #endif #ifdef DEBUGBUILD @@ -152,13 +156,13 @@ static CURLcode global_init(long flags, bool memoryfuncs) Curl_crealloc = (curl_realloc_callback)realloc; Curl_cstrdup = (curl_strdup_callback)system_strdup; Curl_ccalloc = (curl_calloc_callback)calloc; -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) Curl_cwcsdup = (curl_wcsdup_callback)_wcsdup; #endif } - if(Curl_log_init()) { - DEBUGF(fprintf(stderr, "Error: Curl_log_init failed\n")); + if(Curl_trc_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_trc_init failed\n")); goto fail; } @@ -167,39 +171,37 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } -#ifdef WIN32 + if(!Curl_vquic_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_vquic_init failed\n")); + goto fail; + } + if(Curl_win32_init(flags)) { DEBUGF(fprintf(stderr, "Error: win32_init failed\n")); goto fail; } -#endif -#ifdef __AMIGA__ if(Curl_amiga_init()) { DEBUGF(fprintf(stderr, "Error: Curl_amiga_init failed\n")); goto fail; } -#endif - if(Curl_resolver_global_init()) { - DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); + if(Curl_macos_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_macos_init failed\n")); goto fail; } -#if defined(USE_SSH) - if(Curl_ssh_init()) { + if(Curl_async_global_init()) { + DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); goto fail; } -#endif -#ifdef USE_WOLFSSH - if(WS_SUCCESS != wolfSSH_Init()) { - DEBUGF(fprintf(stderr, "Error: wolfSSH_Init failed\n")); - return CURLE_FAILED_INIT; + if(Curl_ssh_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_ssh_init failed\n")); + goto fail; } -#endif - init_flags = flags; + easy_init_flags = flags; #ifdef DEBUGBUILD if(getenv("CURL_GLOBAL_INIT")) @@ -248,7 +250,7 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, global_init_lock(); if(initialized) { - /* Already initialized, don't do it again, but bump the variable anyway to + /* Already initialized, do not do it again, but bump the variable anyway to work like curl_global_init() and require the same amount of cleanup calls. */ initialized++; @@ -274,7 +276,8 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, /** * curl_global_cleanup() globally cleanups curl, uses the value of - * "init_flags" to determine what needs to be cleaned up and what doesn't. + * "easy_init_flags" to determine what needs to be cleaned up and what does + * not. */ void curl_global_cleanup(void) { @@ -291,26 +294,43 @@ void curl_global_cleanup(void) } Curl_ssl_cleanup(); - Curl_resolver_global_cleanup(); + Curl_async_global_cleanup(); -#ifdef WIN32 - Curl_win32_cleanup(init_flags); +#ifdef _WIN32 + Curl_win32_cleanup(easy_init_flags); #endif Curl_amiga_cleanup(); Curl_ssh_cleanup(); -#ifdef USE_WOLFSSH - (void)wolfSSH_Cleanup(); -#endif #ifdef DEBUGBUILD free(leakpointer); #endif - init_flags = 0; + easy_init_flags = 0; + + global_init_unlock(); +} + +/** + * curl_global_trace() globally initializes curl logging. + */ +CURLcode curl_global_trace(const char *config) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + CURLcode result; + global_init_lock(); + + result = Curl_trc_opt(config); global_init_unlock(); + + return result; +#else + (void)config; + return CURLE_OK; +#endif } /* @@ -334,7 +354,7 @@ CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, * curl_easy_init() is the external interface to alloc, setup and init an * easy handle that is returned. If anything goes wrong, NULL is returned. */ -struct Curl_easy *curl_easy_init(void) +CURL *curl_easy_init(void) { CURLcode result; struct Curl_easy *data; @@ -363,7 +383,7 @@ struct Curl_easy *curl_easy_init(void) return data; } -#ifdef CURLDEBUG +#ifdef DEBUGBUILD struct socketmonitor { struct socketmonitor *next; /* the next node in the list or NULL */ @@ -378,25 +398,22 @@ struct events { int running_handles; /* store the returned number */ }; +#define DEBUG_EV_POLL 0 + /* events_timer * * Callback that gets called with a new value when the timeout should be * updated. */ - -static int events_timer(struct Curl_multi *multi, /* multi handle */ +static int events_timer(CURLM *multi, /* multi handle */ long timeout_ms, /* see above */ - void *userp) /* private callback pointer */ + void *userp) /* private callback pointer */ { struct events *ev = userp; (void)multi; - if(timeout_ms == -1) - /* timeout removed */ - timeout_ms = 0; - else if(timeout_ms == 0) - /* timeout is already reached! */ - timeout_ms = 1; /* trigger asap */ - +#if DEBUG_EV_POLL + fprintf(stderr, "events_timer: set timeout %ldms\n", timeout_ms); +#endif ev->ms = timeout_ms; ev->msbump = TRUE; return 0; @@ -439,7 +456,7 @@ static short socketcb2poll(int pollmask) * Callback that gets called with information about socket activity to * monitor. */ -static int events_socket(struct Curl_easy *easy, /* easy handle */ +static int events_socket(CURL *easy, /* easy handle */ curl_socket_t s, /* socket */ int what, /* see above */ void *userp, /* private callback @@ -450,6 +467,8 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ struct events *ev = userp; struct socketmonitor *m; struct socketmonitor *prev = NULL; + bool found = FALSE; + struct Curl_easy *data = easy; #if defined(CURL_DISABLE_VERBOSE_STRINGS) (void) easy; @@ -459,7 +478,7 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ m = ev->list; while(m) { if(m->socket.fd == s) { - + found = TRUE; if(what == CURL_POLL_REMOVE) { struct socketmonitor *nxt = m->next; /* remove this node from the list of monitored sockets */ @@ -468,28 +487,29 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ else ev->list = nxt; free(m); - m = nxt; - infof(easy, "socket cb: socket %d REMOVED", s); + infof(data, "socket cb: socket %" FMT_SOCKET_T " REMOVED", s); } else { /* The socket 's' is already being monitored, update the activity mask. Convert from libcurl bitmask to the poll one. */ m->socket.events = socketcb2poll(what); - infof(easy, "socket cb: socket %d UPDATED as %s%s", s, - (what&CURL_POLL_IN)?"IN":"", - (what&CURL_POLL_OUT)?"OUT":""); + infof(data, "socket cb: socket %" FMT_SOCKET_T + " UPDATED as %s%s", s, + (what&CURL_POLL_IN) ? "IN" : "", + (what&CURL_POLL_OUT) ? "OUT" : ""); } break; } prev = m; m = m->next; /* move to next node */ } - if(!m) { + + if(!found) { if(what == CURL_POLL_REMOVE) { - /* this happens a bit too often, libcurl fix perhaps? */ - /* fprintf(stderr, - "%s: socket %d asked to be REMOVED but not present!\n", - __func__, s); */ + /* should not happen if our logic is correct, but is no drama. */ + DEBUGF(infof(data, "socket cb: asked to REMOVE socket %" + FMT_SOCKET_T "but not present!", s)); + DEBUGASSERT(0); } else { m = malloc(sizeof(struct socketmonitor)); @@ -499,9 +519,9 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ m->socket.events = socketcb2poll(what); m->socket.revents = 0; ev->list = m; - infof(easy, "socket cb: socket %d ADDED as %s%s", s, - (what&CURL_POLL_IN)?"IN":"", - (what&CURL_POLL_OUT)?"OUT":""); + infof(data, "socket cb: socket %" FMT_SOCKET_T " ADDED as %s%s", s, + (what&CURL_POLL_IN) ? "IN" : "", + (what&CURL_POLL_OUT) ? "OUT" : ""); } else return CURLE_OUT_OF_MEMORY; @@ -528,12 +548,34 @@ 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) +{ + unsigned int numfds = 0; + struct pollfd *f; + struct socketmonitor *m; + + f = &fds[0]; + for(m = ev->list; m; m = m->next) { + f->fd = m->socket.fd; + f->events = m->socket.events; + f->revents = 0; +#if DEBUG_EV_POLL + fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); +#endif + f++; + numfds++; + } + return numfds; +} /* wait_or_timeout() * * waits for activity on any of the given sockets, or the timeout to trigger. */ - static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) { bool done = FALSE; @@ -542,34 +584,35 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) while(!done) { CURLMsg *msg; - struct socketmonitor *m; - struct pollfd *f; struct pollfd fds[4]; - int numfds = 0; int pollrc; - int i; struct curltime before; - struct curltime after; - - /* populate the fds[] array */ - for(m = ev->list, f = &fds[0]; m; m = m->next) { - f->fd = m->socket.fd; - f->events = m->socket.events; - f->revents = 0; - /* fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); */ - f++; - numfds++; - } + const unsigned int numfds = populate_fds(fds, ev); /* get the time stamp to use to figure out how long poll takes */ - before = Curl_now(); - - /* wait for activity or timeout */ - pollrc = Curl_poll(fds, numfds, ev->ms); - if(pollrc < 0) - return CURLE_UNRECOVERABLE_POLL; + before = curlx_now(); - after = Curl_now(); + if(numfds) { + /* wait for activity or timeout */ +#if DEBUG_EV_POLL + fprintf(stderr, "poll(numfds=%u, timeout=%ldms)\n", numfds, ev->ms); +#endif + pollrc = Curl_poll(fds, numfds, ev->ms); +#if DEBUG_EV_POLL + fprintf(stderr, "poll(numfds=%u, timeout=%ldms) -> %d\n", + numfds, ev->ms, pollrc); +#endif + if(pollrc < 0) + return CURLE_UNRECOVERABLE_POLL; + } + else { +#if DEBUG_EV_POLL + fprintf(stderr, "poll, but no fds, wait timeout=%ldms\n", ev->ms); +#endif + pollrc = 0; + if(ev->ms > 0) + Curl_wait_ms(ev->ms); + } ev->msbump = FALSE; /* reset here */ @@ -582,25 +625,32 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) } else { /* here pollrc is > 0 */ - /* loop over the monitored sockets to see which ones had activity */ - for(i = 0; i< numfds; i++) { + unsigned int i; + for(i = 0; i < numfds; i++) { if(fds[i].revents) { /* socket activity, tell libcurl */ int act = poll2cselect(fds[i].revents); /* convert */ - infof(multi->easyp, "call curl_multi_socket_action(socket %d)", - fds[i].fd); + + /* 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); mcode = curl_multi_socket_action(multi, fds[i].fd, act, &ev->running_handles); } } - if(!ev->msbump) { + + if(!ev->msbump && ev->ms >= 0) { /* If nothing updated the timeout, we decrease it by the spent time. * If it was updated, it has the new timeout time stored already. */ - timediff_t timediff = Curl_timediff(after, before); + timediff_t timediff = curlx_timediff(curlx_now(), before); if(timediff > 0) { +#if DEBUG_EV_POLL + fprintf(stderr, "poll timeout %ldms not updated, decrease by " + "time spent %ldms\n", ev->ms, (long)timediff); +#endif if(timediff > ev->ms) ev->ms = 0; else @@ -612,7 +662,7 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) if(mcode) return CURLE_URL_MALFORMAT; - /* we don't really care about the "msgs_in_queue" value returned in the + /* we do not really care about the "msgs_in_queue" value returned in the second argument */ msg = curl_multi_info_read(multi, &pollrc); if(msg) { @@ -633,15 +683,15 @@ static CURLcode easy_events(struct Curl_multi *multi) { /* this struct is made static to allow it to be used after this function returns and curl_multi_remove_handle() is called */ - static struct events evs = {2, FALSE, 0, NULL, 0}; + static struct events evs = {-1, FALSE, 0, NULL, 0}; /* if running event-based, do some further multi inits */ events_setup(multi, &evs); return wait_or_timeout(multi, &evs); } -#else /* CURLDEBUG */ -/* when not built with debug, this function doesn't exist */ +#else /* DEBUGBUILD */ +/* when not built with debug, this function does not exist */ #define easy_events(x) CURLE_NOT_BUILT_IN #endif @@ -673,9 +723,9 @@ static CURLcode easy_transfer(struct Curl_multi *multi) /* Make sure to return some kind of error if there was a multi problem */ if(mcode) { result = (mcode == CURLM_OUT_OF_MEMORY) ? CURLE_OUT_OF_MEMORY : - /* The other multi errors should never happen, so return - something suitably generic */ - CURLE_BAD_FUNCTION_ARGUMENT; + /* The other multi errors should never happen, so return + something suitably generic */ + CURLE_BAD_FUNCTION_ARGUMENT; } return result; @@ -691,9 +741,9 @@ static CURLcode easy_transfer(struct Curl_multi *multi) * easy handle, destroys the multi handle and returns the easy handle's return * code. * - * REALITY: it can't just create and destroy the multi handle that easily. It + * REALITY: it cannot just create and destroy the multi handle that easily. It * needs to keep it around since if this easy handle is used again by this - * function, the same multi handle must be re-used so that the same pools and + * function, the same multi handle must be reused so that the same pools and * caches can be used. * * DEBUG: if 'events' is set TRUE, this function will use a replacement engine @@ -713,43 +763,61 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) /* clear this as early as possible */ data->set.errorbuffer[0] = 0; + data->state.os_errno = 0; + if(data->multi) { failf(data, "easy handle already used in multi handle"); return CURLE_FAILED_INIT; } + /* if the handle has a connection still attached (it is/was a connect-only + handle) then disconnect before performing */ + if(data->conn) { + struct connectdata *c; + curl_socket_t s; + Curl_detach_connection(data); + s = Curl_getconnectinfo(data, &c); + if((s != CURL_SOCKET_BAD) && c) { + Curl_conn_terminate(data, c, TRUE); + } + DEBUGASSERT(!data->conn); + } + if(data->multi_easy) multi = data->multi_easy; else { - /* this multi handle will only ever have a single easy handled attached - to it, so make it use minimal hashes */ - multi = Curl_multi_handle(1, 3, 7); + /* this multi handle will only ever have a single easy handle attached to + it, so make it use minimal hash sizes */ + multi = Curl_multi_handle(16, 1, 3, 7, 3); if(!multi) return CURLE_OUT_OF_MEMORY; - data->multi_easy = multi; } if(multi->in_callback) return CURLE_RECURSIVE_API_CALL; /* Copy the MAXCONNECTS option to the multi handle */ - curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, data->set.maxconnects); + curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, (long)data->set.maxconnects); + data->multi_easy = NULL; /* pretend it does not exist */ mcode = curl_multi_add_handle(multi, data); if(mcode) { curl_multi_cleanup(multi); - data->multi_easy = NULL; if(mcode == CURLM_OUT_OF_MEMORY) return CURLE_OUT_OF_MEMORY; return CURLE_FAILED_INIT; } - sigpipe_ignore(data, &pipe_st); + /* assign this after curl_multi_add_handle() */ + data->multi_easy = multi; + + sigpipe_init(&pipe_st); + sigpipe_apply(data, &pipe_st); /* run the transfer */ result = events ? easy_events(multi) : easy_transfer(multi); - /* ignoring the return code isn't nice, but atm we can't really handle + /* ignoring the return code is not nice, but atm we cannot really handle a failure here, room for future improvement! */ (void)curl_multi_remove_handle(multi, data); @@ -764,12 +832,12 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) * curl_easy_perform() is the external interface that performs a blocking * transfer as previously setup. */ -CURLcode curl_easy_perform(struct Curl_easy *data) +CURLcode curl_easy_perform(CURL *data) { return easy_perform(data, FALSE); } -#ifdef CURLDEBUG +#ifdef DEBUGBUILD /* * curl_easy_perform_ev() is the external interface that performs a blocking * transfer using the event-based API internally. @@ -785,8 +853,9 @@ CURLcode curl_easy_perform_ev(struct Curl_easy *data) * curl_easy_cleanup() is the external interface to cleaning/freeing the given * easy handle. */ -void curl_easy_cleanup(struct Curl_easy *data) +void curl_easy_cleanup(CURL *ptr) { + struct Curl_easy *data = ptr; if(GOOD_EASY_HANDLE(data)) { SIGPIPE_VARIABLE(pipe_st); sigpipe_ignore(data, &pipe_st); @@ -800,7 +869,7 @@ void curl_easy_cleanup(struct Curl_easy *data) * information from a performed transfer and similar. */ #undef curl_easy_getinfo -CURLcode curl_easy_getinfo(struct Curl_easy *data, CURLINFO info, ...) +CURLcode curl_easy_getinfo(CURL *data, CURLINFO info, ...) { va_list arg; void *paramp; @@ -826,18 +895,18 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) dst->set = src->set; Curl_mime_initpart(&dst->set.mimepost); - /* clear all string pointers first */ + /* clear all dest string and blob pointers first, in case we error out + mid-function */ memset(dst->set.str, 0, STRING_LAST * sizeof(char *)); + memset(dst->set.blobs, 0, BLOB_LAST * sizeof(struct curl_blob *)); /* duplicate all strings */ - for(i = (enum dupstring)0; i< STRING_LASTZEROTERMINATED; i++) { + for(i = (enum dupstring)0; i < STRING_LASTZEROTERMINATED; i++) { result = Curl_setstropt(&dst->set.str[i], src->set.str[i]); if(result) return result; } - /* clear all blob pointers first */ - memset(dst->set.blobs, 0, BLOB_LAST * sizeof(struct curl_blob *)); /* duplicate all blobs */ for(j = (enum dupblob)0; j < BLOB_LAST; j++) { result = Curl_setblobopt(&dst->set.blobs[j], src->set.blobs[j]); @@ -847,10 +916,13 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) /* duplicate memory areas pointed to */ i = STRING_COPYPOSTFIELDS; - if(src->set.postfieldsize && src->set.str[i]) { - /* postfieldsize is curl_off_t, Curl_memdup() takes a size_t ... */ - dst->set.str[i] = Curl_memdup(src->set.str[i], - curlx_sotouz(src->set.postfieldsize)); + if(src->set.str[i]) { + if(src->set.postfieldsize == -1) + dst->set.str[i] = strdup(src->set.str[i]); + else + /* postfieldsize is curl_off_t, Curl_memdup() takes a size_t ... */ + dst->set.str[i] = Curl_memdup(src->set.str[i], + curlx_sotouz(src->set.postfieldsize)); if(!dst->set.str[i]) return CURLE_OUT_OF_MEMORY; /* point to the new copy */ @@ -866,13 +938,23 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) return result; } +static void dupeasy_meta_freeentry(void *p) +{ + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); +} + /* * curl_easy_duphandle() is an external interface to allow duplication of a * given input easy handle. The returned handle will be a new working handle * with all options set exactly as the input source handle. */ -struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) +CURL *curl_easy_duphandle(CURL *d) { + struct Curl_easy *data = d; struct Curl_easy *outcurl = calloc(1, sizeof(struct Curl_easy)); if(!outcurl) goto fail; @@ -884,34 +966,44 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) */ outcurl->set.buffer_size = data->set.buffer_size; + Curl_hash_init(&outcurl->meta_hash, 23, + Curl_hash_str, curlx_str_key_compare, dupeasy_meta_freeentry); + curlx_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); + Curl_netrc_init(&outcurl->state.netrc); + + /* the connection pool is setup on demand */ + outcurl->state.lastconnect_id = -1; + outcurl->state.recent_conn_id = -1; + outcurl->id = -1; + outcurl->mid = UINT_MAX; + outcurl->master_mid = UINT_MAX; + +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&outcurl->state.httphdrs, NULL); +#endif + Curl_initinfo(outcurl); + /* copy all userdefined values */ if(dupset(outcurl, data)) goto fail; - Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); - - /* the connection cache is setup on demand */ - outcurl->state.conn_cache = NULL; - outcurl->state.lastconnect_id = -1; - - outcurl->progress.flags = data->progress.flags; + outcurl->progress.hide = data->progress.hide; outcurl->progress.callback = data->progress.callback; #ifndef CURL_DISABLE_COOKIES - if(data->cookies) { + outcurl->state.cookielist = NULL; + if(data->cookies && data->state.cookie_engine) { /* If cookies are enabled in the parent handle, we enable them in the clone as well! */ - outcurl->cookies = Curl_cookie_init(data, - data->cookies->filename, - outcurl->cookies, + outcurl->cookies = Curl_cookie_init(outcurl, NULL, outcurl->cookies, data->set.cookiesession); if(!outcurl->cookies) goto fail; } - if(data->set.cookielist) { - outcurl->set.cookielist = Curl_slist_duplicate(data->set.cookielist); - if(!outcurl->set.cookielist) + if(data->state.cookielist) { + outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); + if(!outcurl->state.cookielist) goto fail; } #endif @@ -957,35 +1049,6 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) (void)Curl_hsts_loadcb(outcurl, outcurl->hsts); } #endif - /* Clone the resolver handle, if present, for the new handle */ - if(Curl_resolver_duphandle(outcurl, - &outcurl->state.async.resolver, - data->state.async.resolver)) - goto fail; - -#ifdef USE_ARES - { - CURLcode rc; - - rc = Curl_set_dns_servers(outcurl, data->set.str[STRING_DNS_SERVERS]); - if(rc && rc != CURLE_NOT_BUILT_IN) - goto fail; - - rc = Curl_set_dns_interface(outcurl, data->set.str[STRING_DNS_INTERFACE]); - if(rc && rc != CURLE_NOT_BUILT_IN) - goto fail; - - rc = Curl_set_dns_local_ip4(outcurl, data->set.str[STRING_DNS_LOCAL_IP4]); - if(rc && rc != CURLE_NOT_BUILT_IN) - goto fail; - - rc = Curl_set_dns_local_ip6(outcurl, data->set.str[STRING_DNS_LOCAL_IP6]); - if(rc && rc != CURLE_NOT_BUILT_IN) - goto fail; - } -#endif /* USE_ARES */ - - Curl_initinfo(outcurl); outcurl->magic = CURLEASY_MAGIC_NUMBER; @@ -997,13 +1060,9 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) if(outcurl) { #ifndef CURL_DISABLE_COOKIES - curl_slist_free_all(outcurl->set.cookielist); - outcurl->set.cookielist = NULL; + free(outcurl->cookies); #endif - Curl_safefree(outcurl->state.buffer); - Curl_dyn_free(&outcurl->state.headerb); - Curl_safefree(outcurl->state.url); - Curl_safefree(outcurl->state.referer); + curlx_dyn_free(&outcurl->state.headerb); Curl_altsvc_cleanup(&outcurl->asi); Curl_hsts_cleanup(&outcurl->hsts); Curl_freeset(outcurl); @@ -1017,10 +1076,18 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) * curl_easy_reset() is an external interface that allows an app to re- * initialize a session handle to the default values. */ -void curl_easy_reset(struct Curl_easy *data) +void curl_easy_reset(CURL *d) { - Curl_free_request_state(data); - + struct Curl_easy *data = d; + Curl_req_hard_reset(&data->req, data); + Curl_hash_clean(&data->meta_hash); + + /* clear all meta data */ + Curl_meta_reset(data); + /* clear any resolve data */ + Curl_async_shutdown(data); + Curl_resolv_unlink(data, &data->state.dns[0]); + Curl_resolv_unlink(data, &data->state.dns[1]); /* zero out UserDefined data: */ Curl_freeset(data); memset(&data->set, 0, sizeof(struct UserDefined)); @@ -1032,7 +1099,7 @@ void curl_easy_reset(struct Curl_easy *data) /* zero out PureInfo data: */ Curl_initinfo(data); - data->progress.flags |= PGRS_HIDE; + data->progress.hide = TRUE; data->state.current_speed = -1; /* init to negative == impossible */ data->state.retrycount = 0; /* reset the retry counter */ @@ -1040,9 +1107,10 @@ void curl_easy_reset(struct Curl_easy *data) memset(&data->state.authhost, 0, sizeof(struct auth)); memset(&data->state.authproxy, 0, sizeof(struct auth)); -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) Curl_http_auth_cleanup_digest(data); #endif + data->master_mid = UINT_MAX; } /* @@ -1058,115 +1126,94 @@ void curl_easy_reset(struct Curl_easy *data) * NOTE: This is one of few API functions that are allowed to be called from * within a callback. */ -CURLcode curl_easy_pause(struct Curl_easy *data, int action) +CURLcode curl_easy_pause(CURL *d, int action) { struct SingleRequest *k; CURLcode result = CURLE_OK; int oldstate; int newstate; + bool recursive = FALSE; + bool keep_changed, unpause_read, not_all_paused; + struct Curl_easy *data = d; if(!GOOD_EASY_HANDLE(data) || !data->conn) - /* crazy input, don't continue */ + /* crazy input, do not continue */ return CURLE_BAD_FUNCTION_ARGUMENT; + if(Curl_is_in_callback(data)) + recursive = TRUE; k = &data->req; oldstate = k->keepon & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE); /* first switch off both pause bits then set the new pause bits */ newstate = (k->keepon &~ (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) | - ((action & CURLPAUSE_RECV)?KEEP_RECV_PAUSE:0) | - ((action & CURLPAUSE_SEND)?KEEP_SEND_PAUSE:0); - - if((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) == oldstate) { - /* Not changing any pause state, return */ - DEBUGF(infof(data, "pause: no change, early return")); - return CURLE_OK; - } - - /* Unpause parts in active mime tree. */ - if((k->keepon & ~newstate & KEEP_SEND_PAUSE) && - (data->mstate == MSTATE_PERFORMING || - data->mstate == MSTATE_RATELIMITING) && - data->state.fread_func == (curl_read_callback) Curl_mime_read) { - Curl_mime_unpause(data->state.in); - } - - /* put it back in the keepon */ + ((action & CURLPAUSE_RECV) ? KEEP_RECV_PAUSE : 0) | + ((action & CURLPAUSE_SEND) ? KEEP_SEND_PAUSE : 0); + + keep_changed = ((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) != oldstate); + not_all_paused = (newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != + (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE); + unpause_read = ((k->keepon & ~newstate & KEEP_SEND_PAUSE) && + (data->mstate == MSTATE_PERFORMING || + data->mstate == MSTATE_RATELIMITING)); + /* Unpausing writes is detected on the next run in + * transfer.c:Curl_sendrecv(). This is because this may result + * in a transfer error if the application's callbacks fail */ + + /* Set the new keepon state, so it takes effect no matter what error + * may happen afterwards. */ k->keepon = newstate; - if(!(newstate & KEEP_RECV_PAUSE)) { - Curl_conn_ev_data_pause(data, FALSE); - - if(data->state.tempcount) { - /* there are buffers for sending that can be delivered as the receive - pausing is lifted! */ - unsigned int i; - unsigned int count = data->state.tempcount; - struct tempbuf writebuf[3]; /* there can only be three */ - - /* copy the structs to allow for immediate re-pausing */ - for(i = 0; i < data->state.tempcount; i++) { - writebuf[i] = data->state.tempwrite[i]; - Curl_dyn_init(&data->state.tempwrite[i].b, DYN_PAUSE_BUFFER); - } - data->state.tempcount = 0; - - for(i = 0; i < count; i++) { - /* even if one function returns error, this loops through and frees - all buffers */ - if(!result) - result = Curl_client_write(data, writebuf[i].type, - Curl_dyn_ptr(&writebuf[i].b), - Curl_dyn_len(&writebuf[i].b)); - Curl_dyn_free(&writebuf[i].b); + /* If not completely pausing both directions now, run again in any case. */ + if(not_all_paused) { + Curl_expire(data, 0, EXPIRE_RUN_NOW); + /* reset the too-slow time keeper */ + data->state.keeps_speed.tv_sec = 0; + /* Simulate socket events on next run for unpaused directions */ + if(!(newstate & KEEP_SEND_PAUSE)) + data->state.select_bits |= CURL_CSELECT_OUT; + if(!(newstate & KEEP_RECV_PAUSE)) + data->state.select_bits |= CURL_CSELECT_IN; + /* On changes, tell application to update its timers. */ + if(keep_changed && data->multi) { + if(Curl_update_timer(data->multi)) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; } - - if(result) - return result; } } -#ifdef USE_HYPER - if(!(newstate & KEEP_SEND_PAUSE)) { - /* need to wake the send body waker */ - if(data->hyp.send_body_waker) { - hyper_waker_wake(data->hyp.send_body_waker); - data->hyp.send_body_waker = NULL; - } + if(unpause_read) { + result = Curl_creader_unpause(data); + if(result) + goto out; } -#endif - - /* if there's no error and we're not pausing both directions, we want - to have this handle checked soon */ - if((newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != - (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) { - Curl_expire(data, 0, EXPIRE_RUN_NOW); /* get this handle going again */ - - /* reset the too-slow time keeper */ - data->state.keeps_speed.tv_sec = 0; - if(!data->state.tempcount) - /* if not pausing again, force a recv/send check of this connection as - the data might've been read off the socket already */ - data->conn->cselect_bits = CURL_CSELECT_IN | CURL_CSELECT_OUT; - if(data->multi) { - if(Curl_update_timer(data->multi)) - return CURLE_ABORTED_BY_CALLBACK; - } + if(!(k->keepon & KEEP_RECV_PAUSE) && Curl_cwriter_is_paused(data)) { + Curl_conn_ev_data_pause(data, FALSE); + result = Curl_cwriter_unpause(data); } - if(!data->state.done) - /* This transfer may have been moved in or out of the bundle, update the - corresponding socket callback, if used */ - result = Curl_updatesocket(data); +out: + if(!result && !data->state.done && keep_changed && data->multi) + /* pause/unpausing may result in multi event changes */ + if(Curl_multi_ev_assess_xfer(data->multi, data)) + result = CURLE_ABORTED_BY_CALLBACK; + + if(recursive) + /* this might have called a callback recursively which might have set this + to false again on exit */ + Curl_set_in_callback(data, TRUE); return result; } -static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd, +static CURLcode easy_connection(struct Curl_easy *data, struct connectdata **connp) { + curl_socket_t sfd; + if(!data) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -1176,9 +1223,9 @@ static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd, return CURLE_UNSUPPORTED_PROTOCOL; } - *sfd = Curl_getconnectinfo(data, connp); + sfd = Curl_getconnectinfo(data, connp); - if(*sfd == CURL_SOCKET_BAD) { + if(sfd == CURL_SOCKET_BAD) { failf(data, "Failed to get recent socket"); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1191,18 +1238,17 @@ static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd, * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. * Returns CURLE_OK on success, error code on error. */ -CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, - size_t *n) +CURLcode curl_easy_recv(CURL *d, void *buffer, size_t buflen, size_t *n) { - curl_socket_t sfd; CURLcode result; ssize_t n1; struct connectdata *c; + struct Curl_easy *data = d; if(Curl_is_in_callback(data)) return CURLE_RECURSIVE_API_CALL; - result = easy_connection(data, &sfd, &c); + result = easy_connection(data, &c); if(result) return result; @@ -1212,7 +1258,7 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, Curl_attach_connection(data, c); *n = 0; - result = Curl_read(data, sfd, buffer, buflen, &n1); + result = Curl_conn_recv(data, FIRSTSOCKET, buffer, buflen, &n1); if(result) return result; @@ -1221,14 +1267,13 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, return CURLE_OK; } -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS CURLcode Curl_connect_only_attach(struct Curl_easy *data) { - curl_socket_t sfd; CURLcode result; struct connectdata *c = NULL; - result = easy_connection(data, &sfd, &c); + result = easy_connection(data, &c); if(result) return result; @@ -1239,7 +1284,7 @@ CURLcode Curl_connect_only_attach(struct Curl_easy *data) return CURLE_OK; } -#endif /* USE_WEBSOCKETS */ +#endif /* !CURL_DISABLE_WEBSOCKETS */ /* * Sends data over the connected socket. @@ -1247,15 +1292,14 @@ CURLcode Curl_connect_only_attach(struct Curl_easy *data) * This is the private internal version of curl_easy_send() */ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, - size_t buflen, ssize_t *n) + size_t buflen, size_t *n) { - curl_socket_t sfd; CURLcode result; - ssize_t n1; struct connectdata *c = NULL; SIGPIPE_VARIABLE(pipe_st); - result = easy_connection(data, &sfd, &c); + *n = 0; + result = easy_connection(data, &c); if(result) return result; @@ -1264,20 +1308,12 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, needs to be reattached */ Curl_attach_connection(data, c); - *n = 0; sigpipe_ignore(data, &pipe_st); - result = Curl_write(data, sfd, buffer, buflen, &n1); + result = Curl_conn_send(data, FIRSTSOCKET, buffer, buflen, FALSE, n); sigpipe_restore(&pipe_st); - if(n1 == -1) + if(result && result != CURLE_AGAIN) return CURLE_SEND_ERROR; - - /* detect EAGAIN */ - if(!result && !n1) - return CURLE_AGAIN; - - *n = n1; - return result; } @@ -1285,75 +1321,97 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, * Sends data over the connected socket. Use after successful * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. */ -CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer, - size_t buflen, size_t *n) +CURLcode curl_easy_send(CURL *d, const void *buffer, size_t buflen, size_t *n) { - ssize_t written = 0; + size_t written = 0; CURLcode result; + struct Curl_easy *data = d; if(Curl_is_in_callback(data)) return CURLE_RECURSIVE_API_CALL; result = Curl_senddata(data, buffer, buflen, &written); - *n = (size_t)written; + *n = written; return result; } /* - * Wrapper to call functions in Curl_conncache_foreach() - * - * Returns always 0. + * Performs connection upkeep for the given session handle. */ -static int conn_upkeep(struct Curl_easy *data, - struct connectdata *conn, - void *param) +CURLcode curl_easy_upkeep(CURL *d) { - struct curltime *now = param; - - if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) - return 0; + struct Curl_easy *data = d; + /* Verify that we got an easy handle we can work with. */ + if(!GOOD_EASY_HANDLE(data)) + return CURLE_BAD_FUNCTION_ARGUMENT; - /* briefly attach for action */ - Curl_attach_connection(data, conn); - if(conn->handler->connection_check) { - /* Do a protocol-specific keepalive check on the connection. */ - conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); - } - else { - /* Do the generic action on the FIRSTSOCKE filter chain */ - Curl_conn_keep_alive(data, conn, FIRSTSOCKET); - } - Curl_detach_connection(data); + if(Curl_is_in_callback(data)) + return CURLE_RECURSIVE_API_CALL; - conn->keepalive = *now; - return 0; /* continue iteration */ + /* Use the common function to keep connections alive. */ + return Curl_cpool_upkeep(data); } -static CURLcode upkeep(struct conncache *conn_cache, void *data) +CURLcode curl_easy_ssls_import(CURL *d, const char *session_key, + const unsigned char *shmac, size_t shmac_len, + const unsigned char *sdata, size_t sdata_len) { - struct curltime now = Curl_now(); - /* Loop over every connection and make connection alive. */ - Curl_conncache_foreach(data, - conn_cache, - &now, - conn_upkeep); - return CURLE_OK; +#ifdef USE_SSLS_EXPORT + struct Curl_easy *data = d; + if(!GOOD_EASY_HANDLE(data)) + return CURLE_BAD_FUNCTION_ARGUMENT; + return Curl_ssl_session_import(data, session_key, + shmac, shmac_len, sdata, sdata_len); +#else + (void)d; + (void)session_key; + (void)shmac; + (void)shmac_len; + (void)sdata; + (void)sdata_len; + return CURLE_NOT_BUILT_IN; +#endif } -/* - * Performs connection upkeep for the given session handle. - */ -CURLcode curl_easy_upkeep(struct Curl_easy *data) +CURLcode curl_easy_ssls_export(CURL *d, + curl_ssls_export_cb *export_fn, + void *userptr) { - /* Verify that we got an easy handle we can work with. */ +#ifdef USE_SSLS_EXPORT + struct Curl_easy *data = d; if(!GOOD_EASY_HANDLE(data)) return CURLE_BAD_FUNCTION_ARGUMENT; + return Curl_ssl_session_export(data, export_fn, userptr); +#else + (void)d; + (void)export_fn; + (void)userptr; + return CURLE_NOT_BUILT_IN; +#endif +} - if(data->multi_easy) { - /* Use the common function to keep connections alive. */ - return upkeep(&data->multi_easy->conn_cache, data); - } - else { - /* No connections, so just return success */ - return CURLE_OK; +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, + meta_data, meta_dtor)) { + meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + return CURLE_OUT_OF_MEMORY; } + return CURLE_OK; +} + +void Curl_meta_remove(struct Curl_easy *data, const char *key) +{ + Curl_hash_delete(&data->meta_hash, CURL_UNCONST(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); +} + +void Curl_meta_reset(struct Curl_easy *data) +{ + Curl_hash_clean(&data->meta_hash); } diff --git a/Utilities/cmcurl/lib/easy_lock.h b/Utilities/cmcurl/lib/easy_lock.h index 5fa9477d062..ec324cfc81a 100644 --- a/Utilities/cmcurl/lib/easy_lock.h +++ b/Utilities/cmcurl/lib/easy_lock.h @@ -1,3 +1,5 @@ +#ifndef HEADER_CURL_EASY_LOCK_H +#define HEADER_CURL_EASY_LOCK_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -29,13 +31,6 @@ #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 #ifdef __MINGW32__ -#ifndef __MINGW64_VERSION_MAJOR -#if (__MINGW32_MAJOR_VERSION < 5) || \ - (__MINGW32_MAJOR_VERSION == 5 && __MINGW32_MINOR_VERSION == 0) -/* mingw >= 5.0.1 defines SRWLOCK, and slightly different from MS define */ -typedef PVOID SRWLOCK, *PSRWLOCK; -#endif -#endif #ifndef SRWLOCK_INIT #define SRWLOCK_INIT NULL #endif @@ -74,7 +69,7 @@ typedef PVOID SRWLOCK, *PSRWLOCK; #endif -static inline void curl_simple_lock_lock(curl_simple_lock *lock) +static CURL_INLINE void curl_simple_lock_lock(curl_simple_lock *lock) { for(;;) { if(!atomic_exchange_explicit(lock, true, memory_order_acquire)) @@ -86,6 +81,8 @@ static inline void curl_simple_lock_lock(curl_simple_lock *lock) __builtin_ia32_pause(); #elif defined(__aarch64__) __asm__ volatile("yield" ::: "memory"); +#elif defined(_WIN32) + Sleep(1); #elif defined(HAVE_SCHED_YIELD) sched_yield(); #endif @@ -93,13 +90,24 @@ static inline void curl_simple_lock_lock(curl_simple_lock *lock) } } -static inline void curl_simple_lock_unlock(curl_simple_lock *lock) +static CURL_INLINE void curl_simple_lock_unlock(curl_simple_lock *lock) { atomic_store_explicit(lock, false, memory_order_release); } +#elif defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) + +#include + +#define curl_simple_lock pthread_mutex_t +#define CURL_SIMPLE_LOCK_INIT PTHREAD_MUTEX_INITIALIZER +#define curl_simple_lock_lock(m) pthread_mutex_lock(m) +#define curl_simple_lock_unlock(m) pthread_mutex_unlock(m) + #else #undef GLOBAL_INIT_IS_THREADSAFE #endif + +#endif /* HEADER_CURL_EASY_LOCK_H */ diff --git a/Utilities/cmcurl/lib/easygetopt.c b/Utilities/cmcurl/lib/easygetopt.c index 2b8a521cd2e..5d30d39a49f 100644 --- a/Utilities/cmcurl/lib/easygetopt.c +++ b/Utilities/cmcurl/lib/easygetopt.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * ___|___/|_| ______| * - * Copyright (C) Daniel Stenberg, , et al. + * 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 @@ -29,12 +29,12 @@ #ifndef CURL_DISABLE_GETOPTIONS /* Lookups easy options at runtime */ -static struct curl_easyoption *lookup(const char *name, CURLoption id) +static const struct curl_easyoption *lookup(const char *name, CURLoption id) { DEBUGASSERT(name || id); DEBUGASSERT(!Curl_easyopts_check()); if(name || id) { - struct curl_easyoption *o = &Curl_easyopts[0]; + const struct curl_easyoption *o = &Curl_easyopts[0]; do { if(name) { if(strcasecompare(o->name, name)) @@ -42,7 +42,7 @@ static struct curl_easyoption *lookup(const char *name, CURLoption id) } else { if((o->id == id) && !(o->flags & CURLOT_FLAG_ALIAS)) - /* don't match alias options */ + /* do not match alias options */ return o; } o++; diff --git a/Utilities/cmcurl/lib/easyif.h b/Utilities/cmcurl/lib/easyif.h index 64489529660..181ce38f7bc 100644 --- a/Utilities/cmcurl/lib/easyif.h +++ b/Utilities/cmcurl/lib/easyif.h @@ -28,13 +28,13 @@ * Prototypes for library-wide functions provided by easy.c */ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, - size_t buflen, ssize_t *n); + size_t buflen, size_t *n); -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS CURLcode Curl_connect_only_attach(struct Curl_easy *data); #endif -#ifdef CURLDEBUG +#ifdef DEBUGBUILD CURL_EXTERN CURLcode curl_easy_perform_ev(struct Curl_easy *easy); #endif diff --git a/Utilities/cmcurl/lib/easyoptions.c b/Utilities/cmcurl/lib/easyoptions.c index a9c1efd0064..03d676df0e7 100644 --- a/Utilities/cmcurl/lib/easyoptions.c +++ b/Utilities/cmcurl/lib/easyoptions.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) Daniel Stenberg, , et al. + * 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 @@ -28,7 +28,7 @@ #include "easyoptions.h" /* all easy setopt options listed in alphabetical order */ -struct curl_easyoption Curl_easyopts[] = { +const struct curl_easyoption Curl_easyopts[] = { {"ABSTRACT_UNIX_SOCKET", CURLOPT_ABSTRACT_UNIX_SOCKET, CURLOT_STRING, 0}, {"ACCEPTTIMEOUT_MS", CURLOPT_ACCEPTTIMEOUT_MS, CURLOT_LONG, 0}, {"ACCEPT_ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, 0}, @@ -86,6 +86,7 @@ struct curl_easyoption Curl_easyopts[] = { {"DOH_SSL_VERIFYPEER", CURLOPT_DOH_SSL_VERIFYPEER, CURLOT_LONG, 0}, {"DOH_SSL_VERIFYSTATUS", CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOT_LONG, 0}, {"DOH_URL", CURLOPT_DOH_URL, CURLOT_STRING, 0}, + {"ECH", CURLOPT_ECH, CURLOT_STRING, 0}, {"EGDSOCKET", CURLOPT_EGDSOCKET, CURLOT_STRING, 0}, {"ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, CURLOT_FLAG_ALIAS}, {"ERRORBUFFER", CURLOPT_ERRORBUFFER, CURLOT_OBJECT, 0}, @@ -120,6 +121,7 @@ struct curl_easyoption Curl_easyopts[] = { {"HAPPY_EYEBALLS_TIMEOUT_MS", CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, CURLOT_LONG, 0}, {"HAPROXYPROTOCOL", CURLOPT_HAPROXYPROTOCOL, CURLOT_LONG, 0}, + {"HAPROXY_CLIENT_IP", CURLOPT_HAPROXY_CLIENT_IP, CURLOT_STRING, 0}, {"HEADER", CURLOPT_HEADER, CURLOT_LONG, 0}, {"HEADERDATA", CURLOPT_HEADERDATA, CURLOT_CBPTR, 0}, {"HEADERFUNCTION", CURLOPT_HEADERFUNCTION, CURLOT_FUNCTION, 0}, @@ -164,7 +166,9 @@ struct curl_easyoption Curl_easyopts[] = { {"MAIL_AUTH", CURLOPT_MAIL_AUTH, CURLOT_STRING, 0}, {"MAIL_FROM", CURLOPT_MAIL_FROM, CURLOT_STRING, 0}, {"MAIL_RCPT", CURLOPT_MAIL_RCPT, CURLOT_SLIST, 0}, - {"MAIL_RCPT_ALLLOWFAILS", CURLOPT_MAIL_RCPT_ALLLOWFAILS, CURLOT_LONG, 0}, + {"MAIL_RCPT_ALLLOWFAILS", CURLOPT_MAIL_RCPT_ALLOWFAILS, + CURLOT_LONG, CURLOT_FLAG_ALIAS}, + {"MAIL_RCPT_ALLOWFAILS", CURLOPT_MAIL_RCPT_ALLOWFAILS, CURLOT_LONG, 0}, {"MAXAGE_CONN", CURLOPT_MAXAGE_CONN, CURLOT_LONG, 0}, {"MAXCONNECTS", CURLOPT_MAXCONNECTS, CURLOT_LONG, 0}, {"MAXFILESIZE", CURLOPT_MAXFILESIZE, CURLOT_LONG, 0}, @@ -271,6 +275,8 @@ struct curl_easyoption Curl_easyopts[] = { {"SEEKFUNCTION", CURLOPT_SEEKFUNCTION, CURLOT_FUNCTION, 0}, {"SERVER_RESPONSE_TIMEOUT", CURLOPT_SERVER_RESPONSE_TIMEOUT, CURLOT_LONG, 0}, + {"SERVER_RESPONSE_TIMEOUT_MS", CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, + CURLOT_LONG, 0}, {"SERVICE_NAME", CURLOPT_SERVICE_NAME, CURLOT_STRING, 0}, {"SHARE", CURLOPT_SHARE, CURLOT_OBJECT, 0}, {"SOCKOPTDATA", CURLOPT_SOCKOPTDATA, CURLOT_CBPTR, 0}, @@ -311,6 +317,8 @@ struct curl_easyoption Curl_easyopts[] = { {"SSL_FALSESTART", CURLOPT_SSL_FALSESTART, CURLOT_LONG, 0}, {"SSL_OPTIONS", CURLOPT_SSL_OPTIONS, CURLOT_VALUES, 0}, {"SSL_SESSIONID_CACHE", CURLOPT_SSL_SESSIONID_CACHE, CURLOT_LONG, 0}, + {"SSL_SIGNATURE_ALGORITHMS", CURLOPT_SSL_SIGNATURE_ALGORITHMS, + CURLOT_STRING, 0}, {"SSL_VERIFYHOST", CURLOPT_SSL_VERIFYHOST, CURLOT_LONG, 0}, {"SSL_VERIFYPEER", CURLOPT_SSL_VERIFYPEER, CURLOT_LONG, 0}, {"SSL_VERIFYSTATUS", CURLOPT_SSL_VERIFYSTATUS, CURLOT_LONG, 0}, @@ -322,6 +330,7 @@ struct curl_easyoption Curl_easyopts[] = { CURLOT_LONG, 0}, {"TCP_FASTOPEN", CURLOPT_TCP_FASTOPEN, CURLOT_LONG, 0}, {"TCP_KEEPALIVE", CURLOPT_TCP_KEEPALIVE, CURLOT_LONG, 0}, + {"TCP_KEEPCNT", CURLOPT_TCP_KEEPCNT, CURLOT_LONG, 0}, {"TCP_KEEPIDLE", CURLOPT_TCP_KEEPIDLE, CURLOT_LONG, 0}, {"TCP_KEEPINTVL", CURLOPT_TCP_KEEPINTVL, CURLOT_LONG, 0}, {"TCP_NODELAY", CURLOPT_TCP_NODELAY, CURLOT_LONG, 0}, @@ -346,6 +355,7 @@ struct curl_easyoption Curl_easyopts[] = { {"UPKEEP_INTERVAL_MS", CURLOPT_UPKEEP_INTERVAL_MS, CURLOT_LONG, 0}, {"UPLOAD", CURLOPT_UPLOAD, CURLOT_LONG, 0}, {"UPLOAD_BUFFERSIZE", CURLOPT_UPLOAD_BUFFERSIZE, CURLOT_LONG, 0}, + {"UPLOAD_FLAGS", CURLOPT_UPLOAD_FLAGS, CURLOT_LONG, 0}, {"URL", CURLOPT_URL, CURLOT_STRING, 0}, {"USERAGENT", CURLOPT_USERAGENT, CURLOT_STRING, 0}, {"USERNAME", CURLOPT_USERNAME, CURLOT_STRING, 0}, @@ -370,6 +380,6 @@ struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return ((CURLOPT_LASTENTRY%10000) != (322 + 1)); + return (CURLOPT_LASTENTRY % 10000) != (328 + 1); } #endif diff --git a/Utilities/cmcurl/lib/easyoptions.h b/Utilities/cmcurl/lib/easyoptions.h index 24b4cd93ed9..44b6a8280a8 100644 --- a/Utilities/cmcurl/lib/easyoptions.h +++ b/Utilities/cmcurl/lib/easyoptions.h @@ -29,7 +29,7 @@ #include /* generated table with all easy options */ -extern struct curl_easyoption Curl_easyopts[]; +extern const struct curl_easyoption Curl_easyopts[]; #ifdef DEBUGBUILD int Curl_easyopts_check(void); diff --git a/Utilities/cmcurl/lib/escape.c b/Utilities/cmcurl/lib/escape.c index 56aa2b39888..3cd906dc6c2 100644 --- a/Utilities/cmcurl/lib/escape.c +++ b/Utilities/cmcurl/lib/escape.c @@ -29,42 +29,19 @@ #include +struct Curl_easy; + #include "urldata.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "escape.h" #include "strdup.h" +#include "curlx/strparse.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -/* Portable character check (remember EBCDIC). Do not use isalnum() because - its behavior is altered by the current locale. - See https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 -*/ -bool Curl_isunreserved(unsigned char in) -{ - switch(in) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'a': case 'b': case 'c': case 'd': case 'e': - case 'f': case 'g': case 'h': case 'i': case 'j': - case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': - case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': - case 'K': case 'L': case 'M': case 'N': case 'O': - case 'P': case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '-': case '.': case '_': case '~': - return TRUE; - default: - break; - } - return FALSE; -} - /* for ABI-compatibility with previous versions */ char *curl_escape(const char *string, int inlength) { @@ -80,54 +57,43 @@ char *curl_unescape(const char *string, int length) /* Escapes for URL the given unescaped string of given length. * 'data' is ignored since 7.82.0. */ -char *curl_easy_escape(struct Curl_easy *data, const char *string, +char *curl_easy_escape(CURL *data, const char *string, int inlength) { size_t length; struct dynbuf d; (void)data; - if(inlength < 0) + if(!string || (inlength < 0)) return NULL; - Curl_dyn_init(&d, CURL_MAX_INPUT_LENGTH * 3); - - length = (inlength?(size_t)inlength:strlen(string)); + length = (inlength ? (size_t)inlength : strlen(string)); if(!length) return strdup(""); + curlx_dyn_init(&d, length * 3 + 1); + while(length--) { - unsigned char in = *string++; /* treat the characters unsigned */ + /* treat the characters unsigned */ + unsigned char in = (unsigned char)*string++; - if(Curl_isunreserved(in)) { + if(ISUNRESERVED(in)) { /* append this */ - if(Curl_dyn_addn(&d, &in, 1)) + if(curlx_dyn_addn(&d, &in, 1)) return NULL; } else { /* encode it */ - const char hex[] = "0123456789ABCDEF"; - char out[3]={'%'}; - out[1] = hex[in>>4]; - out[2] = hex[in & 0xf]; - if(Curl_dyn_addn(&d, out, 3)) + unsigned char out[3]={'%'}; + Curl_hexbyte(&out[1], in, FALSE); + if(curlx_dyn_addn(&d, out, 3)) return NULL; } } - return Curl_dyn_ptr(&d); + return curlx_dyn_ptr(&d); } -static const unsigned char hextable[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ - 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ - 0, 10, 11, 12, 13, 14, 15 /* 0x60 - 0x66 */ -}; - -/* the input is a single hex digit */ -#define onehex2dec(x) hextable[x - '0'] - /* * Curl_urldecode() URL decodes the given string. * @@ -154,7 +120,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, DEBUGASSERT(string); DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ - alloc = (length?length:strlen(string)); + alloc = (length ? length : strlen(string)); ns = malloc(alloc + 1); if(!ns) @@ -164,12 +130,12 @@ CURLcode Curl_urldecode(const char *string, size_t length, *ostring = ns; while(alloc) { - unsigned char in = *string; + 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)(onehex2dec(string[1]) << 4) | onehex2dec(string[2]); - + in = (unsigned char)((Curl_hexval(string[1]) << 4) | + Curl_hexval(string[2])); string += 3; alloc -= 3; } @@ -184,7 +150,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, return CURLE_URL_MALFORMAT; } - *ns++ = in; + *ns++ = (char)in; } *ns = 0; /* terminate it */ @@ -202,12 +168,12 @@ CURLcode Curl_urldecode(const char *string, size_t length, * If olen == NULL, no output length is stored. * 'data' is ignored since 7.82.0. */ -char *curl_easy_unescape(struct Curl_easy *data, const char *string, +char *curl_easy_unescape(CURL *data, const char *string, int length, int *olen) { char *str = NULL; (void)data; - if(length >= 0) { + if(string && (length >= 0)) { size_t inputlen = (size_t)length; size_t outputlen; CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen, @@ -233,3 +199,40 @@ void curl_free(void *p) { free(p); } + +/* + * Curl_hexencode() + * + * Converts binary input to lowercase hex-encoded ASCII output. + * Null-terminated. + */ +void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ + unsigned char *out, size_t olen) /* output buffer size */ +{ + DEBUGASSERT(src && len && (olen >= 3)); + if(src && len && (olen >= 3)) { + while(len-- && (olen >= 3)) { + Curl_hexbyte(out, *src, TRUE); + ++src; + out += 2; + olen -= 2; + } + *out = 0; + } + else if(olen) + *out = 0; +} + +/* Curl_hexbyte + * + * Output a single unsigned char as a two-digit hex number, lowercase or + * uppercase + */ +void Curl_hexbyte(unsigned char *dest, /* must fit two bytes */ + unsigned char val, + bool lowercase) +{ + const unsigned char *t = lowercase ? Curl_ldigits : Curl_udigits; + dest[0] = t[val >> 4]; + dest[1] = t[val & 0x0F]; +} diff --git a/Utilities/cmcurl/lib/escape.h b/Utilities/cmcurl/lib/escape.h index cdbb712acc1..1f2bac8fac2 100644 --- a/Utilities/cmcurl/lib/escape.h +++ b/Utilities/cmcurl/lib/escape.h @@ -26,7 +26,7 @@ /* Escape and unescape URL encoding in strings. The functions return a new * allocated string or NULL if an error occurred. */ -bool Curl_isunreserved(unsigned char in); +#include "curl_ctype.h" enum urlreject { REJECT_NADA = 2, @@ -38,4 +38,11 @@ CURLcode Curl_urldecode(const char *string, size_t length, char **ostring, size_t *olen, enum urlreject ctrl); +void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ + unsigned char *out, size_t olen); /* output buffer size */ + +void Curl_hexbyte(unsigned char *dest, /* must fit two bytes */ + unsigned char val, + bool lowercase); + #endif /* HEADER_CURL_ESCAPE_H */ diff --git a/Utilities/cmcurl/lib/fake_addrinfo.c b/Utilities/cmcurl/lib/fake_addrinfo.c new file mode 100644 index 00000000000..20d55ba2d1b --- /dev/null +++ b/Utilities/cmcurl/lib/fake_addrinfo.c @@ -0,0 +1,210 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "fake_addrinfo.h" + +#ifdef USE_FAKE_GETADDRINFO + +#include +#include +#include + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +void r_freeaddrinfo(struct addrinfo *cahead) +{ + struct addrinfo *canext; + struct addrinfo *ca; + + for(ca = cahead; ca; ca = canext) { + canext = ca->ai_next; + free(ca); + } +} + +struct context { + struct ares_addrinfo *result; +}; + +static void async_addrinfo_cb(void *userp, int status, int timeouts, + struct ares_addrinfo *result) +{ + struct context *ctx = (struct context *)userp; + (void)timeouts; + if(ARES_SUCCESS == status) { + ctx->result = result; + } +} + +/* convert the c-ares version into the "native" version */ +static struct addrinfo *mk_getaddrinfo(const struct ares_addrinfo *aihead) +{ + const struct ares_addrinfo_node *ai; + struct addrinfo *ca; + struct addrinfo *cafirst = NULL; + struct addrinfo *calast = NULL; + const char *name = aihead->name; + + /* traverse the addrinfo list */ + for(ai = aihead->nodes; ai != NULL; ai = ai->ai_next) { + size_t ss_size; + size_t namelen = name ? strlen(name) + 1 : 0; + /* ignore elements with unsupported address family, */ + /* settle family-specific sockaddr structure size. */ + if(ai->ai_family == AF_INET) + ss_size = sizeof(struct sockaddr_in); + else if(ai->ai_family == AF_INET6) + ss_size = sizeof(struct sockaddr_in6); + else + continue; + + /* ignore elements without required address info */ + if(!ai->ai_addr || !(ai->ai_addrlen > 0)) + continue; + + /* ignore elements with bogus address size */ + if((size_t)ai->ai_addrlen < ss_size) + continue; + + ca = malloc(sizeof(struct addrinfo) + ss_size + namelen); + if(!ca) { + r_freeaddrinfo(cafirst); + return NULL; + } + + /* copy each structure member individually, member ordering, */ + /* size, or padding might be different for each platform. */ + + ca->ai_flags = ai->ai_flags; + ca->ai_family = ai->ai_family; + ca->ai_socktype = ai->ai_socktype; + ca->ai_protocol = ai->ai_protocol; + ca->ai_addrlen = (curl_socklen_t)ss_size; + ca->ai_addr = NULL; + ca->ai_canonname = NULL; + ca->ai_next = NULL; + + ca->ai_addr = (void *)((char *)ca + sizeof(struct addrinfo)); + memcpy(ca->ai_addr, ai->ai_addr, ss_size); + + if(namelen) { + ca->ai_canonname = (void *)((char *)ca->ai_addr + ss_size); + memcpy(ca->ai_canonname, name, namelen); + + /* the name is only pointed to by the first entry in the "real" + addrinfo chain, so stop now */ + name = NULL; + } + + /* if the return list is empty, this becomes the first element */ + if(!cafirst) + cafirst = ca; + + /* add this element last in the return list */ + if(calast) + calast->ai_next = ca; + calast = ca; + } + + return cafirst; +} + +/* + RETURN VALUE + + getaddrinfo() returns 0 if it succeeds, or one of the following nonzero + error codes: + + ... +*/ +int r_getaddrinfo(const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res) +{ + int status; + struct context ctx; + struct ares_options options; + int optmask = 0; + struct ares_addrinfo_hints ahints; + ares_channel channel; + int rc = 0; + + memset(&options, 0, sizeof(options)); + optmask |= ARES_OPT_EVENT_THREAD; + options.evsys = ARES_EVSYS_DEFAULT; + + memset(&ahints, 0, sizeof(ahints)); + memset(&ctx, 0, sizeof(ctx)); + + if(hints) { + ahints.ai_flags = hints->ai_flags; + ahints.ai_family = hints->ai_family; + ahints.ai_socktype = hints->ai_socktype; + ahints.ai_protocol = hints->ai_protocol; + } + + status = ares_init_options(&channel, &options, optmask); + if(status) + return EAI_MEMORY; /* major problem */ + + else { + const char *env = getenv("CURL_DNS_SERVER"); + if(env) { + rc = ares_set_servers_ports_csv(channel, env); + if(rc) { + fprintf(stderr, "ares_set_servers_ports_csv failed: %d", rc); + /* Cleanup */ + ares_destroy(channel); + return EAI_MEMORY; /* we can't run */ + } + } + } + + ares_getaddrinfo(channel, node, service, &ahints, + async_addrinfo_cb, &ctx); + + /* Wait until no more requests are left to be processed */ + ares_queue_wait_empty(channel, -1); + + if(ctx.result) { + /* convert the c-ares version */ + *res = mk_getaddrinfo(ctx.result); + /* free the old */ + ares_freeaddrinfo(ctx.result); + } + else + rc = EAI_NONAME; /* got nothing */ + + /* Cleanup */ + ares_destroy(channel); + + return rc; +} + +#endif /* USE_FAKE_GETADDRINFO */ diff --git a/Utilities/cmcurl/lib/fake_addrinfo.h b/Utilities/cmcurl/lib/fake_addrinfo.h new file mode 100644 index 00000000000..13b0d71dba6 --- /dev/null +++ b/Utilities/cmcurl/lib/fake_addrinfo.h @@ -0,0 +1,54 @@ +#ifndef HEADER_FAKE_ADDRINFO_H +#define HEADER_FAKE_ADDRINFO_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" + +#ifdef USE_ARES +#include +#endif + +#if defined(CURLDEBUG) && defined(USE_ARES) && defined(HAVE_GETADDRINFO) && \ + (ARES_VERSION >= 0x011a00) /* >= 1.26. 0 */ +#define USE_FAKE_GETADDRINFO 1 +#endif + +#ifdef USE_FAKE_GETADDRINFO + +#ifdef HAVE_NETDB_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif + +void r_freeaddrinfo(struct addrinfo *res); +int r_getaddrinfo(const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res); +#endif /* USE_FAKE_GETADDRINFO */ + +#endif /* HEADER_FAKE_ADDRINFO_H */ diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c index c751e8861a9..b88f6123056 100644 --- a/Utilities/cmcurl/lib/file.c +++ b/Utilities/cmcurl/lib/file.c @@ -50,7 +50,14 @@ #include #endif -#include "strtoofft.h" +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_DIRENT_H +#include +#endif + #include "urldata.h" #include #include "progress.h" @@ -59,27 +66,32 @@ #include "file.h" #include "speedcheck.h" #include "getinfo.h" +#include "multiif.h" #include "transfer.h" #include "url.h" #include "parsedate.h" /* for the week day and month names */ -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_range.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) +#if defined(_WIN32) || defined(MSDOS) #define DOS_FILESYSTEM 1 #elif defined(__amigaos4__) #define AMIGA_FILESYSTEM 1 #endif -#ifdef OPEN_NEEDS_ARG3 -# define open_readonly(p,f) open((p),(f),(0)) -#else -# define open_readonly(p,f) open((p),(f)) -#endif +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_FILE_EASY "meta:proto:file:easy" + +struct FILEPROTO { + char *path; /* the path we operate on */ + char *freepath; /* pointer to the allocated block we must free, this might + differ from the 'path' pointer */ + int fd; /* open file descriptor to read from! */ +}; /* * Forward declarations. @@ -100,7 +112,7 @@ static CURLcode file_setup_connection(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_file = { - "FILE", /* scheme */ + "file", /* scheme */ file_setup_connection, /* setup_connection */ file_do, /* do_it */ file_done, /* done */ @@ -113,9 +125,11 @@ const struct Curl_handler Curl_handler_file = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ file_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ 0, /* defport */ CURLPROTO_FILE, /* protocol */ CURLPROTO_FILE, /* family */ @@ -123,13 +137,34 @@ const struct Curl_handler Curl_handler_file = { }; +static void file_cleanup(struct FILEPROTO *file) +{ + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) { + close(file->fd); + file->fd = -1; + } +} + +static void file_easy_dtor(void *key, size_t klen, void *entry) +{ + struct FILEPROTO *file = entry; + (void)key; + (void)klen; + file_cleanup(file); + free(file); +} + static CURLcode file_setup_connection(struct Curl_easy *data, struct connectdata *conn) { + struct FILEPROTO *filep; (void)conn; /* allocate the FILE specific struct */ - data->req.p.file = calloc(1, sizeof(struct FILEPROTO)); - if(!data->req.p.file) + filep = calloc(1, sizeof(*filep)); + if(!filep || + Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor)) return CURLE_OUT_OF_MEMORY; return CURLE_OK; @@ -137,13 +172,13 @@ static CURLcode file_setup_connection(struct Curl_easy *data, /* * file_connect() gets called from Curl_protocol_connect() to allow us to - * do protocol-specific actions at connect-time. We emulate a + * do protocol-specific actions at connect-time. We emulate a * connect-then-transfer protocol and "connect" to the file here */ static CURLcode file_connect(struct Curl_easy *data, bool *done) { char *real_path; - struct FILEPROTO *file = data->req.p.file; + struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); int fd; #ifdef DOS_FILESYSTEM size_t i; @@ -152,6 +187,9 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) size_t real_path_len; CURLcode result; + if(!file) + return CURLE_FAILED_INIT; + if(file->path) { /* already connected. * the handler->connect_it() is normally only called once, but @@ -167,18 +205,18 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) return result; #ifdef DOS_FILESYSTEM - /* If the first character is a slash, and there's + /* If the first character is a slash, and there is something that looks like a drive at the beginning of - the path, skip the slash. If we remove the initial + the path, skip the slash. If we remove the initial slash in all cases, paths without drive letters end up - relative to the current directory which isn't how + relative to the current directory which is not how browsers work. Some browsers accept | instead of : as the drive letter separator, so we do too. On other platforms, we need the slash to indicate an - absolute pathname. On Windows, absolute paths start + absolute pathname. On Windows, absolute paths start with a drive letter. */ actual_path = real_path; @@ -199,7 +237,7 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) return CURLE_URL_MALFORMAT; } - fd = open_readonly(actual_path, O_RDONLY|O_BINARY); + fd = open(actual_path, O_RDONLY|CURL_O_BINARY); file->path = actual_path; #else if(memchr(real_path, 0, real_path_len)) { @@ -213,7 +251,7 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) * A leading slash in an AmigaDOS path denotes the parent * directory, and hence we block this as it is relative. * Absolute paths start with 'volumename:', so we check for - * this first. Failing that, we treat the path as a real unix + * this first. Failing that, we treat the path as a real Unix * path, but only if the application was compiled with -lunix. */ fd = -1; @@ -223,20 +261,20 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) extern int __unix_path_semantics; if(strchr(real_path + 1, ':')) { /* Amiga absolute path */ - fd = open_readonly(real_path + 1, O_RDONLY); + fd = open(real_path + 1, O_RDONLY); file->path++; } else if(__unix_path_semantics) { /* -lunix fallback */ - fd = open_readonly(real_path, O_RDONLY); + fd = open(real_path, O_RDONLY); } } #else - fd = open_readonly(real_path, O_RDONLY); + fd = open(real_path, O_RDONLY); file->path = real_path; #endif #endif - Curl_safefree(file->freepath); + free(file->freepath); file->freepath = real_path; /* free this when done */ file->fd = fd; @@ -253,17 +291,12 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) static CURLcode file_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct FILEPROTO *file = data->req.p.file; + struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); (void)status; /* not used */ (void)premature; /* not used */ - if(file) { - Curl_safefree(file->freepath); - file->path = NULL; - if(file->fd != -1) - close(file->fd); - file->fd = -1; - } + if(file) + file_cleanup(file); return CURLE_OK; } @@ -283,23 +316,24 @@ static CURLcode file_disconnect(struct Curl_easy *data, #define DIRSEP '/' #endif -static CURLcode file_upload(struct Curl_easy *data) +static CURLcode file_upload(struct Curl_easy *data, + struct FILEPROTO *file) { - struct FILEPROTO *file = data->req.p.file; const char *dir = strchr(file->path, DIRSEP); int fd; int mode; CURLcode result = CURLE_OK; - char *buf = data->state.buffer; + char *xfer_ulbuf; + size_t xfer_ulblen; curl_off_t bytecount = 0; struct_stat file_stat; - const char *buf2; + const char *sendbuf; + bool eos = FALSE; /* - * Since FILE: doesn't do the full init, we need to provide some extra + * Since FILE: does not do the full init, we need to provide some extra * assignments here. */ - data->req.upload_fromhere = buf; if(!dir) return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ @@ -307,20 +341,20 @@ static CURLcode file_upload(struct Curl_easy *data) if(!dir[1]) return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ -#ifdef O_BINARY -#define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY -#else -#define MODE_DEFAULT O_WRONLY|O_CREAT -#endif - + mode = O_WRONLY|O_CREAT|CURL_O_BINARY; if(data->state.resume_from) - mode = MODE_DEFAULT|O_APPEND; + mode |= O_APPEND; else - mode = MODE_DEFAULT|O_TRUNC; + mode |= O_TRUNC; +#if (defined(ANDROID) || defined(__ANDROID__)) && \ + (defined(__i386__) || defined(__arm__)) + fd = open(file->path, mode, (mode_t)data->set.new_file_perms); +#else fd = open(file->path, mode, data->set.new_file_perms); +#endif if(fd < 0) { - failf(data, "Can't open %s for writing", file->path); + failf(data, "cannot open %s for writing", file->path); return CURLE_WRITE_ERROR; } @@ -332,17 +366,22 @@ static CURLcode file_upload(struct Curl_easy *data) if(data->state.resume_from < 0) { if(fstat(fd, &file_stat)) { close(fd); - failf(data, "Can't get the size of %s", file->path); + failf(data, "cannot get the size of %s", file->path); return CURLE_WRITE_ERROR; } data->state.resume_from = (curl_off_t)file_stat.st_size; } - while(!result) { + result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); + if(result) + goto out; + + while(!result && !eos) { size_t nread; ssize_t nwrite; size_t readcount; - result = Curl_fillreadbuffer(data, data->set.buffer_size, &readcount); + + result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos); if(result) break; @@ -356,19 +395,19 @@ static CURLcode file_upload(struct Curl_easy *data) if((curl_off_t)nread <= data->state.resume_from) { data->state.resume_from -= nread; nread = 0; - buf2 = buf; + sendbuf = xfer_ulbuf; } else { - buf2 = buf + data->state.resume_from; + sendbuf = xfer_ulbuf + data->state.resume_from; nread -= (size_t)data->state.resume_from; data->state.resume_from = 0; } } else - buf2 = buf; + sendbuf = xfer_ulbuf; /* write the data to the target */ - nwrite = write(fd, buf2, nread); + nwrite = write(fd, sendbuf, nread); if((size_t)nwrite != nread) { result = CURLE_SEND_ERROR; break; @@ -381,12 +420,14 @@ static CURLcode file_upload(struct Curl_easy *data) if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_now()); + result = Curl_speedcheck(data, curlx_now()); } if(!result && Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; +out: close(fd); + Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); return result; } @@ -395,17 +436,18 @@ static CURLcode file_upload(struct Curl_easy *data) * file_do() is the protocol-specific function for the do-phase, separated * from the connect-phase above. Other protocols merely setup the transfer in * the do-phase, to have it done in the main transfer loop but since some - * platforms we support don't allow select()ing etc on file handles (as + * platforms we support do not allow select()ing etc on file handles (as * opposed to sockets) we instead perform the whole do-operation in this * function. */ static CURLcode file_do(struct Curl_easy *data, bool *done) { - /* This implementation ignores the host name in conformance with + /* This implementation ignores the hostname in conformance with RFC 1738. Only local files (reachable via the standard file system) are supported. This means that files on remotely mounted directories (via NFS, Samba, NT sharing) can be accessed through a file:// URL */ + struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); CURLcode result = CURLE_OK; struct_stat statbuf; /* struct_stat instead of struct stat just to allow the Windows version to have a different struct without @@ -413,19 +455,16 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) curl_off_t expected_size = -1; bool size_known; bool fstated = FALSE; - char *buf = data->state.buffer; - curl_off_t bytecount = 0; int fd; - struct FILEPROTO *file; + char *xfer_buf; + size_t xfer_blen; *done = TRUE; /* unconditionally */ - - Curl_pgrsStartNow(data); + if(!file) + return CURLE_FAILED_INIT; if(data->state.upload) - return file_upload(data); - - file = data->req.p.file; + return file_upload(data, file); /* get the fd from the connection phase */ fd = file->fd; @@ -439,12 +478,9 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) fstated = TRUE; } - if(fstated && !data->state.range && data->set.timecondition) { - if(!Curl_meets_timecondition(data, data->info.filetime)) { - *done = TRUE; - return CURLE_OK; - } - } + if(fstated && !data->state.range && data->set.timecondition && + !Curl_meets_timecondition(data, data->info.filetime)) + return CURLE_OK; if(fstated) { time_t filetime; @@ -452,17 +488,17 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) const struct tm *tm = &buffer; char header[80]; int headerlen; - char accept_ranges[24]= { "Accept-ranges: bytes\r\n" }; + static const char accept_ranges[]= { "Accept-ranges: bytes\r\n" }; if(expected_size >= 0) { - headerlen = msnprintf(header, sizeof(header), - "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", - expected_size); + headerlen = + msnprintf(header, sizeof(header), "Content-Length: %" FMT_OFF_T "\r\n", + expected_size); result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); if(result) return result; result = Curl_client_write(data, CLIENTWRITE_HEADER, - accept_ranges, strlen(accept_ranges)); + accept_ranges, sizeof(accept_ranges) - 1); if(result != CURLE_OK) return result; } @@ -473,23 +509,26 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) return result; /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - headerlen = msnprintf(header, sizeof(header), - "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n%s", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec, - data->req.no_body ? "": "\r\n"); + headerlen = + msnprintf(header, sizeof(header), + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); + if(!result) + /* end of headers */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2); if(result) return result; /* set the file size to make it available post transfer */ Curl_pgrsSetDownloadSize(data, expected_size); if(data->req.no_body) - return result; + return CURLE_OK; } /* Check whether file range has been specified */ @@ -501,7 +540,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) * of the stream if the filesize could be determined */ if(data->state.resume_from < 0) { if(!fstated) { - failf(data, "Can't get the size of file."); + failf(data, "cannot get the size of file."); return CURLE_READ_ERROR; } data->state.resume_from += (curl_off_t)statbuf.st_size; @@ -509,7 +548,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) if(data->state.resume_from > 0) { /* We check explicitly if we have a start offset, because - * expected_size may be -1 if we don't know how large the file is, + * expected_size may be -1 if we do not know how large the file is, * in which case we should not adjust it. */ if(data->state.resume_from <= expected_size) expected_size -= data->state.resume_from; @@ -536,51 +575,95 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) Curl_pgrsSetDownloadSize(data, expected_size); if(data->state.resume_from) { - if(data->state.resume_from != - lseek(fd, data->state.resume_from, SEEK_SET)) + if(!S_ISDIR(statbuf.st_mode)) { +#if defined(__AMIGA__) || defined(__MINGW32CE__) + if(data->state.resume_from != + lseek(fd, (off_t)data->state.resume_from, SEEK_SET)) +#else + if(data->state.resume_from != + lseek(fd, data->state.resume_from, SEEK_SET)) +#endif + return CURLE_BAD_DOWNLOAD_RESUME; + } + else { return CURLE_BAD_DOWNLOAD_RESUME; + } } - Curl_pgrsTime(data, TIMER_STARTTRANSFER); + result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); + if(result) + goto out; - while(!result) { - ssize_t nread; - /* Don't fill a whole buffer if we want less than all data */ - size_t bytestoread; + if(!S_ISDIR(statbuf.st_mode)) { + while(!result) { + ssize_t nread; + /* Do not fill a whole buffer if we want less than all data */ + size_t bytestoread; - if(size_known) { - bytestoread = (expected_size < data->set.buffer_size) ? - curlx_sotouz(expected_size) : (size_t)data->set.buffer_size; - } - else - bytestoread = data->set.buffer_size-1; + if(size_known) { + bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ? + curlx_sotouz(expected_size) : (xfer_blen-1); + } + else + bytestoread = xfer_blen-1; - nread = read(fd, buf, bytestoread); + nread = read(fd, xfer_buf, bytestoread); - if(nread > 0) - buf[nread] = 0; + if(nread > 0) + xfer_buf[nread] = 0; - if(nread <= 0 || (size_known && (expected_size == 0))) - break; + if(nread <= 0 || (size_known && (expected_size == 0))) + break; - bytecount += nread; - if(size_known) - expected_size -= nread; + if(size_known) + expected_size -= nread; - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread); - if(result) - return result; - - Curl_pgrsSetDownloadCounter(data, bytecount); + result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread); + if(result) + goto out; - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, Curl_now()); + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, curlx_now()); + if(result) + goto out; + } } + else { +#ifdef HAVE_OPENDIR + DIR *dir = opendir(file->path); + struct dirent *entry; + + if(!dir) { + result = CURLE_READ_ERROR; + goto out; + } + else { + while((entry = readdir(dir))) { + if(entry->d_name[0] != '.') { + result = Curl_client_write(data, CLIENTWRITE_BODY, + entry->d_name, strlen(entry->d_name)); + if(result) + break; + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(result) + break; + } + } + closedir(dir); + } +#else + failf(data, "Directory listing not yet implemented on this platform."); + result = CURLE_READ_ERROR; +#endif + } + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; +out: + Curl_multi_xfer_buf_release(data, xfer_buf); return result; } diff --git a/Utilities/cmcurl/lib/file.h b/Utilities/cmcurl/lib/file.h index 45655255920..fea1eea57d6 100644 --- a/Utilities/cmcurl/lib/file.h +++ b/Utilities/cmcurl/lib/file.h @@ -24,17 +24,6 @@ * ***************************************************************************/ - -/**************************************************************************** - * FILE unique setup - ***************************************************************************/ -struct FILEPROTO { - char *path; /* the path we operate on */ - char *freepath; /* pointer to the allocated block we must free, this might - differ from the 'path' pointer */ - int fd; /* open file descriptor to read from! */ -}; - #ifndef CURL_DISABLE_FILE extern const struct Curl_handler Curl_handler_file; #endif diff --git a/Utilities/cmcurl/lib/fileinfo.c b/Utilities/cmcurl/lib/fileinfo.c index 2be3b3239b8..c3439af341e 100644 --- a/Utilities/cmcurl/lib/fileinfo.c +++ b/Utilities/cmcurl/lib/fileinfo.c @@ -40,7 +40,7 @@ void Curl_fileinfo_cleanup(struct fileinfo *finfo) if(!finfo) return; - Curl_dyn_free(&finfo->buf); + curlx_dyn_free(&finfo->buf); free(finfo); } #endif diff --git a/Utilities/cmcurl/lib/fileinfo.h b/Utilities/cmcurl/lib/fileinfo.h index ce009da06df..6746ee25755 100644 --- a/Utilities/cmcurl/lib/fileinfo.h +++ b/Utilities/cmcurl/lib/fileinfo.h @@ -26,11 +26,11 @@ #include #include "llist.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" struct fileinfo { struct curl_fileinfo info; - struct Curl_llist_element list; + struct Curl_llist_node list; struct dynbuf buf; }; diff --git a/Utilities/cmcurl/lib/fopen.c b/Utilities/cmcurl/lib/fopen.c index f710dbf05ae..38b87f326e3 100644 --- a/Utilities/cmcurl/lib/fopen.c +++ b/Utilities/cmcurl/lib/fopen.c @@ -39,58 +39,111 @@ #include "curl_memory.h" #include "memdebug.h" +/* + The dirslash() function breaks a null-terminated pathname string into + directory and filename components then returns the directory component up + to, *AND INCLUDING*, a final '/'. If there is no directory in the path, + this instead returns a "" string. + + This function returns a pointer to malloc'ed memory. + + The input path to this function is expected to have a filename part. +*/ + +#ifdef _WIN32 +#define PATHSEP "\\" +#define IS_SEP(x) (((x) == '/') || ((x) == '\\')) +#elif defined(MSDOS) || defined(OS2) +#define PATHSEP "\\" +#define IS_SEP(x) ((x) == '\\') +#else +#define PATHSEP "/" +#define IS_SEP(x) ((x) == '/') +#endif + +static char *dirslash(const char *path) +{ + size_t n; + struct dynbuf out; + DEBUGASSERT(path); + curlx_dyn_init(&out, CURL_MAX_INPUT_LENGTH); + n = strlen(path); + if(n) { + /* find the rightmost path separator, if any */ + while(n && !IS_SEP(path[n-1])) + --n; + /* skip over all the path separators, if any */ + while(n && IS_SEP(path[n-1])) + --n; + } + if(curlx_dyn_addn(&out, path, n)) + return NULL; + /* if there was a directory, append a single trailing slash */ + if(n && curlx_dyn_addn(&out, PATHSEP, 1)) + return NULL; + return curlx_dyn_ptr(&out); +} + /* * Curl_fopen() opens a file for writing with a temp name, to be renamed * to the final name when completed. If there is an existing file using this * name at the time of the open, this function will clone the mode from that - * file. if 'tempname' is non-NULL, it needs a rename after the file is + * file. if 'tempname' is non-NULL, it needs a rename after the file is * written. */ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, FILE **fh, char **tempname) { CURLcode result = CURLE_WRITE_ERROR; - unsigned char randsuffix[9]; + unsigned char randbuf[41]; char *tempstore = NULL; struct_stat sb; int fd = -1; + char *dir = NULL; *tempname = NULL; - if(stat(filename, &sb) == -1 || !S_ISREG(sb.st_mode)) { - /* a non-regular file, fallback to direct fopen() */ - *fh = fopen(filename, FOPEN_WRITETEXT); - if(*fh) - return CURLE_OK; + *fh = fopen(filename, FOPEN_WRITETEXT); + if(!*fh) goto fail; + if( +#ifdef UNDER_CE + stat(filename, &sb) == -1 +#else + fstat(fileno(*fh), &sb) == -1 +#endif + || !S_ISREG(sb.st_mode)) { + return CURLE_OK; } + fclose(*fh); + *fh = NULL; - result = Curl_rand_hex(data, randsuffix, sizeof(randsuffix)); + result = Curl_rand_alnum(data, randbuf, sizeof(randbuf)); if(result) goto fail; - tempstore = aprintf("%s.%s.tmp", filename, randsuffix); + dir = dirslash(filename); + if(dir) { + /* The temp filename should not end up too long for the target file + system */ + tempstore = aprintf("%s%s.tmp", dir, randbuf); + free(dir); + } + if(!tempstore) { result = CURLE_OUT_OF_MEMORY; goto fail; } result = CURLE_WRITE_ERROR; - fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600); +#if (defined(ANDROID) || defined(__ANDROID__)) && \ + (defined(__i386__) || defined(__arm__)) + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, (mode_t)(0600|sb.st_mode)); +#else + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600|sb.st_mode); +#endif if(fd == -1) goto fail; -#ifdef HAVE_FCHMOD - { - struct_stat nsb; - if((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, sb.st_mode) == -1) - goto fail; - } - } -#endif - *fh = fdopen(fd, FOPEN_WRITETEXT); if(!*fh) goto fail; @@ -105,7 +158,6 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, } free(tempstore); - return result; } diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c index 2bdb9f26ecb..2aa8eee94aa 100644 --- a/Utilities/cmcurl/lib/formdata.c +++ b/Utilities/cmcurl/lib/formdata.c @@ -26,12 +26,10 @@ #include -#include "formdata.h" -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_MIME) +struct Curl_easy; -#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) -#include -#endif +#include "formdata.h" +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) #include "urldata.h" /* for struct Curl_easy */ #include "mime.h" @@ -40,7 +38,7 @@ #include "sendf.h" #include "strdup.h" #include "rand.h" -#include "warnless.h" +#include "curlx/warnless.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -66,36 +64,31 @@ * ***************************************************************************/ static struct curl_httppost * -AddHttpPost(char *name, size_t namelength, - char *value, curl_off_t contentslength, - char *buffer, size_t bufferlength, - char *contenttype, - long flags, - struct curl_slist *contentHeader, - char *showfilename, char *userp, +AddHttpPost(struct FormInfo *src, struct curl_httppost *parent_post, struct curl_httppost **httppost, struct curl_httppost **last_post) { struct curl_httppost *post; - if(!namelength && name) - namelength = strlen(name); - if((bufferlength > LONG_MAX) || (namelength > LONG_MAX)) + size_t namelength = src->namelength; + if(!namelength && src->name) + namelength = strlen(src->name); + if((src->bufferlength > LONG_MAX) || (namelength > LONG_MAX)) /* avoid overflow in typecasts below */ return NULL; post = calloc(1, sizeof(struct curl_httppost)); if(post) { - post->name = name; + post->name = src->name; post->namelength = (long)namelength; - post->contents = value; - post->contentlen = contentslength; - post->buffer = buffer; - post->bufferlength = (long)bufferlength; - post->contenttype = contenttype; - post->contentheader = contentHeader; - post->showfilename = showfilename; - post->userp = userp; - post->flags = flags | CURL_HTTPPOST_LARGE; + post->contents = src->value; + post->contentlen = src->contentslength; + post->buffer = src->buffer; + post->bufferlength = (long)src->bufferlength; + post->contenttype = src->contenttype; + post->flags = src->flags | CURL_HTTPPOST_LARGE; + post->contentheader = src->contentheader; + post->showfilename = src->showfilename; + post->userp = src->userp; } else return NULL; @@ -154,6 +147,28 @@ static struct FormInfo *AddFormInfo(char *value, return form_info; } +static void free_formlist(struct FormInfo *ptr) +{ + for(; ptr != NULL; ptr = ptr->more) { + if(ptr->name_alloc) { + Curl_safefree(ptr->name); + ptr->name_alloc = FALSE; + } + if(ptr->value_alloc) { + Curl_safefree(ptr->value); + ptr->value_alloc = FALSE; + } + if(ptr->contenttype_alloc) { + Curl_safefree(ptr->contenttype); + ptr->contenttype_alloc = FALSE; + } + if(ptr->showfilename_alloc) { + Curl_safefree(ptr->showfilename); + ptr->showfilename_alloc = FALSE; + } + } +} + /*************************************************************************** * * FormAdd() @@ -203,21 +218,126 @@ static struct FormInfo *AddFormInfo(char *value, * ***************************************************************************/ +static CURLFORMcode FormAddCheck(struct FormInfo *first_form, + struct curl_httppost **httppost, + struct curl_httppost **last_post) +{ + const char *prevtype = NULL; + struct FormInfo *form = NULL; + struct curl_httppost *post = NULL; + + /* go through the list, check for completeness and if everything is + * alright add the HttpPost item otherwise set retval accordingly */ + + for(form = first_form; + form != NULL; + form = form->more) { + if(((!form->name || !form->value) && !post) || + ( (form->contentslength) && + (form->flags & HTTPPOST_FILENAME) ) || + ( (form->flags & HTTPPOST_FILENAME) && + (form->flags & HTTPPOST_PTRCONTENTS) ) || + + ( (!form->buffer) && + (form->flags & HTTPPOST_BUFFER) && + (form->flags & HTTPPOST_PTRBUFFER) ) || + + ( (form->flags & HTTPPOST_READFILE) && + (form->flags & HTTPPOST_PTRCONTENTS) ) + ) { + return CURL_FORMADD_INCOMPLETE; + } + if(((form->flags & HTTPPOST_FILENAME) || + (form->flags & HTTPPOST_BUFFER)) && + !form->contenttype) { + char *f = (form->flags & HTTPPOST_BUFFER) ? + form->showfilename : form->value; + char const *type; + type = Curl_mime_contenttype(f); + if(!type) + type = prevtype; + if(!type) + type = FILE_CONTENTTYPE_DEFAULT; + + /* our contenttype is missing */ + form->contenttype = strdup(type); + if(!form->contenttype) + return CURL_FORMADD_MEMORY; + + form->contenttype_alloc = TRUE; + } + if(form->name && form->namelength) { + if(memchr(form->name, 0, form->namelength)) + return CURL_FORMADD_NULL; + } + if(!(form->flags & HTTPPOST_PTRNAME) && form->name) { + /* 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. */ + + /* copy name (without strdup; possibly not null-terminated) */ + char *dupname = Curl_memdup0(form->name, form->namelength ? + form->namelength : strlen(form->name)); + if(!dupname) + return CURL_FORMADD_MEMORY; + + form->name = dupname; + form->name_alloc = TRUE; + } + if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | + HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | + HTTPPOST_CALLBACK)) && form->value) { + /* copy value (without strdup; possibly contains null characters) */ + size_t clen = (size_t) form->contentslength; + if(!clen) + clen = strlen(form->value) + 1; + + form->value = Curl_memdup(form->value, clen); + + if(!form->value) + return CURL_FORMADD_MEMORY; + + form->value_alloc = TRUE; + } + post = AddHttpPost(form, post, httppost, last_post); + + if(!post) + return CURL_FORMADD_MEMORY; + + if(form->contenttype) + prevtype = form->contenttype; + } + + return CURL_FORMADD_OK; +} + +/* Shallow cleanup. Remove the newly created chain, the structs only and not + the content they point to */ +static void free_chain(struct curl_httppost *c) +{ + while(c) { + struct curl_httppost *next = c->next; + if(c->more) + free_chain(c->more); + free(c); + c = next; + } +} + static CURLFORMcode FormAdd(struct curl_httppost **httppost, struct curl_httppost **last_post, va_list params) { - struct FormInfo *first_form, *current_form, *form = NULL; - CURLFORMcode return_value = CURL_FORMADD_OK; - const char *prevtype = NULL; - struct curl_httppost *post = NULL; + struct FormInfo *first_form, *curr, *form = NULL; + CURLFORMcode retval = CURL_FORMADD_OK; CURLformoption option; struct curl_forms *forms = NULL; - char *array_value = NULL; /* value read from an array */ + char *avalue = NULL; + struct curl_httppost *newchain = NULL; + struct curl_httppost *lastnode = NULL; - /* This is a state variable, that if TRUE means that we're parsing an - array that we got passed to us. If FALSE we're parsing the input + /* This is a state variable, that if TRUE means that we are parsing an + array that we got passed to us. If FALSE we are parsing the input va_list arguments. */ bool array_state = FALSE; @@ -228,18 +348,18 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, if(!first_form) return CURL_FORMADD_MEMORY; - current_form = first_form; + curr = first_form; /* * Loop through all the options set. Break if we have an error to report. */ - while(return_value == CURL_FORMADD_OK) { + while(retval == CURL_FORMADD_OK) { /* first see if we have more parts of the array param */ if(array_state && forms) { /* get the upcoming option from the given array */ option = forms->option; - array_value = (char *)forms->value; + avalue = (char *)CURL_UNCONST(forms->value); forms++; /* advance this to next entry */ if(CURLFORM_END == option) { @@ -260,14 +380,14 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, switch(option) { case CURLFORM_ARRAY: if(array_state) - /* we don't support an array from within an array */ - return_value = CURL_FORMADD_ILLEGAL_ARRAY; + /* we do not support an array from within an array */ + retval = CURL_FORMADD_ILLEGAL_ARRAY; else { forms = va_arg(params, struct curl_forms *); if(forms) array_state = TRUE; else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; @@ -275,402 +395,253 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, * Set the Name property. */ case CURLFORM_PTRNAME: - current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ + curr->flags |= HTTPPOST_PTRNAME; /* fall through */ - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLFORM_COPYNAME: - if(current_form->name) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->name) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *name = array_state? - array_value:va_arg(params, char *); - if(name) - current_form->name = name; /* store for the moment */ + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) + curr->name = avalue; /* store for the moment */ else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_NAMELENGTH: - if(current_form->namelength) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->namelength) + retval = CURL_FORMADD_OPTION_TWICE; else - current_form->namelength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + curr->namelength = + array_state ? (size_t)avalue : (size_t)va_arg(params, long); break; /* * Set the contents property. */ case CURLFORM_PTRCONTENTS: - current_form->flags |= HTTPPOST_PTRCONTENTS; - /* FALLTHROUGH */ + curr->flags |= HTTPPOST_PTRCONTENTS; + FALLTHROUGH(); case CURLFORM_COPYCONTENTS: - if(current_form->value) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->value) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *value = - array_state?array_value:va_arg(params, char *); - if(value) - current_form->value = value; /* store for the moment */ + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) + curr->value = avalue; /* store for the moment */ else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_CONTENTSLENGTH: - current_form->contentslength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + curr->contentslength = + array_state ? (size_t)avalue : (size_t)va_arg(params, long); break; case CURLFORM_CONTENTLEN: - current_form->flags |= CURL_HTTPPOST_LARGE; - current_form->contentslength = - array_state?(curl_off_t)(size_t)array_value:va_arg(params, curl_off_t); + curr->flags |= CURL_HTTPPOST_LARGE; + curr->contentslength = + array_state ? (curl_off_t)(size_t)avalue : + va_arg(params, curl_off_t); break; - /* Get contents from a given file name */ + /* Get contents from a given filename */ case CURLFORM_FILECONTENT: - if(current_form->flags & (HTTPPOST_PTRCONTENTS|HTTPPOST_READFILE)) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->flags & (HTTPPOST_PTRCONTENTS|HTTPPOST_READFILE)) + retval = CURL_FORMADD_OPTION_TWICE; else { - const char *filename = array_state? - array_value:va_arg(params, char *); - if(filename) { - current_form->value = strdup(filename); - if(!current_form->value) - return_value = CURL_FORMADD_MEMORY; + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) { + curr->value = strdup(avalue); + if(!curr->value) + retval = CURL_FORMADD_MEMORY; else { - current_form->flags |= HTTPPOST_READFILE; - current_form->value_alloc = TRUE; + curr->flags |= HTTPPOST_READFILE; + curr->value_alloc = TRUE; } } else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; /* We upload a file */ case CURLFORM_FILE: - { - const char *filename = array_state?array_value: - va_arg(params, char *); - - if(current_form->value) { - if(current_form->flags & HTTPPOST_FILENAME) { - if(filename) { - char *fname = strdup(filename); - if(!fname) - return_value = CURL_FORMADD_MEMORY; + if(!array_state) + avalue = va_arg(params, char *); + + if(curr->value) { + if(curr->flags & HTTPPOST_FILENAME) { + if(avalue) { + char *fname = strdup(avalue); + if(!fname) + retval = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(fname, NULL, curr); + if(!form) { + free(fname); + retval = CURL_FORMADD_MEMORY; + } else { - form = AddFormInfo(fname, NULL, current_form); - if(!form) { - free(fname); - return_value = CURL_FORMADD_MEMORY; - } - else { - form->value_alloc = TRUE; - current_form = form; - form = NULL; - } + form->value_alloc = TRUE; + curr = form; + form = NULL; } } - else - return_value = CURL_FORMADD_NULL; } else - return_value = CURL_FORMADD_OPTION_TWICE; + retval = CURL_FORMADD_NULL; } - else { - if(filename) { - current_form->value = strdup(filename); - if(!current_form->value) - return_value = CURL_FORMADD_MEMORY; - else { - current_form->flags |= HTTPPOST_FILENAME; - current_form->value_alloc = TRUE; - } + else + retval = CURL_FORMADD_OPTION_TWICE; + } + else { + if(avalue) { + curr->value = strdup(avalue); + if(!curr->value) + retval = CURL_FORMADD_MEMORY; + else { + curr->flags |= HTTPPOST_FILENAME; + curr->value_alloc = TRUE; } - else - return_value = CURL_FORMADD_NULL; } - break; + else + retval = CURL_FORMADD_NULL; } + break; case CURLFORM_BUFFERPTR: - current_form->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER; - if(current_form->buffer) - return_value = CURL_FORMADD_OPTION_TWICE; + curr->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER; + if(curr->buffer) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *buffer = - array_state?array_value:va_arg(params, char *); - if(buffer) { - current_form->buffer = buffer; /* store for the moment */ - current_form->value = buffer; /* make it non-NULL to be accepted + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) { + curr->buffer = avalue; /* store for the moment */ + curr->value = avalue; /* make it non-NULL to be accepted as fine */ } else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_BUFFERLENGTH: - if(current_form->bufferlength) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->bufferlength) + retval = CURL_FORMADD_OPTION_TWICE; else - current_form->bufferlength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + curr->bufferlength = + array_state ? (size_t)avalue : (size_t)va_arg(params, long); break; case CURLFORM_STREAM: - current_form->flags |= HTTPPOST_CALLBACK; - if(current_form->userp) - return_value = CURL_FORMADD_OPTION_TWICE; + curr->flags |= HTTPPOST_CALLBACK; + if(curr->userp) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *userp = - array_state?array_value:va_arg(params, char *); - if(userp) { - current_form->userp = userp; - current_form->value = userp; /* this isn't strictly true but we - derive a value from this later on - and we need this non-NULL to be - accepted as a fine form part */ + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) { + curr->userp = avalue; + curr->value = avalue; /* this is not strictly true but we derive a + value from this later on and we need this + non-NULL to be accepted as a fine form + part */ } else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_CONTENTTYPE: - { - const char *contenttype = - array_state?array_value:va_arg(params, char *); - if(current_form->contenttype) { - if(current_form->flags & HTTPPOST_FILENAME) { - if(contenttype) { - char *type = strdup(contenttype); - if(!type) - return_value = CURL_FORMADD_MEMORY; + if(!array_state) + avalue = va_arg(params, char *); + if(curr->contenttype) { + if(curr->flags & HTTPPOST_FILENAME) { + if(avalue) { + char *type = strdup(avalue); + if(!type) + retval = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(NULL, type, curr); + if(!form) { + free(type); + retval = CURL_FORMADD_MEMORY; + } else { - form = AddFormInfo(NULL, type, current_form); - if(!form) { - free(type); - return_value = CURL_FORMADD_MEMORY; - } - else { - form->contenttype_alloc = TRUE; - current_form = form; - form = NULL; - } + form->contenttype_alloc = TRUE; + curr = form; + form = NULL; } } - else - return_value = CURL_FORMADD_NULL; } else - return_value = CURL_FORMADD_OPTION_TWICE; + retval = CURL_FORMADD_NULL; } - else { - if(contenttype) { - current_form->contenttype = strdup(contenttype); - if(!current_form->contenttype) - return_value = CURL_FORMADD_MEMORY; - else - current_form->contenttype_alloc = TRUE; - } + else + retval = CURL_FORMADD_OPTION_TWICE; + } + else { + if(avalue) { + curr->contenttype = strdup(avalue); + if(!curr->contenttype) + retval = CURL_FORMADD_MEMORY; else - return_value = CURL_FORMADD_NULL; + curr->contenttype_alloc = TRUE; } - break; + else + retval = CURL_FORMADD_NULL; } + break; + case CURLFORM_CONTENTHEADER: { /* this "cast increases required alignment of target type" but we consider it OK anyway */ - struct curl_slist *list = array_state? - (struct curl_slist *)(void *)array_value: + struct curl_slist *list = array_state ? + (struct curl_slist *)(void *)avalue : va_arg(params, struct curl_slist *); - if(current_form->contentheader) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->contentheader) + retval = CURL_FORMADD_OPTION_TWICE; else - current_form->contentheader = list; + curr->contentheader = list; break; } case CURLFORM_FILENAME: case CURLFORM_BUFFER: - { - const char *filename = array_state?array_value: - va_arg(params, char *); - if(current_form->showfilename) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - current_form->showfilename = strdup(filename); - if(!current_form->showfilename) - return_value = CURL_FORMADD_MEMORY; - else - current_form->showfilename_alloc = TRUE; - } - break; + if(!array_state) + avalue = va_arg(params, char *); + if(curr->showfilename) + retval = CURL_FORMADD_OPTION_TWICE; + else { + curr->showfilename = strdup(avalue); + if(!curr->showfilename) + retval = CURL_FORMADD_MEMORY; + else + curr->showfilename_alloc = TRUE; } + break; + default: - return_value = CURL_FORMADD_UNKNOWN_OPTION; + retval = CURL_FORMADD_UNKNOWN_OPTION; break; } } - if(CURL_FORMADD_OK != return_value) { + if(!retval) + retval = FormAddCheck(first_form, &newchain, &lastnode); + + if(retval) /* On error, free allocated fields for all nodes of the FormInfo linked list without deallocating nodes. List nodes are deallocated later on */ - struct FormInfo *ptr; - for(ptr = first_form; ptr != NULL; ptr = ptr->more) { - if(ptr->name_alloc) { - Curl_safefree(ptr->name); - ptr->name_alloc = FALSE; - } - if(ptr->value_alloc) { - Curl_safefree(ptr->value); - ptr->value_alloc = FALSE; - } - if(ptr->contenttype_alloc) { - Curl_safefree(ptr->contenttype); - ptr->contenttype_alloc = FALSE; - } - if(ptr->showfilename_alloc) { - Curl_safefree(ptr->showfilename); - ptr->showfilename_alloc = FALSE; - } - } - } - - if(CURL_FORMADD_OK == return_value) { - /* go through the list, check for completeness and if everything is - * alright add the HttpPost item otherwise set return_value accordingly */ - - post = NULL; - for(form = first_form; - form != NULL; - form = form->more) { - if(((!form->name || !form->value) && !post) || - ( (form->contentslength) && - (form->flags & HTTPPOST_FILENAME) ) || - ( (form->flags & HTTPPOST_FILENAME) && - (form->flags & HTTPPOST_PTRCONTENTS) ) || - - ( (!form->buffer) && - (form->flags & HTTPPOST_BUFFER) && - (form->flags & HTTPPOST_PTRBUFFER) ) || - - ( (form->flags & HTTPPOST_READFILE) && - (form->flags & HTTPPOST_PTRCONTENTS) ) - ) { - return_value = CURL_FORMADD_INCOMPLETE; - break; - } - if(((form->flags & HTTPPOST_FILENAME) || - (form->flags & HTTPPOST_BUFFER)) && - !form->contenttype) { - char *f = (form->flags & HTTPPOST_BUFFER)? - form->showfilename : form->value; - char const *type; - type = Curl_mime_contenttype(f); - if(!type) - type = prevtype; - if(!type) - type = FILE_CONTENTTYPE_DEFAULT; - - /* our contenttype is missing */ - form->contenttype = strdup(type); - if(!form->contenttype) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->contenttype_alloc = TRUE; - } - if(form->name && form->namelength) { - /* Name should not contain nul bytes. */ - size_t i; - for(i = 0; i < form->namelength; i++) - if(!form->name[i]) { - return_value = CURL_FORMADD_NULL; - break; - } - if(return_value != CURL_FORMADD_OK) - break; - } - if(!(form->flags & HTTPPOST_PTRNAME) && - (form == first_form) ) { - /* Note that there's small risk that form->name is NULL here if the - app passed in a bad combo, so we better check for that first. */ - if(form->name) { - /* copy name (without strdup; possibly not null-terminated) */ - form->name = Curl_memdup(form->name, form->namelength? - form->namelength: - strlen(form->name) + 1); - } - if(!form->name) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->name_alloc = TRUE; - } - if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | - HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | - HTTPPOST_CALLBACK)) && form->value) { - /* copy value (without strdup; possibly contains null characters) */ - size_t clen = (size_t) form->contentslength; - if(!clen) - clen = strlen(form->value) + 1; - - form->value = Curl_memdup(form->value, clen); - - if(!form->value) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->value_alloc = TRUE; - } - post = AddHttpPost(form->name, form->namelength, - form->value, form->contentslength, - form->buffer, form->bufferlength, - form->contenttype, form->flags, - form->contentheader, form->showfilename, - form->userp, - post, httppost, - last_post); - - if(!post) { - return_value = CURL_FORMADD_MEMORY; - break; - } - - if(form->contenttype) - prevtype = form->contenttype; - } - if(CURL_FORMADD_OK != return_value) { - /* On error, free allocated fields for nodes of the FormInfo linked - list which are not already owned by the httppost linked list - without deallocating nodes. List nodes are deallocated later on */ - struct FormInfo *ptr; - for(ptr = form; ptr != NULL; ptr = ptr->more) { - if(ptr->name_alloc) { - Curl_safefree(ptr->name); - ptr->name_alloc = FALSE; - } - if(ptr->value_alloc) { - Curl_safefree(ptr->value); - ptr->value_alloc = FALSE; - } - if(ptr->contenttype_alloc) { - Curl_safefree(ptr->contenttype); - ptr->contenttype_alloc = FALSE; - } - if(ptr->showfilename_alloc) { - Curl_safefree(ptr->showfilename); - ptr->showfilename_alloc = FALSE; - } - } - } - } + free_formlist(first_form); /* Always deallocate FormInfo linked list nodes without touching node fields given that these have either been deallocated or are owned @@ -681,7 +652,19 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, first_form = ptr; } - return return_value; + if(!retval) { + /* Only if all is fine, link the new chain into the provided list */ + if(*last_post) + (*last_post)->next = newchain; + else + (*httppost) = newchain; + + (*last_post) = lastnode; + } + else + free_chain(newchain); + + return retval; } /* @@ -764,7 +747,7 @@ void curl_formfree(struct curl_httppost *form) ) free(form->contents); /* free the contents */ free(form->contenttype); /* free the content type */ - free(form->showfilename); /* free the faked file name */ + free(form->showfilename); /* free the faked filename */ free(form); /* free the struct */ form = next; } while(form); /* continue */ @@ -779,16 +762,28 @@ static CURLcode setname(curl_mimepart *part, const char *name, size_t len) if(!name || !len) return curl_mime_name(part, name); - zname = malloc(len + 1); + zname = Curl_memdup0(name, len); if(!zname) return CURLE_OUT_OF_MEMORY; - memcpy(zname, name, len); - zname[len] = '\0'; res = curl_mime_name(part, zname); free(zname); return res; } +/* wrap call to fseeko so it matches the calling convention of callback */ +static int fseeko_wrapper(void *stream, curl_off_t offset, int whence) +{ +#if defined(_WIN32) && defined(USE_WIN32_LARGE_FILES) + return _fseeki64(stream, (__int64)offset, whence); +#elif defined(HAVE_FSEEKO) && defined(HAVE_DECL_FSEEKO) + return fseeko(stream, (off_t)offset, whence); +#else + if(offset > LONG_MAX) + return -1; + return fseek(stream, (long)offset, whence); +#endif +} + /* * Curl_getformdata() converts a linked list of "meta data" into a mime * structure. The input list is in 'post', while the output is stored in @@ -799,7 +794,7 @@ static CURLcode setname(curl_mimepart *part, const char *name, size_t len) * a NULL pointer in the 'data' argument. */ -CURLcode Curl_getformdata(struct Curl_easy *data, +CURLcode Curl_getformdata(CURL *data, curl_mimepart *finalform, struct curl_httppost *post, curl_read_callback fread_func) @@ -868,14 +863,13 @@ CURLcode Curl_getformdata(struct Curl_easy *data, if(post->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE)) { if(!strcmp(file->contents, "-")) { - /* There are a few cases where the code below won't work; in + /* There are a few cases where the code below will not work; in particular, freopen(stdin) by the caller is not guaranteed to result as expected. This feature has been kept for backward - compatibility: use of "-" pseudo file name should be avoided. */ + compatibility: use of "-" pseudo filename should be avoided. */ result = curl_mime_data_cb(part, (curl_off_t) -1, (curl_read_callback) fread, - CURLX_FUNCTION_CAST(curl_seek_callback, - fseek), + fseeko_wrapper, NULL, (void *) stdin); } else @@ -885,7 +879,8 @@ CURLcode Curl_getformdata(struct Curl_easy *data, } else if(post->flags & HTTPPOST_BUFFER) result = curl_mime_data(part, post->buffer, - post->bufferlength? post->bufferlength: -1); + post->bufferlength ? + post->bufferlength : -1); else if(post->flags & HTTPPOST_CALLBACK) { /* the contents should be read with the callback and the size is set with the contentslength */ @@ -904,7 +899,7 @@ CURLcode Curl_getformdata(struct Curl_easy *data, } } - /* Set fake file name. */ + /* Set fake filename. */ if(!result && post->showfilename) if(post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | HTTPPOST_CALLBACK))) @@ -941,7 +936,7 @@ int curl_formget(struct curl_httppost *form, void *arg, void curl_formfree(struct curl_httppost *form) { (void)form; - /* does nothing HTTP is disabled */ + /* Nothing to do. */ } #endif /* if disabled */ diff --git a/Utilities/cmcurl/lib/formdata.h b/Utilities/cmcurl/lib/formdata.h index caabb6324c1..74f00bf4fcb 100644 --- a/Utilities/cmcurl/lib/formdata.h +++ b/Utilities/cmcurl/lib/formdata.h @@ -26,7 +26,7 @@ #include "curl_setup.h" -#ifndef CURL_DISABLE_MIME +#ifndef CURL_DISABLE_FORM_API /* used by FormAdd for temporary storage */ struct FormInfo { @@ -35,28 +35,25 @@ struct FormInfo { char *value; curl_off_t contentslength; char *contenttype; - long flags; char *buffer; /* pointer to existing buffer used for file upload */ size_t bufferlength; - char *showfilename; /* The file name to show. If not set, the actual - file name will be used */ + char *showfilename; /* The filename to show. If not set, the actual + filename will be used */ char *userp; /* pointer for the read callback */ struct curl_slist *contentheader; struct FormInfo *more; - bool name_alloc; - bool value_alloc; - bool contenttype_alloc; - bool showfilename_alloc; + unsigned char flags; + BIT(name_alloc); + BIT(value_alloc); + BIT(contenttype_alloc); + BIT(showfilename_alloc); }; -CURLcode Curl_getformdata(struct Curl_easy *data, +CURLcode Curl_getformdata(CURL *data, curl_mimepart *, struct curl_httppost *post, curl_read_callback fread_func); -#else -/* disabled */ -#define Curl_getformdata(a,b,c,d) CURLE_NOT_BUILT_IN -#endif +#endif /* CURL_DISABLE_FORM_API */ #endif /* HEADER_CURL_FORMDATA_H */ diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c index 4f50cb4113c..2db4a7d5670 100644 --- a/Utilities/cmcurl/lib/ftp.c +++ b/Utilities/cmcurl/lib/ftp.c @@ -32,9 +32,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -57,7 +54,6 @@ #include "ftplistparser.h" #include "curl_range.h" #include "curl_krb5.h" -#include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -65,16 +61,18 @@ #include "connect.h" #include "strerror.h" #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "select.h" #include "parsedate.h" /* for the week day and month names */ #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "multiif.h" #include "url.h" #include "speedcheck.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "http_proxy.h" #include "socks.h" +#include "strdup.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -87,43 +85,124 @@ #define INET_ADDRSTRLEN 16 #endif +/* macro to check for a three-digit ftp status code at the start of the + given string */ +#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ + ISDIGIT(line[2])) + +/* macro to check for the last line in an FTP server response */ +#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) + #ifdef CURL_DISABLE_VERBOSE_STRINGS #define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt +#define FTP_CSTATE(c) ((void)(c), "") +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + /* for tracing purposes */ +static const char * const ftp_state_names[]={ + "STOP", + "WAIT220", + "AUTH", + "USER", + "PASS", + "ACCT", + "PBSZ", + "PROT", + "CCC", + "PWD", + "SYST", + "NAMEFMT", + "QUOTE", + "RETR_PREQUOTE", + "STOR_PREQUOTE", + "POSTQUOTE", + "CWD", + "MKD", + "MDTM", + "TYPE", + "LIST_TYPE", + "RETR_TYPE", + "STOR_TYPE", + "SIZE", + "RETR_SIZE", + "STOR_SIZE", + "REST", + "RETR_REST", + "PORT", + "PRET", + "PASV", + "LIST", + "RETR", + "STOR", + "QUIT" +}; +#define FTP_CSTATE(ftpc) ((ftpc)? ftp_state_names[(ftpc)->state] : "???") + +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +/* This is the ONLY way to change FTP state! */ +static void _ftp_state(struct Curl_easy *data, + struct ftp_conn *ftpc, + ftpstate newstate +#ifdef DEBUGBUILD + , int lineno #endif + ) +{ +#if defined(CURL_DISABLE_VERBOSE_STRINGS) +#ifdef DEBUGBUILD + (void)lineno; +#endif +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + if(ftpc->state != newstate) +#ifdef DEBUGBUILD + CURL_TRC_FTP(data, "[%s] -> [%s] (line %d)", FTP_CSTATE(ftpc), + ftp_state_names[newstate], lineno); +#else + CURL_TRC_FTP(data, "[%s] -> [%s]", FTP_CSTATE(ftpc), + ftp_state_names[newstate]); +#endif +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + + ftpc->state = newstate; +} + /* Local API functions */ #ifndef DEBUGBUILD -static void _state(struct Curl_easy *data, - ftpstate newstate); -#define state(x,y) _state(x,y) -#else -static void _state(struct Curl_easy *data, - ftpstate newstate, - int lineno); -#define state(x,y) _state(x,y,__LINE__) -#endif +#define ftp_state(x,y,z) _ftp_state(x,y,z) +#else /* !DEBUGBUILD */ +#define ftp_state(x,y,z) _ftp_state(x,y,z,__LINE__) +#endif /* DEBUGBUILD */ static CURLcode ftp_sendquote(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, struct curl_slist *quote); -static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn); -static CURLcode ftp_parse_url_path(struct Curl_easy *data); -static CURLcode ftp_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode ftp_quit(struct Curl_easy *data, struct ftp_conn *ftpc); +static CURLcode ftp_parse_url_path(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); +static CURLcode ftp_regular_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + bool *done); #ifndef CURL_DISABLE_VERBOSE_STRINGS static void ftp_pasv_verbose(struct Curl_easy *data, struct Curl_addrinfo *ai, - char *newhost, /* ascii version */ + char *newhost, /* ASCII version */ int port); #endif -static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data); -static CURLcode ftp_state_mdtm(struct Curl_easy *data); +static CURLcode ftp_state_mdtm(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); static CURLcode ftp_state_quote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool init, ftpstate instate); static CURLcode ftp_nb_type(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, + struct FTP *ftp, bool ascii, ftpstate newstate); -static int ftp_need_type(struct connectdata *conn, - bool ascii); +static int ftp_need_type(struct ftp_conn *ftpc, bool ascii); static CURLcode ftp_do(struct Curl_easy *data, bool *done); static CURLcode ftp_done(struct Curl_easy *data, CURLcode, bool premature); @@ -140,16 +219,26 @@ static CURLcode ftp_doing(struct Curl_easy *data, bool *dophase_done); static CURLcode ftp_setup_connection(struct Curl_easy *data, struct connectdata *conn); -static CURLcode init_wc_data(struct Curl_easy *data); -static CURLcode wc_statemach(struct Curl_easy *data); +static CURLcode init_wc_data(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); +static CURLcode wc_statemach(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); static void wc_data_dtor(void *ptr); -static CURLcode ftp_state_retr(struct Curl_easy *data, curl_off_t filesize); +static CURLcode ftp_state_retr(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + curl_off_t filesize); static CURLcode ftp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + struct ftp_conn *ftpc, + int sockindex, struct pingpong *pp, int *ftpcode, size_t *size); static CURLcode ftp_dophase_done(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool connected); /* @@ -157,7 +246,7 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_ftp = { - "FTP", /* scheme */ + "ftp", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -170,9 +259,11 @@ const struct Curl_handler Curl_handler_ftp = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_FTP, /* defport */ CURLPROTO_FTP, /* protocol */ CURLPROTO_FTP, /* family */ @@ -188,7 +279,7 @@ const struct Curl_handler Curl_handler_ftp = { */ const struct Curl_handler Curl_handler_ftps = { - "FTPS", /* scheme */ + "ftps", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -201,9 +292,11 @@ const struct Curl_handler Curl_handler_ftps = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_FTPS, /* defport */ CURLPROTO_FTPS, /* protocol */ CURLPROTO_FTP, /* family */ @@ -213,10 +306,12 @@ const struct Curl_handler Curl_handler_ftps = { #endif static void close_secondarysocket(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc) { + (void)ftpc; + CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_CSTATE(ftpc)); Curl_conn_close(data, SECONDARYSOCKET); - Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); + Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET); } /* @@ -224,10 +319,10 @@ static void close_secondarysocket(struct Curl_easy *data, * requests on files respond with headers passed to the client/stdout that * looked like HTTP ones. * - * This approach is not very elegant, it causes confusion and is error-prone. - * It is subject for removal at the next (or at least a future) soname bump. - * Until then you can test the effects of the removal by undefining the - * following define named CURL_FTP_HTTPSTYLE_HEAD. + * This approach is not elegant, it causes confusion and is error-prone. It is + * subject for removal at the next (or at least a future) soname bump. Until + * then you can test the effects of the removal by undefining the following + * define named CURL_FTP_HTTPSTYLE_HEAD. */ #define CURL_FTP_HTTPSTYLE_HEAD 1 @@ -249,295 +344,228 @@ static void freedirs(struct ftp_conn *ftpc) Curl_safefree(ftpc->newhost); } -/*********************************************************************** - * - * AcceptServerConnect() - * - * After connection request is received from the server this function is - * called to accept the connection and close the listening socket - * +#ifdef CURL_PREFER_LF_LINEENDS +/* + * Lineend Conversions + * On ASCII transfers, e.g. directory listings, we might get lines + * ending in '\r\n' and we prefer just '\n'. + * We might also get a lonely '\r' which we convert into a '\n'. */ -static CURLcode AcceptServerConnect(struct Curl_easy *data) +struct ftp_cw_lc_ctx { + struct Curl_cwriter super; + bool newline_pending; +}; + +static CURLcode ftp_cw_lc_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) { - struct connectdata *conn = data->conn; - curl_socket_t sock = conn->sock[SECONDARYSOCKET]; - curl_socket_t s = CURL_SOCKET_BAD; -#ifdef ENABLE_IPV6 - struct Curl_sockaddr_storage add; -#else - struct sockaddr_in add; -#endif - curl_socklen_t size = (curl_socklen_t) sizeof(add); - CURLcode result; + static const char nl = '\n'; + struct ftp_cw_lc_ctx *ctx = writer->ctx; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + + if(!ftpc) + return CURLE_FAILED_INIT; + + if(!(type & CLIENTWRITE_BODY) || ftpc->transfertype != 'A') + return Curl_cwriter_write(data, writer->next, type, buf, blen); + + /* ASCII mode BODY data, convert lineends */ + while(blen) { + /* do not pass EOS when writing parts */ + int chunk_type = (type & ~CLIENTWRITE_EOS); + const char *cp; + size_t chunk_len; + CURLcode result; + + if(ctx->newline_pending) { + if(buf[0] != '\n') { + /* previous chunk ended in '\r' and we do not see a '\n' in this one, + * need to write a newline. */ + result = Curl_cwriter_write(data, writer->next, chunk_type, &nl, 1); + if(result) + return result; + } + /* either we just wrote the newline or it is part of the next + * chunk of bytes we write. */ + ctx->newline_pending = FALSE; + } - if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { - size = sizeof(add); + cp = memchr(buf, '\r', blen); + if(!cp) + break; - s = accept(sock, (struct sockaddr *) &add, &size); + /* write the bytes before the '\r', excluding the '\r' */ + chunk_len = cp - buf; + if(chunk_len) { + result = Curl_cwriter_write(data, writer->next, chunk_type, + buf, chunk_len); + if(result) + return result; + } + /* skip the '\r', we now have a newline pending */ + buf = cp + 1; + blen = blen - chunk_len - 1; + ctx->newline_pending = TRUE; } - if(CURL_SOCKET_BAD == s) { - failf(data, "Error accept()ing server connect"); - return CURLE_FTP_PORT_FAILED; + /* Any remaining data does not contain a '\r' */ + if(blen) { + DEBUGASSERT(!ctx->newline_pending); + return Curl_cwriter_write(data, writer->next, type, buf, blen); } - infof(data, "Connection accepted from server"); - /* when this happens within the DO state it is important that we mark us as - not needing DO_MORE anymore */ - conn->bits.do_more = FALSE; - - (void)curlx_nonblock(s, TRUE); /* enable non-blocking */ - /* Replace any filter on SECONDARY with one listening on this socket */ - result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s); - if(result) - return result; - - if(data->set.fsockopt) { - int error = 0; - - /* activate callback for setting socket options */ - Curl_set_in_callback(data, true); - error = data->set.fsockopt(data->set.sockopt_client, - s, - CURLSOCKTYPE_ACCEPT); - Curl_set_in_callback(data, false); - - if(error) { - close_secondarysocket(data, conn); - return CURLE_ABORTED_BY_CALLBACK; + else if(type & CLIENTWRITE_EOS) { + /* EndOfStream, if we have a trailing cr, now is the time to write it */ + if(ctx->newline_pending) { + ctx->newline_pending = FALSE; + return Curl_cwriter_write(data, writer->next, type, &nl, 1); } + /* Always pass on the EOS type indicator */ + return Curl_cwriter_write(data, writer->next, type, buf, 0); } - return CURLE_OK; - -} - -/* - * ftp_timeleft_accept() returns the amount of milliseconds left allowed for - * waiting server to connect. If the value is negative, the timeout time has - * already elapsed. - * - * The start time is stored in progress.t_acceptdata - as set with - * Curl_pgrsTime(..., TIMER_STARTACCEPT); - * - */ -static timediff_t ftp_timeleft_accept(struct Curl_easy *data) -{ - timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT; - timediff_t other; - struct curltime now; - - if(data->set.accepttimeout > 0) - timeout_ms = data->set.accepttimeout; - - now = Curl_now(); - - /* check if the generic timeout possibly is set shorter */ - other = Curl_timeleft(data, &now, FALSE); - if(other && (other < timeout_ms)) - /* note that this also works fine for when other happens to be negative - due to it already having elapsed */ - timeout_ms = other; - else { - /* subtract elapsed time */ - timeout_ms -= Curl_timediff(now, data->progress.t_acceptdata); - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; - } - - return timeout_ms; } +static const struct Curl_cwtype ftp_cw_lc = { + "ftp-lineconv", + NULL, + Curl_cwriter_def_init, + ftp_cw_lc_write, + Curl_cwriter_def_close, + sizeof(struct ftp_cw_lc_ctx) +}; +#endif /* CURL_PREFER_LF_LINEENDS */ /*********************************************************************** * - * ReceivedServerConnect() - * - * After allowing server to connect to us from data port, this function - * checks both data connection for connection establishment and ctrl - * connection for a negative response regarding a failure in connecting + * ftp_check_ctrl_on_data_wait() * */ -static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) +static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data, + struct ftp_conn *ftpc) { struct connectdata *conn = data->conn; curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; - curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; - struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; - int result; - timediff_t timeout_ms; ssize_t nread; int ftpcode; - - *received = FALSE; - - timeout_ms = ftp_timeleft_accept(data); - infof(data, "Checking for server connect"); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - return CURLE_FTP_ACCEPT_TIMEOUT; - } + bool response = FALSE; /* First check whether there is a cached response from server */ - if(pp->cache_size && pp->cache && pp->cache[0] > '3') { + if(curlx_dyn_len(&pp->recvbuf) && (*curlx_dyn_ptr(&pp->recvbuf) > '3')) { /* Data connection could not be established, let's return */ infof(data, "There is negative response in cache while serv connect"); (void)Curl_GetFTPResponse(data, &nread, &ftpcode); return CURLE_FTP_ACCEPT_FAILED; } - result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); - - /* see if the connection request is already here */ - switch(result) { - case -1: /* error */ - /* let's die here */ - failf(data, "Error while waiting for server connect"); - return CURLE_FTP_ACCEPT_FAILED; - case 0: /* Server connect is not received yet */ - break; /* loop */ - default: + if(pp->overflow) + /* there is pending control data still in the buffer to read */ + response = TRUE; + else { + int socketstate = Curl_socket_check(ctrl_sock, CURL_SOCKET_BAD, + CURL_SOCKET_BAD, 0); + /* see if the connection request is already here */ + switch(socketstate) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + default: + if(socketstate & CURL_CSELECT_IN) + response = TRUE; + break; + } + } - if(result & CURL_CSELECT_IN2) { - infof(data, "Ready to accept data connection from server"); - *received = TRUE; + if(response) { + infof(data, "Ctrl conn has data while waiting for data conn"); + if(pp->overflow > 3) { + const char *r = curlx_dyn_ptr(&pp->recvbuf); + + DEBUGASSERT((pp->overflow + pp->nfinal) <= + curlx_dyn_len(&pp->recvbuf)); + /* move over the most recently handled response line */ + r += pp->nfinal; + + if(LASTLINE(r)) { + curl_off_t status; + if(!curlx_str_number(&r, &status, 999) && (status == 226)) { + /* funny timing situation where we get the final message on the + control connection before traffic on the data connection has been + noticed. Leave the 226 in there and use this as a trigger to read + the data socket. */ + infof(data, "Got 226 before data activity"); + return CURLE_OK; + } + } } - else if(result & CURL_CSELECT_IN) { - infof(data, "Ctrl conn has data while waiting for data conn"); - (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - if(ftpcode/100 > 3) - return CURLE_FTP_ACCEPT_FAILED; + (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - return CURLE_WEIRD_SERVER_REPLY; - } + infof(data, "FTP code: %03d", ftpcode); - break; - } /* switch() */ + if(ftpcode/100 > 3) + return CURLE_FTP_ACCEPT_FAILED; + + return CURLE_WEIRD_SERVER_REPLY; + } return CURLE_OK; } - /*********************************************************************** * - * InitiateTransfer() + * ftp_initiate_transfer() * * After connection from server is accepted this function is called to * setup transfer parameters and initiate the data transfer. * */ -static CURLcode InitiateTransfer(struct Curl_easy *data) +static CURLcode ftp_initiate_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; bool connected; - DEBUGF(infof(data, "ftp InitiateTransfer()")); - if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && - !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); - if(result) - return result; - } + CURL_TRC_FTP(data, "ftp_initiate_transfer()"); result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &connected); if(result || !connected) return result; - if(conn->proto.ftpc.state_saved == FTP_STOR) { - /* When we know we're uploading a specified file, we can get the file + if(ftpc->state_saved == FTP_STOR) { + /* When we know we are uploading a specified file, we can get the file size prior to the actual upload. */ Curl_pgrsSetUploadSize(data, data->state.infilesize); /* set the SO_SNDBUF for the secondary socket for those who need it */ - Curl_sndbufset(conn->sock[SECONDARYSOCKET]); + Curl_sndbuf_init(data->conn->sock[SECONDARYSOCKET]); - Curl_setup_transfer(data, -1, -1, FALSE, SECONDARYSOCKET); + /* FTP upload, shutdown DATA, ignore shutdown errors, as we rely + * on the server response on the CONTROL connection. */ + Curl_xfer_setup2(data, CURL_XFER_SEND, -1, TRUE, TRUE); } else { - /* FTP download: */ - Curl_setup_transfer(data, SECONDARYSOCKET, - conn->proto.ftpc.retr_size_saved, FALSE, -1); + /* FTP download, shutdown, do not ignore errors */ + Curl_xfer_setup2(data, CURL_XFER_RECV, + ftpc->retr_size_saved, TRUE, FALSE); } - conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ - state(data, FTP_STOP); + ftpc->pp.pending_resp = TRUE; /* expect server response */ + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } -/*********************************************************************** - * - * AllowServerConnect() - * - * When we've issue the PORT command, we have told the server to connect to - * us. This function checks whether data connection is established if so it is - * accepted. - * - */ -static CURLcode AllowServerConnect(struct Curl_easy *data, bool *connected) -{ - timediff_t timeout_ms; - CURLcode result = CURLE_OK; - - *connected = FALSE; - infof(data, "Preparing for accepting server on data port"); - - /* Save the time we start accepting server connect */ - Curl_pgrsTime(data, TIMER_STARTACCEPT); - - timeout_ms = ftp_timeleft_accept(data); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - result = CURLE_FTP_ACCEPT_TIMEOUT; - goto out; - } - - /* see if the connection request is already here */ - result = ReceivedServerConnect(data, connected); - if(result) - goto out; - - if(*connected) { - result = AcceptServerConnect(data); - if(result) - goto out; - - result = InitiateTransfer(data); - if(result) - goto out; - } - else { - /* Add timeout to multi handle and break out of the loop */ - Curl_expire(data, data->set.accepttimeout ? - data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, - EXPIRE_FTP_ACCEPT); - } - -out: - DEBUGF(infof(data, "ftp AllowServerConnect() -> %d", result)); - return result; -} - -/* macro to check for a three-digit ftp status code at the start of the - given string */ -#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ - ISDIGIT(line[2])) - -/* macro to check for the last line in an FTP server response */ -#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) - static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *code) + const char *line, size_t len, int *code) { + curl_off_t status; (void)data; (void)conn; - if((len > 3) && LASTLINE(line)) { - *code = curlx_sltosi(strtol(line, NULL, 10)); + if((len > 3) && LASTLINE(line) && !curlx_str_number(&line, &status, 999)) { + *code = (int)status; return TRUE; } @@ -545,18 +573,19 @@ static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, } static CURLcode ftp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + struct ftp_conn *ftpc, + int sockindex, struct pingpong *pp, int *ftpcode, /* return the ftp-code if done */ size_t *size) /* size of the response */ { int code; - CURLcode result = Curl_pp_readresp(data, sockfd, pp, &code, size); + CURLcode result = Curl_pp_readresp(data, sockindex, pp, &code, size); #ifdef HAVE_GSSAPI { struct connectdata *conn = data->conn; - char * const buf = data->state.buffer; + char * const buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); /* handle the security-oriented responses 6xx ***/ switch(code) { @@ -576,8 +605,9 @@ static CURLcode ftp_readresp(struct Curl_easy *data, } #endif - /* store the latest code for later retrieval */ - data->info.httpcode = code; + /* store the latest code for later retrieval, except during shutdown */ + if(!ftpc->shutdown) + data->info.httpcode = code; if(ftpcode) *ftpcode = code; @@ -591,7 +621,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data, * generically is a good idea. */ infof(data, "We got a 421 - timeout"); - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OPERATION_TIMEDOUT; } @@ -612,7 +642,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, { /* * We cannot read just one byte per read() and then go back to select() as - * the OpenSSL read() doesn't grok that properly. + * the OpenSSL read() does not grok that properly. * * Alas, read as much as possible, split up into lines, use the ending * line in a response or continue reading. */ @@ -620,19 +650,22 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, struct connectdata *conn = data->conn; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); struct pingpong *pp = &ftpc->pp; size_t nread; int cache_skip = 0; int value_to_be_ignored = 0; + CURL_TRC_FTP(data, "getFTPResponse start"); + *nreadp = 0; if(ftpcode) *ftpcode = 0; /* 0 for errors */ else /* make the pointer point to something for the rest of this function */ ftpcode = &value_to_be_ignored; - *nreadp = 0; + if(!ftpc) + return CURLE_FAILED_INIT; while(!*ftpcode && !result) { /* check and reset timeout value every lap */ @@ -655,42 +688,48 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, * * A caution here is that the ftp_readresp() function has a cache that may * contain pieces of a response from the previous invoke and we need to - * make sure we don't just wait for input while there is unhandled data in + * make sure we do not just wait for input while there is unhandled data in * that cache. But also, if the cache is there, we call ftp_readresp() and - * the cache wasn't good enough to continue we must not just busy-loop + * the cache was not good enough to continue we must not just busy-loop * around this function. * */ - if(pp->cache && (cache_skip < 2)) { + if(curlx_dyn_len(&pp->recvbuf) && (cache_skip < 2)) { /* - * There's a cache left since before. We then skipping the wait for + * There is a cache left since before. We then skipping the wait for * socket action, unless this is the same cache like the previous round * as then the cache was deemed not enough to act on and we then need to * wait for more data anyway. */ } else if(!Curl_conn_data_pending(data, FIRSTSOCKET)) { - switch(SOCKET_READABLE(sockfd, interval_ms)) { - case -1: /* select() error, stop reading */ + curl_socket_t wsock = Curl_pp_needs_flush(data, pp) ? + sockfd : CURL_SOCKET_BAD; + int ev = Curl_socket_check(sockfd, CURL_SOCKET_BAD, wsock, interval_ms); + if(ev < 0) { failf(data, "FTP response aborted due to select/poll error: %d", SOCKERRNO); return CURLE_RECV_ERROR; - - case 0: /* timeout */ + } + else if(ev == 0) { if(Curl_pgrsUpdate(data)) return CURLE_ABORTED_BY_CALLBACK; continue; /* just continue in our loop for the timeout duration */ + } + } - default: /* for clarity */ + if(Curl_pp_needs_flush(data, pp)) { + result = Curl_pp_flushsend(data, pp); + if(result) break; - } } - result = ftp_readresp(data, sockfd, pp, ftpcode, &nread); + + result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, ftpcode, &nread); if(result) break; - if(!nread && pp->cache) + if(!nread && curlx_dyn_len(&pp->recvbuf)) /* bump cache skip counter as on repeated skips we must wait for more data */ cache_skip++; @@ -701,100 +740,34 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, *nreadp += nread; - } /* while there's buffer left and loop is requested */ + } /* while there is buffer left and loop is requested */ pp->pending_resp = FALSE; + CURL_TRC_FTP(data, "getFTPResponse -> result=%d, nread=%zd, ftpcode=%d", + result, *nreadp, *ftpcode); return result; } -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ -static const char * const ftp_state_names[]={ - "STOP", - "WAIT220", - "AUTH", - "USER", - "PASS", - "ACCT", - "PBSZ", - "PROT", - "CCC", - "PWD", - "SYST", - "NAMEFMT", - "QUOTE", - "RETR_PREQUOTE", - "STOR_PREQUOTE", - "POSTQUOTE", - "CWD", - "MKD", - "MDTM", - "TYPE", - "LIST_TYPE", - "RETR_TYPE", - "STOR_TYPE", - "SIZE", - "RETR_SIZE", - "STOR_SIZE", - "REST", - "RETR_REST", - "PORT", - "PRET", - "PASV", - "LIST", - "RETR", - "STOR", - "QUIT" -}; -#endif - -/* This is the ONLY way to change FTP state! */ -static void _state(struct Curl_easy *data, - ftpstate newstate -#ifdef DEBUGBUILD - , int lineno -#endif - ) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - -#if defined(DEBUGBUILD) - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) lineno; -#else - if(ftpc->state != newstate) - infof(data, "FTP %p (line %d) state change from %s to %s", - (void *)ftpc, lineno, ftp_state_names[ftpc->state], - ftp_state_names[newstate]); -#endif -#endif - - ftpc->state = newstate; -} - static CURLcode ftp_state_user(struct Curl_easy *data, + struct ftp_conn *ftpc, struct connectdata *conn) { - CURLcode result = Curl_pp_sendf(data, - &conn->proto.ftpc.pp, "USER %s", - conn->user?conn->user:""); + CURLcode result = Curl_pp_sendf(data, &ftpc->pp, "USER %s", + conn->user ? conn->user : ""); if(!result) { - struct ftp_conn *ftpc = &conn->proto.ftpc; ftpc->ftp_trying_alternative = FALSE; - state(data, FTP_USER); + ftp_state(data, ftpc, FTP_USER); } return result; } static CURLcode ftp_state_pwd(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc) { - CURLcode result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PWD"); + CURLcode result = Curl_pp_sendf(data, &ftpc->pp, "%s", "PWD"); if(!result) - state(data, FTP_PWD); + ftp_state(data, ftpc, FTP_PWD); return result; } @@ -804,40 +777,38 @@ static int ftp_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + return ftpc ? Curl_pp_getsock(data, &ftpc->pp, socks) : GETSOCK_BLANK; } /* For the FTP "DO_MORE" phase only */ static int ftp_domore_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); (void)data; + if(!ftpc) + return GETSOCK_BLANK; + /* When in DO_MORE state, we could be either waiting for us to connect to a * remote site, or we could wait for that site to connect to us. Or just * handle ordinary commands. */ - - DEBUGF(infof(data, "ftp_domore_getsock()")); - if(conn->cfilter[SECONDARYSOCKET] - && !Curl_conn_is_connected(conn, SECONDARYSOCKET)) - return Curl_conn_get_select_socks(data, SECONDARYSOCKET, socks); + CURL_TRC_FTP(data, "[%s] ftp_domore_getsock()", FTP_CSTATE(ftpc)); if(FTP_STOP == ftpc->state) { - int bits = GETSOCK_READSOCK(0); - - /* if stopped and still in this state, then we're also waiting for a + /* if stopped and still in this state, then we are also waiting for a connect on the secondary connection */ + DEBUGASSERT(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD || + (conn->cfilter[SECONDARYSOCKET] && + !Curl_conn_is_connected(conn, SECONDARYSOCKET))); socks[0] = conn->sock[FIRSTSOCKET]; - if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { - socks[1] = conn->sock[SECONDARYSOCKET]; - bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1); - } - - return bits; + /* An unconnected SECONDARY will add its socket by itself + * via its adjust_pollset() */ + return GETSOCK_READSOCK(0); } - return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); + return Curl_pp_getsock(data, &ftpc->pp, socks); } /* This is called after the FTP_QUOTE state is passed. @@ -847,14 +818,14 @@ static int ftp_domore_getsock(struct Curl_easy *data, missing ones, if that option is enabled. */ static CURLcode ftp_state_cwd(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; if(ftpc->cwddone) /* already done and fine */ - result = ftp_state_mdtm(data); + result = ftp_state_mdtm(data, ftpc, ftp); else { /* FTPFILE_NOCWD with full path: expect ftpc->cwddone! */ DEBUGASSERT((data->set.ftp_filemethod != FTPFILE_NOCWD) || @@ -862,17 +833,17 @@ static CURLcode ftp_state_cwd(struct Curl_easy *data, ftpc->count2 = 0; /* count2 counts failed CWDs */ - if(conn->bits.reuse && ftpc->entrypath && + if(data->conn->bits.reuse && ftpc->entrypath && /* no need to go to entrypath when we have an absolute path */ !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')) { - /* This is a re-used connection. Since we change directory to where the + /* This is a reused connection. Since we change directory to where the transfer is taking place, we must first get back to the original dir where we ended up after login: */ ftpc->cwdcount = 0; /* we count this as the first path, then we add one for all upcoming ones in the ftp->dirs[] array */ result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->entrypath); if(!result) - state(data, FTP_CWD); + ftp_state(data, ftpc, FTP_CWD); } else { if(ftpc->dirdepth) { @@ -882,11 +853,11 @@ static CURLcode ftp_state_cwd(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->dirs[ftpc->cwdcount -1]); if(!result) - state(data, FTP_CWD); + ftp_state(data, ftpc, FTP_CWD); } else { /* No CWD necessary */ - result = ftp_state_mdtm(data); + result = ftp_state_mdtm(data, ftpc, ftp); } } } @@ -900,11 +871,11 @@ typedef enum { } ftpport; static CURLcode ftp_state_use_port(struct Curl_easy *data, + struct ftp_conn *ftpc, ftpport fcmd) /* start with this */ { CURLcode result = CURLE_FTP_PORT_FAILED; struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; curl_socket_t portsock = CURL_SOCKET_BAD; char myhost[MAX_IPADR_LEN + 1] = ""; @@ -914,21 +885,22 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, char hbuf[NI_MAXHOST]; struct sockaddr *sa = (struct sockaddr *)&ss; struct sockaddr_in * const sa4 = (void *)sa; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 * const sa6 = (void *)sa; #endif static const char mode[][5] = { "EPRT", "PORT" }; - enum resolve_t rc; int error; char *host = NULL; char *string_ftpport = data->set.str[STRING_FTPPORT]; - struct Curl_dns_entry *h = NULL; + struct Curl_dns_entry *dns_entry = NULL; unsigned short port_min = 0; unsigned short port_max = 0; unsigned short port; bool possibly_non_local = TRUE; char buffer[STRERROR_LEN]; char *addr = NULL; + size_t addrlen = 0; + char ipstr[50]; /* Step 1, figure out what is requested, * accepted format : @@ -937,32 +909,17 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(data->set.str[STRING_FTPPORT] && (strlen(data->set.str[STRING_FTPPORT]) > 1)) { - -#ifdef ENABLE_IPV6 - size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? - INET6_ADDRSTRLEN : strlen(string_ftpport); -#else - size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? - INET_ADDRSTRLEN : strlen(string_ftpport); -#endif - char *ip_start = string_ftpport; char *ip_end = NULL; - char *port_start = NULL; - char *port_sep = NULL; - - addr = calloc(addrlen + 1, 1); - if(!addr) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(*string_ftpport == '[') { /* [ipv6]:port(-range) */ - ip_start = string_ftpport + 1; - ip_end = strchr(string_ftpport, ']'); - if(ip_end) - strncpy(addr, ip_start, ip_end - ip_start); + char *ip_start = string_ftpport + 1; + ip_end = strchr(ip_start, ']'); + if(ip_end) { + addrlen = ip_end - ip_start; + addr = ip_start; + } } else #endif @@ -972,33 +929,39 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, } else { ip_end = strchr(string_ftpport, ':'); + addr = string_ftpport; if(ip_end) { /* either ipv6 or (ipv4|domain|interface):port(-range) */ -#ifdef ENABLE_IPV6 - if(Curl_inet_pton(AF_INET6, string_ftpport, sa6) == 1) { + addrlen = ip_end - string_ftpport; +#ifdef USE_IPV6 + if(curlx_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { /* ipv6 */ port_min = port_max = 0; - strcpy(addr, string_ftpport); ip_end = NULL; /* this got no port ! */ } - else #endif - /* (ipv4|domain|interface):port(-range) */ - strncpy(addr, string_ftpport, ip_end - ip_start); } else /* ipv4|interface */ - strcpy(addr, string_ftpport); + addrlen = strlen(string_ftpport); } /* parse the port */ if(ip_end) { - port_start = strchr(ip_end, ':'); - if(port_start) { - port_min = curlx_ultous(strtoul(port_start + 1, NULL, 10)); - port_sep = strchr(port_start, '-'); - if(port_sep) { - port_max = curlx_ultous(strtoul(port_sep + 1, NULL, 10)); + const char *portp = strchr(ip_end, ':'); + if(portp) { + curl_off_t start; + curl_off_t end; + portp++; + if(!curlx_str_number(&portp, &start, 0xffff)) { + /* got the first number */ + port_min = (unsigned short)start; + if(!curlx_str_single(&portp, '-')) { + /* got the dash */ + if(!curlx_str_number(&portp, &end, 0xffff)) + /* got the second number */ + port_max = (unsigned short)end; + } } else port_max = port_min; @@ -1014,22 +977,29 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(port_min > port_max) port_min = port_max = 0; - if(*addr != '\0') { + if(addrlen) { + DEBUGASSERT(addr); + if(addrlen >= sizeof(ipstr)) + goto out; + memcpy(ipstr, addr, addrlen); + ipstr[addrlen] = 0; + /* attempt to get the address of the given interface name */ switch(Curl_if2ip(conn->remote_addr->family, -#ifdef ENABLE_IPV6 - Curl_ipv6_scope(&conn->remote_addr->sa_addr), +#ifdef USE_IPV6 + Curl_ipv6_scope(&conn->remote_addr->curl_sa_addr), conn->scope_id, #endif - addr, hbuf, sizeof(hbuf))) { + ipstr, hbuf, sizeof(hbuf))) { case IF2IP_NOT_FOUND: - /* not an interface, use the given string as host name instead */ - host = addr; + /* not an interface, use the given string as hostname instead */ + host = ipstr; break; case IF2IP_AF_NOT_SUPPORTED: goto out; case IF2IP_FOUND: - host = hbuf; /* use the hbuf for host name */ + host = hbuf; /* use the hbuf for hostname */ + break; } } else @@ -1039,7 +1009,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(!host) { const char *r; - /* not an interface and not a host name, get default by extracting + /* not an interface and not a hostname, get default by extracting the IP from the control connection */ sslen = sizeof(ss); if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { @@ -1048,7 +1018,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } switch(sa->sa_family) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: r = Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); break; @@ -1060,22 +1030,17 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(!r) { goto out; } - host = hbuf; /* use this host name */ + host = hbuf; /* use this hostname */ possibly_non_local = FALSE; /* we know it is local now */ } /* resolv ip/host to ip */ - rc = Curl_resolv(data, host, 0, FALSE, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &h); - if(h) { - res = h->addr; - /* when we return from this function, we can forget about this entry - to we can unlock it now already */ - Curl_resolv_unlock(data, h); - } /* (h) */ - else - res = NULL; /* failure! */ + res = NULL; + result = Curl_resolv_blocking(data, host, 0, conn->ip_version, &dns_entry); + if(!result) { + DEBUGASSERT(dns_entry); + res = dns_entry->addr; + } if(!res) { failf(data, "failed to resolve the address provided to PORT: %s", host); @@ -1098,7 +1063,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(error, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), opened socket")); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), opened socket", + FTP_CSTATE(ftpc)); /* step 3, bind to a suitable local address */ @@ -1108,7 +1074,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, for(port = port_min; port <= port_max;) { if(sa->sa_family == AF_INET) sa4->sin_port = htons(port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else sa6->sin6_port = htons(port); #endif @@ -1116,8 +1082,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(bind(portsock, sa, sslen) ) { /* It failed. */ error = SOCKERRNO; - if(possibly_non_local && (error == EADDRNOTAVAIL)) { - /* The requested bind address is not local. Use the address used for + if(possibly_non_local && (error == SOCKEADDRNOTAVAIL)) { + /* The requested bind address is not local. Use the address used for * the control connection instead and restart the port loop */ infof(data, "bind(port=%hu) on non-local address failed: %s", port, @@ -1130,10 +1096,10 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } port = port_min; - possibly_non_local = FALSE; /* don't try this again */ + possibly_non_local = FALSE; /* do not try this again */ continue; } - if(error != EADDRINUSE && error != EACCES) { + if(error != SOCKEADDRINUSE && error != SOCKEACCES) { failf(data, "bind(port=%hu) failed: %s", port, Curl_strerror(error, buffer, sizeof(buffer))); goto out; @@ -1159,7 +1125,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), socket bound to port %d", port)); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), socket bound to port %d", + FTP_CSTATE(ftpc), port); /* step 4, listen on the socket */ @@ -1168,7 +1135,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), listening on %d", port)); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), listening on %d", + FTP_CSTATE(ftpc), port); /* step 5, send the proper FTP command */ @@ -1176,9 +1144,9 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, below */ Curl_printable_address(ai, myhost, sizeof(myhost)); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) - /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the + /* EPRT is disabled but we are connected to an IPv6 host, so we ignore the request and enable EPRT again! */ conn->bits.ftp_use_eprt = TRUE; #endif @@ -1197,7 +1165,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, case AF_INET: port = ntohs(sa4->sin_port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: port = ntohs(sa6->sin6_port); break; @@ -1216,7 +1184,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, */ result = Curl_pp_sendf(data, &ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], - sa->sa_family == AF_INET?1:2, + sa->sa_family == AF_INET ? 1 : 2, myhost, port); if(result) { failf(data, "Failure sending EPRT command: %s", @@ -1232,7 +1200,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, char *dest = target; /* translate x.x.x.x to x,x,x,x */ - while(source && *source) { + while(*source) { if(*source == '.') *dest = ','; else @@ -1241,7 +1209,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, source++; } *dest = 0; - msnprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff)); + msnprintf(dest, 20, ",%d,%d", (int)(port >> 8), (int)(port & 0xff)); result = Curl_pp_sendf(data, &ftpc->pp, "%s %s", mode[fcmd], target); if(result) { @@ -1255,28 +1223,41 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* store which command was sent */ ftpc->count1 = fcmd; + ftp_state(data, ftpc, FTP_PORT); /* Replace any filter on SECONDARY with one listening on this socket */ result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock); - if(result) - goto out; - portsock = CURL_SOCKET_BAD; /* now held in filter */ - state(data, FTP_PORT); + if(!result) + portsock = CURL_SOCKET_BAD; /* now held in filter */ out: + /* If we looked up a dns_entry, now is the time to safely release it */ + if(dns_entry) + Curl_resolv_unlink(data, &dns_entry); if(result) { - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); + } + else { + /* successfully setup the list socket filter. Do we need more? */ + if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && + !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); + } + data->conn->bits.do_more = FALSE; + Curl_pgrsTime(data, TIMER_STARTACCEPT); + Curl_expire(data, data->set.accepttimeout ? + data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, + EXPIRE_FTP_ACCEPT); } if(portsock != CURL_SOCKET_BAD) Curl_socket_close(data, conn, portsock); - free(addr); return result; } static CURLcode ftp_state_use_pasv(struct Curl_easy *data, + struct ftp_conn *ftpc, struct connectdata *conn) { - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result = CURLE_OK; /* Here's the executive summary on what to do: @@ -1297,17 +1278,17 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, #ifdef PF_INET6 if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) - /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the + /* EPSV is disabled but we are connected to an IPv6 host, so we ignore the request and enable EPSV again! */ conn->bits.ftp_use_epsv = TRUE; #endif - modeoff = conn->bits.ftp_use_epsv?0:1; + modeoff = conn->bits.ftp_use_epsv ? 0 : 1; result = Curl_pp_sendf(data, &ftpc->pp, "%s", mode[modeoff]); if(!result) { ftpc->count1 = modeoff; - state(data, FTP_PASV); + ftp_state(data, ftpc, FTP_PASV); infof(data, "Connect data stream passively"); } return result; @@ -1320,55 +1301,52 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, * request is made. Thus, if an actual transfer is to be made this is where we * take off for real. */ -static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data) +static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; struct connectdata *conn = data->conn; if(ftp->transfer != PPTRANSFER_BODY) { - /* doesn't transfer any data */ + /* does not transfer any data */ /* still possibly do PRE QUOTE jobs */ - state(data, FTP_RETR_PREQUOTE); - result = ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); + ftp_state(data, ftpc, FTP_RETR_PREQUOTE); + result = ftp_state_quote(data, ftpc, ftp, TRUE, FTP_RETR_PREQUOTE); } else if(data->set.ftp_use_port) { /* We have chosen to use the PORT (or similar) command */ - result = ftp_state_use_port(data, EPRT); + result = ftp_state_use_port(data, ftpc, EPRT); } else { /* We have chosen (this is default) to use the PASV (or similar) command */ if(data->set.ftp_use_pret) { /* The user has requested that we send a PRET command to prepare the server for the upcoming PASV */ - struct ftp_conn *ftpc = &conn->proto.ftpc; - if(!conn->proto.ftpc.file) + if(!ftpc->file) result = Curl_pp_sendf(data, &ftpc->pp, "PRET %s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->state.list_only?"NLST":"LIST")); + data->set.str[STRING_CUSTOMREQUEST] ? + data->set.str[STRING_CUSTOMREQUEST] : + (data->state.list_only ? "NLST" : "LIST")); else if(data->state.upload) - result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", - conn->proto.ftpc.file); + result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", ftpc->file); else - result = Curl_pp_sendf(data, &ftpc->pp, "PRET RETR %s", - conn->proto.ftpc.file); + result = Curl_pp_sendf(data, &ftpc->pp, "PRET RETR %s", ftpc->file); if(!result) - state(data, FTP_PRET); + ftp_state(data, ftpc, FTP_PRET); } else - result = ftp_state_use_pasv(data, conn); + result = ftp_state_use_pasv(data, ftpc, conn); } return result; } static CURLcode ftp_state_rest(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; if((ftp->transfer != PPTRANSFER_BODY) && ftpc->file) { /* if a "head"-like request is being made (on a file) */ @@ -1377,40 +1355,39 @@ static CURLcode ftp_state_rest(struct Curl_easy *data, whether it supports range */ result = Curl_pp_sendf(data, &ftpc->pp, "REST %d", 0); if(!result) - state(data, FTP_REST); + ftp_state(data, ftpc, FTP_REST); } else - result = ftp_state_prepare_transfer(data); + result = ftp_state_prepare_transfer(data, ftpc, ftp); return result; } static CURLcode ftp_state_size(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; if((ftp->transfer == PPTRANSFER_INFO) && ftpc->file) { /* if a "head"-like request is being made (on a file) */ - /* we know ftpc->file is a valid pointer to a file name */ + /* we know ftpc->file is a valid pointer to a filename */ result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); if(!result) - state(data, FTP_SIZE); + ftp_state(data, ftpc, FTP_SIZE); } else - result = ftp_state_rest(data, conn); + result = ftp_state_rest(data, ftpc, ftp); return result; } -static CURLcode ftp_state_list(struct Curl_easy *data) +static CURLcode ftp_state_list(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; /* If this output is to be machine-parsed, the NLST command might be better to use, since the LIST command output is not specified or standard in any @@ -1452,49 +1429,52 @@ static CURLcode ftp_state_list(struct Curl_easy *data) } cmd = aprintf("%s%s%s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->state.list_only?"NLST":"LIST"), - lstArg? " ": "", - lstArg? lstArg: ""); + data->set.str[STRING_CUSTOMREQUEST] ? + data->set.str[STRING_CUSTOMREQUEST] : + (data->state.list_only ? "NLST" : "LIST"), + lstArg ? " " : "", + lstArg ? lstArg : ""); free(lstArg); if(!cmd) return CURLE_OUT_OF_MEMORY; - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", cmd); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); free(cmd); if(!result) - state(data, FTP_LIST); + ftp_state(data, ftpc, FTP_LIST); return result; } -static CURLcode ftp_state_retr_prequote(struct Curl_easy *data) +static CURLcode ftp_state_retr_prequote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { - /* We've sent the TYPE, now we must send the list of prequote strings */ - return ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); + /* We have sent the TYPE, now we must send the list of prequote strings */ + return ftp_state_quote(data, ftpc, ftp, TRUE, FTP_RETR_PREQUOTE); } -static CURLcode ftp_state_stor_prequote(struct Curl_easy *data) +static CURLcode ftp_state_stor_prequote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { - /* We've sent the TYPE, now we must send the list of prequote strings */ - return ftp_state_quote(data, TRUE, FTP_STOR_PREQUOTE); + /* We have sent the TYPE, now we must send the list of prequote strings */ + return ftp_state_quote(data, ftpc, ftp, TRUE, FTP_STOR_PREQUOTE); } -static CURLcode ftp_state_type(struct Curl_easy *data) +static CURLcode ftp_state_type(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; /* If we have selected NOBODY and HEADER, it means that we only want file - information. Which in FTP can't be much more than the file size and + information. Which in FTP cannot be much more than the file size and date. */ if(data->req.no_body && ftpc->file && - ftp_need_type(conn, data->state.prefer_ascii)) { + ftp_need_type(ftpc, data->state.prefer_ascii)) { /* The SIZE command is _not_ RFC 959 specified, and therefore many servers may not support it! It is however the only way we have to get a file's size! */ @@ -1504,23 +1484,23 @@ static CURLcode ftp_state_type(struct Curl_easy *data) /* Some servers return different sizes for different modes, and thus we must set the proper type before we check the size */ - result = ftp_nb_type(data, conn, data->state.prefer_ascii, FTP_TYPE); + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, FTP_TYPE); if(result) return result; } else - result = ftp_state_size(data, conn); + result = ftp_state_size(data, ftpc, ftp); return result; } /* This is called after the CWD commands have been done in the beginning of the DO phase */ -static CURLcode ftp_state_mdtm(struct Curl_easy *data) +static CURLcode ftp_state_mdtm(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; /* Requested time of file or time-depended transfer? */ if((data->set.get_filetime || data->set.timecondition) && ftpc->file) { @@ -1530,10 +1510,10 @@ static CURLcode ftp_state_mdtm(struct Curl_easy *data) result = Curl_pp_sendf(data, &ftpc->pp, "MDTM %s", ftpc->file); if(!result) - state(data, FTP_MDTM); + ftp_state(data, ftpc, FTP_MDTM); } else - result = ftp_state_type(data); + result = ftp_state_type(data, ftpc, ftp); return result; } @@ -1541,23 +1521,22 @@ static CURLcode ftp_state_mdtm(struct Curl_easy *data) /* This is called after the TYPE and possible quote commands have been sent */ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool sizechecked) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; bool append = data->set.remote_append; if((data->state.resume_from && !sizechecked) || ((data->state.resume_from > 0) && sizechecked)) { - /* we're about to continue the uploading of a file */ + /* we are about to continue the uploading of a file */ /* 1. get already existing file's size. We use the SIZE command for this which may not exist in the server! The SIZE command is not in RFC959. */ /* 2. This used to set REST. But since we can do append, we - don't another ftp command. We just skip the source file + do not another ftp command. We just skip the source file offset and then we APPEND the rest on the file instead */ /* 3. pass file-size number of bytes in the source file */ @@ -1569,7 +1548,7 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, /* Got no given size to start from, figure it out */ result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); if(!result) - state(data, FTP_STOR_SIZE); + ftp_state(data, ftpc, FTP_STOR_SIZE); return result; } @@ -1577,11 +1556,11 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, append = TRUE; /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1590,15 +1569,16 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, failf(data, "Could not seek stream"); return CURLE_FTP_COULDNT_USE_REST; } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : + (data->state.resume_from - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, + data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); passed += actuallyread; @@ -1611,42 +1591,41 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, } while(passed < data->state.resume_from); } /* now, decrease the size of the read */ - if(data->state.infilesize>0) { + if(data->state.infilesize > 0) { data->state.infilesize -= data->state.resume_from; if(data->state.infilesize <= 0) { infof(data, "File already completely uploaded"); /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); - /* Set ->transfer so that we won't get any error in - * ftp_done() because we didn't transfer anything! */ + /* Set ->transfer so that we will not get any error in + * ftp_done() because we did not transfer anything! */ ftp->transfer = PPTRANSFER_NONE; - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } } - /* we've passed, proceed as normal */ + /* we have passed, proceed as normal */ } /* resume_from */ - result = Curl_pp_sendf(data, &ftpc->pp, append?"APPE %s":"STOR %s", + result = Curl_pp_sendf(data, &ftpc->pp, append ? "APPE %s" : "STOR %s", ftpc->file); if(!result) - state(data, FTP_STOR); + ftp_state(data, ftpc, FTP_STOR); return result; } static CURLcode ftp_state_quote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool init, ftpstate instate) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; bool quote = FALSE; struct curl_slist *item; @@ -1679,7 +1658,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, int i = 0; /* Skip count1 items in the linked list */ - while((i< ftpc->count1) && item) { + while((i < ftpc->count1) && item) { item = item->next; i++; } @@ -1695,7 +1674,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); if(result) return result; - state(data, instate); + ftp_state(data, ftpc, instate); quote = TRUE; } } @@ -1705,44 +1684,44 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, switch(instate) { case FTP_QUOTE: default: - result = ftp_state_cwd(data, conn); + result = ftp_state_cwd(data, ftpc, ftp); break; case FTP_RETR_PREQUOTE: if(ftp->transfer != PPTRANSFER_BODY) - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); else { if(ftpc->known_filesize != -1) { Curl_pgrsSetDownloadSize(data, ftpc->known_filesize); - result = ftp_state_retr(data, ftpc->known_filesize); + result = ftp_state_retr(data, ftpc, ftp, ftpc->known_filesize); } else { if(data->set.ignorecl || data->state.prefer_ascii) { - /* 'ignorecl' is used to support download of growing files. It + /* 'ignorecl' is used to support download of growing files. It prevents the state machine from requesting the file size from - the server. With an unknown file size the download continues + the server. With an unknown file size the download continues until the server terminates it, otherwise the client stops if - the received byte count exceeds the reported file size. Set + the received byte count exceeds the reported file size. Set option CURLOPT_IGNORE_CONTENT_LENGTH to 1 to enable this behavior. In addition: asking for the size for 'TYPE A' transfers is not - constructive since servers don't report the converted size. So + constructive since servers do not report the converted size. So skip it. */ result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) - state(data, FTP_RETR); + ftp_state(data, ftpc, FTP_RETR); } else { result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); if(!result) - state(data, FTP_RETR_SIZE); + ftp_state(data, ftpc, FTP_RETR_SIZE); } } } break; case FTP_STOR_PREQUOTE: - result = ftp_state_ul_setup(data, FALSE); + result = ftp_state_ul_setup(data, ftpc, ftp, FALSE); break; case FTP_POSTQUOTE: break; @@ -1755,6 +1734,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, /* called from ftp_state_pasv_resp to switch to PASV in case of EPSV problems */ static CURLcode ftp_epsv_disable(struct Curl_easy *data, + struct ftp_conn *ftpc, struct connectdata *conn) { CURLcode result = CURLE_OK; @@ -1764,7 +1744,7 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, && !(conn->bits.tunnel_proxy || conn->bits.socksproxy) #endif ) { - /* We can't disable EPSV when doing IPv6, so this is instead a fail */ + /* We cannot disable EPSV when doing IPv6, so this is instead a fail */ failf(data, "Failed EPSV attempt, exiting"); return CURLE_WEIRD_SERVER_REPLY; } @@ -1772,15 +1752,14 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, infof(data, "Failed EPSV attempt. Disabling EPSV"); /* disable it for next transfer */ conn->bits.ftp_use_epsv = FALSE; - Curl_conn_close(data, SECONDARYSOCKET); - Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); + close_secondarysocket(data, ftpc); data->state.errorbuf = FALSE; /* allow error message to get rewritten */ - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PASV"); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "PASV"); if(!result) { - conn->proto.ftpc.count1++; + ftpc->count1++; /* remain in/go to the FTP_PASV state */ - state(data, FTP_PASV); + ftp_state(data, ftpc, FTP_PASV); } return result; } @@ -1789,14 +1768,14 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, static char *control_address(struct connectdata *conn) { /* Returns the control connection IP address. - If a proxy tunnel is used, returns the original host name instead, because + If a proxy tunnel is used, returns the original hostname instead, because the effective control connection address is the proxy address, not the ftp host. */ #ifndef CURL_DISABLE_PROXY if(conn->bits.tunnel_proxy || conn->bits.socksproxy) return conn->host.name; #endif - return conn->primary_ip; + return conn->primary.remote_ip; } static bool match_pasv_6nums(const char *p, @@ -1804,34 +1783,30 @@ static bool match_pasv_6nums(const char *p, { int i; for(i = 0; i < 6; i++) { - unsigned long num; - char *endp; + curl_off_t num; if(i) { if(*p != ',') return FALSE; p++; } - if(!ISDIGIT(*p)) - return FALSE; - num = strtoul(p, &endp, 10); - if(num > 255) + if(curlx_str_number(&p, &num, 0xff)) return FALSE; array[i] = (unsigned int)num; - p = endp; } return TRUE; } static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode) { struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result; - struct Curl_dns_entry *addr = NULL; - enum resolve_t rc; + struct Curl_dns_entry *dns = NULL; unsigned short connectport; /* the local port connect() should use! */ - char *str = &data->state.buffer[4]; /* start on the first letter */ + struct pingpong *pp = &ftpc->pp; + char *str = + curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first letter */ /* if we come here again, make sure the former name is cleared */ Curl_safefree(ftpc->newhost); @@ -1845,23 +1820,17 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, ptr++; /* |||12345| */ sep = ptr[0]; - /* the ISDIGIT() check here is because strtoul() accepts leading minus - etc */ if((ptr[1] == sep) && (ptr[2] == sep) && ISDIGIT(ptr[3])) { - char *endp; - unsigned long num = strtoul(&ptr[3], &endp, 10); - if(*endp != sep) - ptr = NULL; - else if(num > 0xffff) { + const char *p = &ptr[3]; + curl_off_t num; + if(curlx_str_number(&p, &num, 0xffff) || (*p != sep)) { failf(data, "Illegal port number in EPSV reply"); return CURLE_FTP_WEIRD_PASV_REPLY; } - if(ptr) { - ftpc->newport = (unsigned short)(num & 0xffff); - ftpc->newhost = strdup(control_address(conn)); - if(!ftpc->newhost) - return CURLE_OUT_OF_MEMORY; - } + ftpc->newport = (unsigned short)num; + ftpc->newhost = strdup(control_address(conn)); + if(!ftpc->newhost) + return CURLE_OUT_OF_MEMORY; } else ptr = NULL; @@ -1900,7 +1869,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(data->set.ftp_skip_ip) { /* told to ignore the remotely given IP but instead use the host we used for the control connection */ - infof(data, "Skip %u.%u.%u.%u for data connection, re-use %s instead", + infof(data, "Skip %u.%u.%u.%u for data connection, reuse %s instead", ip[0], ip[1], ip[2], ip[3], conn->host.name); ftpc->newhost = strdup(control_address(conn)); @@ -1911,11 +1880,11 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(!ftpc->newhost) return CURLE_OUT_OF_MEMORY; - ftpc->newport = (unsigned short)(((ip[4]<<8) + ip[5]) & 0xffff); + ftpc->newport = (unsigned short)(((ip[4] << 8) + ip[5]) & 0xffff); } else if(ftpc->count1 == 0) { /* EPSV failed, move on to PASV */ - return ftp_epsv_disable(data, conn); + return ftp_epsv_disable(data, ftpc, conn); } else { failf(data, "Bad PASV/EPSV response: %03d", ftpcode); @@ -1926,22 +1895,18 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(conn->bits.proxy) { /* * This connection uses a proxy and we need to connect to the proxy again - * here. We don't want to rely on a former host lookup that might've + * here. We do not want to rely on a former host lookup that might've * expired now, instead we remake the lookup here and now! */ const char * const host_name = conn->bits.socksproxy ? conn->socks_proxy.host.name : conn->http_proxy.host.name; - rc = Curl_resolv(data, host_name, conn->port, FALSE, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING, ignores the return code but 'addr' will be NULL in - case of failure */ - (void)Curl_resolver_wait_resolv(data, &addr); + (void)Curl_resolv_blocking(data, host_name, conn->primary.remote_port, + conn->ip_version, &dns); + /* we connect to the proxy's port */ + connectport = (unsigned short)conn->primary.remote_port; - connectport = - (unsigned short)conn->port; /* we connect to the proxy's port */ - - if(!addr) { - failf(data, "Can't resolve proxy host %s:%hu", host_name, connectport); + if(!dns) { + failf(data, "cannot resolve proxy host %s:%hu", host_name, connectport); return CURLE_COULDNT_RESOLVE_PROXY; } } @@ -1953,34 +1918,30 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, /* postponed address resolution in case of tcp fastopen */ if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) { - Curl_conn_ev_update_info(data, conn); - Curl_safefree(ftpc->newhost); + free(ftpc->newhost); ftpc->newhost = strdup(control_address(conn)); if(!ftpc->newhost) return CURLE_OUT_OF_MEMORY; } - rc = Curl_resolv(data, ftpc->newhost, ftpc->newport, FALSE, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING */ - (void)Curl_resolver_wait_resolv(data, &addr); - + (void)Curl_resolv_blocking(data, ftpc->newhost, ftpc->newport, + conn->ip_version, &dns); connectport = ftpc->newport; /* we connect to the remote port */ - if(!addr) { - failf(data, "Can't resolve new host %s:%hu", ftpc->newhost, connectport); + if(!dns) { + failf(data, "cannot resolve new host %s:%hu", + ftpc->newhost, connectport); return CURLE_FTP_CANT_GET_HOST; } } - result = Curl_conn_setup(data, conn, SECONDARYSOCKET, addr, - conn->bits.ftp_use_data_ssl? + result = Curl_conn_setup(data, conn, SECONDARYSOCKET, dns, + conn->bits.ftp_use_data_ssl ? CURL_CF_SSL_ENABLE : CURL_CF_SSL_DISABLE); if(result) { - Curl_resolv_unlock(data, addr); /* we're done using this address */ if(ftpc->count1 == 0 && ftpcode == 229) - return ftp_epsv_disable(data, conn); + return ftp_epsv_disable(data, ftpc, conn); return result; } @@ -1994,27 +1955,26 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(data->set.verbose) /* this just dumps information about this second connection */ - ftp_pasv_verbose(data, addr->addr, ftpc->newhost, connectport); - - Curl_resolv_unlock(data, addr); /* we're done using this address */ + ftp_pasv_verbose(data, dns->addr, ftpc->newhost, connectport); - Curl_safefree(conn->secondaryhostname); + free(conn->secondaryhostname); conn->secondary_port = ftpc->newport; conn->secondaryhostname = strdup(ftpc->newhost); if(!conn->secondaryhostname) return CURLE_OUT_OF_MEMORY; conn->bits.do_more = TRUE; - state(data, FTP_STOP); /* this phase is completed */ + ftp_state(data, ftpc, FTP_STOP); /* this phase is completed */ return result; } static CURLcode ftp_state_port_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode) { struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; ftpport fcmd = (ftpport)ftpc->count1; CURLcode result = CURLE_OK; @@ -2035,12 +1995,12 @@ static CURLcode ftp_state_port_resp(struct Curl_easy *data, } else /* try next */ - result = ftp_state_use_port(data, fcmd); + result = ftp_state_use_port(data, ftpc, fcmd); } else { infof(data, "Connect data stream actively"); - state(data, FTP_STOP); /* end of DO phase */ - result = ftp_dophase_done(data, FALSE); + ftp_state(data, ftpc, FTP_STOP); /* end of DO phase */ + result = ftp_dophase_done(data, ftpc, ftp, FALSE); } return result; @@ -2070,13 +2030,37 @@ static bool ftp_213_date(const char *p, int *year, int *month, int *day, return TRUE; } +static CURLcode client_write_header(struct Curl_easy *data, + char *buf, size_t blen) +{ + /* Some replies from an FTP server are written to the client + * as CLIENTWRITE_HEADER, formatted as if they came from a + * HTTP conversation. + * In all protocols, CLIENTWRITE_HEADER data is only passed to + * the body write callback when data->set.include_header is set + * via CURLOPT_HEADER. + * For historic reasons, FTP never played this game and expects + * all its HEADERs to do that always. Set that flag during the + * call to Curl_client_write() so it does the right thing. + * + * Notice that we cannot enable this flag for FTP in general, + * as an FTP transfer might involve an HTTP proxy connection and + * headers from CONNECT should not automatically be part of the + * output. */ + CURLcode result; + bool save = data->set.include_header; + data->set.include_header = TRUE; + result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, blen); + data->set.include_header = save; + return result; +} + static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; switch(ftpcode) { case 213: @@ -2084,8 +2068,9 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the last .sss part is optional and means fractions of a second */ int year, month, day, hour, minute, second; - if(ftp_213_date(&data->state.buffer[4], - &year, &month, &day, &hour, &minute, &second)) { + struct pingpong *pp = &ftpc->pp; + char *resp = curlx_dyn_ptr(&pp->recvbuf) + 4; + if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { /* we have a time, reformat it */ char timebuf[24]; msnprintf(timebuf, sizeof(timebuf), @@ -2099,10 +2084,19 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, /* If we asked for a time of the file and we actually got one as well, we "emulate" an HTTP-style header in our output. */ +#if defined(__GNUC__) && (defined(__DJGPP__) || defined(__AMIGA__)) +#pragma GCC diagnostic push +/* 'time_t' is unsigned in MSDOS and AmigaOS. Silence: + warning: comparison of unsigned expression in '>= 0' is always true */ +#pragma GCC diagnostic ignored "-Wtype-limits" +#endif if(data->req.no_body && ftpc->file && data->set.get_filetime && (data->info.filetime >= 0) ) { +#if defined(__GNUC__) && (defined(__DJGPP__) || defined(__AMIGA__)) +#pragma GCC diagnostic pop +#endif char headerbuf[128]; int headerbuflen; time_t filetime = data->info.filetime; @@ -2114,17 +2108,17 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, return result; /* format: "Tue, 15 Nov 1994 12:45:26" */ - headerbuflen = msnprintf(headerbuf, sizeof(headerbuf), - "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - result = Curl_client_write(data, CLIENTWRITE_BOTH, headerbuf, - headerbuflen); + headerbuflen = + msnprintf(headerbuf, sizeof(headerbuf), + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + result = client_write_header(data, headerbuf, headerbuflen); if(result) return result; } /* end of a ridiculous amount of conditionals */ @@ -2151,7 +2145,7 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, infof(data, "The requested document is not new enough"); ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ data->info.timecond = TRUE; - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } break; @@ -2160,7 +2154,7 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, infof(data, "The requested document is not old enough"); ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ data->info.timecond = TRUE; - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } break; @@ -2172,17 +2166,18 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, } if(!result) - result = ftp_state_type(data); + result = ftp_state_type(data, ftpc, ftp); return result; } static CURLcode ftp_state_type_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; if(ftpcode/100 != 2) { /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a @@ -2196,26 +2191,25 @@ static CURLcode ftp_state_type_resp(struct Curl_easy *data, ftpcode); if(instate == FTP_TYPE) - result = ftp_state_size(data, conn); + result = ftp_state_size(data, ftpc, ftp); else if(instate == FTP_LIST_TYPE) - result = ftp_state_list(data); + result = ftp_state_list(data, ftpc, ftp); else if(instate == FTP_RETR_TYPE) - result = ftp_state_retr_prequote(data); + result = ftp_state_retr_prequote(data, ftpc, ftp); else if(instate == FTP_STOR_TYPE) - result = ftp_state_stor_prequote(data); + result = ftp_state_stor_prequote(data, ftpc, ftp); return result; } static CURLcode ftp_state_retr(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, curl_off_t filesize) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - DEBUGF(infof(data, "ftp_state_retr()")); + CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_CSTATE(ftpc)); if(data->set.max_filesize && (filesize > data->set.max_filesize)) { failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; @@ -2226,20 +2220,20 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, /* We always (attempt to) get the size of downloads, so it is done before this even when not doing resumes. */ if(filesize == -1) { - infof(data, "ftp server doesn't support SIZE"); - /* We couldn't get the size and therefore we can't know if there really + infof(data, "ftp server does not support SIZE"); + /* We could not get the size and therefore we cannot know if there really is a part of the file left to get, although the server will just - close the connection when we start the connection so it won't cause + close the connection when we start the connection so it will not cause us any harm, just not make us exit as nicely. */ } else { /* We got a file size report, so we check that there actually is a part of the file left to get, or else we go home. */ - if(data->state.resume_from< 0) { - /* We're supposed to download the last abs(from) bytes */ + if(data->state.resume_from < 0) { + /* We are supposed to download the last abs(from) bytes */ if(filesize < -data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", data->state.resume_from, filesize); return CURLE_BAD_DOWNLOAD_RESUME; } @@ -2250,8 +2244,8 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, } else { if(filesize < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", data->state.resume_from, filesize); return CURLE_BAD_DOWNLOAD_RESUME; } @@ -2262,42 +2256,45 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, if(ftp->downloadsize == 0) { /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); infof(data, "File already completely downloaded"); - /* Set ->transfer so that we won't get any error in ftp_done() - * because we didn't transfer the any file */ + /* Set ->transfer so that we will not get any error in ftp_done() + * because we did not transfer the any file */ ftp->transfer = PPTRANSFER_NONE; - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } /* Set resume file transfer offset */ - infof(data, "Instructs server to resume from offset %" - CURL_FORMAT_CURL_OFF_T, data->state.resume_from); + infof(data, "Instructs server to resume from offset %" FMT_OFF_T, + data->state.resume_from); - result = Curl_pp_sendf(data, &ftpc->pp, "REST %" CURL_FORMAT_CURL_OFF_T, + result = Curl_pp_sendf(data, &ftpc->pp, "REST %" FMT_OFF_T, data->state.resume_from); if(!result) - state(data, FTP_RETR_REST); + ftp_state(data, ftpc, FTP_RETR_REST); } else { /* no resume */ result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) - state(data, FTP_RETR); + ftp_state(data, ftpc, FTP_RETR); } return result; } static CURLcode ftp_state_size_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; curl_off_t filesize = -1; - char *buf = data->state.buffer; + char *buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); + size_t len = ftpc->pp.nfinal; /* get the size from the ascii string: */ if(ftpcode == 213) { @@ -2305,19 +2302,18 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, for all the digits at the end of the response and parse only those as a number. */ char *start = &buf[4]; - char *fdigit = strchr(start, '\r'); + const char *fdigit = memchr(start, '\r', len); if(fdigit) { - do + fdigit--; + if(*fdigit == '\n') + fdigit--; + while(ISDIGIT(fdigit[-1]) && (fdigit > start)) fdigit--; - while(ISDIGIT(*fdigit) && (fdigit > start)); - if(!ISDIGIT(*fdigit)) - fdigit++; } else fdigit = start; - /* ignores parsing errors, which will make the size remain unknown */ - (void)curlx_strtoofft(fdigit, NULL, 10, &filesize); - + 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 @@ -2333,34 +2329,34 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, if(-1 != filesize) { char clbuf[128]; int clbuflen = msnprintf(clbuf, sizeof(clbuf), - "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", filesize); - result = Curl_client_write(data, CLIENTWRITE_BOTH, clbuf, clbuflen); + "Content-Length: %" FMT_OFF_T "\r\n", filesize); + result = client_write_header(data, clbuf, clbuflen); if(result) return result; } #endif Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_rest(data, data->conn); + result = ftp_state_rest(data, ftpc, ftp); } else if(instate == FTP_RETR_SIZE) { Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_retr(data, filesize); + result = ftp_state_retr(data, ftpc, ftp, filesize); } else if(instate == FTP_STOR_SIZE) { data->state.resume_from = filesize; - result = ftp_state_ul_setup(data, TRUE); + result = ftp_state_ul_setup(data, ftpc, ftp, TRUE); } return result; } static CURLcode ftp_state_rest_resp(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; switch(instate) { case FTP_REST: @@ -2368,13 +2364,12 @@ static CURLcode ftp_state_rest_resp(struct Curl_easy *data, #ifdef CURL_FTP_HTTPSTYLE_HEAD if(ftpcode == 350) { char buffer[24]= { "Accept-ranges: bytes\r\n" }; - result = Curl_client_write(data, CLIENTWRITE_BOTH, buffer, - strlen(buffer)); + result = client_write_header(data, buffer, strlen(buffer)); if(result) return result; } #endif - result = ftp_state_prepare_transfer(data); + result = ftp_state_prepare_transfer(data, ftpc, ftp); break; case FTP_RETR_REST: @@ -2385,7 +2380,7 @@ static CURLcode ftp_state_rest_resp(struct Curl_easy *data, else { result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) - state(data, FTP_RETR); + ftp_state(data, ftpc, FTP_RETR); } break; } @@ -2394,49 +2389,48 @@ static CURLcode ftp_state_rest_resp(struct Curl_easy *data, } static CURLcode ftp_state_stor_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; if(ftpcode >= 400) { failf(data, "Failed FTP upload: %0d", ftpcode); - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); /* oops, we never close the sockets! */ return CURLE_UPLOAD_FAILED; } - conn->proto.ftpc.state_saved = instate; + ftpc->state_saved = instate; /* PORT means we are now awaiting the server to connect to us. */ if(data->set.ftp_use_port) { bool connected; - state(data, FTP_STOP); /* no longer in STOR state */ + ftp_state(data, ftpc, FTP_STOP); /* no longer in STOR state */ - result = AllowServerConnect(data, &connected); + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); if(result) return result; if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; infof(data, "Data conn was not available immediately"); ftpc->wait_data_conn = TRUE; + return ftp_check_ctrl_on_data_wait(data, ftpc); } - - return CURLE_OK; + ftpc->wait_data_conn = FALSE; } - return InitiateTransfer(data); + return ftp_initiate_transfer(data, ftpc); } /* for LIST and RETR responses */ static CURLcode ftp_state_get_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; if((ftpcode == 150) || (ftpcode == 125)) { @@ -2474,14 +2468,14 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, !data->set.ignorecl && (ftp->downloadsize < 1)) { /* - * It seems directory listings either don't show the size or very - * often uses size 0 anyway. ASCII transfers may very well turn out - * that the transferred amount of data is not the same as this line - * tells, why using this number in those cases only confuses us. + * It seems directory listings either do not show the size or often uses + * size 0 anyway. ASCII transfers may cause that the transferred amount + * of data is not the same as this line tells, why using this number in + * those cases only confuses us. * * Example D above makes this parsing a little tricky */ - char *bytes; - char *buf = data->state.buffer; + const char *bytes; + char *buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); bytes = strstr(buf, " bytes"); if(bytes) { long in = (long)(--bytes-buf); @@ -2502,7 +2496,8 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, if(bytes) { ++bytes; /* get the number! */ - (void)curlx_strtoofft(bytes, NULL, 10, &size); + if(curlx_str_number(&bytes, &size, CURL_OFF_T_MAX)) + size = 1; } } } @@ -2514,44 +2509,42 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, else if((instate != FTP_LIST) && (data->state.prefer_ascii)) size = -1; /* kludge for servers that understate ASCII mode file size */ - infof(data, "Maxdownload = %" CURL_FORMAT_CURL_OFF_T, - data->req.maxdownload); + infof(data, "Maxdownload = %" FMT_OFF_T, data->req.maxdownload); if(instate != FTP_LIST) - infof(data, "Getting file with size: %" CURL_FORMAT_CURL_OFF_T, - size); + infof(data, "Getting file with size: %" FMT_OFF_T, size); /* FTP download: */ - conn->proto.ftpc.state_saved = instate; - conn->proto.ftpc.retr_size_saved = size; + ftpc->state_saved = instate; + ftpc->retr_size_saved = size; if(data->set.ftp_use_port) { bool connected; - result = AllowServerConnect(data, &connected); + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); if(result) return result; if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; infof(data, "Data conn was not available immediately"); - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); ftpc->wait_data_conn = TRUE; + return ftp_check_ctrl_on_data_wait(data, ftpc); } + ftpc->wait_data_conn = FALSE; } - else - return InitiateTransfer(data); + return ftp_initiate_transfer(data, ftpc); } else { if((instate == FTP_LIST) && (ftpcode == 450)) { /* simply no matching files in the dir listing */ - ftp->transfer = PPTRANSFER_NONE; /* don't download anything */ - state(data, FTP_STOP); /* this phase is over */ + ftp->transfer = PPTRANSFER_NONE; /* do not download anything */ + ftp_state(data, ftpc, FTP_STOP); /* this phase is over */ } else { failf(data, "RETR response: %03d", ftpcode); - return instate == FTP_RETR && ftpcode == 550? - CURLE_REMOTE_FILE_NOT_FOUND: + return instate == FTP_RETR && ftpcode == 550 ? + CURLE_REMOTE_FILE_NOT_FOUND : CURLE_FTP_COULDNT_RETR_FILE; } } @@ -2560,12 +2553,12 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, } /* after USER, PASS and ACCT */ -static CURLcode ftp_state_loggedin(struct Curl_easy *data) +static CURLcode ftp_state_loggedin(struct Curl_easy *data, + struct ftp_conn *ftpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - if(conn->bits.ftp_use_control_ssl) { + if(data->conn->bits.ftp_use_control_ssl) { /* PBSZ = PROTECTION BUFFER SIZE. The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: @@ -2580,44 +2573,43 @@ static CURLcode ftp_state_loggedin(struct Curl_easy *data) parameter of '0' to indicate that no buffering is taking place and the data connection should not be encapsulated. */ - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "PBSZ %d", 0); + result = Curl_pp_sendf(data, &ftpc->pp, "PBSZ %d", 0); if(!result) - state(data, FTP_PBSZ); + ftp_state(data, ftpc, FTP_PBSZ); } else { - result = ftp_state_pwd(data, conn); + result = ftp_state_pwd(data, ftpc); } return result; } /* for USER and PASS responses */ static CURLcode ftp_state_user_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; /* some need password anyway, and others just return 2xx ignored */ if((ftpcode == 331) && (ftpc->state == FTP_USER)) { /* 331 Password required for ... (the server requires to send the user's password too) */ result = Curl_pp_sendf(data, &ftpc->pp, "PASS %s", - conn->passwd?conn->passwd:""); + data->conn->passwd ? data->conn->passwd : ""); if(!result) - state(data, FTP_PASS); + ftp_state(data, ftpc, FTP_PASS); } else if(ftpcode/100 == 2) { /* 230 User ... logged in. (the user logged in with or without password) */ - result = ftp_state_loggedin(data); + result = ftp_state_loggedin(data, ftpc); } else if(ftpcode == 332) { if(data->set.str[STRING_FTP_ACCOUNT]) { result = Curl_pp_sendf(data, &ftpc->pp, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]); if(!result) - state(data, FTP_ACCT); + ftp_state(data, ftpc, FTP_ACCT); } else { failf(data, "ACCT requested but none available"); @@ -2632,13 +2624,13 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && !ftpc->ftp_trying_alternative) { - /* Ok, USER failed. Let's try the supplied command. */ + /* Ok, USER failed. Let's try the supplied command. */ result = Curl_pp_sendf(data, &ftpc->pp, "%s", data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); if(!result) { ftpc->ftp_trying_alternative = TRUE; - state(data, FTP_USER); + ftp_state(data, ftpc, FTP_USER); } } else { @@ -2651,6 +2643,7 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, /* for ACCT response */ static CURLcode ftp_state_acct_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode) { CURLcode result = CURLE_OK; @@ -2659,27 +2652,30 @@ static CURLcode ftp_state_acct_resp(struct Curl_easy *data, result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ } else - result = ftp_state_loggedin(data); + result = ftp_state_loggedin(data, ftpc); return result; } -static CURLcode ftp_statemachine(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode ftp_pp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int ftpcode; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + struct pingpong *pp; static const char * const ftpauth[] = { "SSL", "TLS" }; size_t nread = 0; + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; + pp = &ftpc->pp; if(pp->sendleft) return Curl_pp_flushsend(data, pp); - result = ftp_readresp(data, sock, pp, &ftpcode, &nread); + result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, &ftpcode, &nread); if(result) return result; @@ -2691,7 +2687,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, /* 230 User logged in - already! Take as 220 if TLS required. */ if(data->set.use_ssl <= CURLUSESSL_TRY || conn->bits.ftp_use_control_ssl) - return ftp_state_user_resp(data, ftpcode); + return ftp_state_user_resp(data, ftpc, ftpcode); } else if(ftpcode != 220) { failf(data, "Got a %03d ftp-server response when 220 was expected", @@ -2719,8 +2715,8 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, #endif if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { - /* We don't have a SSL/TLS control connection yet, but FTPS is - requested. Try a FTPS connection now */ + /* We do not have an SSL/TLS control connection yet, but FTPS is + requested. Try an FTPS connection now */ ftpc->count3 = 0; switch(data->set.ftpsslauth) { @@ -2736,21 +2732,21 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, default: failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", (int)data->set.ftpsslauth); - return CURLE_UNKNOWN_OPTION; /* we don't know what to do */ + return CURLE_UNKNOWN_OPTION; /* we do not know what to do */ } result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); if(!result) - state(data, FTP_AUTH); + ftp_state(data, ftpc, FTP_AUTH); } else - result = ftp_state_user(data, conn); + result = ftp_state_user(data, ftpc, conn); break; case FTP_AUTH: /* we have gotten the response to a previous AUTH command */ - if(pp->cache_size) + if(pp->overflow) return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ /* RFC2228 (page 5) says: @@ -2774,7 +2770,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, if(!result) { conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */ conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */ - result = ftp_state_user(data, conn); + result = ftp_state_user(data, ftpc, conn); } } else if(ftpc->count3 < 1) { @@ -2790,17 +2786,17 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = CURLE_USE_SSL_FAILED; else /* ignore the failure and continue */ - result = ftp_state_user(data, conn); + result = ftp_state_user(data, ftpc, conn); } break; case FTP_USER: case FTP_PASS: - result = ftp_state_user_resp(data, ftpcode); + result = ftp_state_user_resp(data, ftpc, ftpcode); break; case FTP_ACCT: - result = ftp_state_acct_resp(data, ftpcode); + result = ftp_state_acct_resp(data, ftpc, ftpcode); break; case FTP_PBSZ: @@ -2808,14 +2804,14 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, Curl_pp_sendf(data, &ftpc->pp, "PROT %c", data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); if(!result) - state(data, FTP_PROT); + ftp_state(data, ftpc, FTP_PROT); break; case FTP_PROT: if(ftpcode/100 == 2) /* We have enabled SSL for the data connection! */ conn->bits.ftp_use_data_ssl = - (data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE; + (data->set.use_ssl != CURLUSESSL_CONTROL); /* FTP servers typically responds with 500 if they decide to reject our 'P' request */ else if(data->set.use_ssl > CURLUSESSL_CONTROL) @@ -2827,35 +2823,38 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, */ result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC"); if(!result) - state(data, FTP_CCC); + ftp_state(data, ftpc, FTP_CCC); } else - result = ftp_state_pwd(data, conn); + result = ftp_state_pwd(data, ftpc); break; case FTP_CCC: if(ftpcode < 500) { /* First shut down the SSL layer (note: this call will block) */ - result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET); + /* This has only been tested on the proftpd server, and the mod_tls + * code sends a close notify alert without waiting for a close notify + * alert in response. Thus we wait for a close notify alert from the + * server, but we do not send one. Let's hope other servers do + * the same... */ + result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET, + (data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)); if(result) failf(data, "Failed to clear the command channel (CCC)"); } if(!result) /* Then continue as normal */ - result = ftp_state_pwd(data, conn); + result = ftp_state_pwd(data, ftpc); break; case FTP_PWD: if(ftpcode == 257) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ - const size_t buf_size = data->set.buffer_size; - char *dir; + char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ bool entry_extracted = FALSE; - - dir = malloc(nread + 1); - if(!dir) - return CURLE_OUT_OF_MEMORY; + struct dynbuf out; + curlx_dyn_init(&out, 1000); /* Reply format is like 257[rubbish]"" and the @@ -2867,33 +2866,30 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, */ /* scan for the first double-quote for non-standard responses */ - while(ptr < &data->state.buffer[buf_size] - && *ptr != '\n' && *ptr != '\0' && *ptr != '"') + while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') ptr++; if('\"' == *ptr) { /* it started good */ - char *store; - ptr++; - for(store = dir; *ptr;) { + for(ptr++; *ptr; ptr++) { if('\"' == *ptr) { if('\"' == ptr[1]) { /* "quote-doubling" */ - *store = ptr[1]; + result = curlx_dyn_addn(&out, &ptr[1], 1); ptr++; } else { /* end of path */ - entry_extracted = TRUE; + if(curlx_dyn_len(&out)) + entry_extracted = TRUE; break; /* get out of this loop */ } } else - *store = *ptr; - store++; - ptr++; + result = curlx_dyn_addn(&out, ptr, 1); + if(result) + return result; } - *store = '\0'; /* null-terminate */ } if(entry_extracted) { /* If the path name does not look like an absolute path (i.e.: it @@ -2907,6 +2903,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, The method used here is to check the server OS: we do it only if the path name looks strange to minimize overhead on other systems. */ + char *dir = curlx_dyn_ptr(&out); if(!ftpc->server_os && dir[0] != '/') { result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); @@ -2914,52 +2911,56 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, free(dir); return result; } - Curl_safefree(ftpc->entrypath); + free(ftpc->entrypath); ftpc->entrypath = dir; /* remember this */ infof(data, "Entry path is '%s'", ftpc->entrypath); /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; - state(data, FTP_SYST); + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; + ftp_state(data, ftpc, FTP_SYST); break; } - Curl_safefree(ftpc->entrypath); + free(ftpc->entrypath); ftpc->entrypath = dir; /* remember this */ infof(data, "Entry path is '%s'", ftpc->entrypath); /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; } else { - /* couldn't get the path */ - free(dir); + /* could not get the path */ + curlx_dyn_free(&out); infof(data, "Failed to figure out path"); } } - state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); break; case FTP_SYST: if(ftpcode == 215) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ + char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ char *os; - char *store; - - os = malloc(nread + 1); - if(!os) - return CURLE_OUT_OF_MEMORY; + char *start; /* Reply format is like 215 */ while(*ptr == ' ') ptr++; - for(store = os; *ptr && *ptr != ' ';) - *store++ = *ptr++; - *store = '\0'; /* null-terminate */ + for(start = ptr; *ptr && *ptr != ' '; ptr++) + ; + os = Curl_memdup0(start, ptr - start); + if(!os) + return CURLE_OUT_OF_MEMORY; /* Check for special servers here. */ - if(strcasecompare(os, "OS/400")) { /* Force OS400 name format 1. */ result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); @@ -2968,33 +2969,33 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, return result; } /* remember target server OS */ - Curl_safefree(ftpc->server_os); + free(ftpc->server_os); ftpc->server_os = os; - state(data, FTP_NAMEFMT); + ftp_state(data, ftpc, FTP_NAMEFMT); break; } /* Nothing special for the target server. */ /* remember target server OS */ - Curl_safefree(ftpc->server_os); + free(ftpc->server_os); ftpc->server_os = os; } else { /* Cannot identify server OS. Continue anyway and cross fingers. */ } - state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); break; case FTP_NAMEFMT: if(ftpcode == 250) { /* Name format change successful: reload initial path. */ - ftp_state_pwd(data, conn); + ftp_state_pwd(data, ftpc); break; } - state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); break; case FTP_QUOTE: @@ -3007,7 +3008,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = CURLE_QUOTE_ERROR; } else - result = ftp_state_quote(data, FALSE, ftpc->state); + result = ftp_state_quote(data, ftpc, ftp, FALSE, ftpc->state); break; case FTP_CWD: @@ -3026,12 +3027,12 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s", ftpc->dirs[ftpc->cwdcount - 1]); if(!result) - state(data, FTP_MKD); + ftp_state(data, ftpc, FTP_MKD); } else { /* return failure */ failf(data, "Server denied you to change to the given directory"); - ftpc->cwdfail = TRUE; /* don't remember this path as we failed + ftpc->cwdfail = TRUE; /* do not remember this path as we failed to enter it */ result = CURLE_REMOTE_ACCESS_DENIED; } @@ -3044,7 +3045,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->dirs[ftpc->cwdcount - 1]); else - result = ftp_state_mdtm(data); + result = ftp_state_mdtm(data, ftpc, ftp); } break; @@ -3055,7 +3056,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = CURLE_REMOTE_ACCESS_DENIED; } else { - state(data, FTP_CWD); + ftp_state(data, ftpc, FTP_CWD); /* send CWD */ result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->dirs[ftpc->cwdcount - 1]); @@ -3063,25 +3064,25 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, break; case FTP_MDTM: - result = ftp_state_mdtm_resp(data, ftpcode); + result = ftp_state_mdtm_resp(data, ftpc, ftp, ftpcode); break; case FTP_TYPE: case FTP_LIST_TYPE: case FTP_RETR_TYPE: case FTP_STOR_TYPE: - result = ftp_state_type_resp(data, ftpcode, ftpc->state); + result = ftp_state_type_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_SIZE: case FTP_RETR_SIZE: case FTP_STOR_SIZE: - result = ftp_state_size_resp(data, ftpcode, ftpc->state); + result = ftp_state_size_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_REST: case FTP_RETR_REST: - result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state); + result = ftp_state_rest_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_PRET: @@ -3090,31 +3091,30 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, failf(data, "PRET command not accepted: %03d", ftpcode); return CURLE_FTP_PRET_FAILED; } - result = ftp_state_use_pasv(data, conn); + result = ftp_state_use_pasv(data, ftpc, conn); break; case FTP_PASV: - result = ftp_state_pasv_resp(data, ftpcode); + result = ftp_state_pasv_resp(data, ftpc, ftpcode); break; case FTP_PORT: - result = ftp_state_port_resp(data, ftpcode); + result = ftp_state_port_resp(data, ftpc, ftp, ftpcode); break; case FTP_LIST: case FTP_RETR: - result = ftp_state_get_resp(data, ftpcode, ftpc->state); + result = ftp_state_get_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_STOR: - result = ftp_state_stor_resp(data, ftpcode, ftpc->state); + result = ftp_state_stor_resp(data, ftpc, ftpcode, ftpc->state); break; case FTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ - state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); break; } } /* if(ftpcode) */ @@ -3124,29 +3124,37 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, /* called repeatedly until done from multi.c */ -static CURLcode ftp_multi_statemach(struct Curl_easy *data, - bool *done) +static CURLcode ftp_statemach(struct Curl_easy *data, + struct ftp_conn *ftpc, + bool *done) { - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result = Curl_pp_statemach(data, &ftpc->pp, FALSE, FALSE); /* Check for the state outside of the Curl_socket_check() return code checks since at times we are in fact already in this state when this function gets called. */ - *done = (ftpc->state == FTP_STOP) ? TRUE : FALSE; + *done = (ftpc->state == FTP_STOP); return result; } +/* called repeatedly until done from multi.c */ +static CURLcode ftp_multi_statemach(struct Curl_easy *data, + bool *done) +{ + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + return ftpc ? ftp_statemach(data, ftpc, done) : CURLE_FAILED_INIT; +} + static CURLcode ftp_block_statemach(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc) { - struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; CURLcode result = CURLE_OK; while(ftpc->state != FTP_STOP) { + if(ftpc->shutdown) + CURL_TRC_FTP(data, "in shutdown, waiting for server response"); result = Curl_pp_statemach(data, pp, TRUE, TRUE /* disconnecting */); if(result) break; @@ -3168,17 +3176,19 @@ static CURLcode ftp_connect(struct Curl_easy *data, { CURLcode result; struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct pingpong *pp; *done = FALSE; /* default to not done yet */ - + if(!ftpc) + return CURLE_FAILED_INIT; + pp = &ftpc->pp; /* We always support persistent connections on ftp */ connkeep(conn, "FTP default"); - PINGPONG_SETUP(pp, ftp_statemachine, ftp_endofresp); + PINGPONG_SETUP(pp, ftp_pp_statemachine, ftp_endofresp); - if(conn->handler->flags & PROTOPT_SSL) { + if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { /* BLOCKING */ result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); if(result) @@ -3186,14 +3196,13 @@ static CURLcode ftp_connect(struct Curl_easy *data, conn->bits.ftp_use_control_ssl = TRUE; } - Curl_pp_setup(pp); /* once per transfer */ - Curl_pp_init(data, pp); /* init the generic pingpong data */ + Curl_pp_init(pp); /* once per transfer */ /* When we connect, we start in the state where we await the 220 response */ - state(data, FTP_WAIT220); + ftp_state(data, ftpc, FTP_WAIT220); - result = ftp_multi_statemach(data, done); + result = ftp_statemach(data, ftpc, done); return result; } @@ -3211,18 +3220,19 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct pingpong *pp; ssize_t nread; int ftpcode; CURLcode result = CURLE_OK; char *rawPath = NULL; size_t pathLen = 0; - if(!ftp) + if(!ftp || !ftpc) return CURLE_OK; + pp = &ftpc->pp; switch(status) { case CURLE_BAD_DOWNLOAD_RESUME: case CURLE_FTP_WEIRD_PASV_REPLY: @@ -3238,14 +3248,13 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, case CURLE_REMOTE_FILE_NOT_FOUND: case CURLE_WRITE_ERROR: /* the connection stays alive fine even though this happened */ - /* fall-through */ - case CURLE_OK: /* doesn't affect the control connection's status */ + case CURLE_OK: /* does not affect the control connection's status */ if(!premature) break; /* until we cope better with prematurely ended requests, let them * fallback as if in complete failure */ - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* by default, an error means the control connection is wedged and should not be used anymore */ ftpc->ctl_valid = FALSE; @@ -3258,9 +3267,9 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, if(data->state.wildcardmatch) { if(data->set.chunk_end && ftpc->file) { - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } ftpc->known_filesize = -1; } @@ -3287,7 +3296,8 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, if(data->set.ftp_filemethod == FTPFILE_NOCWD) pathLen = 0; /* relative path => working directory is FTP home */ else - pathLen -= ftpc->file?strlen(ftpc->file):0; /* file is url-decoded */ + /* file is url-decoded */ + pathLen -= ftpc->file ? strlen(ftpc->file) : 0; rawPath[pathLen] = '\0'; ftpc->prevpath = rawPath; @@ -3305,13 +3315,13 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, /* free the dir tree and file parts */ freedirs(ftpc); - /* shut down the socket to inform the server we're done */ + /* shut down the socket to inform the server we are done */ -#ifdef _WIN32_WCE +#ifdef UNDER_CE shutdown(conn->sock[SECONDARYSOCKET], 2); /* SD_BOTH */ #endif - if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { + if(Curl_conn_is_setup(conn, SECONDARYSOCKET)) { if(!result && ftpc->dont_check && data->req.maxdownload > 0) { /* partial download completed */ result = Curl_pp_sendf(data, pp, "%s", "ABOR"); @@ -3323,7 +3333,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, } } - close_secondarysocket(data, conn); + close_secondarysocket(data, ftpc); } if(!result && (ftp->transfer == PPTRANSFER_BODY) && ftpc->ctl_valid && @@ -3337,7 +3347,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, timediff_t old_time = pp->response_time; pp->response_time = 60*1000; /* give it only a minute for now */ - pp->response = Curl_now(); /* timeout relative now */ + pp->response = curlx_now(); /* timeout relative now */ result = Curl_GetFTPResponse(data, &nread, &ftpcode); @@ -3349,10 +3359,8 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, connclose(conn, "Timeout or similar in FTP DONE operation"); /* close */ } - if(result) { - Curl_safefree(ftp->pathalloc); + if(result) return result; - } if(ftpc->dont_check && data->req.maxdownload > 0) { /* we have just sent ABOR and there is no reliable way to check if it was @@ -3385,12 +3393,15 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, use checking further */ ; else if(data->state.upload) { - if((-1 != data->state.infilesize) && - (data->state.infilesize != data->req.writebytecount) && - !data->set.crlf && - (ftp->transfer == PPTRANSFER_BODY)) { - failf(data, "Uploaded unaligned file size (%" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes)", + if((ftp->transfer == PPTRANSFER_BODY) && + (data->state.infilesize != -1) && /* upload with known size */ + ((!data->set.crlf && !data->state.prefer_ascii && /* no conversion */ + (data->state.infilesize != data->req.writebytecount)) || + ((data->set.crlf || data->state.prefer_ascii) && /* maybe crlf conv */ + (data->state.infilesize > data->req.writebytecount)) + )) { + failf(data, "Uploaded unaligned file size (%" FMT_OFF_T + " out of %" FMT_OFF_T " bytes)", data->req.writebytecount, data->state.infilesize); result = CURLE_PARTIAL_FILE; } @@ -3398,22 +3409,14 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, else { if((-1 != data->req.size) && (data->req.size != data->req.bytecount) && -#ifdef CURL_DO_LINEEND_CONV - /* Most FTP servers don't adjust their file SIZE response for CRLFs, so - * we'll check to see if the discrepancy can be explained by the number - * of CRLFs we've changed to LFs. - */ - ((data->req.size + data->state.crlf_conversions) != - data->req.bytecount) && -#endif /* CURL_DO_LINEEND_CONV */ (data->req.maxdownload != data->req.bytecount)) { - failf(data, "Received only partial file: %" CURL_FORMAT_CURL_OFF_T - " bytes", data->req.bytecount); + failf(data, "Received only partial file: %" FMT_OFF_T " bytes", + data->req.bytecount); result = CURLE_PARTIAL_FILE; } else if(!ftpc->dont_check && !data->req.bytecount && - (data->req.size>0)) { + (data->req.size > 0)) { failf(data, "No data was received"); result = CURLE_FTP_COULDNT_RETR_FILE; } @@ -3425,8 +3428,8 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, /* Send any post-transfer QUOTE strings? */ if(!status && !result && !premature && data->set.postquote) - result = ftp_sendquote(data, conn, data->set.postquote); - Curl_safefree(ftp->pathalloc); + result = ftp_sendquote(data, ftpc, data->set.postquote); + CURL_TRC_FTP(data, "[%s] done, result=%d", FTP_CSTATE(ftpc), result); return result; } @@ -3442,10 +3445,10 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, static CURLcode ftp_sendquote(struct Curl_easy *data, - struct connectdata *conn, struct curl_slist *quote) + struct ftp_conn *ftpc, + struct curl_slist *quote) { struct curl_slist *item; - struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; item = quote; @@ -3460,7 +3463,7 @@ CURLcode ftp_sendquote(struct Curl_easy *data, /* if a command starts with an asterisk, which a legal FTP command never can, the command will be allowed to fail without it causing any aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + is successful, whatever the server responds. */ if(cmd[0] == '*') { cmd++; @@ -3469,7 +3472,7 @@ CURLcode ftp_sendquote(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); if(!result) { - pp->response = Curl_now(); /* timeout relative now */ + pp->response = curlx_now(); /* timeout relative now */ result = Curl_GetFTPResponse(data, &nread, &ftpcode); } if(result) @@ -3493,10 +3496,10 @@ CURLcode ftp_sendquote(struct Curl_easy *data, * * Returns TRUE if we in the current situation should send TYPE */ -static int ftp_need_type(struct connectdata *conn, +static int ftp_need_type(struct ftp_conn *ftpc, bool ascii_wanted) { - return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); + return ftpc->transfertype != (ascii_wanted ? 'A' : 'I'); } /*********************************************************************** @@ -3508,21 +3511,21 @@ static int ftp_need_type(struct connectdata *conn, * If the transfer type is not sent, simulate on OK response in newstate */ static CURLcode ftp_nb_type(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, + struct FTP *ftp, bool ascii, ftpstate newstate) { - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result; - char want = (char)(ascii?'A':'I'); + char want = (char)(ascii ? 'A' : 'I'); if(ftpc->transfertype == want) { - state(data, newstate); - return ftp_state_type_resp(data, 200, newstate); + ftp_state(data, ftpc, newstate); + return ftp_state_type_resp(data, ftpc, ftp, 200, newstate); } result = Curl_pp_sendf(data, &ftpc->pp, "TYPE %c", want); if(!result) { - state(data, newstate); + ftp_state(data, ftpc, newstate); /* keep track of our current transfer type */ ftpc->transfertype = want; @@ -3535,7 +3538,7 @@ static CURLcode ftp_nb_type(struct Curl_easy *data, * ftp_pasv_verbose() * * This function only outputs some informationals about this second connection - * when we've issued a PASV command before and thus we have connected to a + * when we have issued a PASV command before and thus we have connected to a * possibly new IP address. * */ @@ -3543,7 +3546,7 @@ static CURLcode ftp_nb_type(struct Curl_easy *data, static void ftp_pasv_verbose(struct Curl_easy *data, struct Curl_addrinfo *ai, - char *newhost, /* ascii version */ + char *newhost, /* ASCII version */ int port) { char buf[256]; @@ -3566,72 +3569,73 @@ ftp_pasv_verbose(struct Curl_easy *data, static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) { struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); CURLcode result = CURLE_OK; bool connected = FALSE; bool complete = FALSE; - /* the ftp struct is inited in ftp_connect(). If we are connecting to an HTTP * proxy then the state will not be valid until after that connection is * complete */ - struct FTP *ftp = NULL; - - /* if the second connection isn't done yet, wait for it to have - * connected to the remote host. When using proxy tunneling, this - * means the tunnel needs to have been establish. However, we - * can not expect the remote host to talk to us in any way yet. - * So, when using ftps: the SSL handshake will not start until we - * tell the remote server that we are there. */ + + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; + /* if the second connection has been set up, try to connect it fully + * to the remote host. This may not complete at this time, for several + * reasons: + * - we do EPTR and the server will not connect to our listen socket + * until we send more FTP commands + * - an SSL filter is in place and the server will not start the TLS + * handshake until we send more FTP commands + */ if(conn->cfilter[SECONDARYSOCKET]) { + bool is_eptr = Curl_conn_is_tcp_listen(data, SECONDARYSOCKET); result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); - if(result || !Curl_conn_is_ip_connected(data, SECONDARYSOCKET)) { - if(result && (ftpc->count1 == 0)) { + if(result || (!connected && !is_eptr && + !Curl_conn_is_ip_connected(data, SECONDARYSOCKET))) { + if(result && !is_eptr && (ftpc->count1 == 0)) { *completep = -1; /* go back to DOING please */ /* this is a EPSV connect failing, try PASV instead */ - return ftp_epsv_disable(data, conn); + return ftp_epsv_disable(data, ftpc, conn); } + *completep = (int)complete; return result; } } - /* Curl_proxy_connect might have moved the protocol state */ - ftp = data->req.p.ftp; - if(ftpc->state) { /* already in a state so skip the initial commands. They are only done to kickstart the do_more state */ - result = ftp_multi_statemach(data, &complete); + result = ftp_statemach(data, ftpc, &complete); *completep = (int)complete; - /* if we got an error or if we don't wait for a data connection return + /* if we got an error or if we do not wait for a data connection return immediately */ if(result || !ftpc->wait_data_conn) return result; /* if we reach the end of the FTP state machine here, *complete will be TRUE but so is ftpc->wait_data_conn, which says we need to wait for the - data connection and therefore we're not actually complete */ + data connection and therefore we are not actually complete */ *completep = 0; } if(ftp->transfer <= PPTRANSFER_INFO) { - /* a transfer is about to take place, or if not a file name was given - so we'll do a SIZE on it later and then we need the right TYPE first */ + /* a transfer is about to take place, or if not a filename was given so we + will do a SIZE on it later and then we need the right TYPE first */ if(ftpc->wait_data_conn) { bool serv_conned; - result = ReceivedServerConnect(data, &serv_conned); + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &serv_conned); if(result) return result; /* Failed to accept data connection */ if(serv_conned) { /* It looks data connection is established */ - result = AcceptServerConnect(data); ftpc->wait_data_conn = FALSE; - if(!result) - result = InitiateTransfer(data); + result = ftp_initiate_transfer(data, ftpc); if(result) return result; @@ -3639,15 +3643,24 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) *completep = 1; /* this state is now complete when the server has connected back to us */ } + else { + result = ftp_check_ctrl_on_data_wait(data, ftpc); + if(result) + return result; + } } else if(data->state.upload) { - result = ftp_nb_type(data, conn, data->state.prefer_ascii, + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, FTP_STOR_TYPE); if(result) return result; - result = ftp_multi_statemach(data, &complete); - *completep = (int)complete; + result = ftp_statemach(data, ftpc, &complete); + /* ftp_nb_type() might have skipped sending `TYPE A|I` when not + * deemed necessary and directly sent `STORE name`. If this was + * then complete, but we are still waiting on the data connection, + * the transfer has not been initiated yet. */ + *completep = (int)(ftpc->wait_data_conn ? 0 : complete); } else { /* download */ @@ -3656,7 +3669,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) result = Curl_range(data); if(result == CURLE_OK && data->req.maxdownload >= 0) { - /* Don't check for successful transfer */ + /* Do not check for successful transfer */ ftpc->dont_check = TRUE; } @@ -3669,32 +3682,33 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) /* But only if a body transfer was requested. */ if(ftp->transfer == PPTRANSFER_BODY) { - result = ftp_nb_type(data, conn, TRUE, FTP_LIST_TYPE); + result = ftp_nb_type(data, ftpc, ftp, TRUE, FTP_LIST_TYPE); if(result) return result; } /* otherwise just fall through */ } else { - result = ftp_nb_type(data, conn, data->state.prefer_ascii, + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, FTP_RETR_TYPE); if(result) return result; } - result = ftp_multi_statemach(data, &complete); + result = ftp_statemach(data, ftpc, &complete); *completep = (int)complete; } return result; } /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); if(!ftpc->wait_data_conn) { /* no waiting for the data connection so this is now complete */ *completep = 1; - DEBUGF(infof(data, "DO-MORE phase ends with %d", (int)result)); + CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_CSTATE(ftpc), + (int)result); } return result; @@ -3712,36 +3726,42 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) static CURLcode ftp_perform(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool *connected, /* connect status after PASV / PORT */ bool *dophase_done) { /* this is FTP and no proxy */ CURLcode result = CURLE_OK; - DEBUGF(infof(data, "DO phase starts")); + CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_CSTATE(ftpc)); if(data->req.no_body) { /* requested no body means no transfer... */ - struct FTP *ftp = data->req.p.ftp; ftp->transfer = PPTRANSFER_INFO; } *dophase_done = FALSE; /* not done yet */ /* start the first command in the DO phase */ - result = ftp_state_quote(data, TRUE, FTP_QUOTE); + result = ftp_state_quote(data, ftpc, ftp, TRUE, FTP_QUOTE); if(result) return result; /* run the state-machine */ - result = ftp_multi_statemach(data, dophase_done); + result = ftp_statemach(data, ftpc, dophase_done); *connected = Curl_conn_is_connected(data->conn, SECONDARYSOCKET); - infof(data, "ftp_perform ends with SECONDARY: %d", *connected); + if(*connected) + infof(data, "[FTP] [%s] perform, DATA connection established", + FTP_CSTATE(ftpc)); + else + CURL_TRC_FTP(data, "[%s] perform, awaiting DATA connect", + FTP_CSTATE(ftpc)); if(*dophase_done) { - DEBUGF(infof(data, "DO phase is complete1")); + CURL_TRC_FTP(data, "[%s] DO phase is complete1", FTP_CSTATE(ftpc)); } return result; @@ -3755,10 +3775,11 @@ static void wc_data_dtor(void *ptr) free(ftpwc); } -static CURLcode init_wc_data(struct Curl_easy *data) +static CURLcode init_wc_data(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { char *last_slash; - struct FTP *ftp = data->req.p.ftp; char *path = ftp->path; struct WildcardData *wildcard = data->wildcard; CURLcode result = CURLE_OK; @@ -3769,8 +3790,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) last_slash++; if(last_slash[0] == '\0') { wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(data); - return result; + return ftp_parse_url_path(data, ftpc, ftp); } wildcard->pattern = strdup(last_slash); if(!wildcard->pattern) @@ -3786,8 +3806,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) } else { /* only list */ wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(data); - return result; + return ftp_parse_url_path(data, ftpc, ftp); } } @@ -3815,8 +3834,8 @@ static CURLcode init_wc_data(struct Curl_easy *data) if(data->set.ftp_filemethod == FTPFILE_NOCWD) data->set.ftp_filemethod = FTPFILE_MULTICWD; - /* try to parse ftp url */ - result = ftp_parse_url_path(data); + /* try to parse ftp URL */ + result = ftp_parse_url_path(data, ftpc, ftp); if(result) { goto fail; } @@ -3850,16 +3869,17 @@ static CURLcode init_wc_data(struct Curl_easy *data) return result; } -static CURLcode wc_statemach(struct Curl_easy *data) +static CURLcode wc_statemach(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { struct WildcardData * const wildcard = data->wildcard; - struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; for(;;) { switch(wildcard->state) { case CURLWC_INIT: - result = init_wc_data(data); + result = init_wc_data(data, ftpc, ftp); if(wildcard->state == CURLWC_CLEAN) /* only listing! */ return result; @@ -3881,7 +3901,7 @@ static CURLcode wc_statemach(struct Curl_easy *data) wildcard->state = CURLWC_CLEAN; continue; } - if(wildcard->filelist.size == 0) { + if(Curl_llist_count(&wildcard->filelist) == 0) { /* no corresponding file */ wildcard->state = CURLWC_CLEAN; return CURLE_REMOTE_FILE_NOT_FOUND; @@ -3891,9 +3911,8 @@ static CURLcode wc_statemach(struct Curl_easy *data) case CURLWC_DOWNLOADING: { /* filelist has at least one file, lets get first one */ - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct curl_fileinfo *finfo = wildcard->filelist.head->ptr; - struct FTP *ftp = data->req.p.ftp; + struct Curl_llist_node *head = Curl_llist_head(&wildcard->filelist); + struct curl_fileinfo *finfo = Curl_node_elem(head); char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename); if(!tmp_path) @@ -3906,10 +3925,11 @@ static CURLcode wc_statemach(struct Curl_easy *data) infof(data, "Wildcard - START of \"%s\"", finfo->filename); if(data->set.chunk_bgn) { long userresponse; - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); userresponse = data->set.chunk_bgn( - finfo, data->set.wildcardptr, (int)wildcard->filelist.size); - Curl_set_in_callback(data, false); + finfo, data->set.wildcardptr, + (int)Curl_llist_count(&wildcard->filelist)); + Curl_set_in_callback(data, FALSE); switch(userresponse) { case CURL_CHUNK_BGN_FUNC_SKIP: infof(data, "Wildcard - \"%s\" skipped by user", @@ -3929,14 +3949,15 @@ static CURLcode wc_statemach(struct Curl_easy *data) if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) ftpc->known_filesize = finfo->size; - result = ftp_parse_url_path(data); + result = ftp_parse_url_path(data, ftpc, ftp); if(result) return result; - /* we don't need the Curl_fileinfo of first file anymore */ - Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); + /* we do not need the Curl_fileinfo of first file anymore */ + Curl_node_remove(Curl_llist_head(&wildcard->filelist)); - if(wildcard->filelist.size == 0) { /* remains only one file to down. */ + if(Curl_llist_count(&wildcard->filelist) == 0) { + /* remains only one file to down. */ wildcard->state = CURLWC_CLEAN; /* after that will be ftp_do called once again and no transfer will be done because of CURLWC_CLEAN state */ @@ -3947,12 +3968,12 @@ static CURLcode wc_statemach(struct Curl_easy *data) case CURLWC_SKIP: { if(data->set.chunk_end) { - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } - Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); - wildcard->state = (wildcard->filelist.size == 0) ? + Curl_node_remove(Curl_llist_head(&wildcard->filelist)); + wildcard->state = (Curl_llist_count(&wildcard->filelist) == 0) ? CURLWC_CLEAN : CURLWC_DOWNLOADING; continue; } @@ -3992,14 +4013,34 @@ static CURLcode wc_statemach(struct Curl_easy *data) static CURLcode ftp_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); *done = FALSE; /* default to false */ + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; ftpc->wait_data_conn = FALSE; /* default to no such wait */ +#ifdef CURL_PREFER_LF_LINEENDS + { + /* FTP data may need conversion. */ + struct Curl_cwriter *ftp_lc_writer; + + result = Curl_cwriter_create(&ftp_lc_writer, data, &ftp_cw_lc, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ftp_lc_writer); + if(result) { + Curl_cwriter_free(data, ftp_lc_writer); + return result; + } + } +#endif /* CURL_PREFER_LF_LINEENDS */ + if(data->state.wildcardmatch) { - result = wc_statemach(data); + result = wc_statemach(data, ftpc, ftp); if(data->wildcard->state == CURLWC_SKIP || data->wildcard->state == CURLWC_DONE) { /* do not call ftp_regular_transfer */ @@ -4009,12 +4050,12 @@ static CURLcode ftp_do(struct Curl_easy *data, bool *done) return result; } else { /* no wildcard FSM needed */ - result = ftp_parse_url_path(data); + result = ftp_parse_url_path(data, ftpc, ftp); if(result) return result; } - result = ftp_regular_transfer(data, done); + result = ftp_regular_transfer(data, ftpc, ftp, done); return result; } @@ -4029,24 +4070,26 @@ static CURLcode ftp_do(struct Curl_easy *data, bool *done) * connection. * */ -static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn) +static CURLcode ftp_quit(struct Curl_easy *data, + struct ftp_conn *ftpc) { CURLcode result = CURLE_OK; - if(conn->proto.ftpc.ctl_valid) { - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "QUIT"); + if(ftpc->ctl_valid) { + CURL_TRC_FTP(data, "sending QUIT to close session"); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "QUIT"); if(result) { failf(data, "Failure sending QUIT command: %s", curl_easy_strerror(result)); - conn->proto.ftpc.ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "QUIT command failed"); /* mark for connection closure */ - state(data, FTP_STOP); + ftpc->ctl_valid = FALSE; /* mark control connection as bad */ + connclose(data->conn, "QUIT command failed"); /* mark for closure */ + ftp_state(data, ftpc, FTP_STOP); return result; } - state(data, FTP_QUIT); + ftp_state(data, ftpc, FTP_QUIT); - result = ftp_block_statemach(data, conn); + result = ftp_block_statemach(data, ftpc); } return result; @@ -4063,44 +4106,26 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + if(!ftpc) + return CURLE_FAILED_INIT; /* We cannot send quit unconditionally. If this connection is stale or bad in any way, sending quit and waiting around here will make the disconnect wait in vain and cause more problems than we need to. - ftp_quit() will check the state of ftp->ctl_valid. If it's ok it + ftp_quit() will check the state of ftp->ctl_valid. If it is ok it will try to send the QUIT command, otherwise it will just return. */ + ftpc->shutdown = TRUE; if(dead_connection) ftpc->ctl_valid = FALSE; /* The FTP session may or may not have been allocated/setup at this point! */ - (void)ftp_quit(data, conn); /* ignore errors on the QUIT */ - - if(ftpc->entrypath) { - if(data->state.most_recent_ftp_entrypath == ftpc->entrypath) { - data->state.most_recent_ftp_entrypath = NULL; - } - Curl_safefree(ftpc->entrypath); - } - - freedirs(ftpc); - Curl_safefree(ftpc->account); - Curl_safefree(ftpc->alternative_to_user); - Curl_safefree(ftpc->prevpath); - Curl_safefree(ftpc->server_os); - Curl_pp_disconnect(pp); - Curl_sec_end(conn); + (void)ftp_quit(data, ftpc); /* ignore errors on the QUIT */ return CURLE_OK; } -#ifdef _MSC_VER -/* warning C4706: assignment within conditional expression */ -#pragma warning(disable:4706) -#endif - /*********************************************************************** * * ftp_parse_url_path() @@ -4109,12 +4134,10 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, * */ static -CURLcode ftp_parse_url_path(struct Curl_easy *data) +CURLcode ftp_parse_url_path(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { - /* the ftp struct is already inited in ftp_connect() */ - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; const char *slashPos = NULL; const char *fileName = NULL; CURLcode result = CURLE_OK; @@ -4158,18 +4181,17 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; } - ftpc->dirs[0] = calloc(1, dirlen + 1); + ftpc->dirs[0] = Curl_memdup0(rawPath, dirlen); if(!ftpc->dirs[0]) { free(rawPath); return CURLE_OUT_OF_MEMORY; } - strncpy(ftpc->dirs[0], rawPath, dirlen); ftpc->dirdepth = 1; /* we consider it to be a single dir */ - fileName = slashPos + 1; /* rest is file name */ + fileName = slashPos + 1; /* rest is filename */ } else - fileName = rawPath; /* file name only (or empty) */ + fileName = rawPath; /* filename only (or empty) */ break; default: /* allow pretty much anything */ @@ -4192,7 +4214,8 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) } /* parse the URL path into separate path components */ - while((slashPos = strchr(curPos, '/'))) { + /* !checksrc! disable EQUALSNULL 1 */ + while((slashPos = strchr(curPos, '/')) != NULL) { size_t compLen = slashPos - curPos; /* path starts with a slash: add that as a directory */ @@ -4200,22 +4223,21 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) ++compLen; /* we skip empty path components, like "x//y" since the FTP command - CWD requires a parameter and a non-existent parameter a) doesn't + CWD requires a parameter and a non-existent parameter a) does not work on many servers and b) has no effect on the others. */ if(compLen > 0) { - char *comp = calloc(1, compLen + 1); + char *comp = Curl_memdup0(curPos, compLen); if(!comp) { free(rawPath); return CURLE_OUT_OF_MEMORY; } - strncpy(comp, curPos, compLen); ftpc->dirs[ftpc->dirdepth++] = comp; } curPos = slashPos + 1; } } DEBUGASSERT((size_t)ftpc->dirdepth <= dirAlloc); - fileName = curPos; /* the rest is the file name (or empty) */ + fileName = curPos; /* the rest is the filename (or empty) */ } break; } /* switch */ @@ -4227,8 +4249,8 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) we make it a NULL pointer */ if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { - /* We need a file name when uploading. Return error! */ - failf(data, "Uploading to a URL without a file name"); + /* We need a filename when uploading. Return error! */ + failf(data, "Uploading to a URL without a filename"); free(rawPath); return CURLE_URL_MALFORMAT; } @@ -4238,15 +4260,15 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) ftpc->cwddone = TRUE; /* skip CWD for absolute paths */ else { /* newly created FTP connections are already in entry path */ - const char *oldPath = conn->bits.reuse ? ftpc->prevpath : ""; + const char *oldPath = data->conn->bits.reuse ? ftpc->prevpath : ""; if(oldPath) { size_t n = pathLen; if(data->set.ftp_filemethod == FTPFILE_NOCWD) n = 0; /* CWD to entry for relative paths */ else - n -= ftpc->file?strlen(ftpc->file):0; + n -= ftpc->file ? strlen(ftpc->file) : 0; - if((strlen(oldPath) == n) && !strncmp(rawPath, oldPath, n)) { + if((strlen(oldPath) == n) && rawPath && !strncmp(rawPath, oldPath, n)) { infof(data, "Request has same path as previous transfer"); ftpc->cwddone = TRUE; } @@ -4258,28 +4280,27 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) } /* call this when the DO phase has completed */ -static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) +static CURLcode ftp_dophase_done(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + bool connected) { - struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - if(connected) { int completed; CURLcode result = ftp_do_more(data, &completed); if(result) { - close_secondarysocket(data, conn); + close_secondarysocket(data, ftpc); return result; } } if(ftp->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); else if(!connected) - /* since we didn't connect now, we want do_more to get called */ - conn->bits.do_more = TRUE; + /* since we did not connect now, we want do_more to get called */ + data->conn->bits.do_more = TRUE; ftpc->ctl_valid = TRUE; /* seems good */ @@ -4290,14 +4311,20 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) static CURLcode ftp_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = ftp_multi_statemach(data, dophase_done); + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + CURLcode result; + + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; + result = ftp_statemach(data, ftpc, dophase_done); if(result) - DEBUGF(infof(data, "DO phase failed")); + CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_CSTATE(ftpc)); else if(*dophase_done) { - result = ftp_dophase_done(data, FALSE /* not connected */); + result = ftp_dophase_done(data, ftpc, ftp, FALSE /* not connected */); - DEBUGF(infof(data, "DO phase is complete2")); + CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_CSTATE(ftpc)); } return result; } @@ -4316,12 +4343,12 @@ static CURLcode ftp_doing(struct Curl_easy *data, */ static CURLcode ftp_regular_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool *dophase_done) { CURLcode result = CURLE_OK; bool connected = FALSE; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; data->req.size = -1; /* make sure this is unknown at this point */ Curl_pgrsSetUploadCounter(data, 0); @@ -4331,7 +4358,7 @@ CURLcode ftp_regular_transfer(struct Curl_easy *data, ftpc->ctl_valid = TRUE; /* starts good */ - result = ftp_perform(data, + result = ftp_perform(data, ftpc, ftp, &connected, /* have we connected after PASV/PORT */ dophase_done); /* all commands in the DO-phase done? */ @@ -4341,7 +4368,7 @@ CURLcode ftp_regular_transfer(struct Curl_easy *data, /* the DO phase has not completed yet */ return CURLE_OK; - result = ftp_dophase_done(data, connected); + result = ftp_dophase_done(data, ftpc, ftp, connected); if(result) return result; @@ -4352,23 +4379,53 @@ CURLcode ftp_regular_transfer(struct Curl_easy *data, return result; } +static void ftp_easy_dtor(void *key, size_t klen, void *entry) +{ + struct FTP *ftp = entry; + (void)key; + (void)klen; + Curl_safefree(ftp->pathalloc); + free(ftp); +} + +static void ftp_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ftp_conn *ftpc = entry; + (void)key; + (void)klen; + freedirs(ftpc); + Curl_safefree(ftpc->account); + Curl_safefree(ftpc->alternative_to_user); + Curl_safefree(ftpc->entrypath); + Curl_safefree(ftpc->prevpath); + Curl_safefree(ftpc->server_os); + Curl_pp_disconnect(&ftpc->pp); + free(ftpc); +} + static CURLcode ftp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { char *type; struct FTP *ftp; CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc; + + ftp = calloc(1, sizeof(*ftp)); + if(!ftp || + Curl_meta_set(data, CURL_META_FTP_EASY, ftp, ftp_easy_dtor)) + return CURLE_OUT_OF_MEMORY; - ftp = calloc(sizeof(struct FTP), 1); - if(!ftp) + ftpc = calloc(1, sizeof(*ftpc)); + if(!ftpc || + Curl_conn_meta_set(conn, CURL_META_FTP_CONN, ftpc, ftp_conn_dtor)) return CURLE_OUT_OF_MEMORY; /* clone connection related data that is FTP specific */ if(data->set.str[STRING_FTP_ACCOUNT]) { ftpc->account = strdup(data->set.str[STRING_FTP_ACCOUNT]); if(!ftpc->account) { - free(ftp); + Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); return CURLE_OUT_OF_MEMORY; } } @@ -4377,16 +4434,15 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, strdup(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); if(!ftpc->alternative_to_user) { Curl_safefree(ftpc->account); - free(ftp); + Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); return CURLE_OUT_OF_MEMORY; } } - data->req.p.ftp = ftp; - ftp->path = &data->state.up.path[1]; /* don't include the initial slash */ + ftp->path = &data->state.up.path[1]; /* do not include the initial slash */ /* FTP URLs support an extension like ";type=" that - * we'll try to get now! */ + * we will try to get now! */ type = strstr(ftp->path, ";type="); if(!type) @@ -4421,7 +4477,23 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, ftpc->use_ssl = data->set.use_ssl; ftpc->ccc = data->set.ftp_ccc; + CURL_TRC_FTP(data, "[%s] setup connection -> %d", FTP_CSTATE(ftpc), result); return result; } +bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn) +{ + struct ftp_conn *nftpc = Curl_conn_meta_get(needle, CURL_META_FTP_CONN); + struct ftp_conn *cftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + /* Also match ACCOUNT, ALTERNATIVE-TO-USER, USE_SSL and CCC options */ + if(!nftpc || !cftpc || + Curl_timestrcmp(nftpc->account, cftpc->account) || + Curl_timestrcmp(nftpc->alternative_to_user, + cftpc->alternative_to_user) || + (nftpc->use_ssl != cftpc->use_ssl) || + (nftpc->ccc != cftpc->ccc)) + return FALSE; + return TRUE; +} + #endif /* CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/ftp.h b/Utilities/cmcurl/lib/ftp.h index 977fc883b17..c31aa932861 100644 --- a/Utilities/cmcurl/lib/ftp.h +++ b/Utilities/cmcurl/lib/ftp.h @@ -37,6 +37,9 @@ extern const struct Curl_handler Curl_handler_ftps; CURLcode Curl_GetFTPResponse(struct Curl_easy *data, ssize_t *nread, int *ftpcode); + +bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn); + #endif /* CURL_DISABLE_FTP */ /**************************************************************************** @@ -61,7 +64,7 @@ enum { FTP_STOR_PREQUOTE, FTP_POSTQUOTE, FTP_CWD, /* change dir */ - FTP_MKD, /* if the dir didn't exist */ + FTP_MKD, /* if the dir did not exist */ FTP_MDTM, /* to figure out the datestamp */ FTP_TYPE, /* to set type when doing a head-like request */ FTP_LIST_TYPE, /* set type when about to do a dir list */ @@ -123,9 +126,10 @@ struct ftp_conn { char *account; char *alternative_to_user; char *entrypath; /* the PWD reply when we logged on */ - char *file; /* url-decoded file name (or path) */ + char *file; /* url-decoded filename (or path) */ char **dirs; /* realloc()ed array for path components */ - char *newhost; + char *newhost; /* the (allocated) IP addr or hostname to connect the data + connection to */ char *prevpath; /* url-decoded conn->path from the previous transfer */ char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a and others (A/I or zero) */ @@ -139,9 +143,8 @@ struct ftp_conn { int count1; /* general purpose counter for the state machine */ int count2; /* general purpose counter for the state machine */ int count3; /* general purpose counter for the state machine */ - /* newhost is the (allocated) IP addr or host name to connect the data - connection to */ - unsigned short newport; + unsigned short newport; /* the port of 'newhost' to connect the data + connection to */ ftpstate state; /* always use ftp.c:state() to change state! */ ftpstate state_saved; /* transfer type saved to be reloaded after data connection is established */ @@ -160,8 +163,14 @@ struct ftp_conn { BIT(cwdfail); /* set TRUE if a CWD command fails, as then we must prevent caching the current directory */ BIT(wait_data_conn); /* this is set TRUE if data connection is waited */ + BIT(shutdown); /* connection is being shutdown, e.g. QUIT */ }; +/* meta key for storing `struct FTP` as easy meta data */ +#define CURL_META_FTP_EASY "meta:proto:ftp:easy" +/* meta key for storing `struct ftp_conn` as connection meta data */ +#define CURL_META_FTP_CONN "meta:proto:ftp:conn" + #define DEFAULT_ACCEPT_TIMEOUT 60000 /* milliseconds == one minute */ #endif /* HEADER_CURL_FTP_H */ diff --git a/Utilities/cmcurl/lib/ftplistparser.c b/Utilities/cmcurl/lib/ftplistparser.c index 226d9bc033d..70939d6a76f 100644 --- a/Utilities/cmcurl/lib/ftplistparser.c +++ b/Utilities/cmcurl/lib/ftplistparser.c @@ -46,17 +46,16 @@ #include "urldata.h" #include "fileinfo.h" #include "llist.h" -#include "strtoofft.h" #include "ftp.h" #include "ftplistparser.h" #include "curl_fnmatch.h" -#include "curl_memory.h" #include "multiif.h" -/* The last #include file should be: */ -#include "memdebug.h" +#include "curlx/strparse.h" -/* allocs buffer which will contain one line of LIST command response */ -#define FTP_BUFFER_ALLOCSIZE 160 +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" typedef enum { PL_UNIX_TOTALSIZE = 0, @@ -319,7 +318,7 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, struct curl_fileinfo *finfo = &infop->info; /* set the finfo pointers */ - char *str = Curl_dyn_ptr(&infop->buf); + char *str = curlx_dyn_ptr(&infop->buf); finfo->filename = str + parser->offsets.filename; finfo->strings.group = parser->offsets.group ? str + parser->offsets.group : NULL; @@ -337,7 +336,7 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, compare = Curl_fnmatch; /* filter pattern-corresponding filenames */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { /* discard symlink which is containing multiple " -> " */ @@ -349,10 +348,10 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, else { add = FALSE; } - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(add) { - Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list); + Curl_llist_append(llist, finfo, &infop->list); } else { Curl_fileinfo_cleanup(infop); @@ -364,6 +363,574 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, #define MAX_FTPLIST_BUFFER 10000 /* arbitrarily set */ +static CURLcode unix_filetype(const char c, curlfiletype *t) +{ + switch(c) { + case '-': + *t = CURLFILETYPE_FILE; + break; + case 'd': + *t = CURLFILETYPE_DIRECTORY; + break; + case 'l': + *t = CURLFILETYPE_SYMLINK; + break; + case 'p': + *t = CURLFILETYPE_NAMEDPIPE; + break; + case 's': + *t = CURLFILETYPE_SOCKET; + break; + case 'c': + *t = CURLFILETYPE_DEVICE_CHAR; + break; + case 'b': + *t = CURLFILETYPE_DEVICE_BLOCK; + break; + case 'D': + *t = CURLFILETYPE_DOOR; + break; + default: + return CURLE_FTP_BAD_FILE_LIST; + } + return CURLE_OK; +} + +static CURLcode parse_unix(struct Curl_easy *data, + struct ftp_parselist_data *parser, + struct fileinfo *infop, + const char c) +{ + struct curl_fileinfo *finfo = &infop->info; + size_t len = curlx_dyn_len(&infop->buf); + char *mem = curlx_dyn_ptr(&infop->buf); + CURLcode result = CURLE_OK; + + switch(parser->state.UNIX.main) { + case PL_UNIX_TOTALSIZE: + switch(parser->state.UNIX.sub.total_dirsize) { + case PL_UNIX_TOTALSIZE_INIT: + if(c == 't') { + parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING; + parser->item_length++; + } + else { + parser->state.UNIX.main = PL_UNIX_FILETYPE; + /* continue to fall through */ + } + break; + case PL_UNIX_TOTALSIZE_READING: + parser->item_length++; + if(c == '\r') { + parser->item_length--; + if(len) + curlx_dyn_setlen(&infop->buf, --len); + } + else if(c == '\n') { + mem[parser->item_length - 1] = 0; + if(!strncmp("total ", mem, 6)) { + const char *endptr = mem + 6; + /* here we can deal with directory size, pass the leading + whitespace and then the digits */ + curlx_str_passblanks(&endptr); + while(ISDIGIT(*endptr)) + endptr++; + if(*endptr) { + return CURLE_FTP_BAD_FILE_LIST; + } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + curlx_dyn_reset(&infop->buf); + } + else + return CURLE_FTP_BAD_FILE_LIST; + + } + break; + } + if(parser->state.UNIX.main != PL_UNIX_FILETYPE) + break; + FALLTHROUGH(); + case PL_UNIX_FILETYPE: + result = unix_filetype(c, &finfo->filetype); + if(result) + return result; + parser->state.UNIX.main = PL_UNIX_PERMISSION; + parser->item_length = 0; + parser->item_offset = 1; + break; + case PL_UNIX_PERMISSION: + parser->item_length++; + if((parser->item_length <= 9) && !strchr("rwx-tTsS", c)) + return CURLE_FTP_BAD_FILE_LIST; + + else if(parser->item_length == 10) { + unsigned int perm; + if(c != ' ') + return CURLE_FTP_BAD_FILE_LIST; + + mem[10] = 0; /* terminate permissions */ + perm = ftp_pl_get_permission(mem + parser->item_offset); + if(perm & FTP_LP_MALFORMATED_PERM) + return CURLE_FTP_BAD_FILE_LIST; + + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM; + parser->file_data->info.perm = perm; + parser->offsets.perm = parser->item_offset; + + parser->item_length = 0; + parser->state.UNIX.main = PL_UNIX_HLINKS; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE; + } + break; + case PL_UNIX_HLINKS: + switch(parser->state.UNIX.sub.hlinks) { + case PL_UNIX_HLINKS_PRESPACE: + if(c != ' ') { + if(ISDIGIT(c) && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_HLINKS_NUMBER: + parser->item_length ++; + if(c == ' ') { + const char *p = &mem[parser->item_offset]; + curl_off_t hlinks; + mem[parser->item_offset + parser->item_length - 1] = 0; + + if(!curlx_str_number(&p, &hlinks, LONG_MAX)) { + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; + parser->file_data->info.hardlinks = (long)hlinks; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_USER; + parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE; + } + else if(!ISDIGIT(c)) + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + case PL_UNIX_USER: + switch(parser->state.UNIX.sub.user) { + case PL_UNIX_USER_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; + } + break; + case PL_UNIX_USER_PARSING: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.user = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_GROUP; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_GROUP: + switch(parser->state.UNIX.sub.group) { + case PL_UNIX_GROUP_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; + } + break; + case PL_UNIX_GROUP_NAME: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.group = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_SIZE; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_SIZE: + switch(parser->state.UNIX.sub.size) { + case PL_UNIX_SIZE_PRESPACE: + if(c != ' ') { + if(ISDIGIT(c) && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_SIZE_NUMBER: + parser->item_length++; + if(c == ' ') { + const char *p = mem + parser->item_offset; + curl_off_t fsize; + mem[parser->item_offset + parser->item_length - 1] = 0; + if(!curlx_str_numblanks(&p, &fsize)) { + if(p[0] == '\0' && fsize != CURL_OFF_T_MAX) { + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->file_data->info.size = fsize; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_TIME; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1; + } + } + else if(!ISDIGIT(c)) + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + case PL_UNIX_TIME: + switch(parser->state.UNIX.sub.time) { + case PL_UNIX_TIME_PREPART1: + if(c != ' ') { + if(ISALNUM(c) && len) { + parser->item_offset = len -1; + parser->item_length = 1; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_TIME_PART1: + parser->item_length++; + if(c == ' ') + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; + + else if(!ISALNUM(c) && c != '.') + return CURLE_FTP_BAD_FILE_LIST; + + break; + case PL_UNIX_TIME_PREPART2: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_TIME_PART2: + parser->item_length++; + if(c == ' ') + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; + else if(!ISALNUM(c) && c != '.') + return CURLE_FTP_BAD_FILE_LIST; + break; + case PL_UNIX_TIME_PREPART3: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_TIME_PART3: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length -1] = 0; + parser->offsets.time = parser->item_offset; + if(finfo->filetype == CURLFILETYPE_SYMLINK) { + parser->state.UNIX.main = PL_UNIX_SYMLINK; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE; + } + else { + parser->state.UNIX.main = PL_UNIX_FILENAME; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE; + } + } + else if(!ISALNUM(c) && c != '.' && c != ':') + return CURLE_FTP_BAD_FILE_LIST; + break; + } + break; + case PL_UNIX_FILENAME: + switch(parser->state.UNIX.sub.filename) { + case PL_UNIX_FILENAME_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; + } + break; + case PL_UNIX_FILENAME_NAME: + parser->item_length++; + if(c == '\r') + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; + + else if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + } + break; + case PL_UNIX_FILENAME_WINDOWSEOL: + if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + case PL_UNIX_SYMLINK: + switch(parser->state.UNIX.sub.symlink) { + case PL_UNIX_SYMLINK_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_NAME: + parser->item_length++; + if(c == ' ') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; + + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + + break; + case PL_UNIX_SYMLINK_PRETARGET1: + parser->item_length++; + if(c == '-') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; + + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + else + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + break; + case PL_UNIX_SYMLINK_PRETARGET2: + parser->item_length++; + if(c == '>') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + else + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + + break; + case PL_UNIX_SYMLINK_PRETARGET3: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; + /* now place where is symlink following */ + mem[parser->item_offset + parser->item_length - 4] = 0; + parser->offsets.filename = parser->item_offset; + parser->item_length = 0; + parser->item_offset = 0; + } + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + else + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + break; + case PL_UNIX_SYMLINK_PRETARGET4: + if(c != '\r' && c != '\n' && len) { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; + parser->item_offset = len - 1; + parser->item_length = 1; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + case PL_UNIX_SYMLINK_TARGET: + parser->item_length++; + if(c == '\r') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; + + else if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + break; + case PL_UNIX_SYMLINK_WINDOWSEOL: + if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + } + return CURLE_OK; +} + +static CURLcode parse_winnt(struct Curl_easy *data, + struct ftp_parselist_data *parser, + struct fileinfo *infop, + const char c) +{ + struct curl_fileinfo *finfo = &infop->info; + size_t len = curlx_dyn_len(&infop->buf); + char *mem = curlx_dyn_ptr(&infop->buf); + CURLcode result = CURLE_OK; + + switch(parser->state.NT.main) { + case PL_WINNT_DATE: + parser->item_length++; + if(parser->item_length < 9) { + if(!strchr("0123456789-", c)) { /* only simple control */ + return CURLE_FTP_BAD_FILE_LIST; + } + } + else if(parser->item_length == 9) { + if(c == ' ') { + parser->state.NT.main = PL_WINNT_TIME; + parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + else + return CURLE_FTP_BAD_FILE_LIST; + break; + case PL_WINNT_TIME: + parser->item_length++; + switch(parser->state.NT.sub.time) { + case PL_WINNT_TIME_PRESPACE: + if(!ISBLANK(c)) + parser->state.NT.sub.time = PL_WINNT_TIME_TIME; + break; + case PL_WINNT_TIME_TIME: + if(c == ' ') { + parser->offsets.time = parser->item_offset; + mem[parser->item_offset + parser->item_length -1] = 0; + parser->state.NT.main = PL_WINNT_DIRORSIZE; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE; + parser->item_length = 0; + } + else if(!strchr("APM0123456789:", c)) + return CURLE_FTP_BAD_FILE_LIST; + break; + } + break; + case PL_WINNT_DIRORSIZE: + switch(parser->state.NT.sub.dirorsize) { + case PL_WINNT_DIRORSIZE_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; + } + break; + case PL_WINNT_DIRORSIZE_CONTENT: + parser->item_length ++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + if(strcmp("", mem + parser->item_offset) == 0) { + finfo->filetype = CURLFILETYPE_DIRECTORY; + finfo->size = 0; + } + else { + const char *p = mem + parser->item_offset; + if(curlx_str_numblanks(&p, &finfo->size)) { + return CURLE_FTP_BAD_FILE_LIST; + } + /* correct file type */ + parser->file_data->info.filetype = CURLFILETYPE_FILE; + } + + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->item_length = 0; + parser->state.NT.main = PL_WINNT_FILENAME; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + } + break; + case PL_WINNT_FILENAME: + switch(parser->state.NT.sub.filename) { + case PL_WINNT_FILENAME_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len -1; + parser->item_length = 1; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; + } + break; + case PL_WINNT_FILENAME_CONTENT: + parser->item_length++; + if(!len) + return CURLE_FTP_BAD_FILE_LIST; + if(c == '\r') { + parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; + mem[len - 1] = 0; + } + else if(c == '\n') { + parser->offsets.filename = parser->item_offset; + mem[len - 1] = 0; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + case PL_WINNT_FILENAME_WINEOL: + if(c == '\n') { + parser->offsets.filename = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + } + + return CURLE_OK; +} + size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, void *connptr) { @@ -379,7 +946,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, /* scenario: * 1. call => OK.. * 2. call => OUT_OF_MEMORY (or other error) - * 3. (last) call => is skipped RIGHT HERE and the error is hadled later + * 3. (last) call => is skipped RIGHT HERE and the error is handled later * in wc_statemach() */ goto fail; @@ -391,11 +958,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, } while(i < bufflen) { /* FSM */ - char *mem; - size_t len; /* number of bytes of data in the dynbuf */ char c = buffer[i]; struct fileinfo *infop; - struct curl_fileinfo *finfo; if(!parser->file_data) { /* tmp file data is not allocated yet */ parser->file_data = Curl_fileinfo_alloc(); if(!parser->file_data) { @@ -404,627 +968,31 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, } parser->item_offset = 0; parser->item_length = 0; - Curl_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER); + curlx_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER); } infop = parser->file_data; - finfo = &infop->info; - if(Curl_dyn_addn(&infop->buf, &c, 1)) { + if(curlx_dyn_addn(&infop->buf, &c, 1)) { parser->error = CURLE_OUT_OF_MEMORY; goto fail; } - len = Curl_dyn_len(&infop->buf); - mem = Curl_dyn_ptr(&infop->buf); switch(parser->os_type) { case OS_TYPE_UNIX: - switch(parser->state.UNIX.main) { - case PL_UNIX_TOTALSIZE: - switch(parser->state.UNIX.sub.total_dirsize) { - case PL_UNIX_TOTALSIZE_INIT: - if(c == 't') { - parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING; - parser->item_length++; - } - else { - parser->state.UNIX.main = PL_UNIX_FILETYPE; - /* start FSM again not considering size of directory */ - Curl_dyn_reset(&infop->buf); - continue; - } - break; - case PL_UNIX_TOTALSIZE_READING: - parser->item_length++; - if(c == '\r') { - parser->item_length--; - Curl_dyn_setlen(&infop->buf, --len); - } - else if(c == '\n') { - mem[parser->item_length - 1] = 0; - if(!strncmp("total ", mem, 6)) { - char *endptr = mem + 6; - /* here we can deal with directory size, pass the leading - whitespace and then the digits */ - while(ISBLANK(*endptr)) - endptr++; - while(ISDIGIT(*endptr)) - endptr++; - if(*endptr) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - Curl_dyn_reset(&infop->buf); - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - } - break; - case PL_UNIX_FILETYPE: - switch(c) { - case '-': - finfo->filetype = CURLFILETYPE_FILE; - break; - case 'd': - finfo->filetype = CURLFILETYPE_DIRECTORY; - break; - case 'l': - finfo->filetype = CURLFILETYPE_SYMLINK; - break; - case 'p': - finfo->filetype = CURLFILETYPE_NAMEDPIPE; - break; - case 's': - finfo->filetype = CURLFILETYPE_SOCKET; - break; - case 'c': - finfo->filetype = CURLFILETYPE_DEVICE_CHAR; - break; - case 'b': - finfo->filetype = CURLFILETYPE_DEVICE_BLOCK; - break; - case 'D': - finfo->filetype = CURLFILETYPE_DOOR; - break; - default: - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_PERMISSION; - parser->item_length = 0; - parser->item_offset = 1; - break; - case PL_UNIX_PERMISSION: - parser->item_length++; - if(parser->item_length <= 9) { - if(!strchr("rwx-tTsS", c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - else if(parser->item_length == 10) { - unsigned int perm; - if(c != ' ') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - mem[10] = 0; /* terminate permissions */ - perm = ftp_pl_get_permission(mem + parser->item_offset); - if(perm & FTP_LP_MALFORMATED_PERM) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM; - parser->file_data->info.perm = perm; - parser->offsets.perm = parser->item_offset; - - parser->item_length = 0; - parser->state.UNIX.main = PL_UNIX_HLINKS; - parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE; - } - break; - case PL_UNIX_HLINKS: - switch(parser->state.UNIX.sub.hlinks) { - case PL_UNIX_HLINKS_PRESPACE: - if(c != ' ') { - if(ISDIGIT(c)) { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_HLINKS_NUMBER: - parser->item_length ++; - if(c == ' ') { - char *p; - long int hlinks; - mem[parser->item_offset + parser->item_length - 1] = 0; - hlinks = strtol(mem + parser->item_offset, &p, 10); - if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) { - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; - parser->file_data->info.hardlinks = hlinks; - } - parser->item_length = 0; - parser->item_offset = 0; - parser->state.UNIX.main = PL_UNIX_USER; - parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE; - } - else if(!ISDIGIT(c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_USER: - switch(parser->state.UNIX.sub.user) { - case PL_UNIX_USER_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; - } - break; - case PL_UNIX_USER_PARSING: - parser->item_length++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.user = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_GROUP; - parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE; - parser->item_offset = 0; - parser->item_length = 0; - } - break; - } - break; - case PL_UNIX_GROUP: - switch(parser->state.UNIX.sub.group) { - case PL_UNIX_GROUP_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; - } - break; - case PL_UNIX_GROUP_NAME: - parser->item_length++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.group = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_SIZE; - parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE; - parser->item_offset = 0; - parser->item_length = 0; - } - break; - } - break; - case PL_UNIX_SIZE: - switch(parser->state.UNIX.sub.size) { - case PL_UNIX_SIZE_PRESPACE: - if(c != ' ') { - if(ISDIGIT(c)) { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_SIZE_NUMBER: - parser->item_length++; - if(c == ' ') { - char *p; - curl_off_t fsize; - mem[parser->item_offset + parser->item_length - 1] = 0; - if(!curlx_strtoofft(mem + parser->item_offset, - &p, 10, &fsize)) { - if(p[0] == '\0' && fsize != CURL_OFF_T_MAX && - fsize != CURL_OFF_T_MIN) { - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; - parser->file_data->info.size = fsize; - } - parser->item_length = 0; - parser->item_offset = 0; - parser->state.UNIX.main = PL_UNIX_TIME; - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1; - } - } - else if(!ISDIGIT(c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_TIME: - switch(parser->state.UNIX.sub.time) { - case PL_UNIX_TIME_PREPART1: - if(c != ' ') { - if(ISALNUM(c)) { - parser->item_offset = len -1; - parser->item_length = 1; - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_TIME_PART1: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; - } - else if(!ISALNUM(c) && c != '.') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_TIME_PREPART2: - parser->item_length++; - if(c != ' ') { - if(ISALNUM(c)) { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_TIME_PART2: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; - } - else if(!ISALNUM(c) && c != '.') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_TIME_PREPART3: - parser->item_length++; - if(c != ' ') { - if(ISALNUM(c)) { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_TIME_PART3: - parser->item_length++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length -1] = 0; - parser->offsets.time = parser->item_offset; - /* - if(ftp_pl_gettime(parser, finfo->mem + parser->item_offset)) { - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME; - } - */ - if(finfo->filetype == CURLFILETYPE_SYMLINK) { - parser->state.UNIX.main = PL_UNIX_SYMLINK; - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE; - } - else { - parser->state.UNIX.main = PL_UNIX_FILENAME; - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE; - } - } - else if(!ISALNUM(c) && c != '.' && c != ':') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_FILENAME: - switch(parser->state.UNIX.sub.filename) { - case PL_UNIX_FILENAME_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; - } - break; - case PL_UNIX_FILENAME_NAME: - parser->item_length++; - if(c == '\r') { - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; - } - else if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.filename = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_FILETYPE; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - } - break; - case PL_UNIX_FILENAME_WINDOWSEOL: - if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.filename = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_FILETYPE; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_SYMLINK: - switch(parser->state.UNIX.sub.symlink) { - case PL_UNIX_SYMLINK_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_NAME: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_SYMLINK_PRETARGET1: - parser->item_length++; - if(c == '-') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET2: - parser->item_length++; - if(c == '>') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET3: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; - /* now place where is symlink following */ - mem[parser->item_offset + parser->item_length - 4] = 0; - parser->offsets.filename = parser->item_offset; - parser->item_length = 0; - parser->item_offset = 0; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET4: - if(c != '\r' && c != '\n') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; - parser->item_offset = len - 1; - parser->item_length = 1; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_SYMLINK_TARGET: - parser->item_length++; - if(c == '\r') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; - } - else if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.symlink_target = parser->item_offset; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - } - break; - case PL_UNIX_SYMLINK_WINDOWSEOL: - if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.symlink_target = parser->item_offset; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - } + result = parse_unix(data, parser, infop, c); break; case OS_TYPE_WIN_NT: - switch(parser->state.NT.main) { - case PL_WINNT_DATE: - parser->item_length++; - if(parser->item_length < 9) { - if(!strchr("0123456789-", c)) { /* only simple control */ - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - else if(parser->item_length == 9) { - if(c == ' ') { - parser->state.NT.main = PL_WINNT_TIME; - parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_WINNT_TIME: - parser->item_length++; - switch(parser->state.NT.sub.time) { - case PL_WINNT_TIME_PRESPACE: - if(!ISBLANK(c)) { - parser->state.NT.sub.time = PL_WINNT_TIME_TIME; - } - break; - case PL_WINNT_TIME_TIME: - if(c == ' ') { - parser->offsets.time = parser->item_offset; - mem[parser->item_offset + parser->item_length -1] = 0; - parser->state.NT.main = PL_WINNT_DIRORSIZE; - parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE; - parser->item_length = 0; - } - else if(!strchr("APM0123456789:", c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_WINNT_DIRORSIZE: - switch(parser->state.NT.sub.dirorsize) { - case PL_WINNT_DIRORSIZE_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; - } - break; - case PL_WINNT_DIRORSIZE_CONTENT: - parser->item_length ++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length - 1] = 0; - if(strcmp("", mem + parser->item_offset) == 0) { - finfo->filetype = CURLFILETYPE_DIRECTORY; - finfo->size = 0; - } - else { - char *endptr; - if(curlx_strtoofft(mem + - parser->item_offset, - &endptr, 10, &finfo->size)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - /* correct file type */ - parser->file_data->info.filetype = CURLFILETYPE_FILE; - } - - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; - parser->item_length = 0; - parser->state.NT.main = PL_WINNT_FILENAME; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - break; - } - break; - case PL_WINNT_FILENAME: - switch(parser->state.NT.sub.filename) { - case PL_WINNT_FILENAME_PRESPACE: - if(c != ' ') { - parser->item_offset = len -1; - parser->item_length = 1; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; - } - break; - case PL_WINNT_FILENAME_CONTENT: - parser->item_length++; - if(c == '\r') { - parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; - mem[len - 1] = 0; - } - else if(c == '\n') { - parser->offsets.filename = parser->item_offset; - mem[len - 1] = 0; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.NT.main = PL_WINNT_DATE; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - break; - case PL_WINNT_FILENAME_WINEOL: - if(c == '\n') { - parser->offsets.filename = parser->item_offset; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.NT.main = PL_WINNT_DATE; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - } + result = parse_winnt(data, parser, infop, c); break; default: retsize = bufflen + 1; goto fail; } + if(result) { + parser->error = result; + goto fail; + } i++; } diff --git a/Utilities/cmcurl/lib/functypes.h b/Utilities/cmcurl/lib/functypes.h index 075c02e54f6..b4dccc0ce48 100644 --- a/Utilities/cmcurl/lib/functypes.h +++ b/Utilities/cmcurl/lib/functypes.h @@ -38,7 +38,7 @@ 2. For systems with config-*.h files, define them there. */ -#ifdef WIN32 +#ifdef _WIN32 /* int recv(SOCKET, char *, int, int) */ #define RECV_TYPE_ARG1 SOCKET #define RECV_TYPE_ARG2 char * @@ -62,6 +62,7 @@ /* int send(int, const char *, int, int); */ #define SEND_TYPE_ARG1 int +#define SEND_QUAL_ARG2 #define SEND_TYPE_ARG2 char * #define SEND_TYPE_ARG3 int #define SEND_TYPE_RETV int diff --git a/Utilities/cmcurl/lib/getenv.c b/Utilities/cmcurl/lib/getenv.c index 80697847288..3bfcf707a44 100644 --- a/Utilities/cmcurl/lib/getenv.c +++ b/Utilities/cmcurl/lib/getenv.c @@ -31,12 +31,13 @@ static char *GetEnv(const char *variable) { -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_APP) +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) || \ + defined(__ORBIS__) || defined(__PROSPERO__) /* PlayStation 4 and 5 */ (void)variable; return NULL; -#elif defined(WIN32) +#elif defined(_WIN32) /* This uses Windows API instead of C runtime getenv() to get the environment - variable since some changes aren't always visible to the latter. #4774 */ + variable since some changes are not always visible to the latter. #4774 */ char *buf = NULL; char *tmp; DWORD bufsize; @@ -53,8 +54,8 @@ static char *GetEnv(const char *variable) buf = tmp; bufsize = rc; - /* It's possible for rc to be 0 if the variable was found but empty. - Since getenv doesn't make that distinction we ignore it as well. */ + /* it is possible for rc to be 0 if the variable was found but empty. + Since getenv does not make that distinction we ignore it as well. */ rc = GetEnvironmentVariableA(variable, buf, bufsize); if(!rc || rc == bufsize || rc > max) { free(buf); @@ -69,7 +70,7 @@ static char *GetEnv(const char *variable) } #else char *env = getenv(variable); - return (env && env[0])?strdup(env):NULL; + return (env && env[0]) ? strdup(env) : NULL; #endif } diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c index 826ffd0b0c0..388646bf751 100644 --- a/Utilities/cmcurl/lib/getinfo.c +++ b/Utilities/cmcurl/lib/getinfo.c @@ -28,10 +28,10 @@ #include "urldata.h" #include "getinfo.h" - #include "vtls/vtls.h" #include "connect.h" /* Curl_getconnectinfo() */ #include "progress.h" +#include "curlx/strparse.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -53,10 +53,11 @@ CURLcode Curl_initinfo(struct Curl_easy *data) pro->t_connect = 0; pro->t_appconnect = 0; pro->t_pretransfer = 0; + pro->t_posttransfer = 0; pro->t_starttransfer = 0; pro->timespent = 0; pro->t_redirect = 0; - pro->is_t_startransfer_set = false; + pro->is_t_startransfer_set = FALSE; info->httpcode = 0; info->httpproxycode = 0; @@ -68,6 +69,8 @@ CURLcode Curl_initinfo(struct Curl_easy *data) info->request_size = 0; info->proxyauthavail = 0; info->httpauthavail = 0; + info->proxyauthpicked = 0; + info->httpauthpicked = 0; info->numconnects = 0; free(info->contenttype); @@ -76,10 +79,9 @@ CURLcode Curl_initinfo(struct Curl_easy *data) free(info->wouldredirect); info->wouldredirect = NULL; - info->conn_primary_ip[0] = '\0'; - info->conn_local_ip[0] = '\0'; - info->conn_primary_port = 0; - info->conn_local_port = 0; + memset(&info->primary, 0, sizeof(info->primary)); + info->primary.remote_port = -1; + info->primary.local_port = -1; info->retry_after = 0; info->conn_scheme = 0; @@ -96,7 +98,7 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, { switch(info) { case CURLINFO_EFFECTIVE_URL: - *param_charp = data->state.url?data->state.url:(char *)""; + *param_charp = data->state.url ? data->state.url : ""; break; case CURLINFO_EFFECTIVE_METHOD: { const char *m = data->set.str[STRING_CUSTOMREQUEST]; @@ -153,15 +155,19 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_PRIMARY_IP: /* Return the ip address of the most recent (primary) connection */ - *param_charp = data->info.conn_primary_ip; + *param_charp = data->info.primary.remote_ip; break; case CURLINFO_LOCAL_IP: /* Return the source/local ip address of the most recent (primary) connection */ - *param_charp = data->info.conn_local_ip; + *param_charp = data->info.primary.local_ip; break; case CURLINFO_RTSP_SESSION_ID: +#ifndef CURL_DISABLE_RTSP *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; +#else + *param_charp = NULL; +#endif break; case CURLINFO_SCHEME: *param_charp = data->info.conn_scheme; @@ -180,7 +186,6 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, *param_charp = NULL; #endif break; - default: return CURLE_UNKNOWN_OPTION; } @@ -199,9 +204,10 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, } lptr; #ifdef DEBUGBUILD - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, TIME_T_MAX); switch(info) { case CURLINFO_LOCAL_PORT: *param_longp = (long)val; @@ -213,7 +219,8 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, /* use another variable for this to allow different values */ timestr = getenv("CURL_DEBUG_SIZE"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, LONG_MAX); switch(info) { case CURLINFO_HEADER_SIZE: case CURLINFO_REQUEST_SIZE: @@ -235,8 +242,10 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_FILETIME: if(data->info.filetime > LONG_MAX) *param_longp = LONG_MAX; +#if !defined(MSDOS) && !defined(__AMIGA__) else if(data->info.filetime < LONG_MIN) *param_longp = LONG_MIN; +#endif else *param_longp = (long)data->info.filetime; break; @@ -249,11 +258,13 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_SSL_VERIFYRESULT: *param_longp = data->set.ssl.certverifyresult; break; -#ifndef CURL_DISABLE_PROXY case CURLINFO_PROXY_SSL_VERIFYRESULT: +#ifndef CURL_DISABLE_PROXY *param_longp = data->set.proxy_ssl.certverifyresult; - break; +#else + *param_longp = 0; #endif + break; case CURLINFO_REDIRECT_COUNT: *param_longp = data->state.followlocation; break; @@ -265,6 +276,14 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, lptr.to_long = param_longp; *lptr.to_ulong = data->info.proxyauthavail; break; + case CURLINFO_HTTPAUTH_USED: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.httpauthpicked; + break; + case CURLINFO_PROXYAUTH_USED: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.proxyauthpicked; + break; case CURLINFO_OS_ERRNO: *param_longp = data->state.os_errno; break; @@ -274,8 +293,8 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_LASTSOCKET: sockfd = Curl_getconnectinfo(data, NULL); - /* note: this is not a good conversion for systems with 64 bit sockets and - 32 bit longs */ + /* note: this is not a good conversion for systems with 64-bit sockets and + 32-bit longs */ if(sockfd != CURL_SOCKET_BAD) *param_longp = (long)sockfd; else @@ -285,11 +304,11 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_PRIMARY_PORT: /* Return the (remote) port of the most recent (primary) connection */ - *param_longp = data->info.conn_primary_port; + *param_longp = data->info.primary.remote_port; break; case CURLINFO_LOCAL_PORT: /* Return the local port of the most recent (primary) connection */ - *param_longp = data->info.conn_local_port; + *param_longp = data->info.primary.local_port; break; case CURLINFO_PROXY_ERROR: *param_longp = (long)data->info.pxcode; @@ -311,6 +330,12 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_RTSP_CSEQ_RECV: *param_longp = data->state.rtsp_CSeq_recv; break; +#else + case CURLINFO_RTSP_CLIENT_CSEQ: + case CURLINFO_RTSP_SERVER_CSEQ: + case CURLINFO_RTSP_CSEQ_RECV: + *param_longp = 0; + break; #endif case CURLINFO_HTTP_VERSION: switch(data->info.httpversion) { @@ -332,7 +357,16 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, } break; case CURLINFO_PROTOCOL: - *param_longp = data->info.conn_protocol; + *param_longp = (long)data->info.conn_protocol; + break; + case CURLINFO_USED_PROXY: + *param_longp = +#ifdef CURL_DISABLE_PROXY + 0 +#else + data->info.used_proxy +#endif + ; break; default: return CURLE_UNKNOWN_OPTION; @@ -347,15 +381,19 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, curl_off_t *param_offt) { #ifdef DEBUGBUILD - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, CURL_OFF_T_MAX); + switch(info) { case CURLINFO_TOTAL_TIME_T: case CURLINFO_NAMELOOKUP_TIME_T: case CURLINFO_CONNECT_TIME_T: case CURLINFO_APPCONNECT_TIME_T: case CURLINFO_PRETRANSFER_TIME_T: + case CURLINFO_POSTTRANSFER_TIME_T: + case CURLINFO_QUEUE_TIME_T: case CURLINFO_STARTTRANSFER_TIME_T: case CURLINFO_REDIRECT_TIME_T: case CURLINFO_SPEED_DOWNLOAD_T: @@ -372,24 +410,24 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = (curl_off_t)data->info.filetime; break; case CURLINFO_SIZE_UPLOAD_T: - *param_offt = data->progress.uploaded; + *param_offt = data->progress.ul.cur_size; break; case CURLINFO_SIZE_DOWNLOAD_T: - *param_offt = data->progress.downloaded; + *param_offt = data->progress.dl.cur_size; break; case CURLINFO_SPEED_DOWNLOAD_T: - *param_offt = data->progress.dlspeed; + *param_offt = data->progress.dl.speed; break; case CURLINFO_SPEED_UPLOAD_T: - *param_offt = data->progress.ulspeed; + *param_offt = data->progress.ul.speed; break; case CURLINFO_CONTENT_LENGTH_DOWNLOAD_T: - *param_offt = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? - data->progress.size_dl:-1; + *param_offt = data->progress.dl_size_known ? + data->progress.dl.total_size : -1; break; case CURLINFO_CONTENT_LENGTH_UPLOAD_T: - *param_offt = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? - data->progress.size_ul:-1; + *param_offt = data->progress.ul_size_known ? + data->progress.ul.total_size : -1; break; case CURLINFO_TOTAL_TIME_T: *param_offt = data->progress.timespent; @@ -406,15 +444,31 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, case CURLINFO_PRETRANSFER_TIME_T: *param_offt = data->progress.t_pretransfer; break; + case CURLINFO_POSTTRANSFER_TIME_T: + *param_offt = data->progress.t_posttransfer; + break; case CURLINFO_STARTTRANSFER_TIME_T: *param_offt = data->progress.t_starttransfer; break; + case CURLINFO_QUEUE_TIME_T: + *param_offt = data->progress.t_postqueue; + break; case CURLINFO_REDIRECT_TIME_T: *param_offt = data->progress.t_redirect; break; case CURLINFO_RETRY_AFTER: *param_offt = data->info.retry_after; break; + case CURLINFO_XFER_ID: + *param_offt = data->id; + break; + case CURLINFO_CONN_ID: + *param_offt = data->conn ? + data->conn->connection_id : data->state.recent_conn_id; + break; + case CURLINFO_EARLYDATA_SENT_T: + *param_offt = data->progress.earlydata_sent; + break; default: return CURLE_UNKNOWN_OPTION; } @@ -426,9 +480,11 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, double *param_doublep) { #ifdef DEBUGBUILD - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, CURL_OFF_T_MAX); + switch(info) { case CURLINFO_TOTAL_TIME: case CURLINFO_NAMELOOKUP_TIME: @@ -466,24 +522,24 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, *param_doublep = DOUBLE_SECS(data->progress.t_starttransfer); break; case CURLINFO_SIZE_UPLOAD: - *param_doublep = (double)data->progress.uploaded; + *param_doublep = (double)data->progress.ul.cur_size; break; case CURLINFO_SIZE_DOWNLOAD: - *param_doublep = (double)data->progress.downloaded; + *param_doublep = (double)data->progress.dl.cur_size; break; case CURLINFO_SPEED_DOWNLOAD: - *param_doublep = (double)data->progress.dlspeed; + *param_doublep = (double)data->progress.dl.speed; break; case CURLINFO_SPEED_UPLOAD: - *param_doublep = (double)data->progress.ulspeed; + *param_doublep = (double)data->progress.ul.speed; break; case CURLINFO_CONTENT_LENGTH_DOWNLOAD: - *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? - (double)data->progress.size_dl:-1; + *param_doublep = data->progress.dl_size_known ? + (double)data->progress.dl.total_size : -1; break; case CURLINFO_CONTENT_LENGTH_UPLOAD: - *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? - (double)data->progress.size_ul:-1; + *param_doublep = data->progress.ul_size_known ? + (double)data->progress.ul.total_size : -1; break; case CURLINFO_REDIRECT_TIME: *param_doublep = DOUBLE_SECS(data->progress.t_redirect); diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c index 4a11d9364e7..68ccb59e408 100644 --- a/Utilities/cmcurl/lib/gopher.c +++ b/Utilities/cmcurl/lib/gopher.c @@ -39,7 +39,7 @@ #include "vtls/vtls.h" #include "url.h" #include "escape.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_printf.h" #include "curl_memory.h" /* The last #include file should be: */ @@ -62,7 +62,7 @@ static CURLcode gopher_connecting(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_gopher = { - "GOPHER", /* scheme */ + "gopher", /* scheme */ ZERO_NULL, /* setup_connection */ gopher_do, /* do_it */ ZERO_NULL, /* done */ @@ -75,9 +75,11 @@ const struct Curl_handler Curl_handler_gopher = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_GOPHER, /* defport */ CURLPROTO_GOPHER, /* protocol */ CURLPROTO_GOPHER, /* family */ @@ -86,7 +88,7 @@ const struct Curl_handler Curl_handler_gopher = { #ifdef USE_SSL const struct Curl_handler Curl_handler_gophers = { - "GOPHERS", /* scheme */ + "gophers", /* scheme */ ZERO_NULL, /* setup_connection */ gopher_do, /* do_it */ ZERO_NULL, /* done */ @@ -99,9 +101,11 @@ const struct Curl_handler Curl_handler_gophers = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_GOPHER, /* defport */ CURLPROTO_GOPHERS, /* protocol */ CURLPROTO_GOPHER, /* family */ @@ -139,8 +143,8 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) char *sel = NULL; char *sel_org = NULL; timediff_t timeout_ms; - ssize_t amount, k; - size_t len; + ssize_t k; + size_t amount, len; int what; *done = TRUE; /* unconditionally */ @@ -158,7 +162,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) /* Create selector. Degenerate cases: / and /1 => convert to "" */ if(strlen(gopherpath) <= 2) { - sel = (char *)""; + sel = (char *)CURL_UNCONST(""); len = strlen(sel); free(gopherpath); } @@ -185,7 +189,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) if(strlen(sel) < 1) break; - result = Curl_write(data, sockfd, sel, k, &amount); + result = Curl_xfer_send(data, sel, k, FALSE, &amount); if(!result) { /* Which may not have written it all! */ result = Curl_client_write(data, CLIENTWRITE_HEADER, sel, amount); if(result) @@ -207,9 +211,9 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) if(!timeout_ms) timeout_ms = TIMEDIFF_T_MAX; - /* Don't busyloop. The entire loop thing is a work-around as it causes a + /* Do not busyloop. The entire loop thing is a work-around as it causes a BLOCKING behavior which is a NO-NO. This function should rather be - split up in a do and a doing piece where the pieces that aren't + split up in a do and a doing piece where the pieces that are not possible to send now will be sent in the doing function repeatedly until the entire request is sent. */ @@ -227,16 +231,16 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) free(sel_org); if(!result) - result = Curl_write(data, sockfd, "\r\n", 2, &amount); + result = Curl_xfer_send(data, "\r\n", 2, FALSE, &amount); if(result) { failf(data, "Failed sending Gopher request"); return result; } - result = Curl_client_write(data, CLIENTWRITE_HEADER, (char *)"\r\n", 2); + result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2); if(result) return result; - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); return CURLE_OK; } #endif /* CURL_DISABLE_GOPHER */ diff --git a/Utilities/cmcurl/lib/hash.c b/Utilities/cmcurl/lib/hash.c index 30f28e2352a..8d13aae483a 100644 --- a/Utilities/cmcurl/lib/hash.c +++ b/Utilities/cmcurl/lib/hash.c @@ -33,21 +33,50 @@ /* The last #include file should be: */ #include "memdebug.h" -static void -hash_element_dtor(void *user, void *element) +/* random patterns for API verification */ +#ifdef DEBUGBUILD +#define HASHINIT 0x7017e781 +#define ITERINIT 0x5FEDCBA9 +#endif + + +#if 0 /* useful function for debugging hashes and their contents */ +void Curl_hash_print(struct Curl_hash *h, + void (*func)(void *)) { - struct Curl_hash *h = (struct Curl_hash *) user; - struct Curl_hash_element *e = (struct Curl_hash_element *) element; + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + size_t last_index = UINT_MAX; - if(e->ptr) { - h->dtor(e->ptr); - e->ptr = NULL; - } + if(!h) + return; + + fprintf(stderr, "=Hash dump=\n"); - e->key_len = 0; + Curl_hash_start_iterate(h, &iter); - free(e); + he = Curl_hash_next_element(&iter); + while(he) { + if(iter.slot_index != last_index) { + fprintf(stderr, "index %d:", (int)iter.slot_index); + if(last_index != UINT_MAX) { + fprintf(stderr, "\n"); + } + last_index = iter.slot_index; + } + + if(func) + func(he->ptr); + else + fprintf(stderr, " [key=%.*s, he=%p, ptr=%p]", + (int)he->key_len, (char *)he->key, + (void *)he, (void *)he->ptr); + + he = Curl_hash_next_element(&iter); + } + fprintf(stderr, "\n"); } +#endif /* Initializes a hash structure. * Return 1 on error, 0 is fine. @@ -57,7 +86,7 @@ hash_element_dtor(void *user, void *element) */ void Curl_hash_init(struct Curl_hash *h, - int slots, + size_t slots, hash_function hfunc, comp_function comparator, Curl_hash_dtor dtor) @@ -74,70 +103,116 @@ Curl_hash_init(struct Curl_hash *h, h->dtor = dtor; h->size = 0; h->slots = slots; +#ifdef DEBUGBUILD + h->init = HASHINIT; +#endif } static struct Curl_hash_element * -mk_hash_element(const void *key, size_t key_len, const void *p) +hash_elem_create(const void *key, size_t key_len, const void *p, + Curl_hash_elem_dtor dtor) { + struct Curl_hash_element *he; + /* allocate the struct plus memory after it to store the key */ - struct Curl_hash_element *he = malloc(sizeof(struct Curl_hash_element) + - key_len); + he = malloc(sizeof(struct Curl_hash_element) + key_len); if(he) { + he->next = NULL; /* copy the key */ memcpy(he->key, key, key_len); he->key_len = key_len; - he->ptr = (void *) p; + he->ptr = CURL_UNCONST(p); + he->dtor = dtor; } return he; } -#define FETCH_LIST(x,y,z) &x->table[x->hash_func(y, z, x->slots)] +static void hash_elem_clear_ptr(struct Curl_hash *h, + struct Curl_hash_element *he) +{ + DEBUGASSERT(h); + DEBUGASSERT(he); + if(he->ptr) { + if(he->dtor) + he->dtor(he->key, he->key_len, he->ptr); + else + h->dtor(he->ptr); + he->ptr = NULL; + } +} -/* Insert the data in the hash. If there already was a match in the hash, that - * data is replaced. This function also "lazily" allocates the table if - * needed, as it isn't done in the _init function (anymore). - * - * @unittest: 1305 - * @unittest: 1602 - * @unittest: 1603 - */ -void * -Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) +static void hash_elem_destroy(struct Curl_hash *h, + struct Curl_hash_element *he) { - struct Curl_hash_element *he; - struct Curl_llist_element *le; - struct Curl_llist *l; + hash_elem_clear_ptr(h, he); + free(he); +} + +static void hash_elem_unlink(struct Curl_hash *h, + struct Curl_hash_element **he_anchor, + struct Curl_hash_element *he) +{ + *he_anchor = he->next; + --h->size; +} + +static void hash_elem_link(struct Curl_hash *h, + struct Curl_hash_element **he_anchor, + struct Curl_hash_element *he) +{ + he->next = *he_anchor; + *he_anchor = he; + ++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, + Curl_hash_elem_dtor dtor) +{ + struct Curl_hash_element *he, **slot; DEBUGASSERT(h); DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == HASHINIT); if(!h->table) { - int i; - h->table = malloc(h->slots * sizeof(struct Curl_llist)); + h->table = calloc(h->slots, sizeof(struct Curl_hash_element *)); if(!h->table) return NULL; /* OOM */ - for(i = 0; i < h->slots; ++i) - Curl_llist_init(&h->table[i], hash_element_dtor); } - l = FETCH_LIST(h, key, key_len); - - for(le = l->head; le; le = le->next) { - he = (struct Curl_hash_element *) le->ptr; + slot = CURL_HASH_SLOT_ADDR(h, key, key_len); + for(he = *slot; he; he = he->next) { if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_llist_remove(l, le, (void *)h); - --h->size; - break; + /* existing key entry, overwrite by clearing old pointer */ + hash_elem_clear_ptr(h, he); + he->ptr = (void *)p; + he->dtor = dtor; + return p; } } - he = mk_hash_element(key, key_len, p); - if(he) { - Curl_llist_insert_next(l, l->tail, he, &he->list); - ++h->size; - return p; /* return the new entry */ - } + he = hash_elem_create(key, key_len, p, dtor); + if(!he) + return NULL; /* OOM */ - return NULL; /* failure */ + hash_elem_link(h, slot, he); + return p; /* return the new entry */ +} + +/* Insert the data in the hash. If there already was a match in the hash, that + * data is replaced. This function also "lazily" allocates the table if + * needed, as it is not done in the _init function (anymore). + * + * @unittest: 1305 + * @unittest: 1602 + * @unittest: 1603 + */ +void * +Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) +{ + return Curl_hash_add2(h, key, key_len, p, NULL); } /* Remove the identified hash entry. @@ -147,21 +222,21 @@ Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) */ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) { - struct Curl_llist_element *le; - struct Curl_llist *l; - DEBUGASSERT(h); DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == HASHINIT); if(h->table) { - l = FETCH_LIST(h, key, key_len); + struct Curl_hash_element *he, **he_anchor; - for(le = l->head; le; le = le->next) { - struct Curl_hash_element *he = le->ptr; + he_anchor = CURL_HASH_SLOT_ADDR(h, key, key_len); + while(*he_anchor) { + he = *he_anchor; if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_llist_remove(l, le, (void *) h); - --h->size; + hash_elem_unlink(h, he_anchor, he); + hash_elem_destroy(h, he); return 0; } + he_anchor = &he->next; } } return 1; @@ -174,43 +249,22 @@ 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) { - struct Curl_llist_element *le; - struct Curl_llist *l; - DEBUGASSERT(h); + DEBUGASSERT(h->init == HASHINIT); if(h->table) { + struct Curl_hash_element *he; DEBUGASSERT(h->slots); - l = FETCH_LIST(h, key, key_len); - for(le = l->head; le; le = le->next) { - struct Curl_hash_element *he = le->ptr; + he = CURL_HASH_SLOT(h, key, key_len); + while(he) { if(h->comp_func(he->key, he->key_len, key, key_len)) { return he->ptr; } + he = he->next; } } - return NULL; } -#if defined(DEBUGBUILD) && defined(AGGRESSIVE_TEST) -void -Curl_hash_apply(Curl_hash *h, void *user, - void (*cb)(void *user, void *ptr)) -{ - struct Curl_llist_element *le; - int i; - - for(i = 0; i < h->slots; ++i) { - for(le = (h->table[i])->head; - le; - le = le->next) { - Curl_hash_element *el = le->ptr; - cb(user, el->ptr); - } - } -} -#endif - /* Destroys all the entries in the given hash and resets its attributes, * prepping the given hash for [static|dynamic] deallocation. * @@ -221,14 +275,12 @@ Curl_hash_apply(Curl_hash *h, void *user, void Curl_hash_destroy(struct Curl_hash *h) { + DEBUGASSERT(h->init == HASHINIT); if(h->table) { - int i; - for(i = 0; i < h->slots; ++i) { - Curl_llist_destroy(&h->table[i], (void *) h); - } + Curl_hash_clean(h); Curl_safefree(h->table); } - h->size = 0; + DEBUGASSERT(h->size == 0); h->slots = 0; } @@ -236,10 +288,27 @@ Curl_hash_destroy(struct Curl_hash *h) * * @unittest: 1602 */ -void -Curl_hash_clean(struct Curl_hash *h) +void Curl_hash_clean(struct Curl_hash *h) +{ + if(h && h->table) { + struct Curl_hash_element *he, **he_anchor; + size_t i; + DEBUGASSERT(h->init == HASHINIT); + for(i = 0; i < h->slots; ++i) { + he_anchor = &h->table[i]; + while(*he_anchor) { + he = *he_anchor; + hash_elem_unlink(h, he_anchor, he); + hash_elem_destroy(h, he); + } + } + } +} + +size_t Curl_hash_count(struct Curl_hash *h) { - Curl_hash_clean_with_criterium(h, NULL, NULL); + DEBUGASSERT(h->init == HASHINIT); + return h->size; } /* Cleans all entries that pass the comp function criteria. */ @@ -247,26 +316,23 @@ void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)) { - struct Curl_llist_element *le; - struct Curl_llist_element *lnext; - struct Curl_llist *list; - int i; + size_t i; if(!h || !h->table) return; + DEBUGASSERT(h->init == HASHINIT); for(i = 0; i < h->slots; ++i) { - list = &h->table[i]; - le = list->head; /* get first list entry */ - while(le) { - struct Curl_hash_element *he = le->ptr; - lnext = le->next; + struct Curl_hash_element *he, **he_anchor = &h->table[i]; + while(*he_anchor) { /* ask the callback function if we shall remove this entry or not */ - if(!comp || comp(user, he->ptr)) { - Curl_llist_remove(list, le, (void *) h); - --h->size; /* one less entry in the hash now */ + if(!comp || comp(user, (*he_anchor)->ptr)) { + he = *he_anchor; + hash_elem_unlink(h, he_anchor, he); + hash_elem_destroy(h, he); } - le = lnext; + else + he_anchor = &(*he_anchor)->next; } } } @@ -278,14 +344,15 @@ size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num) size_t h = 5381; while(key_str < end) { + size_t j = (size_t)*key_str++; h += h << 5; - h ^= *key_str++; + h ^= j; } return (h % slots_num); } -size_t Curl_str_key_compare(void *k1, size_t key1_len, +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)) @@ -297,74 +364,39 @@ size_t Curl_str_key_compare(void *k1, size_t key1_len, void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter) { + DEBUGASSERT(hash->init == HASHINIT); iter->hash = hash; iter->slot_index = 0; - iter->current_element = NULL; + iter->current = NULL; +#ifdef DEBUGBUILD + iter->init = ITERINIT; +#endif } struct Curl_hash_element * Curl_hash_next_element(struct Curl_hash_iterator *iter) { - struct Curl_hash *h = iter->hash; - + struct Curl_hash *h; + DEBUGASSERT(iter->init == ITERINIT); + h = iter->hash; if(!h->table) return NULL; /* empty hash, nothing to return */ /* Get the next element in the current list, if any */ - if(iter->current_element) - iter->current_element = iter->current_element->next; + if(iter->current) + iter->current = iter->current->next; /* If we have reached the end of the list, find the next one */ - if(!iter->current_element) { - int i; + if(!iter->current) { + size_t i; for(i = iter->slot_index; i < h->slots; i++) { - if(h->table[i].head) { - iter->current_element = h->table[i].head; + if(h->table[i]) { + iter->current = h->table[i]; iter->slot_index = i + 1; break; } } } - if(iter->current_element) { - struct Curl_hash_element *he = iter->current_element->ptr; - return he; - } - return NULL; + return iter->current; } - -#if 0 /* useful function for debugging hashes and their contents */ -void Curl_hash_print(struct Curl_hash *h, - void (*func)(void *)) -{ - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - int last_index = -1; - - if(!h) - return; - - fprintf(stderr, "=Hash dump=\n"); - - Curl_hash_start_iterate(h, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - if(iter.slot_index != last_index) { - fprintf(stderr, "index %d:", iter.slot_index); - if(last_index >= 0) { - fprintf(stderr, "\n"); - } - last_index = iter.slot_index; - } - - if(func) - func(he->ptr); - else - fprintf(stderr, " [%p]", (void *)he->ptr); - - he = Curl_hash_next_element(&iter); - } - fprintf(stderr, "\n"); -} -#endif diff --git a/Utilities/cmcurl/lib/hash.h b/Utilities/cmcurl/lib/hash.h index 9cfffc25b08..314b811e4a4 100644 --- a/Utilities/cmcurl/lib/hash.h +++ b/Utilities/cmcurl/lib/hash.h @@ -45,50 +45,60 @@ typedef size_t (*comp_function) (void *key1, typedef void (*Curl_hash_dtor)(void *); +typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); + +struct Curl_hash_element { + struct Curl_hash_element *next; + void *ptr; + Curl_hash_elem_dtor dtor; + size_t key_len; + char key[1]; /* allocated memory following the struct */ +}; + struct Curl_hash { - struct Curl_llist *table; + 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 */ Curl_hash_dtor dtor; - int slots; + size_t slots; size_t size; -}; - -struct Curl_hash_element { - struct Curl_llist_element list; - void *ptr; - size_t key_len; - char key[1]; /* allocated memory following the struct */ +#ifdef DEBUGBUILD + int init; +#endif }; struct Curl_hash_iterator { struct Curl_hash *hash; - int slot_index; - struct Curl_llist_element *current_element; + size_t slot_index; + struct Curl_hash_element *current; +#ifdef DEBUGBUILD + int init; +#endif }; void Curl_hash_init(struct Curl_hash *h, - int slots, + size_t slots, hash_function hfunc, comp_function comparator, 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, + 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 *, void *key, size_t key_len); -void Curl_hash_apply(struct Curl_hash *h, void *user, - void (*cb)(void *user, void *ptr)); -#define Curl_hash_count(h) ((h)->size) + 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 Curl_str_key_compare(void *k1, size_t key1_len, void *k2, +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); @@ -98,5 +108,4 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter); void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)); - #endif /* HEADER_CURL_HASH_H */ diff --git a/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c index 4367ce797c1..71592a9c737 100644 --- a/Utilities/cmcurl/lib/headers.c +++ b/Utilities/cmcurl/lib/headers.c @@ -27,7 +27,9 @@ #include "urldata.h" #include "strdup.h" #include "strcase.h" +#include "sendf.h" #include "headers.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -41,7 +43,7 @@ static void copy_header_external(struct Curl_header_store *hs, size_t index, size_t amount, - struct Curl_llist_element *e, + struct Curl_llist_node *e, struct curl_header *hout) { struct curl_header *h = hout; @@ -53,7 +55,7 @@ static void copy_header_external(struct Curl_header_store *hs, impossible for applications to do == comparisons, as that would otherwise be very tempting and then lead to the reserved bits not being reserved anymore. */ - h->origin = hs->type | (1<<27); + h->origin = (unsigned int)(hs->type | (1 << 27)); h->anchor = e; } @@ -65,8 +67,8 @@ CURLHcode curl_easy_header(CURL *easy, int request, struct curl_header **hout) { - struct Curl_llist_element *e; - struct Curl_llist_element *e_pick = NULL; + struct Curl_llist_node *e; + struct Curl_llist_node *e_pick = NULL; struct Curl_easy *data = easy; size_t match = 0; size_t amount = 0; @@ -84,8 +86,8 @@ CURLHcode curl_easy_header(CURL *easy, request = data->state.requests; /* we need a first round to count amount of this header */ - for(e = data->state.httphdrs.head; e; e = e->next) { - hs = e->ptr; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + hs = Curl_node_elem(e); if(strcasecompare(hs->name, name) && (hs->type & type) && (hs->request == request)) { @@ -103,8 +105,8 @@ CURLHcode curl_easy_header(CURL *easy, /* if the last or only occurrence is what's asked for, then we know it */ hs = pick; else { - for(e = data->state.httphdrs.head; e; e = e->next) { - hs = e->ptr; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + hs = Curl_node_elem(e); if(strcasecompare(hs->name, name) && (hs->type & type) && (hs->request == request) && @@ -113,7 +115,7 @@ CURLHcode curl_easy_header(CURL *easy, break; } } - if(!e) /* this shouldn't happen */ + if(!e) /* this should not happen */ return CURLHE_MISSING; } /* this is the name we want */ @@ -130,8 +132,8 @@ struct curl_header *curl_easy_nextheader(CURL *easy, struct curl_header *prev) { struct Curl_easy *data = easy; - struct Curl_llist_element *pick; - struct Curl_llist_element *e; + struct Curl_llist_node *pick; + struct Curl_llist_node *e; struct Curl_header_store *hs; size_t amount = 0; size_t index = 0; @@ -146,18 +148,18 @@ struct curl_header *curl_easy_nextheader(CURL *easy, if(!pick) /* something is wrong */ return NULL; - pick = pick->next; + pick = Curl_node_next(pick); } else - pick = data->state.httphdrs.head; + pick = Curl_llist_head(&data->state.httphdrs); if(pick) { /* make sure it is the next header of the desired type */ do { - hs = pick->ptr; + hs = Curl_node_elem(pick); if((hs->type & type) && (hs->request == request)) break; - pick = pick->next; + pick = Curl_node_next(pick); } while(pick); } @@ -165,12 +167,12 @@ struct curl_header *curl_easy_nextheader(CURL *easy, /* no more headers available */ return NULL; - hs = pick->ptr; + 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 = data->state.httphdrs.head; e; e = e->next) { - struct Curl_header_store *check = e->ptr; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + struct Curl_header_store *check = Curl_node_elem(e); if(strcasecompare(hs->name, check->name) && (check->request == request) && (check->type & type)) @@ -185,7 +187,7 @@ struct curl_header *curl_easy_nextheader(CURL *easy, } static CURLcode namevalue(char *header, size_t hlen, unsigned int type, - char **name, char **value) + char **name, char **value) { char *end = header + hlen - 1; /* point to the last byte */ DEBUGASSERT(hlen); @@ -207,15 +209,15 @@ static CURLcode namevalue(char *header, size_t hlen, unsigned int type, else return CURLE_BAD_FUNCTION_ARGUMENT; - /* skip all leading space letters */ - while(*header && ISBLANK(*header)) + /* skip all leading blank letters */ + while(ISBLANK(*header)) header++; *value = header; /* skip all trailing space letters */ - while((end > header) && ISSPACE(*end)) - *end-- = 0; /* nul terminate */ + while((end > header) && ISBLANK(*end)) + *end-- = 0; /* null-terminate */ return CURLE_OK; } @@ -234,7 +236,7 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, oalloc = olen + offset + 1; /* skip all trailing space letters */ - while(vlen && ISSPACE(value[vlen - 1])) + while(vlen && ISBLANK(value[vlen - 1])) vlen--; /* save only one leading space */ @@ -246,13 +248,13 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, /* since this header block might move in the realloc below, it needs to first be unlinked from the list and then re-added again after the realloc */ - Curl_llist_remove(&data->state.httphdrs, &hs->node, NULL); + Curl_node_remove(&hs->node); /* new size = struct + new value length + old name+value length */ newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1); if(!newhs) return CURLE_OUT_OF_MEMORY; - /* ->name' and ->value point into ->buffer (to keep the header allocation + /* ->name and ->value point into ->buffer (to keep the header allocation in a single memory block), which now potentially have moved. Adjust them. */ newhs->name = newhs->buffer; @@ -263,8 +265,7 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, newhs->value[olen + vlen] = 0; /* null-terminate at newline */ /* insert this node into the list of headers */ - Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, - newhs, &newhs->node); + Curl_llist_append(&data->state.httphdrs, newhs, &newhs->node); data->state.prevhead = newhs; return CURLE_OK; } @@ -292,67 +293,135 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, if(!end) { end = strchr(header, '\n'); if(!end) - return CURLE_BAD_FUNCTION_ARGUMENT; + /* neither CR nor LF as terminator is not a valid header */ + return CURLE_WEIRD_SERVER_REPLY; } - hlen = end - header + 1; + hlen = end - header; if((header[0] == ' ') || (header[0] == '\t')) { if(data->state.prevhead) /* line folding, append value to the previous header's value */ return unfold_value(data, header, hlen); - else - /* can't unfold without a previous header */ - return CURLE_BAD_FUNCTION_ARGUMENT; + else { + /* cannot unfold without a previous header. Instead of erroring, just + pass the leading blanks. */ + while(hlen && ISBLANK(*header)) { + header++; + hlen--; + } + if(!hlen) + return CURLE_WEIRD_SERVER_REPLY; + } + } + if(Curl_llist_count(&data->state.httphdrs) >= MAX_HTTP_RESP_HEADER_COUNT) { + failf(data, "Too many response headers, %d is max", + MAX_HTTP_RESP_HEADER_COUNT); + return CURLE_TOO_LARGE; } hs = calloc(1, sizeof(*hs) + hlen); if(!hs) return CURLE_OUT_OF_MEMORY; memcpy(hs->buffer, header, hlen); - hs->buffer[hlen] = 0; /* nul terminate */ + hs->buffer[hlen] = 0; /* null-terminate */ result = namevalue(hs->buffer, hlen, type, &name, &value); - if(result) - goto fail; - - hs->name = name; - hs->value = value; - hs->type = type; - hs->request = data->state.requests; - - /* insert this node into the list of headers */ - Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, - hs, &hs->node); - data->state.prevhead = hs; - return CURLE_OK; -fail: - free(hs); + if(!result) { + hs->name = name; + hs->value = value; + hs->type = type; + hs->request = data->state.requests; + + /* insert this node into the list of headers */ + Curl_llist_append(&data->state.httphdrs, hs, &hs->node); + data->state.prevhead = hs; + } + else { + failf(data, "Invalid response header"); + free(hs); + } return result; } /* - * Curl_headers_init(). Init the headers subsystem. + * Curl_headers_reset(). Reset the headers subsystem. */ -static void headers_init(struct Curl_easy *data) +static void headers_reset(struct Curl_easy *data) { Curl_llist_init(&data->state.httphdrs, NULL); data->state.prevhead = NULL; } +struct hds_cw_collect_ctx { + struct Curl_cwriter super; +}; + +static CURLcode hds_cw_collect_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + if((type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS)) { + unsigned char htype = (unsigned char) + (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT : + (type & CLIENTWRITE_1XX ? CURLH_1XX : + (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER : + CURLH_HEADER))); + CURLcode result = Curl_headers_push(data, buf, htype); + CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d", + htype, blen, result); + if(result) + return result; + } + return Curl_cwriter_write(data, writer->next, type, buf, blen); +} + +static const struct Curl_cwtype hds_cw_collect = { + "hds-collect", + NULL, + Curl_cwriter_def_init, + hds_cw_collect_write, + Curl_cwriter_def_close, + sizeof(struct hds_cw_collect_ctx) +}; + +CURLcode Curl_headers_init(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + CURLcode result; + + if(data->conn && (data->conn->handler->protocol & PROTO_FAMILY_HTTP)) { + /* avoid installing it twice */ + if(Curl_cwriter_get_by_name(data, hds_cw_collect.name)) + return CURLE_OK; + + result = Curl_cwriter_create(&writer, data, &hds_cw_collect, + CURL_CW_PROTOCOL); + if(result) + return result; + + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + return result; + } + } + return CURLE_OK; +} + /* * Curl_headers_cleanup(). Free all stored headers and associated memory. */ CURLcode Curl_headers_cleanup(struct Curl_easy *data) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; - for(e = data->state.httphdrs.head; e; e = n) { - struct Curl_header_store *hs = e->ptr; - n = e->next; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) { + struct Curl_header_store *hs = Curl_node_elem(e); + n = Curl_node_next(e); free(hs); } - headers_init(data); + headers_reset(data); return CURLE_OK; } diff --git a/Utilities/cmcurl/lib/headers.h b/Utilities/cmcurl/lib/headers.h index a5229ea22f0..e11fe9804e5 100644 --- a/Utilities/cmcurl/lib/headers.h +++ b/Utilities/cmcurl/lib/headers.h @@ -28,7 +28,7 @@ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) struct Curl_header_store { - struct Curl_llist_element node; + struct Curl_llist_node node; char *name; /* points into 'buffer' */ char *value; /* points into 'buffer */ int request; /* 0 is the first request, then 1.. 2.. */ @@ -36,6 +36,12 @@ struct Curl_header_store { char buffer[1]; /* this is the raw header blob */ }; +/* + * Initialize header collecting for a transfer. + * Will add a client writer that catches CLIENTWRITE_HEADER writes. + */ +CURLcode Curl_headers_init(struct Curl_easy *data); + /* * Curl_headers_push() gets passed a full header to store. */ @@ -48,6 +54,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, CURLcode Curl_headers_cleanup(struct Curl_easy *data); #else +#define Curl_headers_init(x) CURLE_OK #define Curl_headers_push(x,y,z) CURLE_OK #define Curl_headers_cleanup(x) Curl_nop_stmt #endif diff --git a/Utilities/cmcurl/lib/hmac.c b/Utilities/cmcurl/lib/hmac.c index 8d8de1757dd..3af1f292dac 100644 --- a/Utilities/cmcurl/lib/hmac.c +++ b/Utilities/cmcurl/lib/hmac.c @@ -26,13 +26,15 @@ #include "curl_setup.h" -#ifndef CURL_DISABLE_CRYPTO_AUTH +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) || \ + !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \ + defined(USE_SSL) #include #include "curl_hmac.h" #include "curl_memory.h" -#include "warnless.h" +#include "curlx/warnless.h" /* The last #include file should be: */ #include "memdebug.h" @@ -41,15 +43,13 @@ * Generic HMAC algorithm. * * This module computes HMAC digests based on any hash function. Parameters - * and computing procedures are set-up dynamically at HMAC computation context + * and computing procedures are setup dynamically at HMAC computation context * initialization. */ static const unsigned char hmac_ipad = 0x36; static const unsigned char hmac_opad = 0x5C; - - struct HMAC_context * Curl_HMAC_init(const struct HMAC_params *hashparams, const unsigned char *key, @@ -61,42 +61,40 @@ Curl_HMAC_init(const struct HMAC_params *hashparams, unsigned char b; /* Create HMAC context. */ - i = sizeof(*ctxt) + 2 * hashparams->hmac_ctxtsize + - hashparams->hmac_resultlen; + i = sizeof(*ctxt) + 2 * hashparams->ctxtsize + hashparams->resultlen; ctxt = malloc(i); if(!ctxt) return ctxt; - ctxt->hmac_hash = hashparams; - ctxt->hmac_hashctxt1 = (void *) (ctxt + 1); - ctxt->hmac_hashctxt2 = (void *) ((char *) ctxt->hmac_hashctxt1 + - hashparams->hmac_ctxtsize); + ctxt->hash = hashparams; + ctxt->hashctxt1 = (void *) (ctxt + 1); + ctxt->hashctxt2 = (void *) ((char *) ctxt->hashctxt1 + hashparams->ctxtsize); /* If the key is too long, replace it by its hash digest. */ - if(keylen > hashparams->hmac_maxkeylen) { - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, key, keylen); - hkey = (unsigned char *) ctxt->hmac_hashctxt2 + hashparams->hmac_ctxtsize; - (*hashparams->hmac_hfinal)(hkey, ctxt->hmac_hashctxt1); + if(keylen > hashparams->maxkeylen) { + hashparams->hinit(ctxt->hashctxt1); + hashparams->hupdate(ctxt->hashctxt1, key, keylen); + hkey = (unsigned char *) ctxt->hashctxt2 + hashparams->ctxtsize; + hashparams->hfinal(hkey, ctxt->hashctxt1); key = hkey; - keylen = hashparams->hmac_resultlen; + keylen = hashparams->resultlen; } /* Prime the two hash contexts with the modified key. */ - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt2); + hashparams->hinit(ctxt->hashctxt1); + hashparams->hinit(ctxt->hashctxt2); for(i = 0; i < keylen; i++) { b = (unsigned char)(*key ^ hmac_ipad); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &b, 1); + hashparams->hupdate(ctxt->hashctxt1, &b, 1); b = (unsigned char)(*key++ ^ hmac_opad); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &b, 1); + hashparams->hupdate(ctxt->hashctxt2, &b, 1); } - for(; i < hashparams->hmac_maxkeylen; i++) { - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &hmac_ipad, 1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &hmac_opad, 1); + for(; i < hashparams->maxkeylen; i++) { + hashparams->hupdate(ctxt->hashctxt1, &hmac_ipad, 1); + hashparams->hupdate(ctxt->hashctxt2, &hmac_opad, 1); } /* Done, return pointer to HMAC context. */ @@ -104,31 +102,29 @@ Curl_HMAC_init(const struct HMAC_params *hashparams, } int Curl_HMAC_update(struct HMAC_context *ctxt, - const unsigned char *data, + const unsigned char *ptr, unsigned int len) { /* Update first hash calculation. */ - (*ctxt->hmac_hash->hmac_hupdate)(ctxt->hmac_hashctxt1, data, len); + ctxt->hash->hupdate(ctxt->hashctxt1, ptr, len); return 0; } -int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *result) +int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *output) { - const struct HMAC_params *hashparams = ctxt->hmac_hash; + const struct HMAC_params *hashparams = ctxt->hash; - /* Do not get result if called with a null parameter: only release + /* Do not get output if called with a null parameter: only release storage. */ - if(!result) - result = (unsigned char *) ctxt->hmac_hashctxt2 + - ctxt->hmac_hash->hmac_ctxtsize; + if(!output) + output = (unsigned char *) ctxt->hashctxt2 + ctxt->hash->ctxtsize; - (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, - result, hashparams->hmac_resultlen); - (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt2); - free((char *) ctxt); + hashparams->hfinal(output, ctxt->hashctxt1); + hashparams->hupdate(ctxt->hashctxt2, output, hashparams->resultlen); + hashparams->hfinal(output, ctxt->hashctxt2); + free(ctxt); return 0; } @@ -143,15 +139,15 @@ int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *result) * hashparams [in] - The hash function (Curl_HMAC_MD5). * key [in] - The key to use. * keylen [in] - The length of the key. - * data [in] - The data to encrypt. - * datalen [in] - The length of the data. + * buf [in] - The data to encrypt. + * buflen [in] - The length of the data. * output [in/out] - The output buffer. * * Returns CURLE_OK on success. */ CURLcode Curl_hmacit(const struct HMAC_params *hashparams, const unsigned char *key, const size_t keylen, - const unsigned char *data, const size_t datalen, + const unsigned char *buf, const size_t buflen, unsigned char *output) { struct HMAC_context *ctxt = @@ -161,7 +157,7 @@ CURLcode Curl_hmacit(const struct HMAC_params *hashparams, return CURLE_OUT_OF_MEMORY; /* Update the digest with the given challenge */ - Curl_HMAC_update(ctxt, data, curlx_uztoui(datalen)); + Curl_HMAC_update(ctxt, buf, curlx_uztoui(buflen)); /* Finalise the digest */ Curl_HMAC_final(ctxt, output); @@ -169,4 +165,4 @@ CURLcode Curl_hmacit(const struct HMAC_params *hashparams, return CURLE_OK; } -#endif /* CURL_DISABLE_CRYPTO_AUTH */ +#endif /* Using NTLM (without SSPI) or AWS */ diff --git a/Utilities/cmcurl/lib/hostasyn.c b/Utilities/cmcurl/lib/hostasyn.c deleted file mode 100644 index 2f6762ca4e1..00000000000 --- a/Utilities/cmcurl/lib/hostasyn.c +++ /dev/null @@ -1,123 +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" - -/*********************************************************************** - * Only for builds using asynchronous name resolves - **********************************************************************/ -#ifdef CURLRES_ASYNCH - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread() - * or getaddrinfo_thread() when we got the name resolved (or not!). - * - * If the status argument is CURL_ASYNC_SUCCESS, this function takes - * ownership of the Curl_addrinfo passed, storing the resolved data - * in the DNS cache. - * - * The storage operation locks and unlocks the DNS cache. - */ -CURLcode Curl_addrinfo_callback(struct Curl_easy *data, - int status, - struct Curl_addrinfo *ai) -{ - struct Curl_dns_entry *dns = NULL; - CURLcode result = CURLE_OK; - - data->state.async.status = status; - - if(CURL_ASYNC_SUCCESS == status) { - if(ai) { - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = Curl_cache_addr(data, ai, - data->state.async.hostname, 0, - data->state.async.port); - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - /* failed to store, cleanup and return error */ - Curl_freeaddrinfo(ai); - result = CURLE_OUT_OF_MEMORY; - } - } - else { - result = CURLE_OUT_OF_MEMORY; - } - } - - data->state.async.dns = dns; - - /* Set async.done TRUE last in this function since it may be used multi- - threaded and once this is TRUE the other thread may read fields from the - async struct */ - data->state.async.done = TRUE; - - /* IPv4: The input hostent struct will be freed by ares when we return from - this function */ - return result; -} - -/* - * Curl_getaddrinfo() is the generic low-level name resolve API within this - * source file. There are several versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - return Curl_resolver_getaddrinfo(data, hostname, port, waitp); -} - -#endif /* CURLRES_ASYNCH */ diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/hostip.c index d721403b723..ca6724ed55d 100644 --- a/Utilities/cmcurl/lib/hostip.c +++ b/Utilities/cmcurl/lib/hostip.c @@ -41,36 +41,34 @@ #include #endif -#ifdef HAVE_SETJMP_H #include -#endif -#ifdef HAVE_SIGNAL_H +#ifndef UNDER_CE #include #endif #include "urldata.h" #include "sendf.h" +#include "connect.h" #include "hostip.h" #include "hash.h" #include "rand.h" #include "share.h" #include "url.h" #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "multiif.h" #include "doh.h" -#include "warnless.h" +#include "curlx/warnless.h" +#include "select.h" #include "strcase.h" #include "easy_lock.h" +#include "curlx/strparse.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#if defined(ENABLE_IPV6) && defined(CURL_OSX_CALL_COPYPROXIES) -#include -#endif - #if defined(CURLRES_SYNCH) && \ defined(HAVE_ALARM) && \ defined(SIGALRM) && \ @@ -92,8 +90,8 @@ * source file are these: * * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use - * that. The host may not be able to resolve IPv6, but we don't really have to - * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4 + * that. The host may not be able to resolve IPv6, but we do not really have to + * take that into account. Hosts that are not IPv6-enabled have CURLRES_IPV4 * defined. * * CURLRES_ARES - is defined if libcurl is built to use c-ares for @@ -110,33 +108,25 @@ * The host*.c sources files are split up like this: * * hostip.c - method-independent resolver functions and utility functions - * hostasyn.c - functions for asynchronous name resolves - * hostsyn.c - functions for synchronous name resolves * hostip4.c - IPv4 specific functions * hostip6.c - IPv6 specific functions - * + * asyn.h - common functions for all async resolvers * The two asynchronous name resolver backends are implemented in: - * asyn-ares.c - functions for ares-using name resolves - * asyn-thread.c - functions for threaded name resolves - + * asyn-ares.c - async resolver using c-ares + * asyn-thread.c - async resolver using POSIX threads + * * The hostip.h is the united header file for all this. It defines the * CURLRES_* defines based on the config*.h and curl_setup.h defines. */ -static void freednsentry(void *freethis); +static void dnscache_entry_free(struct Curl_dns_entry *dns); -/* - * Return # of addresses in a Curl_addrinfo struct - */ -static int num_addresses(const struct Curl_addrinfo *addr) -{ - int i = 0; - while(addr) { - addr = addr->ai_next; - i++; - } - return i; -} +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void show_resolve_info(struct Curl_easy *data, + struct Curl_dns_entry *dns); +#else +#define show_resolve_info(x,y) Curl_nop_stmt +#endif /* * Curl_printable_address() stores a printable version of the 1st address @@ -158,7 +148,7 @@ void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, (void)Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, bufsize); break; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: { const struct sockaddr_in6 *sa6 = (const void *)ai->ai_addr; const struct in6_addr *ipaddr6 = &sa6->sin6_addr; @@ -176,28 +166,23 @@ void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, * the DNS caching. Without alloc. Return length of the id string. */ static size_t -create_hostcache_id(const char *name, - size_t nlen, /* 0 or actual name length */ - int port, char *ptr, size_t buflen) +create_dnscache_id(const char *name, + size_t nlen, /* 0 or actual name length */ + int port, char *ptr, size_t buflen) { size_t len = nlen ? nlen : strlen(name); - size_t olen = 0; DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); if(len > (buflen - 7)) len = buflen - 7; /* store and lower case the name */ - while(len--) { - *ptr++ = Curl_raw_tolower(*name++); - olen++; - } - olen += msnprintf(ptr, 7, ":%u", port); - return olen; + Curl_strntolower(ptr, name, len); + return msnprintf(&ptr[len], 7, ":%u", port) + len; } -struct hostcache_prune_data { +struct dnscache_prune_data { time_t now; time_t oldest; /* oldest time in cache not pruned. */ - int cache_timeout; + int max_age_sec; }; /* @@ -208,16 +193,16 @@ struct hostcache_prune_data { * cache. */ static int -hostcache_timestamp_remove(void *datap, void *hc) +dnscache_entry_is_stale(void *datap, void *hc) { - struct hostcache_prune_data *prune = - (struct hostcache_prune_data *) datap; - struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; + struct dnscache_prune_data *prune = + (struct dnscache_prune_data *) datap; + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) hc; - if(c->timestamp) { + if(dns->timestamp) { /* age in seconds */ - time_t age = prune->now - c->timestamp; - if(age >= prune->cache_timeout) + time_t age = prune->now - dns->timestamp; + if(age >= (time_t)prune->max_age_sec) return TRUE; if(age > prune->oldest) prune->oldest = age; @@ -230,44 +215,67 @@ hostcache_timestamp_remove(void *datap, void *hc) * Returns the 'age' of the oldest still kept entry. */ static time_t -hostcache_prune(struct Curl_hash *hostcache, int cache_timeout, - time_t now) +dnscache_prune(struct Curl_hash *hostcache, int cache_timeout, + time_t now) { - struct hostcache_prune_data user; + struct dnscache_prune_data user; - user.cache_timeout = cache_timeout; + user.max_age_sec = cache_timeout; user.now = now; user.oldest = 0; Curl_hash_clean_with_criterium(hostcache, (void *) &user, - hostcache_timestamp_remove); + dnscache_entry_is_stale); return user.oldest; } +static struct Curl_dnscache *dnscache_get(struct Curl_easy *data) +{ + if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) + return &data->share->dnscache; + if(data->multi) + return &data->multi->dnscache; + return NULL; +} + +static void dnscache_lock(struct Curl_easy *data, + struct Curl_dnscache *dnscache) +{ + if(data->share && dnscache == &data->share->dnscache) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); +} + +static void dnscache_unlock(struct Curl_easy *data, + struct Curl_dnscache *dnscache) +{ + if(data->share && dnscache == &data->share->dnscache) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + /* * Library-wide function for pruning the DNS cache. This function takes and * returns the appropriate locks. */ -void Curl_hostcache_prune(struct Curl_easy *data) +void Curl_dnscache_prune(struct Curl_easy *data) { + struct Curl_dnscache *dnscache = dnscache_get(data); time_t now; /* the timeout may be set -1 (forever) */ int timeout = data->set.dns_cache_timeout; - if(!data->dns.hostcache) - /* NULL hostcache means we can't do it */ + if(!dnscache) + /* NULL hostcache means we cannot do it */ return; - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + dnscache_lock(data, dnscache); - time(&now); + now = time(NULL); do { /* Remove outdated and unused entries from the hostcache */ - time_t oldest = hostcache_prune(data->dns.hostcache, timeout, now); + time_t oldest = dnscache_prune(&dnscache->entries, timeout, now); if(oldest < INT_MAX) timeout = (int)oldest; /* we know it fits */ @@ -276,10 +284,10 @@ void Curl_hostcache_prune(struct Curl_easy *data) /* if the cache size is still too big, use the oldest age as new prune limit */ - } while(timeout && (data->dns.hostcache->size > MAX_DNS_CACHE_SIZE)); + } while(timeout && + (Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)); - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + dnscache_unlock(data, dnscache); } #ifdef USE_ALARM_TIMEOUT @@ -292,72 +300,78 @@ static curl_simple_lock curl_jmpenv_lock; /* lookup address, returns entry if found and not stale */ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, const char *hostname, - int port) + int port, + int ip_version) { struct Curl_dns_entry *dns = NULL; char entry_id[MAX_HOSTCACHE_LEN]; + size_t entry_len; + + if(!dnscache) + return NULL; /* Create an entry id, based upon the hostname and port */ - size_t entry_len = create_hostcache_id(hostname, 0, port, - entry_id, sizeof(entry_id)); + entry_len = create_dnscache_id(hostname, 0, port, + entry_id, sizeof(entry_id)); - /* See if its already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + /* See if it is already in our dns cache */ + dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); /* No entry found in cache, check if we might have a wildcard entry */ if(!dns && data->state.wildcard_resolve) { - entry_len = create_hostcache_id("*", 1, port, entry_id, sizeof(entry_id)); + entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id)); - /* See if it's already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + /* See if it is already in our dns cache */ + dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); } if(dns && (data->set.dns_cache_timeout != -1)) { /* See whether the returned entry is stale. Done before we release lock */ - struct hostcache_prune_data user; + struct dnscache_prune_data user; - time(&user.now); - user.cache_timeout = data->set.dns_cache_timeout; + user.now = time(NULL); + user.max_age_sec = data->set.dns_cache_timeout; user.oldest = 0; - if(hostcache_timestamp_remove(&user, dns)) { + if(dnscache_entry_is_stale(&user, dns)) { infof(data, "Hostname in DNS cache was stale, zapped"); dns = NULL; /* the memory deallocation is being handled by the hash */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } } /* See if the returned entry matches the required resolve mode */ - if(dns && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) { + if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) { int pf = PF_INET; - bool found = false; + bool found = FALSE; struct Curl_addrinfo *addr = dns->addr; #ifdef PF_INET6 - if(data->conn->ip_version == CURL_IPRESOLVE_V6) + if(ip_version == CURL_IPRESOLVE_V6) pf = PF_INET6; #endif while(addr) { if(addr->ai_family == pf) { - found = true; + found = TRUE; break; } addr = addr->ai_next; } if(!found) { - infof(data, "Hostname in DNS cache doesn't have needed family, zapped"); + infof(data, "Hostname in DNS cache does not have needed family, zapped"); dns = NULL; /* the memory deallocation is being handled by the hash */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } } return dns; } /* - * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. * * Curl_resolv() checks initially and multi_runsingle() checks each time * it discovers the handle in the state WAITRESOLVE whether the hostname @@ -367,31 +381,43 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, * * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. * - * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after - * use, or we'll leak memory! + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_fetch_addr(struct Curl_easy *data, - const char *hostname, - int port) +Curl_dnscache_get(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { + struct Curl_dnscache *dnscache = dnscache_get(data); struct Curl_dns_entry *dns = NULL; - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = fetch_addr(data, hostname, port); + dnscache_lock(data, dnscache); + dns = fetch_addr(data, dnscache, hostname, port, ip_version); if(dns) - dns->inuse++; /* we use it! */ + dns->refcount++; /* we use it! */ - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + dnscache_unlock(data, dnscache); return dns; } #ifndef CURL_DISABLE_SHUFFLE_DNS +/* + * Return # of addresses in a Curl_addrinfo struct + */ +static int num_addresses(const struct Curl_addrinfo *addr) +{ + int i = 0; + while(addr) { + addr = addr->ai_next; + i++; + } + return i; +} + UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, struct Curl_addrinfo **addr); /* @@ -434,8 +460,8 @@ UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) { struct Curl_addrinfo *swap_tmp; for(i = num_addrs - 1; i > 0; i--) { - swap_tmp = nodes[rnd[i] % (i + 1)]; - nodes[rnd[i] % (i + 1)] = nodes[i]; + swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)]; + nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i]; nodes[i] = swap_tmp; } @@ -460,66 +486,112 @@ UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, } #endif -/* - * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. - * - * When calling Curl_resolv() has resulted in a response with a returned - * address, we call this function to store the information in the dns - * cache etc - * - * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. - */ struct Curl_dns_entry * -Curl_cache_addr(struct Curl_easy *data, - struct Curl_addrinfo *addr, - const char *hostname, - size_t hostlen, /* length or zero */ - int port) +Curl_dnscache_mk_entry(struct Curl_easy *data, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hostlen, /* length or zero */ + int port, + bool permanent) { - char entry_id[MAX_HOSTCACHE_LEN]; - size_t entry_len; struct Curl_dns_entry *dns; - struct Curl_dns_entry *dns2; #ifndef CURL_DISABLE_SHUFFLE_DNS /* shuffle addresses if requested */ if(data->set.dns_shuffle_addresses) { CURLcode result = Curl_shuffle_addr(data, &addr); - if(result) + if(result) { + Curl_freeaddrinfo(addr); return NULL; + } } #endif + if(!hostlen) + hostlen = strlen(hostname); /* Create a new cache entry */ - dns = calloc(1, sizeof(struct Curl_dns_entry)); + dns = calloc(1, sizeof(struct Curl_dns_entry) + hostlen); if(!dns) { + Curl_freeaddrinfo(addr); return NULL; } - /* Create an entry id, based upon the hostname and port */ - entry_len = create_hostcache_id(hostname, hostlen, port, - entry_id, sizeof(entry_id)); - - dns->inuse = 1; /* the cache has the first reference */ + dns->refcount = 1; /* the cache has the first reference */ dns->addr = addr; /* this is the address(es) */ - time(&dns->timestamp); - if(dns->timestamp == 0) - dns->timestamp = 1; /* zero indicates permanent CURLOPT_RESOLVE entry */ + if(permanent) + dns->timestamp = 0; /* an entry that never goes stale */ + else { + dns->timestamp = time(NULL); + if(dns->timestamp == 0) + dns->timestamp = 1; + } + dns->hostport = port; + if(hostlen) + memcpy(dns->hostname, hostname, hostlen); + + return dns; +} + +static struct Curl_dns_entry * +dnscache_add_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hlen, /* length or zero */ + int port, + bool permanent) +{ + char entry_id[MAX_HOSTCACHE_LEN]; + size_t entry_len; + struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns2; + + dns = Curl_dnscache_mk_entry(data, addr, hostname, hlen, port, permanent); + if(!dns) + return NULL; + + /* Create an entry id, based upon the hostname and port */ + entry_len = create_dnscache_id(hostname, hlen, port, + entry_id, sizeof(entry_id)); /* Store the resolved data in our DNS cache. */ - dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1, + dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1, (void *)dns); if(!dns2) { - free(dns); + dnscache_entry_free(dns); return NULL; } dns = dns2; - dns->inuse++; /* mark entry as in-use */ + dns->refcount++; /* mark entry as in-use */ return dns; } -#ifdef ENABLE_IPV6 +CURLcode Curl_dnscache_add(struct Curl_easy *data, + struct Curl_dns_entry *entry) +{ + struct Curl_dnscache *dnscache = dnscache_get(data); + char id[MAX_HOSTCACHE_LEN]; + size_t idlen; + + if(!dnscache) + return CURLE_FAILED_INIT; + /* Create an entry id, based upon the hostname and port */ + idlen = create_dnscache_id(entry->hostname, 0, entry->hostport, + id, sizeof(id)); + + /* Store the resolved data in our DNS cache and up ref count */ + dnscache_lock(data, dnscache); + if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) { + dnscache_unlock(data, dnscache); + return CURLE_OUT_OF_MEMORY; + } + entry->refcount++; + dnscache_unlock(data, dnscache); + return CURLE_OK; +} + +#ifdef USE_IPV6 /* return a static IPv6 ::1 for the name */ static struct Curl_addrinfo *get_localhost6(int port, const char *name) { @@ -529,16 +601,18 @@ static struct Curl_addrinfo *get_localhost6(int port, const char *name) struct sockaddr_in6 sa6; unsigned char ipv6[16]; unsigned short port16 = (unsigned short)(port & 0xffff); - ca = calloc(sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1, 1); + ca = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1); if(!ca) return NULL; sa6.sin6_family = AF_INET6; sa6.sin6_port = htons(port16); sa6.sin6_flowinfo = 0; +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID sa6.sin6_scope_id = 0; - if(Curl_inet_pton(AF_INET6, "::1", ipv6) < 1) - return NULL; +#endif + + (void)curlx_inet_pton(AF_INET6, "::1", ipv6); memcpy(&sa6.sin6_addr, ipv6, sizeof(ipv6)); ca->ai_flags = 0; @@ -561,6 +635,7 @@ static struct Curl_addrinfo *get_localhost6(int port, const char *name) static struct Curl_addrinfo *get_localhost(int port, const char *name) { struct Curl_addrinfo *ca; + struct Curl_addrinfo *ca6; const size_t ss_size = sizeof(struct sockaddr_in); const size_t hostlen = strlen(name); struct sockaddr_in sa; @@ -571,11 +646,11 @@ static struct Curl_addrinfo *get_localhost(int port, const char *name) memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port16); - if(Curl_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1) + if(curlx_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1) return NULL; memcpy(&sa.sin_addr, &ipv4, sizeof(ipv4)); - ca = calloc(sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1, 1); + ca = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1); if(!ca) return NULL; ca->ai_flags = 0; @@ -587,20 +662,24 @@ static struct Curl_addrinfo *get_localhost(int port, const char *name) memcpy(ca->ai_addr, &sa, ss_size); ca->ai_canonname = (char *)ca->ai_addr + ss_size; strcpy(ca->ai_canonname, name); - ca->ai_next = get_localhost6(port, name); - return ca; + + ca6 = get_localhost6(port, name); + if(!ca6) + return ca; + ca6->ai_next = ca; + return ca6; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * Curl_ipv6works() returns TRUE if IPv6 seems to work. */ bool Curl_ipv6works(struct Curl_easy *data) { if(data) { - /* the nature of most system is that IPv6 status doesn't come and go + /* the nature of most system is that IPv6 status does not come and go during a program's lifetime so we only probe the first time and then we - have the info kept for fast re-use */ + have the info kept for fast reuse */ DEBUGASSERT(data); DEBUGASSERT(data->multi); if(data->multi->ipv6_up == IPV6_UNKNOWN) { @@ -614,16 +693,16 @@ bool Curl_ipv6works(struct Curl_easy *data) /* probe to see if we have a working IPv6 stack */ curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); if(s == CURL_SOCKET_BAD) - /* an IPv6 address was requested but we can't get/use one */ + /* an IPv6 address was requested but we cannot get/use one */ ipv6_works = 0; else { ipv6_works = 1; sclose(s); } - return (ipv6_works>0)?TRUE:FALSE; + return ipv6_works > 0; } } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ /* * Curl_host_is_ipnum() returns TRUE if the given string is a numerical IPv4 @@ -632,12 +711,12 @@ bool Curl_ipv6works(struct Curl_easy *data) bool Curl_host_is_ipnum(const char *hostname) { struct in_addr in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr in6; #endif - if(Curl_inet_pton(AF_INET, hostname, &in) > 0 -#ifdef ENABLE_IPV6 - || Curl_inet_pton(AF_INET6, hostname, &in6) > 0 + if(curlx_inet_pton(AF_INET, hostname, &in) > 0 +#ifdef USE_IPV6 + || curlx_inet_pton(AF_INET6, hostname, &in6) > 0 #endif ) return TRUE; @@ -646,220 +725,245 @@ bool Curl_host_is_ipnum(const char *hostname) /* return TRUE if 'part' is a case insensitive tail of 'full' */ -static bool tailmatch(const char *full, const char *part) +static bool tailmatch(const char *full, size_t flen, + const char *part, size_t plen) { - size_t plen = strlen(part); - size_t flen = strlen(full); if(plen > flen) return FALSE; return strncasecompare(part, &full[flen - plen], plen); } +static struct Curl_addrinfo * +convert_ipaddr_direct(const char *hostname, int port, bool *is_ipaddr) +{ + struct in_addr in; + *is_ipaddr = FALSE; + /* First check if this is an IPv4 address string */ + if(curlx_inet_pton(AF_INET, hostname, &in) > 0) { + /* This is a dotted IP address 123.123.123.123-style */ + *is_ipaddr = TRUE; +#ifdef USE_RESOLVE_ON_IPS + (void)port; + return NULL; +#else + return Curl_ip2addr(AF_INET, &in, hostname, port); +#endif + } +#ifdef USE_IPV6 + else { + struct in6_addr in6; + /* check if this is an IPv6 address string */ + if(curlx_inet_pton(AF_INET6, hostname, &in6) > 0) { + /* This is an IPv6 address literal */ + *is_ipaddr = TRUE; +#ifdef USE_RESOLVE_ON_IPS + return NULL; +#else + return Curl_ip2addr(AF_INET6, &in6, hostname, port); +#endif + } + } +#endif /* USE_IPV6 */ + return NULL; +} + +static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version) +{ +#ifdef CURLRES_IPV6 + if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) + return FALSE; +#elif defined(CURLRES_IPV4) + (void)data; + if(ip_version == CURL_IPRESOLVE_V6) + return FALSE; +#else +#error either CURLRES_IPV6 or CURLRES_IPV4 need to be defined +#endif + return TRUE; +} + /* * Curl_resolv() is the main name resolve function within libcurl. It resolves * a name and returns a pointer to the entry in the 'entry' argument (if one - * is provided). This function might return immediately if we're using asynch + * is provided). This function might return immediately if we are using asynch * resolves. See the return codes. * * The cache entry we return will get its 'inuse' counter increased when this - * function is used. You MUST call Curl_resolv_unlock() later (when you're - * done using this struct) to decrease the counter again. + * function is used. You MUST call Curl_resolv_unlink() later (when you are + * done using this struct) to decrease the reference counter again. * * Return codes: - * - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer + * CURLE_OK = success, *entry set to non-NULL + * CURLE_AGAIN = resolving in progress, *entry == NULL + * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL + * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL */ - -enum resolve_t Curl_resolv(struct Curl_easy *data, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **entry) +CURLcode Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + bool allowDOH, + struct Curl_dns_entry **entry) { + struct Curl_dnscache *dnscache = dnscache_get(data); struct Curl_dns_entry *dns = NULL; - CURLcode result; - enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */ - struct connectdata *conn = data->conn; + struct Curl_addrinfo *addr = NULL; + int respwait = 0; + bool is_ipaddr; + size_t hostname_len; + +#ifndef CURL_DISABLE_DOH + data->conn->bits.doh = FALSE; /* default is not */ +#else + (void)allowDOH; +#endif + if(!dnscache) + goto error; + /* We should intentionally error and not resolve .onion TLDs */ - size_t hostname_len = strlen(hostname); + hostname_len = strlen(hostname); if(hostname_len >= 7 && (curl_strequal(&hostname[hostname_len - 6], ".onion") || curl_strequal(&hostname[hostname_len - 7], ".onion."))) { failf(data, "Not resolving .onion address (RFC 7686)"); - return CURLRESOLV_ERROR; + goto error; } - *entry = NULL; -#ifndef CURL_DISABLE_DOH - conn->bits.doh = FALSE; /* default is not */ -#else - (void)allowDOH; -#endif - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = fetch_addr(data, hostname, port); + /* Let's check our DNS cache first */ + dnscache_lock(data, dnscache); + dns = fetch_addr(data, dnscache, hostname, port, ip_version); + if(dns) + dns->refcount++; /* we pass out the reference. */ + dnscache_unlock(data, dnscache); if(dns) { infof(data, "Hostname %s was found in DNS cache", hostname); - dns->inuse++; /* we use it! */ - rc = CURLRESOLV_RESOLVED; + goto out; } - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - /* The entry was not in the cache. Resolve it to IP address */ - - struct Curl_addrinfo *addr = NULL; - int respwait = 0; -#if !defined(CURL_DISABLE_DOH) || !defined(USE_RESOLVE_ON_IPS) - struct in_addr in; -#endif -#ifndef CURL_DISABLE_DOH -#ifndef USE_RESOLVE_ON_IPS - const -#endif - bool ipnum = FALSE; -#endif - - /* notify the resolver start callback */ - if(data->set.resolver_start) { - int st; - Curl_set_in_callback(data, true); - st = data->set.resolver_start( -#ifdef USE_CURL_ASYNC - data->state.async.resolver, -#else - NULL, + /* No luck, we need to resolve hostname. Notify user callback. */ + if(data->set.resolver_start) { + void *resolver = NULL; + int st; +#ifdef CURLRES_ASYNCH + if(Curl_async_get_impl(data, &resolver)) + goto error; #endif - NULL, - data->set.resolver_start_client); - Curl_set_in_callback(data, false); - if(st) - return CURLRESOLV_ERROR; - } + Curl_set_in_callback(data, TRUE); + st = data->set.resolver_start(resolver, NULL, + data->set.resolver_start_client); + Curl_set_in_callback(data, FALSE); + if(st) + goto error; + } -#if defined(ENABLE_IPV6) && defined(CURL_OSX_CALL_COPYPROXIES) - { - /* - * The automagic conversion from IPv4 literals to IPv6 literals only - * works if the SCDynamicStoreCopyProxies system function gets called - * first. As Curl currently doesn't support system-wide HTTP proxies, we - * therefore don't use any value this function might return. - * - * This function is only available on a macOS and is not needed for - * IPv4-only builds, hence the conditions above. - */ - CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL); - if(dict) - CFRelease(dict); - } -#endif + /* shortcut literal IP addresses, if we are not told to resolve them. */ + addr = convert_ipaddr_direct(hostname, port, &is_ipaddr); + if(addr) + goto out; #ifndef USE_RESOLVE_ON_IPS - /* First check if this is an IPv4 address string */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - addr = Curl_ip2addr(AF_INET, &in, hostname, port); -#ifdef ENABLE_IPV6 - if(!addr) { - struct in6_addr in6; - /* check if this is an IPv6 address string */ - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) - /* This is an IPv6 address literal */ - addr = Curl_ip2addr(AF_INET6, &in6, hostname, port); - } -#endif /* ENABLE_IPV6 */ - -#else /* if USE_RESOLVE_ON_IPS */ -#ifndef CURL_DISABLE_DOH - /* First check if this is an IPv4 address string */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - ipnum = TRUE; -#ifdef ENABLE_IPV6 - else { - struct in6_addr in6; - /* check if this is an IPv6 address string */ - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) - /* This is an IPv6 address literal */ - ipnum = TRUE; - } -#endif /* ENABLE_IPV6 */ -#endif /* CURL_DISABLE_DOH */ - -#endif /* !USE_RESOLVE_ON_IPS */ + /* allowed to convert, hostname is IP address, then NULL means error */ + if(is_ipaddr) + goto error; +#endif - if(!addr) { - if(conn->ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) - return CURLRESOLV_ERROR; + /* Really need a resolver for hostname. */ + if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) + goto error; - if(strcasecompare(hostname, "localhost") || - tailmatch(hostname, ".localhost")) - addr = get_localhost(port, hostname); + if(!is_ipaddr && + (strcasecompare(hostname, "localhost") || + strcasecompare(hostname, "localhost.") || + tailmatch(hostname, hostname_len, STRCONST(".localhost")) || + tailmatch(hostname, hostname_len, STRCONST(".localhost.")))) { + addr = get_localhost(port, hostname); + } #ifndef CURL_DISABLE_DOH - else if(allowDOH && data->set.doh && !ipnum) - addr = Curl_doh(data, hostname, port, &respwait); + else if(!is_ipaddr && allowDOH && data->set.doh) { + addr = Curl_doh(data, hostname, port, ip_version, &respwait); + } #endif - else { - /* Check what IP specifics the app has requested and if we can provide - * it. If not, bail out. */ - if(!Curl_ipvalid(data, conn)) - return CURLRESOLV_ERROR; - /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a - non-zero value indicating that we need to wait for the response to - the resolve call */ - addr = Curl_getaddrinfo(data, hostname, port, &respwait); - } - } - if(!addr) { - if(respwait) { - /* the response to our resolve call will come asynchronously at - a later time, good or bad */ - /* First, check that we haven't received the info by now */ - result = Curl_resolv_check(data, &dns); - if(result) /* error detected */ - return CURLRESOLV_ERROR; - if(dns) - rc = CURLRESOLV_RESOLVED; /* pointer provided */ - else - rc = CURLRESOLV_PENDING; /* no info yet */ - } - } - else { - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - /* we got a response, store it in the cache */ - dns = Curl_cache_addr(data, addr, hostname, 0, port); + else { + /* Can we provide the requested IP specifics in resolving? */ + if(!can_resolve_ip_version(data, ip_version)) + goto error; - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +#ifdef CURLRES_ASYNCH + addr = Curl_async_getaddrinfo(data, hostname, port, ip_version, &respwait); +#else + respwait = 0; /* no async waiting here */ + addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version); +#endif + } - if(!dns) - /* returned failure, bail out nicely */ - Curl_freeaddrinfo(addr); - else - rc = CURLRESOLV_RESOLVED; +out: + /* We either have found a `dns` or looked up the `addr` + * or `respwait` is set for an async operation. + * Everything else is a failure to resolve. */ + if(dns) { + *entry = dns; + return CURLE_OK; + } + else if(addr) { + /* we got a response, create a dns entry, add to cache, return */ + dns = Curl_dnscache_mk_entry(data, addr, hostname, 0, port, FALSE); + if(!dns) + goto error; + if(Curl_dnscache_add(data, dns)) + goto error; + show_resolve_info(data, dns); + *entry = dns; + return CURLE_OK; + } + else if(respwait) { + if(!Curl_resolv_check(data, &dns)) { + *entry = dns; + return dns ? CURLE_OK : CURLE_AGAIN; } } +error: + if(dns) + Curl_resolv_unlink(data, &dns); + *entry = NULL; + Curl_async_shutdown(data); + return CURLE_COULDNT_RESOLVE_HOST; +} - *entry = dns; +CURLcode Curl_resolv_blocking(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **dnsentry) +{ + CURLcode result; - return rc; + *dnsentry = NULL; + result = Curl_resolv(data, hostname, port, ip_version, FALSE, dnsentry); + switch(result) { + case CURLE_OK: + DEBUGASSERT(*dnsentry); + return CURLE_OK; + case CURLE_AGAIN: + DEBUGASSERT(!*dnsentry); + result = Curl_async_await(data, dnsentry); + if(result || !*dnsentry) { + /* close the connection, since we cannot return failure here without + cleaning up this connection properly. */ + connclose(data->conn, "async resolve failed"); + } + return result; + default: + return result; + } } #ifdef USE_ALARM_TIMEOUT /* * This signal handler jumps back into the main libcurl code and continues - * execution. This effectively causes the remainder of the application to run + * execution. This effectively causes the remainder of the application to run * within a signal handler which is nonportable and could lead to problems. */ -static +CURL_NORETURN static void alarmfunc(int sig) { (void)sig; @@ -869,12 +973,12 @@ void alarmfunc(int sig) /* * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a - * timeout. This function might return immediately if we're using asynch + * timeout. This function might return immediately if we are using asynch * resolves. See the return codes. * * The cache entry we return will get its 'inuse' counter increased when this - * function is used. You MUST call Curl_resolv_unlock() later (when you're - * done using this struct) to decrease the counter again. + * function is used. You MUST call Curl_resolv_unlink() later (when you are + * done using this struct) to decrease the reference counter again. * * If built with a synchronous resolver and use of signals is not * disabled by the application, then a nonzero timeout will cause a @@ -882,18 +986,18 @@ void alarmfunc(int sig) * is ignored. * * Return codes: - * - * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer + * CURLE_OK = success, *entry set to non-NULL + * CURLE_AGAIN = resolving in progress, *entry == NULL + * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL + * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL */ -enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, - const char *hostname, - int port, - struct Curl_dns_entry **entry, - timediff_t timeoutms) +CURLcode Curl_resolv_timeout(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **entry, + timediff_t timeoutms) { #ifdef USE_ALARM_TIMEOUT #ifdef HAVE_SIGACTION @@ -908,13 +1012,13 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, volatile long timeout; volatile unsigned int prev_alarm = 0; #endif /* USE_ALARM_TIMEOUT */ - enum resolve_t rc; + CURLcode result; *entry = NULL; if(timeoutms < 0) /* got an already expired timeout */ - return CURLRESOLV_TIMEDOUT; + return CURLE_OPERATION_TIMEDOUT; #ifdef USE_ALARM_TIMEOUT if(data->set.no_signal) @@ -923,9 +1027,14 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, else timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms; - if(!timeout) - /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */ - return Curl_resolv(data, hostname, port, TRUE, entry); + if(!timeout +#ifndef CURL_DISABLE_DOH + || data->set.doh +#endif + ) + /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve + done using DoH */ + return Curl_resolv(data, hostname, port, ip_version, TRUE, entry); if(timeout < 1000) { /* The alarm() function only provides integer second resolution, so if @@ -933,20 +1042,20 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, failf(data, "remaining timeout of %ld too small to resolve via SIGALRM method", timeout); - return CURLRESOLV_TIMEDOUT; + return CURLE_OPERATION_TIMEDOUT; } /* This allows us to time-out from the name resolver, as the timeout will generate a signal and we will siglongjmp() from that here. This technique has problems (see alarmfunc). This should be the last thing we do before calling Curl_resolv(), - as otherwise we'd have to worry about variables that get modified + as otherwise we would have to worry about variables that get modified before we invoke Curl_resolv() (and thus use "volatile"). */ curl_simple_lock_lock(&curl_jmpenv_lock); if(sigsetjmp(curl_jmpenv, 1)) { /* this is coming from a siglongjmp() after an alarm signal */ failf(data, "name lookup timed out"); - rc = CURLRESOLV_ERROR; + result = CURLE_OPERATION_TIMEDOUT; goto clean_up; } else { @@ -960,7 +1069,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, keep_copysig = TRUE; /* yes, we have a copy */ sigact.sa_handler = alarmfunc; #ifdef SA_RESTART - /* HPUX doesn't have SA_RESTART but defaults to that behavior! */ + /* HP-UX does not have SA_RESTART but defaults to that behavior! */ sigact.sa_flags &= ~SA_RESTART; #endif /* now set the new struct */ @@ -977,19 +1086,19 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, prev_alarm = alarm(curlx_sltoui(timeout/1000L)); } -#else +#else /* USE_ALARM_TIMEOUT */ #ifndef CURLRES_ASYNCH if(timeoutms) infof(data, "timeout on name lookup is not supported"); #else (void)timeoutms; /* timeoutms not used with an async resolver */ #endif -#endif /* USE_ALARM_TIMEOUT */ +#endif /* else USE_ALARM_TIMEOUT */ /* Perform the actual name resolution. This might be interrupted by an * alarm if it takes too long. */ - rc = Curl_resolv(data, hostname, port, TRUE, entry); + result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry); #ifdef USE_ALARM_TIMEOUT clean_up: @@ -1017,7 +1126,7 @@ enum resolve_t Curl_resolv_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 = Curl_timediff(Curl_now(), + timediff_t elapsed_secs = curlx_timediff(curlx_now(), data->conn->created) / 1000; /* the alarm period is counted in even number of seconds */ @@ -1027,10 +1136,10 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) { /* if the alarm time-left reached zero or turned "negative" (counted with unsigned values), we should fire off a SIGALRM here, but we - won't, and zero would be to switch it off so we never set it to + will not, and zero would be to switch it off so we never set it to less than 1! */ alarm(1); - rc = CURLRESOLV_TIMEDOUT; + result = CURLE_OPERATION_TIMEDOUT; failf(data, "Previous alarm fired off"); } else @@ -1038,111 +1147,109 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, } #endif /* USE_ALARM_TIMEOUT */ - return rc; + return result; } -/* - * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been - * made, the struct may be destroyed due to pruning. It is important that only - * one unlock is made for each Curl_resolv() call. - * - * May be called with 'data' == NULL for global cache. - */ -void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns) +static void dnscache_entry_free(struct Curl_dns_entry *dns) { - if(data && data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - freednsentry(dns); - - if(data && data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + Curl_freeaddrinfo(dns->addr); +#ifdef USE_HTTPSRR + if(dns->hinfo) { + Curl_httpsrr_cleanup(dns->hinfo); + free(dns->hinfo); + } +#endif + free(dns); } /* - * File-internal: release cache dns entry reference, free if inuse drops to 0 + * Curl_resolv_unlink() releases a reference to the given cached DNS entry. + * When the reference count reaches 0, the entry is destroyed. It is important + * that only one unlink is made for each Curl_resolv() call. + * + * May be called with 'data' == NULL for global cache. */ -static void freednsentry(void *freethis) +void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns) { - struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis; - DEBUGASSERT(dns && (dns->inuse>0)); - - dns->inuse--; - if(dns->inuse == 0) { - Curl_freeaddrinfo(dns->addr); - free(dns); + if(*pdns) { + struct Curl_dnscache *dnscache = dnscache_get(data); + struct Curl_dns_entry *dns = *pdns; + *pdns = NULL; + dnscache_lock(data, dnscache); + dns->refcount--; + if(dns->refcount == 0) + dnscache_entry_free(dns); + dnscache_unlock(data, dnscache); } } -/* - * Curl_init_dnscache() inits a new DNS cache. - */ -void Curl_init_dnscache(struct Curl_hash *hash, int size) +static void dnscache_entry_dtor(void *entry) { - Curl_hash_init(hash, size, Curl_hash_str, Curl_str_key_compare, - freednsentry); + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) entry; + DEBUGASSERT(dns && (dns->refcount > 0)); + dns->refcount--; + if(dns->refcount == 0) + dnscache_entry_free(dns); } /* - * Curl_hostcache_clean() - * - * This _can_ be called with 'data' == NULL but then of course no locking - * can be done! + * Curl_dnscache_init() inits a new DNS cache. */ - -void Curl_hostcache_clean(struct Curl_easy *data, - struct Curl_hash *hash) +void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size) { - if(data && data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - Curl_hash_clean(hash); - - if(data && data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare, + dnscache_entry_dtor); } +void Curl_dnscache_destroy(struct Curl_dnscache *dns) +{ + Curl_hash_destroy(&dns->entries); +} CURLcode Curl_loadhostpairs(struct Curl_easy *data) { + struct Curl_dnscache *dnscache = dnscache_get(data); struct curl_slist *hostp; - char *host_end; + + if(!dnscache) + return CURLE_FAILED_INIT; /* Default is no wildcard found */ - data->state.wildcard_resolve = false; + data->state.wildcard_resolve = FALSE; for(hostp = data->state.resolve; hostp; hostp = hostp->next) { char entry_id[MAX_HOSTCACHE_LEN]; - if(!hostp->data) + const char *host = hostp->data; + struct Curl_str source; + if(!host) continue; - if(hostp->data[0] == '-') { - unsigned long num = 0; + if(*host == '-') { + curl_off_t num = 0; size_t entry_len; - size_t hlen = 0; - host_end = strchr(&hostp->data[1], ':'); - - if(host_end) { - hlen = host_end - &hostp->data[1]; - num = strtoul(++host_end, NULL, 10); - if(!hlen || (num > 0xffff)) - host_end = NULL; + host++; + if(!curlx_str_single(&host, '[')) { + if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || + curlx_str_single(&host, ']') || + curlx_str_single(&host, ':')) + continue; } - if(!host_end) { - infof(data, "Bad syntax CURLOPT_RESOLVE removal entry '%s'", - hostp->data); - continue; + else { + if(curlx_str_until(&host, &source, 4096, ':') || + curlx_str_single(&host, ':')) { + continue; + } } - /* Create an entry id, based upon the hostname and port */ - entry_len = create_hostcache_id(&hostp->data[1], hlen, (int)num, - entry_id, sizeof(entry_id)); - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - /* delete entry, ignore if it didn't exist */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + if(!curlx_str_number(&host, &num, 0xffff)) { + /* Create an entry id, based upon the hostname and port */ + entry_len = create_dnscache_id(curlx_str(&source), + curlx_strlen(&source), (int)num, + entry_id, sizeof(entry_id)); + dnscache_lock(data, dnscache); + /* delete entry, ignore if it did not exist */ + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); + dnscache_unlock(data, dnscache); + } } else { struct Curl_dns_entry *dns; @@ -1150,75 +1257,69 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) size_t entry_len; char address[64]; #if !defined(CURL_DISABLE_VERBOSE_STRINGS) - char *addresses = NULL; + const char *addresses = NULL; #endif - char *addr_begin; - char *addr_end; - char *port_ptr; - int port = 0; - char *end_ptr; + curl_off_t port = 0; bool permanent = TRUE; - unsigned long tmp_port; - bool error = true; - char *host_begin = hostp->data; - size_t hlen = 0; + bool error = TRUE; - if(host_begin[0] == '+') { - host_begin++; + if(*host == '+') { + host++; permanent = FALSE; } - host_end = strchr(host_begin, ':'); - if(!host_end) - goto err; - hlen = host_end - host_begin; - - port_ptr = host_end + 1; - tmp_port = strtoul(port_ptr, &end_ptr, 10); - if(tmp_port > USHRT_MAX || end_ptr == port_ptr || *end_ptr != ':') + if(!curlx_str_single(&host, '[')) { + if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || + curlx_str_single(&host, ']')) + continue; + } + else { + if(curlx_str_until(&host, &source, 4096, ':')) + continue; + } + if(curlx_str_single(&host, ':') || + curlx_str_number(&host, &port, 0xffff) || + curlx_str_single(&host, ':')) goto err; - port = (int)tmp_port; #if !defined(CURL_DISABLE_VERBOSE_STRINGS) - addresses = end_ptr + 1; + addresses = host; #endif - while(*end_ptr) { - size_t alen; + /* start the address section */ + while(*host) { + struct Curl_str target; struct Curl_addrinfo *ai; - addr_begin = end_ptr + 1; - addr_end = strchr(addr_begin, ','); - if(!addr_end) - addr_end = addr_begin + strlen(addr_begin); - end_ptr = addr_end; - - /* allow IP(v6) address within [brackets] */ - if(*addr_begin == '[') { - if(addr_end == addr_begin || *(addr_end - 1) != ']') + if(!curlx_str_single(&host, '[')) { + if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') || + curlx_str_single(&host, ']')) goto err; - ++addr_begin; - --addr_end; } - - alen = addr_end - addr_begin; - if(!alen) - continue; - - if(alen >= sizeof(address)) - goto err; - - memcpy(address, addr_begin, alen); - address[alen] = '\0'; - -#ifndef ENABLE_IPV6 - if(strchr(address, ':')) { + else { + if(curlx_str_until(&host, &target, 4096, ',')) { + if(curlx_str_single(&host, ',')) + goto err; + /* survive nothing but just a comma */ + continue; + } + } +#ifndef USE_IPV6 + if(memchr(target.str, ':', target.len)) { infof(data, "Ignoring resolve address '%s', missing IPv6 support.", address); + if(curlx_str_single(&host, ',')) + goto err; continue; } #endif - ai = Curl_str2addr(address, port); + if(curlx_strlen(&target) >= sizeof(address)) + goto err; + + memcpy(address, curlx_str(&target), curlx_strlen(&target)); + address[curlx_strlen(&target)] = '\0'; + + ai = Curl_str2addr(address, (int)port); if(!ai) { infof(data, "Resolve address '%s' found illegal", address); goto err; @@ -1231,12 +1332,14 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) else { head = tail = ai; } + if(curlx_str_single(&host, ',')) + break; } if(!head) goto err; - error = false; + error = FALSE; err: if(error) { failf(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'", @@ -1246,18 +1349,19 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) } /* Create an entry id, based upon the hostname and port */ - entry_len = create_hostcache_id(host_begin, hlen, port, - entry_id, sizeof(entry_id)); + entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source), + (int)port, + entry_id, sizeof(entry_id)); - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + dnscache_lock(data, dnscache); - /* See if it's already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + /* See if it is already in our dns cache */ + dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); if(dns) { - infof(data, "RESOLVE %.*s:%d is - old addresses discarded", - (int)hlen, host_begin, port); + infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T + " - old addresses discarded", (int)curlx_strlen(&source), + curlx_str(&source), port); /* delete old entry, there are two reasons for this 1. old entry may have different addresses. 2. even if entry with correct addresses is already in the cache, @@ -1269,34 +1373,34 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) 4. when adding a non-permanent entry, we want it to get a "fresh" timeout that starts _now_. */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } /* put this new host in the cache */ - dns = Curl_cache_addr(data, head, host_begin, hlen, port); + dns = dnscache_add_addr(data, dnscache, head, curlx_str(&source), + curlx_strlen(&source), (int)port, permanent); if(dns) { - if(permanent) - dns->timestamp = 0; /* mark as permanent */ /* release the returned reference; the cache itself will keep the * entry alive: */ - dns->inuse--; + dns->refcount--; } - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + dnscache_unlock(data, dnscache); - if(!dns) { - Curl_freeaddrinfo(head); + if(!dns) return CURLE_OUT_OF_MEMORY; - } - infof(data, "Added %.*s:%d:%s to DNS cache%s", - (int)hlen, host_begin, port, addresses, + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s", + (int)curlx_strlen(&source), curlx_str(&source), port, addresses, permanent ? "" : " (non-permanent)"); +#endif /* Wildcard hostname */ - if((hlen == 1) && (host_begin[0] == '*')) { - infof(data, "RESOLVE *:%d using wildcard", port); - data->state.wildcard_resolve = true; + if(curlx_str_casecompare(&source, "*")) { + infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard", + port); + data->state.wildcard_resolve = TRUE; } } } @@ -1305,19 +1409,107 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) return CURLE_OK; } +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void show_resolve_info(struct Curl_easy *data, + struct Curl_dns_entry *dns) +{ + struct Curl_addrinfo *a; + CURLcode result = CURLE_OK; +#ifdef CURLRES_IPV6 + struct dynbuf out[2]; +#else + struct dynbuf out[1]; +#endif + DEBUGASSERT(data); + DEBUGASSERT(dns); + + if(!data->set.verbose || + /* ignore no name or numerical IP addresses */ + !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname)) + return; + + a = dns->addr; + + infof(data, "Host %s:%d was resolved.", + (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport); + + curlx_dyn_init(&out[0], 1024); +#ifdef CURLRES_IPV6 + curlx_dyn_init(&out[1], 1024); +#endif + + while(a) { + if( +#ifdef CURLRES_IPV6 + a->ai_family == PF_INET6 || +#endif + a->ai_family == PF_INET) { + char buf[MAX_IPADR_LEN]; + struct dynbuf *d = &out[(a->ai_family != PF_INET)]; + Curl_printable_address(a, buf, sizeof(buf)); + if(curlx_dyn_len(d)) + result = curlx_dyn_addn(d, ", ", 2); + if(!result) + result = curlx_dyn_add(d, buf); + if(result) { + infof(data, "too many IP, cannot show"); + goto fail; + } + } + a = a->ai_next; + } + +#ifdef CURLRES_IPV6 + infof(data, "IPv6: %s", + (curlx_dyn_len(&out[1]) ? curlx_dyn_ptr(&out[1]) : "(none)")); +#endif + infof(data, "IPv4: %s", + (curlx_dyn_len(&out[0]) ? curlx_dyn_ptr(&out[0]) : "(none)")); + +fail: + curlx_dyn_free(&out[0]); +#ifdef CURLRES_IPV6 + curlx_dyn_free(&out[1]); +#endif +} +#endif + +#ifdef USE_CURL_ASYNC CURLcode Curl_resolv_check(struct Curl_easy *data, struct Curl_dns_entry **dns) { -#if defined(CURL_DISABLE_DOH) && !defined(CURLRES_ASYNCH) - (void)data; - (void)dns; -#endif + CURLcode result; + + /* If async resolving is ongoing, this must be set */ + if(!data->state.async.hostname) + return CURLE_FAILED_INIT; + + /* check if we have the name resolved by now (from someone else) */ + *dns = Curl_dnscache_get(data, data->state.async.hostname, + data->state.async.port, + data->state.async.ip_version); + if(*dns) { + /* Tell a possibly async resolver we no longer need the results. */ + infof(data, "Hostname '%s' was found in DNS cache", + data->state.async.hostname); + Curl_async_shutdown(data); + data->state.async.dns = *dns; + data->state.async.done = TRUE; + return CURLE_OK; + } + #ifndef CURL_DISABLE_DOH - if(data->conn->bits.doh) - return Curl_doh_is_resolved(data, dns); + if(data->conn->bits.doh) { + result = Curl_doh_is_resolved(data, dns); + } + else #endif - return Curl_resolver_is_resolved(data, dns); + result = Curl_async_is_resolved(data, dns); + if(*dns) + show_resolve_info(data, *dns); + return result; } +#endif int Curl_resolv_getsock(struct Curl_easy *data, curl_socket_t *socks) @@ -1329,7 +1521,7 @@ int Curl_resolv_getsock(struct Curl_easy *data, sockets */ return GETSOCK_BLANK; #endif - return Curl_resolver_getsock(data, socks); + return Curl_async_getsock(data, socks); #else (void)data; (void)socks; @@ -1342,24 +1534,25 @@ int Curl_resolv_getsock(struct Curl_easy *data, Note: this function disconnects and frees the conn data in case of resolve failure */ -CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) +CURLcode Curl_once_resolved(struct Curl_easy *data, + struct Curl_dns_entry *dns, + bool *protocol_done) { CURLcode result; struct connectdata *conn = data->conn; #ifdef USE_CURL_ASYNC if(data->state.async.dns) { - conn->dns_entry = data->state.async.dns; + DEBUGASSERT(data->state.async.dns == dns); data->state.async.dns = NULL; } #endif - result = Curl_setup_conn(data, protocol_done); + result = Curl_setup_conn(data, dns, protocol_done); if(result) { Curl_detach_connection(data); - Curl_conncache_remove_conn(data, conn, TRUE); - Curl_disconnect(data, conn, TRUE); + Curl_conn_terminate(data, conn, TRUE); } return result; } @@ -1372,25 +1565,21 @@ CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) #ifdef USE_CURL_ASYNC CURLcode Curl_resolver_error(struct Curl_easy *data) { - const char *host_or_proxy; - CURLcode result; + struct connectdata *conn = data->conn; + const char *host_or_proxy = "host"; + const char *name = conn->host.dispname; + CURLcode result = CURLE_COULDNT_RESOLVE_HOST; #ifndef CURL_DISABLE_PROXY - struct connectdata *conn = data->conn; - if(conn->bits.httpproxy) { + if(conn->bits.proxy) { host_or_proxy = "proxy"; result = CURLE_COULDNT_RESOLVE_PROXY; + name = conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname : + conn->http_proxy.host.dispname; } - else #endif - { - host_or_proxy = "host"; - result = CURLE_COULDNT_RESOLVE_HOST; - } - - failf(data, "Could not resolve %s: %s", host_or_proxy, - data->state.async.hostname); + failf(data, "Could not resolve %s: %s", host_or_proxy, name); return result; } #endif /* USE_CURL_ASYNC */ diff --git a/Utilities/cmcurl/lib/hostip.h b/Utilities/cmcurl/lib/hostip.h index 06d08672777..cd3d957e1e7 100644 --- a/Utilities/cmcurl/lib/hostip.h +++ b/Utilities/cmcurl/lib/hostip.h @@ -27,11 +27,14 @@ #include "curl_setup.h" #include "hash.h" #include "curl_addrinfo.h" -#include "timeval.h" /* for timediff_t */ +#include "curlx/timeval.h" /* for timediff_t */ #include "asyn.h" +#include "httpsrr.h" -#ifdef HAVE_SETJMP_H #include + +#ifdef USE_HTTPSRR +# include #endif /* Allocate enough memory to hold the full name information structs and @@ -51,21 +54,30 @@ struct hostent; struct Curl_easy; struct connectdata; -/* - * Curl_global_host_cache_init() initializes and sets up a global DNS cache. - * Global DNS cache is general badness. Do not use. This will be removed in - * a future version. Use the share interface instead! - * - * Returns a struct Curl_hash pointer on success, NULL on failure. - */ -struct Curl_hash *Curl_global_host_cache_init(void); +enum alpnid { + ALPN_none = 0, + ALPN_h1 = CURLALTSVC_H1, + ALPN_h2 = CURLALTSVC_H2, + ALPN_h3 = CURLALTSVC_H3 +}; struct Curl_dns_entry { struct Curl_addrinfo *addr; - /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (doesn't time out) */ +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo *hinfo; +#endif + /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ time_t timestamp; - /* use-counter, use Curl_resolv_unlock to release reference */ - long inuse; + /* reference counter, entry is freed on reaching 0 */ + size_t refcount; + /* hostname port number that resolved to addr. */ + int hostport; + /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ + char hostname[1]; +}; + +struct Curl_dnscache { + struct Curl_hash entries; }; bool Curl_host_is_ipnum(const char *hostname); @@ -74,27 +86,29 @@ bool Curl_host_is_ipnum(const char *hostname); * Curl_resolv() returns an entry with the info for the specified host * and port. * - * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after - * use, or we'll leak memory! - */ -/* return codes */ -enum resolve_t { - CURLRESOLV_TIMEDOUT = -2, - CURLRESOLV_ERROR = -1, - CURLRESOLV_RESOLVED = 0, - CURLRESOLV_PENDING = 1 -}; -enum resolve_t Curl_resolv(struct Curl_easy *data, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **dnsentry); -enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, - const char *hostname, int port, - struct Curl_dns_entry **dnsentry, - timediff_t timeoutms); - -#ifdef ENABLE_IPV6 + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! + */ +CURLcode Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + bool allowDOH, + struct Curl_dns_entry **dnsentry); + +CURLcode Curl_resolv_blocking(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **dnsentry); + +CURLcode Curl_resolv_timeout(struct Curl_easy *data, + const char *hostname, int port, + int ip_version, + struct Curl_dns_entry **dnsentry, + timediff_t timeoutms); + +#ifdef USE_IPV6 /* * Curl_ipv6works() returns TRUE if IPv6 seems to work. */ @@ -103,49 +117,25 @@ bool Curl_ipv6works(struct Curl_easy *data); #define Curl_ipv6works(x) FALSE #endif -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn); - -/* - * Curl_getaddrinfo() is the generic low-level name resolve API within this - * source file. There are several versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp); - - -/* unlock a previously resolved dns entry */ -void Curl_resolv_unlock(struct Curl_easy *data, - struct Curl_dns_entry *dns); +/* unlink a dns entry, potentially shared with a cache */ +void Curl_resolv_unlink(struct Curl_easy *data, + struct Curl_dns_entry **pdns); /* init a new dns cache */ -void Curl_init_dnscache(struct Curl_hash *hash, int hashsize); +void Curl_dnscache_init(struct Curl_dnscache *dns, size_t hashsize); + +void Curl_dnscache_destroy(struct Curl_dnscache *dns); /* prune old entries from the DNS cache */ -void Curl_hostcache_prune(struct Curl_easy *data); +void Curl_dnscache_prune(struct Curl_easy *data); /* IPv4 threadsafe resolve function used for synch and asynch builds */ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); -CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_connect); - -/* - * Curl_addrinfo_callback() is used when we build with any asynch specialty. - * Handles end of async request processing. Inserts ai into hostcache when - * status is CURL_ASYNC_SUCCESS. Twiddles fields in conn to indicate async - * request completed whether successful or failed. - */ -CURLcode Curl_addrinfo_callback(struct Curl_easy *data, - int status, - struct Curl_addrinfo *ai); +CURLcode Curl_once_resolved(struct Curl_easy *data, + struct Curl_dns_entry *dns, + bool *protocol_connect); /* * Curl_printable_address() returns a printable version of the 1st address @@ -156,72 +146,72 @@ void Curl_printable_address(const struct Curl_addrinfo *ip, char *buf, size_t bufsize); /* - * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * Make a `Curl_dns_entry`. + * Creates a dnscache entry *without* adding it to a dnscache. This allows + * further modifications of the entry *before* then adding it to a cache. * - * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. + * The entry is created with a reference count of 1. + * Use `Curl_resolv_unlink()` to release your hold on it. * - * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after - * use, or we'll leak memory! + * The call takes ownership of `addr`and makes a copy of `hostname`. + * + * Returns entry or NULL on OOM. */ struct Curl_dns_entry * -Curl_fetch_addr(struct Curl_easy *data, - const char *hostname, - int port); +Curl_dnscache_mk_entry(struct Curl_easy *data, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hostlen, /* length or zero */ + int port, + bool permanent); /* - * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. + * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. + * + * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. * - * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr, - const char *hostname, size_t hostlen, int port); - -#ifndef INADDR_NONE -#define CURL_INADDR_NONE (in_addr_t) ~0 -#else -#define CURL_INADDR_NONE INADDR_NONE -#endif - -/* - * Function provided by the resolver backend to set DNS servers to use. - */ -CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers); - -/* - * Function provided by the resolver backend to set - * outgoing interface to use for DNS requests - */ -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf); - -/* - * Function provided by the resolver backend to set - * local IPv4 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4); +Curl_dnscache_get(struct Curl_easy *data, + const char *hostname, + int port, int ip_version); /* - * Function provided by the resolver backend to set - * local IPv6 address to use as source address for DNS requests + * Curl_dnscache_addr() adds `entry` to the cache, increasing its + * reference count on success. */ -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6); - -/* - * Clean off entries from the cache - */ -void Curl_hostcache_clean(struct Curl_easy *data, struct Curl_hash *hash); +CURLcode Curl_dnscache_add(struct Curl_easy *data, + struct Curl_dns_entry *entry); /* * Populate the cache with specified entries from CURLOPT_RESOLVE. */ CURLcode Curl_loadhostpairs(struct Curl_easy *data); + +#ifdef USE_CURL_ASYNC CURLcode Curl_resolv_check(struct Curl_easy *data, struct Curl_dns_entry **dns); +#else +#define Curl_resolv_check(x,y) CURLE_NOT_BUILT_IN +#endif int Curl_resolv_getsock(struct Curl_easy *data, curl_socket_t *socks); CURLcode Curl_resolver_error(struct Curl_easy *data); + +#ifdef CURLRES_SYNCH +/* + * Curl_sync_getaddrinfo() is the non-async low-level name resolve API. + * There are several versions of this function - depending on IPV6 + * support and platform. + */ +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version); + +#endif + #endif /* HEADER_CURL_HOSTIP_H */ diff --git a/Utilities/cmcurl/lib/hostip4.c b/Utilities/cmcurl/lib/hostip4.c index 9140180ffd9..14e4d98a866 100644 --- a/Utilities/cmcurl/lib/hostip4.c +++ b/Utilities/cmcurl/lib/hostip4.c @@ -54,24 +54,11 @@ #include "curl_memory.h" #include "memdebug.h" -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) -{ - (void)data; - if(conn->ip_version == CURL_IPRESOLVE_V6) - /* An IPv6 address was requested and we can't get/use one */ - return FALSE; - - return TRUE; /* OK, proceed */ -} #ifdef CURLRES_SYNCH /* - * Curl_getaddrinfo() - the IPv4 synchronous version. + * Curl_sync_getaddrinfo() - the IPv4 synchronous version. * * The original code to this function was from the Dancer source code, written * by Bjorn Reese, it has since been patched and modified considerably. @@ -82,23 +69,22 @@ bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) * detect which one this platform supports in the configure script and set up * the HAVE_GETHOSTBYNAME_R_3, HAVE_GETHOSTBYNAME_R_5 or * HAVE_GETHOSTBYNAME_R_6 defines accordingly. Note that HAVE_GETADDRBYNAME - * has the corresponding rules. This is primarily on *nix. Note that some unix + * has the corresponding rules. This is primarily on *nix. Note that some Unix * flavours have thread-safe versions of the plain gethostbyname() etc. * */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { struct Curl_addrinfo *ai = NULL; + (void)ip_version; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - *waitp = 0; /* synchronous response only */ - ai = Curl_ipv4_resolve_r(hostname, port); if(!ai) infof(data, "Curl_ipv4_resolve_r failed for %s", hostname); @@ -126,8 +112,10 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int res; #endif struct Curl_addrinfo *ai = NULL; +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) struct hostent *h = NULL; struct hostent *buf = NULL; +#endif #if defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE) struct addrinfo hints; @@ -193,8 +181,8 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * small. Previous versions are known to return ERANGE for the same * problem. * - * This wouldn't be such a big problem if older versions wouldn't - * sometimes return EAGAIN on a common failure case. Alas, we can't + * This would not be such a big problem if older versions would not + * sometimes return EAGAIN on a common failure case. Alas, we cannot * assume that EAGAIN *or* ERANGE means ERANGE for any given version of * glibc. * @@ -210,9 +198,9 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * gethostbyname_r() in glibc: * * In glibc 2.2.5 the interface is different (this has also been - * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I can't + * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I cannot * explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 - * (shipped/upgraded by Redhat 7.2) don't show this behavior! + * (shipped/upgraded by Redhat 7.2) do not show this behavior! * * In this "buggy" version, the return code is -1 on error and 'errno' * is set to the ERANGE or EAGAIN code. Note that 'errno' is not a @@ -221,9 +209,9 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, if(!h) /* failure */ #elif defined(HAVE_GETHOSTBYNAME_R_3) - /* AIX, Digital Unix/Tru64, HPUX 10, more? */ + /* AIX, Digital UNIX/Tru64, HP-UX 10, more? */ - /* For AIX 4.3 or later, we don't use gethostbyname_r() at all, because of + /* For AIX 4.3 or later, we do not use gethostbyname_r() at all, because of * the plain fact that it does not return unique full buffers on each * call, but instead several of the pointers in the hostent structs will * point to the same actual data! This have the unfortunate down-side that @@ -237,7 +225,7 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * * Troels Walsted Hansen helped us work this out on March 3rd, 2003. * - * [*] = much later we've found out that it isn't at all "completely + * [*] = much later we have found out that it is not at all "completely * thread-safe", but at least the gethostbyname() function is. */ @@ -253,7 +241,7 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, (struct hostent *)buf, (struct hostent_data *)((char *)buf + sizeof(struct hostent))); - h_errnop = SOCKERRNO; /* we don't deal with this, but set it anyway */ + h_errnop = SOCKERRNO; /* we do not deal with this, but set it anyway */ } else res = -1; /* failure, too smallish buffer size */ @@ -263,8 +251,8 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, h = buf; /* result expected in h */ /* This is the worst kind of the different gethostbyname_r() interfaces. - * Since we don't know how big buffer this particular lookup required, - * we can't realloc down the huge alloc without doing closer analysis of + * Since we do not know how big buffer this particular lookup required, + * we cannot realloc down the huge alloc without doing closer analysis of * the returned data. Thus, we always use CURL_HOSTENT_SIZE for every * name lookup. Fixing this would require an extra malloc() and then * calling Curl_addrinfo_copy() that subsequent realloc()s down the new @@ -280,20 +268,22 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, #else /* (HAVE_GETADDRINFO && HAVE_GETADDRINFO_THREADSAFE) || HAVE_GETHOSTBYNAME_R */ /* - * Here is code for platforms that don't have a thread safe + * Here is code for platforms that do not have a thread safe * getaddrinfo() nor gethostbyname_r() function or for which * gethostbyname() is the preferred one. */ - h = gethostbyname((void *)hostname); + h = gethostbyname(CURL_UNCONST(hostname)); #endif /* (HAVE_GETADDRINFO && HAVE_GETADDRINFO_THREADSAFE) || HAVE_GETHOSTBYNAME_R */ +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) if(h) { ai = Curl_he2ai(h, port); if(buf) /* used a *_r() function */ free(buf); } +#endif return ai; } diff --git a/Utilities/cmcurl/lib/hostip6.c b/Utilities/cmcurl/lib/hostip6.c index 6b0ba55e9f3..35cc2d737bb 100644 --- a/Utilities/cmcurl/lib/hostip6.c +++ b/Utilities/cmcurl/lib/hostip6.c @@ -49,30 +49,17 @@ #include "hash.h" #include "share.h" #include "url.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "connect.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) -{ - if(conn->ip_version == CURL_IPRESOLVE_V6) - return Curl_ipv6works(data); - - return TRUE; -} - #if defined(CURLRES_SYNCH) #ifdef DEBUG_ADDRINFO -static void dump_addrinfo(struct connectdata *conn, - const struct Curl_addrinfo *ai) +static void dump_addrinfo(const struct Curl_addrinfo *ai) { printf("dump_addrinfo:\n"); for(; ai; ai = ai->ai_next) { @@ -84,11 +71,11 @@ static void dump_addrinfo(struct connectdata *conn, } } #else -#define dump_addrinfo(x,y) Curl_nop_stmt +#define dump_addrinfo(x) Curl_nop_stmt #endif /* - * Curl_getaddrinfo() when built IPv6-enabled (non-threading and + * Curl_sync_getaddrinfo() when built IPv6-enabled (non-threading and * non-ares version). * * Returns name information about the given hostname and port number. If @@ -96,10 +83,10 @@ static void dump_addrinfo(struct connectdata *conn, * to memory we need to free after use. That memory *MUST* be freed with * Curl_freeaddrinfo(), nothing else. */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { struct addrinfo hints; struct Curl_addrinfo *res; @@ -111,9 +98,7 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, #endif int pf = PF_INET; - *waitp = 0; /* synchronous response only */ - - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) /* The stack seems to be IPv6-enabled */ pf = PF_UNSPEC; @@ -125,10 +110,10 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, #ifndef USE_RESOLVE_ON_IPS /* * The AI_NUMERICHOST must not be set to get synthesized IPv6 address from - * an IPv4 address on iOS and Mac OS X. + * an IPv4 address on iOS and macOS. */ - if((1 == Curl_inet_pton(AF_INET, hostname, addrbuf)) || - (1 == Curl_inet_pton(AF_INET6, hostname, addrbuf))) { + if((1 == curlx_inet_pton(AF_INET, hostname, addrbuf)) || + (1 == curlx_inet_pton(AF_INET6, hostname, addrbuf))) { /* the given address is numerical only, prevent a reverse lookup */ hints.ai_flags = AI_NUMERICHOST; } @@ -149,7 +134,7 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, Curl_addrinfo_set_port(res, port); } - dump_addrinfo(conn, res); + dump_addrinfo(res); return res; } diff --git a/Utilities/cmcurl/lib/hostsyn.c b/Utilities/cmcurl/lib/hostsyn.c deleted file mode 100644 index ca8b0758c47..00000000000 --- a/Utilities/cmcurl/lib/hostsyn.c +++ /dev/null @@ -1,104 +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" - -/*********************************************************************** - * Only for builds using synchronous name resolves - **********************************************************************/ -#ifdef CURLRES_SYNCH - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * Function provided by the resolver backend to set DNS servers to use. - */ -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -/* - * Function provided by the resolver backend to set - * outgoing interface to use for DNS requests - */ -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) -{ - (void)data; - (void)interf; - return CURLE_NOT_BUILT_IN; -} - -/* - * Function provided by the resolver backend to set - * local IPv4 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) -{ - (void)data; - (void)local_ip4; - return CURLE_NOT_BUILT_IN; -} - -/* - * Function provided by the resolver backend to set - * local IPv6 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) -{ - (void)data; - (void)local_ip6; - return CURLE_NOT_BUILT_IN; -} - -#endif /* truly sync */ diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c index 53c01fc52c8..62a3f890589 100644 --- a/Utilities/cmcurl/lib/hsts.c +++ b/Utilities/cmcurl/lib/hsts.c @@ -35,11 +35,12 @@ #include "curl_get_line.h" #include "strcase.h" #include "sendf.h" -#include "strtoofft.h" #include "parsedate.h" #include "fopen.h" #include "rename.h" #include "share.h" +#include "strdup.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -47,35 +48,33 @@ #include "memdebug.h" #define MAX_HSTS_LINE 4095 -#define MAX_HSTS_HOSTLEN 256 -#define MAX_HSTS_HOSTLENSTR "256" -#define MAX_HSTS_DATELEN 64 -#define MAX_HSTS_DATELENSTR "64" +#define MAX_HSTS_HOSTLEN 2048 +#define MAX_HSTS_DATELEN 256 #define UNLIMITED "unlimited" -#ifdef DEBUGBUILD +#if defined(DEBUGBUILD) || defined(UNITTESTS) /* to play well with debug builds, we can *set* a fixed time this will return */ time_t deltatime; /* allow for "adjustments" for unit test purposes */ -static time_t debugtime(void *unused) +static time_t hsts_debugtime(void *unused) { - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); (void)unused; if(timestr) { curl_off_t val; - (void)curlx_strtoofft(timestr, NULL, 10, &val); - - val += (curl_off_t)deltatime; + if(!curlx_str_number(×tr, &val, TIME_T_MAX)) + val += (curl_off_t)deltatime; return (time_t)val; } return time(NULL); } -#define time(x) debugtime(x) +#undef time +#define time(x) hsts_debugtime(x) #endif struct hsts *Curl_hsts_init(void) { - struct hsts *h = calloc(sizeof(struct hsts), 1); + struct hsts *h = calloc(1, sizeof(struct hsts)); if(h) { Curl_llist_init(&h->list, NULL); } @@ -84,7 +83,7 @@ struct hsts *Curl_hsts_init(void) static void hsts_free(struct stsentry *e) { - free((char *)e->host); + free(CURL_UNCONST(e->host)); free(e); } @@ -92,11 +91,11 @@ void Curl_hsts_cleanup(struct hsts **hp) { struct hsts *h = *hp; if(h) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; - n = e->next; + struct Curl_llist_node *e; + struct Curl_llist_node *n; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + n = Curl_node_next(e); hsts_free(sts); } free(h->filename); @@ -105,37 +104,35 @@ void Curl_hsts_cleanup(struct hsts **hp) } } -static struct stsentry *hsts_entry(void) -{ - return calloc(sizeof(struct stsentry), 1); -} - static CURLcode hsts_create(struct hsts *h, const char *hostname, + size_t hlen, bool subdomains, curl_off_t expires) { - struct stsentry *sts = hsts_entry(); - char *duphost; - size_t hlen; - if(!sts) - return CURLE_OUT_OF_MEMORY; + DEBUGASSERT(h); + DEBUGASSERT(hostname); + + if(hlen && (hostname[hlen - 1] == '.')) + /* strip off any trailing dot */ + --hlen; + if(hlen) { + char *duphost; + struct stsentry *sts = calloc(1, sizeof(struct stsentry)); + if(!sts) + return CURLE_OUT_OF_MEMORY; + + duphost = Curl_memdup0(hostname, hlen); + if(!duphost) { + free(sts); + return CURLE_OUT_OF_MEMORY; + } - duphost = strdup(hostname); - if(!duphost) { - free(sts); - return CURLE_OUT_OF_MEMORY; + sts->host = duphost; + sts->expires = expires; + sts->includeSubDomains = subdomains; + Curl_llist_append(&h->list, sts, &sts->node); } - - hlen = strlen(duphost); - if(duphost[hlen - 1] == '.') - /* strip off trailing any dot */ - duphost[--hlen] = 0; - - sts->host = duphost; - sts->expires = expires; - sts->includeSubDomains = subdomains; - Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node); return CURLE_OK; } @@ -149,6 +146,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, bool subdomains = FALSE; struct stsentry *sts; time_t now = time(NULL); + size_t hlen = strlen(hostname); if(Curl_host_is_ipnum(hostname)) /* "explicit IP address identification of all forms is excluded." @@ -156,30 +154,30 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, return CURLE_OK; do { - while(*p && ISBLANK(*p)) - p++; - if(strncasecompare("max-age=", p, 8)) { + curlx_str_passblanks(&p); + if(strncasecompare("max-age", p, 7)) { bool quoted = FALSE; - CURLofft offt; - char *endp; + int rc; if(gotma) return CURLE_BAD_FUNCTION_ARGUMENT; - p += 8; - while(*p && ISBLANK(*p)) - p++; - if(*p == '\"') { - p++; + p += 7; + curlx_str_passblanks(&p); + if(curlx_str_single(&p, '=')) + return CURLE_BAD_FUNCTION_ARGUMENT; + curlx_str_passblanks(&p); + + if(!curlx_str_single(&p, '\"')) quoted = TRUE; - } - offt = curlx_strtoofft(p, &endp, 10, &expires); - if(offt == CURL_OFFT_FLOW) + + rc = curlx_str_number(&p, &expires, TIME_T_MAX); + if(rc == STRE_OVERFLOW) expires = CURL_OFF_T_MAX; - else if(offt) + else if(rc) /* invalid max-age */ return CURLE_BAD_FUNCTION_ARGUMENT; - p = endp; + if(quoted) { if(*p != '\"') return CURLE_BAD_FUNCTION_ARGUMENT; @@ -200,8 +198,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, p++; } - while(*p && ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); if(*p == ';') p++; } while(*p); @@ -212,9 +209,9 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, if(!expires) { /* remove the entry if present verbatim (without subdomain match) */ - sts = Curl_hsts(h, hostname, FALSE); + sts = Curl_hsts(h, hostname, hlen, FALSE); if(sts) { - Curl_llist_remove(&h->list, &sts->node, NULL); + Curl_node_remove(&sts->node); hsts_free(sts); } return CURLE_OK; @@ -227,66 +224,67 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, expires += now; /* check if it already exists */ - sts = Curl_hsts(h, hostname, FALSE); + sts = Curl_hsts(h, hostname, hlen, FALSE); if(sts) { /* just update these fields */ sts->expires = expires; sts->includeSubDomains = subdomains; } else - return hsts_create(h, hostname, subdomains, expires); + return hsts_create(h, hostname, hlen, subdomains, expires); return CURLE_OK; } /* - * Return TRUE if the given host name is currently an HSTS one. + * Return TRUE if the given hostname is currently an HSTS one. * * The 'subdomain' argument tells the function if subdomain matching should be * attempted. */ struct stsentry *Curl_hsts(struct hsts *h, const char *hostname, - bool subdomain) + size_t hlen, bool subdomain) { + struct stsentry *bestsub = NULL; if(h) { - char buffer[MAX_HSTS_HOSTLEN + 1]; time_t now = time(NULL); - size_t hlen = strlen(hostname); - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; + size_t blen = 0; if((hlen > MAX_HSTS_HOSTLEN) || !hlen) return NULL; - memcpy(buffer, hostname, hlen); if(hostname[hlen-1] == '.') /* remove the trailing dot */ --hlen; - buffer[hlen] = 0; - hostname = buffer; - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; - n = e->next; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + size_t ntail; + n = Curl_node_next(e); if(sts->expires <= now) { /* remove expired entries */ - Curl_llist_remove(&h->list, &sts->node, NULL); + Curl_node_remove(&sts->node); hsts_free(sts); continue; } - if(subdomain && sts->includeSubDomains) { - size_t ntail = strlen(sts->host); - if(ntail < hlen) { - size_t offs = hlen - ntail; - if((hostname[offs-1] == '.') && - strncasecompare(&hostname[offs], sts->host, ntail)) - return sts; + ntail = strlen(sts->host); + if((subdomain && sts->includeSubDomains) && (ntail < hlen)) { + size_t offs = hlen - ntail; + if((hostname[offs-1] == '.') && + strncasecompare(&hostname[offs], sts->host, ntail) && + (ntail > blen)) { + /* save the tail match with the longest tail */ + bestsub = sts; + blen = ntail; } } - if(strcasecompare(hostname, sts->host)) + /* avoid strcasecompare because the host name is not null-terminated */ + if((hlen == ntail) && strncasecompare(hostname, sts->host, hlen)) return sts; } } - return NULL; /* no match */ + return bestsub; } /* @@ -302,7 +300,7 @@ static CURLcode hsts_push(struct Curl_easy *data, struct tm stamp; CURLcode result; - e.name = (char *)sts->host; + e.name = (char *)CURL_UNCONST(sts->host); e.namelen = strlen(sts->host); e.includeSubDomains = sts->includeSubDomains; @@ -352,8 +350,8 @@ static CURLcode hsts_out(struct stsentry *sts, FILE *fp) CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, const char *file) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; CURLcode result = CURLE_OK; FILE *out; char *tempstore = NULL; @@ -367,7 +365,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, file = h->filename; if((h->flags & CURLHSTS_READONLYFILE) || !file || !file[0]) - /* marked as read-only, no file or zero length file name */ + /* marked as read-only, no file or zero length filename */ goto skipsave; result = Curl_fopen(data, file, &out, &tempstore); @@ -375,9 +373,9 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, fputs("# Your HSTS cache. https://curl.se/docs/hsts.html\n" "# This file was generated by libcurl! Edit at your own risk.\n", out); - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; - n = e->next; + 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; @@ -392,14 +390,14 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, free(tempstore); skipsave: if(data->set.hsts_write) { - /* if there's a write callback */ + /* if there is a write callback */ struct curl_index i; /* count */ - i.total = h->list.size; + i.total = Curl_llist_count(&h->list); i.index = 0; - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); bool stop; - n = e->next; + n = Curl_node_next(e); result = hsts_push(data, &i, sts, &stop); if(result || stop) break; @@ -410,36 +408,47 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, } /* only returns SERIOUS errors */ -static CURLcode hsts_add(struct hsts *h, char *line) +static CURLcode hsts_add(struct hsts *h, const char *line) { /* Example lines: example.com "20191231 10:00:00" .example.net "20191231 10:00:00" */ - char host[MAX_HSTS_HOSTLEN + 1]; - char date[MAX_HSTS_DATELEN + 1]; - int rc; - - rc = sscanf(line, - "%" MAX_HSTS_HOSTLENSTR "s \"%" MAX_HSTS_DATELENSTR "[^\"]\"", - host, date); - if(2 == rc) { - time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) : - TIME_T_MAX; + struct Curl_str host; + struct Curl_str date; + + if(curlx_str_word(&line, &host, MAX_HSTS_HOSTLEN) || + curlx_str_singlespace(&line) || + curlx_str_quotedword(&line, &date, MAX_HSTS_DATELEN) || + curlx_str_newline(&line)) + ; + else { CURLcode result = CURLE_OK; - char *p = host; bool subdomain = FALSE; struct stsentry *e; - if(p[0] == '.') { - p++; + char dbuf[MAX_HSTS_DATELEN + 1]; + time_t expires; + const char *hp = curlx_str(&host); + + /* The date parser works on a null-terminated string. The maximum length + is upheld by curlx_str_quotedword(). */ + memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); + dbuf[curlx_strlen(&date)] = 0; + + expires = strcmp(dbuf, UNLIMITED) ? Curl_getdate_capped(dbuf) : + TIME_T_MAX; + + if(hp[0] == '.') { + curlx_str_nudge(&host, 1); subdomain = TRUE; } /* only add it if not already present */ - e = Curl_hsts(h, p, subdomain); + e = Curl_hsts(h, curlx_str(&host), curlx_strlen(&host), subdomain); if(!e) - result = hsts_create(h, p, subdomain, expires); - else { - /* the same host name, use the largest expire time */ + result = hsts_create(h, curlx_str(&host), curlx_strlen(&host), + subdomain, expires); + else if(curlx_str_casecompare(&host, e->host)) { + /* the same hostname, use the largest expire time */ if(expires > e->expires) e->expires = expires; } @@ -472,6 +481,7 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) if(sc == CURLSTS_OK) { time_t expires; CURLcode result; + DEBUGASSERT(e.name[0]); if(!e.name[0]) /* bail out if no name was stored */ return CURLE_BAD_FUNCTION_ARGUMENT; @@ -479,7 +489,7 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) expires = Curl_getdate_capped(e.expire); else expires = TIME_T_MAX; /* the end of time */ - result = hsts_create(h, e.name, + result = hsts_create(h, e.name, strlen(e.name), /* bitfield to bool conversion: */ e.includeSubDomains ? TRUE : FALSE, expires); @@ -504,10 +514,9 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) static CURLcode hsts_load(struct hsts *h, const char *file) { CURLcode result = CURLE_OK; - char *line = NULL; FILE *fp; - /* we need a private copy of the file name so that the hsts cache file + /* we need a private copy of the filename so that the hsts cache file name survives an easy handle reset */ free(h->filename); h->filename = strdup(file); @@ -516,28 +525,25 @@ static CURLcode hsts_load(struct hsts *h, const char *file) fp = fopen(file, FOPEN_READTEXT); if(fp) { - line = malloc(MAX_HSTS_LINE); - if(!line) - goto fail; - while(Curl_get_line(line, MAX_HSTS_LINE, fp)) { - char *lineptr = line; - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - if(*lineptr == '#') - /* skip commented lines */ + struct dynbuf buf; + curlx_dyn_init(&buf, MAX_HSTS_LINE); + while(Curl_get_line(&buf, fp)) { + const char *lineptr = curlx_dyn_ptr(&buf); + curlx_str_passblanks(&lineptr); + + /* + * Skip empty or commented lines, since we know the line will have a + * trailing newline from Curl_get_line we can treat length 1 as empty. + */ + if((*lineptr == '#') || strlen(lineptr) <= 1) continue; hsts_add(h, lineptr); } - free(line); /* free the line buffer */ + curlx_dyn_free(&buf); /* free the line buffer */ fclose(fp); } return result; - -fail: - Curl_safefree(h->filename); - fclose(fp); - return CURLE_OUT_OF_MEMORY; } /* @@ -563,7 +569,7 @@ CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h) void Curl_hsts_loadfiles(struct Curl_easy *data) { - struct curl_slist *l = data->set.hstslist; + struct curl_slist *l = data->state.hstslist; if(l) { Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE); diff --git a/Utilities/cmcurl/lib/hsts.h b/Utilities/cmcurl/lib/hsts.h index d3431a5d7a3..8ec9637cb0c 100644 --- a/Utilities/cmcurl/lib/hsts.h +++ b/Utilities/cmcurl/lib/hsts.h @@ -29,18 +29,18 @@ #include #include "llist.h" -#ifdef DEBUGBUILD +#if defined(DEBUGBUILD) || defined(UNITTESTS) extern time_t deltatime; #endif struct stsentry { - struct Curl_llist_element node; + struct Curl_llist_node node; const char *host; - bool includeSubDomains; curl_off_t expires; /* the timestamp of this entry's expiry */ + BIT(includeSubDomains); }; -/* The HSTS cache. Needs to be able to tailmatch host names. */ +/* The HSTS cache. Needs to be able to tailmatch hostnames. */ struct hsts { struct Curl_llist list; char *filename; @@ -52,7 +52,7 @@ void Curl_hsts_cleanup(struct hsts **hp); CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, const char *sts); struct stsentry *Curl_hsts(struct hsts *h, const char *hostname, - bool subdomain); + size_t hlen, bool subdomain); CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, const char *file); CURLcode Curl_hsts_loadfile(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c index 219dcc2c00f..5942d313b87 100644 --- a/Utilities/cmcurl/lib/http.c +++ b/Utilities/cmcurl/lib/http.c @@ -47,10 +47,6 @@ #include #endif -#ifdef USE_HYPER -#include -#endif - #include "urldata.h" #include #include "transfer.h" @@ -58,29 +54,29 @@ #include "formdata.h" #include "mime.h" #include "progress.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "cookie.h" #include "vauth/vauth.h" #include "vtls/vtls.h" #include "vquic/vquic.h" #include "http_digest.h" #include "http_ntlm.h" -#include "curl_ntlm_wb.h" #include "http_negotiate.h" #include "http_aws_sigv4.h" #include "url.h" +#include "urlapi-int.h" #include "share.h" #include "hostip.h" #include "dynhds.h" #include "http.h" +#include "headers.h" #include "select.h" #include "parsedate.h" /* for the week day and month names */ -#include "strtoofft.h" #include "multiif.h" #include "strcase.h" #include "content_encoding.h" #include "http_proxy.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "http2.h" #include "cfilters.h" #include "connect.h" @@ -88,8 +84,8 @@ #include "altsvc.h" #include "hsts.h" #include "ws.h" -#include "c-hyper.h" #include "curl_ctype.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -100,24 +96,41 @@ * Forward declarations. */ -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks); -static bool http_should_fail(struct Curl_easy *data); - -static CURLcode http_setup_conn(struct Curl_easy *data, +static bool http_should_fail(struct Curl_easy *data, int httpcode); +static bool http_exp100_is_waiting(struct Curl_easy *data); +static CURLcode http_exp100_add_reader(struct Curl_easy *data); +static void http_exp100_send_anyway(struct Curl_easy *data); +static bool http_exp100_is_selected(struct Curl_easy *data); +static void http_exp100_got100(struct Curl_easy *data); +static CURLcode http_firstwrite(struct Curl_easy *data); +static CURLcode http_header(struct Curl_easy *data, + const char *hd, size_t hdlen); +static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn); +static CURLcode http_range(struct Curl_easy *data, + Curl_HttpReq httpreq); +static CURLcode http_req_complete(struct Curl_easy *data, + struct dynbuf *r, int httpversion, + Curl_HttpReq httpreq); +static CURLcode http_req_set_reader(struct Curl_easy *data, + Curl_HttpReq httpreq, int httpversion, + const char **tep); +static CURLcode http_size(struct Curl_easy *data); +static CURLcode http_statusline(struct Curl_easy *data, struct connectdata *conn); -#ifdef USE_WEBSOCKETS -static CURLcode ws_setup_conn(struct Curl_easy *data, - struct connectdata *conn); +static CURLcode http_target(struct Curl_easy *data, struct connectdata *conn, + struct dynbuf *req); +static CURLcode http_useragent(struct Curl_easy *data); +#ifdef HAVE_LIBZ +static CURLcode http_transferencode(struct Curl_easy *data); #endif + /* * HTTP handler interface. */ const struct Curl_handler Curl_handler_http = { - "HTTP", /* scheme */ - http_setup_conn, /* setup_connection */ + "http", /* scheme */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -125,13 +138,15 @@ const struct Curl_handler Curl_handler_http = { ZERO_NULL, /* connecting */ ZERO_NULL, /* doing */ ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_HTTP, /* defport */ CURLPROTO_HTTP, /* protocol */ CURLPROTO_HTTP, /* family */ @@ -139,39 +154,13 @@ const struct Curl_handler Curl_handler_http = { PROTOPT_USERPWDCTRL }; -#ifdef USE_WEBSOCKETS -const struct Curl_handler Curl_handler_ws = { - "WS", /* scheme */ - ws_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - Curl_ws_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_WS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL -}; -#endif - #ifdef USE_SSL /* * HTTPS handler interface. */ const struct Curl_handler Curl_handler_https = { - "HTTPS", /* scheme */ - http_setup_conn, /* setup_connection */ + "https", /* scheme */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -179,13 +168,15 @@ const struct Curl_handler Curl_handler_https = { NULL, /* connecting */ ZERO_NULL, /* doing */ NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_HTTPS, /* defport */ CURLPROTO_HTTPS, /* protocol */ CURLPROTO_HTTP, /* family */ @@ -193,69 +184,61 @@ const struct Curl_handler Curl_handler_https = { PROTOPT_USERPWDCTRL }; -#ifdef USE_WEBSOCKETS -const struct Curl_handler Curl_handler_wss = { - "WSS", /* scheme */ - ws_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - NULL, /* connecting */ - ZERO_NULL, /* doing */ - NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - Curl_ws_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTPS, /* defport */ - CURLPROTO_WSS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL -}; #endif -#endif +void Curl_http_neg_init(struct Curl_easy *data, struct http_negotiation *neg) +{ + memset(neg, 0, sizeof(*neg)); + neg->accept_09 = data->set.http09_allowed; + switch(data->set.httpwant) { + case CURL_HTTP_VERSION_1_0: + neg->wanted = neg->allowed = (CURL_HTTP_V1x); + neg->only_10 = TRUE; + break; + case CURL_HTTP_VERSION_1_1: + neg->wanted = neg->allowed = (CURL_HTTP_V1x); + break; + case CURL_HTTP_VERSION_2_0: + neg->wanted = neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x); + neg->h2_upgrade = TRUE; + break; + case CURL_HTTP_VERSION_2TLS: + neg->wanted = neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x); + break; + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + neg->wanted = neg->allowed = (CURL_HTTP_V2x); + data->state.http_neg.h2_prior_knowledge = TRUE; + break; + case CURL_HTTP_VERSION_3: + neg->wanted = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x); + neg->allowed = neg->wanted; + break; + case CURL_HTTP_VERSION_3ONLY: + neg->wanted = neg->allowed = (CURL_HTTP_V3x); + break; + case CURL_HTTP_VERSION_NONE: + default: + neg->wanted = (CURL_HTTP_V1x | CURL_HTTP_V2x); + neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x); + break; + } +} -static CURLcode http_setup_conn(struct Curl_easy *data, - struct connectdata *conn) +CURLcode Curl_http_setup_conn(struct Curl_easy *data, + struct connectdata *conn) { /* allocate the HTTP-specific struct for the Curl_easy, only to survive during this request */ - struct HTTP *http; - DEBUGASSERT(data->req.p.http == NULL); - - http = calloc(1, sizeof(struct HTTP)); - if(!http) - return CURLE_OUT_OF_MEMORY; - - Curl_mime_initpart(&http->form); - data->req.p.http = http; connkeep(conn, "HTTP default"); - - if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { + if(data->state.http_neg.wanted == CURL_HTTP_V3x) { + /* only HTTP/3, needs to work */ CURLcode result = Curl_conn_may_http3(data, conn); if(result) return result; } - return CURLE_OK; } -#ifdef USE_WEBSOCKETS -static CURLcode ws_setup_conn(struct Curl_easy *data, - struct connectdata *conn) -{ - /* websockets is 1.1 only (for now) */ - data->state.httpwant = CURL_HTTP_VERSION_1_1; - return http_setup_conn(data, conn); -} -#endif - #ifndef CURL_DISABLE_PROXY /* * checkProxyHeaders() checks the linked list of custom proxy headers @@ -289,59 +272,33 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, #endif /* - * Strip off leading and trailing whitespace from the value in the - * given HTTP header line and return a strdupped copy. Returns NULL in - * case of allocation failure. Returns an empty string if the header value - * consists entirely of whitespace. + * Strip off leading and trailing whitespace from the value in the given HTTP + * header line and return a strdup()ed copy. Returns NULL in case of + * allocation failure or bad input. Returns an empty string if the header + * value consists entirely of whitespace. + * + * If the header is provided as "name;", ending with a semicolon, it must + * return a blank string. */ char *Curl_copy_header_value(const char *header) { - const char *start; - const char *end; - char *value; - size_t len; - - /* Find the end of the header name */ - while(*header && (*header != ':')) - ++header; - - if(*header) - /* Skip over colon */ - ++header; - - /* Find the first non-space letter */ - start = header; - while(*start && ISSPACE(*start)) - start++; - - /* data is in the host encoding so - use '\r' and '\n' instead of 0x0d and 0x0a */ - end = strchr(start, '\r'); - if(!end) - end = strchr(start, '\n'); - if(!end) - end = strchr(start, '\0'); - if(!end) - return NULL; - - /* skip all trailing space letters */ - while((end > start) && ISSPACE(*end)) - end--; + struct Curl_str out; - /* get length of the type */ - len = end - start + 1; + /* find the end of the header name */ + if(!curlx_str_cspn(&header, &out, ";:") && + (!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) { + curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_trimblanks(&out); - value = malloc(len + 1); - if(!value) - return NULL; - - memcpy(value, start, len); - value[len] = 0; /* null-terminate */ - - return value; + return Curl_memdup0(curlx_str(&out), curlx_strlen(&out)); + } + /* bad input */ + return NULL; } #ifndef CURL_DISABLE_HTTP_AUTH + +#ifndef CURL_DISABLE_BASIC_AUTH /* * http_output_basic() sets up an Authorization: header (or the proxy version) * for HTTP Basic authentication. @@ -379,7 +336,7 @@ static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) if(!out) return CURLE_OUT_OF_MEMORY; - result = Curl_base64_encode(out, strlen(out), &authorization, &size); + result = curlx_base64_encode(out, strlen(out), &authorization, &size); if(result) goto fail; @@ -403,6 +360,9 @@ static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) return result; } +#endif + +#ifndef CURL_DISABLE_BEARER_AUTH /* * http_output_bearer() sets up an Authorization: header * for HTTP Bearer authentication. @@ -430,6 +390,8 @@ static CURLcode http_output_bearer(struct Curl_easy *data) #endif +#endif + /* pickoneauth() selects the most favourable authentication method from the * ones available and the ones we want. * @@ -446,18 +408,24 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) of preference in case of the existence of multiple accepted types. */ if(avail & CURLAUTH_NEGOTIATE) pick->picked = CURLAUTH_NEGOTIATE; +#ifndef CURL_DISABLE_BEARER_AUTH else if(avail & CURLAUTH_BEARER) pick->picked = CURLAUTH_BEARER; +#endif +#ifndef CURL_DISABLE_DIGEST_AUTH else if(avail & CURLAUTH_DIGEST) pick->picked = CURLAUTH_DIGEST; +#endif else if(avail & CURLAUTH_NTLM) pick->picked = CURLAUTH_NTLM; - else if(avail & CURLAUTH_NTLM_WB) - pick->picked = CURLAUTH_NTLM_WB; +#ifndef CURL_DISABLE_BASIC_AUTH else if(avail & CURLAUTH_BASIC) pick->picked = CURLAUTH_BASIC; +#endif +#ifndef CURL_DISABLE_AWS else if(avail & CURLAUTH_AWS_SIGV4) pick->picked = CURLAUTH_AWS_SIGV4; +#endif else { pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ picked = FALSE; @@ -470,150 +438,84 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) /* * http_perhapsrewind() * - * If we are doing POST or PUT { - * If we have more data to send { - * If we are doing NTLM { - * Keep sending since we must not disconnect - * } - * else { - * If there is more than just a little data left to send, close - * the current connection by force. - * } - * } - * If we have sent any data { - * If we don't have track of all the data { - * call app to tell it to rewind - * } - * else { - * rewind internally so that the operation can restart fine - * } - * } - * } + * The current request needs to be done again - maybe due to a follow + * or authentication negotiation. Check if: + * 1) a rewind of the data sent to the server is necessary + * 2) the current transfer should continue or be stopped early */ static CURLcode http_perhapsrewind(struct Curl_easy *data, struct connectdata *conn) { - struct HTTP *http = data->req.p.http; - curl_off_t bytessent; - curl_off_t expectsend = -1; /* default is unknown */ - - if(!http) - /* If this is still NULL, we have not reach very far and we can safely - skip this rewinding stuff */ - return CURLE_OK; - - switch(data->state.httpreq) { - case HTTPREQ_GET: - case HTTPREQ_HEAD: + curl_off_t bytessent = data->req.writebytecount; + curl_off_t expectsend = Curl_creader_total_length(data); + curl_off_t upload_remain = (expectsend >= 0) ? (expectsend - bytessent) : -1; + bool little_upload_remains = (upload_remain >= 0 && upload_remain < 2000); + bool needs_rewind = Curl_creader_needs_rewind(data); + /* By default, we would like to abort the transfer when little or unknown + * amount remains. This may be overridden by authentications further + * below! */ + bool abort_upload = (!data->req.upload_done && !little_upload_remains); + const char *ongoing_auth = NULL; + + /* We need a rewind before uploading client read data again. The + * checks below just influence of the upload is to be continued + * or aborted early. + * This depends on how much remains to be sent and in what state + * the authentication is. Some auth schemes such as NTLM do not work + * for a new connection. */ + if(needs_rewind) { + infof(data, "Need to rewind upload for next request"); + Curl_creader_set_rewind(data, TRUE); + } + + if(conn->bits.close) + /* If we already decided to close this connection, we cannot veto. */ return CURLE_OK; - default: - break; - } - - bytessent = data->req.writebytecount; - - if(conn->bits.authneg) { - /* This is a state where we are known to be negotiating and we don't send - any data then. */ - expectsend = 0; - } - else if(!conn->bits.protoconnstart) { - /* HTTP CONNECT in progress: there is no body */ - expectsend = 0; - } - else { - /* figure out how much data we are expected to send */ - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - expectsend = data->state.infilesize; - break; - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - expectsend = http->postsize; - break; - default: - break; - } - } - data->state.rewindbeforesend = FALSE; /* default */ - - if((expectsend == -1) || (expectsend > bytessent)) { + if(abort_upload) { + /* We'd like to abort the upload - but should we? */ #if defined(USE_NTLM) - /* There is still data left to send */ if((data->state.authproxy.picked == CURLAUTH_NTLM) || - (data->state.authhost.picked == CURLAUTH_NTLM) || - (data->state.authproxy.picked == CURLAUTH_NTLM_WB) || - (data->state.authhost.picked == CURLAUTH_NTLM_WB)) { - if(((expectsend - bytessent) < 2000) || - (conn->http_ntlm_state != NTLMSTATE_NONE) || + (data->state.authhost.picked == CURLAUTH_NTLM)) { + ongoing_auth = "NTLM"; + if((conn->http_ntlm_state != NTLMSTATE_NONE) || (conn->proxy_ntlm_state != NTLMSTATE_NONE)) { - /* The NTLM-negotiation has started *OR* there is just a little (<2K) - data left to send, keep on sending. */ - - /* rewind data when completely done sending! */ - if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { - data->state.rewindbeforesend = TRUE; - infof(data, "Rewind stream before next send"); - } - - return CURLE_OK; + /* The NTLM-negotiation has started, keep on sending. + * Need to do further work on same connection */ + abort_upload = FALSE; } - - if(conn->bits.close) - /* this is already marked to get closed */ - return CURLE_OK; - - infof(data, "NTLM send, close instead of sending %" - CURL_FORMAT_CURL_OFF_T " bytes", - (curl_off_t)(expectsend - bytessent)); } #endif #if defined(USE_SPNEGO) /* There is still data left to send */ if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) || (data->state.authhost.picked == CURLAUTH_NEGOTIATE)) { - if(((expectsend - bytessent) < 2000) || - (conn->http_negotiate_state != GSS_AUTHNONE) || + ongoing_auth = "NEGOTIATE"; + if((conn->http_negotiate_state != GSS_AUTHNONE) || (conn->proxy_negotiate_state != GSS_AUTHNONE)) { - /* The NEGOTIATE-negotiation has started *OR* - there is just a little (<2K) data left to send, keep on sending. */ - - /* rewind data when completely done sending! */ - if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { - data->state.rewindbeforesend = TRUE; - infof(data, "Rewind stream before next send"); - } - - return CURLE_OK; + /* The NEGOTIATE-negotiation has started, keep on sending. + * Need to do further work on same connection */ + abort_upload = FALSE; } - - if(conn->bits.close) - /* this is already marked to get closed */ - return CURLE_OK; - - infof(data, "NEGOTIATE send, close instead of sending %" - CURL_FORMAT_CURL_OFF_T " bytes", - (curl_off_t)(expectsend - bytessent)); } #endif - - /* This is not NEGOTIATE/NTLM or many bytes left to send: close */ - streamclose(conn, "Mid-auth HTTP and much data left to send"); - data->req.size = 0; /* don't download any more than 0 bytes */ - - /* There still is data left to send, but this connection is marked for - closure so we can safely do the rewind right now */ } - if(bytessent) { - /* mark for rewind since if we already sent something */ - data->state.rewindbeforesend = TRUE; - infof(data, "Please rewind output before next send"); + if(abort_upload) { + if(upload_remain >= 0) + infof(data, "%s%sclose instead of sending %" FMT_OFF_T " more bytes", + ongoing_auth ? ongoing_auth : "", + ongoing_auth ? " send, " : "", + upload_remain); + else + infof(data, "%s%sclose instead of sending unknown amount " + "of more bytes", + ongoing_auth ? ongoing_auth : "", + ongoing_auth ? " send, " : ""); + /* We decided to abort the ongoing transfer */ + streamclose(conn, "Mid-auth HTTP and much data left to send"); + data->req.size = 0; /* do not download any more than 0 bytes */ } - return CURLE_OK; } @@ -640,55 +542,58 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) return CURLE_OK; if(data->state.authproblem) - return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; + return data->set.http_fail_on_error ? CURLE_HTTP_RETURNED_ERROR : CURLE_OK; if((data->state.aptr.user || data->set.str[STRING_BEARER]) && ((data->req.httpcode == 401) || - (conn->bits.authneg && data->req.httpcode < 300))) { + (data->req.authneg && data->req.httpcode < 300))) { pickhost = pickoneauth(&data->state.authhost, authmask); if(!pickhost) data->state.authproblem = TRUE; + else + data->info.httpauthpicked = data->state.authhost.picked; if(data->state.authhost.picked == CURLAUTH_NTLM && - conn->httpversion > 11) { + (data->req.httpversion_sent > 11)) { infof(data, "Forcing HTTP/1.1 for NTLM"); connclose(conn, "Force HTTP/1.1 connection"); - data->state.httpwant = CURL_HTTP_VERSION_1_1; + data->state.http_neg.wanted = CURL_HTTP_V1x; + data->state.http_neg.allowed = CURL_HTTP_V1x; } } #ifndef CURL_DISABLE_PROXY if(conn->bits.proxy_user_passwd && ((data->req.httpcode == 407) || - (conn->bits.authneg && data->req.httpcode < 300))) { + (data->req.authneg && data->req.httpcode < 300))) { pickproxy = pickoneauth(&data->state.authproxy, authmask & ~CURLAUTH_BEARER); if(!pickproxy) data->state.authproblem = TRUE; + else + data->info.proxyauthpicked = data->state.authproxy.picked; + } #endif if(pickhost || pickproxy) { - if((data->state.httpreq != HTTPREQ_GET) && - (data->state.httpreq != HTTPREQ_HEAD) && - !data->state.rewindbeforesend) { - result = http_perhapsrewind(data, conn); - if(result) - return result; - } + result = http_perhapsrewind(data, conn); + if(result) + return result; + /* In case this is GSS auth, the newurl field is already allocated so we must make sure to free it before allocating a new one. As figured out in bug #2284386 */ - Curl_safefree(data->req.newurl); + free(data->req.newurl); data->req.newurl = strdup(data->state.url); /* clone URL */ if(!data->req.newurl) return CURLE_OUT_OF_MEMORY; } else if((data->req.httpcode < 300) && (!data->state.authhost.done) && - conn->bits.authneg) { + data->req.authneg) { /* no (known) authentication available, authentication is not "done" yet and no authentication seems to be required and - we didn't try HEAD or GET */ + we did not try HEAD or GET */ if((data->state.httpreq != HTTPREQ_GET) && (data->state.httpreq != HTTPREQ_HEAD)) { data->req.newurl = strdup(data->state.url); /* clone URL */ @@ -697,7 +602,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) data->state.authhost.done = TRUE; } } - if(http_should_fail(data)) { + if(http_should_fail(data, data->req.httpcode)) { failf(data, "The requested URL returned error: %d", data->req.httpcode); result = CURLE_HTTP_RETURNED_ERROR; @@ -723,14 +628,15 @@ output_auth_headers(struct Curl_easy *data, CURLcode result = CURLE_OK; (void)conn; -#ifdef CURL_DISABLE_CRYPTO_AUTH +#ifdef CURL_DISABLE_DIGEST_AUTH (void)request; (void)path; #endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(authstatus->picked == CURLAUTH_AWS_SIGV4) { +#ifndef CURL_DISABLE_AWS + if((authstatus->picked == CURLAUTH_AWS_SIGV4) && !proxy) { + /* this method is never for proxy */ auth = "AWS_SIGV4"; - result = Curl_output_aws_sigv4(data, proxy); + result = Curl_output_aws_sigv4(data); if(result) return result; } @@ -754,16 +660,7 @@ output_auth_headers(struct Curl_easy *data, } else #endif -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - if(authstatus->picked == CURLAUTH_NTLM_WB) { - auth = "NTLM_WB"; - result = Curl_output_ntlm_wb(data, conn, proxy); - if(result) - return result; - } - else -#endif -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH if(authstatus->picked == CURLAUTH_DIGEST) { auth = "Digest"; result = Curl_output_digest(data, @@ -775,6 +672,7 @@ output_auth_headers(struct Curl_easy *data, } else #endif +#ifndef CURL_DISABLE_BASIC_AUTH if(authstatus->picked == CURLAUTH_BASIC) { /* Basic */ if( @@ -794,6 +692,8 @@ output_auth_headers(struct Curl_easy *data, functions work that way */ authstatus->done = TRUE; } +#endif +#ifndef CURL_DISABLE_BEARER_AUTH if(authstatus->picked == CURLAUTH_BEARER) { /* Bearer */ if((!proxy && data->set.str[STRING_BEARER] && @@ -808,6 +708,7 @@ output_auth_headers(struct Curl_easy *data, functions work that way */ authstatus->done = TRUE; } +#endif if(auth) { #ifndef CURL_DISABLE_PROXY @@ -818,16 +719,17 @@ output_auth_headers(struct Curl_easy *data, (data->state.aptr.user ? data->state.aptr.user : "")); #else + (void)proxy; infof(data, "Server auth using %s with user '%s'", auth, data->state.aptr.user ? data->state.aptr.user : ""); #endif - authstatus->multipass = (!authstatus->done) ? TRUE : FALSE; + authstatus->multipass = !authstatus->done; } else authstatus->multipass = FALSE; - return CURLE_OK; + return result; } /** @@ -866,7 +768,12 @@ Curl_http_output_auth(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY (conn->bits.httpproxy && conn->bits.proxy_user_passwd) || #endif - data->state.aptr.user || data->set.str[STRING_BEARER]) + data->state.aptr.user || +#ifdef USE_SPNEGO + authhost->want & CURLAUTH_NEGOTIATE || + authproxy->want & CURLAUTH_NEGOTIATE || +#endif + data->set.str[STRING_BEARER]) /* continue please */; else { authhost->done = TRUE; @@ -877,13 +784,13 @@ Curl_http_output_auth(struct Curl_easy *data, if(authhost->want && !authhost->picked) /* The app has selected one or more methods, but none has been picked so far by a server round-trip. Then we set the picked one to the - want one, and if this is one single bit it'll be used instantly. */ + want one, and if this is one single bit it will be used instantly. */ authhost->picked = authhost->want; if(authproxy->want && !authproxy->picked) /* The app has selected one or more methods, but none has been picked so far by a proxy round-trip. Then we set the picked one to the want one, - and if this is one single bit it'll be used instantly. */ + and if this is one single bit it will be used instantly. */ authproxy->picked = authproxy->want; #ifndef CURL_DISABLE_PROXY @@ -898,7 +805,7 @@ Curl_http_output_auth(struct Curl_easy *data, #else (void)proxytunnel; #endif /* CURL_DISABLE_PROXY */ - /* we have no proxy so let's pretend we're done authenticating + /* we have no proxy so let's pretend we are done authenticating with it */ authproxy->done = TRUE; @@ -919,10 +826,10 @@ Curl_http_output_auth(struct Curl_easy *data, (httpreq != HTTPREQ_HEAD)) { /* Auth is required and we are not authenticated yet. Make a PUT or POST with content-length zero as a "probe". */ - conn->bits.authneg = TRUE; + data->req.authneg = TRUE; } else - conn->bits.authneg = FALSE; + data->req.authneg = FALSE; return result; } @@ -947,32 +854,168 @@ Curl_http_output_auth(struct Curl_easy *data, } #endif +#if defined(USE_SPNEGO) || defined(USE_NTLM) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_BASIC_AUTH) || \ + !defined(CURL_DISABLE_BEARER_AUTH) +static bool authcmp(const char *auth, const char *line) +{ + /* the auth string must not have an alnum following */ + size_t n = strlen(auth); + return strncasecompare(auth, line, n) && !ISALNUM(line[n]); +} +#endif + +#ifdef USE_SPNEGO +static CURLcode auth_spnego(struct Curl_easy *data, + bool proxy, + const char *auth, + struct auth *authp, + unsigned long *availp) +{ + if((authp->avail & CURLAUTH_NEGOTIATE) || Curl_auth_is_spnego_supported()) { + *availp |= CURLAUTH_NEGOTIATE; + authp->avail |= CURLAUTH_NEGOTIATE; + + 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 : + &conn->http_negotiate_state; + if(!result) { + free(data->req.newurl); + data->req.newurl = strdup(data->state.url); + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authproblem = FALSE; + /* we received a GSS auth token and we dealt with it fine */ + *negstate = GSS_AUTHRECV; + } + else + data->state.authproblem = TRUE; + } + } + return CURLE_OK; +} +#endif + +#ifdef USE_NTLM +static CURLcode auth_ntlm(struct Curl_easy *data, + bool proxy, + const char *auth, + struct auth *authp, + unsigned long *availp) +{ + /* NTLM support requires the SSL crypto libs */ + if((authp->avail & CURLAUTH_NTLM) || Curl_auth_is_ntlm_supported()) { + *availp |= CURLAUTH_NTLM; + authp->avail |= CURLAUTH_NTLM; + + if(authp->picked == CURLAUTH_NTLM) { + /* NTLM authentication is picked and activated */ + CURLcode result = Curl_input_ntlm(data, proxy, auth); + if(!result) + data->state.authproblem = FALSE; + else { + infof(data, "NTLM authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + } + } + return CURLE_OK; +} +#endif + +#ifndef CURL_DISABLE_DIGEST_AUTH +static CURLcode auth_digest(struct Curl_easy *data, + bool proxy, + const char *auth, + struct auth *authp, + unsigned long *availp) +{ + if(authp->avail & CURLAUTH_DIGEST) + infof(data, "Ignoring duplicate digest auth header."); + else if(Curl_auth_is_digest_supported()) { + CURLcode result; + + *availp |= CURLAUTH_DIGEST; + authp->avail |= CURLAUTH_DIGEST; + + /* We call this function on input Digest headers even if Digest + * authentication is not activated yet, as we need to store the + * incoming data from this header in case we are going to use + * Digest */ + result = Curl_input_digest(data, proxy, auth); + if(result) { + infof(data, "Digest authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + } + return CURLE_OK; +} +#endif + +#ifndef CURL_DISABLE_BASIC_AUTH +static CURLcode auth_basic(struct Curl_easy *data, + struct auth *authp, + unsigned long *availp) +{ + *availp |= CURLAUTH_BASIC; + authp->avail |= CURLAUTH_BASIC; + if(authp->picked == CURLAUTH_BASIC) { + /* We asked for Basic authentication but got a 40X back + anyway, which basically means our name+password is not + valid. */ + authp->avail = CURLAUTH_NONE; + infof(data, "Basic authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + return CURLE_OK; +} +#endif + +#ifndef CURL_DISABLE_BEARER_AUTH +static CURLcode auth_bearer(struct Curl_easy *data, + struct auth *authp, + unsigned long *availp) +{ + *availp |= CURLAUTH_BEARER; + authp->avail |= CURLAUTH_BEARER; + if(authp->picked == CURLAUTH_BEARER) { + /* We asked for Bearer authentication but got a 40X back + anyway, which basically means our token is not valid. */ + authp->avail = CURLAUTH_NONE; + infof(data, "Bearer authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + return CURLE_OK; +} +#endif + /* * Curl_http_input_auth() deals with Proxy-Authenticate: and WWW-Authenticate: * 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. */ - -static int is_valid_auth_separator(char ch) -{ - return ch == '\0' || ch == ',' || ISSPACE(ch); -} - CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth) /* the first non-space */ { /* * This resource requires authentication */ - struct connectdata *conn = data->conn; -#ifdef USE_SPNEGO - curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : - &conn->http_negotiate_state; -#endif +#if defined(USE_SPNEGO) || \ + defined(USE_NTLM) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_BASIC_AUTH) || \ + !defined(CURL_DISABLE_BEARER_AUTH) + unsigned long *availp; struct auth *authp; - - (void) conn; /* In case conditionals make it unused. */ + CURLcode result = CURLE_OK; + DEBUGASSERT(auth); + DEBUGASSERT(data); if(proxy) { availp = &data->info.proxyauthavail; @@ -1001,152 +1044,61 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, while(*auth) { #ifdef USE_SPNEGO - if(checkprefix("Negotiate", auth) && is_valid_auth_separator(auth[9])) { - if((authp->avail & CURLAUTH_NEGOTIATE) || - Curl_auth_is_spnego_supported()) { - *availp |= CURLAUTH_NEGOTIATE; - authp->avail |= CURLAUTH_NEGOTIATE; - - if(authp->picked == CURLAUTH_NEGOTIATE) { - CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); - if(!result) { - free(data->req.newurl); - data->req.newurl = strdup(data->state.url); - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authproblem = FALSE; - /* we received a GSS auth token and we dealt with it fine */ - *negstate = GSS_AUTHRECV; - } - else - data->state.authproblem = TRUE; - } - } - } - else + if(authcmp("Negotiate", auth)) + result = auth_spnego(data, proxy, auth, authp, availp); #endif #ifdef USE_NTLM - /* NTLM support requires the SSL crypto libs */ - if(checkprefix("NTLM", auth) && is_valid_auth_separator(auth[4])) { - if((authp->avail & CURLAUTH_NTLM) || - (authp->avail & CURLAUTH_NTLM_WB) || - Curl_auth_is_ntlm_supported()) { - *availp |= CURLAUTH_NTLM; - authp->avail |= CURLAUTH_NTLM; - - if(authp->picked == CURLAUTH_NTLM || - authp->picked == CURLAUTH_NTLM_WB) { - /* NTLM authentication is picked and activated */ - CURLcode result = Curl_input_ntlm(data, proxy, auth); - if(!result) { - data->state.authproblem = FALSE; -#ifdef NTLM_WB_ENABLED - if(authp->picked == CURLAUTH_NTLM_WB) { - *availp &= ~CURLAUTH_NTLM; - authp->avail &= ~CURLAUTH_NTLM; - *availp |= CURLAUTH_NTLM_WB; - authp->avail |= CURLAUTH_NTLM_WB; - - result = Curl_input_ntlm_wb(data, conn, proxy, auth); - if(result) { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } + if(!result && authcmp("NTLM", auth)) + result = auth_ntlm(data, proxy, auth, authp, availp); #endif - } - else { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - } - } - else +#ifndef CURL_DISABLE_DIGEST_AUTH + if(!result && authcmp("Digest", auth)) + result = auth_digest(data, proxy, auth, authp, availp); #endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(checkprefix("Digest", auth) && is_valid_auth_separator(auth[6])) { - if((authp->avail & CURLAUTH_DIGEST) != 0) - infof(data, "Ignoring duplicate digest auth header."); - else if(Curl_auth_is_digest_supported()) { - CURLcode result; - - *availp |= CURLAUTH_DIGEST; - authp->avail |= CURLAUTH_DIGEST; - - /* We call this function on input Digest headers even if Digest - * authentication isn't activated yet, as we need to store the - * incoming data from this header in case we are going to use - * Digest */ - result = Curl_input_digest(data, proxy, auth); - if(result) { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - } - else +#ifndef CURL_DISABLE_BASIC_AUTH + if(!result && authcmp("Basic", auth)) + result = auth_basic(data, authp, availp); #endif - if(checkprefix("Basic", auth) && - is_valid_auth_separator(auth[5])) { - *availp |= CURLAUTH_BASIC; - authp->avail |= CURLAUTH_BASIC; - if(authp->picked == CURLAUTH_BASIC) { - /* We asked for Basic authentication but got a 40X back - anyway, which basically means our name+password isn't - valid. */ - authp->avail = CURLAUTH_NONE; - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - else - if(checkprefix("Bearer", auth) && - is_valid_auth_separator(auth[6])) { - *availp |= CURLAUTH_BEARER; - authp->avail |= CURLAUTH_BEARER; - if(authp->picked == CURLAUTH_BEARER) { - /* We asked for Bearer authentication but got a 40X back - anyway, which basically means our token isn't valid. */ - authp->avail = CURLAUTH_NONE; - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } +#ifndef CURL_DISABLE_BEARER_AUTH + if(authcmp("Bearer", auth)) + result = auth_bearer(data, authp, availp); +#endif + + if(result) + break; /* there may be multiple methods on one line, so keep reading */ - while(*auth && *auth != ',') /* read up to the next comma */ - auth++; - if(*auth == ',') /* if we're on a comma, skip it */ - auth++; - while(*auth && ISSPACE(*auth)) + auth = strchr(auth, ','); + if(auth) /* if we are on a comma, skip it */ auth++; + else + break; + curlx_str_passblanks(&auth); } - + return result; +#else + (void) proxy; + /* nothing to do when disabled */ return CURLE_OK; +#endif } /** - * http_should_fail() determines whether an HTTP response has gotten us + * http_should_fail() determines whether an HTTP response code has gotten us * into an error state or not. * - * @param conn all information about the current connection - * * @retval FALSE communications should continue * * @retval TRUE communications should not continue */ -static bool http_should_fail(struct Curl_easy *data) +static bool http_should_fail(struct Curl_easy *data, int httpcode) { - int httpcode; DEBUGASSERT(data); DEBUGASSERT(data->conn); - httpcode = data->req.httpcode; - /* - ** If we haven't been asked to fail on error, - ** don't fail. + ** If we have not been asked to fail on error, + ** do not fail. */ if(!data->set.http_fail_on_error) return FALSE; @@ -1166,7 +1118,7 @@ static bool http_should_fail(struct Curl_easy *data) return FALSE; /* - ** Any code >= 400 that's not 401 or 407 is always + ** Any code >= 400 that is not 401 or 407 is always ** a terminal error */ if((httpcode != 401) && (httpcode != 407)) @@ -1178,22 +1130,19 @@ static bool http_should_fail(struct Curl_easy *data) DEBUGASSERT((httpcode == 401) || (httpcode == 407)); /* - ** Examine the current authentication state to see if this - ** is an error. The idea is for this function to get - ** called after processing all the headers in a response - ** message. So, if we've been to asked to authenticate a - ** particular stage, and we've done it, we're OK. But, if - ** we're already completely authenticated, it's not OK to - ** get another 401 or 407. + ** Examine the current authentication state to see if this is an error. The + ** idea is for this function to get called after processing all the headers + ** in a response message. So, if we have been to asked to authenticate a + ** particular stage, and we have done it, we are OK. If we are already + ** completely authenticated, it is not OK to get another 401 or 407. ** - ** It is possible for authentication to go stale such that - ** the client needs to reauthenticate. Once that info is - ** available, use it here. + ** It is possible for authentication to go stale such that the client needs + ** to reauthenticate. Once that info is available, use it here. */ /* - ** Either we're not authenticating, or we're supposed to - ** be authenticating something else. This is an error. + ** Either we are not authenticating, or we are supposed to be authenticating + ** something else. This is an error. */ if((httpcode == 401) && !data->state.aptr.user) return TRUE; @@ -1205,260 +1154,308 @@ static bool http_should_fail(struct Curl_easy *data) return data->state.authproblem; } -/* - * readmoredata() is a "fread() emulation" to provide POST and/or request - * data. It is used when a huge POST is to be made and the entire chunk wasn't - * sent in the first send(). This function will then be called from the - * transfer.c loop when more data is to be sent to the peer. - * - * Returns the amount of bytes it filled the buffer with. - */ -static size_t readmoredata(char *buffer, - size_t size, - size_t nitems, - void *userp) +static void http_switch_to_get(struct Curl_easy *data, int code) { - struct HTTP *http = (struct HTTP *)userp; - struct Curl_easy *data = http->backup.data; - size_t fullsize = size * nitems; + const char *req = data->set.str[STRING_CUSTOMREQUEST]; + if((req || data->state.httpreq != HTTPREQ_GET) && + (data->set.http_follow_mode == CURLFOLLOW_OBEYCODE)) { + infof(data, "Switch to GET because of %d response", code); + data->state.http_ignorecustom = TRUE; + } + else if(req && (data->set.http_follow_mode != CURLFOLLOW_FIRSTONLY)) + infof(data, "Stick to %s instead of GET", req); - if(!http->postsize) - /* nothing to return */ - return 0; + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); +} - /* make sure that an HTTP request is never sent away chunked! */ - data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE: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 rewind_result; + bool switch_to_get = FALSE; + + 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.max_send_speed && - (data->set.max_send_speed < (curl_off_t)fullsize) && - (data->set.max_send_speed < http->postsize)) - /* speed limit */ - fullsize = (size_t)data->set.max_send_speed; + if(data->set.http_auto_referer) { + CURLU *u; + char *referer = NULL; - else if(http->postsize <= (curl_off_t)fullsize) { - memcpy(buffer, http->postdata, (size_t)http->postsize); - fullsize = (size_t)http->postsize; + /* 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 */ - if(http->backup.postsize) { - /* move backup data into focus and continue on that */ - http->postdata = http->backup.postdata; - http->postsize = http->backup.postsize; - data->state.fread_func = http->backup.fread_func; - data->state.in = http->backup.fread_in; + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; + } - http->sending++; /* move one step up */ + /* Make a copy of the URL without credentials and fragment */ + u = curl_url(); + if(!u) + return CURLE_OUT_OF_MEMORY; - http->backup.postsize = 0; - } - else - http->postsize = 0; + uc = curl_url_set(u, CURLUPART_URL, 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); - return fullsize; - } - - memcpy(buffer, http->postdata, fullsize); - http->postdata += fullsize; - http->postsize -= fullsize; + curl_url_cleanup(u); - return fullsize; -} + if(uc || !referer) + return CURLE_OUT_OF_MEMORY; -/* - * Curl_buffer_send() sends a header buffer and frees all associated - * memory. Body data may be appended to the header data if desired. - * - * Returns CURLcode - */ -CURLcode Curl_buffer_send(struct dynbuf *in, - struct Curl_easy *data, - struct HTTP *http, - /* add the number of sent bytes to this - counter */ - curl_off_t *bytes_written, - /* how much of the buffer contains body data */ - curl_off_t included_body_bytes, - int socketindex) -{ - ssize_t amount; - CURLcode result; - char *ptr; - size_t size; - struct connectdata *conn = data->conn; - size_t sendsize; - curl_socket_t sockfd; - size_t headersize; + data->state.referer = referer; + data->state.referer_alloc = TRUE; /* yes, free this later */ + } + } + } - DEBUGASSERT(socketindex <= SECONDARYSOCKET); + 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; + } + + DEBUGASSERT(data->state.uh); + uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int) + ((type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME : + ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | + CURLU_ALLOW_SPACE | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); + if(uc) { + if(type != FOLLOW_FAKE) { + failf(data, "The redirect target URL could not be parsed: %s", + curl_url_strerror(uc)); + return Curl_uc_to_curlcode(uc); + } - sockfd = Curl_conn_get_socket(data, socketindex); + /* the URL could not be parsed for some reason, but since this is FAKE + mode, just duplicate the field as-is */ + follow_url = strdup(newurl); + if(!follow_url) + return CURLE_OUT_OF_MEMORY; + } + else { + uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + + /* Clear auth if this redirects to a different port number or protocol, + unless permitted */ + if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { + char *portnum; + int port; + bool clear = FALSE; + + if(data->set.use_port && data->state.allow_port) + /* a custom port is used */ + port = (int)data->set.use_port; + else { + uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, + CURLU_DEFAULT_PORT); + if(uc) { + free(follow_url); + return Curl_uc_to_curlcode(uc); + } + port = atoi(portnum); + free(portnum); + } + if(port != data->info.conn_remote_port) { + infof(data, "Clear auth, redirects to port from %u to %u", + data->info.conn_remote_port, port); + clear = TRUE; + } + else { + char *scheme; + const struct Curl_handler *p; + uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); + if(uc) { + free(follow_url); + return Curl_uc_to_curlcode(uc); + } - /* The looping below is required since we use non-blocking sockets, but due - to the circumstances we will just loop and try again and again etc */ + p = Curl_get_scheme_handler(scheme); + if(p && (p->protocol != data->info.conn_protocol)) { + infof(data, "Clear auth, redirects scheme from %s to %s", + data->info.conn_scheme, scheme); + clear = TRUE; + } + free(scheme); + } + if(clear) { + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); + } + } + } + DEBUGASSERT(follow_url); - ptr = Curl_dyn_ptr(in); - size = Curl_dyn_len(in); + 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; - headersize = size - (size_t)included_body_bytes; /* the initial part that - isn't body is header */ + if(reachedmax) { + failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); + return CURLE_TOO_MANY_REDIRECTS; + } + return CURLE_OK; + } - DEBUGASSERT(size > (size_t)included_body_bytes); + if(disallowport) + data->state.allow_port = FALSE; - if((conn->handler->flags & PROTOPT_SSL -#ifndef CURL_DISABLE_PROXY - || IS_HTTPS_PROXY(conn->http_proxy.proxytype) -#endif - ) - && conn->httpversion != 20) { - /* Make sure this doesn't send more body bytes than what the max send - speed says. The request bytes do not count to the max speed. - */ - if(data->set.max_send_speed && - (included_body_bytes > data->set.max_send_speed)) { - curl_off_t overflow = included_body_bytes - data->set.max_send_speed; - DEBUGASSERT((size_t)overflow < size); - sendsize = size - (size_t)overflow; - } - else - sendsize = size; - - /* OpenSSL is very picky and we must send the SAME buffer pointer to the - library when we attempt to re-send this buffer. Sending the same data - is not enough, we must use the exact same address. For this reason, we - must copy the data to the uploadbuffer first, since that is the buffer - we will be using if this send is retried later. - */ - result = Curl_get_upload_buffer(data); - if(result) { - /* malloc failed, free memory and return to the caller */ - Curl_dyn_free(in); - return result; - } - /* We never send more than upload_buffer_size bytes in one single chunk - when we speak HTTPS, as if only a fraction of it is sent now, this data - needs to fit into the normal read-callback buffer later on and that - buffer is using this size. - */ - if(sendsize > (size_t)data->set.upload_buffer_size) - sendsize = (size_t)data->set.upload_buffer_size; + if(data->state.url_alloc) + Curl_safefree(data->state.url); - memcpy(data->state.ulbuf, ptr, sendsize); - ptr = data->state.ulbuf; + data->state.url = follow_url; + data->state.url_alloc = TRUE; + rewind_result = Curl_req_soft_reset(&data->req, data); + infof(data, "Issue another request to this URL: '%s'", data->state.url); + if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && + data->set.str[STRING_CUSTOMREQUEST] && + !data->state.http_ignorecustom) { + data->state.http_ignorecustom = TRUE; + infof(data, "Drop custom request method for next request"); } - else { -#ifdef CURLDEBUG - /* Allow debug builds to override this logic to force short initial - sends + + /* + * 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. + */ + 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. */ - char *p = getenv("CURL_SMALLREQSEND"); - if(p) { - size_t altsize = (size_t)strtoul(p, NULL, 10); - if(altsize) - sendsize = CURLMIN(size, altsize); - else - sendsize = size; - } - else -#endif - { - /* Make sure this doesn't send more body bytes than what the max send - speed says. The request bytes do not count to the max speed. - */ - if(data->set.max_send_speed && - (included_body_bytes > data->set.max_send_speed)) { - curl_off_t overflow = included_body_bytes - data->set.max_send_speed; - DEBUGASSERT((size_t)overflow < size); - sendsize = size - (size_t)overflow; - } - else - sendsize = size; + break; + case 301: /* Moved Permanently */ + /* (quote from RFC7231, section 6.4.2) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. + */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_301)) { + http_switch_to_get(data, 301); + switch_to_get = TRUE; } - } - - result = Curl_write(data, sockfd, ptr, sendsize, &amount); - - if(!result) { - /* - * Note that we may not send the entire chunk at once, and we have a set - * number of data bytes at the end of the big buffer (out of which we may - * only send away a part). + break; + case 302: /* Found */ + /* (quote from RFC7231, section 6.4.3) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. */ - /* how much of the header that was sent */ - size_t headlen = (size_t)amount>headersize ? headersize : (size_t)amount; - size_t bodylen = amount - headlen; - - /* this data _may_ contain binary stuff */ - Curl_debug(data, CURLINFO_HEADER_OUT, ptr, headlen); - if(bodylen) - /* there was body data sent beyond the initial header part, pass that on - to the debug callback too */ - Curl_debug(data, CURLINFO_DATA_OUT, ptr + headlen, bodylen); - - /* 'amount' can never be a very large value here so typecasting it so a - signed 31 bit value should not cause problems even if ssize_t is - 64bit */ - *bytes_written += (long)amount; - - if(http) { - /* if we sent a piece of the body here, up the byte counter for it - accordingly */ - data->req.writebytecount += bodylen; - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - - if((size_t)amount != size) { - /* The whole request could not be sent in one system call. We must - queue it up and send it later when we get the chance. We must not - loop here and wait until it might work again. */ - - size -= amount; - - ptr = Curl_dyn_ptr(in) + amount; - - /* backup the currently set pointers */ - http->backup.fread_func = data->state.fread_func; - http->backup.fread_in = data->state.in; - http->backup.postdata = http->postdata; - http->backup.postsize = http->postsize; - http->backup.data = data; - - /* set the new pointers for the request-sending */ - data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)http; - http->postdata = ptr; - http->postsize = (curl_off_t)size; - - /* this much data is remaining header: */ - data->req.pendingheader = headersize - headlen; - - http->send_buffer = *in; /* copy the whole struct */ - http->sending = HTTPSEND_REQUEST; - return CURLE_OK; - } - http->sending = HTTPSEND_BODY; - /* the full buffer was sent, clean up and return */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_302)) { + http_switch_to_get(data, 302); + switch_to_get = TRUE; } - else { - if((size_t)amount != size) - /* We have no continue-send mechanism now, fail. This can only happen - when this function is used from the CONNECT sending function. We - currently (stupidly) assume that the whole request is always sent - away in the first single chunk. + break; - This needs FIXing. - */ - return CURLE_SEND_ERROR; + 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 + * method is POST and the user specified to keep it as POST. + * https://github.com/curl/curl/issues/5237#issuecomment-614641049 + */ + if(data->state.httpreq != HTTPREQ_GET && + ((data->state.httpreq != HTTPREQ_POST && + data->state.httpreq != HTTPREQ_POST_FORM && + data->state.httpreq != HTTPREQ_POST_MIME) || + !(data->set.keep_post & CURL_REDIR_POST_303))) { + http_switch_to_get(data, 303); + switch_to_get = 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): + * "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." + */ + break; } - Curl_dyn_free(in); - - /* no remaining header data */ - data->req.pendingheader = 0; - return result; -} -/* end of the add_buffer functions */ -/* ------------------------------------------------------------------------- */ + /* 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. */ + if(rewind_result && !switch_to_get) + return rewind_result; + Curl_pgrsTime(data, TIMER_REDIRECT); + Curl_pgrsResetTransferSizes(data); + return CURLE_OK; +} /* * Curl_compareheader() @@ -1478,43 +1475,32 @@ Curl_compareheader(const char *headerline, /* line to check */ * The field value MAY be preceded by any amount of LWS, though a single SP * is preferred." */ - size_t len; - const char *start; - const char *end; + const char *p; + struct Curl_str val; DEBUGASSERT(hlen); DEBUGASSERT(clen); DEBUGASSERT(header); DEBUGASSERT(content); if(!strncasecompare(headerline, header, hlen)) - return FALSE; /* doesn't start with header */ + return FALSE; /* does not start with header */ /* pass the header */ - start = &headerline[hlen]; - - /* pass all whitespace */ - while(*start && ISSPACE(*start)) - start++; - - /* find the end of the header line */ - end = strchr(start, '\r'); /* lines end with CRLF */ - if(!end) { - /* in case there's a non-standard compliant line here */ - end = strchr(start, '\n'); - - if(!end) - /* hm, there's no line ending here, use the zero byte! */ - end = strchr(start, '\0'); - } + p = &headerline[hlen]; - len = end-start; /* length of the content part of the input line */ + if(curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE)) + return FALSE; + curlx_str_trimblanks(&val); /* find the content string in the rest of the line */ - for(; len >= clen; len--, start++) { - if(strncasecompare(start, content, clen)) - return TRUE; /* match! */ + if(curlx_strlen(&val) >= clen) { + size_t len; + p = curlx_str(&val); + for(len = curlx_strlen(&val); len >= curlx_strlen(&val); len--, p++) { + if(strncasecompare(p, content, clen)) + return TRUE; /* match! */ + } } - return FALSE; /* no match */ } @@ -1527,18 +1513,18 @@ CURLcode Curl_http_connect(struct Curl_easy *data, bool *done) struct connectdata *conn = data->conn; /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ + function to make the reuse checks properly be able to check this bit. */ connkeep(conn, "HTTP default"); return Curl_conn_connect(data, FIRSTSOCKET, FALSE, done); } /* this returns the socket to wait for in the DO and DOING state for the multi - interface and then we're always _sending_ a request and thus we wait for + interface and then we are always _sending_ a request and thus we wait for the single socket to become writable only */ -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +int Curl_http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) { /* write mode */ (void)conn; @@ -1555,27 +1541,13 @@ CURLcode Curl_http_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct HTTP *http = data->req.p.http; - /* Clear multipass flag. If authentication isn't done yet, then it will get + /* Clear multipass flag. If authentication is not done yet, then it will get * a chance to be set back to true when we output the next auth header */ data->state.authhost.multipass = FALSE; data->state.authproxy.multipass = FALSE; - Curl_unencode_cleanup(data); - - /* set the proper values (possibly modified on POST) */ - conn->seek_func = data->set.seek_func; /* restore */ - conn->seek_client = data->set.seek_client; /* restore */ - - if(!http) - return CURLE_OK; - - Curl_dyn_free(&http->send_buffer); - Curl_mime_cleanpart(&http->form); - Curl_dyn_reset(&data->state.headerb); - Curl_hyper_done(data); - Curl_ws_done(data); + curlx_dyn_reset(&data->state.headerb); if(status) return status; @@ -1587,8 +1559,8 @@ CURLcode Curl_http_done(struct Curl_easy *data, (data->req.bytecount + data->req.headerbytecount - data->req.deductheadercount) <= 0) { - /* If this connection isn't simply closed to be retried, AND nothing was - read from the HTTP server (that counts), this can't be right so we + /* If this connection is not simply closed to be retried, AND nothing was + read from the HTTP server (that counts), this cannot be right so we return an error here */ failf(data, "Empty reply from server"); /* Mark it as closed to avoid the "left intact" message */ @@ -1599,149 +1571,67 @@ CURLcode Curl_http_done(struct Curl_easy *data, return CURLE_OK; } -/* - * Determine if we should use HTTP 1.1 (OR BETTER) for this request. Reasons - * to avoid it include: - * - * - if the user specifically requested HTTP 1.0 - * - if the server we are connected to only supports 1.0 - * - if any server previously contacted to handle this request only supports - * 1.0. - */ -bool Curl_use_http_1_1plus(const struct Curl_easy *data, - const struct connectdata *conn) +/* Determine if we may use HTTP 1.1 for this request. */ +static bool http_may_use_1_1(const struct Curl_easy *data) { - if((data->state.httpversion == 10) || (conn->httpversion == 10)) + const struct connectdata *conn = data->conn; + /* We have seen a previous response for *this* transfer with 1.0, + * on another connection or the same one. */ + if(data->state.http_neg.rcvd_min == 10) return FALSE; - if((data->state.httpwant == CURL_HTTP_VERSION_1_0) && - (conn->httpversion <= 10)) + /* We have seen a previous response on *this* connection with 1.0. */ + if(conn && conn->httpversion_seen == 10) return FALSE; - return ((data->state.httpwant == CURL_HTTP_VERSION_NONE) || - (data->state.httpwant >= CURL_HTTP_VERSION_1_1)); -} - -#ifndef USE_HYPER -static const char *get_http_string(const struct Curl_easy *data, - const struct connectdata *conn) -{ - if(Curl_conn_is_http3(data, conn, FIRSTSOCKET)) - return "3"; - if(Curl_conn_is_http2(data, conn, FIRSTSOCKET)) - return "2"; - if(Curl_use_http_1_1plus(data, conn)) - return "1.1"; - - return "1.0"; -} -#endif - -/* check and possibly add an Expect: header */ -static CURLcode expect100(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *req) -{ - CURLcode result = CURLE_OK; - data->state.expect100header = FALSE; /* default to false unless it is set - to TRUE below */ - if(!data->state.disableexpect && Curl_use_http_1_1plus(data, conn) && - (conn->httpversion < 20)) { - /* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an - Expect: 100-continue to the headers which actually speeds up post - operations (as there is one packet coming back from the web server) */ - const char *ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); - } - else { - result = Curl_dyn_addn(req, STRCONST("Expect: 100-continue\r\n")); - if(!result) - data->state.expect100header = TRUE; - } - } - - return result; + /* We want 1.0 and have seen no previous response on *this* connection + with a higher version (maybe no response at all yet). */ + if((data->state.http_neg.only_10) && + (!conn || conn->httpversion_seen <= 10)) + return FALSE; + /* We are not restricted to use 1.0 only. */ + return !data->state.http_neg.only_10; } -enum proxy_use { - HEADER_SERVER, /* direct to server */ - HEADER_PROXY, /* regular request to proxy */ - HEADER_CONNECT /* sending CONNECT to a proxy */ -}; - -/* used to compile the provided trailers into one buffer - will return an error code if one of the headers is - not formatted correctly */ -CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, - struct dynbuf *b, - struct Curl_easy *handle) +static unsigned char http_request_version(struct Curl_easy *data) { - char *ptr = NULL; - CURLcode result = CURLE_OK; - const char *endofline_native = NULL; - const char *endofline_network = NULL; - - if( -#ifdef CURL_DO_LINEEND_CONV - (handle->state.prefer_ascii) || -#endif - (handle->set.crlf)) { - /* \n will become \r\n later on */ - endofline_native = "\n"; - endofline_network = "\x0a"; - } - else { - endofline_native = "\r\n"; - endofline_network = "\x0d\x0a"; - } - - while(trailers) { - /* only add correctly formatted trailers */ - ptr = strchr(trailers->data, ':'); - if(ptr && *(ptr + 1) == ' ') { - result = Curl_dyn_add(b, trailers->data); - if(result) - return result; - result = Curl_dyn_add(b, endofline_native); - if(result) - return result; - } - else - infof(handle, "Malformatted trailing header, skipping trailer"); - trailers = trailers->next; + unsigned char v = Curl_conn_http_version(data, data->conn); + if(!v) { + /* No specific HTTP connection filter installed. */ + v = http_may_use_1_1(data) ? 11 : 10; } - result = Curl_dyn_add(b, endofline_network); - return result; + return v; } -static bool hd_name_eq(const char *n1, size_t n1len, - const char *n2, size_t n2len) +static const char *get_http_string(int httpversion) { - if(n1len == n2len) { - return strncasecompare(n1, n2, n1len); + switch(httpversion) { + case 30: + return "3"; + case 20: + return "2"; + case 11: + return "1.1"; + default: + return "1.0"; } - return FALSE; } -CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, - bool is_connect, - struct dynhds *hds) +CURLcode Curl_add_custom_headers(struct Curl_easy *data, + bool is_connect, int httpversion, + struct dynbuf *req) { - struct connectdata *conn = data->conn; - char *ptr; struct curl_slist *h[2]; struct curl_slist *headers; int numlists = 1; /* by default */ int i; #ifndef CURL_DISABLE_PROXY - enum proxy_use proxy; + enum Curl_proxy_use proxy; if(is_connect) proxy = HEADER_CONNECT; else - proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy? - HEADER_PROXY:HEADER_SERVER; + proxy = data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy ? + HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: @@ -1769,249 +1659,80 @@ CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, /* loop through one or two lists */ for(i = 0; i < numlists; i++) { for(headers = h[i]; headers; headers = headers->next) { - const char *name, *value; - size_t namelen, valuelen; - - /* There are 2 quirks in place for custom headers: - * 1. setting only 'name:' to suppress a header from being sent - * 2. setting only 'name;' to send an empty (illegal) header - */ - ptr = strchr(headers->data, ':'); - if(ptr) { - name = headers->data; - namelen = ptr - headers->data; - ptr++; /* pass the colon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - if(*ptr) { - value = ptr; - valuelen = strlen(value); - } - else { - /* quirk #1, suppress this header */ - continue; - } - } + CURLcode result = CURLE_OK; + bool blankheader = FALSE; + struct Curl_str name; + const char *p = headers->data; + const char *origp = p; + + /* explicitly asked to send header without content is done by a header + that ends with a semicolon, but there must be no colon present in the + name */ + if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ';') && + !curlx_str_single(&p, ';') && + !curlx_str_single(&p, '\0') && + !memchr(curlx_str(&name), ':', curlx_strlen(&name))) + blankheader = TRUE; else { - ptr = strchr(headers->data, ';'); - - if(!ptr) { - /* neither : nor ; in provided header value. We seem - * to ignore this silently */ - continue; + p = origp; + if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ':') && + !curlx_str_single(&p, ':')) { + struct Curl_str val; + curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_trimblanks(&val); + if(!curlx_strlen(&val)) + /* no content, don't send this */ + continue; } - - name = headers->data; - namelen = ptr - headers->data; - ptr++; /* pass the semicolon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - if(!*ptr) { - /* quirk #2, send an empty header */ - value = ""; - valuelen = 0; - } - else { - /* this may be used for something else in the future, - * ignore this for now */ + else + /* no colon */ continue; - } } - DEBUGASSERT(name && value); + /* only send this if the contents was non-blank or done special */ + if(data->state.aptr.host && - /* a Host: header was sent already, don't pass on any custom Host: - header as that will produce *two* in the same request! */ - hd_name_eq(name, namelen, STRCONST("Host:"))) + /* a Host: header was sent already, do not pass on any custom + Host: header as that will produce *two* in the same + request! */ + curlx_str_casecompare(&name, "Host")) ; else if(data->state.httpreq == HTTPREQ_POST_FORM && /* this header (extended by formdata.c) is sent later */ - hd_name_eq(name, namelen, STRCONST("Content-Type:"))) + curlx_str_casecompare(&name, "Content-Type")) ; else if(data->state.httpreq == HTTPREQ_POST_MIME && /* this header is sent later */ - hd_name_eq(name, namelen, STRCONST("Content-Type:"))) + curlx_str_casecompare(&name, "Content-Type")) ; - else if(conn->bits.authneg && - /* while doing auth neg, don't allow the custom length since + else if(data->req.authneg && + /* while doing auth neg, do not allow the custom length since we will force length zero then */ - hd_name_eq(name, namelen, STRCONST("Content-Length:"))) + curlx_str_casecompare(&name, "Content-Length")) ; else if(data->state.aptr.te && - /* when asking for Transfer-Encoding, don't pass on a custom + /* when asking for Transfer-Encoding, do not pass on a custom Connection: */ - hd_name_eq(name, namelen, STRCONST("Connection:"))) + curlx_str_casecompare(&name, "Connection")) ; - else if((conn->httpversion >= 20) && - hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:"))) - /* HTTP/2 doesn't support chunked requests */ + else if((httpversion >= 20) && + curlx_str_casecompare(&name, "Transfer-Encoding")) + /* HTTP/2 does not support chunked requests */ ; - else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) || - hd_name_eq(name, namelen, STRCONST("Cookie:"))) && + else if((curlx_str_casecompare(&name, "Authorization") || + curlx_str_casecompare(&name, "Cookie")) && /* be careful of sending this potentially sensitive header to other hosts */ !Curl_auth_allowed_to_host(data)) ; - else { - CURLcode result; - - result = Curl_dynhds_add(hds, name, namelen, value, valuelen); - if(result) - return result; - } - } - } - - return CURLE_OK; -} - -CURLcode Curl_add_custom_headers(struct Curl_easy *data, - bool is_connect, -#ifndef USE_HYPER - struct dynbuf *req -#else - void *req -#endif - ) -{ - struct connectdata *conn = data->conn; - char *ptr; - struct curl_slist *h[2]; - struct curl_slist *headers; - int numlists = 1; /* by default */ - int i; - -#ifndef CURL_DISABLE_PROXY - enum proxy_use proxy; - - if(is_connect) - proxy = HEADER_CONNECT; - else - proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy? - HEADER_PROXY:HEADER_SERVER; - - switch(proxy) { - case HEADER_SERVER: - h[0] = data->set.headers; - break; - case HEADER_PROXY: - h[0] = data->set.headers; - if(data->set.sep_headers) { - h[1] = data->set.proxyheaders; - numlists++; - } - break; - case HEADER_CONNECT: - if(data->set.sep_headers) - h[0] = data->set.proxyheaders; - else - h[0] = data->set.headers; - break; - } -#else - (void)is_connect; - h[0] = data->set.headers; -#endif + else if(blankheader) + result = curlx_dyn_addf(req, "%.*s:\r\n", (int)curlx_strlen(&name), + curlx_str(&name)); + else + result = curlx_dyn_addf(req, "%s\r\n", origp); - /* loop through one or two lists */ - for(i = 0; i < numlists; i++) { - headers = h[i]; - - while(headers) { - char *semicolonp = NULL; - ptr = strchr(headers->data, ':'); - if(!ptr) { - char *optr; - /* no colon, semicolon? */ - ptr = strchr(headers->data, ';'); - if(ptr) { - optr = ptr; - ptr++; /* pass the semicolon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - - if(*ptr) { - /* this may be used for something else in the future */ - optr = NULL; - } - else { - if(*(--ptr) == ';') { - /* copy the source */ - semicolonp = strdup(headers->data); - if(!semicolonp) { -#ifndef USE_HYPER - Curl_dyn_free(req); -#endif - return CURLE_OUT_OF_MEMORY; - } - /* put a colon where the semicolon is */ - semicolonp[ptr - headers->data] = ':'; - /* point at the colon */ - optr = &semicolonp [ptr - headers->data]; - } - } - ptr = optr; - } - } - if(ptr && (ptr != headers->data)) { - /* we require a colon for this to be a true header */ - - ptr++; /* pass the colon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - - if(*ptr || semicolonp) { - /* only send this if the contents was non-blank or done special */ - CURLcode result = CURLE_OK; - char *compare = semicolonp ? semicolonp : headers->data; - - if(data->state.aptr.host && - /* a Host: header was sent already, don't pass on any custom Host: - header as that will produce *two* in the same request! */ - checkprefix("Host:", compare)) - ; - else if(data->state.httpreq == HTTPREQ_POST_FORM && - /* this header (extended by formdata.c) is sent later */ - checkprefix("Content-Type:", compare)) - ; - else if(data->state.httpreq == HTTPREQ_POST_MIME && - /* this header is sent later */ - checkprefix("Content-Type:", compare)) - ; - else if(conn->bits.authneg && - /* while doing auth neg, don't allow the custom length since - we will force length zero then */ - checkprefix("Content-Length:", compare)) - ; - else if(data->state.aptr.te && - /* when asking for Transfer-Encoding, don't pass on a custom - Connection: */ - checkprefix("Connection:", compare)) - ; - else if((conn->httpversion >= 20) && - checkprefix("Transfer-Encoding:", compare)) - /* HTTP/2 doesn't support chunked requests */ - ; - else if((checkprefix("Authorization:", compare) || - checkprefix("Cookie:", compare)) && - /* be careful of sending this potentially sensitive header to - other hosts */ - !Curl_auth_allowed_to_host(data)) - ; - else { -#ifdef USE_HYPER - result = Curl_hyper_header(data, req, compare); -#else - result = Curl_dyn_addf(req, "%s\r\n", compare); -#endif - } - if(semicolonp) - free(semicolonp); - if(result) - return result; - } - } - headers = headers->next; + if(result) + return result; } } @@ -2020,12 +1741,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, #ifndef CURL_DISABLE_PARSEDATE CURLcode Curl_add_timecondition(struct Curl_easy *data, -#ifndef USE_HYPER - struct dynbuf *req -#else - void *req -#endif - ) + struct dynbuf *req) { const struct tm *tm; struct tm keeptime; @@ -2047,6 +1763,7 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, switch(data->set.timecondition) { default: + DEBUGF(infof(data, "invalid time condition")); return CURLE_BAD_FUNCTION_ARGUMENT; case CURL_TIMECOND_IFMODSINCE: @@ -2079,7 +1796,7 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, msnprintf(datestr, sizeof(datestr), "%s: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", condp, - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], tm->tm_mday, Curl_month[tm->tm_mon], tm->tm_year + 1900, @@ -2087,12 +1804,7 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, tm->tm_min, tm->tm_sec); -#ifndef USE_HYPER - result = Curl_dyn_add(req, datestr); -#else - result = Curl_hyper_header(data, req, datestr); -#endif - + result = curlx_dyn_add(req, datestr); return result; } #else @@ -2116,8 +1828,10 @@ void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, httpreq = HTTPREQ_PUT; /* Now set the 'request' pointer to the proper request string */ - if(data->set.str[STRING_CUSTOMREQUEST]) + if(data->set.str[STRING_CUSTOMREQUEST] && + !data->state.http_ignorecustom) { request = data->set.str[STRING_CUSTOMREQUEST]; + } else { if(data->req.no_body) request = "HEAD"; @@ -2146,7 +1860,7 @@ void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, *reqp = httpreq; } -CURLcode Curl_http_useragent(struct Curl_easy *data) +static CURLcode http_useragent(struct Curl_easy *data) { /* The User-Agent string might have been allocated in url.c already, because it might have been used in the proxy connect, but if we have got a header @@ -2160,7 +1874,7 @@ CURLcode Curl_http_useragent(struct Curl_easy *data) } -CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) +static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn) { const char *ptr; struct dynamically_allocated_data *aptr = &data->state.aptr; @@ -2181,10 +1895,10 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) if(ptr && (!data->state.this_is_a_follow || strcasecompare(data->state.first_host, conn->host.name))) { #if !defined(CURL_DISABLE_COOKIES) - /* If we have a given custom Host: header, we extract the host name in + /* If we have a given custom Host: header, we extract the hostname in order to possibly use it for cookie reasons later on. We only allow the custom Host: header if this is NOT a redirect, as setting Host: in the - redirected request is being out on thin ice. Except if the host name + redirected request is being out on thin ice. Except if the hostname is the same as the first one! */ char *cookiehost = Curl_copy_header_value(ptr); if(!cookiehost) @@ -2210,37 +1924,38 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) if(colon) *colon = 0; /* The host must not include an embedded port number */ } - Curl_safefree(aptr->cookiehost); + free(aptr->cookiehost); aptr->cookiehost = cookiehost; } #endif - if(strcmp("Host:", ptr)) { + if(!strcasecompare("Host:", ptr)) { aptr->host = aprintf("Host:%s\r\n", &ptr[5]); if(!aptr->host) return CURLE_OUT_OF_MEMORY; } } else { - /* When building Host: headers, we must put the host name within - [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ + /* When building Host: headers, we must put the hostname within + [brackets] if the hostname is a plain IPv6-address. RFC2732-style. */ const char *host = conn->host.name; if(((conn->given->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS)) && (conn->remote_port == PORT_HTTPS)) || ((conn->given->protocol&(CURLPROTO_HTTP|CURLPROTO_WS)) && (conn->remote_port == PORT_HTTP)) ) - /* if(HTTPS on port 443) OR (HTTP on port 80) then don't include + /* if(HTTPS on port 443) OR (HTTP on port 80) then do not include the port number in the host string */ - aptr->host = aprintf("Host: %s%s%s\r\n", conn->bits.ipv6_ip?"[":"", - host, conn->bits.ipv6_ip?"]":""); + aptr->host = aprintf("Host: %s%s%s\r\n", conn->bits.ipv6_ip ? "[" : "", + host, conn->bits.ipv6_ip ? "]" : ""); else - aptr->host = aprintf("Host: %s%s%s:%d\r\n", conn->bits.ipv6_ip?"[":"", - host, conn->bits.ipv6_ip?"]":"", + aptr->host = aprintf("Host: %s%s%s:%d\r\n", + conn->bits.ipv6_ip ? "[" : "", + host, conn->bits.ipv6_ip ? "]" : "", conn->remote_port); if(!aptr->host) - /* without Host: we can't make a nice request */ + /* without Host: we cannot make a nice request */ return CURLE_OUT_OF_MEMORY; } return CURLE_OK; @@ -2249,9 +1964,9 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) /* * Append the request-target to the HTTP request */ -CURLcode Curl_http_target(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *r) +static CURLcode http_target(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *r) { CURLcode result = CURLE_OK; const char *path = data->state.up.path; @@ -2268,7 +1983,7 @@ CURLcode Curl_http_target(struct Curl_easy *data, /* The path sent to the proxy is in fact the entire URL. But if the remote host is a IDN-name, we must make sure that the request we produce only - uses the encoded host name! */ + uses the encoded hostname! */ /* and no fragment part */ CURLUcode uc; @@ -2291,7 +2006,7 @@ CURLcode Curl_http_target(struct Curl_easy *data, } if(strcasecompare("http", data->state.up.scheme)) { - /* when getting HTTP, we don't want the userinfo the URL */ + /* when getting HTTP, we do not want the userinfo the URL */ uc = curl_url_set(h, CURLUPART_USER, NULL, 0); if(uc) { curl_url_cleanup(h); @@ -2303,9 +2018,7 @@ CURLcode Curl_http_target(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } } - /* Extract the URL to use in the request. Store in STRING_TEMP_URL for - clean-up reasons if the function returns before the free() further - down. */ + /* Extract the URL to use in the request. */ uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT); if(uc) { curl_url_cleanup(h); @@ -2314,12 +2027,12 @@ CURLcode Curl_http_target(struct Curl_easy *data, curl_url_cleanup(h); - /* target or url */ - result = Curl_dyn_add(r, data->set.str[STRING_TARGET]? - data->set.str[STRING_TARGET]:url); + /* target or URL */ + result = curlx_dyn_add(r, data->set.str[STRING_TARGET] ? + data->set.str[STRING_TARGET] : url); free(url); if(result) - return (result); + return result; if(strcasecompare("ftp", data->state.up.scheme)) { if(data->set.proxy_transfer_mode) { @@ -2336,8 +2049,8 @@ CURLcode Curl_http_target(struct Curl_easy *data, } } if(!type) { - result = Curl_dyn_addf(r, ";type=%c", - data->state.prefer_ascii ? 'a' : 'i'); + result = curlx_dyn_addf(r, ";type=%c", + data->state.prefer_ascii ? 'a' : 'i'); if(result) return result; } @@ -2350,461 +2063,368 @@ CURLcode Curl_http_target(struct Curl_easy *data, (void)conn; /* not used in disabled-proxy builds */ #endif { - result = Curl_dyn_add(r, path); + result = curlx_dyn_add(r, path); if(result) return result; if(query) - result = Curl_dyn_addf(r, "?%s", query); + result = curlx_dyn_addf(r, "?%s", query); } return result; } -CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, - Curl_HttpReq httpreq, const char **tep) +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) +static CURLcode set_post_reader(struct Curl_easy *data, Curl_HttpReq httpreq) { - CURLcode result = CURLE_OK; - const char *ptr; - struct HTTP *http = data->req.p.http; - http->postsize = 0; + CURLcode result; switch(httpreq) { +#ifndef CURL_DISABLE_MIME case HTTPREQ_POST_MIME: - http->sendit = &data->set.mimepost; + data->state.mimepost = &data->set.mimepost; break; +#endif +#ifndef CURL_DISABLE_FORM_API case HTTPREQ_POST_FORM: - /* Convert the form structure into a mime structure. */ - Curl_mime_cleanpart(&http->form); - result = Curl_getformdata(data, &http->form, data->set.httppost, - data->state.fread_func); - if(result) - return result; - http->sendit = &http->form; + /* Convert the form structure into a mime structure, then keep + the conversion */ + if(!data->state.formp) { + data->state.formp = calloc(1, sizeof(curl_mimepart)); + if(!data->state.formp) + return CURLE_OUT_OF_MEMORY; + Curl_mime_cleanpart(data->state.formp); + result = Curl_getformdata(data, data->state.formp, data->set.httppost, + data->state.fread_func); + if(result) { + Curl_safefree(data->state.formp); + return result; + } + data->state.mimepost = data->state.formp; + } break; +#endif default: - http->sendit = NULL; + data->state.mimepost = NULL; + break; } + switch(httpreq) { + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* This is form posting using mime data. */ #ifndef CURL_DISABLE_MIME - if(http->sendit) { - const char *cthdr = Curl_checkheaders(data, STRCONST("Content-Type")); + if(data->state.mimepost) { + const char *cthdr = Curl_checkheaders(data, STRCONST("Content-Type")); - /* Read and seek body only. */ - http->sendit->flags |= MIME_BODY_ONLY; + /* Read and seek body only. */ + data->state.mimepost->flags |= MIME_BODY_ONLY; - /* Prepare the mime structure headers & set content type. */ + /* Prepare the mime structure headers & set content type. */ - if(cthdr) - for(cthdr += 13; *cthdr == ' '; cthdr++) - ; - else if(http->sendit->kind == MIMEKIND_MULTIPART) - cthdr = "multipart/form-data"; - - curl_mime_headers(http->sendit, data->set.headers, 0); - result = Curl_mime_prepare_headers(data, http->sendit, cthdr, - NULL, MIMESTRATEGY_FORM); - curl_mime_headers(http->sendit, NULL, 0); - if(!result) - result = Curl_mime_rewind(http->sendit); - if(result) - return result; - http->postsize = Curl_mime_size(http->sendit); - } -#endif + if(cthdr) + for(cthdr += 13; *cthdr == ' '; cthdr++) + ; + else if(data->state.mimepost->kind == MIMEKIND_MULTIPART) + cthdr = "multipart/form-data"; - ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); - if(ptr) { - /* Some kind of TE is requested, check if 'chunked' is chosen */ - data->req.upload_chunky = - Curl_compareheader(ptr, - STRCONST("Transfer-Encoding:"), STRCONST("chunked")); - } - else { - if((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) && - http->postsize < 0) || - ((data->state.upload || httpreq == HTTPREQ_POST) && - data->state.infilesize == -1))) { - if(conn->bits.authneg) - /* don't enable chunked during auth neg */ - ; - else if(Curl_use_http_1_1plus(data, conn)) { - if(conn->httpversion < 20) - /* HTTP, upload, unknown file size and not HTTP 1.0 */ - data->req.upload_chunky = TRUE; - } - else { - failf(data, "Chunky upload is not supported by HTTP 1.0"); - return CURLE_UPLOAD_FAILED; - } + curl_mime_headers(data->state.mimepost, data->set.headers, 0); + result = Curl_mime_prepare_headers(data, data->state.mimepost, cthdr, + NULL, MIMESTRATEGY_FORM); + if(result) + return result; + curl_mime_headers(data->state.mimepost, NULL, 0); + result = Curl_creader_set_mime(data, data->state.mimepost); + if(result) + return result; } - else { - /* else, no chunky upload */ - data->req.upload_chunky = FALSE; + else +#endif + { + result = Curl_creader_set_null(data); } + data->state.infilesize = Curl_creader_total_length(data); + return result; - if(data->req.upload_chunky) - *tep = "Transfer-Encoding: chunked\r\n"; + default: + return Curl_creader_set_null(data); } - return result; + /* never reached */ } +#endif -CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, - struct dynbuf *r, Curl_HttpReq httpreq) +static CURLcode set_reader(struct Curl_easy *data, Curl_HttpReq httpreq) { -#ifndef USE_HYPER - /* Hyper always handles the body separately */ - curl_off_t included_body = 0; -#else - /* from this point down, this function should not be used */ -#define Curl_buffer_send(a,b,c,d,e,f) CURLE_OK -#endif CURLcode result = CURLE_OK; - struct HTTP *http = data->req.p.http; - const char *ptr; + curl_off_t postsize = data->state.infilesize; - /* If 'authdone' is FALSE, we must not set the write socket index to the - Curl_transfer() call below, as we're not ready to actually upload any - data yet. */ + DEBUGASSERT(data->conn); - switch(httpreq) { + if(data->req.authneg) { + return Curl_creader_set_null(data); + } + switch(httpreq) { case HTTPREQ_PUT: /* Let's PUT the data to the server! */ + return postsize ? Curl_creader_set_fread(data, postsize) : + Curl_creader_set_null(data); - if(conn->bits.authneg) - http->postsize = 0; - else - http->postsize = data->state.infilesize; +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + return set_post_reader(data, httpreq); +#endif - if((http->postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || - !Curl_checkheaders(data, STRCONST("Content-Length")))) { - /* only add Content-Length if not uploading chunked */ - result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; + case HTTPREQ_POST: + /* this is the simple POST, using x-www-form-urlencoded style */ + /* the size of the post body */ + if(!postsize) { + result = Curl_creader_set_null(data); } - - /* For really small puts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); + else if(data->set.postfields) { + if(postsize > 0) + result = Curl_creader_set_buf(data, data->set.postfields, + (size_t)postsize); + else + result = Curl_creader_set_null(data); } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); - if(result) - return result; + else { + /* we read the bytes from the callback. In case "chunked" encoding + * is forced by the application, we disregard `postsize`. This is + * a backward compatibility decision to earlier versions where + * chunking disregarded this. See issue #13229. */ + bool chunked = FALSE; + char *ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + chunked = Curl_compareheader(ptr, STRCONST("Transfer-Encoding:"), + STRCONST("chunked")); + } + result = Curl_creader_set_fread(data, chunked ? -1 : postsize); } + return result; - /* end of headers */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; + default: + /* HTTP GET/HEAD download, has no body, needs no Content-Length */ + data->state.infilesize = 0; + return Curl_creader_set_null(data); + } + /* not reached */ +} - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); +static CURLcode http_resume(struct Curl_easy *data, Curl_HttpReq httpreq) +{ + if((HTTPREQ_POST == 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. + *********************************************************************/ - /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending PUT request"); - else - /* prepare for transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postsize?FIRSTSOCKET:-1); - if(result) - return result; - break; + 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; + } - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - /* This is form posting using mime data. */ - if(conn->bits.authneg) { - /* nothing to post! */ - result = Curl_dyn_addn(r, STRCONST("Content-Length: 0\r\n\r\n")); - if(result) + 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; - - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); - break; + } } + } + return CURLE_OK; +} - data->state.infilesize = http->postsize; +static CURLcode http_req_set_reader(struct Curl_easy *data, + Curl_HttpReq httpreq, int httpversion, + const char **tep) +{ + CURLcode result = CURLE_OK; + const char *ptr; - /* We only set Content-Length and allow a custom Content-Length if - we don't upload data chunked, as RFC2616 forbids us to set both - kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if(http->postsize != -1 && !data->req.upload_chunky && - (!Curl_checkheaders(data, STRCONST("Content-Length")))) { - /* we allow replacing this header if not during auth negotiation, - although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(r, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; - } + result = set_reader(data, httpreq); + if(result) + return result; -#ifndef CURL_DISABLE_MIME - /* Output mime-generated headers. */ - { - struct curl_slist *hdr; + result = http_resume(data, httpreq); + if(result) + return result; - for(hdr = http->sendit->curlheaders; hdr; hdr = hdr->next) { - result = Curl_dyn_addf(r, "%s\r\n", hdr->data); - if(result) - return result; + ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + data->req.upload_chunky = + Curl_compareheader(ptr, + STRCONST("Transfer-Encoding:"), STRCONST("chunked")); + if(data->req.upload_chunky && (httpversion >= 20)) { + infof(data, "suppressing chunked transfer encoding on connection " + "using HTTP version 2 or higher"); + data->req.upload_chunky = FALSE; + } + } + else { + curl_off_t req_clen = Curl_creader_total_length(data); + + if(req_clen < 0) { + /* indeterminate request content length */ + if(httpversion > 10) { + /* On HTTP/1.1, enable chunked, on HTTP/2 and later we do not + * need it */ + data->req.upload_chunky = (httpversion < 20); + } + else { + failf(data, "Chunky upload is not supported by HTTP 1.0"); + return CURLE_UPLOAD_FAILED; } } -#endif - - /* For really small posts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); + else { + /* else, no chunky upload */ + data->req.upload_chunky = FALSE; } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); + + if(data->req.upload_chunky) + *tep = "Transfer-Encoding: chunked\r\n"; + } + return result; +} + +static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r, + int httpversion, bool *announced_exp100) +{ + CURLcode result; + char *ptr; + + *announced_exp100 = FALSE; + /* Avoid Expect: 100-continue if Upgrade: is used */ + if(data->req.upgr101 != UPGR101_INIT) + return CURLE_OK; + + /* For really small puts we do not use Expect: headers at all, and for + the somewhat bigger ones we allow the app to disable it. Just make + sure that the expect100header is always set to the preferred value + here. */ + ptr = Curl_checkheaders(data, STRCONST("Expect")); + if(ptr) { + *announced_exp100 = + Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); + } + else if(!data->state.disableexpect && (httpversion == 11)) { + /* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an + Expect: 100-continue to the headers which actually speeds up post + operations (as there is one packet coming back from the web server) */ + curl_off_t client_len = Curl_creader_client_length(data); + if(client_len > EXPECT_100_THRESHOLD || client_len < 0) { + result = curlx_dyn_addn(r, STRCONST("Expect: 100-continue\r\n")); if(result) return result; + *announced_exp100 = TRUE; } - else - data->state.expect100header = FALSE; - - /* make the request end in a true CRLF */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); + } + return CURLE_OK; +} - /* Read from mime structure. */ - data->state.fread_func = (curl_read_callback) Curl_mime_read; - data->state.in = (void *) http->sendit; - http->sending = HTTPSEND_BODY; +static CURLcode http_req_complete(struct Curl_easy *data, + struct dynbuf *r, int httpversion, + Curl_HttpReq httpreq) +{ + CURLcode result = CURLE_OK; + curl_off_t req_clen; + bool announced_exp100 = FALSE; - /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* prepare for transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postsize?FIRSTSOCKET:-1); + DEBUGASSERT(data->conn); + if(data->req.upload_chunky) { + result = Curl_httpchunk_add_reader(data); if(result) return result; + } - break; - + /* Get the request body length that has been set up */ + req_clen = Curl_creader_total_length(data); + switch(httpreq) { + case HTTPREQ_PUT: case HTTPREQ_POST: - /* this is the simple POST, using x-www-form-urlencoded style */ - - if(conn->bits.authneg) - http->postsize = 0; - else - /* the size of the post body */ - http->postsize = data->state.infilesize; - +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: +#endif /* We only set Content-Length and allow a custom Content-Length if - we don't upload data chunked, as RFC2616 forbids us to set both - kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if((http->postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || + we do not upload data chunked, as RFC2616 forbids us to set both + kinds of headers (Transfer-Encoding: chunked and Content-Length). + We do not override a custom "Content-Length" header, but during + authentication negotiation that header is suppressed. + */ + if(req_clen >= 0 && !data->req.upload_chunky && + (data->req.authneg || !Curl_checkheaders(data, STRCONST("Content-Length")))) { /* we allow replacing this header if not during auth negotiation, - although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; + although it is not very wise to actually set your own */ + result = curlx_dyn_addf(r, "Content-Length: %" FMT_OFF_T "\r\n", + req_clen); } + if(result) + goto out; - if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = Curl_dyn_addn(r, STRCONST("Content-Type: application/" - "x-www-form-urlencoded\r\n")); - if(result) - return result; - } +#ifndef CURL_DISABLE_MIME + /* Output mime-generated headers. */ + if(data->state.mimepost && + ((httpreq == HTTPREQ_POST_FORM) || (httpreq == HTTPREQ_POST_MIME))) { + struct curl_slist *hdr; - /* For really small posts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); - } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); - if(result) - return result; - } - else - data->state.expect100header = FALSE; - -#ifndef USE_HYPER - /* With Hyper the body is always passed on separately */ - if(data->set.postfields) { - - /* In HTTP2, we send request body in DATA frame regardless of - its size. */ - if(conn->httpversion < 20 && - !data->state.expect100header && - (http->postsize < MAX_INITIAL_POST_SIZE)) { - /* if we don't use expect: 100 AND - postsize is less than MAX_INITIAL_POST_SIZE - - then append the post data to the HTTP request header. This limit - is no magic limit but only set to prevent really huge POSTs to - get the data duplicated with malloc() and family. */ - - /* end of headers! */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; - - if(!data->req.upload_chunky) { - /* We're not sending it 'chunked', append it to the request - already now to reduce the number if send() calls */ - result = Curl_dyn_addn(r, data->set.postfields, - (size_t)http->postsize); - included_body = http->postsize; - } - else { - if(http->postsize) { - char chunk[16]; - /* Append the POST data chunky-style */ - msnprintf(chunk, sizeof(chunk), "%x\r\n", (int)http->postsize); - result = Curl_dyn_add(r, chunk); - if(!result) { - included_body = http->postsize + strlen(chunk); - result = Curl_dyn_addn(r, data->set.postfields, - (size_t)http->postsize); - if(!result) - result = Curl_dyn_addn(r, STRCONST("\r\n")); - included_body += 2; - } - } - if(!result) { - result = Curl_dyn_addn(r, STRCONST("\x30\x0d\x0a\x0d\x0a")); - /* 0 CR LF CR LF */ - included_body += 5; - } - } - if(result) - return result; - /* Make sure the progress information is accurate */ - Curl_pgrsSetUploadSize(data, http->postsize); - } - else { - /* A huge POST coming up, do data separate from the request */ - http->postdata = data->set.postfields; - http->sending = HTTPSEND_BODY; - http->backup.data = data; - data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)http; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - /* end of headers! */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); + for(hdr = data->state.mimepost->curlheaders; hdr; hdr = hdr->next) { + result = curlx_dyn_addf(r, "%s\r\n", hdr->data); if(result) - return result; + goto out; } } - else #endif - { - /* end of headers! */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; - - if(data->req.upload_chunky && conn->bits.authneg) { - /* Chunky upload is selected and we're negotiating auth still, send - end-of-data only */ - result = Curl_dyn_addn(r, (char *)STRCONST("\x30\x0d\x0a\x0d\x0a")); - /* 0 CR LF CR LF */ + 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) - return result; - } - - else if(data->state.infilesize) { - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize?http->postsize:-1); - - /* set the pointer to mark that we will send the post body using the - read callback, but only if we're not in authenticate negotiation */ - if(!conn->bits.authneg) - http->postdata = (char *)&http->postdata; + goto out; } } - /* issue the request */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, included_body, - FIRSTSOCKET); - + result = addexpect(data, r, httpversion, &announced_exp100); if(result) - failf(data, "Failed sending HTTP POST request"); - else - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postdata?FIRSTSOCKET:-1); + goto out; break; - default: - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; + break; + } - /* issue the request */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending HTTP request"); -#ifdef USE_WEBSOCKETS - else if((conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) && - !(data->set.connect_only)) - /* Set up the transfer for two-way since without CONNECT_ONLY set, this - request probably wants to send data too post upgrade */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, FIRSTSOCKET); -#endif - else - /* HTTP GET/HEAD download: */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); + /* end of headers */ + result = curlx_dyn_addn(r, STRCONST("\r\n")); + if(!result) { + Curl_pgrsSetUploadSize(data, req_clen); + if(announced_exp100) + result = http_exp100_add_reader(data); } +out: + if(!result) { + /* setup variables for the upcoming transfer */ + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); + } return result; } #if !defined(CURL_DISABLE_COOKIES) -CURLcode Curl_http_cookies(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *r) +static CURLcode http_cookies(struct Curl_easy *data, + struct connectdata *conn, + struct dynbuf *r) { CURLcode result = CURLE_OK; char *addcookies = NULL; @@ -2814,8 +2434,9 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, addcookies = data->set.str[STRING_COOKIE]; if(data->cookies || addcookies) { - struct Cookie *co = NULL; /* no cookies from start */ + struct Curl_llist list; int count = 0; + int rc = 1; if(data->cookies && data->state.cookie_engine) { const char *host = data->state.aptr.cookiehost ? @@ -2824,63 +2445,69 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || strcasecompare("localhost", host) || !strcmp(host, "127.0.0.1") || - !strcmp(host, "::1") ? TRUE : FALSE; + !strcmp(host, "::1"); Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - co = Curl_cookie_getlist(data, data->cookies, host, data->state.up.path, - secure_context); + rc = Curl_cookie_getlist(data, data->cookies, host, data->state.up.path, + secure_context, &list); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } - if(co) { - struct Cookie *store = co; - /* now loop through all cookies that matched */ - while(co) { + if(!rc) { + struct Curl_llist_node *n; + size_t clen = 8; /* hold the size of the generated Cookie: header */ + + /* loop through all cookies that matched */ + for(n = Curl_llist_head(&list); n; n = Curl_node_next(n)) { + struct Cookie *co = Curl_node_elem(n); if(co->value) { - if(0 == count) { - result = Curl_dyn_addn(r, STRCONST("Cookie: ")); + size_t add; + if(!count) { + result = curlx_dyn_addn(r, STRCONST("Cookie: ")); if(result) break; } - if((Curl_dyn_len(r) + strlen(co->name) + strlen(co->value) + 1) >= - MAX_COOKIE_HEADER_LEN) { + add = strlen(co->name) + strlen(co->value) + 1; + if(clen + add >= MAX_COOKIE_HEADER_LEN) { infof(data, "Restricted outgoing cookies due to header size, " "'%s' not sent", co->name); linecap = TRUE; break; } - result = Curl_dyn_addf(r, "%s%s=%s", count?"; ":"", - co->name, co->value); + result = curlx_dyn_addf(r, "%s%s=%s", count ? "; " : "", + co->name, co->value); if(result) break; + clen += add + (count ? 2 : 0); count++; } - co = co->next; /* next cookie please */ } - Curl_cookie_freelist(store); + Curl_llist_destroy(&list, NULL); } if(addcookies && !result && !linecap) { if(!count) - result = Curl_dyn_addn(r, STRCONST("Cookie: ")); + result = curlx_dyn_addn(r, STRCONST("Cookie: ")); if(!result) { - result = Curl_dyn_addf(r, "%s%s", count?"; ":"", addcookies); + result = curlx_dyn_addf(r, "%s%s", count ? "; " : "", addcookies); count++; } } if(count && !result) - result = Curl_dyn_addn(r, STRCONST("\r\n")); + result = curlx_dyn_addn(r, STRCONST("\r\n")); if(result) return result; } return result; } +#else +#define http_cookies(a,b,c) CURLE_OK #endif -CURLcode Curl_http_range(struct Curl_easy *data, - Curl_HttpReq httpreq) +static CURLcode http_range(struct Curl_easy *data, + Curl_HttpReq httpreq) { if(data->state.use_range) { /* - * A range is selected. We use different headers whether we're downloading + * A range is selected. We use different headers whether we are downloading * or uploading and we always let customized headers override our internal * ones if any such are specified. */ @@ -2893,36 +2520,37 @@ CURLcode Curl_http_range(struct Curl_easy *data, } else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && !Curl_checkheaders(data, STRCONST("Content-Range"))) { - + curl_off_t req_clen = Curl_creader_total_length(data); /* if a line like this was already allocated, free the previous one */ free(data->state.aptr.rangeline); if(data->set.set_resume_from < 0) { - /* Upload resume was asked for, but we don't know the size of the + /* Upload resume was asked for, but we do not know the size of the remote part so we tell the server (and act accordingly) that we upload the whole file (again) */ data->state.aptr.rangeline = - aprintf("Content-Range: bytes 0-%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.infilesize - 1, data->state.infilesize); + aprintf("Content-Range: bytes 0-%" FMT_OFF_T "/%" FMT_OFF_T "\r\n", + req_clen - 1, req_clen); } else if(data->state.resume_from) { /* This is because "resume" was selected */ - curl_off_t total_expected_size = - data->state.resume_from + data->state.infilesize; + /* Not sure if we want to send this header during authentication + * negotiation, but test1084 checks for it. In which case we have a + * "null" client reader installed that gives an unexpected length. */ + curl_off_t total_len = data->req.authneg ? + data->state.infilesize : + (data->state.resume_from + req_clen); data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, total_expected_size-1, - total_expected_size); + aprintf("Content-Range: bytes %s%" FMT_OFF_T "/%" FMT_OFF_T "\r\n", + data->state.range, total_len-1, total_len); } else { /* Range was selected and then we just pass the incoming range and append total size */ data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, data->state.infilesize); + aprintf("Content-Range: bytes %s/%" FMT_OFF_T "\r\n", + data->state.range, req_clen); } if(!data->state.aptr.rangeline) return CURLE_OUT_OF_MEMORY; @@ -2931,124 +2559,43 @@ CURLcode Curl_http_range(struct Curl_easy *data, return CURLE_OK; } -CURLcode Curl_http_resume(struct Curl_easy *data, - struct connectdata *conn, - Curl_HttpReq httpreq) +static CURLcode http_firstwrite(struct Curl_easy *data) { - if((HTTPREQ_POST == 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. - *********************************************************************/ + struct connectdata *conn = data->conn; + struct SingleRequest *k = &data->req; - if(data->state.resume_from < 0) { - /* - * This is meant to get the size of the present remote-file by itself. - * We don't support this now. Bail out! - */ - data->state.resume_from = 0; + if(data->req.newurl) { + if(conn->bits.close) { + /* Abort after the headers if "follow Location" is set + and we are set to close anyway. */ + k->keepon &= ~KEEP_RECV; + k->done = TRUE; + return CURLE_OK; } - - if(data->state.resume_from && !data->state.followlocation) { - /* only act on the first request */ - - /* Now, let's read off the proper amount of bytes from the - input. */ - int seekerr = CURL_SEEKFUNC_CANTSEEK; - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_READ_ERROR; - } - /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, - data->state.in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Could only read %" CURL_FORMAT_CURL_OFF_T - " bytes from the input", passed); - return CURLE_READ_ERROR; - } - } while(passed < data->state.resume_from); - } - - /* now, decrease the size of the read */ - if(data->state.infilesize>0) { - data->state.infilesize -= data->state.resume_from; - - if(data->state.infilesize <= 0) { - failf(data, "File already completely uploaded"); - return CURLE_PARTIAL_FILE; - } - } - /* we've passed, proceed as normal */ - } - } - return CURLE_OK; -} - -CURLcode Curl_http_firstwrite(struct Curl_easy *data, - struct connectdata *conn, - bool *done) -{ - struct SingleRequest *k = &data->req; - - if(data->req.newurl) { - if(conn->bits.close) { - /* Abort after the headers if "follow Location" is set - and we're set to close anyway. */ - k->keepon &= ~KEEP_RECV; - *done = TRUE; - return CURLE_OK; - } - /* We have a new url to load, but since we want to be able to re-use this - connection properly, we read the full response in "ignore more" */ - k->ignorebody = TRUE; - infof(data, "Ignoring the response-body"); - } - if(data->state.resume_from && !k->content_range && - (data->state.httpreq == HTTPREQ_GET) && - !k->ignorebody) { + /* We have a new URL to load, but since we want to be able to reuse this + connection properly, we read the full response in "ignore more" */ + k->ignorebody = TRUE; + infof(data, "Ignoring the response-body"); + } + if(data->state.resume_from && !k->content_range && + (data->state.httpreq == HTTPREQ_GET) && + !k->ignorebody) { if(k->size == data->state.resume_from) { /* The resume point is at the end of file, consider this fine even if it - doesn't allow resume from here. */ + does not allow resume from here. */ infof(data, "The entire document is already downloaded"); streamclose(conn, "already downloaded"); /* Abort download */ k->keepon &= ~KEEP_RECV; - *done = TRUE; + k->done = TRUE; return CURLE_OK; } - /* we wanted to resume a download, although the server doesn't seem to - * support this and we did this with a GET (if it wasn't a GET we did a + /* we wanted to resume a download, although the server does not seem to + * support this and we did this with a GET (if it was not a GET we did a * POST or PUT resume) */ - failf(data, "HTTP server doesn't seem to support " + failf(data, "HTTP server does not seem to support " "byte ranges. Cannot resume."); return CURLE_RANGE_ERROR; } @@ -3059,13 +2606,13 @@ CURLcode Curl_http_firstwrite(struct Curl_easy *data, action for an HTTP/1.1 client */ if(!Curl_meets_timecondition(data, k->timeofdoc)) { - *done = TRUE; - /* We're simulating an HTTP 304 from server so we return + 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 - re-use ability. Close the connection */ + reuse ability. Close the connection */ streamclose(conn, "Simulated 304 handling"); return CURLE_OK; } @@ -3075,14 +2622,14 @@ CURLcode Curl_http_firstwrite(struct Curl_easy *data, } #ifdef HAVE_LIBZ -CURLcode Curl_transferencode(struct Curl_easy *data) +static CURLcode http_transferencode(struct Curl_easy *data) { if(!Curl_checkheaders(data, STRCONST("TE")) && data->set.http_transfer_encoding) { /* When we are to insert a TE: header in the request, we must also insert TE in a Connection: header, so we need to merge the custom provided Connection: header and prevent the original to get sent. Note that if - the user has inserted his/her own TE: header we don't do this magic + the user has inserted his/her own TE: header we do not do this magic but then assume that the user will handle it all! */ char *cptr = Curl_checkheaders(data, STRCONST("Connection")); #define TE_HEADER "TE: gzip\r\n" @@ -3107,7 +2654,6 @@ CURLcode Curl_transferencode(struct Curl_easy *data) } #endif -#ifndef USE_HYPER /* * Curl_http() gets called from the generic multi_do() function when an HTTP * request is to be performed. This creates and sends a properly constructed @@ -3117,7 +2663,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - struct HTTP *http; Curl_HttpReq httpreq; const char *te = ""; /* transfer-encoding */ const char *request; @@ -3125,6 +2670,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) struct dynbuf req; char *altused = NULL; const char *p_accept; /* Accept: string */ + unsigned char httpversion; /* Always consider the DO phase done after this function call, even if there may be parts of the request that are not yet sent, since we can deal with @@ -3133,45 +2679,49 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) switch(conn->alpn) { case CURL_HTTP_VERSION_3: - DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET)); + DEBUGASSERT(Curl_conn_http_version(data, conn) == 30); break; case CURL_HTTP_VERSION_2: #ifndef CURL_DISABLE_PROXY - if(!Curl_conn_is_http2(data, conn, FIRSTSOCKET) && + if((Curl_conn_http_version(data, conn) != 20) && conn->bits.proxy && !conn->bits.tunnel_proxy ) { - result = Curl_http2_switch(data, conn, FIRSTSOCKET); + result = Curl_http2_switch(data); if(result) - return result; + goto fail; } else #endif - DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET)); + DEBUGASSERT(Curl_conn_http_version(data, conn) == 20); break; case CURL_HTTP_VERSION_1_1: - /* continue with HTTP/1.1 when explicitly requested */ + /* continue with HTTP/1.x when explicitly requested */ break; default: /* Check if user wants to use HTTP/2 with clear TCP */ - if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) { + if(Curl_http2_may_switch(data)) { DEBUGF(infof(data, "HTTP/2 over clean TCP")); - result = Curl_http2_switch(data, conn, FIRSTSOCKET); + result = Curl_http2_switch(data); if(result) - return result; + goto fail; } break; } - http = data->req.p.http; - DEBUGASSERT(http); + /* Add collecting of headers written to client. For a new connection, + * we might have done that already, but reuse + * or multiplex needs it here as well. */ + result = Curl_headers_init(data); + if(result) + goto fail; - result = Curl_http_host(data, conn); + result = http_host(data, conn); if(result) - return result; + goto fail; - result = Curl_http_useragent(data); + result = http_useragent(data); if(result) - return result; + goto fail; Curl_http_method(data, conn, &request, &httpreq); @@ -3187,7 +2737,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) (pq ? pq : data->state.up.path), FALSE); free(pq); if(result) - return result; + goto fail; } Curl_safefree(data->state.aptr.ref); @@ -3199,7 +2749,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && data->set.str[STRING_ENCODING]) { - Curl_safefree(data->state.aptr.accept_encoding); + free(data->state.aptr.accept_encoding); data->state.aptr.accept_encoding = aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); if(!data->state.aptr.accept_encoding) @@ -3210,43 +2760,40 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) #ifdef HAVE_LIBZ /* we only consider transfer-encoding magic if libz support is built-in */ - result = Curl_transferencode(data); + result = http_transferencode(data); if(result) - return result; + goto fail; #endif - result = Curl_http_body(data, conn, httpreq, &te); + httpversion = http_request_version(data); + httpstring = get_http_string(httpversion); + + result = http_req_set_reader(data, httpreq, httpversion, &te); if(result) - return result; + goto fail; p_accept = Curl_checkheaders(data, - STRCONST("Accept"))?NULL:"Accept: */*\r\n"; + STRCONST("Accept")) ? NULL : "Accept: */*\r\n"; - result = Curl_http_resume(data, conn, httpreq); + result = http_range(data, httpreq); if(result) - return result; - - result = Curl_http_range(data, httpreq); - if(result) - return result; - - httpstring = get_http_string(data, conn); + goto fail; /* initialize a dynamic send-buffer */ - Curl_dyn_init(&req, DYN_HTTP_REQUEST); + curlx_dyn_init(&req, DYN_HTTP_REQUEST); /* make sure the header buffer is reset - if there are leftovers from a previous transfer */ - Curl_dyn_reset(&data->state.headerb); + curlx_dyn_reset(&data->state.headerb); /* add the main request stuff */ /* GET/HEAD/POST/PUT */ - result = Curl_dyn_addf(&req, "%s ", request); + result = curlx_dyn_addf(&req, "%s ", request); if(!result) - result = Curl_http_target(data, conn, &req); + result = http_target(data, conn, &req); if(result) { - Curl_dyn_free(&req); - return result; + curlx_dyn_free(&req); + goto fail; } #ifndef CURL_DISABLE_ALTSVC @@ -3254,143 +2801,120 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) altused = aprintf("Alt-Used: %s:%d\r\n", conn->conn_to_host.name, conn->conn_to_port); if(!altused) { - Curl_dyn_free(&req); + curlx_dyn_free(&req); return CURLE_OUT_OF_MEMORY; } } #endif result = - Curl_dyn_addf(&req, - " HTTP/%s\r\n" /* HTTP version */ - "%s" /* host */ - "%s" /* proxyuserpwd */ - "%s" /* userpwd */ - "%s" /* range */ - "%s" /* user agent */ - "%s" /* accept */ - "%s" /* TE: */ - "%s" /* accept-encoding */ - "%s" /* referer */ - "%s" /* Proxy-Connection */ - "%s" /* transfer-encoding */ - "%s",/* Alt-Used */ - - httpstring, - (data->state.aptr.host?data->state.aptr.host:""), - data->state.aptr.proxyuserpwd? - data->state.aptr.proxyuserpwd:"", - data->state.aptr.userpwd?data->state.aptr.userpwd:"", - (data->state.use_range && data->state.aptr.rangeline)? - data->state.aptr.rangeline:"", - (data->set.str[STRING_USERAGENT] && - *data->set.str[STRING_USERAGENT] && - data->state.aptr.uagent)? - data->state.aptr.uagent:"", - p_accept?p_accept:"", - data->state.aptr.te?data->state.aptr.te:"", - (data->set.str[STRING_ENCODING] && - *data->set.str[STRING_ENCODING] && - data->state.aptr.accept_encoding)? - data->state.aptr.accept_encoding:"", - (data->state.referer && data->state.aptr.ref)? - data->state.aptr.ref:"" /* Referer: */, + curlx_dyn_addf(&req, + " HTTP/%s\r\n" /* HTTP version */ + "%s" /* host */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + "%s" /* range */ + "%s" /* user agent */ + "%s" /* accept */ + "%s" /* TE: */ + "%s" /* accept-encoding */ + "%s" /* referer */ + "%s" /* Proxy-Connection */ + "%s" /* transfer-encoding */ + "%s",/* Alt-Used */ + + httpstring, + (data->state.aptr.host ? data->state.aptr.host : ""), #ifndef CURL_DISABLE_PROXY - (conn->bits.httpproxy && - !conn->bits.tunnel_proxy && - !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && - !Curl_checkProxyheaders(data, - conn, - STRCONST("Proxy-Connection")))? - "Proxy-Connection: Keep-Alive\r\n":"", + data->state.aptr.proxyuserpwd ? + data->state.aptr.proxyuserpwd : "", #else - "", + "", #endif - te, - altused ? altused : "" + data->state.aptr.userpwd ? data->state.aptr.userpwd : "", + (data->state.use_range && data->state.aptr.rangeline) ? + data->state.aptr.rangeline : "", + (data->set.str[STRING_USERAGENT] && + *data->set.str[STRING_USERAGENT] && + data->state.aptr.uagent) ? + data->state.aptr.uagent : "", + p_accept ? p_accept : "", + data->state.aptr.te ? data->state.aptr.te : "", + (data->set.str[STRING_ENCODING] && + *data->set.str[STRING_ENCODING] && + data->state.aptr.accept_encoding) ? + data->state.aptr.accept_encoding : "", + (data->state.referer && data->state.aptr.ref) ? + data->state.aptr.ref : "" /* Referer: */, +#ifndef CURL_DISABLE_PROXY + (conn->bits.httpproxy && + !conn->bits.tunnel_proxy && + !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && + !Curl_checkProxyheaders(data, conn, + STRCONST("Proxy-Connection"))) ? + "Proxy-Connection: Keep-Alive\r\n":"", +#else + "", +#endif + te, + altused ? altused : "" ); - /* clear userpwd and proxyuserpwd to avoid re-using old credentials - * from re-used connections */ + /* clear userpwd and proxyuserpwd to avoid reusing old credentials + * from reused connections */ Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); +#endif free(altused); if(result) { - Curl_dyn_free(&req); - return result; + curlx_dyn_free(&req); + goto fail; } - if(!(conn->handler->flags&PROTOPT_SSL) && - conn->httpversion < 20 && - (data->state.httpwant == CURL_HTTP_VERSION_2)) { - /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && (httpversion < 20) && + (data->state.http_neg.wanted & CURL_HTTP_V2x) && + data->state.http_neg.h2_upgrade) { + /* append HTTP2 upgrade magic stuff to the HTTP request if it is not done over SSL */ result = Curl_http2_request_upgrade(&req, data); if(result) { - Curl_dyn_free(&req); + curlx_dyn_free(&req); return result; } } - result = Curl_http_cookies(data, conn, &req); -#ifdef USE_WEBSOCKETS + result = http_cookies(data, conn, &req); +#ifndef CURL_DISABLE_WEBSOCKETS if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) result = Curl_ws_request(data, &req); #endif if(!result) result = Curl_add_timecondition(data, &req); if(!result) - result = Curl_add_custom_headers(data, FALSE, &req); + result = Curl_add_custom_headers(data, FALSE, httpversion, &req); if(!result) { - http->postdata = NULL; /* nothing to post at this point */ - if((httpreq == HTTPREQ_GET) || - (httpreq == HTTPREQ_HEAD)) - Curl_pgrsSetUploadSize(data, 0); /* nothing */ - - /* bodysend takes ownership of the 'req' memory on success */ - result = Curl_http_bodysend(data, conn, &req, httpreq); - } - if(result) { - Curl_dyn_free(&req); - return result; - } - - if((http->postsize > -1) && - (http->postsize <= data->req.writebytecount) && - (http->sending != HTTPSEND_REQUEST)) - data->req.upload_done = TRUE; - - if(data->req.writebytecount) { - /* if a request-body has been sent off, we make sure this progress is noted - properly */ - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - - if(!http->postsize) { - /* already sent the entire request body, mark the "upload" as - complete */ - infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes", - data->req.writebytecount, http->postsize); - data->req.upload_done = TRUE; - data->req.keepon &= ~KEEP_SEND; /* we're done writing */ - data->req.exp100 = EXP100_SEND_DATA; /* already sent */ - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - } + /* req_send takes ownership of the 'req' memory on success */ + result = http_req_complete(data, &req, httpversion, httpreq); + if(!result) + result = Curl_req_send(data, &req, httpversion); } + curlx_dyn_free(&req); + if(result) + goto fail; - if((conn->httpversion >= 20) && data->req.upload_chunky) + if((httpversion >= 20) && data->req.upload_chunky) /* upload_chunky was set above to set up the request in a chunky fashion, but is disabled here again to avoid that the chunked encoded version is actually used when sending the request body over h2 */ data->req.upload_chunky = FALSE; +fail: + if(CURLE_TOO_LARGE == result) + failf(data, "HTTP request too large"); return result; } -#endif /* USE_HYPER */ - typedef enum { STATUS_UNKNOWN, /* not enough data to tell yet */ STATUS_DONE, /* a status line was read */ @@ -3416,7 +2940,7 @@ checkhttpprefix(struct Curl_easy *data, { struct curl_slist *head = data->set.http200aliases; statusline rc = STATUS_BAD; - statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; + statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN; while(head) { if(checkprefixmax(head->data, s, len)) { @@ -3438,7 +2962,7 @@ checkrtspprefix(struct Curl_easy *data, const char *s, size_t len) { statusline result = STATUS_BAD; - statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; + statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN; (void)data; /* unused */ if(checkprefixmax("RTSP/", s, len)) result = onmatch; @@ -3461,325 +2985,388 @@ checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, return checkhttpprefix(data, s, len); } +/* HTTP header has field name `n` (a string constant) */ +#define HD_IS(hd, hdlen, n) \ + (((hdlen) >= (sizeof(n)-1)) && curl_strnequal((n), (hd), (sizeof(n)-1))) + +#define HD_VAL(hd, hdlen, n) \ + ((((hdlen) >= (sizeof(n)-1)) && \ + curl_strnequal((n), (hd), (sizeof(n)-1)))? (hd + (sizeof(n)-1)) : NULL) + +/* HTTP header has field name `n` (a string constant) and contains `v` + * (a string constant) in its value(s) */ +#define HD_IS_AND_SAYS(hd, hdlen, n, v) \ + (HD_IS(hd, hdlen, n) && \ + ((hdlen) > ((sizeof(n)-1) + (sizeof(v)-1))) && \ + Curl_compareheader(hd, STRCONST(n), STRCONST(v))) + /* - * Curl_http_header() parses a single response header. + * http_header() parses a single response header. */ -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *headp) +static CURLcode http_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { + struct connectdata *conn = data->conn; CURLcode result; struct SingleRequest *k = &data->req; - /* Check for Content-Length: header lines to get size */ - if(!k->http_bodyless && - !data->set.ignorecl && checkprefix("Content-Length:", headp)) { - curl_off_t contentlength; - CURLofft offt = curlx_strtoofft(headp + strlen("Content-Length:"), - NULL, 10, &contentlength); - - if(offt == CURL_OFFT_OK) { - k->size = contentlength; - k->maxdownload = k->size; + const char *v; + + switch(hd[0]) { + case 'a': + case 'A': +#ifndef CURL_DISABLE_ALTSVC + v = (data->asi && + (Curl_conn_is_ssl(data->conn, FIRSTSOCKET) || +#ifdef DEBUGBUILD + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_ALTSVC_HTTP") +#else + 0 +#endif + )) ? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL; + if(v) { + /* the ALPN of the current request */ + enum alpnid id = (k->httpversion == 30) ? ALPN_h3 : + (k->httpversion == 20) ? ALPN_h2 : ALPN_h1; + return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name, + curlx_uitous((unsigned int)conn->remote_port)); } - else if(offt == CURL_OFFT_FLOW) { - /* out of range */ - if(data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; +#endif + break; + case 'c': + case 'C': + /* Check for Content-Length: header lines to get size */ + v = (!k->http_bodyless && !data->set.ignorecl) ? + HD_VAL(hd, hdlen, "Content-Length:") : NULL; + if(v) { + curl_off_t contentlength; + int offt = curlx_str_numblanks(&v, &contentlength); + + if(offt == STRE_OK) { + k->size = contentlength; + k->maxdownload = k->size; + } + else if(offt == STRE_OVERFLOW) { + /* out of range */ + if(data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + streamclose(conn, "overflow content-length"); + infof(data, "Overflow Content-Length: value"); } - streamclose(conn, "overflow content-length"); - infof(data, "Overflow Content-Length: value"); + else { + /* negative or just rubbish - bad HTTP */ + failf(data, "Invalid Content-Length: value"); + return CURLE_WEIRD_SERVER_REPLY; + } + return CURLE_OK; } - else { - /* negative or just rubbish - bad HTTP */ - failf(data, "Invalid Content-Length: value"); - return CURLE_WEIRD_SERVER_REPLY; + v = (!k->http_bodyless && data->set.str[STRING_ENCODING]) ? + HD_VAL(hd, hdlen, "Content-Encoding:") : NULL; + if(v) { + /* + * Process Content-Encoding. Look for the values: identity, + * gzip, deflate, compress, x-gzip and x-compress. x-gzip and + * x-compress are the same as gzip and compress. (Sec 3.5 RFC + * 2616). zlib cannot handle compress. However, errors are + * handled further down when the response body is processed + */ + return Curl_build_unencoding_stack(data, v, FALSE); } - } - /* check for Content-Type: header lines to get the MIME-type */ - else if(checkprefix("Content-Type:", headp)) { - char *contenttype = Curl_copy_header_value(headp); - if(!contenttype) - return CURLE_OUT_OF_MEMORY; - if(!*contenttype) - /* ignore empty data */ - free(contenttype); - else { - Curl_safefree(data->info.contenttype); - data->info.contenttype = contenttype; + /* check for Content-Type: header lines to get the MIME-type */ + v = HD_VAL(hd, hdlen, "Content-Type:"); + if(v) { + char *contenttype = Curl_copy_header_value(hd); + if(!contenttype) + return CURLE_OUT_OF_MEMORY; + if(!*contenttype) + /* ignore empty data */ + free(contenttype); + else { + free(data->info.contenttype); + data->info.contenttype = contenttype; + } + return CURLE_OK; } - } -#ifndef CURL_DISABLE_PROXY - else if((conn->httpversion == 10) && - conn->bits.httpproxy && - Curl_compareheader(headp, - STRCONST("Proxy-Connection:"), - STRCONST("keep-alive"))) { - /* - * When an HTTP/1.0 reply comes when using a proxy, the - * 'Proxy-Connection: keep-alive' line tells us the - * connection will be kept alive for our pleasure. - * Default action for 1.0 is to close. - */ - connkeep(conn, "Proxy-Connection keep-alive"); /* don't close */ - infof(data, "HTTP/1.0 proxy connection set to keep alive"); - } - else if((conn->httpversion == 11) && - conn->bits.httpproxy && - Curl_compareheader(headp, - STRCONST("Proxy-Connection:"), - STRCONST("close"))) { - /* - * We get an HTTP/1.1 response from a proxy and it says it'll - * close down after this transfer. - */ - connclose(conn, "Proxy-Connection: asked to close after done"); - infof(data, "HTTP/1.1 proxy connection set close"); - } -#endif - else if((conn->httpversion == 10) && - Curl_compareheader(headp, - STRCONST("Connection:"), - STRCONST("keep-alive"))) { - /* - * An HTTP/1.0 reply with the 'Connection: keep-alive' line - * tells us the connection will be kept alive for our - * pleasure. Default action for 1.0 is to close. - * - * [RFC2068, section 19.7.1] */ - connkeep(conn, "Connection keep-alive"); - infof(data, "HTTP/1.0 connection set to keep alive"); - } - else if(Curl_compareheader(headp, - STRCONST("Connection:"), STRCONST("close"))) { - /* - * [RFC 2616, section 8.1.2.1] - * "Connection: close" is HTTP/1.1 language and means that - * the connection will close when this request has been - * served. - */ - streamclose(conn, "Connection: close used"); - } - else if(!k->http_bodyless && checkprefix("Transfer-Encoding:", headp)) { - /* One or more encodings. We check for chunked and/or a compression - algorithm. */ - /* - * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding - * means that the server will send a series of "chunks". Each - * chunk starts with line with info (including size of the - * coming block) (terminated with CRLF), then a block of data - * with the previously mentioned size. There can be any amount - * of chunks, and a chunk-data set to zero signals the - * end-of-chunks. */ - - result = Curl_build_unencoding_stack(data, - headp + strlen("Transfer-Encoding:"), - TRUE); - if(result) - return result; - if(!k->chunk && data->set.http_transfer_encoding) { - /* if this isn't chunked, only close can signal the end of this transfer - as Content-Length is said not to be trusted for transfer-encoding! */ - connclose(conn, "HTTP/1.1 transfer-encoding without chunks"); - k->ignore_cl = TRUE; + if(HD_IS_AND_SAYS(hd, hdlen, "Connection:", "close")) { + /* + * [RFC 2616, section 8.1.2.1] + * "Connection: close" is HTTP/1.1 language and means that + * the connection will close when this request has been + * served. + */ + streamclose(conn, "Connection: close used"); + return CURLE_OK; } - } - else if(!k->http_bodyless && checkprefix("Content-Encoding:", headp) && - data->set.str[STRING_ENCODING]) { - /* - * Process Content-Encoding. Look for the values: identity, - * gzip, deflate, compress, x-gzip and x-compress. x-gzip and - * x-compress are the same as gzip and compress. (Sec 3.5 RFC - * 2616). zlib cannot handle compress. However, errors are - * handled further down when the response body is processed - */ - result = Curl_build_unencoding_stack(data, - headp + strlen("Content-Encoding:"), - FALSE); - if(result) - return result; - } - else if(checkprefix("Retry-After:", headp)) { - /* Retry-After = HTTP-date / delay-seconds */ - curl_off_t retry_after = 0; /* zero for unknown or "now" */ - /* Try it as a decimal number, if it works it is not a date */ - (void)curlx_strtoofft(headp + strlen("Retry-After:"), - NULL, 10, &retry_after); - if(!retry_after) { - time_t date = Curl_getdate_capped(headp + strlen("Retry-After:")); - if(-1 != date) - /* convert date to number of seconds into the future */ - retry_after = date - time(NULL); + if((k->httpversion == 10) && + HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) { + /* + * An HTTP/1.0 reply with the 'Connection: keep-alive' line + * tells us the connection will be kept alive for our + * pleasure. Default action for 1.0 is to close. + * + * [RFC2068, section 19.7.1] */ + connkeep(conn, "Connection keep-alive"); + infof(data, "HTTP/1.0 connection set to keep alive"); + return CURLE_OK; } - data->info.retry_after = retry_after; /* store it */ - } - else if(!k->http_bodyless && checkprefix("Content-Range:", headp)) { - /* Content-Range: bytes [num]- - Content-Range: bytes: [num]- - Content-Range: [num]- - Content-Range: [asterisk]/[total] - - The second format was added since Sun's webserver - JavaWebServer/1.1.1 obviously sends the header this way! - The third added since some servers use that! - The fourth means the requested range was unsatisfied. - */ - - char *ptr = headp + strlen("Content-Range:"); - - /* Move forward until first digit or asterisk */ - while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') - ptr++; - - /* if it truly stopped on a digit */ - if(ISDIGIT(*ptr)) { - if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) { - if(data->state.resume_from == k->offset) + v = !k->http_bodyless ? HD_VAL(hd, hdlen, "Content-Range:") : NULL; + if(v) { + /* Content-Range: bytes [num]- + Content-Range: bytes: [num]- + Content-Range: [num]- + Content-Range: [asterisk]/[total] + + The second format was added since Sun's webserver + JavaWebServer/1.1.1 obviously sends the header this way! + The third added since some servers use that! + The fourth means the requested range was unsatisfied. + */ + + const char *ptr = v; + + /* Move forward until first digit or asterisk */ + while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') + ptr++; + + /* if it truly stopped on a digit */ + if(ISDIGIT(*ptr)) { + if(!curlx_str_number(&ptr, &k->offset, CURL_OFF_T_MAX) && + (data->state.resume_from == k->offset)) /* we asked for a resume and we got it */ k->content_range = TRUE; } + else if(k->httpcode < 300) + data->state.resume_from = 0; /* get everything */ } - else - data->state.resume_from = 0; /* get everything */ - } -#if !defined(CURL_DISABLE_COOKIES) - else if(data->cookies && data->state.cookie_engine && - checkprefix("Set-Cookie:", headp)) { - /* If there is a custom-set Host: name, use it here, or else use real peer - host name. */ - const char *host = data->state.aptr.cookiehost? - data->state.aptr.cookiehost:conn->host.name; - const bool secure_context = - conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || - strcasecompare("localhost", host) || - !strcmp(host, "127.0.0.1") || - !strcmp(host, "::1") ? TRUE : FALSE; - - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, - CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_add(data, data->cookies, TRUE, FALSE, - headp + strlen("Set-Cookie:"), host, - data->state.up.path, secure_context); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } -#endif - else if(!k->http_bodyless && checkprefix("Last-Modified:", headp) && - (data->set.timecondition || data->set.get_filetime) ) { - k->timeofdoc = Curl_getdate_capped(headp + strlen("Last-Modified:")); - if(data->set.get_filetime) - data->info.filetime = k->timeofdoc; - } - else if((checkprefix("WWW-Authenticate:", headp) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", headp) && - (407 == k->httpcode))) { - - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; - char *auth = Curl_copy_header_value(headp); - if(!auth) - return CURLE_OUT_OF_MEMORY; + break; + case 'l': + case 'L': + v = (!k->http_bodyless && + (data->set.timecondition || data->set.get_filetime)) ? + HD_VAL(hd, hdlen, "Last-Modified:") : NULL; + if(v) { + k->timeofdoc = Curl_getdate_capped(v); + if(data->set.get_filetime) + data->info.filetime = k->timeofdoc; + return CURLE_OK; + } + if((k->httpcode >= 300 && k->httpcode < 400) && + HD_IS(hd, hdlen, "Location:") && + !data->req.location) { + /* this is the URL that the server advises us to use instead */ + char *location = Curl_copy_header_value(hd); + if(!location) + return CURLE_OUT_OF_MEMORY; + if(!*location) + /* ignore empty data */ + free(location); + else { + data->req.location = location; - result = Curl_http_input_auth(data, proxy, auth); + if(data->set.http_follow_mode) { + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->req.location); /* clone */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; - free(auth); + /* some cases of POST and PUT etc needs to rewind the data + stream at this point */ + result = http_perhapsrewind(data, conn); + if(result) + return result; - if(result) + /* mark the next request as a followed location: */ + data->state.this_is_a_follow = TRUE; + } + } + } + break; + case 'p': + case 'P': +#ifndef CURL_DISABLE_PROXY + v = HD_VAL(hd, hdlen, "Proxy-Connection:"); + if(v) { + if((k->httpversion == 10) && conn->bits.httpproxy && + HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) { + /* + * When an HTTP/1.0 reply comes when using a proxy, the + * 'Proxy-Connection: keep-alive' line tells us the + * connection will be kept alive for our pleasure. + * Default action for 1.0 is to close. + */ + connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */ + infof(data, "HTTP/1.0 proxy connection set to keep alive"); + } + else if((k->httpversion == 11) && conn->bits.httpproxy && + HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) { + /* + * We get an HTTP/1.1 response from a proxy and it says it will + * close down after this transfer. + */ + connclose(conn, "Proxy-Connection: asked to close after done"); + infof(data, "HTTP/1.1 proxy connection set close"); + } + return CURLE_OK; + } +#endif + if((407 == k->httpcode) && HD_IS(hd, hdlen, "Proxy-authenticate:")) { + char *auth = Curl_copy_header_value(hd); + if(!auth) + return CURLE_OUT_OF_MEMORY; + result = Curl_http_input_auth(data, TRUE, auth); + free(auth); return result; - } + } #ifdef USE_SPNEGO - else if(checkprefix("Persistent-Auth:", headp)) { - struct negotiatedata *negdata = &conn->negotiate; - struct auth *authp = &data->state.authhost; - if(authp->picked == CURLAUTH_NEGOTIATE) { - char *persistentauth = Curl_copy_header_value(headp); - if(!persistentauth) - return CURLE_OUT_OF_MEMORY; - negdata->noauthpersist = checkprefix("false", persistentauth)? - TRUE:FALSE; - negdata->havenoauthpersist = TRUE; - infof(data, "Negotiate: noauthpersist -> %d, header part: %s", - negdata->noauthpersist, persistentauth); - free(persistentauth); + if(HD_IS(hd, hdlen, "Persistent-Auth:")) { + struct negotiatedata *negdata = &conn->negotiate; + struct auth *authp = &data->state.authhost; + if(authp->picked == CURLAUTH_NEGOTIATE) { + char *persistentauth = Curl_copy_header_value(hd); + if(!persistentauth) + return CURLE_OUT_OF_MEMORY; + negdata->noauthpersist = !!checkprefix("false", persistentauth); + negdata->havenoauthpersist = TRUE; + infof(data, "Negotiate: noauthpersist -> %d, header part: %s", + negdata->noauthpersist, persistentauth); + free(persistentauth); + } } - } #endif - else if((k->httpcode >= 300 && k->httpcode < 400) && - checkprefix("Location:", headp) && - !data->req.location) { - /* this is the URL that the server advises us to use instead */ - char *location = Curl_copy_header_value(headp); - if(!location) - return CURLE_OUT_OF_MEMORY; - if(!*location) - /* ignore empty data */ - free(location); - else { - data->req.location = location; - - if(data->set.http_follow_location) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->req.location); /* clone */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - - /* some cases of POST and PUT etc needs to rewind the data - stream at this point */ - result = http_perhapsrewind(data, conn); - if(result) - return result; - - /* mark the next request as a followed location: */ - data->state.this_is_a_follow = TRUE; + break; + case 'r': + case 'R': + v = HD_VAL(hd, hdlen, "Retry-After:"); + if(v) { + /* Retry-After = HTTP-date / delay-seconds */ + curl_off_t retry_after = 0; /* zero for unknown or "now" */ + time_t date; + curlx_str_passblanks(&v); + + /* try it as a date first, because a date can otherwise start with and + get treated as a number */ + date = Curl_getdate_capped(v); + + if((time_t)-1 != date) { + time_t current = time(NULL); + if(date >= current) + /* convert date to number of seconds into the future */ + retry_after = date - current; } + else + /* Try it as a decimal number */ + curlx_str_number(&v, &retry_after, CURL_OFF_T_MAX); + /* limit to 6 hours max. this is not documented so that it can be changed + in the future if necessary. */ + if(retry_after > 21600) + retry_after = 21600; + data->info.retry_after = retry_after; + return CURLE_OK; } - } + break; + case 's': + case 'S': +#if !defined(CURL_DISABLE_COOKIES) + v = (data->cookies && data->state.cookie_engine) ? + HD_VAL(hd, hdlen, "Set-Cookie:") : NULL; + if(v) { + /* If there is a custom-set Host: name, use it here, or else use + * real peer hostname. */ + const char *host = data->state.aptr.cookiehost ? + data->state.aptr.cookiehost : conn->host.name; + const bool secure_context = + conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || + strcasecompare("localhost", host) || + !strcmp(host, "127.0.0.1") || + !strcmp(host, "::1"); + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, + CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_add(data, data->cookies, TRUE, FALSE, v, host, + data->state.up.path, secure_context); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + return CURLE_OK; + } +#endif #ifndef CURL_DISABLE_HSTS - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->hsts && checkprefix("Strict-Transport-Security:", headp) && - ((conn->handler->flags & PROTOPT_SSL) || -#ifdef CURLDEBUG + /* If enabled, the header is incoming and this is over HTTPS */ + v = (data->hsts && + (Curl_conn_is_ssl(conn, FIRSTSOCKET) || +#ifdef DEBUGBUILD /* allow debug builds to circumvent the HTTPS restriction */ getenv("CURL_HSTS_HTTP") #else 0 #endif - )) { - CURLcode check = - Curl_hsts_parse(data->hsts, conn->host.name, - headp + strlen("Strict-Transport-Security:")); - if(check) - infof(data, "Illegal STS header skipped"); + ) + ) ? HD_VAL(hd, hdlen, "Strict-Transport-Security:") : NULL; + if(v) { + CURLcode check = + Curl_hsts_parse(data->hsts, conn->host.name, v); + if(check) + infof(data, "Illegal STS header skipped"); #ifdef DEBUGBUILD - else - infof(data, "Parsed STS header fine (%zu entries)", - data->hsts->list.size); -#endif - } + else + infof(data, "Parsed STS header fine (%zu entries)", + Curl_llist_count(&data->hsts->list)); #endif -#ifndef CURL_DISABLE_ALTSVC - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->asi && checkprefix("Alt-Svc:", headp) && - ((conn->handler->flags & PROTOPT_SSL) || -#ifdef CURLDEBUG - /* allow debug builds to circumvent the HTTPS restriction */ - getenv("CURL_ALTSVC_HTTP") -#else - 0 + } #endif - )) { - /* the ALPN of the current request */ - enum alpnid id = (conn->httpversion == 30)? ALPN_h3 : - (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; - result = Curl_altsvc_parse(data, data->asi, - headp + strlen("Alt-Svc:"), - id, conn->host.name, - curlx_uitous((unsigned int)conn->remote_port)); - if(result) + break; + case 't': + case 'T': + /* RFC 9112, ch. 6.1 + * "Transfer-Encoding MAY be sent in a response to a HEAD request or + * in a 304 (Not Modified) response (Section 15.4.5 of [HTTP]) to a + * GET request, neither of which includes a message body, to indicate + * that the origin server would have applied a transfer coding to the + * message body if the request had been an unconditional GET." + * + * Read: in these cases the 'Transfer-Encoding' does not apply + * to any data following the response headers. Do not add any decoders. + */ + v = (!k->http_bodyless && + (data->state.httpreq != HTTPREQ_HEAD) && + (k->httpcode != 304)) ? + HD_VAL(hd, hdlen, "Transfer-Encoding:") : NULL; + if(v) { + /* One or more encodings. We check for chunked and/or a compression + algorithm. */ + result = Curl_build_unencoding_stack(data, v, TRUE); + if(result) + return result; + if(!k->chunk && data->set.http_transfer_encoding) { + /* 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! */ + connclose(conn, "HTTP/1.1 transfer-encoding without chunks"); + k->ignore_cl = TRUE; + } + return CURLE_OK; + } + v = HD_VAL(hd, hdlen, "Trailer:"); + if(v) { + data->req.resp_trailer = TRUE; + return CURLE_OK; + } + break; + case 'w': + case 'W': + if((401 == k->httpcode) && HD_IS(hd, hdlen, "WWW-Authenticate:")) { + char *auth = Curl_copy_header_value(hd); + if(!auth) + return CURLE_OUT_OF_MEMORY; + result = Curl_http_input_auth(data, FALSE, auth); + free(auth); return result; + } + break; } -#endif - else if(conn->handler->protocol & CURLPROTO_RTSP) { - result = Curl_rtsp_parseheader(data, headp); + + if(conn->handler->protocol & CURLPROTO_RTSP) { + result = Curl_rtsp_parseheader(data, hd); if(result) return result; } @@ -3790,25 +3377,49 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, * Called after the first HTTP response line (the status line) has been * received and parsed. */ - -CURLcode Curl_http_statusline(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode http_statusline(struct Curl_easy *data, + struct connectdata *conn) { struct SingleRequest *k = &data->req; + + switch(k->httpversion) { + case 10: + case 11: +#ifdef USE_HTTP2 + case 20: +#endif +#ifdef USE_HTTP3 + case 30: +#endif + /* no major version switch mid-connection */ + if(k->httpversion_sent && + (k->httpversion/10 != k->httpversion_sent/10)) { + failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)", + k->httpversion_sent/10, k->httpversion/10); + return CURLE_WEIRD_SERVER_REPLY; + } + break; + default: + failf(data, "Unsupported HTTP version (%u.%d) in response", + k->httpversion/10, k->httpversion%10); + return CURLE_UNSUPPORTED_PROTOCOL; + } + data->info.httpcode = k->httpcode; + data->info.httpversion = k->httpversion; + conn->httpversion_seen = (unsigned char)k->httpversion; - data->info.httpversion = conn->httpversion; - if(!data->state.httpversion || - data->state.httpversion > conn->httpversion) + if(!data->state.http_neg.rcvd_min || + data->state.http_neg.rcvd_min > k->httpversion) /* store the lowest server version we encounter */ - data->state.httpversion = conn->httpversion; + data->state.http_neg.rcvd_min = (unsigned char)k->httpversion; /* - * This code executes as part of processing the header. As a - * result, it's not totally clear how to interpret the + * This code executes as part of processing the header. As a + * result, it is not totally clear how to interpret the * response code yet as that depends on what other headers may - * be present. 401 and 407 may be errors, but may be OK - * depending on how authentication is working. Other codes + * be present. 401 and 407 may be errors, but may be OK + * depending on how authentication is working. Other codes * are definitely errors, so give up here. */ if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && @@ -3818,25 +3429,16 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, k->ignorebody = TRUE; /* Avoid appending error msg to good data. */ } - if(conn->httpversion == 10) { + if(k->httpversion == 10) { /* Default action for HTTP/1.0 must be to close, unless we get one of those fancy headers that tell us the server keeps it open for us! */ infof(data, "HTTP 1.0, assume close after body"); connclose(conn, "HTTP/1.0 close after body"); } - else if(conn->httpversion == 20 || + else if(k->httpversion == 20 || (k->upgr101 == UPGR101_H2 && k->httpcode == 101)) { DEBUGF(infof(data, "HTTP/2 found, allow multiplexing")); - /* HTTP/2 cannot avoid multiplexing since it is a core functionality - of the protocol */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - } - else if(conn->httpversion >= 11 && - !conn->bits.close) { - /* If HTTP version is >= 1.1 and connection is persistent */ - DEBUGF(infof(data, - "HTTP 1.1 or later with persistent connection")); } k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; @@ -3848,7 +3450,7 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, * fields. */ if(data->set.timecondition) data->info.timecond = TRUE; - /* FALLTHROUGH */ + FALLTHROUGH(); case 204: /* (quote from RFC2616, section 10.2.5): The server has * fulfilled the request but does not need to return an @@ -3866,11 +3468,11 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, } /* Content-Length must be ignored if any Transfer-Encoding is present in the - response. Refer to RFC 7230 section 3.3.3 and RFC2616 section 4.4. This is + response. Refer to RFC 7230 section 3.3.3 and RFC2616 section 4.4. This is figured out here after all headers have been received but before the final call to the user's header callback, so that a valid content length can be retrieved by the user in the final call. */ -CURLcode Curl_http_size(struct Curl_easy *data) +static CURLcode http_size(struct Curl_easy *data) { struct SingleRequest *k = &data->req; if(data->req.ignore_cl || k->chunk) { @@ -3878,22 +3480,24 @@ CURLcode Curl_http_size(struct Curl_easy *data) } else if(k->size != -1) { if(data->set.max_filesize && - k->size > data->set.max_filesize) { + !k->ignorebody && + (k->size > data->set.max_filesize)) { failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; } + if(k->ignorebody) + infof(data, "setting size while ignoring"); Curl_pgrsSetDownloadSize(data, k->size); k->maxdownload = k->size; } return CURLE_OK; } -static CURLcode verify_header(struct Curl_easy *data) +static CURLcode verify_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { struct SingleRequest *k = &data->req; - const char *header = Curl_dyn_ptr(&data->state.headerb); - size_t hlen = Curl_dyn_len(&data->state.headerb); - char *ptr = memchr(header, 0x00, hlen); + char *ptr = memchr(hd, 0x00, hdlen); if(ptr) { /* this is bad, bail out */ failf(data, "Nul byte in header"); @@ -3902,11 +3506,11 @@ static CURLcode verify_header(struct Curl_easy *data) if(k->headerline < 2) /* the first "header" is the status-line and it has no colon */ return CURLE_OK; - if(((header[0] == ' ') || (header[0] == '\t')) && k->headerline > 2) - /* line folding, can't happen on line 2 */ + if(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2) + /* line folding, cannot happen on line 2 */ ; else { - ptr = memchr(header, ':', hlen); + ptr = memchr(hd, ':', hdlen); if(!ptr) { /* this is bad, bail out */ failf(data, "Header without colon"); @@ -3916,75 +3520,574 @@ static CURLcode verify_header(struct Curl_easy *data) return CURLE_OK; } +CURLcode Curl_bump_headersize(struct Curl_easy *data, + size_t delta, + bool connect_only) +{ + size_t bad = 0; + unsigned int max = MAX_HTTP_RESP_HEADER_SIZE; + if(delta < MAX_HTTP_RESP_HEADER_SIZE) { + data->info.header_size += (unsigned int)delta; + data->req.allheadercount += (unsigned int)delta; + if(!connect_only) + data->req.headerbytecount += (unsigned int)delta; + if(data->req.allheadercount > max) + bad = data->req.allheadercount; + else if(data->info.header_size > (max * 20)) { + bad = data->info.header_size; + max *= 20; + } + } + else + bad = data->req.allheadercount + delta; + if(bad) { + failf(data, "Too large response headers: %zu > %u", bad, max); + return CURLE_RECV_ERROR; + } + return CURLE_OK; +} + +static CURLcode http_write_header(struct Curl_easy *data, + const char *hd, size_t hdlen) +{ + CURLcode result; + int writetype; + + /* now, only output this if the header AND body are requested: + */ + Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen); + + writetype = CLIENTWRITE_HEADER | + ((data->req.httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); + + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + + data->req.deductheadercount = (100 <= data->req.httpcode && + 199 >= data->req.httpcode) ? + data->req.headerbytecount : 0; + return result; +} + +static CURLcode http_on_response(struct Curl_easy *data, + const char *last_hd, size_t last_hd_len, + const char *buf, size_t blen, + size_t *pconsumed) +{ + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + + (void)buf; /* not used without HTTP2 enabled */ + *pconsumed = 0; + + if(k->upgr101 == UPGR101_RECEIVED) { + /* supposedly upgraded to http2 now */ + if(data->req.httpversion != 20) + infof(data, "Lying server, not serving HTTP/2"); + } + + if(k->httpcode < 200 && last_hd) { + /* Intermediate responses might trigger processing of more + * responses, write the last header to the client before + * proceeding. */ + result = http_write_header(data, last_hd, last_hd_len); + last_hd = NULL; /* handled it */ + if(result) + goto out; + } + + if(k->httpcode < 100) { + failf(data, "Unsupported response code in HTTP response"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } + else if(k->httpcode < 200) { + /* "A user agent MAY ignore unexpected 1xx status responses." + * By default, we expect to get more responses after this one. */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + + switch(k->httpcode) { + case 100: + /* + * We have made an HTTP PUT or POST and this is 1.1-lingo + * that tells us that the server is OK with this and ready + * to receive the data. + */ + http_exp100_got100(data); + break; + case 101: + /* Switching Protocols only allowed from HTTP/1.1 */ + if(k->httpversion_sent != 11) { + /* invalid for other HTTP versions */ + failf(data, "unexpected 101 response code"); + result = CURLE_WEIRD_SERVER_REPLY; + goto out; + } + if(k->upgr101 == UPGR101_H2) { + /* Switching to HTTP/2, where we will get more responses */ + infof(data, "Received 101, Switching to HTTP/2"); + k->upgr101 = UPGR101_RECEIVED; + data->conn->bits.asks_multiplex = FALSE; + /* We expect more response from HTTP/2 later */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + k->httpversion_sent = 20; /* It's an HTTP/2 request now */ + /* Any remaining `buf` bytes are already HTTP/2 and passed to + * be processed. */ + result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); + if(result) + goto out; + *pconsumed += blen; + } +#ifndef CURL_DISABLE_WEBSOCKETS + else if(k->upgr101 == UPGR101_WS) { + /* verify the response. Any passed `buf` bytes are already in + * WebSockets format and taken in by the protocol handler. */ + result = Curl_ws_accept(data, buf, blen); + if(result) + goto out; + *pconsumed += blen; /* ws accept handled the data */ + k->header = FALSE; /* we will not get more responses */ + if(data->set.connect_only) + k->keepon &= ~KEEP_RECV; /* read no more content */ + } +#endif + else { + /* We silently accept this as the final response. What are we + * switching to if we did not ask for an Upgrade? Maybe the + * application provided an `Upgrade: xxx` header? */ + k->header = FALSE; + } + break; + default: + /* The server may send us other 1xx responses, like informative + * 103. This have no influence on request processing and we expect + * to receive a final response eventually. */ + break; + } + goto out; + } + + /* k->httpcode >= 200, final response */ + k->header = FALSE; + + if(k->upgr101 == UPGR101_H2) { + /* A requested upgrade was denied, poke the multi handle to possibly + allow a pending pipewait to continue */ + data->conn->bits.asks_multiplex = FALSE; + Curl_multi_connchanged(data->multi); + } + + if((k->size == -1) && !k->chunk && !conn->bits.close && + (k->httpversion == 11) && + !(conn->handler->protocol & CURLPROTO_RTSP) && + data->state.httpreq != HTTPREQ_HEAD) { + /* On HTTP 1.1, when connection is not to get closed, but no + Content-Length nor Transfer-Encoding chunked have been + received, according to RFC2616 section 4.4 point 5, we + assume that the server will close the connection to + signal the end of the document. */ + infof(data, "no chunk, no close, no size. Assume close to " + "signal end"); + streamclose(conn, "HTTP: No end-of-message indicator"); + } + + /* At this point we have some idea about the fate of the connection. + If we are closing the connection it may result auth failure. */ +#if defined(USE_NTLM) + if(conn->bits.close && + (((data->req.httpcode == 401) && + (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || + ((data->req.httpcode == 407) && + (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { + infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); + data->state.authproblem = TRUE; + } +#endif +#if defined(USE_SPNEGO) + if(conn->bits.close && + (((data->req.httpcode == 401) && + (conn->http_negotiate_state == GSS_AUTHRECV)) || + ((data->req.httpcode == 407) && + (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { + infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); + data->state.authproblem = TRUE; + } + if((conn->http_negotiate_state == GSS_AUTHDONE) && + (data->req.httpcode != 401)) { + conn->http_negotiate_state = GSS_AUTHSUCC; + } + if((conn->proxy_negotiate_state == GSS_AUTHDONE) && + (data->req.httpcode != 407)) { + conn->proxy_negotiate_state = GSS_AUTHSUCC; + } +#endif + +#ifndef CURL_DISABLE_WEBSOCKETS + /* All >=200 HTTP status codes are errors when wanting WebSockets */ + if(data->req.upgr101 == UPGR101_WS) { + failf(data, "Refused WebSockets upgrade: %d", k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } +#endif + + /* Check if this response means the transfer errored. */ + if(http_should_fail(data, data->req.httpcode)) { + failf(data, "The requested URL returned error: %d", + k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } + + /* Curl_http_auth_act() checks what authentication methods + * that are available and decides which one (if any) to + * use. It will set 'newurl' if an auth method was picked. */ + result = Curl_http_auth_act(data); + if(result) + goto out; + + if(k->httpcode >= 300) { + if((!data->req.authneg) && !conn->bits.close && + !Curl_creader_will_rewind(data)) { + /* + * General treatment of errors when about to send data. Including : + * "417 Expectation Failed", while waiting for 100-continue. + * + * The check for close above is done simply because of something + * else has already deemed the connection to get closed then + * something else should've considered the big picture and we + * avoid this check. + * + */ + + switch(data->state.httpreq) { + case HTTPREQ_PUT: + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* We got an error response. If this happened before the whole + * request body has been sent we stop sending and mark the + * connection for closure after we have read the entire response. + */ + if(!Curl_req_done_sending(data)) { + if((k->httpcode == 417) && http_exp100_is_selected(data)) { + /* 417 Expectation Failed - try again without the Expect + header */ + if(!k->writebytecount && http_exp100_is_waiting(data)) { + infof(data, "Got HTTP failure 417 while waiting for a 100"); + } + else { + infof(data, "Got HTTP failure 417 while sending data"); + streamclose(conn, + "Stop sending data before everything sent"); + result = http_perhapsrewind(data, conn); + if(result) + goto out; + } + data->state.disableexpect = TRUE; + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->state.url); + Curl_req_abort_sending(data); + } + else if(data->set.http_keep_sending_on_error) { + infof(data, "HTTP error before end of send, keep sending"); + http_exp100_send_anyway(data); + } + else { + infof(data, "HTTP error before end of send, stop sending"); + streamclose(conn, "Stop sending data before everything sent"); + result = Curl_req_abort_sending(data); + if(result) + goto out; + } + } + break; + + default: /* default label present to avoid compiler warnings */ + break; + } + } + + if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) { + /* We rewind before next send, continue sending now */ + infof(data, "Keep sending data to get tossed away"); + k->keepon |= KEEP_SEND; + } + + } + + /* If we requested a "no body", this is a good time to get + * out and return home. + */ + if(data->req.no_body) + k->download_done = TRUE; + + /* If max download size is *zero* (nothing) we already have + nothing and can safely return ok now! But for HTTP/2, we would + like to call http2_handle_stream_close to properly close a + stream. In order to do this, we keep reading until we + close the stream. */ + if((0 == k->maxdownload) && (k->httpversion_sent < 20)) + k->download_done = TRUE; + + /* final response without error, prepare to receive the body */ + result = http_firstwrite(data); + + if(!result) + /* This is the last response that we get for the current request. + * Check on the body size and determine if the response is complete. + */ + result = http_size(data); + +out: + if(last_hd) { + /* if not written yet, write it now */ + CURLcode r2 = http_write_header(data, last_hd, last_hd_len); + if(!result) + result = r2; + } + return result; +} + +static CURLcode http_rw_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + const char *buf_remain, size_t blen, + size_t *pconsumed) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + int writetype; + + *pconsumed = 0; + if((0x0a == *hd) || (0x0d == *hd)) { + /* Empty header line means end of headers! */ + struct dynbuf last_header; + size_t consumed; + + curlx_dyn_init(&last_header, hdlen + 1); + result = curlx_dyn_addn(&last_header, hd, hdlen); + if(result) + return result; + + /* analyze the response to find out what to do. */ + /* Caveat: we clear anything in the header brigade, because a + * response might switch HTTP version which may call use recursively. + * Not nice, but that is currently the way of things. */ + curlx_dyn_reset(&data->state.headerb); + result = http_on_response(data, curlx_dyn_ptr(&last_header), + curlx_dyn_len(&last_header), + buf_remain, blen, &consumed); + *pconsumed += consumed; + curlx_dyn_free(&last_header); + return result; + } + + /* + * Checks for special headers coming up. + */ + + writetype = CLIENTWRITE_HEADER; + if(!k->headerline++) { + /* This is the first header, it MUST be the error code line + or else we consider this to be the body right away! */ + bool fine_statusline = FALSE; + + k->httpversion = 0; /* Do not know yet */ + if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + * + * The response code is always a three-digit number in HTTP as the spec + * says. We allow any three-digit number here, but we cannot make + * guarantees on future behaviors since it is not within the protocol. + */ + const char *p = hd; + + curlx_str_passblanks(&p); + if(!strncmp(p, "HTTP/", 5)) { + p += 5; + switch(*p) { + case '1': + p++; + if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { + if(ISBLANK(p[2])) { + k->httpversion = (unsigned char)(10 + (p[1] - '0')); + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + /* RFC 9112 requires a single space following the status code, + but the browsers don't so let's not insist */ + fine_statusline = TRUE; + } + } + } + if(!fine_statusline) { + failf(data, "Unsupported HTTP/1 subversion in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + case '2': + case '3': + if(!ISBLANK(p[1])) + break; + k->httpversion = (unsigned char)((*p - '0') * 10); + p += 2; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(!ISBLANK(*p)) + break; + fine_statusline = TRUE; + } + break; + default: /* unsupported */ + failf(data, "Unsupported HTTP version in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + } + + if(!fine_statusline) { + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + statusline check = checkhttpprefix(data, hd, hdlen); + if(check == STATUS_DONE) { + fine_statusline = TRUE; + k->httpcode = 200; + k->httpversion = 10; + } + } + } + else if(data->conn->handler->protocol & CURLPROTO_RTSP) { + const char *p = hd; + struct Curl_str ver; + curl_off_t status; + /* we set the max string a little excessive to forgive some leading + spaces */ + if(!curlx_str_until(&p, &ver, 32, ' ') && + !curlx_str_single(&p, ' ') && + !curlx_str_number(&p, &status, 999)) { + curlx_str_trimblanks(&ver); + if(curlx_str_cmp(&ver, "RTSP/1.0")) { + k->httpcode = (int)status; + fine_statusline = TRUE; + k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ + } + } + if(!fine_statusline) + return CURLE_WEIRD_SERVER_REPLY; + } + + if(fine_statusline) { + result = http_statusline(data, data->conn); + if(result) + return result; + writetype |= CLIENTWRITE_STATUS; + } + else { + k->header = FALSE; /* this is not a header line */ + return CURLE_WEIRD_SERVER_REPLY; + } + } + + result = verify_header(data, hd, hdlen); + if(result) + return result; + + result = http_header(data, hd, hdlen); + if(result) + return result; + + /* + * Taken in one (more) header. Write it to the client. + */ + Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen); + + if(k->httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + + return CURLE_OK; +} + /* * Read any HTTP header lines from the server and pass them to the client app. */ -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading) +static CURLcode http_parse_headers(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) { - CURLcode result; + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - ssize_t onread = *nread; - char *ostr = k->str; - char *headp; - char *str_start; char *end_ptr; + bool leftover_body = FALSE; /* header line within buffer loop */ - do { - size_t rest_length; - size_t full_length; - int writetype; - - /* str_start is start of line within buf */ - str_start = k->str; - - /* data is in network encoding so use 0x0a instead of '\n' */ - end_ptr = memchr(str_start, 0x0a, *nread); + *pconsumed = 0; + while(blen && k->header) { + size_t consumed; + end_ptr = memchr(buf, '\n', blen); if(!end_ptr) { /* Not a complete header line within buffer, append the data to the end of the headerbuff. */ - result = Curl_dyn_addn(&data->state.headerb, str_start, *nread); + result = curlx_dyn_addn(&data->state.headerb, buf, blen); if(result) return result; + *pconsumed += blen; if(!k->headerline) { /* check if this looks like a protocol header */ statusline st = checkprotoprefix(data, conn, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); + curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb)); if(st == STATUS_BAD) { - /* this is not the beginning of a protocol first header line */ + /* this is not the beginning of a protocol first header line. + * Cannot be 0.9 if version was detected or connection was reused. */ k->header = FALSE; - k->badheader = HEADER_ALLBAD; streamclose(conn, "bad HTTP: No end-of-message indicator"); - if(!data->set.http09_allowed) { + if((k->httpversion >= 10) || conn->bits.reuse) { + failf(data, "Invalid status line"); + return CURLE_WEIRD_SERVER_REPLY; + } + if(!data->state.http_neg.accept_09) { failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } - break; + leftover_body = TRUE; + goto out; } } - - break; /* read more and try again */ + goto out; /* read more and try again */ } /* decrease the size of the remaining (supposed) header line */ - rest_length = (end_ptr - k->str) + 1; - *nread -= (ssize_t)rest_length; - - k->str = end_ptr + 1; /* move past new line */ - - full_length = k->str - str_start; - - result = Curl_dyn_addn(&data->state.headerb, str_start, full_length); + consumed = (end_ptr - buf) + 1; + result = curlx_dyn_addn(&data->state.headerb, buf, consumed); if(result) return result; + blen -= consumed; + buf += consumed; + *pconsumed += consumed; /**** * We now have a FULL header line in 'headerb'. @@ -3993,514 +4096,128 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, if(!k->headerline) { /* the first read header */ statusline st = checkprotoprefix(data, conn, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); + curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb)); if(st == STATUS_BAD) { streamclose(conn, "bad HTTP: No end-of-message indicator"); - /* this is not the beginning of a protocol first header line */ - if(!data->set.http09_allowed) { + /* this is not the beginning of a protocol first header line. + * Cannot be 0.9 if version was detected or connection was reused. */ + if((k->httpversion >= 10) || conn->bits.reuse) { + failf(data, "Invalid status line"); + return CURLE_WEIRD_SERVER_REPLY; + } + if(!data->state.http_neg.accept_09) { failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } k->header = FALSE; - if(*nread) - /* since there's more, this is a partial bad header */ - k->badheader = HEADER_PARTHEADER; - else { - /* this was all we read so it's all a bad header */ - k->badheader = HEADER_ALLBAD; - *nread = onread; - k->str = ostr; - return CURLE_OK; - } - break; + leftover_body = TRUE; + goto out; } } - /* headers are in network encoding so use 0x0a and 0x0d instead of '\n' - and '\r' */ - headp = Curl_dyn_ptr(&data->state.headerb); - if((0x0a == *headp) || (0x0d == *headp)) { - size_t headerlen; - /* Zero-length header line means end of headers! */ - - if('\r' == *headp) - headp++; /* pass the \r byte */ - if('\n' == *headp) - headp++; /* pass the \n byte */ - - if(100 <= k->httpcode && 199 >= k->httpcode) { - /* "A user agent MAY ignore unexpected 1xx status responses." */ - switch(k->httpcode) { - case 100: - /* - * We have made an HTTP PUT or POST and this is 1.1-lingo - * that tells us that the server is OK with this and ready - * to receive the data. - * However, we'll get more headers now so we must get - * back into the header-parsing state! - */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* if we did wait for this do enable write now! */ - if(k->exp100 > EXP100_SEND_DATA) { - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - } - break; - case 101: - /* Switching Protocols */ - if(k->upgr101 == UPGR101_H2) { - /* Switching to HTTP/2 */ - infof(data, "Received 101, Switching to HTTP/2"); - k->upgr101 = UPGR101_RECEIVED; - - /* we'll get more headers (HTTP/2 response) */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* switch to http2 now. The bytes after response headers - are also processed here, otherwise they are lost. */ - result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, - k->str, *nread); - if(result) - return result; - *nread = 0; - } -#ifdef USE_WEBSOCKETS - else if(k->upgr101 == UPGR101_WS) { - /* verify the response */ - result = Curl_ws_accept(data, k->str, *nread); - if(result) - return result; - k->header = FALSE; /* no more header to parse! */ - if(data->set.connect_only) { - k->keepon &= ~KEEP_RECV; /* read no more content */ - *nread = 0; - } - } -#endif - else { - /* Not switching to another protocol */ - k->header = FALSE; /* no more header to parse! */ - } - break; - default: - /* the status code 1xx indicates a provisional response, so - we'll get another set of headers */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - break; - } - } - else { - k->header = FALSE; /* no more header to parse! */ - - if((k->size == -1) && !k->chunk && !conn->bits.close && - (conn->httpversion == 11) && - !(conn->handler->protocol & CURLPROTO_RTSP) && - data->state.httpreq != HTTPREQ_HEAD) { - /* On HTTP 1.1, when connection is not to get closed, but no - Content-Length nor Transfer-Encoding chunked have been - received, according to RFC2616 section 4.4 point 5, we - assume that the server will close the connection to - signal the end of the document. */ - infof(data, "no chunk, no close, no size. Assume close to " - "signal end"); - streamclose(conn, "HTTP: No end-of-message indicator"); - } - } - - if(!k->header) { - result = Curl_http_size(data); - if(result) - return result; - } - - /* At this point we have some idea about the fate of the connection. - If we are closing the connection it may result auth failure. */ -#if defined(USE_NTLM) - if(conn->bits.close && - (((data->req.httpcode == 401) && - (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || - ((data->req.httpcode == 407) && - (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { - infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); - data->state.authproblem = TRUE; - } -#endif -#if defined(USE_SPNEGO) - if(conn->bits.close && - (((data->req.httpcode == 401) && - (conn->http_negotiate_state == GSS_AUTHRECV)) || - ((data->req.httpcode == 407) && - (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { - infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); - data->state.authproblem = TRUE; - } - if((conn->http_negotiate_state == GSS_AUTHDONE) && - (data->req.httpcode != 401)) { - conn->http_negotiate_state = GSS_AUTHSUCC; - } - if((conn->proxy_negotiate_state == GSS_AUTHDONE) && - (data->req.httpcode != 407)) { - conn->proxy_negotiate_state = GSS_AUTHSUCC; - } -#endif - - /* now, only output this if the header AND body are requested: - */ - writetype = CLIENTWRITE_HEADER | - (data->set.include_header ? CLIENTWRITE_BODY : 0) | - ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); - - headerlen = Curl_dyn_len(&data->state.headerb); - result = Curl_client_write(data, writetype, - Curl_dyn_ptr(&data->state.headerb), - headerlen); - if(result) - return result; - - data->info.header_size += (long)headerlen; - data->req.headerbytecount += (long)headerlen; - - /* - * When all the headers have been parsed, see if we should give - * up and return an error. - */ - if(http_should_fail(data)) { - failf(data, "The requested URL returned error: %d", - k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } - -#ifdef USE_WEBSOCKETS - /* All non-101 HTTP status codes are bad when wanting to upgrade to - websockets */ - if(data->req.upgr101 == UPGR101_WS) { - failf(data, "Refused WebSockets upgrade: %d", k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } -#endif - - - data->req.deductheadercount = - (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; - - /* Curl_http_auth_act() checks what authentication methods - * that are available and decides which one (if any) to - * use. It will set 'newurl' if an auth method was picked. */ - result = Curl_http_auth_act(data); - - if(result) - return result; - - if(k->httpcode >= 300) { - if((!conn->bits.authneg) && !conn->bits.close && - !data->state.rewindbeforesend) { - /* - * General treatment of errors when about to send data. Including : - * "417 Expectation Failed", while waiting for 100-continue. - * - * The check for close above is done simply because of something - * else has already deemed the connection to get closed then - * something else should've considered the big picture and we - * avoid this check. - * - * rewindbeforesend indicates that something has told libcurl to - * continue sending even if it gets discarded - */ - - switch(data->state.httpreq) { - case HTTPREQ_PUT: - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - /* We got an error response. If this happened before the whole - * request body has been sent we stop sending and mark the - * connection for closure after we've read the entire response. - */ - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - if(!k->upload_done) { - if((k->httpcode == 417) && data->state.expect100header) { - /* 417 Expectation Failed - try again without the Expect - header */ - infof(data, "Got 417 while waiting for a 100"); - data->state.disableexpect = TRUE; - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->state.url); - Curl_done_sending(data, k); - } - else if(data->set.http_keep_sending_on_error) { - infof(data, "HTTP error before end of send, keep sending"); - if(k->exp100 > EXP100_SEND_DATA) { - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - } - } - else { - infof(data, "HTTP error before end of send, stop sending"); - streamclose(conn, "Stop sending data before everything sent"); - result = Curl_done_sending(data, k); - if(result) - return result; - k->upload_done = TRUE; - if(data->state.expect100header) - k->exp100 = EXP100_FAILED; - } - } - break; - - default: /* default label present to avoid compiler warnings */ - break; - } - } - - if(data->state.rewindbeforesend && - (conn->writesockfd != CURL_SOCKET_BAD)) { - /* We rewind before next send, continue sending now */ - infof(data, "Keep sending data to get tossed away"); - k->keepon |= KEEP_SEND; - } - } - - if(!k->header) { - /* - * really end-of-headers. - * - * If we requested a "no body", this is a good time to get - * out and return home. - */ - if(data->req.no_body) - *stop_reading = TRUE; -#ifndef CURL_DISABLE_RTSP - else if((conn->handler->protocol & CURLPROTO_RTSP) && - (data->set.rtspreq == RTSPREQ_DESCRIBE) && - (k->size <= -1)) - /* Respect section 4.4 of rfc2326: If the Content-Length header is - absent, a length 0 must be assumed. It will prevent libcurl from - hanging on DESCRIBE request that got refused for whatever - reason */ - *stop_reading = TRUE; -#endif - - /* If max download size is *zero* (nothing) we already have - nothing and can safely return ok now! But for HTTP/2, we'd - like to call http2_handle_stream_close to properly close a - stream. In order to do this, we keep reading until we - close the stream. */ - if(0 == k->maxdownload - && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) - && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) - *stop_reading = TRUE; - - if(*stop_reading) { - /* we make sure that this socket isn't read more now */ - k->keepon &= ~KEEP_RECV; - } - - Curl_debug(data, CURLINFO_HEADER_IN, str_start, headerlen); - break; /* exit header line loop */ - } - - /* We continue reading headers, reset the line-based header */ - Curl_dyn_reset(&data->state.headerb); - continue; + result = http_rw_hd(data, curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb), + buf, blen, &consumed); + /* We are done with this line. We reset because response + * processing might switch to HTTP/2 and that might call us + * directly again. */ + curlx_dyn_reset(&data->state.headerb); + if(consumed) { + blen -= consumed; + buf += consumed; + *pconsumed += consumed; } + if(result) + return result; + } - /* - * Checks for special headers coming up. - */ - - writetype = CLIENTWRITE_HEADER; - if(!k->headerline++) { - /* This is the first header, it MUST be the error code line - or else we consider this to be the body right away! */ - bool fine_statusline = FALSE; - if(conn->handler->protocol & PROTO_FAMILY_HTTP) { - /* - * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 - * - * The response code is always a three-digit number in HTTP as the spec - * says. We allow any three-digit number here, but we cannot make - * guarantees on future behaviors since it isn't within the protocol. - */ - int httpversion = 0; - char *p = headp; - - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "HTTP/", 5)) { - p += 5; - switch(*p) { - case '1': - p++; - if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { - if(ISBLANK(p[2])) { - httpversion = 10 + (p[1] - '0'); - p += 3; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) - fine_statusline = TRUE; - } - } - } - if(!fine_statusline) { - failf(data, "Unsupported HTTP/1 subversion in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - break; - case '2': - case '3': - if(!ISBLANK(p[1])) - break; - httpversion = (*p - '0') * 10; - p += 2; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(!ISSPACE(*p)) - break; - fine_statusline = TRUE; - } - break; - default: /* unsupported */ - failf(data, "Unsupported HTTP version in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - } + /* We might have reached the end of the header part here, but + there might be a non-header part left in the end of the read + buffer. */ +out: + if(!k->header && !leftover_body) { + curlx_dyn_free(&data->state.headerb); + } + return CURLE_OK; +} - if(fine_statusline) { - if(k->httpcode < 100) { - failf(data, "Unsupported response code in HTTP response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - switch(httpversion) { - case 10: - case 11: -#ifdef USE_HTTP2 - case 20: -#endif -#ifdef ENABLE_QUIC - case 30: -#endif - conn->httpversion = (unsigned char)httpversion; - break; - default: - failf(data, "Unsupported HTTP version (%u.%d) in response", - httpversion/10, httpversion%10); - return CURLE_UNSUPPORTED_PROTOCOL; - } +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + char tmp = 0; - if(k->upgr101 == UPGR101_RECEIVED) { - /* supposedly upgraded to http2 now */ - if(conn->httpversion != 20) - infof(data, "Lying server, not serving HTTP/2"); - } - if(conn->httpversion < 20) { - conn->bundle->multiuse = BUNDLE_NO_MULTIUSE; - } - } - else { - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - statusline check = - checkhttpprefix(data, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); - if(check == STATUS_DONE) { - fine_statusline = TRUE; - k->httpcode = 200; - conn->httpversion = 10; - } - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - char *p = headp; - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "RTSP/", 5)) { - p += 5; - if(ISDIGIT(*p)) { - p++; - if((p[0] == '.') && ISDIGIT(p[1])) { - if(ISBLANK(p[2])) { - p += 3; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) { - fine_statusline = TRUE; - conn->httpversion = 11; /* RTSP acts like HTTP 1.1 */ - } - } - } - } - } - if(!fine_statusline) - return CURLE_WEIRD_SERVER_REPLY; - } - } + result = http_rw_hd(data, hd, hdlen, &tmp, 0, &consumed); + if(!result && is_eos) { + result = Curl_client_write(data, (CLIENTWRITE_BODY|CLIENTWRITE_EOS), + &tmp, 0); + } + return result; +} - if(fine_statusline) { - result = Curl_http_statusline(data, conn); - if(result) - return result; - writetype |= CLIENTWRITE_STATUS; - } - else { - k->header = FALSE; /* this is not a header line */ - break; +/* + * HTTP protocol `write_resp` implementation. Will parse headers + * when not done yet and otherwise return without consuming data. + */ +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) +{ + if(!data->req.header) { + *pconsumed = 0; + return CURLE_OK; + } + else { + CURLcode result; + + result = http_parse_headers(data, buf, blen, pconsumed); + if(!result && !data->req.header) { + if(!data->req.no_body && curlx_dyn_len(&data->state.headerb)) { + /* leftover from parsing something that turned out not + * to be a header, only happens if we allow for + * HTTP/0.9 like responses */ + result = Curl_client_write(data, CLIENTWRITE_BODY, + curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb)); } + curlx_dyn_free(&data->state.headerb); } + return result; + } +} - result = verify_header(data); - if(result) - return result; - - result = Curl_http_header(data, conn, headp); - if(result) - return result; - - /* - * End of header-checks. Write them to the client. - */ - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - if(k->httpcode/100 == 1) - writetype |= CLIENTWRITE_1XX; - - Curl_debug(data, CURLINFO_HEADER_IN, headp, - Curl_dyn_len(&data->state.headerb)); - - result = Curl_client_write(data, writetype, headp, - Curl_dyn_len(&data->state.headerb)); - if(result) - return result; +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + int flags; - data->info.header_size += Curl_dyn_len(&data->state.headerb); - data->req.headerbytecount += Curl_dyn_len(&data->state.headerb); + result = Curl_http_write_resp_hds(data, buf, blen, &consumed); + if(result || data->req.done) + goto out; - Curl_dyn_reset(&data->state.headerb); + DEBUGASSERT(consumed <= blen); + blen -= consumed; + buf += consumed; + /* either all was consumed in header parsing, or we have data left + * and are done with headers, e.g. it is BODY data */ + DEBUGASSERT(!blen || !data->req.header); + if(!data->req.header && (blen || is_eos)) { + /* BODY data after header been parsed, write and consume */ + flags = CLIENTWRITE_BODY; + if(is_eos) + flags |= CLIENTWRITE_EOS; + result = Curl_client_write(data, flags, buf, blen); } - while(*k->str); /* header line within buffer */ - - /* We might have reached the end of the header part here, but - there might be a non-header part left in the end of the read - buffer. */ - - return CURLE_OK; +out: + return result; } - /* Decode HTTP status code string. */ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) { @@ -4522,21 +4239,10 @@ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) } result = CURLE_OK; out: - *pstatus = result? -1 : status; + *pstatus = result ? -1 : status; return result; } -/* simple implementation of strndup(), which isn't portable */ -static char *my_strndup(const char *ptr, size_t len) -{ - char *copy = malloc(len + 1); - if(!copy) - return NULL; - memcpy(copy, ptr, len); - copy[len] = '\0'; - return copy; -} - CURLcode Curl_http_req_make(struct httpreq **preq, const char *method, size_t m_len, const char *scheme, size_t s_len, @@ -4546,37 +4252,35 @@ CURLcode Curl_http_req_make(struct httpreq **preq, struct httpreq *req; CURLcode result = CURLE_OUT_OF_MEMORY; - DEBUGASSERT(method); - if(m_len + 1 >= sizeof(req->method)) - return CURLE_BAD_FUNCTION_ARGUMENT; + DEBUGASSERT(method && m_len); - req = calloc(1, sizeof(*req)); + req = calloc(1, sizeof(*req) + m_len); if(!req) goto out; memcpy(req->method, method, m_len); if(scheme) { - req->scheme = my_strndup(scheme, s_len); + req->scheme = Curl_memdup0(scheme, s_len); if(!req->scheme) goto out; } if(authority) { - req->authority = my_strndup(authority, a_len); + req->authority = Curl_memdup0(authority, a_len); if(!req->authority) goto out; } if(path) { - req->path = my_strndup(path, p_len); + req->path = Curl_memdup0(path, p_len); if(!req->path) goto out; } - Curl_dynhds_init(&req->headers, 0, DYN_H2_HEADERS); - Curl_dynhds_init(&req->trailers, 0, DYN_H2_TRAILERS); + Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST); result = CURLE_OK; out: if(result && req) Curl_http_req_free(req); - *preq = result? NULL : req; + *preq = result ? NULL : req; return result; } @@ -4588,7 +4292,7 @@ static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url) CURLcode result = CURLE_URL_MALFORMAT; user = pass = host = port = NULL; - Curl_dyn_init(&buf, DYN_HTTP_REQUEST); + curlx_dyn_init(&buf, DYN_HTTP_REQUEST); uc = curl_url_get(url, CURLUPART_HOST, &host, 0); if(uc && uc != CURLUE_NO_HOST) @@ -4612,27 +4316,27 @@ static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url) } if(user) { - result = Curl_dyn_add(&buf, user); + result = curlx_dyn_add(&buf, user); if(result) goto out; if(pass) { - result = Curl_dyn_addf(&buf, ":%s", pass); + result = curlx_dyn_addf(&buf, ":%s", pass); if(result) goto out; } - result = Curl_dyn_add(&buf, "@"); + result = curlx_dyn_add(&buf, "@"); if(result) goto out; } - result = Curl_dyn_add(&buf, host); + result = curlx_dyn_add(&buf, host); if(result) goto out; if(port) { - result = Curl_dyn_addf(&buf, ":%s", port); + result = curlx_dyn_addf(&buf, ":%s", port); if(result) goto out; } - req->authority = strdup(Curl_dyn_ptr(&buf)); + req->authority = strdup(curlx_dyn_ptr(&buf)); if(!req->authority) goto out; result = CURLE_OK; @@ -4642,7 +4346,7 @@ static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url) free(pass); free(host); free(port); - Curl_dyn_free(&buf); + curlx_dyn_free(&buf); return result; } @@ -4654,7 +4358,7 @@ static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url) CURLcode result = CURLE_URL_MALFORMAT; path = query = NULL; - Curl_dyn_init(&buf, DYN_HTTP_REQUEST); + curlx_dyn_init(&buf, DYN_HTTP_REQUEST); uc = curl_url_get(url, CURLUPART_PATH, &path, CURLU_PATH_AS_IS); if(uc) @@ -4672,16 +4376,16 @@ static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url) } else { if(path) { - result = Curl_dyn_add(&buf, path); + result = curlx_dyn_add(&buf, path); if(result) goto out; } if(query) { - result = Curl_dyn_addf(&buf, "?%s", query); + result = curlx_dyn_addf(&buf, "?%s", query); if(result) goto out; } - req->path = strdup(Curl_dyn_ptr(&buf)); + req->path = strdup(curlx_dyn_ptr(&buf)); if(!req->path) goto out; } @@ -4690,7 +4394,7 @@ static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url) out: free(path); free(query); - Curl_dyn_free(&buf); + curlx_dyn_free(&buf); return result; } @@ -4702,11 +4406,9 @@ CURLcode Curl_http_req_make2(struct httpreq **preq, CURLcode result = CURLE_OUT_OF_MEMORY; CURLUcode uc; - DEBUGASSERT(method); - if(m_len + 1 >= sizeof(req->method)) - return CURLE_BAD_FUNCTION_ARGUMENT; + DEBUGASSERT(method && m_len); - req = calloc(1, sizeof(*req)); + req = calloc(1, sizeof(*req) + m_len); if(!req) goto out; memcpy(req->method, method, m_len); @@ -4727,14 +4429,14 @@ CURLcode Curl_http_req_make2(struct httpreq **preq, if(result) goto out; - Curl_dynhds_init(&req->headers, 0, DYN_H2_HEADERS); - Curl_dynhds_init(&req->trailers, 0, DYN_H2_TRAILERS); + Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST); result = CURLE_OK; out: if(result && req) Curl_http_req_free(req); - *preq = result? NULL : req; + *preq = result ? NULL : req; return result; } @@ -4755,6 +4457,7 @@ struct name_const { size_t namelen; }; +/* keep them sorted by length! */ static struct name_const H2_NON_FIELD[] = { { STRCONST("Host") }, { STRCONST("Upgrade") }, @@ -4764,15 +4467,44 @@ static struct name_const H2_NON_FIELD[] = { { STRCONST("Transfer-Encoding") }, }; -static bool h2_non_field(const char *name, size_t namelen) +static bool h2_permissible_field(struct dynhds_entry *e) { size_t i; - for(i = 0; i < sizeof(H2_NON_FIELD)/sizeof(H2_NON_FIELD[0]); ++i) { - if(namelen < H2_NON_FIELD[i].namelen) + for(i = 0; i < CURL_ARRAYSIZE(H2_NON_FIELD); ++i) { + if(e->namelen < H2_NON_FIELD[i].namelen) + return TRUE; + if(e->namelen == H2_NON_FIELD[i].namelen && + strcasecompare(H2_NON_FIELD[i].name, e->name)) + return FALSE; + } + return TRUE; +} + +static bool http_TE_has_token(const char *fvalue, const char *token) +{ + while(*fvalue) { + struct Curl_str name; + + /* skip to first token */ + while(ISBLANK(*fvalue) || *fvalue == ',') + fvalue++; + if(curlx_str_cspn(&fvalue, &name, " \t\r;,")) return FALSE; - if(namelen == H2_NON_FIELD[i].namelen && - strcasecompare(H2_NON_FIELD[i].name, name)) + if(curlx_str_casecompare(&name, token)) return TRUE; + + /* skip any remainder after token, e.g. parameters with quoted strings */ + while(*fvalue && *fvalue != ',') { + if(*fvalue == '"') { + struct Curl_str qw; + /* if we do not cleanly find a quoted word here, the header value + * does not follow HTTP syntax and we reject */ + if(curlx_str_quotedword(&fvalue, &qw, CURL_MAX_HTTP_HEADER)) + return FALSE; + } + else + fvalue++; + } } return FALSE; } @@ -4795,13 +4527,12 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, scheme = Curl_checkheaders(data, STRCONST(HTTP_PSEUDO_SCHEME)); if(scheme) { scheme += sizeof(HTTP_PSEUDO_SCHEME); - while(*scheme && ISBLANK(*scheme)) - scheme++; + curlx_str_passblanks(&scheme); infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme); } else { - scheme = (data->conn && data->conn->handler->flags & PROTOPT_SSL)? - "https" : "http"; + scheme = Curl_conn_is_ssl(data->conn, FIRSTSOCKET) ? + "https" : "http"; } } @@ -4832,7 +4563,14 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, } for(i = 0; !result && i < Curl_dynhds_count(&req->headers); ++i) { e = Curl_dynhds_getn(&req->headers, i); - if(!h2_non_field(e->name, e->namelen)) { + /* "TE" is special in that it is only permissible when it + * has only value "trailers". RFC 9113 ch. 8.2.2 */ + if(e->namelen == 2 && strcasecompare("TE", e->name)) { + if(http_TE_has_token(e->value, "trailers")) + result = Curl_dynhds_add(h2_headers, e->name, e->namelen, + "trailers", sizeof("trailers") - 1); + } + else if(h2_permissible_field(e)) { result = Curl_dynhds_add(h2_headers, e->name, e->namelen, e->value, e->valuelen); } @@ -4858,14 +4596,14 @@ CURLcode Curl_http_resp_make(struct http_resp **presp, if(!resp->description) goto out; } - Curl_dynhds_init(&resp->headers, 0, DYN_H2_HEADERS); - Curl_dynhds_init(&resp->trailers, 0, DYN_H2_TRAILERS); + Curl_dynhds_init(&resp->headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&resp->trailers, 0, DYN_HTTP_REQUEST); result = CURLE_OK; out: if(result && resp) Curl_http_resp_free(resp); - *presp = result? NULL : resp; + *presp = result ? NULL : resp; return result; } @@ -4881,4 +4619,152 @@ void Curl_http_resp_free(struct http_resp *resp) } } +struct cr_exp100_ctx { + struct Curl_creader super; + struct curltime start; /* time started waiting */ + enum expect100 state; +}; + +/* Expect: 100-continue client reader, blocking uploads */ + +static void http_exp100_continue(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + if(ctx->state > EXP100_SEND_DATA) { + ctx->state = EXP100_SEND_DATA; + data->req.keepon |= KEEP_SEND; + data->req.keepon &= ~KEEP_SEND_TIMED; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + } +} + +static CURLcode cr_exp100_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + timediff_t ms; + + switch(ctx->state) { + case EXP100_SENDING_REQUEST: + if(!Curl_req_sendbuf_empty(data)) { + /* The initial request data has not been fully sent yet. Do + * not start the timer yet. */ + DEBUGF(infof(data, "cr_exp100_read, request not full sent yet")); + *nread = 0; + *eos = FALSE; + return CURLE_OK; + } + /* We are now waiting for a reply from the server or + * a timeout on our side IFF the request has been fully sent. */ + DEBUGF(infof(data, "cr_exp100_read, start AWAITING_CONTINUE, " + "timeout %ldms", data->set.expect_100_timeout)); + ctx->state = EXP100_AWAITING_CONTINUE; + ctx->start = curlx_now(); + Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); + data->req.keepon &= ~KEEP_SEND; + data->req.keepon |= KEEP_SEND_TIMED; + *nread = 0; + *eos = FALSE; + return CURLE_OK; + case EXP100_FAILED: + DEBUGF(infof(data, "cr_exp100_read, expectation failed, error")); + *nread = 0; + *eos = FALSE; + return CURLE_READ_ERROR; + case EXP100_AWAITING_CONTINUE: + ms = curlx_timediff(curlx_now(), ctx->start); + if(ms < data->set.expect_100_timeout) { + DEBUGF(infof(data, "cr_exp100_read, AWAITING_CONTINUE, not expired")); + data->req.keepon &= ~KEEP_SEND; + data->req.keepon |= KEEP_SEND_TIMED; + *nread = 0; + *eos = FALSE; + return CURLE_OK; + } + /* we have waited long enough, continue anyway */ + http_exp100_continue(data, reader); + infof(data, "Done waiting for 100-continue"); + FALLTHROUGH(); + default: + DEBUGF(infof(data, "cr_exp100_read, pass through")); + return Curl_creader_read(data, reader->next, buf, blen, nread, eos); + } +} + +static void cr_exp100_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + ctx->state = premature ? EXP100_FAILED : EXP100_SEND_DATA; + data->req.keepon &= ~KEEP_SEND_TIMED; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); +} + +static const struct Curl_crtype cr_exp100 = { + "cr-exp100", + Curl_creader_def_init, + cr_exp100_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + Curl_creader_def_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + cr_exp100_done, + sizeof(struct cr_exp100_ctx) +}; + +static CURLcode http_exp100_add_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &cr_exp100, + CURL_CR_PROTOCOL); + if(!result) + result = Curl_creader_add(data, reader); + if(!result) { + struct cr_exp100_ctx *ctx = reader->ctx; + ctx->state = EXP100_SENDING_REQUEST; + } + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + +static void http_exp100_got100(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) + http_exp100_continue(data, r); +} + +static bool http_exp100_is_waiting(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) { + struct cr_exp100_ctx *ctx = r->ctx; + return ctx->state == EXP100_AWAITING_CONTINUE; + } + return FALSE; +} + +static void http_exp100_send_anyway(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) + http_exp100_continue(data, r); +} + +static bool http_exp100_is_selected(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + return !!r; +} + #endif /* CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h index df3b4e38b8a..8876f380447 100644 --- a/Utilities/cmcurl/lib/http.h +++ b/Utilities/cmcurl/lib/http.h @@ -42,9 +42,27 @@ typedef enum { HTTPREQ_HEAD } Curl_HttpReq; + +/* When redirecting transfers. */ +typedef enum { + FOLLOW_NONE, /* not used within the function, just a placeholder to + allow initing to this */ + FOLLOW_FAKE, /* only records stuff, not actually following */ + FOLLOW_RETRY, /* set if this is a request retry as opposed to a real + redirect following */ + FOLLOW_REDIR /* a full true redirect */ +} followtype; + +#define CURL_HTTP_V1x (1 << 0) +#define CURL_HTTP_V2x (1 << 1) +#define CURL_HTTP_V3x (1 << 2) +/* bitmask of CURL_HTTP_V* values */ +typedef unsigned char http_majors; + + #ifndef CURL_DISABLE_HTTP -#if defined(ENABLE_QUIC) +#if defined(USE_HTTP3) #include #endif @@ -54,15 +72,23 @@ extern const struct Curl_handler Curl_handler_http; extern const struct Curl_handler Curl_handler_https; #endif -#ifdef USE_WEBSOCKETS -extern const struct Curl_handler Curl_handler_ws; +struct dynhds; -#ifdef USE_SSL -extern const struct Curl_handler Curl_handler_wss; -#endif -#endif /* websockets */ +struct http_negotiation { + unsigned char rcvd_min; /* minimum version seen in responses, 09, 10, 11 */ + http_majors wanted; /* wanted major versions when talking to server */ + http_majors allowed; /* allowed major versions when talking to server */ + BIT(h2_upgrade); /* Do HTTP Upgrade from 1.1 to 2 */ + BIT(h2_prior_knowledge); /* Directly do HTTP/2 without ALPN/SSL */ + BIT(accept_09); /* Accept an HTTP/0.9 response */ + BIT(only_10); /* When using major version 1x, use only 1.0 */ +}; -struct dynhds; +void Curl_http_neg_init(struct Curl_easy *data, struct http_negotiation *neg); + +CURLcode Curl_bump_headersize(struct Curl_easy *data, + size_t delta, + bool connect_only); /* Header specific functions */ bool Curl_compareheader(const char *headerline, /* line to check */ @@ -77,86 +103,46 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, const struct connectdata *conn, const char *thisheader, const size_t thislen); -struct HTTP; /* see below */ -CURLcode Curl_buffer_send(struct dynbuf *in, - struct Curl_easy *data, - struct HTTP *http, - curl_off_t *bytes_written, - curl_off_t included_body_bytes, - int socketindex); - -CURLcode Curl_add_timecondition(struct Curl_easy *data, -#ifndef USE_HYPER - struct dynbuf *req -#else - void *headers -#endif - ); -CURLcode Curl_add_custom_headers(struct Curl_easy *data, - bool is_connect, -#ifndef USE_HYPER - struct dynbuf *req -#else - void *headers -#endif - ); -CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, - bool is_connect, - struct dynhds *hds); -CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, - struct dynbuf *buf, - struct Curl_easy *handle); +CURLcode Curl_add_timecondition(struct Curl_easy *data, struct dynbuf *req); +CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, + int httpversion, struct dynbuf *req); +CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect, + struct dynhds *hds); void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, const char **method, Curl_HttpReq *); -CURLcode Curl_http_useragent(struct Curl_easy *data); -CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn); -CURLcode Curl_http_target(struct Curl_easy *data, struct connectdata *conn, - struct dynbuf *req); -CURLcode Curl_http_statusline(struct Curl_easy *data, - struct connectdata *conn); -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *headp); -CURLcode Curl_transferencode(struct Curl_easy *data); -CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, - Curl_HttpReq httpreq, - const char **teep); -CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, - struct dynbuf *r, Curl_HttpReq httpreq); -bool Curl_use_http_1_1plus(const struct Curl_easy *data, - const struct connectdata *conn); -#ifndef CURL_DISABLE_COOKIES -CURLcode Curl_http_cookies(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *r); -#else -#define Curl_http_cookies(a,b,c) CURLE_OK -#endif -CURLcode Curl_http_resume(struct Curl_easy *data, - struct connectdata *conn, - Curl_HttpReq httpreq); -CURLcode Curl_http_range(struct Curl_easy *data, - Curl_HttpReq httpreq); -CURLcode Curl_http_firstwrite(struct Curl_easy *data, - struct connectdata *conn, - bool *done); /* protocol-specific functions set up to be called by the main engine */ +CURLcode Curl_http_setup_conn(struct Curl_easy *data, + struct connectdata *conn); CURLcode Curl_http(struct Curl_easy *data, bool *done); CURLcode Curl_http_done(struct Curl_easy *data, CURLcode, bool premature); CURLcode Curl_http_connect(struct Curl_easy *data, bool *done); +int Curl_http_getsock_do(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos); +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos); /* These functions are in http.c */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth); + CURLcode Curl_http_auth_act(struct Curl_easy *data); +/* follow a redirect or not */ +CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, + followtype type); + /* If only the PICKNONE bit is set, there has been a round-trip and we selected to use no auth at all. Ie, we actively select no auth, as opposed to not having one selected. The other CURLAUTH_* defines are present in the public curl/curl.h header. */ -#define CURLAUTH_PICKNONE (1<<30) /* don't use auth */ +#define CURLAUTH_PICKNONE (1<<30) /* do not use auth */ /* MAX_INITIAL_POST_SIZE indicates the number of bytes that will make the POST data get included in the initial data chunk sent to the server. If the @@ -183,50 +169,25 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data); #define EXPECT_100_THRESHOLD (1024*1024) #endif +/* MAX_HTTP_RESP_HEADER_SIZE is the maximum size of all response headers + combined that libcurl allows for a single HTTP response, any HTTP + version. This count includes CONNECT response headers. */ +#define MAX_HTTP_RESP_HEADER_SIZE (300*1024) + +/* MAX_HTTP_RESP_HEADER_COUNT is the maximum number of response headers that + libcurl allows for a single HTTP response, including CONNECT and + redirects. */ +#define MAX_HTTP_RESP_HEADER_COUNT 5000 + #endif /* CURL_DISABLE_HTTP */ /**************************************************************************** * HTTP unique setup ***************************************************************************/ -struct HTTP { - curl_mimepart *sendit; - curl_off_t postsize; /* off_t to handle large file sizes */ - const char *postdata; - - const char *p_pragma; /* Pragma: string */ - - /* For FORM posting */ - curl_mimepart form; - - struct back { - curl_read_callback fread_func; /* backup storage for fread pointer */ - void *fread_in; /* backup storage for fread_in pointer */ - const char *postdata; - curl_off_t postsize; - struct Curl_easy *data; - } backup; - - enum { - HTTPSEND_NADA, /* init */ - HTTPSEND_REQUEST, /* sending a request */ - HTTPSEND_BODY /* sending body */ - } sending; -#ifndef CURL_DISABLE_HTTP - void *h2_ctx; /* HTTP/2 implementation context */ - void *h3_ctx; /* HTTP/3 implementation context */ - struct dynbuf send_buffer; /* used if the request couldn't be sent in one - chunk, points to an allocated send_buffer - struct */ -#endif -}; - -CURLcode Curl_http_size(struct Curl_easy *data); - -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading); +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed); /** * Curl_http_output_auth() setups the authentication headers for the @@ -261,16 +222,16 @@ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len); * All about a core HTTP request, excluding body and trailers */ struct httpreq { - char method[12]; + struct dynhds headers; + struct dynhds trailers; char *scheme; char *authority; char *path; - struct dynhds headers; - struct dynhds trailers; + char method[1]; }; /** - * Create a HTTP request struct. + * Create an HTTP request struct. */ CURLcode Curl_http_req_make(struct httpreq **preq, const char *method, size_t m_len, @@ -292,7 +253,7 @@ void Curl_http_req_free(struct httpreq *req); /** * Create the list of HTTP/2 headers which represent the request, - * using HTTP/2 pseudo headers preceeding the `req->headers`. + * using HTTP/2 pseudo headers preceding the `req->headers`. * * Applies the following transformations: * - if `authority` is set, any "Host" header is removed. @@ -320,7 +281,7 @@ struct http_resp { }; /** - * Create a HTTP response struct. + * Create an HTTP response struct. */ CURLcode Curl_http_resp_make(struct http_resp **presp, int status, diff --git a/Utilities/cmcurl/lib/http1.c b/Utilities/cmcurl/lib/http1.c index 46fe85509f7..537e6db2ff9 100644 --- a/Utilities/cmcurl/lib/http1.c +++ b/Utilities/cmcurl/lib/http1.c @@ -38,124 +38,98 @@ #include "memdebug.h" -#define MAX_URL_LEN (4*1024) +#define H1_MAX_URL_LEN (8*1024) void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len) { memset(parser, 0, sizeof(*parser)); parser->max_line_len = max_line_len; - Curl_bufq_init(&parser->scratch, max_line_len, 1); + curlx_dyn_init(&parser->scratch, max_line_len); } void Curl_h1_req_parse_free(struct h1_req_parser *parser) { if(parser) { Curl_http_req_free(parser->req); - Curl_bufq_free(&parser->scratch); + curlx_dyn_free(&parser->scratch); parser->req = NULL; parser->done = FALSE; } } +static CURLcode trim_line(struct h1_req_parser *parser, int options) +{ + DEBUGASSERT(parser->line); + if(parser->line_len) { + if(parser->line[parser->line_len - 1] == '\n') + --parser->line_len; + if(parser->line_len) { + if(parser->line[parser->line_len - 1] == '\r') + --parser->line_len; + else if(options & H1_PARSE_OPT_STRICT) + return CURLE_URL_MALFORMAT; + } + else if(options & H1_PARSE_OPT_STRICT) + return CURLE_URL_MALFORMAT; + } + else if(options & H1_PARSE_OPT_STRICT) + return CURLE_URL_MALFORMAT; + + if(parser->line_len > parser->max_line_len) { + return CURLE_URL_MALFORMAT; + } + return CURLE_OK; +} + static ssize_t detect_line(struct h1_req_parser *parser, - const char *buf, const size_t buflen, int options, + const char *buf, const size_t buflen, CURLcode *err) { const char *line_end; - size_t len; DEBUGASSERT(!parser->line); line_end = memchr(buf, '\n', buflen); if(!line_end) { - *err = (buflen > parser->max_line_len)? CURLE_URL_MALFORMAT : CURLE_AGAIN; - return -1; - } - len = line_end - buf + 1; - if(len > parser->max_line_len) { - *err = CURLE_URL_MALFORMAT; + *err = CURLE_AGAIN; return -1; } - - if(options & H1_PARSE_OPT_STRICT) { - if((len == 1) || (buf[len - 2] != '\r')) { - *err = CURLE_URL_MALFORMAT; - return -1; - } - parser->line = buf; - parser->line_len = len - 2; - } - else { - parser->line = buf; - parser->line_len = len - (((len == 1) || (buf[len - 2] != '\r'))? 1 : 2); - } + parser->line = buf; + parser->line_len = line_end - buf + 1; *err = CURLE_OK; - return (ssize_t)len; + return (ssize_t)parser->line_len; } static ssize_t next_line(struct h1_req_parser *parser, const char *buf, const size_t buflen, int options, CURLcode *err) { - ssize_t nread = 0, n; + ssize_t nread = 0; if(parser->line) { - if(parser->scratch_skip) { - /* last line was from scratch. Remove it now, since we are done - * with it and look for the next one. */ - Curl_bufq_skip_and_shift(&parser->scratch, parser->scratch_skip); - parser->scratch_skip = 0; - } parser->line = NULL; parser->line_len = 0; + curlx_dyn_reset(&parser->scratch); } - if(Curl_bufq_is_empty(&parser->scratch)) { - nread = detect_line(parser, buf, buflen, options, err); - if(nread < 0) { - if(*err != CURLE_AGAIN) + nread = detect_line(parser, buf, buflen, err); + if(nread >= 0) { + if(curlx_dyn_len(&parser->scratch)) { + /* append detected line to scratch to have the complete line */ + *err = curlx_dyn_addn(&parser->scratch, parser->line, parser->line_len); + if(*err) return -1; - /* not a complete line, add to scratch for later revisit */ - nread = Curl_bufq_write(&parser->scratch, - (const unsigned char *)buf, buflen, err); - return nread; + parser->line = curlx_dyn_ptr(&parser->scratch); + parser->line_len = curlx_dyn_len(&parser->scratch); } - /* found one */ + *err = trim_line(parser, options); + if(*err) + return -1; } - else { - const char *sbuf; - size_t sbuflen; - - /* scratch contains bytes from last attempt, add more to it */ - if(buflen) { - const char *line_end; - size_t add_len; - ssize_t pos; - - line_end = memchr(buf, '\n', buflen); - pos = line_end? (line_end - buf + 1) : -1; - add_len = (pos >= 0)? (size_t)pos : buflen; - nread = Curl_bufq_write(&parser->scratch, - (const unsigned char *)buf, add_len, err); - if(nread < 0) { - /* Unable to add anything to scratch is an error, since we should - * have seen a line there then before. */ - if(*err == CURLE_AGAIN) - *err = CURLE_URL_MALFORMAT; - return -1; - } - } - - if(Curl_bufq_peek(&parser->scratch, - (const unsigned char **)&sbuf, &sbuflen)) { - n = detect_line(parser, sbuf, sbuflen, options, err); - if(n < 0 && *err != CURLE_AGAIN) - return -1; /* real error */ - parser->scratch_skip = (size_t)n; - } - else { - /* we SHOULD be able to peek at scratch data */ - DEBUGASSERT(0); - } + else if(*err == CURLE_AGAIN) { + /* no line end in `buf`, add it to our scratch */ + *err = curlx_dyn_addn(&parser->scratch, (const unsigned char *)buf, + buflen); + nread = (*err) ? -1 : (ssize_t)buflen; } return nread; } @@ -190,12 +164,10 @@ static CURLcode start_req(struct h1_req_parser *parser, break; } } - /* no SPACE found or empty TARGET or empy HTTP_VERSION */ + /* no SPACE found or empty TARGET or empty HTTP_VERSION */ if(!target_len || !hv_len) goto out; - /* TODO: we do not check HTTP_VERSION for conformity, should - + do that when STRICT option is supplied. */ (void)hv; /* The TARGET can be (rfc 9112, ch. 3.2): @@ -231,20 +203,20 @@ static CURLcode start_req(struct h1_req_parser *parser, else { /* origin-form OR absolute-form */ CURLUcode uc; - char tmp[MAX_URL_LEN]; + char tmp[H1_MAX_URL_LEN]; /* default, unless we see an absolute URL */ path = target; path_len = target_len; - /* URL parser wants 0-termination */ + /* URL parser wants null-termination */ if(target_len >= sizeof(tmp)) goto out; memcpy(tmp, target, target_len); tmp[target_len] = '\0'; /* See if treating TARGET as an absolute URL makes sense */ if(Curl_is_absolute_url(tmp, NULL, 0, FALSE)) { - int url_options; + unsigned int url_options; url = curl_url(); if(!url) { @@ -328,7 +300,7 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, goto out; } parser->done = TRUE; - Curl_bufq_free(&parser->scratch); + curlx_dyn_reset(&parser->scratch); /* last chance adjustments */ } else { @@ -345,5 +317,29 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, return nread; } +CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, + struct dynbuf *dbuf) +{ + CURLcode result; + + result = curlx_dyn_addf(dbuf, "%s %s%s%s%s HTTP/1.%d\r\n", + req->method, + req->scheme ? req->scheme : "", + req->scheme ? "://" : "", + req->authority ? req->authority : "", + req->path ? req->path : "", + http_minor); + if(result) + goto out; + + result = Curl_dynhds_h1_dprint(&req->headers, dbuf); + if(result) + goto out; + + result = curlx_dyn_addn(dbuf, STRCONST("\r\n")); + +out: + return result; +} #endif /* !CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/http1.h b/Utilities/cmcurl/lib/http1.h index 93111efa1b9..b38b32f5911 100644 --- a/Utilities/cmcurl/lib/http1.h +++ b/Utilities/cmcurl/lib/http1.h @@ -33,16 +33,16 @@ #define H1_PARSE_OPT_NONE (0) #define H1_PARSE_OPT_STRICT (1 << 0) -#define H1_PARSE_DEFAULT_MAX_LINE_LEN (8 * 1024) +#define H1_PARSE_DEFAULT_MAX_LINE_LEN DYN_HTTP_REQUEST struct h1_req_parser { struct httpreq *req; - struct bufq scratch; + struct dynbuf scratch; size_t scratch_skip; const char *line; size_t max_line_len; size_t line_len; - bool done; + BIT(done); }; void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len); @@ -56,6 +56,8 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, CURLcode Curl_h1_req_dprint(const struct httpreq *req, struct dynbuf *dbuf); +CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, + struct dynbuf *dbuf); #endif /* !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_HTTP1_H */ diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c index 191d8cd3359..dd82aa963ec 100644 --- a/Utilities/cmcurl/lib/http2.c +++ b/Utilities/cmcurl/lib/http2.c @@ -29,22 +29,24 @@ #include #include "urldata.h" #include "bufq.h" +#include "uint-hash.h" #include "http1.h" #include "http2.h" #include "http.h" #include "sendf.h" #include "select.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "strcase.h" #include "multiif.h" #include "url.h" #include "urlapi-int.h" #include "cfilters.h" #include "connect.h" -#include "strtoofft.h" +#include "rand.h" #include "strdup.h" +#include "curlx/strparse.h" #include "transfer.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "headers.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -67,38 +69,44 @@ /* buffer dimensioning: * use 16K as chunk size, as that fits H2 DATA frames well */ #define H2_CHUNK_SIZE (16 * 1024) -/* this is how much we want "in flight" for a stream */ -#define H2_STREAM_WINDOW_SIZE (10 * 1024 * 1024) -/* on receving from TLS, we prep for holding a full stream window */ -#define H2_NW_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +/* connection window size */ +#define H2_CONN_WINDOW_SIZE (10 * 1024 * 1024) +/* on receiving from TLS, we prep for holding a full stream window */ +#define H2_NW_RECV_CHUNKS (H2_CONN_WINDOW_SIZE / H2_CHUNK_SIZE) /* on send into TLS, we just want to accumulate small frames */ #define H2_NW_SEND_CHUNKS 1 -/* stream recv/send chunks are a result of window / chunk sizes */ -#define H2_STREAM_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +/* this is how much we want "in flight" for a stream, unthrottled */ +#define H2_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) +/* this is how much we want "in flight" for a stream, initially, IFF + * nghttp2 allows us to tweak the local window size. */ +#if NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE +#define H2_STREAM_WINDOW_SIZE_INITIAL (64 * 1024) +#else +#define H2_STREAM_WINDOW_SIZE_INITIAL H2_STREAM_WINDOW_SIZE_MAX +#endif /* keep smaller stream upload buffer (default h2 window size) to have * our progress bars and "upload done" reporting closer to reality */ #define H2_STREAM_SEND_CHUNKS ((64 * 1024) / H2_CHUNK_SIZE) /* spare chunks we keep for a full window */ -#define H2_STREAM_POOL_SPARES (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +#define H2_STREAM_POOL_SPARES (H2_CONN_WINDOW_SIZE / H2_CHUNK_SIZE) -/* We need to accommodate the max number of streams with their window - * sizes on the overall connection. Streams might become PAUSED which - * will block their received QUOTA in the connection window. And if we - * run out of space, the server is blocked from sending us any data. - * See #10988 for an issue with this. */ -#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE) +/* We need to accommodate the max number of streams with their window sizes on + * the overall connection. Streams might become PAUSED which will block their + * received QUOTA in the connection window. If we run out of space, the server + * is blocked from sending us any data. See #10988 for an issue with this. */ +#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE_MAX) #define H2_SETTINGS_IV_LEN 3 #define H2_BINSETTINGS_LEN 80 -static int populate_settings(nghttp2_settings_entry *iv, - struct Curl_easy *data) +static size_t populate_settings(nghttp2_settings_entry *iv, + struct Curl_easy *data) { iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].value = Curl_multi_max_concurrent_streams(data->multi); iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; - iv[1].value = H2_STREAM_WINDOW_SIZE; + iv[1].value = H2_STREAM_WINDOW_SIZE_INITIAL; iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; iv[2].value = data->multi->push_cb != NULL; @@ -106,76 +114,109 @@ static int populate_settings(nghttp2_settings_entry *iv, return 3; } -static size_t populate_binsettings(uint8_t *binsettings, - struct Curl_easy *data) +static ssize_t populate_binsettings(uint8_t *binsettings, + struct Curl_easy *data) { nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; - int ivlen; + size_t ivlen; ivlen = populate_settings(iv, data); - /* this returns number of bytes it wrote */ + /* this returns number of bytes it wrote or a negative number on error. */ return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, iv, ivlen); } struct cf_h2_ctx { nghttp2_session *h2; - uint32_t max_concurrent_streams; /* The easy handle used in the current filter call, cleared at return */ struct cf_call_data call_data; struct bufq inbufq; /* network input */ struct bufq outbufq; /* network output */ 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` */ size_t drain_total; /* sum of all stream's UrlState drain */ - int32_t goaway_error; - int32_t last_stream_id; + uint32_t max_concurrent_streams; + uint32_t goaway_error; /* goaway error code from server */ + int32_t remote_max_sid; /* max id processed by server */ + int32_t local_max_sid; /* max id processed by us */ +#ifdef DEBUGBUILD + int32_t stream_win_max; /* max h2 stream window size */ +#endif + BIT(initialized); + BIT(via_h1_upgrade); BIT(conn_closed); - BIT(goaway); + BIT(rcvd_goaway); + BIT(sent_goaway); BIT(enable_push); + BIT(nw_out_blocked); }; /* How to access `call_data` from a cf_h2 filter */ +#undef CF_CTX_CALL_DATA #define CF_CTX_CALL_DATA(cf) \ ((struct cf_h2_ctx *)(cf)->ctx)->call_data -static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) +static void h2_stream_hash_free(unsigned int id, void *stream); + +static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) { - struct cf_call_data save = ctx->call_data; + Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); + Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); + Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); + curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_uint_hash_init(&ctx->streams, 63, h2_stream_hash_free); + ctx->remote_max_sid = 2147483647; + ctx->via_h1_upgrade = via_h1_upgrade; +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_H2_STREAM_WIN_MAX"); - if(ctx->h2) { - nghttp2_session_del(ctx->h2); + ctx->stream_win_max = H2_STREAM_WINDOW_SIZE_MAX; + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, INT_MAX)) + ctx->stream_win_max = (int32_t)l; + } } - Curl_bufq_free(&ctx->inbufq); - Curl_bufq_free(&ctx->outbufq); - Curl_bufcp_free(&ctx->stream_bufcp); - memset(ctx, 0, sizeof(*ctx)); - ctx->call_data = save; +#endif + ctx->initialized = TRUE; } static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) { - if(ctx) { - cf_h2_ctx_clear(ctx); - free(ctx); + if(ctx && ctx->initialized) { + Curl_bufq_free(&ctx->inbufq); + Curl_bufq_free(&ctx->outbufq); + Curl_bufcp_free(&ctx->stream_bufcp); + curlx_dyn_free(&ctx->scratch); + Curl_uint_hash_destroy(&ctx->streams); + memset(ctx, 0, sizeof(*ctx)); + } + free(ctx); +} + +static void cf_h2_ctx_close(struct cf_h2_ctx *ctx) +{ + if(ctx->h2) { + nghttp2_session_del(ctx->h2); } } static CURLcode h2_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data); + struct Curl_easy *data); /** - * All about the H3 internals of a stream + * All about the H2 internals of a stream */ -struct stream_ctx { - /*********** for HTTP/2 we store stream-local data here *************/ - int32_t id; /* HTTP/2 protocol identifier for stream */ - struct bufq recvbuf; /* response buffer */ +struct h2_stream_ctx { struct bufq sendbuf; /* request buffer */ + 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 upload_left; /* number of request bytes left to upload */ + 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 */ @@ -183,139 +224,236 @@ struct stream_ctx { int status_code; /* HTTP response status code */ uint32_t error; /* stream error code */ - bool closed; /* TRUE on stream close */ - bool reset; /* TRUE on stream reset */ - bool close_handled; /* TRUE if stream closure is handled by libcurl */ - bool bodystarted; - bool send_closed; /* transfer is done sending, we might have still - buffered data in stream->sendbuf to upload. */ + 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 */ + BIT(resp_hds_complete); /* we have a complete, final response */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(close_handled); /* TRUE if stream closure is handled by libcurl */ + BIT(bodystarted); + BIT(body_eos); /* the complete body has been added to `sendbuf` and + * is being/has been processed from there. */ + BIT(write_paused); /* stream write is paused */ }; -#define H2_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h2_ctx \ - : NULL)) -#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx -#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \ - H2_STREAM_CTX(d)->id : -2) +#define H2_STREAM_CTX(ctx,data) \ + ((struct h2_stream_ctx *)( \ + data? Curl_uint_hash_get(&(ctx)->streams, (data)->mid) : NULL)) + +static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) +{ + struct h2_stream_ctx *stream; + + (void)ctx; + stream = calloc(1, sizeof(*stream)); + if(!stream) + return NULL; + + stream->id = -1; + 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); + stream->bodystarted = FALSE; + stream->status_code = -1; + stream->closed = FALSE; + stream->close_handled = FALSE; + stream->error = NGHTTP2_NO_ERROR; + stream->local_window_size = H2_STREAM_WINDOW_SIZE_INITIAL; + stream->nrcvd_data = 0; + return stream; +} + +static void free_push_headers(struct h2_stream_ctx *stream) +{ + size_t i; + for(i = 0; i < stream->push_headers_used; i++) + free(stream->push_headers[i]); + Curl_safefree(stream->push_headers); + stream->push_headers_used = 0; +} + +static void h2_stream_ctx_free(struct h2_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + Curl_dynhds_free(&stream->resp_trailers); + free_push_headers(stream); + free(stream); +} + +static void h2_stream_hash_free(unsigned int id, void *stream) +{ + (void)id; + DEBUGASSERT(stream); + h2_stream_ctx_free((struct h2_stream_ctx *)stream); +} + +#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE +static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; + if(data->set.max_recv_speed && data->set.max_recv_speed < INT32_MAX) { + /* The transfer should only receive `max_recv_speed` bytes per second. + * We restrict the stream's local window size, so that the server cannot + * send us "too much" at a time. + * This gets less precise the higher the latency. */ + return (int32_t)data->set.max_recv_speed; + } +#ifdef DEBUGBUILD + else { + struct cf_h2_ctx *ctx = cf->ctx; + CURL_TRC_CF(data, cf, "stream_win_max=%d", ctx->stream_win_max); + return ctx->stream_win_max; + } +#else + return H2_STREAM_WINDOW_SIZE_MAX; +#endif +} + +static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream) +{ + struct cf_h2_ctx *ctx = cf->ctx; + int32_t dwsize; + int rv; + + dwsize = (stream->write_paused || stream->xfer_result) ? + 0 : cf_h2_get_desired_local_win(cf, data); + if(dwsize != stream->local_window_size) { + int32_t wsize = nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id); + if(dwsize > wsize) { + rv = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize); + if(rv) { + failf(data, "[%d] nghttp2 set_local_window_size(%d) failed: " + "%s(%d)", stream->id, dwsize, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + rv = nghttp2_submit_window_update(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize - wsize); + if(rv) { + failf(data, "[%d] nghttp2_submit_window_update() failed: " + "%s(%d)", stream->id, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + stream->local_window_size = dwsize; + CURL_TRC_CF(data, cf, "[%d] local window update by %d", + stream->id, dwsize - wsize); + } + else { + rv = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize); + if(rv) { + failf(data, "[%d] nghttp2_session_set_local_window_size() failed: " + "%s(%d)", stream->id, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + stream->local_window_size = dwsize; + CURL_TRC_CF(data, cf, "[%d] local window size now %d", + stream->id, dwsize); + } + } + return CURLE_OK; +} + +#else /* NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */ + +static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream) +{ + (void)cf; + (void)data; + (void)stream; + return CURLE_OK; +} +#endif /* !NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */ /* * Mark this transfer to get "drained". */ static void drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream) + struct h2_stream_ctx *stream) { unsigned char bits; (void)cf; bits = CURL_CSELECT_IN; - if(!stream->send_closed && stream->upload_left) + if(!stream->closed && + (!stream->body_eos || !Curl_bufq_is_empty(&stream->sendbuf))) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(stream->closed || (data->state.select_bits != bits)) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", + stream->id, bits); + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } static CURLcode http2_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx **pstream) + struct h2_stream_ctx **pstream) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream; + struct h2_stream_ctx *stream; (void)cf; DEBUGASSERT(data); - if(!data->req.p.http) { - failf(data, "initialization failure, transfer not http initialized"); - return CURLE_FAILED_INIT; - } - stream = H2_STREAM_CTX(data); + stream = H2_STREAM_CTX(ctx, data); if(stream) { *pstream = stream; return CURLE_OK; } - stream = calloc(1, sizeof(*stream)); + stream = h2_stream_ctx_create(ctx); if(!stream) return CURLE_OUT_OF_MEMORY; - stream->id = -1; - Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, - H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, - H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - Curl_dynhds_init(&stream->resp_trailers, 0, DYN_H2_TRAILERS); - stream->resp_hds_len = 0; - stream->bodystarted = FALSE; - stream->status_code = -1; - stream->closed = FALSE; - stream->close_handled = FALSE; - stream->error = NGHTTP2_NO_ERROR; - stream->upload_left = 0; + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { + h2_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } - H2_STREAM_LCTX(data) = stream; *pstream = stream; return CURLE_OK; } -static void http2_data_done(struct Curl_cfilter *cf, - struct Curl_easy *data, bool premature) +static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); DEBUGASSERT(ctx); - (void)premature; - if(!stream) + if(!stream || !ctx->initialized) return; if(ctx->h2) { + bool flush_egress = FALSE; + /* returns error if stream not known, which is fine here */ + (void)nghttp2_session_set_stream_user_data(ctx->h2, stream->id, NULL); + if(!stream->closed && stream->id > 0) { /* RST_STREAM */ - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] premature DATA_DONE, RST stream", - stream->id)); - if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, - stream->id, NGHTTP2_STREAM_CLOSED)) - (void)nghttp2_session_send(ctx->h2); - } - if(!Curl_bufq_is_empty(&stream->recvbuf)) { - /* Anything in the recvbuf is still being counted - * in stream and connection window flow control. Need - * to free that space or the connection window might get - * exhausted eventually. */ - nghttp2_session_consume(ctx->h2, stream->id, - Curl_bufq_len(&stream->recvbuf)); - /* give WINDOW_UPATE a chance to be sent, but ignore any error */ - (void)h2_progress_egress(cf, data); - } - - /* -1 means unassigned and 0 means cleared */ - if(nghttp2_session_get_stream_user_data(ctx->h2, stream->id)) { - int rv = nghttp2_session_set_stream_user_data(ctx->h2, - stream->id, 0); - if(rv) { - infof(data, "http/2: failed to clear user_data for stream %u", - stream->id); - DEBUGASSERT(0); - } + CURL_TRC_CF(data, cf, "[%d] premature DATA_DONE, RST stream", + stream->id); + stream->closed = TRUE; + stream->reset = TRUE; + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + flush_egress = TRUE; } - } - Curl_bufq_free(&stream->sendbuf); - Curl_bufq_free(&stream->recvbuf); - Curl_dynhds_free(&stream->resp_trailers); - if(stream->push_headers) { - /* if they weren't used and then freed before */ - for(; stream->push_headers_used > 0; --stream->push_headers_used) { - free(stream->push_headers[stream->push_headers_used - 1]); - } - free(stream->push_headers); - stream->push_headers = NULL; + if(flush_egress) + nghttp2_session_send(ctx->h2); } - free(stream); - H2_STREAM_LCTX(data) = NULL; + Curl_uint_hash_remove(&ctx->streams, data->mid); } static int h2_client_new(struct Curl_cfilter *cf, @@ -323,6 +461,8 @@ static int h2_client_new(struct Curl_cfilter *cf, { struct cf_h2_ctx *ctx = cf->ctx; nghttp2_option *o; + nghttp2_mem mem = {NULL, Curl_nghttp2_malloc, Curl_nghttp2_free, + Curl_nghttp2_calloc, Curl_nghttp2_realloc}; int rc = nghttp2_option_new(&o); if(rc) @@ -335,7 +475,7 @@ static int h2_client_new(struct Curl_cfilter *cf, HTTP field value. */ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); #endif - rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o); + rc = nghttp2_session_client_new3(&ctx->h2, cbs, cf, o, &mem); nghttp2_option_del(o); return rc; } @@ -357,7 +497,14 @@ static ssize_t nw_out_writer(void *writer_ctx, struct Curl_cfilter *cf = writer_ctx; struct Curl_easy *data = CF_DATA_CURRENT(cf); - return Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, err); + if(data) { + ssize_t nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, + buflen, FALSE, err); + if(nwritten > 0) + CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten); + return nwritten; + } + return 0; } static ssize_t send_callback(nghttp2_session *h2, @@ -365,6 +512,14 @@ static ssize_t send_callback(nghttp2_session *h2, void *userp); static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp); +static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, + void *user_data); +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, + void *userp); +#endif static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *mem, size_t len, void *userp); @@ -377,39 +532,21 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) static int error_callback(nghttp2_session *session, const char *msg, size_t len, void *userp); - -/* - * multi_connchanged() is called to tell that there is a connection in - * this multi handle that has changed state (multiplexing become possible, the - * number of allowed streams changed or similar), and a subsequent use of this - * multi handle should move CONNECT_PEND handles back to CONNECT to have them - * retry. - */ -static void multi_connchanged(struct Curl_multi *multi) -{ - multi->recheckstate = TRUE; -} - -/* - * Initialize the cfilter context - */ -static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool via_h1_upgrade) +#endif +static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream; + struct h2_stream_ctx *stream; CURLcode result = CURLE_OUT_OF_MEMORY; int rc; nghttp2_session_callbacks *cbs = NULL; DEBUGASSERT(!ctx->h2); - Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); - Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); - Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); - ctx->last_stream_id = 2147483647; + DEBUGASSERT(ctx->initialized); rc = nghttp2_session_callbacks_new(&cbs); if(rc) { @@ -419,13 +556,20 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, nghttp2_session_callbacks_set_send_callback(cbs, send_callback); nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(cbs, + cf_h2_on_invalid_frame_recv); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + nghttp2_session_callbacks_set_on_frame_send_callback(cbs, on_frame_send); +#endif 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); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) nghttp2_session_callbacks_set_error_callback(cbs, error_callback); +#endif /* The nghttp2 session is not yet setup, do it */ rc = h2_client_new(cf, cbs); @@ -435,14 +579,19 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, } ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; - if(via_h1_upgrade) { + if(ctx->via_h1_upgrade) { /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted * in the H1 request and we upgrade from there. This stream * is opened implicitly as #1. */ uint8_t binsettings[H2_BINSETTINGS_LEN]; - size_t binlen; /* length of the binsettings data */ + ssize_t binlen; /* length of the binsettings data */ binlen = populate_binsettings(binsettings, data); + if(binlen <= 0) { + failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); + result = CURLE_FAILED_INIT; + goto out; + } result = http2_data_setup(cf, data, &stream); if(result) @@ -450,7 +599,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, DEBUGASSERT(stream); stream->id = 1; /* queue SETTINGS frame (again) */ - rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen, + rc = nghttp2_session_upgrade2(ctx->h2, binsettings, (size_t)binlen, data->state.httpreq == HTTPREQ_HEAD, NULL); if(rc) { @@ -467,10 +616,11 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, stream->id); DEBUGASSERT(0); } + CURL_TRC_CF(data, cf, "created session via Upgrade"); } else { nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; - int ivlen; + size_t ivlen; ivlen = populate_settings(iv, data); rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, @@ -494,6 +644,8 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, /* all set, traffic will be send on connect */ result = CURLE_OK; + CURL_TRC_CF(data, cf, "[0] created h2 session%s", + ctx->via_h1_upgrade ? " (via h1 upgrade)" : ""); out: if(cbs) @@ -528,10 +680,8 @@ static int h2_process_pending_input(struct Curl_cfilter *cf, rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); if(rv < 0) { - failf(data, - "process_pending_input: nghttp2_session_mem_recv() returned " - "%zd:%s", rv, nghttp2_strerror((int)rv)); - *err = CURLE_RECV_ERROR; + failf(data, "nghttp2 recv error %zd: %s", rv, nghttp2_strerror((int)rv)); + *err = CURLE_HTTP2; return -1; } Curl_bufq_skip(&ctx->inbufq, (size_t)rv); @@ -539,8 +689,8 @@ static int h2_process_pending_input(struct Curl_cfilter *cf, break; } else { - DEBUGF(LOG_CF(data, cf, "process_pending_input: %zu bytes left " - "in connection buffer", Curl_bufq_len(&ctx->inbufq))); + CURL_TRC_CF(data, cf, "process_pending_input: %zu bytes left " + "in connection buffer", Curl_bufq_len(&ctx->inbufq)); } } @@ -573,18 +723,17 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, return FALSE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ CURLcode result; ssize_t nread = -1; *input_pending = FALSE; - Curl_attach_connection(data, cf->conn); nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); if(nread != -1) { - DEBUGF(LOG_CF(data, cf, "%zd bytes stray data read before trying " - "h2 connection", nread)); + CURL_TRC_CF(data, cf, "%zd bytes stray data read before trying " + "h2 connection", nread); if(h2_process_pending_input(cf, data, &result) < 0) /* immediate error, considered dead */ alive = FALSE; @@ -592,11 +741,10 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, alive = !should_close_session(ctx); } } - else { + else if(result != CURLE_AGAIN) { /* the read failed so let's say this is dead anyway */ alive = FALSE; } - Curl_detach_connection(data); } return alive; @@ -644,13 +792,16 @@ static CURLcode nw_out_flush(struct Curl_cfilter *cf, if(Curl_bufq_is_empty(&ctx->outbufq)) return CURLE_OK; - DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", - Curl_bufq_len(&ctx->outbufq))); nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result); - if(nwritten < 0 && result != CURLE_AGAIN) { + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "flush nw send buffer(%zu) -> EAGAIN", + Curl_bufq_len(&ctx->outbufq)); + ctx->nw_out_blocked = 1; + } return result; } - return CURLE_OK; + return Curl_bufq_is_empty(&ctx->outbufq) ? CURLE_OK : CURLE_AGAIN; } /* @@ -672,19 +823,24 @@ static ssize_t send_callback(nghttp2_session *h2, (void)flags; DEBUGASSERT(data); - nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, - nw_out_writer, cf, &result); + if(!cf->connected) + nwritten = Curl_bufq_write(&ctx->outbufq, buf, blen, &result); + else + nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, + nw_out_writer, cf, &result); if(nwritten < 0) { if(result == CURLE_AGAIN) { + ctx->nw_out_blocked = 1; return NGHTTP2_ERR_WOULDBLOCK; } failf(data, "Failed sending HTTP2 data"); return NGHTTP2_ERR_CALLBACK_FAILURE; } - if(!nwritten) + if(!nwritten) { + ctx->nw_out_blocked = 1; return NGHTTP2_ERR_WOULDBLOCK; - + } return nwritten; } @@ -693,6 +849,7 @@ static ssize_t send_callback(nghttp2_session *h2, the struct are hidden from the user. */ struct curl_pushheaders { struct Curl_easy *data; + struct h2_stream_ctx *stream; const nghttp2_push_promise *frame; }; @@ -706,9 +863,8 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) if(!h || !GOOD_EASY_HANDLE(h->data)) return NULL; else { - struct stream_ctx *stream = H2_STREAM_CTX(h->data); - if(stream && num < stream->push_headers_used) - return stream->push_headers[num]; + if(h->stream && num < h->stream->push_headers_used) + return h->stream->push_headers[num]; } return NULL; } @@ -718,7 +874,7 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) */ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) { - struct stream_ctx *stream; + struct h2_stream_ctx *stream; size_t len; size_t i; /* Verify that we got a good easy handle in the push header struct, @@ -731,12 +887,12 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) !strcmp(header, ":") || strchr(header + 1, ':')) return NULL; - stream = H2_STREAM_CTX(h->data); + stream = h->stream; if(!stream) return NULL; len = strlen(header); - for(i = 0; ipush_headers_used; i++) { + for(i = 0; i < stream->push_headers_used; i++) { if(!strncmp(header, stream->push_headers[i], len)) { /* sub-match, make sure that it is followed by a colon */ if(stream->push_headers[i][len] != ':') @@ -752,18 +908,9 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, { struct Curl_easy *second = curl_easy_duphandle(data); if(second) { - /* setup the request struct */ - struct HTTP *http = calloc(1, sizeof(struct HTTP)); - if(!http) { - (void)Curl_close(&second); - } - else { - struct stream_ctx *second_stream; - - second->req.p.http = http; - http2_data_setup(cf, second, &second_stream); - second->state.priority.weight = data->state.priority.weight; - } + struct h2_stream_ctx *second_stream; + http2_data_setup(cf, second, &second_stream); + second->state.priority.weight = data->state.priority.weight; } return second; } @@ -791,7 +938,7 @@ static int set_transfer_url(struct Curl_easy *data, v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY); if(v) { - uc = Curl_url_set_authority(u, v, CURLU_DISALLOW_USER); + uc = Curl_url_set_authority(u, v); if(uc) { rc = 2; goto fail; @@ -825,10 +972,7 @@ static int set_transfer_url(struct Curl_easy *data, static void discard_newhandle(struct Curl_cfilter *cf, struct Curl_easy *newhandle) { - if(!newhandle->req.p.http) { - http2_data_done(cf, newhandle, TRUE); - newhandle->req.p.http = NULL; - } + http2_data_done(cf, newhandle); (void)Curl_close(&newhandle); } @@ -839,15 +983,14 @@ static int push_promise(struct Curl_cfilter *cf, struct cf_h2_ctx *ctx = cf->ctx; int rv; /* one of the CURL_PUSH_* defines */ - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] PUSH_PROMISE received", - frame->promised_stream_id)); + CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE received", + frame->promised_stream_id); if(data->multi->push_cb) { - struct stream_ctx *stream; - struct stream_ctx *newstream; + struct h2_stream_ctx *stream; + struct h2_stream_ctx *newstream; struct curl_pushheaders heads; CURLMcode rc; CURLcode result; - size_t i; /* clone the parent */ struct Curl_easy *newhandle = h2_duphandle(cf, data); if(!newhandle) { @@ -856,12 +999,7 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } - heads.data = data; - heads.frame = frame; - /* ask the application */ - DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ask application")); - - stream = H2_STREAM_CTX(data); + stream = H2_STREAM_CTX(ctx, data); if(!stream) { failf(data, "Internal NULL stream"); discard_newhandle(cf, newhandle); @@ -869,48 +1007,40 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } + heads.data = data; + heads.stream = stream; + heads.frame = frame; + rv = set_transfer_url(newhandle, &heads); if(rv) { + CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE, failed to set url -> %d", + frame->promised_stream_id, rv); discard_newhandle(cf, newhandle); rv = CURL_PUSH_DENY; goto fail; } - result = http2_data_setup(cf, newhandle, &newstream); - if(result) { - failf(data, "error setting up stream: %d", result); - discard_newhandle(cf, newhandle); - rv = CURL_PUSH_DENY; - goto fail; - } - DEBUGASSERT(stream); - - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); rv = data->multi->push_cb(data, newhandle, stream->push_headers_used, &heads, data->multi->push_userp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); /* free the headers again */ - for(i = 0; ipush_headers_used; i++) - free(stream->push_headers[i]); - free(stream->push_headers); - stream->push_headers = NULL; - stream->push_headers_used = 0; + free_push_headers(stream); if(rv) { DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); /* denied, kill off the new handle again */ + CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE, denied by application -> %d", + frame->promised_stream_id, rv); discard_newhandle(cf, newhandle); goto fail; } - newstream->id = frame->promised_stream_id; - newhandle->req.maxdownload = -1; - newhandle->req.size = -1; - - /* approved, add to the multi handle and immediately switch to PERFORM - state with the given connection !*/ + /* approved, add to the multi handle for processing. This + * assigns newhandle->mid. For the new `mid` we assign the + * h2_stream instance and remember the stream_id already known. */ rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn); if(rc) { infof(data, "failed to add handle to multi"); @@ -919,6 +1049,21 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } + result = http2_data_setup(cf, newhandle, &newstream); + if(result) { + failf(data, "error setting up stream: %d", result); + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + + DEBUGASSERT(newstream); + newstream->id = frame->promised_stream_id; + newhandle->req.maxdownload = -1; + newhandle->req.size = -1; + + CURL_TRC_CF(data, cf, "promise easy handle added to multi, mid=%u", + newhandle->mid); rv = nghttp2_session_set_stream_user_data(ctx->h2, newstream->id, newhandle); @@ -929,31 +1074,65 @@ static int push_promise(struct Curl_cfilter *cf, rv = CURL_PUSH_DENY; goto fail; } + + /* success, remember max stream id processed */ + if(newstream->id > ctx->local_max_sid) + ctx->local_max_sid = newstream->id; } else { - DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ignore it")); + CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it"); rv = CURL_PUSH_DENY; } fail: return rv; } -static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf, +static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf, struct Curl_easy *data, - const char *buf, size_t blen) + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) { - struct stream_ctx *stream = H2_STREAM_CTX(data); - ssize_t nwritten; - CURLcode result; - (void)cf; - nwritten = Curl_bufq_write(&stream->recvbuf, - (const unsigned char *)buf, blen, &result); - if(nwritten < 0) - return result; - stream->resp_hds_len += (size_t)nwritten; - DEBUGASSERT((size_t)nwritten == blen); - return CURLE_OK; + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(!stream->xfer_result && !eos) + stream->xfer_result = cf_h2_update_local_win(cf, data, stream); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of headers", + stream->id, stream->xfer_result, blen); + } +} + +static void h2_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + struct cf_h2_ctx *ctx = cf->ctx; + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of data, " + "RST-ing stream", + stream->id, stream->xfer_result, blen); + nghttp2_submit_rst_stream(ctx->h2, 0, stream->id, + (uint32_t)NGHTTP2_ERR_CALLBACK_FAILURE); + } + else if(!stream->write_paused && Curl_xfer_write_is_paused(data)) { + CURL_TRC_CF(data, cf, "[%d] stream output paused", stream->id); + stream->write_paused = TRUE; + } + else if(stream->write_paused && !Curl_xfer_write_is_paused(data)) { + CURL_TRC_CF(data, cf, "[%d] stream output unpaused", stream->id); + stream->write_paused = FALSE; + } + + if(!stream->xfer_result && !eos) + stream->xfer_result = cf_h2_update_local_win(cf, data, stream); } static CURLcode on_stream_frame(struct Curl_cfilter *cf, @@ -961,26 +1140,23 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, const nghttp2_frame *frame) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int32_t stream_id = frame->hd.stream_id; - CURLcode result; int rv; if(!stream) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] No proto pointer", stream_id)); + CURL_TRC_CF(data, cf, "[%d] No stream_ctx set", stream_id); return CURLE_FAILED_INIT; } switch(frame->hd.type) { case NGHTTP2_DATA: - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[DATA len=%zu pad=%zu], " - "buffered=%zu, window=%d/%d", - stream_id, frame->hd.length, frame->data.padlen, - Curl_bufq_len(&stream->recvbuf), - nghttp2_session_get_stream_effective_recv_data_length( - ctx->h2, stream->id), - nghttp2_session_get_stream_effective_local_window_size( - ctx->h2, stream->id))); + CURL_TRC_CF(data, cf, "[%d] DATA, window=%d/%d", + stream_id, + nghttp2_session_get_stream_effective_recv_data_length( + ctx->h2, stream->id), + nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id)); /* If !body started on this stream, then receiving DATA is illegal. */ if(!stream->bodystarted) { rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, @@ -990,14 +1166,10 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, return CURLE_RECV_ERROR; } } - if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - drain_stream(cf, data, stream); - } break; case NGHTTP2_HEADERS: - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[HEADERS]", stream_id)); if(stream->bodystarted) { - /* Only valid HEADERS after body started is trailer HEADERS. We + /* Only valid HEADERS after body started is trailer HEADERS. We buffer them in on_header callback. */ break; } @@ -1009,21 +1181,19 @@ 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->bodystarted = TRUE; + else stream->status_code = -1; - } - result = recvbuf_write_hds(cf, data, STRCONST("\r\n")); - if(result) - return result; + h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] %zu header bytes", - stream_id, Curl_bufq_len(&stream->recvbuf))); + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } drain_stream(cf, data, stream); break; case NGHTTP2_PUSH_PROMISE: - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[PUSH_PROMISE]", stream_id)); rv = push_promise(cf, data, &frame->push_promise); if(rv) { /* deny! */ DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); @@ -1033,38 +1203,152 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, if(nghttp2_is_fatal(rv)) return CURLE_SEND_ERROR; else if(rv == CURL_PUSH_ERROROUT) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] fail in PUSH_PROMISE received", - stream_id)); + CURL_TRC_CF(data, cf, "[%d] fail in PUSH_PROMISE received", + stream_id); return CURLE_RECV_ERROR; } } break; case NGHTTP2_RST_STREAM: - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[RST]", stream_id)); stream->closed = TRUE; - stream->reset = TRUE; - stream->send_closed = TRUE; - data->req.keepon &= ~KEEP_SEND_HOLD; + if(frame->rst_stream.error_code) { + stream->reset = TRUE; + } drain_stream(cf, data, stream); break; case NGHTTP2_WINDOW_UPDATE: - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[WINDOW_UPDATE]", stream_id)); - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; + if(CURL_WANT_SEND(data) && Curl_bufq_is_empty(&stream->sendbuf)) { + /* need more data, force processing of transfer */ drain_stream(cf, data, stream); - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] un-holding after win update", - stream_id)); + } + else if(!Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) + return CURLE_SEND_ERROR; } break; default: - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[%x]", - stream_id, frame->hd.type)); break; } + + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + if(!stream->closed && !stream->body_eos && + ((stream->status_code >= 400) || (stream->status_code < 200))) { + /* The server did not give us a positive response and we are not + * done uploading the request body. We need to stop doing that and + * also inform the server that we aborted our side. */ + CURL_TRC_CF(data, cf, "[%d] EOS frame with unfinished upload and " + "HTTP status %d, abort upload by RST", + stream_id, stream->status_code); + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + stream->closed = TRUE; + } + drain_stream(cf, data, stream); + } return CURLE_OK; } +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) +{ + switch(frame->hd.type) { + case NGHTTP2_DATA: { + return msnprintf(buffer, blen, + "FRAME[DATA, len=%d, eos=%d, padlen=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM), + (int)frame->data.padlen); + } + case NGHTTP2_HEADERS: { + return msnprintf(buffer, blen, + "FRAME[HEADERS, len=%d, hend=%d, eos=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); + } + case NGHTTP2_PRIORITY: { + return msnprintf(buffer, blen, + "FRAME[PRIORITY, len=%d, flags=%d]", + (int)frame->hd.length, frame->hd.flags); + } + case NGHTTP2_RST_STREAM: { + return msnprintf(buffer, blen, + "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]", + (int)frame->hd.length, frame->hd.flags, + frame->rst_stream.error_code); + } + case NGHTTP2_SETTINGS: { + if(frame->hd.flags & NGHTTP2_FLAG_ACK) { + return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]"); + } + return msnprintf(buffer, blen, + "FRAME[SETTINGS, len=%d]", (int)frame->hd.length); + } + case NGHTTP2_PUSH_PROMISE: { + return msnprintf(buffer, blen, + "FRAME[PUSH_PROMISE, len=%d, hend=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)); + } + case NGHTTP2_PING: { + return msnprintf(buffer, blen, + "FRAME[PING, len=%d, ack=%d]", + (int)frame->hd.length, + frame->hd.flags&NGHTTP2_FLAG_ACK); + } + case NGHTTP2_GOAWAY: { + char scratch[128]; + size_t s_len = CURL_ARRAYSIZE(scratch); + size_t len = (frame->goaway.opaque_data_len < s_len) ? + frame->goaway.opaque_data_len : s_len-1; + if(len) + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len] = '\0'; + return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " + "last_stream=%d]", frame->goaway.error_code, + scratch, frame->goaway.last_stream_id); + } + case NGHTTP2_WINDOW_UPDATE: { + return msnprintf(buffer, blen, + "FRAME[WINDOW_UPDATE, incr=%d]", + frame->window_update.window_size_increment); + } + default: + return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]", + frame->hd.type, (int)frame->hd.length, + frame->hd.flags); + } +} + +static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, + void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)session; + DEBUGASSERT(data); + if(data && Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer); + } + if((frame->hd.type == NGHTTP2_GOAWAY) && !ctx->sent_goaway) { + /* A GOAWAY not initiated by us, but by nghttp2 itself on detecting + * a protocol error on the connection */ + failf(data, "nghttp2 shuts down connection with error %d: %s", + frame->goaway.error_code, + nghttp2_http2_strerror(frame->goaway.error_code)); + } + return 0; +} +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { @@ -1074,58 +1358,106 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, int32_t stream_id = frame->hd.stream_id; DEBUGASSERT(data); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(Curl_trc_cf_is_verbose(cf, data)) { + char buffer[256]; + int len; + len = fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer); + } +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + if(!stream_id) { /* stream ID zero is for connection-oriented stuff */ DEBUGASSERT(data); switch(frame->hd.type) { case NGHTTP2_SETTINGS: { - uint32_t max_conn = ctx->max_concurrent_streams; - DEBUGF(LOG_CF(data, cf, "FRAME[SETTINGS]")); - ctx->max_concurrent_streams = nghttp2_session_get_remote_settings( - session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); - ctx->enable_push = nghttp2_session_get_remote_settings( - session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0; - DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS == %d", - ctx->max_concurrent_streams)); - DEBUGF(LOG_CF(data, cf, "ENABLE_PUSH == %s", - ctx->enable_push ? "TRUE" : "false")); - if(data && max_conn != ctx->max_concurrent_streams) { - /* only signal change if the value actually changed */ - DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS now %u", - ctx->max_concurrent_streams)); - multi_connchanged(data->multi); + if(!(frame->hd.flags & NGHTTP2_FLAG_ACK)) { + uint32_t max_conn = ctx->max_concurrent_streams; + ctx->max_concurrent_streams = nghttp2_session_get_remote_settings( + session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + ctx->enable_push = nghttp2_session_get_remote_settings( + session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0; + CURL_TRC_CF(data, cf, "[0] MAX_CONCURRENT_STREAMS: %d", + ctx->max_concurrent_streams); + CURL_TRC_CF(data, cf, "[0] ENABLE_PUSH: %s", + ctx->enable_push ? "TRUE" : "false"); + if(data && max_conn != ctx->max_concurrent_streams) { + /* only signal change if the value actually changed */ + CURL_TRC_CF(data, cf, "[0] notify MAX_CONCURRENT_STREAMS: %u", + ctx->max_concurrent_streams); + Curl_multi_connchanged(data->multi); + } + /* Since the initial stream window is 64K, a request might be on HOLD, + * due to exhaustion. The (initial) SETTINGS may announce a much larger + * window and *assume* that we treat this like a WINDOW_UPDATE. Some + * servers send an explicit WINDOW_UPDATE, but not all seem to do that. + * To be safe, we UNHOLD a stream in order not to stall. */ + if(CURL_WANT_SEND(data)) { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(stream) + drain_stream(cf, data, stream); + } } break; } case NGHTTP2_GOAWAY: - ctx->goaway = TRUE; + ctx->rcvd_goaway = TRUE; ctx->goaway_error = frame->goaway.error_code; - ctx->last_stream_id = frame->goaway.last_stream_id; + ctx->remote_max_sid = frame->goaway.last_stream_id; if(data) { - DEBUGF(LOG_CF(data, cf, "FRAME[GOAWAY, error=%d, last_stream=%u]", - ctx->goaway_error, ctx->last_stream_id)); - infof(data, "received GOAWAY, error=%d, last_stream=%u", - ctx->goaway_error, ctx->last_stream_id); - multi_connchanged(data->multi); + infof(data, "received GOAWAY, error=%u, last_stream=%u", + ctx->goaway_error, ctx->remote_max_sid); + Curl_multi_connchanged(data->multi); } break; - case NGHTTP2_WINDOW_UPDATE: - DEBUGF(LOG_CF(data, cf, "FRAME[WINDOW_UPDATE]")); - break; default: - DEBUGF(LOG_CF(data, cf, "recv frame %x on 0", frame->hd.type)); + break; } return 0; } data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] No Curl_easy associated", - stream_id)); + 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, frame) ? NGHTTP2_ERR_CALLBACK_FAILURE : 0; +} + +static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + int ngerr, void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data; + int32_t stream_id = frame->hd.stream_id; + + data = nghttp2_session_get_stream_user_data(session, stream_id); + if(data) { + struct h2_stream_ctx *stream; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char buffer[256]; + int len; + len = fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + failf(data, "[HTTP2] [%d] received invalid frame: %s, error %d: %s", + stream_id, buffer, ngerr, nghttp2_strerror(ngerr)); +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + 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; } static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, @@ -1133,10 +1465,9 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, const uint8_t *mem, size_t len, void *userp) { struct Curl_cfilter *cf = userp; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s; - ssize_t nwritten; - CURLcode result; (void)flags; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ @@ -1148,29 +1479,21 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, /* Receiving a Stream ID not in the hash should not happen - unless we have aborted a transfer artificially and there were more data in the pipeline. Silently ignore. */ - DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%d] Data for unknown", - stream_id)); + CURL_TRC_CF(CF_DATA_CURRENT(cf), cf, "[%d] Data for unknown", + stream_id); /* consumed explicitly as no one will read it */ nghttp2_session_consume(session, stream_id, len); return 0; } - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; - nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result); - if(nwritten < 0) { - if(result != CURLE_AGAIN) - return NGHTTP2_ERR_CALLBACK_FAILURE; - - nwritten = 0; - } + h2_xfer_write_resp(cf, data_s, stream, (const char *)mem, len, FALSE); - /* if we receive data for another handle, wake that up */ - drain_stream(cf, data_s, stream); - - DEBUGASSERT((size_t)nwritten == len); + nghttp2_session_consume(ctx->h2, stream_id, len); + stream->nrcvd_data += (curl_off_t)len; return 0; } @@ -1178,30 +1501,48 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *userp) { struct Curl_cfilter *cf = userp; - struct Curl_easy *data_s; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); + struct h2_stream_ctx *stream; int rv; (void)session; - /* get the stream from the hash based on Stream ID, stream ID zero is for - connection-oriented stuff */ - data_s = stream_id? - nghttp2_session_get_stream_user_data(session, stream_id) : NULL; + 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; if(!data_s) { + CURL_TRC_CF(call_data, cf, + "[%d] on_stream_close, no easy set on stream", stream_id); return 0; } - stream = H2_STREAM_CTX(data_s); - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] on_stream_close(), %s (err %d)", - stream_id, nghttp2_http2_strerror(error_code), error_code)); - if(!stream) + if(!GOOD_EASY_HANDLE(data_s)) { + /* nghttp2 still has an easy registered for the stream which has + * been freed be libcurl. This points to a code path that does not + * 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); 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); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } stream->closed = TRUE; stream->error = error_code; - if(stream->error) + if(stream->error) { stream->reset = TRUE; - data_s->req.keepon &= ~KEEP_SEND_HOLD; + } + if(stream->error) + CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %d)", + stream_id, nghttp2_http2_strerror(error_code), error_code); + else + CURL_TRC_CF(data_s, cf, "[%d] CLOSED", stream_id); drain_stream(cf, data_s, stream); /* remove `data_s` from the nghttp2 stream */ @@ -1211,7 +1552,6 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, stream_id); DEBUGASSERT(0); } - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] closed now", stream_id)); return 0; } @@ -1219,7 +1559,8 @@ static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s = NULL; (void)cf; @@ -1228,13 +1569,11 @@ static int on_begin_headers(nghttp2_session *session, return 0; } - DEBUGF(LOG_CF(data_s, cf, "on_begin_headers() was called")); - if(frame->hd.type != NGHTTP2_HEADERS) { return 0; } - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream || !stream->bodystarted) { return 0; } @@ -1242,6 +1581,23 @@ static int on_begin_headers(nghttp2_session *session, return 0; } +static void cf_h2_header_error(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + CURLcode result) +{ + struct cf_h2_ctx *ctx = cf->ctx; + + failf(data, "Error receiving HTTP2 header: %d(%s)", result, + curl_easy_strerror(result)); + if(stream) { + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + stream->closed = TRUE; + stream->reset = TRUE; + } +} + /* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, @@ -1250,7 +1606,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s; int32_t stream_id = frame->hd.stream_id; CURLcode result; @@ -1260,12 +1617,12 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* get the stream from the hash based on Stream ID */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!data_s) + if(!GOOD_EASY_HANDLE(data_s)) /* 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(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) { failf(data_s, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1306,7 +1663,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, stream->push_headers = malloc(stream->push_headers_alloc * sizeof(char *)); if(!stream->push_headers) - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + return NGHTTP2_ERR_CALLBACK_FAILURE; stream->push_headers_used = 0; } else if(stream->push_headers_used == @@ -1315,15 +1672,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(stream->push_headers_alloc > 1000) { /* this is beyond crazy many headers, bail out */ failf(data_s, "Too many PUSH_PROMISE headers"); - Curl_safefree(stream->push_headers); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + free_push_headers(stream); + return NGHTTP2_ERR_CALLBACK_FAILURE; } stream->push_headers_alloc *= 2; - headp = Curl_saferealloc(stream->push_headers, - stream->push_headers_alloc * sizeof(char *)); + headp = realloc(stream->push_headers, + stream->push_headers_alloc * sizeof(char *)); if(!headp) { - stream->push_headers = NULL; - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + free_push_headers(stream); + return NGHTTP2_ERR_CALLBACK_FAILURE; } stream->push_headers = headp; } @@ -1335,15 +1692,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(stream->bodystarted) { /* This is a trailer */ - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] trailer: %.*s: %.*s", - stream->id, - (int)namelen, name, - (int)valuelen, value)); + CURL_TRC_CF(data_s, cf, "[%d] trailer: %.*s: %.*s", + stream->id, (int)namelen, name, (int)valuelen, value); result = Curl_dynhds_add(&stream->resp_trailers, (const char *)name, namelen, (const char *)value, valuelen); - if(result) + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } return 0; } @@ -1354,55 +1711,63 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, char buffer[32]; result = Curl_http_decode_status(&stream->status_code, (const char *)value, valuelen); - if(result) + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%u\r", stream->status_code); result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; - /* the space character after the status code is mandatory */ - result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n")); - if(result) + } + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 ")); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, value, valuelen); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } /* if we receive data for another handle, wake that up */ if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] status: HTTP/2 %03d", - stream->id, stream->status_code)); + CURL_TRC_CF(data_s, cf, "[%d] status: HTTP/2 %03d", + stream->id, stream->status_code); return 0; } /* nghttp2 guarantees that namelen > 0, and :status was already received, and this is not pseudo-header field . */ /* convert to an HTTP1-style header */ - result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST(": ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n")); - if(result) + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, (const char *)name, namelen); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, (const char *)value, valuelen); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } /* if we receive data for another handle, wake that up */ if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] header: %.*s: %.*s", - stream->id, - (int)namelen, name, - (int)valuelen, value)); + CURL_TRC_CF(data_s, cf, "[%d] header: %.*s: %.*s", + stream->id, (int)namelen, name, (int)valuelen, value); return 0; /* 0 is successful */ } @@ -1415,29 +1780,29 @@ 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 stream_ctx *stream = NULL; + struct h2_stream_ctx *stream = NULL; CURLcode result; ssize_t nread; (void)source; (void)cf; - if(stream_id) { - /* 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) - /* 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(data_s); - if(!stream) - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - else + 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) + /* 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; + nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result); if(nread < 0) { if(result != CURLE_AGAIN) @@ -1445,19 +1810,14 @@ static ssize_t req_body_read_callback(nghttp2_session *session, nread = 0; } - if(nread > 0 && stream->upload_left != -1) - stream->upload_left -= nread; - - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] req_body_read(len=%zu) left=%zd" - " -> %zd, %d", - stream_id, length, stream->upload_left, nread, result)); + CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) eos=%d -> %zd, %d", + stream_id, length, stream->body_eos, nread, result); - if(stream->upload_left == 0) + if(stream->body_eos && Curl_bufq_is_empty(&stream->sendbuf)) { *data_flags = NGHTTP2_DATA_FLAG_EOF; - else if(nread == 0) - return NGHTTP2_ERR_DEFERRED; - - return nread; + return nread; + } + return (nread == 0) ? NGHTTP2_ERR_DEFERRED : nread; } #if !defined(CURL_DISABLE_VERBOSE_STRINGS) @@ -1466,10 +1826,10 @@ static int error_callback(nghttp2_session *session, size_t len, void *userp) { + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = CF_DATA_CURRENT(cf); (void)session; - (void)msg; - (void)len; - (void)userp; + failf(data, "%.*s", (int)len, msg); return 0; } #endif @@ -1485,88 +1845,71 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, size_t blen; struct SingleRequest *k = &data->req; uint8_t binsettings[H2_BINSETTINGS_LEN]; - size_t binlen; /* length of the binsettings data */ + ssize_t binlen; /* length of the binsettings data */ binlen = populate_binsettings(binsettings, data); if(binlen <= 0) { failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); - Curl_dyn_free(req); + curlx_dyn_free(req); return CURLE_FAILED_INIT; } - result = Curl_base64url_encode((const char *)binsettings, binlen, - &base64, &blen); + result = curlx_base64url_encode((const char *)binsettings, (size_t)binlen, + &base64, &blen); if(result) { - Curl_dyn_free(req); + curlx_dyn_free(req); return result; } - result = Curl_dyn_addf(req, - "Connection: Upgrade, HTTP2-Settings\r\n" - "Upgrade: %s\r\n" - "HTTP2-Settings: %s\r\n", - NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); + result = curlx_dyn_addf(req, + "Connection: Upgrade, HTTP2-Settings\r\n" + "Upgrade: %s\r\n" + "HTTP2-Settings: %s\r\n", + NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); free(base64); k->upgr101 = UPGR101_H2; + data->conn->bits.asks_multiplex = TRUE; return result; } -static CURLcode http2_data_done_send(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct stream_ctx *stream = H2_STREAM_CTX(data); - - if(!ctx || !ctx->h2 || !stream) - goto out; - - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] data done send", stream->id)); - if(!stream->send_closed) { - stream->send_closed = TRUE; - if(stream->upload_left) { - /* we now know that everything that is buffered is all there is. */ - stream->upload_left = Curl_bufq_len(&stream->sendbuf); - /* resume sending here to trigger the callback to get called again so - that it can signal EOF to nghttp2 */ - (void)nghttp2_session_resume_data(ctx->h2, stream->id); - drain_stream(cf, data, stream); - } - } - -out: - return result; -} - static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream, + struct h2_stream_ctx *stream, CURLcode *err) { ssize_t rv = 0; if(stream->error == NGHTTP2_REFUSED_STREAM) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] REFUSED_STREAM, try again on a new " - "connection", stream->id)); - connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ + CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new " + "connection", stream->id); + connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ data->state.refused_stream = TRUE; - *err = CURLE_SEND_ERROR; /* trigger Curl_retry_request() later */ - return -1; - } - else if(stream->reset) { - failf(data, "HTTP/2 stream %u was reset", stream->id); - *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ return -1; } else if(stream->error != NGHTTP2_NO_ERROR) { + if(stream->resp_hds_complete && data->req.no_body) { + CURL_TRC_CF(data, cf, "[%d] error after response headers, but we did " + "not want a body anyway, ignore: %s (err %u)", + stream->id, nghttp2_http2_strerror(stream->error), + stream->error); + stream->close_handled = TRUE; + *err = CURLE_OK; + goto out; + } failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", stream->id, nghttp2_http2_strerror(stream->error), stream->error); *err = CURLE_HTTP2_STREAM; return -1; } + else if(stream->reset) { + failf(data, "HTTP/2 stream %u was reset", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2; + return -1; + } if(!stream->bodystarted) { failf(data, "HTTP/2 stream %u was closed cleanly, but before getting " @@ -1582,25 +1925,25 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, size_t i; *err = CURLE_OK; - Curl_dyn_init(&dbuf, DYN_TRAILERS); + curlx_dyn_init(&dbuf, DYN_TRAILERS); for(i = 0; i < Curl_dynhds_count(&stream->resp_trailers); ++i) { e = Curl_dynhds_getn(&stream->resp_trailers, i); if(!e) break; - Curl_dyn_reset(&dbuf); - *err = Curl_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a", - (int)e->namelen, e->name, - (int)e->valuelen, e->value); + curlx_dyn_reset(&dbuf); + *err = curlx_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a", + (int)e->namelen, e->name, + (int)e->valuelen, e->value); if(*err) break; - Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&dbuf), - Curl_dyn_len(&dbuf)); + Curl_debug(data, CURLINFO_HEADER_IN, curlx_dyn_ptr(&dbuf), + curlx_dyn_len(&dbuf)); *err = Curl_client_write(data, CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, - Curl_dyn_ptr(&dbuf), Curl_dyn_len(&dbuf)); + curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf)); if(*err) break; } - Curl_dyn_free(&dbuf); + curlx_dyn_free(&dbuf); if(*err) goto out; } @@ -1610,21 +1953,21 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, rv = 0; out: - DEBUGF(LOG_CF(data, cf, "handle_stream_close -> %zd, %d", rv, *err)); + CURL_TRC_CF(data, cf, "handle_stream_close -> %zd, %d", rv, *err); return rv; } static int sweight_wanted(const struct Curl_easy *data) { /* 0 weight is not set by user and we take the nghttp2 default one */ - return data->set.priority.weight? + return data->set.priority.weight ? data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT; } static int sweight_in_effect(const struct Curl_easy *data) { /* 0 weight is not set by user and we take the nghttp2 default one */ - return data->state.priority.weight? + return data->state.priority.weight ? data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT; } @@ -1634,12 +1977,13 @@ static int sweight_in_effect(const struct Curl_easy *data) * struct. */ -static void h2_pri_spec(struct Curl_easy *data, +static void h2_pri_spec(struct cf_h2_ctx *ctx, + struct Curl_easy *data, nghttp2_priority_spec *pri_spec) { struct Curl_data_priority *prio = &data->set.priority; - struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent); - int32_t depstream_id = depstream? depstream->id:0; + struct h2_stream_ctx *depstream = H2_STREAM_CTX(ctx, prio->parent); + int32_t depstream_id = depstream ? depstream->id : 0; nghttp2_priority_spec_init(pri_spec, depstream_id, sweight_wanted(data), data->set.priority.exclusive); @@ -1647,7 +1991,7 @@ static void h2_pri_spec(struct Curl_easy *data, } /* - * Check if there's been an update in the priority / + * Check if there is been an update in the priority / * dependency settings and if so it submits a PRIORITY frame with the updated * info. * Flush any out data pending in the network buffer. @@ -1656,18 +2000,18 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int rv = 0; - if((sweight_wanted(data) != sweight_in_effect(data)) || - (data->set.priority.exclusive != data->state.priority.exclusive) || - (data->set.priority.parent != data->state.priority.parent) ) { + if(stream && stream->id > 0 && + ((sweight_wanted(data) != sweight_in_effect(data)) || + (data->set.priority.exclusive != data->state.priority.exclusive) || + (data->set.priority.parent != data->state.priority.parent)) ) { /* send new weight and/or dependency */ nghttp2_priority_spec pri_spec; - h2_pri_spec(data, &pri_spec); - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] Queuing PRIORITY", - stream->id)); + h2_pri_spec(ctx, 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); @@ -1675,72 +2019,74 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, goto out; } - while(!rv && nghttp2_session_want_write(ctx->h2)) + ctx->nw_out_blocked = 0; + while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2)) rv = nghttp2_session_send(ctx->h2); out: if(nghttp2_is_fatal(rv)) { - DEBUGF(LOG_CF(data, cf, "nghttp2_session_send error (%s)%d", - nghttp2_strerror(rv), rv)); + CURL_TRC_CF(data, cf, "nghttp2_session_send error (%s)%d", + nghttp2_strerror(rv), rv); return CURLE_SEND_ERROR; } + /* Defer flushing during the connect phase so that the SETTINGS and + * other initial frames are sent together with the first request. + * Unless we are 'connect_only' where the request will never come. */ + if(!cf->connected && !cf->conn->connect_only) + return CURLE_OK; return nw_out_flush(cf, data); } static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + struct h2_stream_ctx *stream, char *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); ssize_t nread = -1; + (void)buf; *err = CURLE_AGAIN; - if(!Curl_bufq_is_empty(&stream->recvbuf)) { - nread = Curl_bufq_read(&stream->recvbuf, - (unsigned char *)buf, len, err); - DEBUGF(LOG_CF(data, cf, "recvbuf read(len=%zu) -> %zd, %d", - len, nread, *err)); - if(nread < 0) - goto out; - DEBUGASSERT(nread > 0); + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%d] xfer write failed", stream->id); + *err = stream->xfer_result; + nread = -1; } - - if(nread < 0) { - if(stream->closed) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] returning CLOSE", stream->id)); - nread = http2_handle_stream_close(cf, data, stream, err); - } - else if(stream->reset || - (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || - (ctx->goaway && ctx->last_stream_id < stream->id)) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] returning ERR", stream->id)); - *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; - nread = -1; - } + else if(stream->closed) { + CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id); + nread = http2_handle_stream_close(cf, data, stream, err); } - else if(nread == 0) { - *err = CURLE_AGAIN; + else if(stream->reset || + (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || + (ctx->rcvd_goaway && ctx->remote_max_sid < stream->id)) { + CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2; nread = -1; } -out: - DEBUGF(LOG_CF(data, cf, "stream_recv(len=%zu) -> %zd, %d", - len, nread, *err)); + if(nread < 0 && *err != CURLE_AGAIN) + CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d", + stream->id, len, nread, *err); return nread; } static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + size_t data_max_bytes) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream; + struct h2_stream_ctx *stream; CURLcode result = CURLE_OK; ssize_t nread; + if(should_close_session(ctx)) { + CURL_TRC_CF(data, cf, "progress ingress, session is closed"); + return CURLE_HTTP2; + } + /* Process network input buffer fist */ if(!Curl_bufq_is_empty(&ctx->inbufq)) { - DEBUGF(LOG_CF(data, cf, "Process %zd bytes in connection buffer", - Curl_bufq_len(&ctx->inbufq))); + CURL_TRC_CF(data, cf, "Process %zu bytes in connection buffer", + Curl_bufq_len(&ctx->inbufq)); if(h2_process_pending_input(cf, data, &result) < 0) return result; } @@ -1749,19 +2095,18 @@ 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(data); - if(stream && (stream->closed || Curl_bufq_is_full(&stream->recvbuf))) { + 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. However, * this may leave data in underlying buffers that will not * be consumed. */ if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data)) - break; + drain_stream(cf, data, stream); + break; } - nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); - /* DEBUGF(LOG_CF(data, cf, "read %zd bytes nw data -> %zd, %d", - Curl_bufq_len(&ctx->inbufq), nread, result)); */ + nread = Curl_bufq_sipn(&ctx->inbufq, 0, nw_in_reader, cf, &result); if(nread < 0) { if(result != CURLE_AGAIN) { failf(data, "Failed receiving HTTP2 data: %d(%s)", result, @@ -1771,18 +2116,27 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, break; } else if(nread == 0) { + CURL_TRC_CF(data, cf, "[0] ingress: connection closed"); ctx->conn_closed = TRUE; break; } + else { + CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", nread); + data_max_bytes = (data_max_bytes > (size_t)nread) ? + (data_max_bytes - (size_t)nread) : 0; + } if(h2_process_pending_input(cf, data, &result)) return result; + CURL_TRC_CF(data, cf, "[0] progress ingress: inbufg=%zu", + Curl_bufq_len(&ctx->inbufq)); } if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { connclose(cf->conn, "GOAWAY received"); } + CURL_TRC_CF(data, cf, "[0] progress ingress: done"); return CURLE_OK; } @@ -1790,87 +2144,137 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); ssize_t nread = -1; CURLcode result; struct cf_call_data save; + if(!stream) { + /* Abnormal call sequence: either this transfer has never opened a stream + * (unlikely) or the transfer has been done, cleaned up its resources, but + * a read() is called anyway. It is not clear what the calling sequence + * is for such a case. */ + failf(data, "http/2 recv on a transfer never opened " + "or already cleared, mid=%u", data->mid); + *err = CURLE_HTTP2; + return -1; + } + CF_DATA_SAVE(save, cf, data); - nread = stream_recv(cf, data, buf, len, err); + nread = stream_recv(cf, data, stream, buf, len, err); if(nread < 0 && *err != CURLE_AGAIN) goto out; if(nread < 0) { - *err = h2_progress_ingress(cf, data); + *err = h2_progress_ingress(cf, data, len); if(*err) goto out; - nread = stream_recv(cf, data, buf, len, err); + nread = stream_recv(cf, data, stream, buf, len, err); } if(nread > 0) { - size_t data_consumed = (size_t)nread; /* Now that we transferred this to the upper layer, we report * the actual amount of DATA consumed to the H2 session, so * that it adjusts stream flow control */ - if(stream->resp_hds_len >= data_consumed) { - stream->resp_hds_len -= data_consumed; /* no DATA */ - } - else { - if(stream->resp_hds_len) { - data_consumed -= stream->resp_hds_len; - stream->resp_hds_len = 0; - } - if(data_consumed) { - nghttp2_session_consume(ctx->h2, stream->id, data_consumed); - } - } - + nghttp2_session_consume(ctx->h2, stream->id, (size_t)nread); if(stream->closed) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] closed stream, set drain", - stream->id)); + CURL_TRC_CF(data, cf, "[%d] DRAIN closed stream", stream->id); drain_stream(cf, data, stream); } } out: result = h2_progress_egress(cf, data); - if(result) { + if(result == CURLE_AGAIN) { + /* pending data to send, need to be called again. Ideally, we + * monitor the socket for POLLOUT, but when not SENDING + * any more, we force processing of the transfer. */ + if(!CURL_WANT_SEND(data)) + drain_stream(cf, data, stream); + } + else if(result) { *err = result; nread = -1; } - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_recv(len=%zu) -> %zd %d, " - "buffered=%zu, window=%d/%d, connection %d/%d", - stream->id, len, nread, *err, - Curl_bufq_len(&stream->recvbuf), - nghttp2_session_get_stream_effective_recv_data_length( - ctx->h2, stream->id), - nghttp2_session_get_stream_effective_local_window_size( - ctx->h2, stream->id), - nghttp2_session_get_local_window_size(ctx->h2), - HTTP2_HUGE_WINDOW_SIZE)); + CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, " + "window=%d/%d, connection %d/%d", + stream->id, len, nread, *err, + nghttp2_session_get_stream_effective_recv_data_length( + ctx->h2, stream->id), + nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id), + nghttp2_session_get_local_window_size(ctx->h2), + HTTP2_HUGE_WINDOW_SIZE); CF_DATA_RESTORE(cf, save); return nread; } -static ssize_t h2_submit(struct stream_ctx **pstream, +static ssize_t cf_h2_body_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const void *buf, size_t blen, bool eos, + CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + ssize_t nwritten; + + if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a final + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%d] discarding data" + "on closed stream with response", stream->id); + if(eos) + stream->body_eos = TRUE; + *err = CURLE_OK; + return (ssize_t)blen; + } + /* Server closed before we got a response, this is an error */ + infof(data, "stream %u closed", stream->id); + *err = CURLE_SEND_ERROR; + return -1; + } + + nwritten = Curl_bufq_write(&stream->sendbuf, buf, blen, err); + if(nwritten < 0) + return -1; + + if(eos && (blen == (size_t)nwritten)) + stream->body_eos = TRUE; + + if(eos || !Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + int rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) { + *err = CURLE_SEND_ERROR; + return -1; + } + } + return nwritten; +} + +static ssize_t h2_submit(struct h2_stream_ctx **pstream, struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, + bool eos, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = NULL; - struct h1_req_parser h1; + struct h2_stream_ctx *stream = NULL; struct dynhds h2_headers; nghttp2_nv *nva = NULL; - size_t nheader, i; + const void *body = NULL; + size_t nheader, bodylen, i; nghttp2_data_provider data_prd; int32_t stream_id; nghttp2_priority_spec pri_spec; ssize_t nwritten; - Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); *err = http2_data_setup(cf, data, &stream); @@ -1879,295 +2283,291 @@ static ssize_t h2_submit(struct stream_ctx **pstream, goto out; } - nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err); + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); if(nwritten < 0) goto out; - DEBUGASSERT(h1.done); - DEBUGASSERT(h1.req); + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); - *err = Curl_http_req_to_h2(&h2_headers, h1.req, data); + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); if(*err) { nwritten = -1; goto out; } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); - nheader = Curl_dynhds_count(&h2_headers); - nva = malloc(sizeof(nghttp2_nv) * nheader); + nva = Curl_dynhds_to_nva(&h2_headers, &nheader); if(!nva) { *err = CURLE_OUT_OF_MEMORY; nwritten = -1; goto out; } - for(i = 0; i < nheader; ++i) { - struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); - nva[i].name = (unsigned char *)e->name; - nva[i].namelen = e->namelen; - nva[i].value = (unsigned char *)e->value; - nva[i].valuelen = e->valuelen; - nva[i].flags = NGHTTP2_NV_FLAG_NONE; - } - -#define MAX_ACC 60000 /* <64KB to account for some overhead */ - { - size_t acc = 0; - - for(i = 0; i < nheader; ++i) { - acc += nva[i].namelen + nva[i].valuelen; - - infof(data, "h2 [%.*s: %.*s]", - (int)nva[i].namelen, nva[i].name, - (int)nva[i].valuelen, nva[i].value); - } - - if(acc > MAX_ACC) { - infof(data, "http_request: Warning: The cumulative length of all " - "headers exceeds %d bytes and that could cause the " - "stream to be rejected.", MAX_ACC); - } - } - - h2_pri_spec(data, &pri_spec); - - DEBUGF(LOG_CF(data, cf, "send request allowed %d (easy handle %p)", - nghttp2_session_check_request_allowed(ctx->h2), (void *)data)); + h2_pri_spec(ctx, data, &pri_spec); + if(!nghttp2_session_check_request_allowed(ctx->h2)) + CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)"); switch(data->state.httpreq) { case HTTPREQ_POST: case HTTPREQ_POST_FORM: case HTTPREQ_POST_MIME: case HTTPREQ_PUT: - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown */ - data_prd.read_callback = req_body_read_callback; data_prd.source.ptr = NULL; stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, &data_prd, data); break; default: - stream->upload_left = 0; /* no request body */ stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, NULL, data); } - Curl_safefree(nva); - if(stream_id < 0) { - DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u", - nghttp2_strerror(stream_id), stream_id)); + CURL_TRC_CF(data, cf, "send: nghttp2_submit_request error (%s)%u", + nghttp2_strerror(stream_id), stream_id); *err = CURLE_SEND_ERROR; nwritten = -1; goto out; } - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) submit %s", - stream_id, len, data->state.url)); - infof(data, "Using Stream ID: %u (easy handle %p)", - stream_id, (void *)data); +#define MAX_ACC 60000 /* <64KB to account for some overhead */ + if(Curl_trc_is_verbose(data)) { + size_t acc = 0; + + infof(data, "[HTTP/2] [%d] OPENED stream for %s", + stream_id, data->state.url); + for(i = 0; i < nheader; ++i) { + acc += nva[i].namelen + nva[i].valuelen; + + infof(data, "[HTTP/2] [%d] [%.*s: %.*s]", stream_id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + + if(acc > MAX_ACC) { + infof(data, "[HTTP/2] Warning: The cumulative length of all " + "headers exceeds %d bytes and that could cause the " + "stream to be rejected.", MAX_ACC); + } + } + stream->id = stream_id; + body = (const char *)buf + nwritten; + bodylen = len - nwritten; + + if(bodylen || eos) { + ssize_t n = cf_h2_body_send(cf, data, stream, body, bodylen, eos, err); + if(n >= 0) + nwritten += n; + else if(*err == CURLE_AGAIN) + *err = CURLE_OK; + else if(*err != CURLE_AGAIN) { + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + } + out: - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] submit -> %zd, %d", - stream? stream->id : -1, nwritten, *err)); + CURL_TRC_CF(data, cf, "[%d] submit -> %zd, %d", + stream ? stream->id : -1, nwritten, *err); + Curl_safefree(nva); *pstream = stream; - Curl_h1_req_parse_free(&h1); Curl_dynhds_free(&h2_headers); return nwritten; } static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { - /* - * Currently, we send request in this function, but this function is also - * used to send request body. It would be nice to add dedicated function for - * request. - */ struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); struct cf_call_data save; - int rv; ssize_t nwritten; CURLcode result; CF_DATA_SAVE(save, cf, data); - if(stream && stream->id != -1) { - if(stream->close_handled) { - infof(data, "stream %u closed", stream->id); - *err = CURLE_HTTP2_STREAM; - nwritten = -1; - goto out; - } - else if(stream->closed) { - nwritten = http2_handle_stream_close(cf, data, stream, err); - goto out; - } - /* If stream_id != -1, we have dispatched request HEADERS, and now - are going to send or sending request body in DATA frame */ - nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); + if(!stream || stream->id == -1) { + nwritten = h2_submit(&stream, cf, data, buf, len, eos, err); if(nwritten < 0) { - if(*err != CURLE_AGAIN) - goto out; - nwritten = 0; - } - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] bufq_write(len=%zu) -> %zd, %d", - stream->id, len, nwritten, *err)); - - if(!Curl_bufq_is_empty(&stream->sendbuf)) { - rv = nghttp2_session_resume_data(ctx->h2, stream->id); - if(nghttp2_is_fatal(rv)) { - *err = CURLE_SEND_ERROR; - nwritten = -1; - goto out; - } - } - - result = h2_progress_ingress(cf, data); - if(result) { - *err = result; - nwritten = -1; goto out; } - - result = h2_progress_egress(cf, data); - if(result) { - *err = result; - nwritten = -1; + DEBUGASSERT(stream); + } + else if(stream->body_eos) { + /* We already wrote this, but CURLE_AGAINed the call due to not + * being able to flush stream->sendbuf. Make a 0-length write + * to trigger flushing again. + * If this works, we report to have written `len` bytes. */ + DEBUGASSERT(eos); + nwritten = cf_h2_body_send(cf, data, stream, buf, 0, eos, err); + CURL_TRC_CF(data, cf, "[%d] cf_body_send last CHUNK -> %zd, %d, eos=%d", + stream->id, nwritten, *err, eos); + if(nwritten < 0) { goto out; } + nwritten = len; + } + else { + nwritten = cf_h2_body_send(cf, data, stream, buf, len, eos, err); + CURL_TRC_CF(data, cf, "[%d] cf_body_send(len=%zu) -> %zd, %d, eos=%d", + stream->id, len, nwritten, *err, eos); + } - if(should_close_session(ctx)) { - if(stream->closed) { - nwritten = http2_handle_stream_close(cf, data, stream, err); - } - else { - DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session")); - *err = CURLE_HTTP2; - nwritten = -1; - } - goto out; - } + /* Call the nghttp2 send loop and flush to write ALL buffered data, + * headers and/or request body completely out to the network */ + result = h2_progress_egress(cf, data); - if(!nwritten) { - size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->id); - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send: win %u/%zu", - stream->id, - nghttp2_session_get_remote_window_size(ctx->h2), rwin)); - if(rwin == 0) { - /* We cannot upload more as the stream's remote window size - * is 0. We need to receive WIN_UPDATEs before we can continue. - */ - data->req.keepon |= KEEP_SEND_HOLD; - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] holding send as remote flow " - "window is exhausted", stream->id)); - } - nwritten = -1; - *err = CURLE_AGAIN; - } - /* handled writing BODY for open stream. */ + /* if the stream has been closed in egress handling (nghttp2 does that + * when it does not like the headers, for example */ + if(stream && stream->closed) { + infof(data, "stream %u closed", stream->id); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + else if(result && (result != CURLE_AGAIN)) { + *err = result; + nwritten = -1; goto out; } - else { - nwritten = h2_submit(&stream, cf, data, buf, len, err); - if(nwritten < 0) { - goto out; - } - result = h2_progress_ingress(cf, data); - if(result) { - *err = result; - nwritten = -1; - goto out; + if(should_close_session(ctx)) { + /* nghttp2 thinks this session is done. If the stream has not been + * closed, this is an error state for out transfer */ + if(stream && stream->closed) { + nwritten = http2_handle_stream_close(cf, data, stream, err); } - - result = h2_progress_egress(cf, data); - if(result) { - *err = result; + else { + CURL_TRC_CF(data, cf, "send: nothing to do in this session"); + *err = CURLE_HTTP2; nwritten = -1; - goto out; - } - - if(should_close_session(ctx)) { - if(stream->closed) { - nwritten = http2_handle_stream_close(cf, data, stream, err); - } - else { - DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session")); - *err = CURLE_HTTP2; - nwritten = -1; - } - goto out; } } out: if(stream) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) -> %zd, %d, " - "buffered=%zu, upload_left=%zu, stream-window=%d, " - "connection-window=%d", - stream->id, len, nwritten, *err, - Curl_bufq_len(&stream->sendbuf), - (ssize_t)stream->upload_left, - nghttp2_session_get_stream_remote_window_size( - ctx->h2, stream->id), - nghttp2_session_get_remote_window_size(ctx->h2))); - drain_stream(cf, data, stream); + CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, " + "eos=%d, h2 windows %d-%d (stream-conn), " + "buffers %zu-%zu (stream-conn)", + stream->id, len, nwritten, *err, + stream->body_eos, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, stream->id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&stream->sendbuf), + Curl_bufq_len(&ctx->outbufq)); } else { - DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) -> %zd, %d, " - "connection-window=%d", - len, nwritten, *err, - nghttp2_session_get_remote_window_size(ctx->h2))); + CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %zd, %d, " + "connection-window=%d, nw_send_buffer(%zu)", + len, nwritten, *err, + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->outbufq)); } CF_DATA_RESTORE(cf, save); return nwritten; } -static int cf_h2_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *sock) +static CURLcode cf_h2_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct SingleRequest *k = &data->req; - struct stream_ctx *stream = H2_STREAM_CTX(data); - int bitmap = GETSOCK_BLANK; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); struct cf_call_data save; + CURLcode result = CURLE_OK; CF_DATA_SAVE(save, cf, data); - sock[0] = Curl_conn_cf_get_socket(cf, data); - - if(!(k->keepon & (KEEP_RECV_PAUSE|KEEP_RECV_HOLD))) - /* Unless paused - in an HTTP/2 connection we can basically always get a - frame so we should always be ready for one */ - bitmap |= GETSOCK_READSOCK(0); - - /* we're (still uploading OR the HTTP/2 layer wants to send data) AND - there's a window to send data in */ - if((((k->keepon & KEEP_SENDBITS) == KEEP_SEND) || - nghttp2_session_want_write(ctx->h2)) && - (nghttp2_session_get_remote_window_size(ctx->h2) && - nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->id))) - bitmap |= GETSOCK_WRITESOCK(0); + if(stream && !Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + int rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) { + result = CURLE_SEND_ERROR; + goto out; + } + } + + result = h2_progress_egress(cf, data); +out: + if(stream) { + CURL_TRC_CF(data, cf, "[%d] flush -> %d, " + "h2 windows %d-%d (stream-conn), " + "buffers %zu-%zu (stream-conn)", + stream->id, result, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, stream->id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&stream->sendbuf), + Curl_bufq_len(&ctx->outbufq)); + } + else { + CURL_TRC_CF(data, cf, "flush -> %d, " + "connection-window=%d, nw_send_buffer(%zu)", + result, nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->outbufq)); + } CF_DATA_RESTORE(cf, save); - return bitmap; + return result; } +static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; + curl_socket_t sock; + bool want_recv, want_send; + + if(!ctx->h2) + return; + + sock = Curl_conn_cf_get_socket(cf, data); + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + if(want_recv || want_send) { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = want_send && !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = want_send && stream && stream->id >= 0 && + !nghttp2_session_get_stream_remote_window_size(ctx->h2, + stream->id); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + (!c_exhaust && nghttp2_session_want_write(ctx->h2)) || + !Curl_bufq_is_empty(&ctx->outbufq); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } + else if(ctx->sent_goaway && !cf->shutdown) { + /* shutdown in progress */ + CF_DATA_SAVE(save, cf, data); + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq); + want_recv = nghttp2_session_want_read(ctx->h2); + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } +} static CURLcode cf_h2_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_h2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; struct cf_call_data save; + bool first_time = FALSE; if(cf->connected) { *done = TRUE; @@ -2176,7 +2576,7 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf, /* Connect the lower filters first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } @@ -2184,18 +2584,24 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf, *done = FALSE; CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(ctx->initialized); if(!ctx->h2) { - result = cf_h2_ctx_init(cf, data, FALSE); + result = cf_h2_ctx_open(cf, data); if(result) goto out; + first_time = TRUE; } - result = h2_progress_ingress(cf, data); - if(result) - goto out; + if(!first_time) { + result = h2_progress_ingress(cf, data, H2_CHUNK_SIZE); + if(result) + goto out; + } + /* Send out our SETTINGS and ACKs and such. If that blocks, we + * have it buffered and can count this filter as being connected */ result = h2_progress_egress(cf, data); - if(result) + if(result && (result != CURLE_AGAIN)) goto out; *done = TRUE; @@ -2203,6 +2609,7 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf, result = CURLE_OK; out: + CURL_TRC_CF(data, cf, "cf_connect() -> %d, %d, ", result, *done); CF_DATA_RESTORE(cf, save); return result; } @@ -2215,9 +2622,12 @@ static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - cf_h2_ctx_clear(ctx); + cf_h2_ctx_close(ctx); CF_DATA_RESTORE(cf, save); + cf->connected = FALSE; } + if(cf->next) + cf->next->cft->do_close(cf->next, data); } static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -2231,37 +2641,75 @@ static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) } } +static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result; + int rv; + + if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + + if(!ctx->sent_goaway) { + ctx->sent_goaway = TRUE; + rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, + ctx->local_max_sid, 0, + (const uint8_t *)"shutdown", + sizeof("shutdown")); + if(rv) { + failf(data, "nghttp2_submit_goaway() failed: %s(%d)", + nghttp2_strerror(rv), rv); + result = CURLE_SEND_ERROR; + goto out; + } + } + /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ + result = CURLE_OK; + if(nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq)) + result = h2_progress_egress(cf, data); + if(!result && nghttp2_session_want_read(ctx->h2)) + result = h2_progress_ingress(cf, data, 0); + + if(result == CURLE_AGAIN) + result = CURLE_OK; + + *done = (ctx->conn_closed || + (!result && !nghttp2_session_want_write(ctx->h2) && + !nghttp2_session_want_read(ctx->h2) && + Curl_bufq_is_empty(&ctx->outbufq))); + +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + static CURLcode http2_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) { -#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); DEBUGASSERT(data); if(ctx && ctx->h2 && stream) { - uint32_t window = !pause * H2_STREAM_WINDOW_SIZE; CURLcode result; - int rv = nghttp2_session_set_local_window_size(ctx->h2, - NGHTTP2_FLAG_NONE, - stream->id, - window); - if(rv) { - failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; - } - - if(!pause) - drain_stream(cf, data, stream); - - /* make sure the window update gets sent */ - result = h2_progress_egress(cf, data); + stream->write_paused = pause; + result = cf_h2_update_local_win(cf, data, stream); if(result) return result; + /* attempt to send the window update */ + (void)h2_progress_egress(cf, data); + if(!pause) { /* Unpausing a h2 transfer, requires it to be run again. The server * may send new DATA on us increasing the flow window, and it may @@ -2271,21 +2719,9 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, drain_stream(cf, data, stream); Curl_expire(data, 0, EXPIRE_RUN_NOW); } - DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u", - window, stream->id)); - -#ifdef DEBUGBUILD - { - /* read out the stream local window again */ - uint32_t window2 = - nghttp2_session_get_stream_local_window_size(ctx->h2, - stream->id); - DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u", - window2, stream->id)); - } -#endif + CURL_TRC_CF(data, cf, "[%d] stream now %spaused", stream->id, + pause ? "" : "un"); } -#endif return CURLE_OK; } @@ -2305,14 +2741,12 @@ static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf, case CF_CTRL_DATA_PAUSE: result = http2_data_pause(cf, data, (arg1 != 0)); break; - case CF_CTRL_DATA_DONE_SEND: { - result = http2_data_done_send(cf, data); + case CF_CTRL_FLUSH: + result = cf_h2_flush(cf, data); break; - } - case CF_CTRL_DATA_DONE: { - http2_data_done(cf, data, arg1 != 0); + case CF_CTRL_DATA_DONE: + http2_data_done(cf, data); break; - } default: break; } @@ -2324,13 +2758,10 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); - if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq) - || (stream && !Curl_bufq_is_empty(&stream->sendbuf)) - || (stream && !Curl_bufq_is_empty(&stream->recvbuf)))) + if(ctx && !Curl_bufq_is_empty(&ctx->inbufq)) return TRUE; - return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } static bool cf_h2_is_alive(struct Curl_cfilter *cf, @@ -2343,8 +2774,8 @@ static bool cf_h2_is_alive(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); result = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending)); - DEBUGF(LOG_CF(data, cf, "conn alive -> %d, input_pending=%d", - result, *input_pending)); + CURL_TRC_CF(data, cf, "conn alive -> %d, input_pending=%d", + result, *input_pending); CF_DATA_RESTORE(cf, save); return result; } @@ -2376,31 +2807,49 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { /* the limit is what we have in use right now */ - effective_max = CONN_INUSE(cf->conn); + effective_max = CONN_ATTACHED(cf->conn); } else { effective_max = ctx->max_concurrent_streams; } - *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max; + *pres1 = (effective_max > INT_MAX) ? INT_MAX : (int)effective_max; CF_DATA_RESTORE(cf, save); return CURLE_OK; + case CF_QUERY_STREAM_ERROR: { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + *pres1 = stream ? (int)stream->error : 0; + return CURLE_OK; + } + case CF_QUERY_NEED_FLUSH: { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(!Curl_bufq_is_empty(&ctx->outbufq) || + (stream && !Curl_bufq_is_empty(&stream->sendbuf))) { + *pres1 = TRUE; + return CURLE_OK; + } + break; + } + case CF_QUERY_HTTP_VERSION: + *pres1 = 20; + return CURLE_OK; default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } struct Curl_cftype Curl_cft_nghttp2 = { "HTTP/2", - CF_TYPE_MULTIPLEX, - CURL_LOG_DEFAULT, + CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, + CURL_LOG_LVL_NONE, cf_h2_destroy, cf_h2_connect, cf_h2_close, + cf_h2_shutdown, Curl_cf_def_get_host, - cf_h2_get_select_socks, + cf_h2_adjust_pollset, cf_h2_data_pending, cf_h2_send, cf_h2_recv, @@ -2413,49 +2862,53 @@ struct Curl_cftype Curl_cft_nghttp2 = { static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int sockindex, + bool via_h1_upgrade) { struct Curl_cfilter *cf = NULL; struct cf_h2_ctx *ctx; CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(data->conn); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) goto out; + cf_h2_ctx_init(ctx, via_h1_upgrade); result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx); if(result) goto out; + ctx = NULL; Curl_conn_cf_add(data, conn, sockindex, cf); - result = CURLE_OK; out: if(result) cf_h2_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + bool via_h1_upgrade) { struct Curl_cfilter *cf_h2 = NULL; struct cf_h2_ctx *ctx; CURLcode result = CURLE_OUT_OF_MEMORY; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) goto out; + cf_h2_ctx_init(ctx, via_h1_upgrade); result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx); if(result) goto out; + ctx = NULL; Curl_conn_cf_insert_after(cf, cf_h2); - result = CURLE_OK; out: if(result) @@ -2463,36 +2916,14 @@ static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, return result; } -static bool Curl_cf_is_http2(struct Curl_cfilter *cf, - const struct Curl_easy *data) -{ - (void)data; - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_nghttp2) - return TRUE; - if(cf->cft->flags & CF_TYPE_IP_CONNECT) - return FALSE; - } - return FALSE; -} - -bool Curl_conn_is_http2(const struct Curl_easy *data, - const struct connectdata *conn, - int sockindex) -{ - return conn? Curl_cf_is_http2(conn->cfilter[sockindex], data) : FALSE; -} - -bool Curl_http2_may_switch(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) +bool Curl_http2_may_switch(struct Curl_easy *data) { - (void)sockindex; - if(!Curl_conn_is_http2(data, conn, sockindex) && - data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { + if(Curl_conn_http_version(data, data->conn) < 20 && + (data->state.http_neg.wanted & CURL_HTTP_V2x) && + data->state.http_neg.h2_prior_knowledge) { #ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* We don't support HTTP/2 proxies yet. Also it's debatable + if(data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy) { + /* We do not support HTTP/2 proxies yet. Also it is debatable whether or not this setting should apply to HTTP/2 proxies. */ infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); return FALSE; @@ -2503,31 +2934,24 @@ bool Curl_http2_may_switch(struct Curl_easy *data, return FALSE; } -CURLcode Curl_http2_switch(struct Curl_easy *data, - struct connectdata *conn, int sockindex) +CURLcode Curl_http2_switch(struct Curl_easy *data) { struct Curl_cfilter *cf; CURLcode result; - DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); - DEBUGF(infof(data, DMSGI(data, sockindex, "switching to HTTP/2"))); + DEBUGASSERT(Curl_conn_http_version(data, data->conn) < 20); - result = http2_cfilter_add(&cf, data, conn, sockindex); + result = http2_cfilter_add(&cf, data, data->conn, FIRSTSOCKET, FALSE); if(result) return result; + CURL_TRC_CF(data, cf, "switching connection to HTTP/2"); - result = cf_h2_ctx_init(cf, data, FALSE); - if(result) - return result; - - conn->httpversion = 20; /* we know we're on HTTP/2 now */ - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - multi_connchanged(data->multi); + data->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + Curl_multi_connchanged(data->multi); if(cf->next) { bool done; - return Curl_conn_cf_connect(cf, data, FALSE, &done); + return Curl_conn_cf_connect(cf, data, &done); } return CURLE_OK; } @@ -2537,25 +2961,19 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cfilter *cf_h2; CURLcode result; - DEBUGASSERT(!Curl_cf_is_http2(cf, data)); + DEBUGASSERT(Curl_conn_http_version(data, data->conn) < 20); - result = http2_cfilter_insert_after(cf, data); + result = http2_cfilter_insert_after(cf, data, FALSE); if(result) return result; cf_h2 = cf->next; - result = cf_h2_ctx_init(cf_h2, data, FALSE); - if(result) - return result; - - cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */ cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - multi_connchanged(data->multi); + Curl_multi_connchanged(data->multi); if(cf_h2->next) { bool done; - return Curl_conn_cf_connect(cf_h2, data, FALSE, &done); + return Curl_conn_cf_connect(cf_h2, data, &done); } return CURLE_OK; } @@ -2568,21 +2986,17 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, struct cf_h2_ctx *ctx; CURLcode result; - DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); - DEBUGF(infof(data, DMSGI(data, sockindex, "upgrading to HTTP/2"))); + DEBUGASSERT(Curl_conn_http_version(data, conn) < 20); DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); - result = http2_cfilter_add(&cf, data, conn, sockindex); + result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE); if(result) return result; + CURL_TRC_CF(data, cf, "upgrading connection to HTTP/2"); DEBUGASSERT(cf->cft == &Curl_cft_nghttp2); ctx = cf->ctx; - result = cf_h2_ctx_init(cf, data, TRUE); - if(result) - return result; - if(nread > 0) { /* Remaining data from the protocol switch reply is already using * the switched protocol, ie. HTTP/2. We add that to the network @@ -2605,14 +3019,12 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, " after upgrade: len=%zu", nread); } - conn->httpversion = 20; /* we know we're on HTTP/2 now */ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - multi_connchanged(data->multi); + Curl_multi_connchanged(data->multi); if(cf->next) { bool done; - return Curl_conn_cf_connect(cf, data, FALSE, &done); + return Curl_conn_cf_connect(cf, data, &done); } return CURLE_OK; } @@ -2621,8 +3033,35 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, CURLE_HTTP2_STREAM error! */ bool Curl_h2_http_1_1_error(struct Curl_easy *data) { - struct stream_ctx *stream = H2_STREAM_CTX(data); - return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED); + if(Curl_conn_http_version(data, data->conn) == 20) { + int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET); + return err == NGHTTP2_HTTP_1_1_REQUIRED; + } + return FALSE; +} + +void *Curl_nghttp2_malloc(size_t size, void *user_data) +{ + (void)user_data; + return Curl_cmalloc(size); +} + +void Curl_nghttp2_free(void *ptr, void *user_data) +{ + (void)user_data; + Curl_cfree(ptr); +} + +void *Curl_nghttp2_calloc(size_t nmemb, size_t size, void *user_data) +{ + (void)user_data; + return Curl_ccalloc(nmemb, size); +} + +void *Curl_nghttp2_realloc(void *ptr, size_t size, void *user_data) +{ + (void)user_data; + return Curl_crealloc(ptr, size); } #else /* !USE_NGHTTP2 */ diff --git a/Utilities/cmcurl/lib/http2.h b/Utilities/cmcurl/lib/http2.h index 80e183480a7..93cc2d44f25 100644 --- a/Utilities/cmcurl/lib/http2.h +++ b/Utilities/cmcurl/lib/http2.h @@ -44,15 +44,9 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, /* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */ bool Curl_h2_http_1_1_error(struct Curl_easy *data); -bool Curl_conn_is_http2(const struct Curl_easy *data, - const struct connectdata *conn, - int sockindex); -bool Curl_http2_may_switch(struct Curl_easy *data, - struct connectdata *conn, - int sockindex); +bool Curl_http2_may_switch(struct Curl_easy *data); -CURLcode Curl_http2_switch(struct Curl_easy *data, - struct connectdata *conn, int sockindex); +CURLcode Curl_http2_switch(struct Curl_easy *data); CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data); @@ -60,16 +54,19 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, struct connectdata *conn, int sockindex, const char *ptr, size_t nread); +void *Curl_nghttp2_malloc(size_t size, void *user_data); +void Curl_nghttp2_free(void *ptr, void *user_data); +void *Curl_nghttp2_calloc(size_t nmemb, size_t size, void *user_data); +void *Curl_nghttp2_realloc(void *ptr, size_t size, void *user_data); + extern struct Curl_cftype Curl_cft_nghttp2; #else /* USE_NGHTTP2 */ -#define Curl_cf_is_http2(a,b) FALSE -#define Curl_conn_is_http2(a,b,c) FALSE -#define Curl_http2_may_switch(a,b,c) FALSE +#define Curl_http2_may_switch(a) FALSE #define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL -#define Curl_http2_switch(a,b,c) CURLE_UNSUPPORTED_PROTOCOL +#define Curl_http2_switch(a) CURLE_UNSUPPORTED_PROTOCOL #define Curl_http2_upgrade(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL #define Curl_h2_http_1_1_error(x) 0 #endif diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c index 806016253ff..74253823591 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.c +++ b/Utilities/cmcurl/lib/http_aws_sigv4.c @@ -9,7 +9,7 @@ * * 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.haxx.se/docs/copyright.html. + * 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 @@ -24,7 +24,7 @@ #include "curl_setup.h" -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) #include "urldata.h" #include "strcase.h" @@ -34,6 +34,8 @@ #include "transfer.h" #include "parsedate.h" #include "sendf.h" +#include "escape.h" +#include "curlx/strparse.h" #include @@ -44,30 +46,48 @@ #include "slist.h" -#define HMAC_SHA256(k, kl, d, dl, o) \ - do { \ - ret = Curl_hmacit(Curl_HMAC_SHA256, \ - (unsigned char *)k, \ - kl, \ - (unsigned char *)d, \ - dl, o); \ - if(ret) { \ - goto fail; \ - } \ +#define HMAC_SHA256(k, kl, d, dl, o) \ + do { \ + result = Curl_hmacit(&Curl_HMAC_SHA256, \ + (const unsigned char *)k, \ + kl, \ + (const unsigned char *)d, \ + dl, o); \ + if(result) { \ + goto fail; \ + } \ } while(0) #define TIMESTAMP_SIZE 17 /* hex-encoded with trailing null */ -#define SHA256_HEX_LENGTH (2 * SHA256_DIGEST_LENGTH + 1) +#define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1) + +#define MAX_QUERY_COMPONENTS 128 + +struct pair { + struct dynbuf key; + struct dynbuf value; +}; + +static void dyn_array_free(struct dynbuf *db, size_t num_elements); +static void pair_array_free(struct pair *pair_array, size_t num_elements); +static CURLcode split_to_dyn_array(const char *source, + struct dynbuf db[MAX_QUERY_COMPONENTS], + size_t *num_splits); +static bool is_reserved_char(const char c); +static CURLcode uri_encode_path(struct Curl_str *original_path, + struct dynbuf *new_path); +static CURLcode encode_query_component(char *component, size_t len, + struct dynbuf *db); +static CURLcode http_aws_decode_encode(const char *in, size_t in_len, + struct dynbuf *out); +static bool should_urlencode(struct Curl_str *service_name); static void sha256_to_hex(char *dst, unsigned char *sha) { - int i; - - for(i = 0; i < SHA256_DIGEST_LENGTH; ++i) { - msnprintf(dst + (i * 2), SHA256_HEX_LENGTH - (i * 2), "%02x", sha[i]); - } + Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH, + (unsigned char *)dst, SHA256_HEX_LENGTH); } static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr) @@ -84,7 +104,7 @@ static void trim_headers(struct curl_slist *head) { struct curl_slist *l; for(l = head; l; l = l->next) { - char *value; /* to read from */ + const char *value; /* to read from */ char *store; size_t colon = strcspn(l->data, ":"); Curl_strntolower(l->data, l->data, colon); @@ -93,15 +113,14 @@ static void trim_headers(struct curl_slist *head) if(!*value) continue; ++value; - store = value; + store = (char *)CURL_UNCONST(value); /* skip leading whitespace */ - while(*value && ISBLANK(*value)) - value++; + curlx_str_passblanks(&value); while(*value) { int space = 0; - while(*value && ISBLANK(*value)) { + while(ISBLANK(*value)) { value++; space++; } @@ -114,28 +133,105 @@ static void trim_headers(struct curl_slist *head) else *store++ = *value++; } - *store = 0; /* null terminate */ + *store = 0; /* null-terminate */ } } /* maximum length for the aws sivg4 parts */ #define MAX_SIGV4_LEN 64 -#define MAX_SIGV4_LEN_TXT "64" - #define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date")) -#define MAX_HOST_LEN 255 -/* FQDN + host: */ -#define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:")) - /* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */ #define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1) +/* alphabetically compare two headers by their name, expecting + headers to use ':' at this point */ +static int compare_header_names(const char *a, const char *b) +{ + const char *colon_a; + const char *colon_b; + size_t len_a; + size_t len_b; + size_t min_len; + int cmp; + + colon_a = strchr(a, ':'); + colon_b = strchr(b, ':'); + + DEBUGASSERT(colon_a); + DEBUGASSERT(colon_b); + + len_a = colon_a ? (size_t)(colon_a - a) : strlen(a); + len_b = colon_b ? (size_t)(colon_b - b) : strlen(b); + + min_len = (len_a < len_b) ? len_a : len_b; + + cmp = strncmp(a, b, min_len); + + /* return the shorter of the two if one is shorter */ + if(!cmp) + return (int)(len_a - len_b); + + return cmp; +} + +/* Merge duplicate header definitions by comma delimiting their values + in the order defined the headers are defined, expecting headers to + be alpha-sorted and use ':' at this point */ +static CURLcode merge_duplicate_headers(struct curl_slist *head) +{ + struct curl_slist *curr = head; + CURLcode result = CURLE_OK; + + while(curr) { + struct curl_slist *next = curr->next; + if(!next) + break; + + if(compare_header_names(curr->data, next->data) == 0) { + struct dynbuf buf; + char *colon_next; + char *val_next; + + curlx_dyn_init(&buf, CURL_MAX_HTTP_HEADER); + + result = curlx_dyn_add(&buf, curr->data); + if(result) + return result; + + colon_next = strchr(next->data, ':'); + DEBUGASSERT(colon_next); + val_next = colon_next + 1; + + result = curlx_dyn_addn(&buf, ",", 1); + if(result) + return result; + + result = curlx_dyn_add(&buf, val_next); + if(result) + return result; + + free(curr->data); + curr->data = curlx_dyn_ptr(&buf); + + curr->next = next->next; + free(next->data); + free(next); + } + else { + curr = curr->next; + } + } + + return CURLE_OK; +} + /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */ static CURLcode make_headers(struct Curl_easy *data, const char *hostname, char *timestamp, - char *provider1, + const char *provider1, + size_t plen, /* length of provider1 */ char **date_header, char *content_sha256_header, struct dynbuf *canonical_headers, @@ -147,48 +243,36 @@ static CURLcode make_headers(struct Curl_easy *data, struct curl_slist *tmp_head = NULL; CURLcode ret = CURLE_OUT_OF_MEMORY; struct curl_slist *l; - int again = 1; + bool again = TRUE; - /* provider1 mid */ - Curl_strntolower(provider1, provider1, strlen(provider1)); - provider1[0] = Curl_raw_toupper(provider1[0]); + msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%.*s-Date", + (int)plen, provider1); + /* provider1 ucfirst */ + Curl_strntolower(&date_hdr_key[2], provider1, plen); + date_hdr_key[2] = Curl_raw_toupper(provider1[0]); - msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1); - - /* provider1 lowercase */ - Curl_strntolower(provider1, provider1, 1); /* first byte only */ msnprintf(date_full_hdr, DATE_FULL_HDR_LEN, - "x-%s-date:%s", provider1, timestamp); + "x-%.*s-date:%s", (int)plen, provider1, timestamp); + /* provider1 lowercase */ + Curl_strntolower(&date_full_hdr[2], provider1, plen); - if(Curl_checkheaders(data, STRCONST("Host"))) { - head = NULL; - } - else { - char full_host[FULL_HOST_LEN + 1]; + if(!Curl_checkheaders(data, STRCONST("Host"))) { + char *fullhost; if(data->state.aptr.host) { - size_t pos; - - if(strlen(data->state.aptr.host) > FULL_HOST_LEN) { - ret = CURLE_URL_MALFORMAT; - goto fail; - } - strcpy(full_host, data->state.aptr.host); /* remove /r/n as the separator for canonical request must be '\n' */ - pos = strcspn(full_host, "\n\r"); - full_host[pos] = 0; - } - else { - if(strlen(hostname) > MAX_HOST_LEN) { - ret = CURLE_URL_MALFORMAT; - goto fail; - } - msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname); + size_t pos = strcspn(data->state.aptr.host, "\n\r"); + fullhost = Curl_memdup0(data->state.aptr.host, pos); } + else + fullhost = aprintf("host:%s", hostname); - head = curl_slist_append(NULL, full_host); - if(!head) + if(fullhost) + head = Curl_slist_append_nodup(NULL, fullhost); + if(!head) { + free(fullhost); goto fail; + } } @@ -199,10 +283,41 @@ static CURLcode make_headers(struct Curl_easy *data, head = tmp_head; } + /* copy user headers to our header list. the logic is based on how http.c + handles user headers. + + user headers in format 'name:' with no value are used to signal that an + internal header of that name should be removed. those user headers are not + added to this list. + + user headers in format 'name;' with no value are used to signal that a + header of that name with no value should be sent. those user headers are + added to this list but in the format that they will be sent, ie the + semi-colon is changed to a colon for format 'name:'. + + user headers with a value of whitespace only, or without a colon or + semi-colon, are not added to this list. + */ for(l = data->set.headers; l; l = l->next) { - tmp_head = curl_slist_append(head, l->data); - if(!tmp_head) + char *dupdata, *ptr; + char *sep = strchr(l->data, ':'); + if(!sep) + sep = strchr(l->data, ';'); + if(!sep || (*sep == ':' && !*(sep + 1))) + continue; + for(ptr = sep + 1; ISBLANK(*ptr); ++ptr) + ; + if(!*ptr && ptr != sep + 1) /* a value of whitespace only */ + continue; + dupdata = strdup(l->data); + if(!dupdata) + goto fail; + dupdata[sep - l->data] = ':'; + tmp_head = Curl_slist_append_nodup(head, dupdata); + if(!tmp_head) { + free(dupdata); goto fail; + } head = tmp_head; } @@ -214,47 +329,58 @@ static CURLcode make_headers(struct Curl_easy *data, if(!tmp_head) goto fail; head = tmp_head; - *date_header = curl_maprintf("%s: %s", date_hdr_key, timestamp); + *date_header = aprintf("%s: %s\r\n", date_hdr_key, timestamp); } else { - char *value; - - *date_header = strdup(*date_header); - if(!*date_header) - goto fail; - + const char *value; + const char *endp; value = strchr(*date_header, ':'); - if(!value) + if(!value) { + *date_header = NULL; goto fail; + } ++value; - while(ISBLANK(*value)) - ++value; - strncpy(timestamp, value, TIMESTAMP_SIZE - 1); - timestamp[TIMESTAMP_SIZE - 1] = 0; + curlx_str_passblanks(&value); + endp = value; + while(*endp && ISALNUM(*endp)) + ++endp; + /* 16 bytes => "19700101T000000Z" */ + if((endp - value) == TIMESTAMP_SIZE - 1) { + memcpy(timestamp, value, TIMESTAMP_SIZE - 1); + timestamp[TIMESTAMP_SIZE - 1] = 0; + } + else + /* bad timestamp length */ + timestamp[0] = 0; + *date_header = NULL; } - /* alpha-sort in a case sensitive manner */ + /* alpha-sort by header name in a case sensitive manner */ do { - again = 0; + again = FALSE; for(l = head; l; l = l->next) { struct curl_slist *next = l->next; - if(next && strcmp(l->data, next->data) > 0) { + if(next && compare_header_names(l->data, next->data) > 0) { char *tmp = l->data; l->data = next->data; next->data = tmp; - again = 1; + again = TRUE; } } } while(again); + ret = merge_duplicate_headers(head); + if(ret) + goto fail; + for(l = head; l; l = l->next) { char *tmp; - if(Curl_dyn_add(canonical_headers, l->data)) + if(curlx_dyn_add(canonical_headers, l->data)) goto fail; - if(Curl_dyn_add(canonical_headers, "\n")) + if(curlx_dyn_add(canonical_headers, "\n")) goto fail; tmp = strchr(l->data, ':'); @@ -262,10 +388,10 @@ static CURLcode make_headers(struct Curl_easy *data, *tmp = 0; if(l != head) { - if(Curl_dyn_add(signed_headers, ";")) + if(curlx_dyn_add(signed_headers, ";")) goto fail; } - if(Curl_dyn_add(signed_headers, l->data)) + if(curlx_dyn_add(signed_headers, l->data)) goto fail; } @@ -282,16 +408,17 @@ static CURLcode make_headers(struct Curl_easy *data, SHA256_HEX_LENGTH) /* try to parse a payload hash from the content-sha256 header */ -static char *parse_content_sha_hdr(struct Curl_easy *data, - const char *provider1, - size_t *value_len) -{ +static const char *parse_content_sha_hdr(struct Curl_easy *data, + const char *provider1, + size_t plen, + size_t *value_len) { char key[CONTENT_SHA256_KEY_LEN]; size_t key_len; - char *value; + const char *value; size_t len; - key_len = msnprintf(key, sizeof(key), "x-%s-content-sha256", provider1); + key_len = msnprintf(key, sizeof(key), "x-%.*s-content-sha256", + (int)plen, provider1); value = Curl_checkheaders(data, key, key_len); if(!value) @@ -302,8 +429,7 @@ static char *parse_content_sha_hdr(struct Curl_easy *data, return NULL; ++value; - while(*value && ISBLANK(*value)) - ++value; + curlx_str_passblanks(&value); len = strlen(value); while(len > 0 && ISBLANK(value[len-1])) @@ -336,7 +462,9 @@ static CURLcode calc_payload_hash(struct Curl_easy *data, #define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD" static CURLcode calc_s3_payload_hash(struct Curl_easy *data, - Curl_HttpReq httpreq, char *provider1, + Curl_HttpReq httpreq, + const char *provider1, + size_t plen, unsigned char *sha_hash, char *sha_hex, char *header) { @@ -363,24 +491,192 @@ static CURLcode calc_s3_payload_hash(struct Curl_easy *data, /* format the required content-sha256 header */ msnprintf(header, CONTENT_SHA256_HDR_LEN, - "x-%s-content-sha256: %s", provider1, sha_hex); + "x-%.*s-content-sha256: %s", (int)plen, provider1, sha_hex); ret = CURLE_OK; fail: return ret; } -CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) +static int compare_func(const void *a, const void *b) { - CURLcode ret = CURLE_OUT_OF_MEMORY; + + const struct pair *aa = a; + const struct pair *bb = b; + const size_t aa_key_len = curlx_dyn_len(&aa->key); + const size_t bb_key_len = curlx_dyn_len(&bb->key); + const size_t aa_value_len = curlx_dyn_len(&aa->value); + const size_t bb_value_len = curlx_dyn_len(&bb->value); + int compare; + + /* If one element is empty, the other is always sorted higher */ + + /* Compare keys */ + if((aa_key_len == 0) && (bb_key_len == 0)) + return 0; + if(aa_key_len == 0) + return -1; + if(bb_key_len == 0) + return 1; + compare = strcmp(curlx_dyn_ptr(&aa->key), curlx_dyn_ptr(&bb->key)); + if(compare) { + return compare; + } + + /* Compare values */ + if((aa_value_len == 0) && (bb_value_len == 0)) + return 0; + if(aa_value_len == 0) + return -1; + if(bb_value_len == 0) + return 1; + compare = strcmp(curlx_dyn_ptr(&aa->value), curlx_dyn_ptr(&bb->value)); + + return compare; + +} + +UNITTEST CURLcode canon_path(const char *q, size_t len, + struct dynbuf *new_path, + bool do_uri_encode) +{ + CURLcode result = CURLE_OK; + + struct Curl_str original_path; + + curlx_str_assign(&original_path, q, len); + + /* Normalized path will be either the same or shorter than the original + * path, plus trailing slash */ + + if(do_uri_encode) + result = uri_encode_path(&original_path, new_path); + else + result = curlx_dyn_addn(new_path, q, len); + + if(!result) { + if(curlx_dyn_len(new_path) == 0) + result = curlx_dyn_add(new_path, "/"); + } + + return result; +} + +UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq) +{ + CURLcode result = CURLE_OK; + + struct dynbuf query_array[MAX_QUERY_COMPONENTS]; + struct pair encoded_query_array[MAX_QUERY_COMPONENTS]; + size_t num_query_components; + size_t counted_query_components = 0; + size_t index; + + if(!query) + return result; + + result = split_to_dyn_array(query, &query_array[0], + &num_query_components); + if(result) { + goto fail; + } + + /* Create list of pairs, each pair containing an encoded query + * component */ + + for(index = 0; index < num_query_components; index++) { + const char *in_key; + size_t in_key_len; + char *offset; + size_t query_part_len = curlx_dyn_len(&query_array[index]); + char *query_part = curlx_dyn_ptr(&query_array[index]); + + in_key = query_part; + + offset = strchr(query_part, '='); + /* If there is no equals, this key has no value */ + if(!offset) { + in_key_len = strlen(in_key); + } + else { + in_key_len = offset - in_key; + } + + curlx_dyn_init(&encoded_query_array[index].key, query_part_len*3 + 1); + curlx_dyn_init(&encoded_query_array[index].value, query_part_len*3 + 1); + counted_query_components++; + + /* Decode/encode the key */ + result = http_aws_decode_encode(in_key, in_key_len, + &encoded_query_array[index].key); + if(result) { + goto fail; + } + + /* Decode/encode the value if it exists */ + if(offset && offset != (query_part + query_part_len - 1)) { + size_t in_value_len; + const char *in_value = offset + 1; + in_value_len = query_part + query_part_len - (offset + 1); + result = http_aws_decode_encode(in_value, in_value_len, + &encoded_query_array[index].value); + if(result) { + goto fail; + } + } + else { + /* If there is no value, the value is an empty string */ + curlx_dyn_init(&encoded_query_array[index].value, 2); + result = curlx_dyn_addn(&encoded_query_array[index].value, "", 1); + } + + if(result) { + goto fail; + } + } + + /* Sort the encoded query components by key and value */ + qsort(&encoded_query_array, num_query_components, + sizeof(struct pair), compare_func); + + /* Append the query components together to make a full query string */ + for(index = 0; index < num_query_components; index++) { + + if(index) + result = curlx_dyn_addn(dq, "&", 1); + if(!result) { + char *key_ptr = curlx_dyn_ptr(&encoded_query_array[index].key); + char *value_ptr = curlx_dyn_ptr(&encoded_query_array[index].value); + size_t vlen = curlx_dyn_len(&encoded_query_array[index].value); + if(value_ptr && vlen) { + result = curlx_dyn_addf(dq, "%s=%s", key_ptr, value_ptr); + } + else { + /* Empty value is always encoded to key= */ + result = curlx_dyn_addf(dq, "%s=", key_ptr); + } + } + if(result) + break; + } + +fail: + if(counted_query_components) + /* the encoded_query_array might not be initialized yet */ + pair_array_free(&encoded_query_array[0], counted_query_components); + dyn_array_free(&query_array[0], num_query_components); + return result; +} + +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; struct connectdata *conn = data->conn; - size_t len; - const char *arg; - char provider0[MAX_SIGV4_LEN + 1]=""; - char provider1[MAX_SIGV4_LEN + 1]=""; - char region[MAX_SIGV4_LEN + 1]=""; - char service[MAX_SIGV4_LEN + 1]=""; - bool sign_as_s3 = false; + const char *line; + struct Curl_str provider0; + struct Curl_str provider1; + struct Curl_str region = { NULL, 0}; + struct Curl_str service = { NULL, 0}; const char *hostname = conn->host.name; time_t clock; struct tm tm; @@ -388,12 +684,14 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) char date[9]; struct dynbuf canonical_headers; struct dynbuf signed_headers; + struct dynbuf canonical_query; + struct dynbuf canonical_path; char *date_header = NULL; Curl_HttpReq httpreq; const char *method = NULL; - char *payload_hash = NULL; + const char *payload_hash = NULL; size_t payload_hash_len = 0; - unsigned char sha_hash[SHA256_DIGEST_LENGTH]; + unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH]; char sha_hex[SHA256_HEX_LENGTH]; char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */ char *canonical_request = NULL; @@ -402,12 +700,14 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) char *str_to_sign = NULL; const char *user = data->state.aptr.user ? data->state.aptr.user : ""; char *secret = NULL; - unsigned char sign0[SHA256_DIGEST_LENGTH] = {0}; - unsigned char sign1[SHA256_DIGEST_LENGTH] = {0}; + unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = {0}; + unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = {0}; char *auth_headers = NULL; - DEBUGASSERT(!proxy); - (void)proxy; + if(data->set.path_as_is) { + failf(data, "Cannot use sigv4 authentication with path-as-is flag"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } if(Curl_checkheaders(data, STRCONST("Authorization"))) { /* Authorization already present, Bailing out */ @@ -415,8 +715,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) } /* we init those buffers here, so goto fail will free initialized dynbuf */ - Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); - Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER); /* * Parameters parsing @@ -425,77 +727,73 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) * AWS is the default because most of non-amazon providers * are still using aws:amz as a prefix. */ - arg = data->set.str[STRING_AWS_SIGV4] ? - data->set.str[STRING_AWS_SIGV4] : "aws:amz"; + line = data->set.str[STRING_AWS_SIGV4]; + if(!line || !*line) + line = "aws:amz"; - /* provider1[:provider2[:region[:service]]] + /* provider0[:provider1[:region[:service]]] No string can be longer than N bytes of non-whitespace - */ - (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]" - ":%" MAX_SIGV4_LEN_TXT "[^:]" - ":%" MAX_SIGV4_LEN_TXT "[^:]" - ":%" MAX_SIGV4_LEN_TXT "s", - provider0, provider1, region, service); - if(!provider0[0]) { - failf(data, "first provider can't be empty"); - ret = CURLE_BAD_FUNCTION_ARGUMENT; + */ + if(curlx_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) { + failf(data, "first aws-sigv4 provider cannot be empty"); + result = CURLE_BAD_FUNCTION_ARGUMENT; goto fail; } - else if(!provider1[0]) - strcpy(provider1, provider0); + if(curlx_str_single(&line, ':') || + curlx_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) { + provider1 = provider0; + } + else if(curlx_str_single(&line, ':') || + curlx_str_until(&line, ®ion, MAX_SIGV4_LEN, ':') || + curlx_str_single(&line, ':') || + curlx_str_until(&line, &service, MAX_SIGV4_LEN, ':')) { + /* nothing to do */ + } - if(!service[0]) { - char *hostdot = strchr(hostname, '.'); - if(!hostdot) { - failf(data, "service missing in parameters and hostname"); - ret = CURLE_URL_MALFORMAT; + if(!curlx_strlen(&service)) { + const char *p = hostname; + if(curlx_str_until(&p, &service, MAX_SIGV4_LEN, '.') || + curlx_str_single(&p, '.')) { + failf(data, "aws-sigv4: service missing in parameters and hostname"); + result = CURLE_URL_MALFORMAT; goto fail; } - len = hostdot - hostname; - if(len > MAX_SIGV4_LEN) { - failf(data, "service too long in hostname"); - ret = CURLE_URL_MALFORMAT; - goto fail; - } - strncpy(service, hostname, len); - service[len] = '\0'; - - if(!region[0]) { - const char *reg = hostdot + 1; - const char *hostreg = strchr(reg, '.'); - if(!hostreg) { - failf(data, "region missing in parameters and hostname"); - ret = CURLE_URL_MALFORMAT; - goto fail; - } - len = hostreg - reg; - if(len > MAX_SIGV4_LEN) { - failf(data, "region too long in hostname"); - ret = CURLE_URL_MALFORMAT; + + infof(data, "aws_sigv4: picked service %.*s from host", + (int)curlx_strlen(&service), curlx_str(&service)); + + if(!curlx_strlen(®ion)) { + if(curlx_str_until(&p, ®ion, MAX_SIGV4_LEN, '.') || + curlx_str_single(&p, '.')) { + failf(data, "aws-sigv4: region missing in parameters and hostname"); + result = CURLE_URL_MALFORMAT; goto fail; } - strncpy(region, reg, len); - region[len] = '\0'; + infof(data, "aws_sigv4: picked region %.*s from host", + (int)curlx_strlen(®ion), curlx_str(®ion)); } } Curl_http_method(data, conn, &method, &httpreq); - /* AWS S3 requires a x-amz-content-sha256 header, and supports special - * values like UNSIGNED-PAYLOAD */ - sign_as_s3 = (strcasecompare(provider0, "aws") && - strcasecompare(service, "s3")); - - payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len); + payload_hash = + parse_content_sha_hdr(data, curlx_str(&provider1), + curlx_strlen(&provider1), &payload_hash_len); if(!payload_hash) { + /* AWS S3 requires a x-amz-content-sha256 header, and supports special + * values like UNSIGNED-PAYLOAD */ + bool sign_as_s3 = curlx_str_casecompare(&provider0, "aws") && + curlx_str_casecompare(&service, "s3"); + if(sign_as_s3) - ret = calc_s3_payload_hash(data, httpreq, provider1, sha_hash, - sha_hex, content_sha256_hdr); + result = calc_s3_payload_hash(data, httpreq, curlx_str(&provider1), + curlx_strlen(&provider1), sha_hash, + sha_hex, content_sha256_hdr); else - ret = calc_payload_hash(data, sha_hash, sha_hex); - if(ret) + result = calc_payload_hash(data, sha_hash, sha_hex); + if(result) goto fail; payload_hash = sha_hex; @@ -509,26 +807,26 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) if(force_timestamp) clock = 0; else - time(&clock); + clock = time(NULL); } #else - time(&clock); + clock = time(NULL); #endif - ret = Curl_gmtime(clock, &tm); - if(ret) { + result = Curl_gmtime(clock, &tm); + if(result) { goto fail; } if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) { - ret = CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; goto fail; } - ret = make_headers(data, hostname, timestamp, provider1, - &date_header, content_sha256_hdr, - &canonical_headers, &signed_headers); - if(ret) + result = make_headers(data, hostname, timestamp, + curlx_str(&provider1), curlx_strlen(&provider1), + &date_header, content_sha256_hdr, + &canonical_headers, &signed_headers); + if(result) goto fail; - ret = CURLE_OUT_OF_MEMORY; if(*content_sha256_hdr) { /* make_headers() needed this without the \r\n for canonicalization */ @@ -540,30 +838,50 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) memcpy(date, timestamp, sizeof(date)); date[sizeof(date) - 1] = 0; + result = canon_query(data->state.up.query, &canonical_query); + if(result) + goto fail; + + result = canon_path(data->state.up.path, strlen(data->state.up.path), + &canonical_path, + should_urlencode(&service)); + if(result) + goto fail; + result = CURLE_OUT_OF_MEMORY; + canonical_request = - curl_maprintf("%s\n" /* HTTPRequestMethod */ - "%s\n" /* CanonicalURI */ - "%s\n" /* CanonicalQueryString */ - "%s\n" /* CanonicalHeaders */ - "%s\n" /* SignedHeaders */ - "%.*s", /* HashedRequestPayload in hex */ - method, - data->state.up.path, - data->state.up.query ? data->state.up.query : "", - Curl_dyn_ptr(&canonical_headers), - Curl_dyn_ptr(&signed_headers), - (int)payload_hash_len, payload_hash); + aprintf("%s\n" /* HTTPRequestMethod */ + "%s\n" /* CanonicalURI */ + "%s\n" /* CanonicalQueryString */ + "%s\n" /* CanonicalHeaders */ + "%s\n" /* SignedHeaders */ + "%.*s", /* HashedRequestPayload in hex */ + method, + curlx_dyn_ptr(&canonical_path), + curlx_dyn_ptr(&canonical_query) ? + curlx_dyn_ptr(&canonical_query) : "", + curlx_dyn_ptr(&canonical_headers), + curlx_dyn_ptr(&signed_headers), + (int)payload_hash_len, payload_hash); if(!canonical_request) goto fail; - /* provider 0 lowercase */ - Curl_strntolower(provider0, provider0, strlen(provider0)); - request_type = curl_maprintf("%s4_request", provider0); + infof(data, "aws_sigv4: Canonical request (enclosed in []) - [%s]", + canonical_request); + + request_type = aprintf("%.*s4_request", + (int)curlx_strlen(&provider0), curlx_str(&provider0)); if(!request_type) goto fail; - credential_scope = curl_maprintf("%s/%s/%s/%s", - date, region, service, request_type); + /* provider0 is lowercased *after* aprintf() so that the buffer can be + written to */ + Curl_strntolower(request_type, request_type, curlx_strlen(&provider0)); + + credential_scope = aprintf("%s/%.*s/%.*s/%s", date, + (int)curlx_strlen(®ion), curlx_str(®ion), + (int)curlx_strlen(&service), curlx_str(&service), + request_type); if(!credential_scope) goto fail; @@ -573,73 +891,262 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) sha256_to_hex(sha_hex, sha_hash); - /* provider 0 uppercase */ - Curl_strntoupper(provider0, provider0, strlen(provider0)); - /* * Google allows using RSA key instead of HMAC, so this code might change * in the future. For now we only support HMAC. */ - str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */ - "%s\n" /* RequestDateTime */ - "%s\n" /* CredentialScope */ - "%s", /* HashedCanonicalRequest in hex */ - provider0, - timestamp, - credential_scope, - sha_hex); - if(!str_to_sign) { + str_to_sign = aprintf("%.*s4-HMAC-SHA256\n" /* Algorithm */ + "%s\n" /* RequestDateTime */ + "%s\n" /* CredentialScope */ + "%s", /* HashedCanonicalRequest in hex */ + (int)curlx_strlen(&provider0), curlx_str(&provider0), + timestamp, + credential_scope, + sha_hex); + if(!str_to_sign) goto fail; - } - /* provider 0 uppercase */ - secret = curl_maprintf("%s4%s", provider0, - data->state.aptr.passwd ? - data->state.aptr.passwd : ""); + /* make provider0 part done uppercase */ + Curl_strntoupper(str_to_sign, curlx_str(&provider0), + curlx_strlen(&provider0)); + + infof(data, "aws_sigv4: String to sign (enclosed in []) - [%s]", + str_to_sign); + + secret = aprintf("%.*s4%s", (int)curlx_strlen(&provider0), + curlx_str(&provider0), data->state.aptr.passwd ? + data->state.aptr.passwd : ""); if(!secret) goto fail; + /* make provider0 part done uppercase */ + Curl_strntoupper(secret, curlx_str(&provider0), curlx_strlen(&provider0)); HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0); - HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1); - HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0); + HMAC_SHA256(sign0, sizeof(sign0), + curlx_str(®ion), curlx_strlen(®ion), sign1); + HMAC_SHA256(sign1, sizeof(sign1), + curlx_str(&service), curlx_strlen(&service), sign0); HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1); HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0); sha256_to_hex(sha_hex, sign0); - /* provider 0 uppercase */ - auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 " - "Credential=%s/%s, " - "SignedHeaders=%s, " - "Signature=%s\r\n" - "%s\r\n" - "%s", /* optional sha256 header includes \r\n */ - provider0, - user, - credential_scope, - Curl_dyn_ptr(&signed_headers), - sha_hex, - date_header, - content_sha256_hdr); + infof(data, "aws_sigv4: Signature - %s", sha_hex); + + auth_headers = aprintf("Authorization: %.*s4-HMAC-SHA256 " + "Credential=%s/%s, " + "SignedHeaders=%s, " + "Signature=%s\r\n" + /* + * date_header is added here, only if it was not + * user-specified (using CURLOPT_HTTPHEADER). + * date_header includes \r\n + */ + "%s" + "%s", /* optional sha256 header includes \r\n */ + (int)curlx_strlen(&provider0), curlx_str(&provider0), + user, + credential_scope, + curlx_dyn_ptr(&signed_headers), + sha_hex, + date_header ? date_header : "", + content_sha256_hdr); if(!auth_headers) { goto fail; } + /* provider 0 uppercase */ + Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1], + curlx_str(&provider0), curlx_strlen(&provider0)); - Curl_safefree(data->state.aptr.userpwd); + free(data->state.aptr.userpwd); data->state.aptr.userpwd = auth_headers; data->state.authhost.done = TRUE; - ret = CURLE_OK; + result = CURLE_OK; fail: - Curl_dyn_free(&canonical_headers); - Curl_dyn_free(&signed_headers); + curlx_dyn_free(&canonical_query); + curlx_dyn_free(&canonical_path); + curlx_dyn_free(&canonical_headers); + curlx_dyn_free(&signed_headers); free(canonical_request); free(request_type); free(credential_scope); free(str_to_sign); free(secret); free(date_header); - return ret; + return result; +} + +/* +* Frees all allocated strings in a dynbuf pair array, and the dynbuf itself +*/ + +static void pair_array_free(struct pair *pair_array, size_t num_elements) +{ + size_t index; + + for(index = 0; index != num_elements; index++) { + curlx_dyn_free(&pair_array[index].key); + curlx_dyn_free(&pair_array[index].value); + } + +} + +/* +* Frees all allocated strings in a split dynbuf, and the dynbuf itself +*/ + +static void dyn_array_free(struct dynbuf *db, size_t num_elements) +{ + size_t index; + + for(index = 0; index < num_elements; index++) + curlx_dyn_free((&db[index])); +} + +/* +* Splits source string by SPLIT_BY, and creates an array of dynbuf in db. +* db is initialized by this function. +* Caller is responsible for freeing the array elements with dyn_array_free +*/ + +#define SPLIT_BY '&' + +static CURLcode split_to_dyn_array(const char *source, + struct dynbuf db[MAX_QUERY_COMPONENTS], + size_t *num_splits_out) +{ + CURLcode result = CURLE_OK; + size_t len = strlen(source); + size_t pos; /* Position in result buffer */ + size_t start = 0; /* Start of current segment */ + size_t segment_length = 0; + size_t index = 0; + size_t num_splits = 0; + + /* Split source_ptr on SPLIT_BY and store the segment offsets and length in + * array */ + for(pos = 0; pos < len; pos++) { + if(source[pos] == SPLIT_BY) { + if(segment_length) { + curlx_dyn_init(&db[index], segment_length + 1); + result = curlx_dyn_addn(&db[index], &source[start], + segment_length); + if(result) + goto fail; + + segment_length = 0; + index++; + if(++num_splits == MAX_QUERY_COMPONENTS) { + result = CURLE_TOO_LARGE; + goto fail; + } + } + start = pos + 1; + } + else { + segment_length++; + } + } + + 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; + } + } +fail: + *num_splits_out = num_splits; + return result; +} + + +static bool is_reserved_char(const char c) +{ + return (ISALNUM(c) || ISURLPUNTCS(c)); +} + +static CURLcode uri_encode_path(struct Curl_str *original_path, + struct dynbuf *new_path) +{ + const char *p = curlx_str(original_path); + size_t i; + + for(i = 0; i < curlx_strlen(original_path); i++) { + /* Do not encode slashes or unreserved chars from RFC 3986 */ + CURLcode result = CURLE_OK; + unsigned char c = p[i]; + if(is_reserved_char(c) || c == '/') + result = curlx_dyn_addn(new_path, &c, 1); + else + result = curlx_dyn_addf(new_path, "%%%02X", c); + if(result) + return result; + } + + return CURLE_OK; +} + + +static CURLcode encode_query_component(char *component, size_t len, + struct dynbuf *db) +{ + size_t i; + for(i = 0; i < len; i++) { + CURLcode result = CURLE_OK; + unsigned char this_char = component[i]; + + if(is_reserved_char(this_char)) + /* Escape unreserved chars from RFC 3986 */ + result = curlx_dyn_addn(db, &this_char, 1); + else if(this_char == '+') + /* Encode '+' as space */ + result = curlx_dyn_add(db, "%20"); + else + result = curlx_dyn_addf(db, "%%%02X", this_char); + if(result) + return result; + } + + return CURLE_OK; +} + +/* +* Populates a dynbuf containing url_encode(url_decode(in)) +*/ + +static CURLcode http_aws_decode_encode(const char *in, size_t in_len, + struct dynbuf *out) +{ + char *out_s; + size_t out_s_len; + CURLcode result = + Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA); + + if(!result) { + result = encode_query_component(out_s, out_s_len, out); + Curl_safefree(out_s); + } + return result; +} + +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 + */ + + if(curlx_str_cmp(service_name, "s3") || + curlx_str_cmp(service_name, "s3-express") || + curlx_str_cmp(service_name, "s3-outposts")) { + return false; + } + return true; } -#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */ +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */ diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.h b/Utilities/cmcurl/lib/http_aws_sigv4.h index 57cc5706eba..9747c948a69 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.h +++ b/Utilities/cmcurl/lib/http_aws_sigv4.h @@ -11,7 +11,7 @@ * * 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.haxx.se/docs/copyright.html. + * 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 @@ -24,8 +24,18 @@ * ***************************************************************************/ #include "curl_setup.h" +#include "curlx/dynbuf.h" +#include "urldata.h" +#include "curlx/strparse.h" /* this is for creating aws_sigv4 header output */ -CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy); +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data); + +#ifdef UNITTESTS +UNITTEST CURLcode canon_path(const char *q, size_t len, + struct dynbuf *new_path, + bool normalize); +UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq); +#endif #endif /* HEADER_CURL_HTTP_AWS_SIGV4_H */ diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c index bda00d38338..63e477c48a6 100644 --- a/Utilities/cmcurl/lib/http_chunks.c +++ b/Utilities/cmcurl/lib/http_chunks.c @@ -27,12 +27,15 @@ #ifndef CURL_DISABLE_HTTP #include "urldata.h" /* it includes http_chunks.h */ +#include "curl_printf.h" +#include "curl_trc.h" #include "sendf.h" /* for the client write stuff */ -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "content_encoding.h" #include "http.h" -#include "strtoofft.h" -#include "warnless.h" +#include "multiif.h" +#include "curlx/strparse.h" +#include "curlx/warnless.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -75,121 +78,156 @@ */ -#define isxdigit_ascii(x) Curl_isxdigit(x) +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) +{ + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + curlx_dyn_init(&ch->trailer, DYN_H1_TRAILER); + ch->ignore_body = ignore_body; +} -void Curl_httpchunk_init(struct Curl_easy *data) +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) { - struct connectdata *conn = data->conn; - struct Curl_chunker *chunk = &conn->chunk; - chunk->hexindex = 0; /* start at 0 */ - chunk->state = CHUNK_HEX; /* we get hex first! */ - Curl_dyn_init(&conn->trailer, DYN_H1_TRAILER); + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + curlx_dyn_reset(&ch->trailer); + ch->ignore_body = ignore_body; } -/* - * chunk_read() returns a OK for normal operations, or a positive return code - * for errors. STOP means this sequence of chunks is complete. The 'wrote' - * argument is set to tell the caller how many bytes we actually passed to the - * client (for byte-counting and whatever). - * - * The states and the state-machine is further explained in the header file. - * - * This function always uses ASCII hex values to accommodate non-ASCII hosts. - * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. - */ -CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, - char *datap, - ssize_t datalen, - ssize_t *wrote, - CURLcode *extrap) +void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch) +{ + (void)data; + curlx_dyn_free(&ch->trailer); +} + +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch) +{ + (void)data; + return ch->state == CHUNK_DONE; +} + +static CURLcode httpchunk_readwrite(struct Curl_easy *data, + struct Curl_chunker *ch, + struct Curl_cwriter *cw_next, + const char *buf, size_t blen, + size_t *pconsumed) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct Curl_chunker *ch = &conn->chunk; - struct SingleRequest *k = &data->req; size_t piece; - curl_off_t length = (curl_off_t)datalen; - *wrote = 0; /* nothing's written yet */ + *pconsumed = 0; /* nothing's written yet */ + /* first check terminal states that will not progress anywhere */ + if(ch->state == CHUNK_DONE) + return CURLE_OK; + if(ch->state == CHUNK_FAILED) + return CURLE_RECV_ERROR; /* the original data is written to the client, but we go on with the chunk read process, to properly calculate the content length */ - if(data->set.http_te_skip && !k->ignorebody) { - result = Curl_client_write(data, CLIENTWRITE_BODY, datap, datalen); + if(data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, blen); + else + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } - while(length) { + while(blen) { switch(ch->state) { case CHUNK_HEX: - if(ISXDIGIT(*datap)) { - if(ch->hexindex < CHUNK_MAXNUM_LEN) { - ch->hexbuffer[ch->hexindex] = *datap; - datap++; - length--; - ch->hexindex++; - } - else { - return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ + if(ISXDIGIT(*buf)) { + if(ch->hexindex >= CHUNK_MAXNUM_LEN) { + failf(data, "chunk hex-length longer than %d", CHUNK_MAXNUM_LEN); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_TOO_LONG_HEX; /* longer than we support */ + return CURLE_RECV_ERROR; } + ch->hexbuffer[ch->hexindex++] = *buf; + buf++; + blen--; + (*pconsumed)++; } else { - char *endptr; - if(0 == ch->hexindex) + const char *p; + if(0 == ch->hexindex) { /* This is illegal data, we received junk where we expected a hexadecimal digit. */ - return CHUNKE_ILLEGAL_HEX; - - /* length and datap are unmodified */ + failf(data, "chunk hex-length char not a hex digit: 0x%x", *buf); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; + } + /* blen and buf are unmodified */ ch->hexbuffer[ch->hexindex] = 0; - - if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize)) - return CHUNKE_ILLEGAL_HEX; + p = &ch->hexbuffer[0]; + if(curlx_str_hex(&p, &ch->datasize, CURL_OFF_T_MAX)) { + failf(data, "invalid chunk size: '%s'", ch->hexbuffer); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; + } ch->state = CHUNK_LF; /* now wait for the CRLF */ } break; case CHUNK_LF: /* waiting for the LF after a chunk size */ - if(*datap == 0x0a) { - /* we're now expecting data to come, unless size was zero! */ + if(*buf == 0x0a) { + /* we are now expecting data to come, unless size was zero! */ if(0 == ch->datasize) { ch->state = CHUNK_TRAILER; /* now check for trailers */ } - else + else { ch->state = CHUNK_DATA; + CURL_TRC_WRITE(data, "http_chunked, chunk start of %" + FMT_OFF_T " bytes", ch->datasize); + } } - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_DATA: - /* We expect 'datasize' of data. We have 'length' right now, it can be + /* We expect 'datasize' of data. We have 'blen' right now, it can be more or less than 'datasize'. Get the smallest piece. */ - piece = curlx_sotouz((ch->datasize >= length)?length:ch->datasize); + piece = blen; + if(ch->datasize < (curl_off_t)blen) + piece = curlx_sotouz(ch->datasize); /* Write the data portion available */ - if(!data->set.http_te_skip && !k->ignorebody) { - if(!data->set.http_ce_skip && k->writer_stack) - result = Curl_unencode_write(data, k->writer_stack, datap, piece); + if(!data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, + buf, piece); else - result = Curl_client_write(data, CLIENTWRITE_BODY, datap, piece); - + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, piece); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } - *wrote += piece; + *pconsumed += piece; ch->datasize -= piece; /* decrease amount left to expect */ - datap += piece; /* move read pointer forward */ - length -= piece; /* decrease space left in this round */ + buf += piece; /* move read pointer forward */ + blen -= piece; /* decrease space left in this round */ + CURL_TRC_WRITE(data, "http_chunked, write %zu body bytes, %" + FMT_OFF_T " bytes in chunk remain", + piece, ch->datasize); if(0 == ch->datasize) /* end of data this round, we now expect a trailing CRLF */ @@ -197,105 +235,139 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, break; case CHUNK_POSTLF: - if(*datap == 0x0a) { + if(*buf == 0x0a) { /* The last one before we go back to hex state and start all over. */ - Curl_httpchunk_init(data); /* sets state back to CHUNK_HEX */ + Curl_httpchunk_reset(data, ch, ch->ignore_body); + } + else if(*buf != 0x0d) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; } - else if(*datap != 0x0d) - return CHUNKE_BAD_CHUNK; - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_TRAILER: - if((*datap == 0x0d) || (*datap == 0x0a)) { - char *tr = Curl_dyn_ptr(&conn->trailer); + if((*buf == 0x0d) || (*buf == 0x0a)) { + char *tr = curlx_dyn_ptr(&ch->trailer); /* this is the end of a trailer, but if the trailer was zero bytes there was no trailer and we move on */ if(tr) { - size_t trlen; - result = Curl_dyn_addn(&conn->trailer, (char *)STRCONST("\x0d\x0a")); - if(result) - return CHUNKE_OUT_OF_MEMORY; - - tr = Curl_dyn_ptr(&conn->trailer); - trlen = Curl_dyn_len(&conn->trailer); + result = curlx_dyn_addn(&ch->trailer, STRCONST("\x0d\x0a")); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; + } + tr = curlx_dyn_ptr(&ch->trailer); if(!data->set.http_te_skip) { - result = Curl_client_write(data, - CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, - tr, trlen); + size_t trlen = curlx_dyn_len(&ch->trailer); + if(cw_next) + result = Curl_cwriter_write(data, cw_next, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); + else + result = Curl_client_write(data, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } - Curl_dyn_reset(&conn->trailer); + curlx_dyn_reset(&ch->trailer); ch->state = CHUNK_TRAILER_CR; - if(*datap == 0x0a) + if(*buf == 0x0a) /* already on the LF */ break; } else { - /* no trailer, we're on the final CRLF pair */ + /* no trailer, we are on the final CRLF pair */ ch->state = CHUNK_TRAILER_POSTCR; - break; /* don't advance the pointer */ + break; /* do not advance the pointer */ } } else { - result = Curl_dyn_addn(&conn->trailer, datap, 1); - if(result) - return CHUNKE_OUT_OF_MEMORY; + result = curlx_dyn_addn(&ch->trailer, buf, 1); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; + } } - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_TRAILER_CR: - if(*datap == 0x0a) { + if(*buf == 0x0a) { ch->state = CHUNK_TRAILER_POSTCR; - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; + } + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; } - else - return CHUNKE_BAD_CHUNK; break; case CHUNK_TRAILER_POSTCR: /* We enter this state when a CR should arrive so we expect to have to first pass a CR before we wait for LF */ - if((*datap != 0x0d) && (*datap != 0x0a)) { + if((*buf != 0x0d) && (*buf != 0x0a)) { /* not a CR then it must be another header in the trailer */ ch->state = CHUNK_TRAILER; break; } - if(*datap == 0x0d) { + if(*buf == 0x0d) { /* skip if CR */ - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; } /* now wait for the final LF */ ch->state = CHUNK_STOP; break; case CHUNK_STOP: - if(*datap == 0x0a) { - length--; - + if(*buf == 0x0a) { + blen--; + (*pconsumed)++; /* Record the length of any data left in the end of the buffer - even if there's no more chunks to read */ - ch->datasize = curlx_sotouz(length); - - return CHUNKE_STOP; /* return stop */ + even if there is no more chunks to read */ + ch->datasize = blen; + ch->state = CHUNK_DONE; + CURL_TRC_WRITE(data, "http_chunk, response complete"); + return CURLE_OK; + } + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + CURL_TRC_WRITE(data, "http_chunk error, expected 0x0a, seeing 0x%ux", + (unsigned int)*buf); + return CURLE_RECV_ERROR; } - else - return CHUNKE_BAD_CHUNK; + case CHUNK_DONE: + return CURLE_OK; + + case CHUNK_FAILED: + return CURLE_RECV_ERROR; } + } - return CHUNKE_OK; + return CURLE_OK; } -const char *Curl_chunked_strerror(CHUNKcode code) +static const char *Curl_chunked_strerror(CHUNKcode code) { switch(code) { default: @@ -307,8 +379,7 @@ const char *Curl_chunked_strerror(CHUNKcode code) case CHUNKE_BAD_CHUNK: return "Malformed encoding found"; case CHUNKE_PASSTHRU_ERROR: - DEBUGASSERT(0); /* never used */ - return ""; + return "Error writing data to client"; case CHUNKE_BAD_ENCODING: return "Bad content-encoding found"; case CHUNKE_OUT_OF_MEMORY: @@ -316,4 +387,295 @@ const char *Curl_chunked_strerror(CHUNKcode code) } } +CURLcode Curl_httpchunk_read(struct Curl_easy *data, + struct Curl_chunker *ch, + char *buf, size_t blen, + size_t *pconsumed) +{ + return httpchunk_readwrite(data, ch, NULL, buf, blen, pconsumed); +} + +struct chunked_writer { + struct Curl_cwriter super; + struct Curl_chunker ch; +}; + +static CURLcode cw_chunked_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = writer->ctx; + + data->req.chunk = TRUE; /* chunks coming our way. */ + Curl_httpchunk_init(data, &ctx->ch, FALSE); + return CURLE_OK; +} + +static void cw_chunked_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = writer->ctx; + Curl_httpchunk_free(data, &ctx->ch); +} + +static CURLcode cw_chunked_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct chunked_writer *ctx = writer->ctx; + CURLcode result; + size_t consumed; + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, blen); + + consumed = 0; + result = httpchunk_readwrite(data, &ctx->ch, writer->next, buf, blen, + &consumed); + + if(result) { + if(CHUNKE_PASSTHRU_ERROR == ctx->ch.last_code) { + failf(data, "Failed reading the chunked-encoded stream"); + } + else { + failf(data, "%s in chunked-encoding", + Curl_chunked_strerror(ctx->ch.last_code)); + } + return result; + } + + blen -= consumed; + if(CHUNK_DONE == ctx->ch.state) { + /* chunks read successfully, download is complete */ + data->req.download_done = TRUE; + if(blen) { + infof(data, "Leftovers after chunking: %zu bytes", blen); + } + } + else if((type & CLIENTWRITE_EOS) && !data->req.no_body) { + failf(data, "transfer closed with outstanding read data remaining"); + return CURLE_PARTIAL_FILE; + } + + return CURLE_OK; +} + +/* HTTP chunked Transfer-Encoding decoder */ +const struct Curl_cwtype Curl_httpchunk_unencoder = { + "chunked", + NULL, + cw_chunked_init, + cw_chunked_write, + cw_chunked_close, + sizeof(struct chunked_writer) +}; + +/* max length of an HTTP chunk that we want to generate */ +#define CURL_CHUNKED_MINLEN (1024) +#define CURL_CHUNKED_MAXLEN (64 * 1024) + +struct chunked_reader { + struct Curl_creader super; + struct bufq chunkbuf; + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ +}; + +static CURLcode cr_chunked_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + (void)data; + Curl_bufq_init2(&ctx->chunkbuf, CURL_CHUNKED_MAXLEN, 2, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void cr_chunked_close(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->chunkbuf); +} + +static CURLcode add_last_chunk(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + struct curl_slist *trailers = NULL, *tr; + CURLcode result; + size_t n; + int rc; + + if(!data->set.trailer_callback) { + CURL_TRC_READ(data, "http_chunk, added last, empty chunk"); + return Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n\r\n"), &n); + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n"), &n); + if(result) + goto out; + + Curl_set_in_callback(data, TRUE); + rc = data->set.trailer_callback(&trailers, data->set.trailer_data); + Curl_set_in_callback(data, FALSE); + + if(rc != CURL_TRAILERFUNC_OK) { + failf(data, "operation aborted by trailing headers callback"); + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + + for(tr = trailers; tr; tr = tr->next) { + /* only add correctly formatted trailers */ + char *ptr = strchr(tr->data, ':'); + if(!ptr || *(ptr + 1) != ' ') { + infof(data, "Malformatted trailing header, skipping trailer"); + continue; + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, tr->data, + strlen(tr->data), &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n); + if(result) + goto out; + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n); + +out: + curl_slist_free_all(trailers); + CURL_TRC_READ(data, "http_chunk, added last chunk with trailers " + "from client -> %d", result); + return result; +} + +static CURLcode add_chunk(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen) +{ + struct chunked_reader *ctx = reader->ctx; + CURLcode result; + char tmp[CURL_CHUNKED_MINLEN]; + size_t nread; + bool eos; + + DEBUGASSERT(!ctx->read_eos); + blen = CURLMIN(blen, CURL_CHUNKED_MAXLEN); /* respect our buffer pref */ + if(blen < sizeof(tmp)) { + /* small read, make a chunk of decent size */ + buf = tmp; + blen = sizeof(tmp); + } + else { + /* larger read, make a chunk that will fit when read back */ + blen -= (8 + 2 + 2); /* deduct max overhead, 8 hex + 2*crlf */ + } + + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + if(eos) + ctx->read_eos = TRUE; + + if(nread) { + /* actually got bytes, wrap them into the chunkbuf */ + char hd[11] = ""; + int hdlen; + size_t n; + + hdlen = msnprintf(hd, sizeof(hd), "%zx\r\n", nread); + if(hdlen <= 0) + return CURLE_READ_ERROR; + /* On a soft-limited bufq, we do not need to check that all was written */ + result = Curl_bufq_cwrite(&ctx->chunkbuf, hd, hdlen, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, buf, nread, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, "\r\n", 2, &n); + CURL_TRC_READ(data, "http_chunk, made chunk of %zu bytes -> %d", + nread, result); + if(result) + return result; + } + + if(ctx->read_eos) + return add_last_chunk(data, reader); + return CURLE_OK; +} + +static CURLcode cr_chunked_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct chunked_reader *ctx = reader->ctx; + CURLcode result = CURLE_READ_ERROR; + + *pnread = 0; + *peos = ctx->eos; + + if(!ctx->eos) { + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) { + /* Still getting data form the next reader, buffer is empty */ + result = add_chunk(data, reader, buf, blen); + if(result) + return result; + } + + if(!Curl_bufq_is_empty(&ctx->chunkbuf)) { + result = Curl_bufq_cread(&ctx->chunkbuf, buf, blen, pnread); + if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + *peos = TRUE; + } + return result; + } + } + /* We may get here, because we are done or because callbacks paused */ + DEBUGASSERT(ctx->eos || !ctx->read_eos); + return CURLE_OK; +} + +static curl_off_t cr_chunked_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} + +/* HTTP chunked Transfer-Encoding encoder */ +const struct Curl_crtype Curl_httpchunk_encoder = { + "chunked", + cr_chunked_init, + cr_chunked_read, + cr_chunked_close, + Curl_creader_def_needs_rewind, + cr_chunked_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct chunked_reader) +}; + +CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &Curl_httpchunk_encoder, + CURL_CR_TRANSFER_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + #endif /* CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/http_chunks.h b/Utilities/cmcurl/lib/http_chunks.h index ed50713162c..b84e479fcd6 100644 --- a/Utilities/cmcurl/lib/http_chunks.h +++ b/Utilities/cmcurl/lib/http_chunks.h @@ -24,17 +24,21 @@ * ***************************************************************************/ +#ifndef CURL_DISABLE_HTTP + +#include "curlx/dynbuf.h" + 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. + * For 64-bit curl_off_t we support 16 digits. For 32-bit, 8 digits. */ #define CHUNK_MAXNUM_LEN (SIZEOF_CURL_OFF_T * 2) typedef enum { - /* await and buffer all hexadecimal digits until we get one that isn't a + /* await and buffer all hexadecimal digits until we get one that is not a hexadecimal digit. When done, we go CHUNK_LF */ CHUNK_HEX, @@ -45,14 +49,14 @@ typedef enum { POST_CR state. */ CHUNK_DATA, - /* POSTLF should get a CR and then a LF and nothing else, then move back to + /* POSTLF should get a CR and then an LF and nothing else, then move back to HEX as the CRLF combination marks the end of a chunk. A missing CR is no big deal. */ CHUNK_POSTLF, - /* Used to mark that we're out of the game. NOTE: that there's a 'datasize' - field in the struct that will tell how many bytes that were not passed to - the client in the end of the last buffer! */ + /* Used to mark that we are out of the game. NOTE: that there is a + 'datasize' field in the struct that will tell how many bytes that were + not passed to the client in the end of the last buffer! */ CHUNK_STOP, /* At this point optional trailer headers can be found, unless the next line @@ -60,41 +64,82 @@ typedef enum { CHUNK_TRAILER, /* A trailer CR has been found - next state is CHUNK_TRAILER_POSTCR. - Next char must be a LF */ + Next char must be an LF */ CHUNK_TRAILER_CR, /* A trailer LF must be found now, otherwise CHUNKE_BAD_CHUNK will be signalled If this is an empty trailer CHUNKE_STOP will be signalled. Otherwise the trailer will be broadcasted via Curl_client_write() and the next state will be CHUNK_TRAILER */ - CHUNK_TRAILER_POSTCR + CHUNK_TRAILER_POSTCR, + + /* Successfully de-chunked everything */ + CHUNK_DONE, + + /* Failed on seeing a bad or not correctly terminated chunk */ + CHUNK_FAILED } ChunkyState; typedef enum { - CHUNKE_STOP = -1, CHUNKE_OK = 0, CHUNKE_TOO_LONG_HEX = 1, CHUNKE_ILLEGAL_HEX, CHUNKE_BAD_CHUNK, CHUNKE_BAD_ENCODING, CHUNKE_OUT_OF_MEMORY, - CHUNKE_PASSTHRU_ERROR, /* Curl_httpchunk_read() returns a CURLcode to use */ - CHUNKE_LAST + CHUNKE_PASSTHRU_ERROR /* Curl_httpchunk_read() returns a CURLcode to use */ } CHUNKcode; -const char *Curl_chunked_strerror(CHUNKcode code); - struct Curl_chunker { curl_off_t datasize; ChunkyState state; + CHUNKcode last_code; + struct dynbuf trailer; /* for chunked-encoded trailer */ unsigned char hexindex; - char hexbuffer[ CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ + char hexbuffer[CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ + BIT(ignore_body); /* never write response body data */ }; /* The following functions are defined in http_chunks.c */ -void Curl_httpchunk_init(struct Curl_easy *data); -CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, char *datap, - ssize_t length, ssize_t *wrote, - CURLcode *passthru); +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); +void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch); +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); + +/* + * Read BODY bytes in HTTP/1.1 chunked encoding from `buf` and return + * the amount of bytes consumed. The actual response bytes and trailer + * headers are written out to the client. + * On success, this will consume all bytes up to the end of the response, + * e.g. the last chunk, has been processed. + * @param data the transfer involved + * @param ch the chunker instance keeping state across calls + * @param buf the response data + * @param blen amount of bytes in `buf` + * @param pconsumed on successful return, the number of bytes in `buf` + * consumed + * + * This function always uses ASCII hex values to accommodate non-ASCII hosts. + * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. + */ +CURLcode Curl_httpchunk_read(struct Curl_easy *data, struct Curl_chunker *ch, + char *buf, size_t blen, size_t *pconsumed); + +/** + * @return TRUE iff chunked decoded has finished successfully. + */ +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch); + +extern const struct Curl_cwtype Curl_httpchunk_unencoder; + +extern const struct Curl_crtype Curl_httpchunk_encoder; + +/** + * Add a transfer-encoding "chunked" reader to the transfers reader stack + */ +CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data); + +#endif /* !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_HTTP_CHUNKS_H */ diff --git a/Utilities/cmcurl/lib/http_digest.c b/Utilities/cmcurl/lib/http_digest.c index 8daad99e32f..651bb834037 100644 --- a/Utilities/cmcurl/lib/http_digest.c +++ b/Utilities/cmcurl/lib/http_digest.c @@ -24,12 +24,13 @@ #include "curl_setup.h" -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) #include "urldata.h" #include "strcase.h" #include "vauth/vauth.h" #include "http_digest.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -62,8 +63,7 @@ CURLcode Curl_input_digest(struct Curl_easy *data, return CURLE_BAD_CONTENT_ENCODING; header += strlen("Digest"); - while(*header && ISBLANK(*header)) - header++; + curlx_str_passblanks(&header); return Curl_auth_decode_digest_http_message(header, digest); } @@ -121,9 +121,9 @@ CURLcode Curl_output_digest(struct Curl_easy *data, passwdp = ""; #if defined(USE_WINDOWS_SSPI) - have_chlg = digest->input_token ? TRUE : FALSE; + have_chlg = !!digest->input_token; #else - have_chlg = digest->nonce ? TRUE : FALSE; + have_chlg = !!digest->nonce; #endif if(!have_chlg) { @@ -145,15 +145,15 @@ CURLcode Curl_output_digest(struct Curl_easy *data, */ if(authp->iestyle) { - tmp = strchr((char *)uripath, '?'); + tmp = strchr((const char *)uripath, '?'); if(tmp) { - size_t urilen = tmp - (char *)uripath; + size_t urilen = tmp - (const char *)uripath; /* typecast is fine here since the value is always less than 32 bits */ path = (unsigned char *) aprintf("%.*s", (int)urilen, uripath); } } if(!tmp) - path = (unsigned char *) strdup((char *) uripath); + path = (unsigned char *) strdup((const char *) uripath); if(!path) return CURLE_OUT_OF_MEMORY; diff --git a/Utilities/cmcurl/lib/http_digest.h b/Utilities/cmcurl/lib/http_digest.h index 7d5cfc1bfd3..5f797310fd9 100644 --- a/Utilities/cmcurl/lib/http_digest.h +++ b/Utilities/cmcurl/lib/http_digest.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) /* this is for digest header input */ CURLcode Curl_input_digest(struct Curl_easy *data, @@ -39,6 +39,6 @@ CURLcode Curl_output_digest(struct Curl_easy *data, void Curl_http_auth_cleanup_digest(struct Curl_easy *data); -#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_CRYPTO_AUTH */ +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_DIGEST_AUTH */ #endif /* HEADER_CURL_HTTP_DIGEST_H */ diff --git a/Utilities/cmcurl/lib/http_negotiate.c b/Utilities/cmcurl/lib/http_negotiate.c index 153e3d4ab81..5bec3b39ff5 100644 --- a/Utilities/cmcurl/lib/http_negotiate.c +++ b/Utilities/cmcurl/lib/http_negotiate.c @@ -27,9 +27,12 @@ #if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) #include "urldata.h" +#include "cfilters.h" #include "sendf.h" #include "http_negotiate.h" #include "vauth/vauth.h" +#include "vtls/vtls.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -84,8 +87,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, /* Obtain the input token, if any */ header += strlen("Negotiate"); - while(*header && ISBLANK(*header)) - header++; + curlx_str_passblanks(&header); len = strlen(header); neg_ctx->havenegdata = len != 0; @@ -95,7 +97,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, Curl_http_auth_cleanup_negotiate(conn); } else if(state != GSS_AUTHNONE) { - /* The server rejected our authentication and hasn't supplied any more + /* The server rejected our authentication and has not supplied any more negotiation mechanisms */ Curl_http_auth_cleanup_negotiate(conn); return CURLE_LOGIN_DENIED; @@ -106,11 +108,31 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, #if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) neg_ctx->sslContext = conn->sslContext; #endif + /* Check if the connection is using SSL and get the channel binding data */ +#ifdef HAVE_GSSAPI +#ifdef USE_SSL + curlx_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE + 1); + if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = Curl_ssl_get_channel_binding( + data, FIRSTSOCKET, &neg_ctx->channel_binding_data); + if(result) { + Curl_http_auth_cleanup_negotiate(conn); + return result; + } + } +#else + curlx_dyn_init(&neg_ctx->channel_binding_data, 1); +#endif /* USE_SSL */ +#endif /* HAVE_GSSAPI */ /* Initialize the security context and decode our challenge */ result = Curl_auth_decode_spnego_message(data, userp, passwdp, service, host, header, neg_ctx); +#ifdef HAVE_GSSAPI + curlx_dyn_free(&neg_ctx->channel_binding_data); +#endif + if(result) Curl_http_auth_cleanup_negotiate(conn); @@ -120,16 +142,29 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, CURLcode Curl_output_negotiate(struct Curl_easy *data, struct connectdata *conn, bool proxy) { - struct negotiatedata *neg_ctx = proxy ? &conn->proxyneg : - &conn->negotiate; - struct auth *authp = proxy ? &data->state.authproxy : &data->state.authhost; - curlnegotiate *state = proxy ? &conn->proxy_negotiate_state : - &conn->http_negotiate_state; + struct negotiatedata *neg_ctx; + struct auth *authp; + curlnegotiate *state; char *base64 = NULL; size_t len = 0; char *userp; CURLcode result; + if(proxy) { +#ifndef CURL_DISABLE_PROXY + neg_ctx = &conn->proxyneg; + authp = &data->state.authproxy; + state = &conn->proxy_negotiate_state; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + else { + neg_ctx = &conn->negotiate; + authp = &data->state.authhost; + state = &conn->http_negotiate_state; + } + authp->done = FALSE; if(*state == GSS_AUTHRECV) { @@ -171,11 +206,13 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, base64); if(proxy) { - Curl_safefree(data->state.aptr.proxyuserpwd); +#ifndef CURL_DISABLE_PROXY + free(data->state.aptr.proxyuserpwd); data->state.aptr.proxyuserpwd = userp; +#endif } else { - Curl_safefree(data->state.aptr.userpwd); + free(data->state.aptr.userpwd); data->state.aptr.userpwd = userp; } @@ -203,7 +240,7 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, if(*state == GSS_AUTHDONE || *state == GSS_AUTHSUCC) { /* connection is already authenticated, - * don't send a header in future requests */ + * do not send a header in future requests */ authp->done = TRUE; } diff --git a/Utilities/cmcurl/lib/http_ntlm.c b/Utilities/cmcurl/lib/http_ntlm.c index b845ddf37fe..e6a3b175a77 100644 --- a/Utilities/cmcurl/lib/http_ntlm.c +++ b/Utilities/cmcurl/lib/http_ntlm.c @@ -33,17 +33,15 @@ * https://www.innovation.ch/java/ntlm.html */ -#define DEBUG_ME 0 - #include "urldata.h" #include "sendf.h" #include "strcase.h" #include "http_ntlm.h" #include "curl_ntlm_core.h" -#include "curl_ntlm_wb.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "vauth/vauth.h" #include "url.h" +#include "curlx/strparse.h" /* SSL backend-specific #if branches in this file must be kept in the order documented in curl_ntlm_core. */ @@ -56,12 +54,6 @@ #include "curl_memory.h" #include "memdebug.h" -#if DEBUG_ME -# define DEBUG_OUT(x) x -#else -# define DEBUG_OUT(x) Curl_nop_stmt -#endif - CURLcode Curl_input_ntlm(struct Curl_easy *data, bool proxy, /* if proxy or not */ const char *header) /* rest of the www-authenticate: @@ -79,14 +71,12 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, if(checkprefix("NTLM", header)) { header += strlen("NTLM"); - while(*header && ISSPACE(*header)) - header++; - + curlx_str_passblanks(&header); if(*header) { unsigned char *hdr; size_t hdrlen; - result = Curl_base64_decode(header, &hdr, &hdrlen); + result = curlx_base64_decode(header, &hdr, &hdrlen); if(!result) { struct bufref hdrbuf; @@ -124,7 +114,7 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, } /* - * This is for creating ntlm header output + * This is for creating NTLM header output */ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) { @@ -188,10 +178,10 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) passwdp = ""; #ifdef USE_WINDOWS_SSPI - if(!s_hSecDll) { + if(!Curl_hSecDll) { /* not thread safe and leaks - use curl_global_init() to avoid */ CURLcode err = Curl_sspi_global_init(); - if(!s_hSecDll) + if(!Curl_hSecDll) return err; } #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS @@ -201,7 +191,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) Curl_bufref_init(&ntlmmsg); - /* connection is already authenticated, don't send a header in future + /* connection is already authenticated, do not send a header in future * requests so go directly to NTLMSTATE_LAST */ if(*state == NTLMSTATE_TYPE3) *state = NTLMSTATE_LAST; @@ -215,7 +205,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) ntlm, &ntlmmsg); if(!result) { DEBUGASSERT(Curl_bufref_len(&ntlmmsg) != 0); - result = Curl_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), + result = curlx_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), Curl_bufref_len(&ntlmmsg), &base64, &len); if(!result) { free(*allocuserpwd); @@ -234,8 +224,8 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) result = Curl_auth_create_ntlm_type3_message(data, userp, passwdp, ntlm, &ntlmmsg); if(!result && Curl_bufref_len(&ntlmmsg)) { - result = Curl_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), - Curl_bufref_len(&ntlmmsg), &base64, &len); + result = curlx_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), + Curl_bufref_len(&ntlmmsg), &base64, &len); if(!result) { free(*allocuserpwd); *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", @@ -253,6 +243,12 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) break; case NTLMSTATE_LAST: + /* since this is a little artificial in that this is used without any + outgoing auth headers being set, we need to set the bit by force */ + if(proxy) + data->info.proxyauthpicked = CURLAUTH_NTLM; + else + data->info.httpauthpicked = CURLAUTH_NTLM; Curl_safefree(*allocuserpwd); authp->done = TRUE; break; @@ -266,10 +262,6 @@ void Curl_http_auth_cleanup_ntlm(struct connectdata *conn) { Curl_auth_cleanup_ntlm(&conn->ntlm); Curl_auth_cleanup_ntlm(&conn->proxyntlm); - -#if defined(NTLM_WB_ENABLED) - Curl_http_auth_cleanup_ntlm_wb(conn); -#endif } #endif /* !CURL_DISABLE_HTTP && USE_NTLM */ diff --git a/Utilities/cmcurl/lib/http_ntlm.h b/Utilities/cmcurl/lib/http_ntlm.h index f37572baecb..c1cf05701f4 100644 --- a/Utilities/cmcurl/lib/http_ntlm.h +++ b/Utilities/cmcurl/lib/http_ntlm.h @@ -28,11 +28,11 @@ #if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) -/* this is for ntlm header input */ +/* this is for NTLM header input */ CURLcode Curl_input_ntlm(struct Curl_easy *data, bool proxy, const char *header); -/* this is for creating ntlm header output */ +/* this is for creating NTLM header output */ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy); void Curl_http_auth_cleanup_ntlm(struct connectdata *conn); diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c index add376ba4fe..3df6329c06f 100644 --- a/Utilities/cmcurl/lib/http_proxy.c +++ b/Utilities/cmcurl/lib/http_proxy.c @@ -29,9 +29,6 @@ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY) #include -#ifdef USE_HYPER -#include -#endif #include "sendf.h" #include "http.h" #include "url.h" @@ -41,25 +38,275 @@ #include "cf-h1-proxy.h" #include "cf-h2-proxy.h" #include "connect.h" -#include "curlx.h" +#include "strcase.h" #include "vtls/vtls.h" #include "transfer.h" #include "multiif.h" +#include "vauth/vauth.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +static bool hd_name_eq(const char *n1, size_t n1len, + const char *n2, size_t n2len) +{ + return (n1len == n2len) ? strncasecompare(n1, n2, n1len) : FALSE; +} + +static CURLcode dynhds_add_custom(struct Curl_easy *data, + bool is_connect, int httpversion, + struct dynhds *hds) +{ + struct connectdata *conn = data->conn; + const char *ptr; + struct curl_slist *h[2]; + struct curl_slist *headers; + int numlists = 1; /* by default */ + int i; + + enum Curl_proxy_use proxy; + + if(is_connect) + proxy = HEADER_CONNECT; + else + proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ? + HEADER_PROXY : HEADER_SERVER; + + switch(proxy) { + case HEADER_SERVER: + h[0] = data->set.headers; + break; + case HEADER_PROXY: + h[0] = data->set.headers; + if(data->set.sep_headers) { + h[1] = data->set.proxyheaders; + numlists++; + } + break; + case HEADER_CONNECT: + if(data->set.sep_headers) + h[0] = data->set.proxyheaders; + else + h[0] = data->set.headers; + break; + } + + /* loop through one or two lists */ + for(i = 0; i < numlists; i++) { + for(headers = h[i]; headers; headers = headers->next) { + const char *name, *value; + size_t namelen, valuelen; + + /* There are 2 quirks in place for custom headers: + * 1. setting only 'name:' to suppress a header from being sent + * 2. setting only 'name;' to send an empty (illegal) header + */ + ptr = strchr(headers->data, ':'); + if(ptr) { + name = headers->data; + namelen = ptr - headers->data; + ptr++; /* pass the colon */ + curlx_str_passblanks(&ptr); + if(*ptr) { + value = ptr; + valuelen = strlen(value); + } + else { + /* quirk #1, suppress this header */ + continue; + } + } + else { + ptr = strchr(headers->data, ';'); + + if(!ptr) { + /* neither : nor ; in provided header value. We seem + * to ignore this silently */ + continue; + } + + name = headers->data; + namelen = ptr - headers->data; + ptr++; /* pass the semicolon */ + curlx_str_passblanks(&ptr); + if(!*ptr) { + /* quirk #2, send an empty header */ + value = ""; + valuelen = 0; + } + else { + /* this may be used for something else in the future, + * ignore this for now */ + continue; + } + } + + DEBUGASSERT(name && value); + if(data->state.aptr.host && + /* a Host: header was sent already, do not pass on any custom Host: + header as that will produce *two* in the same request! */ + hd_name_eq(name, namelen, STRCONST("Host:"))) + ; + else if(data->state.httpreq == HTTPREQ_POST_FORM && + /* this header (extended by formdata.c) is sent later */ + hd_name_eq(name, namelen, STRCONST("Content-Type:"))) + ; + else if(data->state.httpreq == HTTPREQ_POST_MIME && + /* this header is sent later */ + hd_name_eq(name, namelen, STRCONST("Content-Type:"))) + ; + else if(data->req.authneg && + /* while doing auth neg, do not allow the custom length since + we will force length zero then */ + hd_name_eq(name, namelen, STRCONST("Content-Length:"))) + ; + else if(data->state.aptr.te && + /* when asking for Transfer-Encoding, do not pass on a custom + Connection: */ + hd_name_eq(name, namelen, STRCONST("Connection:"))) + ; + else if((httpversion >= 20) && + hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:"))) + /* HTTP/2 and HTTP/3 do not support chunked requests */ + ; + else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) || + hd_name_eq(name, namelen, STRCONST("Cookie:"))) && + /* be careful of sending this potentially sensitive header to + other hosts */ + !Curl_auth_allowed_to_host(data)) + ; + else { + CURLcode result; + + result = Curl_dynhds_add(hds, name, namelen, value, valuelen); + if(result) + return result; + } + } + } + + return CURLE_OK; +} + +CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, + const char **phostname, + int *pport, bool *pipv6_ip) +{ + DEBUGASSERT(cf); + DEBUGASSERT(cf->conn); + + if(cf->conn->bits.conn_to_host) + *phostname = cf->conn->conn_to_host.name; + else if(cf->sockindex == SECONDARYSOCKET) + *phostname = cf->conn->secondaryhostname; + else + *phostname = cf->conn->host.name; + + if(cf->sockindex == SECONDARYSOCKET) + *pport = cf->conn->secondary_port; + else if(cf->conn->bits.conn_to_port) + *pport = cf->conn->conn_to_port; + else + *pport = cf->conn->remote_port; + + if(*phostname != cf->conn->host.name) + *pipv6_ip = (strchr(*phostname, ':') != NULL); + else + *pipv6_ip = cf->conn->bits.ipv6_ip; + + return CURLE_OK; +} struct cf_proxy_ctx { /* the protocol specific sub-filter we install during connect */ struct Curl_cfilter *cf_protocol; + int httpversion; /* HTTP version used to CONNECT */ }; +CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + int http_version_major) +{ + struct cf_proxy_ctx *ctx = cf->ctx; + const char *hostname = NULL; + char *authority = NULL; + int port; + bool ipv6_ip; + CURLcode result; + struct httpreq *req = NULL; + + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + goto out; + + authority = aprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname, + ipv6_ip ?"]" : "", port); + if(!authority) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1, + NULL, 0, authority, strlen(authority), + NULL, 0); + if(result) + goto out; + + /* Setup the proxy-authorization header, if any */ + result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, + req->authority, TRUE); + if(result) + goto out; + + /* If user is not overriding Host: header, we add for HTTP/1.x */ + if(http_version_major == 1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { + result = Curl_dynhds_cadd(&req->headers, "Host", authority); + if(result) + goto out; + } + + if(data->state.aptr.proxyuserpwd) { + result = Curl_dynhds_h1_cadd_line(&req->headers, + data->state.aptr.proxyuserpwd); + if(result) + goto out; + } + + if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && + data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", + data->set.str[STRING_USERAGENT]); + if(result) + goto out; + } + + if(http_version_major == 1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) { + result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive"); + if(result) + goto out; + } + + result = dynhds_add_custom(data, TRUE, ctx->httpversion, &req->headers); + +out: + if(result && req) { + Curl_http_req_free(req); + req = NULL; + } + free(authority); + *preq = req; + return result; +} + static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; @@ -69,16 +316,17 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } - DEBUGF(LOG_CF(data, cf, "connect")); + CURL_TRC_CF(data, cf, "connect"); connect_sub: - result = cf->next->cft->connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; *done = FALSE; if(!ctx->cf_protocol) { struct Curl_cfilter *cf_protocol = NULL; - int alpn = Curl_conn_cf_is_ssl(cf->next)? + int httpversion = 0; + int alpn = Curl_conn_cf_is_ssl(cf->next) ? cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1; /* First time call after the subchain connected */ @@ -86,32 +334,34 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, case CURL_HTTP_VERSION_NONE: case CURL_HTTP_VERSION_1_0: case CURL_HTTP_VERSION_1_1: - DEBUGF(LOG_CF(data, cf, "installing subfilter for HTTP/1.1")); + CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1"); infof(data, "CONNECT tunnel: HTTP/1.%d negotiated", - (alpn == CURL_HTTP_VERSION_1_0)? 0 : 1); + (alpn == CURL_HTTP_VERSION_1_0) ? 0 : 1); result = Curl_cf_h1_proxy_insert_after(cf, data); if(result) goto out; cf_protocol = cf->next; + httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11; break; #ifdef USE_NGHTTP2 case CURL_HTTP_VERSION_2: - DEBUGF(LOG_CF(data, cf, "installing subfilter for HTTP/2")); + CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); infof(data, "CONNECT tunnel: HTTP/2 negotiated"); result = Curl_cf_h2_proxy_insert_after(cf, data); if(result) goto out; cf_protocol = cf->next; + httpversion = 20; break; #endif default: - DEBUGF(LOG_CF(data, cf, "installing subfilter for default HTTP/1.1")); infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn); result = CURLE_COULDNT_CONNECT; goto out; } ctx->cf_protocol = cf_protocol; + ctx->httpversion = httpversion; /* after we installed the filter "below" us, we call connect * on out sub-chain again. */ @@ -156,7 +406,7 @@ static void http_proxy_cf_destroy(struct Curl_cfilter *cf, struct cf_proxy_ctx *ctx = cf->ctx; (void)data; - DEBUGF(LOG_CF(data, cf, "destroy")); + CURL_TRC_CF(data, cf, "destroy"); free(ctx); } @@ -165,7 +415,7 @@ static void http_proxy_cf_close(struct Curl_cfilter *cf, { struct cf_proxy_ctx *ctx = cf->ctx; - DEBUGF(LOG_CF(data, cf, "close")); + CURL_TRC_CF(data, cf, "close"); cf->connected = FALSE; if(ctx->cf_protocol) { struct Curl_cfilter *f; @@ -181,19 +431,20 @@ static void http_proxy_cf_close(struct Curl_cfilter *cf, ctx->cf_protocol = NULL; } if(cf->next) - cf->next->cft->close(cf->next, data); + cf->next->cft->do_close(cf->next, data); } struct Curl_cftype Curl_cft_http_proxy = { "HTTP-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, http_proxy_cf_destroy, http_proxy_cf_connect, http_proxy_cf_close, + Curl_cf_def_shutdown, Curl_cf_http_proxy_get_host, - Curl_cf_def_get_select_socks, + Curl_cf_def_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, diff --git a/Utilities/cmcurl/lib/http_proxy.h b/Utilities/cmcurl/lib/http_proxy.h index a1a03720bd0..2e91ff20371 100644 --- a/Utilities/cmcurl/lib/http_proxy.h +++ b/Utilities/cmcurl/lib/http_proxy.h @@ -30,6 +30,21 @@ #include "urldata.h" +enum Curl_proxy_use { + HEADER_SERVER, /* direct to server */ + HEADER_PROXY, /* regular request to proxy */ + HEADER_CONNECT /* sending CONNECT to a proxy */ +}; + +CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, + const char **phostname, + int *pport, bool *pipv6_ip); + +CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + int http_version_major); + /* Default proxy timeout in milliseconds */ #define PROXY_TIMEOUT (3600*1000) diff --git a/Utilities/cmcurl/lib/httpsrr.c b/Utilities/cmcurl/lib/httpsrr.c new file mode 100644 index 00000000000..df93ac34ac0 --- /dev/null +++ b/Utilities/cmcurl/lib/httpsrr.c @@ -0,0 +1,213 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef USE_HTTPSRR + +#include "urldata.h" +#include "curl_addrinfo.h" +#include "httpsrr.h" +#include "connect.h" +#include "sendf.h" +#include "strdup.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define MAX_ALPN_LENGTH 255 + +static CURLcode httpsrr_decode_alpn(const char *cp, size_t len, + unsigned char *alpns) +{ + /* + * The wire-format value for "alpn" consists of at least one alpn-id + * prefixed by its length as a single octet, and these length-value pairs + * are concatenated to form the SvcParamValue. These pairs MUST exactly fill + * the SvcParamValue; otherwise, the SvcParamValue is malformed. + */ + int idnum = 0; + + while(len > 0) { + size_t tlen = (size_t) *cp++; + enum alpnid id; + len--; + if(tlen > len) + return CURLE_BAD_CONTENT_ENCODING; + + /* we only store ALPN ids we know about */ + id = Curl_alpn2alpnid(cp, tlen); + if(id != ALPN_none) { + if(idnum == MAX_HTTPSRR_ALPNS) + break; + if(idnum && memchr(alpns, id, idnum)) + /* this ALPN id is already stored */ + ; + else + alpns[idnum++] = (unsigned char)id; + } + cp += tlen; + len -= tlen; + } + if(idnum < MAX_HTTPSRR_ALPNS) + alpns[idnum] = ALPN_none; /* terminate the list */ + return CURLE_OK; +} + +CURLcode Curl_httpsrr_set(struct Curl_easy *data, + struct Curl_https_rrinfo *hi, + uint16_t rrkey, const uint8_t *val, size_t vlen) +{ + CURLcode result = CURLE_OK; + switch(rrkey) { + case HTTPS_RR_CODE_MANDATORY: + CURL_TRC_DNS(data, "HTTPS RR MANDATORY left to implement"); + break; + case HTTPS_RR_CODE_ALPN: /* str_list */ + result = httpsrr_decode_alpn((const char *)val, vlen, hi->alpns); + CURL_TRC_DNS(data, "HTTPS RR ALPN: %u %u %u %u", + hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]); + break; + case HTTPS_RR_CODE_NO_DEF_ALPN: + if(vlen) /* no data */ + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->no_def_alpn = TRUE; + CURL_TRC_DNS(data, "HTTPS RR no-def-alpn"); + break; + case HTTPS_RR_CODE_IPV4: /* addr4 list */ + if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */ + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->ipv4hints = Curl_memdup(val, vlen); + if(!hi->ipv4hints) + return CURLE_OUT_OF_MEMORY; + hi->ipv4hints_len = vlen; + CURL_TRC_DNS(data, "HTTPS RR IPv4"); + break; + case HTTPS_RR_CODE_ECH: + if(!vlen) + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->echconfiglist = Curl_memdup(val, vlen); + if(!hi->echconfiglist) + return CURLE_OUT_OF_MEMORY; + hi->echconfiglist_len = vlen; + CURL_TRC_DNS(data, "HTTPS RR ECH"); + break; + case HTTPS_RR_CODE_IPV6: /* addr6 list */ + if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */ + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->ipv6hints = Curl_memdup(val, vlen); + if(!hi->ipv6hints) + return CURLE_OUT_OF_MEMORY; + hi->ipv6hints_len = vlen; + CURL_TRC_DNS(data, "HTTPS RR IPv6"); + break; + case HTTPS_RR_CODE_PORT: + if(vlen != 2) + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->port = (unsigned short)((val[0] << 8) | val[1]); + CURL_TRC_DNS(data, "HTTPS RR port %u", hi->port); + break; + default: + CURL_TRC_DNS(data, "HTTPS RR unknown code"); + break; + } + return result; +} + +struct Curl_https_rrinfo * +Curl_httpsrr_dup_move(struct Curl_https_rrinfo *rrinfo) +{ + struct Curl_https_rrinfo *dup = Curl_memdup(rrinfo, sizeof(*rrinfo)); + if(dup) + memset(rrinfo, 0, sizeof(*rrinfo)); + return dup; +} + +void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) +{ + Curl_safefree(rrinfo->target); + Curl_safefree(rrinfo->echconfiglist); + Curl_safefree(rrinfo->ipv4hints); + Curl_safefree(rrinfo->ipv6hints); +} + + +#ifdef USE_ARES + +static CURLcode httpsrr_opt(struct Curl_easy *data, + const ares_dns_rr_t *rr, + ares_dns_rr_key_t key, size_t idx, + struct Curl_https_rrinfo *hinfo) +{ + const unsigned char *val = NULL; + unsigned short code; + size_t len = 0; + + code = ares_dns_rr_get_opt(rr, key, idx, &val, &len); + return Curl_httpsrr_set(data, hinfo, code, val, len); +} + +CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, + const ares_dns_record_t *dnsrec, + struct Curl_https_rrinfo *hinfo) +{ + CURLcode result = CURLE_OK; + size_t i; + + for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) { + const char *target; + size_t opt; + const ares_dns_rr_t *rr = + ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); + if(ares_dns_rr_get_type(rr) != ARES_REC_TYPE_HTTPS) + continue; + /* When SvcPriority is 0, the SVCB record is in AliasMode. Otherwise, it + is in ServiceMode */ + target = ares_dns_rr_get_str(rr, ARES_RR_HTTPS_TARGET); + if(target && target[0]) { + hinfo->target = strdup(target); + if(!hinfo->target) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + CURL_TRC_DNS(data, "HTTPS RR target: %s", hinfo->target); + } + CURL_TRC_DNS(data, "HTTPS RR priority: %u", + ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY)); + for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS); + opt++) { + result = httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); + if(result) + break; + } + } +out: + return result; +} + +#endif /* USE_ARES */ + +#endif /* USE_HTTPSRR */ diff --git a/Utilities/cmcurl/lib/httpsrr.h b/Utilities/cmcurl/lib/httpsrr.h new file mode 100644 index 00000000000..83119dc6bde --- /dev/null +++ b/Utilities/cmcurl/lib/httpsrr.h @@ -0,0 +1,86 @@ +#ifndef HEADER_CURL_HTTPSRR_H +#define HEADER_CURL_HTTPSRR_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" + +#ifdef USE_ARES +#include +#endif + +#ifdef USE_HTTPSRR + +#define CURL_MAXLEN_host_name 253 +#define MAX_HTTPSRR_ALPNS 4 + +struct Curl_easy; + +struct Curl_https_rrinfo { + /* + * Fields from HTTPS RR. The only mandatory fields are priority and target. + * See https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 + */ + char *target; + unsigned char *ipv4hints; /* keytag = 4 */ + size_t ipv4hints_len; + unsigned char *echconfiglist; /* keytag = 5 */ + size_t echconfiglist_len; + unsigned char *ipv6hints; /* keytag = 6 */ + size_t ipv6hints_len; + unsigned char alpns[MAX_HTTPSRR_ALPNS]; /* keytag = 1 */ + /* store parsed alpnid entries in the array, end with ALPN_none */ + int port; /* -1 means not set */ + uint16_t priority; + BIT(no_def_alpn); /* keytag = 2 */ +}; + +CURLcode Curl_httpsrr_set(struct Curl_easy *data, + struct Curl_https_rrinfo *hi, + uint16_t rrkey, const uint8_t *val, size_t vlen); + +struct Curl_https_rrinfo * +Curl_httpsrr_dup_move(struct Curl_https_rrinfo *rrinfo); + +void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); + +/* + * Code points for DNS wire format SvcParams as per RFC 9460 + */ +#define HTTPS_RR_CODE_MANDATORY 0x00 +#define HTTPS_RR_CODE_ALPN 0x01 +#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02 +#define HTTPS_RR_CODE_PORT 0x03 +#define HTTPS_RR_CODE_IPV4 0x04 +#define HTTPS_RR_CODE_ECH 0x05 +#define HTTPS_RR_CODE_IPV6 0x06 + +#if defined(USE_ARES) +CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, + const ares_dns_record_t *dnsrec, + struct Curl_https_rrinfo *hinfo); +#endif /* USE_ARES */ +#endif /* USE_HTTPSRR */ + +#endif /* HEADER_CURL_HTTPSRR_H */ diff --git a/Utilities/cmcurl/lib/idn.c b/Utilities/cmcurl/lib/idn.c index 5f4b07e0184..798c9aaef1a 100644 --- a/Utilities/cmcurl/lib/idn.c +++ b/Utilities/cmcurl/lib/idn.c @@ -30,13 +30,13 @@ #include "urldata.h" #include "idn.h" #include "sendf.h" -#include "curl_multibyte.h" -#include "warnless.h" +#include "curlx/multibyte.h" +#include "curlx/warnless.h" #ifdef USE_LIBIDN2 #include -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) #define IDN2_LOOKUP(name, host, flags) \ idn2_lookup_u8((const uint8_t *)name, (uint8_t **)host, flags) #else @@ -50,10 +50,110 @@ #include "curl_memory.h" #include "memdebug.h" +/* for macOS and iOS targets */ +#if defined(USE_APPLE_IDN) +#include +#include +#include + +#define MAX_HOST_LENGTH 512 + +static CURLcode iconv_to_utf8(const char *in, size_t inlen, + char **out, size_t *outlen) +{ + iconv_t cd = iconv_open("UTF-8", nl_langinfo(CODESET)); + if(cd != (iconv_t)-1) { + size_t iconv_outlen = *outlen; + char *iconv_in = (char *)CURL_UNCONST(in); + size_t iconv_inlen = inlen; + size_t iconv_result = iconv(cd, &iconv_in, &iconv_inlen, + out, &iconv_outlen); + *outlen -= iconv_outlen; + iconv_close(cd); + if(iconv_result == (size_t)-1) { + /* !checksrc! disable ERRNOVAR 1 */ + if(errno == ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_URL_MALFORMAT; + } + + return CURLE_OK; + } + else { + /* !checksrc! disable ERRNOVAR 1 */ + if(errno == ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_FAILED_INIT; + } +} + +static CURLcode mac_idn_to_ascii(const char *in, char **out) +{ + size_t inlen = strlen(in); + if(inlen < MAX_HOST_LENGTH) { + char iconv_buffer[MAX_HOST_LENGTH] = {0}; + char *iconv_outptr = iconv_buffer; + size_t iconv_outlen = sizeof(iconv_buffer); + CURLcode iconv_result = iconv_to_utf8(in, inlen, + &iconv_outptr, &iconv_outlen); + if(!iconv_result) { + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46( + UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_ASCII, &err); + if(!U_FAILURE(err)) { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[MAX_HOST_LENGTH] = {0}; + (void)uidna_nameToASCII_UTF8(idna, iconv_buffer, (int)iconv_outlen, + buffer, sizeof(buffer) - 1, &info, &err); + uidna_close(idna); + if(!U_FAILURE(err) && !info.errors) { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } + } + else + return iconv_result; + } + return CURLE_URL_MALFORMAT; +} + +static CURLcode mac_ascii_to_idn(const char *in, char **out) +{ + size_t inlen = strlen(in); + if(inlen < MAX_HOST_LENGTH) { + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46( + UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_UNICODE, &err); + if(!U_FAILURE(err)) { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[MAX_HOST_LENGTH] = {0}; + (void)uidna_nameToUnicodeUTF8(idna, in, -1, buffer, + sizeof(buffer) - 1, &info, &err); + uidna_close(idna); + if(!U_FAILURE(err)) { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } + } + return CURLE_URL_MALFORMAT; +} +#endif + #ifdef USE_WIN32_IDN /* using Windows kernel32 and normaliz libraries. */ -#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x600 +#if (!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x600) && \ + (!defined(WINVER) || WINVER < 0x600) WINBASEAPI int WINAPI IdnToAscii(DWORD dwFlags, const WCHAR *lpUnicodeCharStr, int cchUnicodeChar, @@ -68,27 +168,61 @@ WINBASEAPI int WINAPI IdnToUnicode(DWORD dwFlags, #define IDN_MAX_LENGTH 255 -bool Curl_win32_idn_to_ascii(const char *in, char **out) +static CURLcode win32_idn_to_ascii(const char *in, char **out) { - bool success = FALSE; - wchar_t *in_w = curlx_convert_UTF8_to_wchar(in); + *out = NULL; if(in_w) { wchar_t punycode[IDN_MAX_LENGTH]; - int chars = IdnToAscii(0, in_w, -1, punycode, IDN_MAX_LENGTH); + int chars = IdnToAscii(0, in_w, (int)(wcslen(in_w) + 1), punycode, + IDN_MAX_LENGTH); curlx_unicodefree(in_w); if(chars) { char *mstr = curlx_convert_wchar_to_UTF8(punycode); if(mstr) { *out = strdup(mstr); curlx_unicodefree(mstr); - if(*out) - success = TRUE; + if(!*out) + return CURLE_OUT_OF_MEMORY; } + else + return CURLE_OUT_OF_MEMORY; } + else + return CURLE_URL_MALFORMAT; } + else + return CURLE_URL_MALFORMAT; - return success; + return CURLE_OK; +} + +static CURLcode win32_ascii_to_idn(const char *in, char **output) +{ + char *out = NULL; + + wchar_t *in_w = curlx_convert_UTF8_to_wchar(in); + if(in_w) { + WCHAR idn[IDN_MAX_LENGTH]; /* stores a UTF-16 string */ + int chars = IdnToUnicode(0, in_w, (int)(wcslen(in_w) + 1), idn, + IDN_MAX_LENGTH); + if(chars) { + /* 'chars' is "the number of characters retrieved" */ + char *mstr = curlx_convert_wchar_to_UTF8(idn); + if(mstr) { + out = strdup(mstr); + curlx_unicodefree(mstr); + if(!out) + return CURLE_OUT_OF_MEMORY; + } + } + else + return CURLE_URL_MALFORMAT; + } + else + return CURLE_URL_MALFORMAT; + *output = out; + return CURLE_OK; } #endif /* USE_WIN32_IDN */ @@ -115,10 +249,15 @@ bool Curl_is_ASCII_name(const char *hostname) /* * Curl_idn_decode() returns an allocated IDN decoded string if it was * possible. NULL on error. + * + * CURLE_URL_MALFORMAT - the hostname could not be converted + * CURLE_OUT_OF_MEMORY - memory problem + * */ -static char *idn_decode(const char *input) +static CURLcode idn_decode(const char *input, char **output) { char *decoded = NULL; + CURLcode result = CURLE_OK; #ifdef USE_LIBIDN2 if(idn2_check_version(IDN2_VERSION)) { int flags = IDN2_NFC_INPUT @@ -135,26 +274,77 @@ static char *idn_decode(const char *input) compatibility */ rc = IDN2_LOOKUP(input, &decoded, IDN2_TRANSITIONAL); if(rc != IDN2_OK) - decoded = NULL; + result = CURLE_URL_MALFORMAT; } + else + /* a too old libidn2 version */ + result = CURLE_NOT_BUILT_IN; #elif defined(USE_WIN32_IDN) - if(!Curl_win32_idn_to_ascii(input, &decoded)) - decoded = NULL; + result = win32_idn_to_ascii(input, &decoded); +#elif defined(USE_APPLE_IDN) + result = mac_idn_to_ascii(input, &decoded); #endif - return decoded; + if(!result) + *output = decoded; + return result; } -char *Curl_idn_decode(const char *input) +static CURLcode idn_encode(const char *puny, char **output) { - char *d = idn_decode(input); + char *enc = NULL; #ifdef USE_LIBIDN2 - if(d) { + int rc = idn2_to_unicode_8z8z(puny, &enc, 0); + if(rc != IDNA_SUCCESS) + return rc == IDNA_MALLOC_ERROR ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT; +#elif defined(USE_WIN32_IDN) + CURLcode result = win32_ascii_to_idn(puny, &enc); + if(result) + return result; +#elif defined(USE_APPLE_IDN) + CURLcode result = mac_ascii_to_idn(puny, &enc); + if(result) + return result; +#endif + *output = enc; + return CURLE_OK; +} + +CURLcode Curl_idn_decode(const char *input, char **output) +{ + char *d = NULL; + CURLcode result = idn_decode(input, &d); +#ifdef USE_LIBIDN2 + if(!result) { char *c = strdup(d); idn2_free(d); - d = c; + if(c) + d = c; + else + result = CURLE_OUT_OF_MEMORY; } #endif - return d; + if(!result) + *output = d; + return result; +} + +CURLcode Curl_idn_encode(const char *puny, char **output) +{ + char *d = NULL; + CURLcode result = idn_encode(puny, &d); +#ifdef USE_LIBIDN2 + if(!result) { + char *c = strdup(d); + idn2_free(d); + if(c) + d = c; + else + result = CURLE_OUT_OF_MEMORY; + } +#endif + if(!result) + *output = d; + return result; } /* @@ -162,11 +352,7 @@ char *Curl_idn_decode(const char *input) */ void Curl_free_idnconverted_hostname(struct hostname *host) { - if(host->encalloc) { - /* must be freed with idn2_free() if allocated by libidn */ - Curl_idn_free(host->encalloc); - host->encalloc = NULL; - } + Curl_safefree(host->encalloc); } #endif /* USE_IDN */ @@ -176,26 +362,18 @@ void Curl_free_idnconverted_hostname(struct hostname *host) */ CURLcode Curl_idnconvert_hostname(struct hostname *host) { - /* set the name we use to display the host name */ + /* set the name we use to display the hostname */ host->dispname = host->name; #ifdef USE_IDN /* Check name for non-ASCII and convert hostname if we can */ if(!Curl_is_ASCII_name(host->name)) { - char *decoded = idn_decode(host->name); - if(decoded) { - if(!*decoded) { - /* zero length is a bad host name */ - Curl_idn_free(decoded); - return CURLE_URL_MALFORMAT; - } - /* successful */ - host->encalloc = decoded; - /* change the name pointer to point to the encoded hostname */ - host->name = host->encalloc; - } - else - return CURLE_URL_MALFORMAT; + char *decoded; + CURLcode result = Curl_idn_decode(host->name, &decoded); + if(result) + return result; + /* successful */ + host->name = host->encalloc = decoded; } #endif return CURLE_OK; diff --git a/Utilities/cmcurl/lib/idn.h b/Utilities/cmcurl/lib/idn.h index 6c0bbb71093..2bdce8927f9 100644 --- a/Utilities/cmcurl/lib/idn.h +++ b/Utilities/cmcurl/lib/idn.h @@ -24,20 +24,13 @@ * ***************************************************************************/ -#ifdef USE_WIN32_IDN -bool Curl_win32_idn_to_ascii(const char *in, char **out); -#endif /* USE_WIN32_IDN */ bool Curl_is_ASCII_name(const char *hostname); CURLcode Curl_idnconvert_hostname(struct hostname *host); -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) #define USE_IDN void Curl_free_idnconverted_hostname(struct hostname *host); -char *Curl_idn_decode(const char *input); -#ifdef USE_LIBIDN2 -#define Curl_idn_free(x) idn2_free(x) -#else -#define Curl_idn_free(x) free(x) -#endif +CURLcode Curl_idn_decode(const char *input, char **output); +CURLcode Curl_idn_encode(const char *input, char **output); #else #define Curl_free_idnconverted_hostname(x) diff --git a/Utilities/cmcurl/lib/if2ip.c b/Utilities/cmcurl/lib/if2ip.c index 6bf0ce16f79..6da68efd500 100644 --- a/Utilities/cmcurl/lib/if2ip.c +++ b/Utilities/cmcurl/lib/if2ip.c @@ -62,12 +62,13 @@ /* ------------------------------------------------------------------ */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* Return the scope of the given address. */ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) { if(sa->sa_family == AF_INET6) { - const struct sockaddr_in6 * sa6 = (const struct sockaddr_in6 *)(void *) sa; + const struct sockaddr_in6 * sa6 = + (const struct sockaddr_in6 *)(const void *) sa; const unsigned char *b = sa6->sin6_addr.s6_addr; unsigned short w = (unsigned short) ((b[0] << 8) | b[1]); @@ -92,20 +93,22 @@ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) } #endif +#if !defined(CURL_DISABLE_BINDLOCAL) || !defined(CURL_DISABLE_FTP) + #if defined(HAVE_GETIFADDRS) if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { struct ifaddrs *iface, *head; if2ip_result_t res = IF2IP_NOT_FOUND; -#if defined(ENABLE_IPV6) && \ +#if defined(USE_IPV6) && \ !defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) (void) local_scope_id; #endif @@ -119,7 +122,7 @@ if2ip_result_t Curl_if2ip(int af, const char *ip; char scope[12] = ""; char ipstr[64]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID unsigned int scopeid = 0; @@ -180,12 +183,12 @@ if2ip_result_t Curl_if2ip(int af, #elif defined(HAVE_IOCTL_SIOCGIFADDR) if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { struct ifreq req; struct in_addr in; @@ -194,7 +197,7 @@ if2ip_result_t Curl_if2ip(int af, size_t len; const char *r; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 (void)remote_scope; (void)local_scope_id; #endif @@ -214,7 +217,15 @@ if2ip_result_t Curl_if2ip(int af, memcpy(req.ifr_name, interf, len + 1); req.ifr_addr.sa_family = AF_INET; +#if defined(__GNUC__) && defined(_AIX) +/* Suppress warning inside system headers */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-sign-overflow" +#endif if(ioctl(dummy, SIOCGIFADDR, &req) < 0) { +#if defined(__GNUC__) && defined(_AIX) +#pragma GCC diagnostic pop +#endif sclose(dummy); /* With SIOCGIFADDR, we cannot tell the difference between an interface that does not exist and an interface that has no address of the @@ -235,15 +246,15 @@ if2ip_result_t Curl_if2ip(int af, #else if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { (void) af; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 (void) remote_scope; (void) local_scope_id; #endif @@ -254,3 +265,5 @@ if2ip_result_t Curl_if2ip(int af, } #endif + +#endif /* CURL_DISABLE_BINDLOCAL && CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/if2ip.h b/Utilities/cmcurl/lib/if2ip.h index 1f973505c08..f4b2f4c15d9 100644 --- a/Utilities/cmcurl/lib/if2ip.h +++ b/Utilities/cmcurl/lib/if2ip.h @@ -32,7 +32,7 @@ #define IPV6_SCOPE_UNIQUELOCAL 3 /* Unique local */ #define IPV6_SCOPE_NODELOCAL 4 /* Loopback. */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int Curl_ipv6_scope(const struct sockaddr *sa); #else #define Curl_ipv6_scope(x) 0 @@ -45,12 +45,12 @@ typedef enum { } if2ip_result_t; if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size); + char *buf, size_t buf_size); #ifdef __INTERIX diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c index ed197c9316b..fc38a5b61e0 100644 --- a/Utilities/cmcurl/lib/imap.c +++ b/Utilities/cmcurl/lib/imap.c @@ -36,6 +36,7 @@ ***************************************************************************/ #include "curl_setup.h" +#include "curlx/dynbuf.h" #ifndef CURL_DISABLE_IMAP @@ -45,9 +46,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -67,7 +65,7 @@ #include "socks.h" #include "imap.h" #include "mime.h" -#include "strtoofft.h" +#include "curlx/strparse.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -77,7 +75,7 @@ #include "url.h" #include "bufref.h" #include "curl_sasl.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_ctype.h" /* The last 3 #include files should be in this order */ @@ -85,8 +83,73 @@ #include "curl_memory.h" #include "memdebug.h" + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_IMAP_EASY "meta:proto:imap:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_IMAP_CONN "meta:proto:imap:conn" + +typedef enum { + IMAP_STOP, /* do nothing state, stops the state machine */ + IMAP_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + IMAP_CAPABILITY, + IMAP_STARTTLS, + IMAP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + IMAP_AUTHENTICATE, + IMAP_LOGIN, + IMAP_LIST, + IMAP_SELECT, + IMAP_FETCH, + IMAP_FETCH_FINAL, + IMAP_APPEND, + IMAP_APPEND_FINAL, + IMAP_SEARCH, + IMAP_LOGOUT, + IMAP_LAST /* never used */ +} imapstate; + +/* imap_conn is used for struct connection-oriented data */ +struct imap_conn { + struct pingpong pp; + struct SASL sasl; /* SASL-related parameters */ + struct dynbuf dyn; /* for the IMAP commands */ + char *mailbox; /* The last selected mailbox */ + char *mailbox_uidvalidity; /* UIDVALIDITY parsed from select response */ + imapstate state; /* Always use imap.c:state() to change state! */ + char resptag[5]; /* Response tag to wait for */ + unsigned char preftype; /* Preferred authentication type */ + unsigned char cmdid; /* Last used command ID */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(preauth); /* Is this connection PREAUTH? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(login_disabled); /* LOGIN command disabled by server */ + BIT(ir_supported); /* Initial response supported by server */ +}; + +/* This IMAP struct is used in the Curl_easy. All IMAP data that is + connection-oriented must be in imap_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct IMAP { + curl_pp_transfer transfer; + char *mailbox; /* Mailbox to select */ + char *uidvalidity; /* UIDVALIDITY to check in select */ + char *uid; /* Message UID to fetch */ + char *mindex; /* Index in mail box of mail to fetch */ + char *section; /* Message SECTION to fetch */ + char *partial; /* Message PARTIAL to fetch */ + char *query; /* Query to search for */ + char *custom; /* Custom request */ + char *custom_params; /* Parameters for the custom request */ +}; + + /* Local API functions */ -static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode imap_regular_transfer(struct Curl_easy *data, + struct IMAP *imap, + bool *done); static CURLcode imap_do(struct Curl_easy *data, bool *done); static CURLcode imap_done(struct Curl_easy *data, CURLcode status, bool premature); @@ -100,10 +163,15 @@ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done); static CURLcode imap_setup_connection(struct Curl_easy *data, struct connectdata *conn); static char *imap_atom(const char *str, bool escape_only); -static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...); -static CURLcode imap_parse_url_options(struct connectdata *conn); -static CURLcode imap_parse_url_path(struct Curl_easy *data); -static CURLcode imap_parse_custom_request(struct Curl_easy *data); +static CURLcode imap_sendf(struct Curl_easy *data, + struct imap_conn *imapc, + const char *fmt, ...) CURL_PRINTF(3, 4); +static CURLcode imap_parse_url_options(struct connectdata *conn, + struct imap_conn *imapc); +static CURLcode imap_parse_url_path(struct Curl_easy *data, + struct IMAP *imap); +static CURLcode imap_parse_custom_request(struct Curl_easy *data, + struct IMAP *imap); static CURLcode imap_perform_authenticate(struct Curl_easy *data, const char *mech, const struct bufref *initresp); @@ -113,13 +181,14 @@ static CURLcode imap_continue_authenticate(struct Curl_easy *data, static CURLcode imap_cancel_authenticate(struct Curl_easy *data, const char *mech); static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out); +static void imap_easy_reset(struct IMAP *imap); /* * IMAP protocol handler. */ const struct Curl_handler Curl_handler_imap = { - "IMAP", /* scheme */ + "imap", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -132,9 +201,11 @@ const struct Curl_handler Curl_handler_imap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_IMAP, /* defport */ CURLPROTO_IMAP, /* protocol */ CURLPROTO_IMAP, /* family */ @@ -148,7 +219,7 @@ const struct Curl_handler Curl_handler_imap = { */ const struct Curl_handler Curl_handler_imaps = { - "IMAPS", /* scheme */ + "imaps", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -161,9 +232,11 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_IMAPS, /* defport */ CURLPROTO_IMAPS, /* protocol */ CURLPROTO_IMAP, /* family */ @@ -190,19 +263,10 @@ static const struct SASLproto saslimap = { SASL_FLAG_BASE64 /* Configuration flags */ }; - -#ifdef USE_SSL -static void imap_to_imaps(struct connectdata *conn) -{ - /* Change the connection handler */ - conn->handler = &Curl_handler_imaps; - - /* Set the connection's upgraded to TLS flag */ - conn->bits.tls_upgraded = TRUE; -} -#else -#define imap_to_imaps(x) Curl_nop_stmt -#endif +struct ulbits { + int bit; + const char *flag; +}; /*********************************************************************** * @@ -253,14 +317,21 @@ static bool imap_matchresp(const char *line, size_t len, const char *cmd) * response which can be processed by the response handler. */ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *resp) + const char *line, size_t len, int *resp) { - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; - const char *id = imapc->resptag; - size_t id_len = strlen(id); + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + const char *id; + size_t id_len; + + DEBUGASSERT(imapc); + DEBUGASSERT(imap); + if(!imapc || !imap) + return FALSE; /* Do we have a tagged command response? */ + id = imapc->resptag; + id_len = strlen(id); if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') { line += id_len + 1; len -= id_len + 1; @@ -286,17 +357,17 @@ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, case IMAP_LIST: if((!imap->custom && !imap_matchresp(line, len, "LIST")) || - (imap->custom && !imap_matchresp(line, len, imap->custom) && - (!strcasecompare(imap->custom, "STORE") || - !imap_matchresp(line, len, "FETCH")) && - !strcasecompare(imap->custom, "SELECT") && - !strcasecompare(imap->custom, "EXAMINE") && - !strcasecompare(imap->custom, "SEARCH") && - !strcasecompare(imap->custom, "EXPUNGE") && - !strcasecompare(imap->custom, "LSUB") && - !strcasecompare(imap->custom, "UID") && - !strcasecompare(imap->custom, "GETQUOTAROOT") && - !strcasecompare(imap->custom, "NOOP"))) + (imap->custom && !imap_matchresp(line, len, imap->custom) && + (!strcasecompare(imap->custom, "STORE") || + !imap_matchresp(line, len, "FETCH")) && + !strcasecompare(imap->custom, "SELECT") && + !strcasecompare(imap->custom, "EXAMINE") && + !strcasecompare(imap->custom, "SEARCH") && + !strcasecompare(imap->custom, "EXPUNGE") && + !strcasecompare(imap->custom, "LSUB") && + !strcasecompare(imap->custom, "UID") && + !strcasecompare(imap->custom, "GETQUOTAROOT") && + !strcasecompare(imap->custom, "NOOP"))) return FALSE; break; @@ -328,8 +399,8 @@ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, a space and optionally some text as per RFC-3501 for the AUTHENTICATE and APPEND commands and as outlined in Section 4. Examples of RFC-4959 but some email servers ignore this and only send a single + instead. */ - if(imap && !imap->custom && ((len == 3 && line[0] == '+') || - (len >= 2 && !memcmp("+ ", line, 2)))) { + if(!imap->custom && ((len == 3 && line[0] == '+') || + (len >= 2 && !memcmp("+ ", line, 2)))) { switch(imapc->state) { /* States which are interested in continuation responses */ case IMAP_AUTHENTICATE: @@ -357,9 +428,16 @@ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); + char *message; + size_t len; + + if(!imapc) + return CURLE_FAILED_INIT; + message = curlx_dyn_ptr(&imapc->pp.recvbuf); + len = imapc->pp.nfinal; if(len > 2) { /* Find the start of the message */ len -= 2; @@ -385,13 +463,14 @@ static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out) /*********************************************************************** * - * state() + * imap_state() * * This is the ONLY way to change IMAP state! */ -static void state(struct Curl_easy *data, imapstate newstate) +static void imap_state(struct Curl_easy *data, + struct imap_conn *imapc, + imapstate newstate) { - struct imap_conn *imapc = &data->conn->proto.imapc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[]={ @@ -417,7 +496,7 @@ static void state(struct Curl_easy *data, imapstate newstate) infof(data, "IMAP %p state change from %s to %s", (void *)imapc, names[imapc->state], names[newstate]); #endif - + (void)data; imapc->state = newstate; } @@ -429,19 +508,19 @@ static void state(struct Curl_easy *data, imapstate newstate) * supported capabilities. */ static CURLcode imap_perform_capability(struct Curl_easy *data, - struct connectdata *conn) + struct imap_conn *imapc) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; + imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */ imapc->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */ imapc->tls_supported = FALSE; /* Clear the TLS capability */ /* Send the CAPABILITY command */ - result = imap_sendf(data, "CAPABILITY"); + result = imap_sendf(data, imapc, "CAPABILITY"); if(!result) - state(data, IMAP_CAPABILITY); + imap_state(data, imapc, IMAP_CAPABILITY); return result; } @@ -452,13 +531,14 @@ static CURLcode imap_perform_capability(struct Curl_easy *data, * * Sends the STARTTLS command to start the upgrade to TLS. */ -static CURLcode imap_perform_starttls(struct Curl_easy *data) +static CURLcode imap_perform_starttls(struct Curl_easy *data, + struct imap_conn *imapc) { /* Send the STARTTLS command */ - CURLcode result = imap_sendf(data, "STARTTLS"); + CURLcode result = imap_sendf(data, imapc, "STARTTLS"); if(!result) - state(data, IMAP_STARTTLS); + imap_state(data, imapc, IMAP_STARTTLS); return result; } @@ -470,10 +550,11 @@ static CURLcode imap_perform_starttls(struct Curl_easy *data) * Performs the upgrade to TLS. */ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, + struct imap_conn *imapc, struct connectdata *conn) { +#ifdef USE_SSL /* Start the SSL connection */ - struct imap_conn *imapc = &conn->proto.imapc; CURLcode result; bool ssldone = FALSE; @@ -481,21 +562,27 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) goto out; + /* Change the connection handler */ + conn->handler = &Curl_handler_imaps; } + DEBUGASSERT(!imapc->ssldone); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); - if(!result) { + DEBUGF(infof(data, "imap_perform_upgrade_tls, connect -> %d, %d", + result, ssldone)); + if(!result && ssldone) { imapc->ssldone = ssldone; - if(imapc->state != IMAP_UPGRADETLS) - state(data, IMAP_UPGRADETLS); - - if(imapc->ssldone) { - imap_to_imaps(conn); - result = imap_perform_capability(data, conn); - } + /* perform CAPA now, changes imapc->state out of IMAP_UPGRADETLS */ + result = imap_perform_capability(data, imapc); } out: return result; +#else + (void)data; + (void)imapc; + (void)conn; + return CURLE_NOT_BUILT_IN; +#endif } /*********************************************************************** @@ -505,6 +592,7 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, * Sends a clear text LOGIN command to authenticate with. */ static CURLcode imap_perform_login(struct Curl_easy *data, + struct imap_conn *imapc, struct connectdata *conn) { CURLcode result = CURLE_OK; @@ -512,26 +600,26 @@ static CURLcode imap_perform_login(struct Curl_easy *data, char *passwd; /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!data->state.aptr.user) { - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* Make sure the username and password are in the correct atom format */ - user = imap_atom(conn->user, false); - passwd = imap_atom(conn->passwd, false); + user = imap_atom(conn->user, FALSE); + passwd = imap_atom(conn->passwd, FALSE); /* Send the LOGIN command */ - result = imap_sendf(data, "LOGIN %s %s", user ? user : "", + result = imap_sendf(data, imapc, "LOGIN %s %s", user ? user : "", passwd ? passwd : ""); free(user); free(passwd); if(!result) - state(data, IMAP_LOGIN); + imap_state(data, imapc, IMAP_LOGIN); return result; } @@ -547,16 +635,20 @@ static CURLcode imap_perform_authenticate(struct Curl_easy *data, const char *mech, const struct bufref *initresp) { + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); CURLcode result = CURLE_OK; const char *ir = (const char *) Curl_bufref_ptr(initresp); + if(!imapc) + return CURLE_FAILED_INIT; if(ir) { /* Send the AUTHENTICATE command with the initial response */ - result = imap_sendf(data, "AUTHENTICATE %s %s", mech, ir); + result = imap_sendf(data, imapc, "AUTHENTICATE %s %s", mech, ir); } else { /* Send the AUTHENTICATE command */ - result = imap_sendf(data, "AUTHENTICATE %s", mech); + result = imap_sendf(data, imapc, "AUTHENTICATE %s", mech); } return result; @@ -572,10 +664,12 @@ static CURLcode imap_continue_authenticate(struct Curl_easy *data, const char *mech, const struct bufref *resp) { - struct imap_conn *imapc = &data->conn->proto.imapc; + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); (void)mech; - + if(!imapc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &imapc->pp, "%s", (const char *) Curl_bufref_ptr(resp)); } @@ -589,10 +683,12 @@ static CURLcode imap_continue_authenticate(struct Curl_easy *data, static CURLcode imap_cancel_authenticate(struct Curl_easy *data, const char *mech) { - struct imap_conn *imapc = &data->conn->proto.imapc; + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); (void)mech; - + if(!imapc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &imapc->pp, "*"); } @@ -605,17 +701,16 @@ static CURLcode imap_cancel_authenticate(struct Curl_easy *data, * mechanism not be available between the client and server. */ static CURLcode imap_perform_authentication(struct Curl_easy *data, - struct connectdata *conn) + struct imap_conn *imapc) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; saslprogress progress; /* Check if already authenticated OR if there is enough data to authenticate - with and end the connect phase if we don't */ + with and end the connect phase if we do not */ if(imapc->preauth || !Curl_sasl_can_authenticate(&imapc->sasl, data)) { - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } @@ -624,15 +719,12 @@ static CURLcode imap_perform_authentication(struct Curl_easy *data, if(!result) { if(progress == SASL_INPROGRESS) - state(data, IMAP_AUTHENTICATE); + imap_state(data, imapc, IMAP_AUTHENTICATE); else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT)) /* Perform clear text authentication */ - result = imap_perform_login(data, conn); - else { - /* Other mechanisms not supported */ - infof(data, "No known authentication mechanisms supported"); - result = CURLE_LOGIN_DENIED; - } + result = imap_perform_login(data, imapc, data->conn); + else + result = Curl_sasl_is_blocked(&imapc->sasl, data); } return result; @@ -644,30 +736,31 @@ static CURLcode imap_perform_authentication(struct Curl_easy *data, * * Sends a LIST command or an alternative custom request. */ -static CURLcode imap_perform_list(struct Curl_easy *data) +static CURLcode imap_perform_list(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; if(imap->custom) /* Send the custom request */ - result = imap_sendf(data, "%s%s", imap->custom, + result = imap_sendf(data, imapc, "%s%s", imap->custom, imap->custom_params ? imap->custom_params : ""); else { /* Make sure the mailbox is in the correct atom format if necessary */ - char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, true) + char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, TRUE) : strdup(""); if(!mailbox) return CURLE_OUT_OF_MEMORY; /* Send the LIST command */ - result = imap_sendf(data, "LIST \"%s\" *", mailbox); + result = imap_sendf(data, imapc, "LIST \"%s\" *", mailbox); free(mailbox); } if(!result) - state(data, IMAP_LIST); + imap_state(data, imapc, IMAP_LIST); return result; } @@ -678,12 +771,11 @@ static CURLcode imap_perform_list(struct Curl_easy *data) * * Sends a SELECT command to ask the server to change the selected mailbox. */ -static CURLcode imap_perform_select(struct Curl_easy *data) +static CURLcode imap_perform_select(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; char *mailbox; /* Invalidate old information as we are switching mailboxes */ @@ -697,17 +789,17 @@ static CURLcode imap_perform_select(struct Curl_easy *data) } /* Make sure the mailbox is in the correct atom format */ - mailbox = imap_atom(imap->mailbox, false); + mailbox = imap_atom(imap->mailbox, FALSE); if(!mailbox) return CURLE_OUT_OF_MEMORY; /* Send the SELECT command */ - result = imap_sendf(data, "SELECT %s", mailbox); + result = imap_sendf(data, imapc, "SELECT %s", mailbox); free(mailbox); if(!result) - state(data, IMAP_SELECT); + imap_state(data, imapc, IMAP_SELECT); return result; } @@ -718,30 +810,31 @@ static CURLcode imap_perform_select(struct Curl_easy *data) * * Sends a FETCH command to initiate the download of a message. */ -static CURLcode imap_perform_fetch(struct Curl_easy *data) +static CURLcode imap_perform_fetch(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; /* Check we have a UID */ if(imap->uid) { /* Send the FETCH command */ if(imap->partial) - result = imap_sendf(data, "UID FETCH %s BODY[%s]<%s>", + result = imap_sendf(data, imapc, "UID FETCH %s BODY[%s]<%s>", imap->uid, imap->section ? imap->section : "", imap->partial); else - result = imap_sendf(data, "UID FETCH %s BODY[%s]", + result = imap_sendf(data, imapc, "UID FETCH %s BODY[%s]", imap->uid, imap->section ? imap->section : ""); } else if(imap->mindex) { /* Send the FETCH command */ if(imap->partial) - result = imap_sendf(data, "FETCH %s BODY[%s]<%s>", + result = imap_sendf(data, imapc, "FETCH %s BODY[%s]<%s>", imap->mindex, imap->section ? imap->section : "", imap->partial); else - result = imap_sendf(data, "FETCH %s BODY[%s]", + result = imap_sendf(data, imapc, "FETCH %s BODY[%s]", imap->mindex, imap->section ? imap->section : ""); } else { @@ -749,7 +842,7 @@ static CURLcode imap_perform_fetch(struct Curl_easy *data) return CURLE_URL_MALFORMAT; } if(!result) - state(data, IMAP_FETCH); + imap_state(data, imapc, IMAP_FETCH); return result; } @@ -760,11 +853,13 @@ static CURLcode imap_perform_fetch(struct Curl_easy *data) * * Sends an APPEND command to initiate the upload of a message. */ -static CURLcode imap_perform_append(struct Curl_easy *data) +static CURLcode imap_perform_append(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; char *mailbox; + struct dynbuf flags; /* Check we have a mailbox */ if(!imap->mailbox) { @@ -772,10 +867,11 @@ static CURLcode imap_perform_append(struct Curl_easy *data) return CURLE_URL_MALFORMAT; } +#ifndef CURL_DISABLE_MIME /* Prepare the mime data if some. */ if(data->set.mimepost.kind != MIMEKIND_NONE) { /* Use the whole structure as data. */ - data->set.mimepost.flags &= ~MIME_BODY_ONLY; + data->set.mimepost.flags &= ~(unsigned int)MIME_BODY_ONLY; /* Add external headers and mime version. */ curl_mime_headers(&data->set.mimepost, data->set.headers, 0); @@ -787,18 +883,18 @@ static CURLcode imap_perform_append(struct Curl_easy *data) result = Curl_mime_add_header(&data->set.mimepost.curlheaders, "Mime-Version: 1.0"); - /* Make sure we will read the entire mime structure. */ if(!result) - result = Curl_mime_rewind(&data->set.mimepost); - + result = Curl_creader_set_mime(data, &data->set.mimepost); + if(result) + return result; + data->state.infilesize = Curl_creader_client_length(data); + } + else +#endif + { + result = Curl_creader_set_fread(data, data->state.infilesize); if(result) return result; - - data->state.infilesize = Curl_mime_size(&data->set.mimepost); - - /* Read from mime structure. */ - data->state.fread_func = (curl_read_callback) Curl_mime_read; - data->state.in = (void *) &data->set.mimepost; } /* Check we know the size of the upload */ @@ -808,19 +904,52 @@ static CURLcode imap_perform_append(struct Curl_easy *data) } /* Make sure the mailbox is in the correct atom format */ - mailbox = imap_atom(imap->mailbox, false); + mailbox = imap_atom(imap->mailbox, FALSE); if(!mailbox) return CURLE_OUT_OF_MEMORY; - /* Send the APPEND command */ - result = imap_sendf(data, - "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}", - mailbox, data->state.infilesize); + /* Generate flags string and send the APPEND command */ + curlx_dyn_init(&flags, 100); + if(data->set.upload_flags) { + int i; + struct ulbits ulflag[] = { + {CURLULFLAG_ANSWERED, "Answered"}, + {CURLULFLAG_DELETED, "Deleted"}, + {CURLULFLAG_DRAFT, "Draft"}, + {CURLULFLAG_FLAGGED, "Flagged"}, + {CURLULFLAG_SEEN, "Seen"}, + {0, NULL} + }; + + result = CURLE_OUT_OF_MEMORY; + if(curlx_dyn_add(&flags, " (")) { + goto cleanup; + } + + 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(curlx_dyn_add(&flags, ")")) + goto cleanup; + } + else if(curlx_dyn_add(&flags, "")) + goto cleanup; + + result = imap_sendf(data, imapc, "APPEND %s%s {%" FMT_OFF_T "}", + mailbox, curlx_dyn_ptr(&flags), data->state.infilesize); +cleanup: + curlx_dyn_free(&flags); free(mailbox); if(!result) - state(data, IMAP_APPEND); + imap_state(data, imapc, IMAP_APPEND); return result; } @@ -831,10 +960,11 @@ static CURLcode imap_perform_append(struct Curl_easy *data) * * Sends a SEARCH command. */ -static CURLcode imap_perform_search(struct Curl_easy *data) +static CURLcode imap_perform_search(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; /* Check we have a query string */ if(!imap->query) { @@ -843,10 +973,10 @@ static CURLcode imap_perform_search(struct Curl_easy *data) } /* Send the SEARCH command */ - result = imap_sendf(data, "SEARCH %s", imap->query); + result = imap_sendf(data, imapc, "SEARCH %s", imap->query); if(!result) - state(data, IMAP_SEARCH); + imap_state(data, imapc, IMAP_SEARCH); return result; } @@ -857,28 +987,28 @@ static CURLcode imap_perform_search(struct Curl_easy *data) * * Performs the logout action prior to sclose() being called. */ -static CURLcode imap_perform_logout(struct Curl_easy *data) +static CURLcode imap_perform_logout(struct Curl_easy *data, + struct imap_conn *imapc) { /* Send the LOGOUT command */ - CURLcode result = imap_sendf(data, "LOGOUT"); + CURLcode result = imap_sendf(data, imapc, "LOGOUT"); if(!result) - state(data, IMAP_LOGOUT); + imap_state(data, imapc, IMAP_LOGOUT); return result; } /* For the initial server greeting */ static CURLcode imap_state_servergreet_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { - struct connectdata *conn = data->conn; (void)instate; /* no use for this yet */ if(imapcode == IMAP_RESP_PREAUTH) { /* PREAUTH */ - struct imap_conn *imapc = &conn->proto.imapc; imapc->preauth = TRUE; infof(data, "PREAUTH connection, already authenticated"); } @@ -887,22 +1017,22 @@ static CURLcode imap_state_servergreet_resp(struct Curl_easy *data, return CURLE_WEIRD_SERVER_REPLY; } - return imap_perform_capability(data, conn); + return imap_perform_capability(data, imapc); } /* For CAPABILITY responses */ static CURLcode imap_state_capability_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct imap_conn *imapc = &conn->proto.imapc; - const char *line = data->state.buffer; + const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf); (void)instate; /* no use for this yet */ - /* Do we have a untagged response? */ + /* Do we have an untagged response? */ if(imapcode == '*') { line += 2; @@ -958,33 +1088,33 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data, /* PREAUTH is not compatible with STARTTLS. */ if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) { /* Switch to TLS connection now */ - result = imap_perform_starttls(data); + result = imap_perform_starttls(data, imapc); } else if(data->set.use_ssl <= CURLUSESSL_TRY) - result = imap_perform_authentication(data, conn); + result = imap_perform_authentication(data, imapc); else { failf(data, "STARTTLS not available."); result = CURLE_USE_SSL_FAILED; } } else - result = imap_perform_authentication(data, conn); + result = imap_perform_authentication(data, imapc); return result; } /* For STARTTLS responses */ static CURLcode imap_state_starttls_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.imapc.pp.cache_size) + if(imapc->pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(imapcode != IMAP_RESP_OK) { @@ -993,22 +1123,21 @@ static CURLcode imap_state_starttls_resp(struct Curl_easy *data, result = CURLE_USE_SSL_FAILED; } else - result = imap_perform_authentication(data, conn); + result = imap_perform_authentication(data, imapc); } else - result = imap_perform_upgrade_tls(data, conn); + imap_state(data, imapc, IMAP_UPGRADETLS); return result; } /* For SASL authentication responses */ static CURLcode imap_state_auth_resp(struct Curl_easy *data, - struct connectdata *conn, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; saslprogress progress; (void)instate; /* no use for this yet */ @@ -1017,12 +1146,12 @@ static CURLcode imap_state_auth_resp(struct Curl_easy *data, if(!result) switch(progress) { case SASL_DONE: - state(data, IMAP_STOP); /* Authenticated */ + imap_state(data, imapc, IMAP_STOP); /* Authenticated */ break; case SASL_IDLE: /* No mechanism left after cancellation */ if((!imapc->login_disabled) && (imapc->preftype & IMAP_TYPE_CLEARTEXT)) /* Perform clear text authentication */ - result = imap_perform_login(data, conn); + result = imap_perform_login(data, imapc, data->conn); else { failf(data, "Authentication cancelled"); result = CURLE_LOGIN_DENIED; @@ -1037,6 +1166,7 @@ static CURLcode imap_state_auth_resp(struct Curl_easy *data, /* For LOGIN responses */ static CURLcode imap_state_login_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { @@ -1049,55 +1179,61 @@ static CURLcode imap_state_login_resp(struct Curl_easy *data, } else /* End of connect phase */ - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For LIST and SEARCH responses */ static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - char *line = data->state.buffer; - size_t len = strlen(line); + char *line = curlx_dyn_ptr(&imapc->pp.recvbuf); + size_t len = imapc->pp.nfinal; (void)instate; /* No use for this yet */ - if(imapcode == '*') { - /* Temporarily add the LF character back and send as body to the client */ - line[len] = '\n'; - result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } + if(imapcode == '*') + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); else if(imapcode != IMAP_RESP_OK) result = CURLE_QUOTE_ERROR; else /* End of DO phase */ - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For SELECT responses */ -static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, +static CURLcode imap_state_select_resp(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap, + int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; - const char *line = data->state.buffer; + const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf); (void)instate; /* no use for this yet */ if(imapcode == '*') { /* See if this is an UIDVALIDITY response */ - char tmp[20]; - if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) { - Curl_safefree(imapc->mailbox_uidvalidity); - imapc->mailbox_uidvalidity = strdup(tmp); + if(checkprefix("OK [UIDVALIDITY ", line + 2)) { + size_t len = 0; + const char *p = &line[2] + strlen("OK [UIDVALIDITY "); + while((len < 20) && p[len] && ISDIGIT(p[len])) + len++; + if(len && (p[len] == ']')) { + struct dynbuf uid; + curlx_dyn_init(&uid, 20); + if(curlx_dyn_addn(&uid, p, len)) + return CURLE_OUT_OF_MEMORY; + free(imapc->mailbox_uidvalidity); + imapc->mailbox_uidvalidity = curlx_dyn_ptr(&uid); + } } } else if(imapcode == IMAP_RESP_OK) { @@ -1109,14 +1245,17 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, } else { /* Note the currently opened mailbox on this connection */ + DEBUGASSERT(!imapc->mailbox); imapc->mailbox = strdup(imap->mailbox); + if(!imapc->mailbox) + return CURLE_OUT_OF_MEMORY; if(imap->custom) - result = imap_perform_list(data); + result = imap_perform_list(data, imapc, imap); else if(imap->query) - result = imap_perform_search(data); + result = imap_perform_search(data, imapc, imap); else - result = imap_perform_fetch(data); + result = imap_perform_fetch(data, imapc, imap); } } else { @@ -1129,13 +1268,14 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, /* For the (first line of the) FETCH responses */ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, - struct connectdata *conn, int imapcode, + struct imap_conn *imapc, + int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; struct pingpong *pp = &imapc->pp; - const char *ptr = data->state.buffer; + const char *ptr = curlx_dyn_ptr(&imapc->pp.recvbuf); + size_t len = imapc->pp.nfinal; bool parsed = FALSE; curl_off_t size = 0; @@ -1143,94 +1283,91 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, if(imapcode != '*') { Curl_pgrsSetDownloadSize(data, -1); - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return CURLE_REMOTE_FILE_NOT_FOUND; } /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse the continuation data contained within the curly brackets */ - while(*ptr && (*ptr != '{')) + ptr = memchr(ptr, '{', len); + if(ptr) { ptr++; - - if(*ptr == '{') { - char *endptr; - if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) { - if(endptr - ptr > 1 && endptr[0] == '}' && - endptr[1] == '\r' && endptr[2] == '\0') - parsed = TRUE; - } + if(!curlx_str_number(&ptr, &size, CURL_OFF_T_MAX) && + !curlx_str_single(&ptr, '}')) + parsed = TRUE; } if(parsed) { - infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download", - size); + infof(data, "Found %" FMT_OFF_T " bytes to download", size); Curl_pgrsSetDownloadSize(data, size); - if(pp->cache) { - /* At this point there is a bunch of data in the header "cache" that is - actually body content, send it as body and then skip it. Do note - that there may even be additional "headers" after the body. */ - size_t chunk = pp->cache_size; + if(pp->overflow) { + /* At this point there is a data in the receive buffer that is body + content, send it as body and then skip it. Do note that there may + even be additional "headers" after the body. */ + size_t chunk = pp->overflow; + + /* keep only the overflow */ + curlx_dyn_tail(&pp->recvbuf, chunk); + pp->nfinal = 0; /* done */ if(chunk > (size_t)size) /* The conversion from curl_off_t to size_t is always fine here */ chunk = (size_t)size; if(!chunk) { - /* no size, we're done with the data */ - state(data, IMAP_STOP); + /* no size, we are done with the data */ + imap_state(data, imapc, IMAP_STOP); return CURLE_OK; } - result = Curl_client_write(data, CLIENTWRITE_BODY, pp->cache, chunk); + result = Curl_client_write(data, CLIENTWRITE_BODY, + curlx_dyn_ptr(&pp->recvbuf), chunk); if(result) return result; - data->req.bytecount += chunk; - - infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU + infof(data, "Written %zu bytes, %" FMT_OFF_TU " bytes are left for transfer", chunk, size - chunk); - /* Have we used the entire cache or just part of it?*/ - if(pp->cache_size > chunk) { - /* Only part of it so shrink the cache to fit the trailing data */ - memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk); - pp->cache_size -= chunk; + /* Have we used the entire overflow or just part of it?*/ + if(pp->overflow > chunk) { + /* remember the remaining trailing overflow data */ + pp->overflow -= chunk; + curlx_dyn_tail(&pp->recvbuf, pp->overflow); } else { + pp->overflow = 0; /* handled */ /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + curlx_dyn_reset(&pp->recvbuf); } } if(data->req.bytecount == size) /* The entire data is already transferred! */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); else { /* IMAP download */ data->req.maxdownload = size; /* force a recv/send check of this connection, as the data might've been read off the socket already */ - data->conn->cselect_bits = CURL_CSELECT_IN; - Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1); + data->state.select_bits = CURL_CSELECT_IN; + Curl_xfer_setup1(data, CURL_XFER_RECV, size, FALSE); } } else { - /* We don't know how to parse this line */ + /* We do not know how to parse this line */ failf(data, "Failed to parse FETCH response."); result = CURLE_WEIRD_SERVER_REPLY; } /* End of DO phase */ - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For final FETCH responses performed after the download */ static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { @@ -1242,13 +1379,15 @@ static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; else /* End of DONE phase */ - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For APPEND responses */ -static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, +static CURLcode imap_state_append_resp(struct Curl_easy *data, + struct imap_conn *imapc, + int imapcode, imapstate instate) { CURLcode result = CURLE_OK; @@ -1262,10 +1401,10 @@ static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, Curl_pgrsSetUploadSize(data, data->state.infilesize); /* IMAP upload */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); } return result; @@ -1273,6 +1412,7 @@ static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, /* For final APPEND responses performed after the upload */ static CURLcode imap_state_append_final_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { @@ -1284,25 +1424,32 @@ static CURLcode imap_state_append_final_resp(struct Curl_easy *data, result = CURLE_UPLOAD_FAILED; else /* End of DONE phase */ - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } -static CURLcode imap_statemachine(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode imap_pp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int imapcode; - struct imap_conn *imapc = &conn->proto.imapc; - struct pingpong *pp = &imapc->pp; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + struct pingpong *pp; size_t nread = 0; - (void)data; + (void)data; + if(!imapc || !imap) + return CURLE_FAILED_INIT; + pp = &imapc->pp; /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */ - if(imapc->state == IMAP_UPGRADETLS) - return imap_perform_upgrade_tls(data, conn); +upgrade_tls: + if(imapc->state == IMAP_UPGRADETLS) { + result = imap_perform_upgrade_tls(data, imapc, conn); + if(result || (imapc->state == IMAP_UPGRADETLS)) + return result; + } /* Flush any data that needs to be sent */ if(pp->sendleft) @@ -1310,7 +1457,7 @@ static CURLcode imap_statemachine(struct Curl_easy *data, do { /* Read the response from the server */ - result = Curl_pp_readresp(data, sock, pp, &imapcode, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &imapcode, &nread); if(result) return result; @@ -1324,55 +1471,62 @@ static CURLcode imap_statemachine(struct Curl_easy *data, /* We have now received a full IMAP server response */ switch(imapc->state) { case IMAP_SERVERGREET: - result = imap_state_servergreet_resp(data, imapcode, imapc->state); + result = imap_state_servergreet_resp(data, imapc, + imapcode, imapc->state); break; case IMAP_CAPABILITY: - result = imap_state_capability_resp(data, imapcode, imapc->state); + result = imap_state_capability_resp(data, imapc, imapcode, imapc->state); break; case IMAP_STARTTLS: - result = imap_state_starttls_resp(data, imapcode, imapc->state); + result = imap_state_starttls_resp(data, imapc, imapcode, imapc->state); + /* During UPGRADETLS, leave the read loop as we need to connect + * (e.g. TLS handshake) before we continue sending/receiving. */ + if(!result && (imapc->state == IMAP_UPGRADETLS)) + goto upgrade_tls; break; case IMAP_AUTHENTICATE: - result = imap_state_auth_resp(data, conn, imapcode, imapc->state); + result = imap_state_auth_resp(data, imapc, imapcode, imapc->state); break; case IMAP_LOGIN: - result = imap_state_login_resp(data, imapcode, imapc->state); + result = imap_state_login_resp(data, imapc, imapcode, imapc->state); break; case IMAP_LIST: case IMAP_SEARCH: - result = imap_state_listsearch_resp(data, imapcode, imapc->state); + result = imap_state_listsearch_resp(data, imapc, imapcode, imapc->state); break; case IMAP_SELECT: - result = imap_state_select_resp(data, imapcode, imapc->state); + result = imap_state_select_resp(data, imapc, imap, + imapcode, imapc->state); break; case IMAP_FETCH: - result = imap_state_fetch_resp(data, conn, imapcode, imapc->state); + result = imap_state_fetch_resp(data, imapc, imapcode, imapc->state); break; case IMAP_FETCH_FINAL: - result = imap_state_fetch_final_resp(data, imapcode, imapc->state); + result = imap_state_fetch_final_resp(data, imapc, + imapcode, imapc->state); break; case IMAP_APPEND: - result = imap_state_append_resp(data, imapcode, imapc->state); + result = imap_state_append_resp(data, imapc, imapcode, imapc->state); break; case IMAP_APPEND_FINAL: - result = imap_state_append_final_resp(data, imapcode, imapc->state); + result = imap_state_append_final_resp(data, imapc, + imapcode, imapc->state); break; case IMAP_LOGOUT: - /* fallthrough, just stop! */ default: /* internal error */ - state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); break; } } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp)); @@ -1384,29 +1538,23 @@ static CURLcode imap_statemachine(struct Curl_easy *data, static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct imap_conn *imapc = &conn->proto.imapc; - - if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) { - bool ssldone = FALSE; - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); - imapc->ssldone = ssldone; - if(result || !ssldone) - return result; - } + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); + *done = FALSE; + if(!imapc) + return CURLE_FAILED_INIT; result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE); - *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE; + *done = (imapc->state == IMAP_STOP); return result; } static CURLcode imap_block_statemach(struct Curl_easy *data, - struct connectdata *conn, + struct imap_conn *imapc, bool disconnecting) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; while(imapc->state != IMAP_STOP && !result) result = Curl_pp_statemach(data, &imapc->pp, TRUE, disconnecting); @@ -1414,26 +1562,14 @@ static CURLcode imap_block_statemach(struct Curl_easy *data, return result; } -/* Allocate and initialize the struct IMAP for the current Curl_easy if - required */ -static CURLcode imap_init(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct IMAP *imap; - - imap = data->req.p.imap = calloc(sizeof(struct IMAP), 1); - if(!imap) - result = CURLE_OUT_OF_MEMORY; - - return result; -} - /* For the IMAP "protocol connect" and "doing" phases only */ static int imap_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.imapc.pp, socks); + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + return imapc ? + Curl_pp_getsock(data, &imapc->pp, socks) : GETSOCK_BLANK; } /*********************************************************************** @@ -1448,34 +1584,24 @@ static int imap_getsock(struct Curl_easy *data, */ static CURLcode imap_connect(struct Curl_easy *data, bool *done) { + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct imap_conn *imapc = &conn->proto.imapc; - struct pingpong *pp = &imapc->pp; *done = FALSE; /* default to not done yet */ + if(!imapc) + return CURLE_FAILED_INIT; /* We always support persistent connections in IMAP */ - connkeep(conn, "IMAP default"); - - PINGPONG_SETUP(pp, imap_statemachine, imap_endofresp); - - /* Set the default preferred authentication type and mechanism */ - imapc->preftype = IMAP_TYPE_ANY; - Curl_sasl_init(&imapc->sasl, data, &saslimap); - - Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD); - /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + connkeep(data->conn, "IMAP default"); /* Parse the URL options */ - result = imap_parse_url_options(conn); + result = imap_parse_url_options(data->conn, imapc); if(result) return result; /* Start off waiting for the server greeting response */ - state(data, IMAP_SERVERGREET); + imap_state(data, imapc, IMAP_SERVERGREET); /* Start off with an response id of '*' */ strcpy(imapc->resptag, "*"); @@ -1499,10 +1625,13 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; + 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; @@ -1512,37 +1641,24 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, } else if(!data->set.connect_only && !imap->custom && (imap->uid || imap->mindex || data->state.upload || - data->set.mimepost.kind != MIMEKIND_NONE)) { + IS_MIME_POST(data))) { /* Handle responses after FETCH or APPEND transfer has finished */ - if(!data->state.upload && data->set.mimepost.kind == MIMEKIND_NONE) - state(data, IMAP_FETCH_FINAL); + if(!data->state.upload && !IS_MIME_POST(data)) + imap_state(data, imapc, IMAP_FETCH_FINAL); else { /* End the APPEND command first by sending an empty line */ - result = Curl_pp_sendf(data, &conn->proto.imapc.pp, "%s", ""); + result = Curl_pp_sendf(data, &imapc->pp, "%s", ""); if(!result) - state(data, IMAP_APPEND_FINAL); + imap_state(data, imapc, IMAP_APPEND_FINAL); } /* Run the state-machine */ if(!result) - result = imap_block_statemach(data, conn, FALSE); + result = imap_block_statemach(data, imapc, FALSE); } - /* Cleanup our per-request based variables */ - Curl_safefree(imap->mailbox); - Curl_safefree(imap->uidvalidity); - Curl_safefree(imap->uid); - Curl_safefree(imap->mindex); - Curl_safefree(imap->section); - Curl_safefree(imap->partial); - Curl_safefree(imap->query); - Curl_safefree(imap->custom); - Curl_safefree(imap->custom_params); - - /* Clear the transfer mode for the next request */ - imap->transfer = PPTRANSFER_BODY; - + imap_easy_reset(imap); return result; } @@ -1559,11 +1675,13 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, /* This is IMAP and no proxy */ CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); bool selected = FALSE; DEBUGF(infof(data, "DO phase starts")); + if(!imapc || !imap) + return CURLE_FAILED_INIT; if(data->req.no_body) { /* Requested no body means no transfer */ @@ -1581,25 +1699,25 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, selected = TRUE; /* Start the first command in the DO phase */ - if(data->state.upload || data->set.mimepost.kind != MIMEKIND_NONE) + if(data->state.upload || IS_MIME_POST(data)) /* APPEND can be executed directly */ - result = imap_perform_append(data); + result = imap_perform_append(data, imapc, imap); else if(imap->custom && (selected || !imap->mailbox)) /* Custom command using the same mailbox or no mailbox */ - result = imap_perform_list(data); + result = imap_perform_list(data, imapc, imap); else if(!imap->custom && selected && (imap->uid || imap->mindex)) /* FETCH from the same mailbox */ - result = imap_perform_fetch(data); + result = imap_perform_fetch(data, imapc, imap); else if(!imap->custom && selected && imap->query) /* SEARCH the current mailbox */ - result = imap_perform_search(data); + result = imap_perform_search(data, imapc, imap); else if(imap->mailbox && !selected && (imap->custom || imap->uid || imap->mindex || imap->query)) /* SELECT the mailbox */ - result = imap_perform_select(data); + result = imap_perform_select(data, imapc, imap); else /* LIST */ - result = imap_perform_list(data); + result = imap_perform_list(data, imapc, imap); if(result) return result; @@ -1626,20 +1744,23 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, */ static CURLcode imap_do(struct Curl_easy *data, bool *done) { + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); CURLcode result = CURLE_OK; *done = FALSE; /* default to false */ + if(!imap) + return CURLE_FAILED_INIT; /* Parse the URL path */ - result = imap_parse_url_path(data); + result = imap_parse_url_path(data, imap); if(result) return result; /* Parse the custom request */ - result = imap_parse_custom_request(data); + result = imap_parse_custom_request(data, imap); if(result) return result; - result = imap_regular_transfer(data, done); + result = imap_regular_transfer(data, imap, done); return result; } @@ -1654,44 +1775,37 @@ static CURLcode imap_do(struct Curl_easy *data, bool *done) static CURLcode imap_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct imap_conn *imapc = &conn->proto.imapc; - (void)data; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. */ + (void)data; + if(imapc) { + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ + + /* The IMAP session may or may not have been allocated/setup at this + point! */ + if(!dead_connection && conn->bits.protoconnstart) { + if(!imap_perform_logout(data, imapc)) + (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ + } - /* The IMAP session may or may not have been allocated/setup at this - point! */ - if(!dead_connection && conn->bits.protoconnstart) { - if(!imap_perform_logout(data)) - (void)imap_block_statemach(data, conn, TRUE); /* ignore errors */ + /* Cleanup the SASL module */ + Curl_sasl_cleanup(conn, imapc->sasl.authused); } - - /* Disconnect from the server */ - Curl_pp_disconnect(&imapc->pp); - Curl_dyn_free(&imapc->dyn); - - /* Cleanup the SASL module */ - Curl_sasl_cleanup(conn, imapc->sasl.authused); - - /* Cleanup our connection based variables */ - Curl_safefree(imapc->mailbox); - Curl_safefree(imapc->mailbox_uidvalidity); - return CURLE_OK; } /* Call this when the DO phase has completed */ -static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected) +static CURLcode imap_dophase_done(struct Curl_easy *data, + struct IMAP *imap, + bool connected) { - struct IMAP *imap = data->req.p.imap; - (void)connected; if(imap->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); return CURLE_OK; } @@ -1699,12 +1813,17 @@ static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected) /* Called from multi.c while DOing */ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = imap_multi_statemach(data, dophase_done); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + CURLcode result; + + if(!imap) + return CURLE_FAILED_INIT; + result = imap_multi_statemach(data, dophase_done); if(result) DEBUGF(infof(data, "DO phase failed")); else if(*dophase_done) { - result = imap_dophase_done(data, FALSE /* not connected */); + result = imap_dophase_done(data, imap, FALSE /* not connected */); DEBUGF(infof(data, "DO phase is complete")); } @@ -1722,6 +1841,7 @@ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) * remote host. */ static CURLcode imap_regular_transfer(struct Curl_easy *data, + struct IMAP *imap, bool *dophase_done) { CURLcode result = CURLE_OK; @@ -1741,21 +1861,75 @@ static CURLcode imap_regular_transfer(struct Curl_easy *data, /* Perform post DO phase operations if necessary */ if(!result && *dophase_done) - result = imap_dophase_done(data, connected); + result = imap_dophase_done(data, imap, connected); return result; } +static void imap_easy_reset(struct IMAP *imap) +{ + Curl_safefree(imap->mailbox); + Curl_safefree(imap->uidvalidity); + Curl_safefree(imap->uid); + Curl_safefree(imap->mindex); + Curl_safefree(imap->section); + Curl_safefree(imap->partial); + Curl_safefree(imap->query); + Curl_safefree(imap->custom); + Curl_safefree(imap->custom_params); + /* Clear the transfer mode for the next request */ + imap->transfer = PPTRANSFER_BODY; +} + +static void imap_easy_dtor(void *key, size_t klen, void *entry) +{ + struct IMAP *imap = entry; + (void)key; + (void)klen; + imap_easy_reset(imap); + free(imap); +} + +static void imap_conn_dtor(void *key, size_t klen, void *entry) +{ + struct imap_conn *imapc = entry; + (void)key; + (void)klen; + Curl_pp_disconnect(&imapc->pp); + curlx_dyn_free(&imapc->dyn); + Curl_safefree(imapc->mailbox); + Curl_safefree(imapc->mailbox_uidvalidity); + free(imapc); +} + static CURLcode imap_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - /* Initialise the IMAP layer */ - CURLcode result = imap_init(data); - if(result) - return result; + struct imap_conn *imapc; + struct pingpong *pp; + struct IMAP *imap; + + imapc = calloc(1, sizeof(*imapc)); + if(!imapc) + return CURLE_OUT_OF_MEMORY; - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; + pp = &imapc->pp; + PINGPONG_SETUP(pp, imap_pp_statemachine, imap_endofresp); + + /* Set the default preferred authentication type and mechanism */ + imapc->preftype = IMAP_TYPE_ANY; + Curl_sasl_init(&imapc->sasl, data, &saslimap); + + curlx_dyn_init(&imapc->dyn, DYN_IMAP_CMD); + Curl_pp_init(pp); + + if(Curl_conn_meta_set(conn, CURL_META_IMAP_CONN, imapc, imap_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + imap = calloc(1, sizeof(struct IMAP)); + if(!imap || + Curl_meta_set(data, CURL_META_IMAP_EASY, imap, imap_easy_dtor)) + return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -1768,27 +1942,35 @@ static CURLcode imap_setup_connection(struct Curl_easy *data, * * Designed to never block. */ -static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) +static CURLcode imap_sendf(struct Curl_easy *data, + struct imap_conn *imapc, + const char *fmt, ...) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &data->conn->proto.imapc; DEBUGASSERT(fmt); /* Calculate the tag based on the connection ID and command ID */ msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", - 'A' + curlx_sltosi(data->conn->connection_id % 26), + 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)), ++imapc->cmdid); /* start with a blank buffer */ - Curl_dyn_reset(&imapc->dyn); + curlx_dyn_reset(&imapc->dyn); /* append tag + space + fmt */ - result = Curl_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt); + result = curlx_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt); if(!result) { va_list ap; va_start(ap, fmt); - result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + result = Curl_pp_vsendf(data, &imapc->pp, curlx_dyn_ptr(&imapc->dyn), ap); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif va_end(ap); } return result; @@ -1806,79 +1988,37 @@ static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) */ static char *imap_atom(const char *str, bool escape_only) { - /* !checksrc! disable PARENBRACE 1 */ - const char atom_specials[] = "(){ %*]"; - const char *p1; - char *p2; - size_t backsp_count = 0; - size_t quote_count = 0; - bool others_exists = FALSE; - size_t newlen = 0; - char *newstr = NULL; + struct dynbuf line; + size_t nclean; + size_t len; if(!str) return NULL; - /* Look for "atom-specials", counting the backslash and quote characters as - these will need escaping */ - p1 = str; - while(*p1) { - if(*p1 == '\\') - backsp_count++; - else if(*p1 == '"') - quote_count++; - else if(!escape_only) { - const char *p3 = atom_specials; - - while(*p3 && !others_exists) { - if(*p1 == *p3) - others_exists = TRUE; - - p3++; - } - } - - p1++; - } - - /* Does the input contain any "atom-special" characters? */ - if(!backsp_count && !quote_count && !others_exists) + len = strlen(str); + nclean = strcspn(str, "() {%*]\\\""); + if(len == nclean) + /* nothing to escape, return a strdup */ return strdup(str); - /* Calculate the new string length */ - newlen = strlen(str) + backsp_count + quote_count + (escape_only ? 0 : 2); + curlx_dyn_init(&line, 2000); - /* Allocate the new string */ - newstr = (char *) malloc((newlen + 1) * sizeof(char)); - if(!newstr) + if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) return NULL; - /* Surround the string in quotes if necessary */ - p2 = newstr; - if(!escape_only) { - newstr[0] = '"'; - newstr[newlen - 1] = '"'; - p2++; + while(*str) { + if((*str == '\\' || *str == '"') && + curlx_dyn_addn(&line, "\\", 1)) + return NULL; + if(curlx_dyn_addn(&line, str, 1)) + return NULL; + str++; } - /* Copy the string, escaping backslash and quote characters along the way */ - p1 = str; - while(*p1) { - if(*p1 == '\\' || *p1 == '"') { - *p2 = '\\'; - p2++; - } - - *p2 = *p1; - - p1++; - p2++; - } - - /* Terminate the string */ - newstr[newlen] = '\0'; + if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) + return NULL; - return newstr; + return curlx_dyn_ptr(&line); } /*********************************************************************** @@ -1893,7 +2033,7 @@ static bool imap_is_bchar(char ch) /* Performing the alnum check with this macro is faster because of ASCII arithmetic */ if(ISALNUM(ch)) - return true; + return TRUE; switch(ch) { /* bchar */ @@ -1907,10 +2047,10 @@ static bool imap_is_bchar(char ch) case '+': case ',': /* bchar -> achar -> uchar -> pct-encoded */ case '%': /* HEXDIG chars are already included above */ - return true; + return TRUE; default: - return false; + return FALSE; } } @@ -1920,11 +2060,12 @@ static bool imap_is_bchar(char ch) * * Parse the URL login options. */ -static CURLcode imap_parse_url_options(struct connectdata *conn) +static CURLcode imap_parse_url_options(struct connectdata *conn, + struct imap_conn *imapc) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; const char *ptr = conn->options; + bool prefer_login = FALSE; while(!result && ptr && *ptr) { const char *key = ptr; @@ -1938,26 +2079,39 @@ static CURLcode imap_parse_url_options(struct connectdata *conn) while(*ptr && *ptr != ';') ptr++; - if(strncasecompare(key, "AUTH=", 5)) + if(strncasecompare(key, "AUTH=+LOGIN", 11)) { + /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */ + prefer_login = TRUE; + imapc->sasl.prefmech = SASL_AUTH_NONE; + } + else if(strncasecompare(key, "AUTH=", 5)) { + prefer_login = FALSE; result = Curl_sasl_parse_url_auth_option(&imapc->sasl, value, ptr - value); - else + } + else { + prefer_login = FALSE; result = CURLE_URL_MALFORMAT; + } if(*ptr == ';') ptr++; } - switch(imapc->sasl.prefmech) { - case SASL_AUTH_NONE: - imapc->preftype = IMAP_TYPE_NONE; - break; - case SASL_AUTH_DEFAULT: - imapc->preftype = IMAP_TYPE_ANY; - break; - default: - imapc->preftype = IMAP_TYPE_SASL; - break; + if(prefer_login) + imapc->preftype = IMAP_TYPE_CLEARTEXT; + else { + switch(imapc->sasl.prefmech) { + case SASL_AUTH_NONE: + imapc->preftype = IMAP_TYPE_NONE; + break; + case SASL_AUTH_DEFAULT: + imapc->preftype = IMAP_TYPE_ANY; + break; + default: + imapc->preftype = IMAP_TYPE_SASL; + break; + } } return result; @@ -1970,11 +2124,11 @@ static CURLcode imap_parse_url_options(struct connectdata *conn) * Parse the URL path into separate path components. * */ -static CURLcode imap_parse_url_path(struct Curl_easy *data) +static CURLcode imap_parse_url_path(struct Curl_easy *data, + struct IMAP *imap) { /* The imap struct is already initialised in imap_connect() */ CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; const char *begin = &data->state.up.path[1]; /* skip leading slash */ const char *ptr = begin; @@ -2102,10 +2256,10 @@ static CURLcode imap_parse_url_path(struct Curl_easy *data) * * Parse the custom request. */ -static CURLcode imap_parse_custom_request(struct Curl_easy *data) +static CURLcode imap_parse_custom_request(struct Curl_easy *data, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; const char *custom = data->set.str[STRING_CUSTOMREQUEST]; if(custom) { diff --git a/Utilities/cmcurl/lib/imap.h b/Utilities/cmcurl/lib/imap.h index 784ee97e550..f802ed5c0c7 100644 --- a/Utilities/cmcurl/lib/imap.h +++ b/Utilities/cmcurl/lib/imap.h @@ -27,65 +27,6 @@ #include "pingpong.h" #include "curl_sasl.h" -/**************************************************************************** - * IMAP unique setup - ***************************************************************************/ -typedef enum { - IMAP_STOP, /* do nothing state, stops the state machine */ - IMAP_SERVERGREET, /* waiting for the initial greeting immediately after - a connect */ - IMAP_CAPABILITY, - IMAP_STARTTLS, - IMAP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS - (multi mode only) */ - IMAP_AUTHENTICATE, - IMAP_LOGIN, - IMAP_LIST, - IMAP_SELECT, - IMAP_FETCH, - IMAP_FETCH_FINAL, - IMAP_APPEND, - IMAP_APPEND_FINAL, - IMAP_SEARCH, - IMAP_LOGOUT, - IMAP_LAST /* never used */ -} imapstate; - -/* This IMAP struct is used in the Curl_easy. All IMAP data that is - connection-oriented must be in imap_conn to properly deal with the fact that - perhaps the Curl_easy is changed between the times the connection is - used. */ -struct IMAP { - curl_pp_transfer transfer; - char *mailbox; /* Mailbox to select */ - char *uidvalidity; /* UIDVALIDITY to check in select */ - char *uid; /* Message UID to fetch */ - char *mindex; /* Index in mail box of mail to fetch */ - char *section; /* Message SECTION to fetch */ - char *partial; /* Message PARTIAL to fetch */ - char *query; /* Query to search for */ - char *custom; /* Custom request */ - char *custom_params; /* Parameters for the custom request */ -}; - -/* imap_conn is used for struct connection-oriented data in the connectdata - struct */ -struct imap_conn { - struct pingpong pp; - struct SASL sasl; /* SASL-related parameters */ - struct dynbuf dyn; /* for the IMAP commands */ - char *mailbox; /* The last selected mailbox */ - char *mailbox_uidvalidity; /* UIDVALIDITY parsed from select response */ - imapstate state; /* Always use imap.c:state() to change state! */ - char resptag[5]; /* Response tag to wait for */ - unsigned char preftype; /* Preferred authentication type */ - unsigned char cmdid; /* Last used command ID */ - BIT(ssldone); /* Is connect() over SSL done? */ - BIT(preauth); /* Is this connection PREAUTH? */ - BIT(tls_supported); /* StartTLS capability supported by server */ - BIT(login_disabled); /* LOGIN command disabled by server */ - BIT(ir_supported); /* Initial response supported by server */ -}; extern const struct Curl_handler Curl_handler_imap; extern const struct Curl_handler Curl_handler_imaps; diff --git a/Utilities/cmcurl/lib/inet_ntop.c b/Utilities/cmcurl/lib/inet_ntop.c index fa9077376b7..bb2ec578725 100644 --- a/Utilities/cmcurl/lib/inet_ntop.c +++ b/Utilities/cmcurl/lib/inet_ntop.c @@ -38,15 +38,15 @@ #include "curl_printf.h" #define IN6ADDRSZ 16 -#define INADDRSZ 4 +/* #define INADDRSZ 4 */ #define INT16SZ 2 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * 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 * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif @@ -56,9 +56,9 @@ * Returns `dst' (as a const) * Note: * - uses no statics - * - takes a unsigned char* not an in_addr as input + * - takes an unsigned char* not an in_addr as input */ -static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) +static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) { char tmp[sizeof("255.255.255.255")]; size_t len; @@ -74,8 +74,12 @@ static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) len = strlen(tmp); if(len == 0 || len >= size) { - errno = ENOSPC; - return (NULL); +#ifdef USE_WINSOCK + CURL_SETERRNO(WSAEINVAL); +#else + CURL_SETERRNO(ENOSPC); +#endif + return NULL; } strcpy(dst, tmp); return dst; @@ -84,22 +88,22 @@ static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) /* * Convert IPv6 binary address into presentation (printable) format. */ -static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) +static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) { /* * Note that int32_t and int16_t need only be "at least" large enough - * to contain a value of the specified size. On some systems, like + * to contain a value of the specified size. On some systems, like * Crays, there is no such thing as an integer variable with 16 bits. * Keep this in mind if you think this function should have been coded - * to use pointer overlays. All the world's not a VAX. + * to use pointer overlays. All the world's not a VAX. */ char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; char *tp; struct { - long base; - long len; + int base; + int len; } best, cur; - unsigned long words[IN6ADDRSZ / INT16SZ]; + unsigned int words[IN6ADDRSZ / INT16SZ]; int i; /* Preprocess: @@ -108,7 +112,7 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) */ memset(words, '\0', sizeof(words)); for(i = 0; i < IN6ADDRSZ; i++) - words[i/2] |= (src[i] << ((1 - (i % 2)) << 3)); + words[i/2] |= ((unsigned int)src[i] << ((1 - (i % 2)) << 3)); best.base = -1; cur.base = -1; @@ -117,8 +121,9 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { if(words[i] == 0) { - if(cur.base == -1) - cur.base = i, cur.len = 1; + if(cur.base == -1) { + cur.base = i; cur.len = 1; + } else cur.len++; } @@ -152,13 +157,12 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) if(i == 6 && best.base == 0 && (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { if(!inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp))) { - errno = ENOSPC; - return (NULL); + return NULL; } tp += strlen(tp); break; } - tp += msnprintf(tp, 5, "%lx", words[i]); + tp += msnprintf(tp, 5, "%x", words[i]); } /* Was it a trailing run of 0x00's? @@ -167,11 +171,15 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) *tp++ = ':'; *tp++ = '\0'; - /* Check for overflow, copy, and we're done. + /* Check for overflow, copy, and we are done. */ if((size_t)(tp - tmp) > size) { - errno = ENOSPC; - return (NULL); +#ifdef USE_WINSOCK + CURL_SETERRNO(WSAEINVAL); +#else + CURL_SETERRNO(ENOSPC); +#endif + return NULL; } strcpy(dst, tmp); return dst; @@ -184,10 +192,9 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) * Returns NULL on error and errno set with the specific * error, EAFNOSUPPORT or ENOSPC. * - * On Windows we store the error in the thread errno, not - * in the winsock error code. This is to avoid losing the - * actual last winsock error. So when this function returns - * NULL, check errno not SOCKERRNO. + * On Windows we store the error in the thread errno, not in the Winsock error + * code. This is to avoid losing the actual last Winsock error. When this + * function returns NULL, check errno not SOCKERRNO. */ char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) { @@ -197,7 +204,7 @@ char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) case AF_INET6: return inet_ntop6((const unsigned char *)src, buf, size); default: - errno = EAFNOSUPPORT; + CURL_SETERRNO(SOCKEAFNOSUPPORT); return NULL; } } diff --git a/Utilities/cmcurl/lib/inet_ntop.h b/Utilities/cmcurl/lib/inet_ntop.h index 7c3ead43418..9923daaed1b 100644 --- a/Utilities/cmcurl/lib/inet_ntop.h +++ b/Utilities/cmcurl/lib/inet_ntop.h @@ -29,11 +29,23 @@ char *Curl_inet_ntop(int af, const void *addr, char *buf, size_t size); #ifdef HAVE_INET_NTOP +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif #ifdef HAVE_ARPA_INET_H #include #endif -#define Curl_inet_ntop(af,addr,buf,size) \ - inet_ntop(af, addr, buf, (curl_socklen_t)size) +#ifdef __AMIGA__ +#define Curl_inet_ntop(af,addr,buf,size) \ + (char *)inet_ntop(af, CURL_UNCONST(addr), (unsigned char *)buf, \ + (curl_socklen_t)(size)) +#else +#define Curl_inet_ntop(af,addr,buf,size) \ + inet_ntop(af, addr, buf, (curl_socklen_t)(size)) +#endif #endif #endif /* HEADER_CURL_INET_NTOP_H */ diff --git a/Utilities/cmcurl/lib/inet_pton.c b/Utilities/cmcurl/lib/inet_pton.c deleted file mode 100644 index 7d3c6987957..00000000000 --- a/Utilities/cmcurl/lib/inet_pton.c +++ /dev/null @@ -1,242 +0,0 @@ -/* This is from the BIND 4.9.4 release, modified to compile by itself */ - -/* Copyright (c) Internet Software Consortium. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS - * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE - * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR - * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS - * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS - * SOFTWARE. - * - * SPDX-License-Identifier: ISC - */ - -#include "curl_setup.h" - -#ifndef HAVE_INET_PTON - -#ifdef HAVE_SYS_PARAM_H -#include -#endif -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif - -#include "inet_pton.h" - -#define IN6ADDRSZ 16 -#define INADDRSZ 4 -#define INT16SZ 2 - -/* - * If ENABLE_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 - * everywhere. - */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) -#define AF_INET6 (AF_INET + 1) -#endif - -/* - * WARNING: Don't even consider trying to compile this on a system where - * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. - */ - -static int inet_pton4(const char *src, unsigned char *dst); -static int inet_pton6(const char *src, unsigned char *dst); - -/* int - * inet_pton(af, src, dst) - * convert from presentation format (which usually means ASCII printable) - * to network format (which is usually some kind of binary format). - * return: - * 1 if the address was valid for the specified address family - * 0 if the address wasn't valid (`dst' is untouched in this case) - * -1 if some other error occurred (`dst' is untouched in this case, too) - * notice: - * On Windows we store the error in the thread errno, not - * in the winsock error code. This is to avoid losing the - * actual last winsock error. So when this function returns - * -1, check errno not SOCKERRNO. - * author: - * Paul Vixie, 1996. - */ -int -Curl_inet_pton(int af, const char *src, void *dst) -{ - switch(af) { - case AF_INET: - return (inet_pton4(src, (unsigned char *)dst)); - case AF_INET6: - return (inet_pton6(src, (unsigned char *)dst)); - default: - errno = EAFNOSUPPORT; - return (-1); - } - /* NOTREACHED */ -} - -/* int - * inet_pton4(src, dst) - * like inet_aton() but without all the hexadecimal and shorthand. - * return: - * 1 if `src' is a valid dotted quad, else 0. - * notice: - * does not touch `dst' unless it's returning 1. - * author: - * Paul Vixie, 1996. - */ -static int -inet_pton4(const char *src, unsigned char *dst) -{ - static const char digits[] = "0123456789"; - int saw_digit, octets, ch; - unsigned char tmp[INADDRSZ], *tp; - - saw_digit = 0; - octets = 0; - tp = tmp; - *tp = 0; - while((ch = *src++) != '\0') { - const char *pch; - - pch = strchr(digits, ch); - if(pch) { - unsigned int val = *tp * 10 + (unsigned int)(pch - digits); - - if(saw_digit && *tp == 0) - return (0); - if(val > 255) - return (0); - *tp = (unsigned char)val; - if(!saw_digit) { - if(++octets > 4) - return (0); - saw_digit = 1; - } - } - else if(ch == '.' && saw_digit) { - if(octets == 4) - return (0); - *++tp = 0; - saw_digit = 0; - } - else - return (0); - } - if(octets < 4) - return (0); - memcpy(dst, tmp, INADDRSZ); - return (1); -} - -/* int - * inet_pton6(src, dst) - * convert presentation level address to network order binary form. - * return: - * 1 if `src' is a valid [RFC1884 2.2] address, else 0. - * notice: - * (1) does not touch `dst' unless it's returning 1. - * (2) :: in a full address is silently ignored. - * credit: - * inspired by Mark Andrews. - * author: - * Paul Vixie, 1996. - */ -static int -inet_pton6(const char *src, unsigned char *dst) -{ - static const char xdigits_l[] = "0123456789abcdef", - xdigits_u[] = "0123456789ABCDEF"; - unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; - const char *curtok; - int ch, saw_xdigit; - size_t val; - - memset((tp = tmp), 0, IN6ADDRSZ); - endp = tp + IN6ADDRSZ; - colonp = NULL; - /* Leading :: requires some special handling. */ - if(*src == ':') - if(*++src != ':') - return (0); - curtok = src; - saw_xdigit = 0; - val = 0; - while((ch = *src++) != '\0') { - const char *xdigits; - const char *pch; - - pch = strchr((xdigits = xdigits_l), ch); - if(!pch) - pch = strchr((xdigits = xdigits_u), ch); - if(pch) { - val <<= 4; - val |= (pch - xdigits); - if(++saw_xdigit > 4) - return (0); - continue; - } - if(ch == ':') { - curtok = src; - if(!saw_xdigit) { - if(colonp) - return (0); - colonp = tp; - continue; - } - if(tp + INT16SZ > endp) - return (0); - *tp++ = (unsigned char) ((val >> 8) & 0xff); - *tp++ = (unsigned char) (val & 0xff); - saw_xdigit = 0; - val = 0; - continue; - } - if(ch == '.' && ((tp + INADDRSZ) <= endp) && - inet_pton4(curtok, tp) > 0) { - tp += INADDRSZ; - saw_xdigit = 0; - break; /* '\0' was seen by inet_pton4(). */ - } - return (0); - } - if(saw_xdigit) { - if(tp + INT16SZ > endp) - return (0); - *tp++ = (unsigned char) ((val >> 8) & 0xff); - *tp++ = (unsigned char) (val & 0xff); - } - if(colonp) { - /* - * Since some memmove()'s erroneously fail to handle - * overlapping regions, we'll do the shift by hand. - */ - const ssize_t n = tp - colonp; - ssize_t i; - - if(tp == endp) - return (0); - for(i = 1; i <= n; i++) { - *(endp - i) = *(colonp + n - i); - *(colonp + n - i) = 0; - } - tp = endp; - } - if(tp != endp) - return (0); - memcpy(dst, tmp, IN6ADDRSZ); - return (1); -} - -#endif /* HAVE_INET_PTON */ diff --git a/Utilities/cmcurl/lib/krb5.c b/Utilities/cmcurl/lib/krb5.c index a71779ab043..9ebba4e3d1d 100644 --- a/Utilities/cmcurl/lib/krb5.c +++ b/Utilities/cmcurl/lib/krb5.c @@ -1,6 +1,6 @@ /* GSSAPI/krb5 support for FTP - loosely based on old krb4.c * - * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan + * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * Copyright (C) Daniel Stenberg * All rights reserved. @@ -25,7 +25,7 @@ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) @@ -46,14 +46,16 @@ #endif #include "urldata.h" +#include "url.h" #include "cfilters.h" #include "cf-socket.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "ftp.h" #include "curl_gssapi.h" #include "sendf.h" +#include "transfer.h" #include "curl_krb5.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "strcase.h" #include "strdup.h" @@ -62,21 +64,25 @@ #include "curl_memory.h" #include "memdebug.h" +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, const char *cmd) { - ssize_t bytes_written; + size_t bytes_written; #define SBUF_SIZE 1024 char s[SBUF_SIZE]; size_t write_len; char *sptr = s; CURLcode result = CURLE_OK; #ifdef HAVE_GSSAPI - enum protection_level data_sec = conn->data_prot; + unsigned char data_sec = conn->data_prot; #endif - if(!cmd) - return CURLE_BAD_FUNCTION_ARGUMENT; + DEBUGASSERT(cmd); write_len = strlen(cmd); if(!write_len || write_len > (sizeof(s) -3)) @@ -91,8 +97,7 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, #ifdef HAVE_GSSAPI conn->data_prot = PROT_CMD; #endif - result = Curl_write(data, conn->sock[FIRSTSOCKET], sptr, write_len, - &bytes_written); + result = Curl_xfer_send(data, sptr, write_len, FALSE, &bytes_written); #ifdef HAVE_GSSAPI DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); conn->data_prot = data_sec; @@ -101,9 +106,9 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, if(result) break; - Curl_debug(data, CURLINFO_HEADER_OUT, sptr, (size_t)bytes_written); + Curl_debug(data, CURLINFO_HEADER_OUT, sptr, bytes_written); - if(bytes_written != (ssize_t)write_len) { + if(bytes_written != write_len) { write_len -= bytes_written; sptr += bytes_written; } @@ -169,8 +174,8 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal * libraries modify the input buffer in gss_wrap() */ - dec.value = (void *)from; - dec.length = length; + dec.value = CURL_UNCONST(from); + dec.length = (size_t)length; maj = gss_wrap(&min, *context, level == PROT_PRIVATE, GSS_C_QOP_DEFAULT, @@ -179,7 +184,7 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) if(maj != GSS_S_COMPLETE) return -1; - /* malloc a new buffer, in case gss_release_buffer doesn't work as + /* malloc a new buffer, in case gss_release_buffer does not work as expected */ *to = malloc(enc.length); if(!*to) @@ -203,15 +208,20 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) data->set.str[STRING_SERVICE_NAME] : "ftp"; const char *srv_host = "host"; - gss_buffer_desc input_buffer, output_buffer, _gssresp, *gssresp; + gss_buffer_desc input_buffer, output_buffer, *gssresp; + gss_buffer_desc _gssresp = GSS_C_EMPTY_BUFFER; OM_uint32 maj, min; gss_name_t gssname; gss_ctx_id_t *context = app_data; struct gss_channel_bindings_struct chan; size_t base64_sz = 0; struct sockaddr_in *remote_addr = - (struct sockaddr_in *)(void *)&conn->remote_addr->sa_addr; + (struct sockaddr_in *)CURL_UNCONST(&conn->remote_addr->curl_sa_addr); char *stringp; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + + if(!ftpc) + return -2; if(getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&conn->local_addr, &l) < 0) @@ -228,7 +238,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) /* this loop will execute twice (once for service, once for host) */ for(;;) { - /* this really shouldn't be repeated here, but can't help it */ + /* this really should not be repeated here, but cannot help it */ if(service == srv_host) { result = ftpsend(data, conn, "AUTH GSSAPI"); if(result) @@ -236,9 +246,11 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) if(Curl_GetFTPResponse(data, &nread, NULL)) return -1; - - if(data->state.buffer[0] != '3') - return -1; + else { + char *line = curlx_dyn_ptr(&ftpc->pp.recvbuf); + if(line[0] != '3') + return -1; + } } stringp = aprintf("%s@%s", service, host); @@ -261,7 +273,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) } /* We pass NULL as |output_name_type| to avoid a leak. */ gss_display_name(&min, gssname, &output_buffer, NULL); - infof(data, "Trying against %s", output_buffer.value); + infof(data, "Trying against %s", (char *)output_buffer.value); gssresp = GSS_C_NO_BUFFER; *context = GSS_C_NO_CONTEXT; @@ -296,7 +308,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) if(output_buffer.length) { char *cmd; - result = Curl_base64_encode((char *)output_buffer.value, + result = curlx_base64_encode((char *)output_buffer.value, output_buffer.length, &p, &base64_sz); if(result) { infof(data, "base64-encoding: %s", curl_easy_strerror(result)); @@ -322,25 +334,31 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) ret = -1; break; } - - if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3') { - infof(data, "Server didn't accept auth data"); - ret = AUTH_ERROR; - break; + else { + size_t len = curlx_dyn_len(&ftpc->pp.recvbuf); + p = curlx_dyn_ptr(&ftpc->pp.recvbuf); + if((len < 4) || (p[0] != '2' && p[0] != '3')) { + infof(data, "Server did not accept auth data"); + ret = AUTH_ERROR; + break; + } } _gssresp.value = NULL; /* make sure it is initialized */ - p = data->state.buffer + 4; + _gssresp.length = 0; + p += 4; /* over '789 ' */ p = strstr(p, "ADAT="); if(p) { - result = Curl_base64_decode(p + 5, - (unsigned char **)&_gssresp.value, - &_gssresp.length); + unsigned char *outptr; + size_t outlen; + result = curlx_base64_decode(p + 5, &outptr, &outlen); if(result) { failf(data, "base64-decoding: %s", curl_easy_strerror(result)); ret = AUTH_CONTINUE; break; } + _gssresp.value = outptr; + _gssresp.length = outlen; } gssresp = &_gssresp; @@ -354,7 +372,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) free(_gssresp.value); if(ret == AUTH_OK || service == srv_host) - return ret; + break; service = srv_host; } @@ -363,13 +381,13 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) static void krb5_end(void *app_data) { - OM_uint32 min; - gss_ctx_id_t *context = app_data; - if(*context != GSS_C_NO_CONTEXT) { - OM_uint32 maj = gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); - (void)maj; - DEBUGASSERT(maj == GSS_S_COMPLETE); - } + OM_uint32 min; + gss_ctx_id_t *context = app_data; + if(*context != GSS_C_NO_CONTEXT) { + OM_uint32 maj = gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); + (void)maj; + DEBUGASSERT(maj == GSS_S_COMPLETE); + } } static const struct Curl_sec_client_mech Curl_krb5_client_mech = { @@ -385,7 +403,7 @@ static const struct Curl_sec_client_mech Curl_krb5_client_mech = { }; static const struct { - enum protection_level level; + unsigned char level; const char *name; } level_names[] = { { PROT_CLEAR, "clear" }, @@ -394,8 +412,7 @@ static const struct { { PROT_PRIVATE, "private" } }; -static enum protection_level -name_to_level(const char *name) +static unsigned char name_to_level(const char *name) { int i; for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++) @@ -418,7 +435,6 @@ static char level_to_char(int level) case PROT_PRIVATE: return 'P'; case PROT_CMD: - /* Fall through */ default: /* Those 2 cases should not be reached! */ break; @@ -430,6 +446,9 @@ static char level_to_char(int level) /* Send an FTP command defined by |message| and the optional arguments. The function returns the ftp_code. If an error occurs, -1 is returned. */ +static int ftp_send_command(struct Curl_easy *data, const char *message, ...) + CURL_PRINTF(2, 3); + static int ftp_send_command(struct Curl_easy *data, const char *message, ...) { int ftp_code; @@ -463,7 +482,7 @@ socket_read(struct Curl_easy *data, int sockindex, void *to, size_t len) ssize_t nread = 0; while(len > 0) { - nread = Curl_conn_recv(data, sockindex, to_p, len, &result); + result = Curl_conn_recv(data, sockindex, to_p, len, &nread); if(nread > 0) { len -= nread; to_p += nread; @@ -487,11 +506,11 @@ socket_write(struct Curl_easy *data, int sockindex, const void *to, { const char *to_p = to; CURLcode result; - ssize_t written; + size_t written; while(len > 0) { - written = Curl_conn_send(data, sockindex, to_p, len, &result); - if(written > 0) { + result = Curl_conn_send(data, sockindex, to_p, len, FALSE, &written); + if(!result && written > 0) { len -= written; to_p += written; } @@ -517,24 +536,33 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, return result; if(len) { - /* only realloc if there was a length */ - len = ntohl(len); + len = (int)ntohl((uint32_t)len); if(len > CURL_MAX_INPUT_LENGTH) - len = 0; - else - buf->data = Curl_saferealloc(buf->data, len); + return CURLE_TOO_LARGE; + + curlx_dyn_reset(&buf->buf); } - if(!len || !buf->data) - return CURLE_OUT_OF_MEMORY; + else + return CURLE_RECV_ERROR; - result = socket_read(data, sockindex, buf->data, len); - if(result) - return result; - nread = conn->mech->decode(conn->app_data, buf->data, len, - conn->data_prot, conn); + do { + char buffer[1024]; + nread = CURLMIN(len, (int)sizeof(buffer)); + result = socket_read(data, sockindex, buffer, (size_t)nread); + if(result) + return result; + result = curlx_dyn_addn(&buf->buf, buffer, nread); + if(result) + return result; + len -= nread; + } while(len); + /* this decodes the dynbuf *in place* */ + nread = conn->mech->decode(conn->app_data, + curlx_dyn_ptr(&buf->buf), + len, conn->data_prot, conn); if(nread < 0) return CURLE_RECV_ERROR; - buf->size = (size_t)nread; + curlx_dyn_setlen(&buf->buf, nread); buf->index = 0; return CURLE_OK; } @@ -542,9 +570,10 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, static size_t buffer_read(struct krb5buffer *buf, void *data, size_t len) { - if(buf->size - buf->index < len) - len = buf->size - buf->index; - memcpy(data, (char *)buf->data + buf->index, len); + size_t size = curlx_dyn_len(&buf->buf); + if(size - buf->index < len) + len = size - buf->index; + memcpy(data, curlx_dyn_ptr(&buf->buf) + buf->index, len); buf->index += len; return len; } @@ -560,8 +589,11 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, *err = CURLE_OK; /* Handle clear text response. */ - if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) - return Curl_conn_recv(data, sockindex, buffer, len, err); + if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) { + ssize_t nread; + *err = Curl_conn_recv(data, sockindex, buffer, len, &nread); + return nread; + } if(conn->in_buffer.eof_flag) { conn->in_buffer.eof_flag = 0; @@ -576,7 +608,7 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, while(len > 0) { if(read_data(data, sockindex, &conn->in_buffer)) return -1; - if(conn->in_buffer.size == 0) { + if(curlx_dyn_len(&conn->in_buffer.buf) == 0) { if(bytes_read > 0) conn->in_buffer.eof_flag = 1; return bytes_read; @@ -589,10 +621,10 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, return total_read; } -/* Send |length| bytes from |from| to the |fd| socket taking care of encoding - and negotiating with the server. |from| can be NULL. */ +/* Send |length| bytes from |from| to the |sockindex| socket taking care of + encoding and negotiating with the server. |from| can be NULL. */ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t fd, const char *from, int length) + int sockindex, const char *from, int length) { int bytes, htonl_bytes; /* 32-bit integers for htonl */ char *buffer = NULL; @@ -600,7 +632,7 @@ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, size_t cmd_size = 0; CURLcode error; enum protection_level prot_level = conn->data_prot; - bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE; + bool iscmd = (prot_level == PROT_CMD); DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST); @@ -610,13 +642,13 @@ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, else prot_level = conn->command_prot; } - bytes = conn->mech->encode(conn->app_data, from, length, prot_level, + bytes = conn->mech->encode(conn->app_data, from, length, (int)prot_level, (void **)&buffer); if(!buffer || bytes <= 0) return; /* error */ if(iscmd) { - error = Curl_base64_encode(buffer, curlx_sitouz(bytes), + error = curlx_base64_encode(buffer, curlx_sitouz(bytes), &cmd_buffer, &cmd_size); if(error) { free(buffer); @@ -626,27 +658,27 @@ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, static const char *enc = "ENC "; static const char *mic = "MIC "; if(prot_level == PROT_PRIVATE) - socket_write(data, fd, enc, 4); + socket_write(data, sockindex, enc, 4); else - socket_write(data, fd, mic, 4); + socket_write(data, sockindex, mic, 4); - socket_write(data, fd, cmd_buffer, cmd_size); - socket_write(data, fd, "\r\n", 2); - infof(data, "Send: %s%s", prot_level == PROT_PRIVATE?enc:mic, + socket_write(data, sockindex, cmd_buffer, cmd_size); + socket_write(data, sockindex, "\r\n", 2); + infof(data, "Send: %s%s", prot_level == PROT_PRIVATE ? enc : mic, cmd_buffer); free(cmd_buffer); } } else { - htonl_bytes = htonl(bytes); - socket_write(data, fd, &htonl_bytes, sizeof(htonl_bytes)); - socket_write(data, fd, buffer, curlx_sitouz(bytes)); + htonl_bytes = (int)htonl((OM_uint32)bytes); + socket_write(data, sockindex, &htonl_bytes, sizeof(htonl_bytes)); + socket_write(data, sockindex, buffer, curlx_sitouz(bytes)); } free(buffer); } static ssize_t sec_write(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t fd, const char *buffer, size_t length) + int sockindex, const char *buffer, size_t length) { ssize_t tx = 0, len = conn->buffer_size; @@ -656,7 +688,7 @@ static ssize_t sec_write(struct Curl_easy *data, struct connectdata *conn, if(length < (size_t)len) len = length; - do_sec_send(data, conn, fd, buffer, curlx_sztosi(len)); + do_sec_send(data, conn, sockindex, buffer, curlx_sztosi(len)); length -= len; buffer += len; tx += len; @@ -666,12 +698,13 @@ static ssize_t sec_write(struct Curl_easy *data, struct connectdata *conn, /* Matches Curl_send signature */ static ssize_t sec_send(struct Curl_easy *data, int sockindex, - const void *buffer, size_t len, CURLcode *err) + const void *buffer, size_t len, bool eos, + CURLcode *err) { struct connectdata *conn = data->conn; - curl_socket_t fd = conn->sock[sockindex]; + (void)eos; /* unused */ *err = CURLE_OK; - return sec_write(data, conn, fd, buffer, len); + return sec_write(data, conn, sockindex, buffer, len); } int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, @@ -693,7 +726,7 @@ int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); - error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); + error = curlx_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); if(error || decoded_sz == 0) return -1; @@ -704,7 +737,7 @@ int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, decoded_len = curlx_uztosi(decoded_sz); decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len, - level, conn); + (int)level, conn); if(decoded_len <= 0) { free(buf); return -1; @@ -734,7 +767,7 @@ static int sec_set_protection_level(struct Curl_easy *data) { int code; struct connectdata *conn = data->conn; - enum protection_level level = conn->request_data_prot; + unsigned char level = conn->request_data_prot; DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); @@ -751,6 +784,11 @@ static int sec_set_protection_level(struct Curl_easy *data) if(level) { char *pbsz; unsigned int buffer_size = 1 << 20; /* 1048576 */ + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + char *line; + + if(!ftpc) + return -2; code = ftp_send_command(data, "PBSZ %u", buffer_size); if(code < 0) @@ -762,11 +800,12 @@ static int sec_set_protection_level(struct Curl_easy *data) } conn->buffer_size = buffer_size; - pbsz = strstr(data->state.buffer, "PBSZ="); + line = curlx_dyn_ptr(&ftpc->pp.recvbuf); + pbsz = strstr(line, "PBSZ="); if(pbsz) { /* stick to default value if the check fails */ - if(!strncmp(pbsz, "PBSZ=", 5) && ISDIGIT(pbsz[5])) - buffer_size = atoi(&pbsz[5]); + if(ISDIGIT(pbsz[5])) + buffer_size = (unsigned int)atoi(&pbsz[5]); if(buffer_size < conn->buffer_size) conn->buffer_size = buffer_size; } @@ -793,7 +832,7 @@ static int sec_set_protection_level(struct Curl_easy *data) int Curl_sec_request_prot(struct connectdata *conn, const char *level) { - enum protection_level l = name_to_level(level); + unsigned char l = name_to_level(level); if(l == PROT_NONE) return -1; DEBUGASSERT(l > PROT_NONE && l < PROT_LAST); @@ -854,7 +893,7 @@ static CURLcode choose_mech(struct Curl_easy *data, struct connectdata *conn) if(ret != AUTH_CONTINUE) { if(ret != AUTH_OK) { - /* Mechanism has dumped the error to stderr, don't error here. */ + /* Mechanism has dumped the error to stderr, do not error here. */ return CURLE_USE_SSL_FAILED; } DEBUGASSERT(ret == AUTH_OK); @@ -880,24 +919,30 @@ Curl_sec_login(struct Curl_easy *data, struct connectdata *conn) return choose_mech(data, conn); } +void +Curl_sec_conn_init(struct connectdata *conn) +{ + curlx_dyn_init(&conn->in_buffer.buf, CURL_MAX_INPUT_LENGTH); + conn->in_buffer.index = 0; + conn->in_buffer.eof_flag = 0; +} void -Curl_sec_end(struct connectdata *conn) +Curl_sec_conn_destroy(struct connectdata *conn) { if(conn->mech && conn->mech->end) conn->mech->end(conn->app_data); - free(conn->app_data); - conn->app_data = NULL; - if(conn->in_buffer.data) { - free(conn->in_buffer.data); - conn->in_buffer.data = NULL; - conn->in_buffer.size = 0; - conn->in_buffer.index = 0; - conn->in_buffer.eof_flag = 0; - } + Curl_safefree(conn->app_data); + curlx_dyn_free(&conn->in_buffer.buf); + conn->in_buffer.index = 0; + conn->in_buffer.eof_flag = 0; conn->sec_complete = 0; conn->data_prot = PROT_CLEAR; conn->mech = NULL; } +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + #endif /* HAVE_GSSAPI && !CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c index 4c88b0aaeb7..c1be2f4bda9 100644 --- a/Utilities/cmcurl/lib/ldap.c +++ b/Utilities/cmcurl/lib/ldap.c @@ -26,6 +26,11 @@ #if !defined(CURL_DISABLE_LDAP) && !defined(USE_OPENLDAP) +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + /* * Notice that USE_OPENLDAP is only a source code selection switch. When * libcurl is built with USE_OPENLDAP defined the libcurl source code that @@ -50,6 +55,14 @@ #endif #ifdef USE_WIN32_LDAP /* Use Windows LDAP implementation. */ +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4201) +# endif +# include /* for [P]UNICODE_STRING */ +# ifdef _MSC_VER +# pragma warning(pop) +# endif # include # ifndef LDAP_VENDOR_NAME # error Your Platform SDK is NOT sufficient for LDAP support! \ @@ -70,15 +83,16 @@ #include "urldata.h" #include +#include "cfilters.h" #include "sendf.h" #include "escape.h" #include "progress.h" #include "transfer.h" #include "strcase.h" -#include "strtok.h" +#include "curlx/strparse.h" #include "curl_ldap.h" -#include "curl_multibyte.h" -#include "curl_base64.h" +#include "curlx/multibyte.h" +#include "curlx/base64.h" #include "connect.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -129,18 +143,18 @@ static void _ldap_free_urldesc(LDAPURLDesc *ludp); _ldap_trace x; \ } while(0) - static void _ldap_trace(const char *fmt, ...); + static void _ldap_trace(const char *fmt, ...) CURL_PRINTF(1, 2); #else #define LDAP_TRACE(x) Curl_nop_stmt #endif #if defined(USE_WIN32_LDAP) && defined(ldap_err2string) -/* Use ansi error strings in UNICODE builds */ +/* Use ANSI error strings in Unicode builds */ #undef ldap_err2string #define ldap_err2string ldap_err2stringA #endif -#if defined(USE_WIN32_LDAP) && defined(_MSC_VER) && (_MSC_VER <= 1600) +#if defined(USE_WIN32_LDAP) && defined(_MSC_VER) && (_MSC_VER <= 1700) /* Workaround for warning: 'type cast' : conversion from 'int' to 'void *' of greater size */ #undef LDAP_OPT_ON @@ -156,7 +170,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ + "ldap", /* scheme */ ZERO_NULL, /* setup_connection */ ldap_do, /* do_it */ ZERO_NULL, /* done */ @@ -169,9 +183,11 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ CURLPROTO_LDAP, /* family */ @@ -184,7 +200,7 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ + "ldaps", /* scheme */ ZERO_NULL, /* setup_connection */ ldap_do, /* do_it */ ZERO_NULL, /* done */ @@ -197,9 +213,11 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAPS, /* defport */ CURLPROTO_LDAPS, /* protocol */ CURLPROTO_LDAP, /* family */ @@ -231,7 +249,7 @@ static int ldap_win_bind_auth(LDAP *server, const char *user, } else #endif -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_DIGEST_AUTH) if(authflags & CURLAUTH_DIGEST) { method = LDAP_AUTH_DIGEST; } @@ -242,16 +260,17 @@ static int ldap_win_bind_auth(LDAP *server, const char *user, } if(method && user && passwd) { - rc = Curl_create_sspi_identity(user, passwd, &cred); + CURLcode res = Curl_create_sspi_identity(user, passwd, &cred); + rc = (int)res; if(!rc) { - rc = ldap_bind_s(server, NULL, (TCHAR *)&cred, method); + rc = (int)ldap_bind_s(server, NULL, (TCHAR *)&cred, method); Curl_sspi_free_identity(&cred); } } else { /* proceed with current user credentials */ method = LDAP_AUTH_NEGOTIATE; - rc = ldap_bind_s(server, NULL, NULL, method); + rc = (int)ldap_bind_s(server, NULL, NULL, method); } return rc; } @@ -266,17 +285,17 @@ static int ldap_win_bind(struct Curl_easy *data, LDAP *server, PTCHAR inpass = NULL; if(user && passwd && (data->set.httpauth & CURLAUTH_BASIC)) { - inuser = curlx_convert_UTF8_to_tchar((char *) user); - inpass = curlx_convert_UTF8_to_tchar((char *) passwd); + inuser = curlx_convert_UTF8_to_tchar(user); + inpass = curlx_convert_UTF8_to_tchar(passwd); - rc = ldap_simple_bind_s(server, inuser, inpass); + rc = (int)ldap_simple_bind_s(server, inuser, inpass); curlx_unicodefree(inuser); curlx_unicodefree(inpass); } #if defined(USE_WINDOWS_SSPI) else { - rc = ldap_win_bind_auth(server, user, passwd, data->set.httpauth); + rc = (int)ldap_win_bind_auth(server, user, passwd, data->set.httpauth); } #endif @@ -286,8 +305,10 @@ static int ldap_win_bind(struct Curl_easy *data, LDAP *server, #if defined(USE_WIN32_LDAP) #define FREE_ON_WINLDAP(x) curlx_unicodefree(x) +#define curl_ldap_num_t ULONG #else #define FREE_ON_WINLDAP(x) +#define curl_ldap_num_t int #endif @@ -305,7 +326,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) int ldap_ssl = 0; char *val_b64 = NULL; size_t val_b64_sz = 0; - curl_off_t dlsize = 0; #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval ldap_timeout = {10, 0}; /* 10 sec connection/search timeout */ #endif @@ -319,7 +339,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) *done = TRUE; /* unconditionally */ infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d", - LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); + LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); infof(data, "LDAP local: %s", data->state.url); #ifdef HAVE_LDAP_URL_PARSE @@ -328,16 +348,16 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) rc = _ldap_url_parse(data, conn, &ludp); #endif if(rc) { - failf(data, "Bad LDAP URL: %s", ldap_err2string(rc)); + failf(data, "Bad LDAP URL: %s", ldap_err2string((curl_ldap_num_t)rc)); result = CURLE_URL_MALFORMAT; goto quit; } /* Get the URL scheme (either ldap or ldaps) */ - if(conn->given->flags & PROTOPT_SSL) + if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) ldap_ssl = 1; infof(data, "LDAP local: trying to establish %s connection", - ldap_ssl ? "encrypted" : "cleartext"); + ldap_ssl ? "encrypted" : "cleartext"); #if defined(USE_WIN32_LDAP) host = curlx_convert_UTF8_to_tchar(conn->host.name); @@ -363,61 +383,13 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) if(ldap_ssl) { #ifdef HAVE_LDAP_SSL #ifdef USE_WIN32_LDAP - /* Win32 LDAP SDK doesn't support insecure mode without CA! */ - server = ldap_sslinit(host, conn->port, 1); + /* Win32 LDAP SDK does not support insecure mode without CA! */ + server = ldap_sslinit(host, (curl_ldap_num_t)conn->primary.remote_port, 1); ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); #else int ldap_option; char *ldap_ca = conn->ssl_config.CAfile; -#if defined(CURL_HAS_NOVELL_LDAPSDK) - rc = ldapssl_client_init(NULL, NULL); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ldapssl_client_init %s", ldap_err2string(rc)); - result = CURLE_SSL_CERTPROBLEM; - goto quit; - } - if(conn->ssl_config.verifypeer) { - /* Novell SDK supports DER or BASE64 files. */ - int cert_type = LDAPSSL_CERT_FILETYPE_B64; - if((data->set.ssl.cert_type) && - (strcasecompare(data->set.ssl.cert_type, "DER"))) - cert_type = LDAPSSL_CERT_FILETYPE_DER; - if(!ldap_ca) { - failf(data, "LDAP local: ERROR %s CA cert not set", - (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM")); - result = CURLE_SSL_CERTPROBLEM; - goto quit; - } - infof(data, "LDAP local: using %s CA cert '%s'", - (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), - ldap_ca); - rc = ldapssl_add_trusted_cert(ldap_ca, cert_type); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR setting %s CA cert: %s", - (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), - ldap_err2string(rc)); - result = CURLE_SSL_CERTPROBLEM; - goto quit; - } - ldap_option = LDAPSSL_VERIFY_SERVER; - } - else - ldap_option = LDAPSSL_VERIFY_NONE; - rc = ldapssl_set_verify_mode(ldap_option); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR setting cert verify mode: %s", - ldap_err2string(rc)); - result = CURLE_SSL_CERTPROBLEM; - goto quit; - } - server = ldapssl_init(host, conn->port, 1); - if(!server) { - failf(data, "LDAP local: Cannot connect to %s:%u", - conn->host.dispname, conn->port); - result = CURLE_COULDNT_CONNECT; - goto quit; - } -#elif defined(LDAP_OPT_X_TLS) +#ifdef LDAP_OPT_X_TLS if(conn->ssl_config.verifypeer) { /* OpenLDAP SDK supports BASE64 files. */ if((data->set.ssl.cert_type) && @@ -451,10 +423,10 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = CURLE_SSL_CERTPROBLEM; goto quit; } - server = ldap_init(host, conn->port); + server = ldap_init(host, conn->primary.remote_port); if(!server) { failf(data, "LDAP local: Cannot connect to %s:%u", - conn->host.dispname, conn->port); + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -476,6 +448,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) } */ #else + (void)ldap_option; + (void)ldap_ca; /* 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 " @@ -492,10 +466,10 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } else { - server = ldap_init(host, conn->port); + server = ldap_init(host, (curl_ldap_num_t)conn->primary.remote_port); if(!server) { failf(data, "LDAP local: Cannot connect to %s:%u", - conn->host.dispname, conn->port); + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -518,7 +492,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) if(rc) { #ifdef USE_WIN32_LDAP failf(data, "LDAP local: bind via ldap_win_bind %s", - ldap_err2string(rc)); + ldap_err2string((ULONG)rc)); #else failf(data, "LDAP local: bind via ldap_simple_bind_s %s", ldap_err2string(rc)); @@ -527,16 +501,19 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope, - ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg); + Curl_pgrsSetDownloadCounter(data, 0); + rc = (int)ldap_search_s(server, ludp->lud_dn, + (curl_ldap_num_t)ludp->lud_scope, + ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg); if(rc && rc != LDAP_SIZELIMIT_EXCEEDED) { - failf(data, "LDAP remote: %s", ldap_err2string(rc)); + failf(data, "LDAP remote: %s", ldap_err2string((curl_ldap_num_t)rc)); result = CURLE_LDAP_SEARCH_FAILED; goto quit; } - for(num = 0, entryIterator = ldap_first_entry(server, ldapmsg); + num = 0; + for(entryIterator = ldap_first_entry(server, ldapmsg); entryIterator; entryIterator = ldap_next_entry(server, entryIterator), num++) { BerElement *ber = NULL; @@ -566,7 +543,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #endif name_len = strlen(name); - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"DN: ", 4); + result = Curl_client_write(data, CLIENTWRITE_BODY, "DN: ", 4); if(result) { FREE_ON_WINLDAP(name); ldap_memfree(dn); @@ -580,7 +557,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) { FREE_ON_WINLDAP(name); ldap_memfree(dn); @@ -588,8 +565,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - dlsize += name_len + 5; - FREE_ON_WINLDAP(name); ldap_memfree(dn); } @@ -618,7 +593,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) vals = ldap_get_values_len(server, entryIterator, attribute); if(vals) { for(i = 0; (vals[i] != NULL); i++) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\t", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -640,7 +615,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)": ", 2); + result = Curl_client_write(data, CLIENTWRITE_BODY, ": ", 2); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -651,13 +626,11 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - dlsize += attr_len + 3; - if((attr_len > 7) && (strcmp(";binary", attr + (attr_len - 7)) == 0)) { /* Binary attribute, encode to base64. */ - result = Curl_base64_encode(vals[i]->bv_val, vals[i]->bv_len, - &val_b64, &val_b64_sz); + result = curlx_base64_encode(vals[i]->bv_val, vals[i]->bv_len, + &val_b64, &val_b64_sz); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -681,8 +654,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - - dlsize += val_b64_sz; } } else { @@ -697,11 +668,9 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - - dlsize += vals[i]->bv_len; } - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -711,8 +680,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - - dlsize++; } /* Free memory used to store values */ @@ -723,11 +690,9 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) FREE_ON_WINLDAP(attr); ldap_memfree(attribute); - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) goto quit; - dlsize++; - Curl_pgrsSetDownloadCounter(data, dlsize); } if(ber) @@ -745,16 +710,12 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) ldap_free_urldesc(ludp); if(server) ldap_unbind_s(server); -#if defined(HAVE_LDAP_SSL) && defined(CURL_HAS_NOVELL_LDAPSDK) - if(ldap_ssl) - ldapssl_client_deinit(); -#endif /* HAVE_LDAP_SSL && CURL_HAS_NOVELL_LDAPSDK */ FREE_ON_WINLDAP(host); /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - connclose(conn, "LDAP connection always disable re-use"); + Curl_xfer_setup_nop(data); + connclose(conn, "LDAP connection always disable reuse"); return result; } @@ -767,7 +728,9 @@ static void _ldap_trace(const char *fmt, ...) if(do_trace == -1) { const char *env = getenv("CURL_TRACE"); - do_trace = (env && strtol(env, NULL, 10) > 0); + curl_off_t e = 0; + if(!curlx_str_number(&env, &e, INT_MAX)) + do_trace = e > 0; } if(!do_trace) return; @@ -795,39 +758,20 @@ static int str2scope(const char *p) return LDAP_SCOPE_SUBTREE; if(strcasecompare(p, "subtree")) return LDAP_SCOPE_SUBTREE; - return (-1); + return -1; } -/* - * Split 'str' into strings separated by commas. - * Note: out[] points into 'str'. - */ -static bool split_str(char *str, char ***out, size_t *count) +/* number of entries in the attributes list */ +static size_t num_entries(const char *s) { - char **res; - char *lasts; - char *s; - size_t i; size_t items = 1; - s = strchr(str, ','); + s = strchr(s, ','); while(s) { items++; - s = strchr(++s, ','); + s = strchr(s + 1, ','); } - - res = calloc(items, sizeof(char *)); - if(!res) - return FALSE; - - for(i = 0, s = strtok_r(str, ",", &lasts); s && i < items; - s = strtok_r(NULL, ",", &lasts), i++) - res[i] = s; - - *out = res; - *count = items; - - return TRUE; + return items; } /* @@ -921,15 +865,8 @@ static int _ldap_url_parse2(struct Curl_easy *data, *q++ = '\0'; if(*p) { - char **attributes; - size_t count = 0; - - /* Split the string into an array of attributes */ - if(!split_str(p, &attributes, &count)) { - rc = LDAP_NO_MEMORY; - - goto quit; - } + size_t count = num_entries(p); /* at least one */ + const char *atp = p; /* Allocate our array (+1 for the NULL entry) */ #if defined(USE_WIN32_LDAP) @@ -938,27 +875,25 @@ static int _ldap_url_parse2(struct Curl_easy *data, ludp->lud_attrs = calloc(count + 1, sizeof(char *)); #endif if(!ludp->lud_attrs) { - free(attributes); - rc = LDAP_NO_MEMORY; - goto quit; } for(i = 0; i < count; i++) { char *unescaped; CURLcode result; + struct Curl_str out; + + if(curlx_str_until(&atp, &out, 1024, ',')) + break; - LDAP_TRACE(("attr[%zu] '%s'\n", i, attributes[i])); + LDAP_TRACE(("attr[%zu] '%.*s'\n", i, (int)out.len, out.str)); /* Unescape the attribute */ - result = Curl_urldecode(attributes[i], 0, &unescaped, NULL, + result = Curl_urldecode(out.str, out.len, &unescaped, NULL, REJECT_ZERO); if(result) { - free(attributes); - rc = LDAP_NO_MEMORY; - goto quit; } @@ -970,10 +905,7 @@ static int _ldap_url_parse2(struct Curl_easy *data, free(unescaped); if(!ludp->lud_attrs[i]) { - free(attributes); - rc = LDAP_NO_MEMORY; - goto quit; } #else @@ -981,9 +913,9 @@ static int _ldap_url_parse2(struct Curl_easy *data, #endif ludp->lud_attrs_dups++; + if(curlx_str_single(&atp, ',')) + break; } - - free(attributes); } p = q; @@ -1077,7 +1009,7 @@ static int _ldap_url_parse(struct Curl_easy *data, ludp = NULL; } *ludpp = ludp; - return (rc); + return rc; } static void _ldap_free_urldesc(LDAPURLDesc *ludp) @@ -1108,4 +1040,9 @@ static void _ldap_free_urldesc(LDAPURLDesc *ludp) free(ludp); } #endif /* !HAVE_LDAP_URL_PARSE */ + +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + #endif /* !CURL_DISABLE_LDAP && !USE_OPENLDAP */ diff --git a/Utilities/cmcurl/lib/libcurl.rc b/Utilities/cmcurl/lib/libcurl.rc index daa2d62d8a5..1ceb4691fb8 100644 --- a/Utilities/cmcurl/lib/libcurl.rc +++ b/Utilities/cmcurl/lib/libcurl.rc @@ -32,7 +32,7 @@ VS_VERSION_INFO VERSIONINFO FILEVERSION RC_VERSION PRODUCTVERSION RC_VERSION FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#if defined(DEBUGBUILD) || defined(_DEBUG) +#if defined(DEBUGBUILD) || defined(UNITTESTS) || defined(CURLDEBUG) || defined(_DEBUG) FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0L diff --git a/Utilities/cmcurl/lib/llist.c b/Utilities/cmcurl/lib/llist.c index 5b6b0336da2..82934425cfc 100644 --- a/Utilities/cmcurl/lib/llist.c +++ b/Utilities/cmcurl/lib/llist.c @@ -32,16 +32,33 @@ /* this must be the last include file */ #include "memdebug.h" +#ifdef DEBUGBUILD +#define LLISTINIT 0x100cc001 /* random pattern */ +#define NODEINIT 0x12344321 /* random pattern */ +#define NODEREM 0x54321012 /* random pattern */ + +#define VERIFYNODE(x) verifynode(x) +static struct Curl_llist_node *verifynode(struct Curl_llist_node *n) +{ + DEBUGASSERT(!n || (n->_init == NODEINIT)); + return n; +} +#else +#define VERIFYNODE(x) x +#endif /* * @unittest: 1300 */ void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) { - l->size = 0; - l->dtor = dtor; - l->head = NULL; - l->tail = NULL; + l->_size = 0; + l->_dtor = dtor; + l->_head = NULL; + l->_tail = NULL; +#ifdef DEBUGBUILD + l->_init = LLISTINIT; +#endif } /* @@ -56,91 +73,207 @@ 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_element *e, +Curl_llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *e, /* may be NULL */ const void *p, - struct Curl_llist_element *ne) + struct Curl_llist_node *ne) { - ne->ptr = (void *) p; - if(list->size == 0) { - list->head = ne; - list->head->prev = NULL; - list->head->next = NULL; - list->tail = ne; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(ne); + +#ifdef DEBUGBUILD + ne->_init = NODEINIT; +#endif + ne->_ptr = CURL_UNCONST(p); + ne->_list = list; + if(list->_size == 0) { + list->_head = ne; + list->_head->_prev = NULL; + list->_head->_next = NULL; + list->_tail = ne; } else { /* if 'e' is NULL here, we insert the new element first in the list */ - ne->next = e?e->next:list->head; - ne->prev = e; + ne->_next = e ? e->_next : list->_head; + ne->_prev = e; if(!e) { - list->head->prev = ne; - list->head = ne; + list->_head->_prev = ne; + list->_head = ne; } - else if(e->next) { - e->next->prev = ne; + else if(e->_next) { + e->_next->_prev = ne; } else { - list->tail = ne; + list->_tail = ne; } if(e) - e->next = ne; + e->_next = ne; } - ++list->size; + ++list->_size; } /* + * Curl_llist_append() + * + * Adds a new list element to the end of the list. + * + * The 'ne' argument should be a pointer into the object to store. + * * @unittest: 1300 */ void -Curl_llist_remove(struct Curl_llist *list, struct Curl_llist_element *e, - void *user) +Curl_llist_append(struct Curl_llist *list, const void *p, + struct Curl_llist_node *ne) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(ne); + Curl_llist_insert_next(list, list->_tail, p, ne); +} + +void *Curl_node_take_elem(struct Curl_llist_node *e) { void *ptr; - if(!e || list->size == 0) - return; + struct Curl_llist *list; + if(!e) + return NULL; - if(e == list->head) { - list->head = e->next; + list = e->_list; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(list->_size); + DEBUGASSERT(e->_init == NODEINIT); + if(list) { + if(e == list->_head) { + list->_head = e->_next; - if(!list->head) - list->tail = NULL; - else - e->next->prev = NULL; - } - else { - if(e->prev) - e->prev->next = e->next; + if(!list->_head) + list->_tail = NULL; + else + e->_next->_prev = NULL; + } + else { + if(e->_prev) + e->_prev->_next = e->_next; - if(!e->next) - list->tail = e->prev; - else - e->next->prev = e->prev; + if(!e->_next) + list->_tail = e->_prev; + else + e->_next->_prev = e->_prev; + } + --list->_size; } + ptr = e->_ptr; - ptr = e->ptr; + e->_list = NULL; + e->_ptr = NULL; + e->_prev = NULL; + e->_next = NULL; +#ifdef DEBUGBUILD + e->_init = NODEREM; /* specific pattern on remove - not zero */ +#endif - e->ptr = NULL; - e->prev = NULL; - e->next = NULL; + return ptr; +} - --list->size; +/* + * @unittest: 1300 + */ +void +Curl_node_uremove(struct Curl_llist_node *e, void *user) +{ + struct Curl_llist *list; + void *ptr; + if(!e) + return; - /* call the dtor() last for when it actually frees the 'e' memory itself */ - if(list->dtor) - list->dtor(user, ptr); + list = e->_list; + DEBUGASSERT(list); + if(list) { + ptr = Curl_node_take_elem(e); + if(list->_dtor) + list->_dtor(user, ptr); + } +} + +void Curl_node_remove(struct Curl_llist_node *e) +{ + Curl_node_uremove(e, NULL); } void Curl_llist_destroy(struct Curl_llist *list, void *user) { if(list) { - while(list->size > 0) - Curl_llist_remove(list, list->tail, user); + DEBUGASSERT(list->_init == LLISTINIT); + while(list->_size > 0) + Curl_node_uremove(list->_tail, user); } } -size_t -Curl_llist_count(struct Curl_llist *list) +/* Curl_llist_head() returns the first 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return VERIFYNODE(list->_head); +} + +#ifdef UNITTESTS +/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return VERIFYNODE(list->_tail); +} +#endif + +/* Curl_llist_count() returns a size_t the number of nodes in the list */ +size_t Curl_llist_count(struct Curl_llist *list) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return list->_size; +} + +/* Curl_node_elem() returns the custom data from a Curl_llist_node */ +void *Curl_node_elem(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return n->_ptr; +} + +/* Curl_node_next() returns the next element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return VERIFYNODE(n->_next); +} + +#ifdef UNITTESTS + +/* Curl_node_prev() returns the previous element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return VERIFYNODE(n->_prev); +} + +#endif + +struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n) { - return list->size; + DEBUGASSERT(n); + DEBUGASSERT(!n->_list || n->_init == NODEINIT); + return n->_list; } diff --git a/Utilities/cmcurl/lib/llist.h b/Utilities/cmcurl/lib/llist.h index 320580e33cc..597c0e00a33 100644 --- a/Utilities/cmcurl/lib/llist.h +++ b/Utilities/cmcurl/lib/llist.h @@ -27,26 +27,67 @@ #include "curl_setup.h" #include -typedef void (*Curl_llist_dtor)(void *, void *); +typedef void (*Curl_llist_dtor)(void *user, void *elem); -struct Curl_llist_element { - void *ptr; - struct Curl_llist_element *prev; - struct Curl_llist_element *next; -}; +/* none of these struct members should be referenced directly, use the + dedicated functions */ struct Curl_llist { - struct Curl_llist_element *head; - struct Curl_llist_element *tail; - Curl_llist_dtor dtor; - size_t size; + struct Curl_llist_node *_head; + struct Curl_llist_node *_tail; + Curl_llist_dtor _dtor; + size_t _size; +#ifdef DEBUGBUILD + int _init; /* detect API usage mistakes */ +#endif +}; + +struct Curl_llist_node { + struct Curl_llist *_list; /* the list where this belongs */ + void *_ptr; + struct Curl_llist_node *_prev; + struct Curl_llist_node *_next; +#ifdef DEBUGBUILD + int _init; /* detect API usage mistakes */ +#endif }; void Curl_llist_init(struct Curl_llist *, Curl_llist_dtor); -void Curl_llist_insert_next(struct Curl_llist *, struct Curl_llist_element *, - const void *, struct Curl_llist_element *node); -void Curl_llist_remove(struct Curl_llist *, struct Curl_llist_element *, - void *); -size_t Curl_llist_count(struct Curl_llist *); +void Curl_llist_insert_next(struct Curl_llist *, struct Curl_llist_node *, + const void *, struct Curl_llist_node *node); +void Curl_llist_append(struct Curl_llist *, + const void *, struct Curl_llist_node *node); +void Curl_node_uremove(struct Curl_llist_node *, void *); +void Curl_node_remove(struct Curl_llist_node *); void Curl_llist_destroy(struct Curl_llist *, void *); + +/* Curl_llist_head() returns the first 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list); + +/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list); + +/* Curl_llist_count() returns a size_t the number of nodes in the list */ +size_t Curl_llist_count(struct Curl_llist *list); + +/* Curl_node_elem() returns the custom data from a Curl_llist_node */ +void *Curl_node_elem(struct Curl_llist_node *n); + +/* Remove the node from the list and return the custom data + * from a Curl_llist_node. Will NOT incoke a registered `dtor`. */ +void *Curl_node_take_elem(struct Curl_llist_node *); + +/* Curl_node_next() returns the next element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n); + +/* Curl_node_prev() returns the previous element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n); + +/* Curl_node_llist() return the list the node is in or NULL. */ +struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n); + #endif /* HEADER_CURL_LLIST_H */ diff --git a/Utilities/cmcurl/lib/macos.c b/Utilities/cmcurl/lib/macos.c new file mode 100644 index 00000000000..daf2ab94f60 --- /dev/null +++ b/Utilities/cmcurl/lib/macos.c @@ -0,0 +1,53 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef CURL_MACOS_CALL_COPYPROXIES + +#include + +#include "macos.h" + +#include + +CURLcode Curl_macos_init(void) +{ + /* + * The automagic conversion from IPv4 literals to IPv6 literals only + * works if the SCDynamicStoreCopyProxies system function gets called + * first. As curl currently does not support system-wide HTTP proxies, we + * therefore do not use any value this function might return. + * + * This function is only available on macOS and is not needed for + * IPv4-only builds, hence the conditions for defining + * CURL_MACOS_CALL_COPYPROXIES in curl_setup.h. + */ + CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL); + if(dict) + CFRelease(dict); + return CURLE_OK; +} + +#endif diff --git a/Utilities/cmcurl/lib/macos.h b/Utilities/cmcurl/lib/macos.h new file mode 100644 index 00000000000..637860e80fc --- /dev/null +++ b/Utilities/cmcurl/lib/macos.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_MACOS_H +#define HEADER_CURL_MACOS_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" + +#ifdef CURL_MACOS_CALL_COPYPROXIES + +CURLcode Curl_macos_init(void); + +#else + +#define Curl_macos_init() CURLE_OK + +#endif + +#endif /* HEADER_CURL_MACOS_H */ diff --git a/Utilities/cmcurl/lib/md4.c b/Utilities/cmcurl/lib/md4.c index 9ff093b1caa..a77085a6b28 100644 --- a/Utilities/cmcurl/lib/md4.c +++ b/Utilities/cmcurl/lib/md4.c @@ -28,20 +28,24 @@ #include +#include "strdup.h" #include "curl_md4.h" -#include "warnless.h" +#include "curlx/warnless.h" #ifdef USE_OPENSSL -#include -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) && \ - !defined(USE_AMISSL) +#include +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(USE_AMISSL) /* OpenSSL 3.0.0 marks the MD4 functions as deprecated */ #define OPENSSL_NO_MD4 +#else +/* Cover also OPENSSL_NO_MD4 configured in openssl */ +#include #endif #endif /* USE_OPENSSL */ #ifdef USE_WOLFSSL #include +#define VOID_MD4_INIT #ifdef NO_MD4 #define WOLFSSL_NO_MD4 #endif @@ -54,7 +58,8 @@ #else #include #endif -#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ + (MBEDTLS_VERSION_NUMBER < 0x03000000) #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS #endif #endif /* USE_MBEDTLS */ @@ -92,9 +97,10 @@ typedef struct md4_ctx MD4_CTX; -static void MD4_Init(MD4_CTX *ctx) +static int MD4_Init(MD4_CTX *ctx) { md4_init(ctx); + return 1; } static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) @@ -109,14 +115,21 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) #elif defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4) +#ifdef OPENSSL_COEXIST + #define MD4_CTX WOLFSSL_MD4_CTX + #define MD4_Init wolfSSL_MD4_Init + #define MD4_Update wolfSSL_MD4_Update + #define MD4_Final wolfSSL_MD4_Final +#endif + #elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) #elif defined(AN_APPLE_OS) typedef CC_MD4_CTX MD4_CTX; -static void MD4_Init(MD4_CTX *ctx) +static int MD4_Init(MD4_CTX *ctx) { - (void)CC_MD4_Init(ctx); + return CC_MD4_Init(ctx); } static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) @@ -137,20 +150,32 @@ struct md4_ctx { }; typedef struct md4_ctx MD4_CTX; -static void MD4_Init(MD4_CTX *ctx) +static int MD4_Init(MD4_CTX *ctx) { ctx->hCryptProv = 0; ctx->hHash = 0; - if(CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { - CryptCreateHash(ctx->hCryptProv, CALG_MD4, 0, 0, &ctx->hHash); + if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return 0; + + if(!CryptCreateHash(ctx->hCryptProv, CALG_MD4, 0, 0, &ctx->hHash)) { + CryptReleaseContext(ctx->hCryptProv, 0); + ctx->hCryptProv = 0; + return 0; } + + return 1; } static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) { - CryptHashData(ctx->hHash, (BYTE *)data, (unsigned int) size, 0); +#ifdef __MINGW32CE__ + CryptHashData(ctx->hHash, (BYTE *)CURL_UNCONST(data), + (unsigned int) size, 0); +#else + CryptHashData(ctx->hHash, (const BYTE *)data, (unsigned int) size, 0); +#endif } static void MD4_Final(unsigned char *result, MD4_CTX *ctx) @@ -176,20 +201,19 @@ struct md4_ctx { }; typedef struct md4_ctx MD4_CTX; -static void MD4_Init(MD4_CTX *ctx) +static int MD4_Init(MD4_CTX *ctx) { ctx->data = NULL; ctx->size = 0; + return 1; } static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) { if(!ctx->data) { - ctx->data = malloc(size); - if(ctx->data) { - memcpy(ctx->data, data, size); + ctx->data = Curl_memdup(data, size); + if(ctx->data) ctx->size = size; - } } } @@ -208,7 +232,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) } #else -/* When no other crypto library is available, or the crypto library doesn't +/* When no other crypto library is available, or the crypto library does not * support MD4, we use this code segment this implementation of it * * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. @@ -220,8 +244,8 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) * Author: * Alexander Peslyak, better known as Solar Designer * - * This software was written by Alexander Peslyak in 2001. No copyright is - * claimed, and the software is hereby placed in the public domain. In case + * This software was written by Alexander Peslyak in 2001. No copyright is + * claimed, and the software is hereby placed in the public domain. In case * this attempt to disclaim copyright and place the software in the public * domain is deemed null and void, then the software is Copyright (c) 2001 * Alexander Peslyak and it is hereby released to the general public under the @@ -230,19 +254,19 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) * Redistribution and use in source and binary forms, with or without * modification, are permitted. * - * There's ABSOLUTELY NO WARRANTY, express or implied. + * There is ABSOLUTELY NO WARRANTY, express or implied. * * (This is a heavily cut-down "BSD license".) * * This differs from Colin Plumb's older public domain implementation in that * no exactly 32-bit integer data type is required (any 32-bit or wider - * unsigned integer data type will do), there's no compile-time endianness - * configuration, and the function prototypes match OpenSSL's. No code from + * unsigned integer data type will do), there is no compile-time endianness + * configuration, and the function prototypes match OpenSSL's. No code from * Colin Plumb's implementation has been reused; this comment merely compares * the properties of the two independent implementations. * * The primary goals of this implementation are portability and ease of use. - * It is meant to be fast, but not as fast as possible. Some known + * It is meant to be fast, but not as fast as possible. Some known * optimizations are not included to reduce source code size and avoid * compile-time configuration. */ @@ -258,7 +282,7 @@ struct md4_ctx { }; typedef struct md4_ctx MD4_CTX; -static void MD4_Init(MD4_CTX *ctx); +static int MD4_Init(MD4_CTX *ctx); static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size); static void MD4_Final(unsigned char *result, MD4_CTX *ctx); @@ -268,14 +292,14 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx); * F and G are optimized compared to their RFC 1320 definitions, with the * optimization for F borrowed from Colin Plumb's MD5 implementation. */ -#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) -#define G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) -#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define MD4_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define MD4_G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) +#define MD4_H(x, y, z) ((x) ^ (y) ^ (z)) /* * The MD4 transformation for all three rounds. */ -#define STEP(f, a, b, c, d, x, s) \ +#define MD4_STEP(f, a, b, c, d, x, s) \ (a) += f((b), (c), (d)) + (x); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); @@ -284,30 +308,31 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx); * in a properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned - * memory accesses is just an optimization. Nothing will break if it - * doesn't work. + * memory accesses is just an optimization. Nothing will break if it + * does not work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) -#define SET(n) \ - (*(MD4_u32plus *)(void *)&ptr[(n) * 4]) -#define GET(n) \ - SET(n) +#define MD4_SET(n) \ + (*(const MD4_u32plus *)(const void *)&ptr[(n) * 4]) +#define MD4_GET(n) \ + MD4_SET(n) #else -#define SET(n) \ +#define MD4_SET(n) \ (ctx->block[(n)] = \ - (MD4_u32plus)ptr[(n) * 4] | \ - ((MD4_u32plus)ptr[(n) * 4 + 1] << 8) | \ - ((MD4_u32plus)ptr[(n) * 4 + 2] << 16) | \ - ((MD4_u32plus)ptr[(n) * 4 + 3] << 24)) -#define GET(n) \ + (MD4_u32plus)ptr[(n) * 4] | \ + ((MD4_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD4_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD4_u32plus)ptr[(n) * 4 + 3] << 24)) +#define MD4_GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update - * the bit counters. There are no alignment requirements. + * the bit counters. There are no alignment requirements. */ -static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) +static const void *my_md4_body(MD4_CTX *ctx, + const void *data, unsigned long size) { const unsigned char *ptr; MD4_u32plus a, b, c, d; @@ -328,58 +353,58 @@ static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) saved_d = d; /* Round 1 */ - STEP(F, a, b, c, d, SET(0), 3) - STEP(F, d, a, b, c, SET(1), 7) - STEP(F, c, d, a, b, SET(2), 11) - STEP(F, b, c, d, a, SET(3), 19) - STEP(F, a, b, c, d, SET(4), 3) - STEP(F, d, a, b, c, SET(5), 7) - STEP(F, c, d, a, b, SET(6), 11) - STEP(F, b, c, d, a, SET(7), 19) - STEP(F, a, b, c, d, SET(8), 3) - STEP(F, d, a, b, c, SET(9), 7) - STEP(F, c, d, a, b, SET(10), 11) - STEP(F, b, c, d, a, SET(11), 19) - STEP(F, a, b, c, d, SET(12), 3) - STEP(F, d, a, b, c, SET(13), 7) - STEP(F, c, d, a, b, SET(14), 11) - STEP(F, b, c, d, a, SET(15), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(0), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(1), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(2), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(3), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(4), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(5), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(6), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(7), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(8), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(9), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(10), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(11), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(12), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(13), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(14), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(15), 19) /* Round 2 */ - STEP(G, a, b, c, d, GET(0) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(4) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(8) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(12) + 0x5a827999, 13) - STEP(G, a, b, c, d, GET(1) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(5) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(9) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(13) + 0x5a827999, 13) - STEP(G, a, b, c, d, GET(2) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(6) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(10) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(14) + 0x5a827999, 13) - STEP(G, a, b, c, d, GET(3) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(7) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(11) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(15) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(0) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(4) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(8) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(12) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(1) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(5) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(9) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(13) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(2) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(6) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(10) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(14) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(3) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(7) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(11) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(15) + 0x5a827999, 13) /* Round 3 */ - STEP(H, a, b, c, d, GET(0) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(8) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(4) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(12) + 0x6ed9eba1, 15) - STEP(H, a, b, c, d, GET(2) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(10) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(6) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(14) + 0x6ed9eba1, 15) - STEP(H, a, b, c, d, GET(1) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(9) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(5) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(13) + 0x6ed9eba1, 15) - STEP(H, a, b, c, d, GET(3) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(11) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(7) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(15) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(0) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(8) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(4) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(12) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(2) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(10) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(6) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(14) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(1) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(9) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(5) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(13) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(3) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(11) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(7) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(15) + 0x6ed9eba1, 15) a += saved_a; b += saved_b; @@ -397,7 +422,7 @@ static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) return ptr; } -static void MD4_Init(MD4_CTX *ctx) +static int MD4_Init(MD4_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; @@ -406,6 +431,7 @@ static void MD4_Init(MD4_CTX *ctx) ctx->lo = 0; ctx->hi = 0; + return 1; } static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) @@ -432,11 +458,11 @@ static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); } if(size >= 64) { - data = body(ctx, data, size & ~(unsigned long)0x3f); + data = my_md4_body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } @@ -455,7 +481,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) if(available < 8) { memset(&ctx->buffer[used], 0, available); - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); used = 0; available = 64; } @@ -472,7 +498,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); result[0] = curlx_ultouc((ctx->a)&0xff); result[1] = curlx_ultouc((ctx->a >> 8)&0xff); @@ -496,14 +522,21 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) #endif /* CRYPTO LIBS */ -void Curl_md4it(unsigned char *output, const unsigned char *input, - const size_t len) +CURLcode Curl_md4it(unsigned char *output, const unsigned char *input, + const size_t len) { MD4_CTX ctx; +#ifdef VOID_MD4_INIT MD4_Init(&ctx); +#else + if(!MD4_Init(&ctx)) + return CURLE_FAILED_INIT; +#endif + MD4_Update(&ctx, input, curlx_uztoui(len)); MD4_Final(output, &ctx); + return CURLE_OK; } #endif /* USE_CURL_NTLM_CORE */ diff --git a/Utilities/cmcurl/lib/md5.c b/Utilities/cmcurl/lib/md5.c index 0a02cc02acb..6a273c56c0e 100644 --- a/Utilities/cmcurl/lib/md5.c +++ b/Utilities/cmcurl/lib/md5.c @@ -24,14 +24,15 @@ #include "curl_setup.h" -#ifndef CURL_DISABLE_CRYPTO_AUTH +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_DIGEST_AUTH) #include #include #include "curl_md5.h" #include "curl_hmac.h" -#include "warnless.h" +#include "curlx/warnless.h" #ifdef USE_MBEDTLS #include @@ -87,29 +88,30 @@ typedef struct md5_ctx my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { md5_init(ctx); return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *input, unsigned int inputLen) { md5_update(ctx, inputLen, input); } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { md5_digest(ctx, 16, digest); } -#elif defined(USE_OPENSSL_MD5) || defined(USE_WOLFSSL_MD5) +#elif defined(USE_OPENSSL_MD5) || \ + (defined(USE_WOLFSSL_MD5) && !defined(OPENSSL_COEXIST)) typedef MD5_CTX my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { if(!MD5_Init(ctx)) return CURLE_OUT_OF_MEMORY; @@ -117,23 +119,47 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *input, unsigned int len) { (void)MD5_Update(ctx, input, len); } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { (void)MD5_Final(digest, ctx); } +#elif defined(USE_WOLFSSL_MD5) + +typedef WOLFSSL_MD5_CTX my_md5_ctx; + +static CURLcode my_md5_init(void *ctx) +{ + if(!wolfSSL_MD5_Init(ctx)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +static void my_md5_update(void *ctx, + const unsigned char *input, + unsigned int len) +{ + (void)wolfSSL_MD5_Update(ctx, input, len); +} + +static void my_md5_final(unsigned char *digest, void *ctx) +{ + (void)wolfSSL_MD5_Final(digest, ctx); +} + #elif defined(USE_MBEDTLS) typedef mbedtls_md5_context my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { #if (MBEDTLS_VERSION_NUMBER >= 0x03000000) if(mbedtls_md5_starts(ctx)) @@ -147,7 +173,7 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *data, unsigned int length) { @@ -158,7 +184,7 @@ static void my_md5_update(my_md5_ctx *ctx, #endif } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { #if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) (void) mbedtls_md5_finish(ctx, digest); @@ -171,13 +197,13 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) /* For Apple operating systems: CommonCrypto has the functions we need. These functions are available on Tiger and later, as well as iOS 2.0 - and later. If you're building for an older cat, well, sorry. + and later. If you are building for an older cat, well, sorry. Declaring the functions as static like this seems to be a bit more reliable than defining COMMON_DIGEST_FOR_OPENSSL on older cats. */ # define my_md5_ctx CC_MD5_CTX -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { if(!CC_MD5_Init(ctx)) return CURLE_OUT_OF_MEMORY; @@ -185,14 +211,14 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *input, unsigned int inputLen) { CC_MD5_Update(ctx, input, inputLen); } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { CC_MD5_Final(digest, ctx); } @@ -205,29 +231,37 @@ struct md5_ctx { }; typedef struct md5_ctx my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *in) { + my_md5_ctx *ctx = (my_md5_ctx *)in; if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) return CURLE_OUT_OF_MEMORY; if(!CryptCreateHash(ctx->hCryptProv, CALG_MD5, 0, 0, &ctx->hHash)) { CryptReleaseContext(ctx->hCryptProv, 0); - return CURLE_OUT_OF_MEMORY; + ctx->hCryptProv = 0; + return CURLE_FAILED_INIT; } return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *in, const unsigned char *input, unsigned int inputLen) { - CryptHashData(ctx->hHash, (unsigned char *)input, inputLen, 0); + my_md5_ctx *ctx = in; +#ifdef __MINGW32CE__ + CryptHashData(ctx->hHash, (BYTE *)CURL_UNCONST(input), inputLen, 0); +#else + CryptHashData(ctx->hHash, (const BYTE *)input, inputLen, 0); +#endif } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *in) { + my_md5_ctx *ctx = (my_md5_ctx *)in; unsigned long length = 0; CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); if(length == 16) @@ -252,7 +286,7 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) * Author: * Alexander Peslyak, better known as Solar Designer * - * This software was written by Alexander Peslyak in 2001. No copyright is + * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is @@ -262,19 +296,19 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) * Redistribution and use in source and binary forms, with or without * modification, are permitted. * - * There's ABSOLUTELY NO WARRANTY, express or implied. + * There is ABSOLUTELY NO WARRANTY, express or implied. * * (This is a heavily cut-down "BSD license".) * * This differs from Colin Plumb's older public domain implementation in that * no exactly 32-bit integer data type is required (any 32-bit or wider - * unsigned integer data type will do), there's no compile-time endianness - * configuration, and the function prototypes match OpenSSL's. No code from + * unsigned integer data type will do), there is no compile-time endianness + * configuration, and the function prototypes match OpenSSL's. No code from * Colin Plumb's implementation has been reused; this comment merely compares * the properties of the two independent implementations. * * The primary goals of this implementation are portability and ease of use. - * It is meant to be fast, but not as fast as possible. Some known + * It is meant to be fast, but not as fast as possible. Some known * optimizations are not included to reduce source code size and avoid * compile-time configuration. */ @@ -290,10 +324,10 @@ struct md5_ctx { }; typedef struct md5_ctx my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx); -static void my_md5_update(my_md5_ctx *ctx, const void *data, - unsigned long size); -static void my_md5_final(unsigned char *result, my_md5_ctx *ctx); +static CURLcode my_md5_init(void *ctx); +static void my_md5_update(void *ctx, const unsigned char *data, + unsigned int size); +static void my_md5_final(unsigned char *result, void *ctx); /* * The basic MD5 functions. @@ -302,16 +336,16 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx); * architectures that lack an AND-NOT instruction, just like in Colin Plumb's * implementation. */ -#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) -#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) -#define H(x, y, z) (((x) ^ (y)) ^ (z)) -#define H2(x, y, z) ((x) ^ ((y) ^ (z))) -#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define MD5_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define MD5_G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define MD5_H(x, y, z) (((x) ^ (y)) ^ (z)) +#define MD5_H2(x, y, z) ((x) ^ ((y) ^ (z))) +#define MD5_I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ -#define STEP(f, a, b, c, d, x, t, s) \ +#define MD5_STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); @@ -321,30 +355,31 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx); * in a properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned - * memory accesses is just an optimization. Nothing will break if it - * doesn't work. + * memory accesses is just an optimization. Nothing will break if it + * does not work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) -#define SET(n) \ - (*(MD5_u32plus *)(void *)&ptr[(n) * 4]) -#define GET(n) \ - SET(n) +#define MD5_SET(n) \ + (*(const MD5_u32plus *)(const void *)&ptr[(n) * 4]) +#define MD5_GET(n) \ + MD5_SET(n) #else -#define SET(n) \ +#define MD5_SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) -#define GET(n) \ +#define MD5_GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update - * the bit counters. There are no alignment requirements. + * the bit counters. There are no alignment requirements. */ -static const void *body(my_md5_ctx *ctx, const void *data, unsigned long size) +static const void *my_md5_body(my_md5_ctx *ctx, + const void *data, unsigned long size) { const unsigned char *ptr; MD5_u32plus a, b, c, d; @@ -365,76 +400,76 @@ static const void *body(my_md5_ctx *ctx, const void *data, unsigned long size) saved_d = d; /* Round 1 */ - STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) - STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) - STEP(F, c, d, a, b, SET(2), 0x242070db, 17) - STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) - STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) - STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) - STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) - STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) - STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) - STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) - STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) - STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) - STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) - STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) - STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) - STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(0), 0xd76aa478, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(1), 0xe8c7b756, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(2), 0x242070db, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(3), 0xc1bdceee, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(4), 0xf57c0faf, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(5), 0x4787c62a, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(6), 0xa8304613, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(7), 0xfd469501, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(8), 0x698098d8, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(9), 0x8b44f7af, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(10), 0xffff5bb1, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(11), 0x895cd7be, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(12), 0x6b901122, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(13), 0xfd987193, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(14), 0xa679438e, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(15), 0x49b40821, 22) /* Round 2 */ - STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) - STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) - STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) - STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) - STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) - STEP(G, d, a, b, c, GET(10), 0x02441453, 9) - STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) - STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) - STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) - STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) - STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) - STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) - STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) - STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) - STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) - STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(1), 0xf61e2562, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(6), 0xc040b340, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(11), 0x265e5a51, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(0), 0xe9b6c7aa, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(5), 0xd62f105d, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(10), 0x02441453, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(15), 0xd8a1e681, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(4), 0xe7d3fbc8, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(9), 0x21e1cde6, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(14), 0xc33707d6, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(3), 0xf4d50d87, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(8), 0x455a14ed, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(13), 0xa9e3e905, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(2), 0xfcefa3f8, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(7), 0x676f02d9, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(12), 0x8d2a4c8a, 20) /* Round 3 */ - STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) - STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) - STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) - STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) - STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) - STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) - STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) - STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) - STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) - STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) - STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) - STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) - STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) - STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) - STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) - STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(5), 0xfffa3942, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(8), 0x8771f681, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(11), 0x6d9d6122, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(14), 0xfde5380c, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(1), 0xa4beea44, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(4), 0x4bdecfa9, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(7), 0xf6bb4b60, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(10), 0xbebfbc70, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(13), 0x289b7ec6, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(0), 0xeaa127fa, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(3), 0xd4ef3085, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(6), 0x04881d05, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(9), 0xd9d4d039, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(12), 0xe6db99e5, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(15), 0x1fa27cf8, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(2), 0xc4ac5665, 23) /* Round 4 */ - STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) - STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) - STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) - STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) - STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) - STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) - STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) - STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) - STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) - STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) - STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) - STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) - STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) - STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) - STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) - STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(0), 0xf4292244, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(7), 0x432aff97, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(14), 0xab9423a7, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(5), 0xfc93a039, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(12), 0x655b59c3, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(3), 0x8f0ccc92, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(10), 0xffeff47d, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(1), 0x85845dd1, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(8), 0x6fa87e4f, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(15), 0xfe2ce6e0, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(6), 0xa3014314, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(13), 0x4e0811a1, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(4), 0xf7537e82, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(11), 0xbd3af235, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(2), 0x2ad7d2bb, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; @@ -452,8 +487,9 @@ static const void *body(my_md5_ctx *ctx, const void *data, unsigned long size) return ptr; } -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *in) { + my_md5_ctx *ctx = (my_md5_ctx *)in; ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; @@ -465,11 +501,12 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, const void *data, - unsigned long size) +static void my_md5_update(void *in, const unsigned char *data, + unsigned int size) { MD5_u32plus saved_lo; - unsigned long used; + unsigned int used; + my_md5_ctx *ctx = (my_md5_ctx *)in; saved_lo = ctx->lo; ctx->lo = (saved_lo + size) & 0x1fffffff; @@ -480,7 +517,7 @@ static void my_md5_update(my_md5_ctx *ctx, const void *data, used = saved_lo & 0x3f; if(used) { - unsigned long available = 64 - used; + unsigned int available = 64 - used; if(size < available) { memcpy(&ctx->buffer[used], data, size); @@ -490,20 +527,21 @@ static void my_md5_update(my_md5_ctx *ctx, const void *data, memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); } if(size >= 64) { - data = body(ctx, data, size & ~(unsigned long)0x3f); + data = my_md5_body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } -static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *result, void *in) { - unsigned long used, available; + unsigned int used, available; + my_md5_ctx *ctx = (my_md5_ctx *)in; used = ctx->lo & 0x3f; @@ -513,7 +551,7 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) if(available < 8) { memset(&ctx->buffer[used], 0, available); - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); used = 0; available = 64; } @@ -530,7 +568,7 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); result[0] = curlx_ultouc((ctx->a)&0xff); result[1] = curlx_ultouc((ctx->a >> 8)&0xff); @@ -554,36 +592,21 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) #endif /* CRYPTO LIBS */ -const struct HMAC_params Curl_HMAC_MD5[] = { - { - /* Hash initialization function. */ - CURLX_FUNCTION_CAST(HMAC_hinit_func, my_md5_init), - /* Hash update function. */ - CURLX_FUNCTION_CAST(HMAC_hupdate_func, my_md5_update), - /* Hash computation end function. */ - CURLX_FUNCTION_CAST(HMAC_hfinal_func, my_md5_final), - /* Size of hash context structure. */ - sizeof(my_md5_ctx), - /* Maximum key length. */ - 64, - /* Result size. */ - 16 - } +const struct HMAC_params Curl_HMAC_MD5 = { + my_md5_init, /* Hash initialization function. */ + my_md5_update, /* Hash update function. */ + my_md5_final, /* Hash computation end function. */ + sizeof(my_md5_ctx), /* Size of hash context structure. */ + 64, /* Maximum key length. */ + 16 /* Result size. */ }; -const struct MD5_params Curl_DIGEST_MD5[] = { - { - /* Digest initialization function */ - CURLX_FUNCTION_CAST(Curl_MD5_init_func, my_md5_init), - /* Digest update function */ - CURLX_FUNCTION_CAST(Curl_MD5_update_func, my_md5_update), - /* Digest computation end function */ - CURLX_FUNCTION_CAST(Curl_MD5_final_func, my_md5_final), - /* Size of digest context struct */ - sizeof(my_md5_ctx), - /* Result size */ - 16 - } +const struct MD5_params Curl_DIGEST_MD5 = { + my_md5_init, /* Digest initialization function */ + my_md5_update, /* Digest update function */ + my_md5_final, /* Digest computation end function */ + sizeof(my_md5_ctx), /* Size of digest context struct */ + 16 /* Result size */ }; /* @@ -651,4 +674,4 @@ CURLcode Curl_MD5_final(struct MD5_context *context, unsigned char *result) return CURLE_OK; } -#endif /* CURL_DISABLE_CRYPTO_AUTH */ +#endif /* Using NTLM (without SSPI) || Digest */ diff --git a/Utilities/cmcurl/lib/memdebug.c b/Utilities/cmcurl/lib/memdebug.c index d6952a07a19..b351726b320 100644 --- a/Utilities/cmcurl/lib/memdebug.c +++ b/Utilities/cmcurl/lib/memdebug.c @@ -30,7 +30,7 @@ #include "urldata.h" -#define MEMDEBUG_NODEFINES /* don't redefine the standard functions */ +#define MEMDEBUG_NODEFINES /* do not redefine the standard functions */ /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -44,16 +44,16 @@ struct memdebug { double d; void *p; } mem[1]; - /* I'm hoping this is the thing with the strictest alignment - * requirements. That also means we waste some space :-( */ + /* I am hoping this is the thing with the strictest alignment + * requirements. That also means we waste some space :-( */ }; /* - * Note that these debug functions are very simple and they are meant to - * remain so. For advanced analysis, record a log file and write perl scripts - * to analyze them! + * Note that these debug functions are simple and they are meant to remain so. + * For advanced analysis, record a log file and write perl scripts to analyze + * them! * - * Don't use these with multithreaded test programs! + * Do not use these with multithreaded test programs! */ FILE *curl_dbg_logfile = NULL; @@ -75,7 +75,7 @@ static void curl_dbg_cleanup(void) curl_dbg_logfile = NULL; } -/* this sets the log file name */ +/* this sets the log filename */ void curl_dbg_memdebug(const char *logname) { if(!curl_dbg_logfile) { @@ -84,7 +84,7 @@ void curl_dbg_memdebug(const char *logname) else curl_dbg_logfile = stderr; #ifdef MEMDEBUG_LOG_SYNC - /* Flush the log file after every line so the log isn't lost in a crash */ + /* Flush the log file after every line so the log is not lost in a crash */ if(curl_dbg_logfile) setbuf(curl_dbg_logfile, (char *)NULL); #endif @@ -103,7 +103,7 @@ void curl_dbg_memlimit(long limit) } } -/* returns TRUE if this isn't allowed! */ +/* returns TRUE if this is not allowed! */ static bool countcheck(const char *func, int line, const char *source) { /* if source is NULL, then the call is made internally and this check @@ -117,20 +117,19 @@ static bool countcheck(const char *func, int line, const char *source) fprintf(stderr, "LIMIT %s:%d %s reached memlimit\n", source, line, func); fflush(curl_dbg_logfile); /* because it might crash now */ - errno = ENOMEM; + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(ENOMEM); return TRUE; /* RETURN ERROR! */ } else memsize--; /* countdown */ - - } return FALSE; /* allow this */ } -ALLOC_FUNC void *curl_dbg_malloc(size_t wantedsize, - int line, const char *source) +ALLOC_FUNC +void *curl_dbg_malloc(size_t wantedsize, int line, const char *source) { struct memdebug *mem; size_t size; @@ -153,11 +152,12 @@ ALLOC_FUNC void *curl_dbg_malloc(size_t wantedsize, source, line, wantedsize, mem ? (void *)mem->mem : (void *)0); - return (mem ? mem->mem : NULL); + return mem ? mem->mem : NULL; } -ALLOC_FUNC void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, - int line, const char *source) +ALLOC_FUNC +void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, + int line, const char *source) { struct memdebug *mem; size_t size, user_size; @@ -181,11 +181,11 @@ ALLOC_FUNC void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, source, line, wanted_elements, wanted_size, mem ? (void *)mem->mem : (void *)0); - return (mem ? mem->mem : NULL); + return mem ? mem->mem : NULL; } -ALLOC_FUNC char *curl_dbg_strdup(const char *str, - int line, const char *source) +ALLOC_FUNC +char *curl_dbg_strdup(const char *str, int line, const char *source) { char *mem; size_t len; @@ -208,9 +208,9 @@ ALLOC_FUNC char *curl_dbg_strdup(const char *str, return mem; } -#if defined(WIN32) && defined(UNICODE) -ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, - int line, const char *source) +#if defined(_WIN32) && defined(UNICODE) +ALLOC_FUNC +wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source) { wchar_t *mem; size_t wsiz, bsiz; @@ -229,7 +229,7 @@ ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, if(source) curl_dbg_log("MEM %s:%d wcsdup(%p) (%zu) = %p\n", - source, line, (void *)str, bsiz, (void *)mem); + source, line, (const void *)str, bsiz, (void *)mem); return mem; } @@ -238,7 +238,7 @@ ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, /* We provide a realloc() that accepts a NULL as pointer, which then performs a malloc(). In order to work with ares. */ void *curl_dbg_realloc(void *ptr, size_t wantedsize, - int line, const char *source) + int line, const char *source) { struct memdebug *mem = NULL; @@ -304,12 +304,6 @@ void curl_dbg_free(void *ptr, int line, const char *source) curl_socket_t curl_dbg_socket(int domain, int type, int protocol, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socket() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socket() = %ld\n" : - "FD %s:%d socket() = %zd\n"; - curl_socket_t sockfd; if(countcheck("socket", line, source)) @@ -318,7 +312,8 @@ curl_socket_t curl_dbg_socket(int domain, int type, int protocol, sockfd = socket(domain, type, protocol); if(source && (sockfd != CURL_SOCKET_BAD)) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d socket() = %" FMT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } @@ -357,16 +352,12 @@ int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t socket_vector[2], int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socketpair() = %d %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socketpair() = %ld %ld\n" : - "FD %s:%d socketpair() = %zd %zd\n"; - int res = socketpair(domain, type, protocol, socket_vector); if(source && (0 == res)) - curl_dbg_log(fmt, source, line, socket_vector[0], socket_vector[1]); + curl_dbg_log("FD %s:%d socketpair() = " + "%" FMT_SOCKET_T " %" FMT_SOCKET_T "\n", + source, line, socket_vector[0], socket_vector[1]); return res; } @@ -375,34 +366,42 @@ int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d accept() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d accept() = %ld\n" : - "FD %s:%d accept() = %zd\n"; - struct sockaddr *addr = (struct sockaddr *)saddr; curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; curl_socket_t sockfd = accept(s, addr, addrlen); if(source && (sockfd != CURL_SOCKET_BAD)) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } +#ifdef HAVE_ACCEPT4 +curl_socket_t curl_dbg_accept4(curl_socket_t s, void *saddr, void *saddrlen, + int flags, + int line, const char *source) +{ + struct sockaddr *addr = (struct sockaddr *)saddr; + curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; + + curl_socket_t sockfd = accept4(s, addr, addrlen, flags); + + if(source && (sockfd != CURL_SOCKET_BAD)) + curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n", + source, line, sockfd); + + return sockfd; +} +#endif + /* separate function to allow libcurl to mark a "faked" close */ void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d sclose(%d)\n": - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d sclose(%ld)\n": - "FD %s:%d sclose(%zd)\n"; - if(source) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d sclose(%" FMT_SOCKET_T ")\n", + source, line, sockfd); } /* this is our own defined way to close sockets on *ALL* platforms */ @@ -413,8 +412,9 @@ int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source) return res; } -ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, - int line, const char *source) +ALLOC_FUNC +FILE *curl_dbg_fopen(const char *file, const char *mode, + int line, const char *source) { FILE *res = fopen(file, mode); @@ -425,8 +425,9 @@ ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, return res; } -ALLOC_FUNC FILE *curl_dbg_fdopen(int filedes, const char *mode, - int line, const char *source) +ALLOC_FUNC +FILE *curl_dbg_fdopen(int filedes, const char *mode, + int line, const char *source) { FILE *res = fdopen(filedes, mode); if(source) @@ -450,33 +451,25 @@ int curl_dbg_fclose(FILE *file, int line, const char *source) return res; } -#define LOGLINE_BUFSIZE 1024 - /* this does the writing to the memory tracking log file */ void curl_dbg_log(const char *format, ...) { - char *buf; + char buf[1024]; int nchars; va_list ap; if(!curl_dbg_logfile) return; - buf = (Curl_cmalloc)(LOGLINE_BUFSIZE); - if(!buf) - return; - va_start(ap, format); - nchars = mvsnprintf(buf, LOGLINE_BUFSIZE, format, ap); + nchars = mvsnprintf(buf, sizeof(buf), format, ap); va_end(ap); - if(nchars > LOGLINE_BUFSIZE - 1) - nchars = LOGLINE_BUFSIZE - 1; + if(nchars > (int)sizeof(buf) - 1) + nchars = (int)sizeof(buf) - 1; if(nchars > 0) fwrite(buf, 1, (size_t)nchars, curl_dbg_logfile); - - (Curl_cfree)(buf); } #endif /* CURLDEBUG */ diff --git a/Utilities/cmcurl/lib/memdebug.h b/Utilities/cmcurl/lib/memdebug.h index c9eb5dc37c0..11b250dea23 100644 --- a/Utilities/cmcurl/lib/memdebug.h +++ b/Utilities/cmcurl/lib/memdebug.h @@ -33,12 +33,21 @@ #include #include "functypes.h" -#if defined(__GNUC__) && __GNUC__ >= 3 -# define ALLOC_FUNC __attribute__((malloc)) -# define ALLOC_SIZE(s) __attribute__((alloc_size(s))) -# define ALLOC_SIZE2(n, s) __attribute__((alloc_size(n, s))) +#ifdef __clang__ +# define ALLOC_FUNC __attribute__((__malloc__)) +# if __clang_major__ >= 4 +# define ALLOC_SIZE(s) __attribute__((__alloc_size__(s))) +# define ALLOC_SIZE2(n, s) __attribute__((__alloc_size__(n, s))) +# else +# define ALLOC_SIZE(s) +# define ALLOC_SIZE2(n, s) +# endif +#elif defined(__GNUC__) && __GNUC__ >= 3 +# define ALLOC_FUNC __attribute__((__malloc__)) +# define ALLOC_SIZE(s) __attribute__((__alloc_size__(s))) +# define ALLOC_SIZE2(n, s) __attribute__((__alloc_size__(n, s))) #elif defined(_MSC_VER) -# define ALLOC_FUNC __declspec(restrict) +# define ALLOC_FUNC __declspec(restrict) # define ALLOC_SIZE(s) # define ALLOC_SIZE2(n, s) #else @@ -49,30 +58,30 @@ #define CURL_MT_LOGFNAME_BUFSIZE 512 +/* Avoid redundant redeclaration warnings with modern compilers, when including + this header multiple times. */ +#ifndef HEADER_CURL_MEMDEBUG_H_EXTERNS +#define HEADER_CURL_MEMDEBUG_H_EXTERNS extern FILE *curl_dbg_logfile; /* memory functions */ -CURL_EXTERN ALLOC_FUNC ALLOC_SIZE(1) void *curl_dbg_malloc(size_t size, - int line, - const char *source); -CURL_EXTERN ALLOC_FUNC ALLOC_SIZE2(1, 2) void *curl_dbg_calloc(size_t elements, - size_t size, int line, const char *source); -CURL_EXTERN ALLOC_SIZE(2) void *curl_dbg_realloc(void *ptr, - size_t size, - int line, - const char *source); CURL_EXTERN void curl_dbg_free(void *ptr, int line, const char *source); -CURL_EXTERN ALLOC_FUNC char *curl_dbg_strdup(const char *str, int line, - const char *src); -#if defined(WIN32) && defined(UNICODE) -CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, - int line, - const char *source); +CURL_EXTERN ALLOC_FUNC ALLOC_SIZE(1) + void *curl_dbg_malloc(size_t size, int line, const char *source); +CURL_EXTERN ALLOC_FUNC ALLOC_SIZE2(1, 2) + void *curl_dbg_calloc(size_t n, size_t size, int line, const char *source); +CURL_EXTERN ALLOC_SIZE(2) + void *curl_dbg_realloc(void *ptr, size_t size, int line, const char *source); +CURL_EXTERN ALLOC_FUNC + char *curl_dbg_strdup(const char *str, int line, const char *src); +#if defined(_WIN32) && defined(UNICODE) +CURL_EXTERN ALLOC_FUNC + wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source); #endif CURL_EXTERN void curl_dbg_memdebug(const char *logname); CURL_EXTERN void curl_dbg_memlimit(long limit); -CURL_EXTERN void curl_dbg_log(const char *format, ...); +CURL_EXTERN void curl_dbg_log(const char *format, ...) CURL_PRINTF(1, 2); /* file descriptor manipulators */ CURL_EXTERN curl_socket_t curl_dbg_socket(int domain, int type, int protocol, @@ -83,6 +92,11 @@ CURL_EXTERN int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source); CURL_EXTERN curl_socket_t curl_dbg_accept(curl_socket_t s, void *a, void *alen, int line, const char *source); +#ifdef HAVE_ACCEPT4 +CURL_EXTERN curl_socket_t curl_dbg_accept4(curl_socket_t s, void *saddr, + void *saddrlen, int flags, + int line, const char *source); +#endif #ifdef HAVE_SOCKETPAIR CURL_EXTERN int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t socket_vector[2], @@ -102,26 +116,35 @@ CURL_EXTERN RECV_TYPE_RETV curl_dbg_recv(RECV_TYPE_ARG1 sockfd, const char *source); /* FILE functions */ -CURL_EXTERN ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, - int line, const char *source); -CURL_EXTERN ALLOC_FUNC FILE *curl_dbg_fdopen(int filedes, const char *mode, - int line, const char *source); - CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); +CURL_EXTERN ALLOC_FUNC + FILE *curl_dbg_fopen(const char *file, const char *mode, + int line, const char *source); +CURL_EXTERN ALLOC_FUNC + FILE *curl_dbg_fdopen(int filedes, const char *mode, + int line, const char *source); + +#endif /* HEADER_CURL_MEMDEBUG_H_EXTERNS */ #ifndef MEMDEBUG_NODEFINES /* Set this symbol on the command-line, recompile all lib-sources */ #undef strdup #define strdup(ptr) curl_dbg_strdup(ptr, __LINE__, __FILE__) +#undef malloc #define malloc(size) curl_dbg_malloc(size, __LINE__, __FILE__) +#undef calloc #define calloc(nbelem,size) curl_dbg_calloc(nbelem, size, __LINE__, __FILE__) +#undef realloc #define realloc(ptr,size) curl_dbg_realloc(ptr, size, __LINE__, __FILE__) +#undef free #define free(ptr) curl_dbg_free(ptr, __LINE__, __FILE__) +#undef send #define send(a,b,c,d) curl_dbg_send(a,b,c,d, __LINE__, __FILE__) +#undef recv #define recv(a,b,c,d) curl_dbg_recv(a,b,c,d, __LINE__, __FILE__) -#ifdef WIN32 +#ifdef _WIN32 # ifdef UNICODE # undef wcsdup # define wcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) @@ -137,34 +160,20 @@ CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); #undef socket #define socket(domain,type,protocol)\ - curl_dbg_socket(domain, type, protocol, __LINE__, __FILE__) + curl_dbg_socket((int)domain, type, protocol, __LINE__, __FILE__) #undef accept /* for those with accept as a macro */ #define accept(sock,addr,len)\ curl_dbg_accept(sock, addr, len, __LINE__, __FILE__) +#ifdef HAVE_ACCEPT4 +#undef accept4 /* for those with accept4 as a macro */ +#define accept4(sock,addr,len,flags)\ + curl_dbg_accept4(sock, addr, len, flags, __LINE__, __FILE__) +#endif #ifdef HAVE_SOCKETPAIR #define socketpair(domain,type,protocol,socket_vector)\ - curl_dbg_socketpair(domain, type, protocol, socket_vector, __LINE__, __FILE__) -#endif - -#ifdef HAVE_GETADDRINFO -#if defined(getaddrinfo) && defined(__osf__) -/* OSF/1 and Tru64 have getaddrinfo as a define already, so we cannot define - our macro as for other platforms. Instead, we redefine the new name they - define getaddrinfo to become! */ -#define ogetaddrinfo(host,serv,hint,res) \ - curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) -#else -#undef getaddrinfo -#define getaddrinfo(host,serv,hint,res) \ - curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) + curl_dbg_socketpair((int)domain, type, protocol, socket_vector, \ + __LINE__, __FILE__) #endif -#endif /* HAVE_GETADDRINFO */ - -#ifdef HAVE_FREEADDRINFO -#undef freeaddrinfo -#define freeaddrinfo(data) \ - curl_dbg_freeaddrinfo(data, __LINE__, __FILE__) -#endif /* HAVE_FREEADDRINFO */ /* sclose is probably already defined, redefine it! */ #undef sclose diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c index 39aac8f2418..c90c34898d5 100644 --- a/Utilities/cmcurl/lib/mime.c +++ b/Utilities/cmcurl/lib/mime.c @@ -26,10 +26,14 @@ #include +struct Curl_easy; + #include "mime.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "urldata.h" #include "sendf.h" +#include "strdup.h" +#include "curlx/base64.h" #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ !defined(CURL_DISABLE_SMTP) || \ @@ -42,13 +46,13 @@ #include "rand.h" #include "slist.h" #include "strcase.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#ifdef WIN32 +#ifdef _WIN32 # ifndef R_OK # define R_OK 4 # endif @@ -73,6 +77,7 @@ static curl_off_t encoder_base64_size(curl_mimepart *part); static size_t encoder_qp_read(char *buffer, size_t size, bool ateof, curl_mimepart *part); static curl_off_t encoder_qp_size(curl_mimepart *part); +static curl_off_t mime_size(curl_mimepart *part); static const struct mime_encoder encoders[] = { {"binary", encoder_nop_read, encoder_nop_size}, @@ -83,14 +88,10 @@ static const struct mime_encoder encoders[] = { {ZERO_NULL, ZERO_NULL, ZERO_NULL} }; -/* Base64 encoding table */ -static const char base64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - /* Quoted-printable character class table. * * We cannot rely on ctype functions since quoted-printable input data - * is assumed to be ascii-compatible, even on non-ascii platforms. */ + * is assumed to be ASCII-compatible, even on non-ASCII platforms. */ #define QP_OK 1 /* Can be represented by itself. */ #define QP_SP 2 /* Space or tab. */ #define QP_CR 3 /* Carriage return. */ @@ -257,7 +258,7 @@ static char *Curl_basename(char *path) s2 = strrchr(path, '\\'); if(s1 && s2) { - path = (s1 > s2? s1 : s2) + 1; + path = (s1 > s2 ? s1 : s2) + 1; } else if(s1) path = s1 + 1; @@ -311,23 +312,22 @@ static char *escape_string(struct Curl_easy *data, table = formtable; /* data can be NULL when this function is called indirectly from curl_formget(). */ - if(strategy == MIMESTRATEGY_MAIL || - (data && (data->set.mime_options & CURLMIMEOPT_FORMESCAPE))) + if(strategy == MIMESTRATEGY_MAIL || (data && (data->set.mime_formescape))) table = mimetable; - Curl_dyn_init(&db, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); - for(result = Curl_dyn_addn(&db, STRCONST("")); !result && *src; src++) { + for(result = curlx_dyn_addn(&db, STRCONST("")); !result && *src; src++) { for(p = table; *p && **p != *src; p++) ; if(*p) - result = Curl_dyn_add(&db, *p + 1); + result = curlx_dyn_add(&db, *p + 1); else - result = Curl_dyn_addn(&db, src, 1); + result = curlx_dyn_addn(&db, src, 1); } - return Curl_dyn_ptr(&db); + return curlx_dyn_ptr(&db); } /* Check if header matches. */ @@ -423,7 +423,7 @@ static size_t encoder_7bit_read(char *buffer, size_t size, bool ateof, for(cursize = 0; cursize < size; cursize++) { *buffer = st->buf[st->bufbeg]; if(*buffer++ & 0x80) - return cursize? cursize: READ_ERROR; + return cursize ? cursize : READ_ERROR; st->bufbeg++; } @@ -469,10 +469,10 @@ static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, i = st->buf[st->bufbeg++] & 0xFF; i = (i << 8) | (st->buf[st->bufbeg++] & 0xFF); i = (i << 8) | (st->buf[st->bufbeg++] & 0xFF); - *ptr++ = base64[(i >> 18) & 0x3F]; - *ptr++ = base64[(i >> 12) & 0x3F]; - *ptr++ = base64[(i >> 6) & 0x3F]; - *ptr++ = base64[i & 0x3F]; + *ptr++ = Curl_base64encdec[(i >> 18) & 0x3F]; + *ptr++ = Curl_base64encdec[(i >> 12) & 0x3F]; + *ptr++ = Curl_base64encdec[(i >> 6) & 0x3F]; + *ptr++ = Curl_base64encdec[i & 0x3F]; cursize += 4; st->pos += 4; size -= 4; @@ -496,10 +496,10 @@ static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, i = (st->buf[st->bufbeg + 1] & 0xFF) << 8; i |= (st->buf[st->bufbeg] & 0xFF) << 16; - ptr[0] = base64[(i >> 18) & 0x3F]; - ptr[1] = base64[(i >> 12) & 0x3F]; + ptr[0] = Curl_base64encdec[(i >> 18) & 0x3F]; + ptr[1] = Curl_base64encdec[(i >> 12) & 0x3F]; if(++st->bufbeg != st->bufend) { - ptr[2] = base64[(i >> 6) & 0x3F]; + ptr[2] = Curl_base64encdec[(i >> 6) & 0x3F]; st->bufbeg++; } cursize += 4; @@ -537,7 +537,7 @@ static int qp_lookahead_eol(struct mime_encoder_state *st, int ateof, size_t n) if(n >= st->bufend && ateof) return 1; if(n + 2 > st->bufend) - return ateof? 0: -1; + return ateof ? 0 : -1; if(qp_class[st->buf[n] & 0xFF] == QP_CR && qp_class[st->buf[n + 1] & 0xFF] == QP_LF) return 1; @@ -556,7 +556,7 @@ static size_t encoder_qp_read(char *buffer, size_t size, bool ateof, /* On all platforms, input is supposed to be ASCII compatible: for this reason, we use hexadecimal ASCII codes in this function rather than - character constants that can be interpreted as non-ascii on some + character constants that can be interpreted as non-ASCII on some platforms. Preserve ASCII encoding on output too. */ while(st->bufbeg < st->bufend) { size_t len = 1; @@ -650,7 +650,7 @@ static curl_off_t encoder_qp_size(curl_mimepart *part) { /* Determining the size can only be done by reading the data: unless the data size is 0, we return it as unknown (-1). */ - return part->datasize? -1: 0; + return part->datasize ? -1 : 0; } @@ -710,7 +710,7 @@ static int mime_open_file(curl_mimepart *part) if(part->fp) return 0; part->fp = fopen_read(part->data, "rb"); - return part->fp? 0: -1; + return part->fp ? 0 : -1; } static size_t mime_file_read(char *buffer, size_t size, size_t nitems, @@ -737,8 +737,8 @@ static int mime_file_seek(void *instream, curl_off_t offset, int whence) if(mime_open_file(part)) return CURL_SEEKFUNC_FAIL; - return fseek(part->fp, (long) offset, whence)? - CURL_SEEKFUNC_CANTSEEK: CURL_SEEKFUNC_OK; + return fseek(part->fp, (long) offset, whence) ? + CURL_SEEKFUNC_CANTSEEK : CURL_SEEKFUNC_OK; } static void mime_file_free(void *ptr) @@ -818,7 +818,7 @@ static size_t read_part_content(curl_mimepart *part, case MIMEKIND_FILE: if(part->fp && feof(part->fp)) break; /* At EOF. */ - /* FALLTHROUGH */ + FALLTHROUGH(); default: if(part->readfunc) { if(!(part->flags & MIME_FAST_READ)) { @@ -870,7 +870,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, break; case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; default: cursize += sz; buffer += sz; @@ -889,7 +889,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, st->bufend = len; } if(st->bufend >= sizeof(st->buf)) - return cursize? cursize: READ_ERROR; /* Buffer full. */ + return cursize ? cursize : READ_ERROR; /* Buffer full. */ sz = read_part_content(part, st->buf + st->bufend, sizeof(st->buf) - st->bufend, hasread); switch(sz) { @@ -900,7 +900,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, case CURL_READFUNC_PAUSE: case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; default: st->bufend += sz; break; @@ -924,8 +924,8 @@ static size_t readback_part(curl_mimepart *part, switch(part->state.state) { case MIMESTATE_BEGIN: mimesetstate(&part->state, - (part->flags & MIME_BODY_ONLY)? - MIMESTATE_BODY: MIMESTATE_CURLHEADERS, + (part->flags & MIME_BODY_ONLY) ? + MIMESTATE_BODY : MIMESTATE_CURLHEADERS, part->curlheaders); break; case MIMESTATE_USERHEADERS: @@ -937,7 +937,7 @@ static size_t readback_part(curl_mimepart *part, mimesetstate(&part->state, MIMESTATE_USERHEADERS, hdr->next); break; } - /* FALLTHROUGH */ + FALLTHROUGH(); case MIMESTATE_CURLHEADERS: if(!hdr) mimesetstate(&part->state, MIMESTATE_USERHEADERS, part->userheaders); @@ -971,12 +971,12 @@ static size_t readback_part(curl_mimepart *part, fclose(part->fp); part->fp = NULL; } - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_READFUNC_ABORT: case CURL_READFUNC_PAUSE: case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; } break; case MIMESTATE_END: @@ -1042,7 +1042,7 @@ static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, case CURL_READFUNC_PAUSE: case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; case 0: mimesetstate(&mime->state, MIMESTATE_BOUNDARY1, part->nextpart); break; @@ -1136,7 +1136,7 @@ static void cleanup_part_content(curl_mimepart *part) part->datasize = (curl_off_t) 0; /* No size yet. */ cleanup_encoder_state(&part->encstate); part->kind = MIMEKIND_NONE; - part->flags &= ~MIME_FAST_READ; + part->flags &= ~(unsigned int)MIME_FAST_READ; part->lastreadstatus = 1; /* Successful read status. */ part->state.state = MIMESTATE_BEGIN; } @@ -1146,7 +1146,7 @@ static void mime_subparts_free(void *ptr) curl_mime *mime = (curl_mime *) ptr; if(mime && mime->parent) { - mime->parent->freefunc = NULL; /* Be sure we won't be called again. */ + mime->parent->freefunc = NULL; /* Be sure we will not be called again. */ cleanup_part_content(mime->parent); /* Avoid dangling pointer in part. */ } curl_mime_free(mime); @@ -1158,7 +1158,7 @@ static void mime_subparts_unbind(void *ptr) curl_mime *mime = (curl_mime *) ptr; if(mime && mime->parent) { - mime->parent->freefunc = NULL; /* Be sure we won't be called again. */ + mime->parent->freefunc = NULL; /* Be sure we will not be called again. */ cleanup_part_content(mime->parent); /* Avoid dangling pointer in part. */ mime->parent = NULL; } @@ -1167,14 +1167,16 @@ static void mime_subparts_unbind(void *ptr) void Curl_mime_cleanpart(curl_mimepart *part) { - cleanup_part_content(part); - curl_slist_free_all(part->curlheaders); - if(part->flags & MIME_USERHEADERS_OWNER) - curl_slist_free_all(part->userheaders); - Curl_safefree(part->mimetype); - Curl_safefree(part->name); - Curl_safefree(part->filename); - Curl_mime_initpart(part); + if(part) { + cleanup_part_content(part); + curl_slist_free_all(part->curlheaders); + if(part->flags & MIME_USERHEADERS_OWNER) + curl_slist_free_all(part->userheaders); + Curl_safefree(part->mimetype); + Curl_safefree(part->name); + Curl_safefree(part->filename); + Curl_mime_initpart(part); + } } /* Recursively delete a mime handle and its parts. */ @@ -1183,7 +1185,7 @@ void curl_mime_free(curl_mime *mime) curl_mimepart *part; if(mime) { - mime_subparts_unbind(mime); /* Be sure it's not referenced anymore. */ + mime_subparts_unbind(mime); /* Be sure it is not referenced anymore. */ while(mime->firstpart) { part = mime->firstpart; mime->firstpart = part->nextpart; @@ -1225,15 +1227,16 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, /* No one knows about the cloned subparts, thus always attach ownership to the part. */ mime = curl_mime_init(data); - res = mime? curl_mime_subparts(dst, mime): CURLE_OUT_OF_MEMORY; + res = mime ? curl_mime_subparts(dst, mime) : CURLE_OUT_OF_MEMORY; /* Duplicate subparts. */ for(s = ((curl_mime *) src->arg)->firstpart; !res && s; s = s->nextpart) { d = curl_mime_addpart(mime); - res = d? Curl_mime_duppart(data, d, s): CURLE_OUT_OF_MEMORY; + res = d ? Curl_mime_duppart(data, d, s) : CURLE_OUT_OF_MEMORY; } break; default: /* Invalid kind: should not occur. */ + DEBUGF(infof(data, "invalid MIMEKIND* attempt")); res = CURLE_BAD_FUNCTION_ARGUMENT; /* Internal error? */ break; } @@ -1275,7 +1278,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, */ /* Create a mime handle. */ -curl_mime *curl_mime_init(struct Curl_easy *easy) +curl_mime *curl_mime_init(void *easy) { curl_mime *mime; @@ -1287,9 +1290,9 @@ curl_mime *curl_mime_init(struct Curl_easy *easy) mime->lastpart = NULL; memset(mime->boundary, '-', MIME_BOUNDARY_DASHES); - if(Curl_rand_hex(easy, - (unsigned char *) &mime->boundary[MIME_BOUNDARY_DASHES], - MIME_RAND_BOUNDARY_CHARS + 1)) { + if(Curl_rand_alnum(easy, + (unsigned char *) &mime->boundary[MIME_BOUNDARY_DASHES], + MIME_RAND_BOUNDARY_CHARS + 1)) { /* failed to get random separator, bail out */ free(mime); return NULL; @@ -1350,7 +1353,7 @@ CURLcode curl_mime_name(curl_mimepart *part, const char *name) return CURLE_OK; } -/* Set mime part remote file name. */ +/* Set mime part remote filename. */ CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) { if(!part) @@ -1369,27 +1372,22 @@ CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) /* Set mime part content from memory data. */ CURLcode curl_mime_data(curl_mimepart *part, - const char *data, size_t datasize) + const char *ptr, size_t datasize) { if(!part) return CURLE_BAD_FUNCTION_ARGUMENT; cleanup_part_content(part); - if(data) { + if(ptr) { if(datasize == CURL_ZERO_TERMINATED) - datasize = strlen(data); + datasize = strlen(ptr); - part->data = malloc(datasize + 1); + part->data = Curl_memdup0(ptr, datasize); if(!part->data) return CURLE_OUT_OF_MEMORY; part->datasize = datasize; - - if(datasize) - memcpy(part->data, data, datasize); - part->data[datasize] = '\0'; /* Set a null terminator as sentinel. */ - part->readfunc = mime_mem_read; part->seekfunc = mime_mem_seek; part->freefunc = mime_mem_free; @@ -1414,36 +1412,35 @@ CURLcode curl_mime_filedata(curl_mimepart *part, const char *filename) char *base; struct_stat sbuf; - if(stat(filename, &sbuf) || access(filename, R_OK)) + if(stat(filename, &sbuf)) result = CURLE_READ_ERROR; - - part->data = strdup(filename); - if(!part->data) - result = CURLE_OUT_OF_MEMORY; - - part->datasize = -1; - if(!result && S_ISREG(sbuf.st_mode)) { - part->datasize = filesize(filename, sbuf); - part->seekfunc = mime_file_seek; - } - - part->readfunc = mime_file_read; - part->freefunc = mime_file_free; - part->kind = MIMEKIND_FILE; - - /* As a side effect, set the filename to the current file's base name. - It is possible to withdraw this by explicitly calling - curl_mime_filename() with a NULL filename argument after the current - call. */ - base = strippath(filename); - if(!base) - result = CURLE_OUT_OF_MEMORY; else { - CURLcode res = curl_mime_filename(part, base); + part->data = strdup(filename); + if(!part->data) + result = CURLE_OUT_OF_MEMORY; + else { + part->datasize = -1; + if(S_ISREG(sbuf.st_mode)) { + part->datasize = filesize(filename, sbuf); + part->seekfunc = mime_file_seek; + } - if(res) - result = res; - free(base); + part->readfunc = mime_file_read; + part->freefunc = mime_file_free; + part->kind = MIMEKIND_FILE; + + /* As a side effect, set the filename to the current file's base name. + It is possible to withdraw this by explicitly calling + curl_mime_filename() with a NULL filename argument after the current + call. */ + base = strippath(filename); + if(!base) + result = CURLE_OUT_OF_MEMORY; + else { + result = curl_mime_filename(part, base); + free(base); + } + } } } return result; @@ -1499,7 +1496,7 @@ CURLcode curl_mime_headers(curl_mimepart *part, if(part->flags & MIME_USERHEADERS_OWNER) { if(part->userheaders != headers) /* Allow setting twice the same list. */ curl_slist_free_all(part->userheaders); - part->flags &= ~MIME_USERHEADERS_OWNER; + part->flags &= ~(unsigned int)MIME_USERHEADERS_OWNER; } part->userheaders = headers; if(headers && take_ownership) @@ -1556,15 +1553,24 @@ CURLcode Curl_mime_set_subparts(curl_mimepart *part, while(root->parent && root->parent->parent) root = root->parent->parent; if(subparts == root) { - /* Can't add as a subpart of itself. */ + /* cannot add as a subpart of itself. */ return CURLE_BAD_FUNCTION_ARGUMENT; } } + /* If subparts have already been used as a top-level MIMEPOST, + they might not be positioned at start. Rewind them now, as + a future check while rewinding the parent may cause this + content to be skipped. */ + if(mime_subparts_seek(subparts, (curl_off_t) 0, SEEK_SET) != + CURL_SEEKFUNC_OK) + return CURLE_SEND_FAIL_REWIND; + subparts->parent = part; /* Subparts are processed internally: no read callback. */ part->seekfunc = mime_subparts_seek; - part->freefunc = take_ownership? mime_subparts_free: mime_subparts_unbind; + part->freefunc = take_ownership ? mime_subparts_free : + mime_subparts_unbind; part->arg = subparts; part->datasize = -1; part->kind = MIMEKIND_MULTIPART; @@ -1589,6 +1595,8 @@ size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) (void) size; /* Always 1. */ + /* If `nitems` is <= 4, some encoders will return STOP_FILLING without + * adding any data and this loops infinitely. */ do { hasread = FALSE; ret = readback_part(part, buffer, nitems, &hasread); @@ -1604,10 +1612,10 @@ size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) } /* Rewind mime stream. */ -CURLcode Curl_mime_rewind(curl_mimepart *part) +static CURLcode mime_rewind(curl_mimepart *part) { - return mime_part_rewind(part) == CURL_SEEKFUNC_OK? - CURLE_OK: CURLE_SEND_FAIL_REWIND; + return mime_part_rewind(part) == CURL_SEEKFUNC_OK ? + CURLE_OK : CURLE_SEND_FAIL_REWIND; } /* Compute header list size. */ @@ -1636,7 +1644,7 @@ static curl_off_t multipart_size(curl_mime *mime) size = boundarysize; /* Final boundary - CRLF after headers. */ for(part = mime->firstpart; part; part = part->nextpart) { - curl_off_t sz = Curl_mime_size(part); + curl_off_t sz = mime_size(part); if(sz < 0) size = sz; @@ -1649,7 +1657,7 @@ static curl_off_t multipart_size(curl_mime *mime) } /* Get/compute mime size. */ -curl_off_t Curl_mime_size(curl_mimepart *part) +static curl_off_t mime_size(curl_mimepart *part) { curl_off_t size; @@ -1664,7 +1672,8 @@ curl_off_t Curl_mime_size(curl_mimepart *part) if(size >= 0 && !(part->flags & MIME_BODY_ONLY)) { /* Compute total part size. */ size += slist_size(part->curlheaders, 2, NULL, 0); - size += slist_size(part->userheaders, 2, STRCONST("Content-Type")); + size += slist_size(part->userheaders, 2, + STRCONST("Content-Type")); size += 2; /* CRLF after headers. */ } return size; @@ -1679,7 +1688,7 @@ CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) va_list ap; va_start(ap, fmt); - s = curl_mvaprintf(fmt, ap); + s = vaprintf(fmt, ap); va_end(ap); if(s) { @@ -1690,7 +1699,7 @@ CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) free(s); } - return hdr? CURLE_OK: CURLE_OUT_OF_MEMORY; + return hdr ? CURLE_OK : CURLE_OUT_OF_MEMORY; } /* Add a content type header. */ @@ -1698,8 +1707,8 @@ static CURLcode add_content_type(struct curl_slist **slp, const char *type, const char *boundary) { return Curl_mime_add_header(slp, "Content-Type: %s%s%s", type, - boundary? "; boundary=": "", - boundary? boundary: ""); + boundary ? "; boundary=" : "", + boundary ? boundary : ""); } const char *Curl_mime_contenttype(const char *filename) @@ -1730,7 +1739,7 @@ const char *Curl_mime_contenttype(const char *filename) const char *nameend = filename + len1; unsigned int i; - for(i = 0; i < sizeof(ctts) / sizeof(ctts[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctts); i++) { size_t len2 = strlen(ctts[i].extension); if(len1 >= len2 && strcasecompare(nameend - len2, ctts[i].extension)) @@ -1772,7 +1781,7 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, curl_slist_free_all(part->curlheaders); part->curlheaders = NULL; - /* Be sure we won't access old headers later. */ + /* Be sure we will not access old headers later. */ if(part->state.state == MIMESTATE_CURLHEADERS) mimesetstate(&part->state, MIMESTATE_CURLHEADERS, NULL); @@ -1839,12 +1848,12 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, ret = Curl_mime_add_header(&part->curlheaders, "Content-Disposition: %s%s%s%s%s%s%s", disposition, - name? "; name=\"": "", - name? name: "", - name? "\"": "", - filename? "; filename=\"": "", - filename? filename: "", - filename? "\"": ""); + name ? "; name=\"" : "", + name ? name : "", + name ? "\"" : "", + filename ? "; filename=\"" : "", + filename ? filename : "", + filename ? "\"" : ""); Curl_safefree(name); Curl_safefree(filename); if(ret) @@ -1898,7 +1907,7 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, } /* Recursively reset paused status in the given part. */ -void Curl_mime_unpause(curl_mimepart *part) +static void mime_unpause(curl_mimepart *part) { if(part) { if(part->lastreadstatus == CURL_READFUNC_PAUSE) @@ -1910,12 +1919,302 @@ void Curl_mime_unpause(curl_mimepart *part) curl_mimepart *subpart; for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) - Curl_mime_unpause(subpart); + mime_unpause(subpart); } } } } +struct cr_mime_ctx { + struct Curl_creader super; + curl_mimepart *part; + curl_off_t total_len; + curl_off_t read_len; + CURLcode error_result; + struct bufq tmpbuf; + BIT(seen_eos); + BIT(errored); +}; + +static CURLcode cr_mime_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + ctx->total_len = -1; + ctx->read_len = 0; + Curl_bufq_init2(&ctx->tmpbuf, 1024, 1, BUFQ_OPT_NO_SPARES); + return CURLE_OK; +} + +static void cr_mime_close(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->tmpbuf); +} + +/* Real client reader to installed client callbacks. */ +static CURLcode cr_mime_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_mime_ctx *ctx = reader->ctx; + size_t nread; + char tmp[256]; + + + /* Once we have errored, we will return the same error forever */ + if(ctx->errored) { + CURL_TRC_READ(data, "cr_mime_read(len=%zu) is errored -> %d, eos=0", + blen, ctx->error_result); + *pnread = 0; + *peos = FALSE; + return ctx->error_result; + } + if(ctx->seen_eos) { + CURL_TRC_READ(data, "cr_mime_read(len=%zu) seen eos -> 0, eos=1", blen); + *pnread = 0; + *peos = TRUE; + return CURLE_OK; + } + /* respect length limitations */ + if(ctx->total_len >= 0) { + curl_off_t remain = ctx->total_len - ctx->read_len; + if(remain <= 0) + blen = 0; + else if(remain < (curl_off_t)blen) + blen = (size_t)remain; + } + + if(!Curl_bufq_is_empty(&ctx->tmpbuf)) { + CURLcode result = CURLE_OK; + ssize_t n = Curl_bufq_read(&ctx->tmpbuf, (unsigned char *)buf, blen, + &result); + if(n < 0) { + ctx->errored = TRUE; + ctx->error_result = result; + return result; + } + nread = (size_t)n; + } + else if(blen <= 4) { + /* Curl_mime_read() may go into an infinite loop when reading + * via a base64 encoder, as it stalls when the read buffer is too small + * to contain a complete 3 byte encoding. Read into a larger buffer + * and use that until empty. */ + CURL_TRC_READ(data, "cr_mime_read(len=%zu), small read, using tmp", blen); + nread = Curl_mime_read(tmp, 1, sizeof(tmp), ctx->part); + if(nread <= sizeof(tmp)) { + CURLcode result = CURLE_OK; + ssize_t n = Curl_bufq_write(&ctx->tmpbuf, (unsigned char *)tmp, nread, + &result); + if(n < 0) { + ctx->errored = TRUE; + ctx->error_result = result; + return result; + } + /* stored it, read again */ + n = Curl_bufq_read(&ctx->tmpbuf, (unsigned char *)buf, blen, &result); + if(n < 0) { + ctx->errored = TRUE; + ctx->error_result = result; + return result; + } + nread = (size_t)n; + } + } + else + nread = Curl_mime_read(buf, 1, blen, ctx->part); + + CURL_TRC_READ(data, "cr_mime_read(len=%zu), mime_read() -> %zd", + blen, nread); + + switch(nread) { + case 0: + if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { + failf(data, "client mime read EOF fail, " + "only %"FMT_OFF_T"/%"FMT_OFF_T + " of needed bytes read", ctx->read_len, ctx->total_len); + return CURLE_READ_ERROR; + } + *pnread = 0; + *peos = TRUE; + ctx->seen_eos = TRUE; + break; + + case CURL_READFUNC_ABORT: + failf(data, "operation aborted by callback"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_ABORTED_BY_CALLBACK; + return CURLE_ABORTED_BY_CALLBACK; + + case CURL_READFUNC_PAUSE: + /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ + CURL_TRC_READ(data, "cr_mime_read(len=%zu), paused by callback", blen); + data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + *pnread = 0; + *peos = FALSE; + break; /* nothing was read */ + + case STOP_FILLING: + case READ_ERROR: + failf(data, "read error getting mime data"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + + default: + if(nread > blen) { + /* the read function returned a too large value */ + failf(data, "read function returned funny value"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + } + ctx->read_len += nread; + if(ctx->total_len >= 0) + ctx->seen_eos = (ctx->read_len >= ctx->total_len); + *pnread = nread; + *peos = ctx->seen_eos; + break; + } + + CURL_TRC_READ(data, "cr_mime_read(len=%zu, total=%" FMT_OFF_T + ", read=%"FMT_OFF_T") -> %d, %zu, %d", + blen, ctx->total_len, ctx->read_len, CURLE_OK, *pnread, *peos); + return CURLE_OK; +} + +static bool cr_mime_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return ctx->read_len > 0; +} + +static curl_off_t cr_mime_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return ctx->total_len; +} + +static CURLcode cr_mime_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_mime_ctx *ctx = reader->ctx; + + if(offset > 0) { + curl_off_t passed = 0; + + do { + char scratch[4*1024]; + size_t readthisamountnow = + (offset - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : + curlx_sotouz(offset - passed); + size_t nread; + + nread = Curl_mime_read(scratch, 1, readthisamountnow, ctx->part); + passed += (curl_off_t)nread; + if((nread == 0) || (nread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" FMT_OFF_T + " bytes from the mime post", passed); + return CURLE_READ_ERROR; + } + } while(passed < offset); + + /* now, decrease the size of the read */ + if(ctx->total_len > 0) { + ctx->total_len -= offset; + + if(ctx->total_len <= 0) { + failf(data, "Mime post already completely uploaded"); + return CURLE_PARTIAL_FILE; + } + } + /* we have passed, proceed as normal */ + } + return CURLE_OK; +} + +static CURLcode cr_mime_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + CURLcode result = mime_rewind(ctx->part); + if(result) + failf(data, "Cannot rewind mime/post data"); + return result; +} + +static CURLcode cr_mime_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + mime_unpause(ctx->part); + return CURLE_OK; +} + +static bool cr_mime_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return ctx->part && ctx->part->lastreadstatus == CURL_READFUNC_PAUSE; +} + +static const struct Curl_crtype cr_mime = { + "cr-mime", + cr_mime_init, + cr_mime_read, + cr_mime_close, + cr_mime_needs_rewind, + cr_mime_total_length, + cr_mime_resume_from, + cr_mime_rewind, + cr_mime_unpause, + cr_mime_is_paused, + Curl_creader_def_done, + sizeof(struct cr_mime_ctx) +}; + +CURLcode Curl_creader_set_mime(struct Curl_easy *data, curl_mimepart *part) +{ + struct Curl_creader *r; + struct cr_mime_ctx *ctx; + CURLcode result; + + result = Curl_creader_create(&r, data, &cr_mime, CURL_CR_CLIENT); + if(result) + return result; + ctx = r->ctx; + ctx->part = part; + /* Make sure we will read the entire mime structure. */ + result = mime_rewind(ctx->part); + if(result) { + Curl_creader_free(data, r); + return result; + } + ctx->total_len = mime_size(ctx->part); + + return Curl_creader_set(data, r); +} #else /* !CURL_DISABLE_MIME && (!CURL_DISABLE_HTTP || !CURL_DISABLE_SMTP || !CURL_DISABLE_IMAP) */ diff --git a/Utilities/cmcurl/lib/mime.h b/Utilities/cmcurl/lib/mime.h index 04adf2d2476..5073a38f709 100644 --- a/Utilities/cmcurl/lib/mime.h +++ b/Utilities/cmcurl/lib/mime.h @@ -27,7 +27,7 @@ #include "curl_setup.h" #define MIME_BOUNDARY_DASHES 24 /* leading boundary dashes */ -#define MIME_RAND_BOUNDARY_CHARS 16 /* Nb. of random boundary chars. */ +#define MIME_RAND_BOUNDARY_CHARS 22 /* Nb. of random boundary chars. */ #define MAX_ENCODED_LINE_LENGTH 76 /* Maximum encoded line length. */ #define ENCODING_BUFFER_SIZE 256 /* Encoding temp buffers size. */ @@ -112,7 +112,7 @@ struct curl_mimepart { curl_mimepart *nextpart; /* Forward linked list. */ enum mimekind kind; /* The part kind. */ unsigned int flags; /* Flags. */ - char *data; /* Memory data or file name. */ + char *data; /* Memory data or filename. */ curl_read_callback readfunc; /* Read function. */ curl_seek_callback seekfunc; /* Seek function. */ curl_free_callback freefunc; /* Argument free function. */ @@ -121,7 +121,7 @@ struct curl_mimepart { struct curl_slist *curlheaders; /* Part headers. */ struct curl_slist *userheaders; /* Part headers. */ char *mimetype; /* Part mime type. */ - char *filename; /* Remote file name. */ + char *filename; /* Remote filename. */ char *name; /* Data name. */ curl_off_t datasize; /* Expected data size. */ struct mime_state state; /* Current readback state. */ @@ -130,7 +130,8 @@ struct curl_mimepart { size_t lastreadstatus; /* Last read callback returned status. */ }; -CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...); +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) + CURL_PRINTF(2, 3); #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ !defined(CURL_DISABLE_SMTP) || \ @@ -150,12 +151,15 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, const char *contenttype, const char *disposition, enum mimestrategy strategy); -curl_off_t Curl_mime_size(struct curl_mimepart *part); size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream); -CURLcode Curl_mime_rewind(struct curl_mimepart *part); const char *Curl_mime_contenttype(const char *filename); -void Curl_mime_unpause(struct curl_mimepart *part); + +/** + * Install a client reader as upload source that reads the given + * mime part. + */ +CURLcode Curl_creader_set_mime(struct Curl_easy *data, curl_mimepart *part); #else /* if disabled */ @@ -164,10 +168,8 @@ void Curl_mime_unpause(struct curl_mimepart *part); #define Curl_mime_duppart(x,y,z) CURLE_OK /* Nothing to duplicate. Succeed */ #define Curl_mime_set_subparts(a,b,c) CURLE_NOT_BUILT_IN #define Curl_mime_prepare_headers(a,b,c,d,e) CURLE_NOT_BUILT_IN -#define Curl_mime_size(x) (curl_off_t) -1 #define Curl_mime_read NULL -#define Curl_mime_rewind(x) ((void)x, CURLE_NOT_BUILT_IN) -#define Curl_mime_unpause(x) +#define Curl_creader_set_mime(x,y) ((void)x, CURLE_NOT_BUILT_IN) #endif diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c index af5d753d6af..176f8a3e4b7 100644 --- a/Utilities/cmcurl/lib/mprintf.c +++ b/Utilities/cmcurl/lib/mprintf.c @@ -20,58 +20,26 @@ * * SPDX-License-Identifier: curl * - * - * Purpose: - * A merge of Bjorn Reese's format() function and Daniel's dsprintf() - * 1.0. A full blooded printf() clone with full support for $ - * everywhere (parameters, widths and precisions) including variabled - * sized parameters (like doubles, long longs, long doubles and even - * void * in 64-bit architectures). - * - * Current restrictions: - * - Max 128 parameters - * - No 'long double' support. - * - * If you ever want truly portable and good *printf() clones, the project that - * took on from here is named 'Trio' and you find more details on the trio web - * page at https://daniel.haxx.se/projects/trio/ */ #include "curl_setup.h" -#include "dynbuf.h" -#include +#include "curlx/dynbuf.h" +#include "curl_printf.h" +#include "curlx/strparse.h" #include "curl_memory.h" /* The last #include file should be: */ #include "memdebug.h" -/* - * If SIZEOF_SIZE_T has not been defined, default to the size of long. - */ - #ifdef HAVE_LONGLONG # define LONG_LONG_TYPE long long # define HAVE_LONG_LONG_TYPE +#elif defined(_MSC_VER) +# define LONG_LONG_TYPE __int64 +# define HAVE_LONG_LONG_TYPE #else -# if defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) -# define LONG_LONG_TYPE __int64 -# define HAVE_LONG_LONG_TYPE -# else -# undef LONG_LONG_TYPE -# undef HAVE_LONG_LONG_TYPE -# endif -#endif - -/* - * Non-ANSI integer extensions - */ - -#if (defined(__BORLANDC__) && (__BORLANDC__ >= 0x520)) || \ - (defined(__POCC__) && defined(_MSC_VER)) || \ - (defined(_WIN32_WCE)) || \ - (defined(__MINGW32__)) || \ - (defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64)) -# define MP_HAVE_INT_EXTENSIONS +# undef LONG_LONG_TYPE +# undef HAVE_LONG_LONG_TYPE #endif /* @@ -88,79 +56,98 @@ #define BUFFSIZE 326 /* buffer for long-to-str and float-to-str calcs, should fit negative DBL_MAX (317 letters) */ -#define MAX_PARAMETERS 128 /* lame static limit */ +#define MAX_PARAMETERS 128 /* number of input arguments */ +#define MAX_SEGMENTS 128 /* number of output segments */ #ifdef __AMIGA__ # undef FORMAT_INT #endif /* Lower-case digits. */ -static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; +const unsigned char Curl_ldigits[] = "0123456789abcdef"; /* Upper-case digits. */ -static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - -#define OUTCHAR(x) \ - do { \ - if(stream((unsigned char)(x), (FILE *)data) != -1) \ - done++; \ - else \ - return done; /* return immediately on failure */ \ +const unsigned char Curl_udigits[] = "0123456789ABCDEF"; + +#define OUTCHAR(x) \ + do { \ + if(stream((unsigned char)x, userp)) \ + return TRUE; \ + (*donep)++; \ } while(0) /* Data type to read from the arglist */ typedef enum { - FORMAT_UNKNOWN = 0, FORMAT_STRING, FORMAT_PTR, - FORMAT_INT, FORMAT_INTPTR, + FORMAT_INT, FORMAT_LONG, FORMAT_LONGLONG, + FORMAT_INTU, + FORMAT_LONGU, + FORMAT_LONGLONGU, FORMAT_DOUBLE, FORMAT_LONGDOUBLE, - FORMAT_WIDTH /* For internal use */ + FORMAT_WIDTH, + FORMAT_PRECISION } FormatType; /* conversion and display flags */ enum { - FLAGS_NEW = 0, - FLAGS_SPACE = 1<<0, - FLAGS_SHOWSIGN = 1<<1, - FLAGS_LEFT = 1<<2, - FLAGS_ALT = 1<<3, - FLAGS_SHORT = 1<<4, - FLAGS_LONG = 1<<5, - FLAGS_LONGLONG = 1<<6, - FLAGS_LONGDOUBLE = 1<<7, - FLAGS_PAD_NIL = 1<<8, - FLAGS_UNSIGNED = 1<<9, - FLAGS_OCTAL = 1<<10, - FLAGS_HEX = 1<<11, - FLAGS_UPPER = 1<<12, - FLAGS_WIDTH = 1<<13, /* '*' or '*$' used */ - FLAGS_WIDTHPARAM = 1<<14, /* width PARAMETER was specified */ - FLAGS_PREC = 1<<15, /* precision was specified */ - FLAGS_PRECPARAM = 1<<16, /* precision PARAMETER was specified */ - FLAGS_CHAR = 1<<17, /* %c story */ - FLAGS_FLOATE = 1<<18, /* %e or %E */ - FLAGS_FLOATG = 1<<19 /* %g or %G */ + FLAGS_SPACE = 1 << 0, + FLAGS_SHOWSIGN = 1 << 1, + FLAGS_LEFT = 1 << 2, + FLAGS_ALT = 1 << 3, + FLAGS_SHORT = 1 << 4, + FLAGS_LONG = 1 << 5, + FLAGS_LONGLONG = 1 << 6, + FLAGS_LONGDOUBLE = 1 << 7, + FLAGS_PAD_NIL = 1 << 8, + FLAGS_UNSIGNED = 1 << 9, + FLAGS_OCTAL = 1 << 10, + FLAGS_HEX = 1 << 11, + FLAGS_UPPER = 1 << 12, + FLAGS_WIDTH = 1 << 13, /* '*' or '*$' used */ + FLAGS_WIDTHPARAM = 1 << 14, /* width PARAMETER was specified */ + FLAGS_PREC = 1 << 15, /* precision was specified */ + FLAGS_PRECPARAM = 1 << 16, /* precision PARAMETER was specified */ + FLAGS_CHAR = 1 << 17, /* %c story */ + FLAGS_FLOATE = 1 << 18, /* %e or %E */ + FLAGS_FLOATG = 1 << 19, /* %g or %G */ + FLAGS_SUBSTR = 1 << 20 /* no input, only substring */ +}; + +enum { + DOLLAR_UNKNOWN, + DOLLAR_NOPE, + DOLLAR_USE }; -struct va_stack { - FormatType type; - int flags; - long width; /* width OR width parameter number */ - long precision; /* precision OR precision parameter number */ +/* + * Describes an input va_arg type and hold its value. + */ +struct va_input { + FormatType type; /* FormatType */ union { - char *str; + const char *str; void *ptr; - union { - mp_intmax_t as_signed; - mp_uintmax_t as_unsigned; - } num; + mp_intmax_t nums; /* signed */ + mp_uintmax_t numu; /* unsigned */ double dnum; - } data; + } val; +}; + +/* + * Describes an output segment. + */ +struct outsegment { + int width; /* width OR width parameter number */ + int precision; /* precision OR precision parameter number */ + unsigned int flags; + unsigned int input; /* input argument array index */ + const char *start; /* format string start to output */ + size_t outlen; /* number of bytes from the format string to output */ }; struct nsprintf { @@ -171,118 +158,112 @@ struct nsprintf { struct asprintf { struct dynbuf *b; - bool fail; /* if an alloc has failed and thus the output is not the complete - data */ + char merr; }; -static long dprintf_DollarString(char *input, char **end) -{ - int number = 0; - while(ISDIGIT(*input)) { - if(number < MAX_PARAMETERS) { - number *= 10; - number += *input - '0'; - } - input++; - } - if(number <= MAX_PARAMETERS && ('$' == *input)) { - *end = ++input; - return number; - } - return 0; -} +/* the provided input number is 1-based but this returns the number 0-based. -static bool dprintf_IsQualifierNoDollar(const char *fmt) + returns -1 if no valid number was provided. +*/ +static int dollarstring(const char *p, const char **end) { -#if defined(MP_HAVE_INT_EXTENSIONS) - if(!strncmp(fmt, "I32", 3) || !strncmp(fmt, "I64", 3)) { - return TRUE; - } -#endif - - switch(*fmt) { - case '-': case '+': case ' ': case '#': case '.': - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'h': case 'l': case 'L': case 'z': case 'q': - case '*': case 'O': -#if defined(MP_HAVE_INT_EXTENSIONS) - case 'I': -#endif - return TRUE; - - default: - return FALSE; - } + curl_off_t num; + if(curlx_str_number(&p, &num, MAX_PARAMETERS) || + curlx_str_single(&p, '$') || !num) + return -1; + *end = p; + return (int)num - 1; } -/****************************************************************** +#define is_arg_used(x,y) ((x)[(y)/8] & (1 << ((y)&7))) +#define mark_arg_used(x,y) ((x)[y/8] |= (unsigned char)(1 << ((y)&7))) + +/* + * Parse the format string. * - * Pass 1: - * Create an index with the type of each parameter entry and its - * value (may vary in size) + * Create two arrays. One describes the inputs, one describes the outputs. * * Returns zero on success. - * - ******************************************************************/ + */ -static int dprintf_Pass1(const char *format, struct va_stack *vto, - char **endpos, va_list arglist) +#define PFMT_OK 0 +#define PFMT_DOLLAR 1 /* bad dollar for main param */ +#define PFMT_DOLLARWIDTH 2 /* bad dollar use for width */ +#define PFMT_DOLLARPREC 3 /* bad dollar use for precision */ +#define PFMT_MANYARGS 4 /* too many input arguments used */ +#define PFMT_PREC 5 /* precision overflow */ +#define PFMT_PRECMIX 6 /* bad mix of precision specifiers */ +#define PFMT_WIDTH 7 /* width overflow */ +#define PFMT_INPUTGAP 8 /* gap in arguments */ +#define PFMT_WIDTHARG 9 /* attempted to use same arg twice, for width */ +#define PFMT_PRECARG 10 /* attempted to use same arg twice, for prec */ +#define PFMT_MANYSEGS 11 /* maxed out output segments */ + +static int parsefmt(const char *format, + struct outsegment *out, + struct va_input *in, + int *opieces, + int *ipieces, va_list arglist) { - char *fmt = (char *)format; + const char *fmt = format; int param_num = 0; - long this_param; - long width; - long precision; - int flags; - long max_param = 0; - long i; + int max_param = -1; + int i; + int ocount = 0; + unsigned char usedinput[MAX_PARAMETERS/8]; + size_t outlen = 0; + struct outsegment *optr; + int use_dollar = DOLLAR_UNKNOWN; + const char *start = fmt; + + /* clear, set a bit for each used input */ + memset(usedinput, 0, sizeof(usedinput)); while(*fmt) { - if(*fmt++ == '%') { + if(*fmt == '%') { + struct va_input *iptr; + bool loopit = TRUE; + FormatType type; + unsigned int flags = 0; + int width = 0; + int precision = 0; + int param = -1; + fmt++; + outlen = (size_t)(fmt - start - 1); if(*fmt == '%') { + /* this means a %% that should be output only as %. Create an output + segment. */ + if(outlen) { + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } + start = fmt; fmt++; continue; /* while */ } - flags = FLAGS_NEW; - - /* Handle the positional case (N$) */ + if(use_dollar != DOLLAR_NOPE) { + param = dollarstring(fmt, &fmt); + if(param < 0) { + if(use_dollar == DOLLAR_USE) + /* illegal combo */ + return PFMT_DOLLAR; - param_num++; - - this_param = dprintf_DollarString(fmt, &fmt); - if(0 == this_param) - /* we got no positional, get the next counter */ - this_param = param_num; - - if(this_param > max_param) - max_param = this_param; - - /* - * The parameter with number 'i' should be used. Next, we need - * to get SIZE and TYPE of the parameter. Add the information - * to our array. - */ - - width = 0; - precision = 0; - - /* Handle the flags */ - - while(dprintf_IsQualifierNoDollar(fmt)) { -#if defined(MP_HAVE_INT_EXTENSIONS) - if(!strncmp(fmt, "I32", 3)) { - flags |= FLAGS_LONG; - fmt += 3; - } - else if(!strncmp(fmt, "I64", 3)) { - flags |= FLAGS_LONGLONG; - fmt += 3; + /* we got no positional, just get the next arg */ + param = -1; + use_dollar = DOLLAR_NOPE; } else -#endif + use_dollar = DOLLAR_USE; + } + /* Handle the flags */ + while(loopit) { switch(*fmt++) { case ' ': flags |= FLAGS_SPACE; @@ -292,7 +273,7 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, break; case '-': flags |= FLAGS_LEFT; - flags &= ~FLAGS_PAD_NIL; + flags &= ~(unsigned int)FLAGS_PAD_NIL; break; case '#': flags |= FLAGS_ALT; @@ -300,42 +281,61 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, case '.': if('*' == *fmt) { /* The precision is picked from a specified parameter */ - flags |= FLAGS_PRECPARAM; fmt++; - param_num++; - i = dprintf_DollarString(fmt, &fmt); - if(i) - precision = i; + if(use_dollar == DOLLAR_USE) { + precision = dollarstring(fmt, &fmt); + if(precision < 0) + /* illegal combo */ + return PFMT_DOLLARPREC; + } else - precision = param_num; - - if(precision > max_param) - max_param = precision; + /* get it from the next argument */ + precision = -1; } else { + bool is_neg; + curl_off_t num; flags |= FLAGS_PREC; - precision = strtol(fmt, &fmt, 10); + is_neg = ('-' == *fmt); + if(is_neg) + fmt++; + if(curlx_str_number(&fmt, &num, INT_MAX)) + return PFMT_PREC; + precision = (int)num; + if(is_neg) + precision = -precision; } if((flags & (FLAGS_PREC | FLAGS_PRECPARAM)) == (FLAGS_PREC | FLAGS_PRECPARAM)) /* it is not permitted to use both kinds of precision for the same argument */ - return 1; + return PFMT_PRECMIX; break; case 'h': flags |= FLAGS_SHORT; break; -#if defined(MP_HAVE_INT_EXTENSIONS) +#ifdef _WIN32 case 'I': + /* Non-ANSI integer extensions I32 I64 */ + if((fmt[0] == '3') && (fmt[1] == '2')) { + flags |= FLAGS_LONG; + fmt += 2; + } + else if((fmt[0] == '6') && (fmt[1] == '4')) { + flags |= FLAGS_LONGLONG; + fmt += 2; + } + else { #if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) - flags |= FLAGS_LONGLONG; + flags |= FLAGS_LONGLONG; #else - flags |= FLAGS_LONG; + flags |= FLAGS_LONG; #endif + } break; -#endif +#endif /* _WIN32 */ case 'l': if(flags & FLAGS_LONG) flags |= FLAGS_LONGLONG; @@ -367,660 +367,737 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, case '0': if(!(flags & FLAGS_LEFT)) flags |= FLAGS_PAD_NIL; - /* FALLTHROUGH */ + FALLTHROUGH(); case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': + case '5': case '6': case '7': case '8': case '9': { + curl_off_t num; flags |= FLAGS_WIDTH; - width = strtol(fmt-1, &fmt, 10); + fmt--; + if(curlx_str_number(&fmt, &num, INT_MAX)) + return PFMT_WIDTH; + width = (int)num; break; - case '*': /* Special case */ + } + case '*': /* read width from argument list */ flags |= FLAGS_WIDTHPARAM; - param_num++; - - i = dprintf_DollarString(fmt, &fmt); - if(i) - width = i; + if(use_dollar == DOLLAR_USE) { + width = dollarstring(fmt, &fmt); + if(width < 0) + /* illegal combo */ + return PFMT_DOLLARWIDTH; + } else - width = param_num; - if(width > max_param) - max_param = width; + /* pick from the next argument */ + width = -1; break; - case '\0': - fmt--; default: + loopit = FALSE; + fmt--; break; - } - } /* switch */ - - /* Handle the specifier */ - - i = this_param - 1; - - if((i < 0) || (i >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; + } /* switch */ + } /* while */ switch(*fmt) { case 'S': flags |= FLAGS_ALT; - /* FALLTHROUGH */ + FALLTHROUGH(); case 's': - vto[i].type = FORMAT_STRING; + type = FORMAT_STRING; break; case 'n': - vto[i].type = FORMAT_INTPTR; + type = FORMAT_INTPTR; break; case 'p': - vto[i].type = FORMAT_PTR; + type = FORMAT_PTR; break; - case 'd': case 'i': - vto[i].type = FORMAT_INT; + case 'd': + case 'i': + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONG; + else if(flags & FLAGS_LONG) + type = FORMAT_LONG; + else + type = FORMAT_INT; break; case 'u': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_UNSIGNED; break; case 'o': - vto[i].type = FORMAT_INT; - flags |= FLAGS_OCTAL; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; + flags |= FLAGS_OCTAL|FLAGS_UNSIGNED; break; case 'x': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UNSIGNED; break; case 'X': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UPPER|FLAGS_UNSIGNED; break; case 'c': - vto[i].type = FORMAT_INT; + type = FORMAT_INT; flags |= FLAGS_CHAR; break; case 'f': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; break; case 'e': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE; break; case 'E': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE|FLAGS_UPPER; break; case 'g': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG; break; case 'G': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG|FLAGS_UPPER; break; default: - vto[i].type = FORMAT_UNKNOWN; - break; + /* invalid instruction, disregard and continue */ + continue; } /* switch */ - vto[i].flags = flags; - vto[i].width = width; - vto[i].precision = precision; - if(flags & FLAGS_WIDTHPARAM) { - /* we have the width specified from a parameter, so we make that - parameter's info setup properly */ - long k = width - 1; - if((k < 0) || (k >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; - vto[i].width = k; - vto[k].type = FORMAT_WIDTH; - vto[k].flags = FLAGS_NEW; - /* can't use width or precision of width! */ - vto[k].width = 0; - vto[k].precision = 0; + if(width < 0) + width = param_num++; + else { + /* if this identifies a parameter already used, this is illegal */ + if(is_arg_used(usedinput, width)) + return PFMT_WIDTHARG; + } + if(width >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(width >= max_param) + max_param = width; + + in[width].type = FORMAT_WIDTH; + /* mark as used */ + mark_arg_used(usedinput, width); } + if(flags & FLAGS_PRECPARAM) { - /* we have the precision specified from a parameter, so we make that - parameter's info setup properly */ - long k = precision - 1; - if((k < 0) || (k >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; - vto[i].precision = k; - vto[k].type = FORMAT_WIDTH; - vto[k].flags = FLAGS_NEW; - /* can't use width or precision of width! */ - vto[k].width = 0; - vto[k].precision = 0; + if(precision < 0) + precision = param_num++; + else { + /* if this identifies a parameter already used, this is illegal */ + if(is_arg_used(usedinput, precision)) + return PFMT_PRECARG; + } + if(precision >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(precision >= max_param) + max_param = precision; + + in[precision].type = FORMAT_PRECISION; + mark_arg_used(usedinput, precision); } - *endpos++ = fmt + ((*fmt == '\0') ? 0 : 1); /* end of this sequence */ + + /* Handle the specifier */ + if(param < 0) + param = param_num++; + if(param >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(param >= max_param) + max_param = param; + + iptr = &in[param]; + iptr->type = type; + + /* mark this input as used */ + mark_arg_used(usedinput, param); + + fmt++; + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = (unsigned int)param; + optr->flags = flags; + optr->width = width; + optr->precision = precision; + optr->start = start; + optr->outlen = outlen; + start = fmt; } + else + fmt++; } - /* Read the arg list parameters into our data list */ - for(i = 0; i MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } - switch(vto[i].type) { + /* Read the arg list parameters into our data list */ + for(i = 0; i < max_param + 1; i++) { + struct va_input *iptr = &in[i]; + if(!is_arg_used(usedinput, i)) + /* bad input */ + return PFMT_INPUTGAP; + + /* based on the type, read the correct argument */ + switch(iptr->type) { case FORMAT_STRING: - vto[i].data.str = va_arg(arglist, char *); + iptr->val.str = va_arg(arglist, const char *); break; case FORMAT_INTPTR: - case FORMAT_UNKNOWN: case FORMAT_PTR: - vto[i].data.ptr = va_arg(arglist, void *); + iptr->val.ptr = va_arg(arglist, void *); break; - case FORMAT_INT: -#ifdef HAVE_LONG_LONG_TYPE - if((vto[i].flags & FLAGS_LONGLONG) && (vto[i].flags & FLAGS_UNSIGNED)) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); - else if(vto[i].flags & FLAGS_LONGLONG) - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, mp_intmax_t); - else -#endif - { - if((vto[i].flags & FLAGS_LONG) && (vto[i].flags & FLAGS_UNSIGNED)) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, unsigned long); - else if(vto[i].flags & FLAGS_LONG) - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, long); - else if(vto[i].flags & FLAGS_UNSIGNED) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, unsigned int); - else - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, int); - } + case FORMAT_LONGLONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); break; - case FORMAT_DOUBLE: - vto[i].data.dnum = va_arg(arglist, double); + case FORMAT_LONGLONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, mp_intmax_t); + break; + + case FORMAT_LONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned long); break; + case FORMAT_LONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, long); + break; + + case FORMAT_INTU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned int); + break; + + case FORMAT_INT: case FORMAT_WIDTH: - /* Argument has been read. Silently convert it into an integer - * for later use - */ - vto[i].type = FORMAT_INT; + case FORMAT_PRECISION: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, int); + break; + + case FORMAT_DOUBLE: + iptr->val.dnum = va_arg(arglist, double); break; default: + DEBUGASSERT(NULL); /* unexpected */ break; } } + *ipieces = max_param + 1; + *opieces = ocount; - return 0; - + return PFMT_OK; } -static int dprintf_formatf( - void *data, /* untouched by format(), just sent to the stream() function in - the second argument */ - /* function pointer called for each output character */ - int (*stream)(int, FILE *), - const char *format, /* %-formatted string */ - va_list ap_save) /* list of parameters */ +struct mproperty { + int width; /* Width of a field. */ + int prec; /* Precision of a field. */ + unsigned int flags; +}; + +static bool out_double(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + double dnum, + char *work, int *donep) { - /* Base-36 digits for numbers. */ - const char *digits = lower_digits; + char formatbuf[32]="%"; + char *fptr = &formatbuf[1]; + size_t left = sizeof(formatbuf)-strlen(formatbuf); + int flags = p->flags; + int width = p->width; + int prec = p->prec; + + if(flags & FLAGS_LEFT) + *fptr++ = '-'; + if(flags & FLAGS_SHOWSIGN) + *fptr++ = '+'; + if(flags & FLAGS_SPACE) + *fptr++ = ' '; + if(flags & FLAGS_ALT) + *fptr++ = '#'; + + *fptr = 0; + + if(width >= 0) { + size_t dlen; + if(width >= BUFFSIZE) + width = BUFFSIZE - 1; + /* RECURSIVE USAGE */ + dlen = (size_t)curl_msnprintf(fptr, left, "%d", width); + fptr += dlen; + left -= dlen; + } + if(prec >= 0) { + /* for each digit in the integer part, we can have one less + precision */ + int maxprec = BUFFSIZE - 1; + double val = dnum; + int len; + if(prec > maxprec) + prec = maxprec - 1; + if(width > 0 && prec <= width) + maxprec -= width; + while(val >= 10.0) { + val /= 10; + maxprec--; + } - /* Pointer into the format string. */ - char *f; + if(prec > maxprec) + prec = maxprec - 1; + if(prec < 0) + prec = 0; + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, ".%d", prec); + fptr += len; + } + if(flags & FLAGS_LONG) + *fptr++ = 'l'; - /* Number of characters written. */ - int done = 0; + if(flags & FLAGS_FLOATE) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'E' : 'e'); + else if(flags & FLAGS_FLOATG) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'G' : 'g'); + else + *fptr++ = 'f'; - long param; /* current parameter to read */ - long param_num = 0; /* parameter counter */ + *fptr = 0; /* and a final null-termination */ - struct va_stack vto[MAX_PARAMETERS]; - char *endpos[MAX_PARAMETERS]; - char **end; - char work[BUFFSIZE]; - struct va_stack *p; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + /* NOTE NOTE NOTE!! Not all sprintf implementations return number of + output characters */ +#ifdef HAVE_SNPRINTF + /* !checksrc! disable LONGLINE */ + /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + (snprintf)(work, BUFFSIZE, formatbuf, dnum); +#ifdef _WIN32 + /* Old versions of the Windows CRT do not terminate the snprintf output + buffer if it reaches the max size so we do that here. */ + work[BUFFSIZE - 1] = 0; +#endif +#else + (sprintf)(work, formatbuf, dnum); +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + DEBUGASSERT(strlen(work) < BUFFSIZE); + while(*work) { + if(stream(*work++, userp)) + return TRUE; + (*donep)++; + } + return 0; +} + +static bool out_number(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + mp_uintmax_t num, + mp_intmax_t nums, + char *work, int *donep) +{ + const unsigned char *digits = Curl_ldigits; + int flags = p->flags; + int width = p->width; + int prec = p->prec; + bool is_alt = flags & FLAGS_ALT; + bool is_neg = FALSE; + int base = 10; /* 'workend' points to the final buffer byte position, but with an extra - byte as margin to avoid the (false?) warning Coverity gives us + byte as margin to avoid the (FALSE?) warning Coverity gives us otherwise */ - char *workend = &work[sizeof(work) - 2]; + char *workend = &work[BUFFSIZE - 2]; + char *w; - /* Do the actual %-code parsing */ - if(dprintf_Pass1(format, vto, endpos, ap_save)) - return 0; - - end = &endpos[0]; /* the initial end-position from the list dprintf_Pass1() - created for us */ - - f = (char *)format; - while(*f != '\0') { - /* Format spec modifiers. */ - int is_alt; - - /* Width of a field. */ - long width; + if(flags & FLAGS_CHAR) { + /* Character. */ + if(!(flags & FLAGS_LEFT)) + while(--width > 0) + OUTCHAR(' '); + OUTCHAR((char) num); + if(flags & FLAGS_LEFT) + while(--width > 0) + OUTCHAR(' '); + return FALSE; + } + if(flags & FLAGS_OCTAL) + /* Octal unsigned integer */ + base = 8; + + else if(flags & FLAGS_HEX) { + /* Hexadecimal unsigned integer */ + digits = (flags & FLAGS_UPPER) ? Curl_udigits : Curl_ldigits; + base = 16; + } + else if(flags & FLAGS_UNSIGNED) + /* Decimal unsigned integer */ + ; + + else { + /* Decimal integer. */ + is_neg = (nums < 0); + if(is_neg) { + /* signed_num might fail to hold absolute negative minimum by 1 */ + mp_intmax_t signed_num; /* Used to convert negative in positive. */ + signed_num = nums + (mp_intmax_t)1; + signed_num = -signed_num; + num = (mp_uintmax_t)signed_num; + num += (mp_uintmax_t)1; + } + } - /* Precision of a field. */ - long prec; + /* Supply a default precision if none was given. */ + if(prec == -1) + prec = 1; + + /* Put the number in WORK. */ + w = workend; + DEBUGASSERT(base <= 16); + switch(base) { + case 10: + while(num > 0) { + *w-- = (char)('0' + (num % 10)); + num /= 10; + } + break; + default: + while(num > 0) { + *w-- = digits[num % base]; + num /= base; + } + break; + } + width -= (int)(workend - w); + prec -= (int)(workend - w); - /* Decimal integer is negative. */ - int is_neg; + if(is_alt && base == 8 && prec <= 0) { + *w-- = '0'; + --width; + } - /* Base of a number to be written. */ - unsigned long base; + if(prec > 0) { + width -= prec; + while(prec-- > 0 && w >= work) + *w-- = '0'; + } - /* Integral values to be written. */ - mp_uintmax_t num; + if(is_alt && base == 16) + width -= 2; - /* Used to convert negative in positive. */ - mp_intmax_t signed_num; + if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) + --width; - char *w; + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR(' '); - if(*f != '%') { - /* This isn't a format spec, so write everything out until the next one - OR end of string is reached. */ - do { - OUTCHAR(*f); - } while(*++f && ('%' != *f)); - continue; - } + if(is_neg) + OUTCHAR('-'); + else if(flags & FLAGS_SHOWSIGN) + OUTCHAR('+'); + else if(flags & FLAGS_SPACE) + OUTCHAR(' '); - ++f; + if(is_alt && base == 16) { + OUTCHAR('0'); + if(flags & FLAGS_UPPER) + OUTCHAR('X'); + else + OUTCHAR('x'); + } - /* Check for "%%". Note that although the ANSI standard lists - '%' as a conversion specifier, it says "The complete format - specification shall be `%%'," so we can avoid all the width - and precision processing. */ - if(*f == '%') { - ++f; - OUTCHAR('%'); - continue; - } + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR('0'); - /* If this is a positional parameter, the position must follow immediately - after the %, thus create a %$ sequence */ - param = dprintf_DollarString(f, &f); + /* Write the number. */ + while(++w <= workend) { + OUTCHAR(*w); + } - if(!param) - param = param_num; - else - --param; + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); - param_num++; /* increase this always to allow "%2$s %1$s %s" and then the - third %s will pick the 3rd argument */ + return FALSE; +} - p = &vto[param]; +static const char nilstr[] = "(nil)"; - /* pick up the specified width */ - if(p->flags & FLAGS_WIDTHPARAM) { - width = (long)vto[p->width].data.num.as_signed; - param_num++; /* since the width is extracted from a parameter, we - must skip that to get to the next one properly */ - if(width < 0) { - /* "A negative field width is taken as a '-' flag followed by a - positive field width." */ - width = -width; - p->flags |= FLAGS_LEFT; - p->flags &= ~FLAGS_PAD_NIL; - } +static bool out_string(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + const char *str, + int *donep) +{ + int flags = p->flags; + int width = p->width; + int prec = p->prec; + size_t len; + + if(!str) { + /* Write null string if there is space. */ + if(prec == -1 || prec >= (int) sizeof(nilstr) - 1) { + str = nilstr; + len = sizeof(nilstr) - 1; + /* Disable quotes around (nil) */ + flags &= ~(unsigned int)FLAGS_ALT; } - else - width = p->width; - - /* pick up the specified precision */ - if(p->flags & FLAGS_PRECPARAM) { - prec = (long)vto[p->precision].data.num.as_signed; - param_num++; /* since the precision is extracted from a parameter, we - must skip that to get to the next one properly */ - if(prec < 0) - /* "A negative precision is taken as if the precision were - omitted." */ - prec = -1; + else { + str = ""; + len = 0; } - else if(p->flags & FLAGS_PREC) - prec = p->precision; - else - prec = -1; + } + else if(prec != -1) + len = (size_t)prec; + else if(*str == '\0') + len = 0; + else + len = strlen(str); - is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; + width -= (len > INT_MAX) ? INT_MAX : (int)len; - switch(p->type) { - case FORMAT_INT: - num = p->data.num.as_unsigned; - if(p->flags & FLAGS_CHAR) { - /* Character. */ - if(!(p->flags & FLAGS_LEFT)) - while(--width > 0) - OUTCHAR(' '); - OUTCHAR((char) num); - if(p->flags & FLAGS_LEFT) - while(--width > 0) - OUTCHAR(' '); - break; - } - if(p->flags & FLAGS_OCTAL) { - /* Octal unsigned integer. */ - base = 8; - goto unsigned_number; - } - else if(p->flags & FLAGS_HEX) { - /* Hexadecimal unsigned integer. */ + if(flags & FLAGS_ALT) + OUTCHAR('"'); - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; - base = 16; - goto unsigned_number; - } - else if(p->flags & FLAGS_UNSIGNED) { - /* Decimal unsigned integer. */ - base = 10; - goto unsigned_number; - } + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); - /* Decimal integer. */ - base = 10; + for(; len && *str; len--) + OUTCHAR(*str++); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); - is_neg = (p->data.num.as_signed < (mp_intmax_t)0) ? 1 : 0; - if(is_neg) { - /* signed_num might fail to hold absolute negative minimum by 1 */ - signed_num = p->data.num.as_signed + (mp_intmax_t)1; - signed_num = -signed_num; - num = (mp_uintmax_t)signed_num; - num += (mp_uintmax_t)1; - } + if(flags & FLAGS_ALT) + OUTCHAR('"'); - goto number; + return FALSE; +} -unsigned_number: - /* Unsigned number of base BASE. */ - is_neg = 0; +static bool out_pointer(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + const char *ptr, + char *work, + int *donep) +{ + /* Generic pointer. */ + if(ptr) { + size_t num = (size_t) ptr; + + /* 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)) + return TRUE; + } + else { + /* Write "(nil)" for a nil pointer. */ + const char *point; + int width = p->width; + int flags = p->flags; + + width -= (int)(sizeof(nilstr) - 1); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + for(point = nilstr; *point; ++point) + OUTCHAR(*point); + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + } + return FALSE; +} -number: - /* Number of base BASE. */ +/* + * formatf() - the general printf function. + * + * It calls parsefmt() to parse the format string. It populates two arrays; + * one that describes the input arguments and one that describes a number of + * output segments. + * + * On success, the input array describes the type of all arguments and their + * values. + * + * The function then iterates over the output segments and outputs them one + * by one until done. Using the appropriate input arguments (if any). + * + * All output is sent to the 'stream()' callback, one byte at a time. + */ - /* Supply a default precision if none was given. */ - if(prec == -1) - prec = 1; +static int formatf( + void *userp, /* untouched by format(), just sent to the stream() function in + the second argument */ + /* function pointer called for each output character */ + int (*stream)(unsigned char, void *), + const char *format, /* %-formatted string */ + va_list ap_save) /* list of parameters */ +{ + int done = 0; /* number of characters written */ + int i; + int ocount = 0; /* number of output segments */ + int icount = 0; /* number of input arguments */ - /* Put the number in WORK. */ - w = workend; - while(num > 0) { - *w-- = digits[num % base]; - num /= base; - } - width -= (long)(workend - w); - prec -= (long)(workend - w); + struct outsegment output[MAX_SEGMENTS]; + struct va_input input[MAX_PARAMETERS]; + char work[BUFFSIZE + 2]; - if(is_alt && base == 8 && prec <= 0) { - *w-- = '0'; - --width; - } + /* Parse the format string */ + if(parsefmt(format, output, input, &ocount, &icount, ap_save)) + return 0; - if(prec > 0) { - width -= prec; - while(prec-- > 0 && w >= work) - *w-- = '0'; + for(i = 0; i < ocount; i++) { + struct outsegment *optr = &output[i]; + struct va_input *iptr = &input[optr->input]; + struct mproperty p; + size_t outlen = optr->outlen; + + if(outlen) { + const char *str = optr->start; + for(; outlen && *str; outlen--) { + if(stream(*str++, userp)) + return done; + done++; } + if(optr->flags & FLAGS_SUBSTR) + /* this is just a substring */ + continue; + } - if(is_alt && base == 16) - width -= 2; - - if(is_neg || (p->flags & FLAGS_SHOWSIGN) || (p->flags & FLAGS_SPACE)) - --width; - - if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR(' '); - - if(is_neg) - OUTCHAR('-'); - else if(p->flags & FLAGS_SHOWSIGN) - OUTCHAR('+'); - else if(p->flags & FLAGS_SPACE) - OUTCHAR(' '); + p.flags = optr->flags; - if(is_alt && base == 16) { - OUTCHAR('0'); - if(p->flags & FLAGS_UPPER) - OUTCHAR('X'); + /* pick up the specified width */ + if(p.flags & FLAGS_WIDTHPARAM) { + p.width = (int)input[optr->width].val.nums; + if(p.width < 0) { + /* "A negative field width is taken as a '-' flag followed by a + positive field width." */ + if(p.width == INT_MIN) + p.width = INT_MAX; else - OUTCHAR('x'); + p.width = -p.width; + p.flags |= FLAGS_LEFT; + p.flags &= ~(unsigned int)FLAGS_PAD_NIL; } + } + else + p.width = optr->width; - if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR('0'); - - /* Write the number. */ - while(++w <= workend) { - OUTCHAR(*w); - } + /* pick up the specified precision */ + if(p.flags & FLAGS_PRECPARAM) { + p.prec = (int)input[optr->precision].val.nums; + if(p.prec < 0) + /* "A negative precision is taken as if the precision were + omitted." */ + p.prec = -1; + } + else if(p.flags & FLAGS_PREC) + p.prec = optr->precision; + else + p.prec = -1; + + switch(iptr->type) { + case FORMAT_INTU: + case FORMAT_LONGU: + case FORMAT_LONGLONGU: + p.flags |= FLAGS_UNSIGNED; + if(out_number(userp, stream, &p, iptr->val.numu, 0, work, &done)) + return done; + break; - if(p->flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + case FORMAT_INT: + case FORMAT_LONG: + case FORMAT_LONGLONG: + if(out_number(userp, stream, &p, iptr->val.numu, + iptr->val.nums, work, &done)) + return done; break; case FORMAT_STRING: - /* String. */ - { - static const char null[] = "(nil)"; - const char *str; - size_t len; - - str = (char *) p->data.str; - if(!str) { - /* Write null[] if there's space. */ - if(prec == -1 || prec >= (long) sizeof(null) - 1) { - str = null; - len = sizeof(null) - 1; - /* Disable quotes around (nil) */ - p->flags &= (~FLAGS_ALT); - } - else { - str = ""; - len = 0; - } - } - else if(prec != -1) - len = (size_t)prec; - else if(*str == '\0') - len = 0; - else - len = strlen(str); - - width -= (len > LONG_MAX) ? LONG_MAX : (long)len; - - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); - - if(!(p->flags&FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); - - for(; len && *str; len--) - OUTCHAR(*str++); - if(p->flags&FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); - } + if(out_string(userp, stream, &p, iptr->val.str, &done)) + return done; break; case FORMAT_PTR: - /* Generic pointer. */ - { - void *ptr; - ptr = (void *) p->data.ptr; - if(ptr) { - /* If the pointer is not NULL, write it as a %#x spec. */ - base = 16; - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; - is_alt = 1; - num = (size_t) ptr; - is_neg = 0; - goto number; - } - else { - /* Write "(nil)" for a nil pointer. */ - static const char strnil[] = "(nil)"; - const char *point; - - width -= (long)(sizeof(strnil) - 1); - if(p->flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - for(point = strnil; *point != '\0'; ++point) - OUTCHAR(*point); - if(!(p->flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); - } - } + if(out_pointer(userp, stream, &p, iptr->val.ptr, work, &done)) + return done; break; case FORMAT_DOUBLE: - { - char formatbuf[32]="%"; - char *fptr = &formatbuf[1]; - size_t left = sizeof(formatbuf)-strlen(formatbuf); - int len; - - width = -1; - if(p->flags & FLAGS_WIDTH) - width = p->width; - else if(p->flags & FLAGS_WIDTHPARAM) - width = (long)vto[p->width].data.num.as_signed; - - prec = -1; - if(p->flags & FLAGS_PREC) - prec = p->precision; - else if(p->flags & FLAGS_PRECPARAM) - prec = (long)vto[p->precision].data.num.as_signed; - - if(p->flags & FLAGS_LEFT) - *fptr++ = '-'; - if(p->flags & FLAGS_SHOWSIGN) - *fptr++ = '+'; - if(p->flags & FLAGS_SPACE) - *fptr++ = ' '; - if(p->flags & FLAGS_ALT) - *fptr++ = '#'; - - *fptr = 0; - - if(width >= 0) { - if(width >= (long)sizeof(work)) - width = sizeof(work)-1; - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, "%ld", width); - fptr += len; - left -= len; - } - if(prec >= 0) { - /* for each digit in the integer part, we can have one less - precision */ - size_t maxprec = sizeof(work) - 2; - double val = p->data.dnum; - if(width > 0 && prec <= width) - maxprec -= width; - while(val >= 10.0) { - val /= 10; - maxprec--; - } - - if(prec > (long)maxprec) - prec = (long)maxprec-1; - if(prec < 0) - prec = 0; - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, ".%ld", prec); - fptr += len; - } - if(p->flags & FLAGS_LONG) - *fptr++ = 'l'; - - if(p->flags & FLAGS_FLOATE) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'E':'e'); - else if(p->flags & FLAGS_FLOATG) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'G' : 'g'); - else - *fptr++ = 'f'; - - *fptr = 0; /* and a final null-termination */ - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wformat-nonliteral" -#endif - /* NOTE NOTE NOTE!! Not all sprintf implementations return number of - output characters */ -#ifdef HAVE_SNPRINTF - (snprintf)(work, sizeof(work), formatbuf, p->data.dnum); -#else - (sprintf)(work, formatbuf, p->data.dnum); -#endif -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - DEBUGASSERT(strlen(work) <= sizeof(work)); - for(fptr = work; *fptr; fptr++) - OUTCHAR(*fptr); - } + if(out_double(userp, stream, &p, iptr->val.dnum, work, &done)) + return done; break; case FORMAT_INTPTR: /* Answer the count of characters written. */ #ifdef HAVE_LONG_LONG_TYPE - if(p->flags & FLAGS_LONGLONG) - *(LONG_LONG_TYPE *) p->data.ptr = (LONG_LONG_TYPE)done; + if(p.flags & FLAGS_LONGLONG) + *(LONG_LONG_TYPE *) iptr->val.ptr = (LONG_LONG_TYPE)done; else #endif - if(p->flags & FLAGS_LONG) - *(long *) p->data.ptr = (long)done; - else if(!(p->flags & FLAGS_SHORT)) - *(int *) p->data.ptr = (int)done; + if(p.flags & FLAGS_LONG) + *(long *) iptr->val.ptr = (long)done; + else if(!(p.flags & FLAGS_SHORT)) + *(int *) iptr->val.ptr = (int)done; else - *(short *) p->data.ptr = (short)done; + *(short *) iptr->val.ptr = (short)done; break; default: break; } - f = *end++; /* goto end of %-code */ - } return done; } /* fputc() look-alike */ -static int addbyter(int output, FILE *data) +static int addbyter(unsigned char outc, void *f) { - struct nsprintf *infop = (struct nsprintf *)data; - unsigned char outc = (unsigned char)output; - + struct nsprintf *infop = f; if(infop->length < infop->max) { - /* only do this if we haven't reached max length yet */ - infop->buffer[0] = outc; /* store */ - infop->buffer++; /* increase pointer */ + /* only do this if we have not reached max length yet */ + *infop->buffer++ = (char)outc; /* store */ infop->length++; /* we are now one byte larger */ - return outc; /* fputc() returns like this on success */ + return 0; /* fputc() returns like this on success */ } - return -1; + return 1; } int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, @@ -1033,14 +1110,14 @@ int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, info.length = 0; info.max = maxlength; - retcode = dprintf_formatf(&info, addbyter, format, ap_save); + retcode = formatf(&info, addbyter, format, ap_save); if(info.max) { /* we terminate this with a zero byte */ if(info.max == info.length) { - /* we're at maximum, scrap the last letter */ + /* we are at maximum, scrap the last letter */ info.buffer[-1] = 0; DEBUGASSERT(retcode); - retcode--; /* don't count the nul byte */ + retcode--; /* do not count the nul byte */ } else info.buffer[0] = 0; @@ -1059,32 +1136,28 @@ int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) } /* fputc() look-alike */ -static int alloc_addbyter(int output, FILE *data) +static int alloc_addbyter(unsigned char outc, void *f) { - struct asprintf *infop = (struct asprintf *)data; - unsigned char outc = (unsigned char)output; - - if(Curl_dyn_addn(infop->b, &outc, 1)) { - infop->fail = 1; - return -1; /* fail */ + struct asprintf *infop = f; + CURLcode result = curlx_dyn_addn(infop->b, &outc, 1); + if(result) { + infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; + return 1 ; /* fail */ } - return outc; /* fputc() returns like this on success */ + return 0; } -extern int Curl_dyn_vprintf(struct dynbuf *dyn, - const char *format, va_list ap_save); - -/* appends the formatted string, returns 0 on success, 1 on error */ -int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) +/* appends the formatted string, returns MERR error code */ +int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) { struct asprintf info; info.b = dyn; - info.fail = 0; + info.merr = MERR_OK; - (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if(info.fail) { - Curl_dyn_free(info.b); - return 1; + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { + curlx_dyn_free(info.b); + return info.merr; } return 0; } @@ -1094,16 +1167,16 @@ char *curl_mvaprintf(const char *format, va_list ap_save) struct asprintf info; struct dynbuf dyn; info.b = &dyn; - Curl_dyn_init(info.b, DYN_APRINTF); - info.fail = 0; + curlx_dyn_init(info.b, DYN_APRINTF); + info.merr = MERR_OK; - (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if(info.fail) { - Curl_dyn_free(info.b); + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { + curlx_dyn_free(info.b); return NULL; } - if(Curl_dyn_len(info.b)) - return Curl_dyn_ptr(info.b); + if(curlx_dyn_len(info.b)) + return curlx_dyn_ptr(info.b); return strdup(""); } @@ -1117,13 +1190,12 @@ char *curl_maprintf(const char *format, ...) return s; } -static int storebuffer(int output, FILE *data) +static int storebuffer(unsigned char outc, void *f) { - char **buffer = (char **)data; - unsigned char outc = (unsigned char)output; - **buffer = outc; + char **buffer = f; + **buffer = (char)outc; (*buffer)++; - return outc; /* act like fputc() ! */ + return 0; } int curl_msprintf(char *buffer, const char *format, ...) @@ -1131,19 +1203,26 @@ int curl_msprintf(char *buffer, const char *format, ...) va_list ap_save; /* argument pointer */ int retcode; va_start(ap_save, format); - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + retcode = formatf(&buffer, storebuffer, format, ap_save); va_end(ap_save); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } +static int fputc_wrapper(unsigned char outc, void *f) +{ + int out = outc; + FILE *s = f; + int rc = fputc(out, s); + return rc == EOF; +} + int curl_mprintf(const char *format, ...) { int retcode; va_list ap_save; /* argument pointer */ va_start(ap_save, format); - - retcode = dprintf_formatf(stdout, fputc, format, ap_save); + retcode = formatf(stdout, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; } @@ -1153,25 +1232,24 @@ int curl_mfprintf(FILE *whereto, const char *format, ...) int retcode; va_list ap_save; /* argument pointer */ va_start(ap_save, format); - retcode = dprintf_formatf(whereto, fputc, format, ap_save); + retcode = formatf(whereto, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; } int curl_mvsprintf(char *buffer, const char *format, va_list ap_save) { - int retcode; - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + int retcode = formatf(&buffer, storebuffer, format, ap_save); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } int curl_mvprintf(const char *format, va_list ap_save) { - return dprintf_formatf(stdout, fputc, format, ap_save); + return formatf(stdout, fputc_wrapper, format, ap_save); } int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save) { - return dprintf_formatf(whereto, fputc, format, ap_save); + return formatf(whereto, fputc_wrapper, format, ap_save); } diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c index dbe72398ad8..77e73346131 100644 --- a/Utilities/cmcurl/lib/mqtt.c +++ b/Utilities/cmcurl/lib/mqtt.c @@ -37,7 +37,7 @@ #include "strdup.h" #include "url.h" #include "escape.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_printf.h" #include "curl_memory.h" #include "multiif.h" @@ -46,17 +46,59 @@ /* The last #include file should be: */ #include "memdebug.h" -#define MQTT_MSG_CONNECT 0x10 -#define MQTT_MSG_CONNACK 0x20 -#define MQTT_MSG_PUBLISH 0x30 -#define MQTT_MSG_SUBSCRIBE 0x82 -#define MQTT_MSG_SUBACK 0x90 +/* first byte is command. + second byte is for flags. */ +#define MQTT_MSG_CONNECT 0x10 +/* #define MQTT_MSG_CONNACK 0x20 */ +#define MQTT_MSG_PUBLISH 0x30 +#define MQTT_MSG_SUBSCRIBE 0x82 +#define MQTT_MSG_SUBACK 0x90 #define MQTT_MSG_DISCONNECT 0xe0 +#define MQTT_MSG_PINGREQ 0xC0 +#define MQTT_MSG_PINGRESP 0xD0 #define MQTT_CONNACK_LEN 2 #define MQTT_SUBACK_LEN 3 #define MQTT_CLIENTID_LEN 12 /* "curl0123abcd" */ +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_MQTT_EASY "meta:proto:mqtt:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_MQTT_CONN "meta:proto:mqtt:conn" + +enum mqttstate { + MQTT_FIRST, /* 0 */ + MQTT_REMAINING_LENGTH, /* 1 */ + MQTT_CONNACK, /* 2 */ + MQTT_SUBACK, /* 3 */ + 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_NOSTATE /* 7 - never used an actual state */ +}; + +struct mqtt_conn { + enum mqttstate state; + enum mqttstate nextstate; /* switch to this after remaining length is + done */ + unsigned int packetid; +}; + +/* protocol-specific transfer-related data */ +struct MQTT { + struct dynbuf sendbuf; + /* when receiving */ + struct dynbuf recvbuf; + size_t npacket; /* byte counter */ + size_t remaining_length; + unsigned char pkt_hd[4]; /* for decoding the arriving packet length */ + struct curltime lastTime; /* last time we sent or received data */ + unsigned char firstbyte; + BIT(pingsent); /* 1 while we wait for ping response */ +}; + + /* * Forward declarations. */ @@ -75,7 +117,7 @@ static CURLcode mqtt_setup_conn(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_mqtt = { - "MQTT", /* scheme */ + "mqtt", /* scheme */ mqtt_setup_conn, /* setup_connection */ mqtt_do, /* do_it */ mqtt_done, /* done */ @@ -88,55 +130,83 @@ const struct Curl_handler Curl_handler_mqtt = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_MQTT, /* defport */ CURLPROTO_MQTT, /* protocol */ CURLPROTO_MQTT, /* family */ PROTOPT_NONE /* flags */ }; +static void mqtt_easy_dtor(void *key, size_t klen, void *entry) +{ + struct MQTT *mq = entry; + (void)key; + (void)klen; + curlx_dyn_free(&mq->sendbuf); + curlx_dyn_free(&mq->recvbuf); + free(mq); +} + +static void mqtt_conn_dtor(void *key, size_t klen, void *entry) +{ + (void)key; + (void)klen; + free(entry); +} + static CURLcode mqtt_setup_conn(struct Curl_easy *data, struct connectdata *conn) { - /* allocate the HTTP-specific struct for the Curl_easy, only to survive - during this request */ + /* setup MQTT specific meta data at easy handle and connection */ + struct mqtt_conn *mqtt; struct MQTT *mq; - (void)conn; - DEBUGASSERT(data->req.p.mqtt == NULL); + + mqtt = calloc(1, sizeof(*mqtt)); + if(!mqtt || + Curl_conn_meta_set(conn, CURL_META_MQTT_CONN, mqtt, mqtt_conn_dtor)) + return CURLE_OUT_OF_MEMORY; mq = calloc(1, sizeof(struct MQTT)); if(!mq) return CURLE_OUT_OF_MEMORY; - data->req.p.mqtt = mq; + curlx_dyn_init(&mq->recvbuf, DYN_MQTT_RECV); + curlx_dyn_init(&mq->sendbuf, DYN_MQTT_SEND); + if(Curl_meta_set(data, CURL_META_MQTT_EASY, mq, mqtt_easy_dtor)) + return CURLE_OUT_OF_MEMORY; return CURLE_OK; } static CURLcode mqtt_send(struct Curl_easy *data, - char *buf, size_t len) + const char *buf, size_t len) { - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - struct MQTT *mq = data->req.p.mqtt; - ssize_t n; - result = Curl_write(data, sockfd, buf, len, &n); + size_t n; + CURLcode result; + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + + if(!mq) + return CURLE_FAILED_INIT; + + result = Curl_xfer_send(data, buf, len, FALSE, &n); if(result) return result; + mq->lastTime = curlx_now(); Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n); - if(len != (size_t)n) { + if(len != n) { size_t nsend = len - n; - char *sendleftovers = Curl_memdup(&buf[n], nsend); - if(!sendleftovers) - return CURLE_OUT_OF_MEMORY; - mq->sendleftovers = sendleftovers; - mq->nsend = nsend; - } - else { - mq->sendleftovers = NULL; - mq->nsend = 0; + 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); return result; } @@ -154,15 +224,15 @@ static int mqtt_getsock(struct Curl_easy *data, static int mqtt_encode_len(char *buf, size_t len) { - unsigned char encoded; int i; - for(i = 0; (len > 0) && (i<4); i++) { + for(i = 0; (len > 0) && (i < 4); i++) { + unsigned char encoded; encoded = len % 0x80; len /= 0x80; if(len) encoded |= 0x80; - buf[i] = encoded; + buf[i] = (char)encoded; } return i; @@ -256,7 +326,6 @@ static CURLcode mqtt_connect(struct Curl_easy *data) int remain_pos = 0; char remain[4] = {0}; size_t packetlen = 0; - size_t payloadlen = 0; size_t start_user = 0; size_t start_pwd = 0; char client_id[MQTT_CLIENTID_LEN + 1] = "curl"; @@ -271,14 +340,11 @@ static CURLcode mqtt_connect(struct Curl_easy *data) const char *passwd = data->state.aptr.passwd ? data->state.aptr.passwd : ""; const size_t plen = strlen(passwd); - - payloadlen = ulen + plen + MQTT_CLIENTID_LEN + 2; - /* The plus 2 are for the MSB and LSB describing the length of the string to - * be added on the payload. Refer to spec 1.5.2 and 1.5.4 */ - if(ulen) - payloadlen += 2; - if(plen) - payloadlen += 2; + const size_t payloadlen = ulen + plen + MQTT_CLIENTID_LEN + 2 + + /* The plus 2s below are for the MSB and LSB describing the length of the + string to be added on the payload. Refer to spec 1.5.2 and 1.5.4 */ + (ulen ? 2 : 0) + + (plen ? 2 : 0); /* getting how much occupy the remain length */ remain_pos = mqtt_encode_len(remain, payloadlen + 10); @@ -287,39 +353,38 @@ static CURLcode mqtt_connect(struct Curl_easy *data) packetlen = payloadlen + 10 + remain_pos + 1; /* allocating packet */ - if(packetlen > 268435455) + if(packetlen > 0xFFFFFFF) return CURLE_WEIRD_SERVER_REPLY; - packet = malloc(packetlen); + packet = calloc(1, packetlen); if(!packet) return CURLE_OUT_OF_MEMORY; - memset(packet, 0, packetlen); /* set initial values for the CONNECT packet */ pos = init_connpack(packet, remain, remain_pos); - result = Curl_rand_hex(data, (unsigned char *)&client_id[clen], - MQTT_CLIENTID_LEN - clen + 1); + result = Curl_rand_alnum(data, (unsigned char *)&client_id[clen], + MQTT_CLIENTID_LEN - clen + 1); /* add client id */ rc = add_client_id(client_id, strlen(client_id), packet, pos + 1); if(rc) { - failf(data, "Client ID length mismatched: [%lu]", strlen(client_id)); + failf(data, "Client ID length mismatched: [%zu]", strlen(client_id)); result = CURLE_WEIRD_SERVER_REPLY; goto end; } infof(data, "Using client id '%s'", client_id); - /* position where starts the user payload */ + /* position where the user payload starts */ start_user = pos + 3 + MQTT_CLIENTID_LEN; - /* position where starts the password payload */ + /* position where the password payload starts */ start_pwd = start_user + ulen; - /* if user name was provided, add it to the packet */ + /* if username was provided, add it to the packet */ if(ulen) { start_pwd += 2; rc = add_user(username, ulen, (unsigned char *)packet, start_user, remain_pos); if(rc) { - failf(data, "Username is too large: [%lu]", ulen); + failf(data, "Username too long: [%zu]", ulen); result = CURLE_WEIRD_SERVER_REPLY; goto end; } @@ -329,7 +394,7 @@ static CURLcode mqtt_connect(struct Curl_easy *data) if(plen) { rc = add_passwd(passwd, plen, packet, start_pwd, remain_pos); if(rc) { - failf(data, "Password is too large: [%lu]", plen); + failf(data, "Password too long: [%zu]", plen); result = CURLE_WEIRD_SERVER_REPLY; goto end; } @@ -348,40 +413,75 @@ static CURLcode mqtt_connect(struct Curl_easy *data) static CURLcode mqtt_disconnect(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - struct MQTT *mq = data->req.p.mqtt; - result = mqtt_send(data, (char *)"\xe0\x00", 2); - Curl_safefree(mq->sendleftovers); - return result; + return mqtt_send(data, "\xe0\x00", 2); } -static CURLcode mqtt_verify_connack(struct Curl_easy *data) +static CURLcode mqtt_recv_atleast(struct Curl_easy *data, size_t nbytes) { + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + size_t rlen; CURLcode result; - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - unsigned char readbuf[MQTT_CONNACK_LEN]; - ssize_t nread; - result = Curl_read(data, sockfd, (char *)readbuf, MQTT_CONNACK_LEN, &nread); - if(result) - goto fail; + if(!mq) + return CURLE_FAILED_INIT; + rlen = curlx_dyn_len(&mq->recvbuf); - Curl_debug(data, CURLINFO_HEADER_IN, (char *)readbuf, (size_t)nread); + if(rlen < nbytes) { + unsigned char readbuf[1024]; + ssize_t nread; - /* fixme */ - if(nread < MQTT_CONNACK_LEN) { - result = CURLE_WEIRD_SERVER_REPLY; - goto fail; + DEBUGASSERT(nbytes - rlen < sizeof(readbuf)); + result = Curl_xfer_recv(data, (char *)readbuf, nbytes - rlen, &nread); + if(result) + return result; + DEBUGASSERT(nread >= 0); + if(curlx_dyn_addn(&mq->recvbuf, readbuf, (size_t)nread)) + return CURLE_OUT_OF_MEMORY; + rlen = curlx_dyn_len(&mq->recvbuf); } + return (rlen >= nbytes) ? CURLE_OK : CURLE_AGAIN; +} + +static void mqtt_recv_consume(struct Curl_easy *data, size_t nbytes) +{ + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + DEBUGASSERT(mq); + if(mq) { + size_t rlen = curlx_dyn_len(&mq->recvbuf); + if(rlen <= nbytes) + curlx_dyn_reset(&mq->recvbuf); + else + curlx_dyn_tail(&mq->recvbuf, rlen - nbytes); + } +} + +static CURLcode mqtt_verify_connack(struct Curl_easy *data) +{ + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + CURLcode result; + char *ptr; + + DEBUGASSERT(mq); + if(!mq) + return CURLE_FAILED_INIT; + + result = mqtt_recv_atleast(data, MQTT_CONNACK_LEN); + if(result) + goto fail; /* verify CONNACK */ - if(readbuf[0] != 0x00 || readbuf[1] != 0x00) { + DEBUGASSERT(curlx_dyn_len(&mq->recvbuf) >= MQTT_CONNACK_LEN); + ptr = curlx_dyn_ptr(&mq->recvbuf); + Curl_debug(data, CURLINFO_HEADER_IN, ptr, MQTT_CONNACK_LEN); + + if(ptr[0] != 0x00 || ptr[1] != 0x00) { failf(data, "Expected %02x%02x but got %02x%02x", - 0x00, 0x00, readbuf[0], readbuf[1]); + 0x00, 0x00, ptr[0], ptr[1]); + curlx_dyn_reset(&mq->recvbuf); result = CURLE_WEIRD_SERVER_REPLY; + goto fail; } - + mqtt_recv_consume(data, MQTT_CONNACK_LEN); fail: return result; } @@ -414,12 +514,16 @@ static CURLcode mqtt_subscribe(struct Curl_easy *data) char encodedsize[4]; size_t n; struct connectdata *conn = data->conn; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + + if(!mqtt) + return CURLE_FAILED_INIT; result = mqtt_get_topic(data, &topic, &topiclen); if(result) goto fail; - conn->proto.mqtt.packetid++; + mqtt->packetid++; packetlen = topiclen + 5; /* packetid + topic (has a two byte length field) + 2 bytes topic length + QoS byte */ @@ -434,14 +538,14 @@ static CURLcode mqtt_subscribe(struct Curl_easy *data) packet[0] = MQTT_MSG_SUBSCRIBE; memcpy(&packet[1], encodedsize, n); - packet[1 + n] = (conn->proto.mqtt.packetid >> 8) & 0xff; - packet[2 + n] = conn->proto.mqtt.packetid & 0xff; + packet[1 + n] = (mqtt->packetid >> 8) & 0xff; + packet[2 + n] = mqtt->packetid & 0xff; packet[3 + n] = (topiclen >> 8) & 0xff; packet[4 + n ] = topiclen & 0xff; memcpy(&packet[5 + n], topic, topiclen); packet[5 + n + topiclen] = 0; /* QoS zero */ - result = mqtt_send(data, (char *)packet, packetlen); + result = mqtt_send(data, (const char *)packet, packetlen); fail: free(topic); @@ -454,31 +558,32 @@ static CURLcode mqtt_subscribe(struct Curl_easy *data) */ static CURLcode mqtt_verify_suback(struct Curl_easy *data) { - CURLcode result; + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - unsigned char readbuf[MQTT_SUBACK_LEN]; - ssize_t nread; - struct mqtt_conn *mqtt = &conn->proto.mqtt; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + CURLcode result; + char *ptr; + + if(!mqtt || !mq) + return CURLE_FAILED_INIT; - result = Curl_read(data, sockfd, (char *)readbuf, MQTT_SUBACK_LEN, &nread); + result = mqtt_recv_atleast(data, MQTT_SUBACK_LEN); if(result) goto fail; - Curl_debug(data, CURLINFO_HEADER_IN, (char *)readbuf, (size_t)nread); - - /* fixme */ - if(nread < MQTT_SUBACK_LEN) { + /* verify SUBACK */ + DEBUGASSERT(curlx_dyn_len(&mq->recvbuf) >= MQTT_SUBACK_LEN); + ptr = curlx_dyn_ptr(&mq->recvbuf); + Curl_debug(data, CURLINFO_HEADER_IN, ptr, MQTT_SUBACK_LEN); + + if(((unsigned char)ptr[0]) != ((mqtt->packetid >> 8) & 0xff) || + ((unsigned char)ptr[1]) != (mqtt->packetid & 0xff) || + ptr[2] != 0x00) { + curlx_dyn_reset(&mq->recvbuf); result = CURLE_WEIRD_SERVER_REPLY; goto fail; } - - /* verify SUBACK */ - if(readbuf[0] != ((mqtt->packetid >> 8) & 0xff) || - readbuf[1] != (mqtt->packetid & 0xff) || - readbuf[2] != 0x00) - result = CURLE_WEIRD_SERVER_REPLY; - + mqtt_recv_consume(data, MQTT_SUBACK_LEN); fail: return result; } @@ -497,8 +602,10 @@ static CURLcode mqtt_publish(struct Curl_easy *data) char encodedbytes[4]; curl_off_t postfieldsize = data->set.postfieldsize; - if(!payload) + if(!payload) { + DEBUGF(infof(data, "mqtt_publish without payload, return bad arg")); return CURLE_BAD_FUNCTION_ARGUMENT; + } if(postfieldsize < 0) payloadlen = strlen(payload); else @@ -528,7 +635,7 @@ static CURLcode mqtt_publish(struct Curl_easy *data) i += topiclen; memcpy(&pkt[i], payload, payloadlen); i += payloadlen; - result = mqtt_send(data, (char *)pkt, i); + result = mqtt_send(data, (const char *)pkt, i); fail: free(pkt); @@ -556,7 +663,7 @@ static size_t mqtt_decode_len(unsigned char *buf, return len; } -#ifdef CURLDEBUG +#ifdef DEBUGBUILD static const char *statenames[]={ "MQTT_FIRST", "MQTT_REMAINING_LENGTH", @@ -576,12 +683,15 @@ static void mqstate(struct Curl_easy *data, enum mqttstate nextstate) /* used if state == FIRST */ { struct connectdata *conn = data->conn; - struct mqtt_conn *mqtt = &conn->proto.mqtt; -#ifdef CURLDEBUG + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + DEBUGASSERT(mqtt); + if(!mqtt) + return; +#ifdef DEBUGBUILD infof(data, "%s (from %s) (next is %s)", statenames[state], statenames[mqtt->state], - (state == MQTT_FIRST)? statenames[nextstate] : ""); + (state == MQTT_FIRST) ? statenames[nextstate] : ""); #endif mqtt->state = state; if(state == MQTT_FIRST) @@ -589,21 +699,20 @@ static void mqstate(struct Curl_easy *data, } -/* for the publish packet */ -#define MQTT_HEADER_LEN 5 /* max 5 bytes */ - static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; ssize_t nread; - unsigned char *pkt = (unsigned char *)data->state.buffer; size_t remlen; - struct mqtt_conn *mqtt = &conn->proto.mqtt; - struct MQTT *mq = data->req.p.mqtt; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); unsigned char packet; + DEBUGASSERT(mqtt); + if(!mqtt || !mq) + return CURLE_FAILED_INIT; + switch(mqtt->state) { MQTT_SUBACK_COMING: case MQTT_SUBACK_COMING: @@ -636,7 +745,7 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) /* -- switched state -- */ remlen = mq->remaining_length; - infof(data, "Remaining length: %zd bytes", remlen); + infof(data, "Remaining length: %zu bytes", remlen); if(data->set.max_filesize && (curl_off_t)remlen > data->set.max_filesize) { failf(data, "Maximum file size exceeded"); @@ -647,14 +756,14 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) data->req.bytecount = 0; data->req.size = remlen; mq->npacket = remlen; /* get this many bytes */ - /* FALLTHROUGH */ + FALLTHROUGH(); case MQTT_PUB_REMAIN: { /* read rest of packet, but no more. Cap to buffer size */ - struct SingleRequest *k = &data->req; + char buffer[4*1024]; size_t rest = mq->npacket; - if(rest > (size_t)data->set.buffer_size) - rest = (size_t)data->set.buffer_size; - result = Curl_read(data, sockfd, (char *)pkt, rest, &nread); + if(rest > sizeof(buffer)) + rest = sizeof(buffer); + result = Curl_xfer_recv(data, buffer, rest, &nread); if(result) { if(CURLE_AGAIN == result) { infof(data, "EEEE AAAAGAIN"); @@ -666,18 +775,16 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) result = CURLE_PARTIAL_FILE; goto end; } - Curl_debug(data, CURLINFO_DATA_IN, (char *)pkt, (size_t)nread); - mq->npacket -= nread; - k->bytecount += nread; - Curl_pgrsSetDownloadCounter(data, k->bytecount); + /* we received something */ + mq->lastTime = curlx_now(); /* if QoS is set, message contains packet id */ - - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)pkt, nread); + result = Curl_client_write(data, CLIENTWRITE_BODY, buffer, nread); if(result) goto end; + mq->npacket -= nread; if(!mq->npacket) /* no more PUBLISH payload, back to subscribe wait state */ mqstate(data, MQTT_FIRST, MQTT_PUBWAIT); @@ -694,9 +801,15 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) static CURLcode mqtt_do(struct Curl_easy *data, bool *done) { + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); CURLcode result = CURLE_OK; *done = FALSE; /* unconditionally */ + if(!mq) + return CURLE_FAILED_INIT; + mq->lastTime = curlx_now(); + mq->pingsent = FALSE; + result = mqtt_connect(data); if(result) { failf(data, "Error %d sending MQTT CONNECT request", result); @@ -709,40 +822,78 @@ static CURLcode mqtt_do(struct Curl_easy *data, bool *done) static CURLcode mqtt_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct MQTT *mq = data->req.p.mqtt; + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); (void)status; (void)premature; - Curl_safefree(mq->sendleftovers); + if(mq) { + curlx_dyn_free(&mq->sendbuf); + curlx_dyn_free(&mq->recvbuf); + } return CURLE_OK; } -static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) +/* we ping regularly to avoid being disconnected by the server */ +static CURLcode mqtt_ping(struct Curl_easy *data) { + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct mqtt_conn *mqtt = &conn->proto.mqtt; - struct MQTT *mq = data->req.p.mqtt; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + + if(!mqtt || !mq) + return CURLE_FAILED_INIT; + + if(mqtt->state == MQTT_FIRST && + !mq->pingsent && + data->set.upkeep_interval_ms > 0) { + struct curltime t = curlx_now(); + timediff_t diff = curlx_timediff(t, mq->lastTime); + + if(diff > data->set.upkeep_interval_ms) { + /* 0xC0 is PINGREQ, and 0x00 is remaining length */ + unsigned char packet[2] = { 0xC0, 0x00 }; + size_t packetlen = sizeof(packet); + + result = mqtt_send(data, (char *)packet, packetlen); + if(!result) { + mq->pingsent = TRUE; + } + infof(data, "mqtt_ping: sent ping request."); + } + } + return result; +} + +static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) +{ + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + CURLcode result = CURLE_OK; ssize_t nread; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - unsigned char *pkt = (unsigned char *)data->state.buffer; - unsigned char byte; + unsigned char recvbyte; + struct mqtt_conn *mqtt = Curl_conn_meta_get(data->conn, CURL_META_MQTT_CONN); + + if(!mqtt || !mq) + return CURLE_FAILED_INIT; *done = FALSE; - if(mq->nsend) { + if(curlx_dyn_len(&mq->sendbuf)) { /* send the remainder of an outgoing packet */ - char *ptr = mq->sendleftovers; - result = mqtt_send(data, mq->sendleftovers, mq->nsend); - free(ptr); + result = mqtt_send(data, curlx_dyn_ptr(&mq->sendbuf), + curlx_dyn_len(&mq->sendbuf)); if(result) return result; } + result = mqtt_ping(data); + if(result) + return result; + infof(data, "mqtt_doing: state [%d]", (int) mqtt->state); switch(mqtt->state) { case MQTT_FIRST: /* Read the initial byte only */ - result = Curl_read(data, sockfd, (char *)&mq->firstbyte, 1, &nread); + result = Curl_xfer_recv(data, (char *)&mq->firstbyte, 1, &nread); if(result) break; else if(!nread) { @@ -751,26 +902,30 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) result = CURLE_RECV_ERROR; break; } - Curl_debug(data, CURLINFO_HEADER_IN, (char *)&mq->firstbyte, 1); + Curl_debug(data, CURLINFO_HEADER_IN, (const char *)&mq->firstbyte, 1); + + /* we received something */ + mq->lastTime = curlx_now(); + /* remember the first byte */ mq->npacket = 0; mqstate(data, MQTT_REMAINING_LENGTH, MQTT_NOSTATE); - /* FALLTHROUGH */ + FALLTHROUGH(); case MQTT_REMAINING_LENGTH: do { - result = Curl_read(data, sockfd, (char *)&byte, 1, &nread); - if(!nread) + result = Curl_xfer_recv(data, (char *)&recvbyte, 1, &nread); + if(result || !nread) break; - Curl_debug(data, CURLINFO_HEADER_IN, (char *)&byte, 1); - pkt[mq->npacket++] = byte; - } while((byte & 0x80) && (mq->npacket < 4)); - if(nread && (byte & 0x80)) + Curl_debug(data, CURLINFO_HEADER_IN, (const char *)&recvbyte, 1); + mq->pkt_hd[mq->npacket++] = recvbyte; + } while((recvbyte & 0x80) && (mq->npacket < 4)); + if(!result && nread && (recvbyte & 0x80)) /* MQTT supports up to 127 * 128^0 + 127 * 128^1 + 127 * 128^2 + 127 * 128^3 bytes. server tried to send more */ result = CURLE_WEIRD_SERVER_REPLY; if(result) break; - mq->remaining_length = mqtt_decode_len(&pkt[0], mq->npacket, NULL); + mq->remaining_length = mqtt_decode_len(mq->pkt_hd, mq->npacket, NULL); mq->npacket = 0; if(mq->remaining_length) { mqstate(data, mqtt->nextstate, MQTT_NOSTATE); @@ -782,6 +937,13 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) infof(data, "Got DISCONNECT"); *done = TRUE; } + + /* ping response */ + if(mq->firstbyte == MQTT_MSG_PINGRESP) { + infof(data, "Received ping response."); + mq->pingsent = FALSE; + mqstate(data, MQTT_FIRST, MQTT_PUBWAIT); + } break; case MQTT_CONNACK: result = mqtt_verify_connack(data); diff --git a/Utilities/cmcurl/lib/mqtt.h b/Utilities/cmcurl/lib/mqtt.h index 63961366fcb..8fb8a33c022 100644 --- a/Utilities/cmcurl/lib/mqtt.h +++ b/Utilities/cmcurl/lib/mqtt.h @@ -28,34 +28,4 @@ extern const struct Curl_handler Curl_handler_mqtt; #endif -enum mqttstate { - MQTT_FIRST, /* 0 */ - MQTT_REMAINING_LENGTH, /* 1 */ - MQTT_CONNACK, /* 2 */ - MQTT_SUBACK, /* 3 */ - 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_NOSTATE /* 7 - never used an actual state */ -}; - -struct mqtt_conn { - enum mqttstate state; - enum mqttstate nextstate; /* switch to this after remaining length is - done */ - unsigned int packetid; -}; - -/* protocol-specific transfer-related data */ -struct MQTT { - char *sendleftovers; - size_t nsend; /* size of sendleftovers */ - - /* when receiving */ - size_t npacket; /* byte counter */ - unsigned char firstbyte; - size_t remaining_length; -}; - #endif /* HEADER_CURL_MQTT_H */ diff --git a/Utilities/cmcurl/lib/multi.c b/Utilities/cmcurl/lib/multi.c index d1d32b79368..64eb1ae8540 100644 --- a/Utilities/cmcurl/lib/multi.c +++ b/Utilities/cmcurl/lib/multi.c @@ -36,45 +36,35 @@ #include "share.h" #include "psl.h" #include "multiif.h" +#include "multi_ev.h" #include "sendf.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "http.h" #include "select.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "speedcheck.h" #include "conncache.h" #include "multihandle.h" #include "sigpipe.h" #include "vtls/vtls.h" +#include "vtls/vtls_scache.h" #include "http_proxy.h" #include "http2.h" #include "socketpair.h" #include "socks.h" +#include "urlapi-int.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#ifdef __APPLE__ - -#define wakeup_write write -#define wakeup_read read -#define wakeup_close close -#define wakeup_create pipe - -#else /* __APPLE__ */ - -#define wakeup_write swrite -#define wakeup_read sread -#define wakeup_close sclose -#define wakeup_create(p) Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, p) - -#endif /* __APPLE__ */ +/* initial multi->xfers table size for a full multi */ +#define CURL_XFER_TABLE_SIZE 512 /* CURL_SOCKET_HASH_TABLE_SIZE should be a prime number. Increasing it from 97 - to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every - CURL handle takes 45-50 K memory, therefore this 3K are not significant. + to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every + curl handle takes 6K memory, therefore this 3K are not significant. */ #ifndef CURL_SOCKET_HASH_TABLE_SIZE #define CURL_SOCKET_HASH_TABLE_SIZE 911 @@ -88,6 +78,10 @@ #define CURL_DNS_HASH_SIZE 71 #endif +#ifndef CURL_TLS_SESSION_SIZE +#define CURL_TLS_SESSION_SIZE 25 +#endif + #define CURL_MULTI_HANDLE 0x000bab1e #ifdef DEBUGBUILD @@ -95,42 +89,25 @@ * are not NULL, but no longer have the MAGIC touch. This gives * us early warning on things only discovered by valgrind otherwise. */ #define GOOD_MULTI_HANDLE(x) \ - (((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \ + (((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \ (DEBUGASSERT(!(x)), FALSE)) #else #define GOOD_MULTI_HANDLE(x) \ ((x) && (x)->magic == CURL_MULTI_HANDLE) #endif -static CURLMcode singlesocket(struct Curl_multi *multi, - struct Curl_easy *data); +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data); static CURLMcode add_next_timeout(struct curltime now, struct Curl_multi *multi, struct Curl_easy *d); static CURLMcode multi_timeout(struct Curl_multi *multi, + struct curltime *expire_time, long *timeout_ms); static void process_pending_handles(struct Curl_multi *multi); - +static void multi_xfer_bufs_free(struct Curl_multi *multi); #ifdef DEBUGBUILD -static const char * const statename[]={ - "INIT", - "PENDING", - "CONNECT", - "RESOLVING", - "CONNECTING", - "TUNNELING", - "PROTOCONNECT", - "PROTOCONNECTING", - "DO", - "DOING", - "DOING_MORE", - "DID", - "PERFORMING", - "RATELIMITING", - "DONE", - "COMPLETED", - "MSGSENT", -}; +static void multi_xfer_tbl_dump(struct Curl_multi *multi); #endif /* function pointer called once when switching TO a state */ @@ -147,7 +124,7 @@ static void init_completed(struct Curl_easy *data) { /* this is a completed transfer */ - /* Important: reset the conn pointer so that we don't point to memory + /* Important: reset the conn pointer so that we do not point to memory that could be freed anytime */ Curl_detach_connection(data); Curl_expire_clear(data); /* stop all timers */ @@ -164,6 +141,7 @@ static void mstate(struct Curl_easy *data, CURLMstate state static const init_multistate_func finit[MSTATE_LAST] = { NULL, /* INIT */ NULL, /* PENDING */ + NULL, /* SETUP */ Curl_init_CONNECT, /* CONNECT */ NULL, /* RESOLVING */ NULL, /* CONNECTING */ @@ -181,35 +159,28 @@ static void mstate(struct Curl_easy *data, CURLMstate state NULL /* MSGSENT */ }; -#if defined(DEBUGBUILD) && defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) lineno; -#endif - if(oldstate == state) - /* don't bother when the new state is the same as the old state */ + /* do not bother when the new state is the same as the old state */ return; - data->mstate = state; - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(data->mstate >= MSTATE_PENDING && - data->mstate < MSTATE_COMPLETED) { - long connection_id = -5000; - - if(data->conn) - connection_id = data->conn->connection_id; - - infof(data, - "STATE: %s => %s handle %p; line %d (connection #%ld)", - statename[oldstate], statename[data->mstate], - (void *)data, lineno, connection_id); - } +#ifdef DEBUGBUILD + CURL_TRC_M(data, "-> [%s] (line %d)", CURL_MSTATE_NAME(state), lineno); +#else + CURL_TRC_M(data, "-> [%s]", CURL_MSTATE_NAME(state)); #endif + data->mstate = state; + if(state == MSTATE_COMPLETED) { - /* changing to COMPLETED means there's one less easy handle 'alive' */ - DEBUGASSERT(data->multi->num_alive > 0); - data->multi->num_alive--; + /* changing to COMPLETED means it is in process and needs to go */ + DEBUGASSERT(Curl_uint_bset_contains(&data->multi->process, data->mid)); + Curl_uint_bset_remove(&data->multi->process, data->mid); + Curl_uint_bset_remove(&data->multi->pending, data->mid); /* to be sure */ + + if(Curl_uint_bset_empty(&data->multi->process)) { + /* free the transfer buffer when we have no more active transfers */ + multi_xfer_bufs_free(data->multi); + } } /* if this state has an init-function, run it */ @@ -223,167 +194,16 @@ static void mstate(struct Curl_easy *data, CURLMstate state #define multistate(x,y) mstate(x,y, __LINE__) #endif -/* - * We add one of these structs to the sockhash for each socket - */ - -struct Curl_sh_entry { - struct Curl_hash transfers; /* hash of transfers using this socket */ - unsigned int action; /* what combined action READ/WRITE this socket waits - for */ - unsigned int users; /* number of transfers using this */ - void *socketp; /* settable by users with curl_multi_assign() */ - unsigned int readers; /* this many transfers want to read */ - unsigned int writers; /* this many transfers want to write */ -}; -/* bits for 'action' having no bits means this socket is not expecting any - action */ -#define SH_READ 1 -#define SH_WRITE 2 - -/* look up a given socket in the socket hash, skip invalid sockets */ -static struct Curl_sh_entry *sh_getentry(struct Curl_hash *sh, - curl_socket_t s) -{ - if(s != CURL_SOCKET_BAD) { - /* only look for proper sockets */ - return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); - } - return NULL; -} - -#define TRHASH_SIZE 13 -static size_t trhash(void *key, size_t key_length, size_t slots_num) -{ - size_t keyval = (size_t)*(struct Curl_easy **)key; - (void) key_length; - - return (keyval % slots_num); -} - -static size_t trhash_compare(void *k1, size_t k1_len, void *k2, size_t k2_len) -{ - (void)k1_len; - (void)k2_len; - - return *(struct Curl_easy **)k1 == *(struct Curl_easy **)k2; -} - -static void trhash_dtor(void *nada) -{ - (void)nada; -} - -/* - * The sockhash has its own separate subhash in each entry that need to be - * safely destroyed first. - */ -static void sockhash_destroy(struct Curl_hash *h) -{ - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - - DEBUGASSERT(h); - Curl_hash_start_iterate(h, &iter); - he = Curl_hash_next_element(&iter); - while(he) { - struct Curl_sh_entry *sh = (struct Curl_sh_entry *)he->ptr; - Curl_hash_destroy(&sh->transfers); - he = Curl_hash_next_element(&iter); - } - Curl_hash_destroy(h); -} - - -/* make sure this socket is present in the hash for this handle */ -static struct Curl_sh_entry *sh_addentry(struct Curl_hash *sh, - curl_socket_t s) -{ - struct Curl_sh_entry *there = sh_getentry(sh, s); - struct Curl_sh_entry *check; - - if(there) { - /* it is present, return fine */ - return there; - } - - /* not present, add it */ - check = calloc(1, sizeof(struct Curl_sh_entry)); - if(!check) - return NULL; /* major failure */ - - Curl_hash_init(&check->transfers, TRHASH_SIZE, trhash, trhash_compare, - trhash_dtor); - - /* make/add new hash entry */ - if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { - Curl_hash_destroy(&check->transfers); - free(check); - return NULL; /* major failure */ - } - - return check; /* things are good in sockhash land */ -} - - -/* delete the given socket + handle from the hash */ -static void sh_delentry(struct Curl_sh_entry *entry, - struct Curl_hash *sh, curl_socket_t s) -{ - Curl_hash_destroy(&entry->transfers); - - /* We remove the hash entry. This will end up in a call to - sh_freeentry(). */ - Curl_hash_delete(sh, (char *)&s, sizeof(curl_socket_t)); -} -/* - * free a sockhash entry - */ -static void sh_freeentry(void *freethis) +/* multi->proto_hash destructor. Should never be called as elements + * MUST be added with their own destructor */ +static void ph_freeentry(void *p) { - struct Curl_sh_entry *p = (struct Curl_sh_entry *) freethis; - - free(p); -} - -static size_t fd_key_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)); -} - -static size_t hash_fd(void *key, size_t key_length, size_t slots_num) -{ - curl_socket_t fd = *((curl_socket_t *) key); - (void) key_length; - - return (fd % slots_num); -} - -/* - * sh_init() creates a new socket hash and returns the handle for it. - * - * Quote from README.multi_socket: - * - * "Some tests at 7000 and 9000 connections showed that the socket hash lookup - * is somewhat of a bottle neck. Its current implementation may be a bit too - * limiting. It simply has a fixed-size array, and on each entry in the array - * it has a linked list with entries. So the hash only checks which list to - * scan through. The code I had used so for used a list with merely 7 slots - * (as that is what the DNS hash uses) but with 7000 connections that would - * make an average of 1000 nodes in each list to run through. I upped that to - * 97 slots (I believe a prime is suitable) and noticed a significant speed - * increase. I need to reconsider the hash implementation or use a rather - * large default value like this. At 9000 connections I was still below 10us - * per call." - * - */ -static void sh_init(struct Curl_hash *hash, int hashsize) -{ - Curl_hash_init(hash, hashsize, hash_fd, fd_key_compare, - sh_freeentry); + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); } /* @@ -394,13 +214,14 @@ static void sh_init(struct Curl_hash *hash, int hashsize) */ static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) { - Curl_llist_insert_next(&multi->msglist, multi->msglist.tail, msg, - &msg->list); + Curl_llist_append(&multi->msglist, msg, &msg->list); } -struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ - int chashsize, /* connection hash */ - int dnssize) /* dns hash */ +struct Curl_multi *Curl_multi_handle(unsigned int xfer_table_size, + size_t ev_hashsize, /* event hash */ + size_t chashsize, /* connection hash */ + size_t dnssize, /* dns hash */ + size_t sesssize) /* TLS session cache */ { struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); @@ -409,22 +230,46 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ multi->magic = CURL_MULTI_HANDLE; - Curl_init_dnscache(&multi->hostcache, dnssize); + Curl_dnscache_init(&multi->dnscache, dnssize); + Curl_multi_ev_init(multi, ev_hashsize); + Curl_uint_tbl_init(&multi->xfers, NULL); + Curl_uint_bset_init(&multi->process); + Curl_uint_bset_init(&multi->pending); + Curl_uint_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); - sh_init(&multi->sockhash, hashsize); + multi->multiplexing = TRUE; + multi->max_concurrent_streams = 100; + multi->last_timeout_ms = -1; - if(Curl_conncache_init(&multi->conn_cache, chashsize)) + if(Curl_uint_bset_resize(&multi->process, xfer_table_size) || + Curl_uint_bset_resize(&multi->pending, xfer_table_size) || + Curl_uint_bset_resize(&multi->msgsent, xfer_table_size) || + Curl_uint_tbl_resize(&multi->xfers, xfer_table_size)) goto error; - Curl_llist_init(&multi->msglist, NULL); - Curl_llist_init(&multi->pending, NULL); - Curl_llist_init(&multi->msgsent, NULL); + multi->admin = curl_easy_init(); + if(!multi->admin) + goto error; + /* Initialize admin handle to operate inside this multi */ + multi->admin->multi = multi; + multi->admin->state.internal = TRUE; + Curl_llist_init(&multi->admin->state.timeoutlist, NULL); +#ifdef DEBUGBUILD + if(getenv("CURL_DEBUG")) + multi->admin->set.verbose = TRUE; +#endif + Curl_uint_tbl_add(&multi->xfers, multi->admin, &multi->admin->mid); - multi->multiplexing = TRUE; + if(Curl_cshutdn_init(&multi->cshutdn, multi)) + goto error; - /* -1 means it not set by user, use the default value */ - multi->maxconnects = -1; - multi->max_concurrent_streams = 100; + Curl_cpool_init(&multi->cpool, multi->admin, NULL, chashsize); + + if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache)) + goto error; #ifdef USE_WINSOCK multi->wsa_event = WSACreateEvent(); @@ -432,14 +277,7 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ goto error; #else #ifdef ENABLE_WAKEUP - if(wakeup_create(multi->wakeup_pair) < 0) { - multi->wakeup_pair[0] = CURL_SOCKET_BAD; - multi->wakeup_pair[1] = CURL_SOCKET_BAD; - } - else if(curlx_nonblock(multi->wakeup_pair[0], TRUE) < 0 || - curlx_nonblock(multi->wakeup_pair[1], TRUE) < 0) { - wakeup_close(multi->wakeup_pair[0]); - wakeup_close(multi->wakeup_pair[1]); + if(wakeup_create(multi->wakeup_pair, TRUE) < 0) { multi->wakeup_pair[0] = CURL_SOCKET_BAD; multi->wakeup_pair[1] = CURL_SOCKET_BAD; } @@ -450,70 +288,92 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ error: - sockhash_destroy(&multi->sockhash); - Curl_hash_destroy(&multi->hostcache); - Curl_conncache_destroy(&multi->conn_cache); + Curl_multi_ev_cleanup(multi); + Curl_hash_destroy(&multi->proto_hash); + Curl_dnscache_destroy(&multi->dnscache); + Curl_cpool_destroy(&multi->cpool); + Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); + Curl_ssl_scache_destroy(multi->ssl_scache); + if(multi->admin) { + multi->admin->multi = NULL; + Curl_close(&multi->admin); + } + + Curl_uint_bset_destroy(&multi->process); + Curl_uint_bset_destroy(&multi->pending); + Curl_uint_bset_destroy(&multi->msgsent); + Curl_uint_tbl_destroy(&multi->xfers); + free(multi); return NULL; } -struct Curl_multi *curl_multi_init(void) +CURLM *curl_multi_init(void) { - return Curl_multi_handle(CURL_SOCKET_HASH_TABLE_SIZE, + return Curl_multi_handle(CURL_XFER_TABLE_SIZE, + CURL_SOCKET_HASH_TABLE_SIZE, CURL_CONNECTION_HASH_SIZE, - CURL_DNS_HASH_SIZE); + CURL_DNS_HASH_SIZE, + CURL_TLS_SESSION_SIZE); } -/* returns TRUE if the easy handle is supposed to be present in the main link - list */ -static bool in_main_list(struct Curl_easy *data) +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +static void multi_warn_debug(struct Curl_multi *multi, struct Curl_easy *data) { - return ((data->mstate != MSTATE_PENDING) && - (data->mstate != MSTATE_MSGSENT)); + if(!multi->warned) { + infof(data, "!!! WARNING !!!"); + infof(data, "This is a debug build of libcurl, " + "do not use in production."); + multi->warned = TRUE; + } } +#else +#define multi_warn_debug(x,y) Curl_nop_stmt +#endif -static void link_easy(struct Curl_multi *multi, - struct Curl_easy *data) + +static CURLMcode multi_xfers_add(struct Curl_multi *multi, + struct Curl_easy *data) { - /* We add the new easy entry last in the list. */ - data->next = NULL; /* end of the line */ - if(multi->easyp) { - struct Curl_easy *last = multi->easylp; - last->next = data; - data->prev = last; - multi->easylp = data; /* the new last node */ + /* We want `multi->xfers` to have "sufficient" free rows, so that we do + * have to reuse the `mid` from a just removed easy right away. + * Since uint_tbl and uint_bset is quite memory efficient, + * regard less than 25% free as insufficient. + * (for low capacities, e.g. multi_easy, 4 or less). */ + unsigned int capacity = Curl_uint_tbl_capacity(&multi->xfers); + unsigned int unused = capacity - Curl_uint_tbl_count(&multi->xfers); + unsigned int min_unused = CURLMAX(capacity >> 2, 4); + + if(unused <= min_unused) { + /* make it a 64 multiple, since our bitsets frow by that and + * small (easy_multi) grows to at least 64 on first resize. */ + unsigned int newsize = (((capacity + min_unused) + 63) / 64) * 64; + DEBUGASSERT(newsize > capacity); + /* Grow the bitsets first. Should one fail, we do not need + * to downsize the already resized ones. The sets continue + * to work properly when larger than the table, but not + * the other way around. */ + if(Curl_uint_bset_resize(&multi->process, newsize) || + Curl_uint_bset_resize(&multi->pending, newsize) || + Curl_uint_bset_resize(&multi->msgsent, newsize) || + Curl_uint_tbl_resize(&multi->xfers, newsize)) + return CURLM_OUT_OF_MEMORY; + CURL_TRC_M(data, "increased xfer table size to %u", newsize); } - else { - /* first node, make prev NULL! */ - data->prev = NULL; - multi->easylp = multi->easyp = data; /* both first and last */ + /* Insert the easy into the table now that MUST have room for it */ + if(!Curl_uint_tbl_add(&multi->xfers, data, &data->mid)) { + DEBUGASSERT(0); + return CURLM_OUT_OF_MEMORY; } -} - -/* unlink the given easy handle from the linked list of easy handles */ -static void unlink_easy(struct Curl_multi *multi, - struct Curl_easy *data) -{ - /* make the previous node point to our next */ - if(data->prev) - data->prev->next = data->next; - else - multi->easyp = data->next; /* point to first node */ - - /* make our next point to our previous node */ - if(data->next) - data->next->prev = data->prev; - else - multi->easylp = data->prev; /* point to last node */ - - data->prev = data->next = NULL; + return CURLM_OK; } -CURLMcode curl_multi_add_handle(struct Curl_multi *multi, - struct Curl_easy *data) +CURLMcode curl_multi_add_handle(CURLM *m, CURL *d) { CURLMcode rc; + struct Curl_multi *multi = m; + struct Curl_easy *data = d; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -533,24 +393,42 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, if(multi->dead) { /* a "dead" handle cannot get added transfers while any existing easy handles are still alive - but if there are none alive anymore, it is - fine to start over and unmark the "deadness" of this handle */ - if(multi->num_alive) + fine to start over and unmark the "deadness" of this handle. + This means only the admin handle MUST be present. */ + if((Curl_uint_tbl_count(&multi->xfers) != 1) || + !Curl_uint_tbl_contains(&multi->xfers, 0)) return CURLM_ABORTED_BY_CALLBACK; multi->dead = FALSE; + Curl_uint_bset_clear(&multi->process); + Curl_uint_bset_clear(&multi->pending); + Curl_uint_bset_clear(&multi->msgsent); } + if(data->multi_easy) { + /* if this easy handle was previously used for curl_easy_perform(), there + is a private multi handle here that we can kill */ + curl_multi_cleanup(data->multi_easy); + data->multi_easy = NULL; + } + + /* Insert the easy into the multi->xfers table, assigning it a `mid`. */ + if(multi_xfers_add(multi, data)) + return CURLM_OUT_OF_MEMORY; + /* Initialize timeout list for this handle */ Curl_llist_init(&data->state.timeoutlist, NULL); /* - * No failure allowed in this function beyond this point. And no - * modification of easy nor multi handle allowed before this except for - * potential multi's connection cache growing which won't be undone in this - * function no matter what. + * No failure allowed in this function beyond this point. No modification of + * easy nor multi handle allowed before this except for potential multi's + * connection pool growing which will not be undone in this function no + * matter what. */ if(data->set.errorbuffer) data->set.errorbuffer[0] = 0; + data->state.os_errno = 0; + /* make the Curl_easy refer back to this multi handle - before Curl_expire() is called. */ data->multi = multi; @@ -563,40 +441,17 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, happen. */ Curl_expire(data, 0, EXPIRE_RUN_NOW); - /* A somewhat crude work-around for a little glitch in Curl_update_timer() - that happens if the lastcall time is set to the same time when the handle - is removed as when the next handle is added, as then the check in - Curl_update_timer() that prevents calling the application multiple times - with the same timer info will not trigger and then the new handle's - timeout will not be notified to the app. - - The work-around is thus simply to clear the 'lastcall' variable to force - Curl_update_timer() to always trigger a callback to the app when a new - easy handle is added */ - memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); - rc = Curl_update_timer(multi); - if(rc) + if(rc) { + data->multi = NULL; /* not anymore */ + Curl_uint_tbl_remove(&multi->xfers, data->mid); + data->mid = UINT_MAX; return rc; + } /* set the easy handle */ multistate(data, MSTATE_INIT); - /* for multi interface connections, we share DNS cache automatically if the - easy handle's one is currently not set. */ - if(!data->dns.hostcache || - (data->dns.hostcachetype == HCACHE_NONE)) { - data->dns.hostcache = &multi->hostcache; - data->dns.hostcachetype = HCACHE_MULTI; - } - - /* Point to the shared or multi handle connection cache */ - if(data->share && (data->share->specifier & (1<< CURL_LOCK_DATA_CONNECT))) - data->state.conn_cache = &data->share->conn_cache; - else - data->state.conn_cache = &multi->conn_cache; - data->state.lastconnect_id = -1; - #ifdef USE_LIBPSL /* Do the same for PSL. */ if(data->share && (data->share->specifier & (1 << CURL_LOCK_DATA_PSL))) @@ -605,26 +460,25 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, data->psl = &multi->psl; #endif - link_easy(multi, data); + /* add the easy handle to the process set */ + Curl_uint_bset_add(&multi->process, data->mid); + ++multi->xfers_alive; - /* increase the node-counter */ - multi->num_easy++; + Curl_cpool_xfer_init(data); + multi_warn_debug(multi, data); - /* increase the alive-counter */ - multi->num_alive++; - - CONNCACHE_LOCK(data); - /* The closure handle only ever has default timeouts set. To improve the + /* The admin handle only ever has default timeouts set. To improve the state somewhat we clone the timeouts from each added handle so that the - closure handle always has the same timeouts as the most recently added + admin handle always has the same timeouts as the most recently added easy handle. */ - data->state.conn_cache->closure_handle->set.timeout = data->set.timeout; - data->state.conn_cache->closure_handle->set.server_response_timeout = + multi->admin->set.timeout = data->set.timeout; + multi->admin->set.server_response_timeout = data->set.server_response_timeout; - data->state.conn_cache->closure_handle->set.no_signal = - data->set.no_signal; - CONNCACHE_UNLOCK(data); + 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), + Curl_uint_tbl_count(&multi->xfers)); return CURLM_OK; } @@ -644,24 +498,112 @@ static void debug_print_sock_hash(void *p) } #endif +struct multi_done_ctx { + BIT(premature); +}; + +static void multi_done_locked(struct connectdata *conn, + struct Curl_easy *data, + void *userdata) +{ + struct multi_done_ctx *mdctx = userdata; + + Curl_detach_connection(data); + + CURL_TRC_M(data, "multi_done_locked, in use=%u", + Curl_uint_spbset_count(&conn->xfers_attached)); + if(CONN_INUSE(conn)) { + /* Stop if still used. */ + CURL_TRC_M(data, "Connection still in use %u, no more multi_done now!", + Curl_uint_spbset_count(&conn->xfers_attached)); + return; + } + + data->state.done = TRUE; /* called just now! */ + data->state.recent_conn_id = conn->connection_id; + + Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */ + Curl_resolv_unlink(data, &data->state.dns[1]); + Curl_dnscache_prune(data); + + /* if data->set.reuse_forbid is TRUE, it means the libcurl client has + forced us to close this connection. This is ignored for requests taking + place in a NTLM/NEGOTIATE authentication handshake + + if conn->bits.close is TRUE, it means that the connection should be + closed in spite of all our efforts to be nice, due to protocol + restrictions in our or the server's end + + if premature is TRUE, it means this connection was said to be DONE before + the entire request operation is complete and thus we cannot know in what + state it is for reusing, so we are forced to close it. In a perfect world + we can add code that keep track of if we really must close it here or not, + but currently we have no such detail knowledge. + */ + + if((data->set.reuse_forbid +#if defined(USE_NTLM) + && !(conn->http_ntlm_state == NTLMSTATE_TYPE2 || + conn->proxy_ntlm_state == NTLMSTATE_TYPE2) +#endif +#if defined(USE_SPNEGO) + && !(conn->http_negotiate_state == GSS_AUTHRECV || + conn->proxy_negotiate_state == GSS_AUTHRECV) +#endif + ) || conn->bits.close + || (mdctx->premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) { + CURL_TRC_M(data, "multi_done, not reusing connection=%" + FMT_OFF_T ", forbid=%d" + ", close=%d, premature=%d, conn_multiplex=%d", + conn->connection_id, data->set.reuse_forbid, + conn->bits.close, mdctx->premature, + Curl_conn_is_multiplex(conn, FIRSTSOCKET)); + connclose(conn, "disconnecting"); + Curl_conn_terminate(data, conn, mdctx->premature); + } + else { + /* the connection is no longer in use by any transfer */ + if(Curl_cpool_conn_now_idle(data, conn)) { + /* connection kept in the cpool */ + const char *host = +#ifndef CURL_DISABLE_PROXY + conn->bits.socksproxy ? + conn->socks_proxy.host.dispname : + conn->bits.httpproxy ? conn->http_proxy.host.dispname : +#endif + conn->bits.conn_to_host ? conn->conn_to_host.dispname : + conn->host.dispname; + data->state.lastconnect_id = conn->connection_id; + infof(data, "Connection #%" FMT_OFF_T " to host %s left intact", + conn->connection_id, host); + } + else { + /* connection was removed from the cpool and destroyed. */ + data->state.lastconnect_id = -1; + } + } +} + static CURLcode multi_done(struct Curl_easy *data, CURLcode status, /* an error if this is called after an error was detected */ bool premature) { - CURLcode result; + CURLcode result, r2; struct connectdata *conn = data->conn; - unsigned int i; + struct multi_done_ctx mdctx; - DEBUGF(infof(data, "multi_done: status: %d prem: %d done: %d", - (int)status, (int)premature, data->state.done)); + memset(&mdctx, 0, sizeof(mdctx)); + + CURL_TRC_M(data, "multi_done: status: %d prem: %d done: %d", + (int)status, (int)premature, data->state.done); if(data->state.done) /* Stop if multi_done() has already been called */ return CURLE_OK; - /* Stop the resolver and free its own resources (but not dns_entry yet). */ - Curl_resolver_kill(data); + /* Shut down any ongoing async resolver operation. */ + Curl_async_shutdown(data); /* Cleanup possible redirect junk */ Curl_safefree(data->req.newurl); @@ -671,17 +613,18 @@ static CURLcode multi_done(struct Curl_easy *data, case CURLE_ABORTED_BY_CALLBACK: case CURLE_READ_ERROR: case CURLE_WRITE_ERROR: - /* When we're aborted due to a callback return code it basically have to - be counted as premature as there is trouble ahead if we don't. We have + /* When we are aborted due to a callback return code it basically have to + be counted as premature as there is trouble ahead if we do not. We have many callbacks and protocols work differently, we could potentially do this more fine-grained in the future. */ premature = TRUE; + FALLTHROUGH(); default: break; } /* this calls the protocol-specific function pointer previously set */ - if(conn->handler->done) + if(conn->handler->done && (data->mstate >= MSTATE_PROTOCONNECT)) result = conn->handler->done(data, status, premature); else result = status; @@ -694,127 +637,48 @@ static CURLcode multi_done(struct Curl_easy *data, result = CURLE_ABORTED_BY_CALLBACK; } + /* Make sure that transfer client writes are really done now. */ + r2 = Curl_xfer_write_done(data, premature); + if(r2 && !result) + result = r2; + /* Inform connection filters that this transfer is done */ Curl_conn_ev_data_done(data, premature); process_pending_handles(data->multi); /* connection / multiplex */ - Curl_safefree(data->state.ulbuf); + if(!result) + result = Curl_req_done(&data->req, data, premature); - /* if the transfer was completed in a paused state there can be buffered - data left to free */ - for(i = 0; i < data->state.tempcount; i++) { - Curl_dyn_free(&data->state.tempwrite[i].b); - } - data->state.tempcount = 0; - - CONNCACHE_LOCK(data); - Curl_detach_connection(data); - if(CONN_INUSE(conn)) { - /* Stop if still used. */ - CONNCACHE_UNLOCK(data); - DEBUGF(infof(data, "Connection still in use %zu, " - "no more multi_done now!", - conn->easyq.size)); - return CURLE_OK; - } - - data->state.done = TRUE; /* called just now! */ - - if(conn->dns_entry) { - Curl_resolv_unlock(data, conn->dns_entry); /* done with this */ - conn->dns_entry = NULL; - } - Curl_hostcache_prune(data); - - /* if data->set.reuse_forbid is TRUE, it means the libcurl client has - forced us to close this connection. This is ignored for requests taking - place in a NTLM/NEGOTIATE authentication handshake - - if conn->bits.close is TRUE, it means that the connection should be - closed in spite of all our efforts to be nice, due to protocol - restrictions in our or the server's end - - if premature is TRUE, it means this connection was said to be DONE before - the entire request operation is complete and thus we can't know in what - state it is for re-using, so we're forced to close it. In a perfect world - we can add code that keep track of if we really must close it here or not, - but currently we have no such detail knowledge. - */ - - if((data->set.reuse_forbid -#if defined(USE_NTLM) - && !(conn->http_ntlm_state == NTLMSTATE_TYPE2 || - conn->proxy_ntlm_state == NTLMSTATE_TYPE2) -#endif -#if defined(USE_SPNEGO) - && !(conn->http_negotiate_state == GSS_AUTHRECV || - conn->proxy_negotiate_state == GSS_AUTHRECV) -#endif - ) || conn->bits.close - || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) { - DEBUGF(infof(data, "multi_done, not re-using connection=%ld, forbid=%d" - ", close=%d, premature=%d, conn_multiplex=%d", - conn->connection_id, - data->set.reuse_forbid, conn->bits.close, premature, - Curl_conn_is_multiplex(conn, FIRSTSOCKET))); - connclose(conn, "disconnecting"); - Curl_conncache_remove_conn(data, conn, FALSE); - CONNCACHE_UNLOCK(data); - Curl_disconnect(data, conn, premature); - } - else { - char buffer[256]; - const char *host = -#ifndef CURL_DISABLE_PROXY - conn->bits.socksproxy ? - conn->socks_proxy.host.dispname : - conn->bits.httpproxy ? conn->http_proxy.host.dispname : -#endif - conn->bits.conn_to_host ? conn->conn_to_host.dispname : - conn->host.dispname; - /* create string before returning the connection */ - long connection_id = conn->connection_id; - msnprintf(buffer, sizeof(buffer), - "Connection #%ld to host %s left intact", - connection_id, host); - /* the connection is no longer in use by this transfer */ - CONNCACHE_UNLOCK(data); - if(Curl_conncache_return_conn(data, conn)) { - /* remember the most recently used connection */ - data->state.lastconnect_id = connection_id; - infof(data, "%s", buffer); - } - else - data->state.lastconnect_id = -1; - } + /* Under the potential connection pool's share lock, decide what to + * do with the transfer's connection. */ + mdctx.premature = premature; + Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx); - Curl_safefree(data->state.buffer); + /* flush the netrc cache */ + Curl_netrc_cleanup(&data->state.netrc); return result; } -static int close_connect_only(struct Curl_easy *data, - struct connectdata *conn, void *param) +static void close_connect_only(struct connectdata *conn, + struct Curl_easy *data, + void *userdata) { - (void)param; - if(data->state.lastconnect_id != conn->connection_id) - return 0; - - if(!conn->connect_only) - return 1; - - connclose(conn, "Removing connect-only easy handle"); - - return 1; + (void)userdata; + (void)data; + if(conn->connect_only) + connclose(conn, "Removing connect-only easy handle"); } -CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, - struct Curl_easy *data) +CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) { - struct Curl_easy *easy = data; + struct Curl_multi *multi = m; + struct Curl_easy *data = d; bool premature; - struct Curl_llist_element *e; + struct Curl_llist_node *e; CURLMcode rc; + bool removed_timer = FALSE; + unsigned int mid; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) @@ -832,23 +696,26 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, if(data->multi != multi) return CURLM_BAD_EASY_HANDLE; + if(data->mid == UINT_MAX) { + DEBUGASSERT(0); + return CURLM_INTERNAL_ERROR; + } + if(Curl_uint_tbl_get(&multi->xfers, data->mid) != data) { + DEBUGASSERT(0); + return CURLM_INTERNAL_ERROR; + } + if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - premature = (data->mstate < MSTATE_COMPLETED) ? TRUE : FALSE; + premature = (data->mstate < MSTATE_COMPLETED); /* If the 'state' is not INIT or COMPLETED, we might need to do something nice to put the easy_handle in a good known state when this returns. */ - if(premature) { - /* this handle is "alive" so we need to count down the total number of - alive connections when this is removed */ - multi->num_alive--; - } - if(data->conn && data->mstate > MSTATE_DO && data->mstate < MSTATE_COMPLETED) { - /* Set connection owner so that the DONE function closes it. We can + /* Set connection owner so that the DONE function closes it. We can safely do this here since connection is killed. */ streamclose(data->conn, "Removed with partial response"); } @@ -857,7 +724,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* multi_done() clears the association between the easy handle and the connection. - Note that this ignores the return code simply because there's + Note that this ignores the return code simply because there is nothing really useful to do with it anyway! */ (void)multi_done(data, data->result, premature); } @@ -865,45 +732,27 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* The timer must be shut down before data->multi is set to NULL, else the timenode will remain in the splay tree after curl_easy_cleanup is called. Do it after multi_done() in case that sets another time! */ - Curl_expire_clear(data); + removed_timer = Curl_expire_clear(data); - if(data->connect_queue.ptr) { - /* the handle is in the pending or msgsent lists, so go ahead and remove - it */ - if(data->mstate == MSTATE_PENDING) - Curl_llist_remove(&multi->pending, &data->connect_queue, NULL); - else - Curl_llist_remove(&multi->msgsent, &data->connect_queue, NULL); - } - if(in_main_list(data)) - unlink_easy(multi, data); - - if(data->dns.hostcachetype == HCACHE_MULTI) { - /* stop using the multi handle's DNS cache, *after* the possible - multi_done() call above */ - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; - } + /* If in `msgsent`, it was deducted from `multi->xfers_alive` already. */ + if(!Curl_uint_bset_contains(&multi->msgsent, data->mid)) + --multi->xfers_alive; Curl_wildcard_dtor(&data->wildcard); - /* change state without using multistate(), only to make singlesocket() do - what we want */ data->mstate = MSTATE_COMPLETED; - /* This ignores the return code even in case of problems because there's - nothing more to do about that, here */ - (void)singlesocket(multi, easy); /* to let the application know what sockets - that vanish with this handle */ - /* Remove the association between the connection and the handle */ Curl_detach_connection(data); + /* Tell event handling that this transfer is definitely going away */ + Curl_multi_ev_xfer_done(multi, data); + if(data->set.connect_only && !data->multi_easy) { /* This removes a handle that was part the multi interface that used CONNECT_ONLY, that connection is now left alive but since this handle has bits.close set nothing can use that transfer anymore and it is - forbidden from reuse. And this easy handle cannot find the connection + forbidden from reuse. This easy handle cannot find the connection anymore once removed from the multi handle Better close the connection here, at once. @@ -912,15 +761,14 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, curl_socket_t s; s = Curl_getconnectinfo(data, &c); if((s != CURL_SOCKET_BAD) && c) { - Curl_conncache_remove_conn(data, c, TRUE); - Curl_disconnect(data, c, TRUE); + Curl_conn_terminate(data, c, TRUE); } } if(data->state.lastconnect_id != -1) { /* Mark any connect-only connection for closure */ - Curl_conncache_foreach(data, data->state.conn_cache, - NULL, close_connect_only); + Curl_cpool_do_by_id(data, data->state.lastconnect_id, + close_connect_only, NULL); } #ifdef USE_LIBPSL @@ -929,40 +777,49 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, data->psl = NULL; #endif - /* as this was using a shared connection cache we clear the pointer to that - since we're not part of that multi handle anymore */ - data->state.conn_cache = NULL; - - data->multi = NULL; /* clear the association to this multi handle */ - - /* make sure there's no pending message in the queue sent from this easy + /* make sure there is no pending message in the queue sent from this easy handle */ - for(e = multi->msglist.head; e; e = e->next) { - struct Curl_message *msg = e->ptr; + 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 == easy) { - Curl_llist_remove(&multi->msglist, e, NULL); + 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_uint_tbl_contains(&multi->xfers, mid)); + Curl_uint_tbl_remove(&multi->xfers, mid); + Curl_uint_bset_remove(&multi->process, mid); + Curl_uint_bset_remove(&multi->pending, mid); + Curl_uint_bset_remove(&multi->msgsent, mid); + data->multi = NULL; + data->mid = UINT_MAX; + data->master_mid = UINT_MAX; + /* NOTE NOTE NOTE We do not touch the easy handle here! */ - multi->num_easy--; /* one less to care about now */ - process_pending_handles(multi); - rc = Curl_update_timer(multi); - if(rc) - return rc; + if(removed_timer) { + rc = Curl_update_timer(multi); + if(rc) + return rc; + } + + CURL_TRC_M(data, "removed from multi, mid=%u, running=%u, total=%u", + mid, Curl_multi_xfers_running(multi), + Curl_uint_tbl_count(&multi->xfers)); return CURLM_OK; } /* Return TRUE if the application asked for multiplexing */ bool Curl_multiplex_wanted(const struct Curl_multi *multi) { - return (multi && (multi->multiplexing)); + return multi && multi->multiplexing; } /* @@ -975,8 +832,9 @@ void Curl_detach_connection(struct Curl_easy *data) { struct connectdata *conn = data->conn; if(conn) { - Curl_conn_ev_data_detach(conn, data); - Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL); + Curl_uint_spbset_remove(&conn->xfers_attached, data->mid); + if(Curl_uint_spbset_empty(&conn->xfers_attached)) + conn->attached_multi = NULL; } data->conn = NULL; } @@ -987,98 +845,251 @@ void Curl_detach_connection(struct Curl_easy *data) * This is the only function that should assign data->conn */ void Curl_attach_connection(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn) { + DEBUGASSERT(data); DEBUGASSERT(!data->conn); DEBUGASSERT(conn); data->conn = conn; - Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data, - &data->conn_queue); + Curl_uint_spbset_add(&conn->xfers_attached, data->mid); + /* all attached transfers must be from the same multi */ + if(!conn->attached_multi) + conn->attached_multi = data->multi; + DEBUGASSERT(conn->attached_multi == data->multi); + if(conn->handler && conn->handler->attach) conn->handler->attach(data, conn); - Curl_conn_ev_data_attach(conn, data); } -static int domore_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int connecting_getsock(struct Curl_easy *data, curl_socket_t *socks) { - if(conn && conn->handler->domore_getsock) - return conn->handler->domore_getsock(data, conn, socks); + struct connectdata *conn = data->conn; + curl_socket_t sockfd; + + if(!conn) + return GETSOCK_BLANK; + sockfd = Curl_conn_get_socket(data, FIRSTSOCKET); + if(sockfd != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = sockfd; + return GETSOCK_READSOCK(0); + } return GETSOCK_BLANK; } -static int doing_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int protocol_getsock(struct Curl_easy *data, curl_socket_t *socks) { - if(conn && conn->handler->doing_getsock) - return conn->handler->doing_getsock(data, conn, socks); + struct connectdata *conn = data->conn; + curl_socket_t sockfd; + + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->proto_getsock) + return conn->handler->proto_getsock(data, conn, socks); + sockfd = Curl_conn_get_socket(data, FIRSTSOCKET); + if(sockfd != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = sockfd; + return GETSOCK_READSOCK(0); + } return GETSOCK_BLANK; } -static int protocol_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int domore_getsock(struct Curl_easy *data, curl_socket_t *socks) { - if(conn->handler->proto_getsock) - return conn->handler->proto_getsock(data, conn, socks); - return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks); + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->domore_getsock) + return conn->handler->domore_getsock(data, conn, socks); + else if(conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } + return GETSOCK_BLANK; } -/* returns bitmapped flags for this handle and its sockets. The 'socks[]' - array contains MAX_SOCKSPEREASYHANDLE entries. */ -static int multi_getsock(struct Curl_easy *data, - curl_socket_t *socks) +static int doing_getsock(struct Curl_easy *data, curl_socket_t *socks) { struct connectdata *conn = data->conn; - /* The no connection case can happen when this is called from - curl_multi_remove_handle() => singlesocket() => multi_getsock(). - */ if(!conn) - return 0; + return GETSOCK_BLANK; + if(conn->handler->doing_getsock) + return conn->handler->doing_getsock(data, conn, socks); + else if(conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } + return GETSOCK_BLANK; +} + +static int perform_getsock(struct Curl_easy *data, curl_socket_t *sock) +{ + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + else if(conn->handler->perform_getsock) + return conn->handler->perform_getsock(data, conn, sock); + else { + /* Default is to obey the data->req.keepon flags for send/recv */ + int bitmap = GETSOCK_BLANK; + unsigned sockindex = 0; + if(CURL_WANT_RECV(data)) { + DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); + bitmap |= GETSOCK_READSOCK(sockindex); + sock[sockindex] = conn->sockfd; + } + + if(Curl_req_want_send(data)) { + if((conn->sockfd != conn->writesockfd) || + bitmap == GETSOCK_BLANK) { + /* only if they are not the same socket and we have a readable + one, we increase index */ + if(bitmap != GETSOCK_BLANK) + sockindex++; /* increase index if we need two entries */ + + DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); + sock[sockindex] = conn->writesockfd; + } + bitmap |= GETSOCK_WRITESOCK(sockindex); + } + return bitmap; + } +} + +/* Initializes `poll_set` with the current socket poll actions needed + * for transfer `data`. */ +void Curl_multi_getsock(struct Curl_easy *data, + struct easy_pollset *ps, + const char *caller) +{ + bool expect_sockets = TRUE; + + /* If the transfer has no connection, this is fine. Happens when + called via curl_multi_remove_handle() => Curl_multi_ev_assess() => + Curl_multi_getsock(). */ + Curl_pollset_reset(data, ps); + if(!data->conn) + return; switch(data->mstate) { - default: - return 0; + case MSTATE_INIT: + case MSTATE_PENDING: + case MSTATE_SETUP: + case MSTATE_CONNECT: + /* nothing to poll for yet */ + expect_sockets = FALSE; + break; case MSTATE_RESOLVING: - return Curl_resolv_getsock(data, socks); + Curl_pollset_add_socks(data, ps, Curl_resolv_getsock); + /* connection filters are not involved in this phase. It's ok if we get no + * sockets to wait for. Resolving can wake up from other sources. */ + expect_sockets = FALSE; + break; + + case MSTATE_CONNECTING: + case MSTATE_TUNNELING: + Curl_pollset_add_socks(data, ps, connecting_getsock); + Curl_conn_adjust_pollset(data, data->conn, ps); + break; - case MSTATE_PROTOCONNECTING: case MSTATE_PROTOCONNECT: - return protocol_getsock(data, conn, socks); + case MSTATE_PROTOCONNECTING: + Curl_pollset_add_socks(data, ps, protocol_getsock); + Curl_conn_adjust_pollset(data, data->conn, ps); + break; case MSTATE_DO: case MSTATE_DOING: - return doing_getsock(data, conn, socks); - - case MSTATE_TUNNELING: - case MSTATE_CONNECTING: - return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks); + Curl_pollset_add_socks(data, ps, doing_getsock); + Curl_conn_adjust_pollset(data, data->conn, ps); + break; case MSTATE_DOING_MORE: - return domore_getsock(data, conn, socks); + Curl_pollset_add_socks(data, ps, domore_getsock); + Curl_conn_adjust_pollset(data, data->conn, ps); + break; - case MSTATE_DID: /* since is set after DO is completed, we switch to - waiting for the same as the PERFORMING state */ + case MSTATE_DID: /* same as PERFORMING in regard to polling */ case MSTATE_PERFORMING: - return Curl_single_getsock(data, conn, socks); + Curl_pollset_add_socks(data, ps, perform_getsock); + Curl_conn_adjust_pollset(data, data->conn, ps); + break; + + case MSTATE_RATELIMITING: + /* we need to let time pass, ignore socket(s) */ + expect_sockets = FALSE; + break; + + case MSTATE_DONE: + case MSTATE_COMPLETED: + case MSTATE_MSGSENT: + /* nothing more to poll for */ + expect_sockets = FALSE; + break; + + default: + failf(data, "multi_getsock: unexpected multi state %d", data->mstate); + DEBUGASSERT(0); + expect_sockets = FALSE; + break; } + switch(ps->num) { + case 0: + CURL_TRC_M(data, "%s pollset[], timeouts=%zu, paused %d/%d (r/w)", + caller, Curl_llist_count(&data->state.timeoutlist), + Curl_creader_is_paused(data), Curl_cwriter_is_paused(data)); + break; + case 1: + CURL_TRC_M(data, "%s pollset[fd=%" FMT_SOCKET_T " %s%s], timeouts=%zu", + caller, ps->sockets[0], + (ps->actions[0] & CURL_POLL_IN) ? "IN" : "", + (ps->actions[0] & CURL_POLL_OUT) ? "OUT" : "", + Curl_llist_count(&data->state.timeoutlist)); + break; + case 2: + CURL_TRC_M(data, "%s pollset[fd=%" FMT_SOCKET_T " %s%s, " + "fd=%" FMT_SOCKET_T " %s%s], timeouts=%zu", + caller, ps->sockets[0], + (ps->actions[0] & CURL_POLL_IN) ? "IN" : "", + (ps->actions[0] & CURL_POLL_OUT) ? "OUT" : "", + ps->sockets[1], + (ps->actions[1] & CURL_POLL_IN) ? "IN" : "", + (ps->actions[1] & CURL_POLL_OUT) ? "OUT" : "", + Curl_llist_count(&data->state.timeoutlist)); + break; + default: + CURL_TRC_M(data, "%s pollset[fds=%u], timeouts=%zu", + caller, ps->num, Curl_llist_count(&data->state.timeoutlist)); + break; + } + if(expect_sockets && !ps->num && + !Curl_llist_count(&data->state.timeoutlist) && + !Curl_cwriter_is_paused(data) && !Curl_creader_is_paused(data) && + Curl_conn_is_ip_connected(data, FIRSTSOCKET)) { + /* We expected sockets for POLL monitoring, but none are set. + * We are not waiting on any timer. + * None of the READ/WRITE directions are paused. + * We are connected to the server on IP level, at least. */ + infof(data, "WARNING: no socket in pollset or timer, transfer may stall!"); + DEBUGASSERT(0); + } } -CURLMcode curl_multi_fdset(struct Curl_multi *multi, +CURLMcode curl_multi_fdset(CURLM *m, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd) { /* Scan through all the easy handles to get the file descriptors set. Some easy handles may not have connected to the remote host yet, and then we must make sure that is done. */ - struct Curl_easy *data; int this_max_fd = -1; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int i; + struct Curl_multi *multi = m; + unsigned int i, mid; (void)exc_fd_set; /* not used */ if(!GOOD_MULTI_HANDLE(multi)) @@ -1087,49 +1098,95 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - data = multi->easyp; - while(data) { - int bitmap; -#ifdef __clang_analyzer_ - /* to prevent "The left operand of '>=' is a garbage value" warnings */ - memset(sockbunch, 0, sizeof(sockbunch)); -#endif - bitmap = multi_getsock(data, sockbunch); - - for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; + if(Curl_uint_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + struct easy_pollset ps; - if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK(sockbunch[i])) { - if(!FDSET_SOCK(sockbunch[i])) - /* pretend it doesn't exist */ - continue; - FD_SET(sockbunch[i], read_fd_set); - s = sockbunch[i]; + if(!data) { + DEBUGASSERT(0); + continue; } - if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK(sockbunch[i])) { - if(!FDSET_SOCK(sockbunch[i])) - /* pretend it doesn't exist */ + + Curl_multi_getsock(data, &ps, "curl_multi_fdset"); + for(i = 0; i < ps.num; i++) { + if(!FDSET_SOCK(ps.sockets[i])) + /* pretend it does not exist */ continue; - FD_SET(sockbunch[i], write_fd_set); - s = sockbunch[i]; +#if defined(__DJGPP__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warith-conversion" +#endif + if(ps.actions[i] & CURL_POLL_IN) + FD_SET(ps.sockets[i], read_fd_set); + if(ps.actions[i] & CURL_POLL_OUT) + FD_SET(ps.sockets[i], write_fd_set); +#if defined(__DJGPP__) +#pragma GCC diagnostic pop +#endif + if((int)ps.sockets[i] > this_max_fd) + this_max_fd = (int)ps.sockets[i]; } - if(s == CURL_SOCKET_BAD) - /* this socket is unused, break out of loop */ - break; - if((int)s > this_max_fd) - this_max_fd = (int)s; } - - data = data->next; /* check next handle */ + while(Curl_uint_bset_next(&multi->process, mid, &mid)); } + Curl_cshutdn_setfds(&multi->cshutdn, multi->admin, + read_fd_set, write_fd_set, &this_max_fd); + *max_fd = this_max_fd; return CURLM_OK; } +CURLMcode curl_multi_waitfds(CURLM *m, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count) +{ + struct Curl_waitfds cwfds; + CURLMcode result = CURLM_OK; + struct Curl_multi *multi = m; + unsigned int need = 0, mid; + + if(!ufds && (size || !fd_count)) + return CURLM_BAD_FUNCTION_ARGUMENT; + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + Curl_waitfds_init(&cwfds, ufds, size); + if(Curl_uint_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + struct easy_pollset ps; + if(!data) { + DEBUGASSERT(0); + Curl_uint_bset_remove(&multi->process, mid); + continue; + } + Curl_multi_getsock(data, &ps, "curl_multi_waitfds"); + need += Curl_waitfds_add_ps(&cwfds, &ps); + } + while(Curl_uint_bset_next(&multi->process, mid, &mid)); + } + + need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds); + + if(need != cwfds.n && ufds) { + result = CURLM_OUT_OF_MEMORY; + } + + if(fd_count) + *fd_count = need; + return result; +} + #ifdef USE_WINSOCK -/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't +/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets cannot * be reset this way because an empty datagram would be sent. #9203 * * "On Windows the internal state of FD_WRITE as returned from @@ -1154,17 +1211,16 @@ static CURLMcode multi_wait(struct Curl_multi *multi, bool extrawait, /* when no socket, wait */ bool use_wakeup) { - struct Curl_easy *data; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int bitmap; - unsigned int i; - unsigned int nfds = 0; - unsigned int curlfds; + size_t i; + struct curltime expire_time; long timeout_internal; int retcode = 0; struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; - struct pollfd *ufds = &a_few_on_stack[0]; - bool ufds_malloc = FALSE; + struct curl_pollfds cpfds; + unsigned int curl_nfds = 0; /* how many pfds are for curl transfers */ + CURLMcode result = CURLM_OK; + unsigned int mid; + #ifdef USE_WINSOCK WSANETWORKEVENTS wsa_events; DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT); @@ -1182,177 +1238,116 @@ static CURLMcode multi_wait(struct Curl_multi *multi, if(timeout_ms < 0) return CURLM_BAD_FUNCTION_ARGUMENT; - /* Count up how many fds we have from the multi handle */ - data = multi->easyp; - while(data) { - bitmap = multi_getsock(data, sockbunch); - - for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; + Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); - if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { - ++nfds; - s = sockbunch[i]; - } - if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { - ++nfds; - s = sockbunch[i]; - } - if(s == CURL_SOCKET_BAD) { - break; + /* Add the curl handles to our pollfds first */ + if(Curl_uint_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + struct easy_pollset ps; + if(!data) { + DEBUGASSERT(0); + Curl_uint_bset_remove(&multi->process, mid); + continue; + } + Curl_multi_getsock(data, &ps, "multi_wait"); + if(Curl_pollfds_add_ps(&cpfds, &ps)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } } - - data = data->next; /* check next handle */ - } - - /* If the internally desired timeout is actually shorter than requested from - the outside, then use the shorter time! But only if the internal timer - is actually larger than -1! */ - (void)multi_timeout(multi, &timeout_internal); - if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) - timeout_ms = (int)timeout_internal; - - curlfds = nfds; /* number of internal file descriptors */ - nfds += extra_nfds; /* add the externally provided ones */ - -#ifdef ENABLE_WAKEUP -#ifdef USE_WINSOCK - if(use_wakeup) { -#else - if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { -#endif - ++nfds; + while(Curl_uint_bset_next(&multi->process, mid, &mid)); } -#endif - if(nfds > NUM_POLLS_ON_STACK) { - /* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes - big, so at 2^29 sockets this value might wrap. When a process gets - the capability to actually handle over 500 million sockets this - calculation needs a integer overflow check. */ - ufds = malloc(nfds * sizeof(struct pollfd)); - if(!ufds) - return CURLM_OUT_OF_MEMORY; - ufds_malloc = TRUE; + if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } - nfds = 0; - - /* only do the second loop if we found descriptors in the first stage run - above */ - - if(curlfds) { - /* Add the curl handles to our pollfds first */ - data = multi->easyp; - while(data) { - bitmap = multi_getsock(data, sockbunch); - - for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; -#ifdef USE_WINSOCK - long mask = 0; -#endif - if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { - s = sockbunch[i]; -#ifdef USE_WINSOCK - mask |= FD_READ|FD_ACCEPT|FD_CLOSE; -#endif - ufds[nfds].fd = s; - ufds[nfds].events = POLLIN; - ++nfds; - } - if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { - s = sockbunch[i]; -#ifdef USE_WINSOCK - mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - reset_socket_fdwrite(s); -#endif - ufds[nfds].fd = s; - ufds[nfds].events = POLLOUT; - ++nfds; - } - /* s is only set if either being readable or writable is checked */ - if(s == CURL_SOCKET_BAD) { - /* break on entry not checked for being readable or writable */ - break; - } -#ifdef USE_WINSOCK - if(WSAEventSelect(s, multi->wsa_event, mask) != 0) { - if(ufds_malloc) - free(ufds); - return CURLM_INTERNAL_ERROR; - } -#endif - } - data = data->next; /* check next handle */ + curl_nfds = cpfds.n; /* what curl internally uses in cpfds */ + /* Add external file descriptions from poll-like struct curl_waitfd */ + for(i = 0; i < extra_nfds; i++) { + unsigned short events = 0; + if(extra_fds[i].events & CURL_WAIT_POLLIN) + events |= POLLIN; + if(extra_fds[i].events & CURL_WAIT_POLLPRI) + events |= POLLPRI; + if(extra_fds[i].events & CURL_WAIT_POLLOUT) + events |= POLLOUT; + if(Curl_pollfds_add_sock(&cpfds, extra_fds[i].fd, events)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } } - /* Add external file descriptions from poll-like struct curl_waitfd */ - for(i = 0; i < extra_nfds; i++) { #ifdef USE_WINSOCK + /* Set the WSA events based on the collected pollds */ + for(i = 0; i < cpfds.n; i++) { long mask = 0; - if(extra_fds[i].events & CURL_WAIT_POLLIN) + if(cpfds.pfds[i].events & POLLIN) mask |= FD_READ|FD_ACCEPT|FD_CLOSE; - if(extra_fds[i].events & CURL_WAIT_POLLPRI) + if(cpfds.pfds[i].events & POLLPRI) mask |= FD_OOB; - if(extra_fds[i].events & CURL_WAIT_POLLOUT) { + if(cpfds.pfds[i].events & POLLOUT) { mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - reset_socket_fdwrite(extra_fds[i].fd); + reset_socket_fdwrite(cpfds.pfds[i].fd); } - if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) { - if(ufds_malloc) - free(ufds); - return CURLM_INTERNAL_ERROR; + if(mask) { + if(WSAEventSelect(cpfds.pfds[i].fd, multi->wsa_event, mask) != 0) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } } -#endif - ufds[nfds].fd = extra_fds[i].fd; - ufds[nfds].events = 0; - if(extra_fds[i].events & CURL_WAIT_POLLIN) - ufds[nfds].events |= POLLIN; - if(extra_fds[i].events & CURL_WAIT_POLLPRI) - ufds[nfds].events |= POLLPRI; - if(extra_fds[i].events & CURL_WAIT_POLLOUT) - ufds[nfds].events |= POLLOUT; - ++nfds; } +#endif #ifdef ENABLE_WAKEUP #ifndef USE_WINSOCK if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { - ufds[nfds].fd = multi->wakeup_pair[0]; - ufds[nfds].events = POLLIN; - ++nfds; + if(Curl_pollfds_add_sock(&cpfds, multi->wakeup_pair[0], POLLIN)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } } #endif #endif + /* We check the internal timeout *AFTER* we collected all sockets to + * poll. Collecting the sockets may install new timers by protocols + * and connection filters. + * Use the shorter one of the internal and the caller requested timeout. */ + (void)multi_timeout(multi, &expire_time, &timeout_internal); + if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) + timeout_ms = (int)timeout_internal; + #if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) - if(nfds || use_wakeup) { + if(cpfds.n || use_wakeup) { #else - if(nfds) { + if(cpfds.n) { #endif int pollrc; #ifdef USE_WINSOCK - if(nfds) - pollrc = Curl_poll(ufds, nfds, 0); /* just pre-check with WinSock */ + if(cpfds.n) /* just pre-check with Winsock */ + pollrc = Curl_poll(cpfds.pfds, cpfds.n, 0); else pollrc = 0; #else - pollrc = Curl_poll(ufds, nfds, timeout_ms); /* wait... */ + pollrc = Curl_poll(cpfds.pfds, cpfds.n, timeout_ms); /* wait... */ #endif - if(pollrc < 0) - return CURLM_UNRECOVERABLE_POLL; + if(pollrc < 0) { + result = CURLM_UNRECOVERABLE_POLL; + goto out; + } if(pollrc > 0) { retcode = pollrc; #ifdef USE_WINSOCK } else { /* now wait... if not ready during the pre-check (pollrc == 0) */ - WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, timeout_ms, FALSE); + WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, (DWORD)timeout_ms, + FALSE); } - /* With WinSock, we have to run the following section unconditionally + /* With Winsock, we have to run the following section unconditionally to call WSAEventSelect(fd, event, 0) on all the sockets */ { #endif @@ -1360,7 +1355,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, struct, the bit values of the actual underlying poll() implementation may not be the same as the ones in the public libcurl API! */ for(i = 0; i < extra_nfds; i++) { - unsigned r = ufds[curlfds + i].revents; + unsigned r = (unsigned)cpfds.pfds[curl_nfds + i].revents; unsigned short mask = 0; #ifdef USE_WINSOCK curl_socket_t s = extra_fds[i].fd; @@ -1377,7 +1372,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } WSAEventSelect(s, multi->wsa_event, 0); if(!pollrc) { - extra_fds[i].revents = mask; + extra_fds[i].revents = (short)mask; continue; } #endif @@ -1387,41 +1382,25 @@ static CURLMcode multi_wait(struct Curl_multi *multi, mask |= CURL_WAIT_POLLOUT; if(r & POLLPRI) mask |= CURL_WAIT_POLLPRI; - extra_fds[i].revents = mask; + extra_fds[i].revents = (short)mask; } #ifdef USE_WINSOCK /* Count up all our own sockets that had activity, and remove them from the event. */ - if(curlfds) { - data = multi->easyp; - while(data) { - bitmap = multi_getsock(data, sockbunch); - - for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) { - if(bitmap & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))) { - wsa_events.lNetworkEvents = 0; - if(WSAEnumNetworkEvents(sockbunch[i], NULL, &wsa_events) == 0) { - if(ret && !pollrc && wsa_events.lNetworkEvents) - retcode++; - } - WSAEventSelect(sockbunch[i], multi->wsa_event, 0); - } - else { - /* break on entry not checked for being readable or writable */ - break; - } - } - - data = data->next; + for(i = 0; i < curl_nfds; ++i) { + wsa_events.lNetworkEvents = 0; + if(WSAEnumNetworkEvents(cpfds.pfds[i].fd, NULL, &wsa_events) == 0) { + if(ret && !pollrc && wsa_events.lNetworkEvents) + retcode++; } + WSAEventSelect(cpfds.pfds[i].fd, multi->wsa_event, 0); } - WSAResetEvent(multi->wsa_event); #else #ifdef ENABLE_WAKEUP if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { - if(ufds[curlfds + extra_nfds].revents & POLLIN) { + if(cpfds.pfds[curl_nfds + extra_nfds].revents & POLLIN) { char buf[64]; ssize_t nread; while(1) { @@ -1431,7 +1410,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, when there is no more data, breaking the loop. */ nread = wakeup_read(multi->wakeup_pair[0], buf, sizeof(buf)); if(nread <= 0) { - if(nread < 0 && EINTR == SOCKERRNO) + if(nread < 0 && SOCKEINTR == SOCKERRNO) continue; break; } @@ -1445,18 +1424,16 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } } - if(ufds_malloc) - free(ufds); if(ret) *ret = retcode; #if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) - if(extrawait && !nfds && !use_wakeup) { + if(extrawait && !cpfds.n && !use_wakeup) { #else - if(extrawait && !nfds) { + if(extrawait && !cpfds.n) { #endif long sleep_ms = 0; - /* Avoid busy-looping when there's nothing particular to wait for */ + /* Avoid busy-looping when there is nothing particular to wait for */ if(!curl_multi_timeout(multi, &sleep_ms) && sleep_ms) { if(sleep_ms > timeout_ms) sleep_ms = timeout_ms; @@ -1468,10 +1445,12 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } } - return CURLM_OK; +out: + Curl_pollfds_cleanup(&cpfds); + return result; } -CURLMcode curl_multi_wait(struct Curl_multi *multi, +CURLMcode curl_multi_wait(CURLM *multi, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, @@ -1481,7 +1460,7 @@ CURLMcode curl_multi_wait(struct Curl_multi *multi, FALSE); } -CURLMcode curl_multi_poll(struct Curl_multi *multi, +CURLMcode curl_multi_poll(CURLM *multi, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, @@ -1491,11 +1470,12 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi, TRUE); } -CURLMcode curl_multi_wakeup(struct Curl_multi *multi) +CURLMcode curl_multi_wakeup(CURLM *m) { /* this function is usually called from another thread, it has to be careful only to access parts of the Curl_multi struct that are constant */ + struct Curl_multi *multi = m; /* GOOD_MULTI_HANDLE can be safely called */ if(!GOOD_MULTI_HANDLE(multi)) @@ -1510,9 +1490,14 @@ CURLMcode curl_multi_wakeup(struct Curl_multi *multi) making it safe to access from another thread after the init part and before cleanup */ if(multi->wakeup_pair[1] != CURL_SOCKET_BAD) { - char buf[1]; - buf[0] = 1; while(1) { +#ifdef USE_EVENTFD + /* eventfd has a stringent rule of requiring the 8-byte buffer when + calling write(2) on it */ + const uint64_t buf[1] = { 1 }; +#else + const char buf[1] = { 1 }; +#endif /* swrite() is not thread-safe in general, because concurrent calls can have their messages interleaved, but in this case the content of the messages does not matter, which makes it ok to call. @@ -1520,17 +1505,17 @@ CURLMcode curl_multi_wakeup(struct Curl_multi *multi) The write socket is set to non-blocking, this way this function cannot block, making it safe to call even from the same thread that will call curl_multi_wait(). If swrite() returns that it - would block, it's considered successful because it means that + would block, it is considered successful because it means that previous calls to this function will wake up the poll(). */ if(wakeup_write(multi->wakeup_pair[1], buf, sizeof(buf)) < 0) { int err = SOCKERRNO; int return_success; #ifdef USE_WINSOCK - return_success = WSAEWOULDBLOCK == err; + return_success = SOCKEWOULDBLOCK == err; #else - if(EINTR == err) + if(SOCKEINTR == err) continue; - return_success = EWOULDBLOCK == err || EAGAIN == err; + return_success = SOCKEWOULDBLOCK == err || EAGAIN == err; #endif if(!return_success) return CURLM_WAKEUP_FAILURE; @@ -1559,6 +1544,18 @@ static bool multi_ischanged(struct Curl_multi *multi, bool clear) return retval; } +/* + * Curl_multi_connchanged() is called to tell that there is a connection in + * this multi handle that has changed state (multiplexing become possible, the + * number of allowed streams changed or similar), and a subsequent use of this + * multi handle should move CONNECT_PEND handles back to CONNECT to have them + * retry. + */ +void Curl_multi_connchanged(struct Curl_multi *multi) +{ + multi->recheckstate = TRUE; +} + CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, struct Curl_easy *data, struct connectdata *conn) @@ -1571,10 +1568,15 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, rc = curl_multi_add_handle(multi, data); if(!rc) { struct SingleRequest *k = &data->req; + CURLcode result; - /* pass in NULL for 'conn' here since we don't want to init the + /* pass in NULL for 'conn' here since we do not want to init the connection, only this transfer */ - Curl_init_do(data, NULL); + result = Curl_init_do(data, NULL); + if(result) { + curl_multi_remove_handle(multi, data); + return CURLM_INTERNAL_ERROR; + } /* take this handle to the perform state right away */ multistate(data, MSTATE_PERFORMING); @@ -1593,7 +1595,6 @@ static CURLcode multi_do(struct Curl_easy *data, bool *done) DEBUGASSERT(conn->handler); if(conn->handler->do_it) - /* generic protocol-specific function pointer set in curl_connect() */ result = conn->handler->do_it(data, done); return result; @@ -1605,7 +1606,7 @@ static CURLcode multi_do(struct Curl_easy *data, bool *done) * second connection. * * 'complete' can return 0 for incomplete, 1 for done and -1 for go back to - * DOING state there's more work to do! + * DOING state there is more work to do! */ static CURLcode multi_do_more(struct Curl_easy *data, int *complete) @@ -1627,50 +1628,50 @@ static CURLcode multi_do_more(struct Curl_easy *data, int *complete) static bool multi_handle_timeout(struct Curl_easy *data, struct curltime *now, bool *stream_error, - CURLcode *result, - bool connect_timeout) + CURLcode *result) { - timediff_t timeout_ms; - timeout_ms = Curl_timeleft(data, now, connect_timeout); - + bool connect_timeout = data->mstate < MSTATE_DO; + timediff_t timeout_ms = Curl_timeleft(data, now, connect_timeout); if(timeout_ms < 0) { /* Handle timed out */ + struct curltime since; + if(connect_timeout) + since = data->progress.t_startsingle; + else + since = data->progress.t_startop; if(data->mstate == MSTATE_RESOLVING) - failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*now, data->progress.t_startsingle)); + failf(data, "Resolving timed out after %" FMT_TIMEDIFF_T + " milliseconds", curlx_timediff(*now, since)); else if(data->mstate == MSTATE_CONNECTING) - failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*now, data->progress.t_startsingle)); + failf(data, "Connection timed out after %" FMT_TIMEDIFF_T + " milliseconds", curlx_timediff(*now, since)); else { struct SingleRequest *k = &data->req; if(k->size != -1) { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(*now, data->progress.t_startsingle), - k->bytecount, k->size); + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " out of %" + FMT_OFF_T " bytes received", + curlx_timediff(*now, since), k->bytecount, k->size); } else { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T - " bytes received", - Curl_timediff(*now, data->progress.t_startsingle), - k->bytecount); + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " bytes received", + curlx_timediff(*now, since), k->bytecount); } } - - /* Force connection closed if the connection has indeed been used */ - if(data->mstate > MSTATE_DO) { - streamclose(data->conn, "Disconnected with pending data"); - *stream_error = TRUE; - } *result = CURLE_OPERATION_TIMEDOUT; - (void)multi_done(data, *result, TRUE); + if(data->conn) { + /* Force connection closed if the connection has indeed been used */ + if(data->mstate > MSTATE_DO) { + streamclose(data->conn, "Disconnect due to timeout"); + *stream_error = TRUE; + } + (void)multi_done(data, *result, TRUE); + } + return TRUE; } - return (timeout_ms < 0); + return FALSE; } /* @@ -1733,10 +1734,10 @@ static CURLcode protocol_connect(struct Curl_easy *data, && conn->bits.protoconnstart) { /* We already are connected, get back. This may happen when the connect worked fine in the first call, like when we connect to a local server - or proxy. Note that we don't know if the protocol is actually done. + or proxy. Note that we do not know if the protocol is actually done. - Unless this protocol doesn't have any protocol-connect callback, as - then we know we're done. */ + Unless this protocol does not have any protocol-connect callback, as + then we know we are done. */ if(!conn->handler->connecting) *protocol_done = TRUE; @@ -1753,7 +1754,7 @@ static CURLcode protocol_connect(struct Curl_easy *data, else *protocol_done = TRUE; - /* it has started, possibly even completed but that knowledge isn't stored + /* it has started, possibly even completed but that knowledge is not stored in this bit! */ if(!result) conn->bits.protoconnstart = TRUE; @@ -1762,109 +1763,491 @@ static CURLcode protocol_connect(struct Curl_easy *data, return result; /* pass back status */ } +static void set_in_callback(struct Curl_multi *multi, bool value) +{ + multi->in_callback = value; +} + /* - * readrewind() rewinds the read stream. This is typically used for HTTP - * POST/PUT with multi-pass authentication when a sending was denied and a - * resend is necessary. + * posttransfer() is called immediately after a transfer ends */ -static CURLcode readrewind(struct Curl_easy *data) +static void multi_posttransfer(struct Curl_easy *data) { - struct connectdata *conn = data->conn; - curl_mimepart *mimepart = &data->set.mimepost; - DEBUGASSERT(conn); +#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) + /* restore the signal handler for SIGPIPE before we get back */ + if(!data->set.no_signal) + signal(SIGPIPE, data->state.prev_signal); +#else + (void)data; /* unused parameter */ +#endif +} + +/* + * multi_follow() handles the URL redirect magic. Pass in the 'newurl' string + * as given by the remote server and set up the new URL to request. + * + * This function DOES NOT FREE the given url. + */ +static CURLcode multi_follow(struct Curl_easy *data, + const struct Curl_handler *handler, + const char *newurl, /* the Location: string */ + followtype type) /* see transfer.h */ +{ + if(handler && handler->follow) + return handler->follow(data, newurl, type); + return CURLE_TOO_MANY_REDIRECTS; +} + +static CURLMcode state_performing(struct Curl_easy *data, + struct curltime *nowp, + bool *stream_errorp, + CURLcode *resultp) +{ + char *newurl = NULL; + bool retry = FALSE; + timediff_t recv_timeout_ms = 0; + timediff_t send_timeout_ms = 0; + CURLMcode rc = CURLM_OK; + CURLcode result = *resultp = CURLE_OK; + *stream_errorp = FALSE; + + /* check if over send speed */ + if(data->set.max_send_speed) + send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul, + data->set.max_send_speed, + *nowp); + + /* check if over recv speed */ + if(data->set.max_recv_speed) + recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl, + data->set.max_recv_speed, + *nowp); + + if(send_timeout_ms || recv_timeout_ms) { + Curl_ratelimit(data, *nowp); + multistate(data, MSTATE_RATELIMITING); + if(send_timeout_ms >= recv_timeout_ms) + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); + else + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); + return CURLM_OK; + } + + /* read/write data if it is ready to do so */ + result = Curl_sendrecv(data, nowp); + + if(data->req.done || (result == CURLE_RECV_ERROR)) { + /* If CURLE_RECV_ERROR happens early enough, we assume it was a race + * condition and the server closed the reused connection exactly when we + * wanted to use it, so figure out if that is indeed the case. + */ + CURLcode ret = Curl_retry_request(data, &newurl); + if(!ret) + retry = !!newurl; + else if(!result) + result = ret; + + if(retry) { + /* if we are to retry, set the result to OK and consider the + request as done */ + result = CURLE_OK; + data->req.done = TRUE; + } + } +#ifndef CURL_DISABLE_HTTP + else if((CURLE_HTTP2_STREAM == result) && + Curl_h2_http_1_1_error(data)) { + CURLcode ret = Curl_retry_request(data, &newurl); + + if(!ret) { + infof(data, "Downgrades to HTTP/1.1"); + streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); + data->state.http_neg.wanted = CURL_HTTP_V1x; + data->state.http_neg.allowed = CURL_HTTP_V1x; + /* clear the error message bit too as we ignore the one we got */ + data->state.errorbuf = FALSE; + if(!newurl) + /* typically for HTTP_1_1_REQUIRED error on first flight */ + newurl = strdup(data->state.url); + /* if we are to retry, set the result to OK and consider the request + as done */ + retry = TRUE; + result = CURLE_OK; + data->req.done = TRUE; + } + else + result = ret; + } +#endif - data->state.rewindbeforesend = FALSE; /* we rewind now */ + if(result) { + /* + * The transfer phase returned error, we mark the connection to get closed + * to prevent being reused. This is because we cannot possibly know if the + * connection is in a good shape or not now. Unless it is a protocol which + * uses two "channels" like FTP, as then the error happened in the data + * connection. + */ + + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + multi_posttransfer(data); + multi_done(data, result, TRUE); + } + else if(data->req.done && !Curl_cwriter_is_paused(data)) { + const struct Curl_handler *handler = data->conn->handler; + + /* call this even if the readwrite function returned error */ + multi_posttransfer(data); + + /* When we follow redirects or is set to retry the connection, we must to + go back to the CONNECT state */ + if(data->req.newurl || retry) { + followtype follow = FOLLOW_NONE; + if(!retry) { + /* if the URL is a follow-location and not just a retried request then + figure out the URL here */ + free(newurl); + newurl = data->req.newurl; + data->req.newurl = NULL; + follow = FOLLOW_REDIR; + } + else + follow = FOLLOW_RETRY; + (void)multi_done(data, CURLE_OK, FALSE); + /* multi_done() might return CURLE_GOT_NOTHING */ + result = multi_follow(data, handler, newurl, follow); + if(!result) { + multistate(data, MSTATE_SETUP); + rc = CURLM_CALL_MULTI_PERFORM; + } + } + else { + /* after the transfer is done, go DONE */ + + /* but first check to see if we got a location info even though we are + not following redirects */ + if(data->req.location) { + free(newurl); + newurl = data->req.location; + data->req.location = NULL; + result = multi_follow(data, handler, newurl, FOLLOW_FAKE); + if(result) { + *stream_errorp = TRUE; + result = multi_done(data, result, TRUE); + } + } - /* explicitly switch off sending data on this connection now since we are - about to restart a new transfer and thus we want to avoid inadvertently - sending more data on the existing connection until the next transfer - starts */ - data->req.keepon &= ~KEEP_SEND; + if(!result) { + multistate(data, MSTATE_DONE); + rc = CURLM_CALL_MULTI_PERFORM; + } + } + } + else if(data->state.select_bits && !Curl_xfer_is_blocked(data)) { + /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer does + not get stuck on this transfer at the expense of other concurrent + transfers */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + free(newurl); + *resultp = result; + return rc; +} - /* We have sent away data. If not using CURLOPT_POSTFIELDS or - CURLOPT_HTTPPOST, call app to rewind - */ - if(conn->handler->protocol & PROTO_FAMILY_HTTP) { - struct HTTP *http = data->req.p.http; - - if(http->sendit) - mimepart = http->sendit; - } - if(data->set.postfields || - (data->state.httpreq == HTTPREQ_GET) || - (data->state.httpreq == HTTPREQ_HEAD)) - ; /* no need to rewind */ - else if(data->state.httpreq == HTTPREQ_POST_MIME || - data->state.httpreq == HTTPREQ_POST_FORM) { - CURLcode result = Curl_mime_rewind(mimepart); - if(result) { - failf(data, "Cannot rewind mime/post data"); - return result; +static CURLMcode state_do(struct Curl_easy *data, + bool *stream_errorp, + CURLcode *resultp) +{ + CURLMcode rc = CURLM_OK; + CURLcode result = CURLE_OK; + if(data->set.fprereq) { + int prereq_rc; + + /* call the prerequest callback function */ + Curl_set_in_callback(data, TRUE); + prereq_rc = data->set.fprereq(data->set.prereq_userp, + data->info.primary.remote_ip, + data->info.primary.local_ip, + data->info.primary.remote_port, + data->info.primary.local_port); + Curl_set_in_callback(data, FALSE); + if(prereq_rc != CURL_PREREQFUNC_OK) { + failf(data, "operation aborted by pre-request callback"); + /* failure in pre-request callback - do not do any other processing */ + result = CURLE_ABORTED_BY_CALLBACK; + multi_posttransfer(data); + multi_done(data, result, FALSE); + *stream_errorp = TRUE; + goto end; } } + + if(data->set.connect_only && !data->set.connect_only_ws) { + /* keep connection open for application to use the socket */ + connkeep(data->conn, "CONNECT_ONLY"); + multistate(data, MSTATE_DONE); + rc = CURLM_CALL_MULTI_PERFORM; + } else { - if(data->set.seek_func) { - int err; - - Curl_set_in_callback(data, true); - err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); - Curl_set_in_callback(data, false); - if(err) { - failf(data, "seek callback returned error %d", (int)err); - return CURLE_SEND_FAIL_REWIND; + bool dophase_done = FALSE; + /* Perform the protocol's DO action */ + result = multi_do(data, &dophase_done); + + /* When multi_do() returns failure, data->conn might be NULL! */ + + if(!result) { + if(!dophase_done) { +#ifndef CURL_DISABLE_FTP + /* some steps needed for wildcard matching */ + if(data->state.wildcardmatch) { + struct WildcardData *wc = data->wildcard; + if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { + /* skip some states if it is important */ + multi_done(data, CURLE_OK, FALSE); + + /* if there is no connection left, skip the DONE state */ + multistate(data, data->conn ? + MSTATE_DONE : MSTATE_COMPLETED); + rc = CURLM_CALL_MULTI_PERFORM; + goto end; + } + } +#endif + /* DO was not completed in one function call, we must continue + DOING... */ + multistate(data, MSTATE_DOING); + rc = CURLM_CALL_MULTI_PERFORM; + } + + /* after DO, go DO_DONE... or DO_MORE */ + else if(data->conn->bits.do_more) { + /* we are supposed to do more, but we need to sit down, relax and wait + a little while first */ + multistate(data, MSTATE_DOING_MORE); + rc = CURLM_CALL_MULTI_PERFORM; + } + else { + /* we are done with the DO, now DID */ + multistate(data, MSTATE_DID); + rc = CURLM_CALL_MULTI_PERFORM; } } - else if(data->set.ioctl_func) { - curlioerr err; - - Curl_set_in_callback(data, true); - err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, - data->set.ioctl_client); - Curl_set_in_callback(data, false); - infof(data, "the ioctl callback returned %d", (int)err); - - if(err) { - failf(data, "ioctl callback returned error %d", (int)err); - return CURLE_SEND_FAIL_REWIND; + else if((CURLE_SEND_ERROR == result) && + data->conn->bits.reuse) { + /* + * In this situation, a connection that we were trying to use may have + * unexpectedly died. If possible, send the connection back to the + * CONNECT phase so we can try again. + */ + const struct Curl_handler *handler = data->conn->handler; + char *newurl = NULL; + followtype follow = FOLLOW_NONE; + CURLcode drc; + + drc = Curl_retry_request(data, &newurl); + if(drc) { + /* a failure here pretty much implies an out of memory */ + result = drc; + *stream_errorp = TRUE; + } + + multi_posttransfer(data); + drc = multi_done(data, result, FALSE); + + /* When set to retry the connection, we must go back to the CONNECT + * state */ + if(newurl) { + if(!drc || (drc == CURLE_SEND_ERROR)) { + follow = FOLLOW_RETRY; + drc = multi_follow(data, handler, newurl, follow); + if(!drc) { + multistate(data, MSTATE_SETUP); + rc = CURLM_CALL_MULTI_PERFORM; + result = CURLE_OK; + } + else { + /* Follow failed */ + result = drc; + } + } + else { + /* done did not return OK or SEND_ERROR */ + result = drc; + } + } + else { + /* Have error handler disconnect conn if we cannot retry */ + *stream_errorp = TRUE; } + free(newurl); } else { - /* If no CURLOPT_READFUNCTION is used, we know that we operate on a - given FILE * stream and we can actually attempt to rewind that - ourselves with fseek() */ - if(data->state.fread_func == (curl_read_callback)fread) { - if(-1 != fseek(data->state.in, 0, SEEK_SET)) - /* successful rewind */ - return CURLE_OK; - } + /* failure detected */ + multi_posttransfer(data); + if(data->conn) + multi_done(data, result, FALSE); + *stream_errorp = TRUE; + } + } +end: + *resultp = result; + return rc; +} + +static CURLMcode state_ratelimiting(struct Curl_easy *data, + struct curltime *nowp, + CURLcode *resultp) +{ + CURLcode result = CURLE_OK; + CURLMcode rc = CURLM_OK; + DEBUGASSERT(data->conn); + /* if both rates are within spec, resume transfer */ + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, *nowp); + + if(result) { + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + multi_posttransfer(data); + multi_done(data, result, TRUE); + } + else { + timediff_t recv_timeout_ms = 0; + timediff_t send_timeout_ms = 0; + if(data->set.max_send_speed) + send_timeout_ms = + Curl_pgrsLimitWaitTime(&data->progress.ul, + data->set.max_send_speed, + *nowp); + + if(data->set.max_recv_speed) + recv_timeout_ms = + Curl_pgrsLimitWaitTime(&data->progress.dl, + data->set.max_recv_speed, + *nowp); + + if(!send_timeout_ms && !recv_timeout_ms) { + multistate(data, MSTATE_PERFORMING); + Curl_ratelimit(data, *nowp); + /* start performing again right away */ + rc = CURLM_CALL_MULTI_PERFORM; + } + else if(send_timeout_ms >= recv_timeout_ms) + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); + else + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); + } + *resultp = result; + return rc; +} + +static CURLMcode state_resolving(struct Curl_multi *multi, + struct Curl_easy *data, + bool *stream_errorp, + CURLcode *resultp) +{ + struct Curl_dns_entry *dns = NULL; + CURLcode result; + CURLMcode rc = CURLM_OK; + + result = Curl_resolv_check(data, &dns); + CURL_TRC_DNS(data, "Curl_resolv_check() -> %d, %s", + result, dns ? "found" : "missing"); + /* Update sockets here, because the socket(s) may have been closed and the + application thus needs to be told, even if it is likely that the same + socket(s) will again be used further down. If the name has not yet been + resolved, it is likely that new sockets have been opened in an attempt to + contact another resolver. */ + rc = Curl_multi_ev_assess_xfer(multi, data); + if(rc) + return rc; - /* no callback set or failure above, makes us fail at once */ - failf(data, "necessary data rewind wasn't possible"); - return CURLE_SEND_FAIL_REWIND; + if(dns) { + bool connected; + /* Perform the next step in the connection phase, and then move on to the + WAITCONNECT state */ + result = Curl_once_resolved(data, dns, &connected); + + if(result) + /* if Curl_once_resolved() returns failure, the connection struct is + already freed and gone */ + data->conn = NULL; /* no more connection */ + else { + /* call again please so that we get the next socket setup */ + rc = CURLM_CALL_MULTI_PERFORM; + if(connected) + multistate(data, MSTATE_PROTOCONNECT); + else { + multistate(data, MSTATE_CONNECTING); + } } } - return CURLE_OK; + + if(result) + /* failure detected */ + *stream_errorp = TRUE; + + *resultp = result; + return rc; } -/* - * Curl_preconnect() is called immediately before a connect starts. When a - * redirect is followed, this is then called multiple times during a single - * transfer. - */ -CURLcode Curl_preconnect(struct Curl_easy *data) +static CURLMcode state_connect(struct Curl_multi *multi, + struct Curl_easy *data, + struct curltime *nowp, + CURLcode *resultp) { - if(!data->state.buffer) { - data->state.buffer = malloc(data->set.buffer_size + 1); - if(!data->state.buffer) - return CURLE_OUT_OF_MEMORY; + /* Connect. We want to get a connection identifier filled in. This state can + be entered from SETUP and from PENDING. */ + bool connected; + bool async; + CURLMcode rc = CURLM_OK; + CURLcode result = Curl_connect(data, &async, &connected); + if(CURLE_NO_CONNECTION_AVAILABLE == result) { + /* There was no connection available. We will go to the pending state and + wait for an available connection. */ + multistate(data, MSTATE_PENDING); + /* move from process to pending set */ + Curl_uint_bset_remove(&multi->process, data->mid); + Curl_uint_bset_add(&multi->pending, data->mid); + *resultp = CURLE_OK; + return rc; } + else + process_pending_handles(data->multi); - return CURLE_OK; -} + if(!result) { + *nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE); + if(async) + /* We are now waiting for an asynchronous name lookup */ + multistate(data, MSTATE_RESOLVING); + else { + /* after the connect has been sent off, go WAITCONNECT unless the + protocol connect is already done and we can go directly to WAITDO or + DO! */ + rc = CURLM_CALL_MULTI_PERFORM; -static void set_in_callback(struct Curl_multi *multi, bool value) -{ - multi->in_callback = value; + if(connected) { + if(!data->conn->bits.reuse && + Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { + /* new connection, can multiplex, wake pending handles */ + process_pending_handles(data->multi); + } + multistate(data, MSTATE_PROTOCONNECT); + } + else { + multistate(data, MSTATE_CONNECTING); + } + } + } + *resultp = result; + return rc; } static CURLMcode multi_runsingle(struct Curl_multi *multi, @@ -1873,14 +2256,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, { struct Curl_message *msg = NULL; bool connected; - bool async; bool protocol_connected = FALSE; bool dophase_done = FALSE; - bool done = FALSE; CURLMcode rc; CURLcode result = CURLE_OK; - timediff_t recv_timeout_ms; - timediff_t send_timeout_ms; int control; if(!GOOD_EASY_HANDLE(data)) @@ -1890,19 +2269,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* a multi-level callback returned error before, meaning every individual transfer now has failed */ result = CURLE_ABORTED_BY_CALLBACK; - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, FALSE); multistate(data, MSTATE_COMPLETED); } -#ifdef DEBUGBUILD - if(!multi->warned) { - infof(data, "!!! WARNING !!!"); - infof(data, "This is a debug build of libcurl, " - "do not use in production."); - multi->warned = true; - } -#endif + multi_warn_debug(multi, data); do { /* A "stream" here is a logical stream if the protocol can handle that @@ -1911,7 +2283,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, rc = CURLM_OK; if(multi_ischanged(multi, TRUE)) { - DEBUGF(infof(data, "multi changed, check CONNECT_PEND queue")); + CURL_TRC_M(data, "multi changed, check CONNECT_PEND queue"); process_pending_handles(multi); /* multiplexed */ } @@ -1923,182 +2295,61 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, return CURLM_INTERNAL_ERROR; } - if(data->conn && - (data->mstate >= MSTATE_CONNECT) && - (data->mstate < MSTATE_COMPLETED)) { - /* Check for overall operation timeout here but defer handling the - * connection timeout to later, to allow for a connection to be set up - * in the window since we last checked timeout. This prevents us - * tearing down a completed connection in the case where we were slow - * to check the timeout (e.g. process descheduled during this loop). - * We set connect_timeout=FALSE to do this. */ - - /* we need to wait for the connect state as only then is the start time - stored, but we must not check already completed handles */ - if(multi_handle_timeout(data, nowp, &stream_error, &result, FALSE)) { - /* Skip the statemachine and go directly to error handling section. */ - goto statemachine_end; - } - } + /* Wait for the connect state as only then is the start time stored, but + we must not check already completed handles */ + if((data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED) && + multi_handle_timeout(data, nowp, &stream_error, &result)) + /* Skip the statemachine and go directly to error handling section. */ + goto statemachine_end; switch(data->mstate) { case MSTATE_INIT: - /* init this transfer. */ + /* Transitional state. init this transfer. A handle never comes back to + this state. */ result = Curl_pretransfer(data); - - if(!result) { - /* after init, go CONNECT */ - multistate(data, MSTATE_CONNECT); - *nowp = Curl_pgrsTime(data, TIMER_STARTOP); - rc = CURLM_CALL_MULTI_PERFORM; - } - break; - - case MSTATE_CONNECT: - /* Connect. We want to get a connection identifier filled in. */ - /* init this transfer. */ - result = Curl_preconnect(data); if(result) break; + /* after init, go SETUP */ + multistate(data, MSTATE_SETUP); + (void)Curl_pgrsTime(data, TIMER_STARTOP); + FALLTHROUGH(); + + case MSTATE_SETUP: + /* Transitional state. Setup things for a new transfer. The handle + can come back to this state on a redirect. */ *nowp = Curl_pgrsTime(data, TIMER_STARTSINGLE); if(data->set.timeout) Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT); - if(data->set.connecttimeout) + /* Since a connection might go to pending and back to CONNECT several + times before it actually takes off, we need to set the timeout once + in SETUP before we enter CONNECT the first time. */ Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT); - result = Curl_connect(data, &async, &connected); - if(CURLE_NO_CONNECTION_AVAILABLE == result) { - /* There was no connection available. We will go to the pending - state and wait for an available connection. */ - multistate(data, MSTATE_PENDING); - - /* add this handle to the list of connect-pending handles */ - Curl_llist_insert_next(&multi->pending, multi->pending.tail, data, - &data->connect_queue); - /* unlink from the main list */ - unlink_easy(multi, data); - result = CURLE_OK; - break; - } - else if(data->state.previouslypending) { - /* this transfer comes from the pending queue so try move another */ - infof(data, "Transfer was pending, now try another"); - process_pending_handles(data->multi); - } - - if(!result) { - if(async) - /* We're now waiting for an asynchronous name lookup */ - multistate(data, MSTATE_RESOLVING); - else { - /* after the connect has been sent off, go WAITCONNECT unless the - protocol connect is already done and we can go directly to - WAITDO or DO! */ - rc = CURLM_CALL_MULTI_PERFORM; + multistate(data, MSTATE_CONNECT); + FALLTHROUGH(); - if(connected) - multistate(data, MSTATE_PROTOCONNECT); - else { - multistate(data, MSTATE_CONNECTING); - } - } - } + case MSTATE_CONNECT: + rc = state_connect(multi, data, nowp, &result); break; case MSTATE_RESOLVING: /* awaiting an asynch name resolve to complete */ - { - struct Curl_dns_entry *dns = NULL; - struct connectdata *conn = data->conn; - const char *hostname; - - DEBUGASSERT(conn); -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy) - hostname = conn->http_proxy.host.name; - else -#endif - if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; - - /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, hostname, (int)conn->port); - - if(dns) { -#ifdef CURLRES_ASYNCH - data->state.async.dns = dns; - data->state.async.done = TRUE; -#endif - result = CURLE_OK; - infof(data, "Hostname '%s' was found in DNS cache", hostname); - } - - if(!dns) - result = Curl_resolv_check(data, &dns); - - /* Update sockets here, because the socket(s) may have been - closed and the application thus needs to be told, even if it - is likely that the same socket(s) will again be used further - down. If the name has not yet been resolved, it is likely - that new sockets have been opened in an attempt to contact - another resolver. */ - rc = singlesocket(multi, data); - if(rc) - return rc; - - if(dns) { - /* Perform the next step in the connection phase, and then move on - to the WAITCONNECT state */ - result = Curl_once_resolved(data, &connected); - - if(result) - /* if Curl_once_resolved() returns failure, the connection struct - is already freed and gone */ - data->conn = NULL; /* no more connection */ - else { - /* call again please so that we get the next socket setup */ - rc = CURLM_CALL_MULTI_PERFORM; - if(connected) - multistate(data, MSTATE_PROTOCONNECT); - else { - multistate(data, MSTATE_CONNECTING); - } - } - } - - if(result) { - /* failure detected */ - stream_error = TRUE; - break; - } - } - break; + rc = state_resolving(multi, data, &stream_error, &result); + break; #ifndef CURL_DISABLE_HTTP case MSTATE_TUNNELING: /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ DEBUGASSERT(data->conn); result = Curl_http_connect(data, &protocol_connected); -#ifndef CURL_DISABLE_PROXY - if(data->conn->bits.proxy_connect_closed) { + if(!result) { rc = CURLM_CALL_MULTI_PERFORM; - /* connect back to proxy again */ - result = CURLE_OK; - multi_done(data, CURLE_OK, FALSE); - multistate(data, MSTATE_CONNECT); + /* initiate protocol connect phase */ + multistate(data, MSTATE_PROTOCONNECT); } else -#endif - if(!result) { - rc = CURLM_CALL_MULTI_PERFORM; - /* initiate protocol connect phase */ - multistate(data, MSTATE_PROTOCONNECT); - } - else stream_error = TRUE; break; #endif @@ -2108,12 +2359,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, DEBUGASSERT(data->conn); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected); if(connected && !result) { + if(!data->conn->bits.reuse && + Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { + /* new connection, can multiplex, wake pending handles */ + process_pending_handles(data->multi); + } rc = CURLM_CALL_MULTI_PERFORM; multistate(data, MSTATE_PROTOCONNECT); } else if(result) { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, TRUE); stream_error = TRUE; break; @@ -2121,22 +2377,22 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; case MSTATE_PROTOCONNECT: - if(data->state.rewindbeforesend) - result = readrewind(data); - if(!result && data->conn->bits.reuse) { - /* ftp seems to hang when protoconnect on reused connection - * since we handle PROTOCONNECT in general inside the filers, it - * seems wrong to restart this on a reused connection. */ + /* ftp seems to hang when protoconnect on reused connection since we + * handle PROTOCONNECT in general inside the filers, it seems wrong to + * restart this on a reused connection. + */ multistate(data, MSTATE_DO); rc = CURLM_CALL_MULTI_PERFORM; break; } if(!result) result = protocol_connect(data, &protocol_connected); - if(!result && !protocol_connected) + if(!result && !protocol_connected) { /* switch to waiting state */ multistate(data, MSTATE_PROTOCONNECTING); + rc = CURLM_CALL_MULTI_PERFORM; + } else if(!result) { /* protocol connect has completed, go WAITDO or DO */ multistate(data, MSTATE_DO); @@ -2144,7 +2400,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, TRUE); stream_error = TRUE; } @@ -2160,139 +2416,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else if(result) { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, TRUE); stream_error = TRUE; } break; case MSTATE_DO: - if(data->set.fprereq) { - int prereq_rc; - - /* call the prerequest callback function */ - Curl_set_in_callback(data, true); - prereq_rc = data->set.fprereq(data->set.prereq_userp, - data->info.conn_primary_ip, - data->info.conn_local_ip, - data->info.conn_primary_port, - data->info.conn_local_port); - Curl_set_in_callback(data, false); - if(prereq_rc != CURL_PREREQFUNC_OK) { - failf(data, "operation aborted by pre-request callback"); - /* failure in pre-request callback - don't do any other processing */ - result = CURLE_ABORTED_BY_CALLBACK; - Curl_posttransfer(data); - multi_done(data, result, FALSE); - stream_error = TRUE; - break; - } - } - - if(data->set.connect_only == 1) { - /* keep connection open for application to use the socket */ - connkeep(data->conn, "CONNECT_ONLY"); - multistate(data, MSTATE_DONE); - result = CURLE_OK; - rc = CURLM_CALL_MULTI_PERFORM; - } - else { - /* Perform the protocol's DO action */ - result = multi_do(data, &dophase_done); - - /* When multi_do() returns failure, data->conn might be NULL! */ - - if(!result) { - if(!dophase_done) { -#ifndef CURL_DISABLE_FTP - /* some steps needed for wildcard matching */ - if(data->state.wildcardmatch) { - struct WildcardData *wc = data->wildcard; - if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { - /* skip some states if it is important */ - multi_done(data, CURLE_OK, FALSE); - - /* if there's no connection left, skip the DONE state */ - multistate(data, data->conn ? - MSTATE_DONE : MSTATE_COMPLETED); - rc = CURLM_CALL_MULTI_PERFORM; - break; - } - } -#endif - /* DO was not completed in one function call, we must continue - DOING... */ - multistate(data, MSTATE_DOING); - } - - /* after DO, go DO_DONE... or DO_MORE */ - else if(data->conn->bits.do_more) { - /* we're supposed to do more, but we need to sit down, relax - and wait a little while first */ - multistate(data, MSTATE_DOING_MORE); - } - else { - /* we're done with the DO, now DID */ - multistate(data, MSTATE_DID); - rc = CURLM_CALL_MULTI_PERFORM; - } - } - else if((CURLE_SEND_ERROR == result) && - data->conn->bits.reuse) { - /* - * In this situation, a connection that we were trying to use - * may have unexpectedly died. If possible, send the connection - * back to the CONNECT phase so we can try again. - */ - char *newurl = NULL; - followtype follow = FOLLOW_NONE; - CURLcode drc; - - drc = Curl_retry_request(data, &newurl); - if(drc) { - /* a failure here pretty much implies an out of memory */ - result = drc; - stream_error = TRUE; - } - - Curl_posttransfer(data); - drc = multi_done(data, result, FALSE); - - /* When set to retry the connection, we must go back to the CONNECT - * state */ - if(newurl) { - if(!drc || (drc == CURLE_SEND_ERROR)) { - follow = FOLLOW_RETRY; - drc = Curl_follow(data, newurl, follow); - if(!drc) { - multistate(data, MSTATE_CONNECT); - rc = CURLM_CALL_MULTI_PERFORM; - result = CURLE_OK; - } - else { - /* Follow failed */ - result = drc; - } - } - else { - /* done didn't return OK or SEND_ERROR */ - result = drc; - } - } - else { - /* Have error handler disconnect conn if we can't retry */ - stream_error = TRUE; - } - free(newurl); - } - else { - /* failure detected */ - Curl_posttransfer(data); - if(data->conn) - multi_done(data, result, FALSE); - stream_error = TRUE; - } - } + rc = state_do(data, &stream_error, &result); break; case MSTATE_DOING: @@ -2302,14 +2433,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(!result) { if(dophase_done) { /* after DO, go DO_DONE or DO_MORE */ - multistate(data, data->conn->bits.do_more? + multistate(data, data->conn->bits.do_more ? MSTATE_DOING_MORE : MSTATE_DID); rc = CURLM_CALL_MULTI_PERFORM; } /* dophase_done */ } else { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, FALSE); stream_error = TRUE; } @@ -2326,7 +2457,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(control) { /* if positive, advance to DO_DONE if negative, go back to DOING */ - multistate(data, control == 1? + multistate(data, control == 1 ? MSTATE_DID : MSTATE_DOING); rc = CURLM_CALL_MULTI_PERFORM; } @@ -2335,7 +2466,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, FALSE); stream_error = TRUE; } @@ -2347,7 +2478,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* Check if we can move pending requests to send pipe */ process_pending_handles(multi); /* multiplexed */ - /* Only perform the transfer if there's a good socket to work with. + /* Only perform the transfer if there is a good socket to work with. Having both BAD is a signal to skip immediately to DONE */ if((data->conn->sockfd != CURL_SOCKET_BAD) || (data->conn->writesockfd != CURL_SOCKET_BAD)) @@ -2365,204 +2496,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */ - DEBUGASSERT(data->conn); - /* if both rates are within spec, resume transfer */ - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, *nowp); - - if(result) { - if(!(data->conn->handler->flags & PROTOPT_DUAL) && - result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); - - Curl_posttransfer(data); - multi_done(data, result, TRUE); - } - else { - send_timeout_ms = 0; - if(data->set.max_send_speed) - send_timeout_ms = - Curl_pgrsLimitWaitTime(data->progress.uploaded, - data->progress.ul_limit_size, - data->set.max_send_speed, - data->progress.ul_limit_start, - *nowp); - - recv_timeout_ms = 0; - if(data->set.max_recv_speed) - recv_timeout_ms = - Curl_pgrsLimitWaitTime(data->progress.downloaded, - data->progress.dl_limit_size, - data->set.max_recv_speed, - data->progress.dl_limit_start, - *nowp); - - if(!send_timeout_ms && !recv_timeout_ms) { - multistate(data, MSTATE_PERFORMING); - Curl_ratelimit(data, *nowp); - } - else if(send_timeout_ms >= recv_timeout_ms) - Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); - else - Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); - } + rc = state_ratelimiting(data, nowp, &result); break; case MSTATE_PERFORMING: - { - char *newurl = NULL; - bool retry = FALSE; - bool comeback = FALSE; - DEBUGASSERT(data->state.buffer); - /* check if over send speed */ - send_timeout_ms = 0; - if(data->set.max_send_speed) - send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded, - data->progress.ul_limit_size, - data->set.max_send_speed, - data->progress.ul_limit_start, - *nowp); - - /* check if over recv speed */ - recv_timeout_ms = 0; - if(data->set.max_recv_speed) - recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded, - data->progress.dl_limit_size, - data->set.max_recv_speed, - data->progress.dl_limit_start, - *nowp); - - if(send_timeout_ms || recv_timeout_ms) { - Curl_ratelimit(data, *nowp); - multistate(data, MSTATE_RATELIMITING); - if(send_timeout_ms >= recv_timeout_ms) - Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); - else - Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); - break; - } - - /* read/write data if it is ready to do so */ - result = Curl_readwrite(data->conn, data, &done, &comeback); - - if(done || (result == CURLE_RECV_ERROR)) { - /* If CURLE_RECV_ERROR happens early enough, we assume it was a race - * condition and the server closed the re-used connection exactly when - * we wanted to use it, so figure out if that is indeed the case. - */ - CURLcode ret = Curl_retry_request(data, &newurl); - if(!ret) - retry = (newurl)?TRUE:FALSE; - else if(!result) - result = ret; - - if(retry) { - /* if we are to retry, set the result to OK and consider the - request as done */ - result = CURLE_OK; - done = TRUE; - } - } - else if((CURLE_HTTP2_STREAM == result) && - Curl_h2_http_1_1_error(data)) { - CURLcode ret = Curl_retry_request(data, &newurl); - - if(!ret) { - infof(data, "Downgrades to HTTP/1.1"); - streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); - data->state.httpwant = CURL_HTTP_VERSION_1_1; - /* clear the error message bit too as we ignore the one we got */ - data->state.errorbuf = FALSE; - if(!newurl) - /* typically for HTTP_1_1_REQUIRED error on first flight */ - newurl = strdup(data->state.url); - /* if we are to retry, set the result to OK and consider the request - as done */ - retry = TRUE; - result = CURLE_OK; - done = TRUE; - } - else - result = ret; - } - - if(result) { - /* - * The transfer phase returned error, we mark the connection to get - * closed to prevent being re-used. This is because we can't possibly - * know if the connection is in a good shape or not now. Unless it is - * a protocol which uses two "channels" like FTP, as then the error - * happened in the data connection. - */ - - if(!(data->conn->handler->flags & PROTOPT_DUAL) && - result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); - - Curl_posttransfer(data); - multi_done(data, result, TRUE); - } - else if(done) { - - /* call this even if the readwrite function returned error */ - Curl_posttransfer(data); - - /* When we follow redirects or is set to retry the connection, we must - to go back to the CONNECT state */ - if(data->req.newurl || retry) { - followtype follow = FOLLOW_NONE; - if(!retry) { - /* if the URL is a follow-location and not just a retried request - then figure out the URL here */ - free(newurl); - newurl = data->req.newurl; - data->req.newurl = NULL; - follow = FOLLOW_REDIR; - } - else - follow = FOLLOW_RETRY; - (void)multi_done(data, CURLE_OK, FALSE); - /* multi_done() might return CURLE_GOT_NOTHING */ - result = Curl_follow(data, newurl, follow); - if(!result) { - multistate(data, MSTATE_CONNECT); - rc = CURLM_CALL_MULTI_PERFORM; - } - free(newurl); - } - else { - /* after the transfer is done, go DONE */ - - /* but first check to see if we got a location info even though we're - not following redirects */ - if(data->req.location) { - free(newurl); - newurl = data->req.location; - data->req.location = NULL; - result = Curl_follow(data, newurl, FOLLOW_FAKE); - free(newurl); - if(result) { - stream_error = TRUE; - result = multi_done(data, result, TRUE); - } - } - - if(!result) { - multistate(data, MSTATE_DONE); - rc = CURLM_CALL_MULTI_PERFORM; - } - } - } - else if(comeback) { - /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer - won't get stuck on this transfer at the expense of other concurrent - transfers */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); - } + rc = state_performing(data, nowp, &stream_error, &result); break; - } case MSTATE_DONE: /* this state is highly transient, so run another loop after this */ @@ -2571,10 +2510,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->conn) { CURLcode res; - if(data->conn->bits.multiplex) - /* Check if we can move pending requests to connection */ - process_pending_handles(multi); /* multiplexing */ - /* post-transfer command */ res = multi_done(data, result, FALSE); @@ -2593,8 +2528,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } } #endif - /* after we have DONE what we're supposed to do, go COMPLETED, and - it doesn't matter what the multi_done() returned! */ + /* after we have DONE what we are supposed to do, go COMPLETED, and + it does not matter what the multi_done() returned! */ multistate(data, MSTATE_COMPLETED); break; @@ -2604,25 +2539,23 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case MSTATE_PENDING: case MSTATE_MSGSENT: /* handles in these states should NOT be in this list */ - DEBUGASSERT(0); break; default: return CURLM_INTERNAL_ERROR; } - if(data->conn && - data->mstate >= MSTATE_CONNECT && + if(data->mstate >= MSTATE_CONNECT && data->mstate < MSTATE_DO && rc != CURLM_CALL_MULTI_PERFORM && - !multi_ischanged(multi, false)) { + !multi_ischanged(multi, FALSE)) { /* We now handle stream timeouts if and only if this will be the last * loop iteration. We only check this on the last iteration to ensure * that if we know we have additional work to do immediately * (i.e. CURLM_CALL_MULTI_PERFORM == TRUE) then we should do that before * declaring the connection timed out as we may almost have a completed * connection. */ - multi_handle_timeout(data, nowp, &stream_error, &result, TRUE); + multi_handle_timeout(data, nowp, &stream_error, &result); } statemachine_end: @@ -2630,7 +2563,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->mstate < MSTATE_COMPLETED) { if(result) { /* - * If an error was returned, and we aren't in completed state now, + * If an error was returned, and we are not in completed state now, * then we go to completed and consider this transfer aborted. */ @@ -2642,31 +2575,27 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->conn) { if(stream_error) { - /* Don't attempt to send data over a connection that timed out */ + /* Do not attempt to send data over a connection that timed out */ bool dead_connection = result == CURLE_OPERATION_TIMEDOUT; struct connectdata *conn = data->conn; /* This is where we make sure that the conn pointer is reset. - We don't have to do this in every case block above where a + We do not have to do this in every case block above where a failure is detected */ Curl_detach_connection(data); - - /* remove connection from cache */ - Curl_conncache_remove_conn(data, conn, TRUE); - - /* disconnect properly */ - Curl_disconnect(data, conn, dead_connection); + Curl_conn_terminate(data, conn, dead_connection); } } else if(data->mstate == MSTATE_CONNECT) { /* Curl_connect() failed */ - (void)Curl_posttransfer(data); + multi_posttransfer(data); + Curl_pgrsUpdate_nometer(data); } multistate(data, MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; } - /* if there's still a connection to use, call the progress function */ + /* if there is still a connection to use, call the progress function */ else if(data->conn && Curl_pgrsUpdate(data)) { /* aborted due to progress callback return code must close the connection */ @@ -2674,16 +2603,29 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, streamclose(data->conn, "Aborted by callback"); /* if not yet in DONE state, go there, otherwise COMPLETED */ - multistate(data, (data->mstate < MSTATE_DONE)? - MSTATE_DONE: MSTATE_COMPLETED); + multistate(data, (data->mstate < MSTATE_DONE) ? + MSTATE_DONE : MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; } } if(MSTATE_COMPLETED == data->mstate) { - if(data->set.fmultidone) { - /* signal via callback instead */ - data->set.fmultidone(data, result); + if(data->master_mid != UINT_MAX) { + /* A sub transfer, not for msgsent to application */ + struct Curl_easy *mdata; + + CURL_TRC_M(data, "sub xfer done for master %u", data->master_mid); + mdata = Curl_multi_get_easy(multi, data->master_mid); + if(mdata) { + if(mdata->sub_xfer_done) + mdata->sub_xfer_done(mdata, data, result); + else + CURL_TRC_M(data, "master easy %u without sub_xfer_done callback.", + data->master_mid); + } + else { + CURL_TRC_M(data, "master easy %u already gone.", data->master_mid); + } } else { /* now fill in the Curl_message with this info */ @@ -2698,11 +2640,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } multistate(data, MSTATE_MSGSENT); - /* add this handle to the list of msgsent handles */ - Curl_llist_insert_next(&multi->msgsent, multi->msgsent.tail, data, - &data->connect_queue); - /* unlink from the main list */ - unlink_easy(multi, data); + /* remove from the other sets, add to msgsent */ + Curl_uint_bset_remove(&multi->process, data->mid); + Curl_uint_bset_remove(&multi->pending, data->mid); + Curl_uint_bset_add(&multi->msgsent, data->mid); + --multi->xfers_alive; return CURLM_OK; } } while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); @@ -2712,12 +2654,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } -CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) +CURLMcode curl_multi_perform(CURLM *m, int *running_handles) { - struct Curl_easy *data; CURLMcode returncode = CURLM_OK; - struct Curl_tree *t; - struct curltime now = Curl_now(); + struct Curl_tree *t = NULL; + struct curltime now = curlx_now(); + struct Curl_multi *multi = m; + unsigned int mid; + SIGPIPE_VARIABLE(pipe_st); if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -2725,31 +2669,36 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - data = multi->easyp; - if(data) { - CURLMcode result; - bool nosig = data->set.no_signal; - SIGPIPE_VARIABLE(pipe_st); - sigpipe_ignore(data, &pipe_st); - /* Do the loop and only alter the signal ignore state if the next handle - has a different NO_SIGNAL state than the previous */ + sigpipe_init(&pipe_st); + if(Curl_uint_bset_first(&multi->process, &mid)) { + CURL_TRC_M(multi->admin, "multi_perform(running=%u)", + Curl_multi_xfers_running(multi)); do { - /* the current node might be unlinked in multi_runsingle(), get the next - pointer now */ - struct Curl_easy *datanext = data->next; - if(data->set.no_signal != nosig) { - sigpipe_restore(&pipe_st); - sigpipe_ignore(data, &pipe_st); - nosig = data->set.no_signal; + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + CURLMcode result; + if(!data) { + DEBUGASSERT(0); + Curl_uint_bset_remove(&multi->process, mid); + continue; + } + if(data != multi->admin) { + /* admin handle is processed below */ + sigpipe_apply(data, &pipe_st); + result = multi_runsingle(multi, &now, data); + if(result) + returncode = result; } - result = multi_runsingle(multi, &now, data); - if(result) - returncode = result; - data = datanext; /* operate on next handle */ - } while(data); - sigpipe_restore(&pipe_st); + } + while(Curl_uint_bset_next(&multi->process, mid, &mid)); } + sigpipe_apply(multi->admin, &pipe_st); + Curl_cshutdn_perform(&multi->cshutdn, multi->admin, CURL_SOCKET_TIMEOUT); + sigpipe_restore(&pipe_st); + + if(multi_ischanged(m, TRUE)) + process_pending_handles(m); + /* * Simply remove all expired timers from the splay since handles are dealt * with unconditionally by this function and curl_multi_timeout() requires @@ -2762,13 +2711,25 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) */ do { multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) + if(t) { /* the removed may have another timeout in queue */ - (void)add_next_timeout(now, multi, t->payload); - + struct Curl_easy *data = Curl_splayget(t); + if(data->mstate == MSTATE_PENDING) { + bool stream_unused; + CURLcode result_unused; + if(multi_handle_timeout(data, &now, &stream_unused, &result_unused)) { + infof(data, "PENDING handle timeout"); + move_pending_to_connect(multi, data); + } + } + (void)add_next_timeout(now, multi, Curl_splayget(t)); + } } while(t); - *running_handles = multi->num_alive; + if(running_handles) { + unsigned int running = Curl_multi_xfers_running(multi); + *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; + } if(CURLM_OK >= returncode) returncode = Curl_update_timer(multi); @@ -2776,78 +2737,91 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) return returncode; } -/* unlink_all_msgsent_handles() detaches all those easy handles from this - multi handle */ -static void unlink_all_msgsent_handles(struct Curl_multi *multi) -{ - struct Curl_llist_element *e = multi->msgsent.head; - if(e) { - struct Curl_easy *data = e->ptr; - DEBUGASSERT(data->mstate == MSTATE_MSGSENT); - data->multi = NULL; - } -} - -CURLMcode curl_multi_cleanup(struct Curl_multi *multi) +CURLMcode curl_multi_cleanup(CURLM *m) { - struct Curl_easy *data; - struct Curl_easy *nextdata; - + struct Curl_multi *multi = m; if(GOOD_MULTI_HANDLE(multi)) { + void *entry; + unsigned int mid; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - multi->magic = 0; /* not good anymore */ + /* First remove all remaining easy handles, + * close internal ones. admin handle is special */ + if(Curl_uint_tbl_first(&multi->xfers, &mid, &entry)) { + do { + struct Curl_easy *data = entry; + if(!GOOD_EASY_HANDLE(data)) + return CURLM_BAD_HANDLE; - unlink_all_msgsent_handles(multi); - process_pending_handles(multi); - /* First remove all remaining easy handles */ - data = multi->easyp; - while(data) { - nextdata = data->next; - if(!data->state.done && data->conn) - /* if DONE was never called for this handle */ - (void)multi_done(data, CURLE_OK, TRUE); - if(data->dns.hostcachetype == HCACHE_MULTI) { - /* clear out the usage of the shared DNS cache */ - Curl_hostcache_clean(data, data->dns.hostcache); - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; - } +#ifdef DEBUGBUILD + if(mid != data->mid) { + CURL_TRC_M(data, "multi_cleanup: still present with mid=%u, " + "but unexpected data->mid=%u\n", mid, data->mid); + DEBUGASSERT(0); + } +#endif - /* Clear the pointer to the connection cache */ - data->state.conn_cache = NULL; - data->multi = NULL; /* clear the association */ + if(data == multi->admin) + continue; + + if(!data->state.done && data->conn) + /* if DONE was never called for this handle */ + (void)multi_done(data, CURLE_OK, TRUE); + + data->multi = NULL; /* clear the association */ + Curl_uint_tbl_remove(&multi->xfers, mid); + data->mid = UINT_MAX; #ifdef USE_LIBPSL - if(data->psl == &multi->psl) - data->psl = NULL; + if(data->psl == &multi->psl) + data->psl = NULL; #endif + if(data->state.internal) + Curl_close(&data); + } + while(Curl_uint_tbl_next(&multi->xfers, mid, &mid, &entry)); + } - data = nextdata; + Curl_cpool_destroy(&multi->cpool); + Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); + if(multi->admin) { + CURL_TRC_M(multi->admin, "multi_cleanup, closing admin handle, done"); + multi->admin->multi = NULL; + Curl_uint_tbl_remove(&multi->xfers, multi->admin->mid); + Curl_close(&multi->admin); } - /* Close all the connections in the connection cache */ - Curl_conncache_close_all_connections(&multi->conn_cache); + multi->magic = 0; /* not good anymore */ - sockhash_destroy(&multi->sockhash); - Curl_conncache_destroy(&multi->conn_cache); - Curl_hash_destroy(&multi->hostcache); + Curl_multi_ev_cleanup(multi); + Curl_hash_destroy(&multi->proto_hash); + Curl_dnscache_destroy(&multi->dnscache); Curl_psl_destroy(&multi->psl); + Curl_ssl_scache_destroy(multi->ssl_scache); #ifdef USE_WINSOCK WSACloseEvent(multi->wsa_event); #else #ifdef ENABLE_WAKEUP wakeup_close(multi->wakeup_pair[0]); +#ifndef USE_EVENTFD wakeup_close(multi->wakeup_pair[1]); #endif #endif - -#ifdef USE_SSL - Curl_free_multi_ssl_backend_data(multi->ssl_backend_data); #endif + multi_xfer_bufs_free(multi); +#ifdef DEBUGBUILD + if(Curl_uint_tbl_count(&multi->xfers)) { + multi_xfer_tbl_dump(multi); + DEBUGASSERT(0); + } +#endif + Curl_uint_bset_destroy(&multi->process); + Curl_uint_bset_destroy(&multi->pending); + Curl_uint_bset_destroy(&multi->msgsent); + Curl_uint_tbl_destroy(&multi->xfers); free(multi); return CURLM_OK; @@ -2865,251 +2839,42 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) * beyond. The current design is fully O(1). */ -CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue) +CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) { - struct Curl_message *msg; - - *msgs_in_queue = 0; /* default to none */ - - if(GOOD_MULTI_HANDLE(multi) && - !multi->in_callback && - Curl_llist_count(&multi->msglist)) { - /* there is one or more messages in the list */ - struct Curl_llist_element *e; - - /* extract the head of the list to return */ - e = multi->msglist.head; - - msg = e->ptr; - - /* remove the extracted entry */ - Curl_llist_remove(&multi->msglist, e, NULL); - - *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); - - return &msg->extmsg; - } - return NULL; -} - -/* - * singlesocket() checks what sockets we deal with and their "action state" - * and if we have a different state in any of those sockets from last time we - * call the callback accordingly. - */ -static CURLMcode singlesocket(struct Curl_multi *multi, - struct Curl_easy *data) -{ - curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; - int i; - struct Curl_sh_entry *entry; - curl_socket_t s; - int num; - unsigned int curraction; - unsigned char actions[MAX_SOCKSPEREASYHANDLE]; - int rc; - - for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) - socks[i] = CURL_SOCKET_BAD; - - /* Fill in the 'current' struct with the state as it is now: what sockets to - supervise and for what actions */ - curraction = multi_getsock(data, socks); - - /* We have 0 .. N sockets already and we get to know about the 0 .. M - sockets we should have from now on. Detect the differences, remove no - longer supervised ones and add new ones */ - - /* walk over the sockets we got right now */ - for(i = 0; (i< MAX_SOCKSPEREASYHANDLE) && - (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))); - i++) { - unsigned char action = CURL_POLL_NONE; - unsigned char prevaction = 0; - int comboaction; - bool sincebefore = FALSE; - - s = socks[i]; - - /* get it from the hash */ - entry = sh_getentry(&multi->sockhash, s); - - if(curraction & GETSOCK_READSOCK(i)) - action |= CURL_POLL_IN; - if(curraction & GETSOCK_WRITESOCK(i)) - action |= CURL_POLL_OUT; - - actions[i] = action; - if(entry) { - /* check if new for this transfer */ - int j; - for(j = 0; j< data->numsocks; j++) { - if(s == data->sockets[j]) { - prevaction = data->actions[j]; - sincebefore = TRUE; - break; - } - } - } - else { - /* this is a socket we didn't have before, add it to the hash! */ - entry = sh_addentry(&multi->sockhash, s); - if(!entry) - /* fatal */ - return CURLM_OUT_OF_MEMORY; - } - if(sincebefore && (prevaction != action)) { - /* Socket was used already, but different action now */ - if(prevaction & CURL_POLL_IN) - entry->readers--; - if(prevaction & CURL_POLL_OUT) - entry->writers--; - if(action & CURL_POLL_IN) - entry->readers++; - if(action & CURL_POLL_OUT) - entry->writers++; - } - else if(!sincebefore) { - /* a new user */ - entry->users++; - if(action & CURL_POLL_IN) - entry->readers++; - if(action & CURL_POLL_OUT) - entry->writers++; - - /* add 'data' to the transfer hash on this socket! */ - if(!Curl_hash_add(&entry->transfers, (char *)&data, /* hash key */ - sizeof(struct Curl_easy *), data)) { - Curl_hash_destroy(&entry->transfers); - return CURLM_OUT_OF_MEMORY; - } - } - - comboaction = (entry->writers? CURL_POLL_OUT : 0) | - (entry->readers ? CURL_POLL_IN : 0); - - /* socket existed before and has the same action set as before */ - if(sincebefore && ((int)entry->action == comboaction)) - /* same, continue */ - continue; + struct Curl_message *msg; + struct Curl_multi *multi = m; - if(multi->socket_cb) { - set_in_callback(multi, TRUE); - rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, - entry->socketp); - set_in_callback(multi, FALSE); - if(rc == -1) { - multi->dead = TRUE; - return CURLM_ABORTED_BY_CALLBACK; - } - } + *msgs_in_queue = 0; /* default to none */ - entry->action = comboaction; /* store the current action state */ - } + if(GOOD_MULTI_HANDLE(multi) && + !multi->in_callback && + Curl_llist_count(&multi->msglist)) { + /* there is one or more messages in the list */ + struct Curl_llist_node *e; - num = i; /* number of sockets */ + /* extract the head of the list to return */ + e = Curl_llist_head(&multi->msglist); - /* when we've walked over all the sockets we should have right now, we must - make sure to detect sockets that are removed */ - for(i = 0; i< data->numsocks; i++) { - int j; - bool stillused = FALSE; - s = data->sockets[i]; - for(j = 0; j < num; j++) { - if(s == socks[j]) { - /* this is still supervised */ - stillused = TRUE; - break; - } - } - if(stillused) - continue; + msg = Curl_node_elem(e); - entry = sh_getentry(&multi->sockhash, s); - /* if this is NULL here, the socket has been closed and notified so - already by Curl_multi_closed() */ - if(entry) { - unsigned char oldactions = data->actions[i]; - /* this socket has been removed. Decrease user count */ - entry->users--; - if(oldactions & CURL_POLL_OUT) - entry->writers--; - if(oldactions & CURL_POLL_IN) - entry->readers--; - if(!entry->users) { - if(multi->socket_cb) { - set_in_callback(multi, TRUE); - rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, - multi->socket_userp, entry->socketp); - set_in_callback(multi, FALSE); - if(rc == -1) { - multi->dead = TRUE; - return CURLM_ABORTED_BY_CALLBACK; - } - } - sh_delentry(entry, &multi->sockhash, s); - } - else { - /* still users, but remove this handle as a user of this socket */ - if(Curl_hash_delete(&entry->transfers, (char *)&data, - sizeof(struct Curl_easy *))) { - DEBUGASSERT(NULL); - } - } - } - } /* for loop over numsocks */ + /* remove the extracted entry */ + Curl_node_remove(e); - memcpy(data->sockets, socks, num*sizeof(curl_socket_t)); - memcpy(data->actions, actions, num*sizeof(char)); - data->numsocks = num; - return CURLM_OK; -} + *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); -CURLcode Curl_updatesocket(struct Curl_easy *data) -{ - if(singlesocket(data->multi, data)) - return CURLE_ABORTED_BY_CALLBACK; - return CURLE_OK; + return &msg->extmsg; + } + return NULL; } -/* - * Curl_multi_closed() - * - * Used by the connect code to tell the multi_socket code that one of the - * sockets we were using is about to be closed. This function will then - * remove it from the sockethash for this handle to make the multi_socket API - * behave properly, especially for the case when libcurl will create another - * socket again and it gets the same file descriptor number. - */ - -void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s) +void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s) { if(data) { - /* if there's still an easy handle associated with this connection */ struct Curl_multi *multi = data->multi; if(multi) { - /* this is set if this connection is part of a handle that is added to - a multi handle, and only then this is necessary */ - struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); - - if(entry) { - int rc = 0; - if(multi->socket_cb) { - set_in_callback(multi, TRUE); - rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, - multi->socket_userp, entry->socketp); - set_in_callback(multi, FALSE); - } - - /* now remove it from the socket hash */ - sh_delentry(entry, &multi->sockhash, s); - if(rc == -1) - /* This just marks the multi handle as "dead" without returning an - error code primarily because this function is used from many - places where propagating an error back is tricky. */ - multi->dead = TRUE; - } + CURL_TRC_M(data, "Curl_multi_will_close fd=%" FMT_SOCKET_T, s); + Curl_multi_ev_socket_done(multi, data, s); } } } @@ -3132,26 +2897,24 @@ static CURLMcode add_next_timeout(struct curltime now, { struct curltime *tv = &d->state.expiretime; struct Curl_llist *list = &d->state.timeoutlist; - struct Curl_llist_element *e; - struct time_node *node = NULL; + struct Curl_llist_node *e; /* move over the timeout list for this specific handle and remove all timeouts that are now passed tense and store the next pending timeout in *tv */ - for(e = list->head; e;) { - struct Curl_llist_element *n = e->next; - timediff_t diff; - node = (struct time_node *)e->ptr; - diff = Curl_timediff(node->time, now); + for(e = Curl_llist_head(list); e;) { + struct Curl_llist_node *n = Curl_node_next(e); + struct time_node *node = Curl_node_elem(e); + timediff_t diff = curlx_timediff_us(node->time, now); if(diff <= 0) /* remove outdated entry */ - Curl_llist_remove(list, e, NULL); + Curl_node_remove(e); else /* the list is sorted so get out on the first mismatch */ break; e = n; } - e = list->head; + e = Curl_llist_head(list); if(!e) { /* clear the expire times within the handles that we remove from the splay tree */ @@ -3159,10 +2922,11 @@ static CURLMcode add_next_timeout(struct curltime now, tv->tv_usec = 0; } else { + struct time_node *node = Curl_node_elem(e); /* copy the first entry to 'tv' */ memcpy(tv, &node->time, sizeof(*tv)); - /* Insert this node again into the splay. Keep the timer in the list in + /* Insert this node again into the splay. Keep the timer in the list in case we need to recompute future timers. */ multi->timetree = Curl_splayinsert(*tv, multi->timetree, &d->state.timenode); @@ -3170,6 +2934,58 @@ static CURLMcode add_next_timeout(struct curltime now, return CURLM_OK; } +struct multi_run_ctx { + struct Curl_multi *multi; + struct curltime now; + size_t run_xfers; + SIGPIPE_MEMBER(pipe_st); + bool run_cpool; +}; + +static CURLMcode multi_run_expired(struct multi_run_ctx *mrc) +{ + struct Curl_multi *multi = mrc->multi; + struct Curl_easy *data = NULL; + struct Curl_tree *t = NULL; + CURLMcode result = CURLM_OK; + + /* + * The loop following here will go on as long as there are expire-times left + * to process (compared to mrc->now) in the splay and 'data' will be + * re-assigned for every expired handle we deal with. + */ + while(1) { + /* Check if there is one (more) expired timer to deal with! This function + extracts a matching node if there is one */ + multi->timetree = Curl_splaygetbest(mrc->now, multi->timetree, &t); + if(!t) + goto out; + + data = Curl_splayget(t); /* assign this for next loop */ + if(!data) + continue; + + (void)add_next_timeout(mrc->now, multi, data); + if(data == multi->admin) { + mrc->run_cpool = TRUE; + continue; + } + + mrc->run_xfers++; + sigpipe_apply(data, &mrc->pipe_st); + result = multi_runsingle(multi, &mrc->now, data); + + if(CURLM_OK >= result) { + /* reassess event handling of data */ + result = Curl_multi_ev_assess_xfer(multi, data); + if(result) + goto out; + } + } + +out: + return result; +} static CURLMcode multi_socket(struct Curl_multi *multi, bool checkall, curl_socket_t s, @@ -3177,130 +2993,81 @@ static CURLMcode multi_socket(struct Curl_multi *multi, int *running_handles) { CURLMcode result = CURLM_OK; - struct Curl_easy *data = NULL; - struct Curl_tree *t; - struct curltime now = Curl_now(); - bool first = FALSE; - bool nosig = FALSE; - SIGPIPE_VARIABLE(pipe_st); + struct multi_run_ctx mrc; + + (void)ev_bitmask; + memset(&mrc, 0, sizeof(mrc)); + mrc.multi = multi; + mrc.now = curlx_now(); + sigpipe_init(&mrc.pipe_st); if(checkall) { /* *perform() deals with running_handles on its own */ result = curl_multi_perform(multi, running_handles); - /* walk through each easy handle and do the socket state change magic - and callbacks */ if(result != CURLM_BAD_HANDLE) { - data = multi->easyp; - while(data && !result) { - result = singlesocket(multi, data); - data = data->next; - } + /* Reassess event status of all active transfers */ + result = Curl_multi_ev_assess_xfer_bset(multi, &multi->process); } - - /* or should we fall-through and do the timer-based stuff? */ - return result; + mrc.run_cpool = TRUE; + goto out; } - if(s != CURL_SOCKET_TIMEOUT) { - struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); - - if(!entry) - /* Unmatched socket, we can't act on it but we ignore this fact. In - real-world tests it has been proved that libevent can in fact give - the application actions even though the socket was just previously - asked to get removed, so thus we better survive stray socket actions - and just move on. */ - ; - else { - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - - /* the socket can be shared by many transfers, iterate */ - Curl_hash_start_iterate(&entry->transfers, &iter); - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - data = (struct Curl_easy *)he->ptr; - DEBUGASSERT(data); - DEBUGASSERT(data->magic == CURLEASY_MAGIC_NUMBER); - - if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK)) - /* set socket event bitmask if they're not locked */ - data->conn->cselect_bits = (unsigned char)ev_bitmask; - - Curl_expire(data, 0, EXPIRE_RUN_NOW); - } - /* Now we fall-through and do the timer-based stuff, since we don't want - to force the user to have to deal with timeouts as long as at least - one connection in fact has traffic. */ - - data = NULL; /* set data to NULL again to avoid calling - multi_runsingle() in case there's no need to */ - now = Curl_now(); /* get a newer time since the multi_runsingle() loop - may have taken some time */ - } + if(s != CURL_SOCKET_TIMEOUT) { + Curl_multi_ev_expire_xfers(multi, s, &mrc.now, &mrc.run_cpool); } else { - /* Asked to run due to time-out. Clear the 'lastcall' variable to force - Curl_update_timer() to trigger a callback to the app again even if the - same timeout is still the one to run after this call. That handles the - case when the application asks libcurl to run the timeout + /* Asked to run due to time-out. Clear the 'last_expire_ts' variable to + force Curl_update_timer() to trigger a callback to the app again even + if the same timeout is still the one to run after this call. That + handles the case when the application asks libcurl to run the timeout prematurely. */ - memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); + memset(&multi->last_expire_ts, 0, sizeof(multi->last_expire_ts)); + mrc.run_cpool = TRUE; } - /* - * The loop following here will go on as long as there are expire-times left - * to process in the splay and 'data' will be re-assigned for every expired - * handle we deal with. - */ - do { - /* the first loop lap 'data' can be NULL */ - if(data) { - if(!first) { - first = TRUE; - nosig = data->set.no_signal; /* initial state */ - sigpipe_ignore(data, &pipe_st); - } - else if(data->set.no_signal != nosig) { - sigpipe_restore(&pipe_st); - sigpipe_ignore(data, &pipe_st); - nosig = data->set.no_signal; /* remember new state */ - } - result = multi_runsingle(multi, &now, data); + result = multi_run_expired(&mrc); + if(result) + goto out; - if(CURLM_OK >= result) { - /* get the socket(s) and check if the state has been changed since - last */ - result = singlesocket(multi, data); - if(result) - break; - } - } + if(mrc.run_xfers) { + /* Running transfers takes time. With a new timestamp, we might catch + * other expires which are due now. Instead of telling the application + * 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. */ + mrc.now = curlx_now(); + result = multi_run_expired(&mrc); + } - /* Check if there's one (more) expired timer to deal with! This function - extracts a matching node if there is one */ +out: + if(mrc.run_cpool) { + sigpipe_apply(multi->admin, &mrc.pipe_st); + Curl_cshutdn_perform(&multi->cshutdn, multi->admin, s); + } + sigpipe_restore(&mrc.pipe_st); - multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) { - data = t->payload; /* assign this for next loop */ - (void)add_next_timeout(now, multi, t->payload); - } + if(multi_ischanged(multi, TRUE)) + process_pending_handles(multi); - } while(t); - if(first) - sigpipe_restore(&pipe_st); + if(running_handles) { + unsigned int running = Curl_multi_xfers_running(multi); + *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; + } - *running_handles = multi->num_alive; + if(CURLM_OK >= result) + result = Curl_update_timer(multi); return result; } #undef curl_multi_setopt -CURLMcode curl_multi_setopt(struct Curl_multi *multi, +CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) { CURLMcode res = CURLM_OK; va_list param; + unsigned long uarg; + struct Curl_multi *multi = m; if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -3333,7 +3100,9 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, multi->timer_userp = va_arg(param, void *); break; case CURLMOPT_MAXCONNECTS: - multi->maxconnects = va_arg(param, long); + uarg = va_arg(param, unsigned long); + if(uarg <= UINT_MAX) + multi->maxconnects = (unsigned int)uarg; break; case CURLMOPT_MAX_HOST_CONNECTIONS: multi->max_host_connections = va_arg(param, long); @@ -3355,9 +3124,9 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, case CURLMOPT_MAX_CONCURRENT_STREAMS: { long streams = va_arg(param, long); - if(streams < 1) + if((streams < 1) || (streams > INT_MAX)) streams = 100; - multi->max_concurrent_streams = curlx_sltoui(streams); + multi->max_concurrent_streams = (unsigned int)streams; } break; default: @@ -3371,42 +3140,33 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, /* we define curl_multi_socket() in the public multi.h header */ #undef curl_multi_socket -CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s, - int *running_handles) +CURLMcode curl_multi_socket(CURLM *m, curl_socket_t s, int *running_handles) { - CURLMcode result; + struct Curl_multi *multi = m; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - result = multi_socket(multi, FALSE, s, 0, running_handles); - if(CURLM_OK >= result) - result = Curl_update_timer(multi); - return result; + return multi_socket(multi, FALSE, s, 0, running_handles); } -CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s, +CURLMcode curl_multi_socket_action(CURLM *m, curl_socket_t s, int ev_bitmask, int *running_handles) { - CURLMcode result; + struct Curl_multi *multi = m; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - result = multi_socket(multi, FALSE, s, ev_bitmask, running_handles); - if(CURLM_OK >= result) - result = Curl_update_timer(multi); - return result; + return multi_socket(multi, FALSE, s, ev_bitmask, running_handles); } -CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) +CURLMcode curl_multi_socket_all(CURLM *m, int *running_handles) { - CURLMcode result; + struct Curl_multi *multi = m; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - result = multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); - if(CURLM_OK >= result) - result = Curl_update_timer(multi); - return result; + return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); } static CURLMcode multi_timeout(struct Curl_multi *multi, + struct curltime *expire_time, long *timeout_ms) { static const struct curltime tv_zero = {0, 0}; @@ -3418,41 +3178,43 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, if(multi->timetree) { /* we have a tree of expire times */ - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); /* splay the lowest to the bottom */ multi->timetree = Curl_splay(tv_zero, multi->timetree); - - if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) { + /* this will not return NULL from a non-empty tree, but some compilers + * are not convinced of that. Analyzers are hard. */ + *expire_time = multi->timetree ? multi->timetree->key : tv_zero; + + /* 'multi->timetree' will be non-NULL here but the compilers sometimes + yell at us if we assume so */ + if(multi->timetree && + curlx_timediff_us(multi->timetree->key, now) > 0) { /* some time left before expiration */ - timediff_t diff = Curl_timediff(multi->timetree->key, now); - if(diff <= 0) - /* - * Since we only provide millisecond resolution on the returned value - * and the diff might be less than one millisecond here, we don't - * return zero as that may cause short bursts of busyloops on fast - * processors while the diff is still present but less than one - * millisecond! instead we return 1 until the time is ripe. - */ - *timeout_ms = 1; - else - /* this should be safe even on 64 bit archs, as we don't use that - overly long timeouts */ - *timeout_ms = (long)diff; + timediff_t diff = curlx_timediff_ceil(multi->timetree->key, now); + /* this should be safe even on 32-bit archs, as we do not use that + overly long timeouts */ + *timeout_ms = (long)diff; } - else + else { /* 0 means immediately */ *timeout_ms = 0; + } } - else + else { + *expire_time = tv_zero; *timeout_ms = -1; + } return CURLM_OK; } -CURLMcode curl_multi_timeout(struct Curl_multi *multi, +CURLMcode curl_multi_timeout(CURLM *m, long *timeout_ms) { + struct curltime expire_time; + struct Curl_multi *multi = m; + /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -3460,56 +3222,79 @@ CURLMcode curl_multi_timeout(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - return multi_timeout(multi, timeout_ms); + return multi_timeout(multi, &expire_time, timeout_ms); } +#define DEBUG_UPDATE_TIMER 0 + /* * Tell the application it should update its timers, if it subscribes to the * update timer callback. */ CURLMcode Curl_update_timer(struct Curl_multi *multi) { + struct curltime expire_ts; long timeout_ms; int rc; + bool set_value = FALSE; if(!multi->timer_cb || multi->dead) return CURLM_OK; - if(multi_timeout(multi, &timeout_ms)) { - return CURLM_OK; - } - if(timeout_ms < 0) { - static const struct curltime none = {0, 0}; - if(Curl_splaycomparekeys(none, multi->timer_lastcall)) { - multi->timer_lastcall = none; - /* there's no timeout now but there was one previously, tell the app to - disable it */ - set_in_callback(multi, TRUE); - rc = multi->timer_cb(multi, -1, multi->timer_userp); - set_in_callback(multi, FALSE); - if(rc == -1) { - multi->dead = TRUE; - return CURLM_ABORTED_BY_CALLBACK; - } - return CURLM_OK; - } + if(multi_timeout(multi, &expire_ts, &timeout_ms)) { return CURLM_OK; } - /* When multi_timeout() is done, multi->timetree points to the node with the - * timeout we got the (relative) time-out time for. We can thus easily check - * if this is the same (fixed) time as we got in a previous call and then - * avoid calling the callback again. */ - if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0) - return CURLM_OK; - - multi->timer_lastcall = multi->timetree->key; + if(timeout_ms < 0 && multi->last_timeout_ms < 0) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), still no timeout, no change\n"); +#endif + } + else if(timeout_ms < 0) { + /* there is no timeout now but there was one previously */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), remove timeout, " + " last_timeout=%ldms\n", multi->last_timeout_ms); +#endif + timeout_ms = -1; /* normalize */ + set_value = TRUE; + } + else if(multi->last_timeout_ms < 0) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), had no timeout, set now\n"); +#endif + set_value = TRUE; + } + else if(curlx_timediff_us(multi->last_expire_ts, expire_ts)) { + /* We had a timeout before and have one now, the absolute timestamp + * differs. The relative timeout_ms may be the same, but the starting + * point differs. Let the application restart its timer. */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), expire timestamp changed\n"); +#endif + set_value = TRUE; + } + else { + /* We have same expire time as previously. Our relative 'timeout_ms' + * may be different now, but the application has the timer running + * and we do not to tell it to start this again. */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), same expire timestamp, no change\n"); +#endif + } - set_in_callback(multi, TRUE); - rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); - set_in_callback(multi, FALSE); - if(rc == -1) { - multi->dead = TRUE; - return CURLM_ABORTED_BY_CALLBACK; + if(set_value) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), set timeout %ldms\n", timeout_ms); +#endif + multi->last_expire_ts = expire_ts; + multi->last_timeout_ms = timeout_ms; + set_in_callback(multi, TRUE); + rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); + set_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } } return CURLM_OK; } @@ -3522,13 +3307,13 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) static void multi_deltimeout(struct Curl_easy *data, expire_id eid) { - struct Curl_llist_element *e; + struct Curl_llist_node *e; struct Curl_llist *timeoutlist = &data->state.timeoutlist; /* find and remove the specific node from the list */ - for(e = timeoutlist->head; e; e = e->next) { - struct time_node *n = (struct time_node *)e->ptr; + for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { + struct time_node *n = Curl_node_elem(e); if(n->eid == eid) { - Curl_llist_remove(timeoutlist, e, NULL); + Curl_node_remove(e); return; } } @@ -3546,9 +3331,9 @@ multi_addtimeout(struct Curl_easy *data, struct curltime *stamp, expire_id eid) { - struct Curl_llist_element *e; + struct Curl_llist_node *e; struct time_node *node; - struct Curl_llist_element *prev = NULL; + struct Curl_llist_node *prev = NULL; size_t n; struct Curl_llist *timeoutlist = &data->state.timeoutlist; @@ -3561,9 +3346,9 @@ multi_addtimeout(struct Curl_easy *data, n = Curl_llist_count(timeoutlist); if(n) { /* find the correct spot in the list */ - for(e = timeoutlist->head; e; e = e->next) { - struct time_node *check = (struct time_node *)e->ptr; - timediff_t diff = Curl_timediff(check->time, node->time); + for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { + struct time_node *check = Curl_node_elem(e); + timediff_t diff = curlx_timediff(check->time, node->time); if(diff > 0) break; prev = e; @@ -3577,21 +3362,12 @@ multi_addtimeout(struct Curl_easy *data, return CURLM_OK; } -/* - * Curl_expire() - * - * given a number of milliseconds from now to use to set the 'act before - * this'-time for the transfer, to be extracted by curl_multi_timeout() - * - * The timeout will be added to a queue of timeouts if it defines a moment in - * time that is later than the current head of queue. - * - * Expire replaces a former timeout using the same id if already set. - */ -void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) +void Curl_expire_ex(struct Curl_easy *data, + const struct curltime *nowp, + timediff_t milli, expire_id id) { struct Curl_multi *multi = data->multi; - struct curltime *nowp = &data->state.expiretime; + struct curltime *curr_expire = &data->state.expiretime; struct curltime set; /* this is only interesting while there is still an associated multi struct @@ -3601,9 +3377,9 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) DEBUGASSERT(id < EXPIRE_LAST); - set = Curl_now(); - set.tv_sec += (time_t)(milli/1000); /* might be a 64 to 32 bit conversion */ - set.tv_usec += (unsigned int)(milli%1000)*1000; + set = *nowp; + set.tv_sec += (time_t)(milli/1000); /* might be a 64 to 32 bits conversion */ + set.tv_usec += (int)(milli%1000)*1000; if(set.tv_usec >= 1000000) { set.tv_sec++; @@ -3613,20 +3389,20 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) /* Remove any timer with the same id just in case. */ multi_deltimeout(data, id); - /* Add it to the timer list. It must stay in the list until it has expired + /* Add it to the timer list. It must stay in the list until it has expired in case we need to recompute the minimum timer later. */ multi_addtimeout(data, &set, id); - if(nowp->tv_sec || nowp->tv_usec) { + if(curr_expire->tv_sec || curr_expire->tv_usec) { /* This means that the struct is added as a node in the splay tree. Compare if the new time is earlier, and only remove-old/add-new if it is. */ - timediff_t diff = Curl_timediff(set, *nowp); + timediff_t diff = curlx_timediff(set, *curr_expire); int rc; if(diff > 0) { /* The current splay tree entry is sooner than this new expiry time. - We don't need to update our splay tree entry. */ + We do not need to update our splay tree entry. */ return; } @@ -3640,12 +3416,29 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) /* Indicate that we are in the splay tree and insert the new timer expiry value since it is our local minimum. */ - *nowp = set; - data->state.timenode.payload = data; - multi->timetree = Curl_splayinsert(*nowp, multi->timetree, + *curr_expire = set; + Curl_splayset(&data->state.timenode, data); + multi->timetree = Curl_splayinsert(*curr_expire, multi->timetree, &data->state.timenode); } +/* + * Curl_expire() + * + * given a number of milliseconds from now to use to set the 'act before + * this'-time for the transfer, to be extracted by curl_multi_timeout() + * + * The timeout will be added to a queue of timeouts if it defines a moment in + * time that is later than the current head of queue. + * + * Expire replaces a former timeout using the same id if already set. + */ +void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) +{ + struct curltime now = curlx_now(); + Curl_expire_ex(data, &now, milli, id); +} + /* * Curl_expire_done() * @@ -3663,7 +3456,7 @@ void Curl_expire_done(struct Curl_easy *data, expire_id id) * * Clear ALL timeout values for this handle. */ -void Curl_expire_clear(struct Curl_easy *data) +bool Curl_expire_clear(struct Curl_easy *data) { struct Curl_multi *multi = data->multi; struct curltime *nowp = &data->state.expiretime; @@ -3671,7 +3464,7 @@ void Curl_expire_clear(struct Curl_easy *data) /* this is only interesting while there is still an associated multi struct remaining! */ if(!multi) - return; + return FALSE; if(nowp->tv_sec || nowp->tv_usec) { /* Since this is an cleared time, we must remove the previous entry from @@ -3684,143 +3477,322 @@ void Curl_expire_clear(struct Curl_easy *data) if(rc) infof(data, "Internal error clearing splay node = %d", rc); - /* flush the timeout list too */ - while(list->size > 0) { - Curl_llist_remove(list, list->tail, NULL); - } + /* clear the timeout list too */ + Curl_llist_destroy(list, NULL); -#ifdef DEBUGBUILD - infof(data, "Expire cleared (transfer %p)", data); -#endif + CURL_TRC_M(data, "Expire cleared"); nowp->tv_sec = 0; nowp->tv_usec = 0; + return TRUE; } + return FALSE; } +CURLMcode curl_multi_assign(CURLM *m, curl_socket_t s, + void *hashp) +{ + struct Curl_multi *multi = m; + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + return Curl_multi_ev_assign(multi, s, hashp); +} - -CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s, - void *hashp) +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data) { - struct Curl_sh_entry *there = NULL; + DEBUGASSERT(data->mstate == MSTATE_PENDING); + + /* Remove this node from the pending set, add into process set */ + Curl_uint_bset_remove(&multi->pending, data->mid); + Curl_uint_bset_add(&multi->process, data->mid); + + multistate(data, MSTATE_CONNECT); - there = sh_getentry(&multi->sockhash, s); + /* Make sure that the handle will be processed soonish. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); +} - if(!there) - return CURLM_BAD_SOCKET; +/* process_pending_handles() moves a handle from PENDING back into the process + list and change state to CONNECT. - there->socketp = hashp; + We do not move all transfers because that can be a significant amount. + Since this is tried every now and then doing too many too often becomes a + performance problem. - return CURLM_OK; + When there is a change for connection limits like max host connections etc, + this likely only allows one new transfer. When there is a pipewait change, + it can potentially allow hundreds of new transfers. + + We could consider an improvement where we store the queue reason and allow + more pipewait rechecks than others. +*/ +static void process_pending_handles(struct Curl_multi *multi) +{ + unsigned int mid; + if(Curl_uint_bset_first(&multi->pending, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + DEBUGASSERT(data); + if(data) + move_pending_to_connect(multi, data); + } + while(Curl_uint_bset_next(&multi->pending, mid, &mid)); + } } -size_t Curl_multi_max_host_connections(struct Curl_multi *multi) +void Curl_set_in_callback(struct Curl_easy *data, bool value) { - return multi ? multi->max_host_connections : 0; + if(data && data->multi) + data->multi->in_callback = value; } -size_t Curl_multi_max_total_connections(struct Curl_multi *multi) +bool Curl_is_in_callback(struct Curl_easy *data) { - return multi ? multi->max_total_connections : 0; + return data && data->multi && data->multi->in_callback; } -/* - * When information about a connection has appeared, call this! - */ +unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) +{ + DEBUGASSERT(multi); + return multi->max_concurrent_streams; +} + +CURL **curl_multi_get_handles(CURLM *m) +{ + struct Curl_multi *multi = m; + void *entry; + unsigned int count = Curl_uint_tbl_count(&multi->xfers); + CURL **a = malloc(sizeof(struct Curl_easy *) * (count + 1)); + if(a) { + unsigned int i = 0, mid; + + if(Curl_uint_tbl_first(&multi->xfers, &mid, &entry)) { + do { + struct Curl_easy *data = entry; + DEBUGASSERT(i < count); + if(!data->state.internal) + a[i++] = data; + } + while(Curl_uint_tbl_next(&multi->xfers, mid, &mid, &entry)); + } + a[i] = NULL; /* last entry is a NULL */ + } + return a; +} -void Curl_multiuse_state(struct Curl_easy *data, - int bundlestate) /* use BUNDLE_* defines */ +CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen) { - struct connectdata *conn; DEBUGASSERT(data); DEBUGASSERT(data->multi); - conn = data->conn; - DEBUGASSERT(conn); - DEBUGASSERT(conn->bundle); + *pbuf = NULL; + *pbuflen = 0; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(!data->set.buffer_size) { + failf(data, "transfer buffer size is 0"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_buf_borrowed) { + failf(data, "attempt to borrow xfer_buf when already borrowed"); + return CURLE_AGAIN; + } + + if(data->multi->xfer_buf && + data->set.buffer_size > data->multi->xfer_buf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_buf); + data->multi->xfer_buf = NULL; + data->multi->xfer_buf_len = 0; + } + + if(!data->multi->xfer_buf) { + data->multi->xfer_buf = malloc((size_t)data->set.buffer_size); + if(!data->multi->xfer_buf) { + failf(data, "could not allocate xfer_buf of %zu bytes", + (size_t)data->set.buffer_size); + return CURLE_OUT_OF_MEMORY; + } + data->multi->xfer_buf_len = data->set.buffer_size; + } - conn->bundle->multiuse = bundlestate; - process_pending_handles(data->multi); + data->multi->xfer_buf_borrowed = TRUE; + *pbuf = data->multi->xfer_buf; + *pbuflen = data->multi->xfer_buf_len; + return CURLE_OK; } -/* process_pending_handles() moves all handles from PENDING - back into the main list and change state to CONNECT */ -static void process_pending_handles(struct Curl_multi *multi) +void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf) { - struct Curl_llist_element *e = multi->pending.head; - if(e) { - struct Curl_easy *data = e->ptr; + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_buf == buf); + data->multi->xfer_buf_borrowed = FALSE; +} - DEBUGASSERT(data->mstate == MSTATE_PENDING); +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + *pbuflen = 0; + if(!data->multi) { + 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) { + /* not large enough, get a new one */ + free(data->multi->xfer_ulbuf); + data->multi->xfer_ulbuf = NULL; + data->multi->xfer_ulbuf_len = 0; + } + + if(!data->multi->xfer_ulbuf) { + data->multi->xfer_ulbuf = malloc((size_t)data->set.upload_buffer_size); + if(!data->multi->xfer_ulbuf) { + failf(data, "could not allocate xfer_ulbuf of %zu bytes", + (size_t)data->set.upload_buffer_size); + return CURLE_OUT_OF_MEMORY; + } + data->multi->xfer_ulbuf_len = data->set.upload_buffer_size; + } - /* put it back into the main list */ - link_easy(multi, data); + data->multi->xfer_ulbuf_borrowed = TRUE; + *pbuf = data->multi->xfer_ulbuf; + *pbuflen = data->multi->xfer_ulbuf_len; + return CURLE_OK; +} - multistate(data, MSTATE_CONNECT); +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); + data->multi->xfer_ulbuf_borrowed = FALSE; +} - /* Remove this node from the list */ - Curl_llist_remove(&multi->pending, e, NULL); +CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, + size_t blen, char **pbuf) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_sockbuf_borrowed) { + failf(data, "attempt to borrow xfer_sockbuf when already borrowed"); + return CURLE_AGAIN; + } - /* Make sure that the handle will be processed soonish. */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); + if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_sockbuf); + data->multi->xfer_sockbuf = NULL; + data->multi->xfer_sockbuf_len = 0; + } - /* mark this as having been in the pending queue */ - data->state.previouslypending = TRUE; + if(!data->multi->xfer_sockbuf) { + data->multi->xfer_sockbuf = malloc(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; + return CURLE_OK; } -void Curl_set_in_callback(struct Curl_easy *data, bool value) +void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf) { - /* might get called when there is no data pointer! */ - if(data) { - if(data->multi_easy) - data->multi_easy->in_callback = value; - else if(data->multi) - data->multi->in_callback = value; - } + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); + data->multi->xfer_sockbuf_borrowed = FALSE; +} + +static void multi_xfer_bufs_free(struct Curl_multi *multi) +{ + DEBUGASSERT(multi); + Curl_safefree(multi->xfer_buf); + multi->xfer_buf_len = 0; + multi->xfer_buf_borrowed = FALSE; + Curl_safefree(multi->xfer_ulbuf); + multi->xfer_ulbuf_len = 0; + multi->xfer_ulbuf_borrowed = FALSE; + Curl_safefree(multi->xfer_sockbuf); + multi->xfer_sockbuf_len = 0; + multi->xfer_sockbuf_borrowed = FALSE; +} + +struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, + unsigned int mid) +{ + struct Curl_easy *data = mid ? Curl_uint_tbl_get(&multi->xfers, mid) : NULL; + if(data && GOOD_EASY_HANDLE(data)) + return data; + CURL_TRC_M(multi->admin, "invalid easy handle in xfer table for mid=%u", + mid); + Curl_uint_tbl_remove(&multi->xfers, mid); + return NULL; } -bool Curl_is_in_callback(struct Curl_easy *easy) +unsigned int Curl_multi_xfers_running(struct Curl_multi *multi) { - return ((easy->multi && easy->multi->in_callback) || - (easy->multi_easy && easy->multi_easy->in_callback)); + return multi->xfers_alive; } #ifdef DEBUGBUILD -void Curl_multi_dump(struct Curl_multi *multi) +static void multi_xfer_dump(struct Curl_multi *multi, unsigned int mid, + void *entry) { - struct Curl_easy *data; - int i; - fprintf(stderr, "* Multi status: %d handles, %d alive\n", - multi->num_easy, multi->num_alive); - for(data = multi->easyp; data; data = data->next) { - if(data->mstate < MSTATE_COMPLETED) { - /* only display handles that are not completed */ - fprintf(stderr, "handle %p, state %s, %d sockets\n", - (void *)data, - statename[data->mstate], data->numsocks); - for(i = 0; i < data->numsocks; i++) { - curl_socket_t s = data->sockets[i]; - struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); - - fprintf(stderr, "%d ", (int)s); - if(!entry) { - fprintf(stderr, "INTERNAL CONFUSION\n"); - continue; - } - fprintf(stderr, "[%s %s] ", - (entry->action&CURL_POLL_IN)?"RECVING":"", - (entry->action&CURL_POLL_OUT)?"SENDING":""); - } - if(data->numsocks) - fprintf(stderr, "\n"); - } + struct Curl_easy *data = entry; + + (void)multi; + if(!data) { + fprintf(stderr, "mid=%u, entry=NULL, bug in xfer table?\n", mid); + } + else { + fprintf(stderr, "mid=%u, magic=%s, p=%p, id=%" FMT_OFF_T ", url=%s\n", + mid, (data->magic == CURLEASY_MAGIC_NUMBER) ? "GOOD" : "BAD!", + (void *)data, data->id, data->state.url); } } -#endif -unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) +static void multi_xfer_tbl_dump(struct Curl_multi *multi) { - DEBUGASSERT(multi); - return multi->max_concurrent_streams; + unsigned int mid; + void *entry; + fprintf(stderr, "=== multi xfer table (count=%u, capacity=%u\n", + Curl_uint_tbl_count(&multi->xfers), + Curl_uint_tbl_capacity(&multi->xfers)); + if(Curl_uint_tbl_first(&multi->xfers, &mid, &entry)) { + multi_xfer_dump(multi, mid, entry); + while(Curl_uint_tbl_next(&multi->xfers, mid, &mid, &entry)) + multi_xfer_dump(multi, mid, entry); + } + fprintf(stderr, "===\n"); + fflush(stderr); } +#endif /* DEBUGBUILD */ diff --git a/Utilities/cmcurl/lib/multi_ev.c b/Utilities/cmcurl/lib/multi_ev.c new file mode 100644 index 00000000000..21d28676195 --- /dev/null +++ b/Utilities/cmcurl/lib/multi_ev.c @@ -0,0 +1,637 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + +#include "urldata.h" +#include "url.h" +#include "cfilters.h" +#include "curl_trc.h" +#include "multiif.h" +#include "curlx/timeval.h" +#include "multi_ev.h" +#include "select.h" +#include "uint-bset.h" +#include "uint-spbset.h" +#include "uint-table.h" +#include "curlx/warnless.h" +#include "multihandle.h" +#include "socks.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +static void mev_in_callback(struct Curl_multi *multi, bool value) +{ + multi->in_callback = value; +} + +#define CURL_MEV_CONN_HASH_SIZE 3 + +/* Information about a socket for which we inform the libcurl application + * what to supervise (CURL_POLL_IN/CURL_POLL_OUT/CURL_POLL_REMOVE) + */ +struct mev_sh_entry { + struct uint_spbset xfers; /* bitset of transfers `mid`s on this socket */ + struct connectdata *conn; /* connection using this socket or NULL */ + void *user_data; /* libcurl app data via curl_multi_assign() */ + unsigned int action; /* CURL_POLL_IN/CURL_POLL_OUT we last told the + * libcurl application to watch out for */ + unsigned int readers; /* this many transfers want to read */ + unsigned int writers; /* this many transfers want to write */ +}; + +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) +{ + struct mev_sh_entry *entry = (struct mev_sh_entry *)freethis; + Curl_uint_spbset_destroy(&entry->xfers); + free(entry); +} + +/* look up a given socket in the socket hash, skip invalid sockets */ +static struct mev_sh_entry * +mev_sh_entry_get(struct Curl_hash *sh, curl_socket_t s) +{ + if(s != CURL_SOCKET_BAD) { + /* only look for proper sockets */ + return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + } + return NULL; +} + +/* make sure this socket is present in the hash for this handle */ +static struct mev_sh_entry * +mev_sh_entry_add(struct Curl_hash *sh, curl_socket_t s) +{ + struct mev_sh_entry *there = mev_sh_entry_get(sh, s); + struct mev_sh_entry *check; + + if(there) { + /* it is present, return fine */ + return there; + } + + /* not present, add it */ + check = calloc(1, sizeof(struct mev_sh_entry)); + if(!check) + return NULL; /* major failure */ + + Curl_uint_spbset_init(&check->xfers); + + /* make/add new hash entry */ + if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { + mev_sh_entry_dtor(check); + return NULL; /* major failure */ + } + + return check; /* things are good in sockhash land */ +} + +/* delete the given socket entry from the hash */ +static void mev_sh_entry_kill(struct Curl_multi *multi, curl_socket_t s) +{ + Curl_hash_delete(&multi->ev.sh_entries, (char *)&s, sizeof(curl_socket_t)); +} + +static size_t mev_sh_entry_user_count(struct mev_sh_entry *e) +{ + return Curl_uint_spbset_count(&e->xfers) + (e->conn ? 1 : 0); +} + +static bool mev_sh_entry_xfer_known(struct mev_sh_entry *e, + struct Curl_easy *data) +{ + return Curl_uint_spbset_contains(&e->xfers, data->mid); +} + +static bool mev_sh_entry_conn_known(struct mev_sh_entry *e, + struct connectdata *conn) +{ + return (e->conn == conn); +} + +static bool mev_sh_entry_xfer_add(struct mev_sh_entry *e, + struct Curl_easy *data) +{ + /* detect weird values */ + DEBUGASSERT(mev_sh_entry_user_count(e) < 100000); + return Curl_uint_spbset_add(&e->xfers, data->mid); +} + +static bool mev_sh_entry_conn_add(struct mev_sh_entry *e, + struct connectdata *conn) +{ + /* detect weird values */ + DEBUGASSERT(mev_sh_entry_user_count(e) < 100000); + DEBUGASSERT(!e->conn); + if(e->conn) + return FALSE; + e->conn = conn; + return TRUE; +} + + +static bool mev_sh_entry_xfer_remove(struct mev_sh_entry *e, + struct Curl_easy *data) +{ + bool present = Curl_uint_spbset_contains(&e->xfers, data->mid); + if(present) + Curl_uint_spbset_remove(&e->xfers, data->mid); + return present; +} + +static bool mev_sh_entry_conn_remove(struct mev_sh_entry *e, + struct connectdata *conn) +{ + DEBUGASSERT(e->conn == conn); + if(e->conn == conn) { + e->conn = NULL; + return TRUE; + } + return FALSE; +} + +/* Purge any information about socket `s`. + * Let the socket callback know as well when necessary */ +static CURLMcode mev_forget_socket(struct Curl_multi *multi, + struct Curl_easy *data, + curl_socket_t s, + const char *cause) +{ + struct mev_sh_entry *entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + int rc = 0; + + if(!entry) /* we never knew or already forgot about this socket */ + return CURLM_OK; + + /* We managed this socket before, tell the socket callback to forget it. */ + if(multi->socket_cb) { + CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)", + cause, s); + mev_in_callback(multi, TRUE); + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->user_data); + mev_in_callback(multi, FALSE); + } + + mev_sh_entry_kill(multi, s); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; +} + +static CURLMcode mev_sh_entry_update(struct Curl_multi *multi, + struct Curl_easy *data, + struct mev_sh_entry *entry, + curl_socket_t s, + unsigned char last_action, + unsigned char cur_action) +{ + int rc, comboaction; + + /* we should only be called when the callback exists */ + DEBUGASSERT(multi->socket_cb); + if(!multi->socket_cb) + return CURLM_OK; + + /* Transfer `data` goes from `last_action` to `cur_action` on socket `s` + * with `multi->ev.sh_entries` entry `entry`. Update `entry` and trigger + * `multi->socket_cb` on change, if the callback is set. */ + if(last_action == cur_action) /* nothing from `data` changed */ + return CURLM_OK; + + if(last_action & CURL_POLL_IN) { + DEBUGASSERT(entry->readers); + if(!(cur_action & CURL_POLL_IN)) + entry->readers--; + } + else if(cur_action & CURL_POLL_IN) + entry->readers++; + + if(last_action & CURL_POLL_OUT) { + DEBUGASSERT(entry->writers); + if(!(cur_action & CURL_POLL_OUT)) + entry->writers--; + } + else if(cur_action & CURL_POLL_OUT) + entry->writers++; + + DEBUGASSERT(entry->readers <= mev_sh_entry_user_count(entry)); + DEBUGASSERT(entry->writers <= mev_sh_entry_user_count(entry)); + DEBUGASSERT(entry->writers + entry->readers); + + CURL_TRC_M(data, "ev update fd=%" FMT_SOCKET_T ", action '%s%s' -> '%s%s'" + " (%d/%d r/w)", s, + (last_action & CURL_POLL_IN) ? "IN" : "", + (last_action & CURL_POLL_OUT) ? "OUT" : "", + (cur_action & CURL_POLL_IN) ? "IN" : "", + (cur_action & CURL_POLL_OUT) ? "OUT" : "", + entry->readers, entry->writers); + + comboaction = (entry->writers ? CURL_POLL_OUT : 0) | + (entry->readers ? CURL_POLL_IN : 0); + if(((int)entry->action == comboaction)) /* nothing for socket changed */ + return CURLM_OK; + + CURL_TRC_M(data, "ev update call(fd=%" FMT_SOCKET_T ", ev=%s%s)", + s, (comboaction & CURL_POLL_IN) ? "IN" : "", + (comboaction & CURL_POLL_OUT) ? "OUT" : ""); + mev_in_callback(multi, TRUE); + rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, + entry->user_data); + + mev_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + entry->action = (unsigned int)comboaction; + return CURLM_OK; +} + +static CURLMcode mev_pollset_diff(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn, + struct easy_pollset *ps, + struct easy_pollset *prev_ps) +{ + struct mev_sh_entry *entry; + curl_socket_t s; + unsigned int i, j; + CURLMcode mresult; + + /* The transfer `data` reports in `ps` the sockets it is interested + * in and which combination of CURL_POLL_IN/CURL_POLL_OUT it wants + * to have monitored for events. + * There can be more than 1 transfer interested in the same socket + * and 1 transfer might be interested in more than 1 socket. + * `prev_ps` is the pollset copy from the previous call here. On + * the 1st call it will be empty. + */ + DEBUGASSERT(ps); + DEBUGASSERT(prev_ps); + + /* Handle changes to sockets the transfer is interested in. */ + for(i = 0; i < ps->num; i++) { + unsigned char last_action; + bool first_time = FALSE; /* data/conn appears first time on socket */ + + s = ps->sockets[i]; + /* Have we handled this socket before? */ + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + if(!entry) { + /* new socket, add new entry */ + first_time = TRUE; + entry = mev_sh_entry_add(&multi->ev.sh_entries, s); + if(!entry) /* fatal */ + return CURLM_OUT_OF_MEMORY; + CURL_TRC_M(data, "ev new entry fd=%" FMT_SOCKET_T, s); + } + else if(conn) { + first_time = !mev_sh_entry_conn_known(entry, conn); + } + else { + first_time = !mev_sh_entry_xfer_known(entry, data); + } + + /* What was the previous action the transfer had regarding this socket? + * If the transfer is new to the socket, disregard the information + * in `last_poll`, because the socket might have been destroyed and + * reopened. We'd have cleared the sh_entry for that, but the socket + * might still be mentioned in the hashed pollsets. */ + last_action = 0; + if(first_time) { + if(conn) { + if(!mev_sh_entry_conn_add(entry, conn)) + return CURLM_OUT_OF_MEMORY; + } + else { + if(!mev_sh_entry_xfer_add(entry, data)) + return CURLM_OUT_OF_MEMORY; + } + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", added %s #%" FMT_OFF_T + ", total=%u/%d (xfer/conn)", s, + conn ? "connection" : "transfer", + conn ? conn->connection_id : data->mid, + Curl_uint_spbset_count(&entry->xfers), + entry->conn ? 1 : 0); + } + else { + for(j = 0; j < prev_ps->num; j++) { + if(s == prev_ps->sockets[j]) { + last_action = prev_ps->actions[j]; + break; + } + } + } + /* track readers/writers changes and report to socket callback */ + mresult = mev_sh_entry_update(multi, data, entry, s, + last_action, ps->actions[i]); + if(mresult) + return mresult; + } + + /* Handle changes to sockets the transfer is NO LONGER interested in. */ + for(i = 0; i < prev_ps->num; i++) { + bool stillused = FALSE; + + s = prev_ps->sockets[i]; + for(j = 0; j < ps->num; j++) { + if(s == ps->sockets[j]) { + /* socket is still supervised */ + stillused = TRUE; + break; + } + } + if(stillused) + continue; + + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + /* if entry does not exist, we were either never told about it or + * have already cleaned up this socket via Curl_multi_ev_socket_done(). + * In other words: this is perfectly normal */ + if(!entry) + continue; + + if(conn && !mev_sh_entry_conn_remove(entry, conn)) { + /* `conn` says in `prev_ps` that it had been using a socket, + * but `conn` has not been registered for it. + * This should not happen if our book-keeping is correct? */ + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", conn lost " + "interest but is not registered", s); + DEBUGASSERT(NULL); + continue; + } + + if(!conn && !mev_sh_entry_xfer_remove(entry, data)) { + /* `data` says in `prev_ps` that it had been using a socket, + * but `data` has not been registered for it. + * This should not happen if our book-keeping is correct? */ + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", transfer lost " + "interest but is not registered", s); + DEBUGASSERT(NULL); + continue; + } + + if(mev_sh_entry_user_count(entry)) { + /* track readers/writers changes and report to socket callback */ + mresult = mev_sh_entry_update(multi, data, entry, s, + prev_ps->actions[i], 0); + if(mresult) + return mresult; + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", removed transfer, " + "total=%u/%d (xfer/conn)", s, + Curl_uint_spbset_count(&entry->xfers), + entry->conn ? 1 : 0); + } + else { + mresult = mev_forget_socket(multi, data, s, "last user gone"); + if(mresult) + return mresult; + } + } /* for loop over num */ + + /* Remember for next time */ + memcpy(prev_ps, ps, sizeof(*prev_ps)); + return CURLM_OK; +} + +static void mev_pollset_dtor(void *key, size_t klen, void *entry) +{ + (void)key; + (void)klen; + free(entry); +} + +static struct easy_pollset* +mev_add_new_conn_pollset(struct connectdata *conn) +{ + struct easy_pollset *ps; + + ps = calloc(1, sizeof(*ps)); + 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 = calloc(1, sizeof(*ps)); + 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 void mev_init_cur_pollset(struct easy_pollset *ps, + struct Curl_easy *data, + struct connectdata *conn) +{ + memset(ps, 0, sizeof(*ps)); + if(conn) + Curl_conn_adjust_pollset(data, conn, ps); + else if(data) + Curl_multi_getsock(data, ps, "ev assess"); +} + +static CURLMcode mev_assess(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + if(multi && multi->socket_cb) { + struct easy_pollset ps, *last_ps; + + mev_init_cur_pollset(&ps, data, conn); + last_ps = mev_get_last_pollset(data, conn); + + if(!last_ps && ps.num) { + if(conn) + last_ps = mev_add_new_conn_pollset(conn); + else + last_ps = mev_add_new_xfer_pollset(data); + if(!last_ps) + return CURLM_OUT_OF_MEMORY; + } + + if(last_ps) + return mev_pollset_diff(multi, data, conn, &ps, last_ps); + else + DEBUGASSERT(!ps.num); + } + return CURLM_OK; +} + +CURLMcode Curl_multi_ev_assess_xfer(struct Curl_multi *multi, + struct Curl_easy *data) +{ + return mev_assess(multi, data, NULL); +} + +CURLMcode Curl_multi_ev_assess_conn(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + return mev_assess(multi, data, conn); +} + +CURLMcode Curl_multi_ev_assess_xfer_bset(struct Curl_multi *multi, + struct uint_bset *set) +{ + unsigned int mid; + CURLMcode result = CURLM_OK; + + if(multi && multi->socket_cb && Curl_uint_bset_first(set, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(data) + result = Curl_multi_ev_assess_xfer(multi, data); + } + while(!result && Curl_uint_bset_next(set, mid, &mid)); + } + return result; +} + + +CURLMcode Curl_multi_ev_assign(struct Curl_multi *multi, + curl_socket_t s, + void *user_data) +{ + struct mev_sh_entry *e = mev_sh_entry_get(&multi->ev.sh_entries, s); + if(!e) + return CURLM_BAD_SOCKET; + e->user_data = user_data; + return CURLM_OK; +} + +void Curl_multi_ev_expire_xfers(struct Curl_multi *multi, + curl_socket_t s, + const struct curltime *nowp, + bool *run_cpool) +{ + struct mev_sh_entry *entry; + + DEBUGASSERT(s != CURL_SOCKET_TIMEOUT); + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + + /* Unmatched socket, we cannot act on it but we ignore this fact. In + real-world tests it has been proved that libevent can in fact give + the application actions even though the socket was just previously + asked to get removed, so thus we better survive stray socket actions + and just move on. */ + if(entry) { + struct Curl_easy *data; + unsigned int mid; + + if(Curl_uint_spbset_first(&entry->xfers, &mid)) { + do { + data = Curl_multi_get_easy(multi, mid); + if(data) { + /* Expire with out current now, so we will get it below when + * asking the splaytree for expired transfers. */ + Curl_expire_ex(data, nowp, 0, EXPIRE_RUN_NOW); + } + } + while(Curl_uint_spbset_next(&entry->xfers, mid, &mid)); + } + + if(entry->conn) + *run_cpool = TRUE; + } +} + +void Curl_multi_ev_socket_done(struct Curl_multi *multi, + struct Curl_easy *data, curl_socket_t s) +{ + mev_forget_socket(multi, data, s, "socket done"); +} + +void Curl_multi_ev_xfer_done(struct Curl_multi *multi, + struct Curl_easy *data) +{ + DEBUGASSERT(!data->conn); /* transfer should have been detached */ + if(data != multi->admin) { + (void)mev_assess(multi, data, NULL); + Curl_meta_remove(data, CURL_META_MEV_POLLSET); + } +} + +void Curl_multi_ev_conn_done(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + (void)mev_assess(multi, data, conn); + Curl_conn_meta_remove(conn, CURL_META_MEV_POLLSET); +} + +#define CURL_MEV_PS_HASH_SLOTS (991) /* nice prime */ + +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); +} + +void Curl_multi_ev_cleanup(struct Curl_multi *multi) +{ + Curl_hash_destroy(&multi->ev.sh_entries); +} diff --git a/Utilities/cmcurl/lib/multi_ev.h b/Utilities/cmcurl/lib/multi_ev.h new file mode 100644 index 00000000000..06be842fb96 --- /dev/null +++ b/Utilities/cmcurl/lib/multi_ev.h @@ -0,0 +1,83 @@ +#ifndef HEADER_CURL_MULTI_EV_H +#define HEADER_CURL_MULTI_EV_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 "hash.h" + +struct Curl_easy; +struct Curl_multi; +struct easy_pollset; +struct uint_bset; + +/* meta key for event pollset at easy handle or connection */ +#define CURL_META_MEV_POLLSET "meta:mev:ps" + +struct curl_multi_ev { + struct Curl_hash sh_entries; +}; + +/* Setup/teardown of multi event book-keeping. */ +void Curl_multi_ev_init(struct Curl_multi *multi, size_t hashsize); +void Curl_multi_ev_cleanup(struct Curl_multi *multi); + +/* Assign a 'user_data' to be passed to the socket callback when + * invoked with the given socket. This will fail if this socket + * is not active, e.g. the application has not been told to monitor it. */ +CURLMcode Curl_multi_ev_assign(struct Curl_multi *multi, curl_socket_t s, + void *user_data); + +/* Assess the transfer by getting its current pollset, compute + * any changes to the last one and inform the application's socket + * callback if things have changed. */ +CURLMcode Curl_multi_ev_assess_xfer(struct Curl_multi *multi, + struct Curl_easy *data); +/* Assess all easy handles on the list */ +CURLMcode Curl_multi_ev_assess_xfer_bset(struct Curl_multi *multi, + struct uint_bset *set); +/* Assess the connection by getting its current pollset */ +CURLMcode Curl_multi_ev_assess_conn(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn); + +/* Expire all transfers tied to the given socket */ +void Curl_multi_ev_expire_xfers(struct Curl_multi *multi, + curl_socket_t s, + const struct curltime *nowp, + bool *run_cpool); + +/* Socket will be closed, forget anything we know about it. */ +void Curl_multi_ev_socket_done(struct Curl_multi *multi, + struct Curl_easy *data, curl_socket_t s); + +/* Transfer is removed from the multi */ +void Curl_multi_ev_xfer_done(struct Curl_multi *multi, + struct Curl_easy *data); + +/* Connection is being destroyed */ +void Curl_multi_ev_conn_done(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn); + +#endif /* HEADER_CURL_MULTI_EV_H */ diff --git a/Utilities/cmcurl/lib/multihandle.h b/Utilities/cmcurl/lib/multihandle.h index 5b16bb605fe..04db02f0b16 100644 --- a/Utilities/cmcurl/lib/multihandle.h +++ b/Utilities/cmcurl/lib/multihandle.h @@ -27,41 +27,49 @@ #include "llist.h" #include "hash.h" #include "conncache.h" +#include "cshutdn.h" +#include "hostip.h" +#include "multi_ev.h" #include "psl.h" #include "socketpair.h" +#include "uint-bset.h" +#include "uint-spbset.h" +#include "uint-table.h" struct connectdata; +struct Curl_easy; struct Curl_message { - struct Curl_llist_element list; + 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 statename[] array as - well! -*/ +/* NOTE: if you add a state here, add the name to the statenames[] array + * in curl_trc.c as well! + */ typedef enum { MSTATE_INIT, /* 0 - start in this state */ MSTATE_PENDING, /* 1 - no connections, waiting for one */ - MSTATE_CONNECT, /* 2 - resolve/connect has been sent off */ - MSTATE_RESOLVING, /* 3 - awaiting the resolve to finalize */ - MSTATE_CONNECTING, /* 4 - awaiting the TCP connect to finalize */ - MSTATE_TUNNELING, /* 5 - awaiting HTTPS proxy SSL initialization to + MSTATE_SETUP, /* 2 - start a new transfer */ + MSTATE_CONNECT, /* 3 - resolve/connect has been sent off */ + MSTATE_RESOLVING, /* 4 - awaiting the resolve to finalize */ + MSTATE_CONNECTING, /* 5 - awaiting the TCP connect to finalize */ + MSTATE_TUNNELING, /* 6 - awaiting HTTPS proxy SSL initialization to complete and/or proxy CONNECT to finalize */ - MSTATE_PROTOCONNECT, /* 6 - initiate protocol connect procedure */ - MSTATE_PROTOCONNECTING, /* 7 - completing the protocol-specific connect + MSTATE_PROTOCONNECT, /* 7 - initiate protocol connect procedure */ + MSTATE_PROTOCONNECTING, /* 8 - completing the protocol-specific connect phase */ - MSTATE_DO, /* 8 - start send off the request (part 1) */ - MSTATE_DOING, /* 9 - sending off the request (part 1) */ - MSTATE_DOING_MORE, /* 10 - send off the request (part 2) */ - MSTATE_DID, /* 11 - done sending off request */ - MSTATE_PERFORMING, /* 12 - transfer data */ - MSTATE_RATELIMITING, /* 13 - wait because limit-rate exceeded */ - MSTATE_DONE, /* 14 - post data transfer operation */ - MSTATE_COMPLETED, /* 15 - operation complete */ - MSTATE_MSGSENT, /* 16 - the operation complete message is sent */ - MSTATE_LAST /* 17 - not a true state, never use this */ + MSTATE_DO, /* 9 - start send off the request (part 1) */ + MSTATE_DOING, /* 10 - sending off the request (part 1) */ + MSTATE_DOING_MORE, /* 11 - send off the request (part 2) */ + MSTATE_DID, /* 12 - done sending off request */ + MSTATE_PERFORMING, /* 13 - transfer data */ + MSTATE_RATELIMITING, /* 14 - wait because limit-rate exceeded */ + MSTATE_DONE, /* 15 - post data transfer operation */ + MSTATE_COMPLETED, /* 16 - operation complete */ + MSTATE_MSGSENT, /* 17 - the operation complete message is sent */ + MSTATE_LAST /* 18 - not a true state, never use this */ } CURLMstate; /* we support N sockets per easy handle. Set the corresponding bit to what @@ -79,30 +87,24 @@ typedef enum { /* value for MAXIMUM CONCURRENT STREAMS upper limit */ #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) -/* Curl_multi SSL backend-specific data; declared differently by each SSL - backend */ -struct multi_ssl_backend_data; - /* This is the struct known as CURLM on the outside */ struct Curl_multi { /* First a simple identifier to easier detect if a user mix up this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */ unsigned int magic; - /* We have a doubly-linked list with easy handles */ - struct Curl_easy *easyp; - struct Curl_easy *easylp; /* last node */ - - int num_easy; /* amount of entries in the linked list above. */ - int num_alive; /* amount of easy handles that are added but have not yet - reached COMPLETE state */ + unsigned int xfers_alive; /* amount of added transfers that have + not yet reached COMPLETE state */ + struct uint_tbl xfers; /* transfers added to this multi */ + /* Each transfer's mid may be present in at most one of these */ + struct uint_bset process; /* transfer being processed */ + struct uint_bset pending; /* transfers in waiting (conn limit etc.) */ + struct uint_bset msgsent; /* transfers done with message for application */ struct Curl_llist msglist; /* a list of messages from completed transfers */ - struct Curl_llist pending; /* Curl_easys that are in the - MSTATE_PENDING state */ - struct Curl_llist msgsent; /* Curl_easys that are in the - MSTATE_MSGSENT state */ + struct Curl_easy *admin; /* internal easy handle for admin operations. + gets assigned `mid` 0 on multi init */ /* callback function and user data pointer for the *socket() API */ curl_socket_callback socket_cb; @@ -112,8 +114,8 @@ struct Curl_multi { curl_push_callback push_cb; void *push_userp; - /* Hostname cache */ - struct Curl_hash hostcache; + struct Curl_dnscache dnscache; /* DNS cache */ + struct Curl_ssl_scache *ssl_scache; /* TLS session pool */ #ifdef USE_LIBPSL /* PSL cache. */ @@ -124,20 +126,29 @@ struct Curl_multi { times of all currently set timers */ struct Curl_tree *timetree; -#if defined(USE_SSL) - struct multi_ssl_backend_data *ssl_backend_data; -#endif - - /* 'sockhash' is the lookup hash for socket descriptor => easy handles (note - the pluralis form, there can be more than one easy handle waiting on the - same actual socket) */ - struct Curl_hash sockhash; - - /* Shared connection cache (bundles)*/ - struct conncache conn_cache; - - long maxconnects; /* if >0, a fixed limit of the maximum number of entries - we're allowed to grow the connection cache to */ + /* 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 */ + + /* multi event related things */ + struct curl_multi_ev ev; + + /* `proto_hash` is a general key-value store for protocol implementations + * with the lifetime of the multi handle. The number of elements kept here + * should be in the order of supported protocols (and sub-protocols like + * TLS), *not* in the order of connections or current transfers! + * Elements need to be added with their own destructor to be invoked when + * the multi handle is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash proto_hash; + + struct cshutdn cshutdn; /* connection shutdown handling */ + struct cpool cpool; /* connection pool (bundles) */ long max_host_connections; /* if >0, a fixed limit of the maximum number of connections per host */ @@ -148,18 +159,22 @@ struct Curl_multi { /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - struct curltime timer_lastcall; /* the fixed time for the timeout for the - previous callback */ - unsigned int max_concurrent_streams; + long last_timeout_ms; /* the last timeout value set via timer_cb */ + struct curltime last_expire_ts; /* timestamp of last expiry */ #ifdef USE_WINSOCK - WSAEVENT wsa_event; /* winsock event used for waits */ + WSAEVENT wsa_event; /* Winsock event used for waits */ #else #ifdef ENABLE_WAKEUP - curl_socket_t wakeup_pair[2]; /* socketpair() used for wakeup - 0 is used for read, 1 is used for write */ + curl_socket_t wakeup_pair[2]; /* eventfd()/pipe()/socketpair() used for + wakeup 0 is used for read, 1 is used + for write */ #endif #endif + unsigned int max_concurrent_streams; + unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of + entries we are allowed to grow the connection + cache to */ #define IPV6_UNKNOWN 0 #define IPV6_DEAD 1 #define IPV6_WORKS 2 @@ -172,6 +187,9 @@ struct Curl_multi { #endif BIT(dead); /* a callback returned error, everything needs to crash and burn */ + 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 */ #ifdef DEBUGBUILD BIT(warned); /* true after user warned of DEBUGBUILD */ #endif diff --git a/Utilities/cmcurl/lib/multiif.h b/Utilities/cmcurl/lib/multiif.h index cae02cb08c3..eae634ab405 100644 --- a/Utilities/cmcurl/lib/multiif.h +++ b/Utilities/cmcurl/lib/multiif.h @@ -28,9 +28,11 @@ * Prototypes for library-wide functions provided by multi.c */ -CURLcode Curl_updatesocket(struct Curl_easy *data); void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id); -void Curl_expire_clear(struct Curl_easy *data); +void Curl_expire_ex(struct Curl_easy *data, + const struct curltime *nowp, + timediff_t milli, expire_id id); +bool Curl_expire_clear(struct Curl_easy *data); void Curl_expire_done(struct Curl_easy *data, expire_id id); CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT; void Curl_attach_connection(struct Curl_easy *data, @@ -38,13 +40,18 @@ void Curl_attach_connection(struct Curl_easy *data, void Curl_detach_connection(struct Curl_easy *data); bool Curl_multiplex_wanted(const struct Curl_multi *multi); void Curl_set_in_callback(struct Curl_easy *data, bool value); -bool Curl_is_in_callback(struct Curl_easy *easy); +bool Curl_is_in_callback(struct Curl_easy *data); CURLcode Curl_preconnect(struct Curl_easy *data); +void Curl_multi_connchanged(struct Curl_multi *multi); + /* Internal version of curl_multi_init() accepts size parameters for the socket, connection and dns hashes */ -struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize, - int dnssize); +struct Curl_multi *Curl_multi_handle(unsigned int xfer_table_size, + size_t hashsize, + size_t chashsize, + size_t dnssize, + size_t sesssize); /* the write bits start at bit 16 for the *getsock() bitmap */ #define GETSOCK_WRITEBITSTART 16 @@ -57,35 +64,16 @@ struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize, /* set the bit for the given sock number to make the bitmap for readable */ #define GETSOCK_READSOCK(x) (1 << (x)) -#ifdef DEBUGBUILD - /* - * Curl_multi_dump is not a stable public function, this is only meant to - * allow easier tracking of the internal handle's state and what sockets - * they use. Only for research and development DEBUGBUILD enabled builds. - */ -void Curl_multi_dump(struct Curl_multi *multi); -#endif - -/* Return the value of the CURLMOPT_MAX_HOST_CONNECTIONS option */ -size_t Curl_multi_max_host_connections(struct Curl_multi *multi); - -/* Return the value of the CURLMOPT_MAX_TOTAL_CONNECTIONS option */ -size_t Curl_multi_max_total_connections(struct Curl_multi *multi); +/* mask for checking if read and/or write is set for index x */ +#define GETSOCK_MASK_RW(x) (GETSOCK_READSOCK(x)|GETSOCK_WRITESOCK(x)) -void Curl_multiuse_state(struct Curl_easy *data, - int bundlestate); /* use BUNDLE_* defines */ - -/* - * Curl_multi_closed() - * - * Used by the connect code to tell the multi_socket code that one of the - * sockets we were using is about to be closed. This function will then - * remove it from the sockethash for this handle to make the multi_socket API - * behave properly, especially for the case when libcurl will create another - * socket again and it gets the same file descriptor number. +/** + * Let the multi handle know that the socket is about to be closed. + * The multi will then remove anything it knows about the socket, so + * when the OS is using this socket (number) again subsequently, + * the internal book keeping will not get confused. */ - -void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s); +void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s); /* * Add a handle and move it into PERFORM state at once. For pushed streams. @@ -98,4 +86,91 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, /* Return the value of the CURLMOPT_MAX_CONCURRENT_STREAMS option */ unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi); +void Curl_multi_getsock(struct Curl_easy *data, + struct easy_pollset *ps, + const char *caller); + +/** + * Borrow the transfer buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used in one + * multi processing of the easy handle. It MUST be returned to the + * multi before it can be borrowed again. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @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_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen); +/** + * Release the borrowed buffer. All references into the buffer become + * invalid after this. + * @param buf the buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf); + +/** + * Borrow the upload buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used in one + * multi processing of the easy handle. It MUST be returned to the + * multi before it can be borrowed again. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @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_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen); + +/** + * Release the borrowed upload buffer. All references into the buffer become + * invalid after this. + * @param buf the upload buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf); + +/** + * Borrow the socket scratch buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used for + * direct socket I/O operation by one connection at a time and MUST be + * returned to the multi before the I/O call returns. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @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_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, + size_t blen, char **pbuf); +/** + * Release the borrowed buffer. All references into the buffer become + * invalid after this. + * @param buf the buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf); + +/** + * Get the easy handle for the given mid. + * Returns NULL if not found. + */ +struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, + unsigned int mid); + +/* Get the # of transfers current in process/pending. */ +unsigned int Curl_multi_xfers_running(struct Curl_multi *multi); + #endif /* HEADER_CURL_MULTIIF_H */ diff --git a/Utilities/cmcurl/lib/netrc.c b/Utilities/cmcurl/lib/netrc.c index e6a09b187a5..7df3f17fc3a 100644 --- a/Utilities/cmcurl/lib/netrc.c +++ b/Utilities/cmcurl/lib/netrc.c @@ -26,14 +26,20 @@ #ifndef CURL_DISABLE_NETRC #ifdef HAVE_PWD_H +#ifdef __AMIGA__ +#undef __NO_NET_API /* required for AmigaOS to declare getpwuid() */ +#endif #include +#ifdef __AMIGA__ +#define __NO_NET_API +#endif #endif #include #include "netrc.h" -#include "strtok.h" #include "strcase.h" #include "curl_get_line.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -49,237 +55,344 @@ enum host_lookup_state { MACDEF }; -#define NETRC_FILE_MISSING 1 -#define NETRC_FAILED -1 -#define NETRC_SUCCESS 0 +enum found_state { + NONE, + LOGIN, + PASSWORD +}; + +#define FOUND_LOGIN 1 +#define FOUND_PASSWORD 2 + +#define MAX_NETRC_LINE 16384 +#define MAX_NETRC_FILE (128*1024) +#define MAX_NETRC_TOKEN 4096 + +/* convert a dynbuf call CURLcode error to a NETRCcode error */ +#define curl2netrc(result) \ + (((result) == CURLE_OUT_OF_MEMORY) ? \ + NETRC_OUT_OF_MEMORY : NETRC_SYNTAX_ERROR) + +static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf) +{ + NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */ + FILE *file = fopen(filename, FOPEN_READTEXT); + struct dynbuf linebuf; + curlx_dyn_init(&linebuf, MAX_NETRC_LINE); + + if(file) { + ret = NETRC_OK; + while(Curl_get_line(&linebuf, file)) { + CURLcode result; + const char *line = curlx_dyn_ptr(&linebuf); + /* skip comments on load */ + curlx_str_passblanks(&line); + if(*line == '#') + continue; + result = curlx_dyn_add(filebuf, line); + if(result) { + ret = curl2netrc(result); + goto done; + } + } + } +done: + curlx_dyn_free(&linebuf); + if(file) + fclose(file); + return ret; +} /* * Returns zero on success. */ -static int parsenetrc(const char *host, - char **loginp, - char **passwordp, - char *netrcfile) +static NETRCcode parsenetrc(struct store_netrc *store, + const char *host, + char **loginp, /* might point to a username */ + char **passwordp, + const char *netrcfile) { - FILE *file; - int retcode = NETRC_FILE_MISSING; + NETRCcode retcode = NETRC_NO_MATCH; char *login = *loginp; - char *password = *passwordp; - bool specific_login = (login && *login != 0); - bool login_alloc = FALSE; - bool password_alloc = FALSE; + char *password = NULL; + bool specific_login = !!login; /* points to something */ enum host_lookup_state state = NOTHING; + enum found_state keyword = NONE; + unsigned char found = 0; /* login + password found bits, as they can come in + any order */ + bool our_login = FALSE; /* found our login name */ + bool done = FALSE; + char *netrcbuffer; + struct dynbuf token; + struct dynbuf *filebuf = &store->filebuf; + DEBUGASSERT(!*passwordp); + curlx_dyn_init(&token, MAX_NETRC_TOKEN); - char state_login = 0; /* Found a login keyword */ - char state_password = 0; /* Found a password keyword */ - int state_our_login = TRUE; /* With specific_login, found *our* login - name (or login-less line) */ - - DEBUGASSERT(netrcfile); + if(!store->loaded) { + NETRCcode ret = file2memory(netrcfile, filebuf); + if(ret) + return ret; + store->loaded = TRUE; + } - file = fopen(netrcfile, FOPEN_READTEXT); - if(file) { - bool done = FALSE; - char netrcbuffer[4096]; - int netrcbuffsize = (int)sizeof(netrcbuffer); + netrcbuffer = curlx_dyn_ptr(filebuf); - while(!done && Curl_get_line(netrcbuffer, netrcbuffsize, file)) { - char *tok; - char *tok_end; + while(!done) { + const char *tok = netrcbuffer; + while(tok && !done) { + const char *tok_end; bool quoted; + curlx_dyn_reset(&token); + curlx_str_passblanks(&tok); + /* tok is first non-space letter */ if(state == MACDEF) { - if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r')) - state = NOTHING; - else - continue; + if((*tok == '\n') || (*tok == '\r')) + state = NOTHING; /* end of macro definition */ } - tok = netrcbuffer; - while(tok) { - while(ISBLANK(*tok)) - tok++; - /* tok is first non-space letter */ - if(!*tok || (*tok == '#')) - /* end of line or the rest is a comment */ - break; - /* leading double-quote means quoted string */ - quoted = (*tok == '\"'); + if(!*tok || (*tok == '\n')) + /* end of line */ + break; - tok_end = tok; - if(!quoted) { - while(!ISSPACE(*tok_end)) - tok_end++; - *tok_end = 0; + /* leading double-quote means quoted string */ + quoted = (*tok == '\"'); + + tok_end = tok; + if(!quoted) { + size_t len = 0; + CURLcode result; + while(*tok_end > ' ') { + tok_end++; + len++; } - else { - bool escape = FALSE; - bool endquote = FALSE; - char *store = tok; - tok_end++; /* pass the leading quote */ - while(*tok_end) { - char s = *tok_end; - if(escape) { - escape = FALSE; - switch(s) { - case 'n': - s = '\n'; - break; - case 'r': - s = '\r'; - break; - case 't': - s = '\t'; - break; - } - } - else if(s == '\\') { - escape = TRUE; - tok_end++; - continue; - } - else if(s == '\"') { - tok_end++; /* pass the ending quote */ - endquote = TRUE; + if(!len) { + retcode = NETRC_SYNTAX_ERROR; + goto out; + } + result = curlx_dyn_addn(&token, tok, len); + if(result) { + retcode = curl2netrc(result); + goto out; + } + } + else { + bool escape = FALSE; + bool endquote = FALSE; + tok_end++; /* pass the leading quote */ + while(*tok_end) { + CURLcode result; + char s = *tok_end; + if(escape) { + escape = FALSE; + switch(s) { + case 'n': + s = '\n'; + break; + case 'r': + s = '\r'; + break; + case 't': + s = '\t'; break; } - *store++ = s; + } + else if(s == '\\') { + escape = TRUE; tok_end++; + continue; + } + else if(s == '\"') { + tok_end++; /* pass the ending quote */ + endquote = TRUE; + break; } - *store = 0; - if(escape || !endquote) { - /* bad syntax, get out */ - retcode = NETRC_FAILED; + result = curlx_dyn_addn(&token, &s, 1); + if(result) { + retcode = curl2netrc(result); goto out; } + tok_end++; } - - if((login && *login) && (password && *password)) { - done = TRUE; - break; + if(escape || !endquote) { + /* bad syntax, get out */ + retcode = NETRC_SYNTAX_ERROR; + goto out; } + } - switch(state) { - case NOTHING: - if(strcasecompare("macdef", tok)) { - /* Define a macro. A macro is defined with the specified name; its - contents begin with the next .netrc line and continue until a - null line (consecutive new-line characters) is encountered. */ - state = MACDEF; - } - else if(strcasecompare("machine", tok)) { - /* the next tok is the machine name, this is in itself the - delimiter that starts the stuff entered for this machine, - after this we need to search for 'login' and - 'password'. */ - state = HOSTFOUND; - } - else if(strcasecompare("default", tok)) { - state = HOSTVALID; - retcode = NETRC_SUCCESS; /* we did find our host */ - } - break; - case MACDEF: - if(!strlen(tok)) { - state = NOTHING; - } - break; - case HOSTFOUND: - if(strcasecompare(host, tok)) { - /* and yes, this is our host! */ - state = HOSTVALID; - retcode = NETRC_SUCCESS; /* we did find our host */ - } - else - /* not our host */ - state = NOTHING; - break; - case HOSTVALID: - /* we are now parsing sub-keywords concerning "our" host */ - if(state_login) { - if(specific_login) { - state_our_login = !Curl_timestrcmp(login, tok); - } - else if(!login || Curl_timestrcmp(login, tok)) { - if(login_alloc) { - free(login); - login_alloc = FALSE; - } - login = strdup(tok); - if(!login) { - retcode = NETRC_FAILED; /* allocation failed */ - goto out; - } - login_alloc = TRUE; + if(curlx_dyn_len(&token)) + tok = curlx_dyn_ptr(&token); + else + /* since tok might actually be NULL for no content, set it to blank + to avoid having to deal with it being NULL */ + tok = ""; + + switch(state) { + case NOTHING: + if(strcasecompare("macdef", tok)) + /* Define a macro. A macro is defined with the specified name; its + contents begin with the next .netrc line and continue until a + null line (consecutive new-line characters) is encountered. */ + state = MACDEF; + else if(strcasecompare("machine", tok)) { + /* the next tok is the machine name, this is in itself the delimiter + that starts the stuff entered for this machine, after this we + need to search for 'login' and 'password'. */ + state = HOSTFOUND; + keyword = NONE; + found = 0; + our_login = FALSE; + Curl_safefree(password); + if(!specific_login) + Curl_safefree(login); + } + else if(strcasecompare("default", tok)) { + state = HOSTVALID; + retcode = NETRC_OK; /* we did find our host */ + } + break; + case MACDEF: + if(!*tok) + state = NOTHING; + break; + case HOSTFOUND: + if(strcasecompare(host, tok)) { + /* and yes, this is our host! */ + state = HOSTVALID; + retcode = NETRC_OK; /* we did find our host */ + } + else + /* not our host */ + state = NOTHING; + break; + case HOSTVALID: + /* we are now parsing sub-keywords concerning "our" host */ + if(keyword == LOGIN) { + if(specific_login) + our_login = !Curl_timestrcmp(login, tok); + else { + our_login = TRUE; + free(login); + login = strdup(tok); + if(!login) { + retcode = NETRC_OUT_OF_MEMORY; /* allocation failed */ + goto out; } - state_login = 0; } - else if(state_password) { - if((state_our_login || !specific_login) - && (!password || Curl_timestrcmp(password, tok))) { - if(password_alloc) { - free(password); - password_alloc = FALSE; - } - password = strdup(tok); - if(!password) { - retcode = NETRC_FAILED; /* allocation failed */ - goto out; - } - password_alloc = TRUE; - } - state_password = 0; + found |= FOUND_LOGIN; + keyword = NONE; + } + else if(keyword == PASSWORD) { + free(password); + password = strdup(tok); + if(!password) { + retcode = NETRC_OUT_OF_MEMORY; /* allocation failed */ + goto out; } - else if(strcasecompare("login", tok)) - state_login = 1; - else if(strcasecompare("password", tok)) - state_password = 1; - else if(strcasecompare("machine", tok)) { - /* ok, there's machine here go => */ - state = HOSTFOUND; - state_our_login = FALSE; + if(!specific_login || our_login) + found |= FOUND_PASSWORD; + keyword = NONE; + } + else if(strcasecompare("login", tok)) + keyword = LOGIN; + else if(strcasecompare("password", tok)) + keyword = PASSWORD; + else if(strcasecompare("machine", tok)) { + /* a new machine here */ + if(found & FOUND_PASSWORD) { + done = TRUE; + break; } + state = HOSTFOUND; + keyword = NONE; + found = 0; + Curl_safefree(password); + if(!specific_login) + Curl_safefree(login); + } + else if(strcasecompare("default", tok)) { + state = HOSTVALID; + retcode = NETRC_OK; /* we did find our host */ + Curl_safefree(password); + if(!specific_login) + Curl_safefree(login); + } + if((found == (FOUND_PASSWORD|FOUND_LOGIN)) && our_login) { + done = TRUE; break; - } /* switch (state) */ - tok = ++tok_end; - } - } /* while Curl_get_line() */ + } + break; + } /* switch (state) */ + tok = ++tok_end; + } + if(!done) { + char *nl = NULL; + if(tok) + nl = strchr(tok, '\n'); + if(!nl) + break; + /* point to next line */ + netrcbuffer = &nl[1]; + } + } /* while !done */ out: - if(!retcode) { - /* success */ - if(login_alloc) { - if(*loginp) - free(*loginp); - *loginp = login; - } - if(password_alloc) { - if(*passwordp) - free(*passwordp); - *passwordp = password; - } - } - else { - if(login_alloc) - free(login); - if(password_alloc) - free(password); + curlx_dyn_free(&token); + if(!retcode) { + if(!password && our_login) { + /* success without a password, set a blank one */ + password = strdup(""); + if(!password) + retcode = NETRC_OUT_OF_MEMORY; /* out of memory */ } - fclose(file); + else if(!login && !password) + /* a default with no credentials */ + retcode = NETRC_NO_MATCH; + } + if(!retcode) { + /* success */ + if(!specific_login) + *loginp = login; + *passwordp = password; + } + else { + curlx_dyn_free(filebuf); + if(!specific_login) + free(login); + free(password); } return retcode; } +const char *Curl_netrc_strerror(NETRCcode ret) +{ + switch(ret) { + default: + return ""; /* not a legit error */ + case NETRC_FILE_MISSING: + return "no such file"; + case NETRC_NO_MATCH: + return "no matching entry"; + case NETRC_OUT_OF_MEMORY: + return "out of memory"; + case NETRC_SYNTAX_ERROR: + return "syntax error"; + } + /* never reached */ +} + /* * @unittest: 1304 * - * *loginp and *passwordp MUST be allocated if they aren't NULL when passed + * *loginp and *passwordp MUST be allocated if they are not NULL when passed * in. */ -int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, - char *netrcfile) +NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, + char **loginp, char **passwordp, + char *netrcfile) { - int retcode = 1; + NETRCcode retcode = NETRC_OK; char *filealloc = NULL; if(!netrcfile) { @@ -317,33 +430,43 @@ int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, } if(!home) - return retcode; /* no home directory found (or possibly out of - memory) */ + return NETRC_FILE_MISSING; /* no home directory found (or possibly out + of memory) */ - filealloc = curl_maprintf("%s%s.netrc", home, DIR_CHAR); + filealloc = aprintf("%s%s.netrc", home, DIR_CHAR); if(!filealloc) { free(homea); - return -1; + return NETRC_OUT_OF_MEMORY; } - retcode = parsenetrc(host, loginp, passwordp, filealloc); + retcode = parsenetrc(store, host, loginp, passwordp, filealloc); free(filealloc); -#ifdef WIN32 +#ifdef _WIN32 if(retcode == NETRC_FILE_MISSING) { /* fallback to the old-style "_netrc" file */ - filealloc = curl_maprintf("%s%s_netrc", home, DIR_CHAR); + filealloc = aprintf("%s%s_netrc", home, DIR_CHAR); if(!filealloc) { free(homea); - return -1; + return NETRC_OUT_OF_MEMORY; } - retcode = parsenetrc(host, loginp, passwordp, filealloc); + retcode = parsenetrc(store, host, loginp, passwordp, filealloc); free(filealloc); } #endif free(homea); } else - retcode = parsenetrc(host, loginp, passwordp, netrcfile); + retcode = parsenetrc(store, host, loginp, passwordp, netrcfile); return retcode; } +void Curl_netrc_init(struct store_netrc *s) +{ + curlx_dyn_init(&s->filebuf, MAX_NETRC_FILE); + s->loaded = FALSE; +} +void Curl_netrc_cleanup(struct store_netrc *s) +{ + curlx_dyn_free(&s->filebuf); + s->loaded = FALSE; +} #endif diff --git a/Utilities/cmcurl/lib/netrc.h b/Utilities/cmcurl/lib/netrc.h index 9f2815f3bb9..ef3bde5212a 100644 --- a/Utilities/cmcurl/lib/netrc.h +++ b/Utilities/cmcurl/lib/netrc.h @@ -26,10 +26,29 @@ #include "curl_setup.h" #ifndef CURL_DISABLE_NETRC +#include "curlx/dynbuf.h" -/* returns -1 on failure, 0 if the host is found, 1 is the host isn't found */ -int Curl_parsenetrc(const char *host, char **loginp, - char **passwordp, char *filename); +struct store_netrc { + struct dynbuf filebuf; + char *filename; + BIT(loaded); +}; + +typedef enum { + NETRC_OK, + NETRC_NO_MATCH, /* no matching entry in the file */ + NETRC_SYNTAX_ERROR, /* in the netrc file */ + NETRC_FILE_MISSING, /* the netrc file does not exist */ + NETRC_OUT_OF_MEMORY, /* while parsing netrc */ + NETRC_LAST /* never used */ +} NETRCcode; + +const char *Curl_netrc_strerror(NETRCcode ret); +void Curl_netrc_init(struct store_netrc *s); +void Curl_netrc_cleanup(struct store_netrc *s); + +NETRCcode Curl_parsenetrc(struct store_netrc *s, const char *host, + char **loginp, char **passwordp, char *filename); /* Assume: (*passwordp)[0]=0, host[0] != 0. * If (*loginp)[0] = 0, search for login and password within a machine * section in the netrc. @@ -38,6 +57,8 @@ int Curl_parsenetrc(const char *host, char **loginp, #else /* disabled */ #define Curl_parsenetrc(a,b,c,d,e,f) 1 +#define Curl_netrc_init(x) +#define Curl_netrc_cleanup(x) #endif #endif /* HEADER_CURL_NETRC_H */ diff --git a/Utilities/cmcurl/lib/noproxy.c b/Utilities/cmcurl/lib/noproxy.c index 2b9908d8941..f70f57eb5af 100644 --- a/Utilities/cmcurl/lib/noproxy.c +++ b/Utilities/cmcurl/lib/noproxy.c @@ -26,9 +26,10 @@ #ifndef CURL_DISABLE_PROXY -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "strcase.h" #include "noproxy.h" +#include "curlx/strparse.h" #ifdef HAVE_NETINET_IN_H #include @@ -53,9 +54,9 @@ UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ /* strange input */ return FALSE; - if(1 != Curl_inet_pton(AF_INET, ipv4, &address)) + if(1 != curlx_inet_pton(AF_INET, ipv4, &address)) return FALSE; - if(1 != Curl_inet_pton(AF_INET, network, &check)) + if(1 != curlx_inet_pton(AF_INET, network, &check)) return FALSE; if(bits && (bits != 32)) { @@ -71,29 +72,29 @@ UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ return FALSE; return TRUE; } - return (address == check); + return address == check; } UNITTEST bool Curl_cidr6_match(const char *ipv6, const char *network, unsigned int bits) { -#ifdef ENABLE_IPV6 - int bytes; - int rest; +#ifdef USE_IPV6 + unsigned int bytes; + unsigned int rest; unsigned char address[16]; unsigned char check[16]; if(!bits) bits = 128; - bytes = bits/8; + bytes = bits / 8; rest = bits & 0x07; - if(1 != Curl_inet_pton(AF_INET6, ipv6, address)) + if((bytes > 16) || ((bytes == 16) && rest)) return FALSE; - if(1 != Curl_inet_pton(AF_INET6, network, check)) + if(1 != curlx_inet_pton(AF_INET6, ipv6, address)) return FALSE; - if((bytes > 16) || ((bytes == 16) && rest)) + if(1 != curlx_inet_pton(AF_INET6, network, check)) return FALSE; if(bytes && memcmp(address, check, bytes)) return FALSE; @@ -119,13 +120,12 @@ enum nametype { * Checks if the host is in the noproxy list. returns TRUE if it matches and * therefore the proxy should NOT be used. ****************************************************************/ -bool Curl_check_noproxy(const char *name, const char *no_proxy, - bool *spacesep) +bool Curl_check_noproxy(const char *name, const char *no_proxy) { char hostip[128]; - *spacesep = FALSE; + /* - * If we don't have a hostname at all, like for example with a FILE + * If we do not have a hostname at all, like for example with a FILE * transfer, we have nothing to interrogate the noproxy list with. */ if(!name || name[0] == '\0') @@ -143,7 +143,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, if(!strcmp("*", no_proxy)) return TRUE; - /* NO_PROXY was specified and it wasn't just an asterisk */ + /* NO_PROXY was specified and it was not just an asterisk */ if(name[0] == '[') { char *endptr; @@ -163,10 +163,10 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, else { unsigned int address; namelen = strlen(name); - if(1 == Curl_inet_pton(AF_INET, name, &address)) + if(1 == curlx_inet_pton(AF_INET, name, &address)) type = TYPE_IPV4; else { - /* ignore trailing dots in the host name */ + /* ignore trailing dots in the hostname */ if(name[namelen - 1] == '.') namelen--; } @@ -178,8 +178,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, bool match = FALSE; /* pass blanks */ - while(*p && ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); token = p; /* pass over the pattern */ @@ -216,7 +215,6 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, /* case C passes through, not a match */ break; case TYPE_IPV4: - /* FALLTHROUGH */ case TYPE_IPV6: { const char *check = token; char *slash; @@ -233,8 +231,10 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, slash = strchr(check, '/'); /* if the slash is part of this token, use it */ if(slash) { - bits = atoi(slash + 1); - *slash = 0; /* null terminate there */ + /* if the bits variable gets a crazy value here, that is fine as + the value will then be rejected in the cidr function */ + bits = (unsigned int)atoi(slash + 1); + *slash = 0; /* null-terminate there */ } if(type == TYPE_IPV6) match = Curl_cidr6_match(name, check, bits); @@ -247,18 +247,15 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, return TRUE; } /* if(tokenlen) */ /* pass blanks after pattern */ - while(ISBLANK(*p)) - p++; - /* if not a comma! */ - if(*p && (*p != ',')) { - *spacesep = TRUE; - continue; - } + curlx_str_passblanks(&p); + /* if not a comma, this ends the loop */ + if(*p != ',') + break; /* pass any number of commas */ while(*p == ',') p++; } /* while(*p) */ - } /* NO_PROXY was specified and it wasn't just an asterisk */ + } /* NO_PROXY was specified and it was not just an asterisk */ return FALSE; } diff --git a/Utilities/cmcurl/lib/noproxy.h b/Utilities/cmcurl/lib/noproxy.h index a3a6807722d..71ae7eaafa0 100644 --- a/Utilities/cmcurl/lib/noproxy.h +++ b/Utilities/cmcurl/lib/noproxy.h @@ -27,7 +27,7 @@ #ifndef CURL_DISABLE_PROXY -#ifdef DEBUGBUILD +#ifdef UNITTESTS UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ const char *network, /* 1.2.3.4 address */ @@ -37,9 +37,7 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, unsigned int bits); #endif -bool Curl_check_noproxy(const char *name, const char *no_proxy, - bool *spacesep); - +bool Curl_check_noproxy(const char *name, const char *no_proxy); #endif #endif /* HEADER_CURL_NOPROXY_H */ diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c index 41fecf9143c..6343c40c094 100644 --- a/Utilities/cmcurl/lib/openldap.c +++ b/Utilities/cmcurl/lib/openldap.c @@ -41,12 +41,13 @@ #include #include "urldata.h" +#include "url.h" #include #include "sendf.h" #include "vtls/vtls.h" #include "transfer.h" #include "curl_ldap.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "cfilters.h" #include "connect.h" #include "curl_sasl.h" @@ -117,7 +118,7 @@ static Curl_recv oldap_recv; */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ + "ldap", /* scheme */ oldap_setup_connection, /* setup_connection */ oldap_do, /* do_it */ oldap_done, /* done */ @@ -130,9 +131,11 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ CURLPROTO_LDAP, /* family */ @@ -145,7 +148,7 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ + "ldaps", /* scheme */ oldap_setup_connection, /* setup_connection */ oldap_do, /* do_it */ oldap_done, /* done */ @@ -158,9 +161,11 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAPS, /* defport */ CURLPROTO_LDAPS, /* protocol */ CURLPROTO_LDAP, /* family */ @@ -198,15 +203,20 @@ struct ldapreqinfo { int nument; }; +/* meta key for storing ldapconninfo at easy handle */ +#define CURL_META_LDAP_EASY "meta:proto:ldap:easy" +/* meta key for storing ldapconninfo at connection */ +#define CURL_META_LDAP_CONN "meta:proto:ldap:conn" + + /* - * state() + * oldap_state() * * This is the ONLY way to change LDAP state! */ -static void state(struct Curl_easy *data, ldapstate newstate) +static void oldap_state(struct Curl_easy *data, struct ldapconninfo *li, + ldapstate newstate) { - struct ldapconninfo *ldapc = data->conn->proto.ldapc; - #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -221,12 +231,12 @@ static void state(struct Curl_easy *data, ldapstate newstate) /* LAST */ }; - if(ldapc->state != newstate) + if(li->state != newstate) infof(data, "LDAP %p state change from %s to %s", - (void *)ldapc, names[ldapc->state], names[newstate]); + (void *)li, names[li->state], names[newstate]); #endif - - ldapc->state = newstate; + (void)data; + li->state = newstate; } /* Map some particular LDAP error codes to CURLcode values. */ @@ -234,17 +244,13 @@ static CURLcode oldap_map_error(int rc, CURLcode result) { switch(rc) { case LDAP_NO_MEMORY: - result = CURLE_OUT_OF_MEMORY; - break; + return CURLE_OUT_OF_MEMORY; case LDAP_INVALID_CREDENTIALS: - result = CURLE_LOGIN_DENIED; - break; + return CURLE_LOGIN_DENIED; case LDAP_PROTOCOL_ERROR: - result = CURLE_UNSUPPORTED_PROTOCOL; - break; + return CURLE_UNSUPPORTED_PROTOCOL; case LDAP_INSUFFICIENT_ACCESS: - result = CURLE_REMOTE_ACCESS_DENIED; - break; + return CURLE_REMOTE_ACCESS_DENIED; } return result; } @@ -274,9 +280,10 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) if(rc != LDAP_URL_SUCCESS) { const char *msg = "url parsing problem"; - result = rc == LDAP_URL_ERR_MEM? CURLE_OUT_OF_MEMORY: CURLE_URL_MALFORMAT; + result = rc == LDAP_URL_ERR_MEM ? CURLE_OUT_OF_MEMORY : + CURLE_URL_MALFORMAT; rc -= LDAP_URL_SUCCESS; - if((size_t) rc < sizeof(url_errs) / sizeof(url_errs[0])) + if((size_t) rc < CURL_ARRAYSIZE(url_errs)) msg = url_errs[rc]; failf(data, "LDAP local: %s", msg); } @@ -287,9 +294,13 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) static CURLcode oldap_parse_login_options(struct connectdata *conn) { CURLcode result = CURLE_OK; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); const char *ptr = conn->options; + DEBUGASSERT(li); + if(!li) + return CURLE_FAILED_INIT; + while(!result && ptr && *ptr) { const char *key = ptr; const char *value; @@ -311,7 +322,7 @@ static CURLcode oldap_parse_login_options(struct connectdata *conn) ptr++; } - return result == CURLE_URL_MALFORMAT? CURLE_SETOPT_OPTION_SYNTAX: result; + return result == CURLE_URL_MALFORMAT ? CURLE_SETOPT_OPTION_SYNTAX : result; } static CURLcode oldap_setup_connection(struct Curl_easy *data, @@ -319,31 +330,12 @@ static CURLcode oldap_setup_connection(struct Curl_easy *data, { CURLcode result; LDAPURLDesc *lud; - struct ldapconninfo *li; + (void)conn; /* Early URL syntax check. */ result = oldap_url_parse(data, &lud); ldap_free_urldesc(lud); - if(!result) { - li = calloc(1, sizeof(struct ldapconninfo)); - if(!li) - result = CURLE_OUT_OF_MEMORY; - else { - li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); - conn->proto.ldapc = li; - connkeep(conn, "OpenLDAP default"); - - /* Initialize the SASL storage */ - Curl_sasl_init(&li->sasl, data, &saslldap); - - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; - - result = oldap_parse_login_options(conn); - } - } - return result; } @@ -352,7 +344,12 @@ static CURLcode oldap_setup_connection(struct Curl_easy *data, */ static CURLcode oldap_get_message(struct Curl_easy *data, struct bufref *out) { - struct berval *servercred = data->conn->proto.ldapc->servercred; + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); + struct berval *servercred = li ? li->servercred : NULL; + DEBUGASSERT(li); + if(!li) + return CURLE_FAILED_INIT; if(!servercred || !servercred->bv_val) return CURLE_WEIRD_SERVER_REPLY; @@ -367,20 +364,22 @@ static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech, const struct bufref *initresp) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; - CURLcode result = CURLE_OK; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); struct berval cred; struct berval *pcred = &cred; int rc; - cred.bv_val = (char *) Curl_bufref_ptr(initresp); + DEBUGASSERT(li); + if(!li) + return CURLE_FAILED_INIT; + cred.bv_val = (char *)CURL_UNCONST(Curl_bufref_ptr(initresp)); cred.bv_len = Curl_bufref_len(initresp); if(!cred.bv_val) pcred = NULL; rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid); if(rc != LDAP_SUCCESS) - result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); - return result; + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; } /* @@ -390,20 +389,21 @@ static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; - CURLcode result = CURLE_OK; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); struct berval cred; struct berval *pcred = &cred; int rc; - cred.bv_val = (char *) Curl_bufref_ptr(resp); + if(!li) + return CURLE_FAILED_INIT; + cred.bv_val = (char *)CURL_UNCONST(Curl_bufref_ptr(resp)); cred.bv_len = Curl_bufref_len(resp); if(!cred.bv_val) pcred = NULL; rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid); if(rc != LDAP_SUCCESS) - result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); - return result; + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; } /* @@ -411,27 +411,31 @@ static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, */ static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech) { - struct ldapconninfo *li = data->conn->proto.ldapc; - CURLcode result = CURLE_OK; - int rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL, - &li->msgid); + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); + int rc; (void)mech; + if(!li) + return CURLE_FAILED_INIT; + rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL, + &li->msgid); if(rc != LDAP_SUCCESS) - result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); - return result; + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; } /* Starts LDAP simple bind. */ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) { - CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); char *binddn = NULL; struct berval passwd; int rc; + if(!li) + return CURLE_FAILED_INIT; passwd.bv_val = NULL; passwd.bv_len = 0; @@ -443,46 +447,51 @@ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &li->msgid); - if(rc == LDAP_SUCCESS) - state(data, newstate); - else - result = oldap_map_error(rc, - data->state.aptr.user? - CURLE_LOGIN_DENIED: CURLE_LDAP_CANNOT_BIND); - return result; + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, + data->state.aptr.user ? + CURLE_LOGIN_DENIED : CURLE_LDAP_CANNOT_BIND); + oldap_state(data, li, newstate); + return CURLE_OK; } /* Query the supported SASL authentication mechanisms. */ static CURLcode oldap_perform_mechs(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - struct ldapconninfo *li = data->conn->proto.ldapc; + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); int rc; static const char * const supportedSASLMechanisms[] = { "supportedSASLMechanisms", NULL }; + if(!li) + return CURLE_FAILED_INIT; rc = ldap_search_ext(li->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)", - (char **) supportedSASLMechanisms, 0, + (char **)CURL_UNCONST(supportedSASLMechanisms), 0, NULL, NULL, NULL, 0, &li->msgid); - if(rc == LDAP_SUCCESS) - state(data, OLDAP_MECHS); - else - result = oldap_map_error(rc, CURLE_LOGIN_DENIED); - return result; + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, CURLE_LOGIN_DENIED); + oldap_state(data, li, OLDAP_MECHS); + return CURLE_OK; } /* Starts SASL bind. */ static CURLcode oldap_perform_sasl(struct Curl_easy *data) { + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); saslprogress progress = SASL_IDLE; - struct ldapconninfo *li = data->conn->proto.ldapc; - CURLcode result = Curl_sasl_start(&li->sasl, data, TRUE, &progress); + CURLcode result; + + if(!li) + return CURLE_FAILED_INIT; + result = Curl_sasl_start(&li->sasl, data, TRUE, &progress); - state(data, OLDAP_SASL); + oldap_state(data, li, OLDAP_SASL); if(!result && progress != SASL_INPROGRESS) - result = CURLE_LOGIN_DENIED; + result = Curl_sasl_is_blocked(&li->sasl, data); return result; } @@ -491,19 +500,22 @@ static Sockbuf_IO ldapsb_tls; static bool ssl_installed(struct connectdata *conn) { - return conn->proto.ldapc->recv != NULL; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + return li && li->recv != NULL; } static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) { - CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; - bool ssldone = 0; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + bool ssldone = FALSE; + CURLcode result; + if(!li) + return CURLE_FAILED_INIT; result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); if(!result) { - state(data, newstate); + oldap_state(data, li, newstate); if(ssldone) { Sockbuf *sb; @@ -522,51 +534,98 @@ static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) /* Send the STARTTLS request */ static CURLcode oldap_perform_starttls(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - struct ldapconninfo *li = data->conn->proto.ldapc; - int rc = ldap_start_tls(li->ld, NULL, NULL, &li->msgid); + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); + int rc; - if(rc == LDAP_SUCCESS) - state(data, OLDAP_STARTTLS); - else - result = oldap_map_error(rc, CURLE_USE_SSL_FAILED); - return result; + if(!li) + return CURLE_FAILED_INIT; + rc = ldap_start_tls(li->ld, NULL, NULL, &li->msgid); + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, CURLE_USE_SSL_FAILED); + oldap_state(data, li, OLDAP_STARTTLS); + return CURLE_OK; } #endif +static void oldap_easy_dtor(void *key, size_t klen, void *entry) +{ + struct ldapreqinfo *lr = entry; + (void)key; + (void)klen; + free(lr); +} + +static void oldap_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ldapconninfo *li = entry; + (void)key; + (void)klen; + if(li->ld) { + ldap_unbind_ext(li->ld, NULL, NULL); + li->ld = NULL; + } + free(li); +} + static CURLcode oldap_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li; static const int version = LDAP_VERSION3; + char *hosturl = NULL; + CURLcode result; int rc; - char *hosturl; #ifdef CURL_OPENLDAP_DEBUG static int do_trace = -1; #endif (void)done; - hosturl = aprintf("ldap%s://%s:%d", - conn->handler->flags & PROTOPT_SSL? "s": "", - conn->host.name, conn->remote_port); - if(!hosturl) - return CURLE_OUT_OF_MEMORY; + li = calloc(1, sizeof(struct ldapconninfo)); + if(!li) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_conn_meta_set(conn, CURL_META_LDAP_CONN, li, oldap_conn_dtor); + if(result) + goto out; + + li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); + + /* Initialize the SASL storage */ + Curl_sasl_init(&li->sasl, data, &saslldap); + + result = oldap_parse_login_options(conn); + if(result) + goto out; + + hosturl = aprintf("%s://%s%s%s:%d", + conn->handler->scheme, + conn->bits.ipv6_ip ? "[" : "", + conn->host.name, + conn->bits.ipv6_ip ? "]" : "", + conn->remote_port); + if(!hosturl) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); if(rc) { failf(data, "LDAP local: Cannot connect to %s, %s", hosturl, ldap_err2string(rc)); - free(hosturl); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto out; } - free(hosturl); - #ifdef CURL_OPENLDAP_DEBUG if(do_trace < 0) { const char *env = getenv("CURL_OPENLDAP_TRACE"); - do_trace = (env && strtol(env, NULL, 10) > 0); + curl_off_t e = 0; + if(!curlx_str_number(&env, &e, INT_MAX)) + do_trace = e > 0; } if(do_trace) ldap_set_option(li->ld, LDAP_OPT_DEBUG_LEVEL, &do_trace); @@ -579,23 +638,30 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) ldap_set_option(li->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); #ifdef USE_SSL - if(conn->handler->flags & PROTOPT_SSL) - return oldap_ssl_connect(data, OLDAP_SSL); + if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = oldap_ssl_connect(data, OLDAP_SSL); + goto out; + } if(data->set.use_ssl) { - CURLcode result = oldap_perform_starttls(data); - + result = oldap_perform_starttls(data); if(!result || data->set.use_ssl != CURLUSESSL_TRY) - return result; + goto out; } #endif - if(li->sasl.prefmech != SASL_AUTH_NONE) - return oldap_perform_mechs(data); + if(li->sasl.prefmech != SASL_AUTH_NONE) { + result = oldap_perform_mechs(data); + goto out; + } /* Force bind even if anonymous bind is not needed in protocol version 3 to detect missing version 3 support. */ - return oldap_perform_bind(data, OLDAP_BIND); + result = oldap_perform_bind(data, OLDAP_BIND); + +out: + free(hosturl); + return result; } /* Handle the supported SASL mechanisms query response */ @@ -603,12 +669,14 @@ static CURLcode oldap_state_mechs_resp(struct Curl_easy *data, LDAPMessage *msg, int code) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); int rc; BerElement *ber = NULL; CURLcode result = CURLE_OK; struct berval bv, *bvals; + if(!li) + return CURLE_FAILED_INIT; switch(ldap_msgtype(msg)) { case LDAP_RES_SEARCH_ENTRY: /* Got a list of supported SASL mechanisms. */ @@ -644,7 +712,7 @@ static CURLcode oldap_state_mechs_resp(struct Curl_easy *data, switch(code) { case LDAP_SIZELIMIT_EXCEEDED: infof(data, "Too many authentication mechanisms\n"); - /* FALLTHROUGH */ + FALLTHROUGH(); case LDAP_SUCCESS: case LDAP_NO_RESULTS_RETURNED: if(Curl_sasl_can_authenticate(&li->sasl, data)) @@ -668,11 +736,13 @@ static CURLcode oldap_state_sasl_resp(struct Curl_easy *data, LDAPMessage *msg, int code) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode result = CURLE_OK; saslprogress progress; int rc; + if(!li) + return CURLE_FAILED_INIT; li->servercred = NULL; rc = ldap_parse_sasl_bind_result(li->ld, msg, &li->servercred, 0); if(rc != LDAP_SUCCESS) { @@ -682,7 +752,7 @@ static CURLcode oldap_state_sasl_resp(struct Curl_easy *data, else { result = Curl_sasl_continue(&li->sasl, data, code, &progress); if(!result && progress != SASL_INPROGRESS) - state(data, OLDAP_STOP); + oldap_state(data, li, OLDAP_STOP); } if(li->servercred) @@ -695,11 +765,14 @@ static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg, int code) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode result = CURLE_OK; struct berval *bv = NULL; int rc; + if(!li) + return CURLE_FAILED_INIT; + if(code != LDAP_SUCCESS) return oldap_map_error(code, CURLE_LDAP_CANNOT_BIND); @@ -710,7 +783,7 @@ static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg, result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); } else - state(data, OLDAP_STOP); + oldap_state(data, li, OLDAP_STOP); if(bv) ber_bvfree(bv); @@ -721,12 +794,15 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); LDAPMessage *msg = NULL; struct timeval tv = {0, 0}; int code = LDAP_SUCCESS; int rc; + if(!li) + return CURLE_FAILED_INIT; + if(li->state != OLDAP_SSL && li->state != OLDAP_TLS) { /* Get response to last command. */ rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, &tv, &msg); @@ -792,19 +868,22 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) result = oldap_perform_bind(data, OLDAP_BIND); break; } - /* FALLTHROUGH */ + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + break; + FALLTHROUGH(); case OLDAP_TLS: result = oldap_ssl_connect(data, OLDAP_TLS); - if(result && data->set.use_ssl != CURLUSESSL_TRY) + if(result) result = oldap_map_error(code, CURLE_USE_SSL_FAILED); else if(ssl_installed(conn)) { - conn->bits.tls_upgraded = TRUE; if(li->sasl.prefmech != SASL_AUTH_NONE) result = oldap_perform_mechs(data); else if(data->state.aptr.user) result = oldap_perform_bind(data, OLDAP_BIND); else { - state(data, OLDAP_STOP); /* Version 3 supported: no bind required */ + /* Version 3 supported: no bind required */ + oldap_state(data, li, OLDAP_STOP); result = CURLE_OK; } } @@ -844,7 +923,7 @@ static CURLcode oldap_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); (void) dead_connection; #ifndef USE_SSL (void)data; @@ -863,8 +942,6 @@ static CURLcode oldap_disconnect(struct Curl_easy *data, li->ld = NULL; } Curl_sasl_cleanup(conn, li->sasl.authused); - conn->proto.ldapc = NULL; - free(li); } return CURLE_OK; } @@ -872,41 +949,55 @@ static CURLcode oldap_disconnect(struct Curl_easy *data, static CURLcode oldap_do(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); struct ldapreqinfo *lr; CURLcode result; int rc; LDAPURLDesc *lud; int msgid; + if(!li) + return CURLE_FAILED_INIT; connkeep(conn, "OpenLDAP do"); infof(data, "LDAP local: %s", data->state.url); result = oldap_url_parse(data, &lud); - if(!result) { - rc = ldap_search_ext(li->ld, lud->lud_dn, lud->lud_scope, - lud->lud_filter, lud->lud_attrs, 0, - NULL, NULL, NULL, 0, &msgid); - ldap_free_urldesc(lud); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); - result = CURLE_LDAP_SEARCH_FAILED; - } - else { - lr = calloc(1, sizeof(struct ldapreqinfo)); - if(!lr) { - ldap_abandon_ext(li->ld, msgid, NULL, NULL); - result = CURLE_OUT_OF_MEMORY; - } - else { - lr->msgid = msgid; - data->req.p.ldap = lr; - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); - *done = TRUE; - } - } + if(result) + goto out; + +#ifdef USE_SSL + if(ssl_installed(conn)) { + Sockbuf *sb; + /* re-install the libcurl SSL handlers into the sockbuf. */ + ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); + ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data); } +#endif + + rc = ldap_search_ext(li->ld, lud->lud_dn, lud->lud_scope, + lud->lud_filter, lud->lud_attrs, 0, + NULL, NULL, NULL, 0, &msgid); + ldap_free_urldesc(lud); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); + result = CURLE_LDAP_SEARCH_FAILED; + goto out; + } + + lr = calloc(1, sizeof(struct ldapreqinfo)); + if(!lr || + Curl_meta_set(data, CURL_META_LDAP_EASY, lr, oldap_easy_dtor)) { + ldap_abandon_ext(li->ld, msgid, NULL, NULL); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + lr->msgid = msgid; + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); + *done = TRUE; + +out: return result; } @@ -914,7 +1005,7 @@ static CURLcode oldap_done(struct Curl_easy *data, CURLcode res, bool premature) { struct connectdata *conn = data->conn; - struct ldapreqinfo *lr = data->req.p.ldap; + struct ldapreqinfo *lr = Curl_meta_get(data, CURL_META_LDAP_EASY); (void)res; (void)premature; @@ -922,12 +1013,13 @@ static CURLcode oldap_done(struct Curl_easy *data, CURLcode res, if(lr) { /* if there was a search in progress, abandon it */ if(lr->msgid) { - struct ldapconninfo *li = conn->proto.ldapc; - ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + if(li && li->ld) { + ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); + } lr->msgid = 0; } - data->req.p.ldap = NULL; - free(lr); + Curl_meta_remove(data, CURL_META_LDAP_EASY); } return CURLE_OK; @@ -945,19 +1037,13 @@ static CURLcode client_write(struct Curl_easy *data, separator, drop the latter. */ if(!len && plen && prefix[plen - 1] == ' ') plen--; - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) prefix, plen); - if(!result) - data->req.bytecount += plen; + result = Curl_client_write(data, CLIENTWRITE_BODY, prefix, plen); } if(!result && value) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) value, len); - if(!result) - data->req.bytecount += len; + result = Curl_client_write(data, CLIENTWRITE_BODY, value, len); } if(!result && suffix) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) suffix, slen); - if(!result) - data->req.bytecount += slen; + result = Curl_client_write(data, CLIENTWRITE_BODY, suffix, slen); } return result; } @@ -966,14 +1052,14 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, size_t len, CURLcode *err) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; - struct ldapreqinfo *lr = data->req.p.ldap; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + struct ldapreqinfo *lr = Curl_meta_get(data, CURL_META_LDAP_EASY); int rc; LDAPMessage *msg = NULL; BerElement *ber = NULL; struct timeval tv = {0, 0}; struct berval bv, *bvals; - int binary = 0; + bool binary = FALSE; CURLcode result = CURLE_AGAIN; int code; char *info = NULL; @@ -981,6 +1067,10 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, (void)len; (void)buf; (void)sockindex; + if(!li || !lr) { + *err = CURLE_FAILED_INIT; + return -1; + } rc = ldap_result(li->ld, lr->msgid, LDAP_MSG_ONE, &tv, &msg); if(rc < 0) { @@ -1013,7 +1103,7 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, switch(code) { case LDAP_SIZELIMIT_EXCEEDED: infof(data, "There are more than %d entries", lr->nument); - /* FALLTHROUGH */ + FALLTHROUGH(); case LDAP_SUCCESS: data->req.size = data->req.bytecount; break; @@ -1056,10 +1146,10 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, } binary = bv.bv_len > 7 && - !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7); + !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7); for(i = 0; bvals[i].bv_val != NULL; i++) { - int binval = 0; + bool binval = FALSE; result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, STRCONST(":")); @@ -1070,13 +1160,13 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, /* check for leading or trailing whitespace */ if(ISBLANK(bvals[i].bv_val[0]) || ISBLANK(bvals[i].bv_val[bvals[i].bv_len - 1])) - binval = 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 = 1; + binval = TRUE; break; } } @@ -1087,8 +1177,8 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, /* Binary value, encode to base64. */ if(bvals[i].bv_len) - result = Curl_base64_encode(bvals[i].bv_val, bvals[i].bv_len, - &val_b64, &val_b64_sz); + result = curlx_base64_encode(bvals[i].bv_val, bvals[i].bv_len, + &val_b64, &val_b64_sz); if(!result) result = client_write(data, STRCONST(": "), val_b64, val_b64_sz, STRCONST("\n")); @@ -1121,7 +1211,7 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, ldap_msgfree(msg); *err = result; - return result? -1: 0; + return result ? -1 : 0; } #ifdef USE_SSL @@ -1139,7 +1229,7 @@ ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) return 0; } -/* We don't need to do anything because libcurl does it already */ +/* We do not need to do anything because libcurl does it already */ static int ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) { @@ -1166,18 +1256,21 @@ ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) if(data) { struct connectdata *conn = data->conn; if(conn) { - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode err = CURLE_RECV_ERROR; + if(!li) { + SET_SOCKERRNO(SOCKEINVAL); + return -1; + } ret = (li->recv)(data, FIRSTSOCKET, buf, len, &err); if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); + SET_SOCKERRNO(SOCKEWOULDBLOCK); } } } return ret; } - static ber_slen_t ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) { @@ -1186,11 +1279,16 @@ ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) if(data) { struct connectdata *conn = data->conn; if(conn) { - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode err = CURLE_SEND_ERROR; - ret = (li->send)(data, FIRSTSOCKET, buf, len, &err); + + if(!li) { + SET_SOCKERRNO(SOCKEINVAL); + return -1; + } + ret = (li->send)(data, FIRSTSOCKET, buf, len, FALSE, &err); if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); + SET_SOCKERRNO(SOCKEWOULDBLOCK); } } } diff --git a/Utilities/cmcurl/lib/parsedate.c b/Utilities/cmcurl/lib/parsedate.c index 1a7195b16ac..7e0c69106c0 100644 --- a/Utilities/cmcurl/lib/parsedate.c +++ b/Utilities/cmcurl/lib/parsedate.c @@ -81,8 +81,9 @@ #include #include "strcase.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "parsedate.h" +#include "curlx/strparse.h" /* * parsedate() @@ -100,10 +101,12 @@ static int parsedate(const char *date, time_t *output); #define PARSEDATE_OK 0 #define PARSEDATE_FAIL -1 #define PARSEDATE_LATER 1 +#if defined(HAVE_TIME_T_UNSIGNED) || (SIZEOF_TIME_T < 5) #define PARSEDATE_SOONER 2 +#endif #if !defined(CURL_DISABLE_PARSEDATE) || !defined(CURL_DISABLE_FTP) || \ - !defined(CURL_DISABLE_FILE) + !defined(CURL_DISABLE_FILE) || defined(USE_GNUTLS) /* These names are also used by FTP and FILE code */ const char * const Curl_wkday[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; @@ -218,7 +221,7 @@ static int checkday(const char *check, size_t len) what = &Curl_wkday[0]; else return -1; /* too short */ - for(i = 0; i<7; i++) { + for(i = 0; i < 7; i++) { size_t ilen = strlen(what[0]); if((ilen == len) && strncasecompare(check, what[0], len)) @@ -235,7 +238,7 @@ static int checkmonth(const char *check, size_t len) if(len != 3) return -1; /* not a month */ - for(i = 0; i<12; i++) { + for(i = 0; i < 12; i++) { if(strncasecompare(check, what[0], 3)) return i; what++; @@ -244,7 +247,7 @@ static int checkmonth(const char *check, size_t len) } /* return the time zone offset between GMT and the input one, in number - of seconds or -1 if the timezone wasn't found/legal */ + of seconds or -1 if the timezone was not found/legal */ static int checktz(const char *check, size_t len) { @@ -253,7 +256,7 @@ static int checktz(const char *check, size_t len) if(len > 4) /* longer than any valid timezone */ return -1; - for(i = 0; i< sizeof(tz)/sizeof(tz[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(tz); i++) { size_t ilen = strlen(what->name); if((ilen == len) && strncasecompare(check, what->name, len)) @@ -265,7 +268,7 @@ static int checktz(const char *check, size_t len) static void skip(const char **date) { - /* skip everything that aren't letters or digits */ + /* skip everything that are not letters or digits */ while(**date && !ISALNUM(**date)) (*date)++; } @@ -277,7 +280,7 @@ enum assume { }; /* - * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to + * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to * mktime but for GMT only. */ static time_t time2epoch(int sec, int min, int hour, @@ -336,7 +339,7 @@ static bool match_time(const char *date, *h = hh; *m = mm; *s = ss; - *endp = (char *)p; + *endp = (char *)CURL_UNCONST(p); return TRUE; } @@ -409,7 +412,7 @@ static int parsedate(const char *date, time_t *output) } else if(ISDIGIT(*date)) { /* a digit */ - int val; + unsigned int val; char *end; if((secnum == -1) && match_time(date, &hournum, &minnum, &secnum, &end)) { @@ -417,35 +420,24 @@ static int parsedate(const char *date, time_t *output) date = end; } else { - long lval; - int error; - int old_errno; - - old_errno = errno; - errno = 0; - lval = strtol(date, &end, 10); - error = errno; - if(errno != old_errno) - errno = old_errno; - - if(error) + curl_off_t lval; + int num_digits = 0; + const char *p = date; + if(curlx_str_number(&p, &lval, 99999999)) return PARSEDATE_FAIL; -#if LONG_MAX != INT_MAX - if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) - return PARSEDATE_FAIL; -#endif - - val = curlx_sltosi(lval); + /* we know num_digits cannot be larger than 8 */ + num_digits = (int)(p - date); + val = (unsigned int)lval; if((tzoff == -1) && - ((end - date) == 4) && + (num_digits == 4) && (val <= 1400) && - (indate< date) && + (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) { /* four digits and a value less than or equal to 1400 (to take into account all sorts of funny time zone diffs) and it is preceded - with a plus or minus. This is a time zone indication. 1400 is + with a plus or minus. This is a time zone indication. 1400 is picked since +1300 is frequently used and +1400 is mentioned as an edge number in the document "ISO C 200X Proposal: Timezone Functions" at http://david.tribble.com/text/c0xtimezone.html If @@ -456,13 +448,13 @@ static int parsedate(const char *date, time_t *output) /* the + and - prefix indicates the local time compared to GMT, this we need their reversed math to get what we want */ - tzoff = date[-1]=='+'?-tzoff:tzoff; + tzoff = date[-1]=='+' ? -tzoff : tzoff; } - if(((end - date) == 8) && - (yearnum == -1) && - (monnum == -1) && - (mdaynum == -1)) { + else if((num_digits == 8) && + (yearnum == -1) && + (monnum == -1) && + (mdaynum == -1)) { /* 8 digits, no year, month or day yet. This is YYYYMMDD */ found = TRUE; yearnum = val/10000; @@ -471,7 +463,7 @@ static int parsedate(const char *date, time_t *output) } if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { - if((val > 0) && (val<32)) { + if((val > 0) && (val < 32)) { mdaynum = val; found = TRUE; } @@ -494,7 +486,7 @@ static int parsedate(const char *date, time_t *output) if(!found) return PARSEDATE_FAIL; - date = end; + date = p; } } @@ -521,13 +513,13 @@ static int parsedate(const char *date, time_t *output) #if (SIZEOF_TIME_T < 5) #ifdef HAVE_TIME_T_UNSIGNED - /* an unsigned 32 bit time_t can only hold dates to 2106 */ + /* an unsigned 32-bit time_t can only hold dates to 2106 */ if(yearnum > 2105) { *output = TIME_T_MAX; return PARSEDATE_LATER; } #else - /* a signed 32 bit time_t can only hold dates to the beginning of 2038 */ + /* a signed 32-bit time_t can only hold dates to the beginning of 2038 */ if(yearnum > 2037) { *output = TIME_T_MAX; return PARSEDATE_LATER; @@ -549,7 +541,7 @@ static int parsedate(const char *date, time_t *output) return PARSEDATE_FAIL; /* clearly an illegal date */ /* time2epoch() returns a time_t. time_t is often 32 bits, sometimes even on - architectures that feature 64 bit 'long' but ultimately time_t is the + architectures that feature a 64 bits 'long' but ultimately time_t is the correct data type to use. */ t = time2epoch(secnum, minnum, hournum, mdaynum, monnum, yearnum); @@ -558,7 +550,7 @@ static int parsedate(const char *date, time_t *output) if(tzoff == -1) tzoff = 0; - if((tzoff > 0) && (t > TIME_T_MAX - tzoff)) { + if((tzoff > 0) && (t > (time_t)(TIME_T_MAX - tzoff))) { *output = TIME_T_MAX; return PARSEDATE_LATER; /* time_t overflow */ } @@ -586,7 +578,7 @@ time_t curl_getdate(const char *p, const time_t *now) (void)now; /* legacy argument from the past that we ignore */ if(rc == PARSEDATE_OK) { - if(parsed == -1) + if(parsed == (time_t)-1) /* avoid returning -1 for a working scenario */ parsed++; return parsed; @@ -606,7 +598,7 @@ time_t Curl_getdate_capped(const char *p) switch(rc) { case PARSEDATE_OK: - if(parsed == -1) + if(parsed == (time_t)-1) /* avoid returning -1 for a working scenario */ parsed++; return parsed; diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c index f3f7cb93cb9..c5513f60502 100644 --- a/Utilities/cmcurl/lib/pingpong.c +++ b/Utilities/cmcurl/lib/pingpong.c @@ -29,6 +29,7 @@ #include "urldata.h" #include "cfilters.h" +#include "connect.h" #include "sendf.h" #include "select.h" #include "progress.h" @@ -36,6 +37,7 @@ #include "pingpong.h" #include "multiif.h" #include "vtls/vtls.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -49,10 +51,10 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, struct pingpong *pp, bool disconnecting) { - struct connectdata *conn = data->conn; timediff_t timeout_ms; /* in milliseconds */ - timediff_t response_time = (data->set.server_response_timeout)? - data->set.server_response_timeout: pp->response_time; + timediff_t response_time = (data->set.server_response_timeout) ? + data->set.server_response_timeout : pp->response_time; + struct curltime now = curlx_now(); /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is @@ -61,18 +63,20 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, /* Without a requested timeout, we only wait 'response_time' seconds for the full response to arrive before we bail out */ - timeout_ms = response_time - - Curl_timediff(Curl_now(), pp->response); /* spent time */ + timeout_ms = response_time - curlx_timediff(now, pp->response); if(data->set.timeout && !disconnecting) { - /* if timeout is requested, find out how much remaining time we have */ - timediff_t timeout2_ms = data->set.timeout - /* timeout time */ - Curl_timediff(Curl_now(), conn->now); /* spent time */ - + /* if timeout is requested, find out how much overall remains */ + timediff_t timeout2_ms = Curl_timeleft(data, &now, FALSE); /* pick the lowest number */ timeout_ms = CURLMIN(timeout_ms, timeout2_ms); } + if(disconnecting) { + timediff_t total_left_ms = Curl_timeleft(data, NULL, FALSE); + timeout_ms = CURLMIN(timeout_ms, CURLMAX(total_left_ms, 0)); + } + return timeout_ms; } @@ -95,6 +99,7 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; /* already too little time */ } + DEBUGF(infof(data, "pp_statematch, timeout=%" FMT_TIMEDIFF_T, timeout_ms)); if(block) { interval_ms = 1000; /* use 1 second timeout intervals */ if(timeout_ms < interval_ms) @@ -105,24 +110,24 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, if(Curl_conn_data_pending(data, FIRSTSOCKET)) rc = 1; - else if(Curl_pp_moredata(pp)) + else if(pp->overflow) /* We are receiving and there is data in the cache so just read it */ rc = 1; else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET)) /* We are receiving and there is data ready in the SSL library */ rc = 1; else - rc = Curl_socket_check(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */ CURL_SOCKET_BAD, - pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + pp->sendleft ? sock : CURL_SOCKET_BAD, /* writing */ interval_ms); if(block) { - /* if we didn't wait, we don't have to spend time on this now */ + /* if we did not wait, we do not have to spend time on this now */ if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_now()); + result = Curl_speedcheck(data, curlx_now()); if(result) return result; @@ -134,24 +139,22 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, } else if(rc) result = pp->statemachine(data, data->conn); + else if(disconnecting) + return CURLE_OPERATION_TIMEDOUT; return result; } /* initialize stuff to prepare for reading a fresh new response */ -void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp) +void Curl_pp_init(struct pingpong *pp) { - DEBUGASSERT(data); + DEBUGASSERT(!pp->initialised); pp->nread_resp = 0; - pp->linestart_resp = data->state.buffer; + pp->response = curlx_now(); /* start response time-out now! */ pp->pending_resp = TRUE; - pp->response = Curl_now(); /* start response time-out now! */ -} - -/* setup for the coming transfer */ -void Curl_pp_setup(struct pingpong *pp) -{ - Curl_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); + curlx_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); + curlx_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); + pp->initialised = TRUE; } /*********************************************************************** @@ -169,7 +172,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, const char *fmt, va_list args) { - ssize_t bytes_written = 0; + size_t bytes_written = 0; size_t write_len; char *s; CURLcode result; @@ -184,29 +187,32 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, DEBUGASSERT(pp->sendthis == NULL); if(!conn) - /* can't send without a connection! */ + /* cannot send without a connection! */ return CURLE_SEND_ERROR; - Curl_dyn_reset(&pp->sendbuf); - result = Curl_dyn_vaddf(&pp->sendbuf, fmt, args); + curlx_dyn_reset(&pp->sendbuf); + result = curlx_dyn_vaddf(&pp->sendbuf, fmt, args); if(result) return result; /* append CRLF */ - result = Curl_dyn_addn(&pp->sendbuf, "\r\n", 2); + result = curlx_dyn_addn(&pp->sendbuf, "\r\n", 2); if(result) return result; - write_len = Curl_dyn_len(&pp->sendbuf); - s = Curl_dyn_ptr(&pp->sendbuf); - Curl_pp_init(data, pp); + pp->pending_resp = TRUE; + write_len = curlx_dyn_len(&pp->sendbuf); + s = curlx_dyn_ptr(&pp->sendbuf); #ifdef HAVE_GSSAPI conn->data_prot = PROT_CMD; #endif - result = Curl_write(data, conn->sock[FIRSTSOCKET], s, write_len, - &bytes_written); - if(result) + result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE, + &bytes_written); + if(result == CURLE_AGAIN) { + bytes_written = 0; + } + else if(result) return result; #ifdef HAVE_GSSAPI data_sec = conn->data_prot; @@ -214,9 +220,9 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, conn->data_prot = (unsigned char)data_sec; #endif - Curl_debug(data, CURLINFO_HEADER_OUT, s, (size_t)bytes_written); + Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written); - if(bytes_written != (ssize_t)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; @@ -225,7 +231,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, else { pp->sendthis = NULL; pp->sendleft = pp->sendsize = 0; - pp->response = Curl_now(); + pp->response = curlx_now(); } return CURLE_OK; @@ -256,192 +262,131 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, return result; } +static CURLcode pingpong_read(struct Curl_easy *data, + int sockindex, + char *buffer, + size_t buflen, + ssize_t *nread) +{ + CURLcode result; +#ifdef HAVE_GSSAPI + enum protection_level prot = data->conn->data_prot; + data->conn->data_prot = PROT_CLEAR; +#endif + result = Curl_conn_recv(data, sockindex, buffer, buflen, nread); +#ifdef HAVE_GSSAPI + DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); + data->conn->data_prot = (unsigned char)prot; +#endif + return result; +} + /* * Curl_pp_readresp() * * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + int sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size) /* size of the response */ { - ssize_t perline; /* count bytes per line */ - bool keepon = TRUE; - ssize_t gotbytes; - char *ptr; struct connectdata *conn = data->conn; - char * const buf = data->state.buffer; CURLcode result = CURLE_OK; + ssize_t gotbytes; + char buffer[900]; *code = 0; /* 0 for errors or not done */ *size = 0; - ptr = buf + pp->nread_resp; - - /* number of bytes in the current line, so far */ - perline = (ssize_t)(ptr-pp->linestart_resp); - - while((pp->nread_resp < (size_t)data->set.buffer_size) && - (keepon && !result)) { - - if(pp->cache) { - /* we had data in the "cache", copy that instead of doing an actual - * read - * - * pp->cache_size is cast to ssize_t here. This should be safe, because - * it would have been populated with something of size int to begin - * with, even though its datatype may be larger than an int. - */ - if((ptr + pp->cache_size) > (buf + data->set.buffer_size + 1)) { - failf(data, "cached response data too big to handle"); - return CURLE_WEIRD_SERVER_REPLY; - } - memcpy(ptr, pp->cache, pp->cache_size); - gotbytes = (ssize_t)pp->cache_size; - free(pp->cache); /* free the cache */ - pp->cache = NULL; /* clear the pointer */ - pp->cache_size = 0; /* zero the size just in case */ + do { + gotbytes = 0; + if(pp->nfinal) { + /* a previous call left this many bytes in the beginning of the buffer as + that was the final line; now ditch that */ + size_t full = curlx_dyn_len(&pp->recvbuf); + + /* trim off the "final" leading part */ + curlx_dyn_tail(&pp->recvbuf, full - pp->nfinal); + + pp->nfinal = 0; /* now gone */ } - else { -#ifdef HAVE_GSSAPI - enum protection_level prot = conn->data_prot; - conn->data_prot = PROT_CLEAR; -#endif - DEBUGASSERT((ptr + data->set.buffer_size - pp->nread_resp) <= - (buf + data->set.buffer_size + 1)); - result = Curl_read(data, sockfd, ptr, - data->set.buffer_size - pp->nread_resp, - &gotbytes); -#ifdef HAVE_GSSAPI - DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); - conn->data_prot = (unsigned char)prot; -#endif + if(!pp->overflow) { + result = pingpong_read(data, sockindex, buffer, sizeof(buffer), + &gotbytes); if(result == CURLE_AGAIN) - return CURLE_OK; /* return */ + return CURLE_OK; if(result) - /* Set outer result variable to this error. */ - keepon = FALSE; - } + return result; - if(!keepon) - ; - else if(gotbytes <= 0) { - keepon = FALSE; - result = CURLE_RECV_ERROR; - failf(data, "response reading failed (errno: %d)", SOCKERRNO); - } - else { - /* we got a whole chunk of data, which can be anything from one - * byte to a set of lines and possible just a piece of the last - * line */ - ssize_t i; - ssize_t clipamount = 0; - bool restart = FALSE; + if(gotbytes <= 0) { + failf(data, "response reading failed (errno: %d)", SOCKERRNO); + return CURLE_RECV_ERROR; + } + + result = curlx_dyn_addn(&pp->recvbuf, buffer, gotbytes); + if(result) + return result; - data->req.headerbytecount += (long)gotbytes; + data->req.headerbytecount += (unsigned int)gotbytes; pp->nread_resp += gotbytes; - for(i = 0; i < gotbytes; ptr++, i++) { - perline++; - if(*ptr == '\n') { - /* a newline is CRLF in pp-talk, so the CR is ignored as - the line isn't really terminated until the LF comes */ + } + + do { + char *line = curlx_dyn_ptr(&pp->recvbuf); + char *nl = memchr(line, '\n', curlx_dyn_len(&pp->recvbuf)); + if(nl) { + /* a newline is CRLF in pp-talk, so the CR is ignored as + the line is not really terminated until the LF comes */ + size_t length = nl - line + 1; - /* output debug output if that is requested */ + /* output debug output if that is requested */ #ifdef HAVE_GSSAPI - if(!conn->sec_complete) + if(!conn->sec_complete) #endif - Curl_debug(data, CURLINFO_HEADER_IN, - pp->linestart_resp, (size_t)perline); - - /* - * We pass all response-lines to the callback function registered - * for "headers". The response lines can be seen as a kind of - * headers. - */ - result = Curl_client_write(data, CLIENTWRITE_HEADER, - pp->linestart_resp, perline); - if(result) - return result; - - if(pp->endofresp(data, conn, pp->linestart_resp, perline, code)) { - /* This is the end of the last line, copy the last line to the - start of the buffer and null-terminate, for old times sake */ - size_t n = ptr - pp->linestart_resp; - memmove(buf, pp->linestart_resp, n); - buf[n] = 0; /* null-terminate */ - keepon = FALSE; - pp->linestart_resp = ptr + 1; /* advance pointer */ - i++; /* skip this before getting out */ - - *size = pp->nread_resp; /* size of the response */ - pp->nread_resp = 0; /* restart */ - break; - } - perline = 0; /* line starts over here */ - pp->linestart_resp = ptr + 1; - } - } - - if(!keepon && (i != gotbytes)) { - /* We found the end of the response lines, but we didn't parse the - full chunk of data we have read from the server. We therefore need - to store the rest of the data to be checked on the next invoke as - it may actually contain another end of response already! */ - clipamount = gotbytes - i; - restart = TRUE; - DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing " - "server response left", - (int)clipamount)); - } - else if(keepon) { - - if((perline == gotbytes) && - (gotbytes > (ssize_t)data->set.buffer_size/2)) { - /* We got an excessive line without newlines and we need to deal - with it. We keep the first bytes of the line then we throw - away the rest. */ - infof(data, "Excessive server response line length received, " - "%zd bytes. Stripping", gotbytes); - restart = TRUE; - - /* we keep 40 bytes since all our pingpong protocols are only - interested in the first piece */ - clipamount = 40; + Curl_debug(data, CURLINFO_HEADER_IN, line, length); + + /* + * Pass all response-lines to the callback function registered for + * "headers". The response lines can be seen as a kind of headers. + */ + result = Curl_client_write(data, CLIENTWRITE_INFO, line, length); + if(result) + return result; + + if(pp->endofresp(data, conn, line, length, code)) { + /* When at "end of response", keep the endofresp line first in the + buffer since it will be accessed outside (by pingpong + parsers). Store the overflow counter to inform about additional + data in this buffer after the endofresp line. */ + pp->nfinal = length; + if(curlx_dyn_len(&pp->recvbuf) > length) + pp->overflow = curlx_dyn_len(&pp->recvbuf) - length; + else + pp->overflow = 0; + *size = pp->nread_resp; /* size of the response */ + pp->nread_resp = 0; /* restart */ + gotbytes = 0; /* force break out of outer loop */ + break; } - else if(pp->nread_resp > (size_t)data->set.buffer_size/2) { - /* We got a large chunk of data and there's potentially still - trailing data to take care of, so we put any such part in the - "cache", clear the buffer to make space and restart. */ - clipamount = perline; - restart = TRUE; - } - } - else if(i == gotbytes) - restart = TRUE; - - if(clipamount) { - pp->cache_size = clipamount; - pp->cache = malloc(pp->cache_size); - if(pp->cache) - memcpy(pp->cache, pp->linestart_resp, pp->cache_size); + if(curlx_dyn_len(&pp->recvbuf) > length) + /* keep the remaining piece */ + curlx_dyn_tail((&pp->recvbuf), curlx_dyn_len(&pp->recvbuf) - length); else - return CURLE_OUT_OF_MEMORY; + curlx_dyn_reset(&pp->recvbuf); } - if(restart) { - /* now reset a few variables to start over nicely from the start of - the big buffer */ - pp->nread_resp = 0; /* start over from scratch in the buffer */ - ptr = pp->linestart_resp = buf; - perline = 0; + else { + /* without a newline, there is no overflow */ + pp->overflow = 0; + break; } - } /* there was data */ + } while(1); /* while there is buffer left to scan */ - } /* while there's buffer left and loop is requested */ + } while(gotbytes == sizeof(buffer)); pp->pending_resp = FALSE; @@ -463,41 +408,58 @@ int Curl_pp_getsock(struct Curl_easy *data, return GETSOCK_READSOCK(0); } +bool Curl_pp_needs_flush(struct Curl_easy *data, + struct pingpong *pp) +{ + (void)data; + return pp->sendleft > 0; +} + CURLcode Curl_pp_flushsend(struct Curl_easy *data, struct pingpong *pp) { /* we have a piece of a command still left to send */ - struct connectdata *conn = data->conn; - ssize_t written; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - CURLcode result = Curl_write(data, sock, pp->sendthis + pp->sendsize - - pp->sendleft, pp->sendleft, &written); + size_t written; + CURLcode result; + + if(!Curl_pp_needs_flush(data, pp)) + return CURLE_OK; + + result = Curl_conn_send(data, FIRSTSOCKET, + pp->sendthis + pp->sendsize - pp->sendleft, + pp->sendleft, FALSE, &written); + if(result == CURLE_AGAIN) { + result = CURLE_OK; + written = 0; + } if(result) return result; - if(written != (ssize_t)pp->sendleft) { + if(written != pp->sendleft) { /* only a fraction was sent */ pp->sendleft -= written; } else { pp->sendthis = NULL; pp->sendleft = pp->sendsize = 0; - pp->response = Curl_now(); + pp->response = curlx_now(); } return CURLE_OK; } CURLcode Curl_pp_disconnect(struct pingpong *pp) { - Curl_dyn_free(&pp->sendbuf); - Curl_safefree(pp->cache); + if(pp->initialised) { + curlx_dyn_free(&pp->sendbuf); + curlx_dyn_free(&pp->recvbuf); + memset(pp, 0, sizeof(*pp)); + } return CURLE_OK; } bool Curl_pp_moredata(struct pingpong *pp) { - return (!pp->sendleft && pp->cache && pp->nread_resp < pp->cache_size) ? - TRUE : FALSE; + return !pp->sendleft && curlx_dyn_len(&pp->recvbuf) > pp->nfinal; } #endif diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h index 80d3f7718c7..0665b83659e 100644 --- a/Utilities/cmcurl/lib/pingpong.h +++ b/Utilities/cmcurl/lib/pingpong.h @@ -37,7 +37,7 @@ struct connectdata; typedef enum { PPTRANSFER_BODY, /* yes do transfer a body */ PPTRANSFER_INFO, /* do still go through to get info/headers */ - PPTRANSFER_NONE /* don't get anything and don't get info */ + PPTRANSFER_NONE /* do not get anything and do not get info */ } curl_pp_transfer; /* @@ -47,16 +47,8 @@ typedef enum { * It holds response cache and non-blocking sending data. */ struct pingpong { - char *cache; /* data cache between getresponse()-calls */ - size_t cache_size; /* size of cache in bytes */ size_t nread_resp; /* number of bytes currently read of a server response */ - char *linestart_resp; /* line start pointer for the server response - reader function */ - bool pending_resp; /* set TRUE when a server response is pending or in - progress, and is cleared once the last response is - read */ - char *sendthis; /* allocated pointer to a buffer that is to be sent to the - server */ + char *sendthis; /* pointer to a buffer that is to be sent to the server */ size_t sendleft; /* number of bytes left to send from the sendthis buffer */ size_t sendsize; /* total size of the sendthis buffer */ struct curltime response; /* set to Curl_now() when a command has been sent @@ -64,36 +56,41 @@ struct pingpong { timediff_t response_time; /* When no timeout is given, this is the amount of milliseconds we await for a server response. */ struct dynbuf sendbuf; + struct dynbuf recvbuf; + size_t overflow; /* number of bytes left after a final response line */ + size_t nfinal; /* number of bytes in the final response line, which + after a match is first in the receice buffer */ /* Function pointers the protocols MUST implement and provide for the pingpong layer to function */ CURLcode (*statemachine)(struct Curl_easy *data, struct connectdata *conn); bool (*endofresp)(struct Curl_easy *data, struct connectdata *conn, - char *ptr, size_t len, int *code); + const char *ptr, size_t len, int *code); + BIT(initialised); + BIT(pending_resp); /* set TRUE when a server response is pending or in + progress, and is cleared once the last response is + read */ }; #define PINGPONG_SETUP(pp,s,e) \ do { \ - pp->response_time = RESP_TIMEOUT; \ - pp->statemachine = s; \ - pp->endofresp = e; \ + (pp)->response_time = RESP_TIMEOUT; \ + (pp)->statemachine = s; \ + (pp)->endofresp = e; \ } while(0) /* * Curl_pp_statemach() * * called repeatedly until done. Set 'wait' to make it wait a while on the - * socket if there's no traffic. + * socket if there is no traffic. */ CURLcode Curl_pp_statemach(struct Curl_easy *data, struct pingpong *pp, bool block, bool disconnecting); /* initialize stuff to prepare for reading a fresh new response */ -void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp); - -/* setup for the transfer */ -void Curl_pp_setup(struct pingpong *pp); +void Curl_pp_init(struct pingpong *pp); /* Returns timeout in ms. 0 or negative number means the timeout has already triggered */ @@ -113,7 +110,7 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, */ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, - const char *fmt, ...); + const char *fmt, ...) CURL_PRINTF(3, 4); /*********************************************************************** * @@ -128,7 +125,7 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, CURLcode Curl_pp_vsendf(struct Curl_easy *data, struct pingpong *pp, const char *fmt, - va_list args); + va_list args) CURL_PRINTF(3, 0); /* * Curl_pp_readresp() @@ -136,11 +133,13 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + int sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size); /* size of the response */ +bool Curl_pp_needs_flush(struct Curl_easy *data, + struct pingpong *pp); CURLcode Curl_pp_flushsend(struct Curl_easy *data, struct pingpong *pp); diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c index 0de34cc1139..aa8c2d834a9 100644 --- a/Utilities/cmcurl/lib/pop3.c +++ b/Utilities/cmcurl/lib/pop3.c @@ -47,9 +47,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -67,8 +64,8 @@ #include "escape.h" #include "http.h" /* for HTTP proxy tunnel stuff */ #include "socks.h" +#include "pingpong.h" #include "pop3.h" -#include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -79,12 +76,75 @@ #include "bufref.h" #include "curl_sasl.h" #include "curl_md5.h" -#include "warnless.h" +#include "curlx/warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +/* Authentication type flags */ +#define POP3_TYPE_CLEARTEXT (1 << 0) +#define POP3_TYPE_APOP (1 << 1) +#define POP3_TYPE_SASL (1 << 2) + +/* Authentication type values */ +#define POP3_TYPE_NONE 0 +#define POP3_TYPE_ANY (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL) + +/* This is the 5-bytes End-Of-Body marker for POP3 */ +#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" +#define POP3_EOB_LEN 5 + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_POP3_EASY "meta:proto:pop3:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_POP3_CONN "meta:proto:pop3:conn" + +/* + * POP3 easy handle state + */ +struct POP3 { + curl_pp_transfer transfer; + char *id; /* Message ID */ + char *custom; /* Custom Request */ +}; + +/* + * POP3 connection state + */ +typedef enum { + POP3_STOP, /* do nothing state, stops the state machine */ + POP3_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + POP3_CAPA, + POP3_STARTTLS, + POP3_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + POP3_AUTH, + POP3_APOP, + POP3_USER, + POP3_PASS, + POP3_COMMAND, + POP3_QUIT, + POP3_LAST /* never used */ +} pop3state; + +struct pop3_conn { + struct pingpong pp; + pop3state state; /* Always use pop3.c:state() to change state! */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + size_t strip; /* Number of bytes from the start to ignore as + non-body */ + struct SASL sasl; /* SASL-related storage */ + char *apoptimestamp; /* APOP timestamp from the server greeting */ + unsigned char authtypes; /* Accepted authentication types */ + unsigned char preftype; /* Preferred authentication type */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ +}; + /* Local API functions */ static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *done); static CURLcode pop3_do(struct Curl_easy *data, bool *done); @@ -109,12 +169,17 @@ static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech, static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech); static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out); +/* This function scans the body after the end-of-body and writes everything + * until the end is found */ +static CURLcode pop3_write(struct Curl_easy *data, + const char *str, size_t nread, bool is_eos); + /* * POP3 protocol handler. */ const struct Curl_handler Curl_handler_pop3 = { - "POP3", /* scheme */ + "pop3", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -127,9 +192,11 @@ const struct Curl_handler Curl_handler_pop3 = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_POP3, /* defport */ CURLPROTO_POP3, /* protocol */ CURLPROTO_POP3, /* family */ @@ -143,7 +210,7 @@ const struct Curl_handler Curl_handler_pop3 = { */ const struct Curl_handler Curl_handler_pop3s = { - "POP3S", /* scheme */ + "pop3s", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -156,9 +223,11 @@ const struct Curl_handler Curl_handler_pop3s = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_POP3S, /* defport */ CURLPROTO_POP3S, /* protocol */ CURLPROTO_POP3, /* family */ @@ -181,18 +250,52 @@ static const struct SASLproto saslpop3 = { SASL_FLAG_BASE64 /* Configuration flags */ }; -#ifdef USE_SSL -static void pop3_to_pop3s(struct connectdata *conn) -{ - /* Change the connection handler */ - conn->handler = &Curl_handler_pop3s; +struct pop3_cmd { + const char *name; + unsigned short nlen; + BIT(multiline); /* response is multi-line with last '.' line */ + BIT(multiline_with_args); /* is multi-line when command has args */ +}; + +static const struct pop3_cmd pop3cmds[] = { + { "APOP", 4, FALSE, FALSE }, + { "AUTH", 4, FALSE, FALSE }, + { "CAPA", 4, TRUE, TRUE }, + { "DELE", 4, FALSE, FALSE }, + { "LIST", 4, TRUE, FALSE }, + { "MSG", 3, TRUE, TRUE }, + { "NOOP", 4, FALSE, FALSE }, + { "PASS", 4, FALSE, FALSE }, + { "QUIT", 4, FALSE, FALSE }, + { "RETR", 4, TRUE, TRUE }, + { "RSET", 4, FALSE, FALSE }, + { "STAT", 4, FALSE, FALSE }, + { "STLS", 4, FALSE, FALSE }, + { "TOP", 3, TRUE, TRUE }, + { "UIDL", 4, TRUE, FALSE }, + { "USER", 4, FALSE, FALSE }, + { "UTF8", 4, FALSE, FALSE }, + { "XTND", 4, TRUE, TRUE }, +}; - /* Set the connection's upgraded to TLS flag */ - conn->bits.tls_upgraded = TRUE; +/* Return iff a command is defined as "multi-line" (RFC 1939), + * has a response terminated by a last line with a '.'. + */ +static bool pop3_is_multiline(const char *cmdline) +{ + size_t i; + for(i = 0; i < CURL_ARRAYSIZE(pop3cmds); ++i) { + if(strncasecompare(pop3cmds[i].name, cmdline, pop3cmds[i].nlen)) { + if(!cmdline[pop3cmds[i].nlen]) + return pop3cmds[i].multiline; + else if(cmdline[pop3cmds[i].nlen] == ' ') + return pop3cmds[i].multiline_with_args; + } + } + /* Unknown command, assume multi-line for backward compatibility with + * earlier curl versions that only could do multi-line responses. */ + return TRUE; } -#else -#define pop3_to_pop3s(x) Curl_nop_stmt -#endif /*********************************************************************** * @@ -204,10 +307,13 @@ static void pop3_to_pop3s(struct connectdata *conn) * types and allowed SASL mechanisms. */ static bool pop3_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *resp) + const char *line, size_t len, int *resp) { - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); (void)data; + DEBUGASSERT(pop3c); + if(!pop3c) /* internal error */ + return TRUE; /* Do we have an error response? */ if(len >= 4 && !memcmp("-ERR", line, 4)) { @@ -254,9 +360,15 @@ static bool pop3_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); - + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); + char *message; + size_t len; + + if(!pop3c) + return CURLE_FAILED_INIT; + message = curlx_dyn_ptr(&pop3c->pp.recvbuf); + len = pop3c->pp.nfinal; if(len > 2) { /* Find the start of the message */ len -= 2; @@ -282,36 +394,39 @@ static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out) /*********************************************************************** * - * state() + * pop3_state() * * This is the ONLY way to change POP3 state! */ -static void state(struct Curl_easy *data, pop3state newstate) +static void pop3_state(struct Curl_easy *data, pop3state newstate) { - struct pop3_conn *pop3c = &data->conn->proto.pop3c; + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); + if(pop3c) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ - static const char * const names[] = { - "STOP", - "SERVERGREET", - "CAPA", - "STARTTLS", - "UPGRADETLS", - "AUTH", - "APOP", - "USER", - "PASS", - "COMMAND", - "QUIT", - /* LAST */ - }; - - if(pop3c->state != newstate) - infof(data, "POP3 %p state change from %s to %s", - (void *)pop3c, names[pop3c->state], names[newstate]); + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SERVERGREET", + "CAPA", + "STARTTLS", + "UPGRADETLS", + "AUTH", + "APOP", + "USER", + "PASS", + "COMMAND", + "QUIT", + /* LAST */ + }; + + if(pop3c->state != newstate) + infof(data, "POP3 %p state change from %s to %s", + (void *)pop3c, names[pop3c->state], names[newstate]); #endif - pop3c->state = newstate; + pop3c->state = newstate; + } } /*********************************************************************** @@ -324,8 +439,11 @@ static void state(struct Curl_easy *data, pop3state newstate) static CURLcode pop3_perform_capa(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; + + if(!pop3c) + return CURLE_FAILED_INIT; pop3c->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */ pop3c->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */ @@ -335,7 +453,7 @@ static CURLcode pop3_perform_capa(struct Curl_easy *data, result = Curl_pp_sendf(data, &pop3c->pp, "%s", "CAPA"); if(!result) - state(data, POP3_CAPA); + pop3_state(data, POP3_CAPA); return result; } @@ -349,11 +467,16 @@ static CURLcode pop3_perform_capa(struct Curl_easy *data, static CURLcode pop3_perform_starttls(struct Curl_easy *data, struct connectdata *conn) { - /* Send the STLS command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "STLS"); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + CURLcode result; + + if(!pop3c) + return CURLE_FAILED_INIT; + /* Send the STLS command */ + result = Curl_pp_sendf(data, &pop3c->pp, "%s", "STLS"); if(!result) - state(data, POP3_STARTTLS); + pop3_state(data, POP3_STARTTLS); return result; } @@ -367,31 +490,39 @@ static CURLcode pop3_perform_starttls(struct Curl_easy *data, static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, struct connectdata *conn) { +#ifdef USE_SSL /* Start the SSL connection */ - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result; bool ssldone = FALSE; + if(!pop3c) + return CURLE_FAILED_INIT; + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) goto out; + /* Change the connection handler */ + conn->handler = &Curl_handler_pop3s; } + DEBUGASSERT(!pop3c->ssldone); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); - - if(!result) { + DEBUGF(infof(data, "pop3_perform_upgrade_tls, connect -> %d, %d", + result, ssldone)); + if(!result && ssldone) { pop3c->ssldone = ssldone; - if(pop3c->state != POP3_UPGRADETLS) - state(data, POP3_UPGRADETLS); - - if(pop3c->ssldone) { - pop3_to_pop3s(conn); - result = pop3_perform_capa(data, conn); - } + /* perform CAPA now, changes pop3c->state out of POP3_UPGRADETLS */ + result = pop3_perform_capa(data, conn); } out: return result; +#else + (void)data; + (void)conn; + return CURLE_NOT_BUILT_IN; +#endif } /*********************************************************************** @@ -403,26 +534,30 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, static CURLcode pop3_perform_user(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; + if(!pop3c) + return CURLE_FAILED_INIT; + /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!data->state.aptr.user) { - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } /* Send the USER command */ - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "USER %s", + result = Curl_pp_sendf(data, &pop3c->pp, "USER %s", conn->user ? conn->user : ""); if(!result) - state(data, POP3_USER); + pop3_state(data, POP3_USER); return result; } -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH /*********************************************************************** * * pop3_perform_apop() @@ -432,23 +567,26 @@ static CURLcode pop3_perform_user(struct Curl_easy *data, static CURLcode pop3_perform_apop(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; size_t i; struct MD5_context *ctxt; unsigned char digest[MD5_DIGEST_LEN]; char secret[2 * MD5_DIGEST_LEN + 1]; + if(!pop3c) + return CURLE_FAILED_INIT; + /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!data->state.aptr.user) { - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } /* Create the digest */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; @@ -468,7 +606,7 @@ static CURLcode pop3_perform_apop(struct Curl_easy *data, result = Curl_pp_sendf(data, &pop3c->pp, "APOP %s %s", conn->user, secret); if(!result) - state(data, POP3_APOP); + pop3_state(data, POP3_APOP); return result; } @@ -485,10 +623,14 @@ static CURLcode pop3_perform_auth(struct Curl_easy *data, const char *mech, const struct bufref *initresp) { + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &data->conn->proto.pop3c; const char *ir = (const char *) Curl_bufref_ptr(initresp); + if(!pop3c) + return CURLE_FAILED_INIT; + if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", mech, ir); @@ -511,9 +653,12 @@ static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp) { - struct pop3_conn *pop3c = &data->conn->proto.pop3c; + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); (void)mech; + if(!pop3c) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &pop3c->pp, "%s", (const char *) Curl_bufref_ptr(resp)); @@ -527,9 +672,12 @@ static CURLcode pop3_continue_auth(struct Curl_easy *data, */ static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech) { - struct pop3_conn *pop3c = &data->conn->proto.pop3c; + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); (void)mech; + if(!pop3c) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &pop3c->pp, "*"); } @@ -545,14 +693,17 @@ static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech) static CURLcode pop3_perform_authentication(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; saslprogress progress = SASL_IDLE; + if(!pop3c) + return CURLE_FAILED_INIT; + /* Check we have enough data to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!Curl_sasl_can_authenticate(&pop3c->sasl, data)) { - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } @@ -562,11 +713,11 @@ static CURLcode pop3_perform_authentication(struct Curl_easy *data, if(!result) if(progress == SASL_INPROGRESS) - state(data, POP3_AUTH); + pop3_state(data, POP3_AUTH); } if(!result && progress == SASL_IDLE) { -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP) /* Perform APOP authentication */ result = pop3_perform_apop(data, conn); @@ -575,11 +726,8 @@ static CURLcode pop3_perform_authentication(struct Curl_easy *data, if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT) /* Perform clear text authentication */ result = pop3_perform_user(data, conn); - else { - /* Other mechanisms not supported */ - infof(data, "No known authentication mechanisms supported"); - result = CURLE_LOGIN_DENIED; - } + else + result = Curl_sasl_is_blocked(&pop3c->sasl, data); } return result; @@ -595,9 +743,13 @@ static CURLcode pop3_perform_command(struct Curl_easy *data) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); const char *command = NULL; + if(!pop3 || !pop3c) + return CURLE_FAILED_INIT; + /* Calculate the default command */ if(pop3->id[0] == '\0' || data->set.list_only) { command = "LIST"; @@ -609,18 +761,19 @@ static CURLcode pop3_perform_command(struct Curl_easy *data) else command = "RETR"; + if(pop3->custom && pop3->custom[0] != '\0') + command = pop3->custom; + /* Send the command */ if(pop3->id[0] != '\0') - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s %s", - (pop3->custom && pop3->custom[0] != '\0' ? - pop3->custom : command), pop3->id); + result = Curl_pp_sendf(data, &pop3c->pp, "%s %s", command, pop3->id); else - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", - (pop3->custom && pop3->custom[0] != '\0' ? - pop3->custom : command)); + result = Curl_pp_sendf(data, &pop3c->pp, "%s", command); - if(!result) - state(data, POP3_COMMAND); + if(!result) { + pop3_state(data, POP3_COMMAND); + data->req.no_body = !pop3_is_multiline(command); + } return result; } @@ -634,11 +787,16 @@ static CURLcode pop3_perform_command(struct Curl_easy *data) static CURLcode pop3_perform_quit(struct Curl_easy *data, struct connectdata *conn) { - /* Send the QUIT command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "QUIT"); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + CURLcode result; + if(!pop3c) + return CURLE_FAILED_INIT; + + /* Send the QUIT command */ + result = Curl_pp_sendf(data, &pop3c->pp, "%s", "QUIT"); if(!result) - state(data, POP3_QUIT); + pop3_state(data, POP3_QUIT); return result; } @@ -650,54 +808,50 @@ static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + const char *line; + size_t len; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; + + line = curlx_dyn_ptr(&pop3c->pp.recvbuf); + len = pop3c->pp.nfinal; if(pop3code != '+') { failf(data, "Got unexpected pop3-server response"); result = CURLE_WEIRD_SERVER_REPLY; } - else { + else if(len > 3) { /* Does the server support APOP authentication? */ - if(len >= 4 && line[len - 2] == '>') { - /* Look for the APOP timestamp */ - size_t i; - for(i = 3; i < len - 2; ++i) { - if(line[i] == '<') { - /* Calculate the length of the timestamp */ - size_t timestamplen = len - 1 - i; - char *at; - if(!timestamplen) - break; - - /* Allocate some memory for the timestamp */ - pop3c->apoptimestamp = (char *)calloc(1, timestamplen + 1); - - if(!pop3c->apoptimestamp) - break; - - /* Copy the timestamp */ - memcpy(pop3c->apoptimestamp, line + i, timestamplen); - pop3c->apoptimestamp[timestamplen] = '\0'; - - /* If the timestamp does not contain '@' it is not (as required by - RFC-1939) conformant to the RFC-822 message id syntax, and we - therefore do not use APOP authentication. */ - at = strchr(pop3c->apoptimestamp, '@'); - if(!at) - Curl_safefree(pop3c->apoptimestamp); - else - /* Store the APOP capability */ - pop3c->authtypes |= POP3_TYPE_APOP; - break; - } + char *lt; + char *gt = NULL; + + /* Look for the APOP timestamp */ + lt = memchr(line, '<', len); + if(lt) + /* search the remainder for '>' */ + gt = memchr(lt, '>', len - (lt - line)); + if(gt) { + /* the length of the timestamp, including the brackets */ + size_t timestamplen = gt - lt + 1; + char *at = memchr(lt, '@', timestamplen); + /* If the timestamp does not contain '@' it is not (as required by + RFC-1939) conformant to the RFC-822 message id syntax, and we + therefore do not use APOP authentication. */ + if(at) { + /* dupe the timestamp */ + pop3c->apoptimestamp = Curl_memdup0(lt, timestamplen); + if(!pop3c->apoptimestamp) + return CURLE_OUT_OF_MEMORY; + /* Store the APOP capability */ + pop3c->authtypes |= POP3_TYPE_APOP; } } - result = pop3_perform_capa(data, conn); + if(!result) + result = pop3_perform_capa(data, conn); } return result; @@ -709,13 +863,18 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + const char *line; + size_t len; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; + + line = curlx_dyn_ptr(&pop3c->pp.recvbuf); + len = pop3c->pp.nfinal; - /* Do we have a untagged continuation response? */ + /* Do we have an untagged continuation response? */ if(pop3code == '*') { /* Does the server support the STLS capability? */ if(len >= 4 && !memcmp(line, "STLS", 4)) @@ -767,7 +926,7 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, } } else { - /* Clear text is supported when CAPA isn't recognised */ + /* Clear text is supported when CAPA is not recognised */ if(pop3code != '+') pop3c->authtypes |= POP3_TYPE_CLEARTEXT; @@ -794,11 +953,15 @@ static CURLcode pop3_state_starttls_resp(struct Curl_easy *data, int pop3code, pop3state instate) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; + /* Pipelining in response is forbidden. */ - if(data->conn->proto.pop3c.pp.cache_size) + if(pop3c->pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(pop3code != '+') { @@ -810,7 +973,7 @@ static CURLcode pop3_state_starttls_resp(struct Curl_easy *data, result = pop3_perform_authentication(data, conn); } else - result = pop3_perform_upgrade_tls(data, conn); + pop3_state(data, POP3_UPGRADETLS); return result; } @@ -822,19 +985,21 @@ static CURLcode pop3_state_auth_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); saslprogress progress; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; result = Curl_sasl_continue(&pop3c->sasl, data, pop3code, &progress); if(!result) switch(progress) { case SASL_DONE: - state(data, POP3_STOP); /* Authenticated */ + pop3_state(data, POP3_STOP); /* Authenticated */ break; case SASL_IDLE: /* No mechanism left after cancellation */ -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP) /* Perform APOP authentication */ result = pop3_perform_apop(data, conn); @@ -855,7 +1020,7 @@ static CURLcode pop3_state_auth_resp(struct Curl_easy *data, return result; } -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH /* For APOP responses */ static CURLcode pop3_state_apop_resp(struct Curl_easy *data, int pop3code, pop3state instate) @@ -869,7 +1034,7 @@ static CURLcode pop3_state_apop_resp(struct Curl_easy *data, int pop3code, } else /* End of connect phase */ - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } @@ -881,18 +1046,22 @@ static CURLcode pop3_state_user_resp(struct Curl_easy *data, int pop3code, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; + if(pop3code != '+') { failf(data, "Access denied. %c", pop3code); result = CURLE_LOGIN_DENIED; } else /* Send the PASS command */ - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "PASS %s", + result = Curl_pp_sendf(data, &pop3c->pp, "PASS %s", conn->passwd ? conn->passwd : ""); if(!result) - state(data, POP3_PASS); + pop3_state(data, POP3_PASS); return result; } @@ -910,7 +1079,7 @@ static CURLcode pop3_state_pass_resp(struct Curl_easy *data, int pop3code, } else /* End of connect phase */ - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } @@ -922,14 +1091,17 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct POP3 *pop3 = data->req.p.pop3; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + struct pingpong *pp; (void)instate; /* no use for this yet */ + if(!pop3 || !pop3c) + return CURLE_FAILED_INIT; + pp = &pop3c->pp; if(pop3code != '+') { - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); return CURLE_WEIRD_SERVER_REPLY; } @@ -940,34 +1112,39 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, pop3c->eob = 2; /* But since this initial CR LF pair is not part of the actual body, we set - the strip counter here so that these bytes won't be delivered. */ + the strip counter here so that these bytes will not be delivered. */ pop3c->strip = 2; if(pop3->transfer == PPTRANSFER_BODY) { /* POP3 download */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); - if(pp->cache) { - /* The header "cache" contains a bunch of data that is actually body - content so send it as such. Note that there may even be additional - "headers" after the body */ + if(pp->overflow) { + /* The recv buffer contains data that is actually body content so send + it as such. Note that there may even be additional "headers" after + the body */ + + /* keep only the overflow */ + curlx_dyn_tail(&pp->recvbuf, pp->overflow); + pp->nfinal = 0; /* done */ if(!data->req.no_body) { - result = Curl_pop3_write(data, pp->cache, pp->cache_size); + result = pop3_write(data, curlx_dyn_ptr(&pp->recvbuf), + curlx_dyn_len(&pp->recvbuf), FALSE); if(result) return result; } - /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + /* reset the buffer */ + curlx_dyn_reset(&pp->recvbuf); + pp->overflow = 0; } } + else + pp->overflow = 0; /* End of DO phase */ - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } @@ -975,17 +1152,24 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, static CURLcode pop3_statemachine(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int pop3code; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; + struct pingpong *pp; size_t nread = 0; (void)data; + if(!pop3c) + return CURLE_FAILED_INIT; + + pp = &pop3c->pp; /* Busy upgrading the connection; right now all I/O is SSL/TLS, not POP3 */ - if(pop3c->state == POP3_UPGRADETLS) - return pop3_perform_upgrade_tls(data, conn); +upgrade_tls: + if(pop3c->state == POP3_UPGRADETLS) { + result = pop3_perform_upgrade_tls(data, conn); + if(result || (pop3c->state == POP3_UPGRADETLS)) + return result; + } /* Flush any data that needs to be sent */ if(pp->sendleft) @@ -993,7 +1177,7 @@ static CURLcode pop3_statemachine(struct Curl_easy *data, do { /* Read the response from the server */ - result = Curl_pp_readresp(data, sock, pp, &pop3code, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &pop3code, &nread); if(result) return result; @@ -1012,13 +1196,17 @@ static CURLcode pop3_statemachine(struct Curl_easy *data, case POP3_STARTTLS: result = pop3_state_starttls_resp(data, conn, pop3code, pop3c->state); + /* During UPGRADETLS, leave the read loop as we need to connect + * (e.g. TLS handshake) before we continue sending/receiving. */ + if(!result && (pop3c->state == POP3_UPGRADETLS)) + goto upgrade_tls; break; case POP3_AUTH: result = pop3_state_auth_resp(data, pop3code, pop3c->state); break; -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH case POP3_APOP: result = pop3_state_apop_resp(data, pop3code, pop3c->state); break; @@ -1037,12 +1225,12 @@ static CURLcode pop3_statemachine(struct Curl_easy *data, break; case POP3_QUIT: - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); break; default: /* internal error */ - state(data, POP3_STOP); + pop3_state(data, POP3_STOP); break; } } while(!result && pop3c->state != POP3_STOP && Curl_pp_moredata(pp)); @@ -1055,18 +1243,12 @@ static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; - - if((conn->handler->flags & PROTOPT_SSL) && !pop3c->ssldone) { - bool ssldone = FALSE; - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); - pop3c->ssldone = ssldone; - if(result || !pop3c->ssldone) - return result; - } + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + if(!pop3c) + return CURLE_FAILED_INIT; result = Curl_pp_statemach(data, &pop3c->pp, FALSE, FALSE); - *done = (pop3c->state == POP3_STOP) ? TRUE : FALSE; + *done = (pop3c->state == POP3_STOP); return result; } @@ -1076,7 +1258,10 @@ static CURLcode pop3_block_statemach(struct Curl_easy *data, bool disconnecting) { CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + + if(!pop3c) + return CURLE_FAILED_INIT; while(pop3c->state != POP3_STOP && !result) result = Curl_pp_statemach(data, &pop3c->pp, TRUE, disconnecting); @@ -1084,25 +1269,14 @@ static CURLcode pop3_block_statemach(struct Curl_easy *data, return result; } -/* Allocate and initialize the POP3 struct for the current Curl_easy if - required */ -static CURLcode pop3_init(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct POP3 *pop3; - - pop3 = data->req.p.pop3 = calloc(sizeof(struct POP3), 1); - if(!pop3) - result = CURLE_OUT_OF_MEMORY; - - return result; -} - /* For the POP3 "protocol connect" and "doing" phases only */ static int pop3_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.pop3c.pp, socks); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + if(pop3c) + return Curl_pp_getsock(data, &pop3c->pp, socks); + return GETSOCK_BLANK; } /*********************************************************************** @@ -1119,10 +1293,12 @@ static CURLcode pop3_connect(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + struct pingpong *pp = pop3c ? &pop3c->pp : NULL; *done = FALSE; /* default to not done yet */ + if(!pop3c) + return CURLE_FAILED_INIT; /* We always support persistent connections in POP3 */ connkeep(conn, "POP3 default"); @@ -1134,8 +1310,7 @@ static CURLcode pop3_connect(struct Curl_easy *data, bool *done) Curl_sasl_init(&pop3c->sasl, data, &saslpop3); /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(pp); /* Parse the URL options */ result = pop3_parse_url_options(conn); @@ -1143,7 +1318,7 @@ static CURLcode pop3_connect(struct Curl_easy *data, bool *done) return result; /* Start off waiting for the server greeting response */ - state(data, POP3_SERVERGREET); + pop3_state(data, POP3_SERVERGREET); result = pop3_multi_statemach(data, done); @@ -1163,7 +1338,7 @@ static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, bool premature) { CURLcode result = CURLE_OK; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); (void)premature; @@ -1197,7 +1372,10 @@ static CURLcode pop3_perform(struct Curl_easy *data, bool *connected, { /* This is POP3 and no proxy */ CURLcode result = CURLE_OK; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + + if(!pop3) + return CURLE_FAILED_INIT; DEBUGF(infof(data, "DO phase starts")); @@ -1262,9 +1440,12 @@ static CURLcode pop3_do(struct Curl_easy *data, bool *done) static CURLcode pop3_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); (void)data; + if(!pop3c) + return CURLE_FAILED_INIT; + /* We cannot send quit unconditionally. If this connection is stale or bad in any way, sending quit and waiting around here will make the disconnect wait in vain and cause more problems than we need to. */ @@ -1345,16 +1526,42 @@ static CURLcode pop3_regular_transfer(struct Curl_easy *data, return result; } +static void pop3_easy_dtor(void *key, size_t klen, void *entry) +{ + struct POP3 *pop3 = entry; + (void)key; + (void)klen; + DEBUGASSERT(pop3); + /* Cleanup our per-request based variables */ + Curl_safefree(pop3->id); + Curl_safefree(pop3->custom); + free(pop3); +} + +static void pop3_conn_dtor(void *key, size_t klen, void *entry) +{ + struct pop3_conn *pop3c = entry; + (void)key; + (void)klen; + DEBUGASSERT(pop3c); + Curl_pp_disconnect(&pop3c->pp); + Curl_safefree(pop3c->apoptimestamp); + free(pop3c); +} + static CURLcode pop3_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - /* Initialise the POP3 layer */ - CURLcode result = pop3_init(data); - if(result) - return result; + struct pop3_conn *pop3c; + struct POP3 *pop3 = calloc(1, sizeof(*pop3)); + if(!pop3 || + Curl_meta_set(data, CURL_META_POP3_EASY, pop3, pop3_easy_dtor)) + return CURLE_OUT_OF_MEMORY; - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; + pop3c = calloc(1, sizeof(*pop3c)); + if(!pop3c || + Curl_conn_meta_set(conn, CURL_META_POP3_CONN, pop3c, pop3_conn_dtor)) + return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -1367,10 +1574,13 @@ static CURLcode pop3_setup_connection(struct Curl_easy *data, */ static CURLcode pop3_parse_url_options(struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; const char *ptr = conn->options; + if(!pop3c) + return CURLE_FAILED_INIT; + while(!result && ptr && *ptr) { const char *key = ptr; const char *value; @@ -1425,9 +1635,11 @@ static CURLcode pop3_parse_url_options(struct connectdata *conn) static CURLcode pop3_parse_url_path(struct Curl_easy *data) { /* The POP3 struct is already initialised in pop3_connect() */ - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); const char *path = &data->state.up.path[1]; /* skip leading path */ + if(!pop3) + return CURLE_FAILED_INIT; /* URL decode the path for the message ID */ return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL); } @@ -1441,9 +1653,11 @@ static CURLcode pop3_parse_url_path(struct Curl_easy *data) static CURLcode pop3_parse_custom_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + if(!pop3) + return CURLE_FAILED_INIT; /* URL decode the custom request */ if(custom) result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL); @@ -1453,21 +1667,26 @@ static CURLcode pop3_parse_custom_request(struct Curl_easy *data) /*********************************************************************** * - * Curl_pop3_write() + * pop3_write() * * This function scans the body after the end-of-body and writes everything * until the end is found. */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) +static CURLcode pop3_write(struct Curl_easy *data, const char *str, + size_t nread, bool is_eos) { /* This code could be made into a special function in the handler struct */ CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); bool strip_dot = FALSE; size_t last = 0; size_t i; + (void)is_eos; + + if(!pop3c) + return CURLE_FAILED_INIT; /* Search through the buffer looking for the end-of-body marker which is 5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches @@ -1483,7 +1702,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) pop3c->eob++; if(i) { - /* Write out the body part that didn't match */ + /* Write out the body part that did not match */ result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], i - last); @@ -1496,7 +1715,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) else if(pop3c->eob == 3) pop3c->eob++; else - /* If the character match wasn't at position 0 or 3 then restart the + /* If the character match was not at position 0 or 3 then restart the pattern matching */ pop3c->eob = 1; break; @@ -1505,7 +1724,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) if(pop3c->eob == 1 || pop3c->eob == 4) pop3c->eob++; else - /* If the character match wasn't at position 1 or 4 then start the + /* If the character match was not at position 1 or 4 then start the search again */ pop3c->eob = 0; break; @@ -1519,7 +1738,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) pop3c->eob = 0; } else - /* If the character match wasn't at position 2 then start the search + /* If the character match was not at position 2 then start the search again */ pop3c->eob = 0; break; @@ -1543,11 +1762,11 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) /* If the partial match was the CRLF and dot then only write the CRLF as the server would have inserted the dot */ if(strip_dot && prev - 1 > 0) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, prev - 1); } else if(!strip_dot) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, prev); } else { @@ -1567,7 +1786,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) /* We have a full match so the transfer is done, however we must transfer the CRLF at the start of the EOB as this is considered to be part of the message as per RFC-1939, sect. 3 */ - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, 2); + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, 2); k->keepon &= ~KEEP_RECV; pop3c->eob = 0; diff --git a/Utilities/cmcurl/lib/pop3.h b/Utilities/cmcurl/lib/pop3.h index 83f0f831e6c..485e7c2c493 100644 --- a/Utilities/cmcurl/lib/pop3.h +++ b/Utilities/cmcurl/lib/pop3.h @@ -24,74 +24,7 @@ * ***************************************************************************/ -#include "pingpong.h" -#include "curl_sasl.h" - -/**************************************************************************** - * POP3 unique setup - ***************************************************************************/ -typedef enum { - POP3_STOP, /* do nothing state, stops the state machine */ - POP3_SERVERGREET, /* waiting for the initial greeting immediately after - a connect */ - POP3_CAPA, - POP3_STARTTLS, - POP3_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS - (multi mode only) */ - POP3_AUTH, - POP3_APOP, - POP3_USER, - POP3_PASS, - POP3_COMMAND, - POP3_QUIT, - POP3_LAST /* never used */ -} pop3state; - -/* This POP3 struct is used in the Curl_easy. All POP3 data that is - connection-oriented must be in pop3_conn to properly deal with the fact that - perhaps the Curl_easy is changed between the times the connection is - used. */ -struct POP3 { - curl_pp_transfer transfer; - char *id; /* Message ID */ - char *custom; /* Custom Request */ -}; - -/* pop3_conn is used for struct connection-oriented data in the connectdata - struct */ -struct pop3_conn { - struct pingpong pp; - pop3state state; /* Always use pop3.c:state() to change state! */ - size_t eob; /* Number of bytes of the EOB (End Of Body) that - have been received so far */ - size_t strip; /* Number of bytes from the start to ignore as - non-body */ - struct SASL sasl; /* SASL-related storage */ - char *apoptimestamp; /* APOP timestamp from the server greeting */ - unsigned char authtypes; /* Accepted authentication types */ - unsigned char preftype; /* Preferred authentication type */ - BIT(ssldone); /* Is connect() over SSL done? */ - BIT(tls_supported); /* StartTLS capability supported by server */ -}; - extern const struct Curl_handler Curl_handler_pop3; extern const struct Curl_handler Curl_handler_pop3s; -/* Authentication type flags */ -#define POP3_TYPE_CLEARTEXT (1 << 0) -#define POP3_TYPE_APOP (1 << 1) -#define POP3_TYPE_SASL (1 << 2) - -/* Authentication type values */ -#define POP3_TYPE_NONE 0 -#define POP3_TYPE_ANY (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL) - -/* This is the 5-bytes End-Of-Body marker for POP3 */ -#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" -#define POP3_EOB_LEN 5 - -/* This function scans the body after the end-of-body and writes everything - * until the end is found */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread); - #endif /* HEADER_CURL_POP3_H */ diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c index 6092b782c70..8e6d98f0d98 100644 --- a/Utilities/cmcurl/lib/progress.c +++ b/Utilities/cmcurl/lib/progress.c @@ -28,7 +28,7 @@ #include "sendf.h" #include "multiif.h" #include "progress.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "curl_printf.h" /* check rate limits within this many recent milliseconds, at minimum. */ @@ -48,8 +48,7 @@ static void time2str(char *r, curl_off_t seconds) if(h <= CURL_OFF_T_C(99)) { curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); - msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T - ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); + msnprintf(r, 9, "%2" FMT_OFF_T ":%02" FMT_OFF_T ":%02" FMT_OFF_T, h, m, s); } else { /* this equals to more than 99 hours, switch to a more suitable output @@ -57,10 +56,9 @@ static void time2str(char *r, curl_off_t seconds) curl_off_t d = seconds / CURL_OFF_T_C(86400); h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); if(d <= CURL_OFF_T_C(999)) - msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T - "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h); + msnprintf(r, 9, "%3" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h); else - msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d); + msnprintf(r, 9, "%7" FMT_OFF_T "d", d); } } @@ -76,40 +74,40 @@ static char *max5data(curl_off_t bytes, char *max5) #define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) if(bytes < CURL_OFF_T_C(100000)) - msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); + msnprintf(max5, 6, "%5" FMT_OFF_T, bytes); else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "k", bytes/ONE_KILOBYTE); else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) - /* 'XX.XM' is good as long as we're less than 100 megs */ - msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" - CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE, + /* 'XX.XM' is good as long as we are less than 100 megs */ + msnprintf(max5, 6, "%2" FMT_OFF_T ".%0" + FMT_OFF_T "M", bytes/ONE_MEGABYTE, (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) - /* 'XXXXM' is good until we're at 10000MB or above */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); + /* 'XXXXM' is good until we are at 10000MB or above */ + msnprintf(max5, 6, "%4" FMT_OFF_T "M", bytes/ONE_MEGABYTE); else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) /* 10000 MB - 100 GB, we show it as XX.XG */ - msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" - CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE, + msnprintf(max5, 6, "%2" FMT_OFF_T ".%0" + FMT_OFF_T "G", bytes/ONE_GIGABYTE, (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) /* up to 10000GB, display without decimal: XXXXG */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "G", bytes/ONE_GIGABYTE); else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) /* up to 10000TB, display without decimal: XXXXT */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "T", bytes/ONE_TERABYTE); else /* up to 10000PB, display without decimal: XXXXP */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "P", bytes/ONE_PETABYTE); - /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number can + /* 16384 petabytes (16 exabytes) is the maximum a 64-bit unsigned number can hold, but our data type is signed so 8192PB will be the maximum. */ return max5; @@ -138,9 +136,8 @@ int Curl_pgrsDone(struct Curl_easy *data) if(rc) return rc; - if(!(data->progress.flags & PGRS_HIDE) && - !data->progress.callback) - /* only output if we don't use a progress callback and we're not + if(!data->progress.hide && !data->progress.callback) + /* only output if we do not use a progress callback and we are not * hidden */ fprintf(data->set.err, "\n"); @@ -172,11 +169,18 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, case TIMER_STARTOP: /* This is set at the start of a transfer */ data->progress.t_startop = timestamp; + data->progress.t_startqueue = timestamp; + data->progress.t_postqueue = 0; break; case TIMER_STARTSINGLE: - /* This is set at the start of each single fetch */ + /* This is set at the start of each single transfer */ data->progress.t_startsingle = timestamp; - data->progress.is_t_startransfer_set = false; + data->progress.is_t_startransfer_set = FALSE; + break; + case TIMER_POSTQUEUE: + /* Queue time is accumulative from all involved redirects */ + data->progress.t_postqueue += + curlx_timediff_us(timestamp, data->progress.t_startqueue); break; case TIMER_STARTACCEPT: data->progress.t_acceptdata = timestamp; @@ -196,7 +200,7 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, case TIMER_STARTTRANSFER: delta = &data->progress.t_starttransfer; /* prevent updating t_starttransfer unless: - * 1) this is the first time we're setting t_starttransfer + * 1) this is the first time we are setting t_starttransfer * 2) a redirect has occurred since the last time t_starttransfer was set * This prevents repeated invocations of the function from incorrectly * changing the t_starttransfer time. @@ -205,19 +209,20 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, return; } else { - data->progress.is_t_startransfer_set = true; + data->progress.is_t_startransfer_set = TRUE; break; } case TIMER_POSTRANSFER: - /* this is the normal end-of-transfer thing */ + delta = &data->progress.t_posttransfer; break; case TIMER_REDIRECT: - data->progress.t_redirect = Curl_timediff_us(timestamp, + data->progress.t_redirect = curlx_timediff_us(timestamp, data->progress.start); + data->progress.t_startqueue = timestamp; break; } if(delta) { - timediff_t us = Curl_timediff_us(timestamp, data->progress.t_startsingle); + timediff_t us = curlx_timediff_us(timestamp, data->progress.t_startsingle); if(us < 1) us = 1; /* make sure at least one microsecond passed */ *delta += us; @@ -233,7 +238,7 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, */ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) { - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); Curl_pgrsTimeWas(data, timer, now); return now; @@ -241,27 +246,29 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) void Curl_pgrsStartNow(struct Curl_easy *data) { - data->progress.speeder_c = 0; /* reset the progress meter display */ - data->progress.start = Curl_now(); - data->progress.is_t_startransfer_set = false; - data->progress.ul_limit_start = data->progress.start; - data->progress.dl_limit_start = data->progress.start; - data->progress.ul_limit_size = 0; - data->progress.dl_limit_size = 0; - data->progress.downloaded = 0; - data->progress.uploaded = 0; - /* clear all bits except HIDE and HEADERS_OUT */ - data->progress.flags &= PGRS_HIDE|PGRS_HEADERS_OUT; - Curl_ratelimit(data, data->progress.start); + struct Progress *p = &data->progress; + p->speeder_c = 0; /* reset the progress meter display */ + p->start = curlx_now(); + p->is_t_startransfer_set = FALSE; + p->ul.limit.start = p->start; + p->dl.limit.start = p->start; + p->ul.limit.start_size = 0; + p->dl.limit.start_size = 0; + p->dl.cur_size = 0; + p->ul.cur_size = 0; + /* the sizes are unknown at start */ + p->dl_size_known = FALSE; + p->ul_size_known = FALSE; + Curl_ratelimit(data, p->start); } /* * This is used to handle speed limits, calculating how many milliseconds to - * wait until we're back under the speed limit, if needed. + * wait until we are back under the speed limit, if needed. * * The way it works is by having a "starting point" (time & amount of data * transferred by then) used in the speed computation, to be used instead of - * the start of the transfer. This starting point is regularly moved as + * the start of the transfer. This starting point is regularly moved as * transfer goes on, to keep getting accurate values (instead of average over * the entire transfer). * @@ -273,17 +280,15 @@ void Curl_pgrsStartNow(struct Curl_easy *data) * starting point should be reset (to current); or the number of milliseconds * to wait to get back under the speed limit. */ -timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, - curl_off_t startsize, - curl_off_t limit, - struct curltime start, +timediff_t Curl_pgrsLimitWaitTime(struct pgrs_dir *d, + curl_off_t speed_limit, struct curltime now) { - curl_off_t size = cursize - startsize; + curl_off_t size = d->cur_size - d->limit.start_size; timediff_t minimum; timediff_t actual; - if(!limit || !size) + if(!speed_limit || !size) return 0; /* @@ -291,9 +296,9 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, * stay below 'limit'. */ if(size < CURL_OFF_T_MAX/1000) - minimum = (timediff_t) (CURL_OFF_T_C(1000) * size / limit); + minimum = (timediff_t) (CURL_OFF_T_C(1000) * size / speed_limit); else { - minimum = (timediff_t) (size / limit); + minimum = (timediff_t) (size / speed_limit); if(minimum < TIMEDIFF_T_MAX/1000) minimum *= 1000; else @@ -304,11 +309,11 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, * 'actual' is the time in milliseconds it took to actually download the * last 'size' bytes. */ - actual = Curl_timediff(now, start); + actual = curlx_timediff_ceil(now, d->limit.start); if(actual < minimum) { /* if it downloaded the data faster than the limit, make it wait the difference */ - return (minimum - actual); + return minimum - actual; } return 0; @@ -317,9 +322,10 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, /* * Set the number of downloaded bytes so far. */ -void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) +CURLcode Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) { - data->progress.downloaded = size; + data->progress.dl.cur_size = size; + return CURLE_OK; } /* @@ -327,19 +333,19 @@ void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) */ void Curl_ratelimit(struct Curl_easy *data, struct curltime now) { - /* don't set a new stamp unless the time since last update is long enough */ + /* do not set a new stamp unless the time since last update is long enough */ if(data->set.max_recv_speed) { - if(Curl_timediff(now, data->progress.dl_limit_start) >= + if(curlx_timediff(now, data->progress.dl.limit.start) >= MIN_RATE_LIMIT_PERIOD) { - data->progress.dl_limit_start = now; - data->progress.dl_limit_size = data->progress.downloaded; + data->progress.dl.limit.start = now; + data->progress.dl.limit.start_size = data->progress.dl.cur_size; } } if(data->set.max_send_speed) { - if(Curl_timediff(now, data->progress.ul_limit_start) >= + if(curlx_timediff(now, data->progress.ul.limit.start) >= MIN_RATE_LIMIT_PERIOD) { - data->progress.ul_limit_start = now; - data->progress.ul_limit_size = data->progress.uploaded; + data->progress.ul.limit.start = now; + data->progress.ul.limit.start_size = data->progress.ul.cur_size; } } } @@ -349,33 +355,38 @@ void Curl_ratelimit(struct Curl_easy *data, struct curltime now) */ void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size) { - data->progress.uploaded = size; + data->progress.ul.cur_size = size; } void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { - data->progress.size_dl = size; - data->progress.flags |= PGRS_DL_SIZE_KNOWN; + data->progress.dl.total_size = size; + data->progress.dl_size_known = TRUE; } else { - data->progress.size_dl = 0; - data->progress.flags &= ~PGRS_DL_SIZE_KNOWN; + data->progress.dl.total_size = 0; + data->progress.dl_size_known = FALSE; } } void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { - data->progress.size_ul = size; - data->progress.flags |= PGRS_UL_SIZE_KNOWN; + data->progress.ul.total_size = size; + data->progress.ul_size_known = TRUE; } else { - data->progress.size_ul = 0; - data->progress.flags &= ~PGRS_UL_SIZE_KNOWN; + data->progress.ul.total_size = 0; + data->progress.ul_size_known = FALSE; } } +void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent) +{ + data->progress.earlydata_sent = sent; +} + /* returns the average speed in bytes / second */ static curl_off_t trspeed(curl_off_t size, /* number of bytes */ curl_off_t us) /* microseconds */ @@ -390,16 +401,16 @@ static curl_off_t trspeed(curl_off_t size, /* number of bytes */ return CURL_OFF_T_MAX; } -/* returns TRUE if it's time to show the progress meter */ +/* returns TRUE if it is time to show the progress meter */ static bool progress_calc(struct Curl_easy *data, struct curltime now) { bool timetoshow = FALSE; struct Progress * const p = &data->progress; /* The time spent so far (from the start) in microseconds */ - p->timespent = Curl_timediff_us(now, p->start); - p->dlspeed = trspeed(p->downloaded, p->timespent); - p->ulspeed = trspeed(p->uploaded, p->timespent); + p->timespent = curlx_timediff_us(now, p->start); + p->dl.speed = trspeed(p->dl.cur_size, p->timespent); + p->ul.speed = trspeed(p->ul.cur_size, p->timespent); /* Calculations done at most once a second, unless end is reached */ if(p->lastshow != now.tv_sec) { @@ -410,7 +421,7 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) /* Let's do the "current speed" thing, with the dl + ul speeds combined. Store the speed at entry 'nowindex'. */ - p->speeder[ nowindex ] = p->downloaded + p->uploaded; + p->speeder[ nowindex ] = p->dl.cur_size + p->ul.cur_size; /* remember the exact time for this moment */ p->speeder_time [ nowindex ] = now; @@ -422,10 +433,10 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) /* figure out how many index entries of data we have stored in our speeder array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of transfer. Imagine, after one second we have filled in two entries, - after two seconds we've filled in three entries etc. */ - countindex = ((p->speeder_c >= CURR_TIME)? CURR_TIME:p->speeder_c) - 1; + after two seconds we have filled in three entries etc. */ + countindex = ((p->speeder_c >= CURR_TIME) ? CURR_TIME : p->speeder_c) - 1; - /* first of all, we don't do this if there's no counted seconds yet */ + /* first of all, we do not do this if there is no counted seconds yet */ if(countindex) { int checkindex; timediff_t span_ms; @@ -434,10 +445,10 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) /* Get the index position to compare with the 'nowindex' position. Get the oldest entry possible. While we have less than CURR_TIME entries, the first entry will remain the oldest. */ - checkindex = (p->speeder_c >= CURR_TIME)? p->speeder_c%CURR_TIME:0; + checkindex = (p->speeder_c >= CURR_TIME) ? p->speeder_c%CURR_TIME : 0; /* Figure out the exact time for the time span */ - span_ms = Curl_timediff(now, p->speeder_time[checkindex]); + span_ms = curlx_timediff(now, p->speeder_time[checkindex]); if(0 == span_ms) span_ms = 1; /* at least one millisecond MUST have passed */ @@ -456,113 +467,116 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) } else /* the first second we use the average */ - p->current_speed = p->ulspeed + p->dlspeed; + p->current_speed = p->ul.speed + p->dl.speed; } /* Calculations end */ return timetoshow; } #ifndef CURL_DISABLE_PROGRESS_METER + +struct pgrs_estimate { + curl_off_t secs; + curl_off_t percent; +}; + +static curl_off_t pgrs_est_percent(curl_off_t total, curl_off_t cur) +{ + if(total > CURL_OFF_T_C(10000)) + return cur / (total/CURL_OFF_T_C(100)); + else if(total > CURL_OFF_T_C(0)) + return (cur*100) / total; + return 0; +} + +static void pgrs_estimates(struct pgrs_dir *d, + bool total_known, + struct pgrs_estimate *est) +{ + est->secs = 0; + est->percent = 0; + if(total_known && (d->speed > CURL_OFF_T_C(0))) { + est->secs = d->total_size / d->speed; + est->percent = pgrs_est_percent(d->total_size, d->cur_size); + } +} + static void progress_meter(struct Curl_easy *data) { + struct Progress *p = &data->progress; char max5[6][10]; - curl_off_t dlpercen = 0; - curl_off_t ulpercen = 0; - curl_off_t total_percen = 0; - curl_off_t total_transfer; - curl_off_t total_expected_transfer; + struct pgrs_estimate dl_estm; + struct pgrs_estimate ul_estm; + struct pgrs_estimate total_estm; + curl_off_t total_cur_size; + curl_off_t total_expected_size; + curl_off_t dl_size; char time_left[10]; char time_total[10]; char time_spent[10]; - curl_off_t ulestimate = 0; - curl_off_t dlestimate = 0; - curl_off_t total_estimate; - curl_off_t timespent = - (curl_off_t)data->progress.timespent/1000000; /* seconds */ + curl_off_t cur_secs = (curl_off_t)p->timespent/1000000; /* seconds */ - if(!(data->progress.flags & PGRS_HEADERS_OUT)) { + if(!p->headers_out) { if(data->state.resume_from) { fprintf(data->set.err, - "** Resuming transfer from byte position %" - CURL_FORMAT_CURL_OFF_T "\n", data->state.resume_from); + "** Resuming transfer from byte position %" FMT_OFF_T "\n", + data->state.resume_from); } fprintf(data->set.err, " %% Total %% Received %% Xferd Average Speed " "Time Time Time Current\n" " Dload Upload " "Total Spent Left Speed\n"); - data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */ - } - - /* Figure out the estimated time of arrival for the upload */ - if((data->progress.flags & PGRS_UL_SIZE_KNOWN) && - (data->progress.ulspeed > CURL_OFF_T_C(0))) { - ulestimate = data->progress.size_ul / data->progress.ulspeed; - - if(data->progress.size_ul > CURL_OFF_T_C(10000)) - ulpercen = data->progress.uploaded / - (data->progress.size_ul/CURL_OFF_T_C(100)); - else if(data->progress.size_ul > CURL_OFF_T_C(0)) - ulpercen = (data->progress.uploaded*100) / - data->progress.size_ul; + p->headers_out = TRUE; /* headers are shown */ } - /* ... and the download */ - if((data->progress.flags & PGRS_DL_SIZE_KNOWN) && - (data->progress.dlspeed > CURL_OFF_T_C(0))) { - dlestimate = data->progress.size_dl / data->progress.dlspeed; - - if(data->progress.size_dl > CURL_OFF_T_C(10000)) - dlpercen = data->progress.downloaded / - (data->progress.size_dl/CURL_OFF_T_C(100)); - else if(data->progress.size_dl > CURL_OFF_T_C(0)) - dlpercen = (data->progress.downloaded*100) / - data->progress.size_dl; - } - - /* Now figure out which of them is slower and use that one for the - total estimate! */ - total_estimate = ulestimate>dlestimate?ulestimate:dlestimate; + /* Figure out the estimated time of arrival for upload and download */ + pgrs_estimates(&p->ul, (bool)p->ul_size_known, &ul_estm); + pgrs_estimates(&p->dl, (bool)p->dl_size_known, &dl_estm); + /* Since both happen at the same time, total expected duration is max. */ + total_estm.secs = CURLMAX(ul_estm.secs, dl_estm.secs); /* create the three time strings */ - time2str(time_left, total_estimate > 0?(total_estimate - timespent):0); - time2str(time_total, total_estimate); - time2str(time_spent, timespent); + time2str(time_left, total_estm.secs > 0 ? (total_estm.secs - cur_secs) : 0); + time2str(time_total, total_estm.secs); + time2str(time_spent, cur_secs); /* Get the total amount of data expected to get transferred */ - total_expected_transfer = - ((data->progress.flags & PGRS_UL_SIZE_KNOWN)? - data->progress.size_ul:data->progress.uploaded)+ - ((data->progress.flags & PGRS_DL_SIZE_KNOWN)? - data->progress.size_dl:data->progress.downloaded); + total_expected_size = + p->ul_size_known ? p->ul.total_size : p->ul.cur_size; + + dl_size = + p->dl_size_known ? p->dl.total_size : p->dl.cur_size; + + /* integer overflow check */ + if((CURL_OFF_T_MAX - total_expected_size) < dl_size) + total_expected_size = CURL_OFF_T_MAX; /* capped */ + else + total_expected_size += dl_size; /* We have transferred this much so far */ - total_transfer = data->progress.downloaded + data->progress.uploaded; + total_cur_size = p->dl.cur_size + p->ul.cur_size; /* Get the percentage of data transferred so far */ - if(total_expected_transfer > CURL_OFF_T_C(10000)) - total_percen = total_transfer / - (total_expected_transfer/CURL_OFF_T_C(100)); - else if(total_expected_transfer > CURL_OFF_T_C(0)) - total_percen = (total_transfer*100) / total_expected_transfer; + total_estm.percent = pgrs_est_percent(total_expected_size, total_cur_size); fprintf(data->set.err, "\r" - "%3" CURL_FORMAT_CURL_OFF_T " %s " - "%3" CURL_FORMAT_CURL_OFF_T " %s " - "%3" CURL_FORMAT_CURL_OFF_T " %s %s %s %s %s %s %s", - total_percen, /* 3 letters */ /* total % */ - max5data(total_expected_transfer, max5[2]), /* total size */ - dlpercen, /* 3 letters */ /* rcvd % */ - max5data(data->progress.downloaded, max5[0]), /* rcvd size */ - ulpercen, /* 3 letters */ /* xfer % */ - max5data(data->progress.uploaded, max5[1]), /* xfer size */ - max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */ - max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */ + "%3" FMT_OFF_T " %s " + "%3" FMT_OFF_T " %s " + "%3" FMT_OFF_T " %s %s %s %s %s %s %s", + total_estm.percent, /* 3 letters */ /* total % */ + max5data(total_expected_size, max5[2]), /* total size */ + dl_estm.percent, /* 3 letters */ /* rcvd % */ + max5data(p->dl.cur_size, max5[0]), /* rcvd size */ + ul_estm.percent, /* 3 letters */ /* xfer % */ + max5data(p->ul.cur_size, max5[1]), /* xfer size */ + max5data(p->dl.speed, max5[3]), /* avrg dl speed */ + max5data(p->ul.speed, max5[4]), /* avrg ul speed */ time_total, /* 8 letters */ /* total time */ time_spent, /* 8 letters */ /* time spent */ time_left, /* 8 letters */ /* time left */ - max5data(data->progress.current_speed, max5[5]) + max5data(p->current_speed, max5[5]) ); /* we flush the output stream to make it appear as soon as possible */ @@ -578,21 +592,19 @@ static void progress_meter(struct Curl_easy *data) * Curl_pgrsUpdate() returns 0 for success or the value returned by the * progress callback! */ -int Curl_pgrsUpdate(struct Curl_easy *data) +static int pgrsupdate(struct Curl_easy *data, bool showprogress) { - struct curltime now = Curl_now(); /* what time is it */ - bool showprogress = progress_calc(data, now); - if(!(data->progress.flags & PGRS_HIDE)) { + if(!data->progress.hide) { if(data->set.fxferinfo) { int result; - /* There's a callback set, call that */ - Curl_set_in_callback(data, true); + /* There is a callback set, call that */ + Curl_set_in_callback(data, TRUE); result = data->set.fxferinfo(data->set.progress_client, - data->progress.size_dl, - data->progress.downloaded, - data->progress.size_ul, - data->progress.uploaded); - Curl_set_in_callback(data, false); + data->progress.dl.total_size, + data->progress.dl.cur_size, + data->progress.ul.total_size, + data->progress.ul.cur_size); + Curl_set_in_callback(data, FALSE); if(result != CURL_PROGRESSFUNC_CONTINUE) { if(result) failf(data, "Callback aborted"); @@ -602,13 +614,13 @@ int Curl_pgrsUpdate(struct Curl_easy *data) else if(data->set.fprogress) { int result; /* The older deprecated callback is set, call that */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); result = data->set.fprogress(data->set.progress_client, - (double)data->progress.size_dl, - (double)data->progress.downloaded, - (double)data->progress.size_ul, - (double)data->progress.uploaded); - Curl_set_in_callback(data, false); + (double)data->progress.dl.total_size, + (double)data->progress.dl.cur_size, + (double)data->progress.ul.total_size, + (double)data->progress.ul.cur_size); + Curl_set_in_callback(data, FALSE); if(result != CURL_PROGRESSFUNC_CONTINUE) { if(result) failf(data, "Callback aborted"); @@ -622,3 +634,19 @@ int Curl_pgrsUpdate(struct Curl_easy *data) return 0; } + +int Curl_pgrsUpdate(struct Curl_easy *data) +{ + struct curltime now = curlx_now(); /* what time is it */ + bool showprogress = progress_calc(data, now); + return pgrsupdate(data, showprogress); +} + +/* + * Update all progress, do not do progress meter/callbacks. + */ +void Curl_pgrsUpdate_nometer(struct Curl_easy *data) +{ + struct curltime now = curlx_now(); /* what time is it */ + (void)progress_calc(data, now); +} diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h index 0049cd04bee..bbe135cdbcd 100644 --- a/Utilities/cmcurl/lib/progress.h +++ b/Utilities/cmcurl/lib/progress.h @@ -24,13 +24,14 @@ * ***************************************************************************/ -#include "timeval.h" +#include "curlx/timeval.h" typedef enum { TIMER_NONE, TIMER_STARTOP, - TIMER_STARTSINGLE, + TIMER_STARTSINGLE, /* start of transfer, might get queued */ + TIMER_POSTQUEUE, /* start, immediately after dequeue */ TIMER_NAMELOOKUP, TIMER_CONNECT, TIMER_APPCONNECT, @@ -46,16 +47,19 @@ int Curl_pgrsDone(struct Curl_easy *data); void Curl_pgrsStartNow(struct Curl_easy *data); void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size); void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size); -void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size); + +/* It is fine to not check the return code if 'size' is set to 0 */ +CURLcode Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size); + void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size); void Curl_ratelimit(struct Curl_easy *data, struct curltime now); int Curl_pgrsUpdate(struct Curl_easy *data); +void Curl_pgrsUpdate_nometer(struct Curl_easy *data); + void Curl_pgrsResetTransferSizes(struct Curl_easy *data); struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer); -timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, - curl_off_t startsize, - curl_off_t limit, - struct curltime start, +timediff_t Curl_pgrsLimitWaitTime(struct pgrs_dir *d, + curl_off_t speed_limit, struct curltime now); /** * Update progress timer with the elapsed time from its start to `timestamp`. @@ -65,9 +69,6 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, struct curltime timestamp); -#define PGRS_HIDE (1<<4) -#define PGRS_UL_SIZE_KNOWN (1<<5) -#define PGRS_DL_SIZE_KNOWN (1<<6) -#define PGRS_HEADERS_OUT (1<<7) /* set when the headers have been written */ +void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent); #endif /* HEADER_CURL_PROGRESS_H */ diff --git a/Utilities/cmcurl/lib/psl.c b/Utilities/cmcurl/lib/psl.c index 626a203a6d7..a488a46e931 100644 --- a/Utilities/cmcurl/lib/psl.c +++ b/Utilities/cmcurl/lib/psl.c @@ -40,7 +40,7 @@ void Curl_psl_destroy(struct PslCache *pslcache) { if(pslcache->psl) { if(pslcache->dynamic) - psl_free((psl_ctx_t *) pslcache->psl); + psl_free((psl_ctx_t *)CURL_UNCONST(pslcache->psl)); pslcache->psl = NULL; pslcache->dynamic = FALSE; } @@ -48,7 +48,7 @@ void Curl_psl_destroy(struct PslCache *pslcache) static time_t now_seconds(void) { - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); return now.tv_sec; } @@ -81,7 +81,7 @@ const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy) psl = psl_latest(NULL); dynamic = psl != NULL; /* Take care of possible time computation overflow. */ - expires = now < TIME_T_MAX - PSL_TTL? now + PSL_TTL: TIME_T_MAX; + expires = now < TIME_T_MAX - PSL_TTL ? now + PSL_TTL : TIME_T_MAX; /* Only get the built-in PSL if we do not already have the "latest". */ if(!psl && !pslcache->dynamic) diff --git a/Utilities/cmcurl/lib/psl.h b/Utilities/cmcurl/lib/psl.h index 23cfa921c4b..dc11469a524 100644 --- a/Utilities/cmcurl/lib/psl.h +++ b/Utilities/cmcurl/lib/psl.h @@ -27,12 +27,14 @@ #ifdef USE_LIBPSL #include +struct Curl_easy; + #define PSL_TTL (72 * 3600) /* PSL time to live before a refresh. */ struct PslCache { const psl_ctx_t *psl; /* The PSL. */ time_t expires; /* Time this PSL life expires. */ - bool dynamic; /* PSL should be released when no longer needed. */ + BIT(dynamic); /* PSL should be released when no longer needed. */ }; const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy); diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c index 7d24765ccd5..c0368dd7635 100644 --- a/Utilities/cmcurl/lib/rand.c +++ b/Utilities/cmcurl/lib/rand.c @@ -24,36 +24,32 @@ #include "curl_setup.h" +#include + #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_ARC4RANDOM -/* Some platforms might have the prototype missing (ubuntu + libressl) */ -uint32_t arc4random(void); -#endif #include +#include "urldata.h" #include "vtls/vtls.h" #include "sendf.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "rand.h" +#include "escape.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#ifdef WIN32 - -#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) -# define HAVE_MINGW_ORIGINAL -#endif +#ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 && \ - !defined(HAVE_MINGW_ORIGINAL) + !defined(CURL_WINDOWS_UWP) # define HAVE_WIN_BCRYPTGENRANDOM # include # ifdef _MSC_VER @@ -104,111 +100,126 @@ CURLcode Curl_win32_random(unsigned char *entropy, size_t length) } #endif -static CURLcode randit(struct Curl_easy *data, unsigned int *rnd) +#if !defined(USE_SSL) +/* ---- possibly non-cryptographic version following ---- */ +static CURLcode weak_random(struct Curl_easy *data, + unsigned char *entropy, + size_t length) /* always 4, size of int */ { unsigned int r; - CURLcode result = CURLE_OK; - static unsigned int randseed; - static bool seeded = FALSE; + DEBUGASSERT(length == sizeof(int)); -#ifdef CURLDEBUG - char *force_entropy = getenv("CURL_ENTROPY"); - if(force_entropy) { - if(!seeded) { - unsigned int seed = 0; - size_t elen = strlen(force_entropy); - size_t clen = sizeof(seed); - size_t min = elen < clen ? elen : clen; - memcpy((char *)&seed, force_entropy, min); - randseed = ntohl(seed); - seeded = TRUE; - } - else - randseed++; - *rnd = randseed; - return CURLE_OK; + /* Trying cryptographically secure functions first */ +#ifdef _WIN32 + (void)data; + { + CURLcode result = Curl_win32_random(entropy, length); + if(result != CURLE_NOT_BUILT_IN) + return result; } #endif - /* data may be NULL! */ - result = Curl_ssl_random(data, (unsigned char *)rnd, sizeof(*rnd)); - if(result != CURLE_NOT_BUILT_IN) - /* only if there is no random function in the TLS backend do the non crypto - version, otherwise return result */ - return result; - - /* ---- non-cryptographic version following ---- */ +#if defined(HAVE_ARC4RANDOM) + (void)data; + r = (unsigned int)arc4random(); + memcpy(entropy, &r, length); +#else + infof(data, "WARNING: using weak random seed"); + { + static unsigned int randseed; + static bool seeded = FALSE; + unsigned int rnd; + if(!seeded) { + struct curltime now = curlx_now(); + randseed += (unsigned int)now.tv_usec + (unsigned int)now.tv_sec; + randseed = randseed * 1103515245 + 12345; + randseed = randseed * 1103515245 + 12345; + randseed = randseed * 1103515245 + 12345; + seeded = TRUE; + } -#ifdef WIN32 - if(!seeded) { - result = Curl_win32_random((unsigned char *)rnd, sizeof(*rnd)); - if(result != CURLE_NOT_BUILT_IN) - return result; + /* Return an unsigned 32-bit pseudo-random number. */ + r = randseed = randseed * 1103515245 + 12345; + rnd = (r << 16) | ((r >> 16) & 0xFFFF); + memcpy(entropy, &rnd, length); } #endif - -#ifdef HAVE_ARC4RANDOM - *rnd = (unsigned int)arc4random(); return CURLE_OK; +} #endif -#if defined(RANDOM_FILE) && !defined(WIN32) - if(!seeded) { - /* if there's a random file to read a seed from, use it */ - int fd = open(RANDOM_FILE, O_RDONLY); - if(fd > -1) { - /* read random data into the randseed variable */ - ssize_t nread = read(fd, &randseed, sizeof(randseed)); - if(nread == sizeof(randseed)) +#ifdef USE_SSL +#define _random(x,y,z) Curl_ssl_random(x,y,z) +#else +#define _random(x,y,z) weak_random(x,y,z) +#endif + +static CURLcode randit(struct Curl_easy *data, unsigned int *rnd, + bool env_override) +{ +#ifdef DEBUGBUILD + if(env_override) { + char *force_entropy = getenv("CURL_ENTROPY"); + if(force_entropy) { + static unsigned int randseed; + static bool seeded = FALSE; + + if(!seeded) { + unsigned int seed = 0; + size_t elen = strlen(force_entropy); + size_t clen = sizeof(seed); + size_t min = elen < clen ? elen : clen; + memcpy((char *)&seed, force_entropy, min); + randseed = ntohl(seed); seeded = TRUE; - close(fd); + } + else + randseed++; + *rnd = randseed; + return CURLE_OK; } } +#else + (void)env_override; #endif - if(!seeded) { - struct curltime now = Curl_now(); - infof(data, "WARNING: using weak random seed"); - randseed += (unsigned int)now.tv_usec + (unsigned int)now.tv_sec; - randseed = randseed * 1103515245 + 12345; - randseed = randseed * 1103515245 + 12345; - randseed = randseed * 1103515245 + 12345; - seeded = TRUE; - } - - /* Return an unsigned 32-bit pseudo-random number. */ - r = randseed = randseed * 1103515245 + 12345; - *rnd = (r << 16) | ((r >> 16) & 0xFFFF); - return CURLE_OK; + /* data may be NULL! */ + return _random(data, (unsigned char *)rnd, sizeof(*rnd)); } /* * Curl_rand() stores 'num' number of random unsigned characters in the buffer * 'rnd' points to. * - * If libcurl is built without TLS support or with a TLS backend that lacks a - * proper random API (rustls, Gskit or mbedTLS), this function will use "weak" - * random. + * If libcurl is built without TLS support or arc4random, this function will + * use "weak" random. * - * When built *with* TLS support and a backend that offers strong random, it - * will return error if it cannot provide strong random values. + * When built *with* TLS support, it will return error if it cannot provide + * strong random values. * * NOTE: 'data' may be passed in as NULL when coming from external API without * easy handle! * */ -CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num) +CURLcode Curl_rand_bytes(struct Curl_easy *data, +#ifdef DEBUGBUILD + bool env_override, +#endif + unsigned char *rnd, size_t num) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; +#ifndef DEBUGBUILD + const bool env_override = FALSE; +#endif - DEBUGASSERT(num > 0); + DEBUGASSERT(num); while(num) { unsigned int r; size_t left = num < sizeof(unsigned int) ? num : sizeof(unsigned int); - result = randit(data, &r); + result = randit(data, &r, env_override); if(result) return result; @@ -233,9 +244,7 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, size_t num) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; - const char *hex = "0123456789abcdef"; unsigned char buffer[128]; - unsigned char *bufp = buffer; DEBUGASSERT(num > 1); #ifdef __clang_analyzer__ @@ -244,9 +253,11 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, memset(buffer, 0, sizeof(buffer)); #endif - if((num/2 >= sizeof(buffer)) || !(num&1)) + if((num/2 >= sizeof(buffer)) || !(num&1)) { /* make sure it fits in the local buffer and that it is an odd number! */ + DEBUGF(infof(data, "invalid buffer size with Curl_rand_hex")); return CURLE_BAD_FUNCTION_ARGUMENT; + } num--; /* save one for null-termination */ @@ -254,13 +265,37 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, if(result) return result; + Curl_hexencode(buffer, num/2, rnd, num + 1); + return result; +} + +/* + * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random + * alphanumerical chars PLUS a null-terminating byte. + */ + +static const char alnum[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +CURLcode Curl_rand_alnum(struct Curl_easy *data, unsigned char *rnd, + size_t num) +{ + CURLcode result = CURLE_OK; + const unsigned int alnumspace = sizeof(alnum) - 1; + unsigned int r; + DEBUGASSERT(num > 1); + + num--; /* save one for null-termination */ + while(num) { - /* clang-tidy warns on this line without this comment: */ - /* NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) */ - *rnd++ = hex[(*bufp & 0xF0)>>4]; - *rnd++ = hex[*bufp & 0x0F]; - bufp++; - num -= 2; + do { + result = randit(data, &r, TRUE); + if(result) + return result; + } while(r >= (UINT_MAX - UINT_MAX % alnumspace)); + + *rnd++ = (unsigned char)alnum[r % alnumspace]; + num--; } *rnd = 0; diff --git a/Utilities/cmcurl/lib/rand.h b/Utilities/cmcurl/lib/rand.h index cbe05677a16..2ba60e72976 100644 --- a/Utilities/cmcurl/lib/rand.h +++ b/Utilities/cmcurl/lib/rand.h @@ -24,21 +24,17 @@ * ***************************************************************************/ -/* - * Curl_rand() stores 'num' number of random unsigned characters in the buffer - * 'rnd' points to. - * - * If libcurl is built without TLS support or with a TLS backend that lacks a - * proper random API (Gskit or mbedTLS), this function will use "weak" random. - * - * When built *with* TLS support and a backend that offers strong random, it - * will return error if it cannot provide strong random values. - * - * NOTE: 'data' may be passed in as NULL when coming from external API without - * easy handle! - * - */ -CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num); +CURLcode Curl_rand_bytes(struct Curl_easy *data, +#ifdef DEBUGBUILD + bool allow_env_override, +#endif + unsigned char *rnd, size_t num); + +#ifdef DEBUGBUILD +#define Curl_rand(a,b,c) Curl_rand_bytes((a), TRUE, (b), (c)) +#else +#define Curl_rand(a,b,c) Curl_rand_bytes((a), (b), (c)) +#endif /* * Curl_rand_hex() fills the 'rnd' buffer with a given 'num' size with random @@ -48,7 +44,14 @@ CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num); CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, size_t num); -#ifdef WIN32 +/* + * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random + * alphanumerical chars PLUS a null-terminating byte. + */ +CURLcode Curl_rand_alnum(struct Curl_easy *data, unsigned char *rnd, + size_t num); + +#ifdef _WIN32 /* Random generator shared between the Schannel vtls and Curl_rand*() functions */ CURLcode Curl_win32_random(unsigned char *entropy, size_t length); diff --git a/Utilities/cmcurl/lib/rename.c b/Utilities/cmcurl/lib/rename.c index 97a66e947e8..d3a46e0e6a7 100644 --- a/Utilities/cmcurl/lib/rename.c +++ b/Utilities/cmcurl/lib/rename.c @@ -29,8 +29,8 @@ #if (!defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES)) || \ !defined(CURL_DISABLE_ALTSVC) -#include "curl_multibyte.h" -#include "timeval.h" +#include "curlx/multibyte.h" +#include "curlx/timeval.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -40,14 +40,14 @@ /* return 0 on success, 1 on error */ int Curl_rename(const char *oldpath, const char *newpath) { -#ifdef WIN32 - /* rename() on Windows doesn't overwrite, so we can't use it here. +#if defined(_WIN32) && !defined(UNDER_CE) + /* rename() on Windows does not overwrite, so we cannot use it here. MoveFileEx() will overwrite and is usually atomic, however it fails when there are open handles to the file. */ const int max_wait_ms = 1000; - struct curltime start = Curl_now(); - TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar((char *)oldpath); - TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar((char *)newpath); + struct curltime start = curlx_now(); + TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar(oldpath); + TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar(newpath); for(;;) { timediff_t diff; if(MoveFileEx(tchar_oldpath, tchar_newpath, MOVEFILE_REPLACE_EXISTING)) { @@ -55,7 +55,7 @@ int Curl_rename(const char *oldpath, const char *newpath) curlx_unicodefree(tchar_newpath); break; } - diff = Curl_timediff(Curl_now(), start); + diff = curlx_timediff(curlx_now(), start); if(diff < 0 || diff > max_wait_ms) { curlx_unicodefree(tchar_oldpath); curlx_unicodefree(tchar_newpath); diff --git a/Utilities/cmcurl/lib/request.c b/Utilities/cmcurl/lib/request.c new file mode 100644 index 00000000000..f937a7f4bfe --- /dev/null +++ b/Utilities/cmcurl/lib/request.c @@ -0,0 +1,472 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "urldata.h" +#include "cfilters.h" +#include "curlx/dynbuf.h" +#include "doh.h" +#include "multiif.h" +#include "progress.h" +#include "request.h" +#include "sendf.h" +#include "transfer.h" +#include "url.h" +#include "curlx/strparse.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +void Curl_req_init(struct SingleRequest *req) +{ + memset(req, 0, sizeof(*req)); +} + +CURLcode Curl_req_soft_reset(struct SingleRequest *req, + struct Curl_easy *data) +{ + CURLcode result; + + req->done = FALSE; + req->upload_done = FALSE; + req->upload_aborted = FALSE; + req->download_done = FALSE; + req->eos_written = FALSE; + req->eos_read = FALSE; + req->eos_sent = FALSE; + req->ignorebody = FALSE; + req->shutdown = FALSE; + req->bytecount = 0; + req->writebytecount = 0; + req->header = TRUE; /* assume header */ + req->headerline = 0; + req->headerbytecount = 0; + req->allheadercount = 0; + req->deductheadercount = 0; + req->httpversion_sent = 0; + req->httpversion = 0; + req->sendbuf_hds_len = 0; + + result = Curl_client_start(data); + if(result) + return result; + + if(!req->sendbuf_init) { + Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1, + BUFQ_OPT_SOFT_LIMIT); + req->sendbuf_init = TRUE; + } + else { + Curl_bufq_reset(&req->sendbuf); + if(data->set.upload_buffer_size != req->sendbuf.chunk_size) { + Curl_bufq_free(&req->sendbuf); + Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1, + BUFQ_OPT_SOFT_LIMIT); + } + } + + return CURLE_OK; +} + +CURLcode Curl_req_start(struct SingleRequest *req, + struct Curl_easy *data) +{ + req->start = curlx_now(); + return Curl_req_soft_reset(req, data); +} + +static CURLcode req_flush(struct Curl_easy *data); + +CURLcode Curl_req_done(struct SingleRequest *req, + struct Curl_easy *data, bool aborted) +{ + (void)req; + if(!aborted) + (void)req_flush(data); + Curl_client_reset(data); +#ifndef CURL_DISABLE_DOH + Curl_doh_close(data); +#endif + return CURLE_OK; +} + +void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) +{ + struct curltime t0 = {0, 0}; + + Curl_safefree(req->newurl); + Curl_client_reset(data); + if(req->sendbuf_init) + Curl_bufq_reset(&req->sendbuf); + +#ifndef CURL_DISABLE_DOH + Curl_doh_close(data); +#endif + /* Can no longer memset() this struct as we need to keep some state */ + req->size = -1; + req->maxdownload = -1; + req->bytecount = 0; + req->writebytecount = 0; + req->start = t0; + req->headerbytecount = 0; + req->allheadercount = 0; + req->deductheadercount = 0; + req->headerline = 0; + req->offset = 0; + req->httpcode = 0; + req->keepon = 0; + req->upgr101 = UPGR101_INIT; + req->sendbuf_hds_len = 0; + req->timeofdoc = 0; + req->location = NULL; + req->newurl = NULL; +#ifndef CURL_DISABLE_COOKIES + req->setcookies = 0; +#endif + req->header = FALSE; + req->content_range = FALSE; + req->download_done = FALSE; + req->eos_written = FALSE; + req->eos_read = FALSE; + req->eos_sent = FALSE; + req->upload_done = FALSE; + req->upload_aborted = FALSE; + req->ignorebody = FALSE; + req->http_bodyless = FALSE; + req->chunk = FALSE; + req->ignore_cl = FALSE; + req->upload_chunky = FALSE; + req->getheader = FALSE; + req->no_body = data->set.opt_no_body; + req->authneg = FALSE; + req->shutdown = FALSE; +} + +void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data) +{ + Curl_safefree(req->newurl); + if(req->sendbuf_init) + Curl_bufq_free(&req->sendbuf); + Curl_client_cleanup(data); +} + +static CURLcode xfer_send(struct Curl_easy *data, + const char *buf, size_t blen, + size_t hds_len, size_t *pnwritten) +{ + CURLcode result = CURLE_OK; + bool eos = FALSE; + + *pnwritten = 0; + DEBUGASSERT(hds_len <= blen); +#ifdef DEBUGBUILD + { + /* Allow debug builds to override this logic to force short initial + sends */ + size_t body_len = blen - hds_len; + if(body_len) { + const char *p = getenv("CURL_SMALLREQSEND"); + if(p) { + curl_off_t body_small; + if(!curlx_str_number(&p, &body_small, body_len)) + blen = hds_len + (size_t)body_small; + } + } + } +#endif + /* Make sure this does not send more body bytes than what the max send + speed says. The headers do not count to the max speed. */ + if(data->set.max_send_speed) { + size_t body_bytes = blen - hds_len; + if((curl_off_t)body_bytes > data->set.max_send_speed) + blen = hds_len + (size_t)data->set.max_send_speed; + } + + if(data->req.eos_read && + (Curl_bufq_is_empty(&data->req.sendbuf) || + Curl_bufq_len(&data->req.sendbuf) == blen)) { + DEBUGF(infof(data, "sending last upload chunk of %zu bytes", blen)); + eos = TRUE; + } + result = Curl_xfer_send(data, buf, blen, eos, pnwritten); + if(!result) { + if(eos && (blen == *pnwritten)) + data->req.eos_sent = TRUE; + if(*pnwritten) { + if(hds_len) + Curl_debug(data, CURLINFO_HEADER_OUT, buf, + CURLMIN(hds_len, *pnwritten)); + if(*pnwritten > hds_len) { + size_t body_len = *pnwritten - hds_len; + Curl_debug(data, CURLINFO_DATA_OUT, buf + hds_len, body_len); + data->req.writebytecount += body_len; + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + } + } + } + return result; +} + +static CURLcode req_send_buffer_flush(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen; + + while(Curl_bufq_peek(&data->req.sendbuf, &buf, &blen)) { + size_t nwritten, hds_len = CURLMIN(data->req.sendbuf_hds_len, blen); + result = xfer_send(data, (const char *)buf, blen, hds_len, &nwritten); + if(result) + break; + + Curl_bufq_skip(&data->req.sendbuf, nwritten); + if(hds_len) { + data->req.sendbuf_hds_len -= CURLMIN(hds_len, nwritten); + } + /* leave if we could not send all. Maybe network blocking or + * speed limits on transfer */ + if(nwritten < blen) + break; + } + return result; +} + +static CURLcode req_set_upload_done(struct Curl_easy *data) +{ + DEBUGASSERT(!data->req.upload_done); + data->req.upload_done = TRUE; + data->req.keepon &= ~(KEEP_SEND|KEEP_SEND_TIMED); /* we are done sending */ + + Curl_pgrsTime(data, TIMER_POSTRANSFER); + Curl_creader_done(data, data->req.upload_aborted); + + if(data->req.upload_aborted) { + Curl_bufq_reset(&data->req.sendbuf); + if(data->req.writebytecount) + infof(data, "abort upload after having sent %" FMT_OFF_T " bytes", + data->req.writebytecount); + else + infof(data, "abort upload"); + } + else if(data->req.writebytecount) + infof(data, "upload completely sent off: %" FMT_OFF_T " bytes", + data->req.writebytecount); + else if(!data->req.download_done) { + DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); + infof(data, Curl_creader_total_length(data) ? + "We are completely uploaded and fine" : + "Request completely sent off"); + } + + return Curl_xfer_send_close(data); +} + +static CURLcode req_flush(struct Curl_easy *data) +{ + CURLcode result; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + + if(!Curl_bufq_is_empty(&data->req.sendbuf)) { + result = req_send_buffer_flush(data); + if(result) + return result; + if(!Curl_bufq_is_empty(&data->req.sendbuf)) { + DEBUGF(infof(data, "Curl_req_flush(len=%zu) -> EAGAIN", + Curl_bufq_len(&data->req.sendbuf))); + return CURLE_AGAIN; + } + } + else if(Curl_xfer_needs_flush(data)) { + DEBUGF(infof(data, "Curl_req_flush(), xfer send_pending")); + return Curl_xfer_flush(data); + } + + if(data->req.eos_read && !data->req.eos_sent) { + char tmp; + size_t nwritten; + result = xfer_send(data, &tmp, 0, 0, &nwritten); + if(result) + return result; + DEBUGASSERT(data->req.eos_sent); + } + + if(!data->req.upload_done && data->req.eos_read && data->req.eos_sent) { + DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); + if(data->req.shutdown) { + bool done; + result = Curl_xfer_send_shutdown(data, &done); + if(result && data->req.shutdown_err_ignore) { + infof(data, "Shutdown send direction error: %d. Broken server? " + "Proceeding as if everything is ok.", result); + result = CURLE_OK; + done = TRUE; + } + + if(result) + return result; + if(!done) + return CURLE_AGAIN; + } + return req_set_upload_done(data); + } + return CURLE_OK; +} + +static ssize_t add_from_client(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_easy *data = reader_ctx; + size_t nread; + bool eos; + + *err = Curl_client_read(data, (char *)buf, buflen, &nread, &eos); + if(*err) + return -1; + if(eos) + data->req.eos_read = TRUE; + return (ssize_t)nread; +} + +static CURLcode req_send_buffer_add(struct Curl_easy *data, + const char *buf, size_t blen, + size_t hds_len) +{ + CURLcode result = CURLE_OK; + ssize_t n; + n = Curl_bufq_write(&data->req.sendbuf, + (const unsigned char *)buf, blen, &result); + if(n < 0) + return result; + /* We rely on a SOFTLIMIT on sendbuf, so it can take all data in */ + DEBUGASSERT((size_t)n == blen); + data->req.sendbuf_hds_len += hds_len; + return CURLE_OK; +} + +CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req, + unsigned char httpversion) +{ + CURLcode result; + const char *buf; + size_t blen, nwritten; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + + data->req.httpversion_sent = httpversion; + buf = curlx_dyn_ptr(req); + blen = curlx_dyn_len(req); + if(!Curl_creader_total_length(data)) { + /* Request without body. Try to send directly from the buf given. */ + data->req.eos_read = TRUE; + result = xfer_send(data, buf, blen, blen, &nwritten); + if(result) + return result; + buf += nwritten; + blen -= nwritten; + } + + if(blen) { + /* Either we have a request body, or we could not send the complete + * request in one go. Buffer the remainder and try to add as much + * body bytes as room is left in the buffer. Then flush. */ + result = req_send_buffer_add(data, buf, blen, blen); + if(result) + return result; + + return Curl_req_send_more(data); + } + return CURLE_OK; +} + +bool Curl_req_sendbuf_empty(struct Curl_easy *data) +{ + return !data->req.sendbuf_init || Curl_bufq_is_empty(&data->req.sendbuf); +} + +bool Curl_req_want_send(struct Curl_easy *data) +{ + /* Not done and + * - KEEP_SEND and not PAUSEd. + * - or request has buffered data to send + * - or transfer connection has pending data to send */ + return !data->req.done && + (((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) || + !Curl_req_sendbuf_empty(data) || + Curl_xfer_needs_flush(data)); +} + +bool Curl_req_done_sending(struct Curl_easy *data) +{ + return data->req.upload_done && !Curl_req_want_send(data); +} + +CURLcode Curl_req_send_more(struct Curl_easy *data) +{ + CURLcode result; + + /* Fill our send buffer if more from client can be read. */ + if(!data->req.upload_aborted && + !data->req.eos_read && + !(data->req.keepon & KEEP_SEND_PAUSE) && + !Curl_bufq_is_full(&data->req.sendbuf)) { + ssize_t nread = Curl_bufq_sipn(&data->req.sendbuf, 0, + add_from_client, data, &result); + if(nread < 0 && result != CURLE_AGAIN) + return result; + } + + result = req_flush(data); + if(result == CURLE_AGAIN) + result = CURLE_OK; + + return result; +} + +CURLcode Curl_req_abort_sending(struct Curl_easy *data) +{ + if(!data->req.upload_done) { + Curl_bufq_reset(&data->req.sendbuf); + data->req.upload_aborted = TRUE; + /* no longer KEEP_SEND and KEEP_SEND_PAUSE */ + data->req.keepon &= ~KEEP_SENDBITS; + return req_set_upload_done(data); + } + return CURLE_OK; +} + +CURLcode Curl_req_stop_send_recv(struct Curl_easy *data) +{ + /* stop receiving and ALL sending as well, including PAUSE and HOLD. + * We might still be paused on receive client writes though, so + * keep those bits around. */ + data->req.keepon &= ~(KEEP_RECV|KEEP_SENDBITS); + return Curl_req_abort_sending(data); +} diff --git a/Utilities/cmcurl/lib/request.h b/Utilities/cmcurl/lib/request.h new file mode 100644 index 00000000000..74d9f534396 --- /dev/null +++ b/Utilities/cmcurl/lib/request.h @@ -0,0 +1,227 @@ +#ifndef HEADER_CURL_REQUEST_H +#define HEADER_CURL_REQUEST_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 + * + ***************************************************************************/ + +/* This file is for lib internal stuff */ + +#include "curl_setup.h" + +#include "bufq.h" + +/* forward declarations */ +struct UserDefined; + +enum expect100 { + EXP100_SEND_DATA, /* enough waiting, just send the body now */ + EXP100_AWAITING_CONTINUE, /* waiting for the 100 Continue header */ + EXP100_SENDING_REQUEST, /* still sending the request but will wait for + the 100 header once done with the request */ + EXP100_FAILED /* used on 417 Expectation Failed */ +}; + +enum upgrade101 { + UPGR101_INIT, /* default state */ + UPGR101_WS, /* upgrade to WebSockets requested */ + UPGR101_H2, /* upgrade to HTTP/2 requested */ + UPGR101_RECEIVED, /* 101 response received */ + UPGR101_WORKING /* talking upgraded protocol */ +}; + + +/* + * Request specific data in the easy handle (Curl_easy). Previously, + * these members were on the connectdata struct but since a conn struct may + * now be shared between different Curl_easys, we store connection-specific + * data here. This struct only keeps stuff that is interesting for *this* + * request, as it will be cleared between multiple ones + */ +struct SingleRequest { + curl_off_t size; /* -1 if unknown at this point */ + curl_off_t maxdownload; /* in bytes, the maximum amount of data to fetch, + -1 means unlimited */ + curl_off_t bytecount; /* total number of bytes read */ + curl_off_t writebytecount; /* number of bytes written */ + + struct curltime start; /* transfer started at this time */ + unsigned int headerbytecount; /* received server headers (not CONNECT + headers) */ + unsigned int allheadercount; /* all received headers (server + CONNECT) */ + unsigned int deductheadercount; /* this amount of bytes does not count when + we check if anything has been transferred + at the end of a connection. We use this + counter to make only a 100 reply (without + a following second response code) result + in a CURLE_GOT_NOTHING error code */ + int headerline; /* counts header lines to better track the + first one */ + curl_off_t offset; /* possible resume offset read from the + Content-Range: header */ + int httpcode; /* error code from the 'HTTP/1.? XXX' or + 'RTSP/1.? XXX' line */ + int keepon; + unsigned char httpversion_sent; /* Version in request (09, 10, 11, etc.) */ + unsigned char httpversion; /* Version in response (09, 10, 11, etc.) */ + enum upgrade101 upgr101; /* 101 upgrade state */ + + /* Client Writer stack, handles transfer- and content-encodings, protocol + * checks, pausing by client callbacks. */ + struct Curl_cwriter *writer_stack; + /* Client Reader stack, handles transfer- and content-encodings, protocol + * checks, pausing by client callbacks. */ + struct Curl_creader *reader_stack; + struct bufq sendbuf; /* data which needs to be send to the server */ + size_t sendbuf_hds_len; /* amount of header bytes in sendbuf */ + time_t timeofdoc; + char *location; /* This points to an allocated version of the Location: + header data */ + char *newurl; /* Set to the new URL to use when a redirect or a retry is + wanted */ + +#ifndef CURL_DISABLE_COOKIES + unsigned char setcookies; +#endif + BIT(header); /* incoming data has HTTP header */ + BIT(done); /* request is done, e.g. no more send/recv should + * happen. This can be TRUE before `upload_done` or + * `download_done` is TRUE. */ + BIT(content_range); /* set TRUE if Content-Range: was found */ + BIT(download_done); /* set to TRUE when download is complete */ + BIT(eos_written); /* iff EOS has been written to client */ + BIT(eos_read); /* iff EOS has been read from the client */ + BIT(eos_sent); /* iff EOS has been sent to the server */ + BIT(rewind_read); /* iff reader needs rewind at next start */ + BIT(upload_done); /* set to TRUE when all request data has been sent */ + BIT(upload_aborted); /* set to TRUE when upload was aborted. Will also + * show `upload_done` as TRUE. */ + BIT(ignorebody); /* we read a response-body but we ignore it! */ + BIT(http_bodyless); /* HTTP response status code is between 100 and 199, + 204 or 304 */ + BIT(chunk); /* if set, this is a chunked transfer-encoding */ + BIT(resp_trailer); /* response carried 'Trailer:' header field */ + BIT(ignore_cl); /* ignore content-length */ + BIT(upload_chunky); /* set TRUE if we are doing chunked transfer-encoding + on upload */ + BIT(getheader); /* TRUE if header parsing is wanted */ + BIT(no_body); /* the response has no body */ + BIT(authneg); /* TRUE when the auth phase has started, which means + that we are creating a request with an auth header, + but it is not the final request in the auth + negotiation. */ + BIT(sendbuf_init); /* sendbuf is initialized */ + BIT(shutdown); /* request end will shutdown connection */ + BIT(shutdown_err_ignore); /* errors in shutdown will not fail request */ +}; + +/** + * Initialize the state of the request for first use. + */ +void Curl_req_init(struct SingleRequest *req); + +/** + * The request is about to start. Record time and do a soft reset. + */ +CURLcode Curl_req_start(struct SingleRequest *req, + struct Curl_easy *data); + +/** + * The request may continue with a follow up. Reset + * members, but keep start time for overall duration calc. + */ +CURLcode Curl_req_soft_reset(struct SingleRequest *req, + struct Curl_easy *data); + +/** + * The request is done. If not aborted, make sure that buffers are + * flushed to the client. + * @param req the request + * @param data the transfer + * @param aborted TRUE iff the request was aborted/errored + */ +CURLcode Curl_req_done(struct SingleRequest *req, + struct Curl_easy *data, bool aborted); + +/** + * Free the state of the request, not usable afterwards. + */ +void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data); + +/** + * Hard reset the state of the request to virgin state base on + * transfer settings. + */ +void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data); + +/** + * Send request headers. If not all could be sent + * they will be buffered. Use `Curl_req_flush()` to make sure + * bytes are really send. + * @param data the transfer making the request + * @param buf the complete header bytes, no body + * @param httpversion version used in request (09, 10, 11, etc.) + * @return CURLE_OK (on blocking with *pnwritten == 0) or error. + */ +CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf, + unsigned char httpversion); + +/** + * TRUE iff the request has sent all request headers and data. + */ +bool Curl_req_done_sending(struct Curl_easy *data); + +/* + * Read more from client and flush all buffered request bytes. + * @return CURLE_OK on success or the error on the sending. + * Never returns CURLE_AGAIN. + */ +CURLcode Curl_req_send_more(struct Curl_easy *data); + +/** + * TRUE iff the request wants to send, e.g. has buffered bytes. + */ +bool Curl_req_want_send(struct Curl_easy *data); + +/** + * TRUE iff the request has no buffered bytes yet to send. + */ +bool Curl_req_sendbuf_empty(struct Curl_easy *data); + +/** + * Stop sending any more request data to the server. + * Will clear the send buffer and mark request sending as done. + */ +CURLcode Curl_req_abort_sending(struct Curl_easy *data); + +/** + * Stop sending and receiving any more request data. + * Will abort sending if not done. + */ +CURLcode Curl_req_stop_send_recv(struct Curl_easy *data); + +/** + * Invoked when all request data has been uploaded. + */ +CURLcode Curl_req_set_upload_done(struct Curl_easy *data); + +#endif /* HEADER_CURL_REQUEST_H */ diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c index ccd7264b00e..ac44bec4222 100644 --- a/Utilities/cmcurl/lib/rtsp.c +++ b/Utilities/cmcurl/lib/rtsp.c @@ -24,7 +24,7 @@ #include "curl_setup.h" -#if !defined(CURL_DISABLE_RTSP) && !defined(USE_HYPER) +#if !defined(CURL_DISABLE_RTSP) #include "urldata.h" #include @@ -40,34 +40,66 @@ #include "connect.h" #include "cfilters.h" #include "strdup.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ - ((int)((unsigned char)((p)[3])))) + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_RTSP_EASY "meta:proto:rtsp:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_RTSP_CONN "meta:proto:rtsp:conn" + +typedef enum { + RTP_PARSE_SKIP, + RTP_PARSE_CHANNEL, + RTP_PARSE_LEN, + RTP_PARSE_DATA +} rtp_parse_st; + +/* RTSP Connection data + * Currently, only used for tracking incomplete RTP data reads */ +struct rtsp_conn { + struct dynbuf buf; + int rtp_channel; + size_t rtp_len; + rtp_parse_st state; + BIT(in_header); +}; + +/* RTSP transfer data */ +struct RTSP { + long CSeq_sent; /* CSeq of this request */ + long CSeq_recv; /* CSeq received */ +}; + + +#define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \ + ((unsigned int)((unsigned char)((p)[3])))) /* protocol-specific functions set up to be called by the main engine */ static CURLcode rtsp_do(struct Curl_easy *data, bool *done); static CURLcode rtsp_done(struct Curl_easy *data, CURLcode, bool premature); static CURLcode rtsp_connect(struct Curl_easy *data, bool *done); -static CURLcode rtsp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead); static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); /* - * Parse and write out any available RTP data. - * - * nread: amount of data left after k->str. will be modified if RTP - * data is parsed and k->str is moved up - * readmore: whether or not the RTP parser needs more data right away + * Parse and write out an RTSP response. + * @param data the transfer + * @param conn the connection + * @param buf data read from connection + * @param blen amount of data in buf + * @param is_eos TRUE iff this is the last write + * @param readmore out, TRUE iff complete buf was consumed and more data + * is needed */ -static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *readmore); +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos); static CURLcode rtsp_setup_connection(struct Curl_easy *data, struct connectdata *conn); @@ -76,7 +108,7 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data, unsigned int checks_to_perform); /* this returns the socket to wait for in the DO and DOING state for the multi - interface and then we're always _sending_ a request and thus we wait for + interface and then we are always _sending_ a request and thus we wait for the single socket to become writable only */ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) @@ -88,16 +120,16 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, } static -CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len); +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len); static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport); +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport); /* * RTSP handler interface. */ const struct Curl_handler Curl_handler_rtsp = { - "RTSP", /* scheme */ + "rtsp", /* scheme */ rtsp_setup_connection, /* setup_connection */ rtsp_do, /* do_it */ rtsp_done, /* done */ @@ -109,10 +141,12 @@ const struct Curl_handler Curl_handler_rtsp = { rtsp_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - rtsp_disconnect, /* disconnect */ - rtsp_rtp_readwrite, /* readwrite */ + ZERO_NULL, /* disconnect */ + rtsp_rtp_write_resp, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ rtsp_conncheck, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_RTSP, /* defport */ CURLPROTO_RTSP, /* protocol */ CURLPROTO_RTSP, /* family */ @@ -121,17 +155,41 @@ const struct Curl_handler Curl_handler_rtsp = { #define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */ +static void rtsp_easy_dtor(void *key, size_t klen, void *entry) +{ + struct RTSP *rtsp = entry; + (void)key; + (void)klen; + free(rtsp); +} + +static void rtsp_conn_dtor(void *key, size_t klen, void *entry) +{ + struct rtsp_conn *rtspc = entry; + (void)key; + (void)klen; + curlx_dyn_free(&rtspc->buf); + free(rtspc); +} + static CURLcode rtsp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { + struct rtsp_conn *rtspc; struct RTSP *rtsp; - (void)conn; - data->req.p.rtsp = rtsp = calloc(1, sizeof(struct RTSP)); - if(!rtsp) + rtspc = calloc(1, sizeof(*rtspc)); + if(!rtspc) + return CURLE_OUT_OF_MEMORY; + curlx_dyn_init(&rtspc->buf, MAX_RTP_BUFFERSIZE); + if(Curl_conn_meta_set(conn, CURL_META_RTSP_CONN, rtspc, rtsp_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + rtsp = calloc(1, sizeof(struct RTSP)); + if(!rtsp || + Curl_meta_set(data, CURL_META_RTSP_EASY, rtsp, rtsp_easy_dtor)) return CURLE_OUT_OF_MEMORY; - Curl_dyn_init(&conn->proto.rtspc.buf, MAX_RTP_BUFFERSIZE); return CURLE_OK; } @@ -158,8 +216,13 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data, static CURLcode rtsp_connect(struct Curl_easy *data, bool *done) { + struct rtsp_conn *rtspc = + Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN); CURLcode httpStatus; + if(!rtspc) + return CURLE_FAILED_INIT; + httpStatus = Curl_http_connect(data, done); /* Initialize the CSeq if not already done */ @@ -168,34 +231,29 @@ static CURLcode rtsp_connect(struct Curl_easy *data, bool *done) if(data->state.rtsp_next_server_CSeq == 0) data->state.rtsp_next_server_CSeq = 1; - data->conn->proto.rtspc.rtp_channel = -1; + rtspc->rtp_channel = -1; return httpStatus; } -static CURLcode rtsp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead) -{ - (void) dead; - (void) data; - Curl_dyn_free(&conn->proto.rtspc.buf); - return CURLE_OK; -} - - static CURLcode rtsp_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct RTSP *rtsp = data->req.p.rtsp; + struct rtsp_conn *rtspc = + Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN); + struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); CURLcode httpStatus; + if(!rtspc || !rtsp) + return CURLE_FAILED_INIT; + /* Bypass HTTP empty-reply checks on receive */ if(data->set.rtspreq == RTSPREQ_RECEIVE) premature = TRUE; httpStatus = Curl_http_done(data, status, premature); - if(rtsp && !status && !httpStatus) { + if(!status && !httpStatus) { /* Check the sequence numbers */ long CSeq_sent = rtsp->CSeq_sent; long CSeq_recv = rtsp->CSeq_recv; @@ -205,10 +263,14 @@ static CURLcode rtsp_done(struct Curl_easy *data, CSeq_sent, CSeq_recv); return CURLE_RTSP_CSEQ_ERROR; } - if(data->set.rtspreq == RTSPREQ_RECEIVE && - (data->conn->proto.rtspc.rtp_channel == -1)) { + if(data->set.rtspreq == RTSPREQ_RECEIVE && (rtspc->rtp_channel == -1)) { infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv); } + if(data->set.rtspreq == RTSPREQ_RECEIVE && + data->req.eos_written) { + failf(data, "Server prematurely closed the RTSP connection."); + return CURLE_RECV_ERROR; + } } return httpStatus; @@ -219,10 +281,9 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; Curl_RtspReq rtspreq = data->set.rtspreq; - struct RTSP *rtsp = data->req.p.rtsp; + struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); struct dynbuf req_buffer; - curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ - curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort of... */ const char *p_request = NULL; const char *p_session_id = NULL; @@ -237,6 +298,11 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) const char *p_userpwd = NULL; *done = TRUE; + if(!rtsp) + return CURLE_FAILED_INIT; + + /* Initialize a dynamic send buffer */ + curlx_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; rtsp->CSeq_recv = 0; @@ -257,7 +323,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) * Since all RTSP requests are included here, there is no need to * support custom requests like HTTP. **/ - data->req.no_body = TRUE; /* most requests don't contain a body */ + data->req.no_body = TRUE; /* most requests do not contain a body */ switch(rtspreq) { default: failf(data, "Got invalid RTSP request"); @@ -306,17 +372,19 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) } if(rtspreq == RTSPREQ_RECEIVE) { - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); - - return result; + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, TRUE); + goto out; } p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; if(!p_session_id && - (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { + (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS | + RTSPREQ_DESCRIBE | + RTSPREQ_SETUP))) { failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", p_request); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } /* Stream URI. Default to server '*' if not specified */ @@ -332,8 +400,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(rtspreq == RTSPREQ_SETUP && !p_transport) { /* New Transport: setting? */ if(data->set.str[STRING_RTSP_TRANSPORT]) { - Curl_safefree(data->state.aptr.rtsp_transport); - + free(data->state.aptr.rtsp_transport); data->state.aptr.rtsp_transport = aprintf("Transport: %s\r\n", data->set.str[STRING_RTSP_TRANSPORT]); @@ -343,7 +410,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) else { failf(data, "Refusing to issue an RTSP SETUP without a Transport: header."); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } p_transport = data->state.aptr.rtsp_transport; @@ -352,19 +420,20 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) /* Accept Headers for DESCRIBE requests */ if(rtspreq == RTSPREQ_DESCRIBE) { /* Accept Header */ - p_accept = Curl_checkheaders(data, STRCONST("Accept"))? - NULL:"Accept: application/sdp\r\n"; + p_accept = Curl_checkheaders(data, STRCONST("Accept")) ? + NULL : "Accept: application/sdp\r\n"; /* Accept-Encoding header */ if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && data->set.str[STRING_ENCODING]) { - Curl_safefree(data->state.aptr.accept_encoding); + free(data->state.aptr.accept_encoding); data->state.aptr.accept_encoding = aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - if(!data->state.aptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; - + if(!data->state.aptr.accept_encoding) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } p_accept_encoding = data->state.aptr.accept_encoding; } } @@ -386,9 +455,11 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET, p_stream_uri, FALSE); if(result) - return result; + goto out; +#ifndef CURL_DISABLE_PROXY p_proxyuserpwd = data->state.aptr.proxyuserpwd; +#endif p_userpwd = data->state.aptr.userpwd; /* Referrer */ @@ -409,7 +480,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) /* Check to see if there is a range set in the custom headers */ if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { - Curl_safefree(data->state.aptr.rangeline); + free(data->state.aptr.rangeline); data->state.aptr.rangeline = aprintf("Range: %s\r\n", data->state.range); p_range = data->state.aptr.rangeline; } @@ -420,55 +491,54 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) */ if(Curl_checkheaders(data, STRCONST("CSeq"))) { failf(data, "CSeq cannot be set as a custom header."); - return CURLE_RTSP_CSEQ_ERROR; + result = CURLE_RTSP_CSEQ_ERROR; + goto out; } if(Curl_checkheaders(data, STRCONST("Session"))) { failf(data, "Session ID cannot be set as a custom header."); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - /* Initialize a dynamic send buffer */ - Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); - result = - Curl_dyn_addf(&req_buffer, - "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ - "CSeq: %ld\r\n", /* CSeq */ - p_request, p_stream_uri, rtsp->CSeq_sent); + curlx_dyn_addf(&req_buffer, + "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ + "CSeq: %ld\r\n", /* CSeq */ + p_request, p_stream_uri, rtsp->CSeq_sent); if(result) - return result; + goto out; /* * Rather than do a normal alloc line, keep the session_id unformatted * to make comparison easier */ if(p_session_id) { - result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); + result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); if(result) - return result; + goto out; } /* * Shared HTTP-like options */ - result = Curl_dyn_addf(&req_buffer, - "%s" /* transport */ - "%s" /* accept */ - "%s" /* accept-encoding */ - "%s" /* range */ - "%s" /* referrer */ - "%s" /* user-agent */ - "%s" /* proxyuserpwd */ - "%s" /* userpwd */ - , - p_transport ? p_transport : "", - p_accept ? p_accept : "", - p_accept_encoding ? p_accept_encoding : "", - p_range ? p_range : "", - p_referrer ? p_referrer : "", - p_uagent ? p_uagent : "", - p_proxyuserpwd ? p_proxyuserpwd : "", - p_userpwd ? p_userpwd : ""); + result = curlx_dyn_addf(&req_buffer, + "%s" /* transport */ + "%s" /* accept */ + "%s" /* accept-encoding */ + "%s" /* range */ + "%s" /* referrer */ + "%s" /* user-agent */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + , + p_transport ? p_transport : "", + p_accept ? p_accept : "", + p_accept_encoding ? p_accept_encoding : "", + p_range ? p_range : "", + p_referrer ? p_referrer : "", + p_uagent ? p_uagent : "", + p_proxyuserpwd ? p_proxyuserpwd : "", + p_userpwd ? p_userpwd : ""); /* * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM @@ -477,68 +547,79 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) Curl_safefree(data->state.aptr.userpwd); if(result) - return result; + goto out; if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { result = Curl_add_timecondition(data, &req_buffer); if(result) - return result; + goto out; } - result = Curl_add_custom_headers(data, FALSE, &req_buffer); + result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer); if(result) - return result; + goto out; if(rtspreq == RTSPREQ_ANNOUNCE || rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { + curl_off_t req_clen; /* request content length */ if(data->state.upload) { - putsize = data->state.infilesize; + req_clen = data->state.infilesize; data->state.httpreq = HTTPREQ_PUT; - + result = Curl_creader_set_fread(data, req_clen); + if(result) + goto out; } else { - postsize = (data->state.infilesize != -1)? - data->state.infilesize: - (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); - data->state.httpreq = HTTPREQ_POST; + if(data->set.postfields) { + size_t plen = strlen(data->set.postfields); + req_clen = (curl_off_t)plen; + result = Curl_creader_set_buf(data, data->set.postfields, plen); + } + else if(data->state.infilesize >= 0) { + req_clen = data->state.infilesize; + result = Curl_creader_set_fread(data, req_clen); + } + else { + req_clen = 0; + result = Curl_creader_set_null(data); + } + if(result) + goto out; } - if(putsize > 0 || postsize > 0) { + if(req_clen > 0) { /* As stated in the http comments, it is probably not wise to * actually set a custom Content-Length in the headers */ if(!Curl_checkheaders(data, STRCONST("Content-Length"))) { result = - Curl_dyn_addf(&req_buffer, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n", - (data->state.upload ? putsize : postsize)); + curlx_dyn_addf(&req_buffer, "Content-Length: %" FMT_OFF_T"\r\n", + req_clen); if(result) - return result; + goto out; } if(rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = Curl_dyn_addn(&req_buffer, - STRCONST("Content-Type: " - "text/parameters\r\n")); + result = curlx_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "text/parameters\r\n")); if(result) - return result; + goto out; } } if(rtspreq == RTSPREQ_ANNOUNCE) { if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = Curl_dyn_addn(&req_buffer, - STRCONST("Content-Type: " - "application/sdp\r\n")); + result = curlx_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "application/sdp\r\n")); if(result) - return result; + goto out; } } - - data->state.expect100header = FALSE; /* RTSP posts are simple/small */ } else if(rtspreq == RTSPREQ_GET_PARAMETER) { /* Check for an empty GET_PARAMETER (heartbeat) request */ @@ -546,31 +627,26 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) data->req.no_body = TRUE; } } + else { + result = Curl_creader_set_null(data); + if(result) + goto out; + } - /* RTSP never allows chunked transfer */ - data->req.forbidchunk = TRUE; /* Finish the request buffer */ - result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n")); + result = curlx_dyn_addn(&req_buffer, STRCONST("\r\n")); if(result) - return result; + goto out; - if(postsize > 0) { - result = Curl_dyn_addn(&req_buffer, data->set.postfields, - (size_t)postsize); - if(result) - return result; - } + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); /* issue the request */ - result = Curl_buffer_send(&req_buffer, data, data->req.p.http, - &data->info.request_size, 0, FIRSTSOCKET); + result = Curl_req_send(data, &req_buffer, httpversion); if(result) { failf(data, "Failed sending RTSP request"); - return result; + goto out; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, putsize?FIRSTSOCKET:-1); - /* Increment the CSeq on success */ data->state.rtsp_next_client_CSeq++; @@ -581,157 +657,288 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; } - +out: + curlx_dyn_free(&req_buffer); return result; } - -static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *readmore) { - struct SingleRequest *k = &data->req; - struct rtsp_conn *rtspc = &(conn->proto.rtspc); - unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; - - char *rtp; /* moving pointer to rtp data */ - ssize_t rtp_dataleft; /* how much data left to parse in this round */ - CURLcode result; - bool interleaved = false; - size_t skip_size = 0; - - if(Curl_dyn_len(&rtspc->buf)) { - /* There was some leftover data the last time. Append new buffers */ - if(Curl_dyn_addn(&rtspc->buf, k->str, *nread)) - return CURLE_OUT_OF_MEMORY; - rtp = Curl_dyn_ptr(&rtspc->buf); - rtp_dataleft = Curl_dyn_len(&rtspc->buf); +/** + * write any BODY bytes missing to the client, ignore the rest. + */ +static CURLcode rtp_write_body_junk(struct Curl_easy *data, + struct rtsp_conn *rtspc, + const char *buf, + size_t blen) +{ + curl_off_t body_remain; + bool in_body; + + in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + body_remain = in_body ? (data->req.size - data->req.bytecount) : 0; + DEBUGASSERT(body_remain >= 0); + if(body_remain) { + if((curl_off_t)blen > body_remain) + blen = (size_t)body_remain; + return Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); } - else { - /* Just parse the request buffer directly */ - rtp = k->str; - rtp_dataleft = *nread; - } - - while(rtp_dataleft > 0) { - if(rtp[0] == '$') { - if(rtp_dataleft > 4) { - unsigned char rtp_channel; - int rtp_length; - int idx; - int off; - - /* Parse the header */ - /* The channel identifier immediately follows and is 1 byte */ - rtp_channel = (unsigned char)rtp[1]; - idx = rtp_channel / 8; - off = rtp_channel % 8; - if(!(rtp_channel_mask[idx] & (1 << off))) { - /* invalid channel number, maybe not an RTP packet */ - rtp++; - rtp_dataleft--; - skip_size++; - continue; + return CURLE_OK; +} + +static CURLcode rtsp_filter_rtp(struct Curl_easy *data, + struct rtsp_conn *rtspc, + const char *buf, + size_t blen, + size_t *pconsumed) +{ + CURLcode result = CURLE_OK; + size_t skip_len = 0; + + *pconsumed = 0; + while(blen) { + bool in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + switch(rtspc->state) { + + case RTP_PARSE_SKIP: { + DEBUGASSERT(curlx_dyn_len(&rtspc->buf) == 0); + while(blen && buf[0] != '$') { + if(!in_body && buf[0] == 'R' && + data->set.rtspreq != RTSPREQ_RECEIVE) { + if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) { + /* This could be the next response, no consume and return */ + if(*pconsumed) { + DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, " + "skipping %zd bytes of junk", *pconsumed)); + } + rtspc->state = RTP_PARSE_SKIP; + rtspc->in_header = TRUE; + goto out; + } } - if(skip_size > 0) { - DEBUGF(infof(data, "Skip the malformed interleaved data %lu " - "bytes", skip_size)); + /* junk/BODY, consume without buffering */ + *pconsumed += 1; + ++buf; + --blen; + ++skip_len; + } + if(blen && buf[0] == '$') { + /* possible start of an RTP message, buffer */ + if(skip_len) { + /* end of junk/BODY bytes, flush */ + result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len); + skip_len = 0; + if(result) + goto out; } - skip_size = 0; - rtspc->rtp_channel = rtp_channel; - - /* The length is two bytes */ - rtp_length = RTP_PKT_LENGTH(rtp); + if(curlx_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + rtspc->state = RTP_PARSE_CHANNEL; + } + break; + } - if(rtp_dataleft < rtp_length + 4) { - /* Need more - incomplete payload */ - *readmore = TRUE; - break; + case RTP_PARSE_CHANNEL: { + int idx = ((unsigned char)buf[0]) / 8; + int off = ((unsigned char)buf[0]) % 8; + DEBUGASSERT(curlx_dyn_len(&rtspc->buf) == 1); + if(!(data->state.rtp_channel_mask[idx] & (1 << off))) { + /* invalid channel number, junk or BODY data */ + rtspc->state = RTP_PARSE_SKIP; + DEBUGASSERT(skip_len == 0); + /* we do not consume this byte, it is BODY data */ + DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", idx)); + if(*pconsumed == 0) { + /* We did not consume the initial '$' in our buffer, but had + * it from an earlier call. We cannot un-consume it and have + * to write it directly as BODY data */ + result = rtp_write_body_junk(data, rtspc, + curlx_dyn_ptr(&rtspc->buf), 1); + if(result) + goto out; } - interleaved = true; - /* We have the full RTP interleaved packet - * Write out the header including the leading '$' */ - DEBUGF(infof(data, "RTP write channel %d rtp_length %d", - rtspc->rtp_channel, rtp_length)); - result = rtp_client_write(data, &rtp[0], rtp_length + 4); - if(result) { - *readmore = FALSE; - return result; + else { + /* count the '$' as skip and continue */ + skip_len = 1; } + curlx_dyn_free(&rtspc->buf); + break; + } + /* a valid channel, so we expect this to be a real RTP message */ + rtspc->rtp_channel = (unsigned char)buf[0]; + if(curlx_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + rtspc->state = RTP_PARSE_LEN; + break; + } - /* Move forward in the buffer */ - rtp_dataleft -= rtp_length + 4; - rtp += rtp_length + 4; + case RTP_PARSE_LEN: { + size_t rtp_len = curlx_dyn_len(&rtspc->buf); + const char *rtp_buf; + DEBUGASSERT(rtp_len >= 2 && rtp_len < 4); + if(curlx_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + if(rtp_len == 2) + break; + rtp_buf = curlx_dyn_ptr(&rtspc->buf); + rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4; + rtspc->state = RTP_PARSE_DATA; + break; + } - if(data->set.rtspreq == RTSPREQ_RECEIVE) { - /* If we are in a passive receive, give control back - * to the app as often as we can. - */ - k->keepon &= ~KEEP_RECV; + case RTP_PARSE_DATA: { + size_t rtp_len = curlx_dyn_len(&rtspc->buf); + size_t needed; + DEBUGASSERT(rtp_len < rtspc->rtp_len); + needed = rtspc->rtp_len - rtp_len; + if(needed <= blen) { + if(curlx_dyn_addn(&rtspc->buf, buf, needed)) { + result = CURLE_OUT_OF_MEMORY; + goto out; } + *pconsumed += needed; + buf += needed; + blen -= needed; + /* complete RTP message in buffer */ + DEBUGF(infof(data, "RTP write channel %d rtp_len %zu", + rtspc->rtp_channel, rtspc->rtp_len)); + result = rtp_client_write(data, curlx_dyn_ptr(&rtspc->buf), + rtspc->rtp_len); + curlx_dyn_free(&rtspc->buf); + rtspc->state = RTP_PARSE_SKIP; + if(result) + goto out; } else { - /* Need more - incomplete header */ - *readmore = TRUE; - break; - } - } - else { - /* If the following data begins with 'RTSP/', which might be an RTSP - message, we should stop skipping the data. */ - /* If `k-> headerline> 0 && !interleaved` is true, we are maybe in the - middle of an RTSP message. It is difficult to determine this, so we - stop skipping. */ - size_t prefix_len = (rtp_dataleft < 5) ? rtp_dataleft : 5; - if((k->headerline > 0 && !interleaved) || - strncmp(rtp, "RTSP/", prefix_len) == 0) { - if(skip_size > 0) { - DEBUGF(infof(data, "Skip the malformed interleaved data %lu " - "bytes", skip_size)); + if(curlx_dyn_addn(&rtspc->buf, buf, blen)) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - break; /* maybe is an RTSP message */ + *pconsumed += blen; + buf += blen; + blen = 0; } - /* Skip incorrect data util the next RTP packet or RTSP message */ - do { - rtp++; - rtp_dataleft--; - skip_size++; - } while(rtp_dataleft > 0 && rtp[0] != '$' && rtp[0] != 'R'); + break; + } + + default: + DEBUGASSERT(0); + return CURLE_RECV_ERROR; } } +out: + if(!result && skip_len) + result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len); + return result; +} - if(rtp_dataleft && rtp[0] == '$') { - DEBUGF(infof(data, "RTP Rewinding %zd %s", rtp_dataleft, - *readmore ? "(READMORE)" : "")); +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos) +{ + struct rtsp_conn *rtspc = + Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN); + CURLcode result = CURLE_OK; + size_t consumed = 0; - /* Store the incomplete RTP packet for a "rewind" */ - if(!Curl_dyn_len(&rtspc->buf)) { - /* nothing was stored, add this data */ - if(Curl_dyn_addn(&rtspc->buf, rtp, rtp_dataleft)) - return CURLE_OUT_OF_MEMORY; - } - else { - /* keep the remainder */ - Curl_dyn_tail(&rtspc->buf, rtp_dataleft); - } + if(!rtspc) + return CURLE_FAILED_INIT; - /* As far as the transfer is concerned, this data is consumed */ - *nread = 0; - return CURLE_OK; + if(!data->req.header) + rtspc->in_header = FALSE; + if(!blen) { + goto out; } - /* Fix up k->str to point just after the last RTP packet */ - k->str += *nread - rtp_dataleft; - *nread = rtp_dataleft; + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)", + blen, rtspc->in_header, is_eos)); - /* If we get here, we have finished with the leftover/merge buffer */ - Curl_dyn_free(&rtspc->buf); + /* If header parsing is not ongoing, extract RTP messages */ + if(!rtspc->in_header) { + result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed); + if(result) + goto out; + buf += consumed; + blen -= consumed; + /* either we consumed all or are at the start of header parsing */ + if(blen && !data->req.header) + DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body", + blen)); + } - return CURLE_OK; + /* we want to parse headers, do so */ + if(data->req.header && blen) { + rtspc->in_header = TRUE; + result = Curl_http_write_resp_hds(data, buf, blen, &consumed); + if(result) + goto out; + + buf += consumed; + blen -= consumed; + + if(!data->req.header) + rtspc->in_header = FALSE; + + if(!rtspc->in_header) { + /* If header parsing is done, extract interleaved RTP messages */ + if(data->req.size <= -1) { + /* Respect section 4.4 of rfc2326: If the Content-Length header is + absent, a length 0 must be assumed. */ + data->req.size = 0; + data->req.download_done = TRUE; + } + result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed); + if(result) + goto out; + blen -= consumed; + } + } + + if(rtspc->state != RTP_PARSE_SKIP) + data->req.done = FALSE; + /* we SHOULD have consumed all bytes, unless the response is borked. + * In which case we write out the left over bytes, letting the client + * writer deal with it (it will report EXCESS and fail the transfer). */ + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d " + " rtspc->state=%d, req.size=%" FMT_OFF_T ")", + blen, rtspc->in_header, data->req.done, rtspc->state, + data->req.size)); + if(!result && (is_eos || blen)) { + result = Curl_client_write(data, CLIENTWRITE_BODY| + (is_eos ? CLIENTWRITE_EOS : 0), buf, blen); + } + +out: + if((data->set.rtspreq == RTSPREQ_RECEIVE) && + (rtspc->state == RTP_PARSE_SKIP)) { + /* In special mode RECEIVE, we just process one chunk of network + * data, so we stop the transfer here, if we have no incomplete + * RTP message pending. */ + data->req.download_done = TRUE; + } + return result; } static -CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len) +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) { size_t wrote; curl_write_callback writeit; @@ -755,9 +962,9 @@ CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len) user_ptr = data->set.out; } - Curl_set_in_callback(data, true); - wrote = writeit(ptr, 1, len, user_ptr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, TRUE); + wrote = writeit((char *)CURL_UNCONST(ptr), 1, len, user_ptr); + Curl_set_in_callback(data, FALSE); if(CURL_WRITEFUNC_PAUSE == wrote) { failf(data, "Cannot pause RTP"); @@ -772,34 +979,29 @@ CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len) return CURLE_OK; } -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) { if(checkprefix("CSeq:", header)) { - long CSeq = 0; - char *endp; - char *p = &header[5]; - while(ISBLANK(*p)) - p++; - CSeq = strtol(p, &endp, 10); - if(p != endp) { - struct RTSP *rtsp = data->req.p.rtsp; - rtsp->CSeq_recv = CSeq; /* mark the request */ - data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ - } - else { + curl_off_t CSeq = 0; + struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); + const char *p = &header[5]; + if(!rtsp) + return CURLE_FAILED_INIT; + curlx_str_passblanks(&p); + if(curlx_str_number(&p, &CSeq, LONG_MAX)) { failf(data, "Unable to read the CSeq header: [%s]", header); return CURLE_RTSP_CSEQ_ERROR; } + rtsp->CSeq_recv = (long)CSeq; /* mark the request */ + data->state.rtsp_CSeq_recv = (long)CSeq; /* update the handle */ } else if(checkprefix("Session:", header)) { - char *start; - char *end; + const char *start, *end; size_t idlen; /* Find the first non-space letter */ start = header + 8; - while(*start && ISBLANK(*start)) - start++; + curlx_str_passblanks(&start); if(!*start) { failf(data, "Got a blank Session ID"); @@ -809,11 +1011,11 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) /* Find the end of Session ID * * Allow any non whitespace content, up to the field separator or end of - * line. RFC 2326 isn't 100% clear on the session ID and for example + * line. RFC 2326 is not 100% clear on the session ID and for example * gstreamer does url-encoded session ID's not covered by the standard. */ end = start; - while(*end && *end != ';' && !ISSPACE(*end)) + while((*end > ' ') && (*end != ';')) end++; idlen = end - start; @@ -821,7 +1023,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) /* If the Session ID is set, then compare */ if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen || - strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen) != 0) { + strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) { failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", start, data->set.str[STRING_RTSP_SESSION_ID]); return CURLE_RTSP_SESSION_ERROR; @@ -833,11 +1035,9 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) */ /* Copy the id substring into a new buffer */ - data->set.str[STRING_RTSP_SESSION_ID] = malloc(idlen + 1); + data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen); if(!data->set.str[STRING_RTSP_SESSION_ID]) return CURLE_OUT_OF_MEMORY; - memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, idlen); - (data->set.str[STRING_RTSP_SESSION_ID])[idlen] = '\0'; } } else if(checkprefix("Transport:", header)) { @@ -850,39 +1050,33 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) } static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport) { /* If we receive multiple Transport response-headers, the linterleaved channels of each response header is recorded and used together for subsequent data validity checks.*/ /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ - char *start; - char *end; + const char *start, *end; start = transport; while(start && *start) { - while(*start && ISBLANK(*start) ) - start++; + curlx_str_passblanks(&start); end = strchr(start, ';'); if(checkprefix("interleaved=", start)) { - long chan1, chan2, chan; - char *endp; - char *p = start + 12; - chan1 = strtol(p, &endp, 10); - if(p != endp && chan1 >= 0 && chan1 <= 255) { + curl_off_t chan1, chan2, chan; + const char *p = start + 12; + if(!curlx_str_number(&p, &chan1, 255)) { unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; chan2 = chan1; - if(*endp == '-') { - p = endp + 1; - chan2 = strtol(p, &endp, 10); - if(p == endp || chan2 < 0 || chan2 > 255) { + 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; } } for(chan = chan1; chan <= chan2; chan++) { - long idx = chan / 8; - long off = chan % 8; + int idx = (int)chan / 8; + int off = (int)chan % 8; rtp_channel_mask[idx] |= (unsigned char)(1 << off); } } @@ -899,4 +1093,4 @@ CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) } -#endif /* CURL_DISABLE_RTSP or using Hyper */ +#endif /* CURL_DISABLE_RTSP */ diff --git a/Utilities/cmcurl/lib/rtsp.h b/Utilities/cmcurl/lib/rtsp.h index 111bac2a612..59f20a9f167 100644 --- a/Utilities/cmcurl/lib/rtsp.h +++ b/Utilities/cmcurl/lib/rtsp.h @@ -23,15 +23,12 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#ifdef USE_HYPER -#define CURL_DISABLE_RTSP 1 -#endif #ifndef CURL_DISABLE_RTSP extern const struct Curl_handler Curl_handler_rtsp; -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header); #else /* disabled */ @@ -39,33 +36,4 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); #endif /* CURL_DISABLE_RTSP */ -/* - * RTSP Connection data - * - * Currently, only used for tracking incomplete RTP data reads - */ -struct rtsp_conn { - struct dynbuf buf; - int rtp_channel; -}; - -/**************************************************************************** - * RTSP unique setup - ***************************************************************************/ -struct RTSP { - /* - * http_wrapper MUST be the first element of this structure for the wrap - * logic to work. In this way, we get a cheap polymorphism because - * &(data->state.proto.rtsp) == &(data->state.proto.http) per the C spec - * - * HTTP functions can safely treat this as an HTTP struct, but RTSP aware - * functions can also index into the later elements. - */ - struct HTTP http_wrapper; /* wrap HTTP to do the heavy lifting */ - - long CSeq_sent; /* CSeq of this request */ - long CSeq_recv; /* CSeq received */ -}; - - #endif /* HEADER_CURL_RTSP_H */ diff --git a/Utilities/cmcurl/lib/select.c b/Utilities/cmcurl/lib/select.c index cae9beb6c7c..ee239b73217 100644 --- a/Utilities/cmcurl/lib/select.c +++ b/Utilities/cmcurl/lib/select.c @@ -24,6 +24,10 @@ #include "curl_setup.h" +#if !defined(HAVE_SELECT) && !defined(HAVE_POLL) +#error "We cannot compile without select() or poll() support." +#endif + #include #ifdef HAVE_SYS_SELECT_H @@ -32,10 +36,6 @@ #include #endif -#if !defined(HAVE_SELECT) && !defined(HAVE_POLL_FINE) -#error "We can't compile without select() or poll() support." -#endif - #ifdef MSDOS #include /* delay() */ #endif @@ -45,20 +45,23 @@ #include "urldata.h" #include "connect.h" #include "select.h" -#include "timediff.h" -#include "warnless.h" +#include "curlx/timediff.h" +#include "curlx/warnless.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" /* - * Internal function used for waiting a specific amount of ms - * in Curl_socket_check() and Curl_poll() when no file descriptor - * is provided to wait on, just being used to delay execution. - * WinSock select() and poll() timeout mechanisms need a valid - * socket descriptor in a not null file descriptor set to work. - * Waiting indefinitely with this function is not allowed, a - * zero or negative timeout value will return immediately. - * Timeout resolution, accuracy, as well as maximum supported - * value is system dependent, neither factor is a critical issue - * for the intended use of this function in the library. + * Internal function used for waiting a specific amount of ms in + * Curl_socket_check() and Curl_poll() when no file descriptor is provided to + * wait on, just being used to delay execution. Winsock select() and poll() + * timeout mechanisms need a valid socket descriptor in a not null file + * descriptor set to work. Waiting indefinitely with this function is not + * allowed, a zero or negative timeout value will return immediately. Timeout + * resolution, accuracy, as well as maximum supported value is system + * dependent, neither factor is a critical issue for the intended use of this + * function in the library. * * Return values: * -1 = system call error, or invalid timeout value @@ -71,36 +74,29 @@ int Curl_wait_ms(timediff_t timeout_ms) if(!timeout_ms) return 0; if(timeout_ms < 0) { - SET_SOCKERRNO(EINVAL); + SET_SOCKERRNO(SOCKEINVAL); return -1; } #if defined(MSDOS) - delay(timeout_ms); -#elif defined(WIN32) + delay((unsigned int)timeout_ms); +#elif defined(_WIN32) /* prevent overflow, timeout_ms is typecast to ULONG/DWORD. */ #if TIMEDIFF_T_MAX >= ULONG_MAX if(timeout_ms >= ULONG_MAX) timeout_ms = ULONG_MAX-1; - /* don't use ULONG_MAX, because that is equal to INFINITE */ -#endif - Sleep((ULONG)timeout_ms); -#else -#if defined(HAVE_POLL_FINE) - /* prevent overflow, timeout_ms is typecast to int. */ -#if TIMEDIFF_T_MAX > INT_MAX - if(timeout_ms > INT_MAX) - timeout_ms = INT_MAX; + /* do not use ULONG_MAX, because that is equal to INFINITE */ #endif - r = poll(NULL, 0, (int)timeout_ms); + Sleep((DWORD)timeout_ms); #else + /* avoid using poll() for this since it behaves incorrectly with no sockets + on Apple operating systems */ { struct timeval pending_tv; r = select(0, NULL, NULL, NULL, curlx_mstotv(&pending_tv, timeout_ms)); } -#endif /* HAVE_POLL_FINE */ -#endif /* USE_WINSOCK */ +#endif /* _WIN32 */ if(r) { - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) /* make EINTR from select or poll not a "lethal" error */ r = 0; else @@ -109,12 +105,12 @@ int Curl_wait_ms(timediff_t timeout_ms) return r; } -#ifndef HAVE_POLL_FINE +#ifndef HAVE_POLL /* - * This is a wrapper around select() to aid in Windows compatibility. - * A negative timeout value makes this function wait indefinitely, - * unless no valid file descriptor is given, when this happens the - * negative timeout is ignored and the function times out immediately. + * This is a wrapper around select() to aid in Windows compatibility. A + * negative timeout value makes this function wait indefinitely, unless no + * valid file descriptor is given, when this happens the negative timeout is + * ignored and the function times out immediately. * * Return values: * -1 = system call error or fd >= FD_SETSIZE @@ -131,7 +127,7 @@ static int our_select(curl_socket_t maxfd, /* highest socket number */ struct timeval *ptimeout; #ifdef USE_WINSOCK - /* WinSock select() can't handle zero events. See the comment below. */ + /* Winsock select() cannot handle zero events. See the comment below. */ if((!fds_read || fds_read->fd_count == 0) && (!fds_write || fds_write->fd_count == 0) && (!fds_err || fds_err->fd_count == 0)) { @@ -143,16 +139,16 @@ static int our_select(curl_socket_t maxfd, /* highest socket number */ ptimeout = curlx_mstotv(&pending_tv, timeout_ms); #ifdef USE_WINSOCK - /* WinSock select() must not be called with an fd_set that contains zero - fd flags, or it will return WSAEINVAL. But, it also can't be called + /* Winsock select() must not be called with an fd_set that contains zero + fd flags, or it will return WSAEINVAL. But, it also cannot be called with no fd_sets at all! From the documentation: Any two of the parameters, readfds, writefds, or exceptfds, can be given as null. At least one must be non-null, and any non-null descriptor set must contain at least one handle to a socket. - It is unclear why WinSock doesn't just handle this for us instead of - calling this an error. Luckily, with WinSock, we can _also_ ask how + It is unclear why Winsock does not just handle this for us instead of + calling this an error. Luckily, with Winsock, we can _also_ ask how many bits are set on an fd_set. So, let's just check it beforehand. */ return select((int)maxfd + 1, @@ -168,13 +164,13 @@ static int our_select(curl_socket_t maxfd, /* highest socket number */ /* * Wait for read or write events on a set of file descriptors. It uses poll() - * when a fine poll() is available, in order to avoid limits with FD_SETSIZE, - * otherwise select() is used. An error is returned if select() is being used + * when poll() is available, in order to avoid limits with FD_SETSIZE, + * otherwise select() is used. An error is returned if select() is being used * and a file descriptor is too large for FD_SETSIZE. * - * A negative timeout value makes this function wait indefinitely, - * unless no valid file descriptor is given, when this happens the - * negative timeout is ignored and the function times out immediately. + * A negative timeout value makes this function wait indefinitely, unless no + * valid file descriptor is given, when this happens the negative timeout is + * ignored and the function times out immediately. * * Return values: * -1 = system call error or fd >= FD_SETSIZE @@ -201,7 +197,7 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ return Curl_wait_ms(timeout_ms); } - /* Avoid initial timestamp, avoid Curl_now() call, when elapsed + /* Avoid initial timestamp, avoid curlx_now() call, when elapsed time in this function does not need to be measured. This happens when function is called with a zero timeout or a negative timeout value indicating a blocking call should be performed. */ @@ -226,7 +222,7 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ num++; } - r = Curl_poll(pfd, num, timeout_ms); + r = Curl_poll(pfd, (unsigned int)num, timeout_ms); if(r <= 0) return r; @@ -257,8 +253,8 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ } /* - * This is a wrapper around poll(). If poll() does not exist, then - * select() is used instead. An error is returned if select() is + * This is a wrapper around poll(). If poll() does not exist, then + * select() is used instead. An error is returned if select() is * being used and a file descriptor is too large for FD_SETSIZE. * A negative timeout value makes this function wait indefinitely, * unless no valid file descriptor is given, when this happens the @@ -271,7 +267,7 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ */ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) { -#ifdef HAVE_POLL_FINE +#ifdef HAVE_POLL int pending_ms; #else fd_set fds_read; @@ -296,12 +292,12 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) return Curl_wait_ms(timeout_ms); } - /* Avoid initial timestamp, avoid Curl_now() call, when elapsed + /* Avoid initial timestamp, avoid curlx_now() call, when elapsed time in this function does not need to be measured. This happens when function is called with a zero timeout or a negative timeout value indicating a blocking call should be performed. */ -#ifdef HAVE_POLL_FINE +#ifdef HAVE_POLL /* prevent overflow, timeout_ms is typecast to int. */ #if TIMEDIFF_T_MAX > INT_MAX @@ -316,7 +312,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) pending_ms = 0; r = poll(ufds, nfds, pending_ms); if(r <= 0) { - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) /* make EINTR from select or poll not a "lethal" error */ r = 0; return r; @@ -331,7 +327,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) ufds[i].revents |= POLLIN|POLLOUT; } -#else /* HAVE_POLL_FINE */ +#else /* HAVE_POLL */ FD_ZERO(&fds_read); FD_ZERO(&fds_write); @@ -357,14 +353,14 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) } /* - Note also that WinSock ignores the first argument, so we don't worry - about the fact that maxfd is computed incorrectly with WinSock (since + Note also that Winsock ignores the first argument, so we do not worry + about the fact that maxfd is computed incorrectly with Winsock (since curl_socket_t is unsigned in such cases and thus -1 is the largest value). */ r = our_select(maxfd, &fds_read, &fds_write, &fds_err, timeout_ms); if(r <= 0) { - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) /* make EINTR from select or poll not a "lethal" error */ r = 0; return r; @@ -397,7 +393,158 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) r++; } -#endif /* HAVE_POLL_FINE */ +#endif /* HAVE_POLL */ return r; } + +void Curl_pollfds_init(struct curl_pollfds *cpfds, + struct pollfd *static_pfds, + unsigned int static_count) +{ + DEBUGASSERT(cpfds); + memset(cpfds, 0, sizeof(*cpfds)); + if(static_pfds && static_count) { + cpfds->pfds = static_pfds; + cpfds->count = static_count; + } +} + +void Curl_pollfds_reset(struct curl_pollfds *cpfds) +{ + cpfds->n = 0; +} + +void Curl_pollfds_cleanup(struct curl_pollfds *cpfds) +{ + DEBUGASSERT(cpfds); + if(cpfds->allocated_pfds) { + free(cpfds->pfds); + } + memset(cpfds, 0, sizeof(*cpfds)); +} + +static CURLcode cpfds_increase(struct curl_pollfds *cpfds, unsigned int inc) +{ + struct pollfd *new_fds; + unsigned int new_count = cpfds->count + inc; + + new_fds = calloc(new_count, sizeof(struct pollfd)); + if(!new_fds) + return CURLE_OUT_OF_MEMORY; + + memcpy(new_fds, cpfds->pfds, cpfds->count * sizeof(struct pollfd)); + if(cpfds->allocated_pfds) + free(cpfds->pfds); + cpfds->pfds = new_fds; + cpfds->count = new_count; + cpfds->allocated_pfds = TRUE; + return CURLE_OK; +} + +static CURLcode cpfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events, bool fold) +{ + int i; + + if(fold && cpfds->n <= INT_MAX) { + for(i = (int)cpfds->n - 1; i >= 0; --i) { + if(sock == cpfds->pfds[i].fd) { + cpfds->pfds[i].events |= events; + return CURLE_OK; + } + } + } + /* not folded, add new entry */ + if(cpfds->n >= cpfds->count) { + if(cpfds_increase(cpfds, 100)) + return CURLE_OUT_OF_MEMORY; + } + cpfds->pfds[cpfds->n].fd = sock; + cpfds->pfds[cpfds->n].events = events; + ++cpfds->n; + return CURLE_OK; +} + +CURLcode Curl_pollfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events) +{ + return cpfds_add_sock(cpfds, sock, events, FALSE); +} + +CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, + struct easy_pollset *ps) +{ + size_t i; + + DEBUGASSERT(cpfds); + DEBUGASSERT(ps); + for(i = 0; i < ps->num; i++) { + short events = 0; + if(ps->actions[i] & CURL_POLL_IN) + 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; + } + } + return CURLE_OK; +} + +void Curl_waitfds_init(struct Curl_waitfds *cwfds, + struct curl_waitfd *static_wfds, + unsigned int static_count) +{ + DEBUGASSERT(cwfds); + DEBUGASSERT(static_wfds || !static_count); + memset(cwfds, 0, sizeof(*cwfds)); + cwfds->wfds = static_wfds; + cwfds->count = static_count; +} + +static unsigned int cwfds_add_sock(struct Curl_waitfds *cwfds, + curl_socket_t sock, short events) +{ + int i; + if(!cwfds->wfds) { + DEBUGASSERT(!cwfds->count && !cwfds->n); + return 1; + } + if(cwfds->n <= INT_MAX) { + for(i = (int)cwfds->n - 1; i >= 0; --i) { + if(sock == cwfds->wfds[i].fd) { + cwfds->wfds[i].events |= events; + return 0; + } + } + } + /* not folded, add new entry */ + if(cwfds->n < cwfds->count) { + cwfds->wfds[cwfds->n].fd = sock; + cwfds->wfds[cwfds->n].events = events; + ++cwfds->n; + } + return 1; +} + +unsigned int Curl_waitfds_add_ps(struct Curl_waitfds *cwfds, + struct easy_pollset *ps) +{ + size_t i; + unsigned int need = 0; + + DEBUGASSERT(cwfds); + DEBUGASSERT(ps); + for(i = 0; i < ps->num; i++) { + short events = 0; + if(ps->actions[i] & CURL_POLL_IN) + events |= CURL_WAIT_POLLIN; + if(ps->actions[i] & CURL_POLL_OUT) + events |= CURL_WAIT_POLLOUT; + if(events) + need += cwfds_add_sock(cwfds, ps->sockets[i], events); + } + return need; +} diff --git a/Utilities/cmcurl/lib/select.h b/Utilities/cmcurl/lib/select.h index 5b1ca23eb1b..10968fab7f8 100644 --- a/Utilities/cmcurl/lib/select.h +++ b/Utilities/cmcurl/lib/select.h @@ -93,7 +93,7 @@ int Curl_wait_ms(timediff_t timeout_ms); #define FDSET_SOCK(x) 1 #define VERIFY_SOCK(x) do { \ if(!VALID_SOCK(x)) { \ - SET_SOCKERRNO(WSAEINVAL); \ + SET_SOCKERRNO(SOCKEINVAL); \ return -1; \ } \ } while(0) @@ -105,10 +105,44 @@ int Curl_wait_ms(timediff_t timeout_ms); #define VERIFY_SOCK(x) do { \ if(!VALID_SOCK(x) || !FDSET_SOCK(x)) { \ - SET_SOCKERRNO(EINVAL); \ + SET_SOCKERRNO(SOCKEINVAL); \ return -1; \ } \ } while(0) #endif +struct curl_pollfds { + struct pollfd *pfds; + unsigned int n; + unsigned int count; + BIT(allocated_pfds); +}; + +void Curl_pollfds_init(struct curl_pollfds *cpfds, + struct pollfd *static_pfds, + unsigned int static_count); + +void Curl_pollfds_reset(struct curl_pollfds *cpfds); + +void Curl_pollfds_cleanup(struct curl_pollfds *cpfds); + +CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, + struct easy_pollset *ps); + +CURLcode Curl_pollfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events); + +struct Curl_waitfds { + struct curl_waitfd *wfds; + unsigned int n; + unsigned int count; +}; + +void Curl_waitfds_init(struct Curl_waitfds *cwfds, + struct curl_waitfd *static_wfds, + unsigned int static_count); + +unsigned int Curl_waitfds_add_ps(struct Curl_waitfds *cwfds, + struct easy_pollset *ps); + #endif /* HEADER_CURL_SELECT_H */ diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c index 81ee8648263..feb4598b063 100644 --- a/Utilities/cmcurl/lib/sendf.c +++ b/Utilities/cmcurl/lib/sendf.c @@ -40,6 +40,9 @@ #include "sendf.h" #include "cfilters.h" #include "connect.h" +#include "content_encoding.h" +#include "cw-out.h" +#include "cw-pause.h" #include "vtls/vtls.h" #include "vssh/ssh.h" #include "easyif.h" @@ -48,7 +51,8 @@ #include "select.h" #include "strdup.h" #include "http2.h" -#include "headers.h" +#include "progress.h" +#include "curlx/warnless.h" #include "ws.h" /* The last 3 #include files should be in this order */ @@ -56,371 +60,1394 @@ #include "curl_memory.h" #include "memdebug.h" -#if defined(CURL_DO_LINEEND_CONV) && !defined(CURL_DISABLE_FTP) -/* - * convert_lineends() changes CRLF (\r\n) end-of-line markers to a single LF - * (\n), with special processing for CRLF sequences that are split between two - * blocks of data. Remaining, bare CRs are changed to LFs. The possibly new - * size of the data is returned. + +static CURLcode do_init_writer_stack(struct Curl_easy *data); + +/* Curl_client_write() sends data to the write callback(s) + + The bit pattern defines to what "streams" to write to. Body and/or header. + The defines are in sendf.h of course. */ -static size_t convert_lineends(struct Curl_easy *data, - char *startPtr, size_t size) -{ - char *inPtr, *outPtr; - - /* sanity check */ - if(!startPtr || (size < 1)) { - return size; - } - - if(data->state.prev_block_had_trailing_cr) { - /* The previous block of incoming data - had a trailing CR, which was turned into a LF. */ - if(*startPtr == '\n') { - /* This block of incoming data starts with the - previous block's LF so get rid of it */ - memmove(startPtr, startPtr + 1, size-1); - size--; - /* and it wasn't a bare CR but a CRLF conversion instead */ - data->state.crlf_conversions++; - } - data->state.prev_block_had_trailing_cr = FALSE; /* reset the flag */ - } - - /* find 1st CR, if any */ - inPtr = outPtr = memchr(startPtr, '\r', size); - if(inPtr) { - /* at least one CR, now look for CRLF */ - while(inPtr < (startPtr + size-1)) { - /* note that it's size-1, so we'll never look past the last byte */ - if(memcmp(inPtr, "\r\n", 2) == 0) { - /* CRLF found, bump past the CR and copy the NL */ - inPtr++; - *outPtr = *inPtr; - /* keep track of how many CRLFs we converted */ - data->state.crlf_conversions++; - } - else { - if(*inPtr == '\r') { - /* lone CR, move LF instead */ - *outPtr = '\n'; - } - else { - /* not a CRLF nor a CR, just copy whatever it is */ - *outPtr = *inPtr; - } - } - outPtr++; - inPtr++; - } /* end of while loop */ - - if(inPtr < startPtr + size) { - /* handle last byte */ - if(*inPtr == '\r') { - /* deal with a CR at the end of the buffer */ - *outPtr = '\n'; /* copy a NL instead */ - /* note that a CRLF might be split across two blocks */ - data->state.prev_block_had_trailing_cr = TRUE; - } - else { - /* copy last byte */ - *outPtr = *inPtr; +CURLcode Curl_client_write(struct Curl_easy *data, + int type, const char *buf, size_t blen) +{ + CURLcode result; + + /* it is one of those, at least */ + DEBUGASSERT(type & (CLIENTWRITE_BODY|CLIENTWRITE_HEADER|CLIENTWRITE_INFO)); + /* BODY is only BODY (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_BODY) || + ((type & ~(CLIENTWRITE_BODY|CLIENTWRITE_EOS)) == 0)); + /* INFO is only INFO (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_INFO) || + ((type & ~(CLIENTWRITE_INFO|CLIENTWRITE_EOS)) == 0)); + + if(!data->req.writer_stack) { + result = do_init_writer_stack(data); + if(result) + return result; + DEBUGASSERT(data->req.writer_stack); + } + + result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, blen); + CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d", + type, blen, result); + return result; +} + +static void cl_reset_writer(struct Curl_easy *data) +{ + struct Curl_cwriter *writer = data->req.writer_stack; + while(writer) { + data->req.writer_stack = writer->next; + writer->cwt->do_close(data, writer); + free(writer); + writer = data->req.writer_stack; + } +} + +static void cl_reset_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + data->req.reader_stack = reader->next; + reader->crt->do_close(data, reader); + free(reader); + reader = data->req.reader_stack; + } +} + +void Curl_client_cleanup(struct Curl_easy *data) +{ + cl_reset_reader(data); + cl_reset_writer(data); + + data->req.bytecount = 0; + data->req.headerline = 0; +} + +void Curl_client_reset(struct Curl_easy *data) +{ + if(data->req.rewind_read) { + /* already requested */ + CURL_TRC_READ(data, "client_reset, will rewind reader"); + } + else { + CURL_TRC_READ(data, "client_reset, clear readers"); + cl_reset_reader(data); + } + cl_reset_writer(data); + + data->req.bytecount = 0; + data->req.headerline = 0; +} + +CURLcode Curl_client_start(struct Curl_easy *data) +{ + if(data->req.rewind_read) { + struct Curl_creader *r = data->req.reader_stack; + CURLcode result = CURLE_OK; + + CURL_TRC_READ(data, "client start, rewind readers"); + while(r) { + result = r->crt->rewind(data, r); + if(result) { + failf(data, "rewind of client reader '%s' failed: %d", + r->crt->name, result); + return result; } - outPtr++; + r = r->next; } - if(outPtr < startPtr + size) - /* tidy up by null terminating the now shorter data */ - *outPtr = '\0'; + data->req.rewind_read = FALSE; + cl_reset_reader(data); + } + return CURLE_OK; +} - return (outPtr - startPtr); +bool Curl_creader_will_rewind(struct Curl_easy *data) +{ + return data->req.rewind_read; +} + +void Curl_creader_set_rewind(struct Curl_easy *data, bool enable) +{ + data->req.rewind_read = !!enable; +} + +/* Write data using an unencoding writer stack. */ +CURLcode Curl_cwriter_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + if(!writer) + return CURLE_WRITE_ERROR; + return writer->cwt->do_write(data, writer, type, buf, nbytes); +} + +CURLcode Curl_cwriter_def_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (void)data; + (void)writer; + return CURLE_OK; +} + +CURLcode Curl_cwriter_def_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); +} + +void Curl_cwriter_def_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (void) data; + (void) writer; +} + +static size_t get_max_body_write_len(struct Curl_easy *data, curl_off_t limit) +{ + if(limit != -1) { + /* How much more are we allowed to write? */ + curl_off_t remain_diff; + remain_diff = limit - data->req.bytecount; + if(remain_diff < 0) { + /* already written too much! */ + return 0; + } +#if SIZEOF_CURL_OFF_T > SIZEOF_SIZE_T + else if(remain_diff > SSIZE_T_MAX) { + return SIZE_T_MAX; + } +#endif + else { + return (size_t)remain_diff; + } } - return size; + return SIZE_T_MAX; } -#endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */ -/* - * Curl_write() is an internal write function that sends data to the - * server. Works with plain sockets, SCP, SSL or kerberos. - * - * If the write would block (CURLE_AGAIN), we return CURLE_OK and - * (*written == 0). Otherwise we return regular CURLcode value. - */ -CURLcode Curl_write(struct Curl_easy *data, - curl_socket_t sockfd, - const void *mem, - size_t len, - ssize_t *written) +struct cw_download_ctx { + struct Curl_cwriter super; + BIT(started_response); +}; +/* Download client writer in phase CURL_CW_PROTOCOL that + * sees the "real" download body data. */ +static CURLcode cw_download_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { - ssize_t bytes_written; - CURLcode result = CURLE_OK; - struct connectdata *conn; - int num; - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - conn = data->conn; - num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]); - -#ifdef CURLDEBUG - { - /* Allow debug builds to override this logic to force short sends - */ - char *p = getenv("CURL_SMALLSENDS"); - if(p) { - size_t altsize = (size_t)strtoul(p, NULL, 10); - if(altsize) - len = CURLMIN(len, altsize); + struct cw_download_ctx *ctx = writer->ctx; + CURLcode result; + size_t nwrite, excess_len = 0; + bool is_connect = !!(type & CLIENTWRITE_CONNECT); + + if(!is_connect && !ctx->started_response) { + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + ctx->started_response = TRUE; + } + + if(!(type & CLIENTWRITE_BODY)) { + if(is_connect && data->set.suppress_connect_headers) + return CURLE_OK; + result = Curl_cwriter_write(data, writer->next, type, buf, nbytes); + CURL_TRC_WRITE(data, "download_write header(type=%x, blen=%zu) -> %d", + type, nbytes, result); + return result; + } + + /* Here, we deal with REAL BODY bytes. All filtering and transfer + * encodings have been applied and only the true content, e.g. BODY, + * bytes are passed here. + * This allows us to check sizes, update stats, etc. independent + * from the protocol in play. */ + + if(data->req.no_body && nbytes > 0) { + /* BODY arrives although we want none, bail out */ + streamclose(data->conn, "ignoring body"); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu), " + "did not want a BODY", type, nbytes); + data->req.download_done = TRUE; + if(data->info.header_size) + /* if headers have been received, this is fine */ + return CURLE_OK; + return CURLE_WEIRD_SERVER_REPLY; + } + + /* Determine if we see any bytes in excess to what is allowed. + * We write the allowed bytes and handle excess further below. + * This gives deterministic BODY writes on varying buffer receive + * lengths. */ + nwrite = nbytes; + if(-1 != data->req.maxdownload) { + size_t wmax = get_max_body_write_len(data, data->req.maxdownload); + if(nwrite > wmax) { + excess_len = nbytes - wmax; + nwrite = wmax; + } + + if(nwrite == wmax) { + data->req.download_done = TRUE; + } + + if((type & CLIENTWRITE_EOS) && !data->req.no_body && + (data->req.maxdownload > data->req.bytecount)) { + failf(data, "end of response with %" FMT_OFF_T " bytes missing", + data->req.maxdownload - data->req.bytecount); + return CURLE_PARTIAL_FILE; } } -#endif - bytes_written = conn->send[num](data, num, mem, len, &result); - *written = bytes_written; - if(bytes_written >= 0) - /* we completely ignore the curlcode value when subzero is not returned */ - return CURLE_OK; + /* Error on too large filesize is handled below, after writing + * the permitted bytes */ + if(data->set.max_filesize && !data->req.ignorebody) { + size_t wmax = get_max_body_write_len(data, data->set.max_filesize); + if(nwrite > wmax) { + nwrite = wmax; + } + } + + if(!data->req.ignorebody && (nwrite || (type & CLIENTWRITE_EOS))) { + result = Curl_cwriter_write(data, writer->next, type, buf, nwrite); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu) -> %d", + type, nbytes, result); + if(result) + return result; + } + /* Update stats, write and report progress */ + data->req.bytecount += nwrite; + result = Curl_pgrsSetDownloadCounter(data, data->req.bytecount); + if(result) + return result; + + if(excess_len) { + if(!data->req.ignorebody) { + infof(data, + "Excess found writing body:" + " excess = %zu" + ", size = %" FMT_OFF_T + ", maxdownload = %" FMT_OFF_T + ", bytecount = %" FMT_OFF_T, + excess_len, data->req.size, data->req.maxdownload, + data->req.bytecount); + connclose(data->conn, "excess found in a read"); + } + } + else if((nwrite < nbytes) && !data->req.ignorebody) { + failf(data, "Exceeded the maximum allowed file size " + "(%" FMT_OFF_T ") with %" FMT_OFF_T " bytes", + data->set.max_filesize, data->req.bytecount); + return CURLE_FILESIZE_EXCEEDED; + } + + return CURLE_OK; +} + +static const struct Curl_cwtype cw_download = { + "protocol", + NULL, + Curl_cwriter_def_init, + cw_download_write, + Curl_cwriter_def_close, + sizeof(struct cw_download_ctx) +}; + +/* RAW client writer in phase CURL_CW_RAW that + * enabled tracing of raw data. */ +static CURLcode cw_raw_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + if(type & CLIENTWRITE_BODY && data->set.verbose && !data->req.ignorebody) { + Curl_debug(data, CURLINFO_DATA_IN, buf, nbytes); + } + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); +} + +static const struct Curl_cwtype cw_raw = { + "raw", + NULL, + Curl_cwriter_def_init, + cw_raw_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) +}; + +/* Create an unencoding writer stage using the given handler. */ +CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, + struct Curl_easy *data, + const struct Curl_cwtype *cwt, + Curl_cwriter_phase phase) +{ + struct Curl_cwriter *writer = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + void *p; + + DEBUGASSERT(cwt->cwriter_size >= sizeof(struct Curl_cwriter)); + p = calloc(1, cwt->cwriter_size); + if(!p) + goto out; + + writer = (struct Curl_cwriter *)p; + writer->cwt = cwt; + writer->ctx = p; + writer->phase = phase; + result = cwt->do_init(data, writer); + +out: + *pwriter = result ? NULL : writer; + if(result) + free(writer); + return result; +} + +void Curl_cwriter_free(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + if(writer) { + writer->cwt->do_close(data, writer); + free(writer); + } +} + +size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase) +{ + struct Curl_cwriter *w; + size_t n = 0; + + for(w = data->req.writer_stack; w; w = w->next) { + if(w->phase == phase) + ++n; + } + return n; +} + +static CURLcode do_init_writer_stack(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + CURLcode result; + + DEBUGASSERT(!data->req.writer_stack); + result = Curl_cwriter_create(&data->req.writer_stack, + data, &Curl_cwt_out, CURL_CW_CLIENT); + if(result) + return result; + + /* This places the "pause" writer behind the "download" writer that + * is added below. Meaning the "download" can do checks on content length + * and other things *before* write outs are buffered for paused transfers. */ + result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause, + CURL_CW_PROTOCOL); + if(!result) { + result = Curl_cwriter_add(data, writer); + if(result) + Curl_cwriter_free(data, writer); + } + if(result) + return result; + + result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL); + if(!result) { + result = Curl_cwriter_add(data, writer); + if(result) + Curl_cwriter_free(data, writer); + } + if(result) + return result; + + result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW); + if(!result) { + result = Curl_cwriter_add(data, writer); + if(result) + Curl_cwriter_free(data, writer); + } + if(result) + return result; + + return result; +} + +CURLcode Curl_cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + CURLcode result; + struct Curl_cwriter **anchor = &data->req.writer_stack; + + if(!*anchor) { + result = do_init_writer_stack(data); + if(result) + return result; + } + + /* Insert the writer as first in its phase. + * Skip existing writers of lower phases. */ + while(*anchor && (*anchor)->phase < writer->phase) + anchor = &((*anchor)->next); + writer->next = *anchor; + *anchor = writer; + return CURLE_OK; +} + +struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, + const char *name) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(!strcmp(name, writer->cwt->name)) + return writer; + } + return NULL; +} + +struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, + const struct Curl_cwtype *cwt) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(writer->cwt == cwt) + return writer; + } + return NULL; +} + +bool Curl_cwriter_is_content_decoding(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(writer->phase == CURL_CW_CONTENT_DECODE) + return TRUE; + } + return FALSE; +} + +bool Curl_cwriter_is_paused(struct Curl_easy *data) +{ + return Curl_cw_out_is_paused(data); +} + +CURLcode Curl_cwriter_unpause(struct Curl_easy *data) +{ + return Curl_cw_out_unpause(data); +} + +CURLcode Curl_creader_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos) +{ + *nread = 0; + *eos = FALSE; + if(!reader) + return CURLE_READ_ERROR; + return reader->crt->do_read(data, reader, buf, blen, nread, eos); +} + +CURLcode Curl_creader_def_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +void Curl_creader_def_close(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; +} + +CURLcode Curl_creader_def_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos) +{ + if(reader->next) + return reader->next->crt->do_read(data, reader->next, buf, blen, + nread, eos); + else { + *nread = 0; + *eos = FALSE; + return CURLE_READ_ERROR; + } +} + +bool Curl_creader_def_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return FALSE; +} + +curl_off_t Curl_creader_def_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + return reader->next ? + reader->next->crt->total_length(data, reader->next) : -1; +} + +CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + (void)data; + (void)reader; + (void)offset; + return CURLE_READ_ERROR; +} + +CURLcode Curl_creader_def_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +CURLcode Curl_creader_def_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +bool Curl_creader_def_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return FALSE; +} + +void Curl_creader_def_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature) +{ + (void)data; + (void)reader; + (void)premature; +} + +struct cr_in_ctx { + struct Curl_creader super; + curl_read_callback read_cb; + void *cb_user_data; + curl_off_t total_len; + curl_off_t read_len; + CURLcode error_result; + BIT(seen_eos); + BIT(errored); + BIT(has_used_cb); + BIT(is_paused); +}; + +static CURLcode cr_in_init(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + ctx->read_cb = data->state.fread_func; + ctx->cb_user_data = data->state.in; + ctx->total_len = -1; + ctx->read_len = 0; + return CURLE_OK; +} - /* handle CURLE_AGAIN or a send failure */ - switch(result) { - case CURLE_AGAIN: - *written = 0; +/* Real client reader to installed client callbacks. */ +static CURLcode cr_in_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_in_ctx *ctx = reader->ctx; + size_t nread; + + ctx->is_paused = FALSE; + + /* Once we have errored, we will return the same error forever */ + if(ctx->errored) { + *pnread = 0; + *peos = FALSE; + return ctx->error_result; + } + if(ctx->seen_eos) { + *pnread = 0; + *peos = TRUE; return CURLE_OK; + } + /* respect length limitations */ + if(ctx->total_len >= 0) { + curl_off_t remain = ctx->total_len - ctx->read_len; + if(remain <= 0) + blen = 0; + else if(remain < (curl_off_t)blen) + blen = (size_t)remain; + } + nread = 0; + if(ctx->read_cb && blen) { + Curl_set_in_callback(data, TRUE); + nread = ctx->read_cb(buf, 1, blen, ctx->cb_user_data); + Curl_set_in_callback(data, FALSE); + ctx->has_used_cb = TRUE; + } - case CURLE_OK: - /* general send failure */ - return CURLE_SEND_ERROR; + switch(nread) { + case 0: + if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { + failf(data, "client read function EOF fail, " + "only %"FMT_OFF_T"/%"FMT_OFF_T " of needed bytes read", + ctx->read_len, ctx->total_len); + return CURLE_READ_ERROR; + } + *pnread = 0; + *peos = TRUE; + ctx->seen_eos = TRUE; + break; + + case CURL_READFUNC_ABORT: + failf(data, "operation aborted by callback"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_ABORTED_BY_CALLBACK; + return CURLE_ABORTED_BY_CALLBACK; + + case CURL_READFUNC_PAUSE: + if(data->conn->handler->flags & PROTOPT_NONETWORK) { + /* protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it cannot pause since the transfer + is not done using the "normal" procedure. */ + failf(data, "Read callback asked for PAUSE when not supported"); + return CURLE_READ_ERROR; + } + /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ + CURL_TRC_READ(data, "cr_in_read, callback returned CURL_READFUNC_PAUSE"); + ctx->is_paused = TRUE; + data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + *pnread = 0; + *peos = FALSE; + break; /* nothing was read */ default: - /* we got a specific curlcode, forward it */ - return result; + if(nread > blen) { + /* the read function returned a too large value */ + failf(data, "read function returned funny value"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + } + ctx->read_len += nread; + if(ctx->total_len >= 0) + ctx->seen_eos = (ctx->read_len >= ctx->total_len); + *pnread = nread; + *peos = ctx->seen_eos; + break; } + CURL_TRC_READ(data, "cr_in_read(len=%zu, total=%"FMT_OFF_T + ", read=%"FMT_OFF_T") -> %d, nread=%zu, eos=%d", + blen, ctx->total_len, ctx->read_len, CURLE_OK, + *pnread, *peos); + return CURLE_OK; } -static CURLcode pausewrite(struct Curl_easy *data, - int type, /* what type of data */ - const char *ptr, - size_t len) +static bool cr_in_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) { - /* signalled to pause sending on this connection, but since we have data - we want to send we need to dup it to save a copy for when the sending - is again enabled */ - struct SingleRequest *k = &data->req; - struct UrlState *s = &data->state; - unsigned int i; - bool newtype = TRUE; + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->has_used_cb; +} + +static curl_off_t cr_in_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->total_len; +} + +static CURLcode cr_in_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_in_ctx *ctx = reader->ctx; + int seekerr = CURL_SEEKFUNC_CANTSEEK; + + DEBUGASSERT(data->conn); + /* already started reading? */ + if(ctx->read_len) + return CURLE_READ_ERROR; + + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, offset, SEEK_SET); + Curl_set_in_callback(data, FALSE); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_READ_ERROR; + } + /* when seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (offset - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : + curlx_sotouz(offset - passed); + size_t actuallyread; - Curl_conn_ev_data_pause(data, TRUE); + Curl_set_in_callback(data, TRUE); + actuallyread = ctx->read_cb(scratch, 1, readthisamountnow, + ctx->cb_user_data); + Curl_set_in_callback(data, FALSE); - if(s->tempcount) { - for(i = 0; i< s->tempcount; i++) { - if(s->tempwrite[i].type == type) { - /* data for this type exists */ - newtype = FALSE; - break; + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" FMT_OFF_T " bytes from the input", + passed); + return CURLE_READ_ERROR; } + } while(passed < offset); + } + + /* now, decrease the size of the read */ + if(ctx->total_len > 0) { + ctx->total_len -= offset; + + if(ctx->total_len <= 0) { + failf(data, "File already completely uploaded"); + return CURLE_PARTIAL_FILE; } - DEBUGASSERT(i < 3); - if(i >= 3) - /* There are more types to store than what fits: very bad */ - return CURLE_OUT_OF_MEMORY; } - else - i = 0; + /* we have passed, proceed as normal */ + return CURLE_OK; +} + +static CURLcode cr_in_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; - if(newtype) { - /* store this information in the state struct for later use */ - Curl_dyn_init(&s->tempwrite[i].b, DYN_PAUSE_BUFFER); - s->tempwrite[i].type = type; - s->tempcount++; + /* If we never invoked the callback, there is noting to rewind */ + if(!ctx->has_used_cb) + return CURLE_OK; + + if(data->set.seek_func) { + int err; + + Curl_set_in_callback(data, TRUE); + err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); + Curl_set_in_callback(data, FALSE); + CURL_TRC_READ(data, "cr_in, rewind via set.seek_func -> %d", err); + if(err) { + failf(data, "seek callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } } + else if(data->set.ioctl_func) { + curlioerr err; - if(Curl_dyn_addn(&s->tempwrite[i].b, (unsigned char *)ptr, len)) - return CURLE_OUT_OF_MEMORY; + Curl_set_in_callback(data, TRUE); + err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, + data->set.ioctl_client); + Curl_set_in_callback(data, FALSE); + CURL_TRC_READ(data, "cr_in, rewind via set.ioctl_func -> %d", (int)err); + if(err) { + failf(data, "ioctl callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } + } + else { + /* If no CURLOPT_READFUNCTION is used, we know that we operate on a + given FILE * stream and we can actually attempt to rewind that + ourselves with fseek() */ + if(data->state.fread_func == (curl_read_callback)fread) { + int err = fseek(data->state.in, 0, SEEK_SET); + CURL_TRC_READ(data, "cr_in, rewind via fseek -> %d(%d)", + (int)err, (int)errno); + if(-1 != err) + /* successful rewind */ + return CURLE_OK; + } - /* mark the connection as RECV paused */ - k->keepon |= KEEP_RECV_PAUSE; + /* no callback set or failure above, makes us fail at once */ + failf(data, "necessary data rewind was not possible"); + return CURLE_SEND_FAIL_REWIND; + } + return CURLE_OK; +} +static CURLcode cr_in_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + ctx->is_paused = FALSE; return CURLE_OK; } +static bool cr_in_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->is_paused; +} -/* chop_write() writes chunks of data not larger than CURL_MAX_WRITE_SIZE via - * client write callback(s) and takes care of pause requests from the - * callbacks. - */ -static CURLcode chop_write(struct Curl_easy *data, - int type, - char *optr, - size_t olen) -{ - struct connectdata *conn = data->conn; - curl_write_callback writeheader = NULL; - curl_write_callback writebody = NULL; - char *ptr = optr; - size_t len = olen; - void *writebody_ptr = data->set.out; - - if(!len) +static const struct Curl_crtype cr_in = { + "cr-in", + cr_in_init, + cr_in_read, + Curl_creader_def_close, + cr_in_needs_rewind, + cr_in_total_length, + cr_in_resume_from, + cr_in_rewind, + cr_in_unpause, + cr_in_is_paused, + Curl_creader_def_done, + sizeof(struct cr_in_ctx) +}; + +CURLcode Curl_creader_create(struct Curl_creader **preader, + struct Curl_easy *data, + const struct Curl_crtype *crt, + Curl_creader_phase phase) +{ + struct Curl_creader *reader = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + void *p; + + DEBUGASSERT(crt->creader_size >= sizeof(struct Curl_creader)); + p = calloc(1, crt->creader_size); + if(!p) + goto out; + + reader = (struct Curl_creader *)p; + reader->crt = crt; + reader->ctx = p; + reader->phase = phase; + result = crt->do_init(data, reader); + +out: + *preader = result ? NULL : reader; + if(result) + free(reader); + return result; +} + +void Curl_creader_free(struct Curl_easy *data, struct Curl_creader *reader) +{ + if(reader) { + reader->crt->do_close(data, reader); + free(reader); + } +} + +struct cr_lc_ctx { + struct Curl_creader super; + struct bufq buf; + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ + BIT(prev_cr); /* the last byte was a CR */ +}; + +static CURLcode cr_lc_init(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_lc_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void cr_lc_close(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_lc_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->buf); +} + +/* client reader doing line end conversions. */ +static CURLcode cr_lc_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_lc_ctx *ctx = reader->ctx; + CURLcode result; + size_t nread, i, start, n; + bool eos; + + if(ctx->eos) { + *pnread = 0; + *peos = TRUE; return CURLE_OK; + } - /* If reading is paused, append this data to the already held data for this - type. */ - if(data->req.keepon & KEEP_RECV_PAUSE) - return pausewrite(data, type, ptr, len); - - /* Determine the callback(s) to use. */ - if(type & CLIENTWRITE_BODY) { -#ifdef USE_WEBSOCKETS - if(conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) { - writebody = Curl_ws_writecb; - writebody_ptr = data; + if(Curl_bufq_is_empty(&ctx->buf)) { + if(ctx->read_eos) { + ctx->eos = TRUE; + *pnread = 0; + *peos = TRUE; + return CURLE_OK; } - else -#endif - writebody = data->set.fwrite_func; - } - if((type & CLIENTWRITE_HEADER) && - (data->set.fwrite_header || data->set.writeheader)) { - /* - * Write headers to the same callback or to the especially setup - * header callback function (added after version 7.7.1). - */ - writeheader = - data->set.fwrite_header? data->set.fwrite_header: data->set.fwrite_func; - } - - /* Chop data, write chunks. */ - while(len) { - size_t chunklen = len <= CURL_MAX_WRITE_SIZE? len: CURL_MAX_WRITE_SIZE; - - if(writebody) { - size_t wrote; - Curl_set_in_callback(data, true); - wrote = writebody(ptr, 1, chunklen, writebody_ptr); - Curl_set_in_callback(data, false); - - if(CURL_WRITEFUNC_PAUSE == wrote) { - if(conn->handler->flags & PROTOPT_NONETWORK) { - /* Protocols that work without network cannot be paused. This is - actually only FILE:// just now, and it can't pause since the - transfer isn't done using the "normal" procedure. */ - failf(data, "Write callback asked for PAUSE when not supported"); - return CURLE_WRITE_ERROR; - } - return pausewrite(data, type, ptr, len); - } - if(wrote != chunklen) { - failf(data, "Failure writing output to destination"); - return CURLE_WRITE_ERROR; + /* Still getting data form the next reader, ctx->buf is empty */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + ctx->read_eos = eos; + + if(!nread || !memchr(buf, '\n', nread)) { + /* nothing to convert, return this right away */ + if(ctx->read_eos) + ctx->eos = TRUE; + *pnread = nread; + *peos = ctx->eos; + goto out; + } + + /* at least one \n might need conversion to '\r\n', place into ctx->buf */ + for(i = start = 0; i < nread; ++i) { + /* if this byte is not an LF character, or if the preceding character is + a CR (meaning this already is a CRLF pair), go to next */ + if((buf[i] != '\n') || ctx->prev_cr) { + ctx->prev_cr = (buf[i] == '\r'); + continue; } + ctx->prev_cr = FALSE; + /* on a soft limit bufq, we do not need to check length */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->buf, STRCONST("\r\n"), &n); + if(result) + return result; + start = i + 1; } - ptr += chunklen; - len -= chunklen; + if(start < i) { /* leftover */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; + } } -#ifndef CURL_DISABLE_HTTP - /* HTTP header, but not status-line */ - if((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS) ) { - unsigned char htype = (unsigned char) - (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT : - (type & CLIENTWRITE_1XX ? CURLH_1XX : - (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER : - CURLH_HEADER))); - CURLcode result = Curl_headers_push(data, optr, htype); + DEBUGASSERT(!Curl_bufq_is_empty(&ctx->buf)); + *peos = FALSE; + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); + if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + *peos = TRUE; + } + +out: + CURL_TRC_READ(data, "cr_lc_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *pnread, *peos); + return result; +} + +static curl_off_t cr_lc_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 cr_lc = { + "cr-lineconv", + cr_lc_init, + cr_lc_read, + cr_lc_close, + Curl_creader_def_needs_rewind, + cr_lc_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_lc_ctx) +}; + +static CURLcode cr_lc_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &cr_lc, + CURL_CR_CONTENT_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + +static CURLcode do_init_reader_stack(struct Curl_easy *data, + struct Curl_creader *r) +{ + CURLcode result = CURLE_OK; + curl_off_t clen; + + DEBUGASSERT(r); + DEBUGASSERT(r->crt); + DEBUGASSERT(r->phase == CURL_CR_CLIENT); + DEBUGASSERT(!data->req.reader_stack); + + data->req.reader_stack = r; + clen = r->crt->total_length(data, r); + /* if we do not have 0 length init, and crlf conversion is wanted, + * add the reader for it */ + if(clen && (data->set.crlf +#ifdef CURL_PREFER_LF_LINEENDS + || data->state.prefer_ascii +#endif + )) { + result = cr_lc_add(data); if(result) return result; } -#endif - if(writeheader) { - size_t wrote; - - Curl_set_in_callback(data, true); - wrote = writeheader(optr, 1, olen, data->set.writeheader); - Curl_set_in_callback(data, false); - - if(CURL_WRITEFUNC_PAUSE == wrote) - /* here we pass in the HEADER bit only since if this was body as well - then it was passed already and clearly that didn't trigger the - pause, so this is saved for later with the HEADER bit only */ - return pausewrite(data, CLIENTWRITE_HEADER | - (type & (CLIENTWRITE_STATUS|CLIENTWRITE_CONNECT| - CLIENTWRITE_1XX|CLIENTWRITE_TRAILER)), - optr, olen); - if(wrote != olen) { - failf(data, "Failed writing header"); - return CURLE_WRITE_ERROR; + return result; +} + +CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len) +{ + CURLcode result; + struct Curl_creader *r; + struct cr_in_ctx *ctx; + + result = Curl_creader_create(&r, data, &cr_in, CURL_CR_CLIENT); + if(result) + goto out; + ctx = r->ctx; + ctx->total_len = len; + + cl_reset_reader(data); + result = do_init_reader_stack(data, r); +out: + CURL_TRC_READ(data, "add fread reader, len=%"FMT_OFF_T " -> %d", + len, result); + return result; +} + +CURLcode Curl_creader_add(struct Curl_easy *data, + struct Curl_creader *reader) +{ + CURLcode result; + struct Curl_creader **anchor = &data->req.reader_stack; + + if(!*anchor) { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + return result; + } + + /* Insert the writer as first in its phase. + * Skip existing readers of lower phases. */ + while(*anchor && (*anchor)->phase < reader->phase) + anchor = &((*anchor)->next); + reader->next = *anchor; + *anchor = reader; + return CURLE_OK; +} + +CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r) +{ + CURLcode result; + + DEBUGASSERT(r); + DEBUGASSERT(r->crt); + DEBUGASSERT(r->phase == CURL_CR_CLIENT); + + cl_reset_reader(data); + result = do_init_reader_stack(data, r); + if(result) + Curl_creader_free(data, r); + return result; +} + +CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, + size_t *nread, bool *eos) +{ + CURLcode result; + + DEBUGASSERT(buf); + DEBUGASSERT(blen); + DEBUGASSERT(nread); + DEBUGASSERT(eos); + + if(!data->req.reader_stack) { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + return result; + DEBUGASSERT(data->req.reader_stack); + } + + result = Curl_creader_read(data, data->req.reader_stack, buf, blen, + nread, eos); + CURL_TRC_READ(data, "client_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *nread, *eos); + return result; +} + +bool Curl_creader_needs_rewind(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + if(reader->crt->needs_rewind(data, reader)) { + CURL_TRC_READ(data, "client reader needs rewind before next request"); + return TRUE; } + reader = reader->next; } + return FALSE; +} +static CURLcode cr_null_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + (void)data; + (void)reader; + (void)buf; + (void)blen; + *pnread = 0; + *peos = TRUE; return CURLE_OK; } +static curl_off_t cr_null_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return 0; +} -/* Curl_client_write() sends data to the write callback(s) +static const struct Curl_crtype cr_null = { + "cr-null", + Curl_creader_def_init, + cr_null_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + cr_null_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct Curl_creader) +}; - The bit pattern defines to what "streams" to write to. Body and/or header. - The defines are in sendf.h of course. +CURLcode Curl_creader_set_null(struct Curl_easy *data) +{ + struct Curl_creader *r; + CURLcode result; - If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the - local character encoding. This is a problem and should be changed in - the future to leave the original data alone. - */ -CURLcode Curl_client_write(struct Curl_easy *data, - int type, - char *ptr, - size_t len) -{ -#if !defined(CURL_DISABLE_FTP) && defined(CURL_DO_LINEEND_CONV) - /* FTP data may need conversion. */ - if((type & CLIENTWRITE_BODY) && - (data->conn->handler->protocol & PROTO_FAMILY_FTP) && - data->conn->proto.ftpc.transfertype == 'A') { - /* convert end-of-line markers */ - len = convert_lineends(data, ptr, len); + result = Curl_creader_create(&r, data, &cr_null, CURL_CR_CLIENT); + if(result) + return result; + + cl_reset_reader(data); + return do_init_reader_stack(data, r); +} + +struct cr_buf_ctx { + struct Curl_creader super; + const char *buf; + size_t blen; + size_t index; +}; + +static CURLcode cr_buf_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_buf_ctx *ctx = reader->ctx; + size_t nread = ctx->blen - ctx->index; + + (void)data; + if(!nread || !ctx->buf) { + *pnread = 0; + *peos = TRUE; } -#endif - return chop_write(data, type, ptr, len); + else { + if(nread > blen) + nread = blen; + memcpy(buf, ctx->buf + ctx->index, nread); + *pnread = nread; + ctx->index += nread; + *peos = (ctx->index == ctx->blen); + } + CURL_TRC_READ(data, "cr_buf_read(len=%zu) -> 0, nread=%zu, eos=%d", + blen, *pnread, *peos); + return CURLE_OK; } -/* - * Internal read-from-socket function. This is meant to deal with plain - * sockets, SSL sockets and kerberos sockets. - * - * Returns a regular CURLcode value. - */ -CURLcode Curl_read(struct Curl_easy *data, /* transfer */ - curl_socket_t sockfd, /* read from this socket */ - char *buf, /* store read data here */ - size_t sizerequested, /* max amount to read */ - ssize_t *n) /* amount bytes read */ -{ - CURLcode result = CURLE_RECV_ERROR; - ssize_t nread = 0; - size_t bytesfromsocket = 0; - char *buffertofill = NULL; - struct connectdata *conn = data->conn; - - /* Set 'num' to 0 or 1, depending on which socket that has been sent here. - If it is the second socket, we set num to 1. Otherwise to 0. This lets - us use the correct ssl handle. */ - int num = (sockfd == conn->sock[SECONDARYSOCKET]); - - *n = 0; /* reset amount to zero */ - - bytesfromsocket = CURLMIN(sizerequested, (size_t)data->set.buffer_size); - buffertofill = buf; - - nread = conn->recv[num](data, num, buffertofill, bytesfromsocket, &result); - if(nread < 0) +static bool cr_buf_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + return ctx->index > 0; +} + +static CURLcode cr_buf_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + ctx->index = 0; + return CURLE_OK; +} + +static curl_off_t cr_buf_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + return (curl_off_t)ctx->blen; +} + +static CURLcode cr_buf_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_buf_ctx *ctx = reader->ctx; + size_t boffset; + + (void)data; + DEBUGASSERT(data->conn); + /* already started reading? */ + if(ctx->index) + return CURLE_READ_ERROR; + if(offset <= 0) + return CURLE_OK; + boffset = (size_t)offset; + if(boffset > ctx->blen) + return CURLE_READ_ERROR; + + ctx->buf += boffset; + ctx->blen -= boffset; + return CURLE_OK; +} + +static const struct Curl_crtype cr_buf = { + "cr-buf", + Curl_creader_def_init, + cr_buf_read, + Curl_creader_def_close, + cr_buf_needs_rewind, + cr_buf_total_length, + cr_buf_resume_from, + cr_buf_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_buf_ctx) +}; + +CURLcode Curl_creader_set_buf(struct Curl_easy *data, + const char *buf, size_t blen) +{ + CURLcode result; + struct Curl_creader *r; + struct cr_buf_ctx *ctx; + + result = Curl_creader_create(&r, data, &cr_buf, CURL_CR_CLIENT); + if(result) goto out; + ctx = r->ctx; + ctx->buf = buf; + ctx->blen = blen; + ctx->index = 0; - *n += nread; - result = CURLE_OK; + cl_reset_reader(data); + result = do_init_reader_stack(data, r); out: - /* DEBUGF(infof(data, "Curl_read(handle=%p) -> %d, nread=%ld", - data, result, nread)); */ + CURL_TRC_READ(data, "add buf reader, len=%zu -> %d", blen, result); return result; } +curl_off_t Curl_creader_total_length(struct Curl_easy *data) +{ + struct Curl_creader *r = data->req.reader_stack; + return r ? r->crt->total_length(data, r) : -1; +} + +curl_off_t Curl_creader_client_length(struct Curl_easy *data) +{ + struct Curl_creader *r = data->req.reader_stack; + while(r && r->phase != CURL_CR_CLIENT) + r = r->next; + 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; + CURLcode result = CURLE_OK; + + while(reader) { + result = reader->crt->unpause(data, reader); + if(result) + break; + reader = reader->next; + } + return result; +} + +bool Curl_creader_is_paused(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + + while(reader) { + if(reader->crt->is_paused(data, reader)) + return TRUE; + reader = reader->next; + } + return FALSE; +} + +void Curl_creader_done(struct Curl_easy *data, int premature) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + reader->crt->done(data, reader, premature); + reader = reader->next; + } +} + +struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data, + const struct Curl_crtype *crt) +{ + struct Curl_creader *r; + for(r = data->req.reader_stack; r; r = r->next) { + if(r->crt == crt) + return r; + } + return NULL; + +} diff --git a/Utilities/cmcurl/lib/sendf.h b/Utilities/cmcurl/lib/sendf.h index d0c92757052..e5cc600bfe5 100644 --- a/Utilities/cmcurl/lib/sendf.h +++ b/Utilities/cmcurl/lib/sendf.h @@ -26,29 +26,389 @@ #include "curl_setup.h" -#include "curl_log.h" +#include "curl_trc.h" +/** + * Type of data that is being written to the client (application) + * - data written can be either BODY or META data + * - META data is either INFO or HEADER + * - INFO is meta information, e.g. not BODY, that cannot be interpreted + * as headers of a response. Example FTP/IMAP pingpong answers. + * - HEADER can have additional bits set (more than one) + * - STATUS special "header", e.g. response status line in HTTP + * - CONNECT header was received during proxying the connection + * - 1XX header is part of an intermediate response, e.g. HTTP 1xx code + * - TRAILER header is trailing response data, e.g. HTTP trailers + * BODY, INFO and HEADER should not be mixed, as this would lead to + * confusion on how to interpret/format/convert the data. + */ +#define CLIENTWRITE_BODY (1<<0) /* non-meta information, BODY */ +#define CLIENTWRITE_INFO (1<<1) /* meta information, not a HEADER */ +#define CLIENTWRITE_HEADER (1<<2) /* meta information, HEADER */ +#define CLIENTWRITE_STATUS (1<<3) /* a special status HEADER */ +#define CLIENTWRITE_CONNECT (1<<4) /* a CONNECT related HEADER */ +#define CLIENTWRITE_1XX (1<<5) /* a 1xx response related HEADER */ +#define CLIENTWRITE_TRAILER (1<<6) /* a trailer HEADER */ +#define CLIENTWRITE_EOS (1<<7) /* End Of transfer download Stream */ -#define CLIENTWRITE_BODY (1<<0) -#define CLIENTWRITE_HEADER (1<<1) -#define CLIENTWRITE_STATUS (1<<2) /* the first "header" is the status line */ -#define CLIENTWRITE_CONNECT (1<<3) /* a CONNECT response */ -#define CLIENTWRITE_1XX (1<<4) /* a 1xx response */ -#define CLIENTWRITE_TRAILER (1<<5) /* a trailer header */ -#define CLIENTWRITE_BOTH (CLIENTWRITE_BODY|CLIENTWRITE_HEADER) - -CURLcode Curl_client_write(struct Curl_easy *data, int type, char *ptr, +/** + * Write `len` bytes at `prt` to the client. `type` indicates what + * kind of data is being written. + */ +CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *ptr, size_t len) WARN_UNUSED_RESULT; -/* internal read-function, does plain socket, SSL and krb4 */ -CURLcode Curl_read(struct Curl_easy *data, curl_socket_t sockfd, - char *buf, size_t buffersize, - ssize_t *n); +/** + * Free all resources related to client writing. + */ +void Curl_client_cleanup(struct Curl_easy *data); + +/** + * Reset readers and writer chains, keep rewind information + * when necessary. + */ +void Curl_client_reset(struct Curl_easy *data); + +/** + * A new request is starting, perform any ops like rewinding + * previous readers when needed. + */ +CURLcode Curl_client_start(struct Curl_easy *data); + +/** + * Client Writers - a chain passing transfer BODY data to the client. + * Main application: HTTP and related protocols + * Other uses: monitoring of download progress + * + * Writers in the chain are order by their `phase`. First come all + * writers in CURL_CW_RAW, followed by any in CURL_CW_TRANSFER_DECODE, + * followed by any in CURL_CW_PROTOCOL, etc. + * + * When adding a writer, it is inserted as first in its phase. This means + * the order of adding writers of the same phase matters, but writers for + * different phases may be added in any order. + * + * Writers which do modify the BODY data written are expected to be of + * phases TRANSFER_DECODE or CONTENT_DECODE. The other phases are intended + * for monitoring writers. Which do *not* modify the data but gather + * statistics or update progress reporting. + */ + +/* Phase a writer operates at. */ +typedef enum { + CURL_CW_RAW, /* raw data written, before any decoding */ + CURL_CW_TRANSFER_DECODE, /* remove transfer-encodings */ + CURL_CW_PROTOCOL, /* after transfer, but before content decoding */ + CURL_CW_CONTENT_DECODE, /* remove content-encodings */ + CURL_CW_CLIENT /* data written to client */ +} Curl_cwriter_phase; + +/* Client Writer Type, provides the implementation */ +struct Curl_cwtype { + const char *name; /* writer name. */ + const char *alias; /* writer name alias, maybe NULL. */ + CURLcode (*do_init)(struct Curl_easy *data, + struct Curl_cwriter *writer); + CURLcode (*do_write)(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); + void (*do_close)(struct Curl_easy *data, + struct Curl_cwriter *writer); + size_t cwriter_size; /* sizeof() allocated struct Curl_cwriter */ +}; + +/* Client writer instance, allocated on creation. + * `void *ctx` is the pointer from the allocation of + * the `struct Curl_cwriter` itself. This is suitable for "downcasting" + * by the writers implementation. See https://github.com/curl/curl/pull/13054 + * for the alignment problems that arise otherwise. + */ +struct Curl_cwriter { + const struct Curl_cwtype *cwt; /* type implementation */ + struct Curl_cwriter *next; /* Downstream writer. */ + void *ctx; /* allocated instance pointer */ + Curl_cwriter_phase phase; /* phase at which it operates */ +}; + +/** + * Create a new cwriter instance with given type and phase. Is not + * inserted into the writer chain by this call. + * Invokes `writer->do_init()`. + */ +CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, + struct Curl_easy *data, + const struct Curl_cwtype *ce_handler, + Curl_cwriter_phase phase); + +/** + * Free a cwriter instance. + * Invokes `writer->do_close()`. + */ +void Curl_cwriter_free(struct Curl_easy *data, + struct Curl_cwriter *writer); + +/** + * Count the number of writers installed of the given phase. + */ +size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase); + +/** + * Adds a writer to the transfer's writer chain. + * The writers `phase` determines where in the chain it is inserted. + */ +CURLcode Curl_cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer); + +/** + * Look up an installed client writer on `data` by its type. + * @return first writer with that type or NULL + */ +struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, + const struct Curl_cwtype *cwt); + +struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, + const char *name); + +/** + * Convenience method for calling `writer->do_write()` that + * checks for NULL writer. + */ +CURLcode Curl_cwriter_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); + +/** + * Return TRUE iff client writer is paused. + */ +bool Curl_cwriter_is_paused(struct Curl_easy *data); + +bool Curl_cwriter_is_content_decoding(struct Curl_easy *data); + +/** + * Unpause client writer and flush any buffered date to the client. + */ +CURLcode Curl_cwriter_unpause(struct Curl_easy *data); + +/** + * Default implementations for do_init, do_write, do_close that + * do nothing and pass the data through. + */ +CURLcode Curl_cwriter_def_init(struct Curl_easy *data, + struct Curl_cwriter *writer); +CURLcode Curl_cwriter_def_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +void Curl_cwriter_def_close(struct Curl_easy *data, + struct Curl_cwriter *writer); + + + +/* Client Reader Type, provides the implementation */ +struct Curl_crtype { + const char *name; /* writer name. */ + CURLcode (*do_init)(struct Curl_easy *data, struct Curl_creader *reader); + CURLcode (*do_read)(struct Curl_easy *data, struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos); + void (*do_close)(struct Curl_easy *data, struct Curl_creader *reader); + bool (*needs_rewind)(struct Curl_easy *data, struct Curl_creader *reader); + curl_off_t (*total_length)(struct Curl_easy *data, + struct Curl_creader *reader); + CURLcode (*resume_from)(struct Curl_easy *data, + struct Curl_creader *reader, curl_off_t offset); + CURLcode (*rewind)(struct Curl_easy *data, struct Curl_creader *reader); + CURLcode (*unpause)(struct Curl_easy *data, struct Curl_creader *reader); + bool (*is_paused)(struct Curl_easy *data, struct Curl_creader *reader); + void (*done)(struct Curl_easy *data, + struct Curl_creader *reader, int premature); + size_t creader_size; /* sizeof() allocated struct Curl_creader */ +}; + +/* Phase a reader operates at. */ +typedef enum { + CURL_CR_NET, /* data send to the network (connection filters) */ + CURL_CR_TRANSFER_ENCODE, /* add transfer-encodings */ + CURL_CR_PROTOCOL, /* before transfer, but after content decoding */ + CURL_CR_CONTENT_ENCODE, /* add content-encodings */ + CURL_CR_CLIENT /* data read from client */ +} Curl_creader_phase; + +/* Client reader instance, allocated on creation. + * `void *ctx` is the pointer from the allocation of + * the `struct Curl_cwriter` itself. This is suitable for "downcasting" + * by the writers implementation. See https://github.com/curl/curl/pull/13054 + * for the alignment problems that arise otherwise. + */ +struct Curl_creader { + const struct Curl_crtype *crt; /* type implementation */ + struct Curl_creader *next; /* Downstream reader. */ + void *ctx; + Curl_creader_phase phase; /* phase at which it operates */ +}; + +/** + * Default implementations for do_init, do_write, do_close that + * do nothing and pass the data through. + */ +CURLcode Curl_creader_def_init(struct Curl_easy *data, + struct Curl_creader *reader); +void Curl_creader_def_close(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos); +bool Curl_creader_def_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader); +curl_off_t Curl_creader_def_total_length(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset); +CURLcode Curl_creader_def_rewind(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_unpause(struct Curl_easy *data, + struct Curl_creader *reader); +bool Curl_creader_def_is_paused(struct Curl_easy *data, + struct Curl_creader *reader); +void Curl_creader_def_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature); + +/** + * Convenience method for calling `reader->do_read()` that + * checks for NULL reader. + */ +CURLcode Curl_creader_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos); + +/** + * Create a new creader instance with given type and phase. Is not + * inserted into the writer chain by this call. + * Invokes `reader->do_init()`. + */ +CURLcode Curl_creader_create(struct Curl_creader **preader, + struct Curl_easy *data, + const struct Curl_crtype *cr_handler, + Curl_creader_phase phase); + +/** + * Free a creader instance. + * Invokes `reader->do_close()`. + */ +void Curl_creader_free(struct Curl_easy *data, struct Curl_creader *reader); + +/** + * Adds a reader to the transfer's reader chain. + * The readers `phase` determines where in the chain it is inserted. + */ +CURLcode Curl_creader_add(struct Curl_easy *data, + struct Curl_creader *reader); + +/** + * Set the given reader, which needs to be of type CURL_CR_CLIENT, + * as the new first reader. Discard any installed readers and init + * the reader chain anew. + * The function takes ownership of `r`. + */ +CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r); + +/** + * Read at most `blen` bytes at `buf` from the client. + * @param data the transfer to read client bytes for + * @param buf the memory location to read to + * @param blen the amount of memory at `buf` + * @param nread on return the number of bytes read into `buf` + * @param eos TRUE iff bytes are the end of data from client + * @return CURLE_OK on successful read (even 0 length) or error + */ +CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, + size_t *nread, bool *eos) WARN_UNUSED_RESULT; + +/** + * TRUE iff client reader needs rewing before it can be used for + * a retry request. + */ +bool Curl_creader_needs_rewind(struct Curl_easy *data); + +/** + * TRUE iff client reader will rewind at next start + */ +bool Curl_creader_will_rewind(struct Curl_easy *data); + +/** + * En-/disable rewind of client reader at next start. + */ +void Curl_creader_set_rewind(struct Curl_easy *data, bool enable); + +/** + * Get the total length of bytes provided by the installed readers. + * This is independent of the amount already delivered and is calculated + * by all readers in the stack. If a reader like "chunked" or + * "crlf conversion" is installed, the returned length will be -1. + * @return -1 if length is indeterminate + */ +curl_off_t Curl_creader_total_length(struct Curl_easy *data); + +/** + * Get the total length of bytes provided by the reader at phase + * CURL_CR_CLIENT. This may not match the amount of bytes read + * for a request, depending if other, encoding readers are also installed. + * However it allows for rough estimation of the overall length. + * @return -1 if length is indeterminate + */ +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 then 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. + */ +CURLcode Curl_creader_unpause(struct Curl_easy *data); + +/** + * Return TRUE iff any of the installed readers is paused. + */ +bool Curl_creader_is_paused(struct Curl_easy *data); + +/** + * Tell all client readers that they are done. + */ +void Curl_creader_done(struct Curl_easy *data, int premature); + +/** + * Look up an installed client reader on `data` by its type. + * @return first reader with that type or NULL + */ +struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data, + const struct Curl_crtype *crt); + + +/** + * Set the client reader to provide 0 bytes, immediate EOS. + */ +CURLcode Curl_creader_set_null(struct Curl_easy *data); + +/** + * Set the client reader the reads from fread callback. + */ +CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len); -/* internal write-function, does plain socket, SSL, SCP, SFTP and krb4 */ -CURLcode Curl_write(struct Curl_easy *data, - curl_socket_t sockfd, - const void *mem, size_t len, - ssize_t *written); +/** + * Set the client reader the reads from the supplied buf (NOT COPIED). + */ +CURLcode Curl_creader_set_buf(struct Curl_easy *data, + const char *buf, size_t blen); #endif /* HEADER_CURL_SENDF_H */ diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c index 0c3b9634d11..d5ddf6d0c26 100644 --- a/Utilities/cmcurl/lib/setopt.c +++ b/Utilities/cmcurl/lib/setopt.c @@ -43,13 +43,17 @@ #include "strcase.h" #include "share.h" #include "vtls/vtls.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "sendf.h" +#include "hostip.h" #include "http2.h" #include "setopt.h" #include "multiif.h" #include "altsvc.h" #include "hsts.h" +#include "tftp.h" +#include "strdup.h" +#include "escape.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -108,52 +112,75 @@ CURLcode Curl_setblobopt(struct curl_blob **blobp, static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp) { - CURLcode result = CURLE_OK; char *user = NULL; char *passwd = NULL; + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); + /* Parse the login details if specified. It not then we treat NULL as a hint to clear the existing data */ if(option) { size_t len = strlen(option); + CURLcode result; if(len > CURL_MAX_INPUT_LENGTH) return CURLE_BAD_FUNCTION_ARGUMENT; - result = Curl_parse_login_details(option, len, - (userp ? &user : NULL), - (passwdp ? &passwd : NULL), - NULL); + result = Curl_parse_login_details(option, len, &user, &passwd, NULL); + if(result) + return result; } - if(!result) { - /* Store the username part of option if required */ - if(userp) { - if(!user && option && option[0] == ':') { - /* Allocate an empty string instead of returning NULL as user name */ - user = strdup(""); - if(!user) - result = CURLE_OUT_OF_MEMORY; - } + free(*userp); + *userp = user; - Curl_safefree(*userp); - *userp = user; - } + free(*passwdp); + *passwdp = passwd; - /* Store the password part of option if required */ - if(passwdp) { - Curl_safefree(*passwdp); - *passwdp = passwd; - } + return CURLE_OK; +} + +static CURLcode setstropt_interface(char *option, char **devp, + char **ifacep, char **hostp) +{ + char *dev = NULL; + char *iface = NULL; + char *host = NULL; + CURLcode result; + + DEBUGASSERT(devp); + DEBUGASSERT(ifacep); + DEBUGASSERT(hostp); + + if(option) { + /* Parse the interface details if set, otherwise clear them all */ + result = Curl_parse_interface(option, &dev, &iface, &host); + if(result) + return result; } + free(*devp); + *devp = dev; - return result; + free(*ifacep); + *ifacep = iface; + + free(*hostp); + *hostp = host; + + return CURLE_OK; } #define C_SSLVERSION_VALUE(x) (x & 0xffff) -#define C_SSLVERSION_MAX_VALUE(x) (x & 0xffff0000) +#define C_SSLVERSION_MAX_VALUE(x) ((unsigned long)x & 0xffff0000) static CURLcode protocol2num(const char *str, curl_prot_t *val) { + /* + * We are asked to cherry-pick protocols, so play it safe and disallow all + * protocols to start with, and re-add the wanted ones back in. + */ + *val = 0; + if(!str) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -162,16 +189,14 @@ static CURLcode protocol2num(const char *str, curl_prot_t *val) return CURLE_OK; } - *val = 0; - do { const char *token = str; size_t tlen; str = strchr(str, ','); - tlen = str? (size_t) (str - token): strlen(token); + tlen = str ? (size_t) (str - token) : strlen(token); if(tlen) { - const struct Curl_handler *h = Curl_builtin_scheme(token, tlen); + const struct Curl_handler *h = Curl_getn_scheme_handler(token, tlen); if(!h) return CURLE_UNSUPPORTED_PROTOCOL; @@ -186,21 +211,209 @@ static CURLcode protocol2num(const char *str, curl_prot_t *val) return CURLE_OK; } -/* - * Do not make Curl_vsetopt() static: it is called from - * packages/OS400/ccsidcurl.c. - */ -CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) +static CURLcode httpauth(struct Curl_easy *data, bool proxy, + unsigned long auth) { - char *argptr; - CURLcode result = CURLE_OK; - long arg; - unsigned long uarg; - curl_off_t bigsize; + if(auth != CURLAUTH_NONE) { + int bitcheck = 0; + bool authbits = FALSE; + /* the DIGEST_IE bit is only used to set a special marker, for all the + rest we need to handle it as normal DIGEST */ + bool iestyle = !!(auth & CURLAUTH_DIGEST_IE); + if(proxy) + data->state.authproxy.iestyle = iestyle; + else + data->state.authhost.iestyle = iestyle; + + if(auth & CURLAUTH_DIGEST_IE) { + auth |= CURLAUTH_DIGEST; /* set standard digest bit */ + auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ + } + + /* switch off bits we cannot support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM support */ +#endif +#ifndef USE_SPNEGO + auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without GSS-API + or SSPI */ +#endif + + /* check if any auth bit lower than CURLAUTH_ONLY is still set */ + while(bitcheck < 31) { + if(auth & (1UL << bitcheck++)) { + authbits = TRUE; + break; + } + } + if(!authbits) + return CURLE_NOT_BUILT_IN; /* no supported types left! */ + } + if(proxy) + data->set.proxyauth = auth; + else + data->set.httpauth = auth; + return CURLE_OK; +} + +#ifndef CURL_DISABLE_HTTP +static CURLcode setopt_HTTP_VERSION(struct Curl_easy *data, long arg) +{ + /* + * This sets a requested HTTP version to be used. The value is one of + * the listed enums in curl/curl.h. + */ + switch(arg) { + case CURL_HTTP_VERSION_NONE: + /* accepted */ + break; + case CURL_HTTP_VERSION_1_0: + case CURL_HTTP_VERSION_1_1: + /* accepted */ + break; +#ifdef USE_HTTP2 + case CURL_HTTP_VERSION_2_0: + case CURL_HTTP_VERSION_2TLS: + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + /* accepted */ + break; +#endif +#ifdef USE_HTTP3 + case CURL_HTTP_VERSION_3: + case CURL_HTTP_VERSION_3ONLY: + /* accepted */ + break; +#endif + default: + /* not accepted */ + if(arg < CURL_HTTP_VERSION_NONE) + return CURLE_BAD_FUNCTION_ARGUMENT; + return CURLE_UNSUPPORTED_PROTOCOL; + } + data->set.httpwant = (unsigned char)arg; + return CURLE_OK; +} +#endif /* ! CURL_DISABLE_HTTP */ + +#ifdef USE_SSL +static CURLcode setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, + long arg) +{ + /* + * Set explicit SSL version to try to connect with, as some SSL + * implementations are lame. + */ + { + long version, version_max; + struct ssl_primary_config *primary = &data->set.ssl.primary; +#ifndef CURL_DISABLE_PROXY + if(option != CURLOPT_SSLVERSION) + primary = &data->set.proxy_ssl.primary; +#else + if(option) {} +#endif + version = C_SSLVERSION_VALUE(arg); + version_max = (long)C_SSLVERSION_MAX_VALUE(arg); + + if(version < CURL_SSLVERSION_DEFAULT || + version == CURL_SSLVERSION_SSLv2 || + version == CURL_SSLVERSION_SSLv3 || + version >= CURL_SSLVERSION_LAST || + version_max < CURL_SSLVERSION_MAX_NONE || + version_max >= CURL_SSLVERSION_MAX_LAST) + return CURLE_BAD_FUNCTION_ARGUMENT; + + primary->version = (unsigned char)version; + primary->version_max = (unsigned int)version_max; + } + return CURLE_OK; +} +#endif /* ! USE_SSL */ + +#ifndef CURL_DISABLE_RTSP +static CURLcode setopt_RTSP_REQUEST(struct Curl_easy *data, long arg) +{ + /* + * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) + * Would this be better if the RTSPREQ_* were just moved into here? + */ + Curl_RtspReq rtspreq = RTSPREQ_NONE; + switch(arg) { + case CURL_RTSPREQ_OPTIONS: + rtspreq = RTSPREQ_OPTIONS; + break; + + case CURL_RTSPREQ_DESCRIBE: + rtspreq = RTSPREQ_DESCRIBE; + break; + + case CURL_RTSPREQ_ANNOUNCE: + rtspreq = RTSPREQ_ANNOUNCE; + break; + + case CURL_RTSPREQ_SETUP: + rtspreq = RTSPREQ_SETUP; + break; + + case CURL_RTSPREQ_PLAY: + rtspreq = RTSPREQ_PLAY; + break; + + case CURL_RTSPREQ_PAUSE: + rtspreq = RTSPREQ_PAUSE; + break; + + case CURL_RTSPREQ_TEARDOWN: + rtspreq = RTSPREQ_TEARDOWN; + break; + + case CURL_RTSPREQ_GET_PARAMETER: + rtspreq = RTSPREQ_GET_PARAMETER; + break; + + case CURL_RTSPREQ_SET_PARAMETER: + rtspreq = RTSPREQ_SET_PARAMETER; + break; + + case CURL_RTSPREQ_RECORD: + rtspreq = RTSPREQ_RECORD; + break; + + case CURL_RTSPREQ_RECEIVE: + rtspreq = RTSPREQ_RECEIVE; + break; + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + data->set.rtspreq = rtspreq; + return CURLE_OK; +} +#endif /* ! CURL_DISABLE_RTSP */ + +#ifdef USE_SSL +static void set_ssl_options(struct ssl_config_data *ssl, + struct ssl_primary_config *config, + long arg) +{ + config->ssl_options = (unsigned char)(arg & 0xff); + ssl->enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); + ssl->no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); + ssl->no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); + ssl->revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); + ssl->native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); + ssl->auto_client_cert = !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); + ssl->earlydata = !!(arg & CURLSSLOPT_EARLYDATA); +} +#endif +static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, + long arg) +{ + bool enabled = (0 != arg); + unsigned long uarg = (unsigned long)arg; switch(option) { case CURLOPT_DNS_CACHE_TIMEOUT: - arg = va_arg(param, long); if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; else if(arg > INT_MAX) @@ -209,105 +422,64 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.dns_cache_timeout = (int)arg; break; case CURLOPT_CA_CACHE_TIMEOUT: - arg = va_arg(param, long); - if(arg < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - else if(arg > INT_MAX) - arg = INT_MAX; + if(Curl_ssl_supports(data, SSLSUPP_CA_CACHE)) { + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; - data->set.general_ssl.ca_cache_timeout = (int)arg; - break; - case CURLOPT_DNS_USE_GLOBAL_CACHE: - /* deprecated */ - break; - case CURLOPT_SSL_CIPHER_LIST: - /* set a list of cipher we want to use in the SSL connection */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], - va_arg(param, char *)); - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_CIPHER_LIST: - /* set a list of cipher we want to use in the SSL connection for proxy */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_PROXY], - va_arg(param, char *)); - break; -#endif - case CURLOPT_TLS13_CIPHERS: - if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { - /* set preferred list of TLS 1.3 cipher suites */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST], - va_arg(param, char *)); - } - else - return CURLE_NOT_BUILT_IN; - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_TLS13_CIPHERS: - if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { - /* set preferred list of TLS 1.3 cipher suites for proxy */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST_PROXY], - va_arg(param, char *)); + data->set.general_ssl.ca_cache_timeout = (int)arg; } else return CURLE_NOT_BUILT_IN; break; -#endif - case CURLOPT_RANDOM_FILE: - break; - case CURLOPT_EGDSOCKET: - break; case CURLOPT_MAXCONNECTS: /* * Set the absolute number of maximum simultaneous alive connection that * libcurl is allowed to have. */ - arg = va_arg(param, long); - if(arg < 0) + if(uarg > UINT_MAX) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.maxconnects = arg; + data->set.maxconnects = (unsigned int)uarg; break; case CURLOPT_FORBID_REUSE: /* * When this transfer is done, it must not be left to be reused by a * subsequent transfer but shall be closed immediately. */ - data->set.reuse_forbid = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.reuse_forbid = enabled; break; case CURLOPT_FRESH_CONNECT: /* * This transfer shall not use a previously cached connection but * should be made with a fresh new connect! */ - data->set.reuse_fresh = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.reuse_fresh = enabled; break; case CURLOPT_VERBOSE: /* * Verbose means infof() calls that give a lot of information about * the connection and transfer procedures as well as internal choices. */ - data->set.verbose = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.verbose = enabled; break; case CURLOPT_HEADER: /* * Set to include the header in the general data output stream. */ - data->set.include_header = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.include_header = enabled; break; case CURLOPT_NOPROGRESS: /* * Shut off the internal supported progress meter */ - data->set.hide_progress = (0 != va_arg(param, long)) ? TRUE : FALSE; - if(data->set.hide_progress) - data->progress.flags |= PGRS_HIDE; - else - data->progress.flags &= ~PGRS_HIDE; + data->progress.hide = enabled; break; case CURLOPT_NOBODY: /* * Do not include the body part in the output data stream. */ - data->set.opt_no_body = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.opt_no_body = enabled; #ifndef CURL_DISABLE_HTTP if(data->set.opt_no_body) /* in HTTP lingo, no body means using the HEAD request... */ @@ -318,14 +490,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; case CURLOPT_FAILONERROR: /* - * Don't output the >=400 error code HTML-page, but instead only + * Do not output the >=400 error code HTML-page, but instead only * return error. */ - data->set.http_fail_on_error = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.http_fail_on_error = enabled; break; case CURLOPT_KEEP_SENDING_ON_ERROR: - data->set.http_keep_sending_on_error = (0 != va_arg(param, long)) ? - TRUE : FALSE; + data->set.http_keep_sending_on_error = enabled; break; case CURLOPT_UPLOAD: case CURLOPT_PUT: @@ -333,7 +504,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * We want to sent data to the remote host. If this is HTTP, that equals * using the PUT request. */ - arg = va_arg(param, long); if(arg) { /* If this is HTTP, PUT is what's needed to "upload" */ data->set.method = HTTPREQ_PUT; @@ -344,43 +514,49 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) then this can be changed to HEAD later on) */ data->set.method = HTTPREQ_GET; break; - case CURLOPT_REQUEST_TARGET: - result = Curl_setstropt(&data->set.str[STRING_TARGET], - va_arg(param, char *)); - break; case CURLOPT_FILETIME: /* * Try to get the file time of the remote document. The time will * later (possibly) become available using curl_easy_getinfo(). */ - data->set.get_filetime = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.get_filetime = enabled; break; case CURLOPT_SERVER_RESPONSE_TIMEOUT: /* * Option that specifies how quickly a server response must be obtained * before it is considered failure. For pingpong protocols. */ - arg = va_arg(param, long); if((arg >= 0) && (arg <= (INT_MAX/1000))) data->set.server_response_timeout = (unsigned int)arg * 1000; else return CURLE_BAD_FUNCTION_ARGUMENT; break; + case CURLOPT_SERVER_RESPONSE_TIMEOUT_MS: + /* + * Option that specifies how quickly a server response must be obtained + * before it is considered failure. For pingpong protocols. + */ + if((arg >= 0) && (arg <= INT_MAX)) + data->set.server_response_timeout = (unsigned int)arg; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + break; #ifndef CURL_DISABLE_TFTP case CURLOPT_TFTP_NO_OPTIONS: /* * Option that prevents libcurl from sending TFTP option requests to the * server. */ - data->set.tftp_no_options = va_arg(param, long) != 0; + data->set.tftp_no_options = enabled; break; case CURLOPT_TFTP_BLKSIZE: /* * TFTP option that specifies the block size to use for data transmission. */ - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(arg < TFTP_BLKSIZE_MIN) + arg = 512; + else if(arg > TFTP_BLKSIZE_MAX) + arg = TFTP_BLKSIZE_MAX; data->set.tftp_blksize = arg; break; #endif @@ -389,18 +565,10 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * Parse the $HOME/.netrc file */ - arg = va_arg(param, long); if((arg < CURL_NETRC_IGNORED) || (arg >= CURL_NETRC_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.use_netrc = (unsigned char)arg; break; - case CURLOPT_NETRC_FILE: - /* - * Use this file instead of the $HOME/.netrc file - */ - result = Curl_setstropt(&data->set.str[STRING_NETRC_FILE], - va_arg(param, char *)); - break; #endif case CURLOPT_TRANSFERTEXT: /* @@ -409,211 +577,81 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * * Transfer using ASCII (instead of BINARY). */ - data->set.prefer_ascii = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.prefer_ascii = enabled; break; case CURLOPT_TIMECONDITION: /* * Set HTTP time condition. This must be one of the defines in the * curl/curl.h header file. */ - arg = va_arg(param, long); if((arg < CURL_TIMECOND_NONE) || (arg >= CURL_TIMECOND_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.timecondition = (unsigned char)(curl_TimeCond)arg; + data->set.timecondition = (unsigned char)arg; break; case CURLOPT_TIMEVALUE: /* * This is the value to compare with the remote document with the * method set with CURLOPT_TIMECONDITION */ - data->set.timevalue = (time_t)va_arg(param, long); - break; - - case CURLOPT_TIMEVALUE_LARGE: - /* - * This is the value to compare with the remote document with the - * method set with CURLOPT_TIMECONDITION - */ - data->set.timevalue = (time_t)va_arg(param, curl_off_t); + data->set.timevalue = (time_t)arg; break; - case CURLOPT_SSLVERSION: #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSLVERSION: #endif - /* - * Set explicit SSL version to try to connect with, as some SSL - * implementations are lame. - */ #ifdef USE_SSL - { - long version, version_max; - struct ssl_primary_config *primary = &data->set.ssl.primary; -#ifndef CURL_DISABLE_PROXY - if(option != CURLOPT_SSLVERSION) - primary = &data->set.proxy_ssl.primary; -#endif - - arg = va_arg(param, long); - - version = C_SSLVERSION_VALUE(arg); - version_max = C_SSLVERSION_MAX_VALUE(arg); - - if(version < CURL_SSLVERSION_DEFAULT || - version == CURL_SSLVERSION_SSLv2 || - version == CURL_SSLVERSION_SSLv3 || - version >= CURL_SSLVERSION_LAST || - version_max < CURL_SSLVERSION_MAX_NONE || - version_max >= CURL_SSLVERSION_MAX_LAST) - return CURLE_BAD_FUNCTION_ARGUMENT; - - primary->version = (unsigned char)version; - primary->version_max = (unsigned int)version_max; - } + return setopt_SSLVERSION(data, option, arg); #else - result = CURLE_NOT_BUILT_IN; + return CURLE_NOT_BUILT_IN; #endif - break; - - /* MQTT "borrows" some of the HTTP options */ -#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) - case CURLOPT_COPYPOSTFIELDS: - /* - * A string with POST data. Makes curl HTTP POST. Even if it is NULL. - * If needed, CURLOPT_POSTFIELDSIZE must have been set prior to - * CURLOPT_COPYPOSTFIELDS and not altered later. - */ - argptr = va_arg(param, char *); - - if(!argptr || data->set.postfieldsize == -1) - result = Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], argptr); - else { - /* - * Check that requested length does not overflow the size_t type. - */ - - if((data->set.postfieldsize < 0) || - ((sizeof(curl_off_t) != sizeof(size_t)) && - (data->set.postfieldsize > (curl_off_t)((size_t)-1)))) - result = CURLE_OUT_OF_MEMORY; - else { - char *p; - - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - - /* Allocate even when size == 0. This satisfies the need of possible - later address compare to detect the COPYPOSTFIELDS mode, and - to mark that postfields is used rather than read function or - form data. - */ - p = malloc((size_t)(data->set.postfieldsize? - data->set.postfieldsize:1)); - - if(!p) - result = CURLE_OUT_OF_MEMORY; - else { - if(data->set.postfieldsize) - memcpy(p, argptr, (size_t)data->set.postfieldsize); - - data->set.str[STRING_COPYPOSTFIELDS] = p; - } - } - } - - data->set.postfields = data->set.str[STRING_COPYPOSTFIELDS]; - data->set.method = HTTPREQ_POST; - break; - - case CURLOPT_POSTFIELDS: - /* - * Like above, but use static data instead of copying it. - */ - data->set.postfields = va_arg(param, void *); - /* Release old copied data. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.method = HTTPREQ_POST; - break; case CURLOPT_POSTFIELDSIZE: /* * The size of the POSTFIELD data to prevent libcurl to do strlen() to * figure it out. Enables binary posts. */ - bigsize = va_arg(param, long); - if(bigsize < -1) + if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - if(data->set.postfieldsize < bigsize && + if(data->set.postfieldsize < arg && data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); data->set.postfields = NULL; } - data->set.postfieldsize = bigsize; + data->set.postfieldsize = arg; break; - - case CURLOPT_POSTFIELDSIZE_LARGE: +#ifndef CURL_DISABLE_HTTP +#if !defined(CURL_DISABLE_COOKIES) + case CURLOPT_COOKIESESSION: /* - * The size of the POSTFIELD data to prevent libcurl to do strlen() to - * figure it out. Enables binary posts. + * Set this option to TRUE to start a new "cookie session". It will + * prevent the forthcoming read-cookies-from-file actions to accept + * cookies that are marked as being session cookies, as they belong to a + * previous session. */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - - if(data->set.postfieldsize < bigsize && - data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { - /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.postfields = NULL; - } - - data->set.postfieldsize = bigsize; + data->set.cookiesession = enabled; break; #endif -#ifndef CURL_DISABLE_HTTP case CURLOPT_AUTOREFERER: /* * Switch on automatic referer that gets set if curl follows locations. */ - data->set.http_auto_referer = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_ACCEPT_ENCODING: - /* - * String to use at the value of Accept-Encoding header. - * - * If the encoding is set to "" we use an Accept-Encoding header that - * encompasses all the encodings we support. - * If the encoding is set to NULL we don't send an Accept-Encoding header - * and ignore an received Content-Encoding header. - * - */ - argptr = va_arg(param, char *); - if(argptr && !*argptr) { - argptr = Curl_all_content_encodings(); - if(!argptr) - result = CURLE_OUT_OF_MEMORY; - else { - result = Curl_setstropt(&data->set.str[STRING_ENCODING], argptr); - free(argptr); - } - } - else - result = Curl_setstropt(&data->set.str[STRING_ENCODING], argptr); + data->set.http_auto_referer = enabled; break; case CURLOPT_TRANSFER_ENCODING: - data->set.http_transfer_encoding = (0 != va_arg(param, long)) ? - TRUE : FALSE; + data->set.http_transfer_encoding = enabled; break; case CURLOPT_FOLLOWLOCATION: /* * Follow Location: header hints on an HTTP-server. */ - data->set.http_follow_location = (0 != va_arg(param, long)) ? TRUE : FALSE; + if(uarg > 3) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.http_follow_mode = (unsigned char)uarg; break; case CURLOPT_UNRESTRICTED_AUTH: @@ -621,8 +659,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * Send authentication (user+password) when following locations, even when * hostname changed. */ - data->set.allow_auth_to_other_hosts = - (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.allow_auth_to_other_hosts = enabled; break; case CURLOPT_MAXREDIRS: @@ -630,7 +667,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * The maximum amount of hops you allow curl to follow Location: * headers. This should mostly be used to detect never-ending loops. */ - arg = va_arg(param, long); if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.maxredirs = arg; @@ -646,7 +682,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * CURL_REDIR_POST_ALL - POST is kept as POST after 301, 302 and 303 * other - POST is kept as POST after 301 and 302 */ - arg = va_arg(param, long); if(arg < CURL_REDIR_GET_ALL) /* no return error on too high numbers since the bitmask could be extended in a future */ @@ -656,1015 +691,883 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_POST: /* Does this option serve a purpose anymore? Yes it does, when - CURLOPT_POSTFIELDS isn't used and the POST data is read off the + CURLOPT_POSTFIELDS is not used and the POST data is read off the callback! */ - if(va_arg(param, long)) { + if(arg) { data->set.method = HTTPREQ_POST; data->set.opt_no_body = FALSE; /* this is implied */ } else data->set.method = HTTPREQ_GET; break; - -#ifndef CURL_DISABLE_MIME - case CURLOPT_HTTPPOST: + case CURLOPT_HEADEROPT: /* - * Set to make us do HTTP POST + * Set header option. */ - data->set.httppost = va_arg(param, struct curl_httppost *); - data->set.method = HTTPREQ_POST_FORM; - data->set.opt_no_body = FALSE; /* this is implied */ + data->set.sep_headers = !!(arg & CURLHEADER_SEPARATE); break; -#endif + case CURLOPT_HTTPAUTH: + return httpauth(data, FALSE, uarg); - case CURLOPT_AWS_SIGV4: - /* - * String that is merged to some authentication - * parameters are used by the algorithm. - */ - result = Curl_setstropt(&data->set.str[STRING_AWS_SIGV4], - va_arg(param, char *)); + case CURLOPT_HTTPGET: /* - * Basic been set by default it need to be unset here + * Set to force us do HTTP GET */ - if(data->set.str[STRING_AWS_SIGV4]) - data->set.httpauth = CURLAUTH_AWS_SIGV4; + if(enabled) { + data->set.method = HTTPREQ_GET; + data->set.opt_no_body = FALSE; /* this is implied */ + } break; - case CURLOPT_REFERER: + case CURLOPT_HTTP_VERSION: + return setopt_HTTP_VERSION(data, arg); + + case CURLOPT_EXPECT_100_TIMEOUT_MS: /* - * String to set in the HTTP Referer: field. + * Time to wait for a response to an HTTP request containing an + * Expect: 100-continue header before sending the data anyway. */ - if(data->state.referer_alloc) { - Curl_safefree(data->state.referer); - data->state.referer_alloc = FALSE; - } - result = Curl_setstropt(&data->set.str[STRING_SET_REFERER], - va_arg(param, char *)); - data->state.referer = data->set.str[STRING_SET_REFERER]; + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.expect_100_timeout = arg; break; - case CURLOPT_USERAGENT: - /* - * String to use in the HTTP User-Agent field - */ - result = Curl_setstropt(&data->set.str[STRING_USERAGENT], - va_arg(param, char *)); + case CURLOPT_HTTP09_ALLOWED: + data->set.http09_allowed = enabled; break; +#endif /* ! CURL_DISABLE_HTTP */ +#ifndef CURL_DISABLE_MIME + case CURLOPT_MIME_OPTIONS: + data->set.mime_formescape = !!(arg & CURLMIMEOPT_FORMESCAPE); + break; +#endif #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXYHEADER: + case CURLOPT_HTTPPROXYTUNNEL: /* - * Set a list with proxy headers to use (or replace internals with) - * - * Since CURLOPT_HTTPHEADER was the only way to set HTTP headers for a - * long time we remain doing it this way until CURLOPT_PROXYHEADER is - * used. As soon as this option has been used, if set to anything but - * NULL, custom headers for proxies are only picked from this list. - * - * Set this option to NULL to restore the previous behavior. + * Tunnel operations through the proxy instead of normal proxy use */ - data->set.proxyheaders = va_arg(param, struct curl_slist *); + data->set.tunnel_thru_httpproxy = enabled; break; -#endif - case CURLOPT_HEADEROPT: + + case CURLOPT_PROXYPORT: /* - * Set header option. + * Explicitly set HTTP proxy port number. */ - arg = va_arg(param, long); - data->set.sep_headers = (bool)((arg & CURLHEADER_SEPARATE)? TRUE: FALSE); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.proxyport = (unsigned short)arg; break; -#if !defined(CURL_DISABLE_COOKIES) - case CURLOPT_COOKIE: + case CURLOPT_PROXYAUTH: + return httpauth(data, TRUE, uarg); + + case CURLOPT_PROXYTYPE: /* - * Cookie string to send to the remote server in the request. + * Set proxy type. */ - result = Curl_setstropt(&data->set.str[STRING_COOKIE], - va_arg(param, char *)); + if((arg < CURLPROXY_HTTP) || (arg > CURLPROXY_SOCKS5_HOSTNAME)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.proxytype = (unsigned char)(curl_proxytype)arg; break; - case CURLOPT_COOKIEFILE: + case CURLOPT_PROXY_TRANSFER_MODE: /* - * Set cookie file to read and parse. Can be used multiple times. + * set transfer mode (;type=) when doing FTP via an HTTP proxy */ - argptr = (char *)va_arg(param, void *); - if(argptr) { - struct curl_slist *cl; - /* general protection against mistakes and abuse */ - if(strlen(argptr) > CURL_MAX_INPUT_LENGTH) - return CURLE_BAD_FUNCTION_ARGUMENT; - /* append the cookie file name to the list of file names, and deal with - them later */ - cl = curl_slist_append(data->set.cookielist, argptr); - if(!cl) { - curl_slist_free_all(data->set.cookielist); - data->set.cookielist = NULL; - return CURLE_OUT_OF_MEMORY; - } - data->set.cookielist = cl; /* store the list for later use */ - } - else { - /* clear the list of cookie files */ - curl_slist_free_all(data->set.cookielist); - data->set.cookielist = NULL; - - if(!data->share || !data->share->cookies) { - /* throw away all existing cookies if this isn't a shared cookie - container */ - Curl_cookie_clearall(data->cookies); - Curl_cookie_cleanup(data->cookies); - } - /* disable the cookie engine */ - data->cookies = NULL; - } + if(uarg > 1) + /* reserve other values for future use */ + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.proxy_transfer_mode = (bool)uarg; break; - - case CURLOPT_COOKIEJAR: + case CURLOPT_SOCKS5_AUTH: + if(uarg & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) + return CURLE_NOT_BUILT_IN; + data->set.socks5auth = (unsigned char)uarg; + break; + case CURLOPT_HAPROXYPROTOCOL: /* - * Set cookie file name to dump all cookies to when we're done. + * Set to send the HAProxy Proxy Protocol header */ - { - struct CookieInfo *newcookies; - result = Curl_setstropt(&data->set.str[STRING_COOKIEJAR], - va_arg(param, char *)); - + data->set.haproxyprotocol = enabled; + break; + case CURLOPT_PROXY_SSL_VERIFYPEER: /* - * Activate the cookie parser. This may or may not already - * have been made. + * Enable peer SSL verifying for proxy. */ - newcookies = Curl_cookie_init(data, NULL, data->cookies, - data->set.cookiesession); - if(!newcookies) - result = CURLE_OUT_OF_MEMORY; - data->cookies = newcookies; - } - break; + data->set.proxy_ssl.primary.verifypeer = enabled; - case CURLOPT_COOKIESESSION: + /* Update the current connection proxy_ssl_config. */ + Curl_ssl_conn_config_update(data, TRUE); + break; + case CURLOPT_PROXY_SSL_VERIFYHOST: /* - * Set this option to TRUE to start a new "cookie session". It will - * prevent the forthcoming read-cookies-from-file actions to accept - * cookies that are marked as being session cookies, as they belong to a - * previous session. - * - * In the original Netscape cookie spec, "session cookies" are cookies - * with no expire date set. RFC2109 describes the same action if no - * 'Max-Age' is set and RFC2965 includes the RFC2109 description and adds - * a 'Discard' action that can enforce the discard even for cookies that - * have a Max-Age. - * - * We run mostly with the original cookie spec, as hardly anyone implements - * anything else. + * Enable verification of the hostname in the peer certificate for proxy */ - data->set.cookiesession = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_COOKIELIST: - argptr = va_arg(param, char *); - - if(!argptr) - break; - - if(strcasecompare(argptr, "ALL")) { - /* clear all cookies */ - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_clearall(data->cookies); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } - else if(strcasecompare(argptr, "SESS")) { - /* clear session cookies */ - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_clearsess(data->cookies); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } - else if(strcasecompare(argptr, "FLUSH")) { - /* flush cookies to file, takes care of the locking */ - Curl_flush_cookies(data, FALSE); - } - else if(strcasecompare(argptr, "RELOAD")) { - /* reload cookies from file */ - Curl_cookie_loadfiles(data); - break; - } - else { - if(!data->cookies) - /* if cookie engine was not running, activate it */ - data->cookies = Curl_cookie_init(data, NULL, NULL, TRUE); - - /* general protection against mistakes and abuse */ - if(strlen(argptr) > CURL_MAX_INPUT_LENGTH) - return CURLE_BAD_FUNCTION_ARGUMENT; - argptr = strdup(argptr); - if(!argptr || !data->cookies) { - result = CURLE_OUT_OF_MEMORY; - free(argptr); - } - else { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - - if(checkprefix("Set-Cookie:", argptr)) - /* HTTP Header format line */ - Curl_cookie_add(data, data->cookies, TRUE, FALSE, argptr + 11, NULL, - NULL, TRUE); - - else - /* Netscape format line */ - Curl_cookie_add(data, data->cookies, FALSE, FALSE, argptr, NULL, - NULL, TRUE); - - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - free(argptr); - } - } + data->set.proxy_ssl.primary.verifyhost = enabled; + /* Update the current connection proxy_ssl_config. */ + Curl_ssl_conn_config_update(data, TRUE); break; -#endif /* !CURL_DISABLE_COOKIES */ +#endif /* ! CURL_DISABLE_PROXY */ - case CURLOPT_HTTPGET: +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + case CURLOPT_SOCKS5_GSSAPI_NEC: /* - * Set to force us do HTTP GET + * Set flag for NEC SOCK5 support */ - if(va_arg(param, long)) { - data->set.method = HTTPREQ_GET; - data->set.opt_no_body = FALSE; /* this is implied */ - } + data->set.socks5_gssapi_nec = enabled; break; - - case CURLOPT_HTTP_VERSION: +#endif +#ifdef CURL_LIST_ONLY_PROTOCOL + case CURLOPT_DIRLISTONLY: /* - * This sets a requested HTTP version to be used. The value is one of - * the listed enums in curl/curl.h. + * An option that changes the command to one that asks for a list only, no + * file info details. Used for FTP, POP3 and SFTP. */ - arg = va_arg(param, long); - switch(arg) { - case CURL_HTTP_VERSION_NONE: -#ifdef USE_HTTP2 - /* TODO: this seems an undesirable quirk to force a behaviour on - * lower implementations that they should recognize independently? */ - arg = CURL_HTTP_VERSION_2TLS; -#endif - /* accepted */ - break; - case CURL_HTTP_VERSION_1_0: - case CURL_HTTP_VERSION_1_1: - /* accepted */ - break; -#ifdef USE_HTTP2 - case CURL_HTTP_VERSION_2_0: - case CURL_HTTP_VERSION_2TLS: - case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: - /* accepted */ - break; -#endif -#ifdef ENABLE_QUIC - case CURL_HTTP_VERSION_3: - case CURL_HTTP_VERSION_3ONLY: - /* accepted */ - break; + data->set.list_only = enabled; + break; #endif - default: - /* not accepted */ - if(arg < CURL_HTTP_VERSION_NONE) - return CURLE_BAD_FUNCTION_ARGUMENT; - return CURLE_UNSUPPORTED_PROTOCOL; - } - data->set.httpwant = (unsigned char)arg; + case CURLOPT_APPEND: + /* + * We want to upload and append to an existing file. Used for FTP and + * SFTP. + */ + data->set.remote_append = enabled; break; - case CURLOPT_EXPECT_100_TIMEOUT_MS: +#ifndef CURL_DISABLE_FTP + case CURLOPT_FTP_FILEMETHOD: /* - * Time to wait for a response to an HTTP request containing an - * Expect: 100-continue header before sending the data anyway. + * How do access files over FTP. */ - arg = va_arg(param, long); - if(arg < 0) + if((arg < CURLFTPMETHOD_DEFAULT) || (arg >= CURLFTPMETHOD_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.expect_100_timeout = arg; + data->set.ftp_filemethod = (unsigned char)arg; + break; + case CURLOPT_FTP_USE_EPRT: + data->set.ftp_use_eprt = enabled; break; - case CURLOPT_HTTP09_ALLOWED: - arg = va_arg(param, unsigned long); - if(arg > 1L) - return CURLE_BAD_FUNCTION_ARGUMENT; -#ifdef USE_HYPER - /* Hyper does not support HTTP/0.9 */ - if(arg) + case CURLOPT_FTP_USE_EPSV: + data->set.ftp_use_epsv = enabled; + break; + + case CURLOPT_FTP_USE_PRET: + data->set.ftp_use_pret = enabled; + break; + + case CURLOPT_FTP_SSL_CCC: + if((arg < CURLFTPSSL_CCC_NONE) || (arg >= CURLFTPSSL_CCC_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; -#else - data->set.http09_allowed = arg ? TRUE : FALSE; -#endif + data->set.ftp_ccc = (unsigned char)arg; break; - case CURLOPT_HTTP200ALIASES: + case CURLOPT_FTP_SKIP_PASV_IP: /* - * Set a list of aliases for HTTP 200 in response header + * Enable or disable FTP_SKIP_PASV_IP, which will disable/enable the + * bypass of the IP address in PASV responses. */ - data->set.http200aliases = va_arg(param, struct curl_slist *); + data->set.ftp_skip_ip = enabled; break; -#endif /* CURL_DISABLE_HTTP */ -#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ - !defined(CURL_DISABLE_IMAP) -# if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MIME) - case CURLOPT_HTTPHEADER: + case CURLOPT_FTPSSLAUTH: /* - * Set a list with HTTP headers to use (or replace internals with) + * Set a specific auth for FTP-SSL transfers. */ - data->set.headers = va_arg(param, struct curl_slist *); + if((arg < CURLFTPAUTH_DEFAULT) || (arg >= CURLFTPAUTH_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ftpsslauth = (unsigned char)(curl_ftpauth)arg; break; -# endif - -# ifndef CURL_DISABLE_MIME - case CURLOPT_MIMEPOST: + case CURLOPT_ACCEPTTIMEOUT_MS: /* - * Set to make us do MIME POST + * The maximum time for curl to wait for FTP server connect */ - result = Curl_mime_set_subparts(&data->set.mimepost, - va_arg(param, curl_mime *), FALSE); - if(!result) { - data->set.method = HTTPREQ_POST_MIME; - data->set.opt_no_body = FALSE; /* this is implied */ - } + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.accepttimeout = (unsigned int)uarg; break; - - case CURLOPT_MIME_OPTIONS: - data->set.mime_options = (unsigned int)va_arg(param, long); + case CURLOPT_WILDCARDMATCH: + data->set.wildcard_enabled = enabled; break; -# endif -#endif - - case CURLOPT_HTTPAUTH: +#endif /* ! CURL_DISABLE_FTP */ +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + case CURLOPT_FTP_CREATE_MISSING_DIRS: /* - * Set HTTP Authentication type BITMASK. + * An FTP/SFTP option that modifies an upload to create missing + * directories on the server. */ - { - int bitcheck; - bool authbits; - unsigned long auth = va_arg(param, unsigned long); - - if(auth == CURLAUTH_NONE) { - data->set.httpauth = auth; - break; - } - - /* the DIGEST_IE bit is only used to set a special marker, for all the - rest we need to handle it as normal DIGEST */ - data->state.authhost.iestyle = - (bool)((auth & CURLAUTH_DIGEST_IE) ? TRUE : FALSE); - - if(auth & CURLAUTH_DIGEST_IE) { - auth |= CURLAUTH_DIGEST; /* set standard digest bit */ - auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ - } - - /* switch off bits we can't support */ -#ifndef USE_NTLM - auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#endif -#ifndef USE_SPNEGO - auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without - GSS-API or SSPI */ -#endif - - /* check if any auth bit lower than CURLAUTH_ONLY is still set */ - bitcheck = 0; - authbits = FALSE; - while(bitcheck < 31) { - if(auth & (1UL << bitcheck++)) { - authbits = TRUE; - break; - } - } - if(!authbits) - return CURLE_NOT_BUILT_IN; /* no supported types left! */ - - data->set.httpauth = auth; - } - break; - - case CURLOPT_CUSTOMREQUEST: + /* reserve other values for future use */ + if((arg < CURLFTP_CREATE_DIR_NONE) || (arg > CURLFTP_CREATE_DIR_RETRY)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ftp_create_missing_dirs = (unsigned char)arg; + break; +#endif /* ! CURL_DISABLE_FTP || USE_SSH */ + case CURLOPT_INFILESIZE: /* - * Set a custom string to use as request + * If known, this should inform curl about the file size of the + * to-be-uploaded file. */ - result = Curl_setstropt(&data->set.str[STRING_CUSTOMREQUEST], - va_arg(param, char *)); - - /* we don't set - data->set.method = HTTPREQ_CUSTOM; - here, we continue as if we were using the already set type - and this just changes the actual request keyword */ + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.filesize = arg; break; - -#ifndef CURL_DISABLE_PROXY - case CURLOPT_HTTPPROXYTUNNEL: + case CURLOPT_LOW_SPEED_LIMIT: /* - * Tunnel operations through the proxy instead of normal proxy use + * The low speed limit that if transfers are below this for + * CURLOPT_LOW_SPEED_TIME, the transfer is aborted. */ - data->set.tunnel_thru_httpproxy = (0 != va_arg(param, long)) ? - TRUE : FALSE; + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.low_speed_limit = arg; break; - - case CURLOPT_PROXYPORT: + case CURLOPT_LOW_SPEED_TIME: /* - * Explicitly set HTTP proxy port number. + * The low speed time that if transfers are below the set + * CURLOPT_LOW_SPEED_LIMIT during this time, the transfer is aborted. + */ + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.low_speed_time = arg; + break; + case CURLOPT_PORT: + /* + * The port number to use when getting the URL. 0 disables it. */ - arg = va_arg(param, long); if((arg < 0) || (arg > 65535)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.proxyport = (unsigned short)arg; + data->set.use_port = (unsigned short)arg; break; - - case CURLOPT_PROXYAUTH: + case CURLOPT_TIMEOUT: /* - * Set HTTP Authentication type BITMASK. + * The maximum time you allow curl to use for a single transfer + * operation. */ - { - int bitcheck; - bool authbits; - unsigned long auth = va_arg(param, unsigned long); - - if(auth == CURLAUTH_NONE) { - data->set.proxyauth = auth; - break; - } - - /* the DIGEST_IE bit is only used to set a special marker, for all the - rest we need to handle it as normal DIGEST */ - data->state.authproxy.iestyle = - (bool)((auth & CURLAUTH_DIGEST_IE) ? TRUE : FALSE); - - if(auth & CURLAUTH_DIGEST_IE) { - auth |= CURLAUTH_DIGEST; /* set standard digest bit */ - auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ - } - /* switch off bits we can't support */ -#ifndef USE_NTLM - auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#endif -#ifndef USE_SPNEGO - auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without - GSS-API or SSPI */ -#endif - - /* check if any auth bit lower than CURLAUTH_ONLY is still set */ - bitcheck = 0; - authbits = FALSE; - while(bitcheck < 31) { - if(auth & (1UL << bitcheck++)) { - authbits = TRUE; - break; - } - } - if(!authbits) - return CURLE_NOT_BUILT_IN; /* no supported types left! */ + if((arg >= 0) && (arg <= (INT_MAX/1000))) + data->set.timeout = (unsigned int)arg * 1000; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + break; - data->set.proxyauth = auth; - } - break; + case CURLOPT_TIMEOUT_MS: + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.timeout = (unsigned int)uarg; + break; - case CURLOPT_PROXY: + case CURLOPT_CONNECTTIMEOUT: /* - * Set proxy server:port to use as proxy. - * - * If the proxy is set to "" (and CURLOPT_SOCKS_PROXY is set to "" or NULL) - * we explicitly say that we don't want to use a proxy - * (even though there might be environment variables saying so). - * - * Setting it to NULL, means no proxy but allows the environment variables - * to decide for us (if CURLOPT_SOCKS_PROXY setting it to NULL). + * The maximum time you allow curl to use to connect. */ - result = Curl_setstropt(&data->set.str[STRING_PROXY], - va_arg(param, char *)); + if((arg >= 0) && (arg <= (INT_MAX/1000))) + data->set.connecttimeout = (unsigned int)arg * 1000; + else + return CURLE_BAD_FUNCTION_ARGUMENT; break; - case CURLOPT_PRE_PROXY: - /* - * Set proxy server:port to use as SOCKS proxy. - * - * If the proxy is set to "" or NULL we explicitly say that we don't want - * to use the socks proxy. - */ - result = Curl_setstropt(&data->set.str[STRING_PRE_PROXY], - va_arg(param, char *)); + case CURLOPT_CONNECTTIMEOUT_MS: + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.connecttimeout = (unsigned int)uarg; break; - case CURLOPT_PROXYTYPE: + case CURLOPT_RESUME_FROM: /* - * Set proxy type. + * Resume transfer at the given file position */ - arg = va_arg(param, long); - if((arg < CURLPROXY_HTTP) || (arg > CURLPROXY_SOCKS5_HOSTNAME)) + if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.proxytype = (unsigned char)(curl_proxytype)arg; + data->set.set_resume_from = arg; break; - case CURLOPT_PROXY_TRANSFER_MODE: + case CURLOPT_CRLF: /* - * set transfer mode (;type=) when doing FTP via an HTTP proxy + * Kludgy option to enable CRLF conversions. Subject for removal. */ - switch(va_arg(param, long)) { - case 0: - data->set.proxy_transfer_mode = FALSE; - break; - case 1: - data->set.proxy_transfer_mode = TRUE; - break; - default: - /* reserve other values for future use */ - result = CURLE_BAD_FUNCTION_ARGUMENT; - break; - } - break; - - case CURLOPT_SOCKS5_AUTH: - data->set.socks5auth = (unsigned char)va_arg(param, unsigned long); - if(data->set.socks5auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) - result = CURLE_NOT_BUILT_IN; + data->set.crlf = enabled; break; -#endif /* CURL_DISABLE_PROXY */ -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - case CURLOPT_SOCKS5_GSSAPI_NEC: +#ifndef CURL_DISABLE_BINDLOCAL + case CURLOPT_LOCALPORT: /* - * Set flag for NEC SOCK5 support + * Set what local port to bind the socket to when performing an operation. */ - data->set.socks5_gssapi_nec = (0 != va_arg(param, long)) ? TRUE : FALSE; + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.localport = curlx_sltous(arg); break; -#endif -#ifndef CURL_DISABLE_PROXY - case CURLOPT_SOCKS5_GSSAPI_SERVICE: - case CURLOPT_PROXY_SERVICE_NAME: + case CURLOPT_LOCALPORTRANGE: /* - * Set proxy authentication service name for Kerberos 5 and SPNEGO + * Set number of local ports to try, starting with CURLOPT_LOCALPORT. */ - result = Curl_setstropt(&data->set.str[STRING_PROXY_SERVICE_NAME], - va_arg(param, char *)); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.localportrange = curlx_sltous(arg); break; #endif - case CURLOPT_SERVICE_NAME: - /* - * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO - */ - result = Curl_setstropt(&data->set.str[STRING_SERVICE_NAME], - va_arg(param, char *)); - break; - case CURLOPT_HEADERDATA: +#ifdef HAVE_GSSAPI + case CURLOPT_GSSAPI_DELEGATION: /* - * Custom pointer to pass the header write callback function + * GSS-API credential delegation bitmask */ - data->set.writeheader = (void *)va_arg(param, void *); + data->set.gssapi_delegation = (unsigned char)uarg& + (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG); break; - case CURLOPT_ERRORBUFFER: +#endif + case CURLOPT_SSL_VERIFYPEER: /* - * Error buffer provided by the caller to get the human readable - * error string in. + * Enable peer SSL verifying. */ - data->set.errorbuffer = va_arg(param, char *); + data->set.ssl.primary.verifypeer = enabled; + + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); break; - case CURLOPT_WRITEDATA: +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_SSL_VERIFYPEER: /* - * FILE pointer to write to. Or possibly - * used as argument to the write callback. + * Enable peer SSL verifying for DoH. */ - data->set.out = va_arg(param, void *); + data->set.doh_verifypeer = enabled; break; - - case CURLOPT_DIRLISTONLY: + case CURLOPT_DOH_SSL_VERIFYHOST: /* - * An option that changes the command to one that asks for a list only, no - * file info details. Used for FTP, POP3 and SFTP. + * Enable verification of the hostname in the peer certificate for DoH */ - data->set.list_only = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.doh_verifyhost = enabled; break; - - case CURLOPT_APPEND: + case CURLOPT_DOH_SSL_VERIFYSTATUS: /* - * We want to upload and append to an existing file. Used for FTP and - * SFTP. + * Enable certificate status verifying for DoH. */ - data->set.remote_append = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; + if(!Curl_ssl_cert_status_request()) + return CURLE_NOT_BUILT_IN; -#ifndef CURL_DISABLE_FTP - case CURLOPT_FTP_FILEMETHOD: + data->set.doh_verifystatus = enabled; + break; +#endif /* ! CURL_DISABLE_DOH */ + case CURLOPT_SSL_VERIFYHOST: /* - * How do access files over FTP. + * Enable verification of the hostname in the peer certificate */ - arg = va_arg(param, long); - if((arg < CURLFTPMETHOD_DEFAULT) || (arg >= CURLFTPMETHOD_LAST)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.ftp_filemethod = (unsigned char)(curl_ftpfile)arg; + + /* 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 */ + data->set.ssl.primary.verifyhost = enabled; + + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); break; - case CURLOPT_FTPPORT: + case CURLOPT_SSL_VERIFYSTATUS: /* - * Use FTP PORT, this also specifies which IP address to use + * Enable certificate status verifying. */ - result = Curl_setstropt(&data->set.str[STRING_FTPPORT], - va_arg(param, char *)); - data->set.ftp_use_port = (data->set.str[STRING_FTPPORT]) ? TRUE : FALSE; - break; + if(!Curl_ssl_cert_status_request()) + return CURLE_NOT_BUILT_IN; - case CURLOPT_FTP_USE_EPRT: - data->set.ftp_use_eprt = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; + data->set.ssl.primary.verifystatus = enabled; - case CURLOPT_FTP_USE_EPSV: - data->set.ftp_use_epsv = (0 != va_arg(param, long)) ? TRUE : FALSE; + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); break; + case CURLOPT_SSL_FALSESTART: + /* + * Enable TLS false start. + */ + if(!Curl_ssl_false_start()) + return CURLE_NOT_BUILT_IN; - case CURLOPT_FTP_USE_PRET: - data->set.ftp_use_pret = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.ssl.falsestart = enabled; break; - - case CURLOPT_FTP_SSL_CCC: - arg = va_arg(param, long); - if((arg < CURLFTPSSL_CCC_NONE) || (arg >= CURLFTPSSL_CCC_LAST)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.ftp_ccc = (unsigned char)(curl_ftpccc)arg; + case CURLOPT_CERTINFO: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CERTINFO)) + data->set.ssl.certinfo = enabled; + else +#endif + return CURLE_NOT_BUILT_IN; break; - - case CURLOPT_FTP_SKIP_PASV_IP: + case CURLOPT_BUFFERSIZE: /* - * Enable or disable FTP_SKIP_PASV_IP, which will disable/enable the - * bypass of the IP address in PASV responses. + * The application kindly asks for a differently sized receive buffer. + * If it seems reasonable, we will use it. */ - data->set.ftp_skip_ip = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_ACCOUNT: - result = Curl_setstropt(&data->set.str[STRING_FTP_ACCOUNT], - va_arg(param, char *)); - break; + if(arg > READBUFFER_MAX) + arg = READBUFFER_MAX; + else if(arg < 1) + arg = READBUFFER_SIZE; + else if(arg < READBUFFER_MIN) + arg = READBUFFER_MIN; - case CURLOPT_FTP_ALTERNATIVE_TO_USER: - result = Curl_setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], - va_arg(param, char *)); + data->set.buffer_size = (unsigned int)arg; break; - case CURLOPT_FTPSSLAUTH: + case CURLOPT_UPLOAD_BUFFERSIZE: /* - * Set a specific auth for FTP-SSL transfers. + * The application kindly asks for a differently sized upload buffer. + * Cap it to sensible. */ - arg = va_arg(param, long); - if((arg < CURLFTPAUTH_DEFAULT) || (arg >= CURLFTPAUTH_LAST)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.ftpsslauth = (unsigned char)(curl_ftpauth)arg; + if(arg > UPLOADBUFFER_MAX) + arg = UPLOADBUFFER_MAX; + else if(arg < UPLOADBUFFER_MIN) + arg = UPLOADBUFFER_MIN; + + data->set.upload_buffer_size = (unsigned int)arg; break; - case CURLOPT_KRBLEVEL: + + case CURLOPT_NOSIGNAL: /* - * A string that defines the kerberos security level. + * The application asks not to set any signal() or alarm() handlers, + * even when using a timeout. */ - result = Curl_setstropt(&data->set.str[STRING_KRB_LEVEL], - va_arg(param, char *)); - data->set.krb = (data->set.str[STRING_KRB_LEVEL]) ? TRUE : FALSE; + data->set.no_signal = enabled; break; -#endif -#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) - case CURLOPT_FTP_CREATE_MISSING_DIRS: + case CURLOPT_MAXFILESIZE: /* - * An FTP/SFTP option that modifies an upload to create missing - * directories on the server. + * Set the maximum size of a file to download. */ - arg = va_arg(param, long); - /* reserve other values for future use */ - if((arg < CURLFTP_CREATE_DIR_NONE) || - (arg > CURLFTP_CREATE_DIR_RETRY)) - result = CURLE_BAD_FUNCTION_ARGUMENT; - else - data->set.ftp_create_missing_dirs = (unsigned char)arg; + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_filesize = arg; break; - case CURLOPT_POSTQUOTE: +#ifdef USE_SSL + case CURLOPT_USE_SSL: /* - * List of RAW FTP commands to use after a transfer + * Make transfers attempt to use SSL/TLS. */ - data->set.postquote = va_arg(param, struct curl_slist *); + if((arg < CURLUSESSL_NONE) || (arg >= CURLUSESSL_LAST)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.use_ssl = (unsigned char)arg; break; - case CURLOPT_PREQUOTE: - /* - * List of RAW FTP commands to use prior to RETR (Wesley Laxton) - */ - data->set.prequote = va_arg(param, struct curl_slist *); + case CURLOPT_SSL_OPTIONS: + set_ssl_options(&data->set.ssl, &data->set.ssl.primary, arg); break; - case CURLOPT_QUOTE: - /* - * List of RAW FTP commands to use before a transfer - */ - data->set.quote = va_arg(param, struct curl_slist *); + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSL_OPTIONS: + set_ssl_options(&data->set.proxy_ssl, &data->set.proxy_ssl.primary, arg); break; #endif - case CURLOPT_READDATA: - /* - * FILE pointer to read the file to be uploaded from. Or possibly - * used as argument to the read callback. - */ - data->set.in_set = va_arg(param, void *); - break; - case CURLOPT_INFILESIZE: - /* - * If known, this should inform curl about the file size of the - * to-be-uploaded file. - */ - arg = va_arg(param, long); - if(arg < -1) + +#endif /* USE_SSL */ + case CURLOPT_IPRESOLVE: + if((arg < CURL_IPRESOLVE_WHATEVER) || (arg > CURL_IPRESOLVE_V6)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.filesize = arg; + data->set.ipver = (unsigned char) arg; break; - case CURLOPT_INFILESIZE_LARGE: + case CURLOPT_TCP_NODELAY: /* - * If known, this should inform curl about the file size of the - * to-be-uploaded file. + * Enable or disable TCP_NODELAY, which will disable/enable the Nagle + * algorithm */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.filesize = bigsize; + data->set.tcp_nodelay = enabled; break; - case CURLOPT_LOW_SPEED_LIMIT: - /* - * The low speed limit that if transfers are below this for - * CURLOPT_LOW_SPEED_TIME, the transfer is aborted. - */ - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.low_speed_limit = arg; + + case CURLOPT_IGNORE_CONTENT_LENGTH: + data->set.ignorecl = enabled; break; - case CURLOPT_MAX_SEND_SPEED_LARGE: + + case CURLOPT_CONNECT_ONLY: /* - * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE - * bytes per second the transfer is throttled.. + * No data transfer. + * (1) - only do connection + * (2) - do first get request but get no content */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < 0) + if(arg > 2) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_send_speed = bigsize; + data->set.connect_only = !!arg; + data->set.connect_only_ws = (arg == 2); break; - case CURLOPT_MAX_RECV_SPEED_LARGE: - /* - * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per - * second the transfer is throttled.. - */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_recv_speed = bigsize; + + case CURLOPT_SSL_SESSIONID_CACHE: + data->set.ssl.primary.cache_session = enabled; +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl.primary.cache_session = + data->set.ssl.primary.cache_session; +#endif break; - case CURLOPT_LOW_SPEED_TIME: - /* - * The low speed time that if transfers are below the set - * CURLOPT_LOW_SPEED_LIMIT during this time, the transfer is aborted. - */ - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.low_speed_time = arg; + +#ifdef USE_SSH + /* we only include SSH options if explicitly built to support SSH */ + case CURLOPT_SSH_AUTH_TYPES: + data->set.ssh_auth_types = (int)arg; break; - case CURLOPT_CURLU: - /* - * pass CURLU to set URL - */ - data->set.uh = va_arg(param, CURLU *); + case CURLOPT_SSH_COMPRESSION: + data->set.ssh_compression = enabled; break; - case CURLOPT_URL: +#endif + + case CURLOPT_HTTP_TRANSFER_DECODING: /* - * The URL to fetch. + * disable libcurl transfer encoding is used */ - if(data->state.url_alloc) { - /* the already set URL is allocated, free it first! */ - Curl_safefree(data->state.url); - data->state.url_alloc = FALSE; - } - result = Curl_setstropt(&data->set.str[STRING_SET_URL], - va_arg(param, char *)); - data->state.url = data->set.str[STRING_SET_URL]; + data->set.http_te_skip = !enabled; /* reversed */ break; - case CURLOPT_PORT: + + case CURLOPT_HTTP_CONTENT_DECODING: /* - * The port number to use when getting the URL. 0 disables it. + * raw data passed to the application when content encoding is used */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 65535)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.use_port = (unsigned short)arg; + data->set.http_ce_skip = !enabled; /* reversed */ break; - case CURLOPT_TIMEOUT: + +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + case CURLOPT_NEW_FILE_PERMS: /* - * The maximum time you allow curl to use for a single transfer - * operation. + * Uses these permissions instead of 0644 */ - arg = va_arg(param, long); - if((arg >= 0) && (arg <= (INT_MAX/1000))) - data->set.timeout = (unsigned int)arg * 1000; - else + if((arg < 0) || (arg > 0777)) return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.new_file_perms = (unsigned int)arg; break; - - case CURLOPT_TIMEOUT_MS: - uarg = va_arg(param, unsigned long); - if(uarg > UINT_MAX) - uarg = UINT_MAX; - data->set.timeout = (unsigned int)uarg; - break; - - case CURLOPT_CONNECTTIMEOUT: +#endif +#ifdef USE_SSH + case CURLOPT_NEW_DIRECTORY_PERMS: /* - * The maximum time you allow curl to use to connect. + * Uses these permissions instead of 0755 */ - arg = va_arg(param, long); - if((arg >= 0) && (arg <= (INT_MAX/1000))) - data->set.connecttimeout = (unsigned int)arg * 1000; - else + if((arg < 0) || (arg > 0777)) return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.new_directory_perms = (unsigned int)arg; break; - - case CURLOPT_CONNECTTIMEOUT_MS: - uarg = va_arg(param, unsigned long); - if(uarg > UINT_MAX) - uarg = UINT_MAX; - data->set.connecttimeout = (unsigned int)uarg; - break; - -#ifndef CURL_DISABLE_FTP - case CURLOPT_ACCEPTTIMEOUT_MS: +#endif +#ifdef USE_IPV6 + case CURLOPT_ADDRESS_SCOPE: /* - * The maximum time for curl to wait for FTP server connect + * Use this scope id when using IPv6 + * We always get longs when passed plain numericals so we should check + * that the value fits into an unsigned 32-bit integer. */ - uarg = va_arg(param, unsigned long); +#if SIZEOF_LONG > 4 if(uarg > UINT_MAX) - uarg = UINT_MAX; - data->set.accepttimeout = (unsigned int)uarg; + return CURLE_BAD_FUNCTION_ARGUMENT; +#endif + data->set.scope_id = (unsigned int)uarg; break; #endif + case CURLOPT_PROTOCOLS: + /* set the bitmask for the protocols that are allowed to be used for the + transfer, which thus helps the app which takes URLs from users or other + external inputs and want to restrict what protocol(s) to deal with. + Defaults to CURLPROTO_ALL. */ + data->set.allowed_protocols = (curl_prot_t)arg; + break; - case CURLOPT_USERPWD: - /* - * user:password to use in the operation - */ - result = setstropt_userpwd(va_arg(param, char *), - &data->set.str[STRING_USERNAME], - &data->set.str[STRING_PASSWORD]); + case CURLOPT_REDIR_PROTOCOLS: + /* set the bitmask for the protocols that libcurl is allowed to follow to, + as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol + needs to be set in both bitmasks to be allowed to get redirected to. */ + data->set.redir_protocols = (curl_prot_t)arg; break; - case CURLOPT_USERNAME: - /* - * authentication user name to use in the operation - */ - result = Curl_setstropt(&data->set.str[STRING_USERNAME], - va_arg(param, char *)); +#ifndef CURL_DISABLE_SMTP + case CURLOPT_MAIL_RCPT_ALLOWFAILS: + /* allow RCPT TO command to fail for some recipients */ + data->set.mail_rcpt_allowfails = enabled; break; - case CURLOPT_PASSWORD: +#endif /* !CURL_DISABLE_SMTP */ + case CURLOPT_SASL_IR: + /* Enable/disable SASL initial response */ + data->set.sasl_ir = enabled; + break; +#ifndef CURL_DISABLE_RTSP + case CURLOPT_RTSP_REQUEST: + return setopt_RTSP_REQUEST(data, arg); + case CURLOPT_RTSP_CLIENT_CSEQ: /* - * authentication password to use in the operation + * Set the CSEQ number to issue for the next RTSP request. Useful if the + * application is resuming a previously broken connection. The CSEQ + * will increment from this new number henceforth. */ - result = Curl_setstropt(&data->set.str[STRING_PASSWORD], - va_arg(param, char *)); + data->state.rtsp_next_client_CSeq = arg; break; - case CURLOPT_LOGIN_OPTIONS: - /* - * authentication options to use in the operation - */ - result = Curl_setstropt(&data->set.str[STRING_OPTIONS], - va_arg(param, char *)); + case CURLOPT_RTSP_SERVER_CSEQ: + /* Same as the above, but for server-initiated requests */ + data->state.rtsp_next_server_CSeq = arg; break; - case CURLOPT_XOAUTH2_BEARER: - /* - * OAuth 2.0 bearer token to use in the operation - */ - result = Curl_setstropt(&data->set.str[STRING_BEARER], - va_arg(param, char *)); - break; +#endif /* ! CURL_DISABLE_RTSP */ - case CURLOPT_RESOLVE: - /* - * List of HOST:PORT:[addresses] strings to populate the DNS cache with - * Entries added this way will remain in the cache until explicitly - * removed or the handle is cleaned up. - * - * Prefix the HOST with plus sign (+) to have the entry expire just like - * automatically added entries. - * - * Prefix the HOST with dash (-) to _remove_ the entry from the cache. - * - * This API can remove any entry from the DNS cache, but only entries - * that aren't actually in use right now will be pruned immediately. - */ - data->set.resolve = va_arg(param, struct curl_slist *); - data->state.resolve = data->set.resolve; + case CURLOPT_TCP_KEEPALIVE: + data->set.tcp_keepalive = enabled; break; - case CURLOPT_PROGRESSFUNCTION: - /* - * Progress callback function - */ - data->set.fprogress = va_arg(param, curl_progress_callback); - if(data->set.fprogress) - data->progress.callback = TRUE; /* no longer internal */ - else - data->progress.callback = FALSE; /* NULL enforces internal */ + case CURLOPT_TCP_KEEPIDLE: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepidle = (int)arg; + break; + case CURLOPT_TCP_KEEPINTVL: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepintvl = (int)arg; + break; + case CURLOPT_TCP_KEEPCNT: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepcnt = (int)arg; + break; + case CURLOPT_TCP_FASTOPEN: +#if defined(CONNECT_DATA_IDEMPOTENT) || defined(MSG_FASTOPEN) || \ + defined(TCP_FASTOPEN_CONNECT) + data->set.tcp_fastopen = enabled; +#else + return CURLE_NOT_BUILT_IN; +#endif + break; + case CURLOPT_SSL_ENABLE_NPN: + break; + case CURLOPT_SSL_ENABLE_ALPN: + data->set.ssl_enable_alpn = enabled; + break; + case CURLOPT_PATH_AS_IS: + data->set.path_as_is = enabled; + break; + case CURLOPT_PIPEWAIT: + data->set.pipewait = enabled; + break; + case CURLOPT_STREAM_WEIGHT: +#if defined(USE_HTTP2) || defined(USE_HTTP3) + if((arg >= 1) && (arg <= 256)) + data->set.priority.weight = (int)arg; + break; +#else + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_SUPPRESS_CONNECT_HEADERS: + data->set.suppress_connect_headers = enabled; + break; + case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.happy_eyeballs_timeout = (unsigned int)uarg; + break; +#ifndef CURL_DISABLE_SHUFFLE_DNS + case CURLOPT_DNS_SHUFFLE_ADDRESSES: + data->set.dns_shuffle_addresses = enabled; + break; +#endif + case CURLOPT_DISALLOW_USERNAME_IN_URL: + data->set.disallow_username_in_url = enabled; break; - case CURLOPT_XFERINFOFUNCTION: - /* - * Transfer info callback function - */ - data->set.fxferinfo = va_arg(param, curl_xferinfo_callback); - if(data->set.fxferinfo) - data->progress.callback = TRUE; /* no longer internal */ + case CURLOPT_UPKEEP_INTERVAL_MS: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.upkeep_interval_ms = arg; + break; + case CURLOPT_MAXAGE_CONN: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxage_conn = arg; + break; + case CURLOPT_MAXLIFETIME_CONN: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxlifetime_conn = arg; + break; +#ifndef CURL_DISABLE_HSTS + case CURLOPT_HSTS_CTRL: + if(arg & CURLHSTS_ENABLE) { + if(!data->hsts) { + data->hsts = Curl_hsts_init(); + if(!data->hsts) + return CURLE_OUT_OF_MEMORY; + } + } else - data->progress.callback = FALSE; /* NULL enforces internal */ - + Curl_hsts_cleanup(&data->hsts); break; - - case CURLOPT_PROGRESSDATA: +#endif /* ! CURL_DISABLE_HSTS */ +#ifndef CURL_DISABLE_ALTSVC + case CURLOPT_ALTSVC_CTRL: + if(!arg) { + DEBUGF(infof(data, "bad CURLOPT_ALTSVC_CTRL input")); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!data->asi) { + data->asi = Curl_altsvc_init(); + if(!data->asi) + return CURLE_OUT_OF_MEMORY; + } + return Curl_altsvc_ctrl(data->asi, arg); +#endif /* ! CURL_DISABLE_ALTSVC */ +#ifndef CURL_DISABLE_WEBSOCKETS + case CURLOPT_WS_OPTIONS: + data->set.ws_raw_mode = (bool)(arg & CURLWS_RAW_MODE); + data->set.ws_no_auto_pong = (bool)(arg & CURLWS_NOAUTOPONG); + break; +#endif + case CURLOPT_QUICK_EXIT: + data->set.quick_exit = enabled; + break; + case CURLOPT_DNS_USE_GLOBAL_CACHE: + /* deprecated */ + break; + case CURLOPT_SSLENGINE_DEFAULT: /* - * Custom client data to pass to the progress callback + * flag to set engine as default. */ - data->set.progress_client = va_arg(param, void *); + Curl_safefree(data->set.str[STRING_SSL_ENGINE]); + return Curl_ssl_set_engine_default(data); + case CURLOPT_UPLOAD_FLAGS: + data->set.upload_flags = (unsigned char)arg; break; + default: + /* unknown option */ + return CURLE_UNKNOWN_OPTION; + } + return CURLE_OK; +} +static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option, + struct curl_slist *slist) +{ + CURLcode result = CURLE_OK; + switch(option) { #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXYUSERPWD: + case CURLOPT_PROXYHEADER: /* - * user:password needed to use the proxy + * Set a list with proxy headers to use (or replace internals with) + * + * Since CURLOPT_HTTPHEADER was the only way to set HTTP headers for a + * long time we remain doing it this way until CURLOPT_PROXYHEADER is + * used. As soon as this option has been used, if set to anything but + * NULL, custom headers for proxies are only picked from this list. + * + * Set this option to NULL to restore the previous behavior. */ - result = setstropt_userpwd(va_arg(param, char *), - &data->set.str[STRING_PROXYUSERNAME], - &data->set.str[STRING_PROXYPASSWORD]); + data->set.proxyheaders = slist; break; - case CURLOPT_PROXYUSERNAME: +#endif +#ifndef CURL_DISABLE_HTTP + case CURLOPT_HTTP200ALIASES: /* - * authentication user name to use in the operation + * Set a list of aliases for HTTP 200 in response header */ - result = Curl_setstropt(&data->set.str[STRING_PROXYUSERNAME], - va_arg(param, char *)); + data->set.http200aliases = slist; break; - case CURLOPT_PROXYPASSWORD: +#endif +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + case CURLOPT_POSTQUOTE: /* - * authentication password to use in the operation + * List of RAW FTP commands to use after a transfer */ - result = Curl_setstropt(&data->set.str[STRING_PROXYPASSWORD], - va_arg(param, char *)); + data->set.postquote = slist; break; - case CURLOPT_NOPROXY: + case CURLOPT_PREQUOTE: /* - * proxy exception list + * List of RAW FTP commands to use prior to RETR (Wesley Laxton) */ - result = Curl_setstropt(&data->set.str[STRING_NOPROXY], - va_arg(param, char *)); + data->set.prequote = slist; break; -#endif - - case CURLOPT_RANGE: + case CURLOPT_QUOTE: /* - * What range of the file you want to transfer + * List of RAW FTP commands to use before a transfer */ - result = Curl_setstropt(&data->set.str[STRING_SET_RANGE], - va_arg(param, char *)); + data->set.quote = slist; break; - case CURLOPT_RESUME_FROM: +#endif + case CURLOPT_RESOLVE: /* - * Resume transfer at the given file position + * List of HOST:PORT:[addresses] strings to populate the DNS cache with + * Entries added this way will remain in the cache until explicitly + * removed or the handle is cleaned up. + * + * Prefix the HOST with plus sign (+) to have the entry expire just like + * automatically added entries. + * + * Prefix the HOST with dash (-) to _remove_ the entry from the cache. + * + * This API can remove any entry from the DNS cache, but only entries + * that are not actually in use right now will be pruned immediately. */ - arg = va_arg(param, long); - if(arg < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.set_resume_from = arg; + data->set.resolve = slist; + data->state.resolve = data->set.resolve; break; - case CURLOPT_RESUME_FROM_LARGE: +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MIME) + case CURLOPT_HTTPHEADER: /* - * Resume transfer at the given file position + * Set a list with HTTP headers to use (or replace internals with) */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.set_resume_from = bigsize; + data->set.headers = slist; break; - case CURLOPT_DEBUGFUNCTION: +#endif +#ifndef CURL_DISABLE_TELNET + case CURLOPT_TELNETOPTIONS: /* - * stderr write callback. + * Set a linked list of telnet options */ - data->set.fdebug = va_arg(param, curl_debug_callback); + data->set.telnet_options = slist; + break; +#endif +#ifndef CURL_DISABLE_SMTP + case CURLOPT_MAIL_RCPT: + /* Set the list of mail recipients */ + data->set.mail_rcpt = slist; + break; +#endif + case CURLOPT_CONNECT_TO: + data->set.connect_to = slist; + break; + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} + +/* assorted pointer type arguments */ +static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, + va_list param) +{ + CURLcode result = CURLE_OK; + switch(option) { +#ifndef CURL_DISABLE_HTTP +#ifndef CURL_DISABLE_FORM_API + case CURLOPT_HTTPPOST: /* - * if the callback provided is NULL, it'll use the default callback + * Set to make us do HTTP POST. Legacy API-style. */ + data->set.httppost = va_arg(param, struct curl_httppost *); + data->set.method = HTTPREQ_POST_FORM; + data->set.opt_no_body = FALSE; /* this is implied */ + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); + data->state.mimepost = NULL; break; - case CURLOPT_DEBUGDATA: +#endif /* ! CURL_DISABLE_FORM_API */ +#endif /* ! CURL_DISABLE_HTTP */ +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP) +# ifndef CURL_DISABLE_MIME + case CURLOPT_MIMEPOST: /* - * Set to a void * that should receive all error writes. This - * defaults to CURLOPT_STDERR for normal operations. + * Set to make us do MIME POST */ - data->set.debugdata = va_arg(param, void *); + result = Curl_mime_set_subparts(&data->set.mimepost, + va_arg(param, curl_mime *), + FALSE); + if(!result) { + data->set.method = HTTPREQ_POST_MIME; + data->set.opt_no_body = FALSE; /* this is implied */ +#ifndef CURL_DISABLE_FORM_API + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); + data->state.mimepost = NULL; +#endif + } break; +#endif /* ! CURL_DISABLE_MIME */ +#endif /* ! disabled HTTP, SMTP or IMAP */ case CURLOPT_STDERR: /* * Set to a FILE * that should receive all error writes. This @@ -1674,1489 +1577,1497 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) if(!data->set.err) data->set.err = stderr; break; - case CURLOPT_HEADERFUNCTION: - /* - * Set header write callback - */ - data->set.fwrite_header = va_arg(param, curl_write_callback); - break; - case CURLOPT_WRITEFUNCTION: - /* - * Set data write callback - */ - data->set.fwrite_func = va_arg(param, curl_write_callback); - if(!data->set.fwrite_func) - /* When set to NULL, reset to our internal default function */ - data->set.fwrite_func = (curl_write_callback)fwrite; - break; - case CURLOPT_READFUNCTION: - /* - * Read data callback - */ - data->set.fread_func_set = va_arg(param, curl_read_callback); - if(!data->set.fread_func_set) { - data->set.is_fread_set = 0; - /* When set to NULL, reset to our internal default function */ - data->set.fread_func_set = (curl_read_callback)fread; + case CURLOPT_SHARE: + { + struct Curl_share *set = va_arg(param, struct Curl_share *); + + /* disconnect from old share, if any */ + if(data->share) { + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies == data->cookies) + data->cookies = NULL; +#endif + +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts == data->hsts) + data->hsts = NULL; +#endif +#ifdef USE_LIBPSL + if(data->psl == &data->share->psl) + data->psl = data->multi ? &data->multi->psl : NULL; +#endif + if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) { + Curl_resolv_unlink(data, &data->state.dns[0]); + Curl_resolv_unlink(data, &data->state.dns[1]); + } + + data->share->dirty--; + + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + data->share = NULL; } - else - data->set.is_fread_set = 1; - break; - case CURLOPT_SEEKFUNCTION: - /* - * Seek callback. Might be NULL. - */ - data->set.seek_func = va_arg(param, curl_seek_callback); - break; - case CURLOPT_SEEKDATA: - /* - * Seek control callback. Might be NULL. - */ - data->set.seek_client = va_arg(param, void *); - break; - case CURLOPT_IOCTLFUNCTION: - /* - * I/O control callback. Might be NULL. - */ - data->set.ioctl_func = va_arg(param, curl_ioctl_callback); - break; - case CURLOPT_IOCTLDATA: - /* - * I/O control data pointer. Might be NULL. - */ - data->set.ioctl_client = va_arg(param, void *); - break; - case CURLOPT_SSLCERT: - /* - * String that holds file name of the SSL certificate to use - */ - result = Curl_setstropt(&data->set.str[STRING_CERT], - va_arg(param, char *)); - break; - case CURLOPT_SSLCERT_BLOB: - /* - * Blob that holds file content of the SSL certificate to use - */ - result = Curl_setblobopt(&data->set.blobs[BLOB_CERT], - va_arg(param, struct curl_blob *)); - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLCERT: - /* - * String that holds file name of the SSL certificate to use for proxy - */ - result = Curl_setstropt(&data->set.str[STRING_CERT_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_SSLCERT_BLOB: - /* - * Blob that holds file content of the SSL certificate to use for proxy - */ - result = Curl_setblobopt(&data->set.blobs[BLOB_CERT_PROXY], - va_arg(param, struct curl_blob *)); - break; + + if(GOOD_SHARE_HANDLE(set)) + /* use new share if it set */ + data->share = set; + if(data->share) { + + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + + data->share->dirty++; + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies) { + /* use shared cookie list, first free own one if any */ + Curl_cookie_cleanup(data->cookies); + /* enable cookies since we now use a share that uses cookies! */ + data->cookies = data->share->cookies; + } +#endif /* CURL_DISABLE_HTTP */ +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts) { + /* first free the private one if any */ + Curl_hsts_cleanup(&data->hsts); + data->hsts = data->share->hsts; + } #endif - case CURLOPT_SSLCERTTYPE: - /* - * String that holds file type of the SSL certificate to use - */ - result = Curl_setstropt(&data->set.str[STRING_CERT_TYPE], - va_arg(param, char *)); - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLCERTTYPE: - /* - * String that holds file type of the SSL certificate to use for proxy - */ - result = Curl_setstropt(&data->set.str[STRING_CERT_TYPE_PROXY], - va_arg(param, char *)); - break; +#ifdef USE_LIBPSL + if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL)) + data->psl = &data->share->psl; #endif - case CURLOPT_SSLKEY: - /* - * String that holds file name of the SSL key to use - */ - result = Curl_setstropt(&data->set.str[STRING_KEY], - va_arg(param, char *)); - break; - case CURLOPT_SSLKEY_BLOB: - /* - * Blob that holds file content of the SSL key to use - */ - result = Curl_setblobopt(&data->set.blobs[BLOB_KEY], - va_arg(param, struct curl_blob *)); + + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + } + /* check for host cache not needed, + * it will be done by curl_easy_perform */ + } + break; + +#ifdef USE_HTTP2 + case CURLOPT_STREAM_DEPENDS: + case CURLOPT_STREAM_DEPENDS_E: { + struct Curl_easy *dep = va_arg(param, struct Curl_easy *); + if(!dep || GOOD_EASY_HANDLE(dep)) + return Curl_data_priority_add_child(dep, data, + option == CURLOPT_STREAM_DEPENDS_E); break; + } +#endif + + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} + +static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + CURLcode result = CURLE_OK; + switch(option) { + case CURLOPT_SSL_CIPHER_LIST: + if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) + /* set a list of cipher we want to use in the SSL connection */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], ptr); + else + return CURLE_NOT_BUILT_IN; #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLKEY: - /* - * String that holds file name of the SSL key to use for proxy - */ - result = Curl_setstropt(&data->set.str[STRING_KEY_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_SSLKEY_BLOB: - /* - * Blob that holds file content of the SSL key to use for proxy - */ - result = Curl_setblobopt(&data->set.blobs[BLOB_KEY_PROXY], - va_arg(param, struct curl_blob *)); - break; + case CURLOPT_PROXY_SSL_CIPHER_LIST: + if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) { + /* set a list of cipher we want to use in the SSL connection for proxy */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_PROXY], + ptr); + } + else + return CURLE_NOT_BUILT_IN; #endif - case CURLOPT_SSLKEYTYPE: - /* - * String that holds file type of the SSL key to use - */ - result = Curl_setstropt(&data->set.str[STRING_KEY_TYPE], - va_arg(param, char *)); - break; + case CURLOPT_TLS13_CIPHERS: + if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { + /* set preferred list of TLS 1.3 cipher suites */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST], ptr); + } + else + return CURLE_NOT_BUILT_IN; #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLKEYTYPE: - /* - * String that holds file type of the SSL key to use for proxy - */ - result = Curl_setstropt(&data->set.str[STRING_KEY_TYPE_PROXY], - va_arg(param, char *)); - break; + case CURLOPT_PROXY_TLS13_CIPHERS: + if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) + /* set preferred list of TLS 1.3 cipher suites for proxy */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST_PROXY], + ptr); + else + return CURLE_NOT_BUILT_IN; #endif - case CURLOPT_KEYPASSWD: - /* - * String that holds the SSL or SSH private key password. - */ - result = Curl_setstropt(&data->set.str[STRING_KEY_PASSWD], - va_arg(param, char *)); + case CURLOPT_RANDOM_FILE: break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_KEYPASSWD: + case CURLOPT_EGDSOCKET: + break; + case CURLOPT_REQUEST_TARGET: + return Curl_setstropt(&data->set.str[STRING_TARGET], ptr); +#ifndef CURL_DISABLE_NETRC + case CURLOPT_NETRC_FILE: /* - * String that holds the SSL private key password for proxy. + * Use this file instead of the $HOME/.netrc file */ - result = Curl_setstropt(&data->set.str[STRING_KEY_PASSWD_PROXY], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_NETRC_FILE], ptr); #endif - case CURLOPT_SSLENGINE: + +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) + case CURLOPT_COPYPOSTFIELDS: /* - * String that holds the SSL crypto engine. + * A string with POST data. Makes curl HTTP POST. Even if it is NULL. + * If needed, CURLOPT_POSTFIELDSIZE must have been set prior to + * CURLOPT_COPYPOSTFIELDS and not altered later. */ - argptr = va_arg(param, char *); - if(argptr && argptr[0]) { - result = Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], argptr); - if(!result) { - result = Curl_ssl_set_engine(data, argptr); + if(!ptr || data->set.postfieldsize == -1) + result = Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], ptr); + else { + if(data->set.postfieldsize < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; +#if SIZEOF_CURL_OFF_T > SIZEOF_SIZE_T + /* + * Check that requested length does not overflow the size_t type. + */ + else if(data->set.postfieldsize > SIZE_T_MAX) + return CURLE_OUT_OF_MEMORY; +#endif + else { + /* Allocate even when size == 0. This satisfies the need of possible + later address compare to detect the COPYPOSTFIELDS mode, and to + mark that postfields is used rather than read function or form + data. + */ + char *p = Curl_memdup0(ptr, (size_t)data->set.postfieldsize); + if(!p) + return CURLE_OUT_OF_MEMORY; + else { + free(data->set.str[STRING_COPYPOSTFIELDS]); + data->set.str[STRING_COPYPOSTFIELDS] = p; + } } } - break; - case CURLOPT_SSLENGINE_DEFAULT: - /* - * flag to set engine as default. - */ - Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], NULL); - result = Curl_ssl_set_engine_default(data); - break; - case CURLOPT_CRLF: - /* - * Kludgy option to enable CRLF conversions. Subject for removal. - */ - data->set.crlf = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_HAPROXYPROTOCOL: - /* - * Set to send the HAProxy Proxy Protocol header - */ - data->set.haproxyprotocol = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; -#endif - case CURLOPT_INTERFACE: - /* - * Set what interface or address/hostname to bind the socket to when - * performing an operation and thus what from-IP your connection will use. - */ - result = Curl_setstropt(&data->set.str[STRING_DEVICE], - va_arg(param, char *)); - break; - case CURLOPT_LOCALPORT: - /* - * Set what local port to bind the socket to when performing an operation. - */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 65535)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.localport = curlx_sltous(arg); - break; - case CURLOPT_LOCALPORTRANGE: - /* - * Set number of local ports to try, starting with CURLOPT_LOCALPORT. - */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 65535)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.localportrange = curlx_sltous(arg); + data->set.postfields = data->set.str[STRING_COPYPOSTFIELDS]; + data->set.method = HTTPREQ_POST; break; - case CURLOPT_GSSAPI_DELEGATION: + + case CURLOPT_POSTFIELDS: /* - * GSS-API credential delegation bitmask + * Like above, but use static data instead of copying it. */ - uarg = va_arg(param, unsigned long); - data->set.gssapi_delegation = (unsigned char)uarg& - (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG); + data->set.postfields = ptr; + /* Release old copied data. */ + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); + data->set.method = HTTPREQ_POST; break; - case CURLOPT_SSL_VERIFYPEER: +#endif /* ! CURL_DISABLE_HTTP || ! CURL_DISABLE_MQTT */ + +#ifndef CURL_DISABLE_HTTP + case CURLOPT_ACCEPT_ENCODING: /* - * Enable peer SSL verifying. + * String to use at the value of Accept-Encoding header. + * + * If the encoding is set to "" we use an Accept-Encoding header that + * encompasses all the encodings we support. + * If the encoding is set to NULL we do not send an Accept-Encoding header + * and ignore an received Content-Encoding header. + * */ - data->set.ssl.primary.verifypeer = (0 != va_arg(param, long)) ? - TRUE : FALSE; - - /* Update the current connection ssl_config. */ - if(data->conn) { - data->conn->ssl_config.verifypeer = - data->set.ssl.primary.verifypeer; + if(ptr && !*ptr) { + char all[256]; + Curl_all_content_encodings(all, sizeof(all)); + return Curl_setstropt(&data->set.str[STRING_ENCODING], all); } - break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_SSL_VERIFYPEER: + return Curl_setstropt(&data->set.str[STRING_ENCODING], ptr); + +#if !defined(CURL_DISABLE_AWS) + case CURLOPT_AWS_SIGV4: /* - * Enable peer SSL verifying for DoH. + * String that is merged to some authentication + * parameters are used by the algorithm. */ - data->set.doh_verifypeer = (0 != va_arg(param, long)) ? - TRUE : FALSE; - break; -#endif -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_VERIFYPEER: + result = Curl_setstropt(&data->set.str[STRING_AWS_SIGV4], ptr); /* - * Enable peer SSL verifying for proxy. + * Basic been set by default it need to be unset here */ - data->set.proxy_ssl.primary.verifypeer = - (0 != va_arg(param, long))?TRUE:FALSE; - - /* Update the current connection proxy_ssl_config. */ - if(data->conn) { - data->conn->proxy_ssl_config.verifypeer = - data->set.proxy_ssl.primary.verifypeer; - } + if(data->set.str[STRING_AWS_SIGV4]) + data->set.httpauth = CURLAUTH_AWS_SIGV4; break; #endif - case CURLOPT_SSL_VERIFYHOST: + case CURLOPT_REFERER: /* - * Enable verification of the host name in the peer certificate + * String to set in the HTTP Referer: field. */ - arg = va_arg(param, long); - - /* Obviously people are not reading documentation and too many thought - this argument took a boolean when it wasn't and misused it. - Treat 1 and 2 the same */ - data->set.ssl.primary.verifyhost = (bool)((arg & 3) ? TRUE : FALSE); - - /* Update the current connection ssl_config. */ - if(data->conn) { - data->conn->ssl_config.verifyhost = - data->set.ssl.primary.verifyhost; + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; } + result = Curl_setstropt(&data->set.str[STRING_SET_REFERER], ptr); + data->state.referer = data->set.str[STRING_SET_REFERER]; break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_SSL_VERIFYHOST: + + case CURLOPT_USERAGENT: /* - * Enable verification of the host name in the peer certificate for DoH + * String to use in the HTTP User-Agent field */ - arg = va_arg(param, long); + return Curl_setstropt(&data->set.str[STRING_USERAGENT], ptr); - /* Treat both 1 and 2 as TRUE */ - data->set.doh_verifyhost = (bool)((arg & 3) ? TRUE : FALSE); - break; -#endif -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_VERIFYHOST: +#if !defined(CURL_DISABLE_COOKIES) + case CURLOPT_COOKIE: /* - * Enable verification of the host name in the peer certificate for proxy + * Cookie string to send to the remote server in the request. */ - arg = va_arg(param, long); + return Curl_setstropt(&data->set.str[STRING_COOKIE], ptr); - /* Treat both 1 and 2 as TRUE */ - data->set.proxy_ssl.primary.verifyhost = (bool)((arg & 3)?TRUE:FALSE); + case CURLOPT_COOKIEFILE: + /* + * Set cookie file to read and parse. Can be used multiple times. + */ + if(ptr) { + struct curl_slist *cl; + /* general protection against mistakes and abuse */ + if(strlen(ptr) > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + /* append the cookie filename to the list of filenames, and deal with + them later */ + cl = curl_slist_append(data->state.cookielist, ptr); + if(!cl) { + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->state.cookielist = cl; /* store the list for later use */ + } + else { + /* clear the list of cookie files */ + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; - /* Update the current connection proxy_ssl_config. */ - if(data->conn) { - data->conn->proxy_ssl_config.verifyhost = - data->set.proxy_ssl.primary.verifyhost; + if(!data->share || !data->share->cookies) { + /* throw away all existing cookies if this is not a shared cookie + container */ + Curl_cookie_clearall(data->cookies); + Curl_cookie_cleanup(data->cookies); + } + /* disable the cookie engine */ + data->cookies = NULL; } break; -#endif - case CURLOPT_SSL_VERIFYSTATUS: + + case CURLOPT_COOKIEJAR: /* - * Enable certificate status verifying. + * Set cookie filename to dump all cookies to when we are done. */ - if(!Curl_ssl_cert_status_request()) { - result = CURLE_NOT_BUILT_IN; - break; + result = Curl_setstropt(&data->set.str[STRING_COOKIEJAR], ptr); + if(!result) { + /* + * Activate the cookie parser. This may or may not already + * have been made. + */ + struct CookieInfo *newcookies = + Curl_cookie_init(data, NULL, data->cookies, data->set.cookiesession); + if(!newcookies) + result = CURLE_OUT_OF_MEMORY; + data->cookies = newcookies; } + break; - data->set.ssl.primary.verifystatus = (0 != va_arg(param, long)) ? - TRUE : FALSE; + case CURLOPT_COOKIELIST: + if(!ptr) + break; - /* Update the current connection ssl_config. */ - if(data->conn) { - data->conn->ssl_config.verifystatus = - data->set.ssl.primary.verifystatus; + if(strcasecompare(ptr, "ALL")) { + /* clear all cookies */ + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_clearall(data->cookies); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } - break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_SSL_VERIFYSTATUS: - /* - * Enable certificate status verifying for DoH. - */ - if(!Curl_ssl_cert_status_request()) { - result = CURLE_NOT_BUILT_IN; + else if(strcasecompare(ptr, "SESS")) { + /* clear session cookies */ + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_clearsess(data->cookies); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } + else if(strcasecompare(ptr, "FLUSH")) { + /* flush cookies to file, takes care of the locking */ + Curl_flush_cookies(data, FALSE); + } + else if(strcasecompare(ptr, "RELOAD")) { + /* reload cookies from file */ + Curl_cookie_loadfiles(data); break; } + else { + if(!data->cookies) { + /* if cookie engine was not running, activate it */ + data->cookies = Curl_cookie_init(data, NULL, NULL, TRUE); + if(!data->cookies) + return CURLE_OUT_OF_MEMORY; + } - data->set.doh_verifystatus = (0 != va_arg(param, long)) ? - TRUE : FALSE; - break; -#endif - case CURLOPT_SSL_CTX_FUNCTION: - /* - * Set a SSL_CTX callback - */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) - data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); - else -#endif - result = CURLE_NOT_BUILT_IN; + /* general protection against mistakes and abuse */ + if(strlen(ptr) > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + if(checkprefix("Set-Cookie:", ptr)) + /* HTTP Header format line */ + Curl_cookie_add(data, data->cookies, TRUE, FALSE, ptr + 11, NULL, + NULL, TRUE); + else + /* Netscape format line */ + Curl_cookie_add(data, data->cookies, FALSE, FALSE, ptr, NULL, + NULL, TRUE); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } break; - case CURLOPT_SSL_CTX_DATA: +#endif /* !CURL_DISABLE_COOKIES */ + +#endif /* ! CURL_DISABLE_HTTP */ + + case CURLOPT_CUSTOMREQUEST: /* - * Set a SSL_CTX callback parameter pointer + * Set a custom string to use as request */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) - data->set.ssl.fsslctxp = va_arg(param, void *); - else -#endif - result = CURLE_NOT_BUILT_IN; - break; - case CURLOPT_SSL_FALSESTART: + return Curl_setstropt(&data->set.str[STRING_CUSTOMREQUEST], ptr); + + /* we do not set + data->set.method = HTTPREQ_CUSTOM; + here, we continue as if we were using the already set type + and this just changes the actual request keyword */ + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY: /* - * Enable TLS false start. + * Set proxy server:port to use as proxy. + * + * If the proxy is set to "" (and CURLOPT_SOCKS_PROXY is set to "" or NULL) + * we explicitly say that we do not want to use a proxy + * (even though there might be environment variables saying so). + * + * Setting it to NULL, means no proxy but allows the environment variables + * to decide for us (if CURLOPT_SOCKS_PROXY setting it to NULL). */ - if(!Curl_ssl_false_start(data)) { - result = CURLE_NOT_BUILT_IN; - break; - } + return Curl_setstropt(&data->set.str[STRING_PROXY], ptr); - data->set.ssl.falsestart = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_CERTINFO: -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CERTINFO)) - data->set.ssl.certinfo = (0 != va_arg(param, long)) ? TRUE : FALSE; - else -#endif - result = CURLE_NOT_BUILT_IN; - break; - case CURLOPT_PINNEDPUBLICKEY: + case CURLOPT_PRE_PROXY: /* - * Set pinned public key for SSL connection. - * Specify file name of the public key in DER format. + * Set proxy server:port to use as SOCKS proxy. + * + * If the proxy is set to "" or NULL we explicitly say that we do not want + * to use the socks proxy. */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - result = Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY], - va_arg(param, char *)); - else -#endif - result = CURLE_NOT_BUILT_IN; - break; + return Curl_setstropt(&data->set.str[STRING_PRE_PROXY], ptr); +#endif /* CURL_DISABLE_PROXY */ + #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_PINNEDPUBLICKEY: + case CURLOPT_SOCKS5_GSSAPI_SERVICE: + case CURLOPT_PROXY_SERVICE_NAME: /* - * Set pinned public key for SSL connection. - * Specify file name of the public key in DER format. + * Set proxy authentication service name for Kerberos 5 and SPNEGO */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - result = Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY], - va_arg(param, char *)); - else -#endif - result = CURLE_NOT_BUILT_IN; - break; + return Curl_setstropt(&data->set.str[STRING_PROXY_SERVICE_NAME], ptr); #endif - case CURLOPT_CAINFO: + case CURLOPT_SERVICE_NAME: /* - * Set CA info for SSL connection. Specify file name of the CA certificate + * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE], - va_arg(param, char *)); - break; - case CURLOPT_CAINFO_BLOB: + return Curl_setstropt(&data->set.str[STRING_SERVICE_NAME], ptr); + + case CURLOPT_HEADERDATA: /* - * Blob that holds CA info for SSL connection. - * Specify entire PEM of the CA certificate + * Custom pointer to pass the header write callback function */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) - result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO], - va_arg(param, struct curl_blob *)); - else -#endif - return CURLE_NOT_BUILT_IN; - + data->set.writeheader = ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_CAINFO: + case CURLOPT_READDATA: /* - * Set CA info SSL connection for proxy. Specify file name of the - * CA certificate + * FILE pointer to read the file to be uploaded from. Or possibly used as + * argument to the read callback. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY], - va_arg(param, char *)); + data->set.in_set = ptr; break; - case CURLOPT_PROXY_CAINFO_BLOB: + case CURLOPT_WRITEDATA: /* - * Blob that holds CA info for SSL connection proxy. - * Specify entire PEM of the CA certificate + * FILE pointer to write to. Or possibly used as argument to the write + * callback. */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) - result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO_PROXY], - va_arg(param, struct curl_blob *)); - else -#endif - return CURLE_NOT_BUILT_IN; + data->set.out = ptr; break; -#endif - case CURLOPT_CAPATH: + case CURLOPT_DEBUGDATA: /* - * Set CA path info for SSL connection. Specify directory name of the CA - * certificates which have been prepared using openssl c_rehash utility. + * Set to a void * that should receive all error writes. This + * defaults to CURLOPT_STDERR for normal operations. */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) - /* This does not work on windows. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAPATH], - va_arg(param, char *)); - else -#endif - result = CURLE_NOT_BUILT_IN; + data->set.debugdata = ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_CAPATH: + case CURLOPT_PROGRESSDATA: /* - * Set CA path info for SSL connection proxy. Specify directory name of the - * CA certificates which have been prepared using openssl c_rehash utility. + * Custom client data to pass to the progress callback */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) - /* This does not work on windows. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAPATH_PROXY], - va_arg(param, char *)); - else -#endif - result = CURLE_NOT_BUILT_IN; + data->set.progress_client = ptr; break; -#endif - case CURLOPT_CRLFILE: + case CURLOPT_SEEKDATA: /* - * Set CRL file info for SSL connection. Specify file name of the CRL - * to check certificates revocation + * Seek control callback. Might be NULL. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE], - va_arg(param, char *)); + data->set.seek_client = ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_CRLFILE: + case CURLOPT_IOCTLDATA: /* - * Set CRL file info for SSL connection for proxy. Specify file name of the - * CRL to check certificates revocation + * I/O control data pointer. Might be NULL. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE_PROXY], - va_arg(param, char *)); + data->set.ioctl_client = ptr; break; -#endif - case CURLOPT_ISSUERCERT: + case CURLOPT_SSL_CTX_DATA: /* - * Set Issuer certificate file - * to check certificates issuer + * Set an SSL_CTX callback parameter pointer */ - result = Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT], - va_arg(param, char *)); - break; - case CURLOPT_ISSUERCERT_BLOB: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { + data->set.ssl.fsslctxp = ptr; + break; + } + else +#endif + return CURLE_NOT_BUILT_IN; + case CURLOPT_SOCKOPTDATA: /* - * Blob that holds Issuer certificate to check certificates issuer + * socket callback data pointer. Might be NULL. */ - result = Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT], - va_arg(param, struct curl_blob *)); + data->set.sockopt_client = ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_ISSUERCERT: + case CURLOPT_OPENSOCKETDATA: /* - * Set Issuer certificate file - * to check certificates issuer + * socket callback data pointer. Might be NULL. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT_PROXY], - va_arg(param, char *)); + data->set.opensocket_client = ptr; break; - case CURLOPT_PROXY_ISSUERCERT_BLOB: + case CURLOPT_RESOLVER_START_DATA: /* - * Blob that holds Issuer certificate to check certificates issuer + * resolver start callback data pointer. Might be NULL. */ - result = Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY], - va_arg(param, struct curl_blob *)); + data->set.resolver_start_client = ptr; break; -#endif -#ifndef CURL_DISABLE_TELNET - case CURLOPT_TELNETOPTIONS: + case CURLOPT_CLOSESOCKETDATA: /* - * Set a linked list of telnet options + * socket callback data pointer. Might be NULL. */ - data->set.telnet_options = va_arg(param, struct curl_slist *); + data->set.closesocket_client = ptr; break; + case CURLOPT_TRAILERDATA: +#ifndef CURL_DISABLE_HTTP + data->set.trailer_data = ptr; #endif - case CURLOPT_BUFFERSIZE: - /* - * The application kindly asks for a differently sized receive buffer. - * If it seems reasonable, we'll use it. - */ - if(data->state.buffer) - return CURLE_BAD_FUNCTION_ARGUMENT; - - arg = va_arg(param, long); - - if(arg > READBUFFER_MAX) - arg = READBUFFER_MAX; - else if(arg < 1) - arg = READBUFFER_SIZE; - else if(arg < READBUFFER_MIN) - arg = READBUFFER_MIN; - - data->set.buffer_size = (unsigned int)arg; + break; + case CURLOPT_PREREQDATA: + data->set.prereq_userp = ptr; break; - case CURLOPT_UPLOAD_BUFFERSIZE: + case CURLOPT_ERRORBUFFER: /* - * The application kindly asks for a differently sized upload buffer. - * Cap it to sensible. + * Error buffer provided by the caller to get the human readable error + * string in. */ - arg = va_arg(param, long); - - if(arg > UPLOADBUFFER_MAX) - arg = UPLOADBUFFER_MAX; - else if(arg < UPLOADBUFFER_MIN) - arg = UPLOADBUFFER_MIN; - - data->set.upload_buffer_size = (unsigned int)arg; - Curl_safefree(data->state.ulbuf); /* force a realloc next opportunity */ + data->set.errorbuffer = ptr; break; - case CURLOPT_NOSIGNAL: +#ifndef CURL_DISABLE_FTP + case CURLOPT_FTPPORT: /* - * The application asks not to set any signal() or alarm() handlers, - * even when using a timeout. + * Use FTP PORT, this also specifies which IP address to use */ - data->set.no_signal = (0 != va_arg(param, long)) ? TRUE : FALSE; + result = Curl_setstropt(&data->set.str[STRING_FTPPORT], ptr); + data->set.ftp_use_port = !!(data->set.str[STRING_FTPPORT]); break; - case CURLOPT_SHARE: - { - struct Curl_share *set; - set = va_arg(param, struct Curl_share *); - - /* disconnect from old share, if any */ - if(data->share) { - Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); - - if(data->dns.hostcachetype == HCACHE_SHARED) { - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; - } + case CURLOPT_FTP_ACCOUNT: + return Curl_setstropt(&data->set.str[STRING_FTP_ACCOUNT], ptr); -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(data->share->cookies == data->cookies) - data->cookies = NULL; -#endif + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + return Curl_setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], ptr); -#ifndef CURL_DISABLE_HSTS - if(data->share->hsts == data->hsts) - data->hsts = NULL; -#endif -#ifdef USE_SSL - if(data->share->sslsession == data->state.session) - data->state.session = NULL; +#ifdef HAVE_GSSAPI + case CURLOPT_KRBLEVEL: + /* + * A string that defines the kerberos security level. + */ + result = Curl_setstropt(&data->set.str[STRING_KRB_LEVEL], ptr); + data->set.krb = !!(data->set.str[STRING_KRB_LEVEL]); + break; #endif -#ifdef USE_LIBPSL - if(data->psl == &data->share->psl) - data->psl = data->multi? &data->multi->psl: NULL; #endif + case CURLOPT_URL: + /* + * The URL to fetch. + */ + if(data->state.url_alloc) { + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; + } + result = Curl_setstropt(&data->set.str[STRING_SET_URL], ptr); + data->state.url = data->set.str[STRING_SET_URL]; + break; - data->share->dirty--; + case CURLOPT_USERPWD: + /* + * user:password to use in the operation + */ + return setstropt_userpwd(ptr, &data->set.str[STRING_USERNAME], + &data->set.str[STRING_PASSWORD]); - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); - data->share = NULL; - } + case CURLOPT_USERNAME: + /* + * authentication username to use in the operation + */ + return Curl_setstropt(&data->set.str[STRING_USERNAME], ptr); - if(GOOD_SHARE_HANDLE(set)) - /* use new share if it set */ - data->share = set; - if(data->share) { + case CURLOPT_PASSWORD: + /* + * authentication password to use in the operation + */ + return Curl_setstropt(&data->set.str[STRING_PASSWORD], ptr); - Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + case CURLOPT_LOGIN_OPTIONS: + /* + * authentication options to use in the operation + */ + return Curl_setstropt(&data->set.str[STRING_OPTIONS], ptr); - data->share->dirty++; + case CURLOPT_XOAUTH2_BEARER: + /* + * OAuth 2.0 bearer token to use in the operation + */ + return Curl_setstropt(&data->set.str[STRING_BEARER], ptr); - if(data->share->specifier & (1<< CURL_LOCK_DATA_DNS)) { - /* use shared host cache */ - data->dns.hostcache = &data->share->hostcache; - data->dns.hostcachetype = HCACHE_SHARED; - } -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(data->share->cookies) { - /* use shared cookie list, first free own one if any */ - Curl_cookie_cleanup(data->cookies); - /* enable cookies since we now use a share that uses cookies! */ - data->cookies = data->share->cookies; - } -#endif /* CURL_DISABLE_HTTP */ -#ifndef CURL_DISABLE_HSTS - if(data->share->hsts) { - /* first free the private one if any */ - Curl_hsts_cleanup(&data->hsts); - data->hsts = data->share->hsts; - } -#endif /* CURL_DISABLE_HTTP */ -#ifdef USE_SSL - if(data->share->sslsession) { - data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions; - data->state.session = data->share->sslsession; - } -#endif -#ifdef USE_LIBPSL - if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL)) - data->psl = &data->share->psl; -#endif +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXYUSERPWD: { + /* + * user:password needed to use the proxy + */ + char *u = NULL; + char *p = NULL; + result = setstropt_userpwd(ptr, &u, &p); - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + /* URL decode the components */ + if(!result && u) { + Curl_safefree(data->set.str[STRING_PROXYUSERNAME]); + result = Curl_urldecode(u, 0, &data->set.str[STRING_PROXYUSERNAME], NULL, + REJECT_ZERO); } - /* check for host cache not needed, - * it will be done by curl_easy_perform */ + if(!result && p) { + Curl_safefree(data->set.str[STRING_PROXYPASSWORD]); + result = Curl_urldecode(p, 0, &data->set.str[STRING_PROXYPASSWORD], NULL, + REJECT_ZERO); + } + free(u); + free(p); } - break; + break; + case CURLOPT_PROXYUSERNAME: + /* + * authentication username to use in the operation + */ + return Curl_setstropt(&data->set.str[STRING_PROXYUSERNAME], ptr); - case CURLOPT_PRIVATE: + case CURLOPT_PROXYPASSWORD: /* - * Set private data pointer. + * authentication password to use in the operation */ - data->set.private_data = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_PROXYPASSWORD], ptr); - case CURLOPT_MAXFILESIZE: + case CURLOPT_NOPROXY: /* - * Set the maximum size of a file to download. + * proxy exception list */ - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_filesize = arg; - break; + return Curl_setstropt(&data->set.str[STRING_NOPROXY], ptr); +#endif /* ! CURL_DISABLE_PROXY */ -#ifdef USE_SSL - case CURLOPT_USE_SSL: + case CURLOPT_RANGE: /* - * Make transfers attempt to use SSL/TLS. + * What range of the file you want to transfer */ - arg = va_arg(param, long); - if((arg < CURLUSESSL_NONE) || (arg >= CURLUSESSL_LAST)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.use_ssl = (unsigned char)arg; - break; + return Curl_setstropt(&data->set.str[STRING_SET_RANGE], ptr); - case CURLOPT_SSL_OPTIONS: - arg = va_arg(param, long); - data->set.ssl.primary.ssl_options = (unsigned char)(arg & 0xff); - data->set.ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); - data->set.ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); - data->set.ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); - data->set.ssl.revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); - data->set.ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); - data->set.ssl.auto_client_cert = !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); - /* If a setting is added here it should also be added in dohprobe() - which sets its own CURLOPT_SSL_OPTIONS based on these settings. */ + case CURLOPT_CURLU: + /* + * pass CURLU to set URL + */ + if(data->state.url_alloc) { + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; + } + else + data->state.url = NULL; + Curl_safefree(data->set.str[STRING_SET_URL]); + data->set.uh = (CURLU *)ptr; break; + case CURLOPT_SSLCERT: + /* + * String that holds filename of the SSL certificate to use + */ + return Curl_setstropt(&data->set.str[STRING_CERT], ptr); #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_OPTIONS: - arg = va_arg(param, long); - data->set.proxy_ssl.primary.ssl_options = (unsigned char)(arg & 0xff); - data->set.proxy_ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); - data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); - data->set.proxy_ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); - data->set.proxy_ssl.revoke_best_effort = - !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); - data->set.proxy_ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); - data->set.proxy_ssl.auto_client_cert = - !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); - break; -#endif - - case CURLOPT_SSL_EC_CURVES: + case CURLOPT_PROXY_SSLCERT: /* - * Set accepted curves in SSL connection setup. - * Specify colon-delimited list of curve algorithm names. + * String that holds filename of the SSL certificate to use for proxy */ - result = Curl_setstropt(&data->set.str[STRING_SSL_EC_CURVES], - va_arg(param, char *)); - break; -#endif - case CURLOPT_IPRESOLVE: - arg = va_arg(param, long); - if((arg < CURL_IPRESOLVE_WHATEVER) || (arg > CURL_IPRESOLVE_V6)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.ipver = (unsigned char) arg; - break; + return Curl_setstropt(&data->set.str[STRING_CERT_PROXY], ptr); - case CURLOPT_MAXFILESIZE_LARGE: +#endif + case CURLOPT_SSLCERTTYPE: /* - * Set the maximum size of a file to download. + * String that holds file type of the SSL certificate to use */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_filesize = bigsize; - break; + return Curl_setstropt(&data->set.str[STRING_CERT_TYPE], ptr); - case CURLOPT_TCP_NODELAY: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLCERTTYPE: /* - * Enable or disable TCP_NODELAY, which will disable/enable the Nagle - * algorithm + * String that holds file type of the SSL certificate to use for proxy */ - data->set.tcp_nodelay = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; + return Curl_setstropt(&data->set.str[STRING_CERT_TYPE_PROXY], ptr); - case CURLOPT_IGNORE_CONTENT_LENGTH: - data->set.ignorecl = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; +#endif + case CURLOPT_SSLKEY: + /* + * String that holds filename of the SSL key to use + */ + return Curl_setstropt(&data->set.str[STRING_KEY], ptr); - case CURLOPT_CONNECT_ONLY: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLKEY: /* - * No data transfer. - * (1) - only do connection - * (2) - do first get request but get no content + * String that holds filename of the SSL key to use for proxy */ - arg = va_arg(param, long); - if(arg > 2) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.connect_only = (unsigned char)arg; - break; + return Curl_setstropt(&data->set.str[STRING_KEY_PROXY], ptr); - case CURLOPT_SOCKOPTFUNCTION: +#endif + case CURLOPT_SSLKEYTYPE: /* - * socket callback function: called after socket() but before connect() + * String that holds file type of the SSL key to use */ - data->set.fsockopt = va_arg(param, curl_sockopt_callback); - break; + return Curl_setstropt(&data->set.str[STRING_KEY_TYPE], ptr); - case CURLOPT_SOCKOPTDATA: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLKEYTYPE: /* - * socket callback data pointer. Might be NULL. + * String that holds file type of the SSL key to use for proxy */ - data->set.sockopt_client = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_KEY_TYPE_PROXY], ptr); - case CURLOPT_OPENSOCKETFUNCTION: +#endif + case CURLOPT_KEYPASSWD: /* - * open/create socket callback function: called instead of socket(), - * before connect() + * String that holds the SSL or SSH private key password. */ - data->set.fopensocket = va_arg(param, curl_opensocket_callback); - break; + return Curl_setstropt(&data->set.str[STRING_KEY_PASSWD], ptr); - case CURLOPT_OPENSOCKETDATA: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_KEYPASSWD: /* - * socket callback data pointer. Might be NULL. + * String that holds the SSL private key password for proxy. */ - data->set.opensocket_client = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_KEY_PASSWD_PROXY], ptr); - case CURLOPT_CLOSESOCKETFUNCTION: +#endif + case CURLOPT_SSLENGINE: /* - * close socket callback function: called instead of close() - * when shutting down a connection + * String that holds the SSL crypto engine. */ - data->set.fclosesocket = va_arg(param, curl_closesocket_callback); + if(ptr && ptr[0]) { + result = Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], ptr); + if(!result) { + result = Curl_ssl_set_engine(data, ptr); + } + } break; - case CURLOPT_RESOLVER_START_FUNCTION: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_HAPROXY_CLIENT_IP: /* - * resolver start callback function: called before a new resolver request - * is started + * Set the client IP to send through HAProxy PROXY protocol */ - data->set.resolver_start = va_arg(param, curl_resolver_start_callback); + result = Curl_setstropt(&data->set.str[STRING_HAPROXY_CLIENT_IP], ptr); + /* enable the HAProxy protocol */ + data->set.haproxyprotocol = TRUE; break; - case CURLOPT_RESOLVER_START_DATA: +#endif + case CURLOPT_INTERFACE: /* - * resolver start callback data pointer. Might be NULL. + * Set what interface or address/hostname to bind the socket to when + * performing an operation and thus what from-IP your connection will use. */ - data->set.resolver_start_client = va_arg(param, void *); - break; + return setstropt_interface(ptr, + &data->set.str[STRING_DEVICE], + &data->set.str[STRING_INTERFACE], + &data->set.str[STRING_BINDHOST]); - case CURLOPT_CLOSESOCKETDATA: + case CURLOPT_PINNEDPUBLICKEY: /* - * socket callback data pointer. Might be NULL. + * Set pinned public key for SSL connection. + * Specify filename of the public key in DER format. */ - data->set.closesocket_client = va_arg(param, void *); - break; +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) + return Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY], ptr); +#endif + return CURLE_NOT_BUILT_IN; - case CURLOPT_SSL_SESSIONID_CACHE: - data->set.ssl.primary.sessionid = (0 != va_arg(param, long)) ? - TRUE : FALSE; #ifndef CURL_DISABLE_PROXY - data->set.proxy_ssl.primary.sessionid = data->set.ssl.primary.sessionid; + case CURLOPT_PROXY_PINNEDPUBLICKEY: + /* + * Set pinned public key for SSL connection. + * Specify filename of the public key in DER format. + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) + return Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY], + ptr); #endif - break; - -#ifdef USE_SSH - /* we only include SSH options if explicitly built to support SSH */ - case CURLOPT_SSH_AUTH_TYPES: - data->set.ssh_auth_types = (unsigned int)va_arg(param, long); - break; - - case CURLOPT_SSH_PUBLIC_KEYFILE: + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_CAINFO: /* - * Use this file instead of the $HOME/.ssh/id_dsa.pub file + * Set CA info for SSL connection. Specify filename of the CA certificate */ - result = Curl_setstropt(&data->set.str[STRING_SSH_PUBLIC_KEY], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_CAFILE], ptr); - case CURLOPT_SSH_PRIVATE_KEYFILE: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CAINFO: /* - * Use this file instead of the $HOME/.ssh/id_dsa file + * Set CA info SSL connection for proxy. Specify filename of the + * CA certificate */ - result = Curl_setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], - va_arg(param, char *)); - break; - case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + return Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY], ptr); + +#endif + case CURLOPT_CAPATH: /* - * Option to allow for the MD5 of the host public key to be checked - * for validation purposes. + * Set CA path info for SSL connection. Specify directory name of the CA + * certificates which have been prepared using openssl c_rehash utility. */ - result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], - va_arg(param, char *)); - break; - - case CURLOPT_SSH_KNOWNHOSTS: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + /* This does not work on Windows. */ + return Curl_setstropt(&data->set.str[STRING_SSL_CAPATH], ptr); +#endif + return CURLE_NOT_BUILT_IN; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CAPATH: /* - * Store the file name to read known hosts from. + * Set CA path info for SSL connection proxy. Specify directory name of the + * CA certificates which have been prepared using openssl c_rehash utility. */ - result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], - va_arg(param, char *)); - break; -#ifdef USE_LIBSSH2 - case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + /* This does not work on Windows. */ + return Curl_setstropt(&data->set.str[STRING_SSL_CAPATH_PROXY], ptr); +#endif + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_CRLFILE: /* - * Option to allow for the SHA256 of the host public key to be checked - * for validation purposes. + * Set CRL file info for SSL connection. Specify filename of the CRL + * to check certificates revocation */ - result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], - va_arg(param, char *)); - break; - - case CURLOPT_SSH_HOSTKEYFUNCTION: - /* the callback to check the hostkey without the knownhost file */ - data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE], ptr); - case CURLOPT_SSH_HOSTKEYDATA: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CRLFILE: /* - * Custom client data to pass to the SSH keyfunc callback + * Set CRL file info for SSL connection for proxy. Specify filename of the + * CRL to check certificates revocation */ - data->set.ssh_hostkeyfunc_userp = va_arg(param, void *); - break; -#endif - - case CURLOPT_SSH_KEYFUNCTION: - /* setting to NULL is fine since the ssh.c functions themselves will - then revert to use the internal default */ - data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE_PROXY], ptr); - case CURLOPT_SSH_KEYDATA: +#endif + case CURLOPT_ISSUERCERT: /* - * Custom client data to pass to the SSH keyfunc callback + * Set Issuer certificate file + * to check certificates issuer */ - data->set.ssh_keyfunc_userp = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT], ptr); - case CURLOPT_SSH_COMPRESSION: - data->set.ssh_compression = (0 != va_arg(param, long))?TRUE:FALSE; - break; -#endif /* USE_SSH */ +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_ISSUERCERT: + /* + * Set Issuer certificate file + * to check certificates issuer + */ + return Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT_PROXY], ptr); - case CURLOPT_HTTP_TRANSFER_DECODING: +#endif + case CURLOPT_PRIVATE: /* - * disable libcurl transfer encoding is used + * Set private data pointer. */ -#ifndef USE_HYPER - data->set.http_te_skip = (0 == va_arg(param, long)) ? TRUE : FALSE; + data->set.private_data = ptr; break; -#else - return CURLE_NOT_BUILT_IN; /* hyper doesn't support */ -#endif - case CURLOPT_HTTP_CONTENT_DECODING: +#ifdef USE_SSL + case CURLOPT_SSL_EC_CURVES: /* - * raw data passed to the application when content encoding is used + * Set accepted curves in SSL connection setup. + * Specify colon-delimited list of curve algorithm names. */ - data->set.http_ce_skip = (0 == va_arg(param, long)) ? TRUE : FALSE; - break; + return Curl_setstropt(&data->set.str[STRING_SSL_EC_CURVES], ptr); -#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) - case CURLOPT_NEW_FILE_PERMS: + case CURLOPT_SSL_SIGNATURE_ALGORITHMS: /* - * Uses these permissions instead of 0644 + * Set accepted signature algorithms. + * Specify colon-delimited list of signature scheme names. */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 0777)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.new_file_perms = (unsigned int)arg; - break; + if(Curl_ssl_supports(data, SSLSUPP_SIGNATURE_ALGORITHMS)) + return Curl_setstropt(&data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS], + ptr); + return CURLE_NOT_BUILT_IN; #endif #ifdef USE_SSH - case CURLOPT_NEW_DIRECTORY_PERMS: + case CURLOPT_SSH_PUBLIC_KEYFILE: /* - * Uses these permissions instead of 0755 + * Use this file instead of the $HOME/.ssh/id_dsa.pub file */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 0777)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.new_directory_perms = (unsigned int)arg; - break; -#endif + return Curl_setstropt(&data->set.str[STRING_SSH_PUBLIC_KEY], ptr); -#ifdef ENABLE_IPV6 - case CURLOPT_ADDRESS_SCOPE: + case CURLOPT_SSH_PRIVATE_KEYFILE: /* - * Use this scope id when using IPv6 - * We always get longs when passed plain numericals so we should check - * that the value fits into an unsigned 32 bit integer. + * Use this file instead of the $HOME/.ssh/id_dsa file */ - uarg = va_arg(param, unsigned long); -#if SIZEOF_LONG > 4 - if(uarg > UINT_MAX) - return CURLE_BAD_FUNCTION_ARGUMENT; -#endif - data->set.scope_id = (unsigned int)uarg; - break; -#endif - - case CURLOPT_PROTOCOLS: - /* set the bitmask for the protocols that are allowed to be used for the - transfer, which thus helps the app which takes URLs from users or other - external inputs and want to restrict what protocol(s) to deal - with. Defaults to CURLPROTO_ALL. */ - data->set.allowed_protocols = (curl_prot_t)va_arg(param, long); - break; - - case CURLOPT_REDIR_PROTOCOLS: - /* set the bitmask for the protocols that libcurl is allowed to follow to, - as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs - to be set in both bitmasks to be allowed to get redirected to. */ - data->set.redir_protocols = (curl_prot_t)va_arg(param, long); - break; - - case CURLOPT_PROTOCOLS_STR: { - curl_prot_t prot; - argptr = va_arg(param, char *); - result = protocol2num(argptr, &prot); - if(result) - return result; - data->set.allowed_protocols = prot; - break; - } - - case CURLOPT_REDIR_PROTOCOLS_STR: { - curl_prot_t prot; - argptr = va_arg(param, char *); - result = protocol2num(argptr, &prot); - if(result) - return result; - data->set.redir_protocols = prot; - break; - } - - case CURLOPT_DEFAULT_PROTOCOL: - /* Set the protocol to use when the URL doesn't include any protocol */ - result = Curl_setstropt(&data->set.str[STRING_DEFAULT_PROTOCOL], - va_arg(param, char *)); - break; -#ifndef CURL_DISABLE_SMTP - case CURLOPT_MAIL_FROM: - /* Set the SMTP mail originator */ - result = Curl_setstropt(&data->set.str[STRING_MAIL_FROM], - va_arg(param, char *)); - break; - - case CURLOPT_MAIL_AUTH: - /* Set the SMTP auth originator */ - result = Curl_setstropt(&data->set.str[STRING_MAIL_AUTH], - va_arg(param, char *)); - break; - - case CURLOPT_MAIL_RCPT: - /* Set the list of mail recipients */ - data->set.mail_rcpt = va_arg(param, struct curl_slist *); - break; - case CURLOPT_MAIL_RCPT_ALLLOWFAILS: - /* allow RCPT TO command to fail for some recipients */ - data->set.mail_rcpt_allowfails = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; -#endif - - case CURLOPT_SASL_AUTHZID: - /* Authorization identity (identity to act as) */ - result = Curl_setstropt(&data->set.str[STRING_SASL_AUTHZID], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], ptr); - case CURLOPT_SASL_IR: - /* Enable/disable SASL initial response */ - data->set.sasl_ir = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; -#ifndef CURL_DISABLE_RTSP - case CURLOPT_RTSP_REQUEST: - { +#if defined(USE_LIBSSH2) || defined(USE_LIBSSH) + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: /* - * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) - * Would this be better if the RTSPREQ_* were just moved into here? + * Option to allow for the MD5 of the host public key to be checked + * for validation purposes. */ - long in_rtspreq = va_arg(param, long); - Curl_RtspReq rtspreq = RTSPREQ_NONE; - switch(in_rtspreq) { - case CURL_RTSPREQ_OPTIONS: - rtspreq = RTSPREQ_OPTIONS; - break; - - case CURL_RTSPREQ_DESCRIBE: - rtspreq = RTSPREQ_DESCRIBE; - break; - - case CURL_RTSPREQ_ANNOUNCE: - rtspreq = RTSPREQ_ANNOUNCE; - break; - - case CURL_RTSPREQ_SETUP: - rtspreq = RTSPREQ_SETUP; - break; - - case CURL_RTSPREQ_PLAY: - rtspreq = RTSPREQ_PLAY; - break; - - case CURL_RTSPREQ_PAUSE: - rtspreq = RTSPREQ_PAUSE; - break; - - case CURL_RTSPREQ_TEARDOWN: - rtspreq = RTSPREQ_TEARDOWN; - break; + return Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], ptr); - case CURL_RTSPREQ_GET_PARAMETER: - rtspreq = RTSPREQ_GET_PARAMETER; - break; + case CURLOPT_SSH_KNOWNHOSTS: + /* + * Store the filename to read known hosts from. + */ + return Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], ptr); +#endif + case CURLOPT_SSH_KEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_keyfunc_userp = ptr; + break; +#ifdef USE_LIBSSH2 + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: + /* + * Option to allow for the SHA256 of the host public key to be checked + * for validation purposes. + */ + return Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], + ptr); - case CURL_RTSPREQ_SET_PARAMETER: - rtspreq = RTSPREQ_SET_PARAMETER; - break; + case CURLOPT_SSH_HOSTKEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_hostkeyfunc_userp = ptr; + break; +#endif /* USE_LIBSSH2 */ +#endif /* USE_SSH */ + case CURLOPT_PROTOCOLS_STR: + if(ptr) + return protocol2num(ptr, &data->set.allowed_protocols); + /* make a NULL argument reset to default */ + data->set.allowed_protocols = (curl_prot_t) CURLPROTO_ALL; + break; - case CURL_RTSPREQ_RECORD: - rtspreq = RTSPREQ_RECORD; - break; + case CURLOPT_REDIR_PROTOCOLS_STR: + if(ptr) + return protocol2num(ptr, &data->set.redir_protocols); + /* make a NULL argument reset to default */ + data->set.redir_protocols = (curl_prot_t) CURLPROTO_REDIR; + break; - case CURL_RTSPREQ_RECEIVE: - rtspreq = RTSPREQ_RECEIVE; - break; - default: - rtspreq = RTSPREQ_NONE; - } + case CURLOPT_DEFAULT_PROTOCOL: + /* Set the protocol to use when the URL does not include any protocol */ + return Curl_setstropt(&data->set.str[STRING_DEFAULT_PROTOCOL], ptr); - data->set.rtspreq = rtspreq; - break; - } +#ifndef CURL_DISABLE_SMTP + case CURLOPT_MAIL_FROM: + /* Set the SMTP mail originator */ + return Curl_setstropt(&data->set.str[STRING_MAIL_FROM], ptr); + case CURLOPT_MAIL_AUTH: + /* Set the SMTP auth originator */ + return Curl_setstropt(&data->set.str[STRING_MAIL_AUTH], ptr); +#endif + case CURLOPT_SASL_AUTHZID: + /* Authorization identity (identity to act as) */ + return Curl_setstropt(&data->set.str[STRING_SASL_AUTHZID], ptr); +#ifndef CURL_DISABLE_RTSP case CURLOPT_RTSP_SESSION_ID: /* * Set the RTSP Session ID manually. Useful if the application is * resuming a previously established RTSP session */ - result = Curl_setstropt(&data->set.str[STRING_RTSP_SESSION_ID], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_RTSP_SESSION_ID], ptr); case CURLOPT_RTSP_STREAM_URI: /* * Set the Stream URI for the RTSP request. Unless the request is * for generic server options, the application will need to set this. */ - result = Curl_setstropt(&data->set.str[STRING_RTSP_STREAM_URI], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_RTSP_STREAM_URI], ptr); case CURLOPT_RTSP_TRANSPORT: /* * The content of the Transport: header for the RTSP request */ - result = Curl_setstropt(&data->set.str[STRING_RTSP_TRANSPORT], - va_arg(param, char *)); - break; - - case CURLOPT_RTSP_CLIENT_CSEQ: - /* - * Set the CSEQ number to issue for the next RTSP request. Useful if the - * application is resuming a previously broken connection. The CSEQ - * will increment from this new number henceforth. - */ - data->state.rtsp_next_client_CSeq = va_arg(param, long); - break; - - case CURLOPT_RTSP_SERVER_CSEQ: - /* Same as the above, but for server-initiated requests */ - data->state.rtsp_next_server_CSeq = va_arg(param, long); - break; + return Curl_setstropt(&data->set.str[STRING_RTSP_TRANSPORT], ptr); case CURLOPT_INTERLEAVEDATA: - data->set.rtp_out = va_arg(param, void *); - break; - case CURLOPT_INTERLEAVEFUNCTION: - /* Set the user defined RTP write function */ - data->set.fwrite_rtp = va_arg(param, curl_write_callback); + data->set.rtp_out = ptr; break; -#endif +#endif /* ! CURL_DISABLE_RTSP */ #ifndef CURL_DISABLE_FTP - case CURLOPT_WILDCARDMATCH: - data->set.wildcard_enabled = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_CHUNK_BGN_FUNCTION: - data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback); - break; - case CURLOPT_CHUNK_END_FUNCTION: - data->set.chunk_end = va_arg(param, curl_chunk_end_callback); - break; - case CURLOPT_FNMATCH_FUNCTION: - data->set.fnmatch = va_arg(param, curl_fnmatch_callback); - break; case CURLOPT_CHUNK_DATA: - data->set.wildcardptr = va_arg(param, void *); + data->set.wildcardptr = ptr; break; case CURLOPT_FNMATCH_DATA: - data->set.fnmatch_data = va_arg(param, void *); + data->set.fnmatch_data = ptr; break; #endif #ifdef USE_TLS_SRP case CURLOPT_TLSAUTH_USERNAME: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], ptr); + #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_USERNAME: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY], ptr); + #endif case CURLOPT_TLSAUTH_PASSWORD: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], ptr); + #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_PASSWORD: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY], ptr); #endif case CURLOPT_TLSAUTH_TYPE: - argptr = va_arg(param, char *); - if(argptr && !strncasecompare(argptr, "SRP", strlen("SRP"))) + if(ptr && !strcasecompare(ptr, "SRP")) return CURLE_BAD_FUNCTION_ARGUMENT; break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_TYPE: - argptr = va_arg(param, char *); - if(argptr || !strncasecompare(argptr, "SRP", strlen("SRP"))) + if(ptr && !strcasecompare(ptr, "SRP")) return CURLE_BAD_FUNCTION_ARGUMENT; break; #endif #endif -#ifdef USE_ARES +#ifdef CURLRES_ARES case CURLOPT_DNS_SERVERS: - result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], ptr); if(result) return result; - result = Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); - break; + return Curl_async_ares_set_dns_servers(data); + case CURLOPT_DNS_INTERFACE: - result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], ptr); if(result) return result; - result = Curl_set_dns_interface(data, data->set.str[STRING_DNS_INTERFACE]); - break; + return Curl_async_ares_set_dns_interface(data); + case CURLOPT_DNS_LOCAL_IP4: - result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], ptr); if(result) return result; - result = Curl_set_dns_local_ip4(data, data->set.str[STRING_DNS_LOCAL_IP4]); - break; + return Curl_async_ares_set_dns_local_ip4(data); + case CURLOPT_DNS_LOCAL_IP6: - result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], ptr); if(result) return result; - result = Curl_set_dns_local_ip6(data, data->set.str[STRING_DNS_LOCAL_IP6]); + return Curl_async_ares_set_dns_local_ip6(data); + +#endif +#ifdef USE_UNIX_SOCKETS + case CURLOPT_UNIX_SOCKET_PATH: + data->set.abstract_unix_socket = FALSE; + return Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], ptr); + + case CURLOPT_ABSTRACT_UNIX_SOCKET: + data->set.abstract_unix_socket = TRUE; + return Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], ptr); + +#endif + +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_URL: + result = Curl_setstropt(&data->set.str[STRING_DOH], ptr); + data->set.doh = !!(data->set.str[STRING_DOH]); break; #endif - case CURLOPT_TCP_KEEPALIVE: - data->set.tcp_keepalive = (0 != va_arg(param, long)) ? TRUE : FALSE; +#ifndef CURL_DISABLE_HSTS + case CURLOPT_HSTSREADDATA: + data->set.hsts_read_userp = ptr; break; - case CURLOPT_TCP_KEEPIDLE: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - else if(arg > INT_MAX) - arg = INT_MAX; - data->set.tcp_keepidle = (int)arg; + case CURLOPT_HSTSWRITEDATA: + data->set.hsts_write_userp = ptr; break; - case CURLOPT_TCP_KEEPINTVL: - arg = va_arg(param, long); - if(arg < 0) + case CURLOPT_HSTS: { + struct curl_slist *h; + if(!data->hsts) { + data->hsts = Curl_hsts_init(); + if(!data->hsts) + return CURLE_OUT_OF_MEMORY; + } + if(ptr) { + result = Curl_setstropt(&data->set.str[STRING_HSTS], ptr); + if(result) + return result; + /* this needs to build a list of filenames to read from, so that it can + read them later, as we might get a shared HSTS handle to load them + into */ + h = curl_slist_append(data->state.hstslist, ptr); + if(!h) { + curl_slist_free_all(data->state.hstslist); + data->state.hstslist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->state.hstslist = h; /* store the list for later use */ + } + else { + /* clear the list of HSTS files */ + curl_slist_free_all(data->state.hstslist); + data->state.hstslist = NULL; + if(!data->share || !data->share->hsts) + /* throw away the HSTS cache unless shared */ + Curl_hsts_cleanup(&data->hsts); + } + break; + } +#endif /* ! CURL_DISABLE_HSTS */ +#ifndef CURL_DISABLE_ALTSVC + case CURLOPT_ALTSVC: + if(!data->asi) { + data->asi = Curl_altsvc_init(); + if(!data->asi) + return CURLE_OUT_OF_MEMORY; + } + result = Curl_setstropt(&data->set.str[STRING_ALTSVC], ptr); + if(result) + return result; + if(ptr) + (void)Curl_altsvc_load(data->asi, ptr); + break; +#endif /* ! CURL_DISABLE_ALTSVC */ +#ifdef USE_ECH + case CURLOPT_ECH: { + size_t plen = 0; + + if(!ptr) { + data->set.tls_ech = CURLECH_DISABLE; + return CURLE_OK; + } + plen = strlen(ptr); + if(plen > CURL_MAX_INPUT_LENGTH) { + data->set.tls_ech = CURLECH_DISABLE; return CURLE_BAD_FUNCTION_ARGUMENT; - else if(arg > INT_MAX) - arg = INT_MAX; - data->set.tcp_keepintvl = (int)arg; + } + /* set tls_ech flag value, preserving CLA_CFG bit */ + if(!strcmp(ptr, "false")) + data->set.tls_ech = CURLECH_DISABLE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(!strcmp(ptr, "grease")) + data->set.tls_ech = CURLECH_GREASE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(!strcmp(ptr, "true")) + data->set.tls_ech = CURLECH_ENABLE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(!strcmp(ptr, "hard")) + data->set.tls_ech = CURLECH_HARD | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen > 5 && !strncmp(ptr, "ecl:", 4)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_CONFIG], ptr + 4); + if(result) + return result; + data->set.tls_ech |= CURLECH_CLA_CFG; + } + else if(plen > 4 && !strncmp(ptr, "pn:", 3)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_PUBLIC], ptr + 3); + if(result) + return result; + } break; - case CURLOPT_TCP_FASTOPEN: -#if defined(CONNECT_DATA_IDEMPOTENT) || defined(MSG_FASTOPEN) || \ - defined(TCP_FASTOPEN_CONNECT) - data->set.tcp_fastopen = (0 != va_arg(param, long))?TRUE:FALSE; -#else - result = CURLE_NOT_BUILT_IN; + } #endif + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} + +static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, + va_list param) +{ + switch(option) { + case CURLOPT_PROGRESSFUNCTION: + /* + * Progress callback function + */ + data->set.fprogress = va_arg(param, curl_progress_callback); + if(data->set.fprogress) + data->progress.callback = TRUE; /* no longer internal */ + else + data->progress.callback = FALSE; /* NULL enforces internal */ break; - case CURLOPT_SSL_ENABLE_NPN: + + case CURLOPT_XFERINFOFUNCTION: + /* + * Transfer info callback function + */ + data->set.fxferinfo = va_arg(param, curl_xferinfo_callback); + if(data->set.fxferinfo) + data->progress.callback = TRUE; /* no longer internal */ + else + data->progress.callback = FALSE; /* NULL enforces internal */ + break; - case CURLOPT_SSL_ENABLE_ALPN: - data->set.ssl_enable_alpn = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_DEBUGFUNCTION: + /* + * stderr write callback. + */ + data->set.fdebug = va_arg(param, curl_debug_callback); + /* + * if the callback provided is NULL, it will use the default callback + */ break; -#ifdef USE_UNIX_SOCKETS - case CURLOPT_UNIX_SOCKET_PATH: - data->set.abstract_unix_socket = FALSE; - result = Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], - va_arg(param, char *)); + case CURLOPT_HEADERFUNCTION: + /* + * Set header write callback + */ + data->set.fwrite_header = va_arg(param, curl_write_callback); break; - case CURLOPT_ABSTRACT_UNIX_SOCKET: - data->set.abstract_unix_socket = TRUE; - result = Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], - va_arg(param, char *)); + case CURLOPT_WRITEFUNCTION: + /* + * Set data write callback + */ + data->set.fwrite_func = va_arg(param, curl_write_callback); + if(!data->set.fwrite_func) + /* When set to NULL, reset to our internal default function */ + data->set.fwrite_func = (curl_write_callback)fwrite; break; -#endif - - case CURLOPT_PATH_AS_IS: - data->set.path_as_is = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_READFUNCTION: + /* + * Read data callback + */ + data->set.fread_func_set = va_arg(param, curl_read_callback); + if(!data->set.fread_func_set) { + data->set.is_fread_set = 0; + /* When set to NULL, reset to our internal default function */ + data->set.fread_func_set = (curl_read_callback)fread; + } + else + data->set.is_fread_set = 1; break; - case CURLOPT_PIPEWAIT: - data->set.pipewait = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_SEEKFUNCTION: + /* + * Seek callback. Might be NULL. + */ + data->set.seek_func = va_arg(param, curl_seek_callback); break; - case CURLOPT_STREAM_WEIGHT: -#if defined(USE_HTTP2) || defined(USE_HTTP3) - arg = va_arg(param, long); - if((arg >= 1) && (arg <= 256)) - data->set.priority.weight = (int)arg; + case CURLOPT_IOCTLFUNCTION: + /* + * I/O control callback. Might be NULL. + */ + data->set.ioctl_func = va_arg(param, curl_ioctl_callback); break; -#else - return CURLE_NOT_BUILT_IN; -#endif - case CURLOPT_STREAM_DEPENDS: - case CURLOPT_STREAM_DEPENDS_E: - { - struct Curl_easy *dep = va_arg(param, struct Curl_easy *); - if(!dep || GOOD_EASY_HANDLE(dep)) { - return Curl_data_priority_add_child(dep, data, - option == CURLOPT_STREAM_DEPENDS_E); + case CURLOPT_SSL_CTX_FUNCTION: + /* + * Set an SSL_CTX callback + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { + data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); + break; } + else +#endif + return CURLE_NOT_BUILT_IN; + + case CURLOPT_SOCKOPTFUNCTION: + /* + * socket callback function: called after socket() but before connect() + */ + data->set.fsockopt = va_arg(param, curl_sockopt_callback); break; - } - case CURLOPT_CONNECT_TO: - data->set.connect_to = va_arg(param, struct curl_slist *); + + case CURLOPT_OPENSOCKETFUNCTION: + /* + * open/create socket callback function: called instead of socket(), + * before connect() + */ + data->set.fopensocket = va_arg(param, curl_opensocket_callback); break; - case CURLOPT_SUPPRESS_CONNECT_HEADERS: - data->set.suppress_connect_headers = (0 != va_arg(param, long))?TRUE:FALSE; + + case CURLOPT_CLOSESOCKETFUNCTION: + /* + * close socket callback function: called instead of close() + * when shutting down a connection + */ + data->set.fclosesocket = va_arg(param, curl_closesocket_callback); break; - case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: - uarg = va_arg(param, unsigned long); - if(uarg > UINT_MAX) - uarg = UINT_MAX; - data->set.happy_eyeballs_timeout = (unsigned int)uarg; + + case CURLOPT_RESOLVER_START_FUNCTION: + /* + * resolver start callback function: called before a new resolver request + * is started + */ + data->set.resolver_start = va_arg(param, curl_resolver_start_callback); break; -#ifndef CURL_DISABLE_SHUFFLE_DNS - case CURLOPT_DNS_SHUFFLE_ADDRESSES: - data->set.dns_shuffle_addresses = (0 != va_arg(param, long)) ? TRUE:FALSE; + +#ifdef USE_SSH +#ifdef USE_LIBSSH2 + case CURLOPT_SSH_HOSTKEYFUNCTION: + /* the callback to check the hostkey without the knownhost file */ + data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback); break; #endif - case CURLOPT_DISALLOW_USERNAME_IN_URL: - data->set.disallow_username_in_url = - (0 != va_arg(param, long)) ? TRUE : FALSE; + + case CURLOPT_SSH_KEYFUNCTION: + /* setting to NULL is fine since the ssh.c functions themselves will + then revert to use the internal default */ + data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_URL: - result = Curl_setstropt(&data->set.str[STRING_DOH], - va_arg(param, char *)); - data->set.doh = data->set.str[STRING_DOH]?TRUE:FALSE; + +#endif /* USE_SSH */ + +#ifndef CURL_DISABLE_RTSP + case CURLOPT_INTERLEAVEFUNCTION: + /* Set the user defined RTP write function */ + data->set.fwrite_rtp = va_arg(param, curl_write_callback); break; #endif - case CURLOPT_UPKEEP_INTERVAL_MS: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.upkeep_interval_ms = arg; +#ifndef CURL_DISABLE_FTP + case CURLOPT_CHUNK_BGN_FUNCTION: + data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback); break; - case CURLOPT_MAXAGE_CONN: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.maxage_conn = arg; + case CURLOPT_CHUNK_END_FUNCTION: + data->set.chunk_end = va_arg(param, curl_chunk_end_callback); break; - case CURLOPT_MAXLIFETIME_CONN: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.maxlifetime_conn = arg; + case CURLOPT_FNMATCH_FUNCTION: + data->set.fnmatch = va_arg(param, curl_fnmatch_callback); break; - case CURLOPT_TRAILERFUNCTION: +#endif #ifndef CURL_DISABLE_HTTP + case CURLOPT_TRAILERFUNCTION: data->set.trailer_callback = va_arg(param, curl_trailer_callback); -#endif break; - case CURLOPT_TRAILERDATA: -#ifndef CURL_DISABLE_HTTP - data->set.trailer_data = va_arg(param, void *); #endif - break; #ifndef CURL_DISABLE_HSTS case CURLOPT_HSTSREADFUNCTION: data->set.hsts_read = va_arg(param, curl_hstsread_callback); break; - case CURLOPT_HSTSREADDATA: - data->set.hsts_read_userp = va_arg(param, void *); - break; case CURLOPT_HSTSWRITEFUNCTION: data->set.hsts_write = va_arg(param, curl_hstswrite_callback); break; - case CURLOPT_HSTSWRITEDATA: - data->set.hsts_write_userp = va_arg(param, void *); - break; - case CURLOPT_HSTS: { - struct curl_slist *h; - if(!data->hsts) { - data->hsts = Curl_hsts_init(); - if(!data->hsts) - return CURLE_OUT_OF_MEMORY; - } - argptr = va_arg(param, char *); - if(argptr) { - result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr); - if(result) - return result; - /* this needs to build a list of file names to read from, so that it can - read them later, as we might get a shared HSTS handle to load them - into */ - h = curl_slist_append(data->set.hstslist, argptr); - if(!h) { - curl_slist_free_all(data->set.hstslist); - data->set.hstslist = NULL; - return CURLE_OUT_OF_MEMORY; - } - data->set.hstslist = h; /* store the list for later use */ - } - else { - /* clear the list of HSTS files */ - curl_slist_free_all(data->set.hstslist); - data->set.hstslist = NULL; - if(!data->share || !data->share->hsts) - /* throw away the HSTS cache unless shared */ - Curl_hsts_cleanup(&data->hsts); - } +#endif + case CURLOPT_PREREQFUNCTION: + data->set.fprereq = va_arg(param, curl_prereq_callback); break; + default: + return CURLE_UNKNOWN_OPTION; } - case CURLOPT_HSTS_CTRL: - arg = va_arg(param, long); - if(arg & CURLHSTS_ENABLE) { - if(!data->hsts) { - data->hsts = Curl_hsts_init(); - if(!data->hsts) - return CURLE_OUT_OF_MEMORY; - } - } - else - Curl_hsts_cleanup(&data->hsts); + return CURLE_OK; +} + +static CURLcode setopt_offt(struct Curl_easy *data, CURLoption option, + curl_off_t offt) +{ + switch(option) { + case CURLOPT_TIMEVALUE_LARGE: + /* + * This is the value to compare with the remote document with the + * method set with CURLOPT_TIMECONDITION + */ + data->set.timevalue = (time_t)offt; break; -#endif -#ifndef CURL_DISABLE_ALTSVC - case CURLOPT_ALTSVC: - if(!data->asi) { - data->asi = Curl_altsvc_init(); - if(!data->asi) - return CURLE_OUT_OF_MEMORY; + + /* MQTT "borrows" some of the HTTP options */ + case CURLOPT_POSTFIELDSIZE_LARGE: + /* + * The size of the POSTFIELD data to prevent libcurl to do strlen() to + * figure it out. Enables binary posts. + */ + if(offt < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(data->set.postfieldsize < offt && + data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { + /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); + data->set.postfields = NULL; } - argptr = va_arg(param, char *); - result = Curl_setstropt(&data->set.str[STRING_ALTSVC], argptr); - if(result) - return result; - if(argptr) - (void)Curl_altsvc_load(data->asi, argptr); + data->set.postfieldsize = offt; break; - case CURLOPT_ALTSVC_CTRL: - if(!data->asi) { - data->asi = Curl_altsvc_init(); - if(!data->asi) - return CURLE_OUT_OF_MEMORY; - } - arg = va_arg(param, long); - result = Curl_altsvc_ctrl(data->asi, arg); - if(result) - return result; + case CURLOPT_INFILESIZE_LARGE: + /* + * If known, this should inform curl about the file size of the + * to-be-uploaded file. + */ + if(offt < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.filesize = offt; break; -#endif - case CURLOPT_PREREQFUNCTION: - data->set.fprereq = va_arg(param, curl_prereq_callback); + case CURLOPT_MAX_SEND_SPEED_LARGE: + /* + * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE + * bytes per second the transfer is throttled.. + */ + if(offt < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_send_speed = offt; break; - case CURLOPT_PREREQDATA: - data->set.prereq_userp = va_arg(param, void *); + case CURLOPT_MAX_RECV_SPEED_LARGE: + /* + * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per + * second the transfer is throttled.. + */ + if(offt < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_recv_speed = offt; + break; + case CURLOPT_RESUME_FROM_LARGE: + /* + * Resume transfer at the given file position + */ + if(offt < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.set_resume_from = offt; break; -#ifdef USE_WEBSOCKETS - case CURLOPT_WS_OPTIONS: { - bool raw; - arg = va_arg(param, long); - raw = (arg & CURLWS_RAW_MODE); - data->set.ws_raw_mode = raw; + case CURLOPT_MAXFILESIZE_LARGE: + /* + * Set the maximum size of a file to download. + */ + if(offt < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_filesize = offt; break; + + default: + return CURLE_UNKNOWN_OPTION; } + return CURLE_OK; +} + +static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, + struct curl_blob *blob) +{ + switch(option) { + case CURLOPT_SSLCERT_BLOB: + /* + * Blob that holds file content of the SSL certificate to use + */ + return Curl_setblobopt(&data->set.blobs[BLOB_CERT], blob); +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLCERT_BLOB: + /* + * Blob that holds file content of the SSL certificate to use for proxy + */ + return Curl_setblobopt(&data->set.blobs[BLOB_CERT_PROXY], blob); + case CURLOPT_PROXY_SSLKEY_BLOB: + /* + * Blob that holds file content of the SSL key to use for proxy + */ + return Curl_setblobopt(&data->set.blobs[BLOB_KEY_PROXY], blob); + case CURLOPT_PROXY_CAINFO_BLOB: + /* + * Blob that holds CA info for SSL connection proxy. + * Specify entire PEM of the CA certificate + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) + return Curl_setblobopt(&data->set.blobs[BLOB_CAINFO_PROXY], blob); #endif - case CURLOPT_QUICK_EXIT: - data->set.quick_exit = (0 != va_arg(param, long)) ? 1L:0L; - break; + return CURLE_NOT_BUILT_IN; + case CURLOPT_PROXY_ISSUERCERT_BLOB: + /* + * Blob that holds Issuer certificate to check certificates issuer + */ + return Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY], + blob); +#endif + case CURLOPT_SSLKEY_BLOB: + /* + * Blob that holds file content of the SSL key to use + */ + return Curl_setblobopt(&data->set.blobs[BLOB_KEY], blob); + case CURLOPT_CAINFO_BLOB: + /* + * Blob that holds CA info for SSL connection. + * Specify entire PEM of the CA certificate + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) + return Curl_setblobopt(&data->set.blobs[BLOB_CAINFO], blob); +#endif + return CURLE_NOT_BUILT_IN; + case CURLOPT_ISSUERCERT_BLOB: + /* + * Blob that holds Issuer certificate to check certificates issuer + */ + return Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT], blob); + default: - /* unknown tag and its companion, just ignore: */ - result = CURLE_UNKNOWN_OPTION; - break; + return CURLE_UNKNOWN_OPTION; } + /* unreachable */ +} - return result; +/* + * Do not make Curl_vsetopt() static: it is called from + * packages/OS400/ccsidcurl.c. + */ +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) +{ + if(option < CURLOPTTYPE_OBJECTPOINT) + return setopt_long(data, option, va_arg(param, long)); + else if(option < CURLOPTTYPE_FUNCTIONPOINT) { + /* unfortunately, different pointer types cannot be identified any other + way than being listed explicitly */ + switch(option) { + case CURLOPT_HTTPHEADER: + case CURLOPT_QUOTE: + case CURLOPT_POSTQUOTE: + case CURLOPT_TELNETOPTIONS: + case CURLOPT_PREQUOTE: + case CURLOPT_HTTP200ALIASES: + case CURLOPT_MAIL_RCPT: + case CURLOPT_RESOLVE: + case CURLOPT_PROXYHEADER: + case CURLOPT_CONNECT_TO: + return setopt_slist(data, option, va_arg(param, struct curl_slist *)); + case CURLOPT_HTTPPOST: /* curl_httppost * */ + case CURLOPT_MIMEPOST: /* curl_mime * */ + case CURLOPT_STDERR: /* FILE * */ + case CURLOPT_SHARE: /* CURLSH * */ + case CURLOPT_STREAM_DEPENDS: /* CURL * */ + case CURLOPT_STREAM_DEPENDS_E: /* CURL * */ + return setopt_pointers(data, option, param); + default: + break; + } + /* the char pointer options */ + return setopt_cptr(data, option, va_arg(param, char *)); + } + else if(option < CURLOPTTYPE_OFF_T) + return setopt_func(data, option, param); + else if(option < CURLOPTTYPE_BLOB) + return setopt_offt(data, option, va_arg(param, curl_off_t)); + return setopt_blob(data, option, va_arg(param, struct curl_blob *)); } /* @@ -3168,10 +3079,11 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) */ #undef curl_easy_setopt -CURLcode curl_easy_setopt(struct Curl_easy *data, CURLoption tag, ...) +CURLcode curl_easy_setopt(CURL *d, CURLoption tag, ...) { va_list arg; CURLcode result; + struct Curl_easy *data = d; if(!data) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -3181,5 +3093,7 @@ CURLcode curl_easy_setopt(struct Curl_easy *data, CURLoption tag, ...) result = Curl_vsetopt(data, tag, arg); va_end(arg); + if(result == CURLE_BAD_FUNCTION_ARGUMENT) + failf(data, "setopt 0x%x got bad argument", tag); return result; } diff --git a/Utilities/cmcurl/lib/setopt.h b/Utilities/cmcurl/lib/setopt.h index 3c14a05e374..b0237467bd6 100644 --- a/Utilities/cmcurl/lib/setopt.h +++ b/Utilities/cmcurl/lib/setopt.h @@ -24,9 +24,10 @@ * ***************************************************************************/ -CURLcode Curl_setstropt(char **charp, const char *s); +CURLcode Curl_setstropt(char **charp, const char *s) WARN_UNUSED_RESULT; CURLcode Curl_setblobopt(struct curl_blob **blobp, - const struct curl_blob *blob); -CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg); + const struct curl_blob *blob) WARN_UNUSED_RESULT; +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg) + WARN_UNUSED_RESULT; #endif /* HEADER_CURL_SETOPT_H */ diff --git a/Utilities/cmcurl/lib/setup-os400.h b/Utilities/cmcurl/lib/setup-os400.h index 759583466ce..ef7baca67ef 100644 --- a/Utilities/cmcurl/lib/setup-os400.h +++ b/Utilities/cmcurl/lib/setup-os400.h @@ -34,6 +34,18 @@ /* No OS/400 header file defines u_int32_t. */ typedef unsigned long u_int32_t; +/* OS/400 has no idea of a tty! */ +#define isatty(fd) 0 + + +/* Workaround bug in IBM QADRT runtime library: + * function puts() does not output the implicit trailing newline. + */ + +#include /* Be sure it is loaded. */ +#undef puts +#define puts(s) (fputs((s), stdout) == EOF? EOF: putchar('\n')) + /* System API wrapper prototypes & definitions to support ASCII parameters. */ @@ -43,6 +55,8 @@ typedef unsigned long u_int32_t; #include #include +#ifdef BUILDING_LIBCURL + extern int Curl_getaddrinfo_a(const char *nodename, const char *servname, const struct addrinfo *hints, @@ -57,94 +71,6 @@ extern int Curl_getnameinfo_a(const struct sockaddr *sa, int flags); #define getnameinfo Curl_getnameinfo_a - -/* GSKit wrappers. */ - -extern int Curl_gsk_environment_open(gsk_handle * my_env_handle); -#define gsk_environment_open Curl_gsk_environment_open - -extern int Curl_gsk_secure_soc_open(gsk_handle my_env_handle, - gsk_handle * my_session_handle); -#define gsk_secure_soc_open Curl_gsk_secure_soc_open - -extern int Curl_gsk_environment_close(gsk_handle * my_env_handle); -#define gsk_environment_close Curl_gsk_environment_close - -extern int Curl_gsk_secure_soc_close(gsk_handle * my_session_handle); -#define gsk_secure_soc_close Curl_gsk_secure_soc_close - -extern int Curl_gsk_environment_init(gsk_handle my_env_handle); -#define gsk_environment_init Curl_gsk_environment_init - -extern int Curl_gsk_secure_soc_init(gsk_handle my_session_handle); -#define gsk_secure_soc_init Curl_gsk_secure_soc_init - -extern int Curl_gsk_attribute_set_buffer_a(gsk_handle my_gsk_handle, - GSK_BUF_ID bufID, - const char *buffer, - int bufSize); -#define gsk_attribute_set_buffer Curl_gsk_attribute_set_buffer_a - -extern int Curl_gsk_attribute_set_enum(gsk_handle my_gsk_handle, - GSK_ENUM_ID enumID, - GSK_ENUM_VALUE enumValue); -#define gsk_attribute_set_enum Curl_gsk_attribute_set_enum - -extern int Curl_gsk_attribute_set_numeric_value(gsk_handle my_gsk_handle, - GSK_NUM_ID numID, - int numValue); -#define gsk_attribute_set_numeric_value Curl_gsk_attribute_set_numeric_value - -extern int Curl_gsk_attribute_set_callback(gsk_handle my_gsk_handle, - GSK_CALLBACK_ID callBackID, - void *callBackAreaPtr); -#define gsk_attribute_set_callback Curl_gsk_attribute_set_callback - -extern int Curl_gsk_attribute_get_buffer_a(gsk_handle my_gsk_handle, - GSK_BUF_ID bufID, - const char **buffer, - int *bufSize); -#define gsk_attribute_get_buffer Curl_gsk_attribute_get_buffer_a - -extern int Curl_gsk_attribute_get_enum(gsk_handle my_gsk_handle, - GSK_ENUM_ID enumID, - GSK_ENUM_VALUE *enumValue); -#define gsk_attribute_get_enum Curl_gsk_attribute_get_enum - -extern int Curl_gsk_attribute_get_numeric_value(gsk_handle my_gsk_handle, - GSK_NUM_ID numID, - int *numValue); -#define gsk_attribute_get_numeric_value Curl_gsk_attribute_get_numeric_value - -extern int Curl_gsk_attribute_get_cert_info(gsk_handle my_gsk_handle, - GSK_CERT_ID certID, - const gsk_cert_data_elem **certDataElem, - int *certDataElementCount); -#define gsk_attribute_get_cert_info Curl_gsk_attribute_get_cert_info - -extern int Curl_gsk_secure_soc_misc(gsk_handle my_session_handle, - GSK_MISC_ID miscID); -#define gsk_secure_soc_misc Curl_gsk_secure_soc_misc - -extern int Curl_gsk_secure_soc_read(gsk_handle my_session_handle, - char *readBuffer, - int readBufSize, int *amtRead); -#define gsk_secure_soc_read Curl_gsk_secure_soc_read - -extern int Curl_gsk_secure_soc_write(gsk_handle my_session_handle, - char *writeBuffer, - int writeBufSize, int *amtWritten); -#define gsk_secure_soc_write Curl_gsk_secure_soc_write - -extern const char * Curl_gsk_strerror_a(int gsk_return_value); -#define gsk_strerror Curl_gsk_strerror_a - -extern int Curl_gsk_secure_soc_startInit(gsk_handle my_session_handle, - int IOCompletionPort, - Qso_OverlappedIO_t * communicationsArea); -#define gsk_secure_soc_startInit Curl_gsk_secure_soc_startInit - - /* GSSAPI wrappers. */ extern OM_uint32 Curl_gss_import_name_a(OM_uint32 * minor_status, @@ -226,4 +152,6 @@ extern int Curl_os400_getsockname(int sd, struct sockaddr *addr, int *addrlen); #define inflateEnd Curl_os400_inflateEnd #endif +#endif /* BUILDING_LIBCURL */ + #endif /* HEADER_CURL_SETUP_OS400_H */ diff --git a/Utilities/cmcurl/lib/setup-vms.h b/Utilities/cmcurl/lib/setup-vms.h index 46657b2cd4b..d74f4ad6305 100644 --- a/Utilities/cmcurl/lib/setup-vms.h +++ b/Utilities/cmcurl/lib/setup-vms.h @@ -101,7 +101,7 @@ static char *vms_translate_path(const char *path) } } # else - /* VMS translate path is actually not needed on the current 64 bit */ + /* VMS translate path is actually not needed on the current 64-bit */ /* VMS platforms, so instead of figuring out the pointer settings */ /* Change it to a noop */ # define vms_translate_path(__path) __path @@ -144,7 +144,7 @@ static struct passwd *vms_getpwuid(uid_t uid) { struct passwd *my_passwd; -/* Hack needed to support 64 bit builds, decc_getpwnam is 32 bit only */ +/* Hack needed to support 64-bit builds, decc_getpwnam is 32-bit only */ #ifdef __DECC # if __INITIAL_POINTER_SIZE __char_ptr32 unix_path; @@ -262,7 +262,6 @@ static struct passwd *vms_getpwuid(uid_t uid) #define PKCS12_parse PKCS12_PARSE #define RAND_add RAND_ADD #define RAND_bytes RAND_BYTES -#define RAND_egd RAND_EGD #define RAND_file_name RAND_FILE_NAME #define RAND_load_file RAND_LOAD_FILE #define RAND_status RAND_STATUS @@ -371,12 +370,12 @@ static struct passwd *vms_getpwuid(uid_t uid) #define USE_UPPERCASE_KRBAPI 1 -/* AI_NUMERICHOST needed for IP V6 support in Curl */ +/* AI_NUMERICHOST needed for IP V6 support in curl */ #ifdef HAVE_NETDB_H #include #ifndef AI_NUMERICHOST -#ifdef ENABLE_IPV6 -#undef ENABLE_IPV6 +#ifdef USE_IPV6 +#undef USE_IPV6 #endif #endif #endif @@ -395,51 +394,11 @@ static struct passwd *vms_getpwuid(uid_t uid) /* that way a newer port will also work if some one has one */ #ifdef __VAX -# if (OPENSSL_VERSION_NUMBER < 0x00907001L) -# define des_set_odd_parity DES_SET_ODD_PARITY -# define des_set_key DES_SET_KEY -# define des_ecb_encrypt DES_ECB_ENCRYPT - -# endif # include # ifndef OpenSSL_add_all_algorithms # define OpenSSL_add_all_algorithms OPENSSL_ADD_ALL_ALGORITHMS void OPENSSL_ADD_ALL_ALGORITHMS(void); # endif - - /* Curl defines these to lower case and VAX needs them in upper case */ - /* So we need static routines */ -# if (OPENSSL_VERSION_NUMBER < 0x00907001L) - -# undef des_set_odd_parity -# undef DES_set_odd_parity -# undef des_set_key -# undef DES_set_key -# undef des_ecb_encrypt -# undef DES_ecb_encrypt - - static void des_set_odd_parity(des_cblock *key) { - DES_SET_ODD_PARITY(key); - } - - static int des_set_key(const_des_cblock *key, - des_key_schedule schedule) { - return DES_SET_KEY(key, schedule); - } - - static void des_ecb_encrypt(const_des_cblock *input, - des_cblock *output, - des_key_schedule ks, int enc) { - DES_ECB_ENCRYPT(input, output, ks, enc); - } -#endif -/* Need this to stop a macro redefinition error */ -#if OPENSSL_VERSION_NUMBER < 0x00907000L -# ifdef X509_STORE_set_flags -# undef X509_STORE_set_flags -# define X509_STORE_set_flags(x,y) Curl_nop_stmt -# endif -#endif #endif #endif /* HEADER_CURL_SETUP_VMS_H */ diff --git a/Utilities/cmcurl/lib/setup-win32.h b/Utilities/cmcurl/lib/setup-win32.h index 13948389a41..35fe513a842 100644 --- a/Utilities/cmcurl/lib/setup-win32.h +++ b/Utilities/cmcurl/lib/setup-win32.h @@ -24,90 +24,87 @@ * ***************************************************************************/ +#undef USE_WINSOCK +/* ---------------------------------------------------------------- */ +/* Watt-32 TCP/IP SPECIFIC */ +/* ---------------------------------------------------------------- */ +#ifdef USE_WATT32 +# include +# undef byte +# undef word +# define HAVE_SYS_IOCTL_H +# define HAVE_SYS_SOCKET_H +# define HAVE_NETINET_IN_H +# define HAVE_NETDB_H +# define HAVE_ARPA_INET_H +# define SOCKET int +/* ---------------------------------------------------------------- */ +/* BSD-style lwIP TCP/IP stack SPECIFIC */ +/* ---------------------------------------------------------------- */ +#elif defined(USE_LWIPSOCK) + /* Define to use BSD-style lwIP TCP/IP stack. */ + /* #define USE_LWIPSOCK 1 */ +# undef HAVE_GETHOSTNAME +# undef LWIP_POSIX_SOCKETS_IO_NAMES +# undef RECV_TYPE_ARG1 +# undef RECV_TYPE_ARG3 +# undef SEND_TYPE_ARG1 +# undef SEND_TYPE_ARG3 +# define HAVE_GETHOSTBYNAME_R +# define HAVE_GETHOSTBYNAME_R_6 +# define LWIP_POSIX_SOCKETS_IO_NAMES 0 +# define RECV_TYPE_ARG1 int +# define RECV_TYPE_ARG3 size_t +# define SEND_TYPE_ARG1 int +# define SEND_TYPE_ARG3 size_t +#elif defined(_WIN32) +# define USE_WINSOCK 2 +#endif + /* - * Include header files for windows builds before redefining anything. + * Include header files for Windows builds before redefining anything. * Use this preprocessor block only to include or exclude windows.h, - * winsock2.h or ws2tcpip.h. Any other windows thing belongs - * to any other further and independent block. Under Cygwin things work - * just as under linux (e.g. ) and the winsock headers should - * never be included when __CYGWIN__ is defined. configure script takes - * care of this, not defining HAVE_WINDOWS_H, HAVE_WINSOCK2_H, - * neither HAVE_WS2TCPIP_H when __CYGWIN__ is defined. + * winsock2.h or ws2tcpip.h. Any other Windows thing belongs + * to any other further and independent block. Under Cygwin things work + * just as under Linux (e.g. ) and the Winsock headers should + * never be included when __CYGWIN__ is defined. */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # if defined(UNICODE) && !defined(_UNICODE) # error "UNICODE is defined but _UNICODE is not defined" # endif # if defined(_UNICODE) && !defined(UNICODE) # error "_UNICODE is defined but UNICODE is not defined" # endif -/* - * Don't include unneeded stuff in Windows headers to avoid compiler - * warnings and macro clashes. - * Make sure to define this macro before including any Windows headers. - */ -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# ifndef NOGDI -# define NOGDI -# endif -# include +# include +# include # include -# ifdef HAVE_WINSOCK2_H -# include -# ifdef HAVE_WS2TCPIP_H -# include -# endif -# endif +# include # include # ifdef UNICODE typedef wchar_t *(*curl_wcsdup_callback)(const wchar_t *str); # endif #endif -/* - * Define USE_WINSOCK to 2 if we have and use WINSOCK2 API, else - * undefine USE_WINSOCK. - */ - -#undef USE_WINSOCK - -#ifdef HAVE_WINSOCK2_H -# define USE_WINSOCK 2 -#endif - /* * Define _WIN32_WINNT_[OS] symbols because not all Windows build systems have * those symbols to compare against, and even those that do may be missing * newer symbols. */ -#ifndef _WIN32_WINNT_NT4 -#define _WIN32_WINNT_NT4 0x0400 /* Windows NT 4.0 */ -#endif -#ifndef _WIN32_WINNT_WIN2K -#define _WIN32_WINNT_WIN2K 0x0500 /* Windows 2000 */ -#endif #ifndef _WIN32_WINNT_WINXP #define _WIN32_WINNT_WINXP 0x0501 /* Windows XP */ #endif #ifndef _WIN32_WINNT_WS03 #define _WIN32_WINNT_WS03 0x0502 /* Windows Server 2003 */ #endif -#ifndef _WIN32_WINNT_WIN6 -#define _WIN32_WINNT_WIN6 0x0600 /* Windows Vista */ -#endif #ifndef _WIN32_WINNT_VISTA #define _WIN32_WINNT_VISTA 0x0600 /* Windows Vista */ #endif #ifndef _WIN32_WINNT_WS08 #define _WIN32_WINNT_WS08 0x0600 /* Windows Server 2008 */ #endif -#ifndef _WIN32_WINNT_LONGHORN -#define _WIN32_WINNT_LONGHORN 0x0600 /* Windows Vista */ -#endif #ifndef _WIN32_WINNT_WIN7 #define _WIN32_WINNT_WIN7 0x0601 /* Windows 7 */ #endif @@ -117,9 +114,6 @@ #ifndef _WIN32_WINNT_WINBLUE #define _WIN32_WINNT_WINBLUE 0x0603 /* Windows 8.1 */ #endif -#ifndef _WIN32_WINNT_WINTHRESHOLD -#define _WIN32_WINNT_WINTHRESHOLD 0x0A00 /* Windows 10 */ -#endif #ifndef _WIN32_WINNT_WIN10 #define _WIN32_WINNT_WIN10 0x0A00 /* Windows 10 */ #endif diff --git a/Utilities/cmcurl/lib/sha256.c b/Utilities/cmcurl/lib/sha256.c index 767d879c453..d73e0ef0937 100644 --- a/Utilities/cmcurl/lib/sha256.c +++ b/Utilities/cmcurl/lib/sha256.c @@ -25,50 +25,23 @@ #include "curl_setup.h" -#ifndef CURL_DISABLE_CRYPTO_AUTH +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) || defined(USE_SSL) -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_sha256.h" #include "curl_hmac.h" -#ifdef USE_WOLFSSL -#include -#ifndef NO_SHA256 -#define USE_OPENSSL_SHA256 -#endif -#endif - -#if defined(USE_OPENSSL) - -#include - -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) -#define USE_OPENSSL_SHA256 -#endif - -#endif /* USE_OPENSSL */ - -#ifdef USE_MBEDTLS +#ifdef USE_OPENSSL +#include +#elif defined(USE_GNUTLS) +#include +#elif defined(USE_MBEDTLS) #include - #if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ (MBEDTLS_VERSION_NUMBER < 0x03000000) #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS #endif -#endif /* USE_MBEDTLS */ - -#if defined(USE_OPENSSL_SHA256) - -/* When OpenSSL or wolfSSL is available we use their SHA256-functions. */ -#if defined(USE_OPENSSL) -#include -#elif defined(USE_WOLFSSL) -#include -#endif - -#elif defined(USE_GNUTLS) -#include -#elif defined(USE_MBEDTLS) #include #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040)) || \ @@ -97,32 +70,38 @@ * file even if multiple backends are enabled at the same time. */ -#if defined(USE_OPENSSL_SHA256) +#ifdef USE_OPENSSL -struct sha256_ctx { +struct ossl_sha256_ctx { EVP_MD_CTX *openssl_ctx; }; -typedef struct sha256_ctx my_sha256_ctx; +typedef struct ossl_sha256_ctx my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *in) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; ctx->openssl_ctx = EVP_MD_CTX_create(); if(!ctx->openssl_ctx) return CURLE_OUT_OF_MEMORY; - EVP_DigestInit_ex(ctx->openssl_ctx, EVP_sha256(), NULL); + if(!EVP_DigestInit_ex(ctx->openssl_ctx, EVP_sha256(), NULL)) { + EVP_MD_CTX_destroy(ctx->openssl_ctx); + return CURLE_FAILED_INIT; + } return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *in, const unsigned char *data, unsigned int length) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; EVP_DigestUpdate(ctx->openssl_ctx, data, length); } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *in) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; EVP_DigestFinal_ex(ctx->openssl_ctx, digest, NULL); EVP_MD_CTX_destroy(ctx->openssl_ctx); } @@ -131,20 +110,20 @@ static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) typedef struct sha256_ctx my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *ctx) { sha256_init(ctx); return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *ctx, const unsigned char *data, unsigned int length) { sha256_update(ctx, length, data); } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *ctx) { sha256_digest(ctx, SHA256_DIGEST_SIZE, digest); } @@ -153,7 +132,7 @@ static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) typedef mbedtls_sha256_context my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *ctx) { #if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) (void) mbedtls_sha256_starts(ctx, 0); @@ -163,7 +142,7 @@ static CURLcode my_sha256_init(my_sha256_ctx *ctx) return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *ctx, const unsigned char *data, unsigned int length) { @@ -174,7 +153,7 @@ static void my_sha256_update(my_sha256_ctx *ctx, #endif } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *ctx) { #if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) (void) mbedtls_sha256_finish(ctx, digest); @@ -186,20 +165,20 @@ static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) #elif defined(AN_APPLE_OS) typedef CC_SHA256_CTX my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *ctx) { (void) CC_SHA256_Init(ctx); return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *ctx, const unsigned char *data, unsigned int length) { (void) CC_SHA256_Update(ctx, data, length); } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *ctx) { (void) CC_SHA256_Final(digest, ctx); } @@ -216,29 +195,41 @@ typedef struct sha256_ctx my_sha256_ctx; #define CALG_SHA_256 0x0000800c #endif -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *in) { - if(CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_AES, - CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { - CryptCreateHash(ctx->hCryptProv, CALG_SHA_256, 0, 0, &ctx->hHash); + my_sha256_ctx *ctx = (my_sha256_ctx *)in; + if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_AES, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return CURLE_OUT_OF_MEMORY; + + if(!CryptCreateHash(ctx->hCryptProv, CALG_SHA_256, 0, 0, &ctx->hHash)) { + CryptReleaseContext(ctx->hCryptProv, 0); + ctx->hCryptProv = 0; + return CURLE_FAILED_INIT; } return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *in, const unsigned char *data, unsigned int length) { - CryptHashData(ctx->hHash, (unsigned char *) data, length, 0); + my_sha256_ctx *ctx = (my_sha256_ctx *)in; +#ifdef __MINGW32CE__ + CryptHashData(ctx->hHash, (BYTE *)CURL_UNCONST(data), length, 0); +#else + CryptHashData(ctx->hHash, (const BYTE *)data, length, 0); +#endif } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *in) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; unsigned long length = 0; CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); - if(length == SHA256_DIGEST_LENGTH) + if(length == CURL_SHA256_DIGEST_LENGTH) CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); if(ctx->hHash) @@ -325,18 +316,18 @@ static const unsigned long K[64] = { #define RORc(x, y) \ (((((unsigned long)(x) & 0xFFFFFFFFUL) >> (unsigned long)((y) & 31)) | \ ((unsigned long)(x) << (unsigned long)(32 - ((y) & 31)))) & 0xFFFFFFFFUL) -#define Ch(x,y,z) (z ^ (x & (y ^ z))) -#define Maj(x,y,z) (((x | y) & z) | (x & y)) -#define S(x, n) RORc((x), (n)) -#define R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) -#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) -#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) -#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) -#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) +#define Sha256_Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Sha256_Maj(x,y,z) (((x | y) & z) | (x & y)) +#define Sha256_S(x, n) RORc((x), (n)) +#define Sha256_R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) +#define Sigma0(x) (Sha256_S(x, 2) ^ Sha256_S(x, 13) ^ Sha256_S(x, 22)) +#define Sigma1(x) (Sha256_S(x, 6) ^ Sha256_S(x, 11) ^ Sha256_S(x, 25)) +#define Gamma0(x) (Sha256_S(x, 7) ^ Sha256_S(x, 18) ^ Sha256_R(x, 3)) +#define Gamma1(x) (Sha256_S(x, 17) ^ Sha256_S(x, 19) ^ Sha256_R(x, 10)) /* Compress 512-bits */ static int sha256_compress(struct sha256_state *md, - unsigned char *buf) + const unsigned char *buf) { unsigned long S[8], W[64]; int i; @@ -355,12 +346,12 @@ static int sha256_compress(struct sha256_state *md, } /* Compress */ -#define RND(a,b,c,d,e,f,g,h,i) \ - do { \ - unsigned long t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ - unsigned long t1 = Sigma0(a) + Maj(a, b, c); \ - d += t0; \ - h = t0 + t1; \ +#define RND(a,b,c,d,e,f,g,h,i) \ + do { \ + unsigned long t0 = h + Sigma1(e) + Sha256_Ch(e, f, g) + K[i] + W[i]; \ + unsigned long t1 = Sigma0(a) + Sha256_Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; \ } while(0) for(i = 0; i < 64; ++i) { @@ -379,8 +370,9 @@ static int sha256_compress(struct sha256_state *md, } /* Initialize the hash state */ -static CURLcode my_sha256_init(struct sha256_state *md) +static CURLcode my_sha256_init(void *in) { + struct sha256_state *md = (struct sha256_state *)in; md->curlen = 0; md->length = 0; md->state[0] = 0x6A09E667UL; @@ -400,41 +392,39 @@ static CURLcode my_sha256_init(struct sha256_state *md) @param md The hash state @param in The data to hash @param inlen The length of the data (octets) - @return 0 if successful */ -static int my_sha256_update(struct sha256_state *md, - const unsigned char *in, - unsigned long inlen) +static void my_sha256_update(void *ctx, + const unsigned char *in, + unsigned int len) { + unsigned long inlen = len; unsigned long n; - -#define block_size 64 + struct sha256_state *md = (struct sha256_state *)ctx; +#define CURL_SHA256_BLOCK_SIZE 64 if(md->curlen > sizeof(md->buf)) - return -1; + return; while(inlen > 0) { - if(md->curlen == 0 && inlen >= block_size) { - if(sha256_compress(md, (unsigned char *)in) < 0) - return -1; - md->length += block_size * 8; - in += block_size; - inlen -= block_size; + if(md->curlen == 0 && inlen >= CURL_SHA256_BLOCK_SIZE) { + if(sha256_compress(md, in) < 0) + return; + md->length += CURL_SHA256_BLOCK_SIZE * 8; + in += CURL_SHA256_BLOCK_SIZE; + inlen -= CURL_SHA256_BLOCK_SIZE; } else { - n = CURLMIN(inlen, (block_size - md->curlen)); + n = CURLMIN(inlen, (CURL_SHA256_BLOCK_SIZE - md->curlen)); memcpy(md->buf + md->curlen, in, n); md->curlen += n; in += n; inlen -= n; - if(md->curlen == block_size) { + if(md->curlen == CURL_SHA256_BLOCK_SIZE) { if(sha256_compress(md, md->buf) < 0) - return -1; - md->length += 8 * block_size; + return; + md->length += 8 * CURL_SHA256_BLOCK_SIZE; md->curlen = 0; } } } - - return 0; } /* @@ -443,13 +433,13 @@ static int my_sha256_update(struct sha256_state *md, @param out [out] The destination of the hash (32 bytes) @return 0 if successful */ -static int my_sha256_final(unsigned char *out, - struct sha256_state *md) +static void my_sha256_final(unsigned char *out, void *ctx) { + struct sha256_state *md = ctx; int i; if(md->curlen >= sizeof(md->buf)) - return -1; + return; /* Increase the length of the message */ md->length += md->curlen * 8; @@ -458,7 +448,7 @@ static int my_sha256_final(unsigned char *out, md->buf[md->curlen++] = (unsigned char)0x80; /* If the length is currently above 56 bytes we append zeros - * then compress. Then we can fall back to padding zeros and length + * then compress. Then we can fall back to padding zeros and length * encoding like normal. */ if(md->curlen > 56) { @@ -481,8 +471,6 @@ static int my_sha256_final(unsigned char *out, /* Copy output */ for(i = 0; i < 8; i++) WPA_PUT_BE32(out + (4 * i), md->state[i]); - - return 0; } #endif /* CRYPTO LIBS */ @@ -501,7 +489,7 @@ static int my_sha256_final(unsigned char *out, * Returns CURLE_OK on success. */ CURLcode Curl_sha256it(unsigned char *output, const unsigned char *input, - const size_t length) + const size_t length) { CURLcode result; my_sha256_ctx ctx; @@ -515,22 +503,13 @@ CURLcode Curl_sha256it(unsigned char *output, const unsigned char *input, } -const struct HMAC_params Curl_HMAC_SHA256[] = { - { - /* Hash initialization function. */ - CURLX_FUNCTION_CAST(HMAC_hinit_func, my_sha256_init), - /* Hash update function. */ - CURLX_FUNCTION_CAST(HMAC_hupdate_func, my_sha256_update), - /* Hash computation end function. */ - CURLX_FUNCTION_CAST(HMAC_hfinal_func, my_sha256_final), - /* Size of hash context structure. */ - sizeof(my_sha256_ctx), - /* Maximum key length. */ - 64, - /* Result size. */ - 32 - } +const struct HMAC_params Curl_HMAC_SHA256 = { + my_sha256_init, /* Hash initialization function. */ + my_sha256_update, /* Hash update function. */ + my_sha256_final, /* Hash computation end function. */ + sizeof(my_sha256_ctx), /* Size of hash context structure. */ + 64, /* Maximum key length. */ + 32 /* Result size. */ }; - -#endif /* CURL_DISABLE_CRYPTO_AUTH */ +#endif /* AWS, DIGEST, or libssh2 */ diff --git a/Utilities/cmcurl/lib/share.c b/Utilities/cmcurl/lib/share.c index c0a8d806f34..d1ab55eb276 100644 --- a/Utilities/cmcurl/lib/share.c +++ b/Utilities/cmcurl/lib/share.c @@ -26,24 +26,39 @@ #include #include "urldata.h" +#include "connect.h" #include "share.h" #include "psl.h" #include "vtls/vtls.h" +#include "vtls/vtls_scache.h" #include "hsts.h" +#include "url.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -struct Curl_share * +CURLSH * curl_share_init(void) { struct Curl_share *share = calloc(1, sizeof(struct Curl_share)); if(share) { share->magic = CURL_GOOD_SHARE; - share->specifier |= (1<hostcache, 23); + share->specifier |= (1 << CURL_LOCK_DATA_SHARE); + Curl_dnscache_init(&share->dnscache, 23); + share->admin = curl_easy_init(); + if(!share->admin) { + free(share); + return NULL; + } + /* admin handles have mid 0 */ + share->admin->mid = 0; + share->admin->state.internal = TRUE; +#ifdef DEBUGBUILD + if(getenv("CURL_DEBUG")) + share->admin->set.verbose = TRUE; +#endif } return share; @@ -51,7 +66,7 @@ curl_share_init(void) #undef curl_share_setopt CURLSHcode -curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) +curl_share_setopt(CURLSH *sh, CURLSHoption option, ...) { va_list param; int type; @@ -59,12 +74,13 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) curl_unlock_function unlockfunc; void *ptr; CURLSHcode res = CURLSHE_OK; + struct Curl_share *share = sh; if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; if(share->dirty) - /* don't allow setting options while one or more handles are already + /* do not allow setting options while one or more handles are already using this share */ return CURLSHE_IN_USE; @@ -105,12 +121,13 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) case CURL_LOCK_DATA_SSL_SESSION: #ifdef USE_SSL - if(!share->sslsession) { - share->max_ssl_sessions = 8; - share->sslsession = calloc(share->max_ssl_sessions, - sizeof(struct Curl_ssl_session)); - share->sessionage = 0; - if(!share->sslsession) + if(!share->ssl_scache) { + /* There is no way (yet) for the application to configure the + * session cache size, shared between many transfers. As for curl + * itself, a high session count will impact startup time. Also, the + * scache is not optimized for several hundreds of peers. So, + * keep it at a reasonable level. */ + if(Curl_ssl_scache_create(25, 2, &share->ssl_scache)) res = CURLSHE_NOMEM; } #else @@ -119,8 +136,10 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) break; case CURL_LOCK_DATA_CONNECT: - if(Curl_conncache_init(&share->conn_cache, 103)) - res = CURLSHE_NOMEM; + /* It is safe to set this option several times on a share. */ + if(!share->cpool.initialised) { + Curl_cpool_init(&share->cpool, share->admin, share, 103); + } break; case CURL_LOCK_DATA_PSL: @@ -133,13 +152,13 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) res = CURLSHE_BAD_OPTION; } if(!res) - share->specifier |= (1<specifier |= (unsigned int)(1 << type); break; case CURLSHOPT_UNSHARE: /* this is a type this share will no longer share */ type = va_arg(param, int); - share->specifier &= ~(1<specifier &= ~(unsigned int)(1 << type); switch(type) { case CURL_LOCK_DATA_DNS: break; @@ -167,7 +186,10 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) case CURL_LOCK_DATA_SSL_SESSION: #ifdef USE_SSL - Curl_safefree(share->sslsession); + if(share->ssl_scache) { + Curl_ssl_scache_destroy(share->ssl_scache); + share->ssl_scache = NULL; + } #else res = CURLSHE_NOT_BUILT_IN; #endif @@ -208,8 +230,9 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) } CURLSHcode -curl_share_cleanup(struct Curl_share *share) +curl_share_cleanup(CURLSH *sh) { + struct Curl_share *share = sh; if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; @@ -223,9 +246,11 @@ curl_share_cleanup(struct Curl_share *share) return CURLSHE_IN_USE; } - Curl_conncache_close_all_connections(&share->conn_cache); - Curl_conncache_destroy(&share->conn_cache); - Curl_hash_destroy(&share->hostcache); + if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) { + Curl_cpool_destroy(&share->cpool); + } + + Curl_dnscache_destroy(&share->dnscache); #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) Curl_cookie_cleanup(share->cookies); @@ -236,15 +261,14 @@ curl_share_cleanup(struct Curl_share *share) #endif #ifdef USE_SSL - if(share->sslsession) { - size_t i; - for(i = 0; i < share->max_ssl_sessions; i++) - Curl_ssl_kill_session(&(share->sslsession[i])); - free(share->sslsession); + if(share->ssl_scache) { + Curl_ssl_scache_destroy(share->ssl_scache); + share->ssl_scache = NULL; } #endif Curl_psl_destroy(&share->psl); + Curl_close(&share->admin); if(share->unlockfunc) share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); @@ -264,11 +288,11 @@ Curl_share_lock(struct Curl_easy *data, curl_lock_data type, if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<specifier & (unsigned int)(1 << type)) { if(share->lockfunc) /* only call this if set! */ share->lockfunc(data, type, accesstype, share->clientdata); } - /* else if we don't share this, pretend successful lock */ + /* else if we do not share this, pretend successful lock */ return CURLSHE_OK; } @@ -281,7 +305,7 @@ Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<specifier & (unsigned int)(1 << type)) { if(share->unlockfunc) /* only call this if set! */ share->unlockfunc (data, type, share->clientdata); } diff --git a/Utilities/cmcurl/lib/share.h b/Utilities/cmcurl/lib/share.h index 7f55aac853d..974c99dc204 100644 --- a/Utilities/cmcurl/lib/share.h +++ b/Utilities/cmcurl/lib/share.h @@ -31,28 +31,27 @@ #include "urldata.h" #include "conncache.h" -/* SalfordC says "A structure member may not be volatile". Hence: - */ -#ifdef __SALFORDC__ -#define CURL_VOLATILE -#else -#define CURL_VOLATILE volatile -#endif +struct Curl_easy; +struct Curl_ssl_scache; #define CURL_GOOD_SHARE 0x7e117a1e #define GOOD_SHARE_HANDLE(x) ((x) && (x)->magic == CURL_GOOD_SHARE) -/* this struct is libcurl-private, don't export details */ +#define CURL_SHARE_KEEP_CONNECT(s) \ + ((s) && ((s)->specifier & (1<< CURL_LOCK_DATA_CONNECT))) + +/* this struct is libcurl-private, do not export details */ struct Curl_share { unsigned int magic; /* CURL_GOOD_SHARE */ unsigned int specifier; - CURL_VOLATILE unsigned int dirty; + volatile unsigned int dirty; curl_lock_function lockfunc; curl_unlock_function unlockfunc; void *clientdata; - struct conncache conn_cache; - struct Curl_hash hostcache; + struct Curl_easy *admin; + struct cpool cpool; + struct Curl_dnscache dnscache; /* DNS cache */ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) struct CookieInfo *cookies; #endif @@ -63,9 +62,7 @@ struct Curl_share { struct hsts *hsts; #endif #ifdef USE_SSL - struct Curl_ssl_session *sslsession; - size_t max_ssl_sessions; - long sessionage; + struct Curl_ssl_scache *ssl_scache; #endif }; @@ -73,4 +70,9 @@ CURLSHcode Curl_share_lock(struct Curl_easy *, curl_lock_data, curl_lock_access); CURLSHcode Curl_share_unlock(struct Curl_easy *, curl_lock_data); +/* convenience macro to check if this handle is using a shared SSL spool */ +#define CURL_SHARE_ssl_scache(data) (data->share && \ + (data->share->specifier & \ + (1< struct sigpipe_ignore { struct sigaction old_pipe_act; - bool no_signal; + BIT(no_signal); }; #define SIGPIPE_VARIABLE(x) struct sigpipe_ignore x +#define SIGPIPE_MEMBER(x) struct sigpipe_ignore x + +static void sigpipe_init(struct sigpipe_ignore *ig) +{ + memset(ig, 0, sizeof(*ig)); + ig->no_signal = TRUE; +} /* * sigpipe_ignore() makes sure we ignore SIGPIPE while running libcurl @@ -70,11 +77,23 @@ static void sigpipe_restore(struct sigpipe_ignore *ig) sigaction(SIGPIPE, &ig->old_pipe_act, NULL); } +static void sigpipe_apply(struct Curl_easy *data, + struct sigpipe_ignore *ig) +{ + if(data->set.no_signal != ig->no_signal) { + sigpipe_restore(ig); + sigpipe_ignore(data, ig); + } +} + #else /* for systems without sigaction */ #define sigpipe_ignore(x,y) Curl_nop_stmt +#define sigpipe_apply(x,y) Curl_nop_stmt +#define sigpipe_init(x) Curl_nop_stmt #define sigpipe_restore(x) Curl_nop_stmt #define SIGPIPE_VARIABLE(x) +#define SIGPIPE_MEMBER(x) bool x #endif #endif /* HEADER_CURL_SIGPIPE_H */ diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c index d6822213529..ceca9564bf1 100644 --- a/Utilities/cmcurl/lib/smb.c +++ b/Utilities/cmcurl/lib/smb.c @@ -27,14 +27,9 @@ #if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) -#define BUILDING_CURL_SMB_C - -#ifdef WIN32 -#define getpid GetCurrentProcessId -#endif - #include "smb.h" #include "urldata.h" +#include "url.h" #include "sendf.h" #include "multiif.h" #include "cfilters.h" @@ -46,10 +41,256 @@ #include "escape.h" #include "curl_endian.h" -/* The last #include files should be: */ +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" #include "memdebug.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; + 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 + /* Local API functions */ static CURLcode smb_setup_connection(struct Curl_easy *data, struct connectdata *conn); @@ -57,18 +298,17 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done); static CURLcode smb_connection_state(struct Curl_easy *data, bool *done); static CURLcode smb_do(struct Curl_easy *data, bool *done); static CURLcode smb_request_state(struct Curl_easy *data, bool *done); -static CURLcode smb_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead); static int smb_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); static CURLcode smb_parse_url_path(struct Curl_easy *data, - struct connectdata *conn); + struct smb_conn *smbc, + struct smb_request *req); /* * SMB handler interface */ const struct Curl_handler Curl_handler_smb = { - "SMB", /* scheme */ + "smb", /* scheme */ smb_setup_connection, /* setup_connection */ smb_do, /* do_it */ ZERO_NULL, /* done */ @@ -80,10 +320,12 @@ const struct Curl_handler Curl_handler_smb = { smb_getsock, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMB, /* defport */ CURLPROTO_SMB, /* protocol */ CURLPROTO_SMB, /* family */ @@ -95,7 +337,7 @@ const struct Curl_handler Curl_handler_smb = { * SMBS handler interface */ const struct Curl_handler Curl_handler_smbs = { - "SMBS", /* scheme */ + "smbs", /* scheme */ smb_setup_connection, /* setup_connection */ smb_do, /* do_it */ ZERO_NULL, /* done */ @@ -107,10 +349,12 @@ const struct Curl_handler Curl_handler_smbs = { smb_getsock, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMBS, /* defport */ CURLPROTO_SMBS, /* protocol */ CURLPROTO_SMB, /* family */ @@ -123,20 +367,6 @@ const struct Curl_handler Curl_handler_smbs = { #define CLIENTNAME "curl" #define SERVICENAME "?????" -/* Append a string to an SMB message */ -#define MSGCAT(str) \ - do { \ - strcpy(p, (str)); \ - p += strlen(str); \ - } while(0) - -/* Append a null-terminated string to an SMB message */ -#define MSGCATNULL(str) \ - do { \ - strcpy(p, (str)); \ - p += strlen(str) + 1; \ - } while(0) - /* SMB is mostly little endian */ #if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ defined(__OS400__) @@ -163,30 +393,9 @@ static curl_off_t smb_swap64(curl_off_t x) # define smb_swap64(x) (x) #endif -/* 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 */ -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; -}; - -static void conn_state(struct Curl_easy *data, enum smb_conn_state newstate) +static void conn_state(struct Curl_easy *data, struct smb_conn *smbc, + enum smb_conn_state newstate) { - struct smb_conn *smbc = &data->conn->proto.smbc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* For debug purposes */ static const char * const names[] = { @@ -202,34 +411,59 @@ static void conn_state(struct Curl_easy *data, enum smb_conn_state newstate) infof(data, "SMB conn %p state change from %s to %s", (void *)smbc, names[smbc->state], names[newstate]); #endif - + (void)data; smbc->state = newstate; } static void request_state(struct Curl_easy *data, enum smb_req_state newstate) { - struct smb_request *req = data->req.p.smb; + struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); + if(req) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* 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]); + /* 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; + req->state = newstate; + } +} + +static void smb_easy_dtor(void *key, size_t klen, void *entry) +{ + struct smb_request *req = entry; + (void)key; + (void)klen; + /* `req->path` points to somewhere in `struct smb_conn` which is + * kept at the connection meta. If the connection is destroyed first, + * req->path points to free'd memory. */ + free(req); +} + +static void smb_conn_dtor(void *key, size_t klen, void *entry) +{ + struct smb_conn *smbc = entry; + (void)key; + (void)klen; + Curl_safefree(smbc->share); + Curl_safefree(smbc->domain); + Curl_safefree(smbc->recv_buf); + Curl_safefree(smbc->send_buf); + free(smbc); } /* this should setup things in the connection, not in the easy @@ -237,24 +471,34 @@ static void request_state(struct Curl_easy *data, 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 = 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 */ - data->req.p.smb = req = calloc(1, sizeof(struct smb_request)); - if(!req) + req = 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, conn); + 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 = &conn->proto.smbc; + struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); char *slash; (void) done; + if(!smbc) + return CURLE_FAILED_INIT; /* Check we have a username and password to authenticate with */ if(!data->state.aptr.user) @@ -265,6 +509,9 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) smbc->recv_buf = malloc(MAX_MESSAGE_SIZE); if(!smbc->recv_buf) return CURLE_OUT_OF_MEMORY; + smbc->send_buf = malloc(MAX_MESSAGE_SIZE); + if(!smbc->send_buf) + return CURLE_OUT_OF_MEMORY; /* Multiple requests are allowed with this connection */ connkeep(conn, "SMB default"); @@ -291,11 +538,10 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) return CURLE_OK; } -static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) +static CURLcode smb_recv_message(struct Curl_easy *data, + struct smb_conn *smbc, + void **msg) { - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - struct smb_conn *smbc = &conn->proto.smbc; char *buf = smbc->recv_buf; ssize_t bytes_read; size_t nbt_size; @@ -303,7 +549,7 @@ static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) size_t len = MAX_MESSAGE_SIZE - smbc->got; CURLcode result; - result = Curl_read(data, sockfd, buf + smbc->got, len, &bytes_read); + result = Curl_xfer_recv(data, buf + smbc->got, len, &bytes_read); if(result) return result; @@ -340,20 +586,17 @@ static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) return CURLE_OK; } -static void smb_pop_message(struct connectdata *conn) +static void smb_pop_message(struct smb_conn *smbc) { - struct smb_conn *smbc = &conn->proto.smbc; - smbc->got = 0; } -static void smb_format_message(struct Curl_easy *data, struct smb_header *h, +static void smb_format_message(struct smb_conn *smbc, + struct smb_request *req, + struct smb_header *h, unsigned char cmd, size_t len) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; - struct smb_request *req = data->req.p.smb; - unsigned int pid; + const unsigned int pid = 0xbad71d; /* made up */ memset(h, 0, sizeof(*h)); h->nbt_length = htons((unsigned short) (sizeof(*h) - sizeof(unsigned int) + @@ -364,22 +607,17 @@ static void smb_format_message(struct Curl_easy *data, struct smb_header *h, 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); - pid = getpid(); h->pid_high = smb_swap16((unsigned short)(pid >> 16)); h->pid = smb_swap16((unsigned short) pid); } -static CURLcode smb_send(struct Curl_easy *data, ssize_t len, - size_t upload_size) +static CURLcode smb_send(struct Curl_easy *data, struct smb_conn *smbc, + size_t len, size_t upload_size) { - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - struct smb_conn *smbc = &conn->proto.smbc; - ssize_t bytes_written; + size_t bytes_written; CURLcode result; - result = Curl_write(data, sockfd, data->state.ulbuf, - len, &bytes_written); + result = Curl_xfer_send(data, smbc->send_buf, len, FALSE, &bytes_written); if(result) return result; @@ -393,21 +631,17 @@ static CURLcode smb_send(struct Curl_easy *data, ssize_t len, return CURLE_OK; } -static CURLcode smb_flush(struct Curl_easy *data) +static CURLcode smb_flush(struct Curl_easy *data, struct smb_conn *smbc) { - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - struct smb_conn *smbc = &conn->proto.smbc; - ssize_t bytes_written; - ssize_t len = smbc->send_size - smbc->sent; + size_t bytes_written; + size_t len = smbc->send_size - smbc->sent; CURLcode result; if(!smbc->send_size) return CURLE_OK; - result = Curl_write(data, sockfd, - data->state.ulbuf + smbc->sent, - len, &bytes_written); + result = Curl_xfer_send(data, smbc->send_buf + smbc->sent, len, FALSE, + &bytes_written); if(result) return result; @@ -419,41 +653,48 @@ static CURLcode smb_flush(struct Curl_easy *data) return CURLE_OK; } -static CURLcode smb_send_message(struct Curl_easy *data, unsigned char cmd, +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) { - CURLcode result = Curl_get_upload_buffer(data); - if(result) - return result; - smb_format_message(data, (struct smb_header *)data->state.ulbuf, + smb_format_message(smbc, req, (struct smb_header *)smbc->send_buf, cmd, msg_len); - memcpy(data->state.ulbuf + sizeof(struct smb_header), - msg, msg_len); + DEBUGASSERT((sizeof(struct smb_header) + msg_len) <= MAX_MESSAGE_SIZE); + memcpy(smbc->send_buf + sizeof(struct smb_header), msg, msg_len); - return smb_send(data, sizeof(struct smb_header) + msg_len, 0); + return smb_send(data, smbc, sizeof(struct smb_header) + msg_len, 0); } -static CURLcode smb_send_negotiate(struct Curl_easy *data) +static CURLcode smb_send_negotiate(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { const char *msg = "\x00\x0c\x00\x02NT LM 0.12"; - return smb_send_message(data, SMB_COM_NEGOTIATE, msg, 15); + return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, 15); } static CURLcode smb_send_setup(struct Curl_easy *data) { struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; + 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; + + if(!smbc || !req) + return CURLE_FAILED_INIT; - size_t byte_count = sizeof(lm) + sizeof(nt); - byte_count += strlen(smbc->user) + strlen(smbc->domain); - byte_count += strlen(OS) + strlen(CLIENTNAME) + 4; /* 4 null chars */ + byte_count = sizeof(lm) + sizeof(nt) + + strlen(smbc->user) + strlen(smbc->domain) + + strlen(CURL_OS) + strlen(CLIENTNAME) + 4; /* 4 null chars */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; @@ -462,7 +703,7 @@ static CURLcode smb_send_setup(struct Curl_easy *data) Curl_ntlm_core_mk_nt_hash(conn->passwd, nt_hash); Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); - memset(&msg, 0, sizeof(msg)); + 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); @@ -476,59 +717,65 @@ static CURLcode smb_send_setup(struct Curl_easy *data) p += sizeof(lm); memcpy(p, nt, sizeof(nt)); p += sizeof(nt); - MSGCATNULL(smbc->user); - MSGCATNULL(smbc->domain); - MSGCATNULL(OS); - MSGCATNULL(CLIENTNAME); - byte_count = p - msg.bytes; + p += 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, SMB_COM_SETUP_ANDX, &msg, + 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) +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; - struct smb_conn *smbc = &conn->proto.smbc; char *p = msg.bytes; + const size_t byte_count = strlen(conn->host.name) + strlen(smbc->share) + + strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ - size_t byte_count = strlen(conn->host.name) + strlen(smbc->share); - byte_count += strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - memset(&msg, 0, sizeof(msg)); + 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; - MSGCAT("\\\\"); - MSGCAT(conn->host.name); - MSGCAT("\\"); - MSGCATNULL(smbc->share); - MSGCATNULL(SERVICENAME); /* Match any type of service */ - byte_count = p - msg.bytes; + + p += msnprintf(p, byte_count, + "\\\\%s\\" /* hostname */ + "%s%c" /* share */ + "%s", /* service */ + conn->host.name, 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, SMB_COM_TREE_CONNECT_ANDX, &msg, + 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) +static CURLcode smb_send_open(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; struct smb_nt_create msg; - size_t byte_count; + const size_t byte_count = strlen(req->path) + 1; - if((strlen(req->path) + 1) > sizeof(msg.bytes)) + if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - memset(&msg, 0, sizeof(msg)); + memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); msg.word_count = SMB_WC_NT_CREATE_ANDX; msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - byte_count = strlen(req->path); - msg.name_length = smb_swap16((unsigned short)byte_count); + 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); @@ -538,37 +785,40 @@ static CURLcode smb_send_open(struct Curl_easy *data) msg.access = smb_swap32(SMB_GENERIC_READ); msg.create_disposition = smb_swap32(SMB_FILE_OPEN); } - msg.byte_count = smb_swap16((unsigned short) ++byte_count); + msg.byte_count = smb_swap16((unsigned short) byte_count); strcpy(msg.bytes, req->path); - return smb_send_message(data, SMB_COM_NT_CREATE_ANDX, &msg, + 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) +static CURLcode smb_send_close(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; 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, SMB_COM_CLOSE, &msg, sizeof(msg)); + return smb_send_message(data, smbc, req, SMB_COM_CLOSE, &msg, sizeof(msg)); } -static CURLcode smb_send_tree_disconnect(struct Curl_easy *data) +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, SMB_COM_TREE_DISCONNECT, &msg, 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) +static CURLcode smb_send_read(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; curl_off_t offset = data->req.offset; struct smb_read msg; @@ -581,20 +831,19 @@ static CURLcode smb_send_read(struct Curl_easy *data) msg.min_bytes = smb_swap16(MAX_PAYLOAD_SIZE); msg.max_bytes = smb_swap16(MAX_PAYLOAD_SIZE); - return smb_send_message(data, SMB_COM_READ_ANDX, &msg, sizeof(msg)); + return smb_send_message(data, smbc, req, SMB_COM_READ_ANDX, + &msg, sizeof(msg)); } -static CURLcode smb_send_write(struct Curl_easy *data) +static CURLcode smb_send_write(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { struct smb_write *msg; - struct smb_request *req = data->req.p.smb; curl_off_t offset = data->req.offset; curl_off_t upload_size = data->req.size - data->req.bytecount; - CURLcode result = Curl_get_upload_buffer(data); - if(result) - return result; - msg = (struct smb_write *)data->state.ulbuf; + 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; @@ -608,25 +857,25 @@ static CURLcode smb_send_write(struct Curl_easy *data) msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int)); msg->byte_count = smb_swap16((unsigned short) (upload_size + 1)); - smb_format_message(data, &msg->h, SMB_COM_WRITE_ANDX, + smb_format_message(smbc, req, &msg->h, SMB_COM_WRITE_ANDX, sizeof(*msg) - sizeof(msg->h) + (size_t) upload_size); - return smb_send(data, sizeof(*msg), (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, void **msg) +static CURLcode smb_send_and_recv(struct Curl_easy *data, + struct smb_conn *smbc, void **msg) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; 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)data->set.upload_buffer_size ? - (size_t)data->set.upload_buffer_size : smbc->upload_size; - data->req.upload_fromhere = data->state.ulbuf; - result = Curl_fillreadbuffer(data, nread, &nread); + 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) @@ -639,7 +888,7 @@ static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) /* Check if there is data to send */ if(smbc->send_size) { - result = smb_flush(data); + result = smb_flush(data, smbc); if(result) return result; } @@ -648,21 +897,25 @@ static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) if(smbc->send_size || smbc->upload_size) return CURLE_AGAIN; - return smb_recv_message(data, msg); + 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 = &conn->proto.smbc; + 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((conn->handler->flags & PROTOPT_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) @@ -672,17 +925,17 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) } #endif - result = smb_send_negotiate(data); + result = smb_send_negotiate(data, smbc, req); if(result) { connclose(conn, "SMB: failed to send negotiate message"); return result; } - conn_state(data, SMB_NEGOTIATE); + conn_state(data, smbc, SMB_NEGOTIATE); } /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, &msg); + result = smb_send_and_recv(data, smbc, &msg); if(result && result != CURLE_AGAIN) { connclose(conn, "SMB: failed to communicate"); return result; @@ -701,14 +954,23 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) 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) { connclose(conn, "SMB: failed to send setup message"); return result; } - conn_state(data, SMB_SETUP); + conn_state(data, smbc, SMB_SETUP); break; case SMB_SETUP: @@ -717,44 +979,48 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) return CURLE_LOGIN_DENIED; } smbc->uid = smb_swap16(h->uid); - conn_state(data, SMB_CONNECTED); - *done = true; + conn_state(data, smbc, SMB_CONNECTED); + *done = TRUE; break; default: - smb_pop_message(conn); + smb_pop_message(smbc); return CURLE_OK; /* ignore */ } - smb_pop_message(conn); + 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. + * to POSIX time. Cap the output to fit within a time_t. */ static void get_posix_time(time_t *out, curl_off_t timestamp) { - timestamp -= 116444736000000000; - timestamp /= 10000000; + if(timestamp >= CURL_OFF_T_C(116444736000000000)) { + timestamp -= CURL_OFF_T_C(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 + 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; + *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_request *req = data->req.p.smb; + 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; - struct smb_conn *smbc = &conn->proto.smbc; enum smb_req_state next_state = SMB_DONE; unsigned short len; unsigned short off; @@ -762,6 +1028,9 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) 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; @@ -769,7 +1038,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) /* Start the request */ if(req->state == SMB_REQUESTING) { - result = smb_send_tree_connect(data); + result = smb_send_tree_connect(data, smbc, req); if(result) { connclose(conn, "SMB: failed to send tree connect message"); return result; @@ -779,7 +1048,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) } /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, &msg); + result = smb_send_and_recv(data, smbc, &msg); if(result && result != CURLE_AGAIN) { connclose(conn, "SMB: failed to communicate"); return result; @@ -858,9 +1127,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) break; } } - data->req.bytecount += len; data->req.offset += len; - Curl_pgrsSetDownloadCounter(data, data->req.bytecount); next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD; break; @@ -882,7 +1149,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) break; case SMB_CLOSE: - /* We don't care if the close failed, proceed to tree disconnect anyway */ + /* We do not care if the close failed, proceed to tree disconnect anyway */ next_state = SMB_TREE_DISCONNECT; break; @@ -891,36 +1158,36 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) break; default: - smb_pop_message(conn); + smb_pop_message(smbc); return CURLE_OK; /* ignore */ } - smb_pop_message(conn); + smb_pop_message(smbc); switch(next_state) { case SMB_OPEN: - result = smb_send_open(data); + result = smb_send_open(data, smbc, req); break; case SMB_DOWNLOAD: - result = smb_send_read(data); + result = smb_send_read(data, smbc, req); break; case SMB_UPLOAD: - result = smb_send_write(data); + result = smb_send_write(data, smbc, req); break; case SMB_CLOSE: - result = smb_send_close(data); + result = smb_send_close(data, smbc, req); break; case SMB_TREE_DISCONNECT: - result = smb_send_tree_disconnect(data); + result = smb_send_tree_disconnect(data, smbc, req); break; case SMB_DONE: result = req->result; - *done = true; + *done = TRUE; break; default: @@ -937,18 +1204,6 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) return CURLE_OK; } -static CURLcode smb_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead) -{ - struct smb_conn *smbc = &conn->proto.smbc; - (void) dead; - (void) data; - Curl_safefree(smbc->share); - Curl_safefree(smbc->domain); - Curl_safefree(smbc->recv_buf); - return CURLE_OK; -} - static int smb_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { @@ -960,26 +1215,26 @@ static int smb_getsock(struct Curl_easy *data, static CURLcode smb_do(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; + struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); *done = FALSE; - if(smbc->share) { + if(!smbc) + return CURLE_FAILED_INIT; + if(smbc->share) return CURLE_OK; - } return CURLE_URL_MALFORMAT; } static CURLcode smb_parse_url_path(struct Curl_easy *data, - struct connectdata *conn) + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; - struct smb_conn *smbc = &conn->proto.smbc; char *path; char *slash; + CURLcode result; /* URL decode the path */ - CURLcode result = Curl_urldecode(data->state.up.path, 0, &path, NULL, - REJECT_CTRL); + result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL); if(result) return result; diff --git a/Utilities/cmcurl/lib/smb.h b/Utilities/cmcurl/lib/smb.h index c35f3e97006..eb4df6550a3 100644 --- a/Utilities/cmcurl/lib/smb.h +++ b/Utilities/cmcurl/lib/smb.h @@ -25,226 +25,6 @@ * ***************************************************************************/ -enum smb_conn_state { - SMB_NOT_CONNECTED = 0, - SMB_CONNECTING, - SMB_NEGOTIATE, - SMB_SETUP, - SMB_CONNECTED -}; - -struct smb_conn { - enum smb_conn_state state; - char *user; - char *domain; - char *share; - unsigned char challenge[8]; - unsigned int session_key; - unsigned short uid; - char *recv_buf; - size_t upload_size; - size_t send_size; - size_t sent; - size_t got; -}; - -/* - * Definitions for SMB protocol data structures - */ -#ifdef BUILDING_CURL_SMB_C - -#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 - -#endif /* BUILDING_CURL_SMB_C */ - #if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ (SIZEOF_CURL_OFF_T > 4) diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c index c182cace742..d39bb58d5de 100644 --- a/Utilities/cmcurl/lib/smtp.c +++ b/Utilities/cmcurl/lib/smtp.c @@ -49,9 +49,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -71,7 +68,6 @@ #include "mime.h" #include "socks.h" #include "smtp.h" -#include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -82,15 +78,79 @@ #include "curl_gethostname.h" #include "bufref.h" #include "curl_sasl.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "idn.h" +#include "curlx/strparse.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_SMTP_EASY "meta:proto:smtp:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_SMTP_CONN "meta:proto:smtp:conn" + +/**************************************************************************** + * SMTP unique setup + ***************************************************************************/ +typedef enum { + SMTP_STOP, /* do nothing state, stops the state machine */ + SMTP_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + SMTP_EHLO, + SMTP_HELO, + SMTP_STARTTLS, + SMTP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + SMTP_AUTH, + SMTP_COMMAND, /* VRFY, EXPN, NOOP, RSET and HELP */ + SMTP_MAIL, /* MAIL FROM */ + SMTP_RCPT, /* RCPT TO */ + SMTP_DATA, + SMTP_POSTDATA, + SMTP_QUIT, + SMTP_LAST /* never used */ +} smtpstate; + +/* smtp_conn is used for struct connection-oriented data in the connectdata + struct */ +struct smtp_conn { + struct pingpong pp; + struct SASL sasl; /* SASL-related storage */ + smtpstate state; /* Always use smtp.c:state() to change state! */ + char *domain; /* Client address/name to send in the EHLO */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(size_supported); /* If server supports SIZE extension according to + RFC 1870 */ + BIT(utf8_supported); /* If server supports SMTPUTF8 extension according + to RFC 6531 */ + BIT(auth_supported); /* AUTH capability supported by server */ +}; + +/* This SMTP struct is used in the Curl_easy. All SMTP data that is + connection-oriented must be in smtp_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct SMTP { + curl_pp_transfer transfer; + char *custom; /* Custom Request */ + struct curl_slist *rcpt; /* Recipient list */ + int rcpt_last_error; /* The last error received for RCPT TO command */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(rcpt_had_ok); /* Whether any of RCPT TO commands (depends on + total number of recipients) succeeded so far */ + BIT(trailing_crlf); /* Specifies if the trailing CRLF is present */ +}; + /* Local API functions */ -static CURLcode smtp_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode smtp_regular_transfer(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + bool *done); static CURLcode smtp_do(struct Curl_easy *data, bool *done); static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, bool premature); @@ -103,9 +163,12 @@ static int smtp_getsock(struct Curl_easy *data, static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done); static CURLcode smtp_setup_connection(struct Curl_easy *data, struct connectdata *conn); -static CURLcode smtp_parse_url_options(struct connectdata *conn); -static CURLcode smtp_parse_url_path(struct Curl_easy *data); -static CURLcode smtp_parse_custom_request(struct Curl_easy *data); +static CURLcode smtp_parse_url_options(struct connectdata *conn, + struct smtp_conn *smtpc); +static CURLcode smtp_parse_url_path(struct Curl_easy *data, + struct smtp_conn *smtpc); +static CURLcode smtp_parse_custom_request(struct Curl_easy *data, + struct SMTP *smtp); static CURLcode smtp_parse_address(const char *fqma, char **address, struct hostname *host); static CURLcode smtp_perform_auth(struct Curl_easy *data, const char *mech, @@ -114,13 +177,14 @@ static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp); static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech); static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out); +static CURLcode cr_eob_add(struct Curl_easy *data); /* * SMTP protocol handler. */ const struct Curl_handler Curl_handler_smtp = { - "SMTP", /* scheme */ + "smtp", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -133,9 +197,11 @@ const struct Curl_handler Curl_handler_smtp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMTP, /* defport */ CURLPROTO_SMTP, /* protocol */ CURLPROTO_SMTP, /* family */ @@ -149,7 +215,7 @@ const struct Curl_handler Curl_handler_smtp = { */ const struct Curl_handler Curl_handler_smtps = { - "SMTPS", /* scheme */ + "smtps", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -162,9 +228,11 @@ const struct Curl_handler Curl_handler_smtps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMTPS, /* defport */ CURLPROTO_SMTPS, /* protocol */ CURLPROTO_SMTP, /* family */ @@ -187,19 +255,6 @@ static const struct SASLproto saslsmtp = { SASL_FLAG_BASE64 /* Configuration flags */ }; -#ifdef USE_SSL -static void smtp_to_smtps(struct connectdata *conn) -{ - /* Change the connection handler */ - conn->handler = &Curl_handler_smtps; - - /* Set the connection's upgraded to TLS flag */ - conn->bits.tls_upgraded = TRUE; -} -#else -#define smtp_to_smtps(x) Curl_nop_stmt -#endif - /*********************************************************************** * * smtp_endofresp() @@ -209,12 +264,16 @@ static void smtp_to_smtps(struct connectdata *conn) * supported authentication mechanisms. */ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *resp) + const char *line, size_t len, int *resp) { - struct smtp_conn *smtpc = &conn->proto.smtpc; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); bool result = FALSE; (void)data; + DEBUGASSERT(smtpc); + if(!smtpc) + return FALSE; + /* Nothing for us */ if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2])) return FALSE; @@ -225,11 +284,14 @@ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, only send the response code instead as per Section 4.2. */ if(line[3] == ' ' || len == 5) { char tmpline[6]; - + curl_off_t code; + const char *p = tmpline; result = TRUE; - memset(tmpline, '\0', sizeof(tmpline)); memcpy(tmpline, line, (len == 5 ? 5 : 3)); - *resp = curlx_sltosi(strtol(tmpline, NULL, 10)); + tmpline[len == 5 ? 5 : 3 ] = 0; + if(curlx_str_number(&p, &code, len == 5 ? 99999 : 999)) + return FALSE; + *resp = (int) code; /* Make sure real server never sends internal value */ if(*resp == 1) @@ -253,9 +315,16 @@ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + char *message; + size_t len; + if(!smtpc) + return CURLE_FAILED_INIT; + + message = curlx_dyn_ptr(&smtpc->pp.recvbuf); + len = smtpc->pp.nfinal; if(len > 4) { /* Find the start of the message */ len -= 4; @@ -281,14 +350,15 @@ static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) /*********************************************************************** * - * state() + * smtp_state() * * This is the ONLY way to change SMTP state! */ -static void state(struct Curl_easy *data, smtpstate newstate) +static void smtp_state(struct Curl_easy *data, + struct smtp_conn *smtpc, + smtpstate newstate) { - struct smtp_conn *smtpc = &data->conn->proto.smtpc; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { "STOP", @@ -308,8 +378,8 @@ static void state(struct Curl_easy *data, smtpstate newstate) }; if(smtpc->state != newstate) - infof(data, "SMTP %p state change from %s to %s", - (void *)smtpc, names[smtpc->state], names[newstate]); + CURL_TRC_SMTP(data, "state change from %s to %s", + names[smtpc->state], names[newstate]); #endif smtpc->state = newstate; @@ -322,11 +392,10 @@ static void state(struct Curl_easy *data, smtpstate newstate) * Sends the EHLO command to not only initialise communication with the ESMTP * server but to also obtain a list of server side supported capabilities. */ -static CURLcode smtp_perform_ehlo(struct Curl_easy *data) +static CURLcode smtp_perform_ehlo(struct Curl_easy *data, + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; smtpc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanism yet */ smtpc->sasl.authused = SASL_AUTH_NONE; /* Clear the authentication mechanism @@ -338,7 +407,7 @@ static CURLcode smtp_perform_ehlo(struct Curl_easy *data) result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain); if(!result) - state(data, SMTP_EHLO); + smtp_state(data, smtpc, SMTP_EHLO); return result; } @@ -350,10 +419,9 @@ static CURLcode smtp_perform_ehlo(struct Curl_easy *data) * Sends the HELO command to initialise communication with the SMTP server. */ static CURLcode smtp_perform_helo(struct Curl_easy *data, - struct connectdata *conn) + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; smtpc->sasl.authused = SASL_AUTH_NONE; /* No authentication mechanism used in smtp connections */ @@ -362,7 +430,7 @@ static CURLcode smtp_perform_helo(struct Curl_easy *data, result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain); if(!result) - state(data, SMTP_HELO); + smtp_state(data, smtpc, SMTP_HELO); return result; } @@ -374,14 +442,13 @@ static CURLcode smtp_perform_helo(struct Curl_easy *data, * Sends the STLS command to start the upgrade to TLS. */ static CURLcode smtp_perform_starttls(struct Curl_easy *data, - struct connectdata *conn) + struct smtp_conn *smtpc) { /* Send the STARTTLS command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, - "%s", "STARTTLS"); + CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "STARTTLS"); if(!result) - state(data, SMTP_STARTTLS); + smtp_state(data, smtpc, SMTP_STARTTLS); return result; } @@ -392,33 +459,40 @@ static CURLcode smtp_perform_starttls(struct Curl_easy *data, * * Performs the upgrade to TLS. */ -static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data) +static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data, + struct smtp_conn *smtpc) { +#ifdef USE_SSL /* Start the SSL connection */ struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; CURLcode result; bool ssldone = FALSE; + DEBUGASSERT(smtpc->state == SMTP_UPGRADETLS); if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) goto out; + /* Change the connection handler and SMTP state */ + conn->handler = &Curl_handler_smtps; } + DEBUGASSERT(!smtpc->ssldone); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); - if(!result) { + DEBUGF(infof(data, "smtp_perform_upgrade_tls, connect -> %d, %d", + result, ssldone)); + if(!result && ssldone) { smtpc->ssldone = ssldone; - if(smtpc->state != SMTP_UPGRADETLS) - state(data, SMTP_UPGRADETLS); - - if(smtpc->ssldone) { - smtp_to_smtps(conn); - result = smtp_perform_ehlo(data); - } + /* perform EHLO now, changes smtp->state out of SMTP_UPGRADETLS */ + result = smtp_perform_ehlo(data, smtpc); } out: return result; +#else + (void)data; + (void)smtpc; + return CURLE_NOT_BUILT_IN; +#endif } /*********************************************************************** @@ -433,9 +507,13 @@ static CURLcode smtp_perform_auth(struct Curl_easy *data, const struct bufref *initresp) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &data->conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); const char *ir = (const char *) Curl_bufref_ptr(initresp); + if(!smtpc) + return CURLE_FAILED_INIT; + if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); @@ -458,10 +536,12 @@ static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp) { - struct smtp_conn *smtpc = &data->conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); (void)mech; - + if(!smtpc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &smtpc->pp, "%s", (const char *) Curl_bufref_ptr(resp)); } @@ -474,10 +554,12 @@ static CURLcode smtp_continue_auth(struct Curl_easy *data, */ static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech) { - struct smtp_conn *smtpc = &data->conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); (void)mech; - + if(!smtpc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &smtpc->pp, "*"); } @@ -488,18 +570,17 @@ static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech) * Initiates the authentication sequence, with the appropriate SASL * authentication mechanism. */ -static CURLcode smtp_perform_authentication(struct Curl_easy *data) +static CURLcode smtp_perform_authentication(struct Curl_easy *data, + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; saslprogress progress; /* Check we have enough data to authenticate with, and the server supports authentication, and end the connect phase if not */ if(!smtpc->auth_supported || !Curl_sasl_can_authenticate(&smtpc->sasl, data)) { - state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); return result; } @@ -508,12 +589,9 @@ static CURLcode smtp_perform_authentication(struct Curl_easy *data) if(!result) { if(progress == SASL_INPROGRESS) - state(data, SMTP_AUTH); - else { - /* Other mechanisms not supported */ - infof(data, "No known authentication mechanisms supported"); - result = CURLE_LOGIN_DENIED; - } + smtp_state(data, smtpc, SMTP_AUTH); + else + result = Curl_sasl_is_blocked(&smtpc->sasl, data); } return result; @@ -523,27 +601,27 @@ static CURLcode smtp_perform_authentication(struct Curl_easy *data) * * smtp_perform_command() * - * Sends a SMTP based command. + * Sends an SMTP based command. */ -static CURLcode smtp_perform_command(struct Curl_easy *data) +static CURLcode smtp_perform_command(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct SMTP *smtp = data->req.p.smtp; if(smtp->rcpt) { /* We notify the server we are sending UTF-8 data if a) it supports the SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in - either the local address or host name parts. This is regardless of - whether the host name is encoded using IDN ACE */ + either the local address or hostname parts. This is regardless of + whether the hostname is encoded using IDN ACE */ bool utf8 = FALSE; if((!smtp->custom) || (!smtp->custom[0])) { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the mailbox to verify into the local address and host name - parts, converting the host name to an IDN A-label if necessary */ + /* Parse the mailbox to verify into the local address and hostname + parts, converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(smtp->rcpt->data, &address, &host); if(result) @@ -551,13 +629,13 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 6 */ - utf8 = (conn->proto.smtpc.utf8_supported) && + utf8 = (smtpc->utf8_supported) && ((host.encalloc) || (!Curl_is_ASCII_name(address)) || (!Curl_is_ASCII_name(host.name))); - /* Send the VRFY command (Note: The host name part may be absent when the + /* Send the VRFY command (Note: The hostname part may be absent when the host is a local system) */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "VRFY %s%s%s%s", + result = Curl_pp_sendf(data, &smtpc->pp, "VRFY %s%s%s%s", address, host.name ? "@" : "", host.name ? host.name : "", @@ -569,11 +647,10 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) else { /* Establish whether we should report that we support SMTPUTF8 for EXPN commands to the server as per RFC-6531 sect. 3.1 point 6 */ - utf8 = (conn->proto.smtpc.utf8_supported) && - (!strcmp(smtp->custom, "EXPN")); + utf8 = (smtpc->utf8_supported) && (!strcmp(smtp->custom, "EXPN")); /* Send the custom recipient based command such as the EXPN command */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + result = Curl_pp_sendf(data, &smtpc->pp, "%s %s%s", smtp->custom, smtp->rcpt->data, utf8 ? " SMTPUTF8" : ""); @@ -581,12 +658,12 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) } else /* Send the non-recipient based command such as HELP */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", + result = Curl_pp_sendf(data, &smtpc->pp, "%s", smtp->custom && smtp->custom[0] != '\0' ? smtp->custom : "HELP"); if(!result) - state(data, SMTP_COMMAND); + smtp_state(data, smtpc, SMTP_COMMAND); return result; } @@ -597,18 +674,19 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) * * Sends an MAIL command to initiate the upload of a message. */ -static CURLcode smtp_perform_mail(struct Curl_easy *data) +static CURLcode smtp_perform_mail(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp) { char *from = NULL; char *auth = NULL; char *size = NULL; CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; /* We notify the server we are sending UTF-8 data if a) it supports the SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in - either the local address or host name parts. This is regardless of - whether the host name is encoded using IDN ACE */ + either the local address or hostname parts. This is regardless of + whether the hostname is encoded using IDN ACE */ bool utf8 = FALSE; /* Calculate the FROM parameter */ @@ -616,16 +694,16 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the FROM mailbox into the local address and host name parts, - converting the host name to an IDN A-label if necessary */ + /* Parse the FROM mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], &address, &host); if(result) - return result; + goto out; /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ - utf8 = (conn->proto.smtpc.utf8_supported) && + utf8 = (smtpc->utf8_supported) && ((host.encalloc) || (!Curl_is_ASCII_name(address)) || (!Curl_is_ASCII_name(host.name))); @@ -635,8 +713,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) Curl_free_idnconverted_hostname(&host); } else - /* An invalid mailbox was provided but we'll simply let the server worry - about that and reply with a 501 error */ + /* An invalid mailbox was provided but we will simply let the server + worry about that and reply with a 501 error */ from = aprintf("<%s>", address); free(address); @@ -645,27 +723,27 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) /* Null reverse-path, RFC-5321, sect. 3.6.3 */ from = strdup("<>"); - if(!from) - return CURLE_OUT_OF_MEMORY; + if(!from) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } /* Calculate the optional AUTH parameter */ - if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.sasl.authused) { + if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) { if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the AUTH mailbox into the local address and host name parts, - converting the host name to an IDN A-label if necessary */ + /* Parse the AUTH mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], &address, &host); - if(result) { - free(from); - return result; - } + if(result) + goto out; /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ - if((!utf8) && (conn->proto.smtpc.utf8_supported) && + if((!utf8) && (smtpc->utf8_supported) && ((host.encalloc) || (!Curl_is_ASCII_name(address)) || (!Curl_is_ASCII_name(host.name)))) utf8 = TRUE; @@ -676,10 +754,9 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) Curl_free_idnconverted_hostname(&host); } else - /* An invalid mailbox was provided but we'll simply let the server + /* An invalid mailbox was provided but we will simply let the server worry about it */ auth = aprintf("<%s>", address); - free(address); } else @@ -687,16 +764,16 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) auth = strdup("<>"); if(!auth) { - free(from); - - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } } +#ifndef CURL_DISABLE_MIME /* Prepare the mime data if some. */ if(data->set.mimepost.kind != MIMEKIND_NONE) { /* Use the whole structure as data. */ - data->set.mimepost.flags &= ~MIME_BODY_ONLY; + data->set.mimepost.flags &= ~(unsigned int)MIME_BODY_ONLY; /* Add external headers and mime version. */ curl_mime_headers(&data->set.mimepost, data->set.headers, 0); @@ -708,46 +785,39 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) result = Curl_mime_add_header(&data->set.mimepost.curlheaders, "Mime-Version: 1.0"); - /* Make sure we will read the entire mime structure. */ if(!result) - result = Curl_mime_rewind(&data->set.mimepost); - - if(result) { - free(from); - free(auth); - - return result; - } - - data->state.infilesize = Curl_mime_size(&data->set.mimepost); - - /* Read from mime structure. */ - data->state.fread_func = (curl_read_callback) Curl_mime_read; - data->state.in = (void *) &data->set.mimepost; + result = Curl_creader_set_mime(data, &data->set.mimepost); + if(result) + goto out; + data->state.infilesize = Curl_creader_total_length(data); + } + else +#endif + { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + goto out; } /* Calculate the optional SIZE parameter */ - if(conn->proto.smtpc.size_supported && data->state.infilesize > 0) { - size = aprintf("%" CURL_FORMAT_CURL_OFF_T, data->state.infilesize); + if(smtpc->size_supported && data->state.infilesize > 0) { + size = aprintf("%" FMT_OFF_T, data->state.infilesize); if(!size) { - free(from); - free(auth); - - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } } - /* If the mailboxes in the FROM and AUTH parameters don't include a UTF-8 + /* If the mailboxes in the FROM and AUTH parameters do not include a UTF-8 based address then quickly scan through the recipient list and check if any there do, as we need to correctly identify our support for SMTPUTF8 in the envelope, as per RFC-6531 sect. 3.4 */ - if(conn->proto.smtpc.utf8_supported && !utf8) { - struct SMTP *smtp = data->req.p.smtp; + if(smtpc->utf8_supported && !utf8) { struct curl_slist *rcpt = smtp->rcpt; while(rcpt && !utf8) { - /* Does the host name contain non-ASCII characters? */ + /* Does the hostname contain non-ASCII characters? */ if(!Curl_is_ASCII_name(rcpt->data)) utf8 = TRUE; @@ -755,8 +825,13 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) } } + /* Add the client reader doing STMP EOB escaping */ + result = cr_eob_add(data); + if(result) + goto out; + /* Send the MAIL command */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + result = Curl_pp_sendf(data, &smtpc->pp, "MAIL FROM:%s%s%s%s%s%s", from, /* Mandatory */ auth ? " AUTH=" : "", /* Optional on AUTH support */ @@ -766,12 +841,13 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) utf8 ? " SMTPUTF8" /* Internationalised mailbox */ : ""); /* included in our envelope */ +out: free(from); free(auth); free(size); if(!result) - state(data, SMTP_MAIL); + smtp_state(data, smtpc, SMTP_MAIL); return result; } @@ -783,16 +859,16 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) * Sends a RCPT TO command for a given recipient as part of the message upload * process. */ -static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) +static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct SMTP *smtp = data->req.p.smtp; char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the recipient mailbox into the local address and host name parts, - converting the host name to an IDN A-label if necessary */ + /* Parse the recipient mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(smtp->rcpt->data, &address, &host); if(result) @@ -800,19 +876,18 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) /* Send the RCPT TO command */ if(host.name) - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s@%s>", + result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s@%s>", address, host.name); else - /* An invalid mailbox was provided but we'll simply let the server worry + /* An invalid mailbox was provided but we will simply let the server worry about that and reply with a 501 error */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s>", - address); + result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s>", address); Curl_free_idnconverted_hostname(&host); free(address); if(!result) - state(data, SMTP_RCPT); + smtp_state(data, smtpc, SMTP_RCPT); return result; } @@ -824,19 +899,20 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) * Performs the quit action prior to sclose() being called. */ static CURLcode smtp_perform_quit(struct Curl_easy *data, - struct connectdata *conn) + struct smtp_conn *smtpc) { /* Send the QUIT command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", "QUIT"); + CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "QUIT"); if(!result) - state(data, SMTP_QUIT); + smtp_state(data, smtpc, SMTP_QUIT); return result; } /* For the initial server greeting */ static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { @@ -848,13 +924,14 @@ static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; } else - result = smtp_perform_ehlo(data); + result = smtp_perform_ehlo(data, smtpc); return result; } /* For STARTTLS responses */ static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { @@ -862,7 +939,7 @@ static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.smtpc.pp.cache_size) + if(smtpc->pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(smtpcode != 220) { @@ -871,30 +948,30 @@ static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, result = CURLE_USE_SSL_FAILED; } else - result = smtp_perform_authentication(data); + result = smtp_perform_authentication(data, smtpc); } else - result = smtp_perform_upgrade_tls(data); + smtp_state(data, smtpc, SMTP_UPGRADETLS); return result; } /* For EHLO responses */ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, - struct connectdata *conn, int smtpcode, + struct smtp_conn *smtpc, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; - const char *line = data->state.buffer; - size_t len = strlen(line); + const char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; (void)instate; /* no use for this yet */ if(smtpcode/100 != 2 && smtpcode != 1) { if(data->set.use_ssl <= CURLUSESSL_TRY - || Curl_conn_is_ssl(conn, FIRSTSOCKET)) - result = smtp_perform_helo(data, conn); + || Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) + result = smtp_perform_helo(data, smtpc); else { failf(data, "Remote access denied: %d", smtpcode); result = CURLE_REMOTE_ACCESS_DENIED; @@ -958,21 +1035,21 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, } if(smtpcode != 1) { - if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - /* We don't have a SSL/TLS connection yet, but SSL is requested */ + if(data->set.use_ssl && !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) { + /* We do not have an SSL/TLS connection yet, but SSL is requested */ if(smtpc->tls_supported) /* Switch to TLS connection now */ - result = smtp_perform_starttls(data, conn); + result = smtp_perform_starttls(data, smtpc); else if(data->set.use_ssl == CURLUSESSL_TRY) /* Fallback and carry on with authentication */ - result = smtp_perform_authentication(data); + result = smtp_perform_authentication(data, smtpc); else { failf(data, "STARTTLS not supported."); result = CURLE_USE_SSL_FAILED; } } else - result = smtp_perform_authentication(data); + result = smtp_perform_authentication(data, smtpc); } } else { @@ -984,7 +1061,9 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, } /* For HELO responses */ -static CURLcode smtp_state_helo_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_helo_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; @@ -996,19 +1075,18 @@ static CURLcode smtp_state_helo_resp(struct Curl_easy *data, int smtpcode, } else /* End of connect phase */ - state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); return result; } /* For SASL authentication responses */ static CURLcode smtp_state_auth_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; saslprogress progress; (void)instate; /* no use for this yet */ @@ -1017,7 +1095,7 @@ static CURLcode smtp_state_auth_resp(struct Curl_easy *data, if(!result) switch(progress) { case SASL_DONE: - state(data, SMTP_STOP); /* Authenticated */ + smtp_state(data, smtpc, SMTP_STOP); /* Authenticated */ break; case SASL_IDLE: /* No mechanism left after cancellation */ failf(data, "Authentication cancelled"); @@ -1031,13 +1109,15 @@ static CURLcode smtp_state_auth_resp(struct Curl_easy *data, } /* For command responses */ -static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_command_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; - char *line = data->state.buffer; - size_t len = strlen(line); + char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; (void)instate; /* no use for this yet */ @@ -1047,12 +1127,8 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, result = CURLE_WEIRD_SERVER_REPLY; } else { - /* Temporarily add the LF character back and send as body to the client */ - if(!data->req.no_body) { - line[len] = '\n'; - result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } + if(!data->req.no_body) + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); if(smtpcode != 1) { if(smtp->rcpt) { @@ -1060,15 +1136,15 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, if(smtp->rcpt) { /* Send the next command */ - result = smtp_perform_command(data); + result = smtp_perform_command(data, smtpc, smtp); } else /* End of DO phase */ - state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); } else /* End of DO phase */ - state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); } } @@ -1076,7 +1152,10 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, } /* For MAIL responses */ -static CURLcode smtp_state_mail_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_mail_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; @@ -1088,29 +1167,29 @@ static CURLcode smtp_state_mail_resp(struct Curl_easy *data, int smtpcode, } else /* Start the RCPT TO command */ - result = smtp_perform_rcpt_to(data); + result = smtp_perform_rcpt_to(data, smtpc, smtp); return result; } /* For RCPT responses */ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, - struct connectdata *conn, int smtpcode, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; bool is_smtp_err = FALSE; bool is_smtp_blocking_err = FALSE; (void)instate; /* no use for this yet */ - is_smtp_err = (smtpcode/100 != 2) ? TRUE : FALSE; + is_smtp_err = (smtpcode/100 != 2); - /* If there's multiple RCPT TO to be issued, it's possible to ignore errors + /* If there is multiple RCPT TO to be issued, it is possible to ignore errors and proceed with only the valid addresses. */ - is_smtp_blocking_err = - (is_smtp_err && !data->set.mail_rcpt_allowfails) ? TRUE : FALSE; + is_smtp_blocking_err = (is_smtp_err && !data->set.mail_rcpt_allowfails); if(is_smtp_err) { /* Remembering the last failure which we can report if all "RCPT TO" have @@ -1132,9 +1211,9 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, if(smtp->rcpt) /* Send the next RCPT TO command */ - result = smtp_perform_rcpt_to(data); + result = smtp_perform_rcpt_to(data, smtpc, smtp); else { - /* We weren't able to issue a successful RCPT TO command while going + /* We were not able to issue a successful RCPT TO command while going over recipients (potentially multiple). Sending back last error. */ if(!smtp->rcpt_had_ok) { failf(data, "RCPT failed: %d (last error)", smtp->rcpt_last_error); @@ -1142,10 +1221,10 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, } else { /* Send the DATA command */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", "DATA"); + result = Curl_pp_sendf(data, &smtpc->pp, "%s", "DATA"); if(!result) - state(data, SMTP_DATA); + smtp_state(data, smtpc, SMTP_DATA); } } } @@ -1154,7 +1233,9 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, } /* For DATA response */ -static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_data_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; @@ -1169,10 +1250,10 @@ static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, Curl_pgrsSetUploadSize(data, data->state.infilesize); /* SMTP upload */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ - state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); } return result; @@ -1181,6 +1262,7 @@ static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, /* For POSTDATA responses, which are received after the entire DATA part has been sent to the server */ static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { @@ -1192,32 +1274,39 @@ static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; /* End of DONE phase */ - state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); return result; } -static CURLcode smtp_statemachine(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode smtp_pp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int smtpcode; - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct pingpong *pp = &smtpc->pp; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); size_t nread = 0; + if(!smtpc || !smtp) + return CURLE_FAILED_INIT; + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ - if(smtpc->state == SMTP_UPGRADETLS) - return smtp_perform_upgrade_tls(data); +upgrade_tls: + if(smtpc->state == SMTP_UPGRADETLS) { + result = smtp_perform_upgrade_tls(data, smtpc); + if(result || (smtpc->state == SMTP_UPGRADETLS)) + return result; + } /* Flush any data that needs to be sent */ - if(pp->sendleft) - return Curl_pp_flushsend(data, pp); + if(smtpc->pp.sendleft) + return Curl_pp_flushsend(data, &smtpc->pp); do { /* Read the response from the server */ - result = Curl_pp_readresp(data, sock, pp, &smtpcode, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, &smtpc->pp, + &smtpcode, &nread); if(result) return result; @@ -1231,53 +1320,59 @@ static CURLcode smtp_statemachine(struct Curl_easy *data, /* We have now received a full SMTP server response */ switch(smtpc->state) { case SMTP_SERVERGREET: - result = smtp_state_servergreet_resp(data, smtpcode, smtpc->state); + result = smtp_state_servergreet_resp(data, smtpc, + smtpcode, smtpc->state); break; case SMTP_EHLO: - result = smtp_state_ehlo_resp(data, conn, smtpcode, smtpc->state); + result = smtp_state_ehlo_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_HELO: - result = smtp_state_helo_resp(data, smtpcode, smtpc->state); + result = smtp_state_helo_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_STARTTLS: - result = smtp_state_starttls_resp(data, smtpcode, smtpc->state); + result = smtp_state_starttls_resp(data, smtpc, smtpcode, smtpc->state); + /* During UPGRADETLS, leave the read loop as we need to connect + * (e.g. TLS handshake) before we continue sending/receiving. */ + if(!result && (smtpc->state == SMTP_UPGRADETLS)) + goto upgrade_tls; break; case SMTP_AUTH: - result = smtp_state_auth_resp(data, smtpcode, smtpc->state); + result = smtp_state_auth_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_COMMAND: - result = smtp_state_command_resp(data, smtpcode, smtpc->state); + result = smtp_state_command_resp(data, smtpc, smtp, + smtpcode, smtpc->state); break; case SMTP_MAIL: - result = smtp_state_mail_resp(data, smtpcode, smtpc->state); + result = smtp_state_mail_resp(data, smtpc, smtp, smtpcode, smtpc->state); break; case SMTP_RCPT: - result = smtp_state_rcpt_resp(data, conn, smtpcode, smtpc->state); + result = smtp_state_rcpt_resp(data, smtpc, smtp, smtpcode, smtpc->state); break; case SMTP_DATA: - result = smtp_state_data_resp(data, smtpcode, smtpc->state); + result = smtp_state_data_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_POSTDATA: - result = smtp_state_postdata_resp(data, smtpcode, smtpc->state); + result = smtp_state_postdata_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ - state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); break; } - } while(!result && smtpc->state != SMTP_STOP && Curl_pp_moredata(pp)); + } while(!result && smtpc->state != SMTP_STOP && + Curl_pp_moredata(&smtpc->pp)); return result; } @@ -1286,29 +1381,23 @@ static CURLcode smtp_statemachine(struct Curl_easy *data, static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - if((conn->handler->flags & PROTOPT_SSL) && !smtpc->ssldone) { - bool ssldone = FALSE; - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); - smtpc->ssldone = ssldone; - if(result || !smtpc->ssldone) - return result; - } + *done = FALSE; + if(!smtpc) + return CURLE_FAILED_INIT; result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE); - *done = (smtpc->state == SMTP_STOP) ? TRUE : FALSE; - + *done = (smtpc->state == SMTP_STOP); return result; } static CURLcode smtp_block_statemach(struct Curl_easy *data, - struct connectdata *conn, + struct smtp_conn *smtpc, bool disconnecting) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; while(smtpc->state != SMTP_STOP && !result) result = Curl_pp_statemach(data, &smtpc->pp, TRUE, disconnecting); @@ -1316,25 +1405,13 @@ static CURLcode smtp_block_statemach(struct Curl_easy *data, return result; } -/* Allocate and initialize the SMTP struct for the current Curl_easy if - required */ -static CURLcode smtp_init(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct SMTP *smtp; - - smtp = data->req.p.smtp = calloc(sizeof(struct SMTP), 1); - if(!smtp) - result = CURLE_OUT_OF_MEMORY; - - return result; -} - /* For the SMTP "protocol connect" and "doing" phases only */ static int smtp_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.smtpc.pp, socks); + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + return smtpc ? + Curl_pp_getsock(data, &smtpc->pp, socks) : GETSOCK_BLANK; } /*********************************************************************** @@ -1349,37 +1426,37 @@ static int smtp_getsock(struct Curl_easy *data, */ static CURLcode smtp_connect(struct Curl_easy *data, bool *done) { + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct pingpong *pp = &smtpc->pp; *done = FALSE; /* default to not done yet */ + if(!smtpc) + return CURLE_FAILED_INIT; /* We always support persistent connections in SMTP */ - connkeep(conn, "SMTP default"); + connkeep(data->conn, "SMTP default"); - PINGPONG_SETUP(pp, smtp_statemachine, smtp_endofresp); + PINGPONG_SETUP(&smtpc->pp, smtp_pp_statemachine, smtp_endofresp); /* Initialize the SASL storage */ Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(&smtpc->pp); /* Parse the URL options */ - result = smtp_parse_url_options(conn); + result = smtp_parse_url_options(data->conn, smtpc); if(result) return result; /* Parse the URL path */ - result = smtp_parse_url_path(data); + result = smtp_parse_url_path(data, smtpc); if(result) return result; /* Start off waiting for the server greeting response */ - state(data, SMTP_SERVERGREET); + smtp_state(data, smtpc, SMTP_SERVERGREET); result = smtp_multi_statemach(data, done); @@ -1398,16 +1475,16 @@ static CURLcode smtp_connect(struct Curl_easy *data, bool *done) static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, bool premature) { + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct SMTP *smtp = data->req.p.smtp; - struct pingpong *pp = &conn->proto.smtpc.pp; - char *eob; - ssize_t len; - ssize_t bytes_written; + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); (void)premature; + if(!smtpc) + return CURLE_FAILED_INIT; if(!smtp) return CURLE_OK; @@ -1419,57 +1496,18 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, result = status; /* use the already set error code */ } else if(!data->set.connect_only && data->set.mail_rcpt && - (data->state.upload || data->set.mimepost.kind)) { - /* Calculate the EOB taking into account any terminating CRLF from the - previous line of the email or the CRLF of the DATA command when there - is "no mail data". RFC-5321, sect. 4.1.1.4. - - Note: As some SSL backends, such as OpenSSL, will cause Curl_write() to - fail when using a different pointer following a previous write, that - returned CURLE_AGAIN, we duplicate the EOB now rather than when the - bytes written doesn't equal len. */ - if(smtp->trailing_crlf || !data->state.infilesize) { - eob = strdup(&SMTP_EOB[2]); - len = SMTP_EOB_LEN - 2; - } - else { - eob = strdup(SMTP_EOB); - len = SMTP_EOB_LEN; - } - - if(!eob) - return CURLE_OUT_OF_MEMORY; + (data->state.upload || IS_MIME_POST(data))) { - /* Send the end of block data */ - result = Curl_write(data, conn->writesockfd, eob, len, &bytes_written); - if(result) { - free(eob); - return result; - } - - if(bytes_written != len) { - /* The whole chunk was not sent so keep it around and adjust the - pingpong structure accordingly */ - pp->sendthis = eob; - pp->sendsize = len; - pp->sendleft = len - bytes_written; - } - else { - /* Successfully sent so adjust the response timeout relative to now */ - pp->response = Curl_now(); - - free(eob); - } - - state(data, SMTP_POSTDATA); + smtp_state(data, smtpc, SMTP_POSTDATA); /* Run the state-machine */ - result = smtp_block_statemach(data, conn, FALSE); + result = smtp_block_statemach(data, smtpc, FALSE); } /* Clear the transfer mode for the next request */ smtp->transfer = PPTRANSFER_BODY; - + CURL_TRC_SMTP(data, "smtp_done(status=%d, premature=%d) -> %d", + status, premature, result); return result; } @@ -1480,14 +1518,16 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, * This is the actual DO function for SMTP. Transfer a mail, send a command * or get some data according to the options previously setup. */ -static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, +static CURLcode smtp_perform(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + bool *connected, bool *dophase_done) { /* This is SMTP and no proxy */ CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; - DEBUGF(infof(data, "DO phase starts")); + CURL_TRC_SMTP(data, "smtp_perform(), start"); if(data->req.no_body) { /* Requested no body means no transfer */ @@ -1499,10 +1539,10 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, /* Store the first recipient (or NULL if not specified) */ smtp->rcpt = data->set.mail_rcpt; - /* Track of whether we've successfully sent at least one RCPT TO command */ + /* Track of whether we have successfully sent at least one RCPT TO command */ smtp->rcpt_had_ok = FALSE; - /* Track of the last error we've received by sending RCPT TO command */ + /* Track of the last error we have received by sending RCPT TO command */ smtp->rcpt_last_error = 0; /* Initial data character is the first character in line: it is implicitly @@ -1511,24 +1551,24 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, smtp->eob = 2; /* Start the first command in the DO phase */ - if((data->state.upload || data->set.mimepost.kind) && data->set.mail_rcpt) + if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt) /* MAIL transfer */ - result = smtp_perform_mail(data); + result = smtp_perform_mail(data, smtpc, smtp); else /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */ - result = smtp_perform_command(data); + result = smtp_perform_command(data, smtpc, smtp); if(result) - return result; + goto out; /* Run the state-machine */ result = smtp_multi_statemach(data, dophase_done); *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); - if(*dophase_done) - DEBUGF(infof(data, "DO phase is complete")); - +out: + CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d", + result, *connected, *dophase_done); return result; } @@ -1543,16 +1583,24 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, */ static CURLcode smtp_do(struct Curl_easy *data, bool *done) { + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); *done = FALSE; /* default to false */ + if(!smtpc || !smtp) + return CURLE_FAILED_INIT; /* Parse the custom request */ - result = smtp_parse_custom_request(data); + result = smtp_parse_custom_request(data, smtp); if(result) return result; - result = smtp_regular_transfer(data, done); - + result = smtp_regular_transfer(data, smtpc, smtp, done); + CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done); return result; } @@ -1567,40 +1615,37 @@ static CURLcode smtp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct smtp_conn *smtpc = &conn->proto.smtpc; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + (void)data; + if(!smtpc) + return CURLE_FAILED_INIT; /* We cannot send quit unconditionally. If this connection is stale or bad in any way, 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) { - if(!smtp_perform_quit(data, conn)) - (void)smtp_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ + if(!smtp_perform_quit(data, smtpc)) + (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ } - /* Disconnect from the server */ - Curl_pp_disconnect(&smtpc->pp); - /* Cleanup the SASL module */ Curl_sasl_cleanup(conn, smtpc->sasl.authused); - - /* Cleanup our connection based variables */ - Curl_safefree(smtpc->domain); - + CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); return CURLE_OK; } /* Call this when the DO phase has completed */ -static CURLcode smtp_dophase_done(struct Curl_easy *data, bool connected) +static CURLcode smtp_dophase_done(struct Curl_easy *data, + struct SMTP *smtp, + bool connected) { - struct SMTP *smtp = data->req.p.smtp; - (void)connected; if(smtp->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); return CURLE_OK; } @@ -1608,16 +1653,21 @@ static CURLcode smtp_dophase_done(struct Curl_easy *data, bool connected) /* Called from multi.c while DOing */ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = smtp_multi_statemach(data, dophase_done); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); + CURLcode result; + if(!smtp) + return CURLE_FAILED_INIT; + result = smtp_multi_statemach(data, dophase_done); if(result) DEBUGF(infof(data, "DO phase failed")); else if(*dophase_done) { - result = smtp_dophase_done(data, FALSE /* not connected */); + result = smtp_dophase_done(data, smtp, FALSE /* not connected */); DEBUGF(infof(data, "DO phase is complete")); } + CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done); return result; } @@ -1631,6 +1681,8 @@ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) * remote host. */ static CURLcode smtp_regular_transfer(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, bool *dophase_done) { CURLcode result = CURLE_OK; @@ -1646,29 +1698,58 @@ static CURLcode smtp_regular_transfer(struct Curl_easy *data, Curl_pgrsSetDownloadSize(data, -1); /* Carry out the perform */ - result = smtp_perform(data, &connected, dophase_done); + result = smtp_perform(data, smtpc, smtp, &connected, dophase_done); /* Perform post DO phase operations if necessary */ if(!result && *dophase_done) - result = smtp_dophase_done(data, connected); + result = smtp_dophase_done(data, smtp, connected); + CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d", + result, *dophase_done); return result; } + +static void smtp_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SMTP *smtp = entry; + (void)key; + (void)klen; + free(smtp); +} + +static void smtp_conn_dtor(void *key, size_t klen, void *entry) +{ + struct smtp_conn *smtpc = entry; + (void)key; + (void)klen; + Curl_pp_disconnect(&smtpc->pp); + Curl_safefree(smtpc->domain); + free(smtpc); +} + static CURLcode smtp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - CURLcode result; + struct smtp_conn *smtpc; + struct SMTP *smtp; + CURLcode result = CURLE_OK; - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; + smtpc = calloc(1, sizeof(*smtpc)); + if(!smtpc || + Curl_conn_meta_set(conn, CURL_META_SMTP_CONN, smtpc, smtp_conn_dtor)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } - /* Initialise the SMTP layer */ - result = smtp_init(data); - if(result) - return result; + smtp = calloc(1, sizeof(*smtp)); + if(!smtp || + Curl_meta_set(data, CURL_META_SMTP_EASY, smtp, smtp_easy_dtor)) + result = CURLE_OUT_OF_MEMORY; - return CURLE_OK; +out: + CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result); + return result; } /*********************************************************************** @@ -1677,10 +1758,10 @@ static CURLcode smtp_setup_connection(struct Curl_easy *data, * * Parse the URL login options. */ -static CURLcode smtp_parse_url_options(struct connectdata *conn) +static CURLcode smtp_parse_url_options(struct connectdata *conn, + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; const char *ptr = conn->options; while(!result && ptr && *ptr) { @@ -1714,11 +1795,10 @@ static CURLcode smtp_parse_url_options(struct connectdata *conn) * * Parse the URL path into separate path components. */ -static CURLcode smtp_parse_url_path(struct Curl_easy *data) +static CURLcode smtp_parse_url_path(struct Curl_easy *data, + struct smtp_conn *smtpc) { /* The SMTP struct is already initialised in smtp_connect() */ - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; const char *path = &data->state.up.path[1]; /* skip leading path */ char localhost[HOSTNAME_MAX + 1]; @@ -1740,10 +1820,10 @@ static CURLcode smtp_parse_url_path(struct Curl_easy *data) * * Parse the custom request. */ -static CURLcode smtp_parse_custom_request(struct Curl_easy *data) +static CURLcode smtp_parse_custom_request(struct Curl_easy *data, + struct SMTP *smtp) { CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; const char *custom = data->set.str[STRING_CUSTOMREQUEST]; /* URL decode the custom request */ @@ -1758,7 +1838,7 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data) * smtp_parse_address() * * Parse the fully qualified mailbox address into a local address part and the - * host name, converting the host name to an IDN A-label, as per RFC-5890, if + * hostname, converting the hostname to an IDN A-label, as per RFC-5890, if * necessary. * * Parameters: @@ -1769,8 +1849,8 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data) * address [in/out] - A new allocated buffer which holds the local * address part of the mailbox. This buffer must be * free'ed by the caller. - * host [in/out] - The host name structure that holds the original, - * and optionally encoded, host name. + * host [in/out] - The hostname structure that holds the original, + * and optionally encoded, hostname. * Curl_free_idnconverted_hostname() must be called * once the caller has finished with the structure. * @@ -1778,14 +1858,14 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data) * * Notes: * - * Should a UTF-8 host name require conversion to IDN ACE and we cannot honor + * Should a UTF-8 hostname require conversion to IDN ACE and we cannot honor * that conversion then we shall return success. This allow the caller to send * the data to the server as a U-label (as per RFC-6531 sect. 3.2). * * If an mailbox '@' separator cannot be located then the mailbox is considered * to be either a local mailbox or an invalid mailbox (depending on what the * calling function deems it to be) then the input will simply be returned in - * the address part with the host name being NULL. + * the address part with the hostname being NULL. */ static CURLcode smtp_parse_address(const char *fqma, char **address, struct hostname *host) @@ -1794,7 +1874,7 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, size_t length; /* Duplicate the fully qualified email address so we can manipulate it, - ensuring it doesn't contain the delimiters if specified */ + ensuring it does not contain the delimiters if specified */ char *dup = strdup(fqma[0] == '<' ? fqma + 1 : fqma); if(!dup) return CURLE_OUT_OF_MEMORY; @@ -1805,17 +1885,17 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, dup[length - 1] = '\0'; } - /* Extract the host name from the address (if we can) */ + /* Extract the hostname from the address (if we can) */ host->name = strpbrk(dup, "@"); if(host->name) { *host->name = '\0'; host->name = host->name + 1; - /* Attempt to convert the host name to IDN ACE */ + /* Attempt to convert the hostname to IDN ACE */ (void) Curl_idnconvert_hostname(host); /* If Curl_idnconvert_hostname() fails then we shall attempt to continue - and send the host name using UTF-8 rather than as 7-bit ACE (which is + and send the hostname using UTF-8 rather than as 7-bit ACE (which is our preference) */ } @@ -1825,108 +1905,174 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, return result; } -CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, - const ssize_t nread, - const ssize_t offset) +struct cr_eob_ctx { + struct Curl_creader super; + struct bufq buf; + size_t n_eob; /* how many EOB bytes we matched so far */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ +}; + +static CURLcode cr_eob_init(struct Curl_easy *data, + struct Curl_creader *reader) { - /* When sending a SMTP payload we must detect CRLF. sequences making sure - they are sent as CRLF.. instead, as a . on the beginning of a line will - be deleted by the server when not part of an EOB terminator and a - genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of - data by the server - */ - ssize_t i; - ssize_t si; - struct SMTP *smtp = data->req.p.smtp; - char *scratch = data->state.scratch; - char *newscratch = NULL; - char *oldscratch = NULL; - size_t eob_sent; - - /* Do we need to allocate a scratch buffer? */ - if(!scratch || data->set.crlf) { - oldscratch = scratch; - - scratch = newscratch = malloc(2 * data->set.upload_buffer_size); - if(!newscratch) { - failf(data, "Failed to alloc scratch buffer"); - - return CURLE_OUT_OF_MEMORY; - } - } - DEBUGASSERT((size_t)data->set.upload_buffer_size >= (size_t)nread); - - /* Have we already sent part of the EOB? */ - eob_sent = smtp->eob; - - /* This loop can be improved by some kind of Boyer-Moore style of - approach but that is saved for later... */ - if(offset) - memcpy(scratch, data->req.upload_fromhere, offset); - for(i = offset, si = offset; i < nread; i++) { - if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) { - smtp->eob++; - - /* Is the EOB potentially the terminating CRLF? */ - if(2 == smtp->eob || SMTP_EOB_LEN == smtp->eob) - smtp->trailing_crlf = TRUE; - else - smtp->trailing_crlf = FALSE; - } - else if(smtp->eob) { - /* A previous substring matched so output that first */ - memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); - si += smtp->eob - eob_sent; - - /* Then compare the first byte */ - if(SMTP_EOB[0] == data->req.upload_fromhere[i]) - smtp->eob = 1; - else - smtp->eob = 0; + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + /* The first char we read is the first on a line, as if we had + * read CRLF just before */ + ctx->n_eob = 2; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->buf); +} - eob_sent = 0; +/* this is the 5-bytes End-Of-Body marker for SMTP */ +#define SMTP_EOB "\r\n.\r\n" +#define SMTP_EOB_FIND_LEN 3 + +/* client reader doing SMTP End-Of-Body escaping. */ +static CURLcode cr_eob_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_eob_ctx *ctx = reader->ctx; + CURLcode result = CURLE_OK; + size_t nread, i, start, n; + bool eos; + + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* Get more and convert it when needed */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + + ctx->read_eos = eos; + if(nread) { + if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) { + /* not in the middle of a match, no EOB start found, just pass */ + *pnread = nread; + *peos = FALSE; + return CURLE_OK; + } + /* scan for EOB (continuation) and convert */ + for(i = start = 0; i < nread; ++i) { + if(ctx->n_eob >= SMTP_EOB_FIND_LEN) { + /* matched the EOB prefix and seeing additional char, add '.' */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; + result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n); + if(result) + return result; + ctx->n_eob = 0; + start = i; + if(data->state.infilesize > 0) + data->state.infilesize++; + } + + if(buf[i] != SMTP_EOB[ctx->n_eob]) + ctx->n_eob = 0; + + if(buf[i] == SMTP_EOB[ctx->n_eob]) { + /* matching another char of the EOB */ + ++ctx->n_eob; + } + } - /* Reset the trailing CRLF flag as there was more data */ - smtp->trailing_crlf = FALSE; + /* add any remainder to buf */ + if(start < nread) { + result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n); + if(result) + return result; + } } - /* Do we have a match for CRLF. as per RFC-5321, sect. 4.5.2 */ - if(SMTP_EOB_FIND_LEN == smtp->eob) { - /* Copy the replacement data to the target buffer */ - memcpy(&scratch[si], &SMTP_EOB_REPL[eob_sent], - SMTP_EOB_REPL_LEN - eob_sent); - si += SMTP_EOB_REPL_LEN - eob_sent; - smtp->eob = 0; - eob_sent = 0; + if(ctx->read_eos) { + /* if we last matched a CRLF or if the data was empty, add ".\r\n" + * to end the body. If we sent something and it did not end with "\r\n", + * add "\r\n.\r\n" to end the body */ + const char *eob = SMTP_EOB; + switch(ctx->n_eob) { + case 2: + /* seen a CRLF at the end, just add the remainder */ + eob = &SMTP_EOB[2]; + break; + case 3: + /* ended with '\r\n.', we should escpe the last '.' */ + eob = "." SMTP_EOB; + break; + default: + break; + } + result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n); + if(result) + return result; } - else if(!smtp->eob) - scratch[si++] = data->req.upload_fromhere[i]; } - if(smtp->eob - eob_sent) { - /* A substring matched before processing ended so output that now */ - memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); - si += smtp->eob - eob_sent; + *peos = FALSE; + if(!Curl_bufq_is_empty(&ctx->buf)) { + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); + } + else + *pnread = 0; + + if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; } + *peos = ctx->eos; + DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d", + blen, result, *pnread, *peos)); + return result; +} - /* Only use the new buffer if we replaced something */ - if(si != nread) { - /* Upload from the new (replaced) buffer instead */ - data->req.upload_fromhere = scratch; +static curl_off_t cr_eob_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} - /* Save the buffer so it can be freed later */ - data->state.scratch = scratch; +static const struct Curl_crtype cr_eob = { + "cr-smtp-eob", + cr_eob_init, + cr_eob_read, + cr_eob_close, + Curl_creader_def_needs_rewind, + cr_eob_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_eob_ctx) +}; - /* Free the old scratch buffer */ - free(oldscratch); +static CURLcode cr_eob_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; - /* Set the new amount too */ - data->req.upload_present = si; - } - else - free(newscratch); + result = Curl_creader_create(&reader, data, &cr_eob, + CURL_CR_CONTENT_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); - return CURLE_OK; + if(result && reader) + Curl_creader_free(data, reader); + return result; } #endif /* CURL_DISABLE_SMTP */ diff --git a/Utilities/cmcurl/lib/smtp.h b/Utilities/cmcurl/lib/smtp.h index 7a04c215499..ed9824b14cc 100644 --- a/Utilities/cmcurl/lib/smtp.h +++ b/Utilities/cmcurl/lib/smtp.h @@ -27,74 +27,7 @@ #include "pingpong.h" #include "curl_sasl.h" -/**************************************************************************** - * SMTP unique setup - ***************************************************************************/ -typedef enum { - SMTP_STOP, /* do nothing state, stops the state machine */ - SMTP_SERVERGREET, /* waiting for the initial greeting immediately after - a connect */ - SMTP_EHLO, - SMTP_HELO, - SMTP_STARTTLS, - SMTP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS - (multi mode only) */ - SMTP_AUTH, - SMTP_COMMAND, /* VRFY, EXPN, NOOP, RSET and HELP */ - SMTP_MAIL, /* MAIL FROM */ - SMTP_RCPT, /* RCPT TO */ - SMTP_DATA, - SMTP_POSTDATA, - SMTP_QUIT, - SMTP_LAST /* never used */ -} smtpstate; - -/* This SMTP struct is used in the Curl_easy. All SMTP data that is - connection-oriented must be in smtp_conn to properly deal with the fact that - perhaps the Curl_easy is changed between the times the connection is - used. */ -struct SMTP { - curl_pp_transfer transfer; - char *custom; /* Custom Request */ - struct curl_slist *rcpt; /* Recipient list */ - int rcpt_last_error; /* The last error received for RCPT TO command */ - size_t eob; /* Number of bytes of the EOB (End Of Body) that - have been received so far */ - BIT(rcpt_had_ok); /* Whether any of RCPT TO commands (depends on - total number of recipients) succeeded so far */ - BIT(trailing_crlf); /* Specifies if the trailing CRLF is present */ -}; - -/* smtp_conn is used for struct connection-oriented data in the connectdata - struct */ -struct smtp_conn { - struct pingpong pp; - struct SASL sasl; /* SASL-related storage */ - smtpstate state; /* Always use smtp.c:state() to change state! */ - char *domain; /* Client address/name to send in the EHLO */ - BIT(ssldone); /* Is connect() over SSL done? */ - BIT(tls_supported); /* StartTLS capability supported by server */ - BIT(size_supported); /* If server supports SIZE extension according to - RFC 1870 */ - BIT(utf8_supported); /* If server supports SMTPUTF8 extension according - to RFC 6531 */ - BIT(auth_supported); /* AUTH capability supported by server */ -}; - extern const struct Curl_handler Curl_handler_smtp; extern const struct Curl_handler Curl_handler_smtps; -/* this is the 5-bytes End-Of-Body marker for SMTP */ -#define SMTP_EOB "\x0d\x0a\x2e\x0d\x0a" -#define SMTP_EOB_LEN 5 -#define SMTP_EOB_FIND_LEN 3 - -/* if found in data, replace it with this string instead */ -#define SMTP_EOB_REPL "\x0d\x0a\x2e\x2e" -#define SMTP_EOB_REPL_LEN 4 - -CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, - const ssize_t nread, - const ssize_t offset); - #endif /* HEADER_CURL_SMTP_H */ diff --git a/Utilities/cmcurl/lib/sockaddr.h b/Utilities/cmcurl/lib/sockaddr.h index 5a6bb207dc5..2e2d375e063 100644 --- a/Utilities/cmcurl/lib/sockaddr.h +++ b/Utilities/cmcurl/lib/sockaddr.h @@ -30,7 +30,7 @@ struct Curl_sockaddr_storage { union { struct sockaddr sa; struct sockaddr_in sa_in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 sa_in6; #endif #ifdef HAVE_STRUCT_SOCKADDR_STORAGE diff --git a/Utilities/cmcurl/lib/socketpair.c b/Utilities/cmcurl/lib/socketpair.c index 963e1406f60..1d1d5a66f30 100644 --- a/Utilities/cmcurl/lib/socketpair.c +++ b/Utilities/cmcurl/lib/socketpair.c @@ -27,16 +27,92 @@ #include "urldata.h" #include "rand.h" -#if !defined(HAVE_SOCKETPAIR) && !defined(CURL_DISABLE_SOCKETPAIR) -#ifdef WIN32 +#ifdef USE_EVENTFD + +#include + +int Curl_eventfd(curl_socket_t socks[2], bool nonblocking) +{ + int efd = eventfd(0, nonblocking ? EFD_CLOEXEC | EFD_NONBLOCK : EFD_CLOEXEC); + if(efd == -1) { + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } + socks[0] = socks[1] = efd; + return 0; +} + +#elif defined(HAVE_PIPE) + +#ifdef HAVE_FCNTL +#include +#endif + +int Curl_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 + if(pipe(socks)) + return -1; +#ifdef HAVE_FCNTL + if(fcntl(socks[0], F_SETFD, FD_CLOEXEC) || + fcntl(socks[1], F_SETFD, FD_CLOEXEC)) { + close(socks[0]); + close(socks[1]); + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } +#endif + if(nonblocking) { + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) { + close(socks[0]); + close(socks[1]); + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } + } +#endif + + return 0; +} + +#endif /* USE_EVENTFD */ + +#ifndef CURL_DISABLE_SOCKETPAIR +#ifdef HAVE_SOCKETPAIR +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2], bool nonblocking) +{ +#ifdef SOCK_NONBLOCK + type = nonblocking ? type | SOCK_NONBLOCK : type; +#endif + if(socketpair(domain, type, protocol, socks)) + return -1; +#ifndef SOCK_NONBLOCK + if(nonblocking) { + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) { + close(socks[0]); + close(socks[1]); + return -1; + } + } +#endif + return 0; +} +#else /* !HAVE_SOCKETPAIR */ +#ifdef _WIN32 /* * This is a socketpair() implementation for Windows. */ #include -#include -#include -#include +#ifdef HAVE_IO_H #include +#endif #else #ifdef HAVE_NETDB_H #include @@ -50,10 +126,10 @@ #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK 0x7f000001 #endif /* !INADDR_LOOPBACK */ -#endif /* !WIN32 */ +#endif /* !_WIN32 */ -#include "nonblock.h" /* for curlx_nonblock */ -#include "timeval.h" /* needed before select.h */ +#include "curlx/nonblock.h" /* for curlx_nonblock */ +#include "curlx/timeval.h" /* needed before select.h */ #include "select.h" /* for Curl_poll */ /* The last 3 #include files should be in this order */ @@ -62,7 +138,7 @@ #include "memdebug.h" int Curl_socketpair(int domain, int type, int protocol, - curl_socket_t socks[2]) + curl_socket_t socks[2], bool nonblocking) { union { struct sockaddr_in inaddr; @@ -87,8 +163,8 @@ int Curl_socketpair(int domain, int type, int protocol, socks[0] = socks[1] = CURL_SOCKET_BAD; -#if defined(WIN32) || defined(__CYGWIN__) - /* don't set SO_REUSEADDR on Windows */ +#if defined(_WIN32) || defined(__CYGWIN__) + /* do not set SO_REUSEADDR on Windows */ (void)reuse; #ifdef SO_EXCLUSIVEADDRUSE { @@ -116,7 +192,7 @@ int Curl_socketpair(int domain, int type, int protocol, if(connect(socks[0], &a.addr, sizeof(a.inaddr)) == -1) goto error; - /* use non-blocking accept to make sure we don't block forever */ + /* use non-blocking accept to make sure we do not block forever */ if(curlx_nonblock(listener, TRUE) < 0) goto error; pfd[0].fd = listener; @@ -127,7 +203,7 @@ int Curl_socketpair(int domain, int type, int protocol, if(socks[1] == CURL_SOCKET_BAD) goto error; else { - struct curltime start = Curl_now(); + struct curltime start = curlx_now(); char rnd[9]; char check[sizeof(rnd)]; char *p = &check[0]; @@ -150,19 +226,19 @@ int Curl_socketpair(int domain, int type, int protocol, nread = sread(socks[1], p, s); if(nread == -1) { int sockerr = SOCKERRNO; - /* Don't block forever */ - if(Curl_timediff(Curl_now(), start) > (60 * 1000)) + /* Do not block forever */ + if(curlx_timediff(curlx_now(), start) > (60 * 1000)) goto error; if( -#ifdef WSAEWOULDBLOCK +#ifdef USE_WINSOCK /* This is how Windows does it */ - (WSAEWOULDBLOCK == sockerr) + (SOCKEWOULDBLOCK == sockerr) #else /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned due to its inability to send off data without blocking. We therefore treat both error codes the same here */ - (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || - (EINTR == sockerr) || (EINPROGRESS == sockerr) + (SOCKEWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || + (SOCKEINTR == sockerr) || (SOCKEINPROGRESS == sockerr) #endif ) { continue; @@ -180,6 +256,10 @@ int Curl_socketpair(int domain, int type, int protocol, } while(1); } + if(nonblocking) + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) + goto error; sclose(listener); return 0; @@ -189,5 +269,5 @@ int Curl_socketpair(int domain, int type, int protocol, sclose(socks[1]); return -1; } - -#endif /* ! HAVE_SOCKETPAIR */ +#endif +#endif /* !CURL_DISABLE_SOCKETPAIR */ diff --git a/Utilities/cmcurl/lib/socketpair.h b/Utilities/cmcurl/lib/socketpair.h index 306ab5dc4c4..5541899445b 100644 --- a/Utilities/cmcurl/lib/socketpair.h +++ b/Utilities/cmcurl/lib/socketpair.h @@ -25,13 +25,57 @@ ***************************************************************************/ #include "curl_setup.h" -#ifndef HAVE_SOCKETPAIR + +#ifdef USE_EVENTFD + +#define wakeup_write write +#define wakeup_read read +#define wakeup_close close +#define wakeup_create(p,nb) Curl_eventfd(p,nb) + #include +int Curl_eventfd(curl_socket_t socks[2], bool nonblocking); -int Curl_socketpair(int domain, int type, int protocol, - curl_socket_t socks[2]); +#elif defined(HAVE_PIPE) + +#define wakeup_write write +#define wakeup_read read +#define wakeup_close close +#define wakeup_create(p,nb) Curl_pipe(p,nb) + +#include +int Curl_pipe(curl_socket_t socks[2], bool nonblocking); + +#else /* !USE_EVENTFD && !HAVE_PIPE */ + +#define wakeup_write swrite +#define wakeup_read sread +#define wakeup_close sclose + +#if defined(USE_UNIX_SOCKETS) && defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY AF_UNIX +#elif !defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY 0 /* not used */ #else -#define Curl_socketpair(a,b,c,d) socketpair(a,b,c,d) +#error "unsupported Unix domain and socketpair build combo" +#endif + +#ifdef SOCK_CLOEXEC +#define SOCKETPAIR_TYPE (SOCK_STREAM | SOCK_CLOEXEC) +#else +#define SOCKETPAIR_TYPE SOCK_STREAM +#endif + +#define wakeup_create(p,nb)\ +Curl_socketpair(SOCKETPAIR_FAMILY, SOCKETPAIR_TYPE, 0, p, nb) + +#endif /* USE_EVENTFD */ + +#ifndef CURL_DISABLE_SOCKETPAIR +#include + +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2], bool nonblocking); #endif #endif /* HEADER_CURL_SOCKETPAIR_H */ diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c index 53d798a64c1..08bc874f117 100644 --- a/Utilities/cmcurl/lib/socks.c +++ b/Utilities/cmcurl/lib/socks.c @@ -38,10 +38,10 @@ #include "select.h" #include "cfilters.h" #include "connect.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "socks.h" #include "multiif.h" /* for getsock macros */ -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "url.h" /* The last 3 #include files should be in this order */ @@ -71,9 +71,18 @@ enum connect_t { CONNECT_DONE /* 17 connected fine to the remote or the SOCKS proxy */ }; +#define CURL_SOCKS_BUF_SIZE 600 + +/* make sure we configure it not too low */ +#if CURL_SOCKS_BUF_SIZE < 600 +#error CURL_SOCKS_BUF_SIZE must be at least 600 +#endif + + struct socks_state { enum connect_t state; ssize_t outstanding; /* send this many bytes more */ + unsigned char buffer[CURL_SOCKS_BUF_SIZE]; unsigned char *outp; /* send from this pointer */ const char *hostname; @@ -116,7 +125,7 @@ int Curl_blockread_all(struct Curl_cfilter *cf, } nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err); if(nread <= 0) { - result = err; + result = (int)err; if(CURLE_AGAIN == err) continue; if(err) { @@ -161,7 +170,7 @@ static void socksstate(struct socks_state *sx, struct Curl_easy *data, enum connect_t oldstate = sx->state; #ifdef DEBUG_AND_VERBOSE /* synced with the state list in urldata.h */ - static const char * const statename[] = { + static const char * const socks_statename[] = { "INIT", "SOCKS_INIT", "SOCKS_SEND", @@ -185,7 +194,7 @@ static void socksstate(struct socks_state *sx, struct Curl_easy *data, (void)data; if(oldstate == state) - /* don't bother when the new state is the same as the old state */ + /* do not bother when the new state is the same as the old state */ return; sx->state = state; @@ -193,7 +202,7 @@ static void socksstate(struct socks_state *sx, struct Curl_easy *data, #ifdef DEBUG_AND_VERBOSE infof(data, "SXSTATE: %s => %s; line %d", - statename[oldstate], statename[sx->state], + socks_statename[oldstate], socks_statename[sx->state], lineno); #endif } @@ -208,7 +217,7 @@ static CURLproxycode socks_state_send(struct Curl_cfilter *cf, CURLcode result; nwritten = Curl_conn_cf_send(cf->next, data, (char *)sx->outp, - sx->outstanding, &result); + sx->outstanding, FALSE, &result); if(nwritten <= 0) { if(CURLE_AGAIN == result) { return CURLPX_OK; @@ -249,7 +258,7 @@ static CURLproxycode socks_state_recv(struct Curl_cfilter *cf, failf(data, "connection to proxy closed"); return CURLPX_CLOSED; } - failf(data, "SOCKS4: Failed receiving %s: %s", description, + failf(data, "SOCKS: Failed receiving %s: %s", description, curl_easy_strerror(result)); return failcode; } @@ -277,15 +286,12 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, { struct connectdata *conn = cf->conn; const bool protocol4a = - (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; - unsigned char *socksreq = (unsigned char *)data->state.buffer; + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A); + unsigned char *socksreq = sx->buffer; CURLcode result; CURLproxycode presult; struct Curl_dns_entry *dns = NULL; - /* make sure that the buffer is at least 600 bytes */ - DEBUGASSERT(READBUFFER_MIN >= 600); - switch(sx->state) { case CONNECT_SOCKS_INIT: /* SOCKS4 can only do IPv4, insist! */ @@ -315,50 +321,39 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* DNS resolve only for SOCKS4, not SOCKS4a */ if(!protocol4a) { - enum resolve_t rc = - Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); + result = Curl_resolv(data, sx->hostname, sx->remote_port, + cf->conn->ip_version, TRUE, &dns); - if(rc == CURLRESOLV_ERROR) - return CURLPX_RESOLVE_HOST; - else if(rc == CURLRESOLV_PENDING) { + if(result == CURLE_AGAIN) { sxstate(sx, data, CONNECT_RESOLVING); infof(data, "SOCKS4 non-blocking resolve of %s", sx->hostname); return CURLPX_OK; } + else if(result) + return CURLPX_RESOLVE_HOST; sxstate(sx, data, CONNECT_RESOLVED); goto CONNECT_RESOLVED; } - /* socks4a doesn't resolve anything locally */ + /* socks4a does not resolve anything locally */ sxstate(sx, data, CONNECT_REQ_INIT); goto CONNECT_REQ_INIT; case CONNECT_RESOLVING: /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, sx->hostname, (int)conn->port); - - if(dns) { -#ifdef CURLRES_ASYNCH - data->state.async.dns = dns; - data->state.async.done = TRUE; -#endif - infof(data, "Hostname '%s' was found", sx->hostname); - sxstate(sx, data, CONNECT_RESOLVED); - } - else { - result = Curl_resolv_check(data, &dns); - if(!dns) { - if(result) - return CURLPX_RESOLVE_HOST; - return CURLPX_OK; - } + result = Curl_resolv_check(data, &dns); + if(!dns) { + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; } - /* FALLTHROUGH */ + FALLTHROUGH(); + case CONNECT_RESOLVED: CONNECT_RESOLVED: - case CONNECT_RESOLVED: { + { struct Curl_addrinfo *hp = NULL; /* - * We cannot use 'hostent' as a struct that Curl_resolv() returns. It + * We cannot use 'hostent' as a struct that Curl_resolv() returns. It * returns a Curl_addrinfo pointer that may not always look the same. */ if(dns) { @@ -381,7 +376,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, infof(data, "SOCKS4 connect to IPv4 %s (locally resolved)", buf); - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ } else failf(data, "SOCKS4 connection to %s not supported", sx->hostname); @@ -393,17 +388,20 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, if(!hp) return CURLPX_RESOLVE_HOST; } - /* FALLTHROUGH */ -CONNECT_REQ_INIT: + FALLTHROUGH(); case CONNECT_REQ_INIT: +CONNECT_REQ_INIT: /* * This is currently not supporting "Identification Protocol (RFC1413)". */ - socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ + socksreq[8] = 0; /* ensure empty userid is null-terminated */ if(sx->proxy_user) { size_t plen = strlen(sx->proxy_user); - if(plen >= (size_t)data->set.buffer_size - 8) { - failf(data, "Too long SOCKS proxy user name, can't use"); + if(plen > 255) { + /* there is no real size limit to this field in the protocol, but + SOCKS5 limits the proxy user field to 255 bytes and it seems likely + that a longer field is either a mistake or malicious input */ + failf(data, "Too long SOCKS proxy username"); return CURLPX_LONG_USER; } /* copy the proxy name WITH trailing zero */ @@ -426,19 +424,21 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, socksreq[7] = 1; /* append hostname */ hostnamelen = strlen(sx->hostname) + 1; /* length including NUL */ - if(hostnamelen <= 255) + if((hostnamelen <= 255) && + (packetsize + hostnamelen < sizeof(sx->buffer))) strcpy((char *)socksreq + packetsize, sx->hostname); else { - failf(data, "SOCKS4: too long host name"); + failf(data, "SOCKS4: too long hostname"); return CURLPX_LONG_HOSTNAME; } packetsize += hostnamelen; } sx->outp = socksreq; + DEBUGASSERT(packetsize <= sizeof(sx->buffer)); sx->outstanding = packetsize; sxstate(sx, data, CONNECT_REQ_SENDING); } - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_SENDING: /* Send request */ presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, @@ -454,7 +454,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, sx->outp = socksreq; sxstate(sx, data, CONNECT_SOCKS_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_SOCKS_READ: /* Receive response */ presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, @@ -500,11 +500,11 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* Result */ switch(socksreq[1]) { case 90: - infof(data, "SOCKS4%s request granted.", protocol4a?"a":""); + infof(data, "SOCKS4%s request granted.", protocol4a ? "a" : ""); break; case 91: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", request rejected or failed.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), @@ -512,7 +512,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_REQUEST_FAILED; case 92: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", request rejected because SOCKS server cannot connect to " "identd on the client.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], @@ -521,7 +521,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_IDENTD; case 93: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", request rejected because the client program and identd " "report different user-ids.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], @@ -530,7 +530,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_IDENTD_DIFFER; default: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", Unknown.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), @@ -550,7 +550,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, struct Curl_easy *data) { /* - According to the RFC1928, section "6. Replies". This is what a SOCK5 + According to the RFC1928, section "6. Replies". This is what a SOCK5 replies: +----+-----+-------+------+----------+----------+ @@ -566,20 +566,18 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, o X'00' succeeded */ struct connectdata *conn = cf->conn; - unsigned char *socksreq = (unsigned char *)data->state.buffer; - char dest[256] = "unknown"; /* printable hostname:port */ - int idx; + unsigned char *socksreq = sx->buffer; + size_t idx; CURLcode result; CURLproxycode presult; bool socks5_resolve_local = - (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5); const size_t hostname_len = strlen(sx->hostname); - ssize_t len = 0; + size_t len = 0; const unsigned char auth = data->set.socks5auth; bool allow_gssapi = FALSE; struct Curl_dns_entry *dns = NULL; - DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI)); switch(sx->state) { case CONNECT_SOCKS_INIT: if(conn->bits.httpproxy) @@ -588,9 +586,9 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ if(!socks5_resolve_local && hostname_len > 255) { - infof(data, "SOCKS5: server resolving disabled for hostnames of " - "length > 255 [actual len=%zu]", hostname_len); - socks5_resolve_local = TRUE; + failf(data, "SOCKS5: the destination hostname is too long to be " + "resolved remotely by the proxy."); + return CURLPX_LONG_HOSTNAME; } if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) @@ -617,6 +615,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, socksreq[1] = (unsigned char) (idx - 2); sx->outp = socksreq; + DEBUGASSERT(idx <= sizeof(sx->buffer)); sx->outstanding = idx; presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, "initial SOCKS5 request"); @@ -637,12 +636,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* remain in sending state */ return CURLPX_OK; } - /* FALLTHROUGH */ -CONNECT_SOCKS_READ_INIT: + FALLTHROUGH(); case CONNECT_SOCKS_READ_INIT: +CONNECT_SOCKS_READ_INIT: sx->outstanding = 2; /* expect two bytes */ sx->outp = socksreq; /* store it here */ - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_SOCKS_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, "initial SOCKS5 response"); @@ -702,7 +701,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, CONNECT_AUTH_INIT: case CONNECT_AUTH_INIT: { - /* Needs user name and password */ + /* Needs username and password */ size_t proxy_user_len, proxy_password_len; if(sx->proxy_user && sx->proxy_password) { proxy_user_len = strlen(sx->proxy_user); @@ -726,7 +725,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, if(sx->proxy_user && proxy_user_len) { /* the length must fit in a single byte */ if(proxy_user_len > 255) { - failf(data, "Excessive user name length for proxy auth"); + failf(data, "Excessive username length for proxy auth"); return CURLPX_LONG_USER; } memcpy(socksreq + len, sx->proxy_user, proxy_user_len); @@ -743,10 +742,11 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } len += proxy_password_len; sxstate(sx, data, CONNECT_AUTH_SEND); + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len; sx->outp = socksreq; } - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_AUTH_SEND: presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH, "SOCKS5 sub-negotiation request"); @@ -759,7 +759,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sx->outp = socksreq; sx->outstanding = 2; sxstate(sx, data, CONNECT_AUTH_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_AUTH_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH, "SOCKS5 sub-negotiation response"); @@ -778,20 +778,19 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* Everything is good so far, user was authenticated! */ sxstate(sx, data, CONNECT_REQ_INIT); - /* FALLTHROUGH */ -CONNECT_REQ_INIT: + FALLTHROUGH(); case CONNECT_REQ_INIT: +CONNECT_REQ_INIT: if(socks5_resolve_local) { - enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, - TRUE, &dns); - - if(rc == CURLRESOLV_ERROR) - return CURLPX_RESOLVE_HOST; + result = Curl_resolv(data, sx->hostname, sx->remote_port, + cf->conn->ip_version, TRUE, &dns); - if(rc == CURLRESOLV_PENDING) { + if(result == CURLE_AGAIN) { sxstate(sx, data, CONNECT_RESOLVING); return CURLPX_OK; } + else if(result) + return CURLPX_RESOLVE_HOST; sxstate(sx, data, CONNECT_RESOLVED); goto CONNECT_RESOLVED; } @@ -799,31 +798,29 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, case CONNECT_RESOLVING: /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port); - - if(dns) { -#ifdef CURLRES_ASYNCH - data->state.async.dns = dns; - data->state.async.done = TRUE; -#endif - infof(data, "SOCKS5: hostname '%s' found", sx->hostname); - } - + result = Curl_resolv_check(data, &dns); if(!dns) { - result = Curl_resolv_check(data, &dns); - if(!dns) { - if(result) - return CURLPX_RESOLVE_HOST; - return CURLPX_OK; - } + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; } - /* FALLTHROUGH */ + FALLTHROUGH(); + case CONNECT_RESOLVED: CONNECT_RESOLVED: - case CONNECT_RESOLVED: { + { + char dest[MAX_IPADR_LEN]; /* printable address */ struct Curl_addrinfo *hp = NULL; - size_t destlen; if(dns) hp = dns->addr; +#ifdef USE_IPV6 + if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) { + int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ? + AF_INET : AF_INET6; + /* scan for the first proper address */ + while(hp && (hp->ai_family != wanted_family)) + hp = hp->ai_next; + } +#endif if(!hp) { failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", sx->hostname); @@ -831,8 +828,6 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } Curl_printable_address(hp, dest, sizeof(dest)); - destlen = strlen(dest); - msnprintf(dest + destlen, sizeof(dest) - destlen, ":%d", sx->remote_port); len = 0; socksreq[len++] = 5; /* version (SOCKS5) */ @@ -848,9 +843,10 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, socksreq[len++] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[i]; } - infof(data, "SOCKS5 connect to IPv4 %s (locally resolved)", dest); + infof(data, "SOCKS5 connect to %s:%d (locally resolved)", dest, + sx->remote_port); } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(hp->ai_family == AF_INET6) { int i; struct sockaddr_in6 *saddr_in6; @@ -862,7 +858,8 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, ((unsigned char *)&saddr_in6->sin6_addr.s6_addr)[i]; } - infof(data, "SOCKS5 connect to IPv6 %s (locally resolved)", dest); + infof(data, "SOCKS5 connect to [%s]:%d (locally resolved)", dest, + sx->remote_port); } #endif else { @@ -870,7 +867,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, failf(data, "SOCKS5 connection to %s not supported", dest); } - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ goto CONNECT_REQ_SEND; } CONNECT_RESOLVE_REMOTE: @@ -886,10 +883,10 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, IPv6 == 4, IPv4 == 1 */ unsigned char ip4[4]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(conn->bits.ipv6_ip) { char ip6[16]; - if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6)) + if(1 != curlx_inet_pton(AF_INET6, sx->hostname, ip6)) return CURLPX_BAD_ADDRESS_TYPE; socksreq[len++] = 4; memcpy(&socksreq[len], ip6, sizeof(ip6)); @@ -897,24 +894,24 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } else #endif - if(1 == Curl_inet_pton(AF_INET, sx->hostname, ip4)) { + if(1 == curlx_inet_pton(AF_INET, sx->hostname, ip4)) { socksreq[len++] = 1; memcpy(&socksreq[len], ip4, sizeof(ip4)); len += sizeof(ip4); } else { socksreq[len++] = 3; - socksreq[len++] = (char) hostname_len; /* one byte address length */ + socksreq[len++] = (unsigned char) hostname_len; /* one byte length */ memcpy(&socksreq[len], sx->hostname, hostname_len); /* w/o NULL */ len += hostname_len; } infof(data, "SOCKS5 connect to %s:%d (remotely resolved)", sx->hostname, sx->remote_port); } - /* FALLTHROUGH */ + FALLTHROUGH(); -CONNECT_REQ_SEND: case CONNECT_REQ_SEND: +CONNECT_REQ_SEND: /* PORT MSB */ socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* PORT LSB */ @@ -927,9 +924,10 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } #endif sx->outp = socksreq; + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len; sxstate(sx, data, CONNECT_REQ_SENDING); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_SENDING: presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST, "SOCKS5 connect request"); @@ -948,7 +946,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sx->outstanding = 10; /* minimum packet size is 10 */ sx->outp = socksreq; sxstate(sx, data, CONNECT_REQ_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK, "SOCKS5 connect request ack"); @@ -966,7 +964,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, else if(socksreq[1]) { /* Anything besides 0 is an error */ CURLproxycode rc = CURLPX_REPLY_UNASSIGNED; int code = socksreq[1]; - failf(data, "Can't complete SOCKS5 connection to %s. (%d)", + failf(data, "cannot complete SOCKS5 connection to %s. (%d)", sx->hostname, (unsigned char)socksreq[1]); if(code < 9) { /* RFC 1928 section 6 lists: */ @@ -1026,6 +1024,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* decrypt_gssapi_blockread already read the whole packet */ #endif if(len > 10) { + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len - 10; /* get the rest */ sx->outp = &socksreq[10]; sxstate(sx, data, CONNECT_REQ_READ_MORE); @@ -1037,7 +1036,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) } #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_READ_MORE: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS, "SOCKS5 connect request address"); @@ -1095,7 +1094,7 @@ static void socks_proxy_cf_free(struct Curl_cfilter *cf) } /* After a TCP connection to the proxy has been verified, this function does - the next magic steps. If 'done' isn't set TRUE, it is not done yet and + the next magic steps. If 'done' is not set TRUE, it is not done yet and must be called again. Note: this function's sub-functions call failf() @@ -1103,7 +1102,7 @@ static void socks_proxy_cf_free(struct Curl_cfilter *cf) */ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { CURLcode result; struct connectdata *conn = cf->conn; @@ -1115,12 +1114,12 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } - result = cf->next->cft->connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; if(!sx) { - sx = calloc(sizeof(*sx), 1); + sx = calloc(1, sizeof(*sx)); if(!sx) return CURLE_OUT_OF_MEMORY; cf->ctx = sx; @@ -1150,7 +1149,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, result = connect_SOCKS(cf, sx, data); if(!result && sx->state == CONNECT_DONE) { cf->connected = TRUE; - Curl_verboseconnect(data, conn); + Curl_verboseconnect(data, conn, cf->sockindex); socks_proxy_cf_free(cf); } @@ -1158,32 +1157,29 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, return result; } -static int socks_cf_get_select_socks(struct Curl_cfilter *cf, +static void socks_cf_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { struct socks_state *sx = cf->ctx; - int fds; - fds = cf->next->cft->get_select_socks(cf->next, data, socks); - if(!fds && cf->next->connected && !cf->connected && sx) { + if(!cf->connected && sx) { /* If we are not connected, the filter below is and has nothing * to wait on, we determine what to wait for. */ - socks[0] = Curl_conn_cf_get_socket(cf, data); + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); switch(sx->state) { case CONNECT_RESOLVING: case CONNECT_SOCKS_READ: case CONNECT_AUTH_READ: case CONNECT_REQ_READ: case CONNECT_REQ_READ_MORE: - fds = GETSOCK_READSOCK(0); + Curl_pollset_set_in_only(data, ps, sock); break; default: - fds = GETSOCK_WRITESOCK(0); + Curl_pollset_set_out_only(data, ps, sock); break; } } - return fds; } static void socks_proxy_cf_close(struct Curl_cfilter *cf, @@ -1193,7 +1189,7 @@ static void socks_proxy_cf_close(struct Curl_cfilter *cf, DEBUGASSERT(cf->next); cf->connected = FALSE; socks_proxy_cf_free(cf); - cf->next->cft->close(cf->next, data); + cf->next->cft->do_close(cf->next, data); } static void socks_proxy_cf_destroy(struct Curl_cfilter *cf, @@ -1222,13 +1218,14 @@ static void socks_cf_get_host(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_socks_proxy = { "SOCKS-PROXYY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, socks_proxy_cf_destroy, socks_proxy_cf_connect, socks_proxy_cf_close, + Curl_cf_def_shutdown, socks_cf_get_host, - socks_cf_get_select_socks, + socks_cf_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c index 2ede8c7c29a..697e301b772 100644 --- a/Utilities/cmcurl/lib/socks_gssapi.c +++ b/Utilities/cmcurl/lib/socks_gssapi.c @@ -32,15 +32,23 @@ #include "sendf.h" #include "cfilters.h" #include "connect.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "socks.h" -#include "warnless.h" +#include "curlx/warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#define MAX_GSS_LEN 1024 + static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; /* @@ -55,10 +63,9 @@ static int check_gss_err(struct Curl_easy *data, OM_uint32 maj_stat, min_stat; OM_uint32 msg_ctx = 0; gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; - char buf[1024]; - size_t len; + struct dynbuf dbuf; - len = 0; + curlx_dyn_init(&dbuf, MAX_GSS_LEN); msg_ctx = 0; while(!msg_ctx) { /* convert major status code (GSS-API error) to text */ @@ -67,19 +74,16 @@ static int check_gss_err(struct Curl_easy *data, GSS_C_NULL_OID, &msg_ctx, &status_string); if(maj_stat == GSS_S_COMPLETE) { - if(sizeof(buf) > len + status_string.length + 1) { - strcpy(buf + len, (char *) status_string.value); - len += status_string.length; - } + if(curlx_dyn_addn(&dbuf, status_string.value, + status_string.length)) + return 1; /* error */ gss_release_buffer(&min_stat, &status_string); break; } gss_release_buffer(&min_stat, &status_string); } - if(sizeof(buf) > len + 3) { - strcpy(buf + len, ".\n"); - len += 2; - } + if(curlx_dyn_addn(&dbuf, ".\n", 2)) + return 1; /* error */ msg_ctx = 0; while(!msg_ctx) { /* convert minor status code (underlying routine error) to text */ @@ -88,14 +92,17 @@ static int check_gss_err(struct Curl_easy *data, GSS_C_NULL_OID, &msg_ctx, &status_string); if(maj_stat == GSS_S_COMPLETE) { - if(sizeof(buf) > len + status_string.length) - strcpy(buf + len, (char *) status_string.value); + if(curlx_dyn_addn(&dbuf, status_string.value, + status_string.length)) + return 1; /* error */ gss_release_buffer(&min_stat, &status_string); break; } gss_release_buffer(&min_stat, &status_string); } - failf(data, "GSS-API error: %s failed: %s", function, buf); + failf(data, "GSS-API error: %s failed: %s", function, + curlx_dyn_ptr(&dbuf)); + curlx_dyn_free(&dbuf); return 1; } @@ -139,10 +146,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, /* prepare service name */ if(strchr(serviceptr, '/')) { service.length = serviceptr_length; - service.value = malloc(service.length); + service.value = Curl_memdup(serviceptr, service.length); if(!service.value) return CURLE_OUT_OF_MEMORY; - memcpy(service.value, serviceptr, service.length); gss_major_status = gss_import_name(&gss_minor_status, &service, (gss_OID) GSS_C_NULL_OID, &server); @@ -172,7 +178,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, (void)curlx_nonblock(sock, FALSE); - /* As long as we need to keep sending some context info, and there's no */ + /* As long as we need to keep sending some context info, and there is no */ /* errors, keep sending it... */ for(;;) { gss_major_status = Curl_gss_init_sec_context(data, @@ -201,10 +207,11 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(gss_send_token.length) { socksreq[0] = 1; /* GSS-API subnegotiation version */ socksreq[1] = 1; /* authentication message type */ - us_length = htons((short)gss_send_token.length); + us_length = htons((unsigned short)gss_send_token.length); memcpy(socksreq + 2, &us_length, sizeof(short)); - nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, + FALSE, &code); if(code || (4 != nwritten)) { failf(data, "Failed to send GSS-API authentication request."); gss_release_name(&gss_status, &server); @@ -216,7 +223,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, nwritten = Curl_conn_cf_send(cf->next, data, (char *)gss_send_token.value, - gss_send_token.length, &code); + gss_send_token.length, FALSE, &code); if(code || ((ssize_t)gss_send_token.length != nwritten)) { failf(data, "Failed to send GSS-API authentication token."); gss_release_name(&gss_status, &server); @@ -306,7 +313,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_minor_status, "gss_inquire_context")) { gss_delete_sec_context(&gss_status, &gss_context, NULL); gss_release_name(&gss_status, &gss_client_name); - failf(data, "Failed to determine user name."); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } gss_major_status = gss_display_name(&gss_minor_status, gss_client_name, @@ -316,7 +323,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_delete_sec_context(&gss_status, &gss_context, NULL); gss_release_name(&gss_status, &gss_client_name); gss_release_buffer(&gss_status, &gss_send_token); - failf(data, "Failed to determine user name."); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } user = malloc(gss_send_token.length + 1); @@ -348,7 +355,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_enc = 1; infof(data, "SOCKS5 server supports GSS-API %s data protection.", - (gss_enc == 0)?"no":((gss_enc==1)?"integrity":"confidentiality")); + (gss_enc == 0) ? "no" : + ((gss_enc == 1) ? "integrity" : "confidentiality")); /* force for the moment to no data protection */ gss_enc = 0; /* @@ -377,7 +385,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, * * The token is produced by encapsulating an octet containing the * required protection level using gss_seal()/gss_wrap() with conf_req - * set to FALSE. The token is verified using gss_unseal()/ + * set to FALSE. The token is verified using gss_unseal()/ * gss_unwrap(). * */ @@ -387,12 +395,11 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } else { gss_send_token.length = 1; - gss_send_token.value = malloc(1); + gss_send_token.value = Curl_memdup(&gss_enc, 1); if(!gss_send_token.value) { gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_OUT_OF_MEMORY; } - memcpy(gss_send_token.value, &gss_enc, 1); gss_major_status = gss_wrap(&gss_minor_status, gss_context, 0, GSS_C_QOP_DEFAULT, &gss_send_token, @@ -407,11 +414,12 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } gss_release_buffer(&gss_status, &gss_send_token); - us_length = htons((short)gss_w_token.length); + us_length = htons((unsigned short)gss_w_token.length); memcpy(socksreq + 2, &us_length, sizeof(short)); } - nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &code); if(code || (4 != nwritten)) { failf(data, "Failed to send GSS-API encryption request."); gss_release_buffer(&gss_status, &gss_w_token); @@ -421,7 +429,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, FALSE, + &code); if(code || ( 1 != nwritten)) { failf(data, "Failed to send GSS-API encryption type."); gss_delete_sec_context(&gss_status, &gss_context, NULL); @@ -431,7 +440,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, else { nwritten = Curl_conn_cf_send(cf->next, data, (char *)gss_w_token.value, - gss_w_token.length, &code); + gss_w_token.length, FALSE, &code); if(code || ((ssize_t)gss_w_token.length != nwritten)) { failf(data, "Failed to send GSS-API encryption type."); gss_release_buffer(&gss_status, &gss_w_token); @@ -476,7 +485,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_recv_token.length, &actualread); if(result || (actualread != us_length)) { - failf(data, "Failed to receive GSS-API encryptrion type."); + failf(data, "Failed to receive GSS-API encryption type."); gss_release_buffer(&gss_status, &gss_recv_token); gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_COULDNT_CONNECT; @@ -523,8 +532,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, (void)curlx_nonblock(sock, TRUE); infof(data, "SOCKS5 access with%s protection granted.", - (socksreq[0] == 0)?"out GSS-API data": - ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); + (socksreq[0] == 0) ? "out GSS-API data": + ((socksreq[0] == 1) ? " GSS-API integrity" : + " GSS-API confidentiality")); conn->socks5_gssapi_enctype = socksreq[0]; if(socksreq[0] == 0) @@ -533,4 +543,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, return CURLE_OK; } +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + #endif /* HAVE_GSSAPI && !CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c index d1200ea037b..1f0846dda46 100644 --- a/Utilities/cmcurl/lib/socks_sspi.c +++ b/Utilities/cmcurl/lib/socks_sspi.c @@ -32,11 +32,11 @@ #include "cfilters.h" #include "connect.h" #include "strerror.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "socks.h" #include "curl_sspi.h" -#include "curl_multibyte.h" -#include "warnless.h" +#include "curlx/multibyte.h" +#include "curlx/warnless.h" #include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -139,26 +139,26 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, cred_handle.dwLower = 0; cred_handle.dwUpper = 0; - status = s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT("Kerberos"), - SECPKG_CRED_OUTBOUND, - NULL, - NULL, - NULL, - NULL, - &cred_handle, - &expiry); + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(TEXT("Kerberos")), + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &cred_handle, + &expiry); if(check_sspi_err(data, status, "AcquireCredentialsHandle")) { failf(data, "Failed to acquire credentials."); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); return CURLE_COULDNT_CONNECT; } (void)curlx_nonblock(sock, FALSE); - /* As long as we need to keep sending some context info, and there's no */ + /* As long as we need to keep sending some context info, and there is no */ /* errors, keep sending it... */ for(;;) { TCHAR *sname; @@ -167,7 +167,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(!sname) return CURLE_OUT_OF_MEMORY; - status = s_pSecFn->InitializeSecurityContext(&cred_handle, + status = Curl_pSecFn->InitializeSecurityContext(&cred_handle, context_handle, sname, ISC_REQ_MUTUAL_AUTH | @@ -186,17 +186,17 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, curlx_unicodefree(sname); if(sspi_recv_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); sspi_recv_token.pvBuffer = NULL; sspi_recv_token.cbBuffer = 0; } if(check_sspi_err(data, status, "InitializeSecurityContext")) { free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); failf(data, "Failed to initialise security context."); return CURLE_COULDNT_CONNECT; } @@ -204,47 +204,48 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(sspi_send_token.cbBuffer) { socksreq[0] = 1; /* GSS-API subnegotiation version */ socksreq[1] = 1; /* authentication message type */ - us_length = htons((short)sspi_send_token.cbBuffer); + us_length = htons((unsigned short)sspi_send_token.cbBuffer); memcpy(socksreq + 2, &us_length, sizeof(short)); - written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &code); if(code || (4 != written)) { failf(data, "Failed to send SSPI authentication request."); free(service_name); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } written = Curl_conn_cf_send(cf->next, data, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &code); + sspi_send_token.cbBuffer, FALSE, &code); if(code || (sspi_send_token.cbBuffer != (size_t)written)) { failf(data, "Failed to send SSPI authentication token."); free(service_name); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } } if(sspi_send_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); sspi_send_token.pvBuffer = NULL; } sspi_send_token.cbBuffer = 0; if(sspi_recv_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); sspi_recv_token.pvBuffer = NULL; } sspi_recv_token.cbBuffer = 0; @@ -266,8 +267,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI authentication response."); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -276,8 +277,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "User was rejected by the SOCKS5 server (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -285,8 +286,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Invalid SSPI authentication response type (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -298,8 +299,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(!sspi_recv_token.pvBuffer) { free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } result = Curl_blockread_all(cf, data, (char *)sspi_recv_token.pvBuffer, @@ -309,9 +310,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Failed to receive SSPI authentication token."); free(service_name); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -321,19 +322,25 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, free(service_name); /* Everything is good so far, user was authenticated! */ - status = s_pSecFn->QueryCredentialsAttributes(&cred_handle, + status = Curl_pSecFn->QueryCredentialsAttributes(&cred_handle, SECPKG_CRED_ATTR_NAMES, &names); - s_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); if(check_sspi_err(data, status, "QueryCredentialAttributes")) { - s_pSecFn->DeleteSecurityContext(&sspi_context); - s_pSecFn->FreeContextBuffer(names.sUserName); - failf(data, "Failed to determine user name."); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(names.sUserName); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } - infof(data, "SOCKS5 server authenticated user %s with GSS-API.", - names.sUserName); - s_pSecFn->FreeContextBuffer(names.sUserName); + else { +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char *user_utf8 = curlx_convert_tchar_to_UTF8(names.sUserName); + infof(data, "SOCKS5 server authenticated user %s with GSS-API.", + (user_utf8 ? user_utf8 : "(unknown)")); + curlx_unicodefree(user_utf8); +#endif + Curl_pSecFn->FreeContextBuffer(names.sUserName); + } /* Do encryption */ socksreq[0] = 1; /* GSS-API subnegotiation version */ @@ -348,7 +355,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_enc = 1; infof(data, "SOCKS5 server supports GSS-API %s data protection.", - (gss_enc == 0)?"no":((gss_enc == 1)?"integrity":"confidentiality") ); + (gss_enc == 0) ? "no" : + ((gss_enc == 1) ? "integrity":"confidentiality") ); /* force to no data protection, avoid encryption/decryption for now */ gss_enc = 0; /* @@ -377,21 +385,21 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, * * The token is produced by encapsulating an octet containing the * required protection level using gss_seal()/gss_wrap() with conf_req - * set to FALSE. The token is verified using gss_unseal()/ + * set to FALSE. The token is verified using gss_unseal()/ * gss_unwrap(). * */ if(data->set.socks5_gssapi_nec) { - us_length = htons((short)1); + us_length = htons((unsigned short)1); memcpy(socksreq + 2, &us_length, sizeof(short)); } else { - status = s_pSecFn->QueryContextAttributes(&sspi_context, + status = Curl_pSecFn->QueryContextAttributes(&sspi_context, SECPKG_ATTR_SIZES, &sspi_sizes); if(check_sspi_err(data, status, "QueryContextAttributes")) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -401,15 +409,15 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[0].pvBuffer = malloc(sspi_sizes.cbSecurityTrailer); if(!sspi_w_token[0].pvBuffer) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } sspi_w_token[1].cbBuffer = 1; sspi_w_token[1].pvBuffer = malloc(1); if(!sspi_w_token[1].pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -418,20 +426,20 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[2].cbBuffer = sspi_sizes.cbBlockSize; sspi_w_token[2].pvBuffer = malloc(sspi_sizes.cbBlockSize); if(!sspi_w_token[2].pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } - status = s_pSecFn->EncryptMessage(&sspi_context, + status = Curl_pSecFn->EncryptMessage(&sspi_context, KERB_WRAP_NO_ENCRYPT, &wrap_desc, 0); if(check_sspi_err(data, status, "EncryptMessage")) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -440,10 +448,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + sspi_w_token[2].cbBuffer; sspi_send_token.pvBuffer = malloc(sspi_send_token.cbBuffer); if(!sspi_send_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -456,57 +464,59 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + sspi_w_token[1].cbBuffer, sspi_w_token[2].pvBuffer, sspi_w_token[2].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); sspi_w_token[0].pvBuffer = NULL; sspi_w_token[0].cbBuffer = 0; - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); sspi_w_token[1].pvBuffer = NULL; sspi_w_token[1].cbBuffer = 0; - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); sspi_w_token[2].pvBuffer = NULL; sspi_w_token[2].cbBuffer = 0; - us_length = htons((short)sspi_send_token.cbBuffer); + us_length = htons((unsigned short)sspi_send_token.cbBuffer); memcpy(socksreq + 2, &us_length, sizeof(short)); } - written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &code); if(code || (4 != written)) { failf(data, "Failed to send SSPI encryption request."); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, FALSE, + &code); if(code || (1 != written)) { failf(data, "Failed to send SSPI encryption type."); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } } else { written = Curl_conn_cf_send(cf->next, data, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &code); + sspi_send_token.cbBuffer, FALSE, &code); if(code || (sspi_send_token.cbBuffer != (size_t)written)) { failf(data, "Failed to send SSPI encryption type."); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); } result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI encryption response."); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -514,14 +524,14 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(socksreq[1] == 255) { /* status / message type */ failf(data, "User was rejected by the SOCKS5 server (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(socksreq[1] != 2) { /* status / message type */ failf(data, "Invalid SSPI encryption response type (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -531,7 +541,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[0].cbBuffer = us_length; sspi_w_token[0].pvBuffer = malloc(us_length); if(!sspi_w_token[0].pvBuffer) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -540,8 +550,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(result || (actualread != us_length)) { failf(data, "Failed to receive SSPI encryption type."); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -553,17 +563,17 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[1].cbBuffer = 0; sspi_w_token[1].pvBuffer = NULL; - status = s_pSecFn->DecryptMessage(&sspi_context, + status = Curl_pSecFn->DecryptMessage(&sspi_context, &wrap_desc, 0, &qop); if(check_sspi_err(data, status, "DecryptMessage")) { if(sspi_w_token[0].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); if(sspi_w_token[1].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -572,40 +582,41 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Invalid SSPI encryption response length (%lu).", (unsigned long)sspi_w_token[1].cbBuffer); if(sspi_w_token[0].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); if(sspi_w_token[1].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } memcpy(socksreq, sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); } else { if(sspi_w_token[0].cbBuffer != 1) { failf(data, "Invalid SSPI encryption response length (%lu).", (unsigned long)sspi_w_token[0].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); } (void)curlx_nonblock(sock, TRUE); infof(data, "SOCKS5 access with%s protection granted.", - (socksreq[0] == 0)?"out GSS-API data": - ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); + (socksreq[0] == 0) ? "out GSS-API data": + ((socksreq[0] == 1) ? " GSS-API integrity" : + " GSS-API confidentiality")); /* For later use if encryption is required conn->socks5_gssapi_enctype = socksreq[0]; if(socksreq[0] != 0) conn->socks5_sspi_context = sspi_context; else { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); conn->socks5_sspi_context = sspi_context; } */ diff --git a/Utilities/cmcurl/lib/speedcheck.c b/Utilities/cmcurl/lib/speedcheck.c index 580efbde750..16d7d72562c 100644 --- a/Utilities/cmcurl/lib/speedcheck.c +++ b/Utilities/cmcurl/lib/speedcheck.c @@ -52,7 +52,7 @@ CURLcode Curl_speedcheck(struct Curl_easy *data, data->state.keeps_speed = now; else { /* how long has it been under the limit */ - timediff_t howlong = Curl_timediff(now, data->state.keeps_speed); + timediff_t howlong = curlx_timediff(now, data->state.keeps_speed); if(howlong >= data->set.low_speed_time * 1000) { /* too long */ diff --git a/Utilities/cmcurl/lib/speedcheck.h b/Utilities/cmcurl/lib/speedcheck.h index bff2f32b774..f54365cadfa 100644 --- a/Utilities/cmcurl/lib/speedcheck.h +++ b/Utilities/cmcurl/lib/speedcheck.h @@ -26,8 +26,8 @@ #include "curl_setup.h" -#include "timeval.h" - +#include "curlx/timeval.h" +struct Curl_easy; void Curl_speedinit(struct Curl_easy *data); CURLcode Curl_speedcheck(struct Curl_easy *data, struct curltime now); diff --git a/Utilities/cmcurl/lib/splay.c b/Utilities/cmcurl/lib/splay.c index 48e079b32aa..7a0e419ce68 100644 --- a/Utilities/cmcurl/lib/splay.c +++ b/Utilities/cmcurl/lib/splay.c @@ -24,6 +24,7 @@ #include "curl_setup.h" +#include "curlx/timeval.h" #include "splay.h" /* @@ -33,7 +34,7 @@ * zero : when i is equal to j * positive when : when i is larger than j */ -#define compare(i,j) Curl_splaycomparekeys((i),(j)) +#define compare(i,j) curlx_timediff_us(i,j) /* * Splay using the key i (which may or may not be in the tree.) The starting @@ -45,12 +46,12 @@ struct Curl_tree *Curl_splay(struct curltime i, struct Curl_tree N, *l, *r, *y; if(!t) - return t; + return NULL; N.smaller = N.larger = NULL; l = r = &N; for(;;) { - long comp = compare(i, t->key); + timediff_t comp = compare(i, t->key); if(comp < 0) { if(!t->smaller) break; @@ -93,7 +94,7 @@ struct Curl_tree *Curl_splay(struct curltime i, return t; } -/* Insert key i into the tree t. Return a pointer to the resulting tree or +/* Insert key i into the tree t. Return a pointer to the resulting tree or * NULL if something went wrong. * * @unittest: 1309 @@ -106,15 +107,15 @@ struct Curl_tree *Curl_splayinsert(struct curltime i, ~0, -1 }; /* will *NEVER* appear */ - if(!node) - return t; + DEBUGASSERT(node); if(t) { t = Curl_splay(i, t); + DEBUGASSERT(t); if(compare(i, t->key) == 0) { - /* There already exists a node in the tree with the very same key. Build - a doubly-linked circular list of nodes. We add the new 'node' struct - to the end of this list. */ + /* There already exists a node in the tree with the same key. Build a + doubly-linked circular list of nodes. We add the new 'node' struct to + the end of this list. */ node->key = KEY_NOTUSED; /* we set the key in the sub node to NOTUSED to quickly identify this node as a subnode */ @@ -150,7 +151,7 @@ struct Curl_tree *Curl_splayinsert(struct curltime i, } /* 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 + resulting tree. best-fit means the smallest node if it is not larger than the key */ struct Curl_tree *Curl_splaygetbest(struct curltime i, struct Curl_tree *t, @@ -166,6 +167,7 @@ struct Curl_tree *Curl_splaygetbest(struct curltime i, /* find smallest */ t = Curl_splay(tv_zero, t); + DEBUGASSERT(t); if(compare(i, t->key) < 0) { /* even the smallest is too big */ *removed = NULL; @@ -197,13 +199,13 @@ struct Curl_tree *Curl_splaygetbest(struct curltime i, } -/* Deletes the very node we point out from the tree if it's there. Stores a +/* Deletes the node we point out from the tree if it is there. Stores a * pointer to the new resulting tree in 'newroot'. * * Returns zero on success and non-zero on errors! * When returning error, it does not touch the 'newroot' pointer. * - * NOTE: when the last node of the tree is removed, there's no tree left so + * 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 @@ -217,9 +219,11 @@ int Curl_splayremove(struct Curl_tree *t, }; /* will *NEVER* appear */ struct Curl_tree *x; - if(!t || !removenode) + if(!t) return 1; + DEBUGASSERT(removenode); + if(compare(KEY_NOTUSED, removenode->key) == 0) { /* Key set to NOTUSED means it is a subnode within a 'same' linked list and thus we can unlink it easily. */ @@ -238,10 +242,11 @@ int Curl_splayremove(struct Curl_tree *t, } t = Curl_splay(removenode->key, t); + DEBUGASSERT(t); /* First make sure that we got the same root node as the one we want to remove, as otherwise we might be trying to remove a node that - isn't actually in the tree. + is not actually in the tree. We cannot just compare the keys here as a double remove in quick succession of a node with key != KEY_NOTUSED && same != NULL @@ -249,7 +254,7 @@ int Curl_splayremove(struct Curl_tree *t, if(t != removenode) return 2; - /* Check if there is a list with identical sizes, as then we're trying to + /* Check if there is a list with identical sizes, as then we are trying to remove the root node of a list of nodes with identical keys. */ x = t->samen; if(x != t) { @@ -268,6 +273,7 @@ int Curl_splayremove(struct Curl_tree *t, x = t->larger; else { x = Curl_splay(removenode->key, t->smaller); + DEBUGASSERT(x); x->larger = t->larger; } } @@ -276,3 +282,16 @@ int Curl_splayremove(struct Curl_tree *t, return 0; } + +/* set and get the custom payload for this tree node */ +void Curl_splayset(struct Curl_tree *node, void *payload) +{ + DEBUGASSERT(node); + node->ptr = payload; +} + +void *Curl_splayget(struct Curl_tree *node) +{ + DEBUGASSERT(node); + return node->ptr; +} diff --git a/Utilities/cmcurl/lib/splay.h b/Utilities/cmcurl/lib/splay.h index dd1d07ac2e0..ccc3781ec16 100644 --- a/Utilities/cmcurl/lib/splay.h +++ b/Utilities/cmcurl/lib/splay.h @@ -24,15 +24,16 @@ * ***************************************************************************/ #include "curl_setup.h" -#include "timeval.h" +#include "curlx/timeval.h" +/* only use function calls to access this struct */ struct Curl_tree { struct Curl_tree *smaller; /* smaller node */ struct Curl_tree *larger; /* larger node */ struct Curl_tree *samen; /* points to the next node with identical key */ struct Curl_tree *samep; /* points to the prev node with identical key */ - struct curltime key; /* this node's "sort" key */ - void *payload; /* data the splay code doesn't care about */ + struct curltime key; /* this node's "sort" key */ + void *ptr; /* data the splay code does not care about */ }; struct Curl_tree *Curl_splay(struct curltime i, @@ -50,9 +51,8 @@ int Curl_splayremove(struct Curl_tree *t, struct Curl_tree *removenode, struct Curl_tree **newroot); -#define Curl_splaycomparekeys(i,j) ( ((i.tv_sec) < (j.tv_sec)) ? -1 : \ - ( ((i.tv_sec) > (j.tv_sec)) ? 1 : \ - ( ((i.tv_usec) < (j.tv_usec)) ? -1 : \ - ( ((i.tv_usec) > (j.tv_usec)) ? 1 : 0)))) +/* set and get the custom payload for this tree node */ +void Curl_splayset(struct Curl_tree *node, void *payload); +void *Curl_splayget(struct Curl_tree *node); #endif /* HEADER_CURL_SPLAY_H */ diff --git a/Utilities/cmcurl/lib/strcase.c b/Utilities/cmcurl/lib/strcase.c index 7c0b4ef9095..ee5b9072c4c 100644 --- a/Utilities/cmcurl/lib/strcase.c +++ b/Utilities/cmcurl/lib/strcase.c @@ -71,7 +71,7 @@ static const unsigned char tolowermap[256] = { altered by the current locale. */ char Curl_raw_toupper(char in) { - return touppermap[(unsigned char) in]; + return (char)touppermap[(unsigned char) in]; } @@ -79,72 +79,13 @@ char Curl_raw_toupper(char in) altered by the current locale. */ char Curl_raw_tolower(char in) { - return tolowermap[(unsigned char) in]; + return (char)tolowermap[(unsigned char) in]; } -/* - * curl_strequal() is for doing "raw" case insensitive strings. This is meant - * to be locale independent and only compare strings we know are safe for - * this. See https://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for - * further explanations as to why this function is necessary. - */ - -static int casecompare(const char *first, const char *second) -{ - while(*first && *second) { - if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) - /* get out of the loop as soon as they don't match */ - return 0; - first++; - second++; - } - /* If we're here either the strings are the same or the length is different. - We can just test if the "current" character is non-zero for one and zero - for the other. Note that the characters may not be exactly the same even - if they match, we only want to compare zero-ness. */ - return !*first == !*second; -} - -/* --- public function --- */ -int curl_strequal(const char *first, const char *second) -{ - if(first && second) - /* both pointers point to something then compare them */ - return casecompare(first, second); - - /* if both pointers are NULL then treat them as equal */ - return (NULL == first && NULL == second); -} - -static int ncasecompare(const char *first, const char *second, size_t max) -{ - while(*first && *second && max) { - if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) - return 0; - max--; - first++; - second++; - } - if(0 == max) - return 1; /* they are equal this far */ - - return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); -} - -/* --- public function --- */ -int curl_strnequal(const char *first, const char *second, size_t max) -{ - if(first && second) - /* both pointers point to something then compare them */ - return ncasecompare(first, second, max); - - /* if both pointers are NULL then treat them as equal if max is non-zero */ - return (NULL == first && NULL == second && max); -} -/* Copy an upper case version of the string from src to dest. The - * strings may overlap. No more than n characters of the string are copied +/* Copy an upper case version of the string from src to dest. The + * strings may overlap. No more than n characters of the string are copied * (including any NUL) and the destination string will NOT be - * NUL-terminated if that limit is reached. + * null-terminated if that limit is reached. */ void Curl_strntoupper(char *dest, const char *src, size_t n) { @@ -156,10 +97,10 @@ void Curl_strntoupper(char *dest, const char *src, size_t n) } while(*src++ && --n); } -/* Copy a lower case version of the string from src to dest. The - * strings may overlap. No more than n characters of the string are copied +/* Copy a lower case version of the string from src to dest. The + * strings may overlap. No more than n characters of the string are copied * (including any NUL) and the destination string will NOT be - * NUL-terminated if that limit is reached. + * null-terminated if that limit is reached. */ void Curl_strntolower(char *dest, const char *src, size_t n) { @@ -171,7 +112,7 @@ void Curl_strntolower(char *dest, const char *src, size_t n) } while(*src++ && --n); } -/* Compare case-sensitive NUL-terminated strings, taking care of possible +/* Compare case-sensitive null-terminated strings, taking care of possible * null pointers. Return true if arguments match. */ bool Curl_safecmp(char *a, char *b) diff --git a/Utilities/cmcurl/lib/strdup.c b/Utilities/cmcurl/lib/strdup.c index 07a61391ae5..69c41a5e252 100644 --- a/Utilities/cmcurl/lib/strdup.c +++ b/Utilities/cmcurl/lib/strdup.c @@ -26,7 +26,7 @@ #include -#ifdef WIN32 +#ifdef _WIN32 #include #endif @@ -56,7 +56,7 @@ char *Curl_strdup(const char *str) } #endif -#ifdef WIN32 +#ifdef _WIN32 /*************************************************************************** * * Curl_wcsdup(source) @@ -99,6 +99,29 @@ void *Curl_memdup(const void *src, size_t length) return buffer; } +/*************************************************************************** + * + * Curl_memdup0(source, length) + * + * Copies the 'source' string to a newly allocated buffer (that is returned). + * Copies 'length' bytes then adds a null-terminator. + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +void *Curl_memdup0(const char *src, size_t length) +{ + char *buf = malloc(length + 1); + if(!buf) + return NULL; + if(length) { + DEBUGASSERT(src); /* must never be NULL */ + memcpy(buf, src, length); + } + buf[length] = 0; + return buf; +} + /*************************************************************************** * * Curl_saferealloc(ptr, size) diff --git a/Utilities/cmcurl/lib/strdup.h b/Utilities/cmcurl/lib/strdup.h index c3430b54d56..238a2611f64 100644 --- a/Utilities/cmcurl/lib/strdup.h +++ b/Utilities/cmcurl/lib/strdup.h @@ -28,10 +28,11 @@ #ifndef HAVE_STRDUP char *Curl_strdup(const char *str); #endif -#ifdef WIN32 +#ifdef _WIN32 wchar_t* Curl_wcsdup(const wchar_t* src); #endif void *Curl_memdup(const void *src, size_t buffer_length); void *Curl_saferealloc(void *ptr, size_t size); +void *Curl_memdup0(const char *src, size_t length); #endif /* HEADER_CURL_STRDUP_H */ diff --git a/Utilities/cmcurl/lib/strequal.c b/Utilities/cmcurl/lib/strequal.c new file mode 100644 index 00000000000..d1ba91501bb --- /dev/null +++ b/Utilities/cmcurl/lib/strequal.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 "curl_setup.h" + +#include +#include "strcase.h" + +/* + * curl_strequal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See https://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * further explanations as to why this function is necessary. + */ + +static int casecompare(const char *first, const char *second) +{ + while(*first) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + /* get out of the loop as soon as they do not match */ + return 0; + first++; + second++; + } + /* If we are here either the strings are the same or the length is different. + We can just test if the "current" character is non-zero for one and zero + for the other. Note that the characters may not be exactly the same even + if they match, we only want to compare zero-ness. */ + return !*first == !*second; +} + +static int ncasecompare(const char *first, const char *second, size_t max) +{ + while(*first && max) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + return 0; + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); +} + +/* --- public function --- */ +int curl_strequal(const char *first, const char *second) +{ + if(first && second) + /* both pointers point to something then compare them */ + return casecompare(first, second); + + /* if both pointers are NULL then treat them as equal */ + return NULL == first && NULL == second; +} + +/* --- public function --- */ +int curl_strnequal(const char *first, const char *second, size_t max) +{ + if(first && second) + /* both pointers point to something then compare them */ + return ncasecompare(first, second, max); + + /* if both pointers are NULL then treat them as equal if max is non-zero */ + return NULL == first && NULL == second && max; +} diff --git a/Utilities/cmcurl/lib/strerror.c b/Utilities/cmcurl/lib/strerror.c index bd9cc535c37..9cf93dd66e9 100644 --- a/Utilities/cmcurl/lib/strerror.c +++ b/Utilities/cmcurl/lib/strerror.c @@ -42,16 +42,13 @@ #include "curl_sspi.h" #endif +#include "curlx/winapi.h" #include "strerror.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#if defined(WIN32) || defined(_WIN32_WCE) -#define PRESERVE_WINDOWS_ERROR_CODE -#endif - const char * curl_easy_strerror(CURLcode error) { @@ -74,13 +71,13 @@ curl_easy_strerror(CURLcode error) " this libcurl due to a build-time decision."; case CURLE_COULDNT_RESOLVE_PROXY: - return "Couldn't resolve proxy name"; + return "Could not resolve proxy name"; case CURLE_COULDNT_RESOLVE_HOST: - return "Couldn't resolve host name"; + return "Could not resolve hostname"; case CURLE_COULDNT_CONNECT: - return "Couldn't connect to server"; + return "Could not connect to server"; case CURLE_WEIRD_SERVER_REPLY: return "Weird server reply"; @@ -107,19 +104,19 @@ curl_easy_strerror(CURLcode error) return "FTP: unknown 227 response format"; case CURLE_FTP_CANT_GET_HOST: - return "FTP: can't figure out the host in the PASV response"; + return "FTP: cannot figure out the host in the PASV response"; case CURLE_HTTP2: return "Error in the HTTP2 framing layer"; case CURLE_FTP_COULDNT_SET_TYPE: - return "FTP: couldn't set file type"; + return "FTP: could not set file type"; case CURLE_PARTIAL_FILE: return "Transferred a partial file"; case CURLE_FTP_COULDNT_RETR_FILE: - return "FTP: couldn't retrieve (RETR failed) the specified file"; + return "FTP: could not retrieve (RETR failed) the specified file"; case CURLE_QUOTE_ERROR: return "Quote command returned error"; @@ -151,17 +148,14 @@ curl_easy_strerror(CURLcode error) case CURLE_RANGE_ERROR: return "Requested range was not delivered by the server"; - case CURLE_HTTP_POST_ERROR: - return "Internal problem setting up the POST"; - case CURLE_SSL_CONNECT_ERROR: return "SSL connect error"; case CURLE_BAD_DOWNLOAD_RESUME: - return "Couldn't resume download"; + return "Could not resume download"; case CURLE_FILE_COULDNT_READ_FILE: - return "Couldn't read a file:// file"; + return "Could not read a file:// file"; case CURLE_LDAP_CANNOT_BIND: return "LDAP: cannot bind"; @@ -169,9 +163,6 @@ curl_easy_strerror(CURLcode error) case CURLE_LDAP_SEARCH_FAILED: return "LDAP: search failed"; - case CURLE_FUNCTION_NOT_FOUND: - return "A required function in the library was not found"; - case CURLE_ABORTED_BY_CALLBACK: return "Operation was aborted by an application callback"; @@ -212,7 +203,7 @@ curl_easy_strerror(CURLcode error) return "Problem with the local SSL certificate"; case CURLE_SSL_CIPHER: - return "Couldn't use specified SSL cipher"; + return "Could not use specified SSL cipher"; case CURLE_PEER_FAILED_VERIFICATION: return "SSL peer certificate or SSH remote key was not OK"; @@ -319,12 +310,20 @@ curl_easy_strerror(CURLcode error) case CURLE_UNRECOVERABLE_POLL: return "Unrecoverable error in select/poll"; + case CURLE_TOO_LARGE: + return "A value or data field grew larger than allowed"; + + case CURLE_ECH_REQUIRED: + return "ECH attempted but failed"; + /* error codes not used by current libcurl */ case CURLE_OBSOLETE20: case CURLE_OBSOLETE24: case CURLE_OBSOLETE29: case CURLE_OBSOLETE32: + case CURLE_OBSOLETE34: case CURLE_OBSOLETE40: + case CURLE_OBSOLETE41: case CURLE_OBSOLETE44: case CURLE_OBSOLETE46: case CURLE_OBSOLETE50: @@ -333,22 +332,35 @@ curl_easy_strerror(CURLcode error) case CURLE_OBSOLETE62: case CURLE_OBSOLETE75: case CURLE_OBSOLETE76: + + /* error codes used by curl tests */ + case CURLE_RESERVED115: + case CURLE_RESERVED116: + case CURLE_RESERVED117: + case CURLE_RESERVED118: + case CURLE_RESERVED119: + case CURLE_RESERVED120: + case CURLE_RESERVED121: + case CURLE_RESERVED122: + case CURLE_RESERVED123: + case CURLE_RESERVED124: + case CURLE_RESERVED125: + case CURLE_RESERVED126: case CURL_LAST: break; } /* * By using a switch, gcc -Wall will complain about enum values * which do not appear, helping keep this function up-to-date. - * By using gcc -Wall -Werror, you can't forget. + * By using gcc -Wall -Werror, you cannot forget. * - * A table would not have the same benefit. Most compilers will - * generate code very similar to a table in any case, so there - * is little performance gain from a table. And something is broken - * for the user's application, anyways, so does it matter how fast - * it _doesn't_ work? + * A table would not have the same benefit. Most compilers will generate + * code similar to a table in any case, so there is little performance gain + * from a table. Something is broken for the user's application, anyways, so + * does it matter how fast it _does not_ work? * - * The line number for the error will be near this comment, which - * is why it is here, and not at the start of the switch. + * The line number for the error will be near this comment, which is why it + * is here, and not at the start of the switch. */ return "Unknown error"; #else @@ -553,6 +565,9 @@ curl_url_strerror(CURLUcode error) case CURLUE_LACKS_IDN: return "libcurl lacks IDN support"; + case CURLUE_TOO_LARGE: + return "A value or data field is larger than allowed"; + case CURLUE_LAST: break; } @@ -572,10 +587,11 @@ curl_url_strerror(CURLUcode error) * Returns NULL if no error message was found for error code. */ static const char * -get_winsock_error (int err, char *buf, size_t len) +get_winsock_error(int err, char *buf, size_t len) { #ifndef CURL_DISABLE_VERBOSE_STRINGS const char *p; + size_t alen; #endif if(!len) @@ -755,66 +771,23 @@ get_winsock_error (int err, char *buf, size_t len) default: return NULL; } - strncpy(buf, p, len); - buf [len-1] = '\0'; + alen = strlen(p); + if(alen < len) + strcpy(buf, p); return buf; #endif } #endif /* USE_WINSOCK */ -#if defined(WIN32) || defined(_WIN32_WCE) -/* This is a helper function for Curl_strerror that converts Windows API error - * codes (GetLastError) to error messages. - * Returns NULL if no error message was found for error code. - */ -static const char * -get_winapi_error(int err, char *buf, size_t buflen) -{ - char *p; - wchar_t wbuf[256]; - - if(!buflen) - return NULL; - - *buf = '\0'; - *wbuf = L'\0'; - - /* We return the local codepage version of the error string because if it is - output to the user's terminal it will likely be with functions which - expect the local codepage (eg fprintf, failf, infof). - FormatMessageW -> wcstombs is used for Windows CE compatibility. */ - if(FormatMessageW((FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS), NULL, err, - LANG_NEUTRAL, wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL)) { - size_t written = wcstombs(buf, wbuf, buflen - 1); - if(written != (size_t)-1) - buf[written] = '\0'; - else - *buf = '\0'; - } - - /* Truncate multiple lines */ - p = strchr(buf, '\n'); - if(p) { - if(p > buf && *(p-1) == '\r') - *(p-1) = '\0'; - else - *p = '\0'; - } - - return (*buf ? buf : NULL); -} -#endif /* WIN32 || _WIN32_WCE */ - /* * Our thread-safe and smart strerror() replacement. * * The 'err' argument passed in to this function MUST be a true errno number * as reported on this system. We do no range checking on the number before * we pass it to the "number-to-message" conversion function and there might - * be systems that don't do proper range checking in there themselves. + * be systems that do not do proper range checking in there themselves. * - * We don't do range checking (on systems other than Windows) since there is + * We do not do range checking (on systems other than Windows) since there is * no good reliable and portable way to do it. * * On Windows different types of error codes overlap. This function has an @@ -823,41 +796,39 @@ get_winapi_error(int err, char *buf, size_t buflen) * * It may be more correct to call one of the variant functions instead: * Call Curl_sspi_strerror if the error code is definitely Windows SSPI. - * Call Curl_winapi_strerror if the error code is definitely Windows API. + * Call curlx_winapi_strerror if the error code is definitely Windows API. */ const char *Curl_strerror(int err, char *buf, size_t buflen) { -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 DWORD old_win_err = GetLastError(); #endif int old_errno = errno; char *p; - size_t max; if(!buflen) return NULL; -#ifndef WIN32 +#ifndef _WIN32 DEBUGASSERT(err >= 0); #endif - max = buflen - 1; *buf = '\0'; -#if defined(WIN32) || defined(_WIN32_WCE) -#if defined(WIN32) +#ifdef _WIN32 +#ifndef UNDER_CE /* 'sys_nerr' is the maximum errno number, it is not widely portable */ if(err >= 0 && err < sys_nerr) - strncpy(buf, sys_errlist[err], max); + curl_msnprintf(buf, buflen, "%s", sys_errlist[err]); else #endif { if( #ifdef USE_WINSOCK - !get_winsock_error(err, buf, max) && + !get_winsock_error(err, buf, buflen) && #endif - !get_winapi_error((DWORD)err, buf, max)) - msnprintf(buf, max, "Unknown error %d (%#x)", err, err); + !curlx_get_winapi_error(err, buf, buflen)) + curl_msnprintf(buf, buflen, "Unknown error %d (%#x)", err, err); } #else /* not Windows coming up */ @@ -867,9 +838,9 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) * storage is supplied via 'strerrbuf' and 'buflen' to hold the generated * message string, or EINVAL if 'errnum' is not a valid error number. */ - if(0 != strerror_r(err, buf, max)) { + if(0 != strerror_r(err, buf, buflen)) { if('\0' == buf[0]) - msnprintf(buf, max, "Unknown error %d", err); + curl_msnprintf(buf, buflen, "Unknown error %d", err); } #elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) /* @@ -881,25 +852,23 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) char buffer[256]; char *msg = strerror_r(err, buffer, sizeof(buffer)); if(msg) - strncpy(buf, msg, max); + curl_msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, max, "Unknown error %d", err); + curl_msnprintf(buf, buflen, "Unknown error %d", err); } #else { - /* !checksrc! disable STRERROR 1 */ + /* !checksrc! disable BANNEDFUNC 1 */ const char *msg = strerror(err); if(msg) - strncpy(buf, msg, max); + curl_msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, max, "Unknown error %d", err); + curl_msnprintf(buf, buflen, "Unknown error %d", err); } #endif #endif /* end of not Windows */ - buf[max] = '\0'; /* make sure the string is null-terminated */ - /* strip trailing '\r\n' or '\n'. */ p = strrchr(buf, '\n'); if(p && (p - buf) >= 2) @@ -909,56 +878,15 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) *p = '\0'; if(errno != old_errno) - errno = old_errno; - -#ifdef PRESERVE_WINDOWS_ERROR_CODE - if(old_win_err != GetLastError()) - SetLastError(old_win_err); -#endif - - return buf; -} - -/* - * Curl_winapi_strerror: - * Variant of Curl_strerror if the error code is definitely Windows API. - */ -#if defined(WIN32) || defined(_WIN32_WCE) -const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) -{ -#ifdef PRESERVE_WINDOWS_ERROR_CODE - DWORD old_win_err = GetLastError(); -#endif - int old_errno = errno; - - if(!buflen) - return NULL; - - *buf = '\0'; - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - if(!get_winapi_error(err, buf, buflen)) { - msnprintf(buf, buflen, "Unknown error %u (0x%08X)", err, err); - } -#else - { - const char *txt = (err == ERROR_SUCCESS) ? "No error" : "Error"; - strncpy(buf, txt, buflen); - buf[buflen - 1] = '\0'; - } -#endif - - if(errno != old_errno) - errno = old_errno; + CURL_SETERRNO(old_errno); -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 if(old_win_err != GetLastError()) SetLastError(old_win_err); #endif return buf; } -#endif /* WIN32 || _WIN32_WCE */ #ifdef USE_WINDOWS_SSPI /* @@ -967,7 +895,7 @@ const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) */ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) { -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 DWORD old_win_err = GetLastError(); #endif int old_errno = errno; @@ -986,6 +914,10 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) break; #define SEC2TXT(sec) case sec: txt = #sec; break SEC2TXT(CRYPT_E_REVOKED); + SEC2TXT(CRYPT_E_NO_REVOCATION_DLL); + SEC2TXT(CRYPT_E_NO_REVOCATION_CHECK); + SEC2TXT(CRYPT_E_REVOCATION_OFFLINE); + SEC2TXT(CRYPT_E_NOT_IN_REVOCATION_DATABASE); SEC2TXT(SEC_E_ALGORITHM_MISMATCH); SEC2TXT(SEC_E_BAD_BINDINGS); SEC2TXT(SEC_E_BAD_PKGID); @@ -1070,24 +1002,18 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) } if(err == SEC_E_ILLEGAL_MESSAGE) { - msnprintf(buf, buflen, - "SEC_E_ILLEGAL_MESSAGE (0x%08X) - This error usually occurs " - "when a fatal SSL/TLS alert is received (e.g. handshake failed)." - " More detail may be available in the Windows System event log.", - err); + curl_msnprintf(buf, buflen, + "SEC_E_ILLEGAL_MESSAGE (0x%08X) - This error usually " + "occurs when a fatal SSL/TLS alert is received (e.g. " + "handshake failed). More detail may be available in " + "the Windows System event log.", err); } else { - char txtbuf[80]; char msgbuf[256]; - - msnprintf(txtbuf, sizeof(txtbuf), "%s (0x%08X)", txt, err); - - if(get_winapi_error(err, msgbuf, sizeof(msgbuf))) - msnprintf(buf, buflen, "%s - %s", txtbuf, msgbuf); - else { - strncpy(buf, txtbuf, buflen); - buf[buflen - 1] = '\0'; - } + if(curlx_get_winapi_error(err, msgbuf, sizeof(msgbuf))) + curl_msnprintf(buf, buflen, "%s (0x%08X) - %s", txt, err, msgbuf); + else + curl_msnprintf(buf, buflen, "%s (0x%08X)", txt, err); } #else @@ -1095,14 +1021,14 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) txt = "No error"; else txt = "Error"; - strncpy(buf, txt, buflen); - buf[buflen - 1] = '\0'; + if(buflen > strlen(txt)) + strcpy(buf, txt); #endif if(errno != old_errno) - errno = old_errno; + CURL_SETERRNO(old_errno); -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 if(old_win_err != GetLastError()) SetLastError(old_win_err); #endif diff --git a/Utilities/cmcurl/lib/strerror.h b/Utilities/cmcurl/lib/strerror.h index 399712f8ebb..424fb5b7b5e 100644 --- a/Utilities/cmcurl/lib/strerror.h +++ b/Utilities/cmcurl/lib/strerror.h @@ -29,9 +29,6 @@ #define STRERROR_LEN 256 /* a suitable length */ const char *Curl_strerror(int err, char *buf, size_t buflen); -#if defined(WIN32) || defined(_WIN32_WCE) -const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen); -#endif #ifdef USE_WINDOWS_SSPI const char *Curl_sspi_strerror(int err, char *buf, size_t buflen); #endif diff --git a/Utilities/cmcurl/lib/strtok.c b/Utilities/cmcurl/lib/strtok.c deleted file mode 100644 index d8e1e8183f4..00000000000 --- a/Utilities/cmcurl/lib/strtok.c +++ /dev/null @@ -1,68 +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" - -#ifndef HAVE_STRTOK_R -#include - -#include "strtok.h" - -char * -Curl_strtok_r(char *ptr, const char *sep, char **end) -{ - if(!ptr) - /* we got NULL input so then we get our last position instead */ - ptr = *end; - - /* pass all letters that are including in the separator string */ - while(*ptr && strchr(sep, *ptr)) - ++ptr; - - if(*ptr) { - /* so this is where the next piece of string starts */ - char *start = ptr; - - /* set the end pointer to the first byte after the start */ - *end = start + 1; - - /* scan through the string to find where it ends, it ends on a - null byte or a character that exists in the separator string */ - while(**end && !strchr(sep, **end)) - ++*end; - - if(**end) { - /* the end is not a null byte */ - **end = '\0'; /* null-terminate it! */ - ++*end; /* advance the last pointer to beyond the null byte */ - } - - return start; /* return the position where the string starts */ - } - - /* we ended up on a null byte, there are no more strings to find! */ - return NULL; -} - -#endif /* this was only compiled if strtok_r wasn't present */ diff --git a/Utilities/cmcurl/lib/strtok.h b/Utilities/cmcurl/lib/strtok.h deleted file mode 100644 index 321cba23262..00000000000 --- a/Utilities/cmcurl/lib/strtok.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef HEADER_CURL_STRTOK_H -#define HEADER_CURL_STRTOK_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" -#include - -#ifndef HAVE_STRTOK_R -char *Curl_strtok_r(char *s, const char *delim, char **last); -#define strtok_r Curl_strtok_r -#else -#include -#endif - -#endif /* HEADER_CURL_STRTOK_H */ diff --git a/Utilities/cmcurl/lib/strtoofft.c b/Utilities/cmcurl/lib/strtoofft.c deleted file mode 100644 index 077b25792e0..00000000000 --- a/Utilities/cmcurl/lib/strtoofft.c +++ /dev/null @@ -1,245 +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 -#include "curl_setup.h" - -#include "strtoofft.h" - -/* - * NOTE: - * - * In the ISO C standard (IEEE Std 1003.1), there is a strtoimax() function we - * could use in case strtoll() doesn't exist... See - * https://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html - */ - -#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) -# ifdef HAVE_STRTOLL -# define strtooff strtoll -# else -# if defined(_MSC_VER) && (_MSC_VER >= 1300) && (_INTEGRAL_MAX_BITS >= 64) -# if defined(_SAL_VERSION) - _Check_return_ _CRTIMP __int64 __cdecl _strtoi64( - _In_z_ const char *_String, - _Out_opt_ _Deref_post_z_ char **_EndPtr, _In_ int _Radix); -# else - _CRTIMP __int64 __cdecl _strtoi64(const char *_String, - char **_EndPtr, int _Radix); -# endif -# define strtooff _strtoi64 -# else -# define PRIVATE_STRTOOFF 1 -# endif -# endif -#else -# define strtooff strtol -#endif - -#ifdef PRIVATE_STRTOOFF - -/* Range tests can be used for alphanum decoding if characters are consecutive, - like in ASCII. Else an array is scanned. Determine this condition now. */ - -#if('9' - '0') != 9 || ('Z' - 'A') != 25 || ('z' - 'a') != 25 - -#define NO_RANGE_TEST - -static const char valchars[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -#endif - -static int get_char(char c, int base); - -/** - * Custom version of the strtooff function. This extracts a curl_off_t - * value from the given input string and returns it. - */ -static curl_off_t strtooff(const char *nptr, char **endptr, int base) -{ - char *end; - int is_negative = 0; - int overflow; - int i; - curl_off_t value = 0; - curl_off_t newval; - - /* Skip leading whitespace. */ - end = (char *)nptr; - while(ISBLANK(end[0])) { - end++; - } - - /* Handle the sign, if any. */ - if(end[0] == '-') { - is_negative = 1; - end++; - } - else if(end[0] == '+') { - end++; - } - else if(end[0] == '\0') { - /* We had nothing but perhaps some whitespace -- there was no number. */ - if(endptr) { - *endptr = end; - } - return 0; - } - - /* Handle special beginnings, if present and allowed. */ - if(end[0] == '0' && end[1] == 'x') { - if(base == 16 || base == 0) { - end += 2; - base = 16; - } - } - else if(end[0] == '0') { - if(base == 8 || base == 0) { - end++; - base = 8; - } - } - - /* Matching strtol, if the base is 0 and it doesn't look like - * the number is octal or hex, we assume it's base 10. - */ - if(base == 0) { - base = 10; - } - - /* Loop handling digits. */ - value = 0; - overflow = 0; - for(i = get_char(end[0], base); - i != -1; - end++, i = get_char(end[0], base)) { - newval = base * value + i; - if(newval < value) { - /* We've overflowed. */ - overflow = 1; - break; - } - else - value = newval; - } - - if(!overflow) { - if(is_negative) { - /* Fix the sign. */ - value *= -1; - } - } - else { - if(is_negative) - value = CURL_OFF_T_MIN; - else - value = CURL_OFF_T_MAX; - - errno = ERANGE; - } - - if(endptr) - *endptr = end; - - return value; -} - -/** - * Returns the value of c in the given base, or -1 if c cannot - * be interpreted properly in that base (i.e., is out of range, - * is a null, etc.). - * - * @param c the character to interpret according to base - * @param base the base in which to interpret c - * - * @return the value of c in base, or -1 if c isn't in range - */ -static int get_char(char c, int base) -{ -#ifndef NO_RANGE_TEST - int value = -1; - if(c <= '9' && c >= '0') { - value = c - '0'; - } - else if(c <= 'Z' && c >= 'A') { - value = c - 'A' + 10; - } - else if(c <= 'z' && c >= 'a') { - value = c - 'a' + 10; - } -#else - const char *cp; - int value; - - cp = memchr(valchars, c, 10 + 26 + 26); - - if(!cp) - return -1; - - value = cp - valchars; - - if(value >= 10 + 26) - value -= 26; /* Lowercase. */ -#endif - - if(value >= base) { - value = -1; - } - - return value; -} -#endif /* Only present if we need strtoll, but don't have it. */ - -/* - * Parse a *positive* up to 64 bit number written in ascii. - */ -CURLofft curlx_strtoofft(const char *str, char **endp, int base, - curl_off_t *num) -{ - char *end; - curl_off_t number; - errno = 0; - *num = 0; /* clear by default */ - DEBUGASSERT(base); /* starting now, avoid base zero */ - - while(*str && ISBLANK(*str)) - str++; - if(('-' == *str) || (ISSPACE(*str))) { - if(endp) - *endp = (char *)str; /* didn't actually move */ - return CURL_OFFT_INVAL; /* nothing parsed */ - } - number = strtooff(str, &end, base); - if(endp) - *endp = end; - if(errno == ERANGE) - /* overflow/underflow */ - return CURL_OFFT_FLOW; - else if(str == end) - /* nothing parsed */ - return CURL_OFFT_INVAL; - - *num = number; - return CURL_OFFT_OK; -} diff --git a/Utilities/cmcurl/lib/strtoofft.h b/Utilities/cmcurl/lib/strtoofft.h deleted file mode 100644 index 34d293ba382..00000000000 --- a/Utilities/cmcurl/lib/strtoofft.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef HEADER_CURL_STRTOOFFT_H -#define HEADER_CURL_STRTOOFFT_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" - -/* - * Determine which string to integral data type conversion function we use - * to implement string conversion to our curl_off_t integral data type. - * - * Notice that curl_off_t might be 64 or 32 bit wide, and that it might use - * an underlying data type which might be 'long', 'int64_t', 'long long' or - * '__int64' and more remotely other data types. - * - * On systems where the size of curl_off_t is greater than the size of 'long' - * the conversion function to use is strtoll() if it is available, otherwise, - * we emulate its functionality with our own clone. - * - * On systems where the size of curl_off_t is smaller or equal than the size - * of 'long' the conversion function to use is strtol(). - */ - -typedef enum { - CURL_OFFT_OK, /* parsed fine */ - CURL_OFFT_FLOW, /* over or underflow */ - CURL_OFFT_INVAL /* nothing was parsed */ -} CURLofft; - -CURLofft curlx_strtoofft(const char *str, char **endp, int base, - curl_off_t *num); - -#endif /* HEADER_CURL_STRTOOFFT_H */ diff --git a/Utilities/cmcurl/lib/system_win32.c b/Utilities/cmcurl/lib/system_win32.c index 0cdaf3b2fe7..52128875802 100644 --- a/Utilities/cmcurl/lib/system_win32.c +++ b/Utilities/cmcurl/lib/system_win32.c @@ -24,32 +24,29 @@ #include "curl_setup.h" -#if defined(WIN32) +#ifdef _WIN32 #include #include "system_win32.h" -#include "version_win32.h" +#include "curlx/version_win32.h" #include "curl_sspi.h" -#include "warnless.h" +#include "curlx/warnless.h" /* The last #include files should be: */ #include "curl_memory.h" #include "memdebug.h" -LARGE_INTEGER Curl_freq; -bool Curl_isVistaOrGreater; - /* Handle of iphlpapp.dll */ static HMODULE s_hIpHlpApiDll = NULL; /* Pointer to the if_nametoindex function */ IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL; -/* Curl_win32_init() performs win32 global initialization */ +/* 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 just for Winsock at the moment. Any required win32 initialization + is just for Winsock at the moment. Any required Win32 initialization should take place after this block. */ if(flags & CURL_GLOBAL_WIN32) { #ifdef USE_WINSOCK @@ -61,7 +58,7 @@ CURLcode Curl_win32_init(long flags) res = WSAStartup(wVersionRequested, &wsaData); if(res) - /* Tell the user that we couldn't find a usable */ + /* Tell the user that we could not find a usable */ /* winsock.dll. */ return CURLE_FAILED_INIT; @@ -73,7 +70,7 @@ CURLcode Curl_win32_init(long flags) if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested) ) { - /* Tell the user that we couldn't find a usable */ + /* Tell the user that we could not find a usable */ /* winsock.dll. */ WSACleanup(); @@ -96,9 +93,15 @@ CURLcode Curl_win32_init(long flags) s_hIpHlpApiDll = Curl_load_library(TEXT("iphlpapi.dll")); if(s_hIpHlpApiDll) { /* Get the address of the if_nametoindex function */ +#ifdef UNDER_CE + #define CURL_TEXT(n) TEXT(n) +#else + #define CURL_TEXT(n) (n) +#endif IF_NAMETOINDEX_FN pIfNameToIndex = CURLX_FUNCTION_CAST(IF_NAMETOINDEX_FN, - (GetProcAddress(s_hIpHlpApiDll, "if_nametoindex"))); + (GetProcAddress(s_hIpHlpApiDll, + CURL_TEXT("if_nametoindex")))); if(pIfNameToIndex) Curl_if_nametoindex = pIfNameToIndex; @@ -150,7 +153,7 @@ typedef HMODULE (APIENTRY *LOADLIBRARYEX_FN)(LPCTSTR, HANDLE, DWORD); /* See function definitions in winbase.h */ #ifdef UNICODE -# ifdef _WIN32_WCE +# ifdef UNDER_CE # define LOADLIBARYEX L"LoadLibraryExW" # else # define LOADLIBARYEX "LoadLibraryExW" @@ -175,11 +178,11 @@ typedef HMODULE (APIENTRY *LOADLIBRARYEX_FN)(LPCTSTR, HANDLE, DWORD); */ HMODULE Curl_load_library(LPCTSTR filename) { -#ifndef CURL_WINDOWS_APP +#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE) HMODULE hModule = NULL; LOADLIBRARYEX_FN pLoadLibraryEx = NULL; - /* Get a handle to kernel32 so we can access it's functions at runtime */ + /* Get a handle to kernel32 so we can access its functions at runtime */ HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32")); if(!hKernel32) return NULL; @@ -190,7 +193,7 @@ HMODULE Curl_load_library(LPCTSTR filename) CURLX_FUNCTION_CAST(LOADLIBRARYEX_FN, (GetProcAddress(hKernel32, LOADLIBARYEX))); - /* Detect if there's already a path in the filename and load the library if + /* Detect if there is already a path in the filename and load the library if there is. Note: Both back slashes and forward slashes have been supported since the earlier days of DOS at an API level although they are not supported by command prompt */ @@ -211,7 +214,7 @@ HMODULE Curl_load_library(LPCTSTR filename) /* Attempt to get the Windows system path */ UINT systemdirlen = GetSystemDirectory(NULL, 0); if(systemdirlen) { - /* Allocate space for the full DLL path (Room for the null terminator + /* Allocate space for the full DLL path (Room for the null-terminator is included in systemdirlen) */ size_t filenamelen = _tcslen(filename); TCHAR *path = malloc(sizeof(TCHAR) * (systemdirlen + 1 + filenamelen)); @@ -232,10 +235,10 @@ HMODULE Curl_load_library(LPCTSTR filename) } return hModule; #else - /* the Universal Windows Platform (UWP) can't do this */ + /* the Universal Windows Platform (UWP) cannot do this */ (void)filename; return NULL; #endif } -#endif /* WIN32 */ +#endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/system_win32.h b/Utilities/cmcurl/lib/system_win32.h index 24899cb2d5f..b8333c647e1 100644 --- a/Utilities/cmcurl/lib/system_win32.h +++ b/Utilities/cmcurl/lib/system_win32.h @@ -26,7 +26,9 @@ #include "curl_setup.h" -#if defined(WIN32) +#ifdef _WIN32 + +#include extern LARGE_INTEGER Curl_freq; extern bool Curl_isVistaOrGreater; @@ -42,7 +44,8 @@ extern IF_NAMETOINDEX_FN Curl_if_nametoindex; /* This is used to dynamically load DLLs */ HMODULE Curl_load_library(LPCTSTR filename); - -#endif /* WIN32 */ +#else /* _WIN32 */ +#define Curl_win32_init(x) CURLE_OK +#endif /* !_WIN32 */ #endif /* HEADER_CURL_SYSTEM_WIN32_H */ diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c index 643e43d2b0a..737db36d72c 100644 --- a/Utilities/cmcurl/lib/telnet.c +++ b/Utilities/cmcurl/lib/telnet.c @@ -47,6 +47,7 @@ #endif #include "urldata.h" +#include "url.h" #include #include "transfer.h" #include "sendf.h" @@ -57,7 +58,8 @@ #include "arpa_telnet.h" #include "select.h" #include "strcase.h" -#include "warnless.h" +#include "curlx/warnless.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -89,36 +91,6 @@ #define printoption(a,b,c,d) Curl_nop_stmt #endif -static -CURLcode telrcv(struct Curl_easy *data, - const unsigned char *inbuf, /* Data received from socket */ - ssize_t count); /* Number of bytes received */ - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void printoption(struct Curl_easy *data, - const char *direction, - int cmd, int option); -#endif - -static void negotiate(struct Curl_easy *data); -static void send_negotiation(struct Curl_easy *data, int cmd, int option); -static void set_local_option(struct Curl_easy *data, - int option, int newstate); -static void set_remote_option(struct Curl_easy *data, - int option, int newstate); - -static void printsub(struct Curl_easy *data, - int direction, unsigned char *pointer, - size_t length); -static void suboption(struct Curl_easy *data); -static void sendsuboption(struct Curl_easy *data, int option); - -static CURLcode telnet_do(struct Curl_easy *data, bool *done); -static CURLcode telnet_done(struct Curl_easy *data, - CURLcode, bool premature); -static CURLcode send_telnet_data(struct Curl_easy *data, - char *buffer, ssize_t nread); - /* For negotiation compliant to RFC 1143 */ #define CURL_NO 0 #define CURL_YES 1 @@ -128,6 +100,10 @@ static CURLcode send_telnet_data(struct Curl_easy *data, #define CURL_EMPTY 0 #define CURL_OPPOSITE 1 + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_TELNET_EASY "meta:proto:telnet:easy" + /* * Telnet receiver states for fsm */ @@ -154,12 +130,13 @@ struct TELNET { int himq[256]; int him_preferred[256]; int subnegotiation[256]; - char subopt_ttype[32]; /* Set with suboption TTYPE */ - char subopt_xdisploc[128]; /* Set with suboption XDISPLOC */ + char *subopt_ttype; /* Set with suboption TTYPE */ + char *subopt_xdisploc; /* Set with suboption XDISPLOC */ unsigned short subopt_wsx; /* Set with suboption NAWS */ unsigned short subopt_wsy; /* Set with suboption NAWS */ TelnetReceive telrcv_state; struct curl_slist *telnet_vars; /* Environment variables */ + struct dynbuf out; /* output buffer */ /* suboptions */ unsigned char subbuffer[SUBBUFSIZE]; @@ -167,12 +144,44 @@ struct TELNET { }; +static +CURLcode telrcv(struct Curl_easy *data, + struct TELNET *tn, + const unsigned char *inbuf, /* Data received from socket */ + ssize_t count); /* Number of bytes received */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void printoption(struct Curl_easy *data, + const char *direction, + int cmd, int option); +#endif + +static void send_negotiation(struct Curl_easy *data, int cmd, int option); +static void set_local_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate); +static void set_remote_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate); + +static void printsub(struct Curl_easy *data, + int direction, unsigned char *pointer, + size_t length); +static void suboption(struct Curl_easy *data, struct TELNET *tn); +static void sendsuboption(struct Curl_easy *data, + struct TELNET *tn, int option); + +static CURLcode telnet_do(struct Curl_easy *data, bool *done); +static CURLcode telnet_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode send_telnet_data(struct Curl_easy *data, + struct TELNET *tn, + char *buffer, ssize_t nread); + /* * TELNET protocol handler. */ const struct Curl_handler Curl_handler_telnet = { - "TELNET", /* scheme */ + "telnet", /* scheme */ ZERO_NULL, /* setup_connection */ telnet_do, /* do_it */ telnet_done, /* done */ @@ -185,9 +194,11 @@ const struct Curl_handler Curl_handler_telnet = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_TELNET, /* defport */ CURLPROTO_TELNET, /* protocol */ CURLPROTO_TELNET, /* family */ @@ -195,6 +206,16 @@ const struct Curl_handler Curl_handler_telnet = { }; +static void telnet_easy_dtor(void *key, size_t klen, void *entry) +{ + struct TELNET *tn = entry; + (void)key; + (void)klen; + curl_slist_free_all(tn->telnet_vars); + curlx_dyn_free(&tn->out); + free(tn); +} + static CURLcode init_telnet(struct Curl_easy *data) { @@ -204,7 +225,7 @@ CURLcode init_telnet(struct Curl_easy *data) if(!tn) return CURLE_OUT_OF_MEMORY; - data->req.p.telnet = tn; /* make us known */ + curlx_dyn_init(&tn->out, 0xffff); tn->telrcv_state = CURL_TS_DATA; @@ -243,23 +264,23 @@ CURLcode init_telnet(struct Curl_easy *data) based upon the terminal type information that may have been sent using the TERMINAL TYPE Telnet option). */ tn->subnegotiation[CURL_TELOPT_NAWS] = CURL_YES; - return CURLE_OK; + + return Curl_meta_set(data, CURL_META_TELNET_EASY, tn, telnet_easy_dtor); } -static void negotiate(struct Curl_easy *data) +static void telnet_negotiate(struct Curl_easy *data, struct TELNET *tn) { int i; - struct TELNET *tn = data->req.p.telnet; for(i = 0; i < CURL_NTELOPTS; i++) { if(i == CURL_TELOPT_ECHO) continue; if(tn->us_preferred[i] == CURL_YES) - set_local_option(data, i, CURL_YES); + set_local_option(data, tn, i, CURL_YES); if(tn->him_preferred[i] == CURL_YES) - set_remote_option(data, i, CURL_YES); + set_remote_option(data, tn, i, CURL_YES); } } @@ -320,9 +341,9 @@ static void send_negotiation(struct Curl_easy *data, int cmd, int option) } static -void set_remote_option(struct Curl_easy *data, int option, int newstate) +void set_remote_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate) { - struct TELNET *tn = data->req.p.telnet; if(newstate == CURL_YES) { switch(tn->him[option]) { case CURL_NO: @@ -394,9 +415,8 @@ void set_remote_option(struct Curl_easy *data, int option, int newstate) } static -void rec_will(struct Curl_easy *data, int option) +void rec_will(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->him[option]) { case CURL_NO: if(tn->him_preferred[option] == CURL_YES) { @@ -442,9 +462,8 @@ void rec_will(struct Curl_easy *data, int option) } static -void rec_wont(struct Curl_easy *data, int option) +void rec_wont(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->him[option]) { case CURL_NO: /* Already disabled */ @@ -484,9 +503,9 @@ void rec_wont(struct Curl_easy *data, int option) } static void -set_local_option(struct Curl_easy *data, int option, int newstate) +set_local_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate) { - struct TELNET *tn = data->req.p.telnet; if(newstate == CURL_YES) { switch(tn->us[option]) { case CURL_NO: @@ -558,9 +577,8 @@ set_local_option(struct Curl_easy *data, int option, int newstate) } static -void rec_do(struct Curl_easy *data, int option) +void rec_do(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->us[option]) { case CURL_NO: if(tn->us_preferred[option] == CURL_YES) { @@ -568,13 +586,13 @@ void rec_do(struct Curl_easy *data, int option) send_negotiation(data, CURL_WILL, option); if(tn->subnegotiation[option] == CURL_YES) /* transmission of data option */ - sendsuboption(data, option); + sendsuboption(data, tn, option); } else if(tn->subnegotiation[option] == CURL_YES) { /* send information to achieve this option */ tn->us[option] = CURL_YES; send_negotiation(data, CURL_WILL, option); - sendsuboption(data, option); + sendsuboption(data, tn, option); } else send_negotiation(data, CURL_WONT, option); @@ -604,7 +622,7 @@ void rec_do(struct Curl_easy *data, int option) tn->us[option] = CURL_YES; if(tn->subnegotiation[option] == CURL_YES) { /* transmission of data option */ - sendsuboption(data, option); + sendsuboption(data, tn, option); } break; case CURL_OPPOSITE: @@ -618,9 +636,8 @@ void rec_do(struct Curl_easy *data, int option) } static -void rec_dont(struct Curl_easy *data, int option) +void rec_dont(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->us[option]) { case CURL_NO: /* Already disabled */ @@ -668,7 +685,7 @@ static void printsub(struct Curl_easy *data, if(data->set.verbose) { unsigned int i = 0; if(direction) { - infof(data, "%s IAC SB ", (direction == '<')? "RCVD":"SENT"); + infof(data, "%s IAC SB ", (direction == '<') ? "RCVD" : "SENT"); if(length >= 3) { int j; @@ -692,7 +709,10 @@ static void printsub(struct Curl_easy *data, infof(data, ", not IAC SE) "); } } - length -= 2; + if(length >= 2) + length -= 2; + else /* bad input */ + return; } if(length < 1) { infof(data, "(Empty suboption?)"); @@ -718,8 +738,8 @@ static void printsub(struct Curl_easy *data, switch(pointer[0]) { case CURL_TELOPT_NAWS: if(length > 4) - infof(data, "Width: %d ; Height: %d", (pointer[1]<<8) | pointer[2], - (pointer[3]<<8) | pointer[4]); + infof(data, "Width: %d ; Height: %d", (pointer[1] << 8) | pointer[2], + (pointer[3] << 8) | pointer[4]); break; default: switch(pointer[1]) { @@ -770,37 +790,31 @@ static void printsub(struct Curl_easy *data, } } -#ifdef _MSC_VER -#pragma warning(push) -/* warning C4706: assignment within conditional expression */ -#pragma warning(disable:4706) -#endif static bool str_is_nonascii(const char *str) { char c; - while((c = *str++)) + while((c = *str++) != 0) if(c & 0x80) return TRUE; return FALSE; } -#ifdef _MSC_VER -#pragma warning(pop) -#endif -static CURLcode check_telnet_options(struct Curl_easy *data) +static CURLcode check_telnet_options(struct Curl_easy *data, + struct TELNET *tn) { struct curl_slist *head; struct curl_slist *beg; - struct TELNET *tn = data->req.p.telnet; CURLcode result = CURLE_OK; - /* Add the user name as an environment variable if it + /* Add the username as an environment variable if it was given on the command line */ if(data->state.aptr.user) { char buffer[256]; - if(str_is_nonascii(data->conn->user)) + if(str_is_nonascii(data->conn->user)) { + DEBUGF(infof(data, "set a non ASCII username in telnet")); return CURLE_BAD_FUNCTION_ARGUMENT; + } msnprintf(buffer, sizeof(buffer), "USER,%s", data->conn->user); beg = curl_slist_append(tn->telnet_vars, buffer); if(!beg) { @@ -826,23 +840,21 @@ static CURLcode check_telnet_options(struct Curl_easy *data) case 5: /* Terminal type */ if(strncasecompare(option, "TTYPE", 5)) { - strncpy(tn->subopt_ttype, arg, 31); - tn->subopt_ttype[31] = 0; /* String termination */ + tn->subopt_ttype = arg; tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; + break; } - else - result = CURLE_UNKNOWN_OPTION; + result = CURLE_UNKNOWN_OPTION; break; case 8: /* Display variable */ if(strncasecompare(option, "XDISPLOC", 8)) { - strncpy(tn->subopt_xdisploc, arg, 127); - tn->subopt_xdisploc[127] = 0; /* String termination */ + tn->subopt_xdisploc = arg; tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; + break; } - else - result = CURLE_UNKNOWN_OPTION; + result = CURLE_UNKNOWN_OPTION; break; case 7: @@ -863,22 +875,20 @@ static CURLcode check_telnet_options(struct Curl_easy *data) case 2: /* Window Size */ if(strncasecompare(option, "WS", 2)) { - char *p; - unsigned long x = strtoul(arg, &p, 10); - unsigned long y = 0; - if(x && (x <= 0xffff) && Curl_raw_tolower(*p) == 'x') { - p++; - y = strtoul(p, NULL, 10); - if(y && (y <= 0xffff)) { - tn->subopt_wsx = (unsigned short)x; - tn->subopt_wsy = (unsigned short)y; - tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; - } - } - if(!y) { + const char *p = arg; + curl_off_t x = 0; + curl_off_t y = 0; + if(curlx_str_number(&p, &x, 0xffff) || + curlx_str_single(&p, 'x') || + curlx_str_number(&p, &y, 0xffff)) { failf(data, "Syntax error in telnet option: %s", head->data); result = CURLE_SETOPT_OPTION_SYNTAX; } + else { + tn->subopt_wsx = (unsigned short)x; + tn->subopt_wsy = (unsigned short)y; + tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; + } } else result = CURLE_UNKNOWN_OPTION; @@ -923,14 +933,13 @@ static CURLcode check_telnet_options(struct Curl_easy *data) * side. */ -static void suboption(struct Curl_easy *data) +static void suboption(struct Curl_easy *data, struct TELNET *tn) { struct curl_slist *v; unsigned char temp[2048]; ssize_t bytes_written; size_t len; int err; - struct TELNET *tn = data->req.p.telnet; struct connectdata *conn = data->conn; printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2); @@ -1002,13 +1011,13 @@ static void suboption(struct Curl_easy *data) * Send suboption information to the server side. */ -static void sendsuboption(struct Curl_easy *data, int option) +static void sendsuboption(struct Curl_easy *data, + struct TELNET *tn, int option) { ssize_t bytes_written; int err; unsigned short x, y; unsigned char *uc1, *uc2; - struct TELNET *tn = data->req.p.telnet; struct connectdata *conn = data->conn; switch(option) { @@ -1045,7 +1054,7 @@ static void sendsuboption(struct Curl_easy *data, int option) } /* ... then the window size with the send_telnet_data() function to deal with 0xFF cases ... */ - send_telnet_data(data, (char *)tn->subbuffer + 3, 4); + send_telnet_data(data, tn, (char *)tn->subbuffer + 3, 4); /* ... and the footer */ bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer + 7, 2); if(bytes_written < 0) { @@ -1059,6 +1068,7 @@ static void sendsuboption(struct Curl_easy *data, int option) static CURLcode telrcv(struct Curl_easy *data, + struct TELNET *tn, const unsigned char *inbuf, /* Data received from socket */ ssize_t count) /* Number of bytes received */ { @@ -1066,17 +1076,16 @@ CURLcode telrcv(struct Curl_easy *data, CURLcode result; int in = 0; int startwrite = -1; - struct TELNET *tn = data->req.p.telnet; - -#define startskipping() \ - if(startwrite >= 0) { \ - result = Curl_client_write(data, \ - CLIENTWRITE_BODY, \ - (char *)&inbuf[startwrite], \ - in-startwrite); \ - if(result) \ - return result; \ - } \ + +#define startskipping() \ + if(startwrite >= 0) { \ + result = Curl_client_write(data, \ + CLIENTWRITE_BODY, \ + (const char *)&inbuf[startwrite], \ + in-startwrite); \ + if(result) \ + return result; \ + } \ startwrite = -1 #define writebyte() \ @@ -1146,28 +1155,28 @@ CURLcode telrcv(struct Curl_easy *data, case CURL_TS_WILL: printoption(data, "RCVD", CURL_WILL, c); tn->please_negotiate = 1; - rec_will(data, c); + rec_will(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; case CURL_TS_WONT: printoption(data, "RCVD", CURL_WONT, c); tn->please_negotiate = 1; - rec_wont(data, c); + rec_wont(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; case CURL_TS_DO: printoption(data, "RCVD", CURL_DO, c); tn->please_negotiate = 1; - rec_do(data, c); + rec_do(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; case CURL_TS_DONT: printoption(data, "RCVD", CURL_DONT, c); tn->please_negotiate = 1; - rec_dont(data, c); + rec_dont(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; @@ -1182,12 +1191,12 @@ CURLcode telrcv(struct Curl_easy *data, if(c != CURL_SE) { if(c != CURL_IAC) { /* - * This is an error. We only expect to get "IAC IAC" or "IAC SE". - * Several things may have happened. An IAC was not doubled, the + * This is an error. We only expect to get "IAC IAC" or "IAC SE". + * Several things may have happened. An IAC was not doubled, the * IAC SE was left off, or another option got inserted into the - * suboption are all possibilities. If we assume that the IAC was + * suboption are all possibilities. If we assume that the IAC was * not doubled, and really the IAC SE was left off, we could get - * into an infinite loop here. So, instead, we terminate the + * into an infinite loop here. So, instead, we terminate the * suboption, and process the partial suboption if we can. */ CURL_SB_ACCUM(tn, CURL_IAC); @@ -1196,7 +1205,7 @@ CURLcode telrcv(struct Curl_easy *data, CURL_SB_TERM(tn); printoption(data, "In SUBOPTION processing, RCVD", CURL_IAC, c); - suboption(data); /* handle sub-option */ + suboption(data, tn); /* handle sub-option */ tn->telrcv_state = CURL_TS_IAC; goto process_iac; } @@ -1208,7 +1217,7 @@ CURLcode telrcv(struct Curl_easy *data, CURL_SB_ACCUM(tn, CURL_SE); tn->subpointer -= 2; CURL_SB_TERM(tn); - suboption(data); /* handle sub-option */ + suboption(data, tn); /* handle sub-option */ tn->telrcv_state = CURL_TS_DATA; } break; @@ -1221,39 +1230,39 @@ CURLcode telrcv(struct Curl_easy *data, /* Escape and send a telnet data block */ static CURLcode send_telnet_data(struct Curl_easy *data, + struct TELNET *tn, char *buffer, ssize_t nread) { - ssize_t escapes, i, outlen; - unsigned char *outbuf = NULL; + size_t i, outlen; + unsigned char *outbuf; CURLcode result = CURLE_OK; - ssize_t bytes_written, total_written; + size_t bytes_written; + size_t total_written = 0; struct connectdata *conn = data->conn; - /* Determine size of new buffer after escaping */ - escapes = 0; - for(i = 0; i < nread; i++) - if((unsigned char)buffer[i] == CURL_IAC) - escapes++; - outlen = nread + escapes; + DEBUGASSERT(tn); + DEBUGASSERT(nread > 0); + if(nread < 0) + return CURLE_TOO_LARGE; - if(outlen == nread) - outbuf = (unsigned char *)buffer; - else { - ssize_t j; - outbuf = malloc(nread + escapes + 1); - if(!outbuf) - return CURLE_OUT_OF_MEMORY; + if(memchr(buffer, CURL_IAC, nread)) { + /* only use the escape buffer when necessary */ + curlx_dyn_reset(&tn->out); - j = 0; - for(i = 0; i < nread; i++) { - outbuf[j++] = (unsigned char)buffer[i]; - if((unsigned char)buffer[i] == CURL_IAC) - outbuf[j++] = CURL_IAC; + for(i = 0; i < (size_t)nread && !result; i++) { + result = curlx_dyn_addn(&tn->out, &buffer[i], 1); + if(!result && ((unsigned char)buffer[i] == CURL_IAC)) + /* IAC is FF in hex */ + result = curlx_dyn_addn(&tn->out, "\xff", 1); } - outbuf[j] = '\0'; - } - total_written = 0; + outlen = curlx_dyn_len(&tn->out); + outbuf = curlx_dyn_uptr(&tn->out); + } + else { + outlen = (size_t)nread; + outbuf = (unsigned char *)buffer; + } while(!result && total_written < outlen) { /* Make sure socket is writable to avoid EWOULDBLOCK condition */ struct pollfd pfd[1]; @@ -1266,34 +1275,22 @@ static CURLcode send_telnet_data(struct Curl_easy *data, break; default: /* write! */ bytes_written = 0; - result = Curl_write(data, conn->sock[FIRSTSOCKET], - outbuf + total_written, - outlen - total_written, - &bytes_written); + result = Curl_xfer_send(data, outbuf + total_written, + outlen - total_written, FALSE, &bytes_written); total_written += bytes_written; break; } } - /* Free malloc copy if escaped */ - if(outbuf != (unsigned char *)buffer) - free(outbuf); - return result; } static CURLcode telnet_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct TELNET *tn = data->req.p.telnet; (void)status; /* unused */ (void)premature; /* not used */ - - if(!tn) - return CURLE_OK; - - curl_slist_free_all(tn->telnet_vars); - tn->telnet_vars = NULL; + Curl_meta_remove(data, CURL_META_TELNET_EASY); return CURLE_OK; } @@ -1321,7 +1318,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) ssize_t nread; struct curltime now; bool keepon = TRUE; - char *buf = data->state.buffer; + char buffer[4*1024]; struct TELNET *tn; *done = TRUE; /* unconditionally */ @@ -1330,15 +1327,17 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(result) return result; - tn = data->req.p.telnet; + tn = Curl_meta_get(data, CURL_META_TELNET_EASY); + if(!tn) + return CURLE_FAILED_INIT; - result = check_telnet_options(data); + result = check_telnet_options(data, tn); if(result) return result; #ifdef USE_WINSOCK /* We want to wait for both stdin and the socket. Since - ** the select() function in winsock only works on sockets + ** the select() function in Winsock only works on sockets ** we have to use the WaitForMultipleObjects() call. */ @@ -1349,7 +1348,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; } - /* Tell winsock what events we want to listen to */ + /* Tell Winsock what events we want to listen to */ if(WSAEventSelect(sockfd, event_handle, FD_READ|FD_CLOSE) == SOCKET_ERROR) { WSACloseEvent(event_handle); return CURLE_OK; @@ -1366,7 +1365,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) else use the old WaitForMultipleObjects() way */ if(GetFileType(stdin_handle) == FILE_TYPE_PIPE || data->set.is_fread_set) { - /* Don't wait for stdin_handle, just wait for event_handle */ + /* Do not wait for stdin_handle, just wait for event_handle */ obj_count = 1; /* Check stdin_handle per 100 milliseconds */ wait_timeout = 100; @@ -1378,7 +1377,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) /* Keep on listening and act on events */ while(keepon) { - const DWORD buf_size = (DWORD)data->set.buffer_size; + const DWORD buf_size = (DWORD)sizeof(buffer); DWORD waitret = WaitForMultipleObjects(obj_count, objs, FALSE, wait_timeout); switch(waitret) { @@ -1389,7 +1388,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(data->set.is_fread_set) { size_t n; /* read from user-supplied method */ - n = data->state.fread_func(buf, 1, buf_size, data->state.in); + n = data->state.fread_func(buffer, 1, buf_size, data->state.in); if(n == CURL_READFUNC_ABORT) { keepon = FALSE; result = CURLE_READ_ERROR; @@ -1417,7 +1416,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(!readfile_read) break; - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; @@ -1425,7 +1424,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } } - result = send_telnet_data(data, buf, readfile_read); + result = send_telnet_data(data, tn, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1436,14 +1435,14 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) case WAIT_OBJECT_0 + 1: { - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; break; } - result = send_telnet_data(data, buf, readfile_read); + result = send_telnet_data(data, tn, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1456,7 +1455,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) events.lNetworkEvents = 0; if(WSAEnumNetworkEvents(sockfd, event_handle, &events) == SOCKET_ERROR) { err = SOCKERRNO; - if(err != EINPROGRESS) { + if(err != SOCKEINPROGRESS) { infof(data, "WSAEnumNetworkEvents failed (%d)", err); keepon = FALSE; result = CURLE_READ_ERROR; @@ -1465,8 +1464,8 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(events.lNetworkEvents & FD_READ) { /* read data from network */ - result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); - /* read would've blocked. Loop again */ + result = Curl_xfer_recv(data, buffer, sizeof(buffer), &nread); + /* read would have blocked. Loop again */ if(result == CURLE_AGAIN) break; /* returned not-zero, this an error */ @@ -1481,17 +1480,17 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } - result = telrcv(data, (unsigned char *) buf, nread); + result = telrcv(data, tn, (unsigned char *) buffer, nread); if(result) { keepon = FALSE; break; } /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with + otherwise do not. We do not want to speak telnet with non-telnet servers, like POP or SMTP. */ if(tn->please_negotiate && !tn->already_negotiated) { - negotiate(data); + telnet_negotiate(data, tn); tn->already_negotiated = 1; } } @@ -1504,8 +1503,8 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(data->set.timeout) { - now = Curl_now(); - if(Curl_timediff(now, conn->created) >= data->set.timeout) { + now = curlx_now(); + if(curlx_timediff(now, conn->created) >= data->set.timeout) { failf(data, "Time-out"); result = CURLE_OPERATION_TIMEDOUT; keepon = FALSE; @@ -1531,35 +1530,38 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) pfd[1].events = POLLIN; poll_cnt = 2; interval_ms = 1 * 1000; + if(pfd[1].fd < 0) { + failf(data, "cannot read input"); + result = CURLE_RECV_ERROR; + keepon = FALSE; + } } while(keepon) { - DEBUGF(infof(data, "telnet_do(handle=%p), poll %d fds", data, poll_cnt)); - switch(Curl_poll(pfd, poll_cnt, interval_ms)) { + DEBUGF(infof(data, "telnet_do, poll %d fds", poll_cnt)); + switch(Curl_poll(pfd, (unsigned int)poll_cnt, interval_ms)) { case -1: /* error, stop reading */ keepon = FALSE; continue; case 0: /* timeout */ pfd[0].revents = 0; pfd[1].revents = 0; - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* read! */ if(pfd[0].revents & POLLIN) { /* read data from network */ - result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); - /* read would've blocked. Loop again */ + result = Curl_xfer_recv(data, buffer, sizeof(buffer), &nread); + /* read would have blocked. Loop again */ if(result == CURLE_AGAIN) break; /* returned not-zero, this an error */ if(result) { keepon = FALSE; - /* TODO: in test 1452, macOS sees a ECONNRESET sometimes? - * Is this the telnet test server not shutting down the socket - * in a clean way? Seems to be timing related, happens more - * on slow debug build */ - if(data->state.os_errno == ECONNRESET) { - DEBUGF(infof(data, "telnet_do(handle=%p), unexpected ECONNRESET" - " on recv", data)); + /* In test 1452, macOS sees a ECONNRESET sometimes? Is this the + * telnet test server not shutting down the socket in a clean way? + * Seems to be timing related, happens more on slow debug build */ + if(data->state.os_errno == SOCKECONNRESET) { + DEBUGF(infof(data, "telnet_do, unexpected ECONNRESET on recv")); } break; } @@ -1571,18 +1573,19 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } total_dl += nread; - Curl_pgrsSetDownloadCounter(data, total_dl); - result = telrcv(data, (unsigned char *)buf, nread); + result = Curl_pgrsSetDownloadCounter(data, total_dl); + if(!result) + result = telrcv(data, tn, (unsigned char *)buffer, nread); if(result) { keepon = FALSE; break; } /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with + otherwise do not. We do not want to speak telnet with non-telnet servers, like POP or SMTP. */ if(tn->please_negotiate && !tn->already_negotiated) { - negotiate(data); + telnet_negotiate(data, tn); tn->already_negotiated = 1; } } @@ -1590,12 +1593,12 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) nread = 0; if(poll_cnt == 2) { if(pfd[1].revents & POLLIN) { /* read from in file */ - nread = read(pfd[1].fd, buf, data->set.buffer_size); + nread = read(pfd[1].fd, buffer, sizeof(buffer)); } } else { /* read from user-supplied method */ - nread = (int)data->state.fread_func(buf, 1, data->set.buffer_size, + nread = (int)data->state.fread_func(buffer, 1, sizeof(buffer), data->state.in); if(nread == CURL_READFUNC_ABORT) { keepon = FALSE; @@ -1606,7 +1609,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(nread > 0) { - result = send_telnet_data(data, buf, nread); + result = send_telnet_data(data, tn, buffer, nread); if(result) { keepon = FALSE; break; @@ -1621,8 +1624,8 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } /* poll switch statement */ if(data->set.timeout) { - now = Curl_now(); - if(Curl_timediff(now, conn->created) >= data->set.timeout) { + now = curlx_now(); + if(curlx_timediff(now, conn->created) >= data->set.timeout) { failf(data, "Time-out"); result = CURLE_OPERATION_TIMEDOUT; keepon = FALSE; @@ -1636,7 +1639,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } #endif /* mark this as "no further transfer wanted" */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); return result; } diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c index 8ed1b887b4d..e9f4e68eccb 100644 --- a/Utilities/cmcurl/lib/tftp.c +++ b/Utilities/cmcurl/lib/tftp.c @@ -62,6 +62,7 @@ #include "speedcheck.h" #include "select.h" #include "escape.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -70,8 +71,6 @@ /* RFC2348 allows the block size to be negotiated */ #define TFTP_BLKSIZE_DEFAULT 512 -#define TFTP_BLKSIZE_MIN 8 -#define TFTP_BLKSIZE_MAX 65464 #define TFTP_OPTION_BLKSIZE "blksize" /* from RFC2349: */ @@ -122,7 +121,10 @@ struct tftp_packet { unsigned char *data; }; -struct tftp_state_data { +/* meta key for storing protocol meta at connection */ +#define CURL_META_TFTP_CONN "meta:proto:tftp:conn" + +struct tftp_conn { tftp_state_t state; tftp_mode_t mode; tftp_error_t error; @@ -137,9 +139,9 @@ struct tftp_state_data { struct Curl_sockaddr_storage remote_addr; curl_socklen_t remote_addrlen; int rbytes; - int sbytes; - int blksize; - int requested_blksize; + size_t sbytes; + unsigned int blksize; + unsigned int requested_blksize; unsigned short block; struct tftp_packet rpacket; struct tftp_packet spacket; @@ -147,12 +149,9 @@ struct tftp_state_data { /* Forward declarations */ -static CURLcode tftp_rx(struct tftp_state_data *state, tftp_event_t event); -static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event); +static CURLcode tftp_rx(struct tftp_conn *state, tftp_event_t event); +static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event); static CURLcode tftp_connect(struct Curl_easy *data, bool *done); -static CURLcode tftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection); static CURLcode tftp_do(struct Curl_easy *data, bool *done); static CURLcode tftp_done(struct Curl_easy *data, CURLcode, bool premature); @@ -170,7 +169,7 @@ static CURLcode tftp_translate_code(tftp_error_t error); */ const struct Curl_handler Curl_handler_tftp = { - "TFTP", /* scheme */ + "tftp", /* scheme */ tftp_setup_connection, /* setup_connection */ tftp_do, /* do_it */ tftp_done, /* done */ @@ -182,10 +181,12 @@ const struct Curl_handler Curl_handler_tftp = { tftp_getsock, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - tftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_TFTP, /* defport */ CURLPROTO_TFTP, /* protocol */ CURLPROTO_TFTP, /* family */ @@ -202,11 +203,11 @@ const struct Curl_handler Curl_handler_tftp = { * * **********************************************************/ -static CURLcode tftp_set_timeouts(struct tftp_state_data *state) +static CURLcode tftp_set_timeouts(struct tftp_conn *state) { time_t maxtime, timeout; timediff_t timeout_ms; - bool start = (state->state == TFTP_STATE_START) ? TRUE : FALSE; + bool start = (state->state == TFTP_STATE_START); /* Compute drop-dead time */ timeout_ms = Curl_timeleft(state->data, NULL, start); @@ -229,24 +230,23 @@ static CURLcode tftp_set_timeouts(struct tftp_state_data *state) state->retry_max = (int)timeout/5; /* But bound the total number */ - if(state->retry_max<3) + if(state->retry_max < 3) state->retry_max = 3; - if(state->retry_max>50) + if(state->retry_max > 50) state->retry_max = 50; /* Compute the re-ACK interval to suit the timeout */ state->retry_time = (int)(timeout/state->retry_max); - if(state->retry_time<1) + if(state->retry_time < 1) state->retry_time = 1; infof(state->data, - "set timeouts for state %d; Total % " CURL_FORMAT_CURL_OFF_T - ", retry %d maxtry %d", + "set timeouts for state %d; Total % " FMT_OFF_T ", retry %d maxtry %d", (int)state->state, timeout_ms, state->retry_time, state->retry_max); /* init RX time */ - time(&state->rx_time); + state->rx_time = time(NULL); return CURLE_OK; } @@ -310,13 +310,13 @@ static const char *tftp_option_get(const char *buf, size_t len, return &buf[loc]; } -static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, +static CURLcode tftp_parse_option_ack(struct tftp_conn *state, const char *ptr, int len) { const char *tmp = ptr; struct Curl_easy *data = state->data; - /* if OACK doesn't contain blksize option, the default (512) must be used */ + /* if OACK does not contain blksize option, the default (512) must be used */ state->blksize = TFTP_BLKSIZE_DEFAULT; while(tmp < ptr + len) { @@ -331,50 +331,46 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, infof(data, "got option=(%s) value=(%s)", option, value); if(checkprefix(TFTP_OPTION_BLKSIZE, option)) { - long blksize; - - blksize = strtol(value, NULL, 10); - - if(!blksize) { - failf(data, "invalid blocksize value in OACK packet"); - return CURLE_TFTP_ILLEGAL; - } - if(blksize > TFTP_BLKSIZE_MAX) { + curl_off_t blksize; + if(curlx_str_number(&value, &blksize, TFTP_BLKSIZE_MAX)) { failf(data, "%s (%d)", "blksize is larger than max supported", TFTP_BLKSIZE_MAX); return CURLE_TFTP_ILLEGAL; } + if(!blksize) { + failf(data, "invalid blocksize value in OACK packet"); + return CURLE_TFTP_ILLEGAL; + } else if(blksize < TFTP_BLKSIZE_MIN) { failf(data, "%s (%d)", "blksize is smaller than min supported", TFTP_BLKSIZE_MIN); return CURLE_TFTP_ILLEGAL; } else if(blksize > state->requested_blksize) { - /* could realloc pkt buffers here, but the spec doesn't call out + /* could realloc pkt buffers here, but the spec does not call out * support for the server requesting a bigger blksize than the client * requests */ - failf(data, "%s (%ld)", - "server requested blksize larger than allocated", blksize); + failf(data, "server requested blksize larger than allocated (%" + CURL_FORMAT_CURL_OFF_T ")", blksize); return CURLE_TFTP_ILLEGAL; } state->blksize = (int)blksize; - infof(data, "%s (%d) %s (%d)", "blksize parsed from OACK", - state->blksize, "requested", state->requested_blksize); + infof(data, "blksize parsed from OACK (%d) requested (%d)", + state->blksize, state->requested_blksize); } else if(checkprefix(TFTP_OPTION_TSIZE, option)) { - long tsize = 0; - - tsize = strtol(value, NULL, 10); - infof(data, "%s (%ld)", "tsize parsed from OACK", tsize); - + curl_off_t tsize = 0; /* tsize should be ignored on upload: Who cares about the size of the remote file? */ - if(!data->state.upload) { + if(!data->state.upload && + !curlx_str_number(&value, &tsize, CURL_OFF_T_MAX)) { if(!tsize) { failf(data, "invalid tsize -:%s:- value in OACK packet", value); return CURLE_TFTP_ILLEGAL; } + infof(data, "tsize parsed from OACK (%" CURL_FORMAT_CURL_OFF_T ")", + tsize); Curl_pgrsSetDownloadSize(data, tsize); } } @@ -383,7 +379,7 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, return CURLE_OK; } -static CURLcode tftp_option_add(struct tftp_state_data *state, size_t *csize, +static CURLcode tftp_option_add(struct tftp_conn *state, size_t *csize, char *buf, const char *option) { if(( strlen(option) + *csize + 1) > (size_t)state->blksize) @@ -393,7 +389,7 @@ static CURLcode tftp_option_add(struct tftp_state_data *state, size_t *csize, return CURLE_OK; } -static CURLcode tftp_connect_for_tx(struct tftp_state_data *state, +static CURLcode tftp_connect_for_tx(struct tftp_conn *state, tftp_event_t event) { CURLcode result; @@ -409,7 +405,7 @@ static CURLcode tftp_connect_for_tx(struct tftp_state_data *state, return tftp_tx(state, event); } -static CURLcode tftp_connect_for_rx(struct tftp_state_data *state, +static CURLcode tftp_connect_for_rx(struct tftp_conn *state, tftp_event_t event) { CURLcode result; @@ -425,7 +421,7 @@ static CURLcode tftp_connect_for_rx(struct tftp_state_data *state, return tftp_rx(state, event); } -static CURLcode tftp_send_first(struct tftp_state_data *state, +static CURLcode tftp_send_first(struct tftp_conn *state, tftp_event_t event) { size_t sbytes; @@ -435,7 +431,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, struct Curl_easy *data = state->data; CURLcode result = CURLE_OK; - /* Set ascii mode if -B flag was used */ + /* Set ASCII mode if -B flag was used */ if(data->state.prefer_ascii) mode = "netascii"; @@ -445,7 +441,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, case TFTP_EVENT_TIMEOUT: /* Resend the first packet out */ /* Increment the retry counter, quit if over the limit */ state->retries++; - if(state->retries>state->retry_max) { + if(state->retries > state->retry_max) { state->error = TFTP_ERR_NORESPONSE; state->state = TFTP_STATE_FIN; return result; @@ -454,8 +450,6 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, if(data->state.upload) { /* If we are uploading, send an WRQ */ setpacketevent(&state->spacket, TFTP_EVENT_WRQ); - state->data->req.upload_fromhere = - (char *)state->spacket.data + 4; if(data->state.infilesize != -1) Curl_pgrsSetUploadSize(data, data->state.infilesize); } @@ -464,7 +458,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, setpacketevent(&state->spacket, TFTP_EVENT_RRQ); } /* As RFC3617 describes the separator slash is not actually part of the - file name so we skip the always-present first letter of the path + filename so we skip the always-present first letter of the path string. */ result = Curl_urldecode(&state->data->state.up.path[1], 0, &filename, NULL, REJECT_ZERO); @@ -472,9 +466,9 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, return result; if(strlen(filename) > (state->blksize - strlen(mode) - 4)) { - failf(data, "TFTP file name too long"); + failf(data, "TFTP filename too long"); free(filename); - return CURLE_TFTP_ILLEGAL; /* too long file name field */ + return CURLE_TFTP_ILLEGAL; /* too long filename field */ } msnprintf((char *)state->spacket.data + 2, @@ -486,11 +480,9 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, if(!data->set.tftp_no_options) { char buf[64]; /* add tsize option */ - if(data->state.upload && (data->state.infilesize != -1)) - msnprintf(buf, sizeof(buf), "%" CURL_FORMAT_CURL_OFF_T, - data->state.infilesize); - else - strcpy(buf, "0"); /* the destination is large enough */ + msnprintf(buf, sizeof(buf), "%" FMT_OFF_T, + data->state.upload && (data->state.infilesize != -1) ? + data->state.infilesize : 0); result = tftp_option_add(state, &sbytes, (char *)state->spacket.data + sbytes, @@ -528,10 +520,15 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, /* the typecase for the 3rd argument is mostly for systems that do not have a size_t argument, like older unixes that want an 'int' */ +#ifdef __AMIGA__ +#define CURL_SENDTO_ARG5(x) CURL_UNCONST(x) +#else +#define CURL_SENDTO_ARG5(x) (x) +#endif senddata = sendto(state->sockfd, (void *)state->spacket.data, (SEND_TYPE_ARG3)sbytes, 0, - &data->conn->remote_addr->sa_addr, - data->conn->remote_addr->addrlen); + CURL_SENDTO_ARG5(&data->conn->remote_addr->curl_sa_addr), + (curl_socklen_t)data->conn->remote_addr->addrlen); if(senddata != (ssize_t)sbytes) { char buffer[STRERROR_LEN]; failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); @@ -579,8 +576,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, * Event handler for the RX state * **********************************************************/ -static CURLcode tftp_rx(struct tftp_state_data *state, - tftp_event_t event) +static CURLcode tftp_rx(struct tftp_conn *state, tftp_event_t event) { ssize_t sbytes; int rblock; @@ -593,7 +589,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, /* Is this the block we expect? */ rblock = getrpacketblock(&state->rpacket); if(NEXT_BLOCKNUM(state->block) == rblock) { - /* This is the expected block. Reset counters and ACK it. */ + /* This is the expected block. Reset counters and ACK it. */ state->retries = 0; } else if(state->block == rblock) { @@ -629,7 +625,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, else { state->state = TFTP_STATE_RX; } - time(&state->rx_time); + state->rx_time = time(NULL); break; case TFTP_EVENT_OACK: @@ -647,16 +643,16 @@ static CURLcode tftp_rx(struct tftp_state_data *state, return CURLE_SEND_ERROR; } - /* we're ready to RX data */ + /* we are ready to RX data */ state->state = TFTP_STATE_RX; - time(&state->rx_time); + state->rx_time = time(NULL); break; case TFTP_EVENT_TIMEOUT: /* Increment the retry count and fail if over the limit */ state->retries++; infof(data, - "Timeout waiting for block %d ACK. Retries = %d", + "Timeout waiting for block %d ACK. Retries = %d", NEXT_BLOCKNUM(state->block), state->retries); if(state->retries > state->retry_max) { state->error = TFTP_ERR_TIMEOUT; @@ -668,7 +664,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, 4, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } @@ -682,8 +678,8 @@ static CURLcode tftp_rx(struct tftp_state_data *state, 4, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); - /* don't bother with the return code, but if the socket is still up we - * should be a good TFTP client and let the server know we're done */ + /* do not bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we are done */ state->state = TFTP_STATE_FIN; break; @@ -702,7 +698,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, * Event handler for the TX state * **********************************************************/ -static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) +static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event) { struct Curl_easy *data = state->data; ssize_t sbytes; @@ -710,6 +706,8 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) struct SingleRequest *k = &data->req; size_t cb; /* Bytes currently read */ char buffer[STRERROR_LEN]; + char *bufptr; + bool eos; switch(event) { @@ -720,18 +718,18 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) int rblock = getrpacketblock(&state->rpacket); if(rblock != state->block && - /* There's a bug in tftpd-hpa that causes it to send us an ack for - * 65535 when the block number wraps to 0. So when we're expecting + /* There is a bug in tftpd-hpa that causes it to send us an ack for + * 65535 when the block number wraps to 0. So when we are expecting * 0, also accept 65535. See * https://www.syslinux.org/archives/2010-September/015612.html * */ !(state->block == 0 && rblock == 65535)) { - /* This isn't the expected block. Log it and up the retry counter */ + /* This is not the expected block. Log it and up the retry counter */ infof(data, "Received ACK for block %d, expecting %d", rblock, state->block); state->retries++; /* Bail out if over the maximum */ - if(state->retries>state->retry_max) { + if(state->retries > state->retry_max) { failf(data, "tftp_tx: giving up waiting for block %d ack", state->block); result = CURLE_SEND_ERROR; @@ -739,11 +737,11 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) else { /* Re-send the data packet */ sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4 + state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); /* Check all sbytes were sent */ - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); result = CURLE_SEND_ERROR; @@ -752,9 +750,9 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) return result; } - /* This is the expected packet. Reset the counters and send the next + /* This is the expected packet. Reset the counters and send the next block */ - time(&state->rx_time); + state->rx_time = time(NULL); state->block++; } else @@ -773,21 +771,22 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) * data block. * */ state->sbytes = 0; - state->data->req.upload_fromhere = (char *)state->spacket.data + 4; + bufptr = (char *)state->spacket.data + 4; do { - result = Curl_fillreadbuffer(data, state->blksize - state->sbytes, &cb); + result = Curl_client_read(data, bufptr, state->blksize - state->sbytes, + &cb, &eos); if(result) return result; - state->sbytes += (int)cb; - state->data->req.upload_fromhere += cb; + state->sbytes += cb; + bufptr += cb; } while(state->sbytes < state->blksize && cb); sbytes = sendto(state->sockfd, (void *) state->spacket.data, - 4 + state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); /* Check all sbytes were sent */ - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } @@ -801,7 +800,7 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) state->retries++; infof(data, "Timeout waiting for block %d ACK. " " Retries = %d", NEXT_BLOCKNUM(state->block), state->retries); - /* Decide if we've had enough */ + /* Decide if we have had enough */ if(state->retries > state->retry_max) { state->error = TFTP_ERR_TIMEOUT; state->state = TFTP_STATE_FIN; @@ -809,11 +808,11 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) else { /* Re-send the data packet */ sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4 + state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); /* Check all sbytes were sent */ - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } @@ -829,8 +828,8 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) (void)sendto(state->sockfd, (void *)state->spacket.data, 4, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); - /* don't bother with the return code, but if the socket is still up we - * should be a good TFTP client and let the server know we're done */ + /* do not bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we are done */ state->state = TFTP_STATE_FIN; break; @@ -901,7 +900,7 @@ static CURLcode tftp_translate_code(tftp_error_t error) * The tftp state machine event dispatcher * **********************************************************/ -static CURLcode tftp_state_machine(struct tftp_state_data *state, +static CURLcode tftp_state_machine(struct tftp_conn *state, tftp_event_t event) { CURLcode result = CURLE_OK; @@ -933,28 +932,14 @@ static CURLcode tftp_state_machine(struct tftp_state_data *state, return result; } -/********************************************************** - * - * tftp_disconnect - * - * The disconnect callback - * - **********************************************************/ -static CURLcode tftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection) +static void tftp_conn_dtor(void *key, size_t klen, void *entry) { - struct tftp_state_data *state = conn->proto.tftpc; - (void) data; - (void) dead_connection; - - /* done, free dynamically allocated pkt buffers */ - if(state) { - Curl_safefree(state->rpacket.data); - Curl_safefree(state->spacket.data); - free(state); - } - - return CURLE_OK; + struct tftp_conn *state = entry; + (void)key; + (void)klen; + Curl_safefree(state->rpacket.data); + Curl_safefree(state->spacket.data); + free(state); } /********************************************************** @@ -966,23 +951,22 @@ static CURLcode tftp_disconnect(struct Curl_easy *data, **********************************************************/ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) { - struct tftp_state_data *state; + struct tftp_conn *state; int blksize; int need_blksize; struct connectdata *conn = data->conn; blksize = TFTP_BLKSIZE_DEFAULT; - state = conn->proto.tftpc = calloc(1, sizeof(struct tftp_state_data)); - if(!state) + state = calloc(1, sizeof(*state)); + if(!state || + Curl_conn_meta_set(conn, CURL_META_TFTP_CONN, state, tftp_conn_dtor)) return CURLE_OUT_OF_MEMORY; /* alloc pkt buffers based on specified blksize */ - if(data->set.tftp_blksize) { + if(data->set.tftp_blksize) + /* range checked when set */ blksize = (int)data->set.tftp_blksize; - if(blksize > TFTP_BLKSIZE_MAX || blksize < TFTP_BLKSIZE_MIN) - return CURLE_TFTP_ILLEGAL; - } need_blksize = blksize; /* default size is the fallback when no OACK is received */ @@ -1003,7 +987,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) return CURLE_OUT_OF_MEMORY; } - /* we don't keep TFTP connections up basically because there's none or very + /* we do not keep TFTP connections up basically because there is none or * little gain for UDP */ connclose(conn, "TFTP"); @@ -1034,7 +1018,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) * IPv4 and IPv6... */ int rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, - conn->remote_addr->addrlen); + (curl_socklen_t)conn->remote_addr->addrlen); if(rc) { char buffer[STRERROR_LEN]; failf(data, "bind() failed; %s", @@ -1063,7 +1047,7 @@ static CURLcode tftp_done(struct Curl_easy *data, CURLcode status, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); (void)status; /* unused */ (void)premature; /* not used */ @@ -1100,27 +1084,21 @@ static int tftp_getsock(struct Curl_easy *data, * Called once select fires and data is ready on the socket * **********************************************************/ -static CURLcode tftp_receive_packet(struct Curl_easy *data) +static CURLcode tftp_receive_packet(struct Curl_easy *data, + struct tftp_conn *state) { - struct Curl_sockaddr_storage fromaddr; curl_socklen_t fromlen; CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; - struct SingleRequest *k = &data->req; /* Receive the packet */ - fromlen = sizeof(fromaddr); + fromlen = sizeof(state->remote_addr); state->rbytes = (int)recvfrom(state->sockfd, (void *)state->rpacket.data, - state->blksize + 4, + (RECV_TYPE_ARG3)state->blksize + 4, 0, - (struct sockaddr *)&fromaddr, + (struct sockaddr *)&state->remote_addr, &fromlen); - if(state->remote_addrlen == 0) { - memcpy(&state->remote_addr, &fromaddr, fromlen); - state->remote_addrlen = fromlen; - } + state->remote_addrlen = fromlen; /* Sanity check packet length */ if(state->rbytes < 4) { @@ -1135,7 +1113,7 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data) switch(state->event) { case TFTP_EVENT_DATA: - /* Don't pass to the client empty or retransmitted packets */ + /* Do not pass to the client empty or retransmitted packets */ if(state->rbytes > 4 && (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { result = Curl_client_write(data, CLIENTWRITE_BODY, @@ -1145,8 +1123,6 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data) tftp_state_machine(state, TFTP_EVENT_ERROR); return result; } - k->bytecount += state->rbytes-4; - Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount); } break; case TFTP_EVENT_ERROR: @@ -1191,12 +1167,10 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data) * Check if timeouts have been reached * **********************************************************/ -static timediff_t tftp_state_timeout(struct Curl_easy *data, +static timediff_t tftp_state_timeout(struct tftp_conn *state, tftp_event_t *event) { time_t current; - struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; timediff_t timeout_ms; if(event) @@ -1209,11 +1183,11 @@ static timediff_t tftp_state_timeout(struct Curl_easy *data, state->state = TFTP_STATE_FIN; return 0; } - time(¤t); + current = time(NULL); if(current > state->rx_time + state->retry_time) { if(event) *event = TFTP_EVENT_TIMEOUT; - time(&state->rx_time); /* update even though we received nothing */ + state->rx_time = time(NULL); /* update even though we received nothing */ } return timeout_ms; @@ -1231,11 +1205,14 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) tftp_event_t event; CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; - timediff_t timeout_ms = tftp_state_timeout(data, &event); + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); + timediff_t timeout_ms; *done = FALSE; + if(!state) + return CURLE_FAILED_INIT; + timeout_ms = tftp_state_timeout(state, &event); if(timeout_ms < 0) { failf(data, "TFTP response timeout"); return CURLE_OPERATION_TIMEDOUT; @@ -1244,10 +1221,10 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) result = tftp_state_machine(state, event); if(result) return result; - *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + *done = (state->state == TFTP_STATE_FIN); if(*done) - /* Tell curl we're done */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + /* Tell curl we are done */ + Curl_xfer_setup_nop(data); } else { /* no timeouts to handle, check our socket */ @@ -1261,16 +1238,16 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) state->event = TFTP_EVENT_ERROR; } else if(rc) { - result = tftp_receive_packet(data); + result = tftp_receive_packet(data, state); if(result) return result; result = tftp_state_machine(state, state->event); if(result) return result; - *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + *done = (state->state == TFTP_STATE_FIN); if(*done) - /* Tell curl we're done */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + /* Tell curl we are done */ + Curl_xfer_setup_nop(data); } /* if rc == 0, then select() timed out */ } @@ -1294,13 +1271,13 @@ static CURLcode tftp_doing(struct Curl_easy *data, bool *dophase_done) DEBUGF(infof(data, "DO phase is complete")); } else if(!result) { - /* The multi code doesn't have this logic for the DOING state so we + /* The multi code does not have this logic for the DOING state so we provide it for TFTP since it may do the entire transfer in this state. */ if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_now()); + result = Curl_speedcheck(data, curlx_now()); } return result; } @@ -1316,9 +1293,11 @@ static CURLcode tftp_perform(struct Curl_easy *data, bool *dophase_done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); *dophase_done = FALSE; + if(!state) + return CURLE_FAILED_INIT; result = tftp_state_machine(state, TFTP_EVENT_INIT); @@ -1346,21 +1325,21 @@ static CURLcode tftp_perform(struct Curl_easy *data, bool *dophase_done) static CURLcode tftp_do(struct Curl_easy *data, bool *done) { - struct tftp_state_data *state; - CURLcode result; struct connectdata *conn = data->conn; + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); + CURLcode result; *done = FALSE; - if(!conn->proto.tftpc) { + if(!state) { result = tftp_connect(data, done); if(result) return result; - } - state = conn->proto.tftpc; - if(!state) - return CURLE_TFTP_ILLEGAL; + state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); + if(!state) + return CURLE_TFTP_ILLEGAL; + } result = tftp_perform(data, done); @@ -1381,7 +1360,7 @@ static CURLcode tftp_setup_connection(struct Curl_easy *data, conn->transport = TRNSPRT_UDP; /* TFTP URLs support an extension like ";mode=" that - * we'll try to get now! */ + * we will try to get now! */ type = strstr(data->state.up.path, ";mode="); if(!type) diff --git a/Utilities/cmcurl/lib/tftp.h b/Utilities/cmcurl/lib/tftp.h index 5d2d5da6151..12404bf6d20 100644 --- a/Utilities/cmcurl/lib/tftp.h +++ b/Utilities/cmcurl/lib/tftp.h @@ -25,6 +25,9 @@ ***************************************************************************/ #ifndef CURL_DISABLE_TFTP extern const struct Curl_handler Curl_handler_tftp; + +#define TFTP_BLKSIZE_MIN 8 +#define TFTP_BLKSIZE_MAX 65464 #endif #endif /* HEADER_CURL_TFTP_H */ diff --git a/Utilities/cmcurl/lib/timeval.c b/Utilities/cmcurl/lib/timeval.c deleted file mode 100644 index dca1c6fe50a..00000000000 --- a/Utilities/cmcurl/lib/timeval.c +++ /dev/null @@ -1,210 +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 "timeval.h" - -#if defined(WIN32) && !defined(MSDOS) - -/* set in win32_init() */ -extern LARGE_INTEGER Curl_freq; -extern bool Curl_isVistaOrGreater; - -/* In case of bug fix this function has a counterpart in tool_util.c */ -struct curltime Curl_now(void) -{ - struct curltime now; - if(Curl_isVistaOrGreater) { /* QPC timer might have issues pre-Vista */ - LARGE_INTEGER count; - QueryPerformanceCounter(&count); - now.tv_sec = (time_t)(count.QuadPart / Curl_freq.QuadPart); - now.tv_usec = (int)((count.QuadPart % Curl_freq.QuadPart) * 1000000 / - Curl_freq.QuadPart); - } - else { - /* Disable /analyze warning that GetTickCount64 is preferred */ -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:28159) -#endif - DWORD milliseconds = GetTickCount(); -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - - now.tv_sec = milliseconds / 1000; - now.tv_usec = (milliseconds % 1000) * 1000; - } - return now; -} - -#elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) - -struct curltime Curl_now(void) -{ - /* - ** clock_gettime() is granted to be increased monotonically when the - ** monotonic clock is queried. Time starting point is unspecified, it - ** could be the system start-up time, the Epoch, or something else, - ** in any case the time starting point does not change once that the - ** system has started up. - */ -#ifdef HAVE_GETTIMEOFDAY - struct timeval now; -#endif - struct curltime cnow; - struct timespec tsnow; - - /* - ** clock_gettime() may be defined by Apple's SDK as weak symbol thus - ** code compiles but fails during run-time if clock_gettime() is - ** called on unsupported OS version. - */ -#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ - (HAVE_BUILTIN_AVAILABLE == 1) - bool have_clock_gettime = FALSE; - if(__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) - have_clock_gettime = TRUE; -#endif - - if( -#if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ - (HAVE_BUILTIN_AVAILABLE == 1) - have_clock_gettime && -#endif - (0 == clock_gettime(CLOCK_MONOTONIC, &tsnow))) { - cnow.tv_sec = tsnow.tv_sec; - cnow.tv_usec = (unsigned int)(tsnow.tv_nsec / 1000); - } - /* - ** Even when the configure process has truly detected monotonic clock - ** availability, it might happen that it is not actually available at - ** run-time. When this occurs simply fallback to other time source. - */ -#ifdef HAVE_GETTIMEOFDAY - else { - (void)gettimeofday(&now, NULL); - cnow.tv_sec = now.tv_sec; - cnow.tv_usec = (unsigned int)now.tv_usec; - } -#else - else { - cnow.tv_sec = time(NULL); - cnow.tv_usec = 0; - } -#endif - return cnow; -} - -#elif defined(HAVE_MACH_ABSOLUTE_TIME) - -#include -#include - -struct curltime Curl_now(void) -{ - /* - ** Monotonic timer on Mac OS is provided by mach_absolute_time(), which - ** returns time in Mach "absolute time units," which are platform-dependent. - ** To convert to nanoseconds, one must use conversion factors specified by - ** mach_timebase_info(). - */ - static mach_timebase_info_data_t timebase; - struct curltime cnow; - uint64_t usecs; - - if(0 == timebase.denom) - (void) mach_timebase_info(&timebase); - - usecs = mach_absolute_time(); - usecs *= timebase.numer; - usecs /= timebase.denom; - usecs /= 1000; - - cnow.tv_sec = usecs / 1000000; - cnow.tv_usec = (int)(usecs % 1000000); - - return cnow; -} - -#elif defined(HAVE_GETTIMEOFDAY) - -struct curltime Curl_now(void) -{ - /* - ** gettimeofday() is not granted to be increased monotonically, due to - ** clock drifting and external source time synchronization it can jump - ** forward or backward in time. - */ - struct timeval now; - struct curltime ret; - (void)gettimeofday(&now, NULL); - ret.tv_sec = now.tv_sec; - ret.tv_usec = (int)now.tv_usec; - return ret; -} - -#else - -struct curltime Curl_now(void) -{ - /* - ** time() returns the value of time in seconds since the Epoch. - */ - struct curltime now; - now.tv_sec = time(NULL); - now.tv_usec = 0; - return now; -} - -#endif - -/* - * Returns: time difference in number of milliseconds. For too large diffs it - * returns max value. - * - * @unittest: 1323 - */ -timediff_t Curl_timediff(struct curltime newer, struct curltime older) -{ - timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; - if(diff >= (TIMEDIFF_T_MAX/1000)) - return TIMEDIFF_T_MAX; - else if(diff <= (TIMEDIFF_T_MIN/1000)) - return TIMEDIFF_T_MIN; - return diff * 1000 + (newer.tv_usec-older.tv_usec)/1000; -} - -/* - * Returns: time difference in number of microseconds. For too large diffs it - * returns max value. - */ -timediff_t Curl_timediff_us(struct curltime newer, struct curltime older) -{ - timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; - if(diff >= (TIMEDIFF_T_MAX/1000000)) - return TIMEDIFF_T_MAX; - else if(diff <= (TIMEDIFF_T_MIN/1000000)) - return TIMEDIFF_T_MIN; - return diff * 1000000 + newer.tv_usec-older.tv_usec; -} diff --git a/Utilities/cmcurl/lib/timeval.h b/Utilities/cmcurl/lib/timeval.h deleted file mode 100644 index 92e484ad1db..00000000000 --- a/Utilities/cmcurl/lib/timeval.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef HEADER_CURL_TIMEVAL_H -#define HEADER_CURL_TIMEVAL_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" - -#include "timediff.h" - -struct curltime { - time_t tv_sec; /* seconds */ - int tv_usec; /* microseconds */ -}; - -struct curltime Curl_now(void); - -/* - * Make sure that the first argument (t1) is the more recent time and t2 is - * the older time, as otherwise you get a weird negative time-diff back... - * - * Returns: the time difference in number of milliseconds. - */ -timediff_t Curl_timediff(struct curltime t1, struct curltime t2); - -/* - * Make sure that the first argument (t1) is the more recent time and t2 is - * the older time, as otherwise you get a weird negative time-diff back... - * - * Returns: the time difference in number of microseconds. - */ -timediff_t Curl_timediff_us(struct curltime newer, struct curltime older); - -#endif /* HEADER_CURL_TIMEVAL_H */ diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c index d2ff0c24c2d..ef7d7f19af4 100644 --- a/Utilities/cmcurl/lib/transfer.c +++ b/Utilities/cmcurl/lib/transfer.c @@ -23,7 +23,6 @@ ***************************************************************************/ #include "curl_setup.h" -#include "strtoofft.h" #ifdef HAVE_NETINET_IN_H #include @@ -40,7 +39,7 @@ #ifdef HAVE_SYS_IOCTL_H #include #endif -#ifdef HAVE_SIGNAL_H +#ifndef UNDER_CE #include #endif @@ -55,7 +54,7 @@ #endif #ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" +#error "We cannot compile without socket() support!" #endif #include "urldata.h" @@ -65,6 +64,7 @@ #include "content_encoding.h" #include "hostip.h" #include "cfilters.h" +#include "cw-out.h" #include "transfer.h" #include "sendf.h" #include "speedcheck.h" @@ -80,7 +80,6 @@ #include "http2.h" #include "mime.h" #include "strcase.h" -#include "urlapi-int.h" #include "hsts.h" #include "setopt.h" #include "headers.h" @@ -116,254 +115,6 @@ char *Curl_checkheaders(const struct Curl_easy *data, } #endif -CURLcode Curl_get_upload_buffer(struct Curl_easy *data) -{ - if(!data->state.ulbuf) { - data->state.ulbuf = malloc(data->set.upload_buffer_size); - if(!data->state.ulbuf) - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} - -#ifndef CURL_DISABLE_HTTP -/* - * This function will be called to loop through the trailers buffer - * until no more data is available for sending. - */ -static size_t trailers_read(char *buffer, size_t size, size_t nitems, - void *raw) -{ - struct Curl_easy *data = (struct Curl_easy *)raw; - struct dynbuf *trailers_buf = &data->state.trailers_buf; - size_t bytes_left = Curl_dyn_len(trailers_buf) - - data->state.trailers_bytes_sent; - size_t to_copy = (size*nitems < bytes_left) ? size*nitems : bytes_left; - if(to_copy) { - memcpy(buffer, - Curl_dyn_ptr(trailers_buf) + data->state.trailers_bytes_sent, - to_copy); - data->state.trailers_bytes_sent += to_copy; - } - return to_copy; -} - -static size_t trailers_left(void *raw) -{ - struct Curl_easy *data = (struct Curl_easy *)raw; - struct dynbuf *trailers_buf = &data->state.trailers_buf; - return Curl_dyn_len(trailers_buf) - data->state.trailers_bytes_sent; -} -#endif - -/* - * This function will call the read callback to fill our buffer with data - * to upload. - */ -CURLcode Curl_fillreadbuffer(struct Curl_easy *data, size_t bytes, - size_t *nreadp) -{ - size_t buffersize = bytes; - size_t nread; - - curl_read_callback readfunc = NULL; - void *extra_data = NULL; - -#ifndef CURL_DISABLE_HTTP - if(data->state.trailers_state == TRAILERS_INITIALIZED) { - struct curl_slist *trailers = NULL; - CURLcode result; - int trailers_ret_code; - - /* at this point we already verified that the callback exists - so we compile and store the trailers buffer, then proceed */ - infof(data, - "Moving trailers state machine from initialized to sending."); - data->state.trailers_state = TRAILERS_SENDING; - Curl_dyn_init(&data->state.trailers_buf, DYN_TRAILERS); - - data->state.trailers_bytes_sent = 0; - Curl_set_in_callback(data, true); - trailers_ret_code = data->set.trailer_callback(&trailers, - data->set.trailer_data); - Curl_set_in_callback(data, false); - if(trailers_ret_code == CURL_TRAILERFUNC_OK) { - result = Curl_http_compile_trailers(trailers, &data->state.trailers_buf, - data); - } - else { - failf(data, "operation aborted by trailing headers callback"); - *nreadp = 0; - result = CURLE_ABORTED_BY_CALLBACK; - } - if(result) { - Curl_dyn_free(&data->state.trailers_buf); - curl_slist_free_all(trailers); - return result; - } - infof(data, "Successfully compiled trailers."); - curl_slist_free_all(trailers); - } -#endif - -#ifndef CURL_DISABLE_HTTP - /* if we are transmitting trailing data, we don't need to write - a chunk size so we skip this */ - if(data->req.upload_chunky && - data->state.trailers_state == TRAILERS_NONE) { - /* if chunked Transfer-Encoding */ - buffersize -= (8 + 2 + 2); /* 32bit hex + CRLF + CRLF */ - data->req.upload_fromhere += (8 + 2); /* 32bit hex + CRLF */ - } - - if(data->state.trailers_state == TRAILERS_SENDING) { - /* if we're here then that means that we already sent the last empty chunk - but we didn't send a final CR LF, so we sent 0 CR LF. We then start - pulling trailing data until we have no more at which point we - simply return to the previous point in the state machine as if - nothing happened. - */ - readfunc = trailers_read; - extra_data = (void *)data; - } - else -#endif - { - readfunc = data->state.fread_func; - extra_data = data->state.in; - } - - Curl_set_in_callback(data, true); - nread = readfunc(data->req.upload_fromhere, 1, - buffersize, extra_data); - Curl_set_in_callback(data, false); - - if(nread == CURL_READFUNC_ABORT) { - failf(data, "operation aborted by callback"); - *nreadp = 0; - return CURLE_ABORTED_BY_CALLBACK; - } - if(nread == CURL_READFUNC_PAUSE) { - struct SingleRequest *k = &data->req; - - if(data->conn->handler->flags & PROTOPT_NONETWORK) { - /* protocols that work without network cannot be paused. This is - actually only FILE:// just now, and it can't pause since the transfer - isn't done using the "normal" procedure. */ - failf(data, "Read callback asked for PAUSE when not supported"); - return CURLE_READ_ERROR; - } - - /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ - k->keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ - if(data->req.upload_chunky) { - /* Back out the preallocation done above */ - data->req.upload_fromhere -= (8 + 2); - } - *nreadp = 0; - - return CURLE_OK; /* nothing was read */ - } - else if(nread > buffersize) { - /* the read function returned a too large value */ - *nreadp = 0; - failf(data, "read function returned funny value"); - return CURLE_READ_ERROR; - } - -#ifndef CURL_DISABLE_HTTP - if(!data->req.forbidchunk && data->req.upload_chunky) { - /* if chunked Transfer-Encoding - * build chunk: - * - * CRLF - * CRLF - */ - /* On non-ASCII platforms the may or may not be - translated based on state.prefer_ascii while the protocol - portion must always be translated to the network encoding. - To further complicate matters, line end conversion might be - done later on, so we need to prevent CRLFs from becoming - CRCRLFs if that's the case. To do this we use bare LFs - here, knowing they'll become CRLFs later on. - */ - - bool added_crlf = FALSE; - int hexlen = 0; - const char *endofline_native; - const char *endofline_network; - - if( -#ifdef CURL_DO_LINEEND_CONV - (data->state.prefer_ascii) || -#endif - (data->set.crlf)) { - /* \n will become \r\n later on */ - endofline_native = "\n"; - endofline_network = "\x0a"; - } - else { - endofline_native = "\r\n"; - endofline_network = "\x0d\x0a"; - } - - /* if we're not handling trailing data, proceed as usual */ - if(data->state.trailers_state != TRAILERS_SENDING) { - char hexbuffer[11] = ""; - hexlen = msnprintf(hexbuffer, sizeof(hexbuffer), - "%zx%s", nread, endofline_native); - - /* move buffer pointer */ - data->req.upload_fromhere -= hexlen; - nread += hexlen; - - /* copy the prefix to the buffer, leaving out the NUL */ - memcpy(data->req.upload_fromhere, hexbuffer, hexlen); - - /* always append ASCII CRLF to the data unless - we have a valid trailer callback */ - if((nread-hexlen) == 0 && - data->set.trailer_callback != NULL && - data->state.trailers_state == TRAILERS_NONE) { - data->state.trailers_state = TRAILERS_INITIALIZED; - } - else { - memcpy(data->req.upload_fromhere + nread, - endofline_network, - strlen(endofline_network)); - added_crlf = TRUE; - } - } - - if(data->state.trailers_state == TRAILERS_SENDING && - !trailers_left(data)) { - Curl_dyn_free(&data->state.trailers_buf); - data->state.trailers_state = TRAILERS_DONE; - data->set.trailer_data = NULL; - data->set.trailer_callback = NULL; - /* mark the transfer as done */ - data->req.upload_done = TRUE; - infof(data, "Signaling end of chunked upload after trailers."); - } - else - if((nread - hexlen) == 0 && - data->state.trailers_state != TRAILERS_INITIALIZED) { - /* mark this as done once this chunk is transferred */ - data->req.upload_done = TRUE; - infof(data, - "Signaling end of chunked upload via terminating chunk."); - } - - if(added_crlf) - nread += strlen(endofline_network); /* for the added end of line */ - } -#endif - - *nreadp = nread; - - return CURLE_OK; -} - static int data_pending(struct Curl_easy *data) { struct connectdata *conn = data->conn; @@ -409,753 +160,298 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) return TRUE; } +static CURLcode xfer_recv_shutdown(struct Curl_easy *data, bool *done) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->sockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_conn_shutdown(data, sockindex, done); +} + +static bool xfer_recv_shutdown_started(struct Curl_easy *data) +{ + int sockindex; + + if(!data || !data->conn) + return FALSE; + if(data->conn->sockfd == CURL_SOCKET_BAD) + return FALSE; + sockindex = (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_shutdown_started(data, sockindex); +} + +CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->writesockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_conn_shutdown(data, sockindex, done); +} + +/** + * Receive raw response data for the transfer. + * @param data the transfer + * @param buf buffer to keep response data received + * @param blen length of `buf` + * @param eos_reliable if EOS detection in underlying connection is reliable + * @param err error code in case of -1 return + * @return number of bytes read or -1 for error + */ +static ssize_t xfer_recv_resp(struct Curl_easy *data, + char *buf, size_t blen, + bool eos_reliable, + CURLcode *err) +{ + ssize_t nread; + + DEBUGASSERT(blen > 0); + /* If we are reading BODY data and the connection does NOT handle EOF + * and we know the size of the BODY data, limit the read amount */ + if(!eos_reliable && !data->req.header && data->req.size != -1) { + curl_off_t totalleft = data->req.size - data->req.bytecount; + if(totalleft <= 0) + blen = 0; + else if(totalleft < (curl_off_t)blen) + blen = (size_t)totalleft; + } + else if(xfer_recv_shutdown_started(data)) { + /* we already received everything. Do not try more. */ + blen = 0; + } + + if(!blen) { + /* want nothing more */ + *err = CURLE_OK; + nread = 0; + } + else { + *err = Curl_xfer_recv(data, buf, blen, &nread); + } + + if(*err) + return -1; + if(nread == 0) { + if(data->req.shutdown) { + bool done; + *err = xfer_recv_shutdown(data, &done); + if(*err) + return -1; + if(!done) { + *err = CURLE_AGAIN; + return -1; + } + } + DEBUGF(infof(data, "sendrecv_dl: we are done")); + } + DEBUGASSERT(nread >= 0); + return nread; +} + /* * Go ahead and do a read if we have a readable socket or if * the stream was rewound (in which case we have data in a * buffer) - * - * return '*comeback' TRUE if we didn't properly drain the socket so this - * function should get called again without select() or similar in between! */ -static CURLcode readwrite_data(struct Curl_easy *data, - struct connectdata *conn, - struct SingleRequest *k, - int *didwhat, bool *done, - bool *comeback) +static CURLcode sendrecv_dl(struct Curl_easy *data, + struct SingleRequest *k, + int *didwhat) { + struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - ssize_t nread; /* number of bytes read */ - size_t excess = 0; /* excess bytes read */ - bool readmore = FALSE; /* used by RTP to signal for more data */ - int maxloops = 100; - char *buf = data->state.buffer; - DEBUGASSERT(buf); + char *buf, *xfer_buf; + size_t blen, xfer_blen; + int maxloops = 10; + curl_off_t total_received = 0; + bool is_multiplex = FALSE; - *done = FALSE; - *comeback = FALSE; + result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); + if(result) + goto out; /* This is where we loop until we have read everything there is to read or we get a CURLE_AGAIN */ do { - bool is_empty_data = FALSE; - size_t buffersize = data->set.buffer_size; - size_t bytestoread = buffersize; - /* For HTTP/2 and HTTP/3, read data without caring about the content - length. This is safe because body in HTTP/2 is always segmented - thanks to its framing layer. Meanwhile, we have to call Curl_read - to ensure that http2_handle_stream_close is called when we read all - incoming bytes for a particular stream. */ - bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET); - bool data_eof_handled = is_http3 - || Curl_conn_is_http2(data, conn, FIRSTSOCKET); - - if(!data_eof_handled && k->size != -1 && !k->header) { - /* make sure we don't read too much */ - curl_off_t totalleft = k->size - k->bytecount; - if(totalleft < (curl_off_t)bytestoread) - bytestoread = (size_t)totalleft; + bool is_eos = FALSE; + size_t bytestoread; + ssize_t nread; + + if(!is_multiplex) { + /* Multiplexed connection have inherent handling of EOF and we do not + * have to carefully restrict the amount we try to read. + * Multiplexed changes only in one direction. */ + is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); } - if(bytestoread) { - /* receive data from the network! */ - result = Curl_read(data, conn->sockfd, buf, bytestoread, &nread); + buf = xfer_buf; + bytestoread = xfer_blen; - /* read would've blocked */ - if(CURLE_AGAIN == result) { - result = CURLE_OK; - break; /* get out of loop */ + if(bytestoread && data->set.max_recv_speed > 0) { + /* In case of speed limit on receiving: if this loop already got + * data, break out. If not, limit the amount of bytes to receive. + * The overall, timed, speed limiting is done in multi.c */ + if(total_received) + break; + if(data->set.max_recv_speed < (curl_off_t)bytestoread) + bytestoread = (size_t)data->set.max_recv_speed; + } + + nread = xfer_recv_resp(data, buf, bytestoread, is_multiplex, &result); + if(nread < 0) { + if(CURLE_AGAIN != result) + goto out; /* real error */ + result = CURLE_OK; + if(data->req.download_done && data->req.no_body && + !data->req.resp_trailer) { + DEBUGF(infof(data, "EAGAIN, download done, no trailer announced, " + "not waiting for EOS")); + nread = 0; + /* continue as if we read the EOS */ } - - if(result>0) - goto out; - } - else { - /* read nothing but since we wanted nothing we consider this an OK - situation to proceed from */ - DEBUGF(infof(data, DMSG(data, "readwrite_data: we're done"))); - nread = 0; - } - - if(!k->bytecount) { - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - if(k->exp100 > EXP100_SEND_DATA) - /* set time stamp to compare with when waiting for the 100 */ - k->start100 = Curl_now(); + else + break; /* get out of loop */ } + /* We only get a 0-length read on EndOfStream */ + blen = (size_t)nread; + is_eos = (blen == 0); *didwhat |= KEEP_RECV; - /* indicates data of zero size, i.e. empty file */ - is_empty_data = ((nread == 0) && (k->bodywrites == 0)) ? TRUE : FALSE; - if(0 < nread || is_empty_data) { - buf[nread] = 0; - } - else { + if(!blen) { /* if we receive 0 or less here, either the data transfer is done or the server closed the connection and we bail out from this! */ - if(data_eof_handled) + if(is_multiplex) DEBUGF(infof(data, "nread == 0, stream closed, bailing")); else DEBUGF(infof(data, "nread <= 0, server closed connection, bailing")); - k->keepon &= ~KEEP_RECV; - break; - } - - /* Default buffer to use when we write the buffer, it may be changed - in the flow below before the actual storing is done. */ - k->str = buf; - - if(conn->handler->readwrite) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - goto out; - if(readmore) - break; - } - -#ifndef CURL_DISABLE_HTTP - /* Since this is a two-state thing, we check if we are parsing - headers at the moment or not. */ - if(k->header) { - /* we are in parse-the-header-mode */ - bool stop_reading = FALSE; - result = Curl_http_readwrite_headers(data, conn, &nread, &stop_reading); + result = Curl_req_stop_send_recv(data); if(result) goto out; - - if(conn->handler->readwrite && - (k->maxdownload <= 0 && nread > 0)) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - goto out; - if(readmore) - break; - } - - if(stop_reading) { - /* We've stopped dealing with input, get out of the do-while loop */ - - if(nread > 0) { - infof(data, - "Excess found:" - " excess = %zd" - " url = %s (zero-length body)", - nread, data->state.up.path); - } - + if(k->eos_written) /* already did write this to client, leave */ break; - } } -#endif /* CURL_DISABLE_HTTP */ - - - /* This is not an 'else if' since it may be a rest from the header - parsing, where the beginning of the buffer is headers and the end - is non-headers. */ - if(!k->header && (nread > 0 || is_empty_data)) { - - if(data->req.no_body) { - /* data arrives although we want none, bail out */ - streamclose(conn, "ignoring body"); - *done = TRUE; - result = CURLE_WEIRD_SERVER_REPLY; - goto out; - } - -#ifndef CURL_DISABLE_HTTP - if(0 == k->bodywrites && !is_empty_data) { - /* These checks are only made the first time we are about to - write a piece of the body */ - if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { - /* HTTP-only checks */ - result = Curl_http_firstwrite(data, conn, done); - if(result || *done) - goto out; - } - } /* this is the first time we write a body part */ -#endif /* CURL_DISABLE_HTTP */ - - k->bodywrites++; - - /* pass data to the debug function before it gets "dechunked" */ - if(data->set.verbose) { - if(k->badheader) { - Curl_debug(data, CURLINFO_DATA_IN, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); - if(k->badheader == HEADER_PARTHEADER) - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread); - } - else - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread); - } - -#ifndef CURL_DISABLE_HTTP - if(k->chunk) { - /* - * Here comes a chunked transfer flying and we need to decode this - * properly. While the name says read, this function both reads - * and writes away the data. The returned 'nread' holds the number - * of actual data it wrote to the client. - */ - CURLcode extra; - CHUNKcode res = - Curl_httpchunk_read(data, k->str, nread, &nread, &extra); - - if(CHUNKE_OK < res) { - if(CHUNKE_PASSTHRU_ERROR == res) { - failf(data, "Failed reading the chunked-encoded stream"); - result = extra; - goto out; - } - failf(data, "%s in chunked-encoding", Curl_chunked_strerror(res)); - result = CURLE_RECV_ERROR; - goto out; - } - if(CHUNKE_STOP == res) { - /* we're done reading chunks! */ - k->keepon &= ~KEEP_RECV; /* read no more */ - - /* N number of bytes at the end of the str buffer that weren't - written to the client. */ - if(conn->chunk.datasize) { - infof(data, "Leftovers after chunking: % " - CURL_FORMAT_CURL_OFF_T "u bytes", - conn->chunk.datasize); - } - } - /* If it returned OK, we just keep going */ - } -#endif /* CURL_DISABLE_HTTP */ + total_received += blen; - /* Account for body content stored in the header buffer */ - if((k->badheader == HEADER_PARTHEADER) && !k->ignorebody) { - size_t headlen = Curl_dyn_len(&data->state.headerb); - DEBUGF(infof(data, "Increasing bytecount by %zu", headlen)); - k->bytecount += headlen; - } - - if((-1 != k->maxdownload) && - (k->bytecount + nread >= k->maxdownload)) { - - excess = (size_t)(k->bytecount + nread - k->maxdownload); - if(excess > 0 && !k->ignorebody) { - infof(data, - "Excess found in a read:" - " excess = %zu" - ", size = %" CURL_FORMAT_CURL_OFF_T - ", maxdownload = %" CURL_FORMAT_CURL_OFF_T - ", bytecount = %" CURL_FORMAT_CURL_OFF_T, - excess, k->size, k->maxdownload, k->bytecount); - connclose(conn, "excess found in a read"); - } - - nread = (ssize_t) (k->maxdownload - k->bytecount); - if(nread < 0) /* this should be unusual */ - nread = 0; - - /* HTTP/3 over QUIC should keep reading until QUIC connection - is closed. In contrast to HTTP/2 which can stop reading - from TCP connection, HTTP/3 over QUIC needs ACK from server - to ensure stream closure. It should keep reading. */ - if(!is_http3) { - k->keepon &= ~KEEP_RECV; /* we're done reading */ - } - } - - k->bytecount += nread; - - Curl_pgrsSetDownloadCounter(data, k->bytecount); - - if(!k->chunk && (nread || k->badheader || is_empty_data)) { - /* If this is chunky transfer, it was already written */ - - if(k->badheader && !k->ignorebody) { - /* we parsed a piece of data wrongly assuming it was a header - and now we output it as body instead */ - size_t headlen = Curl_dyn_len(&data->state.headerb); - - /* Don't let excess data pollute body writes */ - if(k->maxdownload == -1 || (curl_off_t)headlen <= k->maxdownload) - result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&data->state.headerb), - headlen); - else - result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&data->state.headerb), - (size_t)k->maxdownload); - - if(result) - goto out; - } - if(k->badheader < HEADER_ALLBAD) { - /* This switch handles various content encodings. If there's an - error here, be sure to check over the almost identical code - in http_chunks.c. - Make sure that ALL_CONTENT_ENCODINGS contains all the - encodings handled here. */ - if(data->set.http_ce_skip || !k->writer_stack) { - if(!k->ignorebody && nread) { -#ifndef CURL_DISABLE_POP3 - if(conn->handler->protocol & PROTO_FAMILY_POP3) - result = Curl_pop3_write(data, k->str, nread); - else -#endif /* CURL_DISABLE_POP3 */ - result = Curl_client_write(data, CLIENTWRITE_BODY, k->str, - nread); - } - } - else if(!k->ignorebody && nread) - result = Curl_unencode_write(data, k->writer_stack, k->str, nread); - } - k->badheader = HEADER_NORMAL; /* taken care of now */ - - if(result) - goto out; - } - - } /* if(!header and data to read) */ - - if(conn->handler->readwrite && excess) { - /* Parse the excess data */ - k->str += nread; - - if(&k->str[excess] > &buf[data->set.buffer_size]) { - /* the excess amount was too excessive(!), make sure - it doesn't read out of buffer */ - excess = &buf[data->set.buffer_size] - k->str; - } - nread = (ssize_t)excess; - - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - goto out; - - if(readmore) - k->keepon |= KEEP_RECV; /* we're not done reading */ - break; - } + result = Curl_xfer_write_resp(data, buf, blen, is_eos); + if(result || data->req.done) + goto out; - if(is_empty_data) { - /* if we received nothing, the server closed the connection and we - are done */ - k->keepon &= ~KEEP_RECV; + /* if we are done, we stop receiving. On multiplexed connections, + * we should read the EOS. Which may arrive as meta data after + * the bytes. Not taking it in might lead to RST of streams. */ + if((!is_multiplex && data->req.download_done) || is_eos) { + data->req.keepon &= ~KEEP_RECV; } - - if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) { - /* this is a paused or stopped transfer */ + /* if we are PAUSEd or stopped receiving, leave the loop */ + if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) break; - } - } while(data_pending(data) && maxloops--); + } while(maxloops--); - if(maxloops <= 0) { - /* we mark it as read-again-please */ - data->state.dselect_bits = CURL_CSELECT_IN; - *comeback = TRUE; + if((maxloops <= 0) || data_pending(data)) { + /* did not read until EAGAIN or there is still pending data, mark as + read-again-please */ + data->state.select_bits = CURL_CSELECT_IN; + if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) + data->state.select_bits |= CURL_CSELECT_OUT; } if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) && - conn->bits.close) { - /* When we've read the entire thing and the close bit is set, the server - may now close the connection. If there's now any kind of sending going + (conn->bits.close || is_multiplex)) { + /* When we have read the entire thing and the close bit is set, the server + may now close the connection. If there is now any kind of sending going on from our side, we need to stop that immediately. */ infof(data, "we are done reading and this is set to close, stop send"); - k->keepon &= ~KEEP_SEND; /* no writing anymore either */ + Curl_req_abort_sending(data); } out: + Curl_multi_xfer_buf_release(data, xfer_buf); if(result) - DEBUGF(infof(data, DMSG(data, "readwrite_data() -> %d"), result)); + DEBUGF(infof(data, "sendrecv_dl() -> %d", result)); return result; } -CURLcode Curl_done_sending(struct Curl_easy *data, - struct SingleRequest *k) -{ - k->keepon &= ~KEEP_SEND; /* we're done writing */ - - /* These functions should be moved into the handler struct! */ - Curl_conn_ev_data_done_send(data); - - return CURLE_OK; -} - -#if defined(WIN32) && defined(USE_WINSOCK) -#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY -#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B -#endif - -static void win_update_buffer_size(curl_socket_t sockfd) -{ - int result; - ULONG ideal; - DWORD ideallen; - result = WSAIoctl(sockfd, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0, - &ideal, sizeof(ideal), &ideallen, 0, 0); - if(result == 0) { - setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, - (const char *)&ideal, sizeof(ideal)); - } -} -#else -#define win_update_buffer_size(x) -#endif - -#define curl_upload_refill_watermark(data) \ - ((ssize_t)((data)->set.upload_buffer_size >> 5)) - /* * Send data to upload to the server, when the socket is writable. */ -static CURLcode readwrite_upload(struct Curl_easy *data, - struct connectdata *conn, - int *didwhat) +static CURLcode sendrecv_ul(struct Curl_easy *data, int *didwhat) { - ssize_t i, si; - ssize_t bytes_written; - CURLcode result; - ssize_t nread; /* number of bytes read */ - bool sending_http_headers = FALSE; - struct SingleRequest *k = &data->req; - - if((k->bytecount == 0) && (k->writebytecount == 0)) - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - *didwhat |= KEEP_SEND; - - do { - curl_off_t nbody; - ssize_t offset = 0; - - if(0 != k->upload_present && - k->upload_present < curl_upload_refill_watermark(data) && - !k->upload_chunky &&/*(variable sized chunked header; append not safe)*/ - !k->upload_done && /*!(k->upload_done once k->upload_present sent)*/ - !(k->writebytecount + k->upload_present - k->pendingheader == - data->state.infilesize)) { - offset = k->upload_present; - } - - /* only read more data if there's no upload data already - present in the upload buffer, or if appending to upload buffer */ - if(0 == k->upload_present || offset) { - result = Curl_get_upload_buffer(data); - if(result) - return result; - if(offset && k->upload_fromhere != data->state.ulbuf) - memmove(data->state.ulbuf, k->upload_fromhere, offset); - /* init the "upload from here" pointer */ - k->upload_fromhere = data->state.ulbuf; - - if(!k->upload_done) { - /* HTTP pollution, this should be written nicer to become more - protocol agnostic. */ - size_t fillcount; - struct HTTP *http = k->p.http; - - if((k->exp100 == EXP100_SENDING_REQUEST) && - (http->sending == HTTPSEND_BODY)) { - /* If this call is to send body data, we must take some action: - We have sent off the full HTTP 1.1 request, and we shall now - go into the Expect: 100 state and await such a header */ - k->exp100 = EXP100_AWAITING_CONTINUE; /* wait for the header */ - k->keepon &= ~KEEP_SEND; /* disable writing */ - k->start100 = Curl_now(); /* timeout count starts now */ - *didwhat &= ~KEEP_SEND; /* we didn't write anything actually */ - /* set a timeout for the multi interface */ - Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); - break; - } - - if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { - if(http->sending == HTTPSEND_REQUEST) - /* We're sending the HTTP request headers, not the data. - Remember that so we don't change the line endings. */ - sending_http_headers = TRUE; - else - sending_http_headers = FALSE; - } - - k->upload_fromhere += offset; - result = Curl_fillreadbuffer(data, data->set.upload_buffer_size-offset, - &fillcount); - k->upload_fromhere -= offset; - if(result) - return result; - - nread = offset + fillcount; - } - else - nread = 0; /* we're done uploading/reading */ - - if(!nread && (k->keepon & KEEP_SEND_PAUSE)) { - /* this is a paused transfer */ - break; - } - if(nread <= 0) { - result = Curl_done_sending(data, k); - if(result) - return result; - break; - } - - /* store number of bytes available for upload */ - k->upload_present = nread; - - /* convert LF to CRLF if so asked */ - if((!sending_http_headers) && ( -#ifdef CURL_DO_LINEEND_CONV - /* always convert if we're FTPing in ASCII mode */ - (data->state.prefer_ascii) || -#endif - (data->set.crlf))) { - /* Do we need to allocate a scratch buffer? */ - if(!data->state.scratch) { - data->state.scratch = malloc(2 * data->set.upload_buffer_size); - if(!data->state.scratch) { - failf(data, "Failed to alloc scratch buffer"); - - return CURLE_OUT_OF_MEMORY; - } - } - - /* - * ASCII/EBCDIC Note: This is presumably a text (not binary) - * transfer so the data should already be in ASCII. - * That means the hex values for ASCII CR (0x0d) & LF (0x0a) - * must be used instead of the escape sequences \r & \n. - */ - if(offset) - memcpy(data->state.scratch, k->upload_fromhere, offset); - for(i = offset, si = offset; i < nread; i++, si++) { - if(k->upload_fromhere[i] == 0x0a) { - data->state.scratch[si++] = 0x0d; - data->state.scratch[si] = 0x0a; - if(!data->set.crlf) { - /* we're here only because FTP is in ASCII mode... - bump infilesize for the LF we just added */ - if(data->state.infilesize != -1) - data->state.infilesize++; - } - } - else - data->state.scratch[si] = k->upload_fromhere[i]; - } - - if(si != nread) { - /* only perform the special operation if we really did replace - anything */ - nread = si; - - /* upload from the new (replaced) buffer instead */ - k->upload_fromhere = data->state.scratch; - - /* set the new amount too */ - k->upload_present = nread; - } - } - -#ifndef CURL_DISABLE_SMTP - if(conn->handler->protocol & PROTO_FAMILY_SMTP) { - result = Curl_smtp_escape_eob(data, nread, offset); - if(result) - return result; - } -#endif /* CURL_DISABLE_SMTP */ - } /* if 0 == k->upload_present or appended to upload buffer */ - else { - /* We have a partial buffer left from a previous "round". Use - that instead of reading more data */ - } - - /* write to socket (send away data) */ - result = Curl_write(data, - conn->writesockfd, /* socket to send to */ - k->upload_fromhere, /* buffer pointer */ - k->upload_present, /* buffer size */ - &bytes_written); /* actually sent */ - if(result) - return result; - -#if defined(WIN32) && defined(USE_WINSOCK) - { - struct curltime n = Curl_now(); - if(Curl_timediff(n, k->last_sndbuf_update) > 1000) { - win_update_buffer_size(conn->writesockfd); - k->last_sndbuf_update = n; - } - } -#endif - - if(k->pendingheader) { - /* parts of what was sent was header */ - curl_off_t n = CURLMIN(k->pendingheader, bytes_written); - /* show the data before we change the pointer upload_fromhere */ - Curl_debug(data, CURLINFO_HEADER_OUT, k->upload_fromhere, (size_t)n); - k->pendingheader -= n; - nbody = bytes_written - n; /* size of the written body part */ - } - else - nbody = bytes_written; - - if(nbody) { - /* show the data before we change the pointer upload_fromhere */ - Curl_debug(data, CURLINFO_DATA_OUT, - &k->upload_fromhere[bytes_written - nbody], - (size_t)nbody); - - k->writebytecount += nbody; - Curl_pgrsSetUploadCounter(data, k->writebytecount); - } - - if((!k->upload_chunky || k->forbidchunk) && - (k->writebytecount == data->state.infilesize)) { - /* we have sent all data we were supposed to */ - k->upload_done = TRUE; - infof(data, "We are completely uploaded and fine"); - } - - if(k->upload_present != bytes_written) { - /* we only wrote a part of the buffer (if anything), deal with it! */ - - /* store the amount of bytes left in the buffer to write */ - k->upload_present -= bytes_written; - - /* advance the pointer where to find the buffer when the next send - is to happen */ - k->upload_fromhere += bytes_written; - } - else { - /* we've uploaded that buffer now */ - result = Curl_get_upload_buffer(data); - if(result) - return result; - k->upload_fromhere = data->state.ulbuf; - k->upload_present = 0; /* no more bytes left */ - - if(k->upload_done) { - result = Curl_done_sending(data, k); - if(result) - return result; - } - } - - - } while(0); /* just to break out from! */ + /* We should not get here when the sending is already done. It + * probably means that someone set `data-req.keepon |= KEEP_SEND` + * when it should not. */ + DEBUGASSERT(!Curl_req_done_sending(data)); + if(!Curl_req_done_sending(data)) { + *didwhat |= KEEP_SEND; + return Curl_req_send_more(data); + } return CURLE_OK; } +static int select_bits_paused(struct Curl_easy *data, int select_bits) +{ + /* See issue #11982: we really need to be careful not to progress + * a transfer direction when that direction is paused. Not all parts + * of our state machine are handling PAUSED transfers correctly. So, we + * do not want to go there. + * NOTE: we are only interested in PAUSE, not HOLD. */ + + /* if there is data in a direction not paused, return false */ + if(((select_bits & CURL_CSELECT_IN) && + !(data->req.keepon & KEEP_RECV_PAUSE)) || + ((select_bits & CURL_CSELECT_OUT) && + !(data->req.keepon & KEEP_SEND_PAUSE))) + return FALSE; + + return (data->req.keepon & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)); +} + /* - * Curl_readwrite() is the low-level function to be called when data is to + * Curl_sendrecv() is the low-level function to be called when data is to * be read and written to/from the connection. - * - * return '*comeback' TRUE if we didn't properly drain the socket so this - * function should get called again without select() or similar in between! */ -CURLcode Curl_readwrite(struct connectdata *conn, - struct Curl_easy *data, - bool *done, - bool *comeback) +CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp) { struct SingleRequest *k = &data->req; - CURLcode result; - struct curltime now; + CURLcode result = CURLE_OK; int didwhat = 0; - int select_bits; - - - if(data->state.dselect_bits) { - select_bits = data->state.dselect_bits; - data->state.dselect_bits = 0; - } - else if(conn->cselect_bits) { - select_bits = conn->cselect_bits; - conn->cselect_bits = 0; - } - else { - curl_socket_t fd_read; - curl_socket_t fd_write; - /* only use the proper socket if the *_HOLD bit is not set simultaneously - as then we are in rate limiting state in that transfer direction */ - if((k->keepon & KEEP_RECVBITS) == KEEP_RECV) - fd_read = conn->sockfd; - else - fd_read = CURL_SOCKET_BAD; - - if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) - fd_write = conn->writesockfd; - else - fd_write = CURL_SOCKET_BAD; - - select_bits = Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, 0); - } - - if(select_bits == CURL_CSELECT_ERR) { - failf(data, "select/poll returned error"); - result = CURLE_SEND_ERROR; - goto out; - } -#ifdef USE_HYPER - if(conn->datastream) { - result = conn->datastream(data, conn, &didwhat, done, select_bits); - if(result || *done) + DEBUGASSERT(nowp); + if(data->state.select_bits) { + if(select_bits_paused(data, data->state.select_bits)) { + /* leave the bits unchanged, so they'll tell us what to do when + * this transfer gets unpaused. */ + result = CURLE_OK; goto out; + } + data->state.select_bits = 0; } - else { -#endif - /* We go ahead and do a read if we have a readable socket or if - the stream was rewound (in which case we have data in a - buffer) */ - if((k->keepon & KEEP_RECV) && (select_bits & CURL_CSELECT_IN)) { - result = readwrite_data(data, conn, k, &didwhat, done, comeback); - if(result || *done) + + /* We go ahead and do a read if we have a readable socket or if the stream + was rewound (in which case we have data in a buffer) */ + if(k->keepon & KEEP_RECV) { + result = sendrecv_dl(data, k, &didwhat); + if(result || data->req.done) goto out; } /* If we still have writing to do, we check if we have a writable socket. */ - if((k->keepon & KEEP_SEND) && (select_bits & CURL_CSELECT_OUT)) { - /* write */ - - result = readwrite_upload(data, conn, &didwhat); + if(Curl_req_want_send(data) || (data->req.keepon & KEEP_SEND_TIMED)) { + result = sendrecv_ul(data, &didwhat); if(result) goto out; } -#ifdef USE_HYPER - } -#endif - now = Curl_now(); if(!didwhat) { - /* no read no write, this is a timeout? */ - if(k->exp100 == EXP100_AWAITING_CONTINUE) { - /* This should allow some time for the header to arrive, but only a - very short time as otherwise it'll be too much wasted time too - often. */ - - /* Quoting RFC2616, section "8.2.3 Use of the 100 (Continue) Status": - - Therefore, when a client sends this header field to an origin server - (possibly via a proxy) from which it has never seen a 100 (Continue) - status, the client SHOULD NOT wait for an indefinite period before - sending the request body. - - */ - - timediff_t ms = Curl_timediff(now, k->start100); - if(ms >= data->set.expect_100_timeout) { - /* we've waited long enough, continue anyway */ - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - infof(data, "Done waiting for 100-continue"); - } - } - + /* Transfer wanted to send/recv, but nothing was possible. */ result = Curl_conn_ev_data_idle(data); if(result) goto out; @@ -1164,23 +460,23 @@ CURLcode Curl_readwrite(struct connectdata *conn, if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, now); + result = Curl_speedcheck(data, *nowp); if(result) goto out; if(k->keepon) { - if(0 > Curl_timeleft(data, &now, FALSE)) { + if(0 > Curl_timeleft(data, nowp, FALSE)) { if(k->size != -1) { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(now, data->progress.t_startsingle), + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " out of %" + FMT_OFF_T " bytes received", + curlx_timediff(*nowp, data->progress.t_startsingle), k->bytecount, k->size); } else { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(now, data->progress.t_startsingle), + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " bytes received", + curlx_timediff(*nowp, data->progress.t_startsingle), k->bytecount); } result = CURLE_OPERATION_TIMEDOUT; @@ -1192,97 +488,29 @@ CURLcode Curl_readwrite(struct connectdata *conn, * The transfer has been performed. Just make some general checks before * returning. */ - if(!(data->req.no_body) && (k->size != -1) && - (k->bytecount != k->size) && -#ifdef CURL_DO_LINEEND_CONV - /* Most FTP servers don't adjust their file SIZE response for CRLFs, - so we'll check to see if the discrepancy can be explained - by the number of CRLFs we've changed to LFs. - */ - (k->bytecount != (k->size + data->state.crlf_conversions)) && -#endif /* CURL_DO_LINEEND_CONV */ - !k->newurl) { - failf(data, "transfer closed with %" CURL_FORMAT_CURL_OFF_T + (k->bytecount != k->size) && !k->newurl) { + failf(data, "transfer closed with %" FMT_OFF_T " bytes remaining to read", k->size - k->bytecount); result = CURLE_PARTIAL_FILE; goto out; } - if(!(data->req.no_body) && k->chunk && - (conn->chunk.state != CHUNK_STOP)) { - /* - * In chunked mode, return an error if the connection is closed prior to - * the empty (terminating) chunk is read. - * - * The condition above used to check for - * conn->proto.http->chunk.datasize != 0 which is true after reading - * *any* chunk, not just the empty chunk. - * - */ - failf(data, "transfer closed with outstanding read data remaining"); - result = CURLE_PARTIAL_FILE; - goto out; - } if(Curl_pgrsUpdate(data)) { result = CURLE_ABORTED_BY_CALLBACK; goto out; } } - /* Now update the "done" boolean we return */ - *done = (0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) ? TRUE : FALSE; + /* If there is nothing more to send/recv, the request is done */ + if(0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) + data->req.done = TRUE; + out: if(result) - DEBUGF(infof(data, DMSG(data, "Curl_readwrite() -> %d"), result)); + DEBUGF(infof(data, "Curl_sendrecv() -> %d", result)); return result; } -/* - * Curl_single_getsock() gets called by the multi interface code when the app - * has requested to get the sockets for the current connection. This function - * will then be called once for every connection that the multi interface - * keeps track of. This function will only be called for connections that are - * in the proper state to have this information available. - */ -int Curl_single_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *sock) -{ - int bitmap = GETSOCK_BLANK; - unsigned sockindex = 0; - - if(conn->handler->perform_getsock) - return conn->handler->perform_getsock(data, conn, sock); - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_RECVBITS) == KEEP_RECV) { - - DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); - - bitmap |= GETSOCK_READSOCK(sockindex); - sock[sockindex] = conn->sockfd; - } - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { - if((conn->sockfd != conn->writesockfd) || - bitmap == GETSOCK_BLANK) { - /* only if they are not the same socket and we have a readable - one, we increase index */ - if(bitmap != GETSOCK_BLANK) - sockindex++; /* increase index if we need two entries */ - - DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); - - sock[sockindex] = conn->writesockfd; - } - - bitmap |= GETSOCK_WRITESOCK(sockindex); - } - - return bitmap; -} - /* Curl_init_CONNECT() gets called each time the handle switches to CONNECT which means this gets called once for each subsequent redirect etc */ void Curl_init_CONNECT(struct Curl_easy *data) @@ -1299,22 +527,17 @@ void Curl_init_CONNECT(struct Curl_easy *data) */ CURLcode Curl_pretransfer(struct Curl_easy *data) { - CURLcode result; + CURLcode result = CURLE_OK; - if(!data->state.url && !data->set.uh) { - /* we can't do anything without URL */ + if(!data->set.str[STRING_SET_URL] && !data->set.uh) { + /* we cannot do anything without URL */ failf(data, "No URL set"); return CURLE_URL_MALFORMAT; } - /* since the URL may have been redirected in a previous use of this handle */ - if(data->state.url_alloc) { - /* the already set URL is allocated, free it first! */ - Curl_safefree(data->state.url); - data->state.url_alloc = FALSE; - } - - if(!data->state.url && data->set.uh) { + /* CURLOPT_CURLU overrides CURLOPT_URL and the contents of the CURLU handle + is allowed to be changed by the user between transfers */ + if(data->set.uh) { CURLUcode uc; free(data->set.str[STRING_SET_URL]); uc = curl_url_get(data->set.uh, @@ -1325,30 +548,33 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) } } + /* since the URL may have been redirected in a previous use of this handle */ + if(data->state.url_alloc) { + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; + } + + data->state.url = data->set.str[STRING_SET_URL]; + if(data->set.postfields && data->set.set_resume_from) { - /* we can't */ + /* we cannot */ failf(data, "cannot mix POSTFIELDS with RESUME_FROM"); return CURLE_BAD_FUNCTION_ARGUMENT; } data->state.prefer_ascii = data->set.prefer_ascii; +#ifdef CURL_LIST_ONLY_PROTOCOL data->state.list_only = data->set.list_only; +#endif data->state.httpreq = data->set.method; - data->state.url = data->set.str[STRING_SET_URL]; - - /* Init the SSL session ID cache here. We do it here since we want to do it - after the *_setopt() calls (that could specify the size of the cache) but - before any transfer takes place. */ - result = Curl_ssl_initsessions(data, data->set.general_ssl.max_ssl_sessions); - if(result) - return result; data->state.requests = 0; data->state.followlocation = 0; /* reset the location-follow counter */ data->state.this_is_a_follow = FALSE; /* reset this */ data->state.errorbuf = FALSE; /* no error has occurred */ - data->state.httpwant = data->set.httpwant; - data->state.httpversion = 0; +#ifndef CURL_DISABLE_HTTP + Curl_http_neg_init(data, &data->state.http_neg); +#endif data->state.authproblem = FALSE; data->state.authhost.want = data->set.httpauth; data->state.authproxy.want = data->set.proxyauth; @@ -1394,7 +620,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) Curl_pgrsResetTransferSizes(data); Curl_pgrsStartNow(data); - /* In case the handle is re-used and an authentication method was picked + /* In case the handle is reused and an authentication method was picked in the session we need to make sure we only use the one(s) we now consider to be fine */ data->state.authhost.picked &= data->state.authhost.want; @@ -1410,8 +636,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; } wc = data->wildcard; - if((wc->state < CURLWC_INIT) || - (wc->state >= CURLWC_CLEAN)) { + if(wc->state < CURLWC_INIT) { if(wc->ftpwc) wc->dtor(wc->ftpwc); Curl_safefree(wc->pattern); @@ -1427,340 +652,40 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) /* * Set user-agent. Used for HTTP, but since we can attempt to tunnel - * basically anything through an HTTP proxy we can't limit this based on + * basically anything through an HTTP proxy we cannot limit this based on * protocol. */ if(data->set.str[STRING_USERAGENT]) { - Curl_safefree(data->state.aptr.uagent); + free(data->state.aptr.uagent); data->state.aptr.uagent = aprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); if(!data->state.aptr.uagent) return CURLE_OUT_OF_MEMORY; } + if(data->set.str[STRING_USERNAME] || + data->set.str[STRING_PASSWORD]) + data->state.creds_from = CREDS_OPTION; if(!result) result = Curl_setstropt(&data->state.aptr.user, data->set.str[STRING_USERNAME]); if(!result) result = Curl_setstropt(&data->state.aptr.passwd, data->set.str[STRING_PASSWORD]); +#ifndef CURL_DISABLE_PROXY if(!result) result = Curl_setstropt(&data->state.aptr.proxyuser, data->set.str[STRING_PROXYUSERNAME]); if(!result) result = Curl_setstropt(&data->state.aptr.proxypasswd, data->set.str[STRING_PROXYPASSWORD]); +#endif data->req.headerbytecount = 0; Curl_headers_cleanup(data); return result; } -/* - * Curl_posttransfer() is called immediately after a transfer ends - */ -CURLcode Curl_posttransfer(struct Curl_easy *data) -{ -#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) - /* restore the signal handler for SIGPIPE before we get back */ - if(!data->set.no_signal) - signal(SIGPIPE, data->state.prev_signal); -#else - (void)data; /* unused parameter */ -#endif - - return CURLE_OK; -} - -/* - * Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string - * as given by the remote server and set up the new URL to request. - * - * This function DOES NOT FREE the given url. - */ -CURLcode Curl_follow(struct Curl_easy *data, - char *newurl, /* the Location: string */ - followtype type) /* see transfer.h */ -{ -#ifdef CURL_DISABLE_HTTP - (void)data; - (void)newurl; - (void)type; - /* Location: following will not happen when HTTP is disabled */ - return CURLE_TOO_MANY_REDIRECTS; -#else - - /* Location: redirect */ - bool disallowport = FALSE; - bool reachedmax = FALSE; - CURLUcode uc; - - 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) { - 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 */ - - if(data->state.referer_alloc) { - Curl_safefree(data->state.referer); - data->state.referer_alloc = FALSE; - } - - /* 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, 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); - - if(uc || !referer) - return CURLE_OUT_OF_MEMORY; - - data->state.referer = referer; - data->state.referer_alloc = TRUE; /* yes, free this later */ - } - } - } - - 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: don't allow a custom port number */ - disallowport = TRUE; - - DEBUGASSERT(data->state.uh); - uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, - (type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME : - ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | - CURLU_ALLOW_SPACE | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); - if(uc) { - if(type != FOLLOW_FAKE) { - failf(data, "The redirect target URL could not be parsed: %s", - curl_url_strerror(uc)); - return Curl_uc_to_curlcode(uc); - } - - /* the URL could not be parsed for some reason, but since this is FAKE - mode, just duplicate the field as-is */ - newurl = strdup(newurl); - if(!newurl) - return CURLE_OUT_OF_MEMORY; - } - else { - uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0); - if(uc) - return Curl_uc_to_curlcode(uc); - - /* Clear auth if this redirects to a different port number or protocol, - unless permitted */ - if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { - char *portnum; - int port; - bool clear = FALSE; - - if(data->set.use_port && data->state.allow_port) - /* a custom port is used */ - port = (int)data->set.use_port; - else { - uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, - CURLU_DEFAULT_PORT); - if(uc) { - free(newurl); - return Curl_uc_to_curlcode(uc); - } - port = atoi(portnum); - free(portnum); - } - if(port != data->info.conn_remote_port) { - infof(data, "Clear auth, redirects to port from %u to %u", - data->info.conn_remote_port, port); - clear = TRUE; - } - else { - char *scheme; - const struct Curl_handler *p; - uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); - if(uc) { - free(newurl); - return Curl_uc_to_curlcode(uc); - } - - p = Curl_builtin_scheme(scheme, CURL_ZERO_TERMINATED); - if(p && (p->protocol != data->info.conn_protocol)) { - infof(data, "Clear auth, redirects scheme from %s to %s", - data->info.conn_scheme, scheme); - clear = TRUE; - } - free(scheme); - } - if(clear) { - Curl_safefree(data->state.aptr.user); - Curl_safefree(data->state.aptr.passwd); - } - } - } - - if(type == FOLLOW_FAKE) { - /* we're only figuring out the new url if we would've followed locations - but now we're done so we can get out! */ - data->info.wouldredirect = newurl; - - if(reachedmax) { - failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); - return CURLE_TOO_MANY_REDIRECTS; - } - return CURLE_OK; - } - - if(disallowport) - data->state.allow_port = FALSE; - - if(data->state.url_alloc) - Curl_safefree(data->state.url); - - data->state.url = newurl; - data->state.url_alloc = TRUE; - - infof(data, "Issue another request to this URL: '%s'", data->state.url); - - /* - * 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. - */ - 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've checked RFC2616 and they - * seem to be OK to POST to. - */ - break; - case 301: /* Moved Permanently */ - /* (quote from RFC7231, section 6.4.2) - * - * Note: For historical reasons, a user agent MAY change the request - * method from POST to GET for the subsequent request. If this - * behavior is undesired, the 307 (Temporary Redirect) status code - * can be used instead. - * - * ---- - * - * Many webservers expect this, so these servers often answers to a POST - * request with an error page. To be sure that libcurl gets the page that - * most user agents would get, libcurl has to force GET. - * - * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and - * can be overridden with CURLOPT_POSTREDIR. - */ - if((data->state.httpreq == HTTPREQ_POST - || data->state.httpreq == HTTPREQ_POST_FORM - || data->state.httpreq == HTTPREQ_POST_MIME) - && !(data->set.keep_post & CURL_REDIR_POST_301)) { - infof(data, "Switch from POST to GET"); - data->state.httpreq = HTTPREQ_GET; - } - break; - case 302: /* Found */ - /* (quote from RFC7231, section 6.4.3) - * - * Note: For historical reasons, a user agent MAY change the request - * method from POST to GET for the subsequent request. If this - * behavior is undesired, the 307 (Temporary Redirect) status code - * can be used instead. - * - * ---- - * - * Many webservers expect this, so these servers often answers to a POST - * request with an error page. To be sure that libcurl gets the page that - * most user agents would get, libcurl has to force GET. - * - * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and - * can be overridden with CURLOPT_POSTREDIR. - */ - if((data->state.httpreq == HTTPREQ_POST - || data->state.httpreq == HTTPREQ_POST_FORM - || data->state.httpreq == HTTPREQ_POST_MIME) - && !(data->set.keep_post & CURL_REDIR_POST_302)) { - infof(data, "Switch from POST to GET"); - data->state.httpreq = HTTPREQ_GET; - } - 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 - * method is POST and the user specified to keep it as POST. - * https://github.com/curl/curl/issues/5237#issuecomment-614641049 - */ - if(data->state.httpreq != HTTPREQ_GET && - ((data->state.httpreq != HTTPREQ_POST && - data->state.httpreq != HTTPREQ_POST_FORM && - data->state.httpreq != HTTPREQ_POST_MIME) || - !(data->set.keep_post & CURL_REDIR_POST_303))) { - data->state.httpreq = HTTPREQ_GET; - infof(data, "Switch to %s", - data->req.no_body?"HEAD":"GET"); - } - break; - case 304: /* Not Modified */ - /* 304 means we did a conditional request and it was "Not modified". - * We shouldn't get any Location: header in this response! - */ - break; - case 305: /* Use Proxy */ - /* (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." - */ - break; - } - Curl_pgrsTime(data, TIMER_REDIRECT); - Curl_pgrsResetTransferSizes(data); - - return CURLE_OK; -#endif /* CURL_DISABLE_HTTP */ -} - /* Returns CURLE_OK *and* sets '*url' if a request retry is wanted. NOTE: that the *url is malloc()ed. */ @@ -1770,8 +695,9 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) bool retry = FALSE; *url = NULL; - /* if we're talking upload, we can't do the checks below, unless the protocol - is HTTP as when uploading over HTTP we will still get a response */ + /* if we are talking upload, we cannot do the checks below, unless the + protocol is HTTP as when uploading over HTTP we will still get a + response */ if(data->state.upload && !(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP))) return CURLE_OK; @@ -1783,7 +709,7 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) && (data->set.rtspreq != RTSPREQ_RECEIVE) #endif ) - /* We got no data, we attempted to re-use a connection. For HTTP this + /* We got no data, we attempted to reuse a connection. For HTTP this can be a retry so we try again regardless if we expected a body. For other protocols we only try again only if we expected a body. @@ -1817,66 +743,62 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) return CURLE_OUT_OF_MEMORY; connclose(conn, "retry"); /* close this connection */ - conn->bits.retry = TRUE; /* mark this as a connection we're about + conn->bits.retry = TRUE; /* mark this as a connection we are about to retry. Marking it this way should prevent i.e HTTP transfers to return error just because nothing has been transferred! */ - - - if((conn->handler->protocol&PROTO_FAMILY_HTTP) && - data->req.writebytecount) { - data->state.rewindbeforesend = TRUE; - infof(data, "state.rewindbeforesend = TRUE"); - } + Curl_creader_set_rewind(data, TRUE); } return CURLE_OK; } /* - * Curl_setup_transfer() is called to setup some basic properties for the - * upcoming transfer. + * xfer_setup() is called to setup basic properties for the transfer. */ -void -Curl_setup_transfer( +static void xfer_setup( struct Curl_easy *data, /* transfer */ int sockindex, /* socket index to read from or -1 */ curl_off_t size, /* -1 if unknown at this point */ bool getheader, /* TRUE if header parsing is wanted */ - int writesockindex /* socket index to write to, it may very well be - the same we read from. -1 disables */ + int writesockindex, /* socket index to write to, it may be the same we + read from. -1 disables */ + bool shutdown, /* shutdown connection at transfer end. Only + * supported when sending OR receiving. */ + bool shutdown_err_ignore /* errors during shutdown do not fail the + * transfer */ ) { struct SingleRequest *k = &data->req; struct connectdata *conn = data->conn; - struct HTTP *http = data->req.p.http; - bool httpsending; + bool want_send = Curl_req_want_send(data); DEBUGASSERT(conn != NULL); DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); + DEBUGASSERT((writesockindex <= 1) && (writesockindex >= -1)); + DEBUGASSERT(!shutdown || (sockindex == -1) || (writesockindex == -1)); - httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) && - (http->sending == HTTPSEND_REQUEST)); - - if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) { + if(Curl_conn_is_multiplex(conn, FIRSTSOCKET) || want_send) { /* when multiplexing, the read/write sockets need to be the same! */ conn->sockfd = sockindex == -1 ? ((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) : conn->sock[sockindex]; conn->writesockfd = conn->sockfd; - if(httpsending) - /* special and very HTTP-specific */ + if(want_send) + /* special and HTTP-specific */ writesockindex = FIRSTSOCKET; } else { conn->sockfd = sockindex == -1 ? CURL_SOCKET_BAD : conn->sock[sockindex]; conn->writesockfd = writesockindex == -1 ? - CURL_SOCKET_BAD:conn->sock[writesockindex]; + CURL_SOCKET_BAD : conn->sock[writesockindex]; } - k->getheader = getheader; + k->getheader = getheader; k->size = size; + k->shutdown = shutdown; + k->shutdown_err_ignore = shutdown_err_ignore; /* The code sequence below is placed in this function just because all necessary input is not always known in do_complete() as this function may @@ -1887,43 +809,174 @@ Curl_setup_transfer( if(size > 0) Curl_pgrsSetDownloadSize(data, size); } - /* we want header and/or body, if neither then don't do this! */ + /* we want header and/or body, if neither then do not do this! */ if(k->getheader || !data->req.no_body) { if(sockindex != -1) k->keepon |= KEEP_RECV; - if(writesockindex != -1) { - /* HTTP 1.1 magic: - - Even if we require a 100-return code before uploading data, we might - need to write data before that since the REQUEST may not have been - finished sent off just yet. - - Thus, we must check if the request has been sent before we set the - state info where we wait for the 100-return code - */ - if((data->state.expect100header) && - (conn->handler->protocol&PROTO_FAMILY_HTTP) && - (http->sending == HTTPSEND_BODY)) { - /* wait with write until we either got 100-continue or a timeout */ - k->exp100 = EXP100_AWAITING_CONTINUE; - k->start100 = Curl_now(); - - /* Set a timeout for the multi interface. Add the inaccuracy margin so - that we don't fire slightly too early and get denied to run. */ - Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); - } - else { - if(data->state.expect100header) - /* when we've sent off the rest of the headers, we must await a - 100-continue but first finish sending the request */ - k->exp100 = EXP100_SENDING_REQUEST; - - /* enable the write bit when we're not waiting for continue */ - k->keepon |= KEEP_SEND; - } - } /* if(writesockindex != -1) */ + if(writesockindex != -1) + k->keepon |= KEEP_SEND; } /* if(k->getheader || !data->req.no_body) */ } + +void Curl_xfer_setup_nop(struct Curl_easy *data) +{ + xfer_setup(data, -1, -1, FALSE, -1, FALSE, FALSE); +} + +void Curl_xfer_setup1(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool getheader) +{ + int recv_index = (send_recv & CURL_XFER_RECV) ? FIRSTSOCKET : -1; + int send_index = (send_recv & CURL_XFER_SEND) ? FIRSTSOCKET : -1; + DEBUGASSERT((recv_index >= 0) || (recv_size == -1)); + xfer_setup(data, recv_index, recv_size, getheader, send_index, FALSE, FALSE); +} + +void Curl_xfer_setup2(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool shutdown, + bool shutdown_err_ignore) +{ + int recv_index = (send_recv & CURL_XFER_RECV) ? SECONDARYSOCKET : -1; + int send_index = (send_recv & CURL_XFER_SEND) ? SECONDARYSOCKET : -1; + DEBUGASSERT((recv_index >= 0) || (recv_size == -1)); + xfer_setup(data, recv_index, recv_size, FALSE, send_index, + shutdown, shutdown_err_ignore); +} + +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos) +{ + CURLcode result = CURLE_OK; + + if(data->conn->handler->write_resp) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + result = data->conn->handler->write_resp(data, buf, blen, is_eos); + } + else { + /* No special handling by protocol handler, write all received data + * as BODY to the client. */ + if(blen || is_eos) { + int cwtype = CLIENTWRITE_BODY; + if(is_eos) + cwtype |= CLIENTWRITE_EOS; + result = Curl_client_write(data, cwtype, buf, blen); + } + } + + if(!result && is_eos) { + /* If we wrote the EOS, we are definitely done */ + data->req.eos_written = TRUE; + data->req.download_done = TRUE; + } + CURL_TRC_WRITE(data, "xfer_write_resp(len=%zu, eos=%d) -> %d", + blen, is_eos, result); + return result; +} + +bool Curl_xfer_write_is_paused(struct Curl_easy *data) +{ + return Curl_cwriter_is_paused(data); +} + +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos) +{ + if(data->conn->handler->write_resp_hd) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + return data->conn->handler->write_resp_hd(data, hd0, hdlen, is_eos); + } + /* No special handling by protocol handler, write as response bytes */ + return Curl_xfer_write_resp(data, hd0, hdlen, is_eos); +} + +CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature) +{ + (void)premature; + return Curl_cw_out_done(data); +} + +bool Curl_xfer_needs_flush(struct Curl_easy *data) +{ + int sockindex; + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + return Curl_conn_needs_flush(data, sockindex); +} + +CURLcode Curl_xfer_flush(struct Curl_easy *data) +{ + int sockindex; + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + return Curl_conn_flush(data, sockindex); +} + +CURLcode Curl_xfer_send(struct Curl_easy *data, + const void *buf, size_t blen, bool eos, + size_t *pnwritten) +{ + CURLcode result; + int sockindex; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + result = Curl_conn_send(data, sockindex, buf, blen, eos, pnwritten); + if(result == CURLE_AGAIN) { + result = CURLE_OK; + *pnwritten = 0; + } + else if(!result && *pnwritten) + data->info.request_size += *pnwritten; + + DEBUGF(infof(data, "Curl_xfer_send(len=%zu, eos=%d) -> %d, %zu", + blen, eos, result, *pnwritten)); + return result; +} + +CURLcode Curl_xfer_recv(struct Curl_easy *data, + char *buf, size_t blen, + ssize_t *pnrcvd) +{ + int sockindex; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + DEBUGASSERT(data->set.buffer_size > 0); + + sockindex = ((data->conn->sockfd != CURL_SOCKET_BAD) && + (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET])); + if((size_t)data->set.buffer_size < blen) + blen = (size_t)data->set.buffer_size; + return Curl_conn_recv(data, sockindex, buf, blen, pnrcvd); +} + +CURLcode Curl_xfer_send_close(struct Curl_easy *data) +{ + Curl_conn_ev_data_done_send(data); + return CURLE_OK; +} + +bool Curl_xfer_is_blocked(struct Curl_easy *data) +{ + bool want_send = ((data)->req.keepon & KEEP_SEND); + bool want_recv = ((data)->req.keepon & KEEP_RECV); + if(!want_send) + return want_recv && Curl_cwriter_is_paused(data); + else if(!want_recv) + return want_send && Curl_creader_is_paused(data); + else + return Curl_creader_is_paused(data) && Curl_cwriter_is_paused(data); +} diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h index 536ac249b7c..2c355e0e9d8 100644 --- a/Utilities/cmcurl/lib/transfer.h +++ b/Utilities/cmcurl/lib/transfer.h @@ -32,42 +32,116 @@ char *Curl_checkheaders(const struct Curl_easy *data, void Curl_init_CONNECT(struct Curl_easy *data); CURLcode Curl_pretransfer(struct Curl_easy *data); -CURLcode Curl_posttransfer(struct Curl_easy *data); - -typedef enum { - FOLLOW_NONE, /* not used within the function, just a placeholder to - allow initing to this */ - FOLLOW_FAKE, /* only records stuff, not actually following */ - FOLLOW_RETRY, /* set if this is a request retry as opposed to a real - redirect following */ - FOLLOW_REDIR /* a full true redirect */ -} followtype; - -CURLcode Curl_follow(struct Curl_easy *data, char *newurl, - followtype type); -CURLcode Curl_readwrite(struct connectdata *conn, - struct Curl_easy *data, bool *done, - bool *comeback); + +CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp); int Curl_single_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); -CURLcode Curl_fillreadbuffer(struct Curl_easy *data, size_t bytes, - size_t *nreadp); CURLcode Curl_retry_request(struct Curl_easy *data, char **url); bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc); -CURLcode Curl_get_upload_buffer(struct Curl_easy *data); - -CURLcode Curl_done_sending(struct Curl_easy *data, - struct SingleRequest *k); - -/* This sets up a forthcoming transfer */ -void -Curl_setup_transfer (struct Curl_easy *data, - int sockindex, /* socket index to read from or -1 */ - curl_off_t size, /* -1 if unknown at this point */ - bool getheader, /* TRUE if header parsing is wanted */ - int writesockindex /* socket index to write to. May be - the same we read from. -1 - disables */ - ); + +/** + * Write the transfer raw response bytes, as received from the connection. + * Will handle all passed bytes or return an error. By default, this will + * write the bytes as BODY to the client. Protocols may provide a + * "write_resp" callback in their handler to add specific treatment. E.g. + * HTTP parses response headers and passes them differently to the client. + * @param data the transfer + * @param buf the raw response bytes + * @param blen the amount of bytes in `buf` + * @param is_eos TRUE iff the connection indicates this to be the last + * bytes of the response + */ +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos); + +bool Curl_xfer_write_is_paused(struct Curl_easy *data); + +/** + * Write a single "header" line from a server response. + * @param hd0 the null-terminated, single header line + * @param hdlen the length of the header line + * @param is_eos TRUE iff this is the end of the response + */ +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos); + +#define CURL_XFER_NOP (0) +#define CURL_XFER_RECV (1<<(0)) +#define CURL_XFER_SEND (1<<(1)) +#define CURL_XFER_SENDRECV (CURL_XFER_RECV|CURL_XFER_SEND) + +/** + * The transfer is neither receiving nor sending now. + */ +void Curl_xfer_setup_nop(struct Curl_easy *data); + +/** + * The transfer will use socket 1 to send/recv. `recv_size` is + * the amount to receive or -1 if unknown. `getheader` indicates + * response header processing is expected. + */ +void Curl_xfer_setup1(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool getheader); + +/** + * The transfer will use socket 2 to send/recv. `recv_size` is + * the amount to receive or -1 if unknown. With `shutdown` being + * set, the transfer is only allowed to either send OR receive + * and the socket 2 connection will be shutdown at the end of + * the transfer. An unclean shutdown will fail the transfer + * unless `shutdown_err_ignore` is TRUE. + */ +void Curl_xfer_setup2(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool shutdown, bool shutdown_err_ignore); + +/** + * Multi has set transfer to DONE. Last chance to trigger + * missing response things like writing an EOS to the client. + */ +CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature); + +/** + * Return TRUE iff transfer has pending data to send. Checks involved + * connection filters. + */ +bool Curl_xfer_needs_flush(struct Curl_easy *data); + +/** + * Flush any pending send data on the transfer connection. + */ +CURLcode Curl_xfer_flush(struct Curl_easy *data); + +/** + * Send data on the socket/connection filter designated + * for transfer's outgoing data. + * Will return CURLE_OK on blocking with (*pnwritten == 0). + */ +CURLcode Curl_xfer_send(struct Curl_easy *data, + const void *buf, size_t blen, bool eos, + size_t *pnwritten); + +/** + * Receive data on the socket/connection filter designated + * for transfer's incoming data. + * Will return CURLE_AGAIN on blocking with (*pnrcvd == 0). + */ +CURLcode Curl_xfer_recv(struct Curl_easy *data, + char *buf, size_t blen, + ssize_t *pnrcvd); + +CURLcode Curl_xfer_send_close(struct Curl_easy *data); +CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done); + +/** + * Return TRUE iff the transfer is not done, but further progress + * is blocked. For example when it is only receiving and its writer + * is PAUSED. + */ +bool Curl_xfer_is_blocked(struct Curl_easy *data); #endif /* HEADER_CURL_TRANSFER_H */ diff --git a/Utilities/cmcurl/lib/uint-bset.c b/Utilities/cmcurl/lib/uint-bset.c new file mode 100644 index 00000000000..2560b373634 --- /dev/null +++ b/Utilities/cmcurl/lib/uint-bset.c @@ -0,0 +1,238 @@ +/*************************************************************************** + * _ _ ____ _ + * 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-bset.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef DEBUGBUILD +#define CURL_UINT_BSET_MAGIC 0x62757473 +#endif + +void Curl_uint_bset_init(struct uint_bset *bset) +{ + memset(bset, 0, sizeof(*bset)); +#ifdef DEBUGBUILD + bset->init = CURL_UINT_BSET_MAGIC; +#endif +} + + +CURLcode Curl_uint_bset_resize(struct uint_bset *bset, unsigned int nmax) +{ + unsigned int nslots = (nmax + 63) / 64; + + DEBUGASSERT(bset->init == CURL_UINT_BSET_MAGIC); + if(nslots != bset->nslots) { + curl_uint64_t *slots = calloc(nslots, sizeof(curl_uint64_t)); + if(!slots) + return CURLE_OUT_OF_MEMORY; + + if(bset->slots) { + memcpy(slots, bset->slots, + (CURLMIN(nslots, bset->nslots) * sizeof(curl_uint64_t))); + free(bset->slots); + } + bset->slots = slots; + bset->nslots = nslots; + } + return CURLE_OK; +} + + +void Curl_uint_bset_destroy(struct uint_bset *bset) +{ + DEBUGASSERT(bset->init == CURL_UINT_BSET_MAGIC); + free(bset->slots); + memset(bset, 0, sizeof(*bset)); +} + + +unsigned int Curl_uint_bset_capacity(struct uint_bset *bset) +{ + return bset->nslots * 64; +} + + +unsigned int Curl_uint_bset_count(struct uint_bset *bset) +{ + unsigned int i; + unsigned int n = 0; + for(i = 0; i < bset->nslots; ++i) { + if(bset->slots[i]) + n += CURL_POPCOUNT64(bset->slots[i]); + } + return n; +} + + +bool Curl_uint_bset_empty(struct uint_bset *bset) +{ + unsigned int i; + for(i = 0; i < bset->nslots; ++i) { + if(bset->slots[i]) + return FALSE; + } + return TRUE; +} + + +void Curl_uint_bset_clear(struct uint_bset *bset) +{ + if(bset->nslots) + memset(bset->slots, 0, bset->nslots * sizeof(curl_uint64_t)); +} + + +bool Curl_uint_bset_add(struct uint_bset *bset, unsigned int i) +{ + unsigned int islot = i / 64; + if(islot >= bset->nslots) + return FALSE; + bset->slots[islot] |= ((curl_uint64_t)1 << (i % 64)); + return TRUE; +} + + +void Curl_uint_bset_remove(struct uint_bset *bset, unsigned int i) +{ + size_t islot = i / 64; + if(islot < bset->nslots) + bset->slots[islot] &= ~((curl_uint64_t)1 << (i % 64)); +} + + +bool Curl_uint_bset_contains(struct uint_bset *bset, unsigned int i) +{ + unsigned int islot = i / 64; + if(islot >= bset->nslots) + return FALSE; + return (bset->slots[islot] & ((curl_uint64_t)1 << (i % 64))) != 0; +} + + +bool Curl_uint_bset_first(struct uint_bset *bset, unsigned int *pfirst) +{ + unsigned int i; + for(i = 0; i < bset->nslots; ++i) { + if(bset->slots[i]) { + *pfirst = (i * 64) + CURL_CTZ64(bset->slots[i]); + return TRUE; + } + } + *pfirst = UINT_MAX; /* a value we cannot store */ + return FALSE; +} + +bool Curl_uint_bset_next(struct uint_bset *bset, unsigned int last, + unsigned int *pnext) +{ + unsigned int islot; + curl_uint64_t x; + + ++last; /* look for number one higher than last */ + islot = last / 64; /* the slot this would be in */ + if(islot < bset->nslots) { + /* shift away the bits we already iterated in this slot */ + x = (bset->slots[islot] >> (last % 64)); + if(x) { + /* more bits set, next is `last` + trailing0s of the shifted slot */ + *pnext = last + CURL_CTZ64(x); + return TRUE; + } + /* no more bits set in the last slot, scan forward */ + for(islot = islot + 1; islot < bset->nslots; ++islot) { + if(bset->slots[islot]) { + *pnext = (islot * 64) + CURL_CTZ64(bset->slots[islot]); + return TRUE; + } + } + } + *pnext = UINT_MAX; /* a value we cannot store */ + return FALSE; +} + +#ifdef CURL_POPCOUNT64_IMPLEMENT +unsigned int Curl_popcount64(curl_uint64_t x) +{ + /* Compute the "Hamming Distance" between 'x' and 0, + * which is the number of set bits in 'x'. + * See: https://en.wikipedia.org/wiki/Hamming_weight */ + const curl_uint64_t m1 = CURL_OFF_TU_C(0x5555555555555555); /* 0101+ */ + const curl_uint64_t m2 = CURL_OFF_TU_C(0x3333333333333333); /* 00110011+ */ + const curl_uint64_t m4 = CURL_OFF_TU_C(0x0f0f0f0f0f0f0f0f); /* 00001111+ */ + /* 1 + 256^1 + 256^2 + 256^3 + ... + 256^7 */ + const curl_uint64_t h01 = CURL_OFF_TU_C(0x0101010101010101); + x -= (x >> 1) & m1; /* replace every 2 bits with bits present */ + x = (x & m2) + ((x >> 2) & m2); /* replace every nibble with bits present */ + x = (x + (x >> 4)) & m4; /* replace every byte with bits present */ + /* top 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ... which makes the + * top byte the sum of all individual 8 bytes, throw away the rest */ + return (unsigned int)((x * h01) >> 56); +} +#endif /* CURL_POPCOUNT64_IMPLEMENT */ + + +#ifdef CURL_CTZ64_IMPLEMENT +unsigned int Curl_ctz64(curl_uint64_t x) +{ + /* count trailing zeros in a curl_uint64_t. + * divide and conquer to find the number of lower 0 bits */ + const curl_uint64_t ml32 = CURL_OFF_TU_C(0xFFFFFFFF); /* lower 32 bits */ + const curl_uint64_t ml16 = CURL_OFF_TU_C(0x0000FFFF); /* lower 16 bits */ + const curl_uint64_t ml8 = CURL_OFF_TU_C(0x000000FF); /* lower 8 bits */ + const curl_uint64_t ml4 = CURL_OFF_TU_C(0x0000000F); /* lower 4 bits */ + const curl_uint64_t ml2 = CURL_OFF_TU_C(0x00000003); /* lower 2 bits */ + unsigned int n; + + if(!x) + return 64; + n = 1; + if(!(x & ml32)) { + n = n + 32; + x = x >> 32; + } + if(!(x & ml16)) { + n = n + 16; + x = x >> 16; + } + if(!(x & ml8)) { + n = n + 8; + x = x >> 8; + } + if(!(x & ml4)) { + n = n + 4; + x = x >> 4; + } + if(!(x & ml2)) { + n = n + 2; + x = x >> 2; + } + return n - (unsigned int)(x & 1); +} +#endif /* CURL_CTZ64_IMPLEMENT */ diff --git a/Utilities/cmcurl/lib/uint-bset.h b/Utilities/cmcurl/lib/uint-bset.h new file mode 100644 index 00000000000..d998dccdfe5 --- /dev/null +++ b/Utilities/cmcurl/lib/uint-bset.h @@ -0,0 +1,114 @@ +#ifndef HEADER_CURL_UINT_BSET_H +#define HEADER_CURL_UINT_BSET_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" + +#include + +/* A bitset for unsigned int values. + * It can hold the numbers from 0 - (nmax - 1), + * rounded to the next 64 multiple. + * + * Optimized for high efficiency in adding/removing numbers. + * Efficient storage when the set is (often) relatively full. + * + * If the set's cardinality is only expected to be a fraction of nmax, + * uint_spbset offers a "sparse" variant with more memory efficiency at + * the price of slightly slower operations. + */ + +struct uint_bset { + curl_uint64_t *slots; + unsigned int nslots; +#ifdef DEBUGBUILD + int init; +#endif +}; + +/* Initialize the bitset with capacity 0. */ +void Curl_uint_bset_init(struct uint_bset *bset); + +/* Resize the bitset capacity to hold numbers from 0 to `nmax`, + * which rounds up `nmax` to the next multiple of 64. */ +CURLcode Curl_uint_bset_resize(struct uint_bset *bset, unsigned int nmax); + +/* Destroy the bitset, freeing all resources. */ +void Curl_uint_bset_destroy(struct uint_bset *bset); + +/* Get the bitset capacity, e.g. can hold numbers from 0 to capacity - 1. */ +unsigned int Curl_uint_bset_capacity(struct uint_bset *bset); + +/* Get the cardinality of the bitset, e.g. numbers present in the set. */ +unsigned int Curl_uint_bset_count(struct uint_bset *bset); + +/* TRUE of bitset is empty */ +bool Curl_uint_bset_empty(struct uint_bset *bset); + +/* Clear the bitset, making it empty. */ +void Curl_uint_bset_clear(struct uint_bset *bset); + +/* Add the number `i` to the bitset. Return FALSE if the number is + * outside the set's capacity. + * Numbers can be added more than once, without making a difference. */ +bool Curl_uint_bset_add(struct uint_bset *bset, unsigned int i); + +/* Remove the number `i` from the bitset. */ +void Curl_uint_bset_remove(struct uint_bset *bset, unsigned int i); + +/* Return TRUE if the bitset contains number `i`. */ +bool Curl_uint_bset_contains(struct uint_bset *bset, unsigned int i); + +/* Get the first number in the bitset, e.g. the smallest. + * Returns FALSE when the bitset is empty. */ +bool Curl_uint_bset_first(struct uint_bset *bset, unsigned int *pfirst); + +/* Get the next number in the bitset, following `last` in natural order. + * Put another way, this is the smallest number greater than `last` in + * the bitset. `last` does not have to be present in the set. + * + * Returns FALSE when no such number is in the set. + * + * This allows to iterate the set while being modified: + * - added numbers higher than 'last' will be picked up by the iteration. + * - added numbers lower than 'last' will not show up. + * - removed numbers lower or equal to 'last' will not show up. + * - removed numbers higher than 'last' will not be visited. */ +bool Curl_uint_bset_next(struct uint_bset *bset, unsigned int last, + unsigned int *pnext); + + +#ifndef CURL_POPCOUNT64 +#define CURL_POPCOUNT64(x) Curl_popcount64(x) +#define CURL_POPCOUNT64_IMPLEMENT +unsigned int Curl_popcount64(curl_uint64_t x); +#endif /* !CURL_POPCOUNT64 */ + +#ifndef CURL_CTZ64 +#define CURL_CTZ64(x) Curl_ctz64(x) +#define CURL_CTZ64_IMPLEMENT +unsigned int Curl_ctz64(curl_uint64_t x); +#endif /* !CURL_CTZ64 */ + +#endif /* HEADER_CURL_UINT_BSET_H */ diff --git a/Utilities/cmcurl/lib/uint-hash.c b/Utilities/cmcurl/lib/uint-hash.c new file mode 100644 index 00000000000..afeb684d0ca --- /dev/null +++ b/Utilities/cmcurl/lib/uint-hash.c @@ -0,0 +1,246 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + +#include "uint-hash.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +/* random patterns for API verification */ +#ifdef DEBUGBUILD +#define CURL_UINTHASHINIT 0x7117e779 +#endif + +static unsigned int uint_hash_hash(unsigned int id, unsigned int slots) +{ + return (id % slots); +} + + +struct uint_hash_entry { + struct uint_hash_entry *next; + void *value; + unsigned int id; +}; + +void Curl_uint_hash_init(struct uint_hash *h, + unsigned int slots, + Curl_uint_hash_dtor *dtor) +{ + DEBUGASSERT(h); + DEBUGASSERT(slots); + + h->table = NULL; + h->dtor = dtor; + h->size = 0; + h->slots = slots; +#ifdef DEBUGBUILD + h->init = CURL_UINTHASHINIT; +#endif +} + +static struct uint_hash_entry *uint_hash_mk_entry(unsigned int id, void *value) +{ + struct uint_hash_entry *e; + + /* allocate the struct for the hash entry */ + e = malloc(sizeof(*e)); + if(e) { + e->id = id; + e->next = NULL; + e->value = value; + } + return e; +} + +static void uint_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 uint_hash_entry_destroy(struct uint_hash *h, + struct uint_hash_entry *e) +{ + uint_hash_entry_clear(h, e); + free(e); +} + +static void uint_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 uint_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_UINT_HASH_SLOT(h,id) h->table[uint_hash_hash(id, h->slots)] +#define CURL_UINT_HASH_SLOT_ADDR(h,id) &CURL_UINT_HASH_SLOT(h,id) + +bool Curl_uint_hash_set(struct uint_hash *h, unsigned int id, void *value) +{ + struct uint_hash_entry *he, **slot; + + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(!h->table) { + h->table = calloc(h->slots, sizeof(*he)); + if(!h->table) + return FALSE; /* OOM */ + } + + slot = CURL_UINT_HASH_SLOT_ADDR(h, id); + for(he = *slot; he; he = he->next) { + if(he->id == id) { + /* existing key entry, overwrite by clearing old pointer */ + uint_hash_entry_clear(h, he); + he->value = value; + return TRUE; + } + } + + he = uint_hash_mk_entry(id, value); + if(!he) + return FALSE; /* OOM */ + + uint_hash_elem_link(h, slot, he); + return TRUE; +} + +bool Curl_uint_hash_remove(struct uint_hash *h, unsigned int id) +{ + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(h->table) { + struct uint_hash_entry *he, **he_anchor; + + he_anchor = CURL_UINT_HASH_SLOT_ADDR(h, id); + while(*he_anchor) { + he = *he_anchor; + if(id == he->id) { + uint_hash_entry_unlink(h, he_anchor, he); + uint_hash_entry_destroy(h, he); + return TRUE; + } + he_anchor = &he->next; + } + } + return FALSE; +} + +void *Curl_uint_hash_get(struct uint_hash *h, unsigned int id) +{ + DEBUGASSERT(h); + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(h->table) { + struct uint_hash_entry *he; + DEBUGASSERT(h->slots); + he = CURL_UINT_HASH_SLOT(h, id); + while(he) { + if(id == he->id) { + return he->value; + } + he = he->next; + } + } + return NULL; +} + +static 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_UINTHASHINIT); + for(i = 0; i < h->slots; ++i) { + he_anchor = &h->table[i]; + while(*he_anchor) { + he = *he_anchor; + uint_hash_entry_unlink(h, he_anchor, he); + uint_hash_entry_destroy(h, he); + } + } + } +} + +void Curl_uint_hash_clear(struct uint_hash *h) +{ + uint_hash_clear(h); +} + +void Curl_uint_hash_destroy(struct uint_hash *h) +{ + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(h->table) { + uint_hash_clear(h); + Curl_safefree(h->table); + } + DEBUGASSERT(h->size == 0); + h->slots = 0; +} + +unsigned int Curl_uint_hash_count(struct uint_hash *h) +{ + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + return h->size; +} + +void Curl_uint_hash_visit(struct uint_hash *h, + Curl_uint_hash_visit_cb *cb, + void *user_data) +{ + if(h && h->table && cb) { + struct uint_hash_entry *he; + size_t i; + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + 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/Utilities/cmcurl/lib/uint-hash.h b/Utilities/cmcurl/lib/uint-hash.h new file mode 100644 index 00000000000..1b52dba4c47 --- /dev/null +++ b/Utilities/cmcurl/lib/uint-hash.h @@ -0,0 +1,68 @@ +#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" + +#include + +#include "llist.h" + +/* A version with unsigned int as key */ +typedef void Curl_uint_hash_dtor(unsigned int id, void *value); +struct uint_hash_entry; + +/* Hash for `unsigned int` as key */ +struct uint_hash { + struct uint_hash_entry **table; + Curl_uint_hash_dtor *dtor; + unsigned int slots; + unsigned int size; +#ifdef DEBUGBUILD + int init; +#endif +}; + + +void Curl_uint_hash_init(struct uint_hash *h, + unsigned int slots, + Curl_uint_hash_dtor *dtor); +void Curl_uint_hash_destroy(struct uint_hash *h); +void Curl_uint_hash_clear(struct uint_hash *h); + +bool Curl_uint_hash_set(struct uint_hash *h, unsigned int id, void *value); +bool Curl_uint_hash_remove(struct uint_hash *h, unsigned int id); +void *Curl_uint_hash_get(struct uint_hash *h, unsigned int id); +unsigned int Curl_uint_hash_count(struct uint_hash *h); + + +typedef bool Curl_uint_hash_visit_cb(unsigned int id, void *value, + void *user_data); + +void Curl_uint_hash_visit(struct uint_hash *h, + Curl_uint_hash_visit_cb *cb, + void *user_data); + +#endif /* HEADER_CURL_UINT_HASH_H */ diff --git a/Utilities/cmcurl/lib/uint-spbset.c b/Utilities/cmcurl/lib/uint-spbset.c new file mode 100644 index 00000000000..578b9bd07c1 --- /dev/null +++ b/Utilities/cmcurl/lib/uint-spbset.c @@ -0,0 +1,273 @@ +/*************************************************************************** + * _ _ ____ _ + * 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-bset.h" +#include "uint-spbset.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef DEBUGBUILD +#define CURL_UINT_SPBSET_MAGIC 0x70737362 +#endif + +void Curl_uint_spbset_init(struct uint_spbset *bset) +{ + memset(bset, 0, sizeof(*bset)); +#ifdef DEBUGBUILD + bset->init = CURL_UINT_SPBSET_MAGIC; +#endif +} + +void Curl_uint_spbset_destroy(struct uint_spbset *bset) +{ + DEBUGASSERT(bset->init == CURL_UINT_SPBSET_MAGIC); + Curl_uint_spbset_clear(bset); +} + +unsigned int Curl_uint_spbset_count(struct uint_spbset *bset) +{ + struct uint_spbset_chunk *chunk; + unsigned int i, n = 0; + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) + n += CURL_POPCOUNT64(chunk->slots[i]); + } + } + return n; +} + +bool Curl_uint_spbset_empty(struct uint_spbset *bset) +{ + struct uint_spbset_chunk *chunk; + unsigned int i; + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) + return FALSE; + } + } + return TRUE; +} + +void Curl_uint_spbset_clear(struct uint_spbset *bset) +{ + struct uint_spbset_chunk *next, *chunk; + + for(chunk = bset->head.next; chunk; chunk = next) { + next = chunk->next; + free(chunk); + } + memset(&bset->head, 0, sizeof(bset->head)); +} + + +static struct uint_spbset_chunk * +uint_spbset_get_chunk(struct uint_spbset *bset, unsigned int i, bool grow) +{ + struct uint_spbset_chunk *chunk, **panchor = NULL; + unsigned int i_offset = (i & ~CURL_UINT_SPBSET_CH_MASK); + + if(!bset) + return NULL; + + for(chunk = &bset->head; chunk; + panchor = &chunk->next, chunk = chunk->next) { + if(chunk->offset == i_offset) { + return chunk; + } + else if(chunk->offset > i_offset) { + /* need new chunk here */ + chunk = NULL; + break; + } + } + + if(!grow) + return NULL; + + /* need a new one */ + chunk = calloc(1, sizeof(*chunk)); + if(!chunk) + return NULL; + + if(panchor) { /* insert between panchor and *panchor */ + chunk->next = *panchor; + *panchor = chunk; + } + else { /* prepend to head, switching places */ + memcpy(chunk, &bset->head, sizeof(*chunk)); + memset(&bset->head, 0, sizeof(bset->head)); + bset->head.next = chunk; + } + chunk->offset = i_offset; + return chunk; +} + + +bool Curl_uint_spbset_add(struct uint_spbset *bset, unsigned int i) +{ + struct uint_spbset_chunk *chunk; + unsigned int i_chunk; + + chunk = uint_spbset_get_chunk(bset, i, TRUE); + if(!chunk) + return FALSE; + + DEBUGASSERT(i >= chunk->offset); + i_chunk = (i - chunk->offset); + DEBUGASSERT((i_chunk / 64) < CURL_UINT_SPBSET_CH_SLOTS); + chunk->slots[(i_chunk / 64)] |= ((curl_uint64_t)1 << (i_chunk % 64)); + return TRUE; +} + + +void Curl_uint_spbset_remove(struct uint_spbset *bset, unsigned int i) +{ + struct uint_spbset_chunk *chunk; + unsigned int i_chunk; + + chunk = uint_spbset_get_chunk(bset, i, FALSE); + if(chunk) { + DEBUGASSERT(i >= chunk->offset); + i_chunk = (i - chunk->offset); + DEBUGASSERT((i_chunk / 64) < CURL_UINT_SPBSET_CH_SLOTS); + chunk->slots[(i_chunk / 64)] &= ~((curl_uint64_t)1 << (i_chunk % 64)); + } +} + + +bool Curl_uint_spbset_contains(struct uint_spbset *bset, unsigned int i) +{ + struct uint_spbset_chunk *chunk; + unsigned int i_chunk; + + chunk = uint_spbset_get_chunk(bset, i, FALSE); + if(chunk) { + DEBUGASSERT(i >= chunk->offset); + i_chunk = (i - chunk->offset); + DEBUGASSERT((i_chunk / 64) < CURL_UINT_SPBSET_CH_SLOTS); + return (chunk->slots[i_chunk / 64] & + ((curl_uint64_t)1 << (i_chunk % 64))) != 0; + } + return FALSE; +} + +bool Curl_uint_spbset_first(struct uint_spbset *bset, unsigned int *pfirst) +{ + struct uint_spbset_chunk *chunk; + unsigned int i; + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) { + *pfirst = chunk->offset + ((i * 64) + CURL_CTZ64(chunk->slots[i])); + return TRUE; + } + } + } + *pfirst = 0; /* give it a defined value even if it should not be used */ + return FALSE; +} + + +static bool uint_spbset_chunk_first(struct uint_spbset_chunk *chunk, + unsigned int *pfirst) +{ + unsigned int i; + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) { + *pfirst = chunk->offset + ((i * 64) + CURL_CTZ64(chunk->slots[i])); + return TRUE; + } + } + *pfirst = UINT_MAX; /* a value we cannot store */ + return FALSE; +} + + +static bool uint_spbset_chunk_next(struct uint_spbset_chunk *chunk, + unsigned int last, + unsigned int *pnext) +{ + if(chunk->offset <= last) { + curl_uint64_t x; + unsigned int i = ((last - chunk->offset) / 64); + if(i < CURL_UINT_SPBSET_CH_SLOTS) { + x = (chunk->slots[i] >> (last % 64)); + if(x) { + /* more bits set, next is `last` + trailing0s of the shifted slot */ + *pnext = last + CURL_CTZ64(x); + return TRUE; + } + /* no more bits set in the last slot, scan forward */ + for(i = i + 1; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) { + *pnext = chunk->offset + ((i * 64) + CURL_CTZ64(chunk->slots[i])); + return TRUE; + } + } + } + } + *pnext = UINT_MAX; + return FALSE; +} + +bool Curl_uint_spbset_next(struct uint_spbset *bset, unsigned int last, + unsigned int *pnext) +{ + struct uint_spbset_chunk *chunk; + unsigned int last_offset; + + ++last; /* look for the next higher number */ + last_offset = (last & ~CURL_UINT_SPBSET_CH_MASK); + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + if(chunk->offset >= last_offset) { + break; + } + } + + if(chunk && (chunk->offset == last_offset)) { + /* is there a number higher than last in this chunk? */ + if(uint_spbset_chunk_next(chunk, last, pnext)) + return TRUE; + /* not in this chunk */ + chunk = chunk->next; + } + /* look for the first in the "higher" chunks, if there are any. */ + while(chunk) { + if(uint_spbset_chunk_first(chunk, pnext)) + return TRUE; + chunk = chunk->next; + } + *pnext = UINT_MAX; + return FALSE; +} diff --git a/Utilities/cmcurl/lib/uint-spbset.h b/Utilities/cmcurl/lib/uint-spbset.h new file mode 100644 index 00000000000..571d56753cd --- /dev/null +++ b/Utilities/cmcurl/lib/uint-spbset.h @@ -0,0 +1,99 @@ +#ifndef HEADER_CURL_UINT_SPBSET_H +#define HEADER_CURL_UINT_SPBSET_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" + +#include + +/* A "sparse" bitset for unsigned int values. + * It can hold any unsigned int value. + * + * Optimized for the case where only a small set of numbers need + * to be kept, especially when "close" together. Then storage space + * is most efficient, deteriorating when many number are far apart. + */ + +/* 4 slots = 256 bits, keep this a 2^n value. */ +#define CURL_UINT_SPBSET_CH_SLOTS 4 +#define CURL_UINT_SPBSET_CH_MASK ((CURL_UINT_SPBSET_CH_SLOTS * 64) - 1) + +/* store the uint value from offset to + * (offset + (CURL_UINT_SPBSET_CHUNK_SLOTS * 64) - 1 */ +struct uint_spbset_chunk { + struct uint_spbset_chunk *next; + curl_uint64_t slots[CURL_UINT_SPBSET_CH_SLOTS]; + unsigned int offset; +}; + +struct uint_spbset { + struct uint_spbset_chunk head; +#ifdef DEBUGBUILD + int init; +#endif +}; + +void Curl_uint_spbset_init(struct uint_spbset *bset); + +void Curl_uint_spbset_destroy(struct uint_spbset *bset); + +/* Get the cardinality of the bitset, e.g. numbers present in the set. */ +unsigned int Curl_uint_spbset_count(struct uint_spbset *bset); + +/* TRUE of bitset is empty */ +bool Curl_uint_spbset_empty(struct uint_spbset *bset); + +/* Clear the bitset, making it empty. */ +void Curl_uint_spbset_clear(struct uint_spbset *bset); + +/* Add the number `i` to the bitset. + * Numbers can be added more than once, without making a difference. + * Returns FALSE if allocations failed. */ +bool Curl_uint_spbset_add(struct uint_spbset *bset, unsigned int i); + +/* Remove the number `i` from the bitset. */ +void Curl_uint_spbset_remove(struct uint_spbset *bset, unsigned int i); + +/* Return TRUE if the bitset contains number `i`. */ +bool Curl_uint_spbset_contains(struct uint_spbset *bset, unsigned int i); + +/* Get the first number in the bitset, e.g. the smallest. + * Returns FALSE when the bitset is empty. */ +bool Curl_uint_spbset_first(struct uint_spbset *bset, unsigned int *pfirst); + +/* Get the next number in the bitset, following `last` in natural order. + * Put another way, this is the smallest number greater than `last` in + * the bitset. `last` does not have to be present in the set. + * + * Returns FALSE when no such number is in the set. + * + * This allows to iterate the set while being modified: + * - added numbers higher than 'last' will be picked up by the iteration. + * - added numbers lower than 'last' will not show up. + * - removed numbers lower or equal to 'last' will not show up. + * - removed numbers higher than 'last' will not be visited. */ +bool Curl_uint_spbset_next(struct uint_spbset *bset, unsigned int last, + unsigned int *pnext); + +#endif /* HEADER_CURL_UINT_SPBSET_H */ diff --git a/Utilities/cmcurl/lib/uint-table.c b/Utilities/cmcurl/lib/uint-table.c new file mode 100644 index 00000000000..d8de1b128ab --- /dev/null +++ b/Utilities/cmcurl/lib/uint-table.c @@ -0,0 +1,214 @@ +/*************************************************************************** + * _ _ ____ _ + * 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-table.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef DEBUGBUILD +#define CURL_UINT_TBL_MAGIC 0x62757473 +#endif + +void Curl_uint_tbl_init(struct uint_tbl *tbl, + Curl_uint_tbl_entry_dtor *entry_dtor) +{ + memset(tbl, 0, sizeof(*tbl)); + tbl->entry_dtor = entry_dtor; + tbl->last_key_added = UINT_MAX; +#ifdef DEBUGBUILD + tbl->init = CURL_UINT_TBL_MAGIC; +#endif +} + + +static void uint_tbl_clear_rows(struct uint_tbl *tbl, + unsigned int from, + unsigned int upto_excluding) +{ + unsigned int i, end; + + 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--; + } + } +} + + +CURLcode Curl_uint_tbl_resize(struct uint_tbl *tbl, unsigned int nrows) +{ + /* we use `tbl->nrows + 1` during iteration, want that to work */ + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + if(!nrows || (nrows == UINT_MAX)) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(nrows != tbl->nrows) { + void **rows = calloc(nrows, sizeof(void *)); + if(!rows) + return CURLE_OUT_OF_MEMORY; + if(tbl->rows) { + memcpy(rows, tbl->rows, (CURLMIN(nrows, tbl->nrows) * sizeof(void *))); + if(nrows < tbl->nrows) + uint_tbl_clear_rows(tbl, nrows, tbl->nrows); + free(tbl->rows); + } + tbl->rows = rows; + tbl->nrows = nrows; + } + return CURLE_OK; +} + + +void Curl_uint_tbl_destroy(struct uint_tbl *tbl) +{ + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + Curl_uint_tbl_clear(tbl); + free(tbl->rows); + memset(tbl, 0, sizeof(*tbl)); +} + + +void Curl_uint_tbl_clear(struct uint_tbl *tbl) +{ + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + uint_tbl_clear_rows(tbl, 0, tbl->nrows); + DEBUGASSERT(!tbl->nentries); + tbl->last_key_added = UINT_MAX; +} + + +unsigned int Curl_uint_tbl_capacity(struct uint_tbl *tbl) +{ + return tbl->nrows; +} + + +unsigned int Curl_uint_tbl_count(struct uint_tbl *tbl) +{ + return tbl->nentries; +} + + +void *Curl_uint_tbl_get(struct uint_tbl *tbl, unsigned int key) +{ + return (key < tbl->nrows) ? tbl->rows[key] : NULL; +} + + +bool Curl_uint_tbl_add(struct uint_tbl *tbl, void *entry, unsigned int *pkey) +{ + unsigned int key, start_pos; + + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + if(!entry || !pkey) + return FALSE; + *pkey = UINT_MAX; /* always invalid */ + if(tbl->nentries == tbl->nrows) /* full */ + return FALSE; + + start_pos = CURLMIN(tbl->last_key_added, tbl->nrows) + 1; + for(key = start_pos; key < tbl->nrows; ++key) { + if(!tbl->rows[key]) { + tbl->rows[key] = entry; + tbl->nentries++; + tbl->last_key_added = key; + *pkey = key; + return TRUE; + } + } + /* no free entry at or above tbl->maybe_next_key, wrap around */ + for(key = 0; key < start_pos; ++key) { + if(!tbl->rows[key]) { + tbl->rows[key] = entry; + tbl->nentries++; + tbl->last_key_added = key; + *pkey = key; + return TRUE; + } + } + /* Did not find any free row? Should not happen */ + DEBUGASSERT(0); + return FALSE; +} + + +void Curl_uint_tbl_remove(struct uint_tbl *tbl, unsigned int key) +{ + uint_tbl_clear_rows(tbl, key, key + 1); +} + + +bool Curl_uint_tbl_contains(struct uint_tbl *tbl, unsigned int key) +{ + return (key < tbl->nrows) ? !!tbl->rows[key] : FALSE; +} + + +static bool uint_tbl_next_at(struct uint_tbl *tbl, unsigned int key, + unsigned int *pkey, void **pentry) +{ + for(; key < tbl->nrows; ++key) { + if(tbl->rows[key]) { + *pkey = key; + *pentry = tbl->rows[key]; + return TRUE; + } + } + *pkey = UINT_MAX; /* always invalid */ + *pentry = NULL; + return FALSE; +} + +bool Curl_uint_tbl_first(struct uint_tbl *tbl, + unsigned int *pkey, void **pentry) +{ + if(!pkey || !pentry) + return FALSE; + if(tbl->nentries && uint_tbl_next_at(tbl, 0, pkey, pentry)) + return TRUE; + DEBUGASSERT(!tbl->nentries); + *pkey = UINT_MAX; /* always invalid */ + *pentry = NULL; + return FALSE; +} + + +bool Curl_uint_tbl_next(struct uint_tbl *tbl, unsigned int last_key, + unsigned int *pkey, void **pentry) +{ + if(!pkey || !pentry) + return FALSE; + if(uint_tbl_next_at(tbl, last_key + 1, pkey, pentry)) + return TRUE; + *pkey = UINT_MAX; /* always invalid */ + *pentry = NULL; + return FALSE; +} diff --git a/Utilities/cmcurl/lib/uint-table.h b/Utilities/cmcurl/lib/uint-table.h new file mode 100644 index 00000000000..2c05b1de1ab --- /dev/null +++ b/Utilities/cmcurl/lib/uint-table.h @@ -0,0 +1,101 @@ +#ifndef HEADER_CURL_UINT_TABLE_H +#define HEADER_CURL_UINT_TABLE_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" + +#include + +/* Destructor for a single table entry */ +typedef void Curl_uint_tbl_entry_dtor(unsigned int key, void *entry); + +struct uint_tbl { + void **rows; /* array of void* holding entries */ + Curl_uint_tbl_entry_dtor *entry_dtor; + unsigned int nrows; /* length of `rows` array */ + unsigned int nentries; /* entries in table */ + unsigned int last_key_added; /* UINT_MAX or last key added */ +#ifdef DEBUGBUILD + int init; +#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_uint_tbl_init(struct uint_tbl *tbl, + Curl_uint_tbl_entry_dtor *entry_dtor); + +/* 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_uint_tbl_resize(struct uint_tbl *tbl, unsigned int nmax); + +/* Destroy the table, freeing all entries. */ +void Curl_uint_tbl_destroy(struct uint_tbl *tbl); + +/* Get the table capacity. */ +unsigned int Curl_uint_tbl_capacity(struct uint_tbl *tbl); + +/* Get the number of entries in the table. */ +unsigned int Curl_uint_tbl_count(struct uint_tbl *tbl); + +/* Clear the table, making it empty. */ +void Curl_uint_tbl_clear(struct uint_tbl *tbl); + +/* Get the entry for key or NULL if not present */ +void *Curl_uint_tbl_get(struct uint_tbl *tbl, unsigned int key); + +/* Add a new entry to the table and assign it a free key. + * Returns FALSE if the table is full. + * + * Keys are assigned in a round-robin manner. + * No matter the capacity, UINT_MAX is never assigned. */ +bool Curl_uint_tbl_add(struct uint_tbl *tbl, void *entry, unsigned int *pkey); + +/* Remove the entry with `key`. */ +void Curl_uint_tbl_remove(struct uint_tbl *tbl, unsigned int key); + +/* Return TRUE if the table contains an tryn with that keys. */ +bool Curl_uint_tbl_contains(struct uint_tbl *tbl, unsigned int key); + +/* Get the first entry in the table (with the smallest `key`). + * Returns FALSE if the table is empty. */ +bool Curl_uint_tbl_first(struct uint_tbl *tbl, + unsigned int *pkey, void **pentry); + +/* Get the next key in the table, following `last_key` in natural order. + * Put another way, this is the smallest key greater than `last_key` in + * the table. `last_key` does not have to be present in the table. + * + * Returns FALSE when no such entry is in the table. + * + * This allows to iterate the table while being modified: + * - added keys higher than 'last_key' will be picked up by the iteration. + * - added keys lower than 'last_key' will not show up. + * - removed keys lower or equal to 'last_key' will not show up. + * - removed keys higher than 'last_key' will not be visited. */ +bool Curl_uint_tbl_next(struct uint_tbl *tbl, unsigned int last_key, + unsigned int *pkey, void **pentry); + +#endif /* HEADER_CURL_UINT_TABLE_H */ diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c index 0fb6268341a..10e37ec67f4 100644 --- a/Utilities/cmcurl/lib/url.c +++ b/Utilities/cmcurl/lib/url.c @@ -56,7 +56,7 @@ #endif #ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" +#error "We cannot compile without socket() support!" #endif #include @@ -75,7 +75,6 @@ #include "strcase.h" #include "strerror.h" #include "escape.h" -#include "strtok.h" #include "share.h" #include "content_encoding.h" #include "http_digest.h" @@ -84,13 +83,15 @@ #include "multiif.h" #include "easyif.h" #include "speedcheck.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "getinfo.h" +#include "pop3.h" #include "urlapi-int.h" #include "system_win32.h" #include "hsts.h" #include "noproxy.h" #include "cfilters.h" +#include "curl_krb5.h" #include "idn.h" /* And now for the protocols */ @@ -117,18 +118,14 @@ #include "strdup.h" #include "setopt.h" #include "altsvc.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "headers.h" - +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif - #ifdef USE_NGHTTP2 static void data_priority_cleanup(struct Curl_easy *data); #else @@ -136,7 +133,7 @@ static void data_priority_cleanup(struct Curl_easy *data); #endif /* Some parts of the code (e.g. chunked encoding) assume this buffer has at - * more than just a few bytes to play with. Don't let it become too small or + * more than just a few bytes to play with. Do not let it become too small or * bad things will happen. */ #if READBUFFER_SIZE < READBUFFER_MIN @@ -168,130 +165,6 @@ static curl_prot_t get_protocol_family(const struct Curl_handler *h) return h->family; } - -/* - * Protocol table. Schemes (roughly) in 2019 popularity order: - * - * HTTPS, HTTP, FTP, FTPS, SFTP, FILE, SCP, SMTP, LDAP, IMAPS, TELNET, IMAP, - * LDAPS, SMTPS, TFTP, SMB, POP3, GOPHER POP3S, RTSP, RTMP, SMBS, DICT - */ -static const struct Curl_handler * const protocols[] = { - -#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) - &Curl_handler_https, -#endif - -#ifndef CURL_DISABLE_HTTP - &Curl_handler_http, -#endif - -#ifdef USE_WEBSOCKETS -#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) - &Curl_handler_wss, -#endif - -#ifndef CURL_DISABLE_HTTP - &Curl_handler_ws, -#endif -#endif - -#ifndef CURL_DISABLE_FTP - &Curl_handler_ftp, -#endif - -#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) - &Curl_handler_ftps, -#endif - -#if defined(USE_SSH) - &Curl_handler_sftp, -#endif - -#ifndef CURL_DISABLE_FILE - &Curl_handler_file, -#endif - -#if defined(USE_SSH) && !defined(USE_WOLFSSH) - &Curl_handler_scp, -#endif - -#ifndef CURL_DISABLE_SMTP - &Curl_handler_smtp, -#ifdef USE_SSL - &Curl_handler_smtps, -#endif -#endif - -#ifndef CURL_DISABLE_LDAP - &Curl_handler_ldap, -#if !defined(CURL_DISABLE_LDAPS) && \ - ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ - (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) - &Curl_handler_ldaps, -#endif -#endif - -#ifndef CURL_DISABLE_IMAP - &Curl_handler_imap, -#ifdef USE_SSL - &Curl_handler_imaps, -#endif -#endif - -#ifndef CURL_DISABLE_TELNET - &Curl_handler_telnet, -#endif - -#ifndef CURL_DISABLE_TFTP - &Curl_handler_tftp, -#endif - -#ifndef CURL_DISABLE_POP3 - &Curl_handler_pop3, -#ifdef USE_SSL - &Curl_handler_pop3s, -#endif -#endif - -#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ - (SIZEOF_CURL_OFF_T > 4) - &Curl_handler_smb, -#ifdef USE_SSL - &Curl_handler_smbs, -#endif -#endif - -#ifndef CURL_DISABLE_RTSP - &Curl_handler_rtsp, -#endif - -#ifndef CURL_DISABLE_MQTT - &Curl_handler_mqtt, -#endif - -#ifndef CURL_DISABLE_GOPHER - &Curl_handler_gopher, -#ifdef USE_SSL - &Curl_handler_gophers, -#endif -#endif - -#ifdef USE_LIBRTMP - &Curl_handler_rtmp, - &Curl_handler_rtmpt, - &Curl_handler_rtmpe, - &Curl_handler_rtmpte, - &Curl_handler_rtmps, - &Curl_handler_rtmpts, -#endif - -#ifndef CURL_DISABLE_DICT - &Curl_handler_dict, -#endif - - (struct Curl_handler *) NULL -}; - void Curl_freeset(struct Curl_easy *data) { /* Free all dynamic strings stored in the data->set substructure. */ @@ -320,8 +193,8 @@ void Curl_freeset(struct Curl_easy *data) Curl_mime_cleanpart(&data->set.mimepost); #ifndef CURL_DISABLE_COOKIES - curl_slist_free_all(data->set.cookielist); - data->set.cookielist = NULL; + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; #endif } @@ -358,23 +231,25 @@ CURLcode Curl_close(struct Curl_easy **datap) data = *datap; *datap = NULL; - Curl_expire_clear(data); /* shut off timers */ - /* Detach connection if any is left. This should not be normal, but can be the case for example with CONNECT_ONLY + recv/send (test 556) */ Curl_detach_connection(data); - if(data->multi) - /* This handle is still part of a multi handle, take care of this first - and detach this handle from there. */ - curl_multi_remove_handle(data->multi, data); - - if(data->multi_easy) { - /* when curl_easy_perform() is used, it creates its own multi handle to - use and this is the one */ - curl_multi_cleanup(data->multi_easy); - data->multi_easy = NULL; + if(!data->state.internal) { + if(data->multi) + /* This handle is still part of a multi handle, take care of this first + and detach this handle from there. */ + curl_multi_remove_handle(data->multi, data); + + if(data->multi_easy) { + /* when curl_easy_perform() is used, it creates its own multi handle to + use and this is the one */ + curl_multi_cleanup(data->multi_easy); + data->multi_easy = NULL; + } } + Curl_expire_clear(data); /* shut off any timers left */ + data->magic = 0; /* force a clear AFTER the possibly enforced removal from the multi handle, since that function uses the magic field! */ @@ -382,19 +257,14 @@ CURLcode Curl_close(struct Curl_easy **datap) if(data->state.rangestringalloc) free(data->state.range); - /* freed here just in case DONE wasn't called */ - Curl_free_request_state(data); + /* freed here just in case DONE was not called */ + Curl_req_free(&data->req, data); /* Close down all open SSL info and sessions */ Curl_ssl_close_all(data); Curl_safefree(data->state.first_host); - Curl_safefree(data->state.scratch); Curl_ssl_free_certinfo(data); - /* Cleanup possible redirect junk */ - free(data->req.newurl); - data->req.newurl = NULL; - if(data->state.referer_alloc) { Curl_safefree(data->state.referer); data->state.referer_alloc = FALSE; @@ -402,27 +272,29 @@ CURLcode Curl_close(struct Curl_easy **datap) data->state.referer = NULL; up_free(data); - Curl_safefree(data->state.buffer); - Curl_dyn_free(&data->state.headerb); - Curl_safefree(data->state.ulbuf); + curlx_dyn_free(&data->state.headerb); Curl_flush_cookies(data, TRUE); +#ifndef CURL_DISABLE_ALTSVC Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); Curl_altsvc_cleanup(&data->asi); - Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); +#endif #ifndef CURL_DISABLE_HSTS + Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); if(!data->share || !data->share->hsts) Curl_hsts_cleanup(&data->hsts); - curl_slist_free_all(data->set.hstslist); /* clean up list */ + curl_slist_free_all(data->state.hstslist); /* clean up list */ #endif -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) Curl_http_auth_cleanup_digest(data); #endif + Curl_safefree(data->state.most_recent_ftp_entrypath); Curl_safefree(data->info.contenttype); Curl_safefree(data->info.wouldredirect); - /* this destroys the channel and we cannot use it anymore after this */ - Curl_resolver_cancel(data); - Curl_resolver_cleanup(data->state.async.resolver); + /* release any resolve information this transfer kept */ + Curl_async_destroy(data); + Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */ + Curl_resolv_unlink(data, &data->state.dns[1]); data_priority_cleanup(data); @@ -433,7 +305,10 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); } + Curl_hash_destroy(&data->meta_hash); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); +#endif Curl_safefree(data->state.aptr.uagent); Curl_safefree(data->state.aptr.userpwd); Curl_safefree(data->state.aptr.accept_encoding); @@ -441,26 +316,29 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_safefree(data->state.aptr.rangeline); Curl_safefree(data->state.aptr.ref); Curl_safefree(data->state.aptr.host); +#ifndef CURL_DISABLE_COOKIES Curl_safefree(data->state.aptr.cookiehost); +#endif +#ifndef CURL_DISABLE_RTSP Curl_safefree(data->state.aptr.rtsp_transport); +#endif Curl_safefree(data->state.aptr.user); Curl_safefree(data->state.aptr.passwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuser); Curl_safefree(data->state.aptr.proxypasswd); +#endif -#ifndef CURL_DISABLE_DOH - if(data->req.doh) { - Curl_dyn_free(&data->req.doh->probe[0].serverdoh); - Curl_dyn_free(&data->req.doh->probe[1].serverdoh); - curl_slist_free_all(data->req.doh->headers); - Curl_safefree(data->req.doh); - } +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); #endif /* destruct wildcard structures if it is needed */ Curl_wildcard_dtor(&data->wildcard); Curl_freeset(data); Curl_headers_cleanup(data); + Curl_netrc_cleanup(&data->state.netrc); free(data); return CURLE_OK; } @@ -485,12 +363,11 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->fread_func_set = (curl_read_callback)fread; set->is_fread_set = 0; - set->seek_func = ZERO_NULL; set->seek_client = ZERO_NULL; - set->filesize = -1; /* we don't know the size */ + set->filesize = -1; /* we do not know the size */ set->postfieldsize = -1; /* unknown size */ - set->maxredirs = -1; /* allow any amount by default */ + set->maxredirs = 30; /* sensible default */ set->method = HTTPREQ_GET; /* Default HTTP request */ #ifndef CURL_DISABLE_RTSP @@ -505,8 +382,6 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) #endif set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */ - /* Set the default size of the SSL session ID cache */ - set->general_ssl.max_ssl_sessions = 5; /* Timeout every 24 hours by default */ set->general_ssl.ca_cache_timeout = 24 * 60 * 60; @@ -520,36 +395,22 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI; #endif - /* make libcurl quiet by default: */ - set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */ - Curl_mime_initpart(&set->mimepost); - /* - * libcurl 7.10 introduced SSL verification *by default*! This needs to be - * switched off unless wanted. - */ + Curl_ssl_easy_config_init(data); #ifndef CURL_DISABLE_DOH set->doh_verifyhost = TRUE; set->doh_verifypeer = TRUE; #endif - set->ssl.primary.verifypeer = TRUE; - set->ssl.primary.verifyhost = TRUE; #ifdef USE_SSH /* defaults to any auth type */ set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; set->new_directory_perms = 0755; /* Default permissions */ #endif - set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by - default */ -#ifndef CURL_DISABLE_PROXY - set->proxy_ssl = set->ssl; -#endif set->new_file_perms = 0644; /* Default permissions */ set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL; - set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | - CURLPROTO_FTPS; + set->redir_protocols = CURLPROTO_REDIR; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) /* @@ -561,29 +422,33 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) /* Set the default CA cert bundle/path detected/specified at build time. * - * If Schannel is the selected SSL backend then these locations are - * ignored. We allow setting CA location for schannel only when explicitly - * specified by the user via CURLOPT_CAINFO / --cacert. + * If Schannel or Secure Transport is the selected SSL backend then these + * locations are ignored. We allow setting CA location for Schannel and + * Secure Transport when explicitly specified by the user via + * CURLOPT_CAINFO / --cacert. */ - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { -#if defined(CURL_CA_BUNDLE) + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL && + Curl_ssl_backend() != CURLSSLBACKEND_SECURETRANSPORT) { +#ifdef CURL_CA_BUNDLE result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); if(result) return result; - +#ifndef CURL_DISABLE_PROXY result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], CURL_CA_BUNDLE); if(result) return result; #endif -#if defined(CURL_CA_PATH) +#endif +#ifdef CURL_CA_PATH result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); if(result) return result; - +#ifndef CURL_DISABLE_PROXY result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); if(result) return result; +#endif #endif } @@ -596,6 +461,7 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->tcp_keepalive = FALSE; set->tcp_keepintvl = 60; set->tcp_keepidle = 60; + set->tcp_keepcnt = 9; set->tcp_fastopen = FALSE; set->tcp_nodelay = TRUE; set->ssl_enable_alpn = TRUE; @@ -609,19 +475,31 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->maxage_conn = 118; set->maxlifetime_conn = 0; set->http09_allowed = FALSE; -#ifdef USE_HTTP2 - set->httpwant = CURL_HTTP_VERSION_2TLS -#else - set->httpwant = CURL_HTTP_VERSION_1_1 -#endif + set->httpwant = CURL_HTTP_VERSION_NONE ; #if defined(USE_HTTP2) || defined(USE_HTTP3) memset(&set->priority, 0, sizeof(set->priority)); #endif set->quick_exit = 0L; +#ifndef CURL_DISABLE_WEBSOCKETS + set->ws_raw_mode = FALSE; + set->ws_no_auto_pong = FALSE; +#endif + return result; } +/* easy->meta_hash destructor. Should never be called as elements + * MUST be added with their own destructor */ +static void easy_meta_freeentry(void *p) +{ + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); +} + /** * Curl_open() * @@ -635,67 +513,62 @@ CURLcode Curl_open(struct Curl_easy **curl) CURLcode result; struct Curl_easy *data; - /* Very simple start-up: alloc the struct, init it with zeroes and return */ + /* simple start-up: alloc the struct, init it with zeroes and return */ data = calloc(1, sizeof(struct Curl_easy)); if(!data) { - /* this is a very serious error */ + /* this is a serious error */ DEBUGF(fprintf(stderr, "Error: calloc of Curl_easy failed\n")); return CURLE_OUT_OF_MEMORY; } data->magic = CURLEASY_MAGIC_NUMBER; - - result = Curl_resolver_init(data, &data->state.async.resolver); - if(result) { - DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); - free(data); - return result; - } + /* most recent connection is not yet defined */ + data->state.lastconnect_id = -1; + data->state.recent_conn_id = -1; + /* and not assigned an id yet */ + data->id = -1; + data->mid = UINT_MAX; + data->master_mid = UINT_MAX; + data->progress.hide = TRUE; + data->state.current_speed = -1; /* init to negative == impossible */ + + Curl_hash_init(&data->meta_hash, 23, + Curl_hash_str, curlx_str_key_compare, easy_meta_freeentry); + curlx_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); + Curl_req_init(&data->req); + Curl_initinfo(data); +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&data->state.httphdrs, NULL); +#endif + Curl_netrc_init(&data->state.netrc); result = Curl_init_userdefined(data); - if(!result) { - Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); - Curl_initinfo(data); - - /* most recent connection is not yet defined */ - data->state.lastconnect_id = -1; - - data->progress.flags |= PGRS_HIDE; - data->state.current_speed = -1; /* init to negative == impossible */ - } if(result) { - Curl_resolver_cleanup(data->state.async.resolver); - Curl_dyn_free(&data->state.headerb); + curlx_dyn_free(&data->state.headerb); Curl_freeset(data); + Curl_req_free(&data->req, data); + Curl_hash_destroy(&data->meta_hash); + data->magic = 0; free(data); data = NULL; } else *curl = data; - return result; } -static void conn_shutdown(struct Curl_easy *data) -{ - DEBUGASSERT(data); - infof(data, "Closing connection %ld", data->conn->connection_id); - - /* possible left-overs from the async name resolvers */ - Curl_resolver_cancel(data); - - Curl_conn_close(data, SECONDARYSOCKET); - Curl_conn_close(data, FIRSTSOCKET); -} - -static void conn_free(struct Curl_easy *data, struct connectdata *conn) +void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) { size_t i; DEBUGASSERT(conn); - for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { + if(conn->handler && conn->handler->disconnect && + !conn->bits.shutdown_handler) + conn->handler->disconnect(data, conn, TRUE); + + for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) { Curl_conn_cf_discard_all(data, conn, (int)i); } @@ -710,122 +583,54 @@ static void conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_safefree(conn->socks_proxy.passwd); Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */ Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */ - Curl_free_primary_ssl_config(&conn->proxy_ssl_config); #endif + Curl_sec_conn_destroy(conn); Curl_safefree(conn->user); Curl_safefree(conn->passwd); Curl_safefree(conn->sasl_authzid); Curl_safefree(conn->options); Curl_safefree(conn->oauth_bearer); -#ifndef CURL_DISABLE_HTTP - Curl_dyn_free(&conn->trailer); -#endif - Curl_safefree(conn->host.rawalloc); /* host name buffer */ - Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */ + Curl_safefree(conn->host.rawalloc); /* hostname buffer */ + Curl_safefree(conn->conn_to_host.rawalloc); /* hostname buffer */ Curl_safefree(conn->hostname_resolve); Curl_safefree(conn->secondaryhostname); Curl_safefree(conn->localdev); - Curl_free_primary_ssl_config(&conn->ssl_config); + Curl_ssl_conn_config_cleanup(conn); #ifdef USE_UNIX_SOCKETS Curl_safefree(conn->unix_domain_socket); #endif + Curl_safefree(conn->destination); + Curl_uint_spbset_destroy(&conn->xfers_attached); + Curl_hash_destroy(&conn->meta_hash); free(conn); /* free all the connection oriented data */ } /* - * Disconnects the given connection. Note the connection may not be the - * primary connection, like when freeing room in the connection cache or - * killing of a dead old connection. - * - * A connection needs an easy handle when closing down. We support this passed - * in separately since the connection to get closed here is often already - * disassociated from an easy handle. - * - * This function MUST NOT reset state in the Curl_easy struct if that - * isn't strictly bound to the life-time of *this* particular connection. - * - */ - -void Curl_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection) -{ - /* there must be a connection to close */ - DEBUGASSERT(conn); - - /* it must be removed from the connection cache */ - DEBUGASSERT(!conn->bundle); - - /* there must be an associated transfer */ - DEBUGASSERT(data); - - /* the transfer must be detached from the connection */ - DEBUGASSERT(!data->conn); - - DEBUGF(infof(data, "Curl_disconnect(conn #%ld, dead=%d)", - conn->connection_id, dead_connection)); - /* - * If this connection isn't marked to force-close, leave it open if there - * are other users of it - */ - if(CONN_INUSE(conn) && !dead_connection) { - DEBUGF(infof(data, "Curl_disconnect when inuse: %zu", CONN_INUSE(conn))); - return; - } - - if(conn->dns_entry) { - Curl_resolv_unlock(data, conn->dns_entry); - conn->dns_entry = NULL; - } - - /* Cleanup NTLM connection-related data */ - Curl_http_auth_cleanup_ntlm(conn); - - /* Cleanup NEGOTIATE connection-related data */ - Curl_http_auth_cleanup_negotiate(conn); - - if(conn->connect_only) - /* treat the connection as dead in CONNECT_ONLY situations */ - dead_connection = TRUE; - - /* temporarily attach the connection to this transfer handle for the - disconnect and shutdown */ - Curl_attach_connection(data, conn); - - if(conn->handler && conn->handler->disconnect) - /* This is set if protocol-specific cleanups should be made */ - conn->handler->disconnect(data, conn, dead_connection); - - conn_shutdown(data); - - /* detach it again */ - Curl_detach_connection(data); - - conn_free(data, conn); -} - -/* - * IsMultiplexingPossible() + * xfer_may_multiplex() * - * Return a bitmask with the available multiplexing options for the given - * requested connection. + * Return a TRUE, iff the transfer can be done over an (appropriate) + * multiplexed connection. */ -static int IsMultiplexingPossible(const struct Curl_easy *handle, - const struct connectdata *conn) +static bool xfer_may_multiplex(const struct Curl_easy *data, + const struct connectdata *conn) { - int avail = 0; - +#ifndef CURL_DISABLE_HTTP /* If an HTTP protocol and multiplexing is enabled */ if((conn->handler->protocol & PROTO_FAMILY_HTTP) && (!conn->bits.protoconnstart || !conn->bits.close)) { - if(Curl_multiplex_wanted(handle->multi) && - (handle->state.httpwant >= CURL_HTTP_VERSION_2)) - /* allows HTTP/2 */ - avail |= CURLPIPE_MULTIPLEX; + if(Curl_multiplex_wanted(data->multi) && + (data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x))) + /* allows HTTP/2 or newer */ + return TRUE; } - return avail; +#else + (void)data; + (void)conn; +#endif + return FALSE; } #ifndef CURL_DISABLE_PROXY @@ -860,7 +665,7 @@ socks_proxy_info_matches(const struct proxy_info *data, return TRUE; } #else -/* disabled, won't get called */ +/* disabled, will not get called */ #define proxy_info_matches(x,y) FALSE #define socks_proxy_info_matches(x,y) FALSE #endif @@ -875,22 +680,22 @@ static bool conn_maxage(struct Curl_easy *data, { timediff_t idletime, lifetime; - idletime = Curl_timediff(now, conn->lastused); + idletime = curlx_timediff(now, conn->lastused); idletime /= 1000; /* integer seconds is fine */ if(idletime > data->set.maxage_conn) { - infof(data, "Too old connection (%ld seconds idle), disconnect it", - idletime); + infof(data, "Too old connection (%" FMT_TIMEDIFF_T + " seconds idle), disconnect it", idletime); return TRUE; } - lifetime = Curl_timediff(now, conn->created); + lifetime = curlx_timediff(now, conn->created); lifetime /= 1000; /* integer seconds is fine */ if(data->set.maxlifetime_conn && lifetime > data->set.maxlifetime_conn) { infof(data, - "Too old connection (%ld seconds since creation), disconnect it", - lifetime); + "Too old connection (%" FMT_TIMEDIFF_T + " seconds since creation), disconnect it", lifetime); return TRUE; } @@ -899,23 +704,24 @@ static bool conn_maxage(struct Curl_easy *data, } /* - * This function checks if the given connection is dead and extracts it from - * the connection cache if so. - * - * When this is called as a Curl_conncache_foreach() callback, the connection - * cache lock is held! - * - * Returns TRUE if the connection was dead and extracted. + * Return TRUE iff the given connection is considered dead. */ -static bool extract_if_dead(struct connectdata *conn, - struct Curl_easy *data) +bool Curl_conn_seems_dead(struct connectdata *conn, + struct Curl_easy *data, + struct curltime *pnow) { + DEBUGASSERT(!data->conn); if(!CONN_INUSE(conn)) { - /* The check for a dead socket makes sense only if the connection isn't in + /* The check for a dead socket makes sense only if the connection is not in use */ bool dead; - struct curltime now = Curl_now(); - if(conn_maxage(data, conn, now)) { + struct curltime now; + if(!pnow) { + now = curlx_now(); + pnow = &now; + } + + if(conn_maxage(data, conn, *pnow)) { /* avoid check if already too old */ dead = TRUE; } @@ -935,538 +741,633 @@ static bool extract_if_dead(struct connectdata *conn, } else { - bool input_pending; + bool input_pending = FALSE; + Curl_attach_connection(data, conn); dead = !Curl_conn_is_alive(data, conn, &input_pending); if(input_pending) { /* For reuse, we want a "clean" connection state. The includes * that we expect - in general - no waiting input data. Input * waiting might be a TLS Notify Close, for example. We reject * that. - * For protocols where data from other other end may arrive at + * For protocols where data from other end may arrive at * any time (HTTP/2 PING for example), the protocol handler needs * to install its own `connection_check` callback. */ + DEBUGF(infof(data, "connection has input pending, not reusable")); dead = TRUE; } + Curl_detach_connection(data); } if(dead) { - infof(data, "Connection %ld seems to be dead", conn->connection_id); - Curl_conncache_remove_conn(data, conn, FALSE); + /* remove connection from cpool */ + infof(data, "Connection %" FMT_OFF_T " seems to be dead", + conn->connection_id); return TRUE; } } return FALSE; } -struct prunedead { - struct Curl_easy *data; - struct connectdata *extracted; -}; - -/* - * Wrapper to use extract_if_dead() function in Curl_conncache_foreach() - * - */ -static int call_extract_if_dead(struct Curl_easy *data, - struct connectdata *conn, void *param) -{ - struct prunedead *p = (struct prunedead *)param; - if(extract_if_dead(conn, data)) { - /* stop the iteration here, pass back the connection that was extracted */ - p->extracted = conn; - return 1; - } - return 0; /* continue iteration */ -} - -/* - * This function scans the connection cache 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. - */ -static void prune_dead_connections(struct Curl_easy *data) +CURLcode Curl_conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + struct curltime *now) { - struct curltime now = Curl_now(); - timediff_t elapsed; - - DEBUGASSERT(!data->conn); /* no connection */ - CONNCACHE_LOCK(data); - elapsed = - Curl_timediff(now, data->state.conn_cache->last_cleanup); - CONNCACHE_UNLOCK(data); - - if(elapsed >= 1000L) { - struct prunedead prune; - prune.data = data; - prune.extracted = NULL; - while(Curl_conncache_foreach(data, data->state.conn_cache, &prune, - call_extract_if_dead)) { - /* unlocked */ - - /* remove connection from cache */ - Curl_conncache_remove_conn(data, prune.extracted, TRUE); + CURLcode result = CURLE_OK; + if(curlx_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) + return result; - /* disconnect it */ - Curl_disconnect(data, prune.extracted, TRUE); - } - CONNCACHE_LOCK(data); - data->state.conn_cache->last_cleanup = now; - CONNCACHE_UNLOCK(data); + /* briefly attach for action */ + Curl_attach_connection(data, conn); + if(conn->handler->connection_check) { + /* Do a protocol-specific keepalive check on the connection. */ + unsigned int rc; + rc = conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); + if(rc & CONNRESULT_DEAD) + result = CURLE_RECV_ERROR; + } + else { + /* Do the generic action on the FIRSTSOCKET filter chain */ + result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET); } + Curl_detach_connection(data); + + conn->keepalive = *now; + return result; } #ifdef USE_SSH static bool ssh_config_matches(struct connectdata *one, struct connectdata *two) { - return (Curl_safecmp(one->proto.sshc.rsa, two->proto.sshc.rsa) && - Curl_safecmp(one->proto.sshc.rsa_pub, two->proto.sshc.rsa_pub)); + struct ssh_conn *sshc1, *sshc2; + + sshc1 = Curl_conn_meta_get(one, CURL_META_SSH_CONN); + sshc2 = Curl_conn_meta_get(two, CURL_META_SSH_CONN); + return (sshc1 && sshc2 && Curl_safecmp(sshc1->rsa, sshc2->rsa) && + Curl_safecmp(sshc1->rsa_pub, sshc2->rsa_pub)); } -#else -#define ssh_config_matches(x,y) FALSE #endif -/* - * Given one filled in connection struct (named needle), this function should - * detect if there already is one that has all the significant details - * exactly the same and thus should be used instead. - * - * If there is a match, this function returns TRUE - and has marked the - * connection as 'in-use'. It must later be called with ConnectionDone() to - * return back to 'idle' (unused) state. - * - * The force_reuse flag is set if the connection must be used. - */ -static bool -ConnectionExists(struct Curl_easy *data, - struct connectdata *needle, - struct connectdata **usethis, - bool *force_reuse, - bool *waitpipe) -{ - struct connectdata *check; - struct connectdata *chosen = 0; - bool foundPendingCandidate = FALSE; - bool canmultiplex = IsMultiplexingPossible(data, needle); - struct connectbundle *bundle; +struct url_conn_match { + struct connectdata *found; + struct Curl_easy *data; + struct connectdata *needle; + BIT(may_multiplex); + BIT(want_ntlm_http); + BIT(want_proxy_ntlm_http); + + BIT(wait_pipe); + BIT(force_reuse); + BIT(seen_pending_conn); + BIT(seen_single_use_conn); + BIT(seen_multiplex_conn); +}; -#ifdef USE_NTLM - bool wantNTLMhttp = ((data->state.authhost.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && - (needle->handler->protocol & PROTO_FAMILY_HTTP)); -#ifndef CURL_DISABLE_PROXY - bool wantProxyNTLMhttp = (needle->bits.proxy_user_passwd && - ((data->state.authproxy.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && - (needle->handler->protocol & PROTO_FAMILY_HTTP))); -#else - bool wantProxyNTLMhttp = FALSE; -#endif -#endif +static bool url_match_connect_config(struct connectdata *conn, + struct url_conn_match *m) +{ + /* connect-only or to-be-closed connections will not be reused */ + if(conn->connect_only || conn->bits.close) + return FALSE; - *force_reuse = FALSE; - *waitpipe = FALSE; + /* ip_version must match */ + if(m->data->set.ipver != CURL_IPRESOLVE_WHATEVER + && m->data->set.ipver != conn->ip_version) + return FALSE; - /* Look up the bundle with all the connections to this particular host. - Locks the connection cache, beware of early returns! */ - bundle = Curl_conncache_find_bundle(data, needle, data->state.conn_cache); - if(bundle) { - /* Max pipe length is zero (unlimited) for multiplexed connections */ - struct Curl_llist_element *curr; + if(m->needle->localdev || 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; + } - infof(data, "Found bundle for host: %p [%s]", - (void *)bundle, (bundle->multiuse == BUNDLE_MULTIPLEX ? - "can multiplex" : "serially")); + if(m->needle->bits.conn_to_host != conn->bits.conn_to_host) + /* do not mix connections that use the "connect to host" feature and + * connections that do not use this feature */ + return FALSE; - /* We can't multiplex if we don't know anything about the server */ - if(canmultiplex) { - if(bundle->multiuse == BUNDLE_UNKNOWN) { - if(data->set.pipewait) { - infof(data, "Server doesn't support multiplex yet, wait"); - *waitpipe = TRUE; - CONNCACHE_UNLOCK(data); - return FALSE; /* no re-use */ - } + if(m->needle->bits.conn_to_port != conn->bits.conn_to_port) + /* do not mix connections that use the "connect to port" feature and + * connections that do not use this feature */ + return FALSE; - infof(data, "Server doesn't support multiplex (yet)"); - canmultiplex = FALSE; - } - if((bundle->multiuse == BUNDLE_MULTIPLEX) && - !Curl_multiplex_wanted(data->multi)) { - infof(data, "Could multiplex, but not asked to"); - canmultiplex = FALSE; - } - if(bundle->multiuse == BUNDLE_NO_MULTIUSE) { - infof(data, "Can not multiplex, even if we wanted to"); - canmultiplex = FALSE; - } - } + /* Does `conn` use the correct protocol? */ +#ifdef USE_UNIX_SOCKETS + if(m->needle->unix_domain_socket) { + if(!conn->unix_domain_socket) + return FALSE; + if(strcmp(m->needle->unix_domain_socket, conn->unix_domain_socket)) + return FALSE; + if(m->needle->bits.abstract_unix_socket != conn->bits.abstract_unix_socket) + return FALSE; + } + else if(conn->unix_domain_socket) + return FALSE; +#endif - curr = bundle->conn_list.head; - while(curr) { - bool match = FALSE; - size_t multiplexed = 0; + return TRUE; +} - /* - * Note that if we use an HTTP proxy in normal mode (no tunneling), we - * check connections to that proxy and not to the actual remote server. - */ - check = curr->ptr; - curr = curr->next; - - if(check->connect_only || check->bits.close) - /* connect-only or to-be-closed connections will not be reused */ - continue; - - if(extract_if_dead(check, data)) { - /* disconnect it */ - Curl_disconnect(data, check, TRUE); - continue; - } - - if(data->set.ipver != CURL_IPRESOLVE_WHATEVER - && data->set.ipver != check->ip_version) { - /* skip because the connection is not via the requested IP version */ - continue; - } - - if(bundle->multiuse == BUNDLE_MULTIPLEX) - multiplexed = CONN_INUSE(check); - - if(!canmultiplex) { - if(multiplexed) { - /* can only happen within multi handles, and means that another easy - handle is using this connection */ - continue; - } +static bool url_match_fully_connected(struct connectdata *conn, + struct url_conn_match *m) +{ + if(!Curl_conn_is_connected(conn, FIRSTSOCKET) || + conn->bits.asks_multiplex) { + /* Not yet connected, or not yet decided if it multiplexes. The later + * happens for HTTP/2 Upgrade: requests that need a response. */ + if(m->may_multiplex) { + m->seen_pending_conn = TRUE; + /* Do not pick a connection that has not connected yet */ + infof(m->data, "Connection #%" FMT_OFF_T + " is not open enough, cannot reuse", conn->connection_id); + } + /* Do not pick a connection that has not connected yet */ + return FALSE; + } + return TRUE; +} - if(Curl_resolver_asynch()) { - /* primary_ip[0] is NUL only if the resolving of the name hasn't - completed yet and until then we don't re-use this connection */ - if(!check->primary_ip[0]) { - infof(data, - "Connection #%ld is still name resolving, can't reuse", - check->connection_id); - continue; - } - } - } +static bool url_match_multi(struct connectdata *conn, + struct url_conn_match *m) +{ + if(CONN_INUSE(conn)) { + DEBUGASSERT(conn->attached_multi); + if(conn->attached_multi != m->data->multi) + return FALSE; + } + return TRUE; +} - if(!Curl_conn_is_connected(check, FIRSTSOCKET)) { - foundPendingCandidate = TRUE; - /* Don't pick a connection that hasn't connected yet */ - infof(data, "Connection #%ld isn't open enough, can't reuse", - check->connection_id); - continue; - } +static bool url_match_multiplex_needs(struct connectdata *conn, + struct url_conn_match *m) +{ + if(CONN_INUSE(conn)) { + if(!conn->bits.multiplex) { + /* conn busy and conn cannot take more transfers */ + m->seen_single_use_conn = TRUE; + return FALSE; + } + m->seen_multiplex_conn = TRUE; + if(!m->may_multiplex || !url_match_multi(conn, m)) + /* conn busy and transfer cannot be multiplexed */ + return FALSE; + } + return TRUE; +} -#ifdef USE_UNIX_SOCKETS - if(needle->unix_domain_socket) { - if(!check->unix_domain_socket) - continue; - if(strcmp(needle->unix_domain_socket, check->unix_domain_socket)) - continue; - if(needle->bits.abstract_unix_socket != - check->bits.abstract_unix_socket) - continue; - } - else if(check->unix_domain_socket) - continue; -#endif +static bool url_match_multiplex_limits(struct connectdata *conn, + struct url_conn_match *m) +{ + if(CONN_INUSE(conn) && m->may_multiplex) { + DEBUGASSERT(conn->bits.multiplex); + /* If multiplexed, make sure we do not go over concurrency limit */ + if(CONN_ATTACHED(conn) >= + Curl_multi_max_concurrent_streams(m->data->multi)) { + infof(m->data, "client side MAX_CONCURRENT_STREAMS reached" + ", skip (%u)", CONN_ATTACHED(conn)); + return FALSE; + } + if(CONN_ATTACHED(conn) >= + Curl_conn_get_max_concurrent(m->data, conn, FIRSTSOCKET)) { + infof(m->data, "MAX_CONCURRENT_STREAMS reached, skip (%u)", + CONN_ATTACHED(conn)); + return FALSE; + } + /* When not multiplexed, we have a match here! */ + infof(m->data, "Multiplexed connection found"); + } + return TRUE; +} - if((needle->handler->flags&PROTOPT_SSL) != - (check->handler->flags&PROTOPT_SSL)) - /* don't do mixed SSL and non-SSL connections */ - if(get_protocol_family(check->handler) != - needle->handler->protocol || !check->bits.tls_upgraded) - /* except protocols that have been upgraded via TLS */ - continue; +static bool url_match_ssl_use(struct connectdata *conn, + struct url_conn_match *m) +{ + if(m->needle->handler->flags&PROTOPT_SSL) { + /* We are looking for SSL, if `conn` does not do it, not a match. */ + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) + return FALSE; + } + else if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + /* We are not *requiring* SSL, however `conn` has it. If the + * protocol *family* is not the same, not a match. */ + if(get_protocol_family(conn->handler) != m->needle->handler->protocol) + return FALSE; + } + return TRUE; +} #ifndef CURL_DISABLE_PROXY - if(needle->bits.httpproxy != check->bits.httpproxy || - needle->bits.socksproxy != check->bits.socksproxy) - continue; - - if(needle->bits.socksproxy && - !socks_proxy_info_matches(&needle->socks_proxy, - &check->socks_proxy)) - continue; -#endif - if(needle->bits.conn_to_host != check->bits.conn_to_host) - /* don't mix connections that use the "connect to host" feature and - * connections that don't use this feature */ - continue; +static bool url_match_proxy_use(struct connectdata *conn, + struct url_conn_match *m) +{ + if(m->needle->bits.httpproxy != conn->bits.httpproxy || + m->needle->bits.socksproxy != conn->bits.socksproxy) + return FALSE; - if(needle->bits.conn_to_port != check->bits.conn_to_port) - /* don't mix connections that use the "connect to port" feature and - * connections that don't use this feature */ - continue; + if(m->needle->bits.socksproxy && + !socks_proxy_info_matches(&m->needle->socks_proxy, + &conn->socks_proxy)) + return FALSE; -#ifndef CURL_DISABLE_PROXY - if(needle->bits.httpproxy) { - if(!proxy_info_matches(&needle->http_proxy, &check->http_proxy)) - continue; - - if(needle->bits.tunnel_proxy != check->bits.tunnel_proxy) - continue; - - if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) { - /* use https proxy */ - if(needle->http_proxy.proxytype != - check->http_proxy.proxytype) - continue; - else if(needle->handler->flags&PROTOPT_SSL) { - /* use double layer ssl */ - if(!Curl_ssl_config_matches(&needle->proxy_ssl_config, - &check->proxy_ssl_config)) - continue; - } - else if(!Curl_ssl_config_matches(&needle->ssl_config, - &check->ssl_config)) - continue; - } + if(m->needle->bits.httpproxy) { + if(m->needle->bits.tunnel_proxy != conn->bits.tunnel_proxy) + return FALSE; + + if(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy)) + return FALSE; + + if(IS_HTTPS_PROXY(m->needle->http_proxy.proxytype)) { + /* https proxies come in different types, http/1.1, h2, ... */ + if(m->needle->http_proxy.proxytype != conn->http_proxy.proxytype) + return FALSE; + /* match SSL config to proxy */ + if(!Curl_ssl_conn_config_match(m->data, conn, TRUE)) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T + " has different SSL proxy parameters, cannot reuse", + conn->connection_id)); + return FALSE; } + /* the SSL config to the server, which may apply here is checked + * further below */ + } + } + return TRUE; +} +#else +#define url_match_proxy_use(c,m) ((void)c, (void)m, TRUE) #endif - if(!canmultiplex && CONN_INUSE(check)) - /* this request can't be multiplexed but the checked connection is - already in use so we skip it */ - continue; +#ifndef CURL_DISABLE_HTTP +static bool url_match_http_multiplex(struct connectdata *conn, + struct url_conn_match *m) +{ + if(m->may_multiplex && + (m->data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x)) && + (m->needle->handler->protocol & CURLPROTO_HTTP) && + !conn->httpversion_seen) { + if(m->data->set.pipewait) { + infof(m->data, "Server upgrade does not support multiplex yet, wait"); + m->found = NULL; + m->wait_pipe = TRUE; + return TRUE; /* stop searching, we want to wait */ + } + infof(m->data, "Server upgrade cannot be used"); + return FALSE; + } + return TRUE; +} - if(CONN_INUSE(check)) { - /* Subject for multiplex use if 'checks' belongs to the same multi - handle as 'data' is. */ - struct Curl_llist_element *e = check->easyq.head; - struct Curl_easy *entry = e->ptr; - if(entry->multi != data->multi) - continue; +static bool url_match_http_version(struct connectdata *conn, + struct url_conn_match *m) +{ + /* If looking for HTTP and the HTTP versions allowed do not include + * the HTTP version of conn, continue looking. */ + if((m->needle->handler->protocol & PROTO_FAMILY_HTTP)) { + switch(Curl_conn_http_version(m->data, conn)) { + case 30: + if(!(m->data->state.http_neg.allowed & CURL_HTTP_V3x)) { + DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T + ", we do not want h3", conn->connection_id)); + return FALSE; } - - if(needle->localdev || needle->localport) { - /* If we are bound to a specific local end (IP+port), we must not - re-use a random other one, although if we didn't 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 re-uses of bound connections - will most likely also re-use the exact same binding parameters and - missing out a few edge cases shouldn't hurt anyone very much. - */ - if((check->localport != needle->localport) || - (check->localportrange != needle->localportrange) || - (needle->localdev && - (!check->localdev || strcmp(check->localdev, needle->localdev)))) - continue; + break; + case 20: + if(!(m->data->state.http_neg.allowed & CURL_HTTP_V2x)) { + DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T + ", we do not want h2", conn->connection_id)); + return FALSE; } - - if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) { - /* This protocol requires credentials per connection, - so verify that we're using the same name and password as well */ - if(Curl_timestrcmp(needle->user, check->user) || - Curl_timestrcmp(needle->passwd, check->passwd) || - Curl_timestrcmp(needle->sasl_authzid, check->sasl_authzid) || - Curl_timestrcmp(needle->oauth_bearer, check->oauth_bearer)) { - /* one of them was different */ - continue; - } + break; + default: + if(!(m->data->state.http_neg.allowed & CURL_HTTP_V1x)) { + DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T + ", we do not want h1", conn->connection_id)); + return FALSE; } + break; + } + } + 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) +#endif + +static bool url_match_proto_config(struct connectdata *conn, + struct url_conn_match *m) +{ + if(!url_match_http_version(conn, m)) + return FALSE; - /* GSS delegation differences do not actually affect every connection - and auth method, but this check takes precaution before efficiency */ - if(needle->gssapi_delegation != check->gssapi_delegation) - continue; - - /* If multiplexing isn't enabled on the h2 connection and h1 is - explicitly requested, handle it: */ - if((needle->handler->protocol & PROTO_FAMILY_HTTP) && - (((check->httpversion >= 20) && - (data->state.httpwant < CURL_HTTP_VERSION_2_0)) - || ((check->httpversion >= 30) && - (data->state.httpwant < CURL_HTTP_VERSION_3)))) - continue; #ifdef USE_SSH - else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) { - if(!ssh_config_matches(needle, check)) - continue; - } + if(get_protocol_family(m->needle->handler) & PROTO_FAMILY_SSH) { + if(!ssh_config_matches(m->needle, conn)) + return FALSE; + } #endif #ifndef CURL_DISABLE_FTP - else if(get_protocol_family(needle->handler) & PROTO_FAMILY_FTP) { - /* Also match ACCOUNT, ALTERNATIVE-TO-USER, USE_SSL and CCC options */ - if(Curl_timestrcmp(needle->proto.ftpc.account, - check->proto.ftpc.account) || - Curl_timestrcmp(needle->proto.ftpc.alternative_to_user, - check->proto.ftpc.alternative_to_user) || - (needle->proto.ftpc.use_ssl != check->proto.ftpc.use_ssl) || - (needle->proto.ftpc.ccc != check->proto.ftpc.ccc)) - continue; - } + else if(get_protocol_family(m->needle->handler) & PROTO_FAMILY_FTP) { + if(!ftp_conns_match(m->needle, conn)) + return FALSE; + } #endif + return TRUE; +} - if((needle->handler->flags&PROTOPT_SSL) -#ifndef CURL_DISABLE_PROXY - || !needle->bits.httpproxy || needle->bits.tunnel_proxy -#endif - ) { - /* The requested connection does not use an HTTP proxy or it uses SSL - or it is a non-SSL protocol tunneled or it is a non-SSL protocol - which is allowed to be upgraded via TLS */ - - if((strcasecompare(needle->handler->scheme, check->handler->scheme) || - (get_protocol_family(check->handler) == - needle->handler->protocol && check->bits.tls_upgraded)) && - (!needle->bits.conn_to_host || strcasecompare( - needle->conn_to_host.name, check->conn_to_host.name)) && - (!needle->bits.conn_to_port || - needle->conn_to_port == check->conn_to_port) && - strcasecompare(needle->host.name, check->host.name) && - needle->remote_port == check->remote_port) { - /* The schemes match or the protocol family is the same and the - previous connection was TLS upgraded, and the hostname and host - port match */ - if(needle->handler->flags & PROTOPT_SSL) { - /* This is a SSL connection so verify that we're using the same - SSL options as well */ - if(!Curl_ssl_config_matches(&needle->ssl_config, - &check->ssl_config)) { - DEBUGF(infof(data, - "Connection #%ld has different SSL parameters, " - "can't reuse", - check->connection_id)); - continue; - } - } - match = TRUE; - } - } - else { - /* The requested connection is using the same HTTP proxy in normal - mode (no tunneling) */ - match = TRUE; - } +static bool url_match_auth(struct connectdata *conn, + struct url_conn_match *m) +{ + if(!(m->needle->handler->flags & PROTOPT_CREDSPERREQUEST)) { + /* This protocol requires credentials per connection, + so verify that we are using the same name and password as well */ + if(Curl_timestrcmp(m->needle->user, conn->user) || + Curl_timestrcmp(m->needle->passwd, conn->passwd) || + Curl_timestrcmp(m->needle->sasl_authzid, conn->sasl_authzid) || + Curl_timestrcmp(m->needle->oauth_bearer, conn->oauth_bearer)) { + /* one of them was different */ + return FALSE; + } + } +#ifdef HAVE_GSSAPI + /* GSS delegation differences do not actually affect every connection + and auth method, but this check takes precaution before efficiency */ + if(m->needle->gssapi_delegation != conn->gssapi_delegation) + return FALSE; +#endif - if(match) { -#if defined(USE_NTLM) - /* If we are looking for an HTTP+NTLM connection, check if this is - already authenticating with the right credentials. If not, keep - looking so that we can reuse NTLM connections if - possible. (Especially we must not reuse the same connection if - partway through a handshake!) */ - if(wantNTLMhttp) { - if(Curl_timestrcmp(needle->user, check->user) || - Curl_timestrcmp(needle->passwd, check->passwd)) { - - /* we prefer a credential match, but this is at least a connection - that can be reused and "upgraded" to NTLM */ - if(check->http_ntlm_state == NTLMSTATE_NONE) - chosen = check; - continue; - } - } - else if(check->http_ntlm_state != NTLMSTATE_NONE) { - /* Connection is using NTLM auth but we don't want NTLM */ - continue; - } + return TRUE; +} +static bool url_match_destination(struct connectdata *conn, + struct url_conn_match *m) +{ + /* Additional match requirements if talking TLS OR + * not talking to an HTTP proxy OR using a tunnel through a proxy */ + if((m->needle->handler->flags&PROTOPT_SSL) #ifndef CURL_DISABLE_PROXY - /* Same for Proxy NTLM authentication */ - if(wantProxyNTLMhttp) { - /* Both check->http_proxy.user and check->http_proxy.passwd can be - * NULL */ - if(!check->http_proxy.user || !check->http_proxy.passwd) - continue; - - if(Curl_timestrcmp(needle->http_proxy.user, - check->http_proxy.user) || - Curl_timestrcmp(needle->http_proxy.passwd, - check->http_proxy.passwd)) - continue; - } - else if(check->proxy_ntlm_state != NTLMSTATE_NONE) { - /* Proxy connection is using NTLM auth but we don't want NTLM */ - continue; - } + || !m->needle->bits.httpproxy || m->needle->bits.tunnel_proxy #endif - if(wantNTLMhttp || wantProxyNTLMhttp) { - /* Credentials are already checked, we can use this connection */ - chosen = check; - - if((wantNTLMhttp && - (check->http_ntlm_state != NTLMSTATE_NONE)) || - (wantProxyNTLMhttp && - (check->proxy_ntlm_state != NTLMSTATE_NONE))) { - /* We must use this connection, no other */ - *force_reuse = TRUE; - break; - } + ) { + if(!strcasecompare(m->needle->handler->scheme, conn->handler->scheme)) { + /* `needle` and `conn` do not have the same scheme... */ + if(get_protocol_family(conn->handler) != m->needle->handler->protocol) { + /* and `conn`s protocol family is not the protocol `needle` wants. + * IMAPS would work for IMAP, but no vice versa. */ + return FALSE; + } + /* We are in an IMAPS vs IMAP like case. We expect `conn` to have SSL */ + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T " has compatible protocol family, " + "but no SSL, no match", conn->connection_id)); + return FALSE; + } + } - /* Continue look up for a better connection */ - continue; - } -#endif - if(canmultiplex) { - /* We can multiplex if we want to. Let's continue looking for - the optimal connection to use. */ + /* If needle has "conn_to_*" set, conn must match this */ + if((m->needle->bits.conn_to_host && !strcasecompare( + m->needle->conn_to_host.name, conn->conn_to_host.name)) || + (m->needle->bits.conn_to_port && + m->needle->conn_to_port != conn->conn_to_port)) + return FALSE; + + /* hostname and port must match */ + if(!strcasecompare(m->needle->host.name, conn->host.name) || + m->needle->remote_port != conn->remote_port) + return FALSE; + } + return TRUE; +} - if(!multiplexed) { - /* We have the optimal connection. Let's stop looking. */ - chosen = check; - break; - } +static bool url_match_ssl_config(struct connectdata *conn, + struct url_conn_match *m) +{ + /* If talking TLS, conn needs to use the same SSL options. */ + if((m->needle->handler->flags & PROTOPT_SSL) && + !Curl_ssl_conn_config_match(m->data, conn, FALSE)) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T + " has different SSL parameters, cannot reuse", + conn->connection_id)); + return FALSE; + } + return TRUE; +} -#ifdef USE_NGHTTP2 - /* If multiplexed, make sure we don't go over concurrency limit */ - if(check->bits.multiplex) { - if(multiplexed >= Curl_conn_get_max_concurrent(data, check, - FIRSTSOCKET)) { - infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)", - multiplexed); - continue; - } - else if(multiplexed >= - Curl_multi_max_concurrent_streams(data->multi)) { - infof(data, "client side MAX_CONCURRENT_STREAMS reached" - ", skip (%zu)", - multiplexed); - continue; - } - } +#ifdef USE_NTLM +static bool url_match_auth_ntlm(struct connectdata *conn, + struct url_conn_match *m) +{ + /* If we are looking for an HTTP+NTLM connection, check if this is + already authenticating with the right credentials. If not, keep + looking so that we can reuse NTLM connections if + possible. (Especially we must not reuse the same connection if + partway through a handshake!) */ + if(m->want_ntlm_http) { + if(Curl_timestrcmp(m->needle->user, conn->user) || + Curl_timestrcmp(m->needle->passwd, conn->passwd)) { + + /* we prefer a credential match, but this is at least a connection + that can be reused and "upgraded" to NTLM */ + if(conn->http_ntlm_state == NTLMSTATE_NONE) + m->found = conn; + return FALSE; + } + } + else if(conn->http_ntlm_state != NTLMSTATE_NONE) { + /* Connection is using NTLM auth but we do not want NTLM */ + return FALSE; + } + +#ifndef CURL_DISABLE_PROXY + /* Same for Proxy NTLM authentication */ + if(m->want_proxy_ntlm_http) { + /* Both conn->http_proxy.user and conn->http_proxy.passwd can be + * NULL */ + if(!conn->http_proxy.user || !conn->http_proxy.passwd) + return FALSE; + + if(Curl_timestrcmp(m->needle->http_proxy.user, + conn->http_proxy.user) || + Curl_timestrcmp(m->needle->http_proxy.passwd, + conn->http_proxy.passwd)) + return FALSE; + } + else if(conn->proxy_ntlm_state != NTLMSTATE_NONE) { + /* Proxy connection is using NTLM auth but we do not want NTLM */ + return FALSE; + } #endif - /* When not multiplexed, we have a match here! */ - chosen = check; - infof(data, "Multiplexed connection found"); - break; - } - else { - /* We have found a connection. Let's stop searching. */ - chosen = check; - break; - } - } + 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 +#define url_match_auth_ntlm(c,m) ((void)c, (void)m, TRUE) +#endif - if(chosen) { - /* mark it as used before releasing the lock */ - Curl_attach_connection(data, chosen); - CONNCACHE_UNLOCK(data); - *usethis = chosen; - return TRUE; /* yes, we found one to use! */ +static bool url_match_conn(struct connectdata *conn, void *userdata) +{ + struct url_conn_match *m = userdata; + /* Check if `conn` can be used for transfer `m->data` */ + + /* general connect config setting match? */ + if(!url_match_connect_config(conn, m)) + return FALSE; + + if(!url_match_destination(conn, m)) + return FALSE; + + if(!url_match_fully_connected(conn, m)) + return FALSE; + + if(!url_match_multiplex_needs(conn, m)) + return FALSE; + + if(!url_match_ssl_use(conn, m)) + return FALSE; + if(!url_match_proxy_use(conn, m)) + return FALSE; + if(!url_match_ssl_config(conn, m)) + return FALSE; + + if(!url_match_http_multiplex(conn, m)) + return FALSE; + else if(m->wait_pipe) + /* we decided to wait on PIPELINING */ + return TRUE; + + if(!url_match_auth(conn, m)) + return FALSE; + + if(!url_match_proto_config(conn, m)) + return FALSE; + + if(!url_match_auth_ntlm(conn, m)) + return FALSE; + else if(m->force_reuse) + return TRUE; + + if(!url_match_multiplex_limits(conn, m)) + return FALSE; + + if(!CONN_INUSE(conn) && Curl_conn_seems_dead(conn, m->data, NULL)) { + /* remove and disconnect. */ + Curl_conn_terminate(m->data, conn, FALSE); + return FALSE; } - CONNCACHE_UNLOCK(data); - if(foundPendingCandidate && data->set.pipewait) { - infof(data, + /* conn matches our needs. */ + m->found = conn; + return TRUE; +} + +static bool url_match_result(bool result, void *userdata) +{ + struct url_conn_match *match = userdata; + (void)result; + if(match->found) { + /* Attach it now while still under lock, so the connection does + * no longer appear idle and can be reaped. */ + Curl_attach_connection(match->data, match->found); + return TRUE; + } + else if(match->seen_single_use_conn && !match->seen_multiplex_conn) { + /* We've seen a single-use, existing connection to the destination and + * no multiplexed one. It seems safe to assume that the server does + * not support multiplexing. */ + match->wait_pipe = FALSE; + } + else if(match->seen_pending_conn && match->data->set.pipewait) { + infof(match->data, "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set"); - *waitpipe = TRUE; + match->wait_pipe = TRUE; } + match->force_reuse = FALSE; + return FALSE; +} + +/* + * Given one filled in connection struct (named needle), this function should + * detect if there already is one that has all the significant details + * exactly the same and thus should be used instead. + * + * If there is a match, this function returns TRUE - and has marked the + * connection as 'in-use'. It must later be called with ConnectionDone() to + * return back to 'idle' (unused) state. + * + * The force_reuse flag is set if the connection must be used. + */ +static bool +ConnectionExists(struct Curl_easy *data, + struct connectdata *needle, + struct connectdata **usethis, + bool *force_reuse, + bool *waitpipe) +{ + struct url_conn_match match; + bool result; + + memset(&match, 0, sizeof(match)); + match.data = data; + match.needle = needle; + match.may_multiplex = xfer_may_multiplex(data, needle); - return FALSE; /* no matching connecting exists */ +#ifdef USE_NTLM + match.want_ntlm_http = ((data->state.authhost.want & CURLAUTH_NTLM) && + (needle->handler->protocol & PROTO_FAMILY_HTTP)); +#ifndef CURL_DISABLE_PROXY + match.want_proxy_ntlm_http = + (needle->bits.proxy_user_passwd && + (data->state.authproxy.want & CURLAUTH_NTLM) && + (needle->handler->protocol & PROTO_FAMILY_HTTP)); +#endif +#endif + + /* Find a connection in the pool that matches what "data + needle" + * requires. If a suitable candidate is found, it is attached to "data". */ + result = Curl_cpool_find(data, needle->destination, + url_match_conn, url_match_result, &match); + + /* wait_pipe is TRUE if we encounter a bundle that is undecided. There + * is no matching connection then, yet. */ + *usethis = match.found; + *force_reuse = match.force_reuse; + *waitpipe = match.wait_pipe; + return result; } /* @@ -1474,17 +1375,30 @@ ConnectionExists(struct Curl_easy *data, */ #ifndef CURL_DISABLE_VERBOSE_STRINGS void Curl_verboseconnect(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, int sockindex) { - if(data->set.verbose) - infof(data, "Connected to %s (%s) port %u (#%ld)", -#ifndef CURL_DISABLE_PROXY - conn->bits.socksproxy ? conn->socks_proxy.host.dispname : - conn->bits.httpproxy ? conn->http_proxy.host.dispname : + if(data->set.verbose && sockindex == SECONDARYSOCKET) + infof(data, "Connected 2nd connection to %s port %u", + conn->secondary.remote_ip, conn->secondary.remote_port); + else + infof(data, "Connected to %s (%s) port %u", + CURL_CONN_HOST_DISPNAME(conn), conn->primary.remote_ip, + conn->primary.remote_port); +#ifndef CURL_DISABLE_HTTP + if(conn->handler->protocol & PROTO_FAMILY_HTTP) { + switch(conn->alpn) { + case CURL_HTTP_VERSION_3: + infof(data, "using HTTP/3"); + break; + case CURL_HTTP_VERSION_2: + infof(data, "using HTTP/2"); + break; + default: + infof(data, "using HTTP/1.x"); + break; + } + } #endif - conn->bits.conn_to_host ? conn->conn_to_host.dispname : - conn->host.dispname, - conn->primary_ip, conn->port, conn->connection_id); } #endif @@ -1501,17 +1415,19 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->sockfd = CURL_SOCKET_BAD; + conn->writesockfd = CURL_SOCKET_BAD; conn->connection_id = -1; /* no ID */ - conn->port = -1; /* unknown at this point */ + conn->primary.remote_port = -1; /* unknown at this point */ conn->remote_port = -1; /* unknown at this point */ - /* Default protocol-independent behavior doesn't support persistent + /* Default protocol-independent behavior does not support persistent connections, so we set this to force-close. Protocols that support this need to set this to FALSE in their "curl_do" functions. */ connclose(conn, "Default to force-close"); /* Store creation time to help future close decision making */ - conn->created = Curl_now(); + conn->created = curlx_now(); /* Store current time to give a baseline to keepalive connection times. */ conn->keepalive = conn->created; @@ -1523,52 +1439,32 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) /* note that these two proxy bits are now just on what looks to be requested, they may be altered down the road */ conn->bits.proxy = (data->set.str[STRING_PROXY] && - *data->set.str[STRING_PROXY]) ? TRUE : FALSE; + *data->set.str[STRING_PROXY]); conn->bits.httpproxy = (conn->bits.proxy && (conn->http_proxy.proxytype == CURLPROXY_HTTP || conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0 || - IS_HTTPS_PROXY(conn->http_proxy.proxytype))) ? - TRUE : FALSE; - conn->bits.socksproxy = (conn->bits.proxy && - !conn->bits.httpproxy) ? TRUE : FALSE; + IS_HTTPS_PROXY(conn->http_proxy.proxytype))); + conn->bits.socksproxy = (conn->bits.proxy && !conn->bits.httpproxy); if(data->set.str[STRING_PRE_PROXY] && *data->set.str[STRING_PRE_PROXY]) { conn->bits.proxy = TRUE; conn->bits.socksproxy = TRUE; } - conn->bits.proxy_user_passwd = - (data->state.aptr.proxyuser) ? TRUE : FALSE; + conn->bits.proxy_user_passwd = !!data->state.aptr.proxyuser; conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy; #endif /* CURL_DISABLE_PROXY */ #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; -#endif - conn->ssl_config.verifystatus = data->set.ssl.primary.verifystatus; - conn->ssl_config.verifypeer = data->set.ssl.primary.verifypeer; - conn->ssl_config.verifyhost = data->set.ssl.primary.verifyhost; - conn->ssl_config.ssl_options = data->set.ssl.primary.ssl_options; -#ifndef CURL_DISABLE_PROXY - conn->proxy_ssl_config.verifystatus = - data->set.proxy_ssl.primary.verifystatus; - conn->proxy_ssl_config.verifypeer = data->set.proxy_ssl.primary.verifypeer; - conn->proxy_ssl_config.verifyhost = data->set.proxy_ssl.primary.verifyhost; - conn->proxy_ssl_config.ssl_options = data->set.proxy_ssl.primary.ssl_options; #endif conn->ip_version = data->set.ipver; conn->connect_only = data->set.connect_only; conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */ -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - conn->ntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - conn->proxyntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; -#endif - - /* Initialize the easy handle list */ - Curl_llist_init(&conn->easyq, NULL); + /* Initialize the attached xfers bitset */ + Curl_uint_spbset_init(&conn->xfers_attached); #ifdef HAVE_GSSAPI conn->data_prot = PROT_CLEAR; @@ -1580,16 +1476,19 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) if(!conn->localdev) goto error; } +#ifndef CURL_DISABLE_BINDLOCAL conn->localportrange = data->set.localportrange; conn->localport = data->set.localport; +#endif /* the close socket stuff needs to be copied to the connection struct as it may live on without (this specific) Curl_easy */ conn->fclosesocket = data->set.fclosesocket; conn->closesocket_client = data->set.closesocket_client; conn->lastused = conn->created; +#ifdef HAVE_GSSAPI conn->gssapi_delegation = data->set.gssapi_delegation; - +#endif return conn; error: @@ -1598,30 +1497,231 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) return NULL; } -/* returns the handler if the given scheme is built-in */ -const struct Curl_handler *Curl_builtin_scheme(const char *scheme, - size_t schemelen) +const struct Curl_handler *Curl_get_scheme_handler(const char *scheme) { - const struct Curl_handler * const *pp; - const struct Curl_handler *p; - /* Scan protocol handler table and match against 'scheme'. The handler may - be changed later when the protocol specific setup function is called. */ - if(schemelen == CURL_ZERO_TERMINATED) - schemelen = strlen(scheme); - for(pp = protocols; (p = *pp) != NULL; pp++) - if(strncasecompare(p->scheme, scheme, schemelen) && !p->scheme[schemelen]) - /* Protocol found in table. */ - return p; - return NULL; /* not found */ + return Curl_getn_scheme_handler(scheme, strlen(scheme)); } +/* returns the handler if the given scheme is built-in */ +const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme, + size_t len) +{ + /* table generated by schemetable.c: + 1. gcc schemetable.c && ./a.out + 2. check how small the table gets + 3. tweak the hash algorithm, then rerun from 1 + 4. when the table is good enough + 5. copy the table into this source code + 6. make sure this function uses the same hash function that worked for + schemetable.c + 7. if needed, adjust the #ifdefs in schemetable.c and rerun + */ + static const struct Curl_handler * const protocols[67] = { +#ifndef CURL_DISABLE_FILE + &Curl_handler_file, +#else + NULL, +#endif + NULL, NULL, +#if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER) + &Curl_handler_gophers, +#else + NULL, +#endif + NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpe, +#else + NULL, +#endif +#ifndef CURL_DISABLE_SMTP + &Curl_handler_smtp, +#else + NULL, +#endif +#ifdef USE_SSH + &Curl_handler_sftp, +#else + NULL, +#endif +#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ + (SIZEOF_CURL_OFF_T > 4) + &Curl_handler_smb, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP) + &Curl_handler_smtps, +#else + NULL, +#endif +#ifndef CURL_DISABLE_TELNET + &Curl_handler_telnet, +#else + NULL, +#endif +#ifndef CURL_DISABLE_GOPHER + &Curl_handler_gopher, +#else + NULL, +#endif +#ifndef CURL_DISABLE_TFTP + &Curl_handler_tftp, +#else + NULL, +#endif + NULL, NULL, NULL, +#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) + &Curl_handler_ftps, +#else + NULL, +#endif +#ifndef CURL_DISABLE_HTTP + &Curl_handler_http, +#else + NULL, +#endif +#ifndef CURL_DISABLE_IMAP + &Curl_handler_imap, +#else + NULL, +#endif +#ifdef USE_LIBRTMP + &Curl_handler_rtmps, +#else + NULL, +#endif +#ifdef USE_LIBRTMP + &Curl_handler_rtmpt, +#else + NULL, +#endif + NULL, NULL, NULL, +#if !defined(CURL_DISABLE_LDAP) && \ + !defined(CURL_DISABLE_LDAPS) && \ + ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ + (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) + &Curl_handler_ldaps, +#else + NULL, +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && \ + defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_wss, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_https, +#else + NULL, +#endif + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#ifndef CURL_DISABLE_RTSP + &Curl_handler_rtsp, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMB) && \ + defined(USE_CURL_NTLM_CORE) && (SIZEOF_CURL_OFF_T > 4) + &Curl_handler_smbs, +#else + NULL, +#endif +#if defined(USE_SSH) && !defined(USE_WOLFSSH) + &Curl_handler_scp, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_POP3 + &Curl_handler_pop3, +#else + NULL, +#endif + NULL, NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmp, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpte, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_DICT + &Curl_handler_dict, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_MQTT + &Curl_handler_mqtt, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3) + &Curl_handler_pop3s, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP) + &Curl_handler_imaps, +#else + NULL, +#endif + NULL, +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_ws, +#else + NULL, +#endif + NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpts, +#else + NULL, +#endif +#ifndef CURL_DISABLE_LDAP + &Curl_handler_ldap, +#else + NULL, +#endif + NULL, NULL, +#ifndef CURL_DISABLE_FTP + &Curl_handler_ftp, +#else + NULL, +#endif + }; + + if(len && (len <= 7)) { + const char *s = scheme; + size_t l = len; + const struct Curl_handler *h; + unsigned int c = 978; + while(l) { + c <<= 5; + c += (unsigned int)Curl_raw_tolower(*s); + s++; + l--; + } + + h = protocols[c % 67]; + if(h && strncasecompare(scheme, h->scheme, len) && !h->scheme[len]) + return h; + } + return NULL; +} static CURLcode findprotocol(struct Curl_easy *data, struct connectdata *conn, const char *protostr) { - const struct Curl_handler *p = Curl_builtin_scheme(protostr, - CURL_ZERO_TERMINATED); + const struct Curl_handler *p = Curl_get_scheme_handler(protostr); if(p && /* Protocol found in table. Check if allowed */ (data->set.allowed_protocols & p->protocol)) { @@ -1635,17 +1735,17 @@ static CURLcode findprotocol(struct Curl_easy *data, else { /* Perform setup complement if some. */ conn->handler = conn->given = p; - /* 'port' and 'remote_port' are set in setup_connection_internals() */ return CURLE_OK; } } - /* The protocol was not found in the table, but we don't have to assign it + /* The protocol was not found in the table, but we do not have to assign it to anything since it is already assigned to a dummy-struct in the create_conn() function when the connectdata struct is allocated. */ - failf(data, "Protocol \"%s\" not supported or disabled in " LIBCURL_NAME, - protostr); + failf(data, "Protocol \"%s\" %s%s", protostr, + p ? "disabled" : "not supported", + data->state.this_is_a_follow ? " (in redirect)":""); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1665,7 +1765,7 @@ CURLcode Curl_uc_to_curlcode(CURLUcode uc) } } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * If the URL was set with an IPv6 numerical address with a zone id part, set * the scope_id based on that! @@ -1681,21 +1781,21 @@ static void zonefrom_url(CURLU *uh, struct Curl_easy *data, #endif if(!uc && zoneid) { - char *endp; - unsigned long scope = strtoul(zoneid, &endp, 10); - if(!*endp && (scope < UINT_MAX)) + const char *p = zoneid; + curl_off_t scope; + if(!curlx_str_number(&p, &scope, UINT_MAX)) /* A plain number, use it directly as a scope id. */ conn->scope_id = (unsigned int)scope; -#if defined(HAVE_IF_NAMETOINDEX) +#ifdef HAVE_IF_NAMETOINDEX else { -#elif defined(WIN32) +#elif defined(_WIN32) else if(Curl_if_nametoindex) { #endif -#if defined(HAVE_IF_NAMETOINDEX) || defined(WIN32) +#if defined(HAVE_IF_NAMETOINDEX) || defined(_WIN32) /* Zone identifier is not numeric */ unsigned int scopeidx = 0; -#if defined(WIN32) +#ifdef _WIN32 scopeidx = Curl_if_nametoindex(zoneid); #else scopeidx = if_nametoindex(zoneid); @@ -1710,7 +1810,7 @@ static void zonefrom_url(CURLU *uh, struct Curl_easy *data, else conn->scope_id = scopeidx; } -#endif /* HAVE_IF_NAMETOINDEX || WIN32 */ +#endif /* HAVE_IF_NAMETOINDEX || _WIN32 */ free(zoneid); } @@ -1758,12 +1858,12 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, if(!use_set_uh) { char *newurl; - uc = curl_url_set(uh, CURLUPART_URL, data->state.url, - CURLU_GUESS_SCHEME | - CURLU_NON_SUPPORT_SCHEME | - (data->set.disallow_username_in_url ? - CURLU_DISALLOW_USER : 0) | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + uc = curl_url_set(uh, CURLUPART_URL, 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)); return Curl_uc_to_curlcode(uc); @@ -1789,7 +1889,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } else if(strlen(data->state.up.hostname) > MAX_URL_LEN) { - failf(data, "Too long host name (maximum is %d)", MAX_URL_LEN); + failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN); return CURLE_URL_MALFORMAT; } hostname = data->state.up.hostname; @@ -1807,7 +1907,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, zonefrom_url(uh, data, conn); } - /* make sure the connect struct gets its own copy of the host name */ + /* make sure the connect struct gets its own copy of the hostname */ conn->host.rawalloc = strdup(hostname ? hostname : ""); if(!conn->host.rawalloc) return CURLE_OUT_OF_MEMORY; @@ -1824,7 +1924,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, /* HSTS upgrade */ if(data->hsts && strcasecompare("http", data->state.up.scheme)) { /* This MUST use the IDN decoded name */ - if(Curl_hsts(data->hsts, conn->host.name, TRUE)) { + if(Curl_hsts(data->hsts, conn->host.name, strlen(conn->host.name), TRUE)) { char *url; Curl_safefree(data->state.up.scheme); uc = curl_url_set(uh, CURLUPART_SCHEME, "https", 0); @@ -1854,10 +1954,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return result; /* - * User name and password set with their own options override the - * credentials possibly set in the URL. + * username and password set with their own options override the credentials + * possibly set in the URL, but netrc does not. */ - if(!data->state.aptr.passwd) { + if(!data->state.aptr.passwd || (data->state.creds_from != CREDS_OPTION)) { uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0); if(!uc) { char *decoded; @@ -1870,13 +1970,14 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, result = Curl_setstropt(&data->state.aptr.passwd, decoded); if(result) return result; + data->state.creds_from = CREDS_URL; } else if(uc != CURLUE_NO_PASSWORD) return Curl_uc_to_curlcode(uc); } - if(!data->set.str[STRING_USERNAME]) { - /* we don't use the URL API's URL decoder option here since it rejects + if(!data->state.aptr.user || (data->state.creds_from != CREDS_OPTION)) { + /* we do not use the URL API's URL decoder option here since it rejects control codes and we want to allow them for some schemes in the user and password fields */ uc = curl_url_get(uh, CURLUPART_USER, &data->state.up.user, 0); @@ -1889,13 +1990,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return result; conn->user = decoded; result = Curl_setstropt(&data->state.aptr.user, decoded); + data->state.creds_from = CREDS_URL; } else if(uc != CURLUE_NO_USER) return Curl_uc_to_curlcode(uc); - else if(data->state.aptr.passwd) { - /* no user was set but a password, set a blank user */ - result = Curl_setstropt(&data->state.aptr.user, ""); - } if(result) return result; } @@ -1922,15 +2020,22 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } else { - unsigned long port = strtoul(data->state.up.port, NULL, 10); - conn->port = conn->remote_port = - (data->set.use_port && data->state.allow_port) ? - data->set.use_port : curlx_ultous(port); + curl_off_t port; + bool valid = TRUE; + if(data->set.use_port && data->state.allow_port) + port = data->set.use_port; + else { + const char *p = data->state.up.port; + if(curlx_str_number(&p, &port, 0xffff)) + valid = FALSE; + } + if(valid) + conn->primary.remote_port = conn->remote_port = (unsigned short)port; } (void)curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, 0); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(data->set.scope_id) /* Override any scope that was set above. */ conn->scope_id = data->set.scope_id; @@ -1941,7 +2046,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, /* - * If we're doing a resumed transfer, we need to setup our stuff + * If we are doing a resumed transfer, we need to setup our stuff * properly. */ static CURLcode setup_range(struct Curl_easy *data) @@ -1953,15 +2058,15 @@ static CURLcode setup_range(struct Curl_easy *data) free(s->range); if(s->resume_from) - s->range = aprintf("%" CURL_FORMAT_CURL_OFF_T "-", s->resume_from); + s->range = aprintf("%" FMT_OFF_T "-", s->resume_from); else s->range = strdup(data->set.str[STRING_SET_RANGE]); - s->rangestringalloc = (s->range) ? TRUE : FALSE; - if(!s->range) return CURLE_OUT_OF_MEMORY; + s->rangestringalloc = TRUE; + /* tell ourselves to fetch this range */ s->use_range = TRUE; /* enable range download */ } @@ -1985,6 +2090,8 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, struct connectdata *conn) { const struct Curl_handler *p; + const char *hostname; + int port; CURLcode result; /* Perform setup complement if some. */ @@ -1997,32 +2104,41 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, return result; p = conn->handler; /* May have changed. */ - } - - if(conn->port < 0) - /* we check for -1 here since if proxy was detected already, this - was very likely already set to the proxy port */ - conn->port = p->defport; - - return CURLE_OK; -} - -/* - * Curl_free_request_state() should free temp data that was allocated in the - * Curl_easy for this single request. - */ + } -void Curl_free_request_state(struct Curl_easy *data) -{ - Curl_safefree(data->req.p.http); - Curl_safefree(data->req.newurl); + if(conn->primary.remote_port < 0) + /* we check for -1 here since if proxy was detected already, this was + likely already set to the proxy port */ + conn->primary.remote_port = p->defport; -#ifndef CURL_DISABLE_DOH - if(data->req.doh) { - Curl_close(&data->req.doh->probe[0].easy); - Curl_close(&data->req.doh->probe[1].easy); + /* Now create the destination name */ +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + hostname = conn->http_proxy.host.name; + port = conn->primary.remote_port; + } + else +#endif + { + port = conn->remote_port; + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; } + +#ifdef USE_IPV6 + conn->destination = aprintf("%u/%d/%s", conn->scope_id, port, hostname); +#else + conn->destination = aprintf("%d/%s", port, hostname); #endif + if(!conn->destination) + return CURLE_OUT_OF_MEMORY; + + Curl_strntolower(conn->destination, conn->destination, + strlen(conn->destination)); + + return CURLE_OK; } @@ -2054,28 +2170,21 @@ static char *detect_proxy(struct Curl_easy *data, * the first to check for.) * * For compatibility, the all-uppercase versions of these variables are - * checked if the lowercase versions don't exist. + * checked if the lowercase versions do not exist. */ - char proxy_env[128]; - const char *protop = conn->handler->scheme; - char *envp = proxy_env; - char *prox; + char proxy_env[20]; + const char *envp = proxy_env; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - /* Now, build _proxy and check for such a one to use */ - while(*protop) - *envp++ = Curl_raw_tolower(*protop++); - - /* append _proxy */ - strcpy(envp, "_proxy"); + msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", conn->handler->scheme); /* read the protocol proxy: */ - prox = curl_getenv(proxy_env); + proxy = curl_getenv(proxy_env); /* - * We don't try the uppercase version of HTTP_PROXY because of + * We do not try the uppercase version of HTTP_PROXY because of * security reasons: * * When curl is used in a webserver application @@ -2086,23 +2195,34 @@ static char *detect_proxy(struct Curl_easy *data, * This can cause 'internal' http/ftp requests to be * arbitrarily redirected by any external attacker. */ - if(!prox && !strcasecompare("http_proxy", proxy_env)) { + if(!proxy && !strcasecompare("http_proxy", proxy_env)) { /* There was no lowercase variable, try the uppercase version: */ Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); - prox = curl_getenv(proxy_env); + proxy = curl_getenv(proxy_env); } - envp = proxy_env; - if(prox) { - proxy = prox; /* use this */ - } - else { - envp = (char *)"all_proxy"; - proxy = curl_getenv(envp); /* default proxy to use */ + if(!proxy) { +#ifndef CURL_DISABLE_WEBSOCKETS + /* websocket proxy fallbacks */ + if(strcasecompare("ws_proxy", proxy_env)) { + proxy = curl_getenv("http_proxy"); + } + else if(strcasecompare("wss_proxy", proxy_env)) { + proxy = curl_getenv("https_proxy"); + if(!proxy) + proxy = curl_getenv("HTTPS_PROXY"); + } if(!proxy) { - envp = (char *)"ALL_PROXY"; - proxy = curl_getenv(envp); +#endif + envp = "all_proxy"; + proxy = curl_getenv(envp); /* default proxy to use */ + if(!proxy) { + envp = "ALL_PROXY"; + proxy = curl_getenv(envp); + } +#ifndef CURL_DISABLE_WEBSOCKETS } +#endif } if(proxy) infof(data, "Uses proxy env variable %s == '%s'", envp, proxy); @@ -2113,7 +2233,7 @@ static char *detect_proxy(struct Curl_easy *data, /* * If this is supposed to use a proxy, we need to figure out the proxy - * host name, so that we can re-use an existing connection + * hostname, so that we can reuse an existing connection * that may exist registered to the same proxy host. */ static CURLcode parse_proxy(struct Curl_easy *data, @@ -2213,7 +2333,7 @@ static CURLcode parse_proxy(struct Curl_easy *data, goto error; if(proxyuser || proxypasswd) { - Curl_safefree(proxyinfo->user); + free(proxyinfo->user); proxyinfo->user = proxyuser; result = Curl_setstropt(&data->state.aptr.proxyuser, proxyuser); proxyuser = NULL; @@ -2238,7 +2358,10 @@ static CURLcode parse_proxy(struct Curl_easy *data, (void)curl_url_get(uhp, CURLUPART_PORT, &portptr, 0); if(portptr) { - port = (int)strtol(portptr, NULL, 10); + curl_off_t num; + const char *p = portptr; + if(!curlx_str_number(&p, &num, 0xffff)) + port = (int)num; free(portptr); } else { @@ -2255,11 +2378,12 @@ static CURLcode parse_proxy(struct Curl_easy *data, } if(port >= 0) { proxyinfo->port = port; - if(conn->port < 0 || sockstype || !conn->socks_proxy.host.rawalloc) - conn->port = port; + if(conn->primary.remote_port < 0 || sockstype || + !conn->socks_proxy.host.rawalloc) + conn->primary.remote_port = port; } - /* now, clone the proxy host name */ + /* now, clone the proxy hostname */ uc = curl_url_get(uhp, CURLUPART_HOST, &host, CURLU_URLDECODE); if(uc) { result = CURLE_OUT_OF_MEMORY; @@ -2281,7 +2405,7 @@ static CURLcode parse_proxy(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; goto error; } - Curl_safefree(proxyinfo->host.rawalloc); + free(proxyinfo->host.rawalloc); proxyinfo->host.rawalloc = host; proxyinfo->host.name = host; host = NULL; @@ -2290,7 +2414,7 @@ static CURLcode parse_proxy(struct Curl_easy *data, if(!is_unix_proxy) { #endif - Curl_safefree(proxyinfo->host.rawalloc); + free(proxyinfo->host.rawalloc); proxyinfo->host.rawalloc = host; if(host[0] == '[') { /* this is a numerical IPv6, strip off the brackets */ @@ -2327,21 +2451,20 @@ static CURLcode parse_proxy_auth(struct Curl_easy *data, data->state.aptr.proxyuser : ""; const char *proxypasswd = data->state.aptr.proxypasswd ? data->state.aptr.proxypasswd : ""; - CURLcode result = Curl_urldecode(proxyuser, 0, &conn->http_proxy.user, NULL, - REJECT_ZERO); - if(!result) - result = Curl_setstropt(&data->state.aptr.proxyuser, - conn->http_proxy.user); - if(!result) - result = Curl_urldecode(proxypasswd, 0, &conn->http_proxy.passwd, - NULL, REJECT_ZERO); - if(!result) - result = Curl_setstropt(&data->state.aptr.proxypasswd, - conn->http_proxy.passwd); + CURLcode result = CURLE_OUT_OF_MEMORY; + + conn->http_proxy.user = strdup(proxyuser); + if(conn->http_proxy.user) { + conn->http_proxy.passwd = strdup(proxypasswd); + if(conn->http_proxy.passwd) + result = CURLE_OK; + else + Curl_safefree(conn->http_proxy.user); + } return result; } -/* create_conn helper to parse and init proxy values. to be called after unix +/* create_conn helper to parse and init proxy values. to be called after Unix socket init but before any proxy vars are evaluated. */ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, struct connectdata *conn) @@ -2350,7 +2473,6 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, char *socksproxy = NULL; char *no_proxy = NULL; CURLcode result = CURLE_OK; - bool spacesep = FALSE; /************************************************************* * Extract the user and password from the authentication string @@ -2397,8 +2519,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, } if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy, - &spacesep)) { + data->set.str[STRING_NOPROXY] : no_proxy)) { Curl_safefree(proxy); Curl_safefree(socksproxy); } @@ -2407,13 +2528,10 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, /* if the host is not in the noproxy list, detect proxy. */ proxy = detect_proxy(data, conn); #endif /* CURL_DISABLE_HTTP */ - if(spacesep) - infof(data, "space-separated NOPROXY patterns are deprecated"); - Curl_safefree(no_proxy); #ifdef USE_UNIX_SOCKETS - /* For the time being do not mix proxy and unix domain sockets. See #1274 */ + /* For the time being do not mix proxy and Unix domain sockets. See #1274 */ if(proxy && conn->unix_domain_socket) { free(proxy); proxy = NULL; @@ -2421,20 +2539,20 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, #endif if(proxy && (!*proxy || (conn->handler->flags & PROTOPT_NONETWORK))) { - free(proxy); /* Don't bother with an empty proxy string or if the - protocol doesn't work with network */ + free(proxy); /* Do not bother with an empty proxy string or if the + protocol does not work with network */ proxy = NULL; } if(socksproxy && (!*socksproxy || (conn->handler->flags & PROTOPT_NONETWORK))) { - free(socksproxy); /* Don't bother with an empty socks proxy string or if - the protocol doesn't work with network */ + free(socksproxy); /* Do not bother with an empty socks proxy string or if + the protocol does not work with network */ socksproxy = NULL; } /*********************************************************************** * If this is supposed to use a proxy, we need to figure out the proxy host - * name, proxy type and port number, so that we can re-use an existing + * name, proxy type and port number, so that we can reuse an existing * connection that may exist registered to the same proxy host. ***********************************************************************/ if(proxy || socksproxy) { @@ -2483,7 +2601,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, if(!conn->socks_proxy.user) { conn->socks_proxy.user = conn->http_proxy.user; conn->http_proxy.user = NULL; - Curl_safefree(conn->socks_proxy.passwd); + free(conn->socks_proxy.passwd); conn->socks_proxy.passwd = conn->http_proxy.passwd; conn->http_proxy.passwd = NULL; } @@ -2500,7 +2618,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, conn->bits.proxy = conn->bits.httpproxy || conn->bits.socksproxy; if(!conn->bits.proxy) { - /* we aren't using the proxy after all... */ + /* we are not using the proxy after all... */ conn->bits.proxy = FALSE; conn->bits.httpproxy = FALSE; conn->bits.socksproxy = FALSE; @@ -2522,7 +2640,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, /* * Curl_parse_login_details() * - * This is used to parse a login string for user name, password and options in + * This is used to parse a login string for username, password and options in * the following formats: * * user @@ -2537,14 +2655,15 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, * * Parameters: * - * login [in] - The login string. - * len [in] - The length of the login string. - * userp [in/out] - The address where a pointer to newly allocated memory + * login [in] - login string. + * len [in] - length of the login string. + * userp [in/out] - address where a pointer to newly allocated memory * holding the user will be stored upon completion. - * passwdp [in/out] - The address where a pointer to newly allocated memory + * passwdp [in/out] - address where a pointer to newly allocated memory * holding the password will be stored upon completion. - * optionsp [in/out] - The address where a pointer to newly allocated memory - * holding the options will be stored upon completion. + * optionsp [in/out] - OPTIONAL address where a pointer to newly allocated + * memory holding the options will be stored upon + * completion. * * Returns CURLE_OK on success. */ @@ -2552,19 +2671,19 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, char **userp, char **passwdp, char **optionsp) { - CURLcode result = CURLE_OK; char *ubuf = NULL; char *pbuf = NULL; - char *obuf = NULL; const char *psep = NULL; const char *osep = NULL; size_t ulen; size_t plen; size_t olen; + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); + /* Attempt to find the password separator */ - if(passwdp) - psep = memchr(login, ':', len); + psep = memchr(login, ':', len); /* Attempt to find the options separator */ if(optionsp) @@ -2576,64 +2695,40 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, (osep ? (size_t)(osep - login) : len)); plen = (psep ? (osep && osep > psep ? (size_t)(osep - psep) : - (size_t)(login + len - psep)) - 1 : 0); + (size_t)(login + len - psep)) - 1 : 0); olen = (osep ? (psep && psep > osep ? (size_t)(psep - osep) : - (size_t)(login + len - osep)) - 1 : 0); + (size_t)(login + len - osep)) - 1 : 0); - /* Allocate the user portion buffer, which can be zero length */ - if(userp) { - ubuf = malloc(ulen + 1); - if(!ubuf) - result = CURLE_OUT_OF_MEMORY; - } + /* Clone the user portion buffer, which can be zero length */ + ubuf = Curl_memdup0(login, ulen); + if(!ubuf) + goto error; - /* Allocate the password portion buffer */ - if(!result && passwdp && psep) { - pbuf = malloc(plen + 1); - if(!pbuf) { - free(ubuf); - result = CURLE_OUT_OF_MEMORY; - } + /* Clone the password portion buffer */ + if(psep) { + pbuf = Curl_memdup0(&psep[1], plen); + if(!pbuf) + goto error; } /* Allocate the options portion buffer */ - if(!result && optionsp && olen) { - obuf = malloc(olen + 1); - if(!obuf) { - free(pbuf); - free(ubuf); - result = CURLE_OUT_OF_MEMORY; - } - } - - if(!result) { - /* Store the user portion if necessary */ - if(ubuf) { - memcpy(ubuf, login, ulen); - ubuf[ulen] = '\0'; - Curl_safefree(*userp); - *userp = ubuf; - } - - /* Store the password portion if necessary */ - if(pbuf) { - memcpy(pbuf, psep + 1, plen); - pbuf[plen] = '\0'; - Curl_safefree(*passwdp); - *passwdp = pbuf; - } - - /* Store the options portion if necessary */ - if(obuf) { - memcpy(obuf, osep + 1, olen); - obuf[olen] = '\0'; - Curl_safefree(*optionsp); - *optionsp = obuf; + if(optionsp) { + char *obuf = NULL; + if(olen) { + obuf = Curl_memdup0(&osep[1], olen); + if(!obuf) + goto error; } + *optionsp = obuf; } - - return result; + *userp = ubuf; + *passwdp = pbuf; + return CURLE_OK; +error: + free(ubuf); + free(pbuf); + return CURLE_OUT_OF_MEMORY; } /************************************************************* @@ -2662,6 +2757,17 @@ static CURLcode parse_remote_port(struct Curl_easy *data, return CURLE_OK; } +static bool str_has_ctrl(const char *input) +{ + const unsigned char *str = (const unsigned char *)input; + while(*str) { + if(*str < 0x20) + return TRUE; + str++; + } + return FALSE; +} + /* * Override the login details from the URL with that in the CURLOPT_USERPWD * option or a .netrc file, if applicable. @@ -2688,34 +2794,49 @@ static CURLcode override_login(struct Curl_easy *data, } conn->bits.netrc = FALSE; if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) { - int ret; bool url_provided = FALSE; - if(data->state.aptr.user) { - /* there was a user name in the URL. Use the URL decoded version */ + if(data->state.aptr.user && + (data->state.creds_from != CREDS_NETRC)) { + /* there was a username with a length in the URL. Use the URL decoded + version */ userp = &data->state.aptr.user; url_provided = TRUE; } - ret = Curl_parsenetrc(conn->host.name, - userp, passwdp, - data->set.str[STRING_NETRC_FILE]); - if(ret > 0) { - infof(data, "Couldn't find host %s in the %s file; using defaults", - conn->host.name, data->set.str[STRING_NETRC_FILE]); - } - else if(ret < 0) { - failf(data, ".netrc parser error"); - return CURLE_READ_ERROR; - } - else { - /* set bits.netrc TRUE to remember that we got the name from a .netrc - file, so that it is safe to use even if we followed a Location: to a - different host or similar. */ - conn->bits.netrc = TRUE; + if(!*passwdp) { + NETRCcode ret = Curl_parsenetrc(&data->state.netrc, conn->host.name, + userp, passwdp, + data->set.str[STRING_NETRC_FILE]); + if(ret && ((ret == NETRC_NO_MATCH) || + (data->set.use_netrc == CURL_NETRC_OPTIONAL))) { + infof(data, "Couldn't find host %s in the %s file; using defaults", + conn->host.name, + (data->set.str[STRING_NETRC_FILE] ? + data->set.str[STRING_NETRC_FILE] : ".netrc")); + } + else if(ret) { + const char *m = Curl_netrc_strerror(ret); + failf(data, ".netrc error: %s", m); + return CURLE_READ_ERROR; + } + else { + if(!(conn->handler->flags&PROTOPT_USERPWDCTRL)) { + /* if the protocol can't handle control codes in credentials, make + sure there are none */ + if(str_has_ctrl(*userp) || str_has_ctrl(*passwdp)) { + failf(data, "control code detected in .netrc credentials"); + return CURLE_READ_ERROR; + } + } + /* set bits.netrc TRUE to remember that we got the name from a .netrc + file, so that it is safe to use even if we followed a Location: to a + different host or similar. */ + conn->bits.netrc = TRUE; + } } if(url_provided) { - Curl_safefree(conn->user); + free(conn->user); conn->user = strdup(*userp); if(!conn->user) return CURLE_OUT_OF_MEMORY; @@ -2737,6 +2858,7 @@ static CURLcode override_login(struct Curl_easy *data, result = Curl_setstropt(&data->state.aptr.user, *userp); if(result) return result; + data->state.creds_from = CREDS_NETRC; } } if(data->state.aptr.user) { @@ -2754,6 +2876,7 @@ static CURLcode override_login(struct Curl_easy *data, CURLcode result = Curl_setstropt(&data->state.aptr.passwd, *passwdp); if(result) return result; + data->state.creds_from = CREDS_NETRC; } if(data->state.aptr.passwd) { uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD, @@ -2771,7 +2894,7 @@ static CURLcode override_login(struct Curl_easy *data, } /* - * Set the login details so they're available in the connection + * Set the login details so they are available in the connection */ static CURLcode set_login(struct Curl_easy *data, struct connectdata *conn) @@ -2821,7 +2944,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, int port = -1; CURLcode result = CURLE_OK; -#if defined(CURL_DISABLE_VERBOSE_STRINGS) +#ifdef CURL_DISABLE_VERBOSE_STRINGS (void) data; #endif @@ -2842,7 +2965,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, /* detect and extract RFC6874-style IPv6-addresses */ if(*hostptr == '[') { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 char *ptr = ++hostptr; /* advance beyond the initial bracket */ while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.'))) ptr++; @@ -2862,8 +2985,8 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, else infof(data, "Invalid IPv6 address format"); portptr = ptr; - /* Note that if this didn't end with a bracket, we still advanced the - * hostptr first, but I can't see anything wrong with that as no host + /* Note that if this did not end with a bracket, we still advanced the + * hostptr first, but I cannot see anything wrong with that as no host * name nor a numeric can legally start with a bracket. */ #else @@ -2876,23 +2999,22 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, /* Get port number off server.com:1080 */ host_portno = strchr(portptr, ':'); if(host_portno) { - char *endp = NULL; - *host_portno = '\0'; /* cut off number from host name */ + *host_portno = '\0'; /* cut off number from hostname */ host_portno++; if(*host_portno) { - long portparse = strtol(host_portno, &endp, 10); - if((endp && *endp) || (portparse < 0) || (portparse > 65535)) { + curl_off_t portparse; + const char *p = host_portno; + if(curlx_str_number(&p, &portparse, 0xffff)) { failf(data, "No valid port number in connect to host string (%s)", host_portno); result = CURLE_SETOPT_OPTION_SYNTAX; goto error; } - else - port = (int)portparse; /* we know it will fit */ + port = (int)portparse; /* we know it will fit */ } } - /* now, clone the cleaned host name */ + /* now, clone the cleaned hostname */ DEBUGASSERT(hostptr); *hostname_result = strdup(hostptr); if(!*hostname_result) { @@ -2919,8 +3041,8 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data, { CURLcode result = CURLE_OK; const char *ptr = conn_to_host; - int host_match = FALSE; - int port_match = FALSE; + bool host_match = FALSE; + bool port_match = FALSE; *host_result = NULL; *port_result = -1; @@ -2959,12 +3081,11 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data, /* check whether the URL's port matches */ char *ptr_next = strchr(ptr, ':'); if(ptr_next) { - char *endp = NULL; - long port_to_match = strtol(ptr, &endp, 10); - if((endp == ptr_next) && (port_to_match == conn->remote_port)) { + curl_off_t port_to_match; + if(!curlx_str_number(&ptr, &port_to_match, 0xffff) && + (port_to_match == (curl_off_t)conn->remote_port)) port_match = TRUE; - ptr = ptr_next + 1; - } + ptr = ptr_next + 1; } } } @@ -3025,7 +3146,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, #ifndef CURL_DISABLE_ALTSVC if(data->asi && !host && (port == -1) && ((conn->handler->protocol == CURLPROTO_HTTPS) || -#ifdef CURLDEBUG +#ifdef DEBUGBUILD /* allow debug builds to circumvent the HTTPS restriction */ getenv("CURL_ALTSVC_HTTP") #else @@ -3033,35 +3154,56 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, #endif )) { /* no connect_to match, try alt-svc! */ - enum alpnid srcalpnid; - bool hit; - struct altsvc *as; - const int allowed_versions = ( ALPN_h1 -#ifdef USE_HTTP2 - | ALPN_h2 + enum alpnid srcalpnid = ALPN_none; + bool hit = FALSE; + struct altsvc *as = NULL; + int allowed_alpns = ALPN_none; + struct http_negotiation *neg = &data->state.http_neg; + + DEBUGF(infof(data, "Alt-svc check wanted=%x, allowed=%x", + neg->wanted, neg->allowed)); +#ifdef USE_HTTP3 + if(neg->allowed & CURL_HTTP_V3x) + allowed_alpns |= ALPN_h3; #endif -#ifdef ENABLE_QUIC - | ALPN_h3 +#ifdef USE_HTTP2 + if(neg->allowed & CURL_HTTP_V2x) + allowed_alpns |= ALPN_h2; #endif - ) & data->asi->flags; + if(neg->allowed & CURL_HTTP_V1x) + allowed_alpns |= ALPN_h1; + allowed_alpns &= (int)data->asi->flags; host = conn->host.rawalloc; -#ifdef USE_HTTP2 - /* with h2 support, check that first */ - srcalpnid = ALPN_h2; - hit = Curl_altsvc_lookup(data->asi, - srcalpnid, host, conn->remote_port, /* from */ - &as /* to */, - allowed_versions); - if(!hit) -#endif - { + DEBUGF(infof(data, "check Alt-Svc for host %s", host)); +#ifdef USE_HTTP3 + if(!hit && (neg->wanted & CURL_HTTP_V3x)) { + srcalpnid = ALPN_h3; + hit = Curl_altsvc_lookup(data->asi, + ALPN_h3, host, conn->remote_port, /* from */ + &as /* to */, + allowed_alpns); + } + #endif + #ifdef USE_HTTP2 + if(!hit && (neg->wanted & CURL_HTTP_V2x) && + !neg->h2_prior_knowledge) { + srcalpnid = ALPN_h2; + hit = Curl_altsvc_lookup(data->asi, + ALPN_h2, host, conn->remote_port, /* from */ + &as /* to */, + allowed_alpns); + } + #endif + if(!hit && (neg->wanted & CURL_HTTP_V1x) && + !neg->only_10) { srcalpnid = ALPN_h1; hit = Curl_altsvc_lookup(data->asi, - srcalpnid, host, conn->remote_port, /* from */ + ALPN_h1, host, conn->remote_port, /* from */ &as /* to */, - allowed_versions); + allowed_alpns); } + if(hit) { char *hostd = strdup((char *)as->dst.host); if(!hostd) @@ -3079,16 +3221,17 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, /* protocol version switch */ switch(as->dst.alpnid) { case ALPN_h1: - conn->httpversion = 11; + neg->wanted = neg->allowed = CURL_HTTP_V1x; + neg->only_10 = FALSE; break; case ALPN_h2: - conn->httpversion = 20; + neg->wanted = neg->allowed = CURL_HTTP_V2x; break; case ALPN_h3: conn->transport = TRNSPRT_QUIC; - conn->httpversion = 30; + neg->wanted = neg->allowed = CURL_HTTP_V3x; break; - default: /* shouldn't be possible */ + default: /* should not be possible */ break; } } @@ -3102,13 +3245,14 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, #ifdef USE_UNIX_SOCKETS static CURLcode resolve_unix(struct Curl_easy *data, struct connectdata *conn, - char *unix_path) + char *unix_path, + struct Curl_dns_entry **pdns) { - struct Curl_dns_entry *hostaddr = NULL; + struct Curl_dns_entry *hostaddr; bool longpath = FALSE; DEBUGASSERT(unix_path); - DEBUGASSERT(conn->dns_entry == NULL); + *pdns = NULL; /* Unix domain sockets are local. The host gets ignored, just use the * specified domain socket address. Do not cache "DNS entries". There is @@ -3127,143 +3271,92 @@ static CURLcode resolve_unix(struct Curl_easy *data, return longpath ? CURLE_COULDNT_RESOLVE_HOST : CURLE_OUT_OF_MEMORY; } - hostaddr->inuse++; - conn->dns_entry = hostaddr; + hostaddr->refcount = 1; /* connection is the only one holding this */ + *pdns = hostaddr; return CURLE_OK; } #endif -#ifndef CURL_DISABLE_PROXY -static CURLcode resolve_proxy(struct Curl_easy *data, - struct connectdata *conn, - bool *async) +/************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ +static CURLcode resolve_server(struct Curl_easy *data, + struct connectdata *conn, + bool *async, + struct Curl_dns_entry **pdns) { - struct Curl_dns_entry *hostaddr = NULL; - struct hostname *host; + struct hostname *ehost; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - int rc; + const char *peertype = "host"; + CURLcode result; - DEBUGASSERT(conn->dns_entry == NULL); + *pdns = NULL; - host = conn->bits.socksproxy ? &conn->socks_proxy.host : - &conn->http_proxy.host; +#ifdef USE_UNIX_SOCKETS + { + char *unix_path = conn->unix_domain_socket; - conn->hostname_resolve = strdup(host->name); - if(!conn->hostname_resolve) - return CURLE_OUT_OF_MEMORY; +#ifndef CURL_DISABLE_PROXY + if(!unix_path && CONN_IS_PROXIED(conn) && conn->socks_proxy.host.name && + !strncmp(UNIX_SOCKET_PREFIX"/", + conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX))) + unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1; +#endif - rc = Curl_resolv_timeout(data, conn->hostname_resolve, (int)conn->port, - &hostaddr, timeout_ms); - conn->dns_entry = hostaddr; - if(rc == CURLRESOLV_PENDING) - *async = TRUE; - else if(rc == CURLRESOLV_TIMEDOUT) - return CURLE_OPERATION_TIMEDOUT; - else if(!hostaddr) { - failf(data, "Couldn't resolve proxy '%s'", host->dispname); - return CURLE_COULDNT_RESOLVE_PROXY; + if(unix_path) { + /* This only works if previous transport is TRNSPRT_TCP. Check it? */ + conn->transport = TRNSPRT_UNIX; + return resolve_unix(data, conn, unix_path, pdns); + } } - - return CURLE_OK; -} #endif -static CURLcode resolve_host(struct Curl_easy *data, - struct connectdata *conn, - bool *async) -{ - struct Curl_dns_entry *hostaddr = NULL; - struct hostname *connhost; - timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - int rc; - - DEBUGASSERT(conn->dns_entry == NULL); - - connhost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host; - - /* If not connecting via a proxy, extract the port from the URL, if it is - * there, thus overriding any defaults that might have been set above. */ - conn->port = conn->bits.conn_to_port ? conn->conn_to_port : - conn->remote_port; +#ifndef CURL_DISABLE_PROXY + if(CONN_IS_PROXIED(conn)) { + ehost = conn->bits.socksproxy ? &conn->socks_proxy.host : + &conn->http_proxy.host; + peertype = "proxy"; + } + else +#endif + { + ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host; + /* If not connecting via a proxy, extract the port from the URL, if it is + * there, thus overriding any defaults that might have been set above. */ + conn->primary.remote_port = conn->bits.conn_to_port ? conn->conn_to_port : + conn->remote_port; + } /* Resolve target host right on */ - conn->hostname_resolve = strdup(connhost->name); + conn->hostname_resolve = strdup(ehost->name); if(!conn->hostname_resolve) return CURLE_OUT_OF_MEMORY; - rc = Curl_resolv_timeout(data, conn->hostname_resolve, (int)conn->port, - &hostaddr, timeout_ms); - conn->dns_entry = hostaddr; - if(rc == CURLRESOLV_PENDING) + result = Curl_resolv_timeout(data, conn->hostname_resolve, + conn->primary.remote_port, conn->ip_version, + pdns, timeout_ms); + DEBUGASSERT(!result || !*pdns); + if(result == CURLE_AGAIN) { *async = TRUE; - else if(rc == CURLRESOLV_TIMEDOUT) { - failf(data, "Failed to resolve host '%s' with timeout after %ld ms", - connhost->dispname, - Curl_timediff(Curl_now(), data->progress.t_startsingle)); + return CURLE_OK; + } + else if(result == CURLE_OPERATION_TIMEDOUT) { + failf(data, "Failed to resolve %s '%s' with timeout after %" + FMT_TIMEDIFF_T " ms", peertype, ehost->dispname, + curlx_timediff(curlx_now(), data->progress.t_startsingle)); return CURLE_OPERATION_TIMEDOUT; } - else if(!hostaddr) { - failf(data, "Could not resolve host: %s", connhost->dispname); - return CURLE_COULDNT_RESOLVE_HOST; + else if(result) { + failf(data, "Could not resolve %s: %s", peertype, ehost->dispname); + return result; } - + DEBUGASSERT(*pdns); return CURLE_OK; } -/* Perform a fresh resolve */ -static CURLcode resolve_fresh(struct Curl_easy *data, - struct connectdata *conn, - bool *async) -{ -#ifdef USE_UNIX_SOCKETS - char *unix_path = conn->unix_domain_socket; - -#ifndef CURL_DISABLE_PROXY - if(!unix_path && conn->socks_proxy.host.name && - !strncmp(UNIX_SOCKET_PREFIX"/", - conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX))) - unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1; -#endif - - if(unix_path) { - conn->transport = TRNSPRT_UNIX; - return resolve_unix(data, conn, unix_path); - } -#endif - -#ifndef CURL_DISABLE_PROXY - if(CONN_IS_PROXIED(conn)) - return resolve_proxy(data, conn, async); -#endif - - return resolve_host(data, conn, async); -} - -/************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ -static CURLcode resolve_server(struct Curl_easy *data, - struct connectdata *conn, - bool *async) -{ - DEBUGASSERT(conn); - DEBUGASSERT(data); - - /* Resolve the name of the server or proxy */ - if(conn->bits.reuse) { - /* We're reusing the connection - no need to resolve anything, and - idnconvert_hostname() was called already in create_conn() for the re-use - case. */ - *async = FALSE; - return CURLE_OK; - } - - return resolve_fresh(data, conn, async); -} - /* * Cleanup the connection `temp`, just allocated for `data`, before using the - * previously `existing` one for `data`. All relevant info is copied over + * previously `existing` one for `data`. All relevant info is copied over * and `temp` is freed. */ static void reuse_conn(struct Curl_easy *data, @@ -3271,11 +3364,11 @@ static void reuse_conn(struct Curl_easy *data, struct connectdata *existing) { /* get the user+password information from the temp struct since it may - * be new for this request even when we re-use an existing connection */ + * be new for this request even when we reuse an existing connection */ if(temp->user) { - /* use the new user name and password though */ - Curl_safefree(existing->user); - Curl_safefree(existing->passwd); + /* use the new username and password though */ + free(existing->user); + free(existing->passwd); existing->user = temp->user; existing->passwd = temp->passwd; temp->user = NULL; @@ -3285,11 +3378,11 @@ static void reuse_conn(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY existing->bits.proxy_user_passwd = temp->bits.proxy_user_passwd; if(existing->bits.proxy_user_passwd) { - /* use the new proxy user name and proxy password though */ - Curl_safefree(existing->http_proxy.user); - Curl_safefree(existing->socks_proxy.user); - Curl_safefree(existing->http_proxy.passwd); - Curl_safefree(existing->socks_proxy.passwd); + /* use the new proxy username and proxy password though */ + free(existing->http_proxy.user); + free(existing->socks_proxy.user); + free(existing->http_proxy.passwd); + free(existing->socks_proxy.passwd); existing->http_proxy.user = temp->http_proxy.user; existing->socks_proxy.user = temp->socks_proxy.user; existing->http_proxy.passwd = temp->http_proxy.passwd; @@ -3301,7 +3394,7 @@ static void reuse_conn(struct Curl_easy *data, } #endif - /* Finding a connection for reuse in the cache matches, among other + /* Finding a connection for reuse in the cpool matches, among other * things on the "remote-relevant" hostname. This is not necessarily * the authority of the URL, e.g. conn->host. For example: * - we use a proxy (not tunneling). we want to send all requests @@ -3311,7 +3404,7 @@ static void reuse_conn(struct Curl_easy *data, * We want to reuse an existing conn to the remote endpoint. * Since connection reuse does not match on conn->host necessarily, we * switch `existing` conn to `temp` conn's host settings. - * TODO: is this correct in the case of TLS connections that have + * Is this correct in the case of TLS connections that have * used the original hostname in SNI to negotiate? Do we send * requests for another host through the different SNI? */ @@ -3326,20 +3419,28 @@ static void reuse_conn(struct Curl_easy *data, temp->conn_to_host.rawalloc = NULL; existing->conn_to_port = temp->conn_to_port; existing->remote_port = temp->remote_port; - Curl_safefree(existing->hostname_resolve); - + free(existing->hostname_resolve); existing->hostname_resolve = temp->hostname_resolve; temp->hostname_resolve = NULL; - /* re-use init */ - existing->bits.reuse = TRUE; /* yes, we're re-using here */ + /* reuse init */ + existing->bits.reuse = TRUE; /* yes, we are reusing here */ + + Curl_conn_free(data, temp); +} - conn_free(data, temp); +static void conn_meta_freeentry(void *p) +{ + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); } /** - * create_conn() sets up a new connectdata struct, or re-uses an already - * existing one, and resolves host name. + * create_conn() sets up a new connectdata struct, or reuses an already + * existing one, and resolves hostname. * * if this function returns CURLE_OK and *async is set to TRUE, the resolve * response will be coming asynchronously. If *async is FALSE, the name is @@ -3347,14 +3448,14 @@ static void reuse_conn(struct Curl_easy *data, * * @param data The sessionhandle pointer * @param in_connect is set to the next connection data pointer - * @param async is set TRUE when an async DNS resolution is pending + * @param reusedp is set to to TRUE if connection was reused * @see Curl_setup_conn() * */ static CURLcode create_conn(struct Curl_easy *data, struct connectdata **in_connect, - bool *async) + bool *reusedp) { CURLcode result = CURLE_OK; struct connectdata *conn; @@ -3363,10 +3464,8 @@ static CURLcode create_conn(struct Curl_easy *data, bool connections_available = TRUE; bool force_reuse = FALSE; bool waitpipe = FALSE; - size_t max_host_connections = Curl_multi_max_host_connections(data->multi); - size_t max_total_connections = Curl_multi_max_total_connections(data->multi); - *async = FALSE; + *reusedp = FALSE; *in_connect = NULL; /************************************************************* @@ -3393,6 +3492,13 @@ static CURLcode create_conn(struct Curl_easy *data, any failure */ *in_connect = conn; + /* Do the unfailable inits first, before checks that may early return */ + Curl_hash_init(&conn->meta_hash, 23, + Curl_hash_str, curlx_str_key_compare, conn_meta_freeentry); + + /* GSSAPI related inits */ + Curl_sec_conn_init(conn); + result = parseurlandfillconn(data, conn); if(result) goto out; @@ -3424,7 +3530,7 @@ static CURLcode create_conn(struct Curl_easy *data, } #endif - /* After the unix socket init but before the proxy vars are used, parse and + /* After the Unix socket init but before the proxy vars are used, parse and initialize the proxy vars */ #ifndef CURL_DISABLE_PROXY result = create_conn_helper_init_proxy(data, conn); @@ -3521,7 +3627,7 @@ static CURLcode create_conn(struct Curl_easy *data, goto out; /*********************************************************************** - * file: is a special case in that it doesn't need a network connection + * file: is a special case in that it does not need a network connection ***********************************************************************/ #ifndef CURL_DISABLE_FILE if(conn->handler->flags & PROTOPT_NONETWORK) { @@ -3529,128 +3635,51 @@ static CURLcode create_conn(struct Curl_easy *data, /* this is supposed to be the connect function so we better at least check that the file is present here! */ DEBUGASSERT(conn->handler->connect_it); - Curl_persistconninfo(data, conn, NULL, -1); + data->info.conn_scheme = conn->handler->scheme; + /* conn_protocol can only provide "old" protocols */ + data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; result = conn->handler->connect_it(data, &done); + if(result) + goto out; - /* Setup a "faked" transfer that'll do nothing */ + /* Setup a "faked" transfer that will do nothing */ + Curl_attach_connection(data, conn); + result = Curl_cpool_add(data, conn); if(!result) { - Curl_attach_connection(data, conn); - result = Curl_conncache_add_conn(data); - if(result) - goto out; - - /* - * Setup whatever necessary for a resumed transfer - */ + /* Setup whatever necessary for a resumed transfer */ result = setup_range(data); - if(result) { - DEBUGASSERT(conn->handler->done); - /* we ignore the return code for the protocol-specific DONE */ - (void)conn->handler->done(data, result, FALSE); - goto out; + if(!result) { + Curl_xfer_setup_nop(data); + result = Curl_init_do(data, conn); } - Curl_setup_transfer(data, -1, -1, FALSE, -1); } - /* since we skip do_init() */ - Curl_init_do(data, conn); - + if(result) { + DEBUGASSERT(conn->handler->done); + /* we ignore the return code for the protocol-specific DONE */ + (void)conn->handler->done(data, result, FALSE); + } goto out; } #endif /* Setup filter for network connections */ - conn->recv[FIRSTSOCKET] = Curl_conn_recv; - conn->send[FIRSTSOCKET] = Curl_conn_send; - conn->recv[SECONDARYSOCKET] = Curl_conn_recv; - conn->send[SECONDARYSOCKET] = Curl_conn_send; + conn->recv[FIRSTSOCKET] = Curl_cf_recv; + conn->send[FIRSTSOCKET] = Curl_cf_send; + conn->recv[SECONDARYSOCKET] = Curl_cf_recv; + conn->send[SECONDARYSOCKET] = Curl_cf_send; conn->bits.tcp_fastopen = data->set.tcp_fastopen; - /* Get a cloned copy of the SSL config situation stored in the - connection struct. But to get this going nicely, we must first make - sure that the strings in the master copy are pointing to the correct - strings in the session handle strings array! - - Keep in mind that the pointers in the master copy are pointing to strings - that will be freed as part of the Curl_easy struct, but all cloned - copies will be separately allocated. - */ - data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH]; - data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE]; - data->set.ssl.primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; - data->set.ssl.primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; - data->set.ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST]; - data->set.ssl.primary.cipher_list13 = - data->set.str[STRING_SSL_CIPHER13_LIST]; - data->set.ssl.primary.pinned_key = - data->set.str[STRING_SSL_PINNEDPUBLICKEY]; - data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT]; - data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; - data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES]; - -#ifndef CURL_DISABLE_PROXY - data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; - data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; - data->set.proxy_ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; - data->set.proxy_ssl.primary.cipher_list13 = - data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; - data->set.proxy_ssl.primary.pinned_key = - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; - data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; - data->set.proxy_ssl.primary.ca_info_blob = - data->set.blobs[BLOB_CAINFO_PROXY]; - data->set.proxy_ssl.primary.issuercert = - data->set.str[STRING_SSL_ISSUERCERT_PROXY]; - data->set.proxy_ssl.primary.issuercert_blob = - data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; - data->set.proxy_ssl.primary.CRLfile = - data->set.str[STRING_SSL_CRLFILE_PROXY]; - data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; - data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY]; - data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; - data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; - data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY]; - data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY]; -#endif - data->set.ssl.primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; - data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE]; - data->set.ssl.key = data->set.str[STRING_KEY]; - data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE]; - data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD]; - data->set.ssl.primary.clientcert = data->set.str[STRING_CERT]; -#ifdef USE_TLS_SRP - data->set.ssl.primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; - data->set.ssl.primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; -#ifndef CURL_DISABLE_PROXY - data->set.proxy_ssl.primary.username = - data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; - data->set.proxy_ssl.primary.password = - data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; -#endif -#endif - data->set.ssl.key_blob = data->set.blobs[BLOB_KEY]; - - if(!Curl_clone_primary_ssl_config(&data->set.ssl.primary, - &conn->ssl_config)) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - -#ifndef CURL_DISABLE_PROXY - if(!Curl_clone_primary_ssl_config(&data->set.proxy_ssl.primary, - &conn->proxy_ssl_config)) { - result = CURLE_OUT_OF_MEMORY; + /* Complete the easy's SSL configuration for connection cache matching */ + result = Curl_ssl_easy_config_complete(data); + if(result) goto out; - } -#endif - prune_dead_connections(data); + Curl_cpool_prune_dead(data); /************************************************************* * Check the current list of connections to see if we can - * re-use an already existing one or if we have to create a + * reuse an already existing one or if we have to create a * new one. *************************************************************/ @@ -3658,7 +3687,7 @@ static CURLcode create_conn(struct Curl_easy *data, DEBUGASSERT(conn->passwd); /* reuse_fresh is TRUE if we are told to use a new connection by force, but - we only acknowledge this option if this is not a re-used connection + we only acknowledge this option if this is not a reused connection already (which happens due to follow-location or during an HTTP authentication phase). CONNECT_ONLY transfers also refuse reuse. */ if((data->set.reuse_fresh && !data->state.followlocation) || @@ -3673,20 +3702,26 @@ static CURLcode create_conn(struct Curl_easy *data, * `existing` and thus we need to cleanup the one we just * allocated before we can move along and use `existing`. */ + bool tls_upgraded = (!(conn->given->flags & PROTOPT_SSL) && + Curl_conn_is_ssl(conn, FIRSTSOCKET)); + reuse_conn(data, conn, existing); conn = existing; *in_connect = conn; #ifndef CURL_DISABLE_PROXY - infof(data, "Re-using existing connection #%ld with %s %s", - conn->connection_id, - conn->bits.proxy?"proxy":"host", + infof(data, "Re-using existing %s: connection%s with %s %s", + conn->given->scheme, + tls_upgraded ? " (upgraded to SSL)" : "", + conn->bits.proxy ? "proxy" : "host", conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname : conn->http_proxy.host.name ? conn->http_proxy.host.dispname : conn->host.dispname); #else - infof(data, "Re-using existing connection #%ld with host %s", - conn->connection_id, conn->host.dispname); + infof(data, "Re-using existing %s: connection%s with host %s", + conn->given->scheme, + tls_upgraded ? " (upgraded to SSL)" : "", + conn->host.dispname); #endif } else { @@ -3701,55 +3736,35 @@ static CURLcode create_conn(struct Curl_easy *data, conn->bits.tls_enable_alpn = TRUE; } - if(waitpipe) + if(waitpipe) { /* 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."); connections_available = FALSE; + } else { - /* this gets a lock on the conncache */ - struct connectbundle *bundle = - Curl_conncache_find_bundle(data, conn, data->state.conn_cache); - - if(max_host_connections > 0 && bundle && - (bundle->num_connections >= max_host_connections)) { - struct connectdata *conn_candidate; - - /* The bundle is full. Extract the oldest connection. */ - conn_candidate = Curl_conncache_extract_bundle(data, bundle); - CONNCACHE_UNLOCK(data); - - if(conn_candidate) - Curl_disconnect(data, conn_candidate, FALSE); + switch(Curl_cpool_check_limits(data, conn)) { + case CPOOL_LIMIT_DEST: + infof(data, "No more connections allowed to host"); + connections_available = FALSE; + break; + case CPOOL_LIMIT_TOTAL: + if(data->master_mid != UINT_MAX) + CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override " + "max connection limit"); else { - infof(data, "No more connections allowed to host: %zu", - max_host_connections); + infof(data, "No connections available, total of %ld reached.", + data->multi->max_total_connections); connections_available = FALSE; } - } - else - CONNCACHE_UNLOCK(data); - - } - - if(connections_available && - (max_total_connections > 0) && - (Curl_conncache_size(data) >= max_total_connections)) { - struct connectdata *conn_candidate; - - /* The cache is full. Let's see if we can kill a connection. */ - conn_candidate = Curl_conncache_extract_oldest(data); - if(conn_candidate) - Curl_disconnect(data, conn_candidate, FALSE); - else { - infof(data, "No connections available in cache"); - connections_available = FALSE; + break; + default: + break; } } if(!connections_available) { - infof(data, "No connections available."); - - conn_free(data, conn); + Curl_conn_free(data, conn); *in_connect = NULL; result = CURLE_NO_CONNECTION_AVAILABLE; @@ -3760,24 +3775,30 @@ static CURLcode create_conn(struct Curl_easy *data, * This is a brand new connection, so let's store it in the connection * cache of ours! */ + result = Curl_ssl_conn_config_init(data, conn); + if(result) { + DEBUGF(fprintf(stderr, "Error: init connection ssl config\n")); + goto out; + } + Curl_attach_connection(data, conn); - result = Curl_conncache_add_conn(data); + result = Curl_cpool_add(data, conn); if(result) goto out; } -#if defined(USE_NTLM) - /* If NTLM is requested in a part of this connection, make sure we don't +#ifdef USE_NTLM + /* If NTLM is requested in a part of this connection, make sure we do not assume the state is fine as this is a fresh connection and NTLM is connection based. */ - if((data->state.authhost.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authhost.picked & CURLAUTH_NTLM) && data->state.authhost.done) { infof(data, "NTLM picked AND auth done set, clear picked"); data->state.authhost.picked = CURLAUTH_NONE; data->state.authhost.done = FALSE; } - if((data->state.authproxy.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authproxy.picked & CURLAUTH_NTLM) && data->state.authproxy.done) { infof(data, "NTLM-proxy picked AND auth done set, clear picked"); data->state.authproxy.picked = CURLAUTH_NONE; @@ -3787,7 +3808,9 @@ static CURLcode create_conn(struct Curl_easy *data, } /* Setup and init stuff before DO starts, in preparing for the transfer. */ - Curl_init_do(data, conn); + result = Curl_init_do(data, conn); + if(result) + goto out; /* * Setup whatever necessary for a resumed transfer @@ -3798,23 +3821,27 @@ static CURLcode create_conn(struct Curl_easy *data, /* Continue connectdata initialization here. */ - /* - * Inherit the proper values from the urldata struct AFTER we have arranged - * the persistent connection stuff - */ - conn->seek_func = data->set.seek_func; - conn->seek_client = data->set.seek_client; + if(conn->bits.reuse) { + /* We are reusing the connection - no need to resolve anything, and + idnconvert_hostname() was called already in create_conn() for the reuse + case. */ + *reusedp = TRUE; + } - /************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ - result = resolve_server(data, conn, async); - if(result) - goto out; + /* persist the scheme and handler the transfer is using */ + data->info.conn_scheme = conn->handler->scheme; + /* conn_protocol can only provide "old" protocols */ + data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; + data->info.used_proxy = +#ifdef CURL_DISABLE_PROXY + 0 +#else + conn->bits.proxy +#endif + ; /* Everything general done, inform filters that they need - * to prepare for a data transfer. - */ + * to prepare for a data transfer. */ result = Curl_conn_ev_data_setup(data); out: @@ -3827,37 +3854,21 @@ static CURLcode create_conn(struct Curl_easy *data, * Curl_setup_conn() also handles reused connections */ CURLcode Curl_setup_conn(struct Curl_easy *data, + struct Curl_dns_entry *dns, bool *protocol_done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; + DEBUGASSERT(dns); Curl_pgrsTime(data, TIMER_NAMELOOKUP); - if(conn->handler->flags & PROTOPT_NONETWORK) { - /* nothing to setup when not using a network */ - *protocol_done = TRUE; - return result; - } - -#ifndef CURL_DISABLE_PROXY - /* set proxy_connect_closed to false unconditionally already here since it - is used strictly to provide extra information to a parent function in the - case of proxy CONNECT failures and we must make sure we don't have it - lingering set from a previous invoke */ - conn->bits.proxy_connect_closed = FALSE; -#endif - -#ifdef CURL_DO_LINEEND_CONV - data->state.crlf_conversions = 0; /* reset CRLF conversion counter */ -#endif /* CURL_DO_LINEEND_CONV */ - - /* set start time here for timeout purposes in the connect procedure, it - is later set again for the progress meter purpose */ - conn->now = Curl_now(); if(!conn->bits.reuse) - result = Curl_conn_setup(data, conn, FIRSTSOCKET, conn->dns_entry, + result = Curl_conn_setup(data, conn, FIRSTSOCKET, dns, CURL_CF_SSL_DEFAULT); + if(!result) + result = Curl_headers_init(data); + /* not sure we need this flag to be passed around any more */ *protocol_done = FALSE; return result; @@ -3869,39 +3880,56 @@ CURLcode Curl_connect(struct Curl_easy *data, { CURLcode result; struct connectdata *conn; + bool reused = FALSE; *asyncp = FALSE; /* assume synchronous resolves by default */ + *protocol_done = FALSE; - /* init the single-transfer specific data */ - Curl_free_request_state(data); - memset(&data->req, 0, sizeof(struct SingleRequest)); - data->req.size = data->req.maxdownload = -1; - data->req.no_body = data->set.opt_no_body; + /* Set the request to virgin state based on transfer settings */ + Curl_req_hard_reset(&data->req, data); /* call the stuff that needs to be called */ - result = create_conn(data, &conn, asyncp); + result = create_conn(data, &conn, &reused); + + if(result == CURLE_NO_CONNECTION_AVAILABLE) { + DEBUGASSERT(!conn); + return result; + } if(!result) { - if(CONN_INUSE(conn) > 1) - /* multiplexed */ + DEBUGASSERT(conn); + if(reused) { + if(CONN_ATTACHED(conn) > 1) + /* multiplexed */ + *protocol_done = TRUE; + } + else if(conn->handler->flags & PROTOPT_NONETWORK) { + *asyncp = FALSE; + Curl_pgrsTime(data, TIMER_NAMELOOKUP); *protocol_done = TRUE; - else if(!*asyncp) { - /* DNS resolution is done: that's either because this is a reused - connection, in which case DNS was unnecessary, or because DNS - really did finish already (synch resolver/fast async resolve) */ - result = Curl_setup_conn(data, protocol_done); + } + else { + /************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ + struct Curl_dns_entry *dns; + result = resolve_server(data, conn, asyncp, &dns); + if(!result) { + *asyncp = !dns; + if(dns) + /* DNS resolution is done: that is either because this is a reused + connection, in which case DNS was unnecessary, or because DNS + really did finish already (synch resolver/fast async resolve) */ + result = Curl_setup_conn(data, dns, protocol_done); + } } } - if(result == CURLE_NO_CONNECTION_AVAILABLE) { - return result; - } - else if(result && conn) { - /* We're not allowed to return failure with memory left allocated in the + if(result && conn) { + /* We are not allowed to return failure with memory left allocated in the connectdata struct, free those here */ Curl_detach_connection(data); - Curl_conncache_remove_conn(data, conn, TRUE); - Curl_disconnect(data, conn, TRUE); + Curl_conn_terminate(data, conn, TRUE); } return result; @@ -3919,39 +3947,31 @@ CURLcode Curl_connect(struct Curl_easy *data, CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) { - struct SingleRequest *k = &data->req; - /* if this is a pushed stream, we need this: */ - CURLcode result = Curl_preconnect(data); - if(result) - return result; + CURLcode result; if(conn) { - conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to + conn->bits.do_more = FALSE; /* by default there is no curl_do_more() to use */ - /* if the protocol used doesn't support wildcards, switch it off */ + /* if the protocol used does not support wildcards, switch it off */ if(data->state.wildcardmatch && !(conn->handler->flags & PROTOPT_WILDCARD)) data->state.wildcardmatch = FALSE; } data->state.done = FALSE; /* *_done() is not called yet */ - data->state.expect100header = FALSE; if(data->req.no_body) /* in HTTP lingo, no body means using the HEAD request... */ data->state.httpreq = HTTPREQ_HEAD; - k->start = Curl_now(); /* start time */ - k->header = TRUE; /* assume header */ - k->bytecount = 0; - k->ignorebody = FALSE; - - Curl_speedinit(data); - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - - return CURLE_OK; + result = Curl_req_start(&data->req, data); + if(!result) { + Curl_speedinit(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + } + return result; } #if defined(USE_HTTP2) || defined(USE_HTTP3) @@ -4052,3 +4072,25 @@ void Curl_data_priority_clear_state(struct Curl_easy *data) } #endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */ + + +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, + meta_data, meta_dtor)) { + meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} + +void Curl_conn_meta_remove(struct connectdata *conn, const char *key) +{ + Curl_hash_delete(&conn->meta_hash, CURL_UNCONST(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); +} diff --git a/Utilities/cmcurl/lib/url.h b/Utilities/cmcurl/lib/url.h index f6a5b257371..7aba98dbb93 100644 --- a/Utilities/cmcurl/lib/url.h +++ b/Utilities/cmcurl/lib/url.h @@ -37,28 +37,70 @@ void Curl_freeset(struct Curl_easy *data); CURLcode Curl_uc_to_curlcode(CURLUcode uc); CURLcode Curl_close(struct Curl_easy **datap); /* opposite of curl_open() */ CURLcode Curl_connect(struct Curl_easy *, bool *async, bool *protocol_connect); -void Curl_disconnect(struct Curl_easy *data, - struct connectdata *, bool dead_connection); CURLcode Curl_setup_conn(struct Curl_easy *data, + struct Curl_dns_entry *dns, bool *protocol_done); -void Curl_free_request_state(struct Curl_easy *data); +void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn); CURLcode Curl_parse_login_details(const char *login, const size_t len, char **userptr, char **passwdptr, char **optionsptr); -const struct Curl_handler *Curl_builtin_scheme(const char *scheme, - size_t schemelen); +/* 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); + +/* Set the transfer meta data for the key. Any existing entry for that + * key will be destroyed. + * Takes ownership of `meta_data` and destroys it when the call fails. */ +CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor); +void Curl_meta_remove(struct Curl_easy *data, const char *key); +void *Curl_meta_get(struct Curl_easy *data, const char *key); +void Curl_meta_reset(struct Curl_easy *data); + +/* Set connection meta data for the key. Any existing entry for that + * key will be destroyed. + * Takes ownership of `meta_data` and destroys it when the call fails. */ +CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor); +void Curl_conn_meta_remove(struct connectdata *conn, const char *key); +void *Curl_conn_meta_get(struct connectdata *conn, const char *key); + +/* Get protocol handler for a URI scheme + * @param scheme URI scheme, case-insensitive + * @return NULL of handler not found + */ +const struct Curl_handler *Curl_get_scheme_handler(const char *scheme); +const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme, + size_t len); #define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */ #define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless specified */ #ifdef CURL_DISABLE_VERBOSE_STRINGS -#define Curl_verboseconnect(x,y) Curl_nop_stmt +#define Curl_verboseconnect(x,y,z) Curl_nop_stmt #else -void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn); +void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn, + int sockindex); #endif +/** + * Return TRUE iff the given connection is considered dead. + * @param nowp NULL or pointer to time being checked against. + */ +bool Curl_conn_seems_dead(struct connectdata *conn, + struct Curl_easy *data, + struct curltime *nowp); + +/** + * Perform upkeep operations on the connection. + */ +CURLcode Curl_conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + struct curltime *now); + #if defined(USE_HTTP2) || defined(USE_HTTP3) void Curl_data_priority_clear_state(struct Curl_easy *data); #else diff --git a/Utilities/cmcurl/lib/urlapi-int.h b/Utilities/cmcurl/lib/urlapi-int.h index d6e240aa369..fbce1837ff9 100644 --- a/Utilities/cmcurl/lib/urlapi-int.h +++ b/Utilities/cmcurl/lib/urlapi-int.h @@ -28,12 +28,13 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, bool guess_scheme); -CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, - unsigned int flags); +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority); -#ifdef DEBUGBUILD -CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, - bool has_scheme); +CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace); + +#ifdef UNITTESTS +UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, + bool has_scheme); #endif #endif /* HEADER_CURL_URLAPI_INT_H */ diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c index a4530f919a8..6a7ab1a575e 100644 --- a/Utilities/cmcurl/lib/urlapi.c +++ b/Utilities/cmcurl/lib/urlapi.c @@ -30,10 +30,11 @@ #include "url.h" #include "escape.h" #include "curl_ctype.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "inet_ntop.h" #include "strdup.h" #include "idn.h" +#include "curlx/strparse.h" #include "curl_memrchr.h" /* The last 3 #include files should be in this order */ @@ -41,13 +42,13 @@ #include "curl_memory.h" #include "memdebug.h" - /* MSDOS/Windows style drive prefix, eg c: in c:foo */ + /* MS-DOS/Windows style drive prefix, eg c: in c:foo */ #define STARTS_WITH_DRIVE_PREFIX(str) \ ((('a' <= str[0] && str[0] <= 'z') || \ ('A' <= str[0] && str[0] <= 'Z')) && \ (str[1] == ':')) - /* MSDOS/Windows style drive prefix, optionally with + /* MS-DOS/Windows style drive prefix, optionally with * a '|' instead of ':', followed by a slash or NUL */ #define STARTS_WITH_URL_DRIVE_PREFIX(str) \ ((('a' <= (str)[0] && (str)[0] <= 'z') || \ @@ -59,11 +60,11 @@ #define MAX_SCHEME_LEN 40 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * 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 * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif @@ -79,11 +80,17 @@ struct Curl_URL { char *path; char *query; char *fragment; - long portnum; /* the numerical version */ + unsigned short portnum; /* the numerical version (if 'port' is set) */ + BIT(query_present); /* to support blank */ + BIT(fragment_present); /* to support blank */ + BIT(guessed_scheme); /* when a URL without scheme is parsed */ }; #define DEFAULT_SCHEME "https" +static CURLUcode parseurl_and_replace(const char *url, CURLU *u, + unsigned int flags); + static void free_urlhandle(struct Curl_URL *u) { free(u->scheme); @@ -99,43 +106,33 @@ static void free_urlhandle(struct Curl_URL *u) } /* - * Find the separator at the end of the host name, or the '?' in cases like - * http://www.url.com?id=2380 + * Find the separator at the end of the hostname, or the '?' in cases like + * http://www.example.com?id=2380 */ static const char *find_host_sep(const char *url) { - const char *sep; - const char *query; - /* Find the start of the hostname */ - sep = strstr(url, "//"); + const char *sep = strstr(url, "//"); if(!sep) sep = url; else sep += 2; - query = strchr(sep, '?'); - sep = strchr(sep, '/'); + /* Find first / or ? */ + while(*sep && *sep != '/' && *sep != '?') + sep++; - if(!sep) - sep = url + strlen(url); - - if(!query) - query = url + strlen(url); - - return sep < query ? sep : query; + return sep; } -/* - * Decide whether a character in a URL must be escaped. - */ -#define urlchar_needs_escaping(c) (!(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c))) +/* convert CURLcode to CURLUcode */ +#define cc2cu(x) ((x) == CURLE_TOO_LARGE ? CURLUE_TOO_LARGE : \ + CURLUE_OUT_OF_MEMORY) -static const char hexdigits[] = "0123456789abcdef"; /* urlencode_str() writes data into an output dynbuf and URL-encodes the * spaces in the source URL accordingly. * - * URL encoding should be skipped for host names, otherwise IDN resolution + * URL encoding should be skipped for hostnames, otherwise IDN resolution * will fail. */ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, @@ -146,47 +143,39 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, bool left = !query; const unsigned char *iptr; const unsigned char *host_sep = (const unsigned char *) url; + CURLcode result = CURLE_OK; - if(!relative) + if(!relative) { + size_t n; host_sep = (const unsigned char *) find_host_sep(url); - for(iptr = (unsigned char *)url; /* read from here */ - len; iptr++, len--) { - - if(iptr < host_sep) { - if(Curl_dyn_addn(o, iptr, 1)) - return CURLUE_OUT_OF_MEMORY; - continue; - } + /* output the first piece as-is */ + n = (const char *)host_sep - url; + result = curlx_dyn_addn(o, url, n); + len -= n; + } + for(iptr = host_sep; len && !result; iptr++, len--) { if(*iptr == ' ') { - if(left) { - if(Curl_dyn_addn(o, "%20", 3)) - return CURLUE_OUT_OF_MEMORY; - } - else { - if(Curl_dyn_addn(o, "+", 1)) - return CURLUE_OUT_OF_MEMORY; - } - continue; + if(left) + result = curlx_dyn_addn(o, "%20", 3); + else + result = curlx_dyn_addn(o, "+", 1); } - - if(*iptr == '?') - left = FALSE; - - if(urlchar_needs_escaping(*iptr)) { - char out[3]={'%'}; - out[1] = hexdigits[*iptr>>4]; - out[2] = hexdigits[*iptr & 0xf]; - if(Curl_dyn_addn(o, out, 3)) - return CURLUE_OUT_OF_MEMORY; + else if((*iptr < ' ') || (*iptr >= 0x7f)) { + unsigned char out[3]={'%'}; + Curl_hexbyte(&out[1], *iptr, TRUE); + result = curlx_dyn_addn(o, out, 3); } else { - if(Curl_dyn_addn(o, iptr, 1)) - return CURLUE_OUT_OF_MEMORY; + result = curlx_dyn_addn(o, iptr, 1); + if(*iptr == '?') + left = FALSE; } } + if(result) + return cc2cu(result); return CURLUE_OK; } @@ -201,38 +190,37 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, bool guess_scheme) { - int i; + size_t i = 0; DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN)); (void)buflen; /* only used in debug-builds */ if(buf) buf[0] = 0; /* always leave a defined value in buf */ -#ifdef WIN32 +#ifdef _WIN32 if(guess_scheme && STARTS_WITH_DRIVE_PREFIX(url)) return 0; #endif - for(i = 0; 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 / "+" / "-" / "." ) - */ - } - else { - break; + if(ISALPHA(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 / "+" / "-" / "." ) + */ + } + 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 - be the host name "data" with a specified port number. */ + be the hostname "data" with a specified port number. */ /* the length of the scheme is the name part only */ size_t len = i; if(buf) { + Curl_strntolower(buf, url, i); buf[i] = 0; - while(i--) { - buf[i] = Curl_raw_tolower(url[i]); - } } return len; } @@ -240,164 +228,95 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, } /* - * Concatenate a relative URL to a base URL making it absolute. - * URL-encodes any spaces. - * The returned pointer must be freed by the caller unless NULL - * (returns NULL on out of memory). - * - * Note that this function destroys the 'base' string. + * Concatenate a relative URL onto a base URL making it absolute. */ -static char *concat_url(char *base, const char *relurl) +static CURLUcode redirect_url(const char *base, const char *relurl, + CURLU *u, unsigned int flags) { - /*** - TRY to append this new path to the old URL - to the right of the host part. Oh crap, this is doomed to cause - problems in the future... - */ - struct dynbuf newest; - char *protsep; - char *pathsep; + struct dynbuf urlbuf; bool host_changed = FALSE; const char *useurl = relurl; - - /* protsep points to the start of the host name */ - protsep = strstr(base, "//"); - if(!protsep) - protsep = base; - else - protsep += 2; /* pass the slashes */ - - if('/' != relurl[0]) { - int level = 0; - - /* First we need to find out if there's a ?-letter in the URL, - and cut it and the right-side of that off */ - pathsep = strchr(protsep, '?'); - if(pathsep) - *pathsep = 0; - - /* we have a relative path to append to the last slash if there's one - available, or if the new URL is just a query string (starts with a - '?') we append the new one at the end of the entire currently worked - out URL */ - if(useurl[0] != '?') { - pathsep = strrchr(protsep, '/'); - if(pathsep) - *pathsep = 0; + const char *cutoff = NULL; + size_t prelen; + CURLUcode uc; + + /* protsep points to the start of the hostname, after [scheme]:// */ + const char *protsep = base + strlen(u->scheme) + 3; + DEBUGASSERT(base && relurl && u); /* all set here */ + if(!base) + return CURLUE_MALFORMED_INPUT; /* should never happen */ + + /* handle different relative URL types */ + switch(relurl[0]) { + case '/': + if(relurl[1] == '/') { + /* protocol-relative URL: //example.com/path */ + cutoff = protsep; + useurl = &relurl[2]; + host_changed = TRUE; } - - /* Check if there's any slash after the host name, and if so, remember - that position instead */ - pathsep = strchr(protsep, '/'); - if(pathsep) - protsep = pathsep + 1; else - protsep = NULL; - - /* now deal with one "./" or any amount of "../" in the newurl - and act accordingly */ - - if((useurl[0] == '.') && (useurl[1] == '/')) - useurl += 2; /* just skip the "./" */ - - while((useurl[0] == '.') && - (useurl[1] == '.') && - (useurl[2] == '/')) { - level++; - useurl += 3; /* pass the "../" */ - } + /* absolute /path */ + cutoff = strchr(protsep, '/'); + break; - if(protsep) { - while(level--) { - /* cut off one more level from the right of the original URL */ - pathsep = strrchr(protsep, '/'); - if(pathsep) - *pathsep = 0; - else { - *protsep = 0; - break; - } - } - } - } - else { - /* We got a new absolute path for this server */ + case '#': + /* fragment-only change */ + if(u->fragment) + cutoff = strchr(protsep, '#'); + break; - if(relurl[1] == '/') { - /* the new URL starts with //, just keep the protocol part from the - original one */ - *protsep = 0; - useurl = &relurl[2]; /* we keep the slashes from the original, so we - skip the new ones */ - host_changed = TRUE; - } - else { - /* cut off the original URL from the first slash, or deal with URLs - without slash */ - pathsep = strchr(protsep, '/'); - if(pathsep) { - /* When people use badly formatted URLs, such as - "http://www.url.com?dir=/home/daniel" we must not use the first - slash, if there's a ?-letter before it! */ - char *sep = strchr(protsep, '?'); - if(sep && (sep < pathsep)) - pathsep = sep; - *pathsep = 0; - } - else { - /* There was no slash. Now, since we might be operating on a badly - formatted URL, such as "http://www.url.com?id=2380" which doesn't - use a slash separator as it is supposed to, we need to check for a - ?-letter as well! */ - pathsep = strchr(protsep, '?'); - if(pathsep) - *pathsep = 0; - } + default: + /* path or query-only change */ + if(u->query && u->query[0]) + /* remove existing query */ + cutoff = strchr(protsep, '?'); + else if(u->fragment && u->fragment[0]) + /* Remove existing fragment */ + cutoff = strchr(protsep, '#'); + + if(relurl[0] != '?') { + /* append a relative path after the last slash */ + cutoff = memrchr(protsep, '/', + cutoff ? (size_t)(cutoff - protsep) : strlen(protsep)); + if(cutoff) + cutoff++; /* truncate after last slash */ } + break; } - Curl_dyn_init(&newest, CURL_MAX_INPUT_LENGTH); + prelen = cutoff ? (size_t)(cutoff - base) : strlen(base); - /* copy over the root url part */ - if(Curl_dyn_add(&newest, base)) - return NULL; + /* build new URL */ + curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH); - /* check if we need to append a slash */ - if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) - ; - else { - if(Curl_dyn_addn(&newest, "/", 1)) - return NULL; + if(!curlx_dyn_addn(&urlbuf, base, prelen) && + !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed, FALSE)) { + uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u, + flags & ~CURLU_PATH_AS_IS); } + else + uc = CURLUE_OUT_OF_MEMORY; - /* then append the new piece on the right side */ - urlencode_str(&newest, useurl, strlen(useurl), !host_changed, FALSE); - - return Curl_dyn_ptr(&newest); + curlx_dyn_free(&urlbuf); + return uc; } /* scan for byte values <= 31, 127 and sometimes space */ -static CURLUcode junkscan(const char *url, size_t *urllen, unsigned int flags) +CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace) { - static const char badbytes[]={ - /* */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0x7f, 0x00 /* null-terminate */ - }; size_t n = strlen(url); - size_t nfine; - + size_t i; + unsigned char control; + const unsigned char *p = (const unsigned char *)url; if(n > CURL_MAX_INPUT_LENGTH) - /* excessive input length */ - return CURLUE_MALFORMED_INPUT; - - nfine = strcspn(url, badbytes); - if((nfine != n) || - (!(flags & CURLU_ALLOW_SPACE) && strchr(url, ' '))) return CURLUE_MALFORMED_INPUT; + control = allowspace ? 0x1f : 0x20; + for(i = 0; i < n; i++) { + if(p[i] <= control || p[i] == 127) + return CURLUE_MALFORMED_INPUT; + } *urllen = n; return CURLUE_OK; } @@ -405,15 +324,15 @@ static CURLUcode junkscan(const char *url, size_t *urllen, unsigned int flags) /* * parse_hostname_login() * - * Parse the login details (user name, password and options) from the URL and - * strip them out of the host name + * Parse the login details (username, password and options) from the URL and + * strip them out of the hostname * */ static CURLUcode parse_hostname_login(struct Curl_URL *u, const char *login, size_t len, unsigned int flags, - size_t *offset) /* to the host name */ + size_t *offset) /* to the hostname */ { CURLUcode result = CURLUE_OK; CURLcode ccode; @@ -440,19 +359,19 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, /* We will now try to extract the * possible login information in a string like: - * ftp://user:password@ftp.my.site:8021/README */ + * ftp://user:password@ftp.site.example:8021/README */ ptr++; /* if this is a known scheme, get some details */ if(u->scheme) - h = Curl_builtin_scheme(u->scheme, CURL_ZERO_TERMINATED); + h = Curl_get_scheme_handler(u->scheme); /* We could use the login information in the URL so extract it. Only parse options if the handler says we should. Note that 'h' might be NULL! */ ccode = Curl_parse_login_details(login, ptr - login - 1, &userp, &passwdp, (h && (h->flags & PROTOPT_URLOPTIONS)) ? - &optionsp:NULL); + &optionsp : NULL); if(ccode) { result = CURLUE_BAD_LOGIN; goto out; @@ -460,7 +379,7 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, if(userp) { if(flags & CURLU_DISALLOW_USER) { - /* Option DISALLOW_USER is set and url contains username. */ + /* Option DISALLOW_USER is set and URL contains username. */ result = CURLUE_USER_NOT_ALLOWED; goto out; } @@ -478,7 +397,7 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, u->options = optionsp; } - /* the host name starts at this offset */ + /* the hostname starts at this offset */ *offset = ptr - login; return CURLUE_OK; @@ -497,8 +416,8 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, bool has_scheme) { - char *portptr; - char *hostname = Curl_dyn_ptr(host); + const char *portptr; + char *hostname = curlx_dyn_ptr(host); /* * Find the end of an IPv6 address on the ']' ending bracket. */ @@ -519,37 +438,28 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, portptr = strchr(hostname, ':'); if(portptr) { - char *rest; - long port; + curl_off_t port; size_t keep = portptr - hostname; - /* Browser behavior adaptation. If there's a colon with no digits after, + /* Browser behavior adaptation. If there is a colon with no digits after, just cut off the name there which makes us ignore the colon and just use the default port. Firefox, Chrome and Safari all do that. - Don't do it if the URL has no scheme, to make something that looks like + Do not do it if the URL has no scheme, to make something that looks like a scheme not work! */ - Curl_dyn_setlen(host, keep); + curlx_dyn_setlen(host, keep); portptr++; if(!*portptr) return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER; - if(!ISDIGIT(*portptr)) + if(curlx_str_number(&portptr, &port, 0xffff) || *portptr) return CURLUE_BAD_PORT_NUMBER; - port = strtol(portptr, &rest, 10); /* Port number must be decimal */ - - if(port > 0xffff) - return CURLUE_BAD_PORT_NUMBER; - - if(rest[0]) - return CURLUE_BAD_PORT_NUMBER; - - u->portnum = port; + u->portnum = (unsigned short) port; /* generate a new port number string to get rid of leading zeroes etc */ free(u->port); - u->port = aprintf("%ld", port); + u->port = aprintf("%" CURL_FORMAT_CURL_OFF_T, port); if(!u->port) return CURLUE_OUT_OF_MEMORY; } @@ -578,7 +488,7 @@ static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, char zoneid[16]; int i = 0; char *h = &hostname[len + 1]; - /* pass '25' if present and is a url encoded percent sign */ + /* pass '25' if present and is a URL encoded percent sign */ if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']')) h += 2; while(*h && (*h != ']') && (i < 15)) @@ -597,19 +507,14 @@ static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, /* hostname is fine */ } - /* Check the IPv6 address. */ + /* Normalize the IPv6 address */ { char dest[16]; /* fits a binary IPv6 address */ - char norm[MAX_IPADR_LEN]; hostname[hlen] = 0; /* end the address there */ - if(1 != Curl_inet_pton(AF_INET6, hostname, dest)) + if(1 != curlx_inet_pton(AF_INET6, hostname, dest)) return CURLUE_BAD_IPV6; - - /* check if it can be done shorter */ - if(Curl_inet_ntop(AF_INET6, dest, norm, sizeof(norm)) && - (strlen(norm) < hlen)) { - strcpy(hostname, norm); - hlen = strlen(norm); + if(Curl_inet_ntop(AF_INET6, dest, hostname, hlen)) { + hlen = strlen(hostname); /* might be shorter now */ hostname[hlen + 1] = 0; } hostname[hlen] = ']'; /* restore ending bracket */ @@ -651,7 +556,6 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, */ #define HOST_ERROR -1 /* out of memory */ -#define HOST_BAD -2 /* bad IPv4 address */ #define HOST_NAME 1 #define HOST_IPV4 2 @@ -661,23 +565,31 @@ static int ipv4_normalize(struct dynbuf *host) { bool done = FALSE; int n = 0; - const char *c = Curl_dyn_ptr(host); - unsigned long parts[4] = {0, 0, 0, 0}; + const char *c = curlx_dyn_ptr(host); + unsigned int parts[4] = {0, 0, 0, 0}; CURLcode result = CURLE_OK; if(*c == '[') return HOST_IPV6; while(!done) { - char *endp; - unsigned long l; - if(!ISDIGIT(*c)) - /* most importantly this doesn't allow a leading plus or minus */ + int rc; + curl_off_t l; + if(*c == '0') { + if(c[1] == 'x') { + c += 2; /* skip the prefix */ + rc = curlx_str_hex(&c, &l, UINT_MAX); + } + else + rc = curlx_str_octal(&c, &l, UINT_MAX); + } + else + rc = curlx_str_number(&c, &l, UINT_MAX); + + if(rc) return HOST_NAME; - l = strtoul(c, &endp, 0); - parts[n] = l; - c = endp; + parts[n] = (unsigned int)l; switch(*c) { case '.': @@ -694,49 +606,48 @@ static int ipv4_normalize(struct dynbuf *host) default: return HOST_NAME; } - - /* overflow */ - if((l == ULONG_MAX) && (errno == ERANGE)) - return HOST_NAME; - -#if SIZEOF_LONG > 4 - /* a value larger than 32 bits */ - if(l > UINT_MAX) - return HOST_NAME; -#endif } switch(n) { case 0: /* a -- 32 bits */ - Curl_dyn_reset(host); + curlx_dyn_reset(host); - result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0] >> 24, (parts[0] >> 16) & 0xff, - (parts[0] >> 8) & 0xff, parts[0] & 0xff); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0] >> 24), + ((parts[0] >> 16) & 0xff), + ((parts[0] >> 8) & 0xff), + (parts[0] & 0xff)); break; case 1: /* a.b -- 8.24 bits */ if((parts[0] > 0xff) || (parts[1] > 0xffffff)) return HOST_NAME; - Curl_dyn_reset(host); - result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], (parts[1] >> 16) & 0xff, - (parts[1] >> 8) & 0xff, parts[1] & 0xff); + curlx_dyn_reset(host); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0]), + ((parts[1] >> 16) & 0xff), + ((parts[1] >> 8) & 0xff), + (parts[1] & 0xff)); break; case 2: /* a.b.c -- 8.8.16 bits */ if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff)) return HOST_NAME; - Curl_dyn_reset(host); - result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], parts[1], (parts[2] >> 8) & 0xff, - parts[2] & 0xff); + curlx_dyn_reset(host); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0]), + (parts[1]), + ((parts[2] >> 8) & 0xff), + (parts[2] & 0xff)); break; case 3: /* a.b.c.d -- 8.8.8.8 bits */ if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) || (parts[3] > 0xff)) return HOST_NAME; - Curl_dyn_reset(host); - result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], parts[1], parts[2], parts[3]); + curlx_dyn_reset(host); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0]), + (parts[1]), + (parts[2]), + (parts[3])); break; } if(result) @@ -748,7 +659,7 @@ static int ipv4_normalize(struct dynbuf *host) static CURLUcode urldecode_host(struct dynbuf *host) { char *per = NULL; - const char *hostname = Curl_dyn_ptr(host); + const char *hostname = curlx_dyn_ptr(host); per = strchr(hostname, '%'); if(!per) /* nothing to decode */ @@ -761,11 +672,11 @@ static CURLUcode urldecode_host(struct dynbuf *host) REJECT_CTRL); if(result) return CURLUE_BAD_HOSTNAME; - Curl_dyn_reset(host); - result = Curl_dyn_addn(host, decoded, dlen); + curlx_dyn_reset(host); + result = curlx_dyn_addn(host, decoded, dlen); free(decoded); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } return CURLUE_OK; @@ -778,67 +689,68 @@ static CURLUcode parse_authority(struct Curl_URL *u, bool has_scheme) { size_t offset; - CURLUcode result; + CURLUcode uc; + CURLcode result; /* - * Parse the login details and strip them out of the host name. + * Parse the login details and strip them out of the hostname. */ - result = parse_hostname_login(u, auth, authlen, flags, &offset); - if(result) + uc = parse_hostname_login(u, auth, authlen, flags, &offset); + if(uc) goto out; - if(Curl_dyn_addn(host, auth + offset, authlen - offset)) { - result = CURLUE_OUT_OF_MEMORY; + result = curlx_dyn_addn(host, auth + offset, authlen - offset); + if(result) { + uc = cc2cu(result); goto out; } - result = Curl_parse_port(u, host, has_scheme); - if(result) + uc = Curl_parse_port(u, host, has_scheme); + if(uc) goto out; - if(!Curl_dyn_len(host)) + if(!curlx_dyn_len(host)) return CURLUE_NO_HOST; switch(ipv4_normalize(host)) { case HOST_IPV4: break; case HOST_IPV6: - result = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_NAME: - result = urldecode_host(host); - if(!result) - result = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = urldecode_host(host); + if(!uc) + uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_ERROR: - result = CURLUE_OUT_OF_MEMORY; + uc = CURLUE_OUT_OF_MEMORY; break; - case HOST_BAD: default: - result = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ + uc = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ break; } out: - return result; + return uc; } -CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, - unsigned int flags) +/* used for HTTP/2 server push */ +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) { CURLUcode result; struct dynbuf host; DEBUGASSERT(authority); - Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); - result = parse_authority(u, authority, strlen(authority), flags, - &host, !!u->scheme); + result = parse_authority(u, authority, strlen(authority), + CURLU_DISALLOW_USER, &host, !!u->scheme); if(result) - Curl_dyn_free(&host); + curlx_dyn_free(&host); else { free(u->host); - u->host = Curl_dyn_ptr(&host); + u->host = curlx_dyn_ptr(&host); } return result; } @@ -848,6 +760,25 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, * https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4 */ +static bool is_dot(const char **str, size_t *clen) +{ + const char *p = *str; + if(*p == '.') { + (*str)++; + (*clen)--; + return TRUE; + } + else if((*clen >= 3) && + (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) { + *str += 3; + *clen -= 3; + return TRUE; + } + return FALSE; +} + +#define ISSLASH(x) ((x) == '/') + /* * dedotdotify() * @unittest: 1395 @@ -856,8 +787,7 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, * passed in and strips them off according to the rules in RFC 3986 section * 5.2.4. * - * The function handles a query part ('?' + stuff) appended but it expects - * that fragments ('#' + stuff) have already been cut off. + * The function handles a path. It should not contain the query nor fragment. * * RETURNS * @@ -866,112 +796,109 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, UNITTEST int dedotdotify(const char *input, size_t clen, char **outp); UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) { - char *outptr; - const char *endp = &input[clen]; - char *out; + struct dynbuf out; + CURLcode result = CURLE_OK; *outp = NULL; /* the path always starts with a slash, and a slash has not dot */ - if((clen < 2) || !memchr(input, '.', clen)) + if(clen < 2) return 0; - out = malloc(clen + 1); - if(!out) - return 1; /* out of memory */ - - *out = 0; /* null-terminates, for inputs like "./" */ - outptr = out; - - do { - bool dotdot = TRUE; - if(*input == '.') { - /* A. If the input buffer begins with a prefix of "../" or "./", then - remove that prefix from the input buffer; otherwise, */ - - if(!strncmp("./", input, 2)) { - input += 2; - clen -= 2; - } - else if(!strncmp("../", input, 3)) { - input += 3; - clen -= 3; - } - /* D. if the input buffer consists only of "." or "..", then remove - that from the input buffer; otherwise, */ + curlx_dyn_init(&out, clen + 1); + + /* A. If the input buffer begins with a prefix of "../" or "./", then + remove that prefix from the input buffer; otherwise, */ + if(is_dot(&input, &clen)) { + const char *p = input; + size_t blen = clen; + + if(!clen) + /* . [end] */ + goto end; + else if(ISSLASH(*p)) { + /* one dot followed by a slash */ + input = p + 1; + clen--; + } - else if(!strcmp(".", input) || !strcmp("..", input) || - !strncmp(".?", input, 2) || !strncmp("..?", input, 3)) { - *out = 0; - break; + /* D. if the input buffer consists only of "." or "..", then remove + that from the input buffer; otherwise, */ + else if(is_dot(&p, &blen)) { + if(!blen) + /* .. [end] */ + goto end; + else if(ISSLASH(*p)) { + /* ../ */ + input = p + 1; + clen = blen - 1; } - else - dotdot = FALSE; } - else if(*input == '/') { - /* B. if the input buffer begins with a prefix of "/./" or "/.", where + } + + while(clen && !result) { /* until end of path content */ + if(ISSLASH(*input)) { + const char *p = &input[1]; + size_t blen = clen - 1; + /* B. if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, */ - if(!strncmp("/./", input, 3)) { - input += 2; - clen -= 2; - } - else if(!strcmp("/.", input) || !strncmp("/.?", input, 3)) { - *outptr++ = '/'; - *outptr = 0; - break; - } - - /* C. if the input buffer begins with a prefix of "/../" or "/..", - where ".." is a complete path segment, then replace that prefix with - "/" in the input buffer and remove the last segment and its - preceding "/" (if any) from the output buffer; otherwise, */ - - else if(!strncmp("/../", input, 4)) { - input += 3; - clen -= 3; - /* remove the last segment from the output buffer */ - while(outptr > out) { - outptr--; - if(*outptr == '/') - break; + if(is_dot(&p, &blen)) { + if(!blen) { /* /. */ + result = curlx_dyn_addn(&out, "/", 1); + break; } - *outptr = 0; /* null-terminate where it stops */ - } - else if(!strcmp("/..", input) || !strncmp("/..?", input, 4)) { - /* remove the last segment from the output buffer */ - while(outptr > out) { - outptr--; - if(*outptr == '/') - break; + else if(ISSLASH(*p)) { /* /./ */ + input = p; + clen = blen; + continue; + } + + /* C. if the input buffer begins with a prefix of "/../" or "/..", + where ".." is a complete path segment, then replace that prefix + with "/" in the input buffer and remove the last segment and its + preceding "/" (if any) from the output buffer; otherwise, */ + else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) { + /* remove the last segment from the output buffer */ + size_t len = curlx_dyn_len(&out); + if(len) { + char *ptr = curlx_dyn_ptr(&out); + char *last = memrchr(ptr, '/', len); + if(last) + /* trim the output at the slash */ + curlx_dyn_setlen(&out, last - ptr); + } + + if(blen) { /* /../ */ + input = p; + clen = blen; + continue; + } + result = curlx_dyn_addn(&out, "/", 1); + break; } - *outptr++ = '/'; - *outptr = 0; /* null-terminate where it stops */ - break; } - else - dotdot = FALSE; - } - else - dotdot = FALSE; - - if(!dotdot) { - /* E. move the first path segment in the input buffer to the end of - the output buffer, including the initial "/" character (if any) and - any subsequent characters up to, but not including, the next "/" - character or the end of the input buffer. */ - - do { - *outptr++ = *input++; - clen--; - } while(*input && (*input != '/') && (*input != '?')); - *outptr = 0; } - /* continue until end of path */ - } while(input < endp); + /* E. move the first path segment in the input buffer to the end of + the output buffer, including the initial "/" character (if any) and + any subsequent characters up to, but not including, the next "/" + character or the end of the input buffer. */ - *outp = out; - return 0; /* success */ + result = curlx_dyn_addn(&out, input, 1); + input++; + clen--; + } +end: + if(!result) { + if(curlx_dyn_len(&out)) + *outp = curlx_dyn_ptr(&out); + else { + *outp = strdup(""); + if(!*outp) + return 1; + } + } + return result ? 1 : 0; /* success */ } static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) @@ -989,9 +916,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) DEBUGASSERT(url); - Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); - result = junkscan(url, &urllen, flags); + result = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE)); if(result) goto fail; @@ -1009,7 +936,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } /* path has been allocated large enough to hold this */ - path = (char *)&url[5]; + path = &url[5]; pathlen = urllen - 5; u->scheme = strdup("file"); @@ -1048,19 +975,19 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) * Appendix E, but believe me, it was meant to be there. --MK) */ if(ptr[0] != '/' && !STARTS_WITH_URL_DRIVE_PREFIX(ptr)) { - /* the URL includes a host name, it must match "localhost" or + /* the URL includes a hostname, it must match "localhost" or "127.0.0.1" to be valid */ if(checkprefix("localhost/", ptr) || checkprefix("127.0.0.1/", ptr)) { ptr += 9; /* now points to the slash after the host */ } else { -#if defined(WIN32) +#ifdef _WIN32 size_t len; - /* the host name, NetBIOS computer name, can not contain disallowed + /* the hostname, NetBIOS computer name, can not contain disallowed chars, and the delimiting slash character must be appended to the - host name */ + hostname */ path = strpbrk(ptr, "/\\:*?\"<>|"); if(!path || *path != '/') { result = CURLUE_BAD_FILE_URL; @@ -1069,8 +996,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) len = path - ptr; if(len) { - if(Curl_dyn_addn(&host, ptr, len)) { - result = CURLUE_OUT_OF_MEMORY; + CURLcode code = curlx_dyn_addn(&host, ptr, len); + if(code) { + result = cc2cu(code); goto fail; } uncpath = TRUE; @@ -1092,14 +1020,14 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(!uncpath) /* no host for file: URLs by default */ - Curl_dyn_reset(&host); + curlx_dyn_reset(&host); -#if !defined(MSDOS) && !defined(WIN32) && !defined(__CYGWIN__) - /* Don't allow Windows drive letters when not in Windows. +#if !defined(_WIN32) && !defined(MSDOS) && !defined(__CYGWIN__) + /* Do not allow Windows drive letters when not in Windows. * This catches both "file:/c:" and "file:c:" */ if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) || STARTS_WITH_URL_DRIVE_PREFIX(path)) { - /* File drive letters are only accepted in MSDOS/Windows */ + /* File drive letters are only accepted in MS-DOS/Windows */ result = CURLUE_BAD_FILE_URL; goto fail; } @@ -1108,6 +1036,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) { /* This cannot be done with strcpy, as the memory chunks overlap! */ path++; + pathlen--; } #endif @@ -1127,7 +1056,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } schemep = schemebuf; - if(!Curl_builtin_scheme(schemep, CURL_ZERO_TERMINATED) && + if(!Curl_get_scheme_handler(schemep) && !(flags & CURLU_NON_SUPPORT_SCHEME)) { result = CURLUE_UNSUPPORTED_SCHEME; goto fail; @@ -1138,7 +1067,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) result = CURLUE_BAD_SLASHES; goto fail; } - hostp = p; /* host name starts here */ + hostp = p; /* hostname starts here */ } else { /* no scheme! */ @@ -1164,7 +1093,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } } - /* find the end of the host name + port number */ + /* find the end of the hostname + port number */ hostlen = strcspn(hostp, "/?#"); path = &hostp[hostlen]; @@ -1177,8 +1106,8 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) goto fail; if((flags & CURLU_GUESS_SCHEME) && !schemep) { - const char *hostname = Curl_dyn_ptr(&host); - /* legacy curl-style guess based on host name */ + const char *hostname = curlx_dyn_ptr(&host); + /* legacy curl-style guess based on hostname */ if(checkprefix("ftp.", hostname)) schemep = "ftp"; else if(checkprefix("dict.", hostname)) @@ -1199,11 +1128,12 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) result = CURLUE_OUT_OF_MEMORY; goto fail; } + u->guessed_scheme = TRUE; } } else if(flags & CURLU_NO_AUTHORITY) { /* allowed to be empty. */ - if(Curl_dyn_add(&host, "")) { + if(curlx_dyn_add(&host, "")) { result = CURLUE_OUT_OF_MEMORY; goto fail; } @@ -1217,19 +1147,19 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) fragment = strchr(path, '#'); if(fragment) { fraglen = pathlen - (fragment - path); + u->fragment_present = TRUE; if(fraglen > 1) { /* skip the leading '#' in the copy but include the terminating null */ if(flags & CURLU_URLENCODE) { struct dynbuf enc; - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, fragment + 1, fraglen, TRUE, FALSE)) { - result = CURLUE_OUT_OF_MEMORY; + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + result = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE); + if(result) goto fail; - } - u->fragment = Curl_dyn_ptr(&enc); + u->fragment = curlx_dyn_ptr(&enc); } else { - u->fragment = Curl_memdup(fragment + 1, fraglen); + u->fragment = Curl_memdup0(fragment + 1, fraglen - 1); if(!u->fragment) { result = CURLUE_OUT_OF_MEMORY; goto fail; @@ -1240,30 +1170,28 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) pathlen -= fraglen; } - DEBUGASSERT(pathlen < urllen); query = memchr(path, '?', pathlen); if(query) { size_t qlen = fragment ? (size_t)(fragment - query) : pathlen - (query - path); pathlen -= qlen; + u->query_present = TRUE; if(qlen > 1) { if(flags & CURLU_URLENCODE) { struct dynbuf enc; - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); /* skip the leading question mark */ - if(urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE)) { - result = CURLUE_OUT_OF_MEMORY; + result = urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE); + if(result) goto fail; - } - u->query = Curl_dyn_ptr(&enc); + u->query = curlx_dyn_ptr(&enc); } else { - u->query = Curl_memdup(query + 1, qlen); + u->query = Curl_memdup0(query + 1, qlen - 1); if(!u->query) { result = CURLUE_OUT_OF_MEMORY; goto fail; } - u->query[qlen - 1] = 0; } } else { @@ -1278,13 +1206,12 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(pathlen && (flags & CURLU_URLENCODE)) { struct dynbuf enc; - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, path, pathlen, TRUE, FALSE)) { - result = CURLUE_OUT_OF_MEMORY; + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + result = urlencode_str(&enc, path, pathlen, TRUE, FALSE); + if(result) goto fail; - } - pathlen = Curl_dyn_len(&enc); - path = u->path = Curl_dyn_ptr(&enc); + pathlen = curlx_dyn_len(&enc); + path = u->path = curlx_dyn_ptr(&enc); } if(pathlen <= 1) { @@ -1293,12 +1220,11 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } else { if(!u->path) { - u->path = Curl_memdup(path, pathlen + 1); + u->path = Curl_memdup0(path, pathlen); if(!u->path) { result = CURLUE_OUT_OF_MEMORY; goto fail; } - u->path[pathlen] = 0; path = u->path; } else if(flags & CURLU_URLENCODE) @@ -1308,7 +1234,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(!(flags & CURLU_PATH_AS_IS)) { /* remove ../ and ./ sequences according to RFC3986 */ char *dedot; - int err = dedotdotify((char *)path, pathlen, &dedot); + int err = dedotdotify(path, pathlen, &dedot); if(err) { result = CURLUE_OUT_OF_MEMORY; goto fail; @@ -1320,11 +1246,11 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } } - u->host = Curl_dyn_ptr(&host); + u->host = curlx_dyn_ptr(&host); return result; fail: - Curl_dyn_free(&host); + curlx_dyn_free(&host); free_urlhandle(u); return result; } @@ -1350,7 +1276,7 @@ static CURLUcode parseurl_and_replace(const char *url, CURLU *u, */ CURLU *curl_url(void) { - return calloc(sizeof(struct Curl_URL), 1); + return calloc(1, sizeof(struct Curl_URL)); } void curl_url_cleanup(CURLU *u) @@ -1372,7 +1298,7 @@ void curl_url_cleanup(CURLU *u) CURLU *curl_url_dup(const CURLU *in) { - struct Curl_URL *u = calloc(sizeof(struct Curl_URL), 1); + struct Curl_URL *u = calloc(1, sizeof(struct Curl_URL)); if(u) { DUP(u, in, scheme); DUP(u, in, user); @@ -1383,7 +1309,10 @@ CURLU *curl_url_dup(const CURLU *in) DUP(u, in, path); DUP(u, in, query); DUP(u, in, fragment); + DUP(u, in, zoneid); u->portnum = in->portnum; + u->fragment_present = in->fragment_present; + u->query_present = in->query_present; } return u; fail: @@ -1397,9 +1326,10 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, const char *ptr; CURLUcode ifmissing = CURLUE_UNKNOWN_PART; char portbuf[7]; - bool urldecode = (flags & CURLU_URLDECODE)?1:0; - bool urlencode = (flags & CURLU_URLENCODE)?1:0; + bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0; + bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0; bool punycode = FALSE; + bool depunyfy = FALSE; bool plusdecode = FALSE; (void)flags; if(!u) @@ -1413,6 +1343,8 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ptr = u->scheme; ifmissing = CURLUE_NO_SCHEME; urldecode = FALSE; /* never for schemes */ + if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme) + return CURLUE_NO_SCHEME; break; case CURLUPART_USER: ptr = u->user; @@ -1429,7 +1361,8 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, case CURLUPART_HOST: ptr = u->host; ifmissing = CURLUE_NO_HOST; - punycode = (flags & CURLU_PUNYCODE)?1:0; + punycode = (flags & CURLU_PUNYCODE) ? 1 : 0; + depunyfy = (flags & CURLU_PUNY2IDN) ? 1 : 0; break; case CURLUPART_ZONEID: ptr = u->zoneid; @@ -1440,10 +1373,9 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ifmissing = CURLUE_NO_PORT; urldecode = FALSE; /* never for port */ if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { - /* there's no stored port number, but asked to deliver + /* there is no stored port number, but asked to deliver a default one for the scheme */ - const struct Curl_handler *h = - Curl_builtin_scheme(u->scheme, CURL_ZERO_TERMINATED); + const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); if(h) { msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); ptr = portbuf; @@ -1452,8 +1384,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, else if(ptr && u->scheme) { /* there is a stored port number, but ask to inhibit if it matches the default one for the scheme */ - const struct Curl_handler *h = - Curl_builtin_scheme(u->scheme, CURL_ZERO_TERMINATED); + const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); if(h && (h->defport == u->portnum) && (flags & CURLU_NO_DEFAULT_PORT)) ptr = NULL; @@ -1468,38 +1399,53 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ptr = u->query; ifmissing = CURLUE_NO_QUERY; plusdecode = urldecode; + if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) + /* there was a blank query and the user do not ask for it */ + ptr = NULL; break; case CURLUPART_FRAGMENT: ptr = u->fragment; ifmissing = CURLUE_NO_FRAGMENT; + if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY) + /* there was a blank fragment and the user asks for it */ + ptr = ""; break; case CURLUPART_URL: { char *url; - char *scheme; + const char *scheme; char *options = u->options; char *port = u->port; char *allochost = NULL; - punycode = (flags & CURLU_PUNYCODE)?1:0; + bool show_fragment = + u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY); + bool show_query = + (u->query && u->query[0]) || + (u->query_present && flags & CURLU_GET_EMPTY); + punycode = (flags & CURLU_PUNYCODE) ? 1 : 0; + depunyfy = (flags & CURLU_PUNY2IDN) ? 1 : 0; if(u->scheme && strcasecompare("file", u->scheme)) { - url = aprintf("file://%s%s%s", + url = aprintf("file://%s%s%s%s%s", u->path, - u->fragment? "#": "", - u->fragment? u->fragment : ""); + show_query ? "?": "", + u->query ? u->query : "", + show_fragment ? "#": "", + u->fragment ? u->fragment : ""); } else if(!u->host) return CURLUE_NO_HOST; else { const struct Curl_handler *h = NULL; + char schemebuf[MAX_SCHEME_LEN + 5]; if(u->scheme) scheme = u->scheme; else if(flags & CURLU_DEFAULT_SCHEME) - scheme = (char *) DEFAULT_SCHEME; + scheme = DEFAULT_SCHEME; else return CURLUE_NO_SCHEME; - h = Curl_builtin_scheme(scheme, CURL_ZERO_TERMINATED); + h = Curl_get_scheme_handler(scheme); if(!port && (flags & CURLU_DEFAULT_PORT)) { - /* there's no stored port number, but asked to deliver + /* there is no stored port number, but asked to deliver a default one for the scheme */ if(h) { msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); @@ -1522,11 +1468,11 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, /* make it '[ host %25 zoneid ]' */ struct dynbuf enc; size_t hostlen = strlen(u->host); - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(Curl_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host, - u->zoneid)) + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host, + u->zoneid)) return CURLUE_OUT_OF_MEMORY; - allochost = Curl_dyn_ptr(&enc); + allochost = curlx_dyn_ptr(&enc); } } else if(urlencode) { @@ -1539,15 +1485,34 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, #ifndef USE_IDN return CURLUE_LACKS_IDN; #else - allochost = Curl_idn_decode(u->host); - if(!allochost) - return CURLUE_OUT_OF_MEMORY; + CURLcode result = Curl_idn_decode(u->host, &allochost); + if(result) + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; +#endif + } + } + else if(depunyfy) { + if(Curl_is_ASCII_name(u->host)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + CURLcode result = Curl_idn_encode(u->host, &allochost); + if(result) + /* this is the most likely error */ + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; #endif } } - url = aprintf("%s://%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", - scheme, + if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme) + msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme); + else + schemebuf[0] = 0; + + url = aprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + schemebuf, u->user ? u->user : "", u->password ? ":": "", u->password ? u->password : "", @@ -1557,12 +1522,11 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, allochost ? allochost : u->host, port ? ":": "", port ? port : "", - (u->path && (u->path[0] != '/')) ? "/": "", u->path ? u->path : "/", - (u->query && u->query[0]) ? "?": "", - (u->query && u->query[0]) ? u->query : "", - u->fragment? "#": "", - u->fragment? u->fragment : ""); + show_query ? "?": "", + u->query ? u->query : "", + show_fragment ? "#": "", + u->fragment ? u->fragment : ""); free(allochost); } if(!url) @@ -1577,7 +1541,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, if(ptr) { size_t partlen = strlen(ptr); size_t i = 0; - *part = Curl_memdup(ptr, partlen + 1); + *part = Curl_memdup0(ptr, partlen); if(!*part) return CURLUE_OUT_OF_MEMORY; if(plusdecode) { @@ -1604,21 +1568,39 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, } if(urlencode) { struct dynbuf enc; - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, *part, partlen, TRUE, - what == CURLUPART_QUERY)) - return CURLUE_OUT_OF_MEMORY; + CURLUcode uc; + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + uc = urlencode_str(&enc, *part, partlen, TRUE, what == CURLUPART_QUERY); + if(uc) + return uc; free(*part); - *part = Curl_dyn_ptr(&enc); + *part = curlx_dyn_ptr(&enc); } else if(punycode) { if(!Curl_is_ASCII_name(u->host)) { #ifndef USE_IDN return CURLUE_LACKS_IDN; #else - char *allochost = Curl_idn_decode(*part); - if(!allochost) - return CURLUE_OUT_OF_MEMORY; + char *allochost; + CURLcode result = Curl_idn_decode(*part, &allochost); + if(result) + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; + free(*part); + *part = allochost; +#endif + } + } + else if(depunyfy) { + if(Curl_is_ASCII_name(u->host)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + char *allochost; + CURLcode result = Curl_idn_encode(*part, &allochost); + if(result) + return (result == CURLE_OUT_OF_MEMORY) ? + CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME; free(*part); *part = allochost; #endif @@ -1631,16 +1613,100 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, return ifmissing; } +static CURLUcode set_url_scheme(CURLU *u, const char *scheme, + unsigned int flags) +{ + size_t plen = strlen(scheme); + const char *s = scheme; + if((plen > MAX_SCHEME_LEN) || (plen < 1)) + /* too long or too short */ + return CURLUE_BAD_SCHEME; + /* verify that it is a fine scheme */ + if(!(flags & CURLU_NON_SUPPORT_SCHEME) && !Curl_get_scheme_handler(scheme)) + return CURLUE_UNSUPPORTED_SCHEME; + if(ISALPHA(*s)) { + /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ + while(--plen) { + if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.')) + s++; /* fine */ + else + return CURLUE_BAD_SCHEME; + } + } + else + return CURLUE_BAD_SCHEME; + u->guessed_scheme = FALSE; + return CURLUE_OK; +} + +static CURLUcode set_url_port(CURLU *u, const char *provided_port) +{ + char *tmp; + curl_off_t port; + if(!ISDIGIT(provided_port[0])) + /* not a number */ + return CURLUE_BAD_PORT_NUMBER; + if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port) + /* weirdly provided number, not good! */ + return CURLUE_BAD_PORT_NUMBER; + tmp = aprintf("%" CURL_FORMAT_CURL_OFF_T, port); + if(!tmp) + return CURLUE_OUT_OF_MEMORY; + free(u->port); + u->port = tmp; + u->portnum = (unsigned short)port; + return CURLUE_OK; +} + +static CURLUcode set_url(CURLU *u, const char *url, size_t part_size, + unsigned int flags) +{ + /* + * Allow a new URL to replace the existing (if any) contents. + * + * If the existing contents is enough for a URL, allow a relative URL to + * replace it. + */ + CURLUcode uc; + char *oldurl = NULL; + + if(!part_size) { + /* a blank URL is not a valid URL unless we already have a complete one + and this is a redirect */ + if(!curl_url_get(u, CURLUPART_URL, &oldurl, flags)) { + /* success, meaning the "" is a fine relative URL, but nothing + changes */ + free(oldurl); + return CURLUE_OK; + } + return CURLUE_MALFORMED_INPUT; + } + + /* if the new thing is absolute or the old one is not (we could not get an + * absolute URL in 'oldurl'), then replace the existing with the new. */ + if(Curl_is_absolute_url(url, NULL, 0, + flags & (CURLU_GUESS_SCHEME|CURLU_DEFAULT_SCHEME)) + || curl_url_get(u, CURLUPART_URL, &oldurl, flags)) { + return parseurl_and_replace(url, u, flags); + } + DEBUGASSERT(oldurl); /* it is set here */ + /* apply the relative part to create a new URL */ + uc = redirect_url(oldurl, url, u, flags); + free(oldurl); + return uc; +} + CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *part, unsigned int flags) { char **storep = NULL; - long port = 0; - bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0; + bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0; bool plusencode = FALSE; bool urlskipslash = FALSE; + bool leadingslash = FALSE; bool appendquery = FALSE; bool equalsencode = FALSE; + size_t nalloc; if(!u) return CURLUE_BAD_HANDLE; @@ -1651,6 +1717,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, break; case CURLUPART_SCHEME: storep = &u->scheme; + u->guessed_scheme = FALSE; break; case CURLUPART_USER: storep = &u->user; @@ -1676,9 +1743,11 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, break; case CURLUPART_QUERY: storep = &u->query; + u->query_present = FALSE; break; case CURLUPART_FRAGMENT: storep = &u->fragment; + u->fragment_present = FALSE; break; default: return CURLUE_UNKNOWN_PART; @@ -1693,26 +1762,18 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, return CURLUE_OK; } + nalloc = strlen(part); + if(nalloc > CURL_MAX_INPUT_LENGTH) + /* excessive input length */ + return CURLUE_MALFORMED_INPUT; + switch(what) { case CURLUPART_SCHEME: { - size_t plen = strlen(part); - const char *s = part; - if((plen > MAX_SCHEME_LEN) || (plen < 1)) - /* too long or too short */ - return CURLUE_BAD_SCHEME; - if(!(flags & CURLU_NON_SUPPORT_SCHEME) && - /* verify that it is a fine scheme */ - !Curl_builtin_scheme(part, CURL_ZERO_TERMINATED)) - return CURLUE_UNSUPPORTED_SCHEME; + CURLUcode status = set_url_scheme(u, part, flags); + if(status) + return status; storep = &u->scheme; urlencode = FALSE; /* never */ - /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ - while(plen--) { - if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.')) - s++; /* fine */ - else - return CURLUE_BAD_SCHEME; - } break; } case CURLUPART_USER: @@ -1732,115 +1793,75 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, storep = &u->zoneid; break; case CURLUPART_PORT: - { - char *endp; - urlencode = FALSE; /* never */ - port = strtol(part, &endp, 10); /* Port number must be decimal */ - if((port <= 0) || (port > 0xffff)) - return CURLUE_BAD_PORT_NUMBER; - if(*endp) - /* weirdly provided number, not good! */ - return CURLUE_BAD_PORT_NUMBER; - storep = &u->port; - } - break; + return set_url_port(u, part); case CURLUPART_PATH: urlskipslash = TRUE; + leadingslash = TRUE; /* enforce */ storep = &u->path; break; case CURLUPART_QUERY: plusencode = urlencode; - appendquery = (flags & CURLU_APPENDQUERY)?1:0; + appendquery = (flags & CURLU_APPENDQUERY) ? 1 : 0; equalsencode = appendquery; storep = &u->query; + u->query_present = TRUE; break; case CURLUPART_FRAGMENT: storep = &u->fragment; + u->fragment_present = TRUE; break; case CURLUPART_URL: { - /* - * Allow a new URL to replace the existing (if any) contents. - * - * If the existing contents is enough for a URL, allow a relative URL to - * replace it. - */ - CURLUcode result; - char *oldurl; - char *redired_url; - - /* if the new thing is absolute or the old one is not - * (we could not get an absolute url in 'oldurl'), - * then replace the existing with the new. */ - if(Curl_is_absolute_url(part, NULL, 0, - flags & (CURLU_GUESS_SCHEME| - CURLU_DEFAULT_SCHEME)) - || curl_url_get(u, CURLUPART_URL, &oldurl, flags)) { - return parseurl_and_replace(part, u, flags); - } - - /* apply the relative part to create a new URL - * and replace the existing one with it. */ - redired_url = concat_url(oldurl, part); - free(oldurl); - if(!redired_url) - return CURLUE_OUT_OF_MEMORY; - - result = parseurl_and_replace(redired_url, u, flags); - free(redired_url); - return result; + return set_url(u, part, nalloc, flags); } default: return CURLUE_UNKNOWN_PART; } DEBUGASSERT(storep); { - const char *newp = part; - size_t nalloc = strlen(part); - - if(nalloc > CURL_MAX_INPUT_LENGTH) - /* excessive input length */ - return CURLUE_MALFORMED_INPUT; + const char *newp; + struct dynbuf enc; + curlx_dyn_init(&enc, nalloc * 3 + 1 + leadingslash); + if(leadingslash && (part[0] != '/')) { + CURLcode result = curlx_dyn_addn(&enc, "/", 1); + if(result) + return cc2cu(result); + } if(urlencode) { const unsigned char *i; - struct dynbuf enc; - - Curl_dyn_init(&enc, nalloc * 3 + 1); for(i = (const unsigned char *)part; *i; i++) { CURLcode result; if((*i == ' ') && plusencode) { - result = Curl_dyn_addn(&enc, "+", 1); + result = curlx_dyn_addn(&enc, "+", 1); if(result) return CURLUE_OUT_OF_MEMORY; } - else if(Curl_isunreserved(*i) || + else if(ISUNRESERVED(*i) || ((*i == '/') && urlskipslash) || ((*i == '=') && equalsencode)) { if((*i == '=') && equalsencode) /* only skip the first equals sign */ equalsencode = FALSE; - result = Curl_dyn_addn(&enc, i, 1); + result = curlx_dyn_addn(&enc, i, 1); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } else { - char out[3]={'%'}; - out[1] = hexdigits[*i>>4]; - out[2] = hexdigits[*i & 0xf]; - result = Curl_dyn_addn(&enc, out, 3); + unsigned char out[3]={'%'}; + Curl_hexbyte(&out[1], *i, TRUE); + result = curlx_dyn_addn(&enc, out, 3); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } } - newp = Curl_dyn_ptr(&enc); } else { char *p; - newp = strdup(part); - if(!newp) - return CURLUE_OUT_OF_MEMORY; - p = (char *)newp; + CURLcode result = curlx_dyn_add(&enc, part); + if(result) + return cc2cu(result); + p = curlx_dyn_ptr(&enc); while(*p) { /* make sure percent encoded are lower case */ if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) && @@ -1853,55 +1874,68 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, p++; } } + newp = curlx_dyn_ptr(&enc); - if(appendquery) { + if(appendquery && newp) { /* Append the 'newp' string onto the old query. Add a '&' separator if none is present at the end of the existing query already */ size_t querylen = u->query ? strlen(u->query) : 0; bool addamperand = querylen && (u->query[querylen -1] != '&'); if(querylen) { - struct dynbuf enc; - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + struct dynbuf qbuf; + curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); - if(Curl_dyn_addn(&enc, u->query, querylen)) /* add original query */ + if(curlx_dyn_addn(&qbuf, u->query, querylen)) /* add original query */ goto nomem; if(addamperand) { - if(Curl_dyn_addn(&enc, "&", 1)) + if(curlx_dyn_addn(&qbuf, "&", 1)) goto nomem; } - if(Curl_dyn_add(&enc, newp)) + if(curlx_dyn_add(&qbuf, newp)) goto nomem; - free((char *)newp); + curlx_dyn_free(&enc); free(*storep); - *storep = Curl_dyn_ptr(&enc); + *storep = curlx_dyn_ptr(&qbuf); return CURLUE_OK; nomem: - free((char *)newp); + curlx_dyn_free(&enc); return CURLUE_OUT_OF_MEMORY; } } - if(what == CURLUPART_HOST) { - size_t n = strlen(newp); + else if(what == CURLUPART_HOST) { + size_t n = curlx_dyn_len(&enc); if(!n && (flags & CURLU_NO_AUTHORITY)) { - /* Skip hostname check, it's allowed to be empty. */ + /* Skip hostname check, it is allowed to be empty. */ } else { - if(!n || hostname_check(u, (char *)newp, n)) { - free((char *)newp); + bool bad = FALSE; + if(!n) + bad = TRUE; /* empty hostname is not okay */ + else if(!urlencode) { + /* if the host name part was not URL encoded here, it was set ready + URL encoded so we need to decode it to check */ + size_t dlen; + char *decoded = NULL; + CURLcode result = + Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); + if(result || hostname_check(u, decoded, dlen)) + bad = TRUE; + free(decoded); + } + else if(hostname_check(u, (char *)CURL_UNCONST(newp), n)) + bad = TRUE; + if(bad) { + curlx_dyn_free(&enc); return CURLUE_BAD_HOSTNAME; } } } free(*storep); - *storep = (char *)newp; + *storep = (char *)CURL_UNCONST(newp); } - /* set after the string, to make it not assigned if the allocation above - fails */ - if(port) - u->portnum = port; return CURLUE_OK; } diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h index f02e6654143..45052e84b18 100644 --- a/Utilities/cmcurl/lib/urldata.h +++ b/Utilities/cmcurl/lib/urldata.h @@ -53,10 +53,21 @@ #define PORT_GOPHER 70 #define PORT_MQTT 1883 -#ifdef USE_WEBSOCKETS +struct curl_trc_featt; + +#ifdef USE_ECH +/* CURLECH_ bits for the tls_ech option */ +# define CURLECH_DISABLE (1<<0) +# define CURLECH_GREASE (1<<1) +# define CURLECH_ENABLE (1<<2) +# define CURLECH_HARD (1<<3) +# define CURLECH_CLA_CFG (1<<4) +#endif + +#ifndef CURL_DISABLE_WEBSOCKETS /* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number, * the rest are internal information. If we use higher bits we only do this on - * platforms that have a >= 64 bit type and then we use such a type for the + * platforms that have a >= 64-bit type and then we use such a type for the * protocol fields in the protocol handler. */ #define CURLPROTO_WS (1<<30) @@ -66,6 +77,10 @@ #define CURLPROTO_WSS 0 #endif +/* the default protocols accepting a redirect to */ +#define CURLPROTO_REDIR (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | \ + CURLPROTO_FTPS) + /* This should be undefined once we need bit 32 or higher */ #define PROTO_TYPE_SMALL @@ -80,16 +95,15 @@ typedef unsigned int curl_prot_t; in the API */ #define CURLPROTO_MASK (0x3ffffff) -#define DICT_MATCH "/MATCH:" -#define DICT_MATCH2 "/M:" -#define DICT_MATCH3 "/FIND:" -#define DICT_DEFINE "/DEFINE:" -#define DICT_DEFINE2 "/D:" -#define DICT_DEFINE3 "/LOOKUP:" - #define CURL_DEFAULT_USER "anonymous" #define CURL_DEFAULT_PASSWORD "ftp@example.com" +#if !defined(_WIN32) && !defined(MSDOS) +/* do FTP line-end CRLF => LF conversions on platforms that prefer LF-only. It + also means: keep CRLF line endings on the CRLF platforms */ +#define CURL_PREFER_LF_LINEENDS +#endif + /* Convenience defines for checking protocols or their SSL based version. Each protocol handler should only ever have a single CURLPROTO_ in its protocol field. */ @@ -101,6 +115,12 @@ typedef unsigned int curl_prot_t; #define PROTO_FAMILY_SMTP (CURLPROTO_SMTP|CURLPROTO_SMTPS) #define PROTO_FAMILY_SSH (CURLPROTO_SCP|CURLPROTO_SFTP) +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) || \ + !defined(CURL_DISABLE_POP3) || !defined(CURL_DISABLE_FILE) +/* these protocols support CURLOPT_DIRLISTONLY */ +#define CURL_LIST_ONLY_PROTOCOL 1 +#endif + #define DEFAULT_CONNCACHE_SIZE 5 /* length of longest IPv6 address string including the trailing null */ @@ -125,7 +145,7 @@ typedef unsigned int curl_prot_t; #include #endif -#include "timeval.h" +#include "curlx/timeval.h" #include @@ -133,14 +153,17 @@ typedef unsigned int curl_prot_t; #include "hostip.h" #include "hash.h" #include "splay.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "dynhds.h" +#include "request.h" +#include "netrc.h" /* return the count of bytes sent, or -1 on error */ typedef ssize_t (Curl_send)(struct Curl_easy *data, /* transfer */ int sockindex, /* socketindex */ const void *buf, /* data to write */ size_t len, /* max amount to write */ + bool eos, /* last chunk */ CURLcode *err); /* error to return */ /* return the count of bytes read, or -1 on error */ @@ -150,17 +173,8 @@ typedef ssize_t (Curl_recv)(struct Curl_easy *data, /* transfer */ size_t len, /* max amount to read */ CURLcode *err); /* error to return */ -#ifdef USE_HYPER -typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, - struct connectdata *conn, - int *didwhat, - bool *done, - int select_res); -#endif - #include "mime.h" #include "imap.h" -#include "pop3.h" #include "smtp.h" #include "ftp.h" #include "file.h" @@ -171,7 +185,6 @@ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, #include "mqtt.h" #include "ftplistparser.h" #include "multihandle.h" -#include "c-hyper.h" #include "cf-socket.h" #ifdef HAVE_GSSAPI @@ -224,8 +237,7 @@ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, #ifdef HAVE_GSSAPI /* Types needed for krb5-ftp connections */ struct krb5buffer { - void *data; - size_t size; + struct dynbuf buf; size_t index; BIT(eof_flag); }; @@ -241,32 +253,18 @@ enum protection_level { }; #endif -/* enum for the nonblocking SSL connection state machine */ -typedef enum { - ssl_connect_1, - ssl_connect_2, - ssl_connect_2_reading, - ssl_connect_2_writing, - ssl_connect_3, - ssl_connect_done -} ssl_connect_state; - -typedef enum { - ssl_connection_none, - ssl_connection_negotiating, - ssl_connection_complete -} ssl_connection_state; - /* SSL backend-specific data; declared differently by each SSL backend */ struct ssl_backend_data; +struct Curl_ssl_scache_entry; struct ssl_primary_config { - char *CApath; /* certificate dir (doesn't work on windows) */ + char *CApath; /* certificate dir (does not work on Windows) */ char *CAfile; /* certificate to verify peer against */ char *issuercert; /* optional issuer certificate filename */ char *clientcert; char *cipher_list; /* list of ciphers to use */ char *cipher_list13; /* list of TLS 1.3 cipher suites to use */ + char *signature_algorithms; /* list of signature algorithms to use */ char *pinned_key; char *CRLfile; /* CRL to check certificate revocation */ struct curl_blob *cert_blob; @@ -277,13 +275,13 @@ struct ssl_primary_config { char *password; /* TLS password (for, e.g., SRP) */ #endif char *curves; /* list of curves to use */ - unsigned char ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ unsigned int version_max; /* max supported version the client wants to use */ + unsigned char ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ unsigned char version; /* what version the client wants to use */ BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ - BIT(sessionid); /* cache session IDs or not */ + BIT(cache_session); /* cache session or not */ }; struct ssl_config_data { @@ -292,15 +290,16 @@ struct ssl_config_data { curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */ void *fsslctxp; /* parameter for call back */ char *cert_type; /* format for certificate (default: PEM)*/ - char *key; /* private key file name */ + char *key; /* private key filename */ struct curl_blob *key_blob; char *key_type; /* format for private key (default: PEM) */ char *key_passwd; /* plain text private key password */ BIT(certinfo); /* gather lots of certificate info */ BIT(falsestart); + BIT(earlydata); /* use tls1.3 early data */ BIT(enable_beast); /* allow this flaw for interoperability's sake */ BIT(no_revoke); /* disable SSL certificate revocation checks */ - BIT(no_partialchain); /* don't accept partial certificate chains */ + BIT(no_partialchain); /* do not accept partial certificate chains */ BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation list errors */ BIT(native_ca_store); /* use the native ca store of operating system */ @@ -309,27 +308,14 @@ struct ssl_config_data { }; struct ssl_general_config { - size_t max_ssl_sessions; /* SSL session id cache size */ int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ }; -/* information stored about one single SSL session */ -struct Curl_ssl_session { - char *name; /* host name for which this ID was used */ - char *conn_to_host; /* host name for the connection (may be NULL) */ - const char *scheme; /* protocol scheme used */ - void *sessionid; /* as returned from the SSL layer */ - size_t idsize; /* if known, otherwise 0 */ - long age; /* just a number, the higher the more recent */ - int remote_port; /* remote port */ - int conn_to_port; /* remote port for the connection (may be -1) */ - struct ssl_primary_config ssl_config; /* setup for this session */ -}; - #ifdef USE_WINDOWS_SSPI #include "curl_sspi.h" #endif +#ifndef CURL_DISABLE_DIGEST_AUTH /* Struct used for Digest challenge-response authentication */ struct digestdata { #if defined(USE_WINDOWS_SSPI) @@ -353,6 +339,7 @@ struct digestdata { BIT(userhash); #endif }; +#endif typedef enum { NTLMSTATE_NONE, @@ -420,15 +407,7 @@ struct ntlmdata { unsigned int flags; unsigned char nonce[8]; unsigned int target_info_len; - void *target_info; /* TargetInfo received in the ntlm type-2 message */ - -#if defined(NTLM_WB_ENABLED) - /* used for communication with Samba's winbind daemon helper ntlm_auth */ - curl_socket_t ntlm_auth_hlpr_socket; - pid_t ntlm_auth_hlpr_pid; - char *challenge; /* The received base64 encoded ntlm type-2 message */ - char *response; /* The generated base64 ntlm type-1/type-3 message */ -#endif + void *target_info; /* TargetInfo received in the NTLM type-2 message */ #endif }; #endif @@ -441,6 +420,7 @@ struct negotiatedata { gss_ctx_id_t context; gss_name_t spn; gss_buffer_desc output_token; + struct dynbuf channel_binding_data; #else #ifdef USE_WINDOWS_SSPI #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS @@ -482,14 +462,11 @@ struct ConnectBits { This is implicit when SSL-protocols are used through proxies, but can also be enabled explicitly by apps */ - BIT(proxy_connect_closed); /* TRUE if a proxy disconnected the connection - in a CONNECT request with auth, so that - libcurl should reconnect and continue. */ BIT(proxy); /* if set, this transfer is done through a proxy - any type */ #endif /* always modify bits.close with the connclose() and connkeep() macros! */ BIT(close); /* if set, we close the connection after this request */ - BIT(reuse); /* if set, this is a re-used connection */ + BIT(reuse); /* if set, this is a reused connection */ BIT(altused); /* this is an alt-svc "redirect" */ BIT(conn_to_host); /* if set, this connection has a "connect to host" that overrides the host in the URL */ @@ -504,16 +481,12 @@ struct ConnectBits { the TCP layer connect */ BIT(retry); /* this connection is about to get closed and then re-attempted at another connection. */ - BIT(authneg); /* TRUE when the auth phase has started, which means - that we are creating a request with an auth header, - but it is not the final request in the auth - negotiation. */ #ifndef CURL_DISABLE_FTP BIT(ftp_use_epsv); /* As set with CURLOPT_FTP_USE_EPSV, but if we find out - EPSV doesn't work we disable it for the forthcoming + EPSV does not work we disable it for the forthcoming requests */ BIT(ftp_use_eprt); /* As set with CURLOPT_FTP_USE_EPRT, but if we find out - EPRT doesn't work we disable it for the forthcoming + EPRT does not work we disable it for the forthcoming requests */ BIT(ftp_use_data_ssl); /* Enabled SSL for the data connection */ BIT(ftp_use_control_ssl); /* Enabled SSL for the control connection */ @@ -523,6 +496,7 @@ struct ConnectBits { #endif BIT(bound); /* set true if bind() has already been done on this socket/ connection */ + BIT(asks_multiplex); /* connection asks for multiplexing, but is not yet */ BIT(multiplex); /* connection is multiplexed */ BIT(tcp_fastopen); /* use TCP Fast Open */ BIT(tls_enable_alpn); /* TLS ALPN extension? */ @@ -532,11 +506,14 @@ struct ConnectBits { #ifdef USE_UNIX_SOCKETS BIT(abstract_unix_socket); #endif - BIT(tls_upgraded); BIT(sock_accepted); /* TRUE if the SECONDARYSOCKET was created with accept() */ BIT(parallel_connect); /* set TRUE when a parallel connect attempt has started (happy eyeballs) */ + BIT(aborted); /* connection was aborted, e.g. in unclean state */ + 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 */ }; struct hostname { @@ -560,165 +537,34 @@ struct hostname { #define KEEP_RECV_PAUSE (1<<4) /* reading is paused */ #define KEEP_SEND_PAUSE (1<<5) /* writing is paused */ +/* KEEP_SEND_TIMED is set when the transfer should attempt sending + * at timer (or other) events. A transfer waiting on a timer will + * remove KEEP_SEND to suppress POLLOUTs of the connection. + * Adding KEEP_SEND_TIMED will then attempt to send whenever the transfer + * enters the "readwrite" loop, e.g. when a timer fires. + * This is used in HTTP for 'Expect: 100-continue' waiting. */ +#define KEEP_SEND_TIMED (1<<6) + #define KEEP_RECVBITS (KEEP_RECV | KEEP_RECV_HOLD | KEEP_RECV_PAUSE) #define KEEP_SENDBITS (KEEP_SEND | KEEP_SEND_HOLD | KEEP_SEND_PAUSE) -#if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) -#define USE_CURL_ASYNC -struct Curl_async { - char *hostname; - struct Curl_dns_entry *dns; - struct thread_data *tdata; - void *resolver; /* resolver state, if it is used in the URL state - - ares_channel e.g. */ - int port; - int status; /* if done is TRUE, this is the status from the callback */ - BIT(done); /* set TRUE when the lookup is complete */ -}; - -#endif +/* transfer wants to send is not PAUSE or HOLD */ +#define CURL_WANT_SEND(data) \ + (((data)->req.keepon & KEEP_SENDBITS) == KEEP_SEND) +/* transfer receive is not on PAUSE or HOLD */ +#define CURL_WANT_RECV(data) \ + (((data)->req.keepon & KEEP_RECVBITS) == KEEP_RECV) #define FIRSTSOCKET 0 #define SECONDARYSOCKET 1 -enum expect100 { - EXP100_SEND_DATA, /* enough waiting, just send the body now */ - EXP100_AWAITING_CONTINUE, /* waiting for the 100 Continue header */ - EXP100_SENDING_REQUEST, /* still sending the request but will wait for - the 100 header once done with the request */ - EXP100_FAILED /* used on 417 Expectation Failed */ -}; - -enum upgrade101 { - UPGR101_INIT, /* default state */ - UPGR101_WS, /* upgrade to WebSockets requested */ - UPGR101_H2, /* upgrade to HTTP/2 requested */ - UPGR101_RECEIVED, /* 101 response received */ - UPGR101_WORKING /* talking upgraded protocol */ -}; - -enum doh_slots { - /* Explicit values for first two symbols so as to match hard-coded - * constants in existing code - */ - DOH_PROBE_SLOT_IPADDR_V4 = 0, /* make 'V4' stand out for readability */ - DOH_PROBE_SLOT_IPADDR_V6 = 1, /* 'V6' likewise */ - - /* Space here for (possibly build-specific) additional slot definitions */ - - /* for example */ - /* #ifdef WANT_DOH_FOOBAR_TXT */ - /* DOH_PROBE_SLOT_FOOBAR_TXT, */ - /* #endif */ - - /* AFTER all slot definitions, establish how many we have */ - DOH_PROBE_SLOTS -}; - -/* - * Request specific data in the easy handle (Curl_easy). Previously, - * these members were on the connectdata struct but since a conn struct may - * now be shared between different Curl_easys, we store connection-specific - * data here. This struct only keeps stuff that's interesting for *this* - * request, as it will be cleared between multiple ones +/* Polling requested by an easy handle. + * `action` is CURL_POLL_IN, CURL_POLL_OUT or CURL_POLL_INOUT. */ -struct SingleRequest { - curl_off_t size; /* -1 if unknown at this point */ - curl_off_t maxdownload; /* in bytes, the maximum amount of data to fetch, - -1 means unlimited */ - curl_off_t bytecount; /* total number of bytes read */ - curl_off_t writebytecount; /* number of bytes written */ - - curl_off_t headerbytecount; /* only count received headers */ - curl_off_t deductheadercount; /* this amount of bytes doesn't count when we - check if anything has been transferred at - the end of a connection. We use this - counter to make only a 100 reply (without a - following second response code) result in a - CURLE_GOT_NOTHING error code */ - - curl_off_t pendingheader; /* this many bytes left to send is actually - header and not body */ - struct curltime start; /* transfer started at this time */ - enum { - HEADER_NORMAL, /* no bad header at all */ - HEADER_PARTHEADER, /* part of the chunk is a bad header, the rest - is normal data */ - HEADER_ALLBAD /* all was believed to be header */ - } badheader; /* the header was deemed bad and will be - written as body */ - int headerline; /* counts header lines to better track the - first one */ - char *str; /* within buf */ - curl_off_t offset; /* possible resume offset read from the - Content-Range: header */ - int httpcode; /* error code from the 'HTTP/1.? XXX' or - 'RTSP/1.? XXX' line */ - int keepon; - struct curltime start100; /* time stamp to wait for the 100 code from */ - enum expect100 exp100; /* expect 100 continue state */ - enum upgrade101 upgr101; /* 101 upgrade state */ - - /* Content unencoding stack. See sec 3.5, RFC2616. */ - struct contenc_writer *writer_stack; - time_t timeofdoc; - long bodywrites; - char *location; /* This points to an allocated version of the Location: - header data */ - char *newurl; /* Set to the new URL to use when a redirect or a retry is - wanted */ - - /* 'upload_present' is used to keep a byte counter of how much data there is - still left in the buffer, aimed for upload. */ - ssize_t upload_present; - - /* 'upload_fromhere' is used as a read-pointer when we uploaded parts of a - buffer, so the next read should read from where this pointer points to, - and the 'upload_present' contains the number of bytes available at this - position */ - char *upload_fromhere; - - /* Allocated protocol-specific data. Each protocol handler makes sure this - points to data it needs. */ - union { - struct FILEPROTO *file; - struct FTP *ftp; - struct HTTP *http; - struct IMAP *imap; - struct ldapreqinfo *ldap; - struct MQTT *mqtt; - struct POP3 *pop3; - struct RTSP *rtsp; - struct smb_request *smb; - struct SMTP *smtp; - struct SSHPROTO *ssh; - struct TELNET *telnet; - } p; -#ifndef CURL_DISABLE_DOH - struct dohdata *doh; /* DoH specific data for this request */ -#endif -#if defined(WIN32) && defined(USE_WINSOCK) - struct curltime last_sndbuf_update; /* last time readwrite_upload called - win_update_buffer_size */ -#endif - unsigned char setcookies; - unsigned char writer_stack_depth; /* Unencoding stack depth. */ - BIT(header); /* incoming data has HTTP header */ - BIT(content_range); /* set TRUE if Content-Range: was found */ - BIT(upload_done); /* set to TRUE when doing chunked transfer-encoding - upload and we're uploading the last chunk */ - BIT(ignorebody); /* we read a response-body but we ignore it! */ - BIT(http_bodyless); /* HTTP response status code is between 100 and 199, - 204 or 304 */ - BIT(chunk); /* if set, this is a chunked transfer-encoding */ - BIT(ignore_cl); /* ignore content-length */ - BIT(upload_chunky); /* set TRUE if we are doing chunked transfer-encoding - on upload */ - BIT(getheader); /* TRUE if header parsing is wanted */ - BIT(forbidchunk); /* used only to explicitly forbid chunk-upload for - specific upload buffers. See readmoredata() in http.c - for details. */ - BIT(no_body); /* the response has no body */ +struct easy_pollset { + curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; + unsigned int num; + unsigned char actions[MAX_SOCKSPEREASYHANDLE]; }; /* @@ -726,7 +572,7 @@ struct SingleRequest { */ struct Curl_handler { - const char *scheme; /* URL scheme name. */ + const char *scheme; /* URL scheme name in lowercase */ /* Complement to setup_connection_internals(). This is done before the transfer "owns" the connection. */ @@ -746,8 +592,8 @@ struct Curl_handler { /* This function *MAY* be set to a protocol-dependent function that is run * after the connect() and everything is done, as a step in the connection. * The 'done' pointer points to a bool that should be set to TRUE if the - * function completes before return. If it doesn't complete, the caller - * should call the curl_connecting() function until it is. + * function completes before return. If it does not complete, the caller + * should call the ->connecting() function until it is. */ CURLcode (*connect_it)(struct Curl_easy *data, bool *done); @@ -777,7 +623,7 @@ struct Curl_handler { struct connectdata *conn, curl_socket_t *socks); /* This function *MAY* be set to a protocol-dependent function that is run - * by the curl_disconnect(), as a step in the disconnection. If the handler + * by the curl_disconnect(), as a step in the disconnection. If the handler * is called because the connection has been considered dead, * dead_connection is set to TRUE. The connection is (again) associated with * the transfer here. @@ -785,10 +631,17 @@ struct Curl_handler { CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *, bool dead_connection); - /* If used, this function gets called from transfer.c:readwrite_data() to - allow the protocol to do extra reads/writes */ - CURLcode (*readwrite)(struct Curl_easy *data, struct connectdata *conn, - ssize_t *nread, bool *readmore); + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing response to + the client. */ + CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen, + bool is_eos); + + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing a single response + header line to the client. */ + CURLcode (*write_resp_hd)(struct Curl_easy *data, + const char *hd, size_t hdlen, bool is_eos); /* This function can perform various checks on the connection. See CONNCHECK_* for more information about the checks that can be performed, @@ -800,6 +653,12 @@ struct Curl_handler { /* attach() attaches this transfer to this connection */ void (*attach)(struct Curl_easy *data, struct connectdata *conn); + /* return CURLE_OK if a redirect to `newurl` should be followed, + CURLE_TOO_MANY_REDIRECTS otherwise. May alter `data` to change + the way the follow request is performed. */ + CURLcode (*follow)(struct Curl_easy *data, const char *newurl, + followtype type); + int defport; /* Default port. */ curl_prot_t protocol; /* See CURLPROTO_* - this needs to be the single specific protocol bit */ @@ -818,11 +677,11 @@ struct Curl_handler { the send function might need to be called while uploading, or vice versa. */ #define PROTOPT_DIRLOCK (1<<3) -#define PROTOPT_NONETWORK (1<<4) /* protocol doesn't use the network! */ +#define PROTOPT_NONETWORK (1<<4) /* protocol does not use the network! */ #define PROTOPT_NEEDSPWD (1<<5) /* needs a password, and if none is set it gets a default */ -#define PROTOPT_NOURLQUERY (1<<6) /* protocol can't handle - url query strings (?foo=bar) ! */ +#define PROTOPT_NOURLQUERY (1<<6) /* protocol cannot handle + URL query strings (?foo=bar) ! */ #define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per request instead of per connection */ #define PROTOPT_ALPN (1<<8) /* set ALPN for this */ @@ -833,9 +692,9 @@ struct Curl_handler { HTTP proxy as HTTP proxies may know this protocol and act as a gateway */ #define PROTOPT_WILDCARD (1<<12) /* protocol supports wildcard matching */ -#define PROTOPT_USERPWDCTRL (1<<13) /* Allow "control bytes" (< 32 ascii) in - user name and password */ -#define PROTOPT_NOTCPPROXY (1<<14) /* this protocol can't proxy over TCP */ +#define PROTOPT_USERPWDCTRL (1<<13) /* Allow "control bytes" (< 32 ASCII) in + username and password */ +#define PROTOPT_NOTCPPROXY (1<<14) /* this protocol cannot proxy over TCP */ #define CONNCHECK_NONE 0 /* No checks */ #define CONNCHECK_ISDEAD (1<<0) /* Check if the connection is dead. */ @@ -844,17 +703,22 @@ struct Curl_handler { #define CONNRESULT_NONE 0 /* No extra information. */ #define CONNRESULT_DEAD (1<<0) /* The connection is dead. */ +struct ip_quadruple { + char remote_ip[MAX_IPADR_LEN]; + char local_ip[MAX_IPADR_LEN]; + int remote_port; + int local_port; +}; + struct proxy_info { struct hostname host; int port; unsigned char proxytype; /* curl_proxytype: what kind of proxy that is in use */ - char *user; /* proxy user name string, allocated */ + char *user; /* proxy username string, allocated */ char *passwd; /* proxy password string, allocated */ }; -struct ldapconninfo; - #define TRNSPRT_TCP 3 #define TRNSPRT_UDP 4 #define TRNSPRT_QUIC 5 @@ -865,66 +729,67 @@ struct ldapconninfo; * unique for an entire connection. */ struct connectdata { - struct Curl_llist_element bundle_node; /* conncache */ - - /* chunk is for HTTP chunked encoding, but is in the general connectdata - struct only because we can do just about any protocol through an HTTP - proxy and an HTTP proxy may in fact respond using chunked encoding */ - struct Curl_chunker chunk; + struct Curl_llist_node cpool_node; /* conncache lists */ + struct Curl_llist_node cshutdn_node; /* cshutdn list */ curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ void *closesocket_client; - /* This is used by the connection cache logic. If this returns TRUE, this + /* This is used by the connection pool logic. If this returns TRUE, this handle is still used by one or more easy handles and can only used by any other easy handle without careful consideration (== only for multiplexing) and it cannot be used by another multi handle! */ -#define CONN_INUSE(c) ((c)->easyq.size) +#define CONN_INUSE(c) (!Curl_uint_spbset_empty(&(c)->xfers_attached)) +#define CONN_ATTACHED(c) Curl_uint_spbset_count(&(c)->xfers_attached) /**** Fields set when inited and not modified again */ - long connection_id; /* Contains a unique number to make it easier to - track the connections in the log output */ + curl_off_t connection_id; /* Contains a unique number to make it easier to + track the connections in the log output */ + char *destination; /* string carrying normalized hostname+port+scope */ - /* 'dns_entry' is the particular host we use. This points to an entry in the - DNS cache and it will not get pruned while locked. It gets unlocked in - multi_done(). This entry will be NULL if the connection is re-used as then - there is no name resolve done. */ - struct Curl_dns_entry *dns_entry; + /* `meta_hash` is a general key-value store for implementations + * with the lifetime of the connection. + * Elements need to be added with their own destructor to be invoked when + * the connection is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash meta_hash; /* 'remote_addr' is the particular IP we connected to. it is owned, set * and NULLed by the connected socket filter (if there is one). */ const struct Curl_sockaddr_ex *remote_addr; struct hostname host; - char *hostname_resolve; /* host name to resolve to address, allocated */ - char *secondaryhostname; /* secondary socket host name (ftp) */ + char *hostname_resolve; /* hostname to resolve to address, allocated */ + char *secondaryhostname; /* secondary socket hostname (ftp) */ struct hostname conn_to_host; /* the host to connect to. valid only if bits.conn_to_host is set */ #ifndef CURL_DISABLE_PROXY struct proxy_info socks_proxy; struct proxy_info http_proxy; #endif - /* 'primary_ip' and 'primary_port' get filled with peer's numerical - ip address and port number whenever an outgoing connection is - *attempted* from the primary socket to a remote address. When more - than one address is tried for a connection these will hold data + /* 'primary' and 'secondary' get filled with IP quadruple + (local/remote numerical ip address and port) whenever a connect is + *attempted*. + When more than one address is tried for a connection these will hold data for the last attempt. When the connection is actually established these are updated with data which comes directly from the socket. */ - - char primary_ip[MAX_IPADR_LEN]; - char *user; /* user name string, allocated */ + struct ip_quadruple primary; + struct ip_quadruple secondary; + char *user; /* username string, allocated */ char *passwd; /* password string, allocated */ char *options; /* options string, allocated */ char *sasl_authzid; /* authorization identity string, allocated */ char *oauth_bearer; /* OAUTH2 bearer, allocated */ - struct curltime now; /* "current" time */ struct curltime created; /* creation time */ - struct curltime lastused; /* when returned to the connection cache */ + struct curltime lastused; /* when returned to the connection poolas idle */ curl_socket_t sock[2]; /* two sockets, the second is used for the data transfer when doing FTP */ Curl_recv *recv[2]; Curl_send *send[2]; struct Curl_cfilter *cfilter[2]; /* connection filters */ + struct { + struct curltime start[2]; /* when filter shutdown started */ + unsigned int timeout_ms; /* 0 means no timeout */ + } shutdown; struct ssl_primary_config ssl_config; #ifndef CURL_DISABLE_PROXY @@ -943,9 +808,8 @@ struct connectdata { /**** curl_get() phase fields */ curl_socket_t sockfd; /* socket to read from or CURL_SOCKET_BAD */ - curl_socket_t writesockfd; /* socket to write to, it may very - well be the same we read from. - CURL_SOCKET_BAD disables */ + curl_socket_t writesockfd; /* socket to write to, it may be the same we read + from. CURL_SOCKET_BAD disables */ #ifdef HAVE_GSSAPI BIT(sec_complete); /* if Kerberos is enabled for this connection */ @@ -963,9 +827,14 @@ struct connectdata { struct kerberos5data krb5; /* variables into the structure definition, */ #endif /* however, some of them are ftp specific. */ - struct Curl_llist easyq; /* List of easy handles using this connection */ - curl_seek_callback seek_func; /* function that seeks the input */ - void *seek_client; /* pointer to pass to the seek() above */ + struct uint_spbset xfers_attached; /* mids of attached transfers */ + /* A connection cache from a SHARE might be used in several multi handles. + * We MUST not reuse connections that are running in another multi, + * for concurrency reasons. That multi might run in another thread. + * `attached_multi` is set by the first transfer attached and cleared + * when the last one is detached. + * NEVER call anything on this multi, just check for equality. */ + struct Curl_multi *attached_multi; /*************** Request - specific items ************/ #if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) @@ -994,58 +863,14 @@ struct connectdata { struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */ #endif -#ifndef CURL_DISABLE_HTTP - /* for chunked-encoded trailer */ - struct dynbuf trailer; -#endif - - union { -#ifndef CURL_DISABLE_FTP - struct ftp_conn ftpc; -#endif -#ifdef USE_SSH - struct ssh_conn sshc; -#endif -#ifndef CURL_DISABLE_TFTP - struct tftp_state_data *tftpc; -#endif -#ifndef CURL_DISABLE_IMAP - struct imap_conn imapc; -#endif -#ifndef CURL_DISABLE_POP3 - struct pop3_conn pop3c; -#endif -#ifndef CURL_DISABLE_SMTP - struct smtp_conn smtpc; -#endif -#ifndef CURL_DISABLE_RTSP - struct rtsp_conn rtspc; -#endif -#ifndef CURL_DISABLE_SMB - struct smb_conn smbc; -#endif - void *rtmp; - struct ldapconninfo *ldapc; -#ifndef CURL_DISABLE_MQTT - struct mqtt_conn mqtt; -#endif -#ifdef USE_WEBSOCKETS - struct websocket *ws; -#endif - } proto; - - struct connectbundle *bundle; /* The bundle we are member of */ #ifdef USE_UNIX_SOCKETS char *unix_domain_socket; #endif -#ifdef USE_HYPER - /* if set, an alternative data transfer function */ - Curl_datastream datastream; -#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 aren't accidentally re-using + that subsequent bound-requested connections are not accidentally reusing wrong connections. */ char *localdev; unsigned short localportrange; @@ -1054,17 +879,15 @@ struct connectdata { int socks5_gssapi_enctype; #endif /* The field below gets set in connect.c:connecthost() */ - int port; /* which port to use locally - to connect to */ int remote_port; /* the remote port, not the proxy port! */ int conn_to_port; /* the remote port to connect to. valid only if bits.conn_to_port is set */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int scope_id; /* Scope id for IPv6 */ #endif unsigned short localport; unsigned short secondary_port; /* secondary socket remote port to connect to (ftp) */ - unsigned char cselect_bits; /* bitmask of socket events */ unsigned char alpn; /* APLN TLS negotiated protocol, a CURL_HTTP_VERSION* value */ #ifndef CURL_DISABLE_PROXY @@ -1072,11 +895,25 @@ struct connectdata { #endif unsigned char transport; /* one of the TRNSPRT_* defines */ unsigned char ip_version; /* copied from the Curl_easy at creation time */ - unsigned char httpversion; /* the HTTP version*10 reported by the server */ + /* HTTP version last responded with by the server. + * 0 at start, then one of 09, 10, 11, etc. */ + unsigned char httpversion_seen; unsigned char connect_only; unsigned char gssapi_delegation; /* inherited from set.gssapi_delegation */ }; +#ifndef CURL_DISABLE_PROXY +#define CURL_CONN_HOST_DISPNAME(c) \ + ((c)->bits.socksproxy ? (c)->socks_proxy.host.dispname : \ + (c)->bits.httpproxy ? (c)->http_proxy.host.dispname : \ + (c)->bits.conn_to_host ? (c)->conn_to_host.dispname : \ + (c)->host.dispname) +#else +#define CURL_CONN_HOST_DISPNAME(c) \ + (c)->bits.conn_to_host ? (c)->conn_to_host.dispname : \ + (c)->host.dispname +#endif + /* The end of connectdata. */ /* @@ -1089,86 +926,84 @@ struct PureInfo { int httpversion; /* the http version number X.Y = X*10+Y */ time_t filetime; /* If requested, this is might get set. Set to -1 if the time was unretrievable. */ - curl_off_t header_size; /* size of read header(s) in bytes */ curl_off_t request_size; /* the amount of bytes sent in the request(s) */ unsigned long proxyauthavail; /* what proxy auth types were announced */ unsigned long httpauthavail; /* what host auth types were announced */ + unsigned long proxyauthpicked; /* selected proxy auth type */ + unsigned long httpauthpicked; /* selected host auth type */ long numconnects; /* how many new connection did libcurl created */ char *contenttype; /* the content type of the object */ - char *wouldredirect; /* URL this would've been redirected to if asked to */ + char *wouldredirect; /* URL this would have been redirected to if asked to */ curl_off_t retry_after; /* info from Retry-After: header */ - - /* PureInfo members 'conn_primary_ip', 'conn_primary_port', 'conn_local_ip' - and, 'conn_local_port' are copied over from the connectdata struct in - order to allow curl_easy_getinfo() to return this information even when - the session handle is no longer associated with a connection, and also - allow curl_easy_reset() to clear this information from the session handle - without disturbing information which is still alive, and that might be - reused, in the connection cache. */ - - char conn_primary_ip[MAX_IPADR_LEN]; - int conn_primary_port; /* this is the destination port to the connection, - which might have been a proxy */ + unsigned int header_size; /* size of read header(s) in bytes */ + + /* PureInfo primary ip_quadruple is copied over from the connectdata + struct in order to allow curl_easy_getinfo() to return this information + even when the session handle is no longer associated with a connection, + and also allow curl_easy_reset() to clear this information from the + session handle without disturbing information which is still alive, and + that might be reused, in the connection pool. */ + struct ip_quadruple primary; int conn_remote_port; /* this is the "remote port", which is the port number of the used URL, independent of proxy or not */ - char conn_local_ip[MAX_IPADR_LEN]; - int conn_local_port; const char *conn_scheme; unsigned int conn_protocol; - struct curl_certinfo certs; /* info about the certs, only populated in - OpenSSL, GnuTLS, Schannel, NSS and GSKit - builds. Asked for with CURLOPT_CERTINFO - / CURLINFO_CERTINFO */ + struct curl_certinfo certs; /* info about the certs. Asked for with + CURLOPT_CERTINFO / CURLINFO_CERTINFO */ CURLproxycode pxcode; - BIT(timecond); /* set to TRUE if the time condition didn't match, which + BIT(timecond); /* set to TRUE if the time condition did not match, which thus made the document NOT get fetched */ + BIT(used_proxy); /* the transfer used a proxy */ }; +struct pgrs_measure { + struct curltime start; /* when measure started */ + curl_off_t start_size; /* the 'cur_size' the measure started at */ +}; + +struct pgrs_dir { + curl_off_t total_size; /* total expected bytes */ + curl_off_t cur_size; /* transferred bytes so far */ + curl_off_t speed; /* bytes per second transferred */ + struct pgrs_measure limit; +}; struct Progress { time_t lastshow; /* time() of the last displayed progress meter or NULL to force redraw at next call */ - curl_off_t size_dl; /* total expected size */ - curl_off_t size_ul; /* total expected size */ - curl_off_t downloaded; /* transferred so far */ - curl_off_t uploaded; /* transferred so far */ + struct pgrs_dir ul; + struct pgrs_dir dl; curl_off_t current_speed; /* uses the currently fastest transfer */ - - int width; /* screen width at download start */ - int flags; /* see progress.h */ + curl_off_t earlydata_sent; timediff_t timespent; - curl_off_t dlspeed; - curl_off_t ulspeed; - + timediff_t t_postqueue; timediff_t t_nslookup; timediff_t t_connect; timediff_t t_appconnect; timediff_t t_pretransfer; + timediff_t t_posttransfer; timediff_t t_starttransfer; timediff_t t_redirect; struct curltime start; struct curltime t_startsingle; struct curltime t_startop; + struct curltime t_startqueue; struct curltime t_acceptdata; - - /* upload speed limit */ - struct curltime ul_limit_start; - curl_off_t ul_limit_size; - /* download speed limit */ - struct curltime dl_limit_start; - curl_off_t dl_limit_size; - #define CURR_TIME (5 + 1) /* 6 entries for 5 seconds */ curl_off_t speeder[ CURR_TIME ]; struct curltime speeder_time[ CURR_TIME ]; - int speeder_c; + unsigned char speeder_c; + BIT(hide); + BIT(ul_size_known); + BIT(dl_size_known); + BIT(headers_out); /* when the headers have been written */ BIT(callback); /* set when progress callback is used */ BIT(is_t_startransfer_set); }; @@ -1213,7 +1048,6 @@ struct Curl_data_prio_node { /** * Priority information for an easy handle in relation to others * on the same connection. - * TODO: we need to adapt it to the new priority scheme as defined in RFC 9218 */ struct Curl_data_priority { #ifdef USE_NGHTTP2 @@ -1227,17 +1061,6 @@ struct Curl_data_priority { #endif }; -/* - * This struct is for holding data that was attempted to get sent to the user's - * callback but is held due to pausing. One instance per type (BOTH, HEADER, - * BODY). - */ -struct tempbuf { - struct dynbuf b; - int type; /* type of the 'tempwrite' buffer as a bitmask that is used with - Curl_client_write() */ -}; - /* Timers */ typedef enum { EXPIRE_100_TIMEOUT, @@ -1255,6 +1078,7 @@ typedef enum { EXPIRE_QUIC, EXPIRE_FTP_ACCEPT, EXPIRE_ALPN_EYEBALLS, + EXPIRE_SHUTDOWN, EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; @@ -1271,7 +1095,7 @@ typedef enum { * One instance for each timeout an easy handle can set. */ struct time_node { - struct Curl_llist_element list; + struct Curl_llist_node list; struct curltime time; expire_id eid; }; @@ -1288,22 +1112,26 @@ struct urlpieces { char *query; }; +#define CREDS_NONE 0 +#define CREDS_URL 1 /* from URL */ +#define CREDS_OPTION 2 /* set with a CURLOPT_ */ +#define CREDS_NETRC 3 /* found in netrc */ + struct UrlState { - /* Points to the connection cache */ - struct conncache *conn_cache; /* buffers to store authentication data in, as parsed from input options */ struct curltime keeps_speed; /* for the progress meter really */ - long lastconnect_id; /* The last connection, -1 if undefined */ + curl_off_t lastconnect_id; /* The last connection, -1 if undefined */ + curl_off_t recent_conn_id; /* The most recent connection used, might no + * longer exist */ struct dynbuf headerb; /* buffer to store headers in */ - - char *buffer; /* download buffer */ - char *ulbuf; /* allocated upload buffer or NULL */ + struct curl_slist *hstslist; /* list of HSTS files set by + curl_easy_setopt(HSTS) calls */ curl_off_t current_speed; /* the ProgressShow() function sets this, bytes / second */ - /* host name, port number and protocol of the first (not followed) request. - if set, this should be the host name that we will sent authorization to, + /* hostname, port number and protocol of the first (not followed) request. + if set, this should be the hostname that we will sent authorization to, no else. Used to make Location: following not keep sending user+password. This is strdup()ed data. */ char *first_host; @@ -1311,24 +1139,21 @@ struct UrlState { curl_prot_t first_remote_protocol; int retrycount; /* number of retries on a new connection */ - struct Curl_ssl_session *session; /* array of 'max_ssl_sessions' size */ - long sessionage; /* number of the most recent session */ - struct tempbuf tempwrite[3]; /* BOTH, HEADER, BODY */ - unsigned int tempcount; /* number of entries in use in tempwrite, 0 - 3 */ int os_errno; /* filled in with errno whenever an error occurs */ - char *scratch; /* huge buffer[set.buffer_size*2] for upload CRLF replacing */ long followlocation; /* redirect counter */ int requests; /* request counter: redirects + authentication retakes */ #ifdef HAVE_SIGNAL /* storage for the previous bag^H^H^HSIGPIPE signal handler :-) */ void (*prev_signal)(int sig); #endif -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH struct digestdata digest; /* state data for host Digest auth */ struct digestdata proxydigest; /* state data for proxy Digest auth */ #endif struct auth authhost; /* auth details for host */ struct auth authproxy; /* auth details for proxy */ + + struct Curl_dns_entry *dns[2]; /* DNS to connect FIRST/SECONDARY */ #ifdef USE_CURL_ASYNC struct Curl_async async; /* asynchronous name resolver data */ #endif @@ -1336,6 +1161,13 @@ struct UrlState { #if defined(USE_OPENSSL) /* void instead of ENGINE to avoid bleeding OpenSSL into this header */ void *engine; + /* void instead of OSSL_PROVIDER */ + void *provider; + void *baseprov; + void *libctx; + char *propq; /* for a provider */ + + BIT(provider_loaded); #endif /* USE_OPENSSL */ struct curltime expiretime; /* set this with Curl_expire() only */ struct Curl_tree timenode; /* for the splay stuff */ @@ -1344,14 +1176,6 @@ struct UrlState { /* a place to store the most recently set (S)FTP entrypath */ char *most_recent_ftp_entrypath; -#if !defined(WIN32) && !defined(MSDOS) && !defined(__EMX__) -/* do FTP line-end conversions on most platforms */ -#define CURL_DO_LINEEND_CONV - /* for FTP downloads: track CRLF sequences that span blocks */ - BIT(prev_block_had_trailing_cr); - /* for FTP downloads: how many CRLFs did we converted to LFs? */ - curl_off_t crlf_conversions; -#endif char *range; /* range, if used. See README for detailed specification on this syntax. */ curl_off_t resume_from; /* continue [ftp] transfer from here */ @@ -1381,6 +1205,11 @@ struct UrlState { struct curl_slist *resolve; /* set to point to the set.resolve list when this should be dealt with in pretransfer */ #ifndef CURL_DISABLE_HTTP + curl_mimepart *mimepost; +#ifndef CURL_DISABLE_FORM_API + curl_mimepart *formp; /* storage for old API form-posting, allocated on + demand */ +#endif size_t trailers_bytes_sent; struct dynbuf trailers_buf; /* a buffer containing the compiled trailing headers */ @@ -1390,44 +1219,56 @@ struct UrlState { trailers_state trailers_state; /* whether we are sending trailers and what stage are we at */ #endif -#ifdef USE_HYPER - bool hconnect; /* set if a CONNECT request */ - CURLcode hresult; /* used to pass return codes back from hyper callbacks */ +#ifndef CURL_DISABLE_COOKIES + struct curl_slist *cookielist; /* list of cookie files set by + curl_easy_setopt(COOKIEFILE) calls */ +#endif + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + struct curl_trc_feat *feat; /* opt. trace feature transfer is part of */ +#endif + +#ifndef CURL_DISABLE_NETRC + struct store_netrc netrc; #endif /* Dynamically allocated strings, MUST be freed before this struct is killed. */ struct dynamically_allocated_data { - char *proxyuserpwd; char *uagent; char *accept_encoding; char *userpwd; char *rangeline; char *ref; char *host; +#ifndef CURL_DISABLE_COOKIES char *cookiehost; +#endif +#ifndef CURL_DISABLE_RTSP char *rtsp_transport; +#endif char *te; /* TE: request header */ /* transfer credentials */ char *user; char *passwd; +#ifndef CURL_DISABLE_PROXY + char *proxyuserpwd; char *proxyuser; char *proxypasswd; +#endif } aptr; - - unsigned char httpwant; /* when non-zero, a specific HTTP version requested - to be used in the library's request(s) */ - unsigned char httpversion; /* the lowest HTTP version*10 reported by any - server involved in this request */ +#ifndef CURL_DISABLE_HTTP + struct http_negotiation http_neg; +#endif unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) is this */ - unsigned char dselect_bits; /* != 0 -> bitmask of socket events for this + unsigned char select_bits; /* != 0 -> bitmask of socket events for this transfer overriding anything the socket may report */ -#ifdef CURLDEBUG - BIT(conncache_lock); -#endif + unsigned int creds_from:2; /* where is the server credentials originating + from, see the CREDS_* defines above */ + /* when curl_easy_perform() is called, the multi handle is "owned" by the easy handle so curl_easy_cleanup() on such an easy handle will also close the multi handle! */ @@ -1440,10 +1281,9 @@ struct UrlState { called. */ BIT(allow_port); /* Is set.use_port allowed to take effect or not. This is always set TRUE when curl_easy_perform() is called. */ - BIT(authproblem); /* TRUE if there's some problem authenticating */ + BIT(authproblem); /* TRUE if there is some problem authenticating */ /* set after initial USER failure, to prevent an authentication loop */ BIT(wildcardmatch); /* enable wildcard matching */ - BIT(expect100header); /* TRUE if we added Expect: 100-continue */ BIT(disableexpect); /* TRUE if Expect: is disabled due to a previous 417 response */ BIT(use_range); @@ -1451,17 +1291,21 @@ struct UrlState { BIT(done); /* set to FALSE when Curl_init_do() is called and set to TRUE when multi_done() is called, to prevent multi_done() to get invoked twice when the multi interface is used. */ - BIT(previouslypending); /* this transfer WAS in the multi->pending queue */ +#ifndef CURL_DISABLE_COOKIES BIT(cookie_engine); +#endif BIT(prefer_ascii); /* ASCII rather than binary */ +#ifdef CURL_LIST_ONLY_PROTOCOL BIT(list_only); /* list directory contents */ +#endif BIT(url_alloc); /* URL string is malloc()'ed */ BIT(referer_alloc); /* referer string is malloc()ed */ BIT(wildcard_resolve); /* Set to true if any resolve change is a wildcard */ - BIT(rewindbeforesend);/* TRUE when the sending couldn't be stopped even - though it will be discarded. We must call the data - rewind callback before trying to send again. */ BIT(upload); /* upload request */ + BIT(internal); /* internal: true if this easy handle was created for + internal use and the user does not have ownership of the + handle. */ + BIT(http_ignorecustom); /* ignore custom method from now */ }; /* @@ -1475,94 +1319,130 @@ struct UrlState { struct Curl_multi; /* declared in multihandle.c */ -/* - * This enumeration MUST not use conditional directives (#ifdefs), new - * null terminated strings MUST be added to the enumeration immediately - * before STRING_LASTZEROTERMINATED, binary fields immediately before - * STRING_LAST. When doing so, ensure that the packages/OS400/chkstring.c - * test is updated and applicable changes for EBCDIC to ASCII conversion - * are catered for in curl_easy_setopt_ccsid() - */ enum dupstring { - STRING_CERT, /* client certificate file name */ - STRING_CERT_PROXY, /* client certificate file name */ + STRING_CERT, /* client certificate filename */ STRING_CERT_TYPE, /* format for certificate (default: PEM)*/ + STRING_KEY, /* private key filename */ + STRING_KEY_PASSWD, /* plain text private key password */ + STRING_KEY_TYPE, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH, /* CA directory name (does not work on Windows) */ + STRING_SSL_CAFILE, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ + STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ + STRING_SERVICE_NAME, /* Service name */ +#ifndef CURL_DISABLE_PROXY + STRING_CERT_PROXY, /* client certificate filename */ STRING_CERT_TYPE_PROXY, /* format for certificate (default: PEM)*/ + STRING_KEY_PROXY, /* private key filename */ + STRING_KEY_PASSWD_PROXY, /* plain text private key password */ + STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH_PROXY, /* CA directory name (does not work on Windows) */ + STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ + STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ + STRING_PROXY_SERVICE_NAME, /* Proxy service name */ +#endif +#ifndef CURL_DISABLE_COOKIES STRING_COOKIE, /* HTTP cookie string to send */ STRING_COOKIEJAR, /* dump all cookies to this file */ +#endif STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ - STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL doesn't specify */ + 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_ENCODING, /* Accept-Encoding string */ +#ifndef CURL_DISABLE_FTP STRING_FTP_ACCOUNT, /* ftp account data */ STRING_FTP_ALTERNATIVE_TO_USER, /* command to send if USER/PASS fails */ STRING_FTPPORT, /* port to send with the FTP PORT command */ - STRING_KEY, /* private key file name */ - STRING_KEY_PROXY, /* private key file name */ - STRING_KEY_PASSWD, /* plain text private key password */ - STRING_KEY_PASSWD_PROXY, /* plain text private key password */ - STRING_KEY_TYPE, /* format for private key (default: PEM) */ - STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ +#endif +#if defined(HAVE_GSSAPI) STRING_KRB_LEVEL, /* krb security level */ +#endif +#ifndef CURL_DISABLE_NETRC STRING_NETRC_FILE, /* if not NULL, use this instead of trying to find $HOME/.netrc */ +#endif +#ifndef CURL_DISABLE_PROXY STRING_PROXY, /* proxy to use */ STRING_PRE_PROXY, /* pre socks proxy to use */ +#endif STRING_SET_RANGE, /* range, if used */ STRING_SET_REFERER, /* custom string for the HTTP referer field */ STRING_SET_URL, /* what original URL to work on */ - STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAFILE, /* certificate file to verify peer against */ - STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ - STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ - STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ - STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ - STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ STRING_USERAGENT, /* User-Agent string */ - STRING_SSL_CRLFILE, /* crl file to check certificate */ - STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ - STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ - STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ STRING_SSL_ENGINE, /* name of ssl engine */ STRING_USERNAME, /* , if used */ STRING_PASSWORD, /* , if used */ STRING_OPTIONS, /* , if used */ +#ifndef CURL_DISABLE_PROXY STRING_PROXYUSERNAME, /* Proxy , if used */ STRING_PROXYPASSWORD, /* Proxy , if used */ STRING_NOPROXY, /* List of hosts which should not use the proxy, if used */ +#endif +#ifndef CURL_DISABLE_RTSP STRING_RTSP_SESSION_ID, /* Session ID to use */ STRING_RTSP_STREAM_URI, /* Stream URI for this request */ STRING_RTSP_TRANSPORT, /* Transport for this session */ +#endif +#ifdef USE_SSH STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ - STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ + STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ASCII hex */ STRING_SSH_HOST_PUBLIC_KEY_SHA256, /* sha256 of host public key in base64 */ - STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */ - STRING_PROXY_SERVICE_NAME, /* Proxy service name */ - STRING_SERVICE_NAME, /* Service name */ + STRING_SSH_KNOWNHOSTS, /* filename of knownhosts file */ +#endif +#ifndef CURL_DISABLE_SMTP STRING_MAIL_FROM, STRING_MAIL_AUTH, +#endif +#ifdef USE_TLS_SRP STRING_TLSAUTH_USERNAME, /* TLS auth */ - STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth */ STRING_TLSAUTH_PASSWORD, /* TLS auth */ +#ifndef CURL_DISABLE_PROXY + STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth */ STRING_TLSAUTH_PASSWORD_PROXY, /* TLS auth */ +#endif +#endif STRING_BEARER, /* , if used */ +#ifdef USE_UNIX_SOCKETS STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */ +#endif STRING_TARGET, /* CURLOPT_REQUEST_TARGET */ +#ifndef CURL_DISABLE_DOH STRING_DOH, /* CURLOPT_DOH_URL */ +#endif +#ifndef CURL_DISABLE_ALTSVC STRING_ALTSVC, /* CURLOPT_ALTSVC */ +#endif +#ifndef CURL_DISABLE_HSTS STRING_HSTS, /* CURLOPT_HSTS */ +#endif STRING_SASL_AUTHZID, /* CURLOPT_SASL_AUTHZID */ +#ifdef USE_ARES STRING_DNS_SERVERS, STRING_DNS_INTERFACE, STRING_DNS_LOCAL_IP4, STRING_DNS_LOCAL_IP6, +#endif STRING_SSL_EC_CURVES, +#ifndef CURL_DISABLE_AWS STRING_AWS_SIGV4, /* Parameters for V4 signature */ +#endif +#ifndef CURL_DISABLE_PROXY + STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ +#endif + STRING_ECH_CONFIG, /* CURLOPT_ECH_CONFIG */ + STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */ + STRING_SSL_SIGNATURE_ALGORITHMS, /* CURLOPT_SSL_SIGNATURE_ALGORITHMS */ /* -- end of null-terminated strings -- */ @@ -1577,20 +1457,18 @@ enum dupstring { enum dupblob { BLOB_CERT, - BLOB_CERT_PROXY, BLOB_KEY, - BLOB_KEY_PROXY, BLOB_SSL_ISSUERCERT, - BLOB_SSL_ISSUERCERT_PROXY, BLOB_CAINFO, +#ifndef CURL_DISABLE_PROXY + BLOB_CERT_PROXY, + BLOB_KEY_PROXY, + BLOB_SSL_ISSUERCERT_PROXY, BLOB_CAINFO_PROXY, +#endif BLOB_LAST }; -/* callback that gets called when this easy handle is completed within a multi - handle. Only used for internally created transfers, like for example - DoH. */ -typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result); struct UserDefined { FILE *err; /* the stderr user data goes here */ @@ -1599,21 +1477,15 @@ struct UserDefined { void *out; /* CURLOPT_WRITEDATA */ void *in_set; /* CURLOPT_READDATA */ void *writeheader; /* write the header to this if non-NULL */ - unsigned short use_port; /* which port to use (when not using default) */ unsigned long httpauth; /* kind of HTTP authentication to use (bitmask) */ unsigned long proxyauth; /* kind of proxy authentication to use (bitmask) */ long maxredirs; /* maximum no. of http(s) redirects to follow, set to -1 for infinity */ - void *postfields; /* if POST, set the fields' values here */ 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) */ - unsigned short localport; /* local port number to bind to */ - unsigned short localportrange; /* number of additional port numbers to test - in case the 'localport' one can't be - bind()ed */ curl_write_callback fwrite_func; /* function that stores the output */ curl_write_callback fwrite_header; /* function that stores headers */ curl_write_callback fwrite_rtp; /* function that stores interleaved RTP */ @@ -1635,13 +1507,7 @@ struct UserDefined { void *prereq_userp; /* pre-initial request user data */ void *seek_client; /* pointer to pass to the seek callback */ -#ifndef CURL_DISABLE_COOKIES - struct curl_slist *cookielist; /* list of cookie files set by - curl_easy_setopt(COOKIEFILE) calls */ -#endif #ifndef CURL_DISABLE_HSTS - struct curl_slist *hstslist; /* list of HSTS files set by - curl_easy_setopt(HSTS) calls */ curl_hstsread_callback hsts_read; void *hsts_read_userp; curl_hstswrite_callback hsts_write; @@ -1649,10 +1515,6 @@ struct UserDefined { #endif void *progress_client; /* pointer to pass to the progress callback */ void *ioctl_client; /* pointer to pass to the ioctl callback */ - unsigned int timeout; /* ms, 0 means no timeout */ - unsigned int connecttimeout; /* ms, 0 means no timeout */ - unsigned int happy_eyeballs_timeout; /* ms, 0 is a valid value */ - unsigned int server_response_timeout; /* ms, 0 means no timeout */ long maxage_conn; /* in seconds, max idle time to allow a connection that is to be reused */ long maxlifetime_conn; /* in seconds, max time since creation to allow a @@ -1669,7 +1531,9 @@ struct UserDefined { curl_off_t set_resume_from; /* continue [ftp] transfer from here */ struct curl_slist *headers; /* linked list of extra headers */ struct curl_httppost *httppost; /* linked list of old POST data */ +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) curl_mimepart mimepost; /* MIME/POST data. */ +#endif #ifndef CURL_DISABLE_TELNET struct curl_slist *telnet_options; /* linked list of telnet options */ #endif @@ -1678,10 +1542,6 @@ 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 */ - unsigned char timecondition; /* kind of time comparison: curl_TimeCond */ - unsigned char method; /* what kind of HTTP request: Curl_HttpReq */ - unsigned char httpwant; /* when non-zero, a specific HTTP version requested - to be used in the library's request(s) */ struct ssl_config_data ssl; /* user defined SSL stuff */ #ifndef CURL_DISABLE_PROXY struct ssl_config_data proxy_ssl; /* user defined SSL stuff for proxy */ @@ -1701,24 +1561,17 @@ struct UserDefined { #ifndef CURL_DISABLE_HTTP struct curl_slist *http200aliases; /* linked list of aliases for http200 */ #endif - unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header - file 0 - whatever, 1 - v2, 2 - v6 */ curl_off_t max_filesize; /* Maximum file size to download */ #ifndef CURL_DISABLE_FTP + unsigned int accepttimeout; /* in milliseconds, 0 means no timeout */ unsigned char ftp_filemethod; /* how to get to a file: curl_ftpfile */ unsigned char ftpsslauth; /* what AUTH XXX to try: curl_ftpauth */ unsigned char ftp_ccc; /* FTP CCC options: curl_ftpccc */ - unsigned int accepttimeout; /* in milliseconds, 0 means no timeout */ #endif #if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) struct curl_slist *quote; /* after connection is established */ struct curl_slist *postquote; /* after the transfer */ struct curl_slist *prequote; /* before the transfer, after type */ - /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP - 1 - create directories that don't exist - 2 - the same but also allow MKD to fail once - */ - unsigned char ftp_create_missing_dirs; #endif #ifdef USE_LIBSSH2 curl_sshhostkeycallback ssh_hostkeyfunc; /* hostkey check callback */ @@ -1729,21 +1582,15 @@ struct UserDefined { void *ssh_keyfunc_userp; /* custom pointer to callback */ int ssh_auth_types; /* allowed SSH auth types */ unsigned int new_directory_perms; /* when creating remote dirs */ -#endif -#ifndef CURL_DISABLE_NETRC - unsigned char use_netrc; /* enum CURL_NETRC_OPTION values */ #endif unsigned int new_file_perms; /* when creating remote files */ char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ struct curl_blob *blobs[BLOB_LAST]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int scope_id; /* Scope id for IPv6 */ #endif curl_prot_t allowed_protocols; curl_prot_t redir_protocols; -#ifndef CURL_DISABLE_MIME - unsigned int mime_options; /* Mime option flags. */ -#endif #ifndef CURL_DISABLE_RTSP void *rtp_out; /* write RTP to this if non-NULL */ /* Common RTSP header options */ @@ -1759,14 +1606,15 @@ struct UserDefined { void *fnmatch_data; void *wildcardptr; #endif - /* GSS-API credential delegation, see the documentation of - CURLOPT_GSSAPI_DELEGATION */ - unsigned char gssapi_delegation; + unsigned int timeout; /* ms, 0 means no timeout */ + unsigned int connecttimeout; /* ms, 0 means default timeout */ + unsigned int happy_eyeballs_timeout; /* ms, 0 is a valid value */ + unsigned int server_response_timeout; /* ms, 0 means no timeout */ + unsigned int shutdowntimeout; /* ms, 0 means default timeout */ int tcp_keepidle; /* seconds in idle before sending keepalive probe */ int tcp_keepintvl; /* seconds between TCP keepalive probes */ - - size_t maxconnects; /* Max idle connections in the connection cache */ + int tcp_keepcnt; /* maximum number of keepalive probes */ long expect_100_timeout; /* in milliseconds */ #if defined(USE_HTTP2) || defined(USE_HTTP3) @@ -1776,47 +1624,91 @@ struct UserDefined { before resolver start */ void *resolver_start_client; /* pointer to pass to resolver start callback */ long upkeep_interval_ms; /* Time between calls for connection upkeep. */ - multidone_func fmultidone; -#ifndef CURL_DISABLE_DOH - struct Curl_easy *dohfor; /* this is a DoH request for that transfer */ -#endif CURLU *uh; /* URL handle for the current parsed URL */ #ifndef CURL_DISABLE_HTTP void *trailer_data; /* pointer to pass to trailer data callback */ curl_trailer_callback trailer_callback; /* trailing data callback */ #endif +#ifndef CURL_DISABLE_SMTP + struct curl_slist *mail_rcpt; /* linked list of mail recipients */ +#endif + unsigned int maxconnects; /* Max idle connections in the connection cache */ +#ifdef USE_ECH + int tls_ech; /* TLS ECH configuration */ +#endif + unsigned short use_port; /* which port to use (when not using default) */ +#ifndef CURL_DISABLE_BINDLOCAL + unsigned short localport; /* local port number to bind to */ + unsigned short localportrange; /* number of additional port numbers to test + in case the 'localport' one cannot be + bind()ed */ +#endif +#ifndef CURL_DISABLE_NETRC + unsigned char use_netrc; /* enum CURL_NETRC_OPTION values */ +#endif +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP + 1 - create directories that do not exist + 2 - the same but also allow MKD to fail once + */ + unsigned char ftp_create_missing_dirs; +#endif + unsigned char use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or + IMAP or POP3 or others! (type: curl_usessl)*/ char keep_post; /* keep POSTs as POSTs after a 30x request; each bit represents a request, from 301 to 303 */ + unsigned char timecondition; /* kind of time comparison: curl_TimeCond */ + unsigned char method; /* what kind of HTTP request: Curl_HttpReq */ + unsigned char httpwant; /* when non-zero, a specific HTTP version requested + to be used in the library's request(s) */ + unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header + file 0 - whatever, 1 - v2, 2 - v6 */ + unsigned char upload_flags; /* flags set by CURLOPT_UPLOAD_FLAGS */ +#ifdef HAVE_GSSAPI + /* GSS-API credential delegation, see the documentation of + CURLOPT_GSSAPI_DELEGATION */ + unsigned char gssapi_delegation; +#endif + unsigned char http_follow_mode; /* follow HTTP redirects */ + BIT(connect_only); /* make connection/request, then let application use the + socket */ + BIT(connect_only_ws); /* special websocket connect-only level */ #ifndef CURL_DISABLE_SMTP - struct curl_slist *mail_rcpt; /* linked list of mail recipients */ BIT(mail_rcpt_allowfails); /* allow RCPT TO command to fail for some recipients */ #endif - unsigned char use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or - IMAP or POP3 or others! (type: curl_usessl)*/ - unsigned char connect_only; /* make connection/request, then let - application use the socket */ +#ifndef CURL_DISABLE_MIME + BIT(mime_formescape); +#endif BIT(is_fread_set); /* has read callback been set to non-NULL? */ #ifndef CURL_DISABLE_TFTP BIT(tftp_no_options); /* do not send TFTP options requests */ #endif BIT(sep_headers); /* handle host and proxy headers separately */ +#ifndef CURL_DISABLE_COOKIES BIT(cookiesession); /* new cookie session? */ +#endif BIT(crlf); /* convert crlf on ftp upload(?) */ +#ifdef USE_SSH BIT(ssh_compression); /* enable SSH compression */ +#endif /* Here follows boolean settings that define how to behave during this session. They are STATIC, set by libcurl users or at least initially - and they don't change during operations. */ + and they do not change during operations. */ BIT(quick_exit); /* set 1L when it is okay to leak things (like - threads), as we're about to exit() anyway and - don't want lengthy cleanups to delay termination, + threads), as we are about to exit() anyway and + do not want lengthy cleanups to delay termination, e.g. after a DNS timeout */ BIT(get_filetime); /* get the time and get of the remote file */ +#ifndef CURL_DISABLE_PROXY BIT(tunnel_thru_httpproxy); /* use CONNECT through an HTTP proxy */ +#endif BIT(prefer_ascii); /* ASCII rather than binary */ BIT(remote_append); /* append, not overwrite, on upload */ +#ifdef CURL_LIST_ONLY_PROTOCOL BIT(list_only); /* list directory */ +#endif #ifndef CURL_DISABLE_FTP BIT(ftp_use_port); /* use the FTP PORT command */ BIT(ftp_use_epsv); /* if EPSV is to be attempted or not */ @@ -1826,10 +1718,8 @@ struct UserDefined { us */ BIT(wildcard_enabled); /* enable wildcard matching */ #endif - BIT(hide_progress); /* don't use the progress meter */ BIT(http_fail_on_error); /* fail on HTTP error codes >= 400 */ BIT(http_keep_sending_on_error); /* for HTTP status codes >= 300 */ - BIT(http_follow_location); /* follow HTTP redirects */ BIT(http_transfer_encoding); /* request compressed HTTP transfer-encoding */ BIT(allow_auth_to_other_hosts); BIT(include_header); /* include received protocol headers in data output */ @@ -1838,9 +1728,11 @@ struct UserDefined { location: */ BIT(opt_no_body); /* as set with CURLOPT_NOBODY */ BIT(verbose); /* output verbosity */ +#if defined(HAVE_GSSAPI) BIT(krb); /* Kerberos connection requested */ +#endif BIT(reuse_forbid); /* forbidden to be reused, close after use */ - BIT(reuse_fresh); /* do not re-use an existing connection */ + BIT(reuse_fresh); /* do not reuse an existing connection */ BIT(no_signal); /* do not use any signal/alarm handler */ BIT(tcp_nodelay); /* whether to enable TCP_NODELAY or not */ BIT(ignorecl); /* ignore content length */ @@ -1863,10 +1755,14 @@ struct UserDefined { BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers from user callbacks */ BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */ +#ifndef CURL_DISABLE_PROXY BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1 header */ +#endif +#ifdef USE_UNIX_SOCKETS BIT(abstract_unix_socket); - BIT(disallow_username_in_url); /* disallow username in url */ +#endif + BIT(disallow_username_in_url); /* disallow username in URL */ #ifndef CURL_DISABLE_DOH BIT(doh); /* DNS-over-HTTPS enabled */ BIT(doh_verifypeer); /* DoH certificate peer verification */ @@ -1874,19 +1770,23 @@ struct UserDefined { BIT(doh_verifystatus); /* DoH certificate status verification */ #endif BIT(http09_allowed); /* allow HTTP/0.9 responses */ -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS BIT(ws_raw_mode); + BIT(ws_no_auto_pong); #endif }; -struct Names { - struct Curl_hash *hostcache; - enum { - HCACHE_NONE, /* not pointing to anything */ - HCACHE_MULTI, /* points to a shared one in the multi handle */ - HCACHE_SHARED /* points to a shared one in a shared object */ - } hostcachetype; -}; +#ifndef CURL_DISABLE_MIME +#define IS_MIME_POST(a) ((a)->set.mimepost.kind != MIMEKIND_NONE) +#else +#define IS_MIME_POST(a) FALSE +#endif + +/* callback that gets called when a sub easy (data->master_mid set) is + DONE. Called on the master easy. */ +typedef void multi_sub_xfer_done_cb(struct Curl_easy *master_easy, + struct Curl_easy *sub_easy, + CURLcode result); /* * The 'connectdata' struct MUST have all the connection oriented stuff as we @@ -1902,31 +1802,27 @@ struct Curl_easy { /* First a simple identifier to easier detect if a user mix up this easy handle with a multi handle. Set this to CURLEASY_MAGIC_NUMBER */ unsigned int magic; - - /* first, two fields for the linked list of these */ - struct Curl_easy *next; - struct Curl_easy *prev; + /* 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 + tracking in log output. This may wrap around after LONG_MAX to 0 again, + so it has no uniqueness guarantee for large processings. Note: it has no + uniqueness either IFF more than one connection pool is used by the + libcurl application. */ + curl_off_t id; + /* once an easy handle is added to a multi, either explicitly by the + * libcurl application or implicitly during `curl_easy_perform()`, + * a unique identifier inside this one multi instance. */ + unsigned int mid; + unsigned int master_mid; /* if set, this transfer belongs to a master */ + multi_sub_xfer_done_cb *sub_xfer_done; struct connectdata *conn; - struct Curl_llist_element connect_queue; /* for the pending and msgsent - lists */ - struct Curl_llist_element conn_queue; /* list per connectdata */ CURLMstate mstate; /* the handle's state */ CURLcode result; /* previous result */ struct Curl_message msg; /* A single posted message. */ - /* Array with the plain socket numbers this handle takes care of, in no - particular order. Note that all sockets are added to the sockhash, where - the state etc are also kept. This array is mostly used to detect when a - socket is to be removed from the hash. See singlesocket(). */ - curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; - unsigned char actions[MAX_SOCKSPEREASYHANDLE]; /* action for each socket in - sockets[] */ - int numsocks; - - struct Names dns; struct Curl_multi *multi; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the multi interface */ @@ -1934,6 +1830,13 @@ struct Curl_easy { struct to which this "belongs" when used by the easy interface */ struct Curl_share *share; /* Share, handles global variable mutexing */ + + /* `meta_hash` is a general key-value store for implementations + * with the lifetime of the easy handle. + * Elements need to be added with their own destructor to be invoked when + * the easy handle is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash meta_hash; + #ifdef USE_LIBPSL struct PslCache *psl; /* The associated PSL cache. */ #endif @@ -1960,9 +1863,6 @@ struct Curl_easy { struct PureInfo info; /* stats, reports and info data */ struct curl_tlssessioninfo tsi; /* Information about the TLS session, only valid after a client has asked for it */ -#ifdef USE_HYPER - struct hyptransfer hyp; -#endif }; #define LIBCURL_NAME "libcurl" diff --git a/Utilities/cmcurl/lib/vauth/cleartext.c b/Utilities/cmcurl/lib/vauth/cleartext.c index c651fc51453..719abd19606 100644 --- a/Utilities/cmcurl/lib/vauth/cleartext.c +++ b/Utilities/cmcurl/lib/vauth/cleartext.c @@ -25,25 +25,23 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_POP3) || \ (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include -#include "urldata.h" +#include "../urldata.h" -#include "vauth/vauth.h" -#include "curl_md5.h" -#include "warnless.h" -#include "strtok.h" -#include "sendf.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curlx/warnless.h" +#include "../sendf.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_create_plain_message() @@ -101,39 +99,38 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, * Curl_auth_create_login_message() * * This is used to generate an already encoded LOGIN message containing the - * user name or password ready for sending to the recipient. + * username or password ready for sending to the recipient. * * Parameters: * - * valuep [in] - The user name or user's password. + * valuep [in] - The username or user's password. * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_login_message(const char *valuep, struct bufref *out) +void Curl_auth_create_login_message(const char *valuep, struct bufref *out) { Curl_bufref_set(out, valuep, strlen(valuep), NULL); - return CURLE_OK; } /* * Curl_auth_create_external_message() * * This is used to generate an already encoded EXTERNAL message containing - * the user name ready for sending to the recipient. + * the username ready for sending to the recipient. * * Parameters: * - * user [in] - The user name. + * user [in] - The username. * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_external_message(const char *user, +void Curl_auth_create_external_message(const char *user, struct bufref *out) { /* This is the same formatting as the login message */ - return Curl_auth_create_login_message(user, out); + Curl_auth_create_login_message(user, out); } #endif /* if no users */ diff --git a/Utilities/cmcurl/lib/vauth/cram.c b/Utilities/cmcurl/lib/vauth/cram.c index 5894ed4bcfe..3586e1012d0 100644 --- a/Utilities/cmcurl/lib/vauth/cram.c +++ b/Utilities/cmcurl/lib/vauth/cram.c @@ -24,22 +24,22 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#ifndef CURL_DISABLE_DIGEST_AUTH #include -#include "urldata.h" +#include "../urldata.h" -#include "vauth/vauth.h" -#include "curl_hmac.h" -#include "curl_md5.h" -#include "warnless.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curl_hmac.h" +#include "../curl_md5.h" +#include "../curlx/warnless.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* @@ -51,7 +51,7 @@ * Parameters: * * chlg [in] - The challenge. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * out [out] - The result storage. * @@ -67,7 +67,7 @@ CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, char *response; /* Compute the digest using the password as the key */ - ctxt = Curl_HMAC_init(Curl_HMAC_MD5, + ctxt = Curl_HMAC_init(&Curl_HMAC_MD5, (const unsigned char *) passwdp, curlx_uztoui(strlen(passwdp))); if(!ctxt) @@ -94,4 +94,4 @@ CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, return CURLE_OK; } -#endif /* !CURL_DISABLE_CRYPTO_AUTH */ +#endif /* !CURL_DISABLE_DIGEST_AUTH */ diff --git a/Utilities/cmcurl/lib/vauth/digest.c b/Utilities/cmcurl/lib/vauth/digest.c index fda2d911f7b..ec4e82256c4 100644 --- a/Utilities/cmcurl/lib/vauth/digest.c +++ b/Utilities/cmcurl/lib/vauth/digest.c @@ -25,30 +25,32 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#ifndef CURL_DISABLE_DIGEST_AUTH #include -#include "vauth/vauth.h" -#include "vauth/digest.h" -#include "urldata.h" -#include "curl_base64.h" -#include "curl_hmac.h" -#include "curl_md5.h" -#include "curl_sha256.h" -#include "vtls/vtls.h" -#include "warnless.h" -#include "strtok.h" -#include "strcase.h" -#include "curl_printf.h" -#include "rand.h" +#include "vauth.h" +#include "digest.h" +#include "../urldata.h" +#include "../curlx/base64.h" +#include "../curl_hmac.h" +#include "../curl_md5.h" +#include "../curl_sha256.h" +#include "../curl_sha512_256.h" +#include "../vtls/vtls.h" +#include "../curlx/warnless.h" +#include "../curlx/strparse.h" +#include "../strcase.h" +#include "../curl_printf.h" +#include "../rand.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" +#ifndef USE_WINDOWS_SSPI #define SESSION_ALGO 1 /* for algos with this bit set */ #define ALGO_MD5 0 @@ -58,7 +60,6 @@ #define ALGO_SHA512_256 4 #define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO) -#if !defined(USE_WINDOWS_SSPI) #define DIGEST_QOP_VALUE_AUTH (1 << 0) #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1) #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2) @@ -102,7 +103,7 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, case ',': if(!starts_with_quote) { - /* This signals the end of the content if we didn't get a starting + /* This signals the end of the content if we did not get a starting quote and then we do "sloppy" parsing */ c = 0; /* the end */ continue; @@ -125,7 +126,6 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, } else return FALSE; - break; } } @@ -141,8 +141,8 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, return TRUE; } -#if !defined(USE_WINDOWS_SSPI) -/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */ +#ifndef USE_WINDOWS_SSPI +/* Convert MD5 chunk to RFC2617 (section 3.1.3) -suitable ASCII string */ static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ unsigned char *dest) /* 33 bytes */ { @@ -151,7 +151,7 @@ static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]); } -/* Convert sha256 chunk to RFC7616 -suitable ascii string */ +/* Convert sha256 or SHA-512/256 chunk to RFC7616 -suitable ASCII string */ static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */ unsigned char *dest) /* 65 bytes */ { @@ -165,7 +165,7 @@ static char *auth_digest_string_quoted(const char *source) { char *dest; const char *s = source; - size_t n = 1; /* null terminator */ + size_t n = 1; /* null-terminator */ /* Calculate size needed */ while(*s) { @@ -219,33 +219,21 @@ static bool auth_digest_get_key_value(const char *chlg, static CURLcode auth_digest_get_qop_values(const char *options, int *value) { - char *tmp; - char *token; - char *tok_buf = NULL; - + struct Curl_str out; /* Initialise the output */ *value = 0; - /* Tokenise the list of qop values. Use a temporary clone of the buffer since - strtok_r() ruins it. */ - tmp = strdup(options); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - - token = strtok_r(tmp, ",", &tok_buf); - while(token) { - if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) + while(!curlx_str_until(&options, &out, 32, ',')) { + if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) *value |= DIGEST_QOP_VALUE_AUTH; - else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) + else if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH_INT)) *value |= DIGEST_QOP_VALUE_AUTH_INT; - else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) + else if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) *value |= DIGEST_QOP_VALUE_AUTH_CONF; - - token = strtok_r(NULL, ",", &tok_buf); + if(curlx_str_single(&options, ',')) + break; } - free(tmp); - return CURLE_OK; } @@ -288,7 +276,7 @@ static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref, /* Retrieve realm string from the challenge */ if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) { /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ - strcpy(realm, ""); + *realm = '\0'; } /* Retrieve algorithm string from the challenge */ @@ -326,7 +314,7 @@ bool Curl_auth_is_digest_supported(void) * * data [in] - The session handle. * chlg [in] - The challenge message. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. * out [out] - The result storage. @@ -388,7 +376,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, return result; /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; @@ -402,7 +390,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, curlx_uztoui(strlen(passwdp))); Curl_MD5_final(ctxt, digest); - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; @@ -420,12 +408,12 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); /* Generate our SPN */ - spn = Curl_auth_build_spn(service, realm, NULL); + spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); if(!spn) return CURLE_OUT_OF_MEMORY; /* Calculate H(A2) */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) { free(spn); @@ -443,7 +431,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); /* Now calculate the response hash */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) { free(spn); @@ -504,10 +492,6 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, struct digestdata *digest) { bool before = FALSE; /* got a nonce before */ - bool foundAuth = FALSE; - bool foundAuthInt = FALSE; - char *token = NULL; - char *tmp = NULL; /* If we already have received a nonce, keep that in mind */ if(digest->nonce) @@ -551,29 +535,25 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, return CURLE_OUT_OF_MEMORY; } else if(strcasecompare(value, "qop")) { - char *tok_buf = NULL; - /* Tokenize the list and choose auth if possible, use a temporary - clone of the buffer since strtok_r() ruins it */ - tmp = strdup(content); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - - token = strtok_r(tmp, ",", &tok_buf); - while(token) { - /* Pass additional spaces here */ - while(*token && ISBLANK(*token)) - token++; - if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) { + const char *token = content; + struct Curl_str out; + bool foundAuth = FALSE; + bool foundAuthInt = FALSE; + /* Pass leading spaces */ + while(*token && ISBLANK(*token)) + token++; + while(!curlx_str_until(&token, &out, 32, ',')) { + if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) foundAuth = TRUE; - } - else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) { + else if(curlx_str_casecompare(&out, + DIGEST_QOP_VALUE_STRING_AUTH_INT)) foundAuthInt = TRUE; - } - token = strtok_r(NULL, ",", &tok_buf); + if(curlx_str_single(&token, ',')) + break; + while(*token && ISBLANK(*token)) + token++; } - free(tmp); - /* Select only auth or auth-int. Otherwise, ignore */ if(foundAuth) { free(digest->qop); @@ -602,10 +582,20 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, digest->algo = ALGO_SHA256; else if(strcasecompare(content, "SHA-256-SESS")) digest->algo = ALGO_SHA256SESS; - else if(strcasecompare(content, "SHA-512-256")) + else if(strcasecompare(content, "SHA-512-256")) { +#ifdef CURL_HAVE_SHA512_256 digest->algo = ALGO_SHA512_256; - else if(strcasecompare(content, "SHA-512-256-SESS")) +#else /* ! CURL_HAVE_SHA512_256 */ + return CURLE_NOT_BUILT_IN; +#endif /* ! CURL_HAVE_SHA512_256 */ + } + else if(strcasecompare(content, "SHA-512-256-SESS")) { +#ifdef CURL_HAVE_SHA512_256 digest->algo = ALGO_SHA512_256SESS; +#else /* ! CURL_HAVE_SHA512_256 */ + return CURLE_NOT_BUILT_IN; +#endif /* ! CURL_HAVE_SHA512_256 */ + } else return CURLE_BAD_CONTENT_ENCODING; } @@ -619,7 +609,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, } } else - break; /* We're done here */ + break; /* We are done here */ /* Pass all additional spaces here */ while(*chlg && ISBLANK(*chlg)) @@ -636,7 +626,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, if(before && !digest->stale) return CURLE_BAD_CONTENT_ENCODING; - /* We got this header without a nonce, that's a bad Digest line! */ + /* We got this header without a nonce, that is a bad Digest line! */ if(!digest->nonce) return CURLE_BAD_CONTENT_ENCODING; @@ -656,7 +646,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. @@ -699,14 +689,18 @@ static CURLcode auth_create_digest_http_message( digest->nc = 1; if(!digest->cnonce) { - char cnoncebuf[33]; - result = Curl_rand_hex(data, (unsigned char *)cnoncebuf, - sizeof(cnoncebuf)); + char cnoncebuf[12]; + result = Curl_rand_bytes(data, +#ifdef DEBUGBUILD + TRUE, +#endif + (unsigned char *)cnoncebuf, + sizeof(cnoncebuf)); if(result) return result; - result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf), - &cnonce, &cnonce_sz); + result = curlx_base64_encode(cnoncebuf, sizeof(cnoncebuf), + &cnonce, &cnonce_sz); if(result) return result; @@ -718,8 +712,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, (unsigned char *)userh); } @@ -739,8 +735,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, ha1); if(digest->algo & SESSION_ALGO) { @@ -749,8 +747,10 @@ static CURLcode auth_create_digest_http_message( if(!tmp) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); + result = hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); free(tmp); + if(result) + return result; convert_to_ascii(hashbuf, ha1); } @@ -772,11 +772,15 @@ static CURLcode auth_create_digest_http_message( return CURLE_OUT_OF_MEMORY; if(digest->qop && strcasecompare(digest->qop, "auth-int")) { - /* We don't support auth-int for PUT or POST */ + /* We do not support auth-int for PUT or POST */ char hashed[65]; char *hashthis2; - hash(hashbuf, (const unsigned char *)"", 0); + result = hash(hashbuf, (const unsigned char *)"", 0); + if(result) { + free(hashthis); + return result; + } convert_to_ascii(hashbuf, (unsigned char *)hashed); hashthis2 = aprintf("%s:%s", hashthis, hashed); @@ -787,8 +791,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, ha2); if(digest->qop) { @@ -802,8 +808,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, request_digest); /* For test case 64 (snooped from a Mozilla 1.3a request) @@ -811,12 +819,12 @@ static CURLcode auth_create_digest_http_message( Authorization: Digest username="testuser", realm="testrealm", \ nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" - Digest parameters are all quoted strings. Username which is provided by + Digest parameters are all quoted strings. Username which is provided by the user will need double quotes and backslashes within it escaped. realm, nonce, and opaque will need backslashes as well as they were - de-escaped when copied from request header. cnonce is generated with - web-safe characters. uri is already percent encoded. nc is 8 hex - characters. algorithm and qop with standard values only contain web-safe + de-escaped when copied from request header. cnonce is generated with + web-safe characters. uri is already percent encoded. nc is 8 hex + characters. algorithm and qop with standard values only contain web-safe characters. */ userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp); @@ -933,7 +941,7 @@ static CURLcode auth_create_digest_http_message( * Parameters: * * data [in] - The session handle. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. @@ -958,12 +966,24 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, outptr, outlen, auth_digest_md5_to_ascii, Curl_md5it); - DEBUGASSERT(digest->algo <= ALGO_SHA512_256SESS); - return auth_create_digest_http_message(data, userp, passwdp, - request, uripath, digest, - outptr, outlen, - auth_digest_sha256_to_ascii, - Curl_sha256it); + + if(digest->algo <= ALGO_SHA256SESS) + return auth_create_digest_http_message(data, userp, passwdp, + request, uripath, digest, + outptr, outlen, + auth_digest_sha256_to_ascii, + Curl_sha256it); +#ifdef CURL_HAVE_SHA512_256 + if(digest->algo <= ALGO_SHA512_256SESS) + return auth_create_digest_http_message(data, userp, passwdp, + request, uripath, digest, + outptr, outlen, + auth_digest_sha256_to_ascii, + Curl_sha512_256it); +#endif /* CURL_HAVE_SHA512_256 */ + + /* Should be unreachable */ + return CURLE_BAD_CONTENT_ENCODING; } /* @@ -992,4 +1012,4 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) } #endif /* !USE_WINDOWS_SSPI */ -#endif /* CURL_DISABLE_CRYPTO_AUTH */ +#endif /* !CURL_DISABLE_DIGEST_AUTH */ diff --git a/Utilities/cmcurl/lib/vauth/digest.h b/Utilities/cmcurl/lib/vauth/digest.h index 68fdb28c472..99ce1f91389 100644 --- a/Utilities/cmcurl/lib/vauth/digest.h +++ b/Utilities/cmcurl/lib/vauth/digest.h @@ -26,7 +26,7 @@ #include -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#ifndef CURL_DISABLE_DIGEST_AUTH #define DIGEST_MAX_VALUE_LENGTH 256 #define DIGEST_MAX_CONTENT_LENGTH 1024 diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c index 8fb86693936..2761c599650 100644 --- a/Utilities/cmcurl/lib/vauth/digest_sspi.c +++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c @@ -25,25 +25,25 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_CRYPTO_AUTH) +#if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_DIGEST_AUTH) #include -#include "vauth/vauth.h" -#include "vauth/digest.h" -#include "urldata.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" -#include "strdup.h" -#include "strcase.h" -#include "strerror.h" +#include "vauth.h" +#include "digest.h" +#include "../urldata.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" +#include "../strdup.h" +#include "../strcase.h" +#include "../strerror.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_digest_supported() @@ -60,15 +60,17 @@ bool Curl_auth_is_digest_supported(void) SECURITY_STATUS status; /* Query the security package for Digest */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + status = + Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return status == SEC_E_OK; } /* @@ -81,7 +83,7 @@ bool Curl_auth_is_digest_supported(void) * * data [in] - The session handle. * chlg [in] - The challenge message. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. * out [out] - The result storage. @@ -119,17 +121,19 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, } /* Query the security package for DigestSSP */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + status = + Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ output_token = malloc(token_max); @@ -160,11 +164,11 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, p_identity = NULL; /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT(SP_NAME_DIGEST), - SECPKG_CRED_OUTBOUND, NULL, - p_identity, NULL, NULL, - &credentials, &expiry); + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + SECPKG_CRED_OUTBOUND, NULL, + p_identity, NULL, NULL, + &credentials, &expiry); if(status != SEC_E_OK) { Curl_sspi_free_identity(p_identity); @@ -178,7 +182,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; - chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg); + chlg_buf.pvBuffer = CURL_UNCONST(Curl_bufref_ptr(chlg)); chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); /* Setup the response "output" security buffer */ @@ -190,20 +194,20 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(token_max); /* Generate our response message */ - status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, + status = Curl_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, 0, 0, 0, &chlg_desc, 0, &context, &resp_desc, &attrs, &expiry); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + Curl_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { #if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; #endif - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(spn); free(output_token); @@ -211,8 +215,10 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) infof(data, "schannel: InitializeSecurityContext failed: %s", Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif return CURLE_AUTH_ERROR; } @@ -221,8 +227,8 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, Curl_bufref_set(out, output_token, resp_buf.cbBuffer, curl_free); /* Free our handles */ - s_pSecFn->DeleteSecurityContext(&context); - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->DeleteSecurityContext(&context); + Curl_pSecFn->FreeCredentialsHandle(&credentials); /* Free the identity structure */ Curl_sspi_free_identity(p_identity); @@ -236,7 +242,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* * Curl_override_sspi_http_realm() * - * This is used to populate the domain in a SSPI identity structure + * This is used to populate the domain in an SSPI identity structure * The realm is extracted from the challenge message and used as the * domain if it is not already explicitly set. * @@ -267,7 +273,7 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, if(strcasecompare(value, "realm")) { /* Setup identity's domain and length */ - domain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *) content); + domain.tchar_ptr = curlx_convert_UTF8_to_tchar(content); if(!domain.tchar_ptr) return CURLE_OUT_OF_MEMORY; @@ -289,7 +295,7 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, } } else - break; /* We're done here */ + break; /* We are done here */ /* Pass all additional spaces here */ while(*chlg && ISBLANK(*chlg)) @@ -322,10 +328,10 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, { size_t chlglen = strlen(chlg); - /* We had an input token before so if there's another one now that means we - provided bad credentials in the previous request or it's stale. */ + /* We had an input token before so if there is another one now that means we + provided bad credentials in the previous request or it is stale. */ if(digest->input_token) { - bool stale = false; + bool stale = FALSE; const char *p = chlg; /* Check for the 'stale' directive */ @@ -341,7 +347,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, if(strcasecompare(value, "stale") && strcasecompare(content, "true")) { - stale = true; + stale = TRUE; break; } @@ -377,7 +383,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. @@ -408,17 +414,19 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, (void) data; /* Query the security package for DigestSSP */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + status = + Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate the output buffer according to the max token size as indicated by the security package */ @@ -434,7 +442,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, (userp && digest->user && Curl_timestrcmp(userp, digest->user)) || (passwdp && digest->passwd && Curl_timestrcmp(passwdp, digest->passwd))) { if(digest->http_context) { - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_pSecFn->DeleteSecurityContext(digest->http_context); Curl_safefree(digest->http_context); } Curl_safefree(digest->user); @@ -449,10 +457,10 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, chlg_buf[0].pvBuffer = NULL; chlg_buf[0].cbBuffer = 0; chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].pvBuffer = CURL_UNCONST(request); chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[2].pvBuffer = (void *) uripath; + chlg_buf[2].pvBuffer = CURL_UNCONST(uripath); chlg_buf[2].cbBuffer = curlx_uztoul(strlen((const char *) uripath)); chlg_buf[3].BufferType = SECBUFFER_PKG_PARAMS; chlg_buf[3].pvBuffer = NULL; @@ -461,13 +469,14 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, chlg_buf[4].pvBuffer = output_token; chlg_buf[4].cbBuffer = curlx_uztoul(token_max); - status = s_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, 0); + status = Curl_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, + 0); if(status == SEC_E_OK) output_token_len = chlg_buf[4].cbBuffer; else { /* delete the context so a new one can be made */ infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx", (long)status); - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_pSecFn->DeleteSecurityContext(digest->http_context); Curl_safefree(digest->http_context); } } @@ -527,11 +536,11 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, } /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT(SP_NAME_DIGEST), - SECPKG_CRED_OUTBOUND, NULL, - p_identity, NULL, NULL, - &credentials, &expiry); + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + SECPKG_CRED_OUTBOUND, NULL, + p_identity, NULL, NULL, + &credentials, &expiry); if(status != SEC_E_OK) { Curl_sspi_free_identity(p_identity); free(output_token); @@ -547,7 +556,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, chlg_buf[0].pvBuffer = digest->input_token; chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len); chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].pvBuffer = CURL_UNCONST(request); chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; chlg_buf[2].pvBuffer = NULL; @@ -561,9 +570,9 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, resp_buf.pvBuffer = output_token; resp_buf.cbBuffer = curlx_uztoul(token_max); - spn = curlx_convert_UTF8_to_tchar((char *) uripath); + spn = curlx_convert_UTF8_to_tchar((const char *) uripath); if(!spn) { - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); @@ -573,11 +582,15 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Allocate our new context handle */ digest->http_context = calloc(1, sizeof(CtxtHandle)); - if(!digest->http_context) + if(!digest->http_context) { + curlx_unicodefree(spn); + Curl_sspi_free_identity(p_identity); + free(output_token); return CURLE_OUT_OF_MEMORY; + } /* Generate our response message */ - status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, + status = Curl_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, ISC_REQ_USE_HTTP_STYLE, 0, 0, &chlg_desc, 0, @@ -587,13 +600,13 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + Curl_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { #if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; #endif - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); @@ -603,15 +616,17 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) infof(data, "schannel: InitializeSecurityContext failed: %s", Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif return CURLE_AUTH_ERROR; } output_token_len = resp_buf.cbBuffer; - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); } @@ -656,7 +671,7 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) /* Delete security context */ if(digest->http_context) { - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_pSecFn->DeleteSecurityContext(digest->http_context); Curl_safefree(digest->http_context); } @@ -665,4 +680,4 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) Curl_safefree(digest->passwd); } -#endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_CRYPTO_AUTH */ +#endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_DIGEST_AUTH */ diff --git a/Utilities/cmcurl/lib/vauth/gsasl.c b/Utilities/cmcurl/lib/vauth/gsasl.c index c7d0a8d3b21..3684c8f4b24 100644 --- a/Utilities/cmcurl/lib/vauth/gsasl.c +++ b/Utilities/cmcurl/lib/vauth/gsasl.c @@ -24,22 +24,22 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_GSASL #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "sendf.h" +#include "vauth.h" +#include "../urldata.h" +#include "../sendf.h" #include /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, const char *mech, @@ -59,7 +59,7 @@ bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, return FALSE; } - return true; + return TRUE; } CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c index 65eb3e1b508..b5590406175 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c @@ -25,22 +25,27 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(HAVE_GSSAPI) && defined(USE_KERBEROS5) #include -#include "vauth/vauth.h" -#include "curl_sasl.h" -#include "urldata.h" -#include "curl_gssapi.h" -#include "sendf.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curl_sasl.h" +#include "../urldata.h" +#include "../curl_gssapi.h" +#include "../sendf.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" + +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif /* * Curl_auth_is_gssapi_supported() @@ -65,10 +70,10 @@ bool Curl_auth_is_gssapi_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in[ - The host name. + * host [in[ - The hostname. * mutual_auth [in] - Flag specifying whether or not mutual authentication * is enabled. * chlg [in] - Optional challenge message. @@ -128,7 +133,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, infof(data, "GSSAPI handshake failure (empty challenge message)"); return CURLE_BAD_CONTENT_ENCODING; } - input_token.value = (void *) Curl_bufref_ptr(chlg); + input_token.value = CURL_UNCONST(Curl_bufref_ptr(chlg)); input_token.length = Curl_bufref_len(chlg); } @@ -158,7 +163,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, gss_release_buffer(&unused_status, &output_token); } else - Curl_bufref_set(out, mutual_auth? "": NULL, 0, NULL); + Curl_bufref_set(out, mutual_auth ? "": NULL, 0, NULL); return result; } @@ -205,7 +210,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, } /* Setup the challenge "input" security buffer */ - input_token.value = (void *) Curl_bufref_ptr(chlg); + input_token.value = CURL_UNCONST(Curl_bufref_ptr(chlg)); input_token.length = Curl_bufref_len(chlg); /* Decrypt the inbound challenge and obtain the qop */ @@ -226,7 +231,8 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Extract the security layer and the maximum message size */ indata = output_token.value; sec_layer = indata[0]; - max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + max_size = ((unsigned int)indata[1] << 16) | + ((unsigned int)indata[2] << 8) | indata[3]; /* Free the challenge as it is not required anymore */ gss_release_buffer(&unused_status, &output_token); @@ -242,7 +248,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Process the maximum message size the server can receive */ if(max_size > 0) { /* The server has told us it supports a maximum receive buffer, however, as - we don't require one unless we are encrypting data, we tell the server + we do not require one unless we are encrypting data, we tell the server our receive buffer is zero. */ max_size = 0; } @@ -320,4 +326,8 @@ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) } } +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + #endif /* HAVE_GSSAPI && USE_KERBEROS5 */ diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c index c487149b9d7..a29358569d9 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_sspi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c @@ -24,21 +24,21 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_WINDOWS_SSPI) && defined(USE_KERBEROS5) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_gssapi_supported() @@ -55,16 +55,16 @@ bool Curl_auth_is_gssapi_supported(void) SECURITY_STATUS status; /* Query the security package for Kerberos */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_KERBEROS), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return status == SEC_E_OK; } /* @@ -76,10 +76,10 @@ bool Curl_auth_is_gssapi_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * mutual_auth [in] - Flag specifying whether or not mutual authentication * is enabled. * chlg [in] - Optional challenge message. @@ -118,18 +118,18 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, if(!krb5->output_token) { /* Query the security package for Kerberos */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_KERBEROS), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } krb5->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ krb5->output_token = malloc(krb5->token_max); @@ -158,12 +158,11 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) - TEXT(SP_NAME_KERBEROS), - SECPKG_CRED_OUTBOUND, NULL, - krb5->p_identity, NULL, NULL, - krb5->credentials, &expiry); + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + SECPKG_CRED_OUTBOUND, NULL, + krb5->p_identity, NULL, NULL, + krb5->credentials, &expiry); if(status != SEC_E_OK) return CURLE_LOGIN_DENIED; @@ -184,7 +183,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; - chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg); + chlg_buf.pvBuffer = CURL_UNCONST(Curl_bufref_ptr(chlg)); chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); } @@ -197,7 +196,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(krb5->token_max); /* Generate our challenge-response message */ - status = s_pSecFn->InitializeSecurityContext(krb5->credentials, + status = Curl_pSecFn->InitializeSecurityContext(krb5->credentials, chlg ? krb5->context : NULL, krb5->spn, (mutual_auth ? @@ -215,7 +214,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, return CURLE_AUTH_ERROR; if(memcmp(&context, krb5->context, sizeof(context))) { - s_pSecFn->DeleteSecurityContext(krb5->context); + Curl_pSecFn->DeleteSecurityContext(krb5->context); memcpy(krb5->context, &context, sizeof(context)); } @@ -282,7 +281,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, } /* Get our response size information */ - status = s_pSecFn->QueryContextAttributes(krb5->context, + status = Curl_pSecFn->QueryContextAttributes(krb5->context, SECPKG_ATTR_SIZES, &sizes); @@ -297,14 +296,14 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, input_desc.cBuffers = 2; input_desc.pBuffers = input_buf; input_buf[0].BufferType = SECBUFFER_STREAM; - input_buf[0].pvBuffer = (void *) Curl_bufref_ptr(chlg); + input_buf[0].pvBuffer = CURL_UNCONST(Curl_bufref_ptr(chlg)); input_buf[0].cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); input_buf[1].BufferType = SECBUFFER_DATA; input_buf[1].pvBuffer = NULL; input_buf[1].cbBuffer = 0; /* Decrypt the inbound challenge and obtain the qop */ - status = s_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); + status = Curl_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); if(status != SEC_E_OK) { infof(data, "GSSAPI handshake failure (empty security message)"); return CURLE_BAD_CONTENT_ENCODING; @@ -319,10 +318,11 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Extract the security layer and the maximum message size */ indata = input_buf[1].pvBuffer; sec_layer = indata[0]; - max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + max_size = ((unsigned long)indata[1] << 16) | + ((unsigned long)indata[2] << 8) | indata[3]; /* Free the challenge as it is not required anymore */ - s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); /* Process the security layer */ if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) { @@ -334,7 +334,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Process the maximum message size the server can receive */ if(max_size > 0) { /* The server has told us it supports a maximum receive buffer, however, as - we don't require one unless we are encrypting data, we tell the server + we do not require one unless we are encrypting data, we tell the server our receive buffer is zero. */ max_size = 0; } @@ -391,7 +391,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, wrap_buf[2].cbBuffer = sizes.cbBlockSize; /* Encrypt the data */ - status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, + status = Curl_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, &wrap_desc, 0); if(status != SEC_E_OK) { free(padding); @@ -447,14 +447,14 @@ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) { /* Free our security context */ if(krb5->context) { - s_pSecFn->DeleteSecurityContext(krb5->context); + Curl_pSecFn->DeleteSecurityContext(krb5->context); free(krb5->context); krb5->context = NULL; } /* Free our credentials handle */ if(krb5->credentials) { - s_pSecFn->FreeCredentialsHandle(krb5->credentials); + Curl_pSecFn->FreeCredentialsHandle(krb5->credentials); free(krb5->credentials); krb5->credentials = NULL; } diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c index 93096ba5ecc..5cda790dda3 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm.c +++ b/Utilities/cmcurl/lib/vauth/ntlm.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_NTLM) && !defined(USE_WINDOWS_SSPI) @@ -35,39 +35,134 @@ #define DEBUG_ME 0 -#include "urldata.h" -#include "sendf.h" -#include "curl_ntlm_core.h" -#include "curl_gethostname.h" -#include "curl_multibyte.h" -#include "curl_md5.h" -#include "warnless.h" -#include "rand.h" -#include "vtls/vtls.h" - -/* SSL backend-specific #if branches in this file must be kept in the order - documented in curl_ntlm_core. */ -#if defined(NTLM_NEEDS_NSS_INIT) -#include "vtls/nssg.h" /* for Curl_nss_force_init() */ -#endif - -#define BUILDING_CURL_NTLM_MSGS_C -#include "vauth/vauth.h" -#include "vauth/ntlm.h" -#include "curl_endian.h" -#include "curl_printf.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curl_ntlm_core.h" +#include "../curl_gethostname.h" +#include "../curlx/multibyte.h" +#include "../curl_md5.h" +#include "../curlx/warnless.h" +#include "../rand.h" +#include "../vtls/vtls.h" +#include "../strdup.h" + +#include "vauth.h" +#include "../curl_endian.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" + + +/* NTLM buffer fixed size, large enough for long user + host + domain */ +#define NTLM_BUFSIZE 1024 + +/* Flag bits definitions based on + https://davenport.sourceforge.net/ntlm.html */ + +#define NTLMFLAG_NEGOTIATE_UNICODE (1<<0) +/* Indicates that Unicode strings are supported for use in security buffer + data. */ + +#define NTLMFLAG_NEGOTIATE_OEM (1<<1) +/* Indicates that OEM strings are supported for use in security buffer data. */ + +#define NTLMFLAG_REQUEST_TARGET (1<<2) +/* Requests that the server's authentication realm be included in the Type 2 + message. */ + +/* unknown (1<<3) */ +#define NTLMFLAG_NEGOTIATE_SIGN (1<<4) +/* Specifies that authenticated communication between the client and server + should carry a digital signature (message integrity). */ + +#define NTLMFLAG_NEGOTIATE_SEAL (1<<5) +/* Specifies that authenticated communication between the client and server + should be encrypted (message confidentiality). */ + +#define NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE (1<<6) +/* Indicates that datagram authentication is being used. */ + +#define NTLMFLAG_NEGOTIATE_LM_KEY (1<<7) +/* Indicates that the LAN Manager session key should be used for signing and + sealing authenticated communications. */ + +#define NTLMFLAG_NEGOTIATE_NTLM_KEY (1<<9) +/* Indicates that NTLM authentication is being used. */ + +/* unknown (1<<10) */ + +#define NTLMFLAG_NEGOTIATE_ANONYMOUS (1<<11) +/* Sent by the client in the Type 3 message to indicate that an anonymous + context has been established. This also affects the response fields. */ + +#define NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED (1<<12) +/* Sent by the client in the Type 1 message to indicate that a desired + authentication realm is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED (1<<13) +/* Sent by the client in the Type 1 message to indicate that the client + workstation's name is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_LOCAL_CALL (1<<14) +/* Sent by the server to indicate that the server and client are on the same + machine. Implies that the client may use a pre-established local security + context rather than responding to the challenge. */ + +#define NTLMFLAG_NEGOTIATE_ALWAYS_SIGN (1<<15) +/* Indicates that authenticated communication between the client and server + should be signed with a "dummy" signature. */ + +#define NTLMFLAG_TARGET_TYPE_DOMAIN (1<<16) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a domain. */ + +#define NTLMFLAG_TARGET_TYPE_SERVER (1<<17) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a server. */ + +#define NTLMFLAG_TARGET_TYPE_SHARE (1<<18) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a share. Presumably, this is for share-level + authentication. Usage is unclear. */ + +#define NTLMFLAG_NEGOTIATE_NTLM2_KEY (1<<19) +/* Indicates that the NTLM2 signing and sealing scheme should be used for + protecting authenticated communications. */ + +#define NTLMFLAG_REQUEST_INIT_RESPONSE (1<<20) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_ACCEPT_RESPONSE (1<<21) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_NONNT_SESSION_KEY (1<<22) +/* unknown purpose */ + +#define NTLMFLAG_NEGOTIATE_TARGET_INFO (1<<23) +/* Sent by the server in the Type 2 message to indicate that it is including a + Target Information block in the message. */ + +/* unknown (1<24) */ +/* unknown (1<25) */ +/* unknown (1<26) */ +/* unknown (1<27) */ +/* unknown (1<28) */ + +#define NTLMFLAG_NEGOTIATE_128 (1<<29) +/* Indicates that 128-bit encryption is supported. */ + +#define NTLMFLAG_NEGOTIATE_KEY_EXCHANGE (1<<30) +/* Indicates that the client will provide an encrypted master key in + the "Session Key" field of the Type 3 message. */ + +#define NTLMFLAG_NEGOTIATE_56 (1<<31) +/* Indicates that 56-bit encryption is supported. */ /* "NTLMSSP" signature is always in ASCII regardless of the platform */ #define NTLMSSP_SIGNATURE "\x4e\x54\x4c\x4d\x53\x53\x50" -/* The fixed host name we provide, in order to not leak our real local host - name. Copy the name used by Firefox. */ -#define NTLM_HOSTNAME "WORKSTATION" - #if DEBUG_ME # define DEBUG_OUT(x) x static void ntlm_print_flags(FILE *handle, unsigned long flags) @@ -78,7 +173,7 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_NEGOTIATE_OEM "); if(flags & NTLMFLAG_REQUEST_TARGET) fprintf(handle, "NTLMFLAG_REQUEST_TARGET "); - if(flags & (1<<3)) + if(flags & (1 << 3)) fprintf(handle, "NTLMFLAG_UNKNOWN_3 "); if(flags & NTLMFLAG_NEGOTIATE_SIGN) fprintf(handle, "NTLMFLAG_NEGOTIATE_SIGN "); @@ -90,7 +185,7 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_NEGOTIATE_LM_KEY "); if(flags & NTLMFLAG_NEGOTIATE_NTLM_KEY) fprintf(handle, "NTLMFLAG_NEGOTIATE_NTLM_KEY "); - if(flags & (1<<10)) + if(flags & (1 << 10)) fprintf(handle, "NTLMFLAG_UNKNOWN_10 "); if(flags & NTLMFLAG_NEGOTIATE_ANONYMOUS) fprintf(handle, "NTLMFLAG_NEGOTIATE_ANONYMOUS "); @@ -118,15 +213,15 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_REQUEST_NONNT_SESSION_KEY "); if(flags & NTLMFLAG_NEGOTIATE_TARGET_INFO) fprintf(handle, "NTLMFLAG_NEGOTIATE_TARGET_INFO "); - if(flags & (1<<24)) + if(flags & (1 << 24)) fprintf(handle, "NTLMFLAG_UNKNOWN_24 "); - if(flags & (1<<25)) + if(flags & (1 << 25)) fprintf(handle, "NTLMFLAG_UNKNOWN_25 "); - if(flags & (1<<26)) + if(flags & (1 << 26)) fprintf(handle, "NTLMFLAG_UNKNOWN_26 "); - if(flags & (1<<27)) + if(flags & (1 << 27)) fprintf(handle, "NTLMFLAG_UNKNOWN_27 "); - if(flags & (1<<28)) + if(flags & (1 << 28)) fprintf(handle, "NTLMFLAG_UNKNOWN_28 "); if(flags & NTLMFLAG_NEGOTIATE_128) fprintf(handle, "NTLMFLAG_NEGOTIATE_128 "); @@ -190,11 +285,10 @@ static CURLcode ntlm_decode_type2_target(struct Curl_easy *data, } free(ntlm->target_info); /* replace any previous data */ - ntlm->target_info = malloc(target_info_len); + ntlm->target_info = Curl_memdup(&type2[target_info_offset], + target_info_len); if(!ntlm->target_info) return CURLE_OUT_OF_MEMORY; - - memcpy(ntlm->target_info, &type2[target_info_offset], target_info_len); } } @@ -274,12 +368,7 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, const unsigned char *type2 = Curl_bufref_ptr(type2ref); size_t type2len = Curl_bufref_len(type2ref); -#if defined(NTLM_NEEDS_NSS_INIT) - /* Make sure the crypto backend is initialized */ - result = Curl_nss_force_init(data); - if(result) - return result; -#elif defined(CURL_DISABLE_VERBOSE_STRINGS) +#if defined(CURL_DISABLE_VERBOSE_STRINGS) (void)data; #endif @@ -336,10 +425,10 @@ static void unicodecpy(unsigned char *dest, const char *src, size_t length) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. * @@ -395,9 +484,9 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, "%c%c" /* 2 zeroes */ "%c%c" /* host length */ "%c%c" /* host allocated space */ - "%c%c" /* host name offset */ + "%c%c" /* hostname offset */ "%c%c" /* 2 zeroes */ - "%s" /* host name */ + "%s" /* hostname */ "%s", /* domain string */ 0, /* trailing zero */ 0, 0, 0, /* part of type-1 long */ @@ -459,7 +548,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. @@ -481,7 +570,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 12 LM/LMv2 Response security buffer 20 NTLM/NTLMv2 Response security buffer 28 Target Name security buffer - 36 User Name security buffer + 36 username security buffer 44 Workstation Name security buffer (52) Session Key security buffer (*) (60) Flags long (*) @@ -493,15 +582,17 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, CURLcode result = CURLE_OK; size_t size; unsigned char ntlmbuf[NTLM_BUFSIZE]; - int lmrespoff; + unsigned int lmrespoff; unsigned char lmresp[24]; /* fixed-size */ - int ntrespoff; + unsigned int ntrespoff; unsigned int ntresplen = 24; unsigned char ntresp[24]; /* fixed-size */ unsigned char *ptr_ntresp = &ntresp[0]; unsigned char *ntlmv2resp = NULL; - bool unicode = (ntlm->flags & NTLMFLAG_NEGOTIATE_UNICODE) ? TRUE : FALSE; - char host[HOSTNAME_MAX + 1] = ""; + bool unicode = (ntlm->flags & NTLMFLAG_NEGOTIATE_UNICODE); + /* The fixed hostname we provide, in order to not leak our real local host + name. Copy the name used by Firefox. */ + static const char host[] = "WORKSTATION"; const char *user; const char *domain = ""; size_t hostoff = 0; @@ -526,21 +617,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, user = userp; userlen = strlen(user); - -#ifndef NTLM_HOSTNAME - /* Get the machine's un-qualified host name as NTLM doesn't like the fully - qualified domain name */ - if(Curl_gethostname(host, sizeof(host))) { - infof(data, "gethostname() failed, continuing without"); - hostlen = 0; - } - else { - hostlen = strlen(host); - } -#else - (void)msnprintf(host, sizeof(host), "%s", NTLM_HOSTNAME); - hostlen = sizeof(NTLM_HOSTNAME)-1; -#endif + hostlen = sizeof(host) - 1; if(ntlm->flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) { unsigned char ntbuffer[0x18]; @@ -596,7 +673,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, return result; Curl_ntlm_core_lm_resp(lmbuffer, &ntlm->nonce[0], lmresp); - ntlm->flags &= ~NTLMFLAG_NEGOTIATE_NTLM2_KEY; + ntlm->flags &= ~(unsigned int)NTLMFLAG_NEGOTIATE_NTLM2_KEY; /* A safer but less compatible alternative is: * Curl_ntlm_core_lm_resp(ntbuffer, &ntlm->nonce[0], lmresp); @@ -733,7 +810,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, /* Make sure that the domain, user and host strings fit in the buffer before we copy them there. */ if(size + userlen + domlen + hostlen >= NTLM_BUFSIZE) { - failf(data, "user + domain + host name too big"); + failf(data, "user + domain + hostname too big"); return CURLE_OUT_OF_MEMORY; } diff --git a/Utilities/cmcurl/lib/vauth/ntlm.h b/Utilities/cmcurl/lib/vauth/ntlm.h deleted file mode 100644 index 31ce921cd12..00000000000 --- a/Utilities/cmcurl/lib/vauth/ntlm.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef HEADER_VAUTH_NTLM_H -#define HEADER_VAUTH_NTLM_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" - -#ifdef USE_NTLM - -/* NTLM buffer fixed size, large enough for long user + host + domain */ -#define NTLM_BUFSIZE 1024 - -/* Stuff only required for curl_ntlm_msgs.c */ -#ifdef BUILDING_CURL_NTLM_MSGS_C - -/* Flag bits definitions based on - https://davenport.sourceforge.net/ntlm.html */ - -#define NTLMFLAG_NEGOTIATE_UNICODE (1<<0) -/* Indicates that Unicode strings are supported for use in security buffer - data. */ - -#define NTLMFLAG_NEGOTIATE_OEM (1<<1) -/* Indicates that OEM strings are supported for use in security buffer data. */ - -#define NTLMFLAG_REQUEST_TARGET (1<<2) -/* Requests that the server's authentication realm be included in the Type 2 - message. */ - -/* unknown (1<<3) */ -#define NTLMFLAG_NEGOTIATE_SIGN (1<<4) -/* Specifies that authenticated communication between the client and server - should carry a digital signature (message integrity). */ - -#define NTLMFLAG_NEGOTIATE_SEAL (1<<5) -/* Specifies that authenticated communication between the client and server - should be encrypted (message confidentiality). */ - -#define NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE (1<<6) -/* Indicates that datagram authentication is being used. */ - -#define NTLMFLAG_NEGOTIATE_LM_KEY (1<<7) -/* Indicates that the LAN Manager session key should be used for signing and - sealing authenticated communications. */ - -#define NTLMFLAG_NEGOTIATE_NTLM_KEY (1<<9) -/* Indicates that NTLM authentication is being used. */ - -/* unknown (1<<10) */ - -#define NTLMFLAG_NEGOTIATE_ANONYMOUS (1<<11) -/* Sent by the client in the Type 3 message to indicate that an anonymous - context has been established. This also affects the response fields. */ - -#define NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED (1<<12) -/* Sent by the client in the Type 1 message to indicate that a desired - authentication realm is included in the message. */ - -#define NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED (1<<13) -/* Sent by the client in the Type 1 message to indicate that the client - workstation's name is included in the message. */ - -#define NTLMFLAG_NEGOTIATE_LOCAL_CALL (1<<14) -/* Sent by the server to indicate that the server and client are on the same - machine. Implies that the client may use a pre-established local security - context rather than responding to the challenge. */ - -#define NTLMFLAG_NEGOTIATE_ALWAYS_SIGN (1<<15) -/* Indicates that authenticated communication between the client and server - should be signed with a "dummy" signature. */ - -#define NTLMFLAG_TARGET_TYPE_DOMAIN (1<<16) -/* Sent by the server in the Type 2 message to indicate that the target - authentication realm is a domain. */ - -#define NTLMFLAG_TARGET_TYPE_SERVER (1<<17) -/* Sent by the server in the Type 2 message to indicate that the target - authentication realm is a server. */ - -#define NTLMFLAG_TARGET_TYPE_SHARE (1<<18) -/* Sent by the server in the Type 2 message to indicate that the target - authentication realm is a share. Presumably, this is for share-level - authentication. Usage is unclear. */ - -#define NTLMFLAG_NEGOTIATE_NTLM2_KEY (1<<19) -/* Indicates that the NTLM2 signing and sealing scheme should be used for - protecting authenticated communications. */ - -#define NTLMFLAG_REQUEST_INIT_RESPONSE (1<<20) -/* unknown purpose */ - -#define NTLMFLAG_REQUEST_ACCEPT_RESPONSE (1<<21) -/* unknown purpose */ - -#define NTLMFLAG_REQUEST_NONNT_SESSION_KEY (1<<22) -/* unknown purpose */ - -#define NTLMFLAG_NEGOTIATE_TARGET_INFO (1<<23) -/* Sent by the server in the Type 2 message to indicate that it is including a - Target Information block in the message. */ - -/* unknown (1<24) */ -/* unknown (1<25) */ -/* unknown (1<26) */ -/* unknown (1<27) */ -/* unknown (1<28) */ - -#define NTLMFLAG_NEGOTIATE_128 (1<<29) -/* Indicates that 128-bit encryption is supported. */ - -#define NTLMFLAG_NEGOTIATE_KEY_EXCHANGE (1<<30) -/* Indicates that the client will provide an encrypted master key in - the "Session Key" field of the Type 3 message. */ - -#define NTLMFLAG_NEGOTIATE_56 (1<<31) -/* Indicates that 56-bit encryption is supported. */ - -#endif /* BUILDING_CURL_NTLM_MSGS_C */ - -#endif /* USE_NTLM */ - -#endif /* HEADER_VAUTH_NTLM_H */ diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c index 5118963f4da..86b4bccfdf4 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c +++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c @@ -22,22 +22,23 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_WINDOWS_SSPI) && defined(USE_NTLM) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "curl_ntlm_core.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curl_ntlm_core.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" +#include "../strdup.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_ntlm_supported() @@ -54,15 +55,16 @@ bool Curl_auth_is_ntlm_supported(void) SECURITY_STATUS status; /* Query the security package for NTLM */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return status == SEC_E_OK; } /* @@ -74,10 +76,10 @@ bool Curl_auth_is_ntlm_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. * @@ -102,17 +104,18 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, Curl_auth_cleanup_ntlm(ntlm); /* Query the security package for NTLM */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } ntlm->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ ntlm->output_token = malloc(ntlm->token_max); @@ -140,11 +143,11 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT(SP_NAME_NTLM), - SECPKG_CRED_OUTBOUND, NULL, - ntlm->p_identity, NULL, NULL, - ntlm->credentials, &expiry); + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + SECPKG_CRED_OUTBOUND, NULL, + ntlm->p_identity, NULL, NULL, + ntlm->credentials, &expiry); if(status != SEC_E_OK) return CURLE_LOGIN_DENIED; @@ -166,7 +169,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, type_1_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-1 message */ - status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, NULL, + status = Curl_pSecFn->InitializeSecurityContext(ntlm->credentials, NULL, ntlm->spn, 0, 0, SECURITY_NETWORK_DREP, NULL, 0, @@ -174,7 +177,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, &attrs, &expiry); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(ntlm->context, &type_1_desc); + Curl_pSecFn->CompleteAuthToken(ntlm->context, &type_1_desc); else if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) @@ -213,11 +216,10 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, } /* Store the challenge for later use */ - ntlm->input_token = malloc(Curl_bufref_len(type2) + 1); + ntlm->input_token = Curl_memdup0((const char *)Curl_bufref_ptr(type2), + Curl_bufref_len(type2)); if(!ntlm->input_token) return CURLE_OUT_OF_MEMORY; - memcpy(ntlm->input_token, Curl_bufref_ptr(type2), Curl_bufref_len(type2)); - ntlm->input_token[Curl_bufref_len(type2)] = '\0'; ntlm->input_token_len = Curl_bufref_len(type2); return CURLE_OK; @@ -233,7 +235,7 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. @@ -282,7 +284,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, SEC_CHANNEL_BINDINGS channelBindings; SecPkgContext_Bindings pkgBindings; pkgBindings.Bindings = &channelBindings; - status = s_pSecFn->QueryContextAttributes( + status = Curl_pSecFn->QueryContextAttributes( ntlm->sslContext, SECPKG_ATTR_ENDPOINT_BINDINGS, &pkgBindings @@ -305,7 +307,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, type_3_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-3 message */ - status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, + status = Curl_pSecFn->InitializeSecurityContext(ntlm->credentials, ntlm->context, ntlm->spn, 0, 0, SECURITY_NETWORK_DREP, @@ -314,7 +316,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, &type_3_desc, &attrs, &expiry); if(status != SEC_E_OK) { - infof(data, "NTLM handshake failure (type-3 message): Status=%x", + infof(data, "NTLM handshake failure (type-3 message): Status=%lx", status); if(status == SEC_E_INSUFFICIENT_MEMORY) @@ -343,14 +345,14 @@ void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm) { /* Free our security context */ if(ntlm->context) { - s_pSecFn->DeleteSecurityContext(ntlm->context); + Curl_pSecFn->DeleteSecurityContext(ntlm->context); free(ntlm->context); ntlm->context = NULL; } /* Free our credentials handle */ if(ntlm->credentials) { - s_pSecFn->FreeCredentialsHandle(ntlm->credentials); + Curl_pSecFn->FreeCredentialsHandle(ntlm->credentials); free(ntlm->credentials); ntlm->credentials = NULL; } diff --git a/Utilities/cmcurl/lib/vauth/oauth2.c b/Utilities/cmcurl/lib/vauth/oauth2.c index a4adbdcf153..76e77c6a2f4 100644 --- a/Utilities/cmcurl/lib/vauth/oauth2.c +++ b/Utilities/cmcurl/lib/vauth/oauth2.c @@ -24,22 +24,22 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_POP3) || \ (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include -#include "urldata.h" +#include "../urldata.h" -#include "vauth/vauth.h" -#include "warnless.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curlx/warnless.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_create_oauth_bearer_message() @@ -49,8 +49,8 @@ * * Parameters: * - * user[in] - The user name. - * host[in] - The host name. + * user[in] - The username. + * host[in] - The hostname. * port[in] - The port(when not Port 80). * bearer[in] - The bearer token. * out[out] - The result storage. @@ -87,7 +87,7 @@ CURLcode Curl_auth_create_oauth_bearer_message(const char *user, * * Parameters: * - * user[in] - The user name. + * user[in] - The username. * bearer[in] - The bearer token. * out[out] - The result storage. * diff --git a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c index e1d52b755c2..b17ee46d175 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c @@ -24,23 +24,28 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(HAVE_GSSAPI) && defined(USE_SPNEGO) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "curl_base64.h" -#include "curl_gssapi.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curlx/base64.h" +#include "../curl_gssapi.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" + +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif /* * Curl_auth_is_spnego_supported() @@ -65,10 +70,10 @@ bool Curl_auth_is_spnego_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * chlg64 [in] - The optional base64 encoded challenge message. * nego [in/out] - The Negotiate data struct being used and modified. * @@ -91,14 +96,16 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_channel_bindings_t chan_bindings = GSS_C_NO_CHANNEL_BINDINGS; + struct gss_channel_bindings_struct chan; (void) user; (void) password; if(nego->context && nego->status == GSS_S_COMPLETE) { /* We finished successfully our part of authentication, but server - * rejected it (since we're again here). Exit with an error since we - * can't invent anything better */ + * rejected it (since we are again here). Exit with an error since we + * cannot invent anything better */ Curl_auth_cleanup_spnego(nego); return CURLE_LOGIN_DENIED; } @@ -132,7 +139,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { - result = Curl_base64_decode(chlg64, &chlg, &chlglen); + result = curlx_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } @@ -148,13 +155,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, input_token.length = chlglen; } + /* Set channel binding data if available */ + if(curlx_dyn_len(&nego->channel_binding_data)) { + memset(&chan, 0, sizeof(struct gss_channel_bindings_struct)); + chan.application_data.length = curlx_dyn_len(&nego->channel_binding_data); + chan.application_data.value = curlx_dyn_ptr(&nego->channel_binding_data); + chan_bindings = &chan; + } + /* Generate our challenge-response message */ major_status = Curl_gss_init_sec_context(data, &minor_status, &nego->context, nego->spn, &Curl_spnego_mech_oid, - GSS_C_NO_CHANNEL_BINDINGS, + chan_bindings, &input_token, &output_token, TRUE, @@ -213,9 +228,9 @@ CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, OM_uint32 minor_status; /* Base64 encode the already generated response */ - result = Curl_base64_encode(nego->output_token.value, - nego->output_token.length, - outptr, outlen); + result = curlx_base64_encode(nego->output_token.value, + nego->output_token.length, + outptr, outlen); if(result) { gss_release_buffer(&minor_status, &nego->output_token); @@ -278,4 +293,8 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) nego->havemultiplerequests = FALSE; } +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + #endif /* HAVE_GSSAPI && USE_SPNEGO */ diff --git a/Utilities/cmcurl/lib/vauth/spnego_sspi.c b/Utilities/cmcurl/lib/vauth/spnego_sspi.c index d3245d0b189..c19a1ff4236 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_sspi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_sspi.c @@ -24,23 +24,23 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_WINDOWS_SSPI) && defined(USE_SPNEGO) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "curl_base64.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" -#include "strerror.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curlx/base64.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" +#include "../strerror.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_spnego_supported() @@ -57,17 +57,17 @@ bool Curl_auth_is_spnego_supported(void) SECURITY_STATUS status; /* Query the security package for Negotiate */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_NEGOTIATE), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return status == SEC_E_OK; } /* @@ -79,10 +79,10 @@ bool Curl_auth_is_spnego_supported(void) * Parameters: * * data [in] - The session handle. - * user [in] - The user name in the format User or Domain\User. + * user [in] - The username in the format User or Domain\User. * password [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * chlg64 [in] - The optional base64 encoded challenge message. * nego [in/out] - The Negotiate data struct being used and modified. * @@ -113,8 +113,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(nego->context && nego->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server - * rejected it (since we're again here). Exit with an error since we - * can't invent anything better */ + * rejected it (since we are again here). Exit with an error since we + * cannot invent anything better */ Curl_auth_cleanup_spnego(nego); return CURLE_LOGIN_DENIED; } @@ -128,18 +128,18 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(!nego->output_token) { /* Query the security package for Negotiate */ - nego->status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_NEGOTIATE), - &SecurityPackage); + nego->status = (DWORD)Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + &SecurityPackage); if(nego->status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } nego->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ nego->output_token = malloc(nego->token_max); @@ -168,12 +168,12 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Acquire our credentials handle */ - nego->status = - s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)TEXT(SP_NAME_NEGOTIATE), - SECPKG_CRED_OUTBOUND, NULL, - nego->p_identity, NULL, NULL, - nego->credentials, &expiry); + nego->status = (DWORD) + Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + SECPKG_CRED_OUTBOUND, NULL, + nego->p_identity, NULL, NULL, + nego->credentials, &expiry); if(nego->status != SEC_E_OK) return CURLE_AUTH_ERROR; @@ -186,7 +186,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { - result = Curl_base64_decode(chlg64, &chlg, &chlglen); + result = curlx_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } @@ -218,7 +218,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, SEC_CHANNEL_BINDINGS channelBindings; SecPkgContext_Bindings pkgBindings; pkgBindings.Bindings = &channelBindings; - nego->status = s_pSecFn->QueryContextAttributes( + nego->status = (DWORD)Curl_pSecFn->QueryContextAttributes( nego->sslContext, SECPKG_ATTR_ENDPOINT_BINDINGS, &pkgBindings @@ -242,16 +242,16 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(nego->token_max); /* Generate our challenge-response message */ - nego->status = s_pSecFn->InitializeSecurityContext(nego->credentials, - chlg ? nego->context : - NULL, - nego->spn, - ISC_REQ_CONFIDENTIALITY, - 0, SECURITY_NATIVE_DREP, - chlg ? &chlg_desc : NULL, - 0, nego->context, - &resp_desc, &attrs, - &expiry); + nego->status = + (DWORD)Curl_pSecFn->InitializeSecurityContext(nego->credentials, + chlg ? nego->context : NULL, + nego->spn, + ISC_REQ_CONFIDENTIALITY, + 0, SECURITY_NATIVE_DREP, + chlg ? &chlg_desc : NULL, + 0, nego->context, + &resp_desc, &attrs, + &expiry); /* Free the decoded challenge as it is not required anymore */ free(chlg); @@ -259,7 +259,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(GSS_ERROR(nego->status)) { char buffer[STRERROR_LEN]; failf(data, "InitializeSecurityContext failed: %s", - Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); + Curl_sspi_strerror((int)nego->status, buffer, sizeof(buffer))); if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; @@ -269,11 +269,12 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(nego->status == SEC_I_COMPLETE_NEEDED || nego->status == SEC_I_COMPLETE_AND_CONTINUE) { - nego->status = s_pSecFn->CompleteAuthToken(nego->context, &resp_desc); + nego->status = (DWORD)Curl_pSecFn->CompleteAuthToken(nego->context, + &resp_desc); if(GSS_ERROR(nego->status)) { char buffer[STRERROR_LEN]; failf(data, "CompleteAuthToken failed: %s", - Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); + Curl_sspi_strerror((int)nego->status, buffer, sizeof(buffer))); if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; @@ -307,9 +308,9 @@ CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, char **outptr, size_t *outlen) { /* Base64 encode the already generated response */ - CURLcode result = Curl_base64_encode((const char *) nego->output_token, - nego->output_token_length, outptr, - outlen); + CURLcode result = curlx_base64_encode((const char *) nego->output_token, + nego->output_token_length, outptr, + outlen); if(!result && (!*outptr || !*outlen)) { free(*outptr); result = CURLE_REMOTE_ACCESS_DENIED; @@ -332,14 +333,14 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) { /* Free our security context */ if(nego->context) { - s_pSecFn->DeleteSecurityContext(nego->context); + Curl_pSecFn->DeleteSecurityContext(nego->context); free(nego->context); nego->context = NULL; } /* Free our credentials handle */ if(nego->credentials) { - s_pSecFn->FreeCredentialsHandle(nego->credentials); + Curl_pSecFn->FreeCredentialsHandle(nego->credentials); free(nego->credentials); nego->credentials = NULL; } diff --git a/Utilities/cmcurl/lib/vauth/vauth.c b/Utilities/cmcurl/lib/vauth/vauth.c index 62fc7c40fe8..e2872b2522a 100644 --- a/Utilities/cmcurl/lib/vauth/vauth.c +++ b/Utilities/cmcurl/lib/vauth/vauth.c @@ -22,24 +22,24 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include #include "vauth.h" -#include "urldata.h" -#include "strcase.h" -#include "curl_multibyte.h" -#include "curl_printf.h" +#include "../urldata.h" +#include "../strcase.h" +#include "../curlx/multibyte.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_build_spn() * - * This is used to build a SPN string in the following formats: + * This is used to build an SPN string in the following formats: * * service/host@realm (Not currently used) * service/host (Not used by GSS-API) @@ -48,7 +48,7 @@ * Parameters: * * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * realm [in] - The realm. * * Returns a pointer to the newly allocated SPN. @@ -93,7 +93,7 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, return NULL; /* Allocate and return a TCHAR based SPN. Since curlx_convert_UTF8_to_tchar - must be freed by curlx_unicodefree we'll dupe the result so that the + must be freed by curlx_unicodefree we will dupe the result so that the pointer this function returns can be normally free'd. */ tchar_spn = curlx_convert_UTF8_to_tchar(utf8_spn); free(utf8_spn); @@ -115,14 +115,14 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, * Domain/User (curl Down-level format - for compatibility with existing code) * User@Domain (User Principal Name) * - * Note: The user name may be empty when using a GSS-API library or Windows + * Note: The username may be empty when using a GSS-API library or Windows * SSPI as the user and domain are either obtained from the credentials cache * when using GSS-API or via the currently logged in user's credentials when * using Windows SSPI. * * Parameters: * - * user [in] - The user name. + * user [in] - The username. * * Returns TRUE on success; otherwise FALSE. */ @@ -134,8 +134,7 @@ bool Curl_auth_user_contains_domain(const char *user) /* Check we have a domain name or UPN present */ char *p = strpbrk(user, "\\/@"); - valid = (p != NULL && p > user && p < user + strlen(user) - 1 ? TRUE : - FALSE); + valid = (p != NULL && p > user && p < user + strlen(user) - 1); } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) else @@ -154,10 +153,10 @@ bool Curl_auth_user_contains_domain(const char *user) bool Curl_auth_allowed_to_host(struct Curl_easy *data) { struct connectdata *conn = data->conn; - return (!data->state.this_is_a_follow || - data->set.allow_auth_to_other_hosts || - (data->state.first_host && - strcasecompare(data->state.first_host, conn->host.name) && - (data->state.first_remote_port == conn->remote_port) && - (data->state.first_remote_protocol == conn->handler->protocol))); + return !data->state.this_is_a_follow || + data->set.allow_auth_to_other_hosts || + (data->state.first_host && + strcasecompare(data->state.first_host, conn->host.name) && + (data->state.first_remote_port == conn->remote_port) && + (data->state.first_remote_protocol == conn->handler->protocol)); } diff --git a/Utilities/cmcurl/lib/vauth/vauth.h b/Utilities/cmcurl/lib/vauth/vauth.h index d8cff24381e..58b13f582c7 100644 --- a/Utilities/cmcurl/lib/vauth/vauth.h +++ b/Utilities/cmcurl/lib/vauth/vauth.h @@ -26,11 +26,11 @@ #include -#include "bufref.h" +#include "../bufref.h" struct Curl_easy; -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_DIGEST_AUTH) struct digestdata; #endif @@ -60,7 +60,7 @@ struct gsasldata; */ bool Curl_auth_allowed_to_host(struct Curl_easy *data); -/* This is used to build a SPN string */ +/* This is used to build an SPN string */ #if !defined(USE_WINDOWS_SSPI) char *Curl_auth_build_spn(const char *service, const char *host, const char *realm); @@ -79,14 +79,12 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, struct bufref *out); /* This is used to generate a LOGIN cleartext message */ -CURLcode Curl_auth_create_login_message(const char *value, - struct bufref *out); +void Curl_auth_create_login_message(const char *value, struct bufref *out); /* This is used to generate an EXTERNAL cleartext message */ -CURLcode Curl_auth_create_external_message(const char *user, - struct bufref *out); +void Curl_auth_create_external_message(const char *user, struct bufref *out); -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#ifndef CURL_DISABLE_DIGEST_AUTH /* This is used to generate a CRAM-MD5 response message */ CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, const char *userp, @@ -119,7 +117,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* This is used to clean up the digest specific data */ void Curl_auth_digest_cleanup(struct digestdata *digest); -#endif /* !CURL_DISABLE_CRYPTO_AUTH */ +#endif /* !CURL_DISABLE_DIGEST_AUTH */ #ifdef USE_GSASL /* This is used to evaluate if MECH is supported by gsasl */ @@ -169,6 +167,8 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, /* This is used to clean up the NTLM specific data */ void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm); +#else +#define Curl_auth_is_ntlm_supported() FALSE #endif /* USE_NTLM */ /* This is used to generate a base64 encoded OAuth 2.0 message */ @@ -209,6 +209,8 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* This is used to clean up the GSSAPI specific data */ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5); +#else +#define Curl_auth_is_gssapi_supported() FALSE #endif /* USE_KERBEROS5 */ #if defined(USE_SPNEGO) diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c index 5800ad3c48e..4451d3e5343 100644 --- a/Utilities/cmcurl/lib/version.c +++ b/Utilities/cmcurl/lib/version.c @@ -38,10 +38,6 @@ #include "easy_lock.h" #ifdef USE_ARES -# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ - defined(WIN32) -# define CARES_STATICLIB -# endif # include #endif @@ -55,6 +51,7 @@ #ifdef USE_LIBRTMP #include +#include "curl_rtmp.h" #endif #ifdef HAVE_LIBZ @@ -62,13 +59,13 @@ #endif #ifdef HAVE_BROTLI -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) /* Ignore -Wvla warnings in brotli headers */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvla" #endif #include -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif @@ -92,20 +89,71 @@ static void brotli_version(char *buf, size_t bufsz) unsigned int major = brotli_version >> 24; unsigned int minor = (brotli_version & 0x00FFFFFF) >> 12; unsigned int patch = brotli_version & 0x00000FFF; - (void)msnprintf(buf, bufsz, "%u.%u.%u", major, minor, patch); + (void)msnprintf(buf, bufsz, "brotli/%u.%u.%u", major, minor, patch); } #endif #ifdef HAVE_ZSTD static void zstd_version(char *buf, size_t bufsz) { - unsigned long zstd_version = (unsigned long)ZSTD_versionNumber(); - unsigned int major = (unsigned int)(zstd_version / (100 * 100)); - unsigned int minor = (unsigned int)((zstd_version - - (major * 100 * 100)) / 100); - unsigned int patch = (unsigned int)(zstd_version - - (major * 100 * 100) - (minor * 100)); - (void)msnprintf(buf, bufsz, "%u.%u.%u", major, minor, patch); + unsigned int version = ZSTD_versionNumber(); + unsigned int major = version / (100 * 100); + unsigned int minor = (version - (major * 100 * 100)) / 100; + unsigned int patch = version - (major * 100 * 100) - (minor * 100); + (void)msnprintf(buf, bufsz, "zstd/%u.%u.%u", major, minor, patch); +} +#endif + +#ifdef USE_OPENLDAP +static void oldap_version(char *buf, size_t bufsz) +{ + LDAPAPIInfo api; + api.ldapai_info_version = LDAP_API_INFO_VERSION; + + if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) == LDAP_OPT_SUCCESS) { + unsigned int patch = (unsigned int)(api.ldapai_vendor_version % 100); + unsigned int major = (unsigned int)(api.ldapai_vendor_version / 10000); + unsigned int minor = + (((unsigned int)api.ldapai_vendor_version - major * 10000) + - patch) / 100; + msnprintf(buf, bufsz, "%s/%u.%u.%u", + api.ldapai_vendor_name, major, minor, patch); + ldap_memfree(api.ldapai_vendor_name); + ber_memvfree((void **)api.ldapai_extensions); + } + else + msnprintf(buf, bufsz, "OpenLDAP"); +} +#endif + +#ifdef USE_LIBPSL +static void psl_version(char *buf, size_t bufsz) +{ +#if defined(PSL_VERSION_MAJOR) && (PSL_VERSION_MAJOR > 0 || \ + PSL_VERSION_MINOR >= 11) + int num = psl_check_version_number(0); + msnprintf(buf, bufsz, "libpsl/%d.%d.%d", + num >> 16, (num >> 8) & 0xff, num & 0xff); +#else + msnprintf(buf, bufsz, "libpsl/%s", psl_get_version()); +#endif +} +#endif + +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) +#define USE_IDN +#endif + +#ifdef USE_IDN +static void idn_version(char *buf, size_t bufsz) +{ +#ifdef USE_LIBIDN2 + msnprintf(buf, bufsz, "libidn2/%s", idn2_check_version(NULL)); +#elif defined(USE_WIN32_IDN) + msnprintf(buf, bufsz, "WinIDN"); +#elif defined(USE_APPLE_IDN) + msnprintf(buf, bufsz, "AppleIDN"); +#endif } #endif @@ -129,37 +177,34 @@ char *curl_version(void) char ssl_version[200]; #endif #ifdef HAVE_LIBZ - char z_version[40]; + char z_version[30]; #endif #ifdef HAVE_BROTLI - char br_version[40] = "brotli/"; + char br_version[30]; #endif #ifdef HAVE_ZSTD - char zst_version[40] = "zstd/"; + char zstd_ver[30]; #endif #ifdef USE_ARES - char cares_version[40]; + char cares_version[30]; #endif -#if defined(USE_LIBIDN2) - char idn_version[40]; +#ifdef USE_IDN + char idn_ver[30]; #endif #ifdef USE_LIBPSL - char psl_version[40]; + char psl_ver[30]; #endif #ifdef USE_SSH - char ssh_version[40]; + char ssh_version[30]; #endif #ifdef USE_NGHTTP2 - char h2_version[40]; + char h2_version[30]; #endif -#ifdef ENABLE_QUIC - char h3_version[40]; +#ifdef USE_HTTP3 + char h3_version[30]; #endif #ifdef USE_LIBRTMP - char rtmp_version[40]; -#endif -#ifdef USE_HYPER - char hyper_buf[30]; + char rtmp_version[30]; #endif #ifdef USE_GSASL char gsasl_buf[30]; @@ -174,8 +219,7 @@ char *curl_version(void) /* Override version string when environment variable CURL_VERSION is set */ const char *debugversion = getenv("CURL_VERSION"); if(debugversion) { - strncpy(out, debugversion, sizeof(out)-1); - out[sizeof(out)-1] = '\0'; + msnprintf(out, sizeof(out), "%s", debugversion); return out; } #endif @@ -190,31 +234,26 @@ char *curl_version(void) src[i++] = z_version; #endif #ifdef HAVE_BROTLI - brotli_version(&br_version[7], sizeof(br_version) - 7); + brotli_version(br_version, sizeof(br_version)); src[i++] = br_version; #endif #ifdef HAVE_ZSTD - zstd_version(&zst_version[5], sizeof(zst_version) - 5); - src[i++] = zst_version; + zstd_version(zstd_ver, sizeof(zstd_ver)); + src[i++] = zstd_ver; #endif #ifdef USE_ARES msnprintf(cares_version, sizeof(cares_version), "c-ares/%s", ares_version(NULL)); src[i++] = cares_version; #endif -#ifdef USE_LIBIDN2 - msnprintf(idn_version, sizeof(idn_version), - "libidn2/%s", idn2_check_version(NULL)); - src[i++] = idn_version; -#elif defined(USE_WIN32_IDN) - src[i++] = (char *)"WinIDN"; +#ifdef USE_IDN + idn_version(idn_ver, sizeof(idn_ver)); + src[i++] = idn_ver; #endif - #ifdef USE_LIBPSL - msnprintf(psl_version, sizeof(psl_version), "libpsl/%s", psl_get_version()); - src[i++] = psl_version; + psl_version(psl_ver, sizeof(psl_ver)); + src[i++] = psl_ver; #endif - #ifdef USE_SSH Curl_ssh_version(ssh_version, sizeof(ssh_version)); src[i++] = ssh_version; @@ -223,29 +262,13 @@ char *curl_version(void) Curl_http2_ver(h2_version, sizeof(h2_version)); src[i++] = h2_version; #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 Curl_quic_ver(h3_version, sizeof(h3_version)); src[i++] = h3_version; #endif #ifdef USE_LIBRTMP - { - char suff[2]; - if(RTMP_LIB_VERSION & 0xff) { - suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; - suff[1] = '\0'; - } - else - suff[0] = '\0'; - - msnprintf(rtmp_version, sizeof(rtmp_version), "librtmp/%d.%d%s", - RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, - suff); - src[i++] = rtmp_version; - } -#endif -#ifdef USE_HYPER - msnprintf(hyper_buf, sizeof(hyper_buf), "Hyper/%s", hyper_version()); - src[i++] = hyper_buf; + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + src[i++] = rtmp_version; #endif #ifdef USE_GSASL msnprintf(gsasl_buf, sizeof(gsasl_buf), "libgsasl/%s", @@ -253,22 +276,8 @@ char *curl_version(void) src[i++] = gsasl_buf; #endif #ifdef USE_OPENLDAP - { - LDAPAPIInfo api; - api.ldapai_info_version = LDAP_API_INFO_VERSION; - - if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) == LDAP_OPT_SUCCESS) { - unsigned int patch = api.ldapai_vendor_version % 100; - unsigned int major = api.ldapai_vendor_version / 10000; - unsigned int minor = - ((api.ldapai_vendor_version - major * 10000) - patch) / 100; - msnprintf(ldap_buf, sizeof(ldap_buf), "%s/%u.%u.%u", - api.ldapai_vendor_name, major, minor, patch); - src[i++] = ldap_buf; - ldap_memfree(api.ldapai_vendor_name); - ber_memvfree((void **)api.ldapai_extensions); - } - } + oldap_version(ldap_buf, sizeof(ldap_buf)); + src[i++] = ldap_buf; #endif DEBUGASSERT(i <= VERSION_PARTS); @@ -300,7 +309,7 @@ char *curl_version(void) protocol line has its own #if line to make things easier on the eye. */ -static const char * const protocols[] = { +static const char * const supported_protocols[] = { #ifndef CURL_DISABLE_DICT "dict", #endif @@ -383,33 +392,40 @@ static const char * const protocols[] = { #ifndef CURL_DISABLE_TFTP "tftp", #endif -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_HTTP + /* WebSocket support relies on HTTP */ +#ifndef CURL_DISABLE_WEBSOCKETS "ws", #endif -#if defined(USE_SSL) && defined(USE_WEBSOCKETS) +#if defined(USE_SSL) && !defined(CURL_DISABLE_WEBSOCKETS) "wss", +#endif #endif NULL }; /* - * Feature presence run-time check functions. + * Feature presence runtime check functions. * * Warning: the value returned by these should not change between * curl_global_init() and curl_global_cleanup() calls. */ -#if defined(USE_LIBIDN2) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) static int idn_present(curl_version_info_data *info) { +#if defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) + (void)info; + return TRUE; +#else return info->libidn != NULL; +#endif } -#else -#define idn_present NULL #endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) +#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ + !defined(CURL_DISABLE_HTTP) static int https_proxy_present(curl_version_info_data *info) { (void) info; @@ -417,6 +433,14 @@ static int https_proxy_present(curl_version_info_data *info) } #endif +#if defined(USE_SSL) && defined(USE_ECH) +static int ech_present(curl_version_info_data *info) +{ + (void) info; + return Curl_ssl_supports(NULL, SSLSUPP_ECH); +} +#endif + /* * Features table. * @@ -436,6 +460,9 @@ static const struct feat features_table[] = { #ifndef CURL_DISABLE_ALTSVC FEATURE("alt-svc", NULL, CURL_VERSION_ALTSVC), #endif +#if defined(USE_ARES) && defined(CURLRES_THREADED) && defined(USE_HTTPSRR) + FEATURE("asyn-rr", NULL, 0), +#endif #ifdef CURLRES_ASYNCH FEATURE("AsynchDNS", NULL, CURL_VERSION_ASYNCHDNS), #endif @@ -445,6 +472,13 @@ static const struct feat features_table[] = { #ifdef DEBUGBUILD FEATURE("Debug", NULL, CURL_VERSION_DEBUG), #endif +#if defined(USE_SSL) && defined(USE_ECH) + FEATURE("ECH", ech_present, 0), + +#ifndef USE_HTTPSRR +#error "ECH enabled but not HTTPSRR, must be a config error" +#endif +#endif #ifdef USE_GSASL FEATURE("gsasl", NULL, CURL_VERSION_GSASL), #endif @@ -454,19 +488,23 @@ static const struct feat features_table[] = { #ifndef CURL_DISABLE_HSTS FEATURE("HSTS", NULL, CURL_VERSION_HSTS), #endif -#if defined(USE_NGHTTP2) || defined(USE_HYPER) +#if defined(USE_NGHTTP2) FEATURE("HTTP2", NULL, CURL_VERSION_HTTP2), #endif -#if defined(ENABLE_QUIC) +#if defined(USE_HTTP3) FEATURE("HTTP3", NULL, CURL_VERSION_HTTP3), #endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) +#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ + !defined(CURL_DISABLE_HTTP) FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), #endif -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_HTTPSRR) + FEATURE("HTTPSRR", NULL, 0), +#endif +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) FEATURE("IDN", idn_present, CURL_VERSION_IDN), #endif -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 FEATURE("IPv6", NULL, CURL_VERSION_IPV6), #endif #ifdef USE_KERBEROS5 @@ -485,10 +523,6 @@ static const struct feat features_table[] = { #ifdef USE_NTLM FEATURE("NTLM", NULL, CURL_VERSION_NTLM), #endif -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - FEATURE("NTLM_WB", NULL, CURL_VERSION_NTLM_WB), -#endif #if defined(USE_LIBPSL) FEATURE("PSL", NULL, CURL_VERSION_PSL), #endif @@ -498,6 +532,9 @@ static const struct feat features_table[] = { #ifdef USE_SSL FEATURE("SSL", NULL, CURL_VERSION_SSL), #endif +#if defined(USE_SSLS_EXPORT) + FEATURE("SSLS-EXPORT", NULL, 0), +#endif #ifdef USE_WINDOWS_SSPI FEATURE("SSPI", NULL, CURL_VERSION_SSPI), #endif @@ -510,7 +547,7 @@ static const struct feat features_table[] = { #ifdef CURLDEBUG FEATURE("TrackMemory", NULL, CURL_VERSION_CURLDEBUG), #endif -#if defined(WIN32) && defined(UNICODE) && defined(_UNICODE) +#if defined(_WIN32) && defined(UNICODE) && defined(_UNICODE) FEATURE("Unicode", NULL, CURL_VERSION_UNICODE), #endif #ifdef USE_UNIX_SOCKETS @@ -519,7 +556,7 @@ static const struct feat features_table[] = { #ifdef HAVE_ZSTD FEATURE("zstd", NULL, CURL_VERSION_ZSTD), #endif - {NULL, NULL, 0} + {NULL, NULL, 0} }; static const char *feature_names[sizeof(features_table) / @@ -530,12 +567,12 @@ static curl_version_info_data version_info = { CURLVERSION_NOW, LIBCURL_VERSION, LIBCURL_VERSION_NUM, - OS, /* as found by configure or set by hand at build-time */ - 0, /* features bitmask is built at run-time */ + CURL_OS, /* as found by configure or set by hand at build-time */ + 0, /* features bitmask is built at runtime */ NULL, /* ssl_version */ 0, /* ssl_version_num, this is kept at zero */ NULL, /* zlib_version */ - protocols, + supported_protocols, NULL, /* c-ares version */ 0, /* c-ares version numerical */ NULL, /* libidn version */ @@ -560,7 +597,8 @@ static curl_version_info_data version_info = { NULL, /* zstd version */ NULL, /* Hyper version */ NULL, /* gsasl version */ - feature_names + feature_names, + NULL /* rtmp version */ }; curl_version_info_data *curl_version_info(CURLversion stamp) @@ -570,7 +608,7 @@ curl_version_info_data *curl_version_info(CURLversion stamp) int features = 0; #if defined(USE_SSH) - static char ssh_buffer[80]; + static char ssh_buf[80]; /* 'ssh_buffer' clashes with libssh/libssh.h */ #endif #ifdef USE_SSL #ifdef CURL_WITH_MULTI_SSL @@ -586,7 +624,7 @@ curl_version_info_data *curl_version_info(CURLversion stamp) static char zstd_buffer[80]; #endif - (void)stamp; /* avoid compiler warnings, we don't use this */ + (void)stamp; /* avoid compiler warnings, we do not use this */ #ifdef USE_SSL Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); @@ -611,8 +649,8 @@ curl_version_info_data *curl_version_info(CURLversion stamp) #endif #if defined(USE_SSH) - Curl_ssh_version(ssh_buffer, sizeof(ssh_buffer)); - version_info.libssh_version = ssh_buffer; + Curl_ssh_version(ssh_buf, sizeof(ssh_buf)); + version_info.libssh_version = ssh_buf; #endif #ifdef HAVE_BROTLI @@ -630,12 +668,12 @@ curl_version_info_data *curl_version_info(CURLversion stamp) #ifdef USE_NGHTTP2 { nghttp2_info *h2 = nghttp2_version(0); - version_info.nghttp2_ver_num = h2->version_num; + version_info.nghttp2_ver_num = (unsigned int)h2->version_num; version_info.nghttp2_version = h2->version_str; } #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 { static char quicbuffer[80]; Curl_quic_ver(quicbuffer, sizeof(quicbuffer)); @@ -643,14 +681,6 @@ curl_version_info_data *curl_version_info(CURLversion stamp) } #endif -#ifdef USE_HYPER - { - static char hyper_buffer[30]; - msnprintf(hyper_buffer, sizeof(hyper_buffer), "Hyper/%s", hyper_version()); - version_info.hyper_version = hyper_buffer; - } -#endif - #ifdef USE_GSASL { version_info.gsasl_version = gsasl_check_version(NULL); @@ -668,5 +698,13 @@ curl_version_info_data *curl_version_info(CURLversion stamp) feature_names[n] = NULL; /* Terminate array. */ version_info.features = features; +#ifdef USE_LIBRTMP + { + static char rtmp_version[30]; + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + version_info.rtmp_version = rtmp_version; + } +#endif + return &version_info; } diff --git a/Utilities/cmcurl/lib/version_win32.c b/Utilities/cmcurl/lib/version_win32.c deleted file mode 100644 index 872d5b4f3c5..00000000000 --- a/Utilities/cmcurl/lib/version_win32.c +++ /dev/null @@ -1,319 +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" - -#if defined(WIN32) - -#include -#include "version_win32.h" -#include "warnless.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -/* This Unicode version struct works for VerifyVersionInfoW (OSVERSIONINFOEXW) - and RtlVerifyVersionInfo (RTLOSVERSIONINFOEXW) */ -struct OUR_OSVERSIONINFOEXW { - ULONG dwOSVersionInfoSize; - ULONG dwMajorVersion; - ULONG dwMinorVersion; - ULONG dwBuildNumber; - ULONG dwPlatformId; - WCHAR szCSDVersion[128]; - USHORT wServicePackMajor; - USHORT wServicePackMinor; - USHORT wSuiteMask; - UCHAR wProductType; - UCHAR wReserved; -}; - -/* - * curlx_verify_windows_version() - * - * This is used to verify if we are running on a specific windows version. - * - * Parameters: - * - * majorVersion [in] - The major version number. - * minorVersion [in] - The minor version number. - * buildVersion [in] - The build version number. If 0, this parameter is - * ignored. - * platform [in] - The optional platform identifier. - * condition [in] - The test condition used to specifier whether we are - * checking a version less then, equal to or greater than - * what is specified in the major and minor version - * numbers. - * - * Returns TRUE if matched; otherwise FALSE. - */ -bool curlx_verify_windows_version(const unsigned int majorVersion, - const unsigned int minorVersion, - const unsigned int buildVersion, - const PlatformIdentifier platform, - const VersionCondition condition) -{ - bool matched = FALSE; - -#if defined(CURL_WINDOWS_APP) - (void)buildVersion; - - /* We have no way to determine the Windows version from Windows apps, - so let's assume we're running on the target Windows version. */ - const WORD fullVersion = MAKEWORD(minorVersion, majorVersion); - const WORD targetVersion = (WORD)_WIN32_WINNT; - - switch(condition) { - case VERSION_LESS_THAN: - matched = targetVersion < fullVersion; - break; - - case VERSION_LESS_THAN_EQUAL: - matched = targetVersion <= fullVersion; - break; - - case VERSION_EQUAL: - matched = targetVersion == fullVersion; - break; - - case VERSION_GREATER_THAN_EQUAL: - matched = targetVersion >= fullVersion; - break; - - case VERSION_GREATER_THAN: - matched = targetVersion > fullVersion; - break; - } - - if(matched && (platform == PLATFORM_WINDOWS)) { - /* we're always running on PLATFORM_WINNT */ - matched = FALSE; - } -#elif !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_WIN2K) || \ - (_WIN32_WINNT < _WIN32_WINNT_WIN2K) - OSVERSIONINFO osver; - - memset(&osver, 0, sizeof(osver)); - osver.dwOSVersionInfoSize = sizeof(osver); - - /* Find out Windows version */ - if(GetVersionEx(&osver)) { - /* Verify the Operating System version number */ - switch(condition) { - case VERSION_LESS_THAN: - if(osver.dwMajorVersion < majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion < minorVersion) || - (buildVersion != 0 && - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - osver.dwBuildNumber < buildVersion))) - matched = TRUE; - break; - - case VERSION_LESS_THAN_EQUAL: - if(osver.dwMajorVersion < majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion < minorVersion) || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - (buildVersion == 0 || - osver.dwBuildNumber <= buildVersion))) - matched = TRUE; - break; - - case VERSION_EQUAL: - if(osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - (buildVersion == 0 || - osver.dwBuildNumber == buildVersion)) - matched = TRUE; - break; - - case VERSION_GREATER_THAN_EQUAL: - if(osver.dwMajorVersion > majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion > minorVersion) || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - (buildVersion == 0 || - osver.dwBuildNumber >= buildVersion))) - matched = TRUE; - break; - - case VERSION_GREATER_THAN: - if(osver.dwMajorVersion > majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion > minorVersion) || - (buildVersion != 0 && - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - osver.dwBuildNumber > buildVersion))) - matched = TRUE; - break; - } - - /* Verify the platform identifier (if necessary) */ - if(matched) { - switch(platform) { - case PLATFORM_WINDOWS: - if(osver.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) - matched = FALSE; - break; - - case PLATFORM_WINNT: - if(osver.dwPlatformId != VER_PLATFORM_WIN32_NT) - matched = FALSE; - break; - - default: /* like platform == PLATFORM_DONT_CARE */ - break; - } - } - } -#else - ULONGLONG cm = 0; - struct OUR_OSVERSIONINFOEXW osver; - BYTE majorCondition; - BYTE minorCondition; - BYTE buildCondition; - BYTE spMajorCondition; - BYTE spMinorCondition; - DWORD dwTypeMask = VER_MAJORVERSION | VER_MINORVERSION | - VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR; - - typedef LONG (APIENTRY *RTLVERIFYVERSIONINFO_FN) - (struct OUR_OSVERSIONINFOEXW *, ULONG, ULONGLONG); - static RTLVERIFYVERSIONINFO_FN pRtlVerifyVersionInfo; - static bool onetime = true; /* safe because first call is during init */ - - if(onetime) { - pRtlVerifyVersionInfo = CURLX_FUNCTION_CAST(RTLVERIFYVERSIONINFO_FN, - (GetProcAddress(GetModuleHandleA("ntdll"), "RtlVerifyVersionInfo"))); - onetime = false; - } - - switch(condition) { - case VERSION_LESS_THAN: - majorCondition = VER_LESS; - minorCondition = VER_LESS; - buildCondition = VER_LESS; - spMajorCondition = VER_LESS_EQUAL; - spMinorCondition = VER_LESS_EQUAL; - break; - - case VERSION_LESS_THAN_EQUAL: - majorCondition = VER_LESS_EQUAL; - minorCondition = VER_LESS_EQUAL; - buildCondition = VER_LESS_EQUAL; - spMajorCondition = VER_LESS_EQUAL; - spMinorCondition = VER_LESS_EQUAL; - break; - - case VERSION_EQUAL: - majorCondition = VER_EQUAL; - minorCondition = VER_EQUAL; - buildCondition = VER_EQUAL; - spMajorCondition = VER_GREATER_EQUAL; - spMinorCondition = VER_GREATER_EQUAL; - break; - - case VERSION_GREATER_THAN_EQUAL: - majorCondition = VER_GREATER_EQUAL; - minorCondition = VER_GREATER_EQUAL; - buildCondition = VER_GREATER_EQUAL; - spMajorCondition = VER_GREATER_EQUAL; - spMinorCondition = VER_GREATER_EQUAL; - break; - - case VERSION_GREATER_THAN: - majorCondition = VER_GREATER; - minorCondition = VER_GREATER; - buildCondition = VER_GREATER; - spMajorCondition = VER_GREATER_EQUAL; - spMinorCondition = VER_GREATER_EQUAL; - break; - - default: - return FALSE; - } - - memset(&osver, 0, sizeof(osver)); - osver.dwOSVersionInfoSize = sizeof(osver); - osver.dwMajorVersion = majorVersion; - osver.dwMinorVersion = minorVersion; - osver.dwBuildNumber = buildVersion; - if(platform == PLATFORM_WINDOWS) - osver.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; - else if(platform == PLATFORM_WINNT) - osver.dwPlatformId = VER_PLATFORM_WIN32_NT; - - cm = VerSetConditionMask(cm, VER_MAJORVERSION, majorCondition); - cm = VerSetConditionMask(cm, VER_MINORVERSION, minorCondition); - cm = VerSetConditionMask(cm, VER_SERVICEPACKMAJOR, spMajorCondition); - cm = VerSetConditionMask(cm, VER_SERVICEPACKMINOR, spMinorCondition); - - if(platform != PLATFORM_DONT_CARE) { - cm = VerSetConditionMask(cm, VER_PLATFORMID, VER_EQUAL); - dwTypeMask |= VER_PLATFORMID; - } - - /* Later versions of Windows have version functions that may not return the - real version of Windows unless the application is so manifested. We prefer - the real version always, so we use the Rtl variant of the function when - possible. Note though the function signatures have underlying fundamental - types that are the same, the return values are different. */ - if(pRtlVerifyVersionInfo) - matched = !pRtlVerifyVersionInfo(&osver, dwTypeMask, cm); - else - matched = !!VerifyVersionInfoW((OSVERSIONINFOEXW *)&osver, dwTypeMask, cm); - - /* Compare the build number separately. VerifyVersionInfo normally compares - major.minor in hierarchical order (eg 1.9 is less than 2.0) but does not - do the same for build (eg 1.9 build 222 is not less than 2.0 build 111). - Build comparison is only needed when build numbers are equal (eg 1.9 is - always less than 2.0 so build comparison is not needed). */ - if(matched && buildVersion && - (condition == VERSION_EQUAL || - ((condition == VERSION_GREATER_THAN_EQUAL || - condition == VERSION_LESS_THAN_EQUAL) && - curlx_verify_windows_version(majorVersion, minorVersion, 0, - platform, VERSION_EQUAL)))) { - - cm = VerSetConditionMask(0, VER_BUILDNUMBER, buildCondition); - dwTypeMask = VER_BUILDNUMBER; - if(pRtlVerifyVersionInfo) - matched = !pRtlVerifyVersionInfo(&osver, dwTypeMask, cm); - else - matched = !!VerifyVersionInfoW((OSVERSIONINFOEXW *)&osver, - dwTypeMask, cm); - } - -#endif - - return matched; -} - -#endif /* WIN32 */ diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.c b/Utilities/cmcurl/lib/vquic/curl_msh3.c index 173886739b6..cbeb6505163 100644 --- a/Utilities/cmcurl/lib/vquic/curl_msh3.c +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.c @@ -22,28 +22,36 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MSH3 -#include "urldata.h" -#include "timeval.h" -#include "multiif.h" -#include "sendf.h" -#include "curl_log.h" -#include "cfilters.h" -#include "cf-socket.h" -#include "connect.h" -#include "progress.h" -#include "http1.h" +#include "../urldata.h" +#include "../hash.h" +#include "../uint-hash.h" +#include "../curlx/timeval.h" +#include "../multiif.h" +#include "../sendf.h" +#include "../curl_trc.h" +#include "../cfilters.h" +#include "../cf-socket.h" +#include "../connect.h" +#include "../progress.h" +#include "../http1.h" #include "curl_msh3.h" -#include "socketpair.h" -#include "vquic/vquic.h" +#include "../socketpair.h" +#include "../vtls/vtls.h" +#include "vquic.h" +#include "vquic_int.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" + +#ifdef CURL_DISABLE_SOCKETPAIR +#error "MSH3 cannot be build with CURL_DISABLE_SOCKETPAIR set" +#endif #define H3_STREAM_WINDOW_SIZE (128 * 1024) #define H3_STREAM_CHUNK_SIZE (16 * 1024) @@ -65,7 +73,7 @@ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ pthread_mutex_init(lock, &attr); \ pthread_mutexattr_destroy(&attr); \ -}while(0) +} while(0) #define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock) #define msh3_lock_acquire(lock) pthread_mutex_lock(lock) #define msh3_lock_release(lock) pthread_mutex_unlock(lock) @@ -113,23 +121,57 @@ struct cf_msh3_ctx { struct cf_call_data call_data; struct curltime connect_started; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ + struct uint_hash streams; /* hash `data->mid` to `stream_ctx` */ /* Flags written by msh3/msquic thread */ - bool handshake_complete; - bool handshake_succeeded; - bool connected; + BIT(handshake_complete); + BIT(handshake_succeeded); + BIT(connected); + BIT(initialized); /* Flags written by curl thread */ BIT(verbose); BIT(active); }; +static void h3_stream_hash_free(unsigned int id, void *stream); + +static CURLcode cf_msh3_ctx_init(struct cf_msh3_ctx *ctx, + const struct Curl_addrinfo *ai) +{ + CURLcode result; + + DEBUGASSERT(!ctx->initialized); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); + + result = Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); + if(result) + return result; + + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + ctx->initialized = TRUE; + + return result; +} + +static void cf_msh3_ctx_free(struct cf_msh3_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_uint_hash_destroy(&ctx->streams); + } + free(ctx); +} + +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data); + /* How to access `call_data` from a cf_msh3 filter */ +#undef CF_CTX_CALL_DATA #define CF_CTX_CALL_DATA(cf) \ ((struct cf_msh3_ctx *)(cf)->ctx)->call_data /** * All about the H3 internals of a stream */ -struct stream_ctx { +struct h3_stream_ctx { struct MSH3_REQUEST *req; struct bufq recvbuf; /* h3 response */ #ifdef _WIN32 @@ -140,25 +182,31 @@ struct stream_ctx { uint64_t error3; /* HTTP/3 stream error code */ int status_code; /* HTTP status code */ CURLcode recv_error; - bool closed; - bool reset; - bool upload_done; - bool firstheader; /* FALSE until headers arrive */ - bool recv_header_complete; + BIT(closed); + BIT(reset); + BIT(upload_done); + BIT(firstheader); /* FALSE until headers arrive */ + BIT(recv_header_complete); }; -#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + Curl_bufq_free(&stream->recvbuf); + free(stream); +} +static void h3_stream_hash_free(unsigned int id, void *stream) +{ + (void)id; + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -167,30 +215,34 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - H3_STREAM_LCTX(data) = stream; stream->req = ZERO_NULL; msh3_lock_initialize(&stream->recv_lock); Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - DEBUGF(LOG_CF(data, cf, "data setup (easy %p)", (void *)data)); + CURL_TRC_CF(data, cf, "data setup"); + + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { - DEBUGF(LOG_CF(data, cf, "easy handle is done")); - Curl_bufq_free(&stream->recvbuf); - free(stream); - H3_STREAM_LCTX(data) = NULL; + CURL_TRC_CF(data, cf, "easy handle is done"); + Curl_uint_hash_remove(&ctx->streams, data->mid); } } static void drain_stream_from_other_thread(struct Curl_easy *data, - struct stream_ctx *stream) + struct h3_stream_ctx *stream) { unsigned char bits; @@ -198,24 +250,25 @@ static void drain_stream_from_other_thread(struct Curl_easy *data, bits = CURL_CSELECT_IN; if(stream && !stream->upload_done) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; /* cannot expire from other thread */ } } -static void drain_stream(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; bits = CURL_CSELECT_IN; if(stream && !stream->upload_done) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -234,10 +287,10 @@ static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection, struct Curl_easy *data = CF_DATA_CURRENT(cf); (void)Connection; - DEBUGF(LOG_CF(data, cf, "[MSH3] connected")); - ctx->handshake_succeeded = true; - ctx->connected = true; - ctx->handshake_complete = true; + CURL_TRC_CF(data, cf, "[MSH3] connected"); + ctx->handshake_succeeded = TRUE; + ctx->connected = TRUE; + ctx->handshake_complete = TRUE; } static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, @@ -248,9 +301,9 @@ static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, struct Curl_easy *data = CF_DATA_CURRENT(cf); (void)Connection; - DEBUGF(LOG_CF(data, cf, "[MSH3] shutdown complete")); - ctx->connected = false; - ctx->handshake_complete = true; + CURL_TRC_CF(data, cf, "[MSH3] shutdown complete"); + ctx->connected = FALSE; + ctx->handshake_complete = TRUE; } static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection, @@ -270,7 +323,7 @@ static const MSH3_REQUEST_IF msh3_request_if = { msh3_data_sent }; -/* Decode HTTP status code. Returns -1 if no valid status code was +/* Decode HTTP status code. Returns -1 if no valid status code was decoded. (duplicate from http2.c) */ static int decode_status_code(const char *value, size_t len) { @@ -305,7 +358,8 @@ static int decode_status_code(const char *value, size_t len) static CURLcode write_resp_raw(struct Curl_easy *data, const void *mem, size_t memlen) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -331,10 +385,12 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, const MSH3_HEADER *hd) { struct Curl_easy *data = userp; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; (void)Request; + DEBUGF(infof(data, "[MSH3] header received, stream=%d", !!stream)); if(!stream || stream->recv_header_complete) { return; } @@ -342,7 +398,7 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, msh3_lock_acquire(&stream->recv_lock); if((hd->NameLength == 7) && - !strncmp(HTTP_PSEUDO_STATUS, (char *)hd->Name, 7)) { + !strncmp(HTTP_PSEUDO_STATUS, (const char *)hd->Name, 7)) { char line[14]; /* status line is always 13 characters long */ size_t ncopy; @@ -380,14 +436,15 @@ static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, const uint8_t *buf) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; bool rv = FALSE; - /* TODO: we would like to limit the amount of data we are buffer here. - * There seems to be no mechanism in msh3 to adjust flow control and - * it is undocumented what happens if we return FALSE here or less - * length (buflen is an inout parameter). + /* We would like to limit the amount of data we are buffer here. There seems + * to be no mechanism in msh3 to adjust flow control and it is undocumented + * what happens if we return FALSE here or less length (buflen is an inout + * parameter). */ (void)Request; if(!stream) @@ -401,7 +458,7 @@ static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, stream->recv_error = result; goto out; } - stream->recv_header_complete = true; + stream->recv_header_complete = TRUE; } result = write_resp_raw(data, buf, *buflen); @@ -419,14 +476,15 @@ static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, bool aborted, uint64_t error) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)Request; if(!stream) return; msh3_lock_acquire(&stream->recv_lock); stream->closed = TRUE; - stream->recv_header_complete = true; + stream->recv_header_complete = TRUE; if(error) stream->error3 = error; if(aborted) @@ -438,7 +496,8 @@ static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, void *IfContext) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; @@ -450,7 +509,8 @@ static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, void *IfContext, void *SendContext) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; (void)Request; @@ -462,7 +522,8 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; if(!stream) { @@ -473,18 +534,18 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, if(stream->reset) { failf(data, "HTTP/3 stream reset by server"); *err = CURLE_PARTIAL_FILE; - DEBUGF(LOG_CF(data, cf, "cf_recv, was reset -> %d", *err)); + CURL_TRC_CF(data, cf, "cf_recv, was reset -> %d", *err); goto out; } else if(stream->error3) { failf(data, "HTTP/3 stream was not closed cleanly: (error %zd)", (ssize_t)stream->error3); *err = CURLE_HTTP3; - DEBUGF(LOG_CF(data, cf, "cf_recv, closed uncleanly -> %d", *err)); + CURL_TRC_CF(data, cf, "cf_recv, closed uncleanly -> %d", *err); goto out; } else { - DEBUGF(LOG_CF(data, cf, "cf_recv, closed ok -> %d", *err)); + CURL_TRC_CF(data, cf, "cf_recv, closed ok -> %d", *err); } *err = CURLE_OK; nread = 0; @@ -495,7 +556,8 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); /* we have no indication from msh3 when it would be a good time * to juggle the connection again. So, we compromise by calling @@ -512,17 +574,17 @@ static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; - (void)cf; + CURL_TRC_CF(data, cf, "cf_recv(len=%zu), stream=%d", len, !!stream); if(!stream) { *err = CURLE_RECV_ERROR; return -1; } CF_DATA_SAVE(save, cf, data); - DEBUGF(LOG_CF(data, cf, "req: recv with %zu byte buffer", len)); msh3_lock_acquire(&stream->recv_lock); @@ -537,19 +599,19 @@ static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(!Curl_bufq_is_empty(&stream->recvbuf)) { nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); - DEBUGF(LOG_CF(data, cf, "read recvbuf(len=%zu) -> %zd, %d", - len, nread, *err)); + CURL_TRC_CF(data, cf, "read recvbuf(len=%zu) -> %zd, %d", + len, nread, *err); if(nread < 0) goto out; if(stream->closed) - drain_stream(cf, data); + h3_drain_stream(cf, data); } else if(stream->closed) { nread = recv_closed_stream(cf, data, err); goto out; } else { - DEBUGF(LOG_CF(data, cf, "req: nothing here, call again")); + CURL_TRC_CF(data, cf, "req: nothing here, call again"); *err = CURLE_AGAIN; } @@ -561,17 +623,17 @@ static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct h1_req_parser h1; struct dynhds h2_headers; MSH3_HEADER *nva = NULL; size_t nheader, i; ssize_t nwritten = -1; struct cf_call_data save; - bool eos; CF_DATA_SAVE(save, cf, data); @@ -580,7 +642,7 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* Sizes must match for cast below to work" */ DEBUGASSERT(stream); - DEBUGF(LOG_CF(data, cf, "req: send %zu bytes", len)); + CURL_TRC_CF(data, cf, "req: send %zu bytes", len); if(!stream->req) { /* The first send on the request contains the headers and possibly some @@ -614,22 +676,7 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, nva[i].ValueLength = e->valuelen; } - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: - /* known request body size or -1 */ - eos = FALSE; - break; - default: - /* there is not request body */ - eos = TRUE; - stream->upload_done = TRUE; - break; - } - - DEBUGF(LOG_CF(data, cf, "req: send %zu headers", nheader)); + CURL_TRC_CF(data, cf, "req: send %zu headers", nheader); stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data, nva, nheader, eos ? MSH3_REQUEST_FLAG_FIN : @@ -645,7 +692,7 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, } else { /* request is open */ - DEBUGF(LOG_CF(data, cf, "req: send %zd body bytes", len)); + CURL_TRC_CF(data, cf, "req: send %zu body bytes", len); if(len > 0xFFFFFFFF) { len = 0xFFFFFFFF; } @@ -656,8 +703,8 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } - /* TODO - msh3/msquic will hold onto this memory until the send complete - event. How do we make sure curl doesn't free it until then? */ + /* msh3/msquic will hold onto this memory until the send complete event. + How do we make sure curl does not free it until then? */ *err = CURLE_OK; nwritten = len; } @@ -671,37 +718,32 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, return nwritten; } -static int cf_msh3_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); - int bitmap = GETSOCK_BLANK; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CF_DATA_SAVE(save, cf, data); if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) { - socks[0] = ctx->sock[SP_LOCAL]; - if(stream->recv_error) { - bitmap |= GETSOCK_READSOCK(0); - drain_stream(cf, data); + Curl_pollset_add_in(data, ps, ctx->sock[SP_LOCAL]); + h3_drain_stream(cf, data); } else if(stream->req) { - bitmap |= GETSOCK_READSOCK(0); - drain_stream(cf, data); + Curl_pollset_add_out(data, ps, ctx->sock[SP_LOCAL]); + h3_drain_stream(cf, data); } } - DEBUGF(LOG_CF(data, cf, "select_sock -> %d", bitmap)); - CF_DATA_RESTORE(cf, save); - return bitmap; } static bool cf_msh3_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; bool pending = FALSE; @@ -710,41 +752,25 @@ static bool cf_msh3_data_pending(struct Curl_cfilter *cf, (void)cf; if(stream && stream->req) { msh3_lock_acquire(&stream->recv_lock); - DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data pending = %zu", - Curl_bufq_len(&stream->recvbuf))); + CURL_TRC_CF((struct Curl_easy *)CURL_UNCONST(data), cf, + "data pending = %zu", + Curl_bufq_len(&stream->recvbuf)); pending = !Curl_bufq_is_empty(&stream->recvbuf); msh3_lock_release(&stream->recv_lock); if(pending) - drain_stream(cf, (struct Curl_easy *)data); + h3_drain_stream(cf, (struct Curl_easy *)CURL_UNCONST(data)); } CF_DATA_RESTORE(cf, save); return pending; } -static void cf_msh3_active(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_msh3_ctx *ctx = cf->ctx; - - /* use this socket from now on */ - cf->conn->sock[cf->sockindex] = ctx->sock[SP_LOCAL]; - /* the first socket info gets set at conn and data */ - if(cf->sockindex == FIRSTSOCKET) { - cf->conn->remote_addr = &ctx->addr; - #ifdef ENABLE_IPV6 - cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; - #endif - Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); - } - ctx->active = TRUE; -} - static CURLcode h3_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) { if(!pause) { - drain_stream(cf, data); + h3_drain_stream(cf, data); Curl_expire(data, 0, EXPIRE_RUN_NOW); } return CURLE_OK; @@ -754,7 +780,8 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CURLcode result = CURLE_OK; @@ -773,7 +800,7 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, h3_data_done(cf, data); break; case CF_CTRL_DATA_DONE_SEND: - DEBUGF(LOG_CF(data, cf, "req: send done")); + CURL_TRC_CF(data, cf, "req: send done"); if(stream) { stream->upload_done = TRUE; if(stream->req) { @@ -785,10 +812,6 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, } } break; - case CF_CTRL_CONN_INFO_UPDATE: - DEBUGF(LOG_CF(data, cf, "req: update info")); - cf_msh3_active(cf, data); - break; default: break; } @@ -801,32 +824,33 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_msh3_ctx *ctx = cf->ctx; - bool verify = !!cf->conn->ssl_config.verifypeer; + struct ssl_primary_config *conn_config; MSH3_ADDR addr = {0}; CURLcode result; + bool verify; + + DEBUGASSERT(ctx->initialized); + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + verify = !!conn_config->verifypeer; - memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen); + memcpy(&addr, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port); - if(verify && (cf->conn->ssl_config.CAfile || cf->conn->ssl_config.CApath)) { - /* TODO: need a way to provide trust anchors to MSH3 */ -#ifdef DEBUGBUILD - /* we need this for our test cases to run */ - DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, " - "switching off verifypeer in DEBUG mode")); - verify = 0; -#else - DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, " - "attempting with built-in verification")); -#endif + if(verify && (conn_config->CAfile || conn_config->CApath)) { + /* Note there's currently no way to provide trust anchors to MSH3 and + that causes tests to fail. */ + CURL_TRC_CF(data, cf, "non-standard CA not supported, " + "attempting with built-in verification"); } - DEBUGF(LOG_CF(data, cf, "connecting to %s:%d (verify=%d)", - cf->conn->host.name, (int)cf->conn->remote_port, verify)); + CURL_TRC_CF(data, cf, "connecting to %s:%d (verify=%d)", + cf->conn->host.name, (int)cf->conn->remote_port, verify); ctx->api = MsH3ApiOpen(); if(!ctx->api) { - failf(data, "can't create msh3 api"); + failf(data, "cannot create msh3 api"); return CURLE_FAILED_INIT; } @@ -837,7 +861,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, &addr, !verify); if(!ctx->qconn) { - failf(data, "can't create msh3 connection"); + failf(data, "cannot create msh3 connection"); if(ctx->api) { MsH3ApiClose(ctx->api); ctx->api = NULL; @@ -854,13 +878,12 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_msh3_ctx *ctx = cf->ctx; struct cf_call_data save; CURLcode result = CURLE_OK; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; @@ -869,7 +892,7 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) { - if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) { + if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0], FALSE) < 0) { ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; return CURLE_COULDNT_CONNECT; @@ -878,19 +901,17 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ctx->qconn) { - ctx->connect_started = Curl_now(); + ctx->connect_started = curlx_now(); result = cf_connect_start(cf, data); if(result) goto out; } if(ctx->handshake_complete) { - ctx->handshake_at = Curl_now(); + ctx->handshake_at = curlx_now(); if(ctx->handshake_succeeded) { - DEBUGF(LOG_CF(data, cf, "handshake succeeded")); + CURL_TRC_CF(data, cf, "handshake succeeded"); cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - cf->conn->httpversion = 30; - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; cf->connected = TRUE; cf->conn->alpn = CURL_HTTP_VERSION_3; *done = TRUE; @@ -917,7 +938,7 @@ static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data) CF_DATA_SAVE(save, cf, data); if(ctx) { - DEBUGF(LOG_CF(data, cf, "destroying")); + CURL_TRC_CF(data, cf, "destroying"); if(ctx->qconn) { MsH3ConnectionClose(ctx->qconn); ctx->qconn = NULL; @@ -934,13 +955,13 @@ static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data) */ ctx->active = FALSE; if(ctx->sock[SP_LOCAL] == cf->conn->sock[cf->sockindex]) { - DEBUGF(LOG_CF(data, cf, "cf_msh3_close(%d) active", - (int)ctx->sock[SP_LOCAL])); + CURL_TRC_CF(data, cf, "cf_msh3_close(%d) active", + (int)ctx->sock[SP_LOCAL]); cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; } else { - DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at " - "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL])); + CURL_TRC_CF(data, cf, "cf_socket_close(%d) no longer at " + "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL]); ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; } if(cf->sockindex == FIRSTSOCKET) @@ -964,10 +985,11 @@ static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) CF_DATA_SAVE(save, cf, data); cf_msh3_close(cf, data); - free(cf->ctx); - cf->ctx = NULL; + if(cf->ctx) { + cf_msh3_ctx_free(cf->ctx); + cf->ctx = NULL; + } /* no CF_DATA_RESTORE(cf, save); its gone */ - } static CURLcode cf_msh3_query(struct Curl_cfilter *cf, @@ -978,7 +1000,7 @@ static CURLcode cf_msh3_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - /* TODO: we do not have access to this so far, fake it */ + /* We do not have access to this so far, fake it */ (void)ctx; *pres1 = 100; return CURLE_OK; @@ -996,10 +1018,13 @@ static CURLcode cf_msh3_query(struct Curl_cfilter *cf, *when = ctx->handshake_at; return CURLE_OK; } + case CF_QUERY_HTTP_VERSION: + *pres1 = 30; + return CURLE_OK; default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1018,13 +1043,14 @@ static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_http3 = { "HTTP/3", - CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, 0, cf_msh3_destroy, cf_msh3_connect, cf_msh3_close, + Curl_cf_def_shutdown, Curl_cf_def_get_host, - cf_msh3_get_select_socks, + cf_msh3_adjust_pollset, cf_msh3_data_pending, cf_msh3_send, cf_msh3_recv, @@ -1034,6 +1060,20 @@ struct Curl_cftype Curl_cft_http3 = { cf_msh3_query, }; +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data) +{ + if(data && data->conn) { + struct Curl_cfilter *cf = data->conn->cfilter[FIRSTSOCKET]; + while(cf) { + if(cf->cft == &Curl_cft_http3) + return cf->ctx; + cf = cf->next; + } + } + DEBUGF(infof(data, "no filter context found")); + return NULL; +} + CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, @@ -1045,23 +1085,24 @@ CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, (void)data; (void)conn; - (void)ai; /* TODO: msh3 resolves itself? */ - ctx = calloc(sizeof(*ctx), 1); + (void)ai; /* msh3 resolves itself? */ + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); - ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; - ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + + result = cf_msh3_ctx_init(ctx, ai); + if(result) + goto out; result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); - Curl_safefree(ctx); + cf_msh3_ctx_free(ctx); } return result; @@ -1071,7 +1112,7 @@ bool Curl_conn_is_msh3(const struct Curl_easy *data, const struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; (void)data; for(; cf; cf = cf->next) { diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.h b/Utilities/cmcurl/lib/vquic/curl_msh3.h index 33931f59bbc..d2862c2d896 100644 --- a/Utilities/cmcurl/lib/vquic/curl_msh3.h +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MSH3 diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c index 7627940ff51..f529f7e4fa8 100644 --- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) #include @@ -30,54 +30,57 @@ #ifdef USE_OPENSSL #include -#ifdef OPENSSL_IS_BORINGSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) #include +#elif defined(OPENSSL_QUIC_API2) +#include #else -#include +#include #endif -#include "vtls/openssl.h" +#include "../vtls/openssl.h" #elif defined(USE_GNUTLS) #include -#include "vtls/gtls.h" +#include "../vtls/gtls.h" #elif defined(USE_WOLFSSL) #include -#include "vtls/wolfssl.h" +#include "../vtls/wolfssl.h" #endif -#include "urldata.h" -#include "sendf.h" -#include "strdup.h" -#include "rand.h" -#include "multiif.h" -#include "strcase.h" -#include "cfilters.h" -#include "cf-socket.h" -#include "connect.h" -#include "progress.h" -#include "strerror.h" -#include "dynbuf.h" -#include "http1.h" -#include "select.h" +#include "../urldata.h" +#include "../uint-hash.h" +#include "../sendf.h" +#include "../strdup.h" +#include "../rand.h" +#include "../multiif.h" +#include "../strcase.h" +#include "../cfilters.h" +#include "../cf-socket.h" +#include "../connect.h" +#include "../progress.h" +#include "../strerror.h" +#include "../curlx/dynbuf.h" +#include "../http1.h" +#include "../select.h" +#include "../curlx/inet_pton.h" +#include "../transfer.h" #include "vquic.h" #include "vquic_int.h" -#include "vtls/keylog.h" -#include "vtls/vtls.h" +#include "vquic-tls.h" +#include "../vtls/keylog.h" +#include "../vtls/vtls.h" +#include "../vtls/vtls_scache.h" #include "curl_ngtcp2.h" -#include "warnless.h" +#include "../curlx/warnless.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" -#define H3_ALPN_H3_29 "\x5h3-29" -#define H3_ALPN_H3 "\x2h3" - #define QUIC_MAX_STREAMS (256*1024) #define QUIC_MAX_DATA (1*1024*1024) -#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS) #define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS) /* A stream window is the maximum amount we need to buffer for @@ -86,10 +89,14 @@ * Chunk size is large enough to take a full DATA frame */ #define H3_STREAM_WINDOW_SIZE (128 * 1024) #define H3_STREAM_CHUNK_SIZE (16 * 1024) +#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 windows * seems good. More does not seem to improve performance. * The benefit of the pool is that stream buffer to not keep - * spares. So memory consumption goes down when streams run empty, + * spares. Memory consumption goes down when streams run empty, * have a large upload done, etc. */ #define H3_STREAM_POOL_SPARES \ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 @@ -101,25 +108,6 @@ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) -#ifdef USE_OPENSSL -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:X25519:P-384:P-521" -#elif defined(USE_GNUTLS) -#define QUIC_PRIORITY \ - "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ - "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ - "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ - "%DISABLE_TLS13_COMPAT_MODE" -#elif defined(USE_WOLFSSL) -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:P-384:P-521" -#endif - - /* * Store ngtcp2 version info in this buffer. */ @@ -133,6 +121,11 @@ void Curl_ngtcp2_ver(char *p, size_t len) struct cf_ngtcp2_ctx { struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; +#ifdef OPENSSL_QUIC_API2 + ngtcp2_crypto_ossl_ctx *ossl_ctx; +#endif ngtcp2_path connected_path; ngtcp2_conn *qconn; ngtcp2_cid dcid; @@ -142,67 +135,150 @@ struct cf_ngtcp2_ctx { ngtcp2_transport_params transport_params; ngtcp2_ccerr last_error; ngtcp2_crypto_conn_ref conn_ref; -#ifdef USE_OPENSSL - SSL_CTX *sslctx; - SSL *ssl; -#elif defined(USE_GNUTLS) - struct gtls_instance *gtls; -#elif defined(USE_WOLFSSL) - WOLFSSL_CTX *sslctx; - WOLFSSL *ssl; -#endif struct cf_call_data call_data; nghttp3_conn *h3conn; nghttp3_settings h3settings; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ - struct curltime reconnect_at; /* time the next attempt should start */ 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` */ size_t max_stream_window; /* max flow window for one stream */ + 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 + server on session reuse */ + size_t earlydata_skip; /* sending bytes to skip when earlydata + * is accepted by peer */ + CURLcode tls_vrfy_result; /* result of TLS peer verification */ int qlogfd; - BIT(got_first_byte); /* if first byte was received */ + BIT(initialized); + BIT(tls_handshake_complete); /* TLS handshake is done */ + BIT(use_earlydata); /* Using 0RTT data */ + BIT(earlydata_accepted); /* 0RTT was acceptd by server */ + BIT(shutdown_started); /* queued shutdown packets */ }; /* How to access `call_data` from a cf_ngtcp2 filter */ +#undef CF_CTX_CALL_DATA #define CF_CTX_CALL_DATA(cf) \ ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data +static void h3_stream_hash_free(unsigned int id, void *stream); + +static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx) +{ + DEBUGASSERT(!ctx->initialized); + ctx->qlogfd = -1; + ctx->version = NGTCP2_PROTO_VER_MAX; + ctx->max_stream_window = H3_STREAM_WINDOW_SIZE; + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->initialized = TRUE; +} + +static void cf_ngtcp2_ctx_free(struct cf_ngtcp2_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_vquic_tls_cleanup(&ctx->tls); + vquic_ctx_free(&ctx->q); + Curl_bufcp_free(&ctx->stream_bufcp); + curlx_dyn_free(&ctx->scratch); + Curl_uint_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->peer); + } + free(ctx); +} + +static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const ngtcp2_transport_params *rp; + /* Peer should have sent us its transport parameters. If it + * announces a positive `max_idle_timeout` it will close the + * connection when it does not hear from us for that time. + * + * Some servers use this as a keep-alive timer at a rather low + * value. We are doing HTTP/3 here and waiting for the response + * to a request may take a considerable amount of time. We need + * to prevent the peer's QUIC stack from closing in this case. + */ + if(!ctx->qconn) + return; + + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(!rp || !rp->max_idle_timeout) { + 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_uint_hash_count(&ctx->streams)) { + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); + CURL_TRC_CF(data, cf, "no active streams, unset keep-alive"); + } + else { + ngtcp2_duration keep_ns; + keep_ns = (rp->max_idle_timeout > 1) ? (rp->max_idle_timeout / 2) : 1; + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, keep_ns); + CURL_TRC_CF(data, cf, "peer idle timeout is %" FMT_PRIu64 "ms, " + "set keep-alive to %" FMT_PRIu64 " ms.", + (curl_uint64_t)(rp->max_idle_timeout / NGTCP2_MILLISECONDS), + (curl_uint64_t)(keep_ns / NGTCP2_MILLISECONDS)); + } +} + + +struct pkt_io_ctx; +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); + /** * All about the H3 internals of a stream */ -struct stream_ctx { - int64_t id; /* HTTP/3 protocol identifier */ +struct h3_stream_ctx { + curl_int64_t id; /* HTTP/3 protocol identifier */ struct bufq sendbuf; /* h3 request body */ - struct bufq recvbuf; /* h3 response body */ + struct h1_req_parser h1; /* h1 request parsing */ size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ - size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ - uint64_t error3; /* HTTP/3 stream error code */ + curl_uint64_t error3; /* HTTP/3 stream error code */ curl_off_t upload_left; /* number of request bytes left to upload */ int status_code; /* HTTP status code */ - bool resp_hds_complete; /* we have a complete, final response */ - bool closed; /* TRUE on stream close */ - bool reset; /* TRUE on stream reset */ - bool send_closed; /* stream is local closed */ + 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 */ + BIT(reset); /* TRUE on stream reset */ + BIT(send_closed); /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(unsigned int id, void *stream) +{ + (void)id; + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - if(!data || !data->req.p.http) { - failf(data, "initialization failure, transfer not http initialized"); + if(!data) return CURLE_FAILED_INIT; - } if(stream) return CURLE_OK; @@ -216,29 +292,70 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); stream->sendbuf_len_in_flight = 0; - /* on recv, we need a flexible buffer limit since we also write - * headers to it that are not counted against the nghttp3 flow limits. */ - Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, - H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - stream->recv_buf_nonflow = 0; - - H3_STREAM_LCTX(data) = stream; - DEBUGF(LOG_CF(data, cf, "data setup (easy %p)", (void *)data)); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + if(Curl_uint_hash_count(&ctx->streams) == 1) + cf_ngtcp2_setup_keep_alive(cf, data); + return CURLE_OK; } -static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_ngtcp2_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_ngtcp2_ctx *ctx = cf->ctx; + DEBUGASSERT(data); + DEBUGASSERT(stream); + if(!stream->closed && ctx->qconn && ctx->h3conn) { + CURLcode result; + + nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); + ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); + stream->closed = TRUE; + (void)ngtcp2_conn_shutdown_stream(ctx->qconn, 0, stream->id, + NGHTTP3_H3_REQUEST_CANCELLED); + result = cf_progress_egress(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cancel stream -> %d", + stream->id, result); + } +} +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] easy handle is done", - stream->id)); - Curl_bufq_free(&stream->sendbuf); - Curl_bufq_free(&stream->recvbuf); - free(stream); - H3_STREAM_LCTX(data) = NULL; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] easy handle is done", + stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + Curl_uint_hash_remove(&ctx->streams, data->mid); + if(!Curl_uint_hash_count(&ctx->streams)) + cf_ngtcp2_setup_keep_alive(cf, data); + } +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && stream->upload_left && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -246,10 +363,33 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) the maximum packet burst to MAX_PKT_BURST packets. */ #define MAX_PKT_BURST 10 -static CURLcode cf_process_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data); -static CURLcode cf_flush_egress(struct Curl_cfilter *cf, - struct Curl_easy *data); +struct pkt_io_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + ngtcp2_tstamp ts; + ngtcp2_path_storage ps; +}; + +static void pktx_update_time(struct pkt_io_ctx *pktx, + struct Curl_cfilter *cf) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + vquic_ctx_update_time(&ctx->q); + pktx->ts = (ngtcp2_tstamp)ctx->q.last_op.tv_sec * NGTCP2_SECONDS + + (ngtcp2_tstamp)ctx->q.last_op.tv_usec * NGTCP2_MICROSECONDS; +} + +static void pktx_init(struct pkt_io_ctx *pktx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + pktx->cf = cf; + pktx->data = data; + ngtcp2_path_storage_zero(&pktx->ps); + pktx_update_time(pktx, cf); +} + static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, uint64_t datalen, void *user_data, void *stream_user_data); @@ -261,19 +401,13 @@ static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) return ctx->qconn; } -static ngtcp2_tstamp timestamp(void) -{ - struct curltime ct = Curl_now(); - return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS; -} - #ifdef DEBUG_NGTCP2 static void quic_printf(void *user_data, const char *fmt, ...) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - (void)ctx; /* TODO: need an easy handle to infof() message */ + (void)ctx; /* need an easy handle to infof() message */ va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); @@ -300,7 +434,8 @@ static void qlog_callback(void *user_data, uint32_t flags, } static void quic_settings(struct cf_ngtcp2_ctx *ctx, - struct Curl_easy *data) + struct Curl_easy *data, + struct pkt_io_ctx *pktx) { ngtcp2_settings *s = &ctx->settings; ngtcp2_transport_params *t = &ctx->transport_params; @@ -314,10 +449,10 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, #endif (void)data; - s->initial_ts = timestamp(); + s->initial_ts = pktx->ts; s->handshake_timeout = QUIC_HANDSHAKE_TIMEOUT; s->max_window = 100 * ctx->max_stream_window; - s->max_stream_window = ctx->max_stream_window; + s->max_stream_window = 10 * ctx->max_stream_window; t->initial_max_data = 10 * ctx->max_stream_window; t->initial_max_stream_data_bidi_local = ctx->max_stream_window; @@ -325,400 +460,123 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, t->initial_max_stream_data_uni = ctx->max_stream_window; t->initial_max_streams_bidi = QUIC_MAX_STREAMS; t->initial_max_streams_uni = QUIC_MAX_STREAMS; - t->max_idle_timeout = QUIC_IDLE_TIMEOUT; + t->max_idle_timeout = 0; /* no idle timeout from our side */ if(ctx->qlogfd != -1) { - s->qlog.write = qlog_callback; + s->qlog_write = qlog_callback; } } -#ifdef USE_OPENSSL -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#elif defined(USE_GNUTLS) -static int keylog_callback(gnutls_session_t session, const char *label, - const gnutls_datum_t *secret) -{ - gnutls_datum_t crandom; - gnutls_datum_t srandom; - - gnutls_session_get_random(session, &crandom, &srandom); - if(crandom.size != 32) { - return -1; - } +static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, + struct Curl_easy *data); - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); - return 0; -} -#elif defined(USE_WOLFSSL) -#if defined(HAVE_SECRET_CALLBACK) -static void keylog_callback(const WOLFSSL *ssl, const char *line) +static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) { - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#endif -#endif - -static int init_ngh3_conn(struct Curl_cfilter *cf); + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; + struct Curl_easy *data; -#ifdef USE_OPENSSL -static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct connectdata *conn = cf->conn; - CURLcode result = CURLE_FAILED_INIT; - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + (void)tconn; + DEBUGASSERT(ctx); + data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(!ctx || !data) + return NGHTTP3_ERR_CALLBACK_FAILURE; - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } + ctx->handshake_at = curlx_now(); + ctx->tls_handshake_complete = TRUE; + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ -#ifdef OPENSSL_IS_BORINGSSL - if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); - goto out; - } -#else - if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_openssl_configure_client_context failed"); - goto out; - } + ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf, + data, &ctx->peer); + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)curlx_timediff(ctx->handshake_at, ctx->started_at)); + /* In case of earlydata, where we simulate being connected, update + * the handshake time when we really did connect */ + if(ctx->use_earlydata) + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + if(ctx->use_earlydata) { +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + ctx->earlydata_accepted = + (SSL_get_early_data_status(ctx->tls.ossl.ssl) != + SSL_EARLY_DATA_REJECTED); #endif - - SSL_CTX_set_default_verify_paths(ssl_ctx); - -#ifdef OPENSSL_IS_BORINGSSL - if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_curves_list failed"); - goto out; - } +#ifdef USE_GNUTLS + int flags = gnutls_session_get_flags(ctx->tls.gtls.session); + ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); +#endif +#ifdef USE_WOLFSSL +#ifdef WOLFSSL_EARLY_DATA + ctx->earlydata_accepted = + (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != + WOLFSSL_EARLY_DATA_REJECTED); #else - if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) { - char error_buffer[256]; - ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); - failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); - goto out; - } - - if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - goto out; - } + DEBUGASSERT(0); /* should not come here if ED is disabled. */ + ctx->earlydata_accepted = FALSE; +#endif /* WOLFSSL_EARLY_DATA */ #endif - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); - } - - result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx); - if(result) - goto out; - - /* OpenSSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(ssl_ctx, conn->ssl_config.verifypeer ? - SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } + CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", + ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); + Curl_pgrsEarlyData(data, ctx->earlydata_accepted ? + (curl_off_t)ctx->earlydata_skip : + -(curl_off_t)ctx->earlydata_skip); } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; -} - -static CURLcode quic_set_client_cert(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - SSL_CTX *ssl_ctx = ctx->sslctx; - const struct ssl_config_data *ssl_config; - - ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET); - DEBUGASSERT(ssl_config); - - if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob - || ssl_config->cert_type) { - return Curl_ossl_set_client_cert( - data, ssl_ctx, ssl_config->primary.clientcert, - ssl_config->primary.cert_blob, ssl_config->cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd); - } - - return CURLE_OK; + return 0; } -/** SSL callbacks ***/ +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data); -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) +static bool cf_ngtcp2_err_is_fatal(int code) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - - (void)data; - DEBUGASSERT(!ctx->ssl); - ctx->ssl = SSL_new(ctx->sslctx); - - SSL_set_app_data(ctx->ssl, &ctx->conn_ref); - SSL_set_connect_state(ctx->ssl); - SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name); - return CURLE_OK; -} -#elif defined(USE_GNUTLS) -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result; - gnutls_datum_t alpn[2]; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = cf->conn->host.name; - long * const pverifyresult = &data->set.ssl.certverifyresult; - int rc; - - DEBUGASSERT(ctx->gtls == NULL); - ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); - if(!ctx->gtls) - return CURLE_OUT_OF_MEMORY; - - result = gtls_client_init(data, &cf->conn->ssl_config, &data->set.ssl, - hostname, ctx->gtls, pverifyresult); - if(result) - return result; - - gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref); - - if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { - DEBUGF(LOG_CF(data, cf, - "ngtcp2_crypto_gnutls_configure_client_session failed\n")); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL); - if(rc < 0) { - DEBUGF(LOG_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", - gnutls_strerror(rc))); - return CURLE_QUIC_CONNECT_ERROR; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); - } - - /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ - alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1; - alpn[0].size = sizeof(H3_ALPN_H3_29) - 2; - alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; - alpn[1].size = sizeof(H3_ALPN_H3) - 2; - - gnutls_alpn_set_protocols(ctx->gtls->session, - alpn, 2, GNUTLS_ALPN_MANDATORY); - return CURLE_OK; + return (NGTCP2_ERR_FATAL >= code) || + (NGTCP2_ERR_DROP_CONN == code) || + (NGTCP2_ERR_IDLE_CLOSE == code); } -#elif defined(USE_WOLFSSL) -static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_ngtcp2_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) { - struct connectdata *conn = cf->conn; - CURLcode result = CURLE_FAILED_INIT; - WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); - goto out; - } - - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - - if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) { - char error_buffer[256]; - ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); - failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); - goto out; - } - - if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - goto out; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { -#if defined(HAVE_SECRET_CALLBACK) - wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); -#else - failf(data, "wolfSSL was built without keylog callback"); - goto out; -#endif - } - - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - if(conn->ssl_config.CAfile || conn->ssl_config.CApath) { - /* tell wolfSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - goto out; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + struct cf_ngtcp2_ctx *ctx = cf->ctx; + if(!ctx->last_error.error_code) { + if(NGTCP2_ERR_CRYPTO == code) { + ngtcp2_ccerr_set_tls_alert(&ctx->last_error, + ngtcp2_conn_get_tls_alert(ctx->qconn), + NULL, 0); } -#ifdef CURL_CA_FALLBACK else { - /* verifying the peer without any CA certificates won't work so - use wolfssl's built-in default as fallback */ - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - else { - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); - } - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; + ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); } } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = cf->conn->host.name; - - (void)data; - DEBUGASSERT(!ctx->ssl); - ctx->ssl = wolfSSL_new(ctx->sslctx); - - wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref); - wolfSSL_set_connect_state(ctx->ssl); - wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME, - hostname, (unsigned short)strlen(hostname)); - - return CURLE_OK; + if(cf_ngtcp2_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); } -#endif /* defined(USE_WOLFSSL) */ -static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) +static bool cf_ngtcp2_h3_err_is_fatal(int code) { - (void)user_data; - (void)tconn; - return 0; + return (NGHTTP3_ERR_FATAL >= code) || + (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); } -static void report_consumed_data(struct Curl_cfilter *cf, - struct Curl_easy *data, - size_t consumed) +static void cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) { - struct stream_ctx *stream = H3_STREAM_CTX(data); struct cf_ngtcp2_ctx *ctx = cf->ctx; - - if(!stream) - return; - /* the HTTP/1.1 response headers are written to the buffer, but - * consuming those does not count against flow control. */ - if(stream->recv_buf_nonflow) { - if(consumed >= stream->recv_buf_nonflow) { - consumed -= stream->recv_buf_nonflow; - stream->recv_buf_nonflow = 0; - } - else { - stream->recv_buf_nonflow -= consumed; - consumed = 0; - } - } - if(consumed > 0) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] consumed %zu DATA bytes", - stream->id, consumed)); - ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, - consumed); - ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + if(!ctx->last_error.error_code) { + ngtcp2_ccerr_set_application_error(&ctx->last_error, + nghttp3_err_infer_quic_app_error_code(code), NULL, 0); } + if(cf_ngtcp2_h3_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); } static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream_id, uint64_t offset, + int64_t sid, uint64_t offset, const uint8_t *buf, size_t buflen, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; nghttp3_ssize nconsumed; int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; struct Curl_easy *data = stream_user_data; @@ -727,20 +585,26 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, nconsumed = nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read_stream(len=%zu) -> %zd", - stream_id, buflen, nconsumed)); + if(!data) + data = CF_DATA_CURRENT(cf); + if(data) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read_stream(len=%zu) -> %zd", + stream_id, buflen, nconsumed); if(nconsumed < 0) { - ngtcp2_ccerr_set_application_error( - &ctx->last_error, - nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + if(data && stream) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] error on known stream, " + "reset=%d, closed=%d", + stream_id, stream->reset, stream->closed); + } return NGTCP2_ERR_CALLBACK_FAILURE; } /* number of bytes inside buflen which consists of framing overhead * including QPACK HEADERS. In other words, it does not consume payload of * DATA frame. */ - ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed); - ngtcp2_conn_extend_max_offset(tconn, nconsumed); + ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, (uint64_t)nconsumed); + ngtcp2_conn_extend_max_offset(tconn, (uint64_t)nconsumed); return 0; } @@ -760,7 +624,7 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, (void)stream_user_data; rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -768,41 +632,45 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, } static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream3_id, uint64_t app_error_code, + int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct Curl_easy *data = stream_user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = (curl_int64_t)sid; int rv; (void)tconn; - (void)data; /* stream is closed... */ + if(!data) + data = CF_DATA_CURRENT(cf); + if(!data) + return NGTCP2_ERR_CALLBACK_FAILURE; if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { app_error_code = NGHTTP3_H3_NO_ERROR; } - rv = nghttp3_conn_close_stream(ctx->h3conn, stream3_id, - app_error_code); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] quic close(err=%" - PRIu64 ") -> %d", stream3_id, app_error_code, rv)); - if(rv) { - ngtcp2_ccerr_set_application_error( - &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, app_error_code); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] quic close(app_error=%" + FMT_PRIu64 ") -> %d", stream_id, (curl_uint64_t)app_error_code, + rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + cf_ngtcp2_h3_err_set(cf, data, rv); return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } -static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, +static int cb_stream_reset(ngtcp2_conn *tconn, int64_t sid, uint64_t final_size, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; struct Curl_easy *data = stream_user_data; int rv; (void)tconn; @@ -811,8 +679,8 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, (void)data; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv)); - if(rv) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -831,7 +699,7 @@ static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, (void)stream_user_data; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -842,10 +710,16 @@ static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, uint64_t max_streams, void *user_data) { - (void)tconn; - (void)max_streams; - (void)user_data; + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + (void)tconn; + ctx->max_bidi_streams = max_streams; + if(data) + CURL_TRC_CF(data, cf, "max bidi streams now %" FMT_PRIu64 + ", used %" FMT_PRIu64, (curl_uint64_t)ctx->max_bidi_streams, + (curl_uint64_t)ctx->used_bidi_streams); return 0; } @@ -855,16 +729,23 @@ 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; int rv; (void)tconn; (void)max_data; - (void)stream_user_data; rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); - if(rv) { + 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, "[%" FMT_PRId64 "] unblock quic flow", + (curl_int64_t)stream_id); + stream->quic_flow_blocked = FALSE; + h3_drain_stream(cf, s_data); + } return 0; } @@ -876,7 +757,7 @@ static void cb_rand(uint8_t *dest, size_t destlen, result = Curl_rand(NULL, dest, destlen); if(result) { - /* cb_rand is only used for non-cryptographic context. If Curl_rand + /* cb_rand is only used for non-cryptographic context. If Curl_rand failed, just fill 0 and call it *random*. */ memset(dest, 0, destlen); } @@ -902,28 +783,36 @@ static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, return 0; } -static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level, +static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level, void *user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; + struct Curl_easy *data = CF_DATA_CURRENT(cf); (void)tconn; - if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) { + if(level != NGTCP2_ENCRYPTION_LEVEL_1RTT) return 0; - } - if(init_ngh3_conn(cf) != CURLE_OK) { - return NGTCP2_ERR_CALLBACK_FAILURE; + DEBUGASSERT(ctx); + DEBUGASSERT(data); + if(ctx && data && !ctx->h3conn) { + if(init_ngh3_conn(cf, data)) + return NGTCP2_ERR_CALLBACK_FAILURE; } - return 0; } +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(push) +# pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#endif + static ngtcp2_callbacks ng_callbacks = { ngtcp2_crypto_client_initial_cb, NULL, /* recv_client_initial */ ngtcp2_crypto_recv_crypto_data_cb, - cb_handshake_completed, + cf_ngtcp2_handshake_completed, NULL, /* recv_version_negotiation */ ngtcp2_crypto_encrypt_cb, ngtcp2_crypto_decrypt_cb, @@ -962,131 +851,186 @@ static ngtcp2_callbacks ng_callbacks = { NULL, /* early_data_rejected */ }; -static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(pop) +#endif + +/** + * Connection maintenance like timeouts on packet ACKs etc. are done by us, not + * the OS like for TCP. POLL events on the socket therefore are not + * sufficient. + * ngtcp2 tells us when it wants to be invoked again. We handle that via + * the `Curl_expire()` mechanisms. + */ +static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct SingleRequest *k = &data->req; - int rv = GETSOCK_BLANK; - struct stream_ctx *stream = H3_STREAM_CTX(data); - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - socks[0] = ctx->q.sockfd; + struct pkt_io_ctx local_pktx; + ngtcp2_tstamp expiry; - /* in HTTP/3 we can always get a frame, so check read */ - rv |= GETSOCK_READSOCK(0); + if(!pktx) { + pktx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + pktx_update_time(pktx, cf); + } - /* we're still uploading or the HTTP/2 layer wants to send data */ - if((k->keepon & KEEP_SENDBITS) == KEEP_SEND && - ngtcp2_conn_get_cwnd_left(ctx->qconn) && - ngtcp2_conn_get_max_data_left(ctx->qconn) && - stream && nghttp3_conn_is_stream_writable(ctx->h3conn, stream->id)) - rv |= GETSOCK_WRITESOCK(0); + expiry = ngtcp2_conn_get_expiry(ctx->qconn); + if(expiry != UINT64_MAX) { + if(expiry <= pktx->ts) { + CURLcode result; + int rv = ngtcp2_conn_handle_expiry(ctx->qconn, pktx->ts); + if(rv) { + failf(data, "ngtcp2_conn_handle_expiry returned error: %s", + ngtcp2_strerror(rv)); + cf_ngtcp2_err_set(cf, data, rv); + return CURLE_SEND_ERROR; + } + result = cf_progress_ingress(cf, data, pktx); + if(result) + return result; + result = cf_progress_egress(cf, data, pktx); + if(result) + return result; + /* ask again, things might have changed */ + expiry = ngtcp2_conn_get_expiry(ctx->qconn); + } - /* DEBUGF(LOG_CF(data, cf, "get_select_socks -> %x (sock=%d)", - rv, (int)socks[0])); */ - CF_DATA_RESTORE(cf, save); - return rv; + if(expiry > pktx->ts) { + ngtcp2_duration timeout = expiry - pktx->ts; + if(timeout % NGTCP2_MILLISECONDS) { + timeout += NGTCP2_MILLISECONDS; + } + Curl_expire(data, (timediff_t)(timeout / NGTCP2_MILLISECONDS), + EXPIRE_QUIC); + } + } + return CURLE_OK; } -static void drain_stream(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { - struct stream_ctx *stream = H3_STREAM_CTX(data); - unsigned char bits; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool want_recv, want_send; - (void)cf; - bits = CURL_CSELECT_IN; - if(stream && !stream->send_closed && stream->upload_left) - bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; - Curl_expire(data, 0, EXPIRE_RUN_NOW); + if(!ctx->qconn) + return; + + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(!want_send && !Curl_bufq_is_empty(&ctx->q.sendbuf)) + want_send = TRUE; + + if(want_recv || want_send) { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = want_send && (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || + !ngtcp2_conn_get_max_data_left(ctx->qconn)); + s_exhaust = want_send && stream && stream->id >= 0 && + stream->quic_flow_blocked; + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); + + Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + CF_DATA_RESTORE(cf, save); } } -static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_stream_close(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct stream_ctx *stream = H3_STREAM_CTX(data); + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; - (void)app_error_code; - (void)cf; /* we might be called by nghttp3 after we already cleaned up */ if(!stream) return 0; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] h3 close(err=%" PRId64 ")", - stream_id, app_error_code)); stream->closed = TRUE; - stream->error3 = app_error_code; - if(app_error_code == NGHTTP3_H3_INTERNAL_ERROR) { + stream->error3 = (curl_uint64_t)app_error_code; + if(stream->error3 != NGHTTP3_H3_NO_ERROR) { stream->reset = TRUE; stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] RESET: error %" FMT_PRIu64, + stream->id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] CLOSED", stream->id); } - drain_stream(cf, data); + h3_drain_stream(cf, data); return 0; } -/* - * write_resp_raw() copies response data in raw format to the `data`'s - * receive buffer. If not enough space is available, it appends to the - * `data`'s overflow buffer. - */ -static CURLcode write_resp_raw(struct Curl_cfilter *cf, - struct Curl_easy *data, - const void *mem, size_t memlen, - bool flow) +static void h3_xfer_write_resp_hd(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) { - struct stream_ctx *stream = H3_STREAM_CTX(data); - CURLcode result = CURLE_OK; - ssize_t nwritten; - (void)cf; - if(!stream) { - return CURLE_RECV_ERROR; - } - nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); - /* DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] add recvbuf(len=%zu) " - "-> %zd, %d", stream->id, memlen, nwritten, result)); - */ - if(nwritten < 0) { - return result; + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] error %d writing %zu " + "bytes of headers", stream->id, stream->xfer_result, blen); } +} - if(!flow) - stream->recv_buf_nonflow += (size_t)nwritten; +static void h3_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ - if((size_t)nwritten < memlen) { - /* This MUST not happen. Our recbuf is dimensioned to hold the - * full max_stream_window and then some for this very reason. */ - DEBUGASSERT(0); - return CURLE_RECV_ERROR; + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] error %d writing %zu bytes " + "of data", stream->id, stream->xfer_result, blen); + } } - return result; } static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, - const uint8_t *buf, size_t buflen, + const uint8_t *buf, size_t blen, 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; - CURLcode result; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream3_id; - result = write_resp_raw(cf, data, buf, buflen, TRUE); - drain_stream(cf, data); - return result? -1 : 0; + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + h3_xfer_write_resp(cf, data, stream, (const char *)buf, blen, FALSE); + if(blen) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] ACK %zu bytes of DATA", + stream->id, blen); + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, blen); + ngtcp2_conn_extend_max_offset(ctx->qconn, blen); + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu", stream->id, blen); + return 0; } static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, @@ -1105,13 +1049,14 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, return 0; } -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t sid, 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 stream_ctx *stream = H3_STREAM_CTX(data); - CURLcode result = CURLE_OK; + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; (void)fin; @@ -1119,31 +1064,30 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, if(!stream) return 0; - /* add a CRLF only if we've received some headers */ - result = write_resp_raw(cf, data, "\r\n", 2, FALSE); - if(result) { - return -1; - } + /* add a CRLF only if we have received some headers */ + h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] end_headers(status_code=%d", - stream_id, stream->status_code)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] end_headers, status=%d", + stream_id, stream->status_code); if(stream->status_code / 100 != 1) { stream->resp_hds_complete = TRUE; } - drain_stream(cf, data); + h3_drain_stream(cf, data); return 0; } -static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); struct Curl_easy *data = stream_user_data; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; (void)conn; (void)stream_id; @@ -1156,43 +1100,45 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, return 0; if(token == NGHTTP3_QPACK_TOKEN__STATUS) { - char line[14]; /* status line is always 13 characters long */ - size_t ncopy; result = Curl_http_decode_status(&stream->status_code, (const char *)h3val.base, h3val.len); if(result) return -1; - ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", - stream->status_code); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] status: %s", - stream_id, line)); - result = write_resp_raw(cf, data, line, ncopy, FALSE); + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 ")); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] status: %s", + stream_id, curlx_dyn_ptr(&ctx->scratch)); if(result) { return -1; } } else { /* store as an HTTP1-style header */ - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] header: %.*s: %.*s", - stream_id, (int)h3name.len, h3name.base, - (int)h3val.len, h3val.base)); - result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, ": ", 2, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, "\r\n", 2, FALSE); - if(result) { - return -1; - } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] header: %.*s: %.*s", + stream_id, (int)h3name.len, h3name.base, + (int)h3val.len, h3val.base); + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, + (const char *)h3name.base, h3name.len); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = curlx_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); } return 0; } @@ -1207,29 +1153,31 @@ static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, (void)conn; (void)stream_user_data; - rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, stream_id, app_error_code); + rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, 0, stream_id, + app_error_code); if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; + return NGHTTP3_ERR_CALLBACK_FAILURE; } return 0; } -static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; struct Curl_easy *data = stream_user_data; int rv; (void)conn; (void)data; - rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, stream_id, + rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, app_error_code); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; + return NGHTTP3_ERR_CALLBACK_FAILURE; } return 0; @@ -1249,17 +1197,19 @@ static nghttp3_callbacks ngh3_callbacks = { cb_h3_stop_sending, NULL, /* end_stream */ cb_h3_reset_stream, - NULL /* shutdown */ + NULL, /* shutdown */ + NULL /* recv_settings */ }; -static int init_ngh3_conn(struct Curl_cfilter *cf) +static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result; - int rc; int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; + int rc; if(ngtcp2_conn_get_streams_uni_left(ctx->qconn) < 3) { + failf(data, "QUIC connection lacks 3 uni streams to run HTTP/3"); return CURLE_QUIC_CONNECT_ERROR; } @@ -1271,87 +1221,70 @@ static int init_ngh3_conn(struct Curl_cfilter *cf) nghttp3_mem_default(), cf); if(rc) { - result = CURLE_OUT_OF_MEMORY; - goto fail; + failf(data, "error creating nghttp3 connection instance"); + return CURLE_OUT_OF_MEMORY; } rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; + failf(data, "error creating HTTP/3 control stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; } rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id); if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; + failf(data, "error binding HTTP/3 control stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; } rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL); if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; + failf(data, "error creating HTTP/3 qpack encoding stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; } rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL); if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; + failf(data, "error creating HTTP/3 qpack decoding stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; } rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id, qpack_dec_stream_id); if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; + failf(data, "error binding HTTP/3 qpack streams: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; } return CURLE_OK; -fail: - - return result; } static ssize_t recv_closed_stream(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream, + struct h3_stream_ctx *stream, CURLcode *err) { ssize_t nread = -1; (void)cf; if(stream->reset) { - failf(data, - "HTTP/3 stream %" PRId64 " reset by server", stream->id); - *err = CURLE_PARTIAL_FILE; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d", - stream->id, *err)); + failf(data, "HTTP/3 stream %" FMT_PRId64 " reset by server", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; goto out; } - else if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + else if(!stream->resp_hds_complete) { failf(data, - "HTTP/3 stream %" PRId64 " was not closed cleanly: " - "(err %"PRId64")", stream->id, stream->error3); - *err = CURLE_HTTP3; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed uncleanly" - " -> %d", stream->id, *err)); - goto out; - } - - if(!stream->resp_hds_complete) { - failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", + "HTTP/3 stream %" FMT_PRId64 " was closed cleanly, but before " + "getting all response header fields, treated as error", stream->id); *err = CURLE_HTTP3; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete" - " -> %d", stream->id, *err)); goto out; } - else { - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok" - " -> %d", stream->id, *err)); - } *err = CURLE_OK; nread = 0; @@ -1361,14 +1294,16 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, /* incoming data frames on the h3 stream */ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - char *buf, size_t len, CURLcode *err) + char *buf, size_t blen, CURLcode *err) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; + struct pkt_io_ctx pktx; (void)ctx; + (void)buf; CF_DATA_SAVE(save, cf, data); DEBUGASSERT(cf->connected); @@ -1377,57 +1312,51 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(ctx->h3conn); *err = CURLE_OK; - if(!stream) { + /* handshake verification failed in callback, do not recv anything */ + if(ctx->tls_vrfy_result) + return ctx->tls_vrfy_result; + + pktx_init(&pktx, cf, data); + + if(!stream || ctx->shutdown_started) { *err = CURLE_RECV_ERROR; goto out; } - if(!Curl_bufq_is_empty(&stream->recvbuf)) { - nread = Curl_bufq_read(&stream->recvbuf, - (unsigned char *)buf, len, err); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read recvbuf(len=%zu) " - "-> %zd, %d", stream->id, len, nread, *err)); - if(nread < 0) - goto out; - report_consumed_data(cf, data, nread); - } - - if(cf_process_ingress(cf, data)) { + if(cf_progress_ingress(cf, data, &pktx)) { *err = CURLE_RECV_ERROR; nread = -1; goto out; } - /* recvbuf had nothing before, maybe after progressing ingress? */ - if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { - nread = Curl_bufq_read(&stream->recvbuf, - (unsigned char *)buf, len, err); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read recvbuf(len=%zu) " - "-> %zd, %d", stream->id, len, nread, *err)); - if(nread < 0) - goto out; - report_consumed_data(cf, data, nread); - } - - if(nread > 0) { - drain_stream(cf, data); - } - else { - if(stream->closed) { - nread = recv_closed_stream(cf, data, stream, err); - goto out; - } - *err = CURLE_AGAIN; + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; nread = -1; + goto out; } + else if(stream->closed) { + nread = recv_closed_stream(cf, data, stream, err); + goto out; + } + *err = CURLE_AGAIN; + nread = -1; out: - if(cf_flush_egress(cf, data)) { + if(cf_progress_egress(cf, data, &pktx)) { *err = CURLE_SEND_ERROR; nread = -1; } - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv(len=%zu) -> %zd, %d", - stream? stream->id : -1, len, nread, *err)); + else { + CURLcode result2 = check_and_set_expiry(cf, data, &pktx); + if(result2) { + *err = result2; + nread = -1; + } + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_recv(blen=%zu) -> %zd, %d", + stream ? stream->id : -1, blen, nread, *err); CF_DATA_RESTORE(cf, save); return nread; } @@ -1437,8 +1366,9 @@ 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 stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t skiplen; (void)cf; @@ -1454,20 +1384,11 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, Curl_bufq_skip(&stream->sendbuf, skiplen); stream->sendbuf_len_in_flight -= skiplen; - /* `sendbuf` *might* now have more room. If so, resume this - * possibly paused stream. And also tell our transfer engine that - * it may continue KEEP_SEND if told to PAUSE. */ - if(!Curl_bufq_is_full(&stream->sendbuf)) { + /* Resume upload processing if we have more data to send */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { int rv = nghttp3_conn_resume_stream(conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - drain_stream(cf, data); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] unpausing acks", - stream_id)); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; } } return 0; @@ -1480,8 +1401,9 @@ 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 stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten = 0; size_t nvecs = 0; (void)cf; @@ -1504,7 +1426,7 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, while(nvecs < veccnt && Curl_bufq_peek_at(&stream->sendbuf, stream->sendbuf_len_in_flight, - (const unsigned char **)&vec[nvecs].base, + CURL_UNCONST(&vec[nvecs].base), &vec[nvecs].len)) { stream->sendbuf_len_in_flight += vec[nvecs].len; nwritten += vec[nvecs].len; @@ -1524,16 +1446,17 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, } else if(!nwritten) { /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read req body -> AGAIN", - stream->id)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> AGAIN", + stream->id); return NGHTTP3_ERR_WOULDBLOCK; } - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read req body -> " - "%d vecs%s with %zu (buffered=%zu, left=%zd)", stream->id, - (int)nvecs, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"", - nwritten, Curl_bufq_len(&stream->sendbuf), - stream->upload_left)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", + stream->id, (int)nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", + nwritten, Curl_bufq_len(&stream->sendbuf), + stream->upload_left); return (nghttp3_ssize)nvecs; } @@ -1547,8 +1470,8 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, CURLcode *err) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = NULL; - struct h1_req_parser h1; + struct h3_stream_ctx *stream = NULL; + int64_t sid; struct dynhds h2_headers; size_t nheader; nghttp3_nv *nva = NULL; @@ -1558,33 +1481,34 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, nghttp3_data_reader reader; nghttp3_data_reader *preader = NULL; - Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); *err = h3_data_setup(cf, data); if(*err) goto out; - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); - - rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream->id, NULL); - if(rc) { - failf(data, "can get bidi streams"); - *err = CURLE_SEND_ERROR; + if(!stream) { + *err = CURLE_FAILED_INIT; goto out; } - nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err); + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); if(nwritten < 0) goto out; - DEBUGASSERT(h1.done); - DEBUGASSERT(h1.req); + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); - *err = Curl_http_req_to_h2(&h2_headers, h1.req, data); + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); if(*err) { nwritten = -1; goto out; } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); nheader = Curl_dynhds_count(&h2_headers); nva = malloc(sizeof(nghttp3_nv) * nheader); @@ -1603,6 +1527,16 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, nva[i].flags = NGHTTP3_NV_FLAG_NONE; } + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); + if(rc) { + failf(data, "can get bidi streams"); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + stream->id = (curl_int64_t)sid; + ++ctx->used_bidi_streams; + switch(data->state.httpreq) { case HTTPREQ_POST: case HTTPREQ_POST_FORM: @@ -1614,27 +1548,30 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, else /* data sending without specifying the data amount up front */ stream->upload_left = -1; /* unknown */ - reader.read_data = cb_h3_read_req_body; - preader = &reader; break; default: /* there is not request body */ stream->upload_left = 0; /* no request body */ - preader = NULL; break; } + stream->send_closed = (stream->upload_left == 0); + if(!stream->send_closed) { + reader.read_data = cb_h3_read_req_body; + preader = &reader; + } + rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, nva, nheader, preader, data); if(rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: - DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send, " - "connection is closing", stream->id)); + CURL_TRC_CF(data, cf, "h3sid[%" FMT_PRId64 "] failed to send, " + "connection is closing", stream->id); break; default: - DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)", - stream->id, rc, ngtcp2_strerror(rc))); + CURL_TRC_CF(data, cf, "h3sid[%" FMT_PRId64 "] failed to send -> " + "%d (%s)", stream->id, rc, nghttp3_strerror(rc)); break; } *err = CURLE_SEND_ERROR; @@ -1642,166 +1579,148 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, goto out; } - infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)", - stream->id, (void *)data); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s", - stream->id, data->state.url)); + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] OPENED stream for %s", + stream->id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] [%.*s: %.*s]", stream->id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + } out: free(nva); - Curl_h1_req_parse_free(&h1); Curl_dynhds_free(&h2_headers); return nwritten; } static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); - ssize_t sent = 0; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t sent = -1; struct cf_call_data save; + struct pkt_io_ctx pktx; + CURLcode result; CF_DATA_SAVE(save, cf, data); DEBUGASSERT(cf->connected); DEBUGASSERT(ctx->qconn); DEBUGASSERT(ctx->h3conn); + pktx_init(&pktx, cf, data); *err = CURLE_OK; - if(stream && stream->closed) { - *err = CURLE_HTTP3; - sent = -1; - goto out; + /* handshake verification failed in callback, do not send anything */ + if(ctx->tls_vrfy_result) + return ctx->tls_vrfy_result; + + (void)eos; /* use for stream EOF and block handling */ + result = cf_progress_ingress(cf, data, &pktx); + if(result) { + *err = result; } if(!stream || stream->id < 0) { + if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot open stream on closed connection"); + *err = CURLE_SEND_ERROR; + goto out; + } sent = h3_stream_open(cf, data, buf, len, err); if(sent < 0) { - DEBUGF(LOG_CF(data, cf, "failed to open stream -> %d", *err)); + CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); + goto out; + } + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; + goto out; + } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a final + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] discarding data" + "on closed stream with response", stream->id); + *err = CURLE_OK; + sent = (ssize_t)len; goto out; } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + *err = CURLE_HTTP3; + sent = -1; + goto out; + } + else if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot send on closed connection"); + *err = CURLE_SEND_ERROR; + goto out; } else { sent = Curl_bufq_write(&stream->sendbuf, buf, len, err); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send, add to " - "sendbuf(len=%zu) -> %zd, %d", - stream->id, len, sent, *err)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %zd, %d", + stream->id, len, sent, *err); if(sent < 0) { - if(*err == CURLE_AGAIN) { - /* Can't add more to the send buf, needs to drain first. - * Pause the sending to avoid a busy loop. */ - data->req.keepon |= KEEP_SEND_HOLD; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] pause send", - stream->id)); - } goto out; } (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); } - if(cf_flush_egress(cf, data)) { - *err = CURLE_SEND_ERROR; + if(sent > 0 && !ctx->tls_handshake_complete && ctx->use_earlydata) + ctx->earlydata_skip += sent; + + result = cf_progress_egress(cf, data, &pktx); + if(result) { + *err = result; sent = -1; - goto out; } out: + result = check_and_set_expiry(cf, data, &pktx); + if(result) { + *err = result; + sent = -1; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->id : -1, len, sent, *err); CF_DATA_RESTORE(cf, save); return sent; } -static CURLcode qng_verify_peer(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - const char *hostname, *disp_hostname; - int port; - char *snihost; - - Curl_conn_get_host(data, cf->sockindex, &hostname, &disp_hostname, &port); - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) - return CURLE_PEER_FAILED_VERIFICATION; - - cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - cf->conn->httpversion = 30; - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - - if(cf->conn->ssl_config.verifyhost) { -#ifdef USE_OPENSSL - X509 *server_cert; - server_cert = SSL_get_peer_certificate(ctx->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, cf->conn, server_cert); - X509_free(server_cert); - if(result) - return result; -#elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(data, ctx->gtls->session, - &cf->conn->ssl_config, &data->set.ssl, - hostname, disp_hostname, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); - if(result) - return result; -#elif defined(USE_WOLFSSL) - if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE) - return CURLE_PEER_FAILED_VERIFICATION; -#endif - infof(data, "Verified certificate just fine"); - } - else - infof(data, "Skipped certificate verification"); -#ifdef USE_OPENSSL - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); -#endif - return result; -} - -struct recv_ctx { - struct Curl_cfilter *cf; - struct Curl_easy *data; - ngtcp2_tstamp ts; - size_t pkt_count; -}; - static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, struct sockaddr_storage *remote_addr, socklen_t remote_addrlen, int ecn, void *userp) { - struct recv_ctx *r = userp; - struct cf_ngtcp2_ctx *ctx = r->cf->ctx; + struct pkt_io_ctx *pktx = userp; + struct cf_ngtcp2_ctx *ctx = pktx->cf->ctx; ngtcp2_pkt_info pi; ngtcp2_path path; int rv; - ++r->pkt_count; ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); + (socklen_t)ctx->q.local_addrlen); ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, remote_addrlen); - pi.ecn = (uint32_t)ecn; + pi.ecn = (uint8_t)ecn; - rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, pkt, pktlen, r->ts); + rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, pkt, pktlen, pktx->ts); if(rv) { - DEBUGF(LOG_CF(r->data, r->cf, "ingress, read_pkt -> %s", - ngtcp2_strerror(rv))); - if(!ctx->last_error.error_code) { - if(rv == NGTCP2_ERR_CRYPTO) { - ngtcp2_ccerr_set_tls_alert(&ctx->last_error, - ngtcp2_conn_get_tls_alert(ctx->qconn), - NULL, 0); - } - else { - ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); - } - } + CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", + ngtcp2_strerror(rv), rv); + cf_ngtcp2_err_set(pktx->cf, pktx->data, rv); if(rv == NGTCP2_ERR_CRYPTO) /* this is a "TLS problem", but a failed certificate verification @@ -1813,40 +1732,25 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, return CURLE_OK; } -static CURLcode cf_process_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct recv_ctx rctx; - size_t pkts_chunk = 128, i; - size_t pkts_max = 10 * pkts_chunk; - CURLcode result; - - rctx.cf = cf; - rctx.data = data; - rctx.ts = timestamp(); - rctx.pkt_count = 0; + struct pkt_io_ctx local_pktx; + CURLcode result = CURLE_OK; - for(i = 0; i < pkts_max; i += pkts_chunk) { - rctx.pkt_count = 0; - result = vquic_recv_packets(cf, data, &ctx->q, pkts_chunk, - recv_pkt, &rctx); - if(result) /* error */ - break; - if(rctx.pkt_count < pkts_chunk) /* got less than we could */ - break; - /* give egress a chance before we receive more */ - result = cf_flush_egress(cf, data); + if(!pktx) { + pktx_init(&local_pktx, cf, data); + pktx = &local_pktx; } - return result; -} -struct read_ctx { - struct Curl_cfilter *cf; - struct Curl_easy *data; - ngtcp2_tstamp ts; - ngtcp2_path_storage *ps; -}; + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; + + return vquic_recv_packets(cf, data, &ctx->q, 1000, recv_pkt, pktx); +} /** * Read a network packet to send from ngtcp2 into `buf`. @@ -1856,7 +1760,7 @@ static ssize_t read_pkt_to_send(void *userp, unsigned char *buf, size_t buflen, CURLcode *err) { - struct read_ctx *x = userp; + struct pkt_io_ctx *x = userp; struct cf_ngtcp2_ctx *ctx = x->cf->ctx; nghttp3_vec vec[16]; nghttp3_ssize veccnt; @@ -1864,7 +1768,7 @@ static ssize_t read_pkt_to_send(void *userp, uint32_t flags; int64_t stream_id; int fin; - ssize_t nwritten, n; + ssize_t nwritten = 0, n; veccnt = 0; stream_id = -1; fin = 0; @@ -1876,19 +1780,16 @@ static ssize_t read_pkt_to_send(void *userp, * When ngtcp2 is happy (because it has no other frame that would fit * or it has nothing more to send), it returns the total length * of the assembled packet. This may be 0 if there was nothing to send. */ - nwritten = 0; *err = CURLE_OK; for(;;) { if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) { veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec, - sizeof(vec) / sizeof(vec[0])); + CURL_ARRAYSIZE(vec)); if(veccnt < 0) { failf(x->data, "nghttp3_conn_writev_stream returned error: %s", nghttp3_strerror((int)veccnt)); - ngtcp2_ccerr_set_application_error( - &ctx->last_error, - nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0); + cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); *err = CURLE_SEND_ERROR; return -1; } @@ -1896,7 +1797,7 @@ static ssize_t read_pkt_to_send(void *userp, flags = NGTCP2_WRITE_STREAM_FLAG_MORE | (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); - n = ngtcp2_conn_writev_stream(ctx->qconn, x->ps? &x->ps->path : NULL, + n = ngtcp2_conn_writev_stream(ctx->qconn, &x->ps.path, NULL, buf, buflen, &ndatalen, flags, stream_id, (const ngtcp2_vec *)vec, veccnt, x->ts); @@ -1908,11 +1809,18 @@ static ssize_t read_pkt_to_send(void *userp, } else if(n < 0) { switch(n) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: + case NGTCP2_ERR_STREAM_DATA_BLOCKED: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); DEBUGASSERT(ndatalen == -1); nghttp3_conn_block_stream(ctx->h3conn, stream_id); + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] block quic flow", + (curl_int64_t)stream_id); + DEBUGASSERT(stream); + if(stream) + stream->quic_flow_blocked = TRUE; n = 0; break; + } case NGTCP2_ERR_STREAM_SHUT_WR: DEBUGASSERT(ndatalen == -1); nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); @@ -1928,7 +1836,7 @@ static ssize_t read_pkt_to_send(void *userp, DEBUGASSERT(ndatalen == -1); failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", ngtcp2_strerror((int)n)); - ngtcp2_ccerr_set_liberr(&ctx->last_error, (int)n, NULL, 0); + cf_ngtcp2_err_set(x->cf, x->data, (int)n); *err = CURLE_SEND_ERROR; nwritten = -1; goto out; @@ -1955,28 +1863,25 @@ static ssize_t read_pkt_to_send(void *userp, return nwritten; } -static CURLcode cf_flush_egress(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - int rv; ssize_t nread; size_t max_payload_size, path_max_payload_size, max_pktcnt; size_t pktcnt = 0; size_t gsolen = 0; /* this disables gso until we have a clue */ - ngtcp2_path_storage ps; - ngtcp2_tstamp ts = timestamp(); - ngtcp2_tstamp expiry; - ngtcp2_duration timeout; CURLcode curlcode; - struct read_ctx readx; + struct pkt_io_ctx local_pktx; - rv = ngtcp2_conn_handle_expiry(ctx->qconn, ts); - if(rv) { - failf(data, "ngtcp2_conn_handle_expiry returned error: %s", - ngtcp2_strerror(rv)); - ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); - return CURLE_SEND_ERROR; + if(!pktx) { + pktx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + pktx_update_time(pktx, cf); + ngtcp2_path_storage_zero(&pktx->ps); } curlcode = vquic_flush(cf, data, &ctx->q); @@ -1988,10 +1893,8 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, return curlcode; } - ngtcp2_path_storage_zero(&ps); - /* In UDP, there is a maximum theoretical packet paload length and - * a minimum payload length that is "guarantueed" to work. + * a minimum payload length that is "guaranteed" to work. * To detect if this minimum payload can be increased, ngtcp2 sends * now and then a packet payload larger than the minimum. It that * is ACKed by the peer, both parties know that it works and @@ -2008,17 +1911,10 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, max_pktcnt = CURLMIN(MAX_PKT_BURST, ctx->q.sendbuf.chunk_size / max_payload_size); - readx.cf = cf; - readx.data = data; - readx.ts = ts; - readx.ps = &ps; - for(;;) { /* add the next packet to send, if any, to our buffer */ nread = Curl_bufq_sipn(&ctx->q.sendbuf, max_payload_size, - read_pkt_to_send, &readx, &curlcode); - /* DEBUGF(LOG_CF(data, cf, "sip packet(maxlen=%zu) -> %zd, %d", - max_payload_size, nread, curlcode)); */ + read_pkt_to_send, pktx, &curlcode); if(nread < 0) { if(curlcode != CURLE_AGAIN) return curlcode; @@ -2037,7 +1933,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, DEBUGASSERT(nread > 0); if(pktcnt == 0) { /* first packet in buffer. This is either of a known, "good" - * payload size or it is a PMTUD. We'll see. */ + * payload size or it is a PMTUD. We will see. */ gsolen = (size_t)nread; } else if((size_t)nread > gsolen || @@ -2076,21 +1972,6 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, } out: - /* non-errored exit. check when we should run again. */ - expiry = ngtcp2_conn_get_expiry(ctx->qconn); - if(expiry != UINT64_MAX) { - if(expiry <= ts) { - timeout = 0; - } - else { - timeout = expiry - ts; - if(timeout % NGTCP2_MILLISECONDS) { - timeout += NGTCP2_MILLISECONDS; - } - } - Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC); - } - return CURLE_OK; } @@ -2101,19 +1982,19 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - const struct stream_ctx *stream = H3_STREAM_CTX(data); (void)cf; - return stream && !Curl_bufq_is_empty(&stream->recvbuf); + (void)data; + return FALSE; } static CURLcode h3_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) { - /* TODO: there seems right now no API in ngtcp2 to shrink/enlarge - * the streams windows. As we do in HTTP/2. */ + /* There seems to exist no API in ngtcp2 to shrink/enlarge the streams + * windows. As we do in HTTP/2. */ if(!pause) { - drain_stream(cf, data); + h3_drain_stream(cf, data); Curl_expire(data, 0, EXPIRE_RUN_NOW); } return CURLE_OK; @@ -2136,26 +2017,29 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, case CF_CTRL_DATA_PAUSE: result = h3_data_pause(cf, data, (arg1 != 0)); break; - case CF_CTRL_DATA_DONE: { + case CF_CTRL_DATA_DONE: h3_data_done(cf, data); break; - } case CF_CTRL_DATA_DONE_SEND: { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { stream->send_closed = TRUE; - stream->upload_left = Curl_bufq_len(&stream->sendbuf); + stream->upload_left = Curl_bufq_len(&stream->sendbuf) - + stream->sendbuf_len_in_flight; (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); } break; } - case CF_CTRL_DATA_IDLE: - if(timestamp() >= ngtcp2_conn_get_expiry(ctx->qconn)) { - if(cf_flush_egress(cf, data)) { - result = CURLE_SEND_ERROR; - } + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURL_TRC_CF(data, cf, "data idle"); + if(stream && !stream->closed) { + result = check_and_set_expiry(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "data idle, check_and_set_expiry -> %d", result); } break; + } default: break; } @@ -2163,125 +2047,437 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, return result; } -static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) +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) { close(ctx->qlogfd); } -#ifdef USE_OPENSSL - if(ctx->ssl) - SSL_free(ctx->ssl); - if(ctx->sslctx) - SSL_CTX_free(ctx->sslctx); -#elif defined(USE_GNUTLS) - if(ctx->gtls) { - if(ctx->gtls->cred) - gnutls_certificate_free_credentials(ctx->gtls->cred); - if(ctx->gtls->session) - gnutls_deinit(ctx->gtls->session); - free(ctx->gtls); - } -#elif defined(USE_WOLFSSL) - if(ctx->ssl) - wolfSSL_free(ctx->ssl); - if(ctx->sslctx) - wolfSSL_CTX_free(ctx->sslctx); -#endif + ctx->qlogfd = -1; + Curl_vquic_tls_cleanup(&ctx->tls); vquic_ctx_free(&ctx->q); - if(ctx->h3conn) + if(ctx->h3conn) { nghttp3_conn_del(ctx->h3conn); - if(ctx->qconn) + ctx->h3conn = NULL; + } + if(ctx->qconn) { ngtcp2_conn_del(ctx->qconn); - Curl_bufcp_free(&ctx->stream_bufcp); - - memset(ctx, 0, sizeof(*ctx)); - ctx->qlogfd = -1; + 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_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct cf_call_data save; + struct pkt_io_ctx pktx; + CURLcode result = CURLE_OK; + + if(cf->shutdown || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } CF_DATA_SAVE(save, cf, data); - if(ctx && ctx->qconn) { + *done = FALSE; + pktx_init(&pktx, cf, data); + + if(!ctx->shutdown_started) { char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_tstamp ts; - ngtcp2_ssize rc; - - DEBUGF(LOG_CF(data, cf, "close")); - ts = timestamp(); - rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */ - NULL, /* pkt_info */ - (uint8_t *)buffer, sizeof(buffer), - &ctx->last_error, ts); - if(rc > 0) { - while((send(ctx->q.sockfd, buffer, (SEND_TYPE_ARG3)rc, 0) == -1) && - SOCKERRNO == EINTR); + ngtcp2_ssize nwritten; + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = cf_progress_egress(cf, data, &pktx); + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } } - cf_ngtcp2_ctx_clear(ctx); + DEBUGASSERT(Curl_bufq_is_empty(&ctx->q.sendbuf)); + ctx->shutdown_started = TRUE; + nwritten = ngtcp2_conn_write_connection_close( + ctx->qconn, NULL, /* path */ + NULL, /* pkt_info */ + (uint8_t *)buffer, sizeof(buffer), + &ctx->last_error, pktx.ts); + CURL_TRC_CF(data, cf, "start shutdown(err_type=%d, err_code=%" + FMT_PRIu64 ") -> %d", ctx->last_error.type, + (curl_uint64_t)ctx->last_error.error_code, (int)nwritten); + /* there are cases listed in ngtcp2 documentation where this call + * may fail. Since we are doing a connection shutdown as graceful + * as we can, such an error is ignored here. */ + if(nwritten > 0) { + /* Ignore amount written. sendbuf was empty and has always room for + * NGTCP2_MAX_UDP_PAYLOAD_SIZE. It can only completely fail, in which + * case `result` is set non zero. */ + (void)Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, + (size_t)nwritten, &result); + if(result) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packets to sendbuf, " + "aborting shutdown", result); + goto out; + } + + ctx->q.no_gso = TRUE; + ctx->q.gsolen = (size_t)nwritten; + ctx->q.split_len = 0; + } } - cf->connected = FALSE; + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing egress"); + result = vquic_flush(cf, data, &ctx->q); + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } + } + + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* Sent everything off. ngtcp2 seems to have no support for graceful + * shutdowns. So, we are done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + result = CURLE_OK; + } +out: CF_DATA_RESTORE(cf, save); + return result; } -static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + bool done; + cf_ngtcp2_shutdown(cf, data, &done); +} + +static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - DEBUGF(LOG_CF(data, cf, "destroy")); - if(ctx) { - cf_ngtcp2_ctx_clear(ctx); - free(ctx); + if(ctx && ctx->qconn) { + cf_ngtcp2_conn_close(cf, data); + cf_ngtcp2_ctx_close(ctx); + CURL_TRC_CF(data, cf, "close"); + } + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + CURL_TRC_CF(data, cf, "destroy"); + if(cf->ctx) { + cf_ngtcp2_close(cf, data); + cf_ngtcp2_ctx_free(cf->ctx); + cf->ctx = NULL; + } +} + +#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. + */ +static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +{ + struct Curl_cfilter *cf; + struct cf_ngtcp2_ctx *ctx; + struct Curl_easy *data; + ngtcp2_crypto_conn_ref *cref; + + cref = (ngtcp2_crypto_conn_ref *)SSL_get_app_data(ssl); + cf = cref ? cref->user_data : NULL; + ctx = cf ? cf->ctx : NULL; + data = cf ? CF_DATA_CURRENT(cf) : NULL; + if(cf && data && ctx) { + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; +#ifdef HAVE_OPENSSL_EARLYDATA + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } +#endif + Curl_ossl_add_session(cf, data, ctx->peer.scache_key, ssl_sessionid, + SSL_version(ssl), "h3", quic_tp, quic_tp_len); + return 1; + } + return 0; +} +#endif /* USE_OPENSSL */ + +#ifdef USE_GNUTLS + +static const char *gtls_hs_msg_name(int mtype) +{ + switch(mtype) { + case 1: return "ClientHello"; + case 2: return "ServerHello"; + case 4: return "SessionTicket"; + case 8: return "EncryptedExtensions"; + case 11: return "Certificate"; + case 13: return "CertificateRequest"; + case 15: return "CertificateVerify"; + case 20: return "Finished"; + case 24: return "KeyUpdate"; + case 254: return "MessageHash"; + } + return "Unknown"; +} + +static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; + + (void)msg; + (void)incoming; + if(when && cf && ctx) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(!data) + return 0; + CURL_TRC_CF(data, cf, "SSL message: %s %s [%d]", + incoming ? "<-" : "->", gtls_hs_msg_name(htype), htype); + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } + (void)Curl_gtls_cache_session(cf, data, ctx->peer.scache_key, + session, 0, "h3", quic_tp, quic_tp_len); + break; + } + default: + break; + } + } + return 0; +} +#endif /* USE_GNUTLS */ + +#ifdef USE_WOLFSSL +static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) +{ + ngtcp2_crypto_conn_ref *conn_ref = wolfSSL_get_app_data(ssl); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; + + DEBUGASSERT(cf != NULL); + if(cf && session) { + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(data && ctx) { + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } + (void)Curl_wssl_cache_session(cf, data, ctx->peer.scache_key, + session, wolfSSL_version(ssl), + "h3", quic_tp, quic_tp_len); + } + } + return 0; +} +#endif /* USE_WOLFSSL */ + +static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data) +{ + struct curl_tls_ctx *ctx = user_data; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + +#ifdef USE_OPENSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) + != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#elif defined(OPENSSL_QUIC_API2) + /* nothing to do */ +#else + if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ + if(ssl_config->primary.cache_session) { + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); + } + +#elif defined(USE_GNUTLS) + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) { + failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); + return CURLE_FAILED_INIT; + } + if(ssl_config->primary.cache_session) { + gnutls_handshake_set_hook_function(ctx->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + quic_gtls_handshake_cb); + } + +#elif defined(USE_WOLFSSL) + if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } + if(ssl_config->primary.cache_session) { + /* Register to get notified when a new session is received */ + wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ssl_ctx, wssl_quic_new_session_cb); + } +#endif + return CURLE_OK; +} + +static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + *do_early_data = FALSE; +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + ctx->earlydata_max = scs->earlydata_max; +#endif +#ifdef USE_GNUTLS + ctx->earlydata_max = + gnutls_record_get_max_early_data_size(ctx->tls.gtls.session); +#endif +#ifdef USE_WOLFSSL +#ifdef WOLFSSL_EARLY_DATA + ctx->earlydata_max = scs->earlydata_max; +#else + ctx->earlydata_max = 0; +#endif /* WOLFSSL_EARLY_DATA */ +#endif +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + (defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)) + if((!ctx->earlydata_max)) { + CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); + } + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { + CURL_TRC_CF(data, cf, "SSL session from different ALPN, no early data"); + } + else if(!scs->quic_tp || !scs->quic_tp_len) { + CURL_TRC_CF(data, cf, "no 0RTT transport parameters, no early data, "); + } + else { + int rv; + rv = ngtcp2_conn_decode_and_set_0rtt_transport_params( + ctx->qconn, (const uint8_t *)scs->quic_tp, scs->quic_tp_len); + if(rv) + CURL_TRC_CF(data, cf, "no early data, failed to set 0RTT transport " + "parameters: %s", ngtcp2_strerror(rv)); + else { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", ctx->earlydata_max, scs->alpn); + result = init_ngh3_conn(cf, data); + if(!result) { + ctx->use_earlydata = TRUE; + cf->connected = TRUE; + *do_early_data = TRUE; + } + } } - cf->ctx = NULL; - /* No CF_DATA_RESTORE(cf, save) possible */ - (void)save; +#else /* not supported in the TLS backend */ + (void)data; + (void)ctx; + (void)scs; + (void)alpns; +#endif + return result; } /* * Might be called twice for happy eyeballs. */ static CURLcode cf_connect_start(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct pkt_io_ctx *pktx) { struct cf_ngtcp2_ctx *ctx = cf->ctx; int rc; int rv; CURLcode result; - const struct Curl_sockaddr_ex *sockaddr; + const struct Curl_sockaddr_ex *sockaddr = NULL; int qfd; +static const struct alpn_spec ALPN_SPEC_H3 = { + { "h3", "h3-29" }, 2 +}; - ctx->version = NGTCP2_PROTO_VER_MAX; - ctx->max_stream_window = H3_STREAM_WINDOW_SIZE; - Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, - H3_STREAM_POOL_SPARES); - -#ifdef USE_OPENSSL - result = quic_ssl_ctx(&ctx->sslctx, cf, data); - if(result) - return result; - - result = quic_set_client_cert(cf, data); - if(result) - return result; -#elif defined(USE_WOLFSSL) - result = quic_ssl_ctx(&ctx->sslctx, cf, data); - if(result) - return result; -#endif - - result = quic_init_ssl(cf, data); - if(result) - return result; - + DEBUGASSERT(ctx->initialized); ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); if(result) @@ -2294,14 +2490,15 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); ctx->qlogfd = qfd; /* -1 if failure above */ - quic_settings(ctx, data); + quic_settings(ctx, data, pktx); result = vquic_ctx_init(&ctx->q); if(result) return result; - Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, - &sockaddr, NULL, NULL, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL); + if(!sockaddr) + return CURLE_QUIC_CONNECT_ERROR; ctx->q.local_addrlen = sizeof(ctx->q.local_addr); rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, &ctx->q.local_addrlen); @@ -2312,7 +2509,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, (struct sockaddr *)&ctx->q.local_addr, ctx->q.local_addrlen); ngtcp2_addr_init(&ctx->connected_path.remote, - &sockaddr->sa_addr, sockaddr->addrlen); + &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, &ctx->connected_path, @@ -2322,28 +2519,51 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(rc) return CURLE_QUIC_CONNECT_ERROR; -#ifdef USE_GNUTLS - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session); + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; + + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, &ALPN_SPEC_H3, + cf_ngtcp2_tls_ctx_setup, &ctx->tls, + &ctx->conn_ref, + cf_ngtcp2_on_session_reuse); + if(result) + return result; + +#if defined(USE_OPENSSL) && defined(OPENSSL_QUIC_API2) + if(ngtcp2_crypto_ossl_ctx_new(&ctx->ossl_ctx, ctx->tls.ossl.ssl) != 0) { + failf(data, "ngtcp2_crypto_ossl_ctx_new failed"); + return CURLE_FAILED_INIT; + } + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ossl_ctx); + if(ngtcp2_crypto_ossl_configure_client_session(ctx->tls.ossl.ssl) != 0) { + failf(data, "ngtcp2_crypto_ossl_configure_client_session failed"); + return CURLE_FAILED_INIT; + } +#elif defined(USE_OPENSSL) + SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); +#elif defined(USE_GNUTLS) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session); +#elif defined(USE_WOLFSSL) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.ssl); #else - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl); + #error "ngtcp2 TLS backend not defined" #endif ngtcp2_ccerr_default(&ctx->last_error); - ctx->conn_ref.get_conn = get_conn; - ctx->conn_ref.user_data = cf; - return CURLE_OK; } static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_ngtcp2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; struct cf_call_data save; struct curltime now; + struct pkt_io_ctx pktx; if(cf->connected) { *done = TRUE; @@ -2352,47 +2572,44 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, /* Connect the UDP filter first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } *done = FALSE; - now = Curl_now(); + now = curlx_now(); + pktx_init(&pktx, cf, data); CF_DATA_SAVE(save, cf, data); - if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) { - /* Not time yet to attempt the next connect */ - DEBUGF(LOG_CF(data, cf, "waiting for reconnect time")); - goto out; - } - if(!ctx->qconn) { ctx->started_at = now; - result = cf_connect_start(cf, data); + result = cf_connect_start(cf, data, &pktx); if(result) goto out; - result = cf_flush_egress(cf, data); + if(cf->connected) { + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + goto out; + } + result = cf_progress_egress(cf, data, &pktx); /* we do not expect to be able to recv anything yet */ goto out; } - result = cf_process_ingress(cf, data); + result = cf_progress_ingress(cf, data, &pktx); if(result) goto out; - result = cf_flush_egress(cf, data); + result = cf_progress_egress(cf, data, &pktx); if(result) goto out; if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) { - ctx->handshake_at = now; - DEBUGF(LOG_CF(data, cf, "handshake complete after %dms", - (int)Curl_timediff(now, ctx->started_at))); - result = qng_verify_peer(cf, data); + result = ctx->tls_vrfy_result; if(!result) { - DEBUGF(LOG_CF(data, cf, "peer verified")); + CURL_TRC_CF(data, cf, "peer verified"); cf->connected = TRUE; cf->conn->alpn = CURL_HTTP_VERSION_3; *done = TRUE; @@ -2402,44 +2619,28 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, out: if(result == CURLE_RECV_ERROR && ctx->qconn && - ngtcp2_conn_is_in_draining_period(ctx->qconn)) { + ngtcp2_conn_in_draining_period(ctx->qconn)) { /* When a QUIC server instance is shutting down, it may send us a * CONNECTION_CLOSE right away. Our connection then enters the DRAINING - * state. - * This may be a stopping of the service or it may be that the server - * is reloading and a new instance will start serving soon. - * In any case, we tear down our socket and start over with a new one. - * We re-open the underlying UDP cf right now, but do not start - * connecting until called again. - */ - int reconn_delay_ms = 200; - - DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms", - reconn_delay_ms)); - Curl_conn_cf_close(cf->next, data); - cf_ngtcp2_ctx_clear(ctx); - result = Curl_conn_cf_connect(cf->next, data, FALSE, done); - if(!result && *done) { - *done = FALSE; - ctx->reconnect_at = now; - ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000; - Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC); - result = CURLE_OK; - } + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; } #ifndef CURL_DISABLE_VERBOSE_STRINGS if(result) { - const char *r_ip; - int r_port; + struct ip_quadruple ip; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); infof(data, "QUIC connect to %s port %u failed: %s", - r_ip, r_port, curl_easy_strerror(result)); + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); } #endif - DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done)); + if(!result && ctx->qconn) { + result = check_and_set_expiry(cf, data, &pktx); + } + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); CF_DATA_RESTORE(cf, save); return result; } @@ -2453,32 +2654,43 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - const ngtcp2_transport_params *rp; DEBUGASSERT(pres1); - CF_DATA_SAVE(save, cf, data); - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - if(rp) - *pres1 = (rp->initial_max_streams_bidi > INT_MAX)? - INT_MAX : (int)rp->initial_max_streams_bidi; - else /* not arrived yet? */ - *pres1 = Curl_multi_max_concurrent_streams(data->multi); - DEBUGF(LOG_CF(data, cf, "query max_conncurrent -> %d", *pres1)); + /* Set after transport params arrived and continually updated + * by callback. QUIC counts the number over the lifetime of the + * connection, ever increasing. + * We count the *open* transfers plus the budget for new ones. */ + if(!ctx->qconn || ctx->shutdown_started) { + *pres1 = 0; + } + else if(ctx->max_bidi_streams) { + uint64_t avail_bidi_streams = 0; + uint64_t max_streams = CONN_ATTACHED(cf->conn); + if(ctx->max_bidi_streams > ctx->used_bidi_streams) + avail_bidi_streams = ctx->max_bidi_streams - ctx->used_bidi_streams; + 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); + CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " + "MAX_CONCURRENT -> %d (%u in use)", + cf->conn->connection_id, *pres1, CONN_ATTACHED(cf->conn)); CF_DATA_RESTORE(cf, save); return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->got_first_byte) { - timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); - *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + if(ctx->q.got_first_byte) { + timediff_t ms = curlx_timediff(ctx->q.first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else *pres1 = -1; return CURLE_OK; case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { @@ -2487,10 +2699,13 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, *when = ctx->handshake_at; return CURLE_OK; } + case CF_QUERY_HTTP_VERSION: + *pres1 = 30; + return CURLE_OK; default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -2499,38 +2714,55 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { - bool alive = TRUE; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool alive = FALSE; + const ngtcp2_transport_params *rp; + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); *input_pending = FALSE; + if(!ctx->qconn || ctx->shutdown_started) + goto out; + + /* We do not announce a max idle timeout, but when the peer does + * it will close the connection when it expires. */ + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(rp && rp->max_idle_timeout) { + timediff_t idletime = curlx_timediff(curlx_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > rp->max_idle_timeout) + goto out; + } + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) - return FALSE; + goto out; + alive = TRUE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + CURLcode result; + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ *input_pending = FALSE; - Curl_attach_connection(data, cf->conn); - if(cf_process_ingress(cf, data)) - alive = FALSE; - else { - alive = TRUE; - } - Curl_detach_connection(data); + result = cf_progress_ingress(cf, data, NULL); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result ? FALSE : TRUE; } +out: + CF_DATA_RESTORE(cf, save); return alive; } struct Curl_cftype Curl_cft_http3 = { "HTTP/3", - CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, 0, cf_ngtcp2_destroy, cf_ngtcp2_connect, cf_ngtcp2_close, + cf_ngtcp2_shutdown, Curl_cf_def_get_host, - cf_ngtcp2_get_select_socks, + cf_ngtcp2_adjust_pollset, cf_ngtcp2_data_pending, cf_ngtcp2_send, cf_ngtcp2_recv, @@ -2550,13 +2782,12 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, CURLcode result; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - ctx->qlogfd = -1; - cf_ngtcp2_ctx_clear(ctx); + cf_ngtcp2_ctx_init(ctx); result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) @@ -2572,12 +2803,12 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, cf->next = udp_cf; out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { if(udp_cf) Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); Curl_safefree(cf); - Curl_safefree(ctx); + cf_ngtcp2_ctx_free(ctx); } return result; } @@ -2586,7 +2817,7 @@ bool Curl_conn_is_ngtcp2(const struct Curl_easy *data, const struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; (void)data; for(; cf; cf = cf->next) { diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h index db3e611bd0c..884662523f6 100644 --- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) @@ -33,6 +33,9 @@ #endif #include +#ifdef OPENSSL_QUIC_API2 +#include +#endif #include #ifdef USE_OPENSSL #include @@ -44,7 +47,7 @@ struct Curl_cfilter; -#include "urldata.h" +#include "../urldata.h" void Curl_ngtcp2_ver(char *p, size_t len); diff --git a/Utilities/cmcurl/lib/vquic/curl_osslq.c b/Utilities/cmcurl/lib/vquic/curl_osslq.c new file mode 100644 index 00000000000..d5af10b06b2 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_osslq.c @@ -0,0 +1,2492 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#include +#include +#include +#include + +#include "../urldata.h" +#include "../hash.h" +#include "../sendf.h" +#include "../strdup.h" +#include "../rand.h" +#include "../multiif.h" +#include "../strcase.h" +#include "../cfilters.h" +#include "../cf-socket.h" +#include "../connect.h" +#include "../progress.h" +#include "../strerror.h" +#include "../curlx/dynbuf.h" +#include "../http1.h" +#include "../select.h" +#include "../curlx/inet_pton.h" +#include "../uint-hash.h" +#include "vquic.h" +#include "vquic_int.h" +#include "vquic-tls.h" +#include "../vtls/keylog.h" +#include "../vtls/vtls.h" +#include "../vtls/openssl.h" +#include "curl_osslq.h" +#include "../curlx/warnless.h" + +/* The last 3 #include files should be in this order */ +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" + +/* A stream window is the maximum amount we need to buffer for + * 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 H3_STREAM_WINDOW_SIZE (128 * 1024) +#define H3_STREAM_CHUNK_SIZE (16 * 1024) +/* The pool keeps spares around and half of a full stream window + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffer to not keep + * spares. Memory consumption goes down when streams run empty, + * have a large upload done, etc. */ +#define H3_STREAM_POOL_SPARES \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 +/* Receive and Send max number of chunks just follows from the + * chunk size and window size */ +#define H3_STREAM_RECV_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) +#define H3_STREAM_SEND_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef uint32_t sslerr_t; +#else +typedef unsigned long sslerr_t; +#endif + + +/* How to access `call_data` from a cf_osslq filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_osslq_ctx *)(cf)->ctx)->call_data + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data); + +static const char *osslq_SSL_ERROR_to_str(int err) +{ + switch(err) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; +#if defined(SSL_ERROR_WANT_ASYNC) + case SSL_ERROR_WANT_ASYNC: + return "SSL_ERROR_WANT_ASYNC"; +#endif +#if defined(SSL_ERROR_WANT_ASYNC_JOB) + case SSL_ERROR_WANT_ASYNC_JOB: + return "SSL_ERROR_WANT_ASYNC_JOB"; +#endif +#if defined(SSL_ERROR_WANT_EARLY) + case SSL_ERROR_WANT_EARLY: + return "SSL_ERROR_WANT_EARLY"; +#endif + default: + return "SSL_ERROR unknown"; + } +} + +/* Return error string for last OpenSSL error */ +static char *osslq_strerror(unsigned long error, char *buf, size_t size) +{ + DEBUGASSERT(size); + *buf = '\0'; + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + ERR_error_string_n((uint32_t)error, buf, size); +#else + ERR_error_string_n(error, buf, size); +#endif + + if(!*buf) { + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); + } + + return buf; +} + +static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, + const struct Curl_sockaddr_ex *addr) +{ + BIO_ADDR *ba; + CURLcode result = CURLE_FAILED_INIT; + + ba = BIO_ADDR_new(); + if(!ba) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + switch(addr->family) { + case AF_INET: { + struct sockaddr_in * const sin = + (struct sockaddr_in * const)CURL_UNCONST(&addr->curl_sa_addr); + if(!BIO_ADDR_rawmake(ba, AF_INET, &sin->sin_addr, + sizeof(sin->sin_addr), sin->sin_port)) { + goto out; + } + result = CURLE_OK; + break; + } +#ifdef USE_IPV6 + case AF_INET6: { + struct sockaddr_in6 * const sin = + (struct sockaddr_in6 * const)CURL_UNCONST(&addr->curl_sa_addr); + if(!BIO_ADDR_rawmake(ba, AF_INET6, &sin->sin6_addr, + sizeof(sin->sin6_addr), sin->sin6_port)) { + } + result = CURLE_OK; + break; + } +#endif /* USE_IPV6 */ + default: + /* sunsupported */ + DEBUGASSERT(0); + break; + } + +out: + if(result && ba) { + BIO_ADDR_free(ba); + ba = NULL; + } + *pbio_addr = ba; + return result; +} + +/* QUIC stream (not necessarily H3) */ +struct cf_osslq_stream { + curl_int64_t id; + SSL *ssl; + struct bufq recvbuf; /* QUIC war data recv buffer */ + BIT(recvd_eos); + BIT(closed); + BIT(reset); + BIT(send_blocked); +}; + +static CURLcode cf_osslq_stream_open(struct cf_osslq_stream *s, + SSL *conn, + uint64_t flags, + struct bufc_pool *bufcp, + void *user_data) +{ + DEBUGASSERT(!s->ssl); + Curl_bufq_initp(&s->recvbuf, bufcp, 1, BUFQ_OPT_NONE); + s->ssl = SSL_new_stream(conn, flags); + if(!s->ssl) { + return CURLE_FAILED_INIT; + } + s->id = (curl_int64_t)SSL_get_stream_id(s->ssl); + SSL_set_app_data(s->ssl, user_data); + return CURLE_OK; +} + +static void cf_osslq_stream_cleanup(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_set_app_data(s->ssl, NULL); + SSL_free(s->ssl); + } + Curl_bufq_free(&s->recvbuf); + memset(s, 0, sizeof(*s)); +} + +static void cf_osslq_stream_close(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } +} + +struct cf_osslq_h3conn { + nghttp3_conn *conn; + nghttp3_settings settings; + struct cf_osslq_stream s_ctrl; + struct cf_osslq_stream s_qpack_enc; + struct cf_osslq_stream s_qpack_dec; + struct cf_osslq_stream remote_ctrl[3]; /* uni streams opened by the peer */ + size_t remote_ctrl_n; /* number of peer streams opened */ +}; + +static void cf_osslq_h3conn_cleanup(struct cf_osslq_h3conn *h3) +{ + size_t i; + + if(h3->conn) + nghttp3_conn_del(h3->conn); + cf_osslq_stream_cleanup(&h3->s_ctrl); + cf_osslq_stream_cleanup(&h3->s_qpack_enc); + cf_osslq_stream_cleanup(&h3->s_qpack_dec); + for(i = 0; i < h3->remote_ctrl_n; ++i) { + cf_osslq_stream_cleanup(&h3->remote_ctrl[i]); + } +} + +struct cf_osslq_ctx { + struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; + struct cf_call_data call_data; + struct cf_osslq_h3conn h3; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct uint_hash streams; /* hash `data->mid` to `h3_stream_ctx` */ + size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ + SSL_POLL_ITEM *poll_items; /* Array for polling on writable state */ + struct Curl_easy **curl_items; /* Array of easy objs */ + size_t items_max; /* max elements in poll/curl_items */ + BIT(initialized); + BIT(got_first_byte); /* if first byte was received */ + BIT(x509_store_setup); /* if x509 store has been set up */ + BIT(protocol_shutdown); /* QUIC connection is shut down */ + BIT(need_recv); /* QUIC connection needs to receive */ + BIT(need_send); /* QUIC connection needs to send */ +}; + +static void h3_stream_hash_free(unsigned int id, void *stream); + +static void cf_osslq_ctx_init(struct cf_osslq_ctx *ctx) +{ + DEBUGASSERT(!ctx->initialized); + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->poll_items = NULL; + ctx->curl_items = NULL; + ctx->items_max = 0; + ctx->initialized = TRUE; +} + +static void cf_osslq_ctx_free(struct cf_osslq_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_uint_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->peer); + free(ctx->poll_items); + free(ctx->curl_items); + } + free(ctx); +} + +static void cf_osslq_ctx_close(struct cf_osslq_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + cf_osslq_h3conn_cleanup(&ctx->h3); + Curl_vquic_tls_cleanup(&ctx->tls); + vquic_ctx_free(&ctx->q); + ctx->call_data = save; +} + +static CURLcode cf_osslq_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + int rc; + + CF_DATA_SAVE(save, cf, data); + + if(cf->shutdown || ctx->protocol_shutdown) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + *done = FALSE; + ctx->need_send = FALSE; + ctx->need_recv = FALSE; + + rc = SSL_shutdown_ex(ctx->tls.ossl.ssl, + SSL_SHUTDOWN_FLAG_NO_BLOCK, NULL, 0); + if(rc == 0) { /* ongoing */ + CURL_TRC_CF(data, cf, "shutdown ongoing"); + ctx->need_recv = TRUE; + goto out; + } + else if(rc == 1) { /* done */ + CURL_TRC_CF(data, cf, "shutdown finished"); + *done = TRUE; + goto out; + } + else { + long sslerr; + char err_buffer[256]; + int err = SSL_get_error(ctx->tls.ossl.ssl, rc); + + switch(err) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + CURL_TRC_CF(data, cf, "shutdown not received, but closed"); + *done = TRUE; + goto out; + case SSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "shutdown sent, want receive"); + ctx->need_recv = TRUE; + goto out; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "shutdown send blocked"); + ctx->need_send = TRUE; + goto out; + default: + /* We give up on this. */ + sslerr = ERR_get_error(); + CURL_TRC_CF(data, cf, "shutdown, ignore recv error: '%s', errno %d", + (sslerr ? + osslq_strerror(sslerr, err_buffer, sizeof(err_buffer)) : + osslq_SSL_ERROR_to_str(err)), + SOCKERRNO); + *done = TRUE; + result = CURLE_OK; + goto out; + } + } +out: + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_osslq_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->tls.ossl.ssl) { + CURL_TRC_CF(data, cf, "cf_osslq_close()"); + if(!cf->shutdown && !ctx->protocol_shutdown) { + /* last best effort, which OpenSSL calls a "rapid" shutdown. */ + SSL_shutdown_ex(ctx->tls.ossl.ssl, + (SSL_SHUTDOWN_FLAG_NO_BLOCK | SSL_SHUTDOWN_FLAG_RAPID), + NULL, 0); + } + cf_osslq_ctx_close(ctx); + } + + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_osslq_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + CURL_TRC_CF(data, cf, "cf_osslq_destroy()"); + if(ctx->tls.ossl.ssl) + cf_osslq_ctx_close(ctx); + cf_osslq_ctx_free(ctx); + } + cf->ctx = NULL; + /* No CF_DATA_RESTORE(cf, save) possible */ + (void)save; +} + +static CURLcode cf_osslq_h3conn_add_stream(struct cf_osslq_h3conn *h3, + SSL *stream_ssl, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)SSL_get_stream_id(stream_ssl); + + if(h3->remote_ctrl_n >= CURL_ARRAYSIZE(h3->remote_ctrl)) { + /* rejected, we are full */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] rejecting remote stream", + stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + switch(SSL_get_stream_type(stream_ssl)) { + case SSL_STREAM_TYPE_READ: { + struct cf_osslq_stream *nstream = &h3->remote_ctrl[h3->remote_ctrl_n++]; + nstream->id = stream_id; + nstream->ssl = stream_ssl; + Curl_bufq_initp(&nstream->recvbuf, &ctx->stream_bufcp, 1, BUFQ_OPT_NONE); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] accepted remote uni stream", + stream_id); + break; + } + default: + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reject remote non-uni-read" + " stream", stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + return CURLE_OK; + +} + +static CURLcode cf_osslq_ssl_err(struct Curl_cfilter *cf, + struct Curl_easy *data, + int detail, CURLcode def_result) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = def_result; + sslerr_t errdetail; + char ebuf[256] = "unknown"; + const char *err_descr = ebuf; + long lerr; + int lib; + int reason; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + + errdetail = ERR_get_error(); + lib = ERR_GET_LIB(errdetail); + reason = ERR_GET_REASON(errdetail); + + if((lib == ERR_LIB_SSL) && + ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) || + (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { + result = CURLE_PEER_FAILED_VERIFICATION; + + lerr = SSL_get_verify_result(ctx->tls.ossl.ssl); + if(lerr != X509_V_OK) { + ssl_config->certverifyresult = lerr; + msnprintf(ebuf, sizeof(ebuf), + "SSL certificate problem: %s", + X509_verify_cert_error_string(lerr)); + } + else + err_descr = "SSL certificate verification failed"; + } +#if defined(SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED) + /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on + OpenSSL version above v1.1.1, not LibreSSL, BoringSSL, or AWS-LC */ + else if((lib == ERR_LIB_SSL) && + (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) { + /* If client certificate is required, communicate the + error to client */ + result = CURLE_SSL_CLIENTCERT; + osslq_strerror(errdetail, ebuf, sizeof(ebuf)); + } +#endif + else if((lib == ERR_LIB_SSL) && (reason == SSL_R_PROTOCOL_IS_SHUTDOWN)) { + ctx->protocol_shutdown = TRUE; + err_descr = "QUIC connection has been shut down"; + result = def_result; + } + else { + result = def_result; + osslq_strerror(errdetail, ebuf, sizeof(ebuf)); + } + + /* detail is already set to the SSL error above */ + + /* If we e.g. use SSLv2 request-method and the server does not like us + * (RST connection, etc.), OpenSSL gives no explanation whatsoever and + * the SO_ERROR is also lost. + */ + if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) { + char extramsg[80]=""; + int sockerr = SOCKERRNO; + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + if(sockerr && detail == SSL_ERROR_SYSCALL) + Curl_strerror(sockerr, extramsg, sizeof(extramsg)); + failf(data, "QUIC connect: %s in connection to %s:%d (%s)", + extramsg[0] ? extramsg : osslq_SSL_ERROR_to_str(detail), + ctx->peer.dispname, ip.remote_port, ip.remote_ip); + } + else { + /* Could be a CERT problem */ + failf(data, "%s", err_descr); + } + return result; +} + +static CURLcode cf_osslq_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + struct cf_osslq_stream s; + struct bufq sendbuf; /* h3 request body */ + struct bufq recvbuf; /* h3 response body */ + struct h1_req_parser h1; /* h1 request parsing */ + size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ + size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ + curl_uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + curl_off_t download_recvd; /* number of response DATA bytes received */ + int status_code; /* HTTP status code */ + BIT(resp_hds_complete); /* we have a complete, final response */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(send_closed); /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + cf_osslq_stream_cleanup(&stream->s); + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(unsigned int id, void *stream) +{ + (void)id; + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(!data) + return CURLE_FAILED_INIT; + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->s.id = -1; + /* on send, we control how much we put into the buffer */ + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + stream->sendbuf_len_in_flight = 0; + /* on recv, we need a flexible buffer limit since we also write + * headers to it that are not counted against the nghttp3 flow limits. */ + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + stream->recv_buf_nonflow = 0; + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] easy handle is done", + stream->s.id); + if(ctx->h3.conn && (stream->s.id >= 0) && !stream->closed) { + nghttp3_conn_shutdown_stream_read(ctx->h3.conn, stream->s.id); + nghttp3_conn_close_stream(ctx->h3.conn, stream->s.id, + NGHTTP3_H3_REQUEST_CANCELLED); + nghttp3_conn_set_stream_user_data(ctx->h3.conn, stream->s.id, NULL); + stream->closed = TRUE; + } + + Curl_uint_hash_remove(&ctx->streams, data->mid); + } +} + +struct cf_ossq_find_ctx { + curl_int64_t stream_id; + struct h3_stream_ctx *stream; +}; + +static bool cf_osslq_find_stream(unsigned int mid, void *val, void *user_data) +{ + struct h3_stream_ctx *stream = val; + struct cf_ossq_find_ctx *fctx = user_data; + + (void)mid; + if(stream && stream->s.id == fctx->stream_id) { + fctx->stream = stream; + return FALSE; /* stop iterating */ + } + return TRUE; +} + +static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(stream && stream->s.id == stream_id) { + return &stream->s; + } + else if(ctx->h3.s_ctrl.id == stream_id) { + return &ctx->h3.s_ctrl; + } + else if(ctx->h3.s_qpack_enc.id == stream_id) { + return &ctx->h3.s_qpack_enc; + } + else if(ctx->h3.s_qpack_dec.id == stream_id) { + return &ctx->h3.s_qpack_dec; + } + else { + struct cf_ossq_find_ctx fctx; + fctx.stream_id = stream_id; + fctx.stream = NULL; + Curl_uint_hash_visit(&ctx->streams, cf_osslq_find_stream, &fctx); + if(fctx.stream) + return &fctx.stream->s; + } + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && stream->upload_left && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + if(!pause) { + /* unpaused. make it run again right away */ + h3_drain_stream(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return CURLE_OK; +} + +static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + (void)stream_id; + + /* we might be called by nghttp3 after we already cleaned up */ + if(!stream) + return 0; + + stream->closed = TRUE; + stream->error3 = app_error_code; + if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + stream->reset = TRUE; + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] RESET: error %" FMT_PRIu64, + stream->s.id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] CLOSED", stream->s.id); + } + h3_drain_stream(cf, data); + return 0; +} + +/* + * write_resp_raw() copies response data in raw format to the `data`'s + * receive buffer. If not enough space is available, it appends to the + * `data`'s overflow buffer. + */ +static CURLcode write_resp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, size_t memlen, + bool flow) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result = CURLE_OK; + ssize_t nwritten; + + (void)cf; + if(!stream) { + return CURLE_RECV_ERROR; + } + nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); + if(nwritten < 0) { + return result; + } + + if(!flow) + stream->recv_buf_nonflow += (size_t)nwritten; + + if((size_t)nwritten < memlen) { + /* This MUST not happen. Our recbuf is dimensioned to hold the + * full max_stream_window and then some for this very reason. */ + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } + return result; +} + +static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result; + + (void)conn; + (void)stream3_id; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + result = write_resp_raw(cf, data, buf, buflen, TRUE); + if(result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu, ERROR %d", + stream->s.id, buflen, result); + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + stream->download_recvd += (curl_off_t)buflen; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu, total=%" FMT_OFF_T, + stream->s.id, buflen, stream->download_recvd); + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t consumed, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)conn; + (void)stream_id; + if(stream) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] deferred consume %zu bytes", + stream->s.id, consumed); + return 0; +} + +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + curl_int64_t stream_id = sid; + struct cf_osslq_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); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)token; + (void)flags; + (void)cf; + + /* we might have cleaned up this transfer already */ + if(!stream) + return 0; + + if(token == NGHTTP3_QPACK_TOKEN__STATUS) { + char line[14]; /* status line is always 13 characters long */ + size_t ncopy; + + result = Curl_http_decode_status(&stream->status_code, + (const char *)h3val.base, h3val.len); + if(result) + return -1; + ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", + stream->status_code); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] status: %s", stream_id, line); + result = write_resp_raw(cf, data, line, ncopy, FALSE); + if(result) { + return -1; + } + } + else { + /* store as an HTTP1-style header */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] header: %.*s: %.*s", + stream_id, (int)h3name.len, h3name.base, + (int)h3val.len, h3val.base); + result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, ": ", 2, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + } + return 0; +} + +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t sid, + int fin, void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + if(!stream) + return 0; + /* add a CRLF only if we have received some headers */ + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] end_headers, status=%d", + stream_id, stream->status_code); + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + (void)app_error_code; + + if(!stream || !stream->s.ssl) + return 0; + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] stop_sending", stream_id); + cf_osslq_stream_close(&stream->s); + return 0; +} + +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + int rv; + (void)conn; + + if(stream && stream->s.ssl) { + SSL_STREAM_RESET_ARGS args = {0}; + args.quic_error_code = app_error_code; + rv = !SSL_stream_reset(stream->s.ssl, &args, sizeof(args)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); + if(!rv) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_ssize +cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nwritten = 0; + size_t nvecs = 0; + (void)cf; + (void)conn; + (void)stream_id; + (void)user_data; + (void)veccnt; + + if(!stream) + 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). + * `sendbuf_len_in_flight` is the amount of bytes in `sendbuf` + * that we have already passed to nghttp3, but which have not been + * ACKed yet. + * Any amount beyond `sendbuf_len_in_flight` we need still to pass + * to nghttp3. Do that now, if we can. */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + nvecs = 0; + while(nvecs < veccnt && + Curl_bufq_peek_at(&stream->sendbuf, + stream->sendbuf_len_in_flight, + CURL_UNCONST(&vec[nvecs].base), + &vec[nvecs].len)) { + stream->sendbuf_len_in_flight += vec[nvecs].len; + nwritten += vec[nvecs].len; + ++nvecs; + } + DEBUGASSERT(nvecs > 0); /* we SHOULD have been be able to peek */ + } + + if(nwritten > 0 && stream->upload_left != -1) + stream->upload_left -= nwritten; + + /* When we stopped sending and everything in `sendbuf` is "in flight", + * we are at the end of the request body. */ + if(stream->upload_left == 0) { + *pflags = NGHTTP3_DATA_FLAG_EOF; + stream->send_closed = TRUE; + } + else if(!nwritten) { + /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> AGAIN", + stream->s.id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", + stream->s.id, (int)nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", + nwritten, Curl_bufq_len(&stream->sendbuf), + stream->upload_left); + return (nghttp3_ssize)nvecs; +} + +static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + size_t skiplen; + + (void)cf; + if(!stream) + return 0; + /* The server acknowledged `datalen` of bytes from our request body. + * This is a delta. We have kept this data in `sendbuf` for + * re-transmissions and can free it now. */ + if(datalen >= (uint64_t)stream->sendbuf_len_in_flight) + skiplen = stream->sendbuf_len_in_flight; + else + skiplen = (size_t)datalen; + Curl_bufq_skip(&stream->sendbuf, skiplen); + stream->sendbuf_len_in_flight -= skiplen; + + /* Resume upload processing if we have more data to send */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + int rv = nghttp3_conn_resume_stream(conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_callbacks ngh3_callbacks = { + cb_h3_acked_stream_data, + cb_h3_stream_close, + cb_h3_recv_data, + cb_h3_deferred_consume, + NULL, /* begin_headers */ + cb_h3_recv_header, + cb_h3_end_headers, + NULL, /* begin_trailers */ + cb_h3_recv_header, + NULL, /* end_trailers */ + cb_h3_stop_sending, + NULL, /* end_stream */ + cb_h3_reset_stream, + NULL, /* shutdown */ + NULL /* recv_settings */ +}; + +static CURLcode cf_osslq_h3conn_init(struct cf_osslq_ctx *ctx, SSL *conn, + void *user_data) +{ + struct cf_osslq_h3conn *h3 = &ctx->h3; + CURLcode result; + int rc; + + nghttp3_settings_default(&h3->settings); + rc = nghttp3_conn_client_new(&h3->conn, + &ngh3_callbacks, + &h3->settings, + nghttp3_mem_default(), + user_data); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = cf_osslq_stream_open(&h3->s_ctrl, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_enc, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_dec, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + rc = nghttp3_conn_bind_control_stream(h3->conn, h3->s_ctrl.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + rc = nghttp3_conn_bind_qpack_streams(h3->conn, h3->s_qpack_enc.id, + h3->s_qpack_dec.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + result = CURLE_OK; +out: + return result; +} + +static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result; + int rv; + const struct Curl_sockaddr_ex *peer_addr = NULL; + BIO *bio = NULL; + BIO_ADDR *baddr = NULL; +static const struct alpn_spec ALPN_SPEC_H3 = { + { "h3" }, 1 +}; + + DEBUGASSERT(ctx->initialized); + +#define H3_ALPN "\x2h3" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + &ALPN_SPEC_H3, NULL, NULL, NULL, NULL); + if(result) + goto out; + + result = vquic_ctx_init(&ctx->q); + if(result) + goto out; + + result = CURLE_QUIC_CONNECT_ERROR; + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &peer_addr, NULL); + if(!peer_addr) + goto out; + + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + goto out; + + result = make_bio_addr(&baddr, peer_addr); + if(result) { + failf(data, "error creating BIO_ADDR from sockaddr"); + goto out; + } + + /* Type conversions, see #12861: OpenSSL wants an `int`, but on 64-bit + * Win32 systems, Microsoft defines SOCKET as `unsigned long long`. + */ +#if defined(_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) + if(ctx->q.sockfd > INT_MAX) { + failf(data, "Windows socket identifier larger than MAX_INT, " + "unable to set in OpenSSL dgram API."); + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + bio = BIO_new_dgram((int)ctx->q.sockfd, BIO_NOCLOSE); +#else + bio = BIO_new_dgram(ctx->q.sockfd, BIO_NOCLOSE); +#endif + if(!bio) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(!SSL_set1_initial_peer_addr(ctx->tls.ossl.ssl, baddr)) { + failf(data, "failed to set the initial peer address"); + result = CURLE_FAILED_INIT; + goto out; + } + if(!SSL_set_blocking_mode(ctx->tls.ossl.ssl, 0)) { + failf(data, "failed to turn off blocking mode"); + result = CURLE_FAILED_INIT; + goto out; + } + + SSL_set_bio(ctx->tls.ossl.ssl, bio, bio); + bio = NULL; + SSL_set_connect_state(ctx->tls.ossl.ssl); + SSL_set_incoming_stream_policy(ctx->tls.ossl.ssl, + SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); + /* setup the H3 things on top of the QUIC connection */ + result = cf_osslq_h3conn_init(ctx, ctx->tls.ossl.ssl, cf); + +out: + if(bio) + BIO_free(bio); + if(baddr) + BIO_ADDR_free(baddr); + CURL_TRC_CF(data, cf, "QUIC tls init -> %d", result); + return result; +} + +struct h3_quic_recv_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct cf_osslq_stream *s; +}; + +static ssize_t h3_quic_recv(void *reader_ctx, + unsigned char *buf, size_t len, + CURLcode *err) +{ + struct h3_quic_recv_ctx *x = reader_ctx; + size_t nread; + int rv; + + *err = CURLE_OK; + rv = SSL_read_ex(x->s->ssl, buf, len, &nread); + if(rv <= 0) { + int detail = SSL_get_error(x->s->ssl, rv); + if(detail == SSL_ERROR_WANT_READ || detail == SSL_ERROR_WANT_WRITE) { + *err = CURLE_AGAIN; + return -1; + } + else if(detail == SSL_ERROR_ZERO_RETURN) { + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] h3_quic_recv -> EOS", + x->s->id); + x->s->recvd_eos = TRUE; + return 0; + } + else if(SSL_get_stream_read_state(x->s->ssl) == + SSL_STREAM_STATE_RESET_REMOTE) { + uint64_t app_error_code = NGHTTP3_H3_NO_ERROR; + SSL_get_stream_read_error_code(x->s->ssl, &app_error_code); + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] h3_quic_recv -> RESET, " + "rv=%d, app_err=%" FMT_PRIu64, + x->s->id, rv, (curl_uint64_t)app_error_code); + if(app_error_code != NGHTTP3_H3_NO_ERROR) { + x->s->reset = TRUE; + } + x->s->recvd_eos = TRUE; + return 0; + } + else { + *err = cf_osslq_ssl_err(x->cf, x->data, detail, CURLE_RECV_ERROR); + return -1; + } + } + return (ssize_t)nread; +} + +static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + ssize_t nread; + struct h3_quic_recv_ctx x; + bool eagain = FALSE; + size_t total_recv_len = 0; + + DEBUGASSERT(s); + if(s->closed) + return CURLE_OK; + + x.cf = cf; + x.data = data; + x.s = s; + while(s->ssl && !s->closed && !eagain && + (total_recv_len < H3_STREAM_CHUNK_SIZE)) { + if(Curl_bufq_is_empty(&s->recvbuf) && !s->recvd_eos) { + while(!eagain && !s->recvd_eos && !Curl_bufq_is_full(&s->recvbuf)) { + nread = Curl_bufq_sipn(&s->recvbuf, 0, h3_quic_recv, &x, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + goto out; + result = CURLE_OK; + eagain = TRUE; + } + } + } + + /* Forward what we have to nghttp3 */ + if(!Curl_bufq_is_empty(&s->recvbuf)) { + const unsigned char *buf; + size_t blen; + + while(Curl_bufq_peek(&s->recvbuf, &buf, &blen)) { + nread = nghttp3_conn_read_stream(ctx->h3.conn, s->id, + buf, blen, 0); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] forward %zu bytes " + "to nghttp3 -> %zd", s->id, blen, nread); + if(nread < 0) { + failf(data, "nghttp3_conn_read_stream(len=%zu) error: %s", + blen, nghttp3_strerror((int)nread)); + result = CURLE_RECV_ERROR; + goto out; + } + /* success, `nread` is the flow for QUIC to count as "consumed", + * not sure how that will work with OpenSSL. Anyways, without error, + * all data that we passed is not owned by nghttp3. */ + Curl_bufq_skip(&s->recvbuf, blen); + total_recv_len += blen; + } + } + + /* When we forwarded everything, handle RESET/EOS */ + if(Curl_bufq_is_empty(&s->recvbuf) && !s->closed) { + int rv; + result = CURLE_OK; + if(s->reset) { + uint64_t app_error; + if(!SSL_get_stream_read_error_code(s->ssl, &app_error)) { + failf(data, "SSL_get_stream_read_error_code returned error"); + result = CURLE_RECV_ERROR; + goto out; + } + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, app_error); + s->closed = TRUE; + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + else if(s->recvd_eos) { + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, + NGHTTP3_H3_NO_ERROR); + s->closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] close nghttp3 stream -> %d", + s->id, rv); + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + } + } +out: + if(result) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_osslq_stream_recv -> %d", + s->id, result); + return result; +} + +struct cf_ossq_recv_ctx { + struct Curl_cfilter *cf; + struct Curl_multi *multi; + CURLcode result; +}; + +static bool cf_osslq_iter_recv(unsigned int mid, void *val, void *user_data) +{ + struct h3_stream_ctx *stream = val; + struct cf_ossq_recv_ctx *rctx = user_data; + + (void)mid; + if(stream && !stream->closed && !Curl_bufq_is_full(&stream->recvbuf)) { + struct Curl_easy *sdata = Curl_multi_get_easy(rctx->multi, mid); + if(sdata) { + rctx->result = cf_osslq_stream_recv(&stream->s, rctx->cf, sdata); + if(rctx->result) + return FALSE; /* abort iteration */ + } + } + return TRUE; +} + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl) + goto out; + + ERR_clear_error(); + + /* 1. Check for new incoming streams */ + while(1) { + SSL *snew = SSL_accept_stream(ctx->tls.ossl.ssl, + SSL_ACCEPT_STREAM_NO_BLOCK); + if(!snew) + break; + + (void)cf_osslq_h3conn_add_stream(&ctx->h3, snew, cf, data); + } + + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_RECV_ERROR); + } + + if(ctx->h3.conn) { + size_t i; + for(i = 0; i < ctx->h3.remote_ctrl_n; ++i) { + result = cf_osslq_stream_recv(&ctx->h3.remote_ctrl[i], cf, data); + if(result) + goto out; + } + } + + if(ctx->h3.conn) { + struct cf_ossq_recv_ctx rctx; + + DEBUGASSERT(data->multi); + rctx.cf = cf; + rctx.multi = data->multi; + rctx.result = CURLE_OK; + Curl_uint_hash_visit(&ctx->streams, cf_osslq_iter_recv, &rctx); + result = rctx.result; + } + +out: + CURL_TRC_CF(data, cf, "progress_ingress -> %d", result); + return result; +} + +struct cf_ossq_fill_ctx { + struct cf_osslq_ctx *ctx; + struct Curl_multi *multi; + size_t n; +}; + +static bool cf_osslq_collect_block_send(unsigned int mid, void *val, + void *user_data) +{ + struct h3_stream_ctx *stream = val; + struct cf_ossq_fill_ctx *fctx = user_data; + struct cf_osslq_ctx *ctx = fctx->ctx; + + if(fctx->n >= ctx->items_max) /* should not happen, prevent mayhem */ + return FALSE; + + if(stream && stream->s.ssl && stream->s.send_blocked) { + struct Curl_easy *sdata = Curl_multi_get_easy(fctx->multi, mid); + if(sdata) { + ctx->poll_items[fctx->n].desc = SSL_as_poll_descriptor(stream->s.ssl); + ctx->poll_items[fctx->n].events = SSL_POLL_EVENT_W; + ctx->curl_items[fctx->n] = sdata; + fctx->n++; + } + } + return TRUE; +} + +/* Iterate over all streams and check if blocked can be unblocked */ +static CURLcode cf_osslq_check_and_unblock(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream; + size_t poll_count; + size_t result_count = 0; + size_t idx_count = 0; + CURLcode res = CURLE_OK; + struct timeval timeout; + void *tmpptr; + + if(ctx->h3.conn) { + struct cf_ossq_fill_ctx fill_ctx; + + if(ctx->items_max < Curl_uint_hash_count(&ctx->streams)) { + size_t nmax = Curl_uint_hash_count(&ctx->streams); + ctx->items_max = 0; + tmpptr = realloc(ctx->poll_items, nmax * sizeof(SSL_POLL_ITEM)); + if(!tmpptr) { + free(ctx->poll_items); + ctx->poll_items = NULL; + res = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->poll_items = tmpptr; + + tmpptr = realloc(ctx->curl_items, nmax * sizeof(struct Curl_easy *)); + if(!tmpptr) { + free(ctx->curl_items); + ctx->curl_items = NULL; + res = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->curl_items = tmpptr; + ctx->items_max = nmax; + } + + fill_ctx.ctx = ctx; + fill_ctx.multi = data->multi; + fill_ctx.n = 0; + Curl_uint_hash_visit(&ctx->streams, cf_osslq_collect_block_send, + &fill_ctx); + poll_count = fill_ctx.n; + if(poll_count) { + CURL_TRC_CF(data, cf, "polling %zu blocked streams", poll_count); + + memset(&timeout, 0, sizeof(struct timeval)); + res = CURLE_UNRECOVERABLE_POLL; + if(!SSL_poll(ctx->poll_items, poll_count, sizeof(SSL_POLL_ITEM), + &timeout, 0, &result_count)) + goto out; + + res = CURLE_OK; + + for(idx_count = 0; idx_count < poll_count && result_count > 0; + idx_count++) { + if(ctx->poll_items[idx_count].revents & SSL_POLL_EVENT_W) { + stream = H3_STREAM_CTX(ctx, ctx->curl_items[idx_count]); + DEBUGASSERT(stream); /* should still exist */ + if(stream) { + nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); + stream->s.send_blocked = FALSE; + h3_drain_stream(cf, ctx->curl_items[idx_count]); + CURL_TRC_CF(ctx->curl_items[idx_count], cf, "unblocked"); + } + result_count--; + } + } + } + } + +out: + return res; +} + +static CURLcode h3_send_streams(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl || !ctx->h3.conn) + goto out; + + for(;;) { + struct cf_osslq_stream *s = NULL; + nghttp3_vec vec[16]; + nghttp3_ssize n, i; + int64_t stream_id; + size_t written; + int eos, ok, rv; + size_t total_len, acked_len = 0; + bool blocked = FALSE, eos_written = FALSE; + + n = nghttp3_conn_writev_stream(ctx->h3.conn, &stream_id, &eos, + vec, CURL_ARRAYSIZE(vec)); + if(n < 0) { + failf(data, "nghttp3_conn_writev_stream returned error: %s", + nghttp3_strerror((int)n)); + result = CURLE_SEND_ERROR; + goto out; + } + if(stream_id < 0) { + result = CURLE_OK; + goto out; + } + + /* Get the stream for this data */ + s = cf_osslq_get_qstream(cf, data, stream_id); + if(!s) { + failf(data, "nghttp3_conn_writev_stream gave unknown stream %" + FMT_PRId64, (curl_int64_t)stream_id); + result = CURLE_SEND_ERROR; + goto out; + } + /* Now write the data to the stream's SSL*, it may not all fit! */ + DEBUGASSERT(s->id == stream_id); + for(i = 0, total_len = 0; i < n; ++i) { + total_len += vec[i].len; + } + for(i = 0; (i < n) && !blocked; ++i) { + /* Without stream->s.ssl, we closed that already, so + * pretend the write did succeed. */ + uint64_t flags = (eos && ((i + 1) == n)) ? SSL_WRITE_FLAG_CONCLUDE : 0; + written = vec[i].len; + ok = !s->ssl || SSL_write_ex2(s->ssl, vec[i].base, vec[i].len, flags, + &written); + if(ok && flags & SSL_WRITE_FLAG_CONCLUDE) + eos_written = TRUE; + if(ok) { + /* As OpenSSL buffers the data, we count this as acknowledged + * from nghttp3's point of view */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send %zu bytes to QUIC ok", + s->id, vec[i].len); + acked_len += vec[i].len; + } + else { + int detail = SSL_get_error(s->ssl, 0); + switch(detail) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + /* QUIC blocked us from writing more */ + CURL_TRC_CF(data, cf, "[%"FMT_PRId64 "] send %zu bytes to " + "QUIC blocked", s->id, vec[i].len); + written = 0; + nghttp3_conn_block_stream(ctx->h3.conn, s->id); + s->send_blocked = blocked = TRUE; + break; + default: + failf(data, "[%"FMT_PRId64 "] send %zu bytes to QUIC, SSL error %d", + s->id, vec[i].len, detail); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_HTTP3); + goto out; + } + } + } + + if(acked_len > 0 || (eos && !s->send_blocked)) { + /* Since QUIC buffers the data written internally, we can tell + * nghttp3 that it can move forward on it */ + ctx->q.last_io = curlx_now(); + rv = nghttp3_conn_add_write_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + rv = nghttp3_conn_add_ack_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_ack_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] forwarded %zu/%zu h3 bytes " + "to QUIC, eos=%d", s->id, acked_len, total_len, eos); + } + + if(eos && !s->send_blocked && !eos_written) { + /* wrote everything and H3 indicates end of stream */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] closing QUIC stream", s->id); + SSL_stream_conclude(s->ssl, 0); + } + } + +out: + CURL_TRC_CF(data, cf, "h3_send_streams -> %d", result); + return result; +} + +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl) + goto out; + + ERR_clear_error(); + result = h3_send_streams(cf, data); + if(result) + goto out; + + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); + } + + result = cf_osslq_check_and_unblock(cf, data); + +out: + CURL_TRC_CF(data, cf, "progress_egress -> %d", result); + return result; +} + +static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct timeval tv; + timediff_t timeoutms; + int is_infinite = 1; + + if(ctx->tls.ossl.ssl && + SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite) && + !is_infinite) { + timeoutms = curlx_tvtoms(&tv); + /* QUIC want to be called again latest at the returned timeout */ + if(timeoutms <= 0) { + result = cf_progress_ingress(cf, data); + if(result) + goto out; + result = cf_progress_egress(cf, data); + if(result) + goto out; + if(SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite)) { + timeoutms = curlx_tvtoms(&tv); + } + } + if(!is_infinite) { + Curl_expire(data, timeoutms, EXPIRE_QUIC); + CURL_TRC_CF(data, cf, "QUIC expiry in %ldms", (long)timeoutms); + } + } +out: + return result; +} + +static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct curltime now; + int err; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the UDP filter first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, done); + if(result || !*done) + return result; + } + + *done = FALSE; + now = curlx_now(); + CF_DATA_SAVE(save, cf, data); + + if(!ctx->tls.ossl.ssl) { + ctx->started_at = now; + result = cf_osslq_ctx_start(cf, data); + if(result) + goto out; + } + + if(!ctx->got_first_byte) { + int readable = SOCKET_READABLE(ctx->q.sockfd, 0); + if(readable > 0 && (readable & CURL_CSELECT_IN)) { + ctx->got_first_byte = TRUE; + ctx->first_byte_at = curlx_now(); + } + } + + /* Since OpenSSL does its own send/recv internally, we may miss the + * moment to populate the x509 store right before the server response. + * Do it instead before we start the handshake, at the loss of the + * time to set this up. */ + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + goto out; + + ERR_clear_error(); + err = SSL_do_handshake(ctx->tls.ossl.ssl); + + if(err == 1) { + /* connected */ + ctx->handshake_at = now; + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)curlx_timediff(now, ctx->started_at)); + result = cf_osslq_verify_peer(cf, data); + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + } + } + else { + int detail = SSL_get_error(ctx->tls.ossl.ssl, err); + switch(detail) { + case SSL_ERROR_WANT_READ: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_RECV"); + goto out; + case SSL_ERROR_WANT_WRITE: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_SEND"); + result = CURLE_OK; + goto out; +#ifdef SSL_ERROR_WANT_ASYNC + case SSL_ERROR_WANT_ASYNC: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_ASYNC"); + result = CURLE_OK; + goto out; +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + case SSL_ERROR_WANT_RETRY_VERIFY: + result = CURLE_OK; + goto out; +#endif + default: + result = cf_osslq_ssl_err(cf, data, detail, CURLE_COULDNT_CONNECT); + goto out; + } + } + +out: + if(result == CURLE_RECV_ERROR && ctx->tls.ossl.ssl && + ctx->protocol_shutdown) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result) { + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + infof(data, "QUIC connect to %s port %u failed: %s", + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); + } +#endif + if(!result) + result = check_and_set_expiry(cf, data); + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +static ssize_t h3_stream_open(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t len, + CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = NULL; + struct dynhds h2_headers; + size_t nheader; + nghttp3_nv *nva = NULL; + int rc = 0; + unsigned int i; + ssize_t nwritten = -1; + nghttp3_data_reader reader; + nghttp3_data_reader *preader = NULL; + + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + *err = h3_data_setup(cf, data); + if(*err) + goto out; + stream = H3_STREAM_CTX(ctx, data); + DEBUGASSERT(stream); + if(!stream) { + *err = CURLE_FAILED_INIT; + goto out; + } + + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); + if(nwritten < 0) + goto out; + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); + + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); + if(*err) { + nwritten = -1; + goto out; + } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); + + nheader = Curl_dynhds_count(&h2_headers); + nva = malloc(sizeof(nghttp3_nv) * nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } + + for(i = 0; i < nheader; ++i) { + struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP3_NV_FLAG_NONE; + } + + DEBUGASSERT(stream->s.id == -1); + *err = cf_osslq_stream_open(&stream->s, ctx->tls.ossl.ssl, 0, + &ctx->stream_bufcp, data); + if(*err) { + failf(data, "cannot get bidi streams"); + *err = CURLE_SEND_ERROR; + goto out; + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + /* known request body size or -1 */ + if(data->state.infilesize != -1) + stream->upload_left = data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown */ + break; + default: + /* there is not request body */ + stream->upload_left = 0; /* no request body */ + break; + } + + stream->send_closed = (stream->upload_left == 0); + if(!stream->send_closed) { + reader.read_data = cb_h3_read_req_body; + preader = &reader; + } + + rc = nghttp3_conn_submit_request(ctx->h3.conn, stream->s.id, + nva, nheader, preader, data); + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + CURL_TRC_CF(data, cf, "h3sid[%"FMT_PRId64"] failed to send, " + "connection is closing", stream->s.id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%"FMT_PRId64 "] failed to send -> %d (%s)", + stream->s.id, rc, nghttp3_strerror(rc)); + break; + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] OPENED stream for %s", + stream->s.id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] [%.*s: %.*s]", + stream->s.id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + } + +out: + free(nva); + Curl_dynhds_free(&h2_headers); + return nwritten; +} + +static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + ssize_t nwritten; + CURLcode result; + + (void)eos; /* use to end stream */ + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->tls.ossl.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + if(!stream || stream->s.id < 0) { + nwritten = h3_stream_open(cf, data, buf, len, err); + if(nwritten < 0) { + CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); + goto out; + } + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a final + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] discarding data" + "on closed stream with response", stream->s.id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->s.id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + else { + nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %zd, %d", + stream->s.id, len, nwritten, *err); + if(nwritten < 0) { + goto out; + } + + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + } + +out: + result = check_and_set_expiry(cf, data); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->s.id : -1, len, nwritten, *err); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + CURLcode *err) +{ + ssize_t nread = -1; + + (void)cf; + if(stream->reset) { + failf(data, + "HTTP/3 stream %" FMT_PRId64 " reset by server", + stream->s.id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + goto out; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" FMT_PRId64 + " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->s.id); + *err = CURLE_HTTP3; + goto out; + } + *err = CURLE_OK; + nread = 0; + +out: + return nread; +} + +static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nread = -1; + struct cf_call_data save; + CURLcode result; + + (void)ctx; + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->tls.ossl.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + if(!stream) { + *err = CURLE_RECV_ERROR; + goto out; + } + + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + nread = -1; + goto out; + } + + /* recvbuf had nothing before, maybe after progressing ingress? */ + if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + if(nread > 0) { + h3_drain_stream(cf, data); + } + else { + if(stream->closed) { + nread = recv_closed_stream(cf, data, stream, err); + goto out; + } + *err = CURLE_AGAIN; + nread = -1; + } + +out: + if(cf_progress_egress(cf, data)) { + *err = CURLE_SEND_ERROR; + nread = -1; + } + else { + CURLcode result2 = check_and_set_expiry(cf, data); + if(result2) { + *err = result2; + nread = -1; + } + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_recv(len=%zu) -> %zd, %d", + stream ? stream->s.id : -1, len, nread, *err); + CF_DATA_RESTORE(cf, save); + return nread; +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_osslq_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)cf; + return stream && !Curl_bufq_is_empty(&stream->recvbuf); +} + +static CURLcode cf_osslq_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_SETUP: + break; + case CF_CTRL_DATA_PAUSE: + result = h3_data_pause(cf, data, (arg1 != 0)); + break; + case CF_CTRL_DATA_DONE: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE_SEND: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + if(stream && !stream->send_closed) { + stream->send_closed = TRUE; + stream->upload_left = Curl_bufq_len(&stream->sendbuf) - + stream->sendbuf_len_in_flight; + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURL_TRC_CF(data, cf, "data idle"); + if(stream && !stream->closed) { + result = check_and_set_expiry(cf, data); + } + break; + } + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + bool alive = FALSE; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + *input_pending = FALSE; + if(!ctx->tls.ossl.ssl) + goto out; + +#ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT + /* Added in OpenSSL v3.3.x */ + { + timediff_t idletime; + uint64_t idle_ms = ctx->max_idle_ms; + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, + SSL_VALUE_CLASS_FEATURE_NEGOTIATED, + SSL_VALUE_QUIC_IDLE_TIMEOUT, &idle_ms)) { + CURL_TRC_CF(data, cf, "error getting negotiated idle timeout, " + "assume connection is dead."); + goto out; + } + CURL_TRC_CF(data, cf, "negotiated idle timeout: %zums", (size_t)idle_ms); + idletime = curlx_timediff(curlx_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > idle_ms) + goto out; + } + +#endif + + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + goto out; + + alive = TRUE; + if(*input_pending) { + CURLcode result; + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, + only "protocol frames" */ + *input_pending = FALSE; + result = cf_progress_ingress(cf, data); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result ? FALSE : TRUE; + } + +out: + CF_DATA_RESTORE(cf, save); + return alive; +} + +static void cf_osslq_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + if(!ctx->tls.ossl.ssl) { + /* NOP */ + } + else if(!cf->connected) { + /* during handshake, transfer has not started yet. we always + * add our socket for polling if SSL wants to send/recv */ + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); + } + else { + /* once connected, we only modify the socket if it is present. + * this avoids adding it for paused transfers. */ + bool want_recv, want_send; + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); + } + else if(ctx->need_recv || ctx->need_send) { + Curl_pollset_set(data, ps, ctx->q.sockfd, + ctx->need_recv, ctx->need_send); + } + } +} + +static CURLcode cf_osslq_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { +#ifdef SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL + /* Added in OpenSSL v3.3.x */ + uint64_t v; + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, SSL_VALUE_CLASS_GENERIC, + SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, &v)) { + CURL_TRC_CF(data, cf, "error getting available local bidi streams"); + return CURLE_HTTP3; + } + /* we report avail + in_use */ + v += CONN_ATTACHED(cf->conn); + *pres1 = (v > INT_MAX) ? INT_MAX : (int)v; +#else + *pres1 = 100; +#endif + CURL_TRC_CF(data, cf, "query max_concurrent -> %d", *pres1); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->got_first_byte) { + timediff_t ms = curlx_timediff(ctx->first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; + } + else + *pres1 = -1; + return CURLE_OK; + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + if(ctx->got_first_byte) + *when = ctx->first_byte_at; + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + if(cf->connected) + *when = ctx->handshake_at; + return CURLE_OK; + } + case CF_QUERY_HTTP_VERSION: + *pres1 = 30; + return CURLE_OK; + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, + 0, + cf_osslq_destroy, + cf_osslq_connect, + cf_osslq_close, + cf_osslq_shutdown, + Curl_cf_def_get_host, + cf_osslq_adjust_pollset, + cf_osslq_data_pending, + cf_osslq_send, + cf_osslq_recv, + cf_osslq_data_event, + cf_osslq_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_osslq_query, +}; + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_osslq_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL, *udp_cf = NULL; + CURLcode result; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_osslq_ctx_init(ctx); + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + + result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC); + if(result) + goto out; + + cf->conn = conn; + udp_cf->conn = cf->conn; + udp_cf->sockindex = cf->sockindex; + cf->next = udp_cf; + +out: + *pcf = (!result) ? cf : NULL; + if(result) { + if(udp_cf) + Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); + Curl_safefree(cf); + cf_osslq_ctx_free(ctx); + } + return result; +} + +bool Curl_conn_is_osslq(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; + + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_http3) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +/* + * Store ngtcp2 version info in this buffer. + */ +void Curl_osslq_ver(char *p, size_t len) +{ + const nghttp3_info *ht3 = nghttp3_version(0); + (void)msnprintf(p, len, "nghttp3/%s", ht3->version_str); +} + +#endif /* USE_OPENSSL_QUIC && USE_NGHTTP3 */ diff --git a/Utilities/cmcurl/lib/vquic/curl_osslq.h b/Utilities/cmcurl/lib/vquic/curl_osslq.h new file mode 100644 index 00000000000..a2809c92bcc --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_osslq.h @@ -0,0 +1,51 @@ +#ifndef HEADER_CURL_VQUIC_CURL_OSSLQ_H +#define HEADER_CURL_VQUIC_CURL_OSSLQ_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" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#ifdef HAVE_NETINET_UDP_H +#include +#endif + +struct Curl_cfilter; + +#include "../urldata.h" + +void Curl_osslq_ver(char *p, size_t len); + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_osslq(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +#endif + +#endif /* HEADER_CURL_VQUIC_CURL_OSSLQ_H */ diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.c b/Utilities/cmcurl/lib/vquic/curl_quiche.c index 3a4f9f9f201..88065328896 100644 --- a/Utilities/cmcurl/lib/vquic/curl_quiche.c +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.c @@ -22,49 +22,52 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_QUICHE #include #include #include -#include "bufq.h" -#include "urldata.h" -#include "cfilters.h" -#include "cf-socket.h" -#include "sendf.h" -#include "strdup.h" -#include "rand.h" -#include "strcase.h" -#include "multiif.h" -#include "connect.h" -#include "progress.h" -#include "strerror.h" -#include "http1.h" +#include "../bufq.h" +#include "../uint-hash.h" +#include "../urldata.h" +#include "../cfilters.h" +#include "../cf-socket.h" +#include "../sendf.h" +#include "../strdup.h" +#include "../rand.h" +#include "../strcase.h" +#include "../multiif.h" +#include "../connect.h" +#include "../progress.h" +#include "../strerror.h" +#include "../http1.h" #include "vquic.h" #include "vquic_int.h" +#include "vquic-tls.h" #include "curl_quiche.h" -#include "transfer.h" -#include "vtls/openssl.h" -#include "vtls/keylog.h" +#include "../transfer.h" +#include "../curlx/inet_pton.h" +#include "../vtls/openssl.h" +#include "../vtls/keylog.h" +#include "../vtls/vtls.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" -/* #define DEBUG_QUICHE */ +/* HTTP/3 error values defined in RFC 9114, ch. 8.1 */ +#define CURL_H3_NO_ERROR (0x0100) #define QUIC_MAX_STREAMS (100) -#define QUIC_IDLE_TIMEOUT (5 * 1000) /* milliseconds */ #define H3_STREAM_WINDOW_SIZE (128 * 1024) #define H3_STREAM_CHUNK_SIZE (16 * 1024) -/* The pool keeps spares around and half of a full stream windows - * seems good. More does not seem to improve performance. - * The benefit of the pool is that stream buffer to not keep - * spares. So memory consumption goes down when streams run empty, - * have a large upload done, etc. */ +/* The pool keeps spares around and half of a full stream windows seems good. + * More does not seem to improve performance. The benefit of the pool is that + * stream buffer to not keep spares. Memory consumption goes down when streams + * run empty, have a large upload done, etc. */ #define H3_STREAM_POOL_SPARES \ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 /* Receive and Send max number of chunks just follows from the @@ -82,81 +85,29 @@ void Curl_quiche_ver(char *p, size_t len) (void)msnprintf(p, len, "quiche/%s", quiche_version()); } -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} - -static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) -{ - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - - SSL_CTX_set_alpn_protos(ssl_ctx, - (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - - SSL_CTX_set_default_verify_paths(ssl_ctx); - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); - } - - { - struct connectdata *conn = data->conn; - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - if(ssl_cafile || ssl_capath) { - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - /* tell OpenSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!SSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return NULL; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } -#ifdef CURL_CA_FALLBACK - else { - /* verifying the peer without any CA certificates won't work so - use openssl's built-in default as fallback */ - SSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - } - return ssl_ctx; -} - struct cf_quiche_ctx { struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; quiche_conn *qconn; quiche_config *cfg; quiche_h3_conn *h3c; quiche_h3_config *h3config; uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; - SSL_CTX *sslctx; - SSL *ssl; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ - struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct uint_hash streams; /* hash `data->mid` to `stream_ctx` */ curl_off_t data_recvd; - size_t sends_on_hold; /* # of streams with SEND_HOLD set */ + BIT(initialized); BIT(goaway); /* got GOAWAY from server */ - BIT(got_first_byte); /* if first byte was received */ + BIT(x509_store_setup); /* if x509 store has been set up */ + BIT(shutdown_started); /* queued shutdown packets */ }; #ifdef DEBUG_QUICHE +/* initialize debug log callback only once */ +static int debug_log_init = 0; static void quiche_debug_log(const char *line, void *argp) { (void)argp; @@ -164,107 +115,151 @@ static void quiche_debug_log(const char *line, void *argp) } #endif -static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx) +static void h3_stream_hash_free(unsigned int id, void *stream); + +static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) { - if(ctx) { + DEBUGASSERT(!ctx->initialized); +#ifdef DEBUG_QUICHE + if(!debug_log_init) { + quiche_enable_debug_logging(quiche_debug_log, NULL); + debug_log_init = 1; + } +#endif + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->data_recvd = 0; + ctx->initialized = TRUE; +} + +static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) +{ + if(ctx && ctx->initialized) { + /* quiche just freed it */ + ctx->tls.ossl.ssl = NULL; + Curl_vquic_tls_cleanup(&ctx->tls); + Curl_ssl_peer_cleanup(&ctx->peer); vquic_ctx_free(&ctx->q); - if(ctx->qconn) - quiche_conn_free(ctx->qconn); - if(ctx->h3config) - quiche_h3_config_free(ctx->h3config); - if(ctx->h3c) - quiche_h3_conn_free(ctx->h3c); - if(ctx->cfg) - quiche_config_free(ctx->cfg); Curl_bufcp_free(&ctx->stream_bufcp); - memset(ctx, 0, sizeof(*ctx)); + Curl_uint_hash_destroy(&ctx->streams); } + free(ctx); } +static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx) +{ + if(ctx->h3c) + quiche_h3_conn_free(ctx->h3c); + if(ctx->h3config) + quiche_h3_config_free(ctx->h3config); + if(ctx->qconn) + quiche_conn_free(ctx->qconn); + if(ctx->cfg) + quiche_config_free(ctx->cfg); +} + +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data); + /** * All about the H3 internals of a stream */ -struct stream_ctx { - int64_t id; /* HTTP/3 protocol stream identifier */ +struct h3_stream_ctx { + curl_uint64_t id; /* HTTP/3 protocol stream identifier */ struct bufq recvbuf; /* h3 response */ - uint64_t error3; /* HTTP/3 stream error code */ - curl_off_t upload_left; /* number of request bytes left to upload */ - bool closed; /* TRUE on stream close */ - bool reset; /* TRUE on stream reset */ - bool send_closed; /* stream is locally closed */ - bool resp_hds_complete; /* complete, final response has been received */ - bool resp_got_header; /* TRUE when h3 stream has recvd some HEADER */ + struct h1_req_parser h1; /* h1 request parsing */ + curl_uint64_t error3; /* HTTP/3 stream error code */ + BIT(opened); /* TRUE after stream has been opened */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(send_closed); /* stream is locally closed */ + BIT(resp_hds_complete); /* final response has been received */ + BIT(resp_got_header); /* TRUE when h3 stream has recvd some HEADER */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) - -static bool stream_send_is_suspended(struct Curl_easy *data) +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) { - return (data->req.keepon & KEEP_SEND_HOLD); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); } -static void stream_send_suspend(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void h3_stream_hash_free(unsigned int id, void *stream) { - struct cf_quiche_ctx *ctx = cf->ctx; + (void)id; + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} - if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { - data->req.keepon |= KEEP_SEND_HOLD; - ++ctx->sends_on_hold; - if(H3_STREAM_ID(data) >= 0) - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] suspend sending", - H3_STREAM_ID(data))); - else - DEBUGF(LOG_CF(data, cf, "[%s] suspend sending", - data->state.url)); - } +typedef bool cf_quiche_svisit(struct Curl_cfilter *cf, + struct Curl_easy *sdata, + struct h3_stream_ctx *stream, + void *user_data); + +struct cf_quiche_visit_ctx { + struct Curl_cfilter *cf; + struct Curl_multi *multi; + cf_quiche_svisit *cb; + void *user_data; +}; + +static bool cf_quiche_stream_do(unsigned int mid, void *val, void *user_data) +{ + struct cf_quiche_visit_ctx *vctx = user_data; + struct h3_stream_ctx *stream = val; + struct Curl_easy *sdata = Curl_multi_get_easy(vctx->multi, mid); + if(sdata) + return vctx->cb(vctx->cf, sdata, stream, vctx->user_data); + return TRUE; } -static void stream_send_resume(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void cf_quiche_for_all_streams(struct Curl_cfilter *cf, + struct Curl_multi *multi, + cf_quiche_svisit *do_cb, + void *user_data) { struct cf_quiche_ctx *ctx = cf->ctx; + struct cf_quiche_visit_ctx vctx; + vctx.cf = cf; + vctx.multi = multi; + vctx.cb = do_cb; + vctx.user_data = user_data; + Curl_uint_hash_visit(&ctx->streams, cf_quiche_stream_do, &vctx); +} - if(stream_send_is_suspended(data)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - --ctx->sends_on_hold; - if(H3_STREAM_ID(data) >= 0) - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] resume sending", - H3_STREAM_ID(data))); - else - DEBUGF(LOG_CF(data, cf, "[%s] resume sending", - data->state.url)); - Curl_expire(data, 0, EXPIRE_RUN_NOW); +static bool cf_quiche_do_resume(struct Curl_cfilter *cf, + struct Curl_easy *sdata, + struct h3_stream_ctx *stream, + void *user_data) +{ + (void)user_data; + if(stream->quic_flow_blocked) { + stream->quic_flow_blocked = FALSE; + Curl_expire(sdata, 0, EXPIRE_RUN_NOW); + CURL_TRC_CF(sdata, cf, "[%"FMT_PRIu64"] unblock", stream->id); } + return TRUE; } -static void check_resumes(struct Curl_cfilter *cf, - struct Curl_easy *data) +static bool cf_quiche_do_expire(struct Curl_cfilter *cf, + struct Curl_easy *sdata, + struct h3_stream_ctx *stream, + void *user_data) { - struct cf_quiche_ctx *ctx = cf->ctx; - struct Curl_easy *sdata; - - if(ctx->sends_on_hold) { - DEBUGASSERT(data->multi); - for(sdata = data->multi->easyp; - sdata && ctx->sends_on_hold; sdata = sdata->next) { - if(stream_send_is_suspended(sdata)) { - stream_send_resume(cf, sdata); - } - } - } + (void)stream; + (void)user_data; + CURL_TRC_CF(sdata, cf, "conn closed, expire transfer"); + Curl_expire(sdata, 0, EXPIRE_RUN_NOW); + return TRUE; } static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -273,68 +268,68 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - H3_STREAM_LCTX(data) = stream; stream->id = -1; Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - DEBUGF(LOG_CF(data, cf, "data setup (easy %p)", (void *)data)); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result; (void)cf; if(stream) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] easy handle is done", - stream->id)); - if(stream_send_is_suspended(data)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - --ctx->sends_on_hold; + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] easy handle is done", stream->id); + if(ctx->qconn && !stream->closed) { + quiche_conn_stream_shutdown(ctx->qconn, stream->id, + QUICHE_SHUTDOWN_READ, CURL_H3_NO_ERROR); + if(!stream->send_closed) { + quiche_conn_stream_shutdown(ctx->qconn, stream->id, + QUICHE_SHUTDOWN_WRITE, CURL_H3_NO_ERROR); + stream->send_closed = TRUE; + } + stream->closed = TRUE; + result = cf_flush_egress(cf, data); + if(result) + CURL_TRC_CF(data, cf, "data_done, flush egress -> %d", result); } - Curl_bufq_free(&stream->recvbuf); - free(stream); - H3_STREAM_LCTX(data) = NULL; + Curl_uint_hash_remove(&ctx->streams, data->mid); } } -static void drain_stream(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; bits = CURL_CSELECT_IN; - if(stream && !stream->send_closed && stream->upload_left) + if(stream && !stream->send_closed) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } -static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, - struct Curl_easy *data, - int64_t stream3_id) +static void cf_quiche_expire_conn_closed(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_easy *sdata; - - (void)cf; - if(H3_STREAM_ID(data) == stream3_id) { - return data; - } - else { - DEBUGASSERT(data->multi); - for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { - if(H3_STREAM_ID(sdata) == stream3_id) { - return sdata; - } - } - } - return NULL; + DEBUGASSERT(data->multi); + CURL_TRC_CF(data, cf, "conn closed, expire all transfers"); + cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_expire, NULL); } /* @@ -346,7 +341,8 @@ static CURLcode write_resp_raw(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, size_t memlen) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -376,11 +372,16 @@ static int cb_each_header(uint8_t *name, size_t name_len, void *argp) { struct cb_ctx *x = argp; - struct stream_ctx *stream = H3_STREAM_CTX(x->data); + struct cf_quiche_ctx *ctx = x->cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); CURLcode result; - (void)stream; + if(!stream) + return CURLE_OK; + if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) { + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRIu64 "] status: %.*s", + stream->id, (int)value_len, value); result = write_resp_raw(x->cf, x->data, "HTTP/3 ", sizeof("HTTP/3 ") - 1); if(!result) result = write_resp_raw(x->cf, x->data, value, value_len); @@ -388,6 +389,9 @@ static int cb_each_header(uint8_t *name, size_t name_len, result = write_resp_raw(x->cf, x->data, " \r\n", 3); } else { + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRIu64 "] header: %.*s: %.*s", + stream->id, (int)name_len, name, + (int)value_len, value); result = write_resp_raw(x->cf, x->data, name, name_len); if(!result) result = write_resp_raw(x->cf, x->data, ": ", 2); @@ -397,10 +401,8 @@ static int cb_each_header(uint8_t *name, size_t name_len, result = write_resp_raw(x->cf, x->data, "\r\n", 2); } if(result) { - DEBUGF(LOG_CF(x->data, x->cf, - "[h3sid=%"PRId64"][HEADERS][%.*s: %.*s] error %d", - stream? stream->id : -1, (int)name_len, name, - (int)value_len, value, result)); + CURL_TRC_CF(x->data, x->cf, "[%"FMT_PRIu64"] on header error %d", + stream->id, result); } return result; } @@ -411,7 +413,7 @@ static ssize_t stream_resp_read(void *reader_ctx, { struct cb_ctx *x = reader_ctx; struct cf_quiche_ctx *ctx = x->cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(x->data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); ssize_t nread; if(!stream) { @@ -425,12 +427,8 @@ static ssize_t stream_resp_read(void *reader_ctx, *err = CURLE_OK; return nread; } - else if(nread < 0) { - *err = CURLE_AGAIN; - return -1; - } else { - *err = stream->resp_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + *err = CURLE_AGAIN; return -1; } } @@ -438,7 +436,8 @@ static ssize_t stream_resp_read(void *reader_ctx, static CURLcode cf_recv_body(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten; struct cb_ctx cb_ctx; CURLcode result = CURLE_OK; @@ -459,10 +458,10 @@ static CURLcode cf_recv_body(struct Curl_cfilter *cf, stream_resp_read, &cb_ctx, &result); if(nwritten < 0 && result != CURLE_AGAIN) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv_body error %zd", - stream->id, nwritten)); - failf(data, "Error %zd in HTTP/3 response body for stream[%"PRId64"]", - nwritten, stream->id); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] recv_body error %zd", + stream->id, nwritten); + failf(data, "Error %d in HTTP/3 response body for stream[%"FMT_PRIu64"]", + result, stream->id); stream->closed = TRUE; stream->reset = TRUE; stream->send_closed = TRUE; @@ -496,17 +495,15 @@ static const char *cf_ev_name(quiche_h3_event *ev) static CURLcode h3_process_event(struct Curl_cfilter *cf, struct Curl_easy *data, - int64_t stream3_id, + struct h3_stream_ctx *stream, quiche_h3_event *ev) { - struct stream_ctx *stream = H3_STREAM_CTX(data); struct cb_ctx cb_ctx; CURLcode result = CURLE_OK; int rc; if(!stream) return CURLE_OK; - DEBUGASSERT(stream3_id == stream->id); switch(quiche_h3_event_type(ev)) { case QUICHE_H3_EVENT_HEADERS: stream->resp_got_header = TRUE; @@ -514,11 +511,11 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, cb_ctx.data = data; rc = quiche_h3_event_for_each_header(ev, cb_each_header, &cb_ctx); if(rc) { - failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]", - rc, stream3_id); + failf(data, "Error %d in HTTP/3 response header for stream[%" + FMT_PRIu64"]", rc, stream->id); return CURLE_RECV_ERROR; } - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][HEADERS]", stream3_id)); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] <- [HEADERS]", stream->id); break; case QUICHE_H3_EVENT_DATA: @@ -528,7 +525,7 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_RESET: - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][RESET]", stream3_id)); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] RESET", stream->id); stream->closed = TRUE; stream->reset = TRUE; stream->send_closed = TRUE; @@ -536,7 +533,7 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_FINISHED: - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][FINISHED]", stream3_id)); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] CLOSED", stream->id); if(!stream->resp_hds_complete) { result = write_resp_raw(cf, data, "\r\n", 2); if(result) @@ -548,57 +545,91 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_GOAWAY: - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][GOAWAY]", stream3_id)); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] <- [GOAWAY]", stream->id); break; default: - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, unhandled event %d", - stream3_id, quiche_h3_event_type(ev))); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] recv, unhandled event %d", + stream->id, quiche_h3_event_type(ev)); break; } return result; } +static CURLcode cf_quiche_ev_process(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + quiche_h3_event *ev) +{ + CURLcode result = h3_process_event(cf, data, stream, ev); + h3_drain_stream(cf, data); + if(result) + CURL_TRC_CF(data, cf, "error processing event %s " + "for [%"FMT_PRIu64"] -> %d", cf_ev_name(ev), + stream->id, result); + return result; +} + +struct cf_quich_disp_ctx { + curl_uint64_t stream_id; + struct Curl_cfilter *cf; + struct Curl_multi *multi; + quiche_h3_event *ev; + CURLcode result; +}; + +static bool cf_quiche_disp_event(unsigned int mid, void *val, void *user_data) +{ + struct cf_quich_disp_ctx *dctx = user_data; + struct h3_stream_ctx *stream = val; + + if(stream->id == dctx->stream_id) { + struct Curl_easy *sdata = Curl_multi_get_easy(dctx->multi, mid); + if(sdata) + dctx->result = cf_quiche_ev_process(dctx->cf, sdata, stream, dctx->ev); + return FALSE; /* stop iterating */ + } + return TRUE; +} + static CURLcode cf_poll_events(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); - struct Curl_easy *sdata; + struct h3_stream_ctx *stream = NULL; quiche_h3_event *ev; - CURLcode result; /* Take in the events and distribute them to the transfers. */ while(ctx->h3c) { - int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); + curl_int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); if(stream3_id == QUICHE_H3_ERR_DONE) { break; } else if(stream3_id < 0) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] error poll: %"PRId64, - stream? stream->id : -1, stream3_id)); + CURL_TRC_CF(data, cf, "error poll: %"FMT_PRId64, stream3_id); return CURLE_HTTP3; } - - sdata = get_stream_easy(cf, data, stream3_id); - if(!sdata) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] discard event %s for " - "unknown [h3sid=%"PRId64"]", - stream? stream->id : -1, cf_ev_name(ev), - stream3_id)); - } else { - result = h3_process_event(cf, sdata, stream3_id, ev); - drain_stream(cf, sdata); - if(result) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] error processing event %s " - "for [h3sid=%"PRId64"] -> %d", - stream? stream->id : -1, cf_ev_name(ev), - stream3_id, result)); + struct cf_quich_disp_ctx dctx; + dctx.stream_id = (curl_uint64_t)stream3_id; + dctx.cf = cf; + dctx.multi = data->multi; + dctx.ev = ev; + dctx.result = CURLE_OK; + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == dctx.stream_id) { + /* event for calling transfer */ + CURLcode result = cf_quiche_ev_process(cf, data, stream, ev); + quiche_h3_event_free(ev); + if(result) + return result; + } + else { + /* another transfer, do not return errors, as they are not for + * the calling transfer */ + Curl_uint_hash_visit(&ctx->streams, cf_quiche_disp_event, &dctx); quiche_h3_event_free(ev); - return result; } - quiche_h3_event_free(ev); } } return CURLE_OK; @@ -628,15 +659,24 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, recv_info.from = (struct sockaddr *)remote_addr; recv_info.from_len = remote_addrlen; - nread = quiche_conn_recv(ctx->qconn, (unsigned char *)pkt, pktlen, + nread = quiche_conn_recv(ctx->qconn, + (unsigned char *)CURL_UNCONST(pkt), pktlen, &recv_info); if(nread < 0) { if(QUICHE_ERR_DONE == nread) { - DEBUGF(LOG_CF(r->data, r->cf, "ingress, quiche is DONE")); + if(quiche_conn_is_draining(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is draining"); + return CURLE_RECV_ERROR; + } + if(quiche_conn_is_closed(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is closed"); + return CURLE_RECV_ERROR; + } + CURL_TRC_CF(r->data, r->cf, "ingress, quiche is DONE"); return CURLE_OK; } else if(QUICHE_ERR_TLS_FAIL == nread) { - long verify_ok = SSL_get_verify_result(ctx->ssl); + long verify_ok = SSL_get_verify_result(ctx->tls.ossl.ssl); if(verify_ok != X509_V_OK) { failf(r->data, "SSL certificate problem: %s", X509_verify_cert_error_string(verify_ok)); @@ -649,8 +689,8 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, } } else if((size_t)nread < pktlen) { - DEBUGF(LOG_CF(r->data, r->cf, "ingress, quiche only read %zd/%zd bytes", - nread, pktlen)); + CURL_TRC_CF(r->data, r->cf, "ingress, quiche only read %zd/%zu bytes", + nread, pktlen); } return CURLE_OK; @@ -664,6 +704,10 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf, CURLcode result; DEBUGASSERT(ctx->qconn); + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; + rctx.cf = cf; rctx.data = data; rctx.pkts = 0; @@ -675,7 +719,8 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf, if(rctx.pkts > 0) { /* quiche digested ingress packets. It might have opened flow control * windows again. */ - check_resumes(cf, data); + DEBUGASSERT(data->multi); + cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_resume, NULL); } return cf_poll_events(cf, data); } @@ -719,10 +764,26 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, struct cf_quiche_ctx *ctx = cf->ctx; ssize_t nread; CURLcode result; - int64_t timeout_ns; + curl_int64_t expiry_ns; + curl_int64_t timeout_ns; struct read_ctx readx; size_t pkt_count, gsolen; + expiry_ns = quiche_conn_timeout_as_nanos(ctx->qconn); + if(!expiry_ns) { + quiche_conn_on_timeout(ctx->qconn); + if(quiche_conn_is_closed(ctx->qconn)) { + if(quiche_conn_is_timed_out(ctx->qconn)) + failf(data, "connection closed by idle timeout"); + else + failf(data, "connection closed by server"); + /* Connection timed out, expire all transfers belonging to it + * as will not get any more POLL events here. */ + cf_quiche_expire_conn_closed(cf, data); + return CURLE_SEND_ERROR; + } + } + result = vquic_flush(cf, data, &ctx->q); if(result) { if(result == CURLE_AGAIN) { @@ -741,9 +802,6 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, /* add the next packet to send, if any, to our buffer */ nread = Curl_bufq_sipn(&ctx->q.sendbuf, 0, read_pkt_to_send, &readx, &result); - /* DEBUGF(LOG_CF(data, cf, "sip packet(maxlen=%zu) -> %zd, %d", - (size_t)0, nread, result)); */ - if(nread < 0) { if(result != CURLE_AGAIN) return result; @@ -786,32 +844,31 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; DEBUGASSERT(stream); if(stream->reset) { failf(data, - "HTTP/3 stream %" PRId64 " reset by server", stream->id); - *err = stream->resp_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d", - stream->id, *err)); + "HTTP/3 stream %" FMT_PRIu64 " reset by server", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_recv, was reset -> %d", + stream->id, *err); } else if(!stream->resp_got_header) { failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", + "HTTP/3 stream %" FMT_PRIu64 " was closed cleanly, but before " + "getting all response header fields, treated as error", stream->id); /* *err = CURLE_PARTIAL_FILE; */ - *err = CURLE_RECV_ERROR; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete" - " -> %d", stream->id, *err)); + *err = CURLE_HTTP3; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_recv, closed incomplete" + " -> %d", stream->id, *err); } else { *err = CURLE_OK; nread = 0; - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok" - " -> %d", stream->id, *err)); } return nread; } @@ -820,26 +877,28 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; CURLcode result; + vquic_ctx_update_time(&ctx->q); + if(!stream) { *err = CURLE_RECV_ERROR; - goto out; + return -1; } if(!Curl_bufq_is_empty(&stream->recvbuf)) { nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read recvbuf(len=%zu) " - "-> %zd, %d", stream->id, len, nread, *err)); + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->id, len, nread, *err); if(nread < 0) goto out; } if(cf_process_ingress(cf, data)) { - DEBUGF(LOG_CF(data, cf, "cf_recv, error on ingress")); + CURL_TRC_CF(data, cf, "cf_recv, error on ingress"); *err = CURLE_RECV_ERROR; nread = -1; goto out; @@ -849,15 +908,15 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read recvbuf(len=%zu) " - "-> %zd, %d", stream->id, len, nread, *err)); + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->id, len, nread, *err); if(nread < 0) goto out; } if(nread > 0) { if(stream->closed) - drain_stream(cf, data); + h3_drain_stream(cf, data); } else { if(stream->closed) { @@ -877,31 +936,82 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, out: result = cf_flush_egress(cf, data); if(result) { - DEBUGF(LOG_CF(data, cf, "cf_recv, flush egress failed")); + CURL_TRC_CF(data, cf, "cf_recv, flush egress failed"); *err = result; nread = -1; } if(nread > 0) ctx->data_recvd += nread; - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] cf_recv(total=%zd) -> %zd, %d", - stream->id, ctx->data_recvd, nread, *err)); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] cf_recv(total=%" + FMT_OFF_T ") -> %zd, %d", + stream->id, ctx->data_recvd, nread, *err); return nread; } +static ssize_t cf_quiche_send_body(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const void *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + ssize_t nwritten; + + nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id, + (uint8_t *)CURL_UNCONST(buf), len, eos); + if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) { + /* Blocked on flow control and should HOLD sending. But when do we open + * again? */ + if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> window exhausted", stream->id, len); + stream->quic_flow_blocked = TRUE; + } + *err = CURLE_AGAIN; + return -1; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> invalid stream state", stream->id, len); + *err = CURLE_HTTP3; + return -1; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> exceeds size", stream->id, len); + *err = CURLE_SEND_ERROR; + return -1; + } + else if(nwritten < 0) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> quiche err %zd", stream->id, len, nwritten); + *err = CURLE_SEND_ERROR; + return -1; + } + else { + if(eos && (len == (size_t)nwritten)) + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send body(len=%zu, " + "eos=%d) -> %zd", + stream->id, len, stream->send_closed, nwritten); + *err = CURLE_OK; + return nwritten; + } +} + /* Index where :authority header field will appear in request header field list. */ #define AUTHORITY_DST_IDX 3 static ssize_t h3_open_stream(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, + const char *buf, size_t len, bool eos, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t nheader, i; - int64_t stream3_id; - struct h1_req_parser h1; + curl_int64_t stream3_id; struct dynhds h2_headers; quiche_h3_header *nva = NULL; ssize_t nwritten; @@ -909,28 +1019,31 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, if(!stream) { *err = h3_data_setup(cf, data); if(*err) { - nwritten = -1; - goto out; + return -1; } - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); } - Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); DEBUGASSERT(stream); - nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err); + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); if(nwritten < 0) goto out; - DEBUGASSERT(h1.done); - DEBUGASSERT(h1.req); + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); - *err = Curl_http_req_to_h2(&h2_headers, h1.req, data); + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); if(*err) { nwritten = -1; goto out; } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); nheader = Curl_dynhds_count(&h2_headers); nva = malloc(sizeof(quiche_h3_header) * nheader); @@ -948,23 +1061,7 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, nva[i].value_len = e->valuelen; } - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown */ - break; - default: - stream->upload_left = 0; /* no request body */ - break; - } - - if(stream->upload_left == 0) + if(eos && ((size_t)nwritten == len)) stream->send_closed = TRUE; stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader, @@ -973,105 +1070,107 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) { /* quiche seems to report this error if the connection window is * exhausted. Which happens frequently and intermittent. */ - DEBUGF(LOG_CF(data, cf, "send_request(%s) rejected with BLOCKED", - data->state.url)); - stream_send_suspend(cf, data); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] blocked", stream->id); + stream->quic_flow_blocked = TRUE; *err = CURLE_AGAIN; nwritten = -1; goto out; } else { - DEBUGF(LOG_CF(data, cf, "send_request(%s) -> %" PRId64, - data->state.url, stream3_id)); + CURL_TRC_CF(data, cf, "send_request(%s) -> %" FMT_PRIu64, + data->state.url, stream3_id); } *err = CURLE_SEND_ERROR; nwritten = -1; goto out; } - DEBUGASSERT(stream->id == -1); + DEBUGASSERT(!stream->opened); *err = CURLE_OK; stream->id = stream3_id; + stream->opened = TRUE; stream->closed = FALSE; stream->reset = FALSE; - infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)", - stream3_id, (void *)data); - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s", - stream3_id, data->state.url)); + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" FMT_PRIu64 "] OPENED stream for %s", + stream->id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" FMT_PRIu64 "] [%.*s: %.*s]", stream->id, + (int)nva[i].name_len, nva[i].name, + (int)nva[i].value_len, nva[i].value); + } + } + + if(nwritten > 0 && ((size_t)nwritten < len)) { + /* after the headers, there was request BODY data */ + size_t hds_len = (size_t)nwritten; + ssize_t bwritten; + + bwritten = cf_quiche_send_body(cf, data, stream, + buf + hds_len, len - hds_len, eos, err); + if((bwritten < 0) && (CURLE_AGAIN != *err)) { + /* real error, fail */ + nwritten = -1; + } + else if(bwritten > 0) { + nwritten += bwritten; + } + } out: free(nva); - Curl_h1_req_parse_free(&h1); Curl_dynhds_free(&h2_headers); return nwritten; } static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; ssize_t nwritten; + vquic_ctx_update_time(&ctx->q); + *err = cf_process_ingress(cf, data); if(*err) { nwritten = -1; goto out; } - if(!stream || stream->id < 0) { - nwritten = h3_open_stream(cf, data, buf, len, err); + if(!stream || !stream->opened) { + nwritten = h3_open_stream(cf, data, buf, len, eos, err); if(nwritten < 0) goto out; - stream = H3_STREAM_CTX(data); - } - else { - bool eof = (stream->upload_left >= 0 && - (curl_off_t)len >= stream->upload_left); - nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id, - (uint8_t *)buf, len, eof); - if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) { - /* TODO: we seem to be blocked on flow control and should HOLD - * sending. But when do we open again? */ - if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send_body(len=%zu) " - "-> window exhausted", stream->id, len)); - stream_send_suspend(cf, data); - } - *err = CURLE_AGAIN; - nwritten = -1; - goto out; - } - else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send_body(len=%zu) " - "-> exceeds size", stream->id, len)); - *err = CURLE_SEND_ERROR; - nwritten = -1; - goto out; - } - else if(nwritten < 0) { - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send_body(len=%zu) " - "-> quiche err %zd", stream->id, len, nwritten)); - *err = CURLE_SEND_ERROR; - nwritten = -1; - goto out; - } - else { - /* quiche accepted all or at least a part of the buf */ - if(stream->upload_left > 0) { - stream->upload_left = (nwritten < stream->upload_left)? - (stream->upload_left - nwritten) : 0; - } - if(stream->upload_left == 0) - stream->send_closed = TRUE; - - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send body(len=%zu, " - "left=%zd) -> %zd", - stream->id, len, stream->upload_left, nwritten)); + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* sending request body on a stream that has been closed by the + * server. If the server has send us a final response, we should + * silently discard the send data. + * This happens for example on redirects where the server, instead + * of reading the full request body just closed the stream after + * sending the 30x response. + * This is sort of a race: had the transfer loop called recv first, + * it would see the response and stop/discard sending on its own- */ + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] discarding data" + "on closed stream with response", stream->id); *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; } + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + else { + nwritten = cf_quiche_send_body(cf, data, stream, buf, len, eos, err); } out: @@ -1080,8 +1179,8 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, *err = result; nwritten = -1; } - DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send(len=%zu) -> %zd, %d", - stream? stream->id : -1, len, nwritten, *err)); + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->id : (curl_uint64_t)~0, len, nwritten, *err); return nwritten; } @@ -1089,32 +1188,37 @@ static bool stream_is_writeable(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - return stream && - quiche_conn_stream_writable(ctx->qconn, (uint64_t)stream->id, 1); + return stream && (quiche_conn_stream_writable( + ctx->qconn, (curl_uint64_t)stream->id, 1) > 0); } -static int cf_quiche_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { struct cf_quiche_ctx *ctx = cf->ctx; - struct SingleRequest *k = &data->req; - int rv = GETSOCK_BLANK; + bool want_recv, want_send; - socks[0] = ctx->q.sockfd; + if(!ctx->qconn) + return; - /* in an HTTP/3 connection we can basically always get a frame so we should - always be ready for one */ - rv |= GETSOCK_READSOCK(0); + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + bool c_exhaust, s_exhaust; - /* we're still uploading or the HTTP/3 layer wants to send data */ - if(((k->keepon & KEEP_SENDBITS) == KEEP_SEND) - && stream_is_writeable(cf, data)) - rv |= GETSOCK_WRITESOCK(0); + c_exhaust = FALSE; /* Have not found any call in quiche that tells + us if the connection itself is blocked */ + s_exhaust = want_send && stream && stream->opened && + (stream->quic_flow_blocked || !stream_is_writeable(cf, data)); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); - return rv; + Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + } } /* @@ -1124,7 +1228,8 @@ static int cf_quiche_get_select_socks(struct Curl_cfilter *cf, static bool cf_quiche_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - const struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; return stream && !Curl_bufq_is_empty(&stream->recvbuf); } @@ -1133,10 +1238,10 @@ static CURLcode h3_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) { - /* TODO: there seems right now no API in quiche to shrink/enlarge - * the streams windows. As we do in HTTP/2. */ + /* There seems to exist no API in quiche to shrink/enlarge the streams + * windows. As we do in HTTP/2. */ if(!pause) { - drain_stream(cf, data); + h3_drain_stream(cf, data); Curl_expire(data, 0, EXPIRE_RUN_NOW); } return CURLE_OK; @@ -1146,124 +1251,62 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2) { + struct cf_quiche_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; (void)arg1; (void)arg2; switch(event) { - case CF_CTRL_DATA_SETUP: { - result = h3_data_setup(cf, data); + case CF_CTRL_DATA_SETUP: break; - } case CF_CTRL_DATA_PAUSE: result = h3_data_pause(cf, data, (arg1 != 0)); break; - case CF_CTRL_DATA_DONE: { + case CF_CTRL_DATA_DONE: h3_data_done(cf, data); break; - } case CF_CTRL_DATA_DONE_SEND: { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { unsigned char body[1]; ssize_t sent; stream->send_closed = TRUE; - stream->upload_left = 0; body[0] = 'X'; - sent = cf_quiche_send(cf, data, body, 0, &result); - DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] DONE_SEND -> %zd, %d", - stream->id, sent, result)); + sent = cf_quiche_send(cf, data, body, 0, TRUE, &result); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] DONE_SEND -> %zd, %d", + stream->id, sent, result); } break; } - case CF_CTRL_DATA_IDLE: - result = cf_flush_egress(cf, data); - if(result) - DEBUGF(LOG_CF(data, cf, "data idle, flush egress -> %d", result)); + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + if(stream && !stream->closed) { + result = cf_flush_egress(cf, data); + if(result) + CURL_TRC_CF(data, cf, "data idle, flush egress -> %d", result); + } break; + } default: break; } return result; } -static CURLcode cf_verify_peer(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_quiche_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - cf->conn->httpversion = 30; - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - - if(cf->conn->ssl_config.verifyhost) { - X509 *server_cert; - server_cert = SSL_get_peer_certificate(ctx->ssl); - if(!server_cert) { - result = CURLE_PEER_FAILED_VERIFICATION; - goto out; - } - result = Curl_ossl_verifyhost(data, cf->conn, server_cert); - X509_free(server_cert); - if(result) - goto out; - } - else - DEBUGF(LOG_CF(data, cf, "Skipped certificate verification")); - - ctx->h3config = quiche_h3_config_new(); - if(!ctx->h3config) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - /* Create a new HTTP/3 connection on the QUIC connection. */ - ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); - if(!ctx->h3c) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); - -out: - if(result) { - if(ctx->h3config) { - quiche_h3_config_free(ctx->h3config); - ctx->h3config = NULL; - } - if(ctx->h3c) { - quiche_h3_conn_free(ctx->h3c); - ctx->h3c = NULL; - } - } - return result; -} - -static CURLcode cf_connect_start(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; int rv; CURLcode result; const struct Curl_sockaddr_ex *sockaddr; +static const struct alpn_spec ALPN_SPEC_H3 = { + { "h3" }, 1 +}; DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD); - -#ifdef DEBUG_QUICHE - /* initialize debug log callback only once */ - static int debug_log_init = 0; - if(!debug_log_init) { - quiche_enable_debug_logging(quiche_debug_log, NULL); - debug_log_init = 1; - } -#endif - Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, - H3_STREAM_POOL_SPARES); - ctx->data_recvd = 0; + DEBUGASSERT(ctx->initialized); result = vquic_ctx_init(&ctx->q); if(result) @@ -1271,11 +1314,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); if(!ctx->cfg) { - failf(data, "can't create quiche config"); + failf(data, "cannot create quiche config"); return CURLE_FAILED_INIT; } - quiche_config_enable_pacing(ctx->cfg, false); - quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT); + quiche_config_enable_pacing(ctx->cfg, FALSE); quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024) /* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */); quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS); @@ -1292,29 +1334,20 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, 10 * QUIC_MAX_STREAMS * H3_STREAM_WINDOW_SIZE); quiche_config_set_max_stream_window(ctx->cfg, 10 * H3_STREAM_WINDOW_SIZE); quiche_config_set_application_protos(ctx->cfg, - (uint8_t *) - QUICHE_H3_APPLICATION_PROTOCOL, + (uint8_t *)CURL_UNCONST(QUICHE_H3_APPLICATION_PROTOCOL), sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - DEBUGASSERT(!ctx->ssl); - DEBUGASSERT(!ctx->sslctx); - ctx->sslctx = quic_ssl_ctx(data); - if(!ctx->sslctx) - return CURLE_QUIC_CONNECT_ERROR; - ctx->ssl = SSL_new(ctx->sslctx); - if(!ctx->ssl) - return CURLE_QUIC_CONNECT_ERROR; - - SSL_set_app_data(ctx->ssl, cf); - SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name); + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + &ALPN_SPEC_H3, NULL, NULL, cf, NULL); + if(result) + return result; result = Curl_rand(data, ctx->scid, sizeof(ctx->scid)); if(result) return result; - Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, - &sockaddr, NULL, NULL, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL); ctx->q.local_addrlen = sizeof(ctx->q.local_addr); rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, &ctx->q.local_addrlen); @@ -1322,18 +1355,19 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, return CURLE_QUIC_CONNECT_ERROR; ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid, - sizeof(ctx->scid), NULL, 0, - (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen, - &sockaddr->sa_addr, sockaddr->addrlen, - ctx->cfg, ctx->ssl, false); + sizeof(ctx->scid), NULL, 0, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen, + &sockaddr->curl_sa_addr, + sockaddr->addrlen, + ctx->cfg, ctx->tls.ossl.ssl, FALSE); if(!ctx->qconn) { - failf(data, "can't create quiche connection"); + failf(data, "cannot create quiche connection"); return CURLE_OUT_OF_MEMORY; } /* Known to not work on Windows */ -#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) +#if !defined(_WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) { int qfd; (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd); @@ -1343,11 +1377,6 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, } #endif - /* we do not get a setup event for the initial transfer */ - result = h3_data_setup(cf, data); - if(result) - return result; - result = cf_flush_egress(cf, data); if(result) return result; @@ -1363,20 +1392,29 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, offset += 1 + alpn_len; } - DEBUGF(LOG_CF(data, cf, "Sent QUIC client Initial, ALPN: %s", - alpn_protocols + 1)); + CURL_TRC_CF(data, cf, "Sent QUIC client Initial, ALPN: %s", + alpn_protocols + 1); } return CURLE_OK; } +static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_quiche_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - struct curltime now; if(cf->connected) { *done = TRUE; @@ -1385,25 +1423,19 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, /* Connect the UDP filter first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } *done = FALSE; - now = Curl_now(); - - if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) { - /* Not time yet to attempt the next connect */ - DEBUGF(LOG_CF(data, cf, "waiting for reconnect time")); - goto out; - } + vquic_ctx_update_time(&ctx->q); if(!ctx->qconn) { - result = cf_connect_start(cf, data); + result = cf_quiche_ctx_open(cf, data); if(result) goto out; - ctx->started_at = now; + ctx->started_at = ctx->q.last_op; result = cf_flush_egress(cf, data); /* we do not expect to be able to recv anything yet */ goto out; @@ -1418,12 +1450,24 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, goto out; if(quiche_conn_is_established(ctx->qconn)) { - DEBUGF(LOG_CF(data, cf, "handshake complete after %dms", - (int)Curl_timediff(now, ctx->started_at))); - ctx->handshake_at = now; - result = cf_verify_peer(cf, data); + ctx->handshake_at = ctx->q.last_op; + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)curlx_timediff(ctx->handshake_at, ctx->started_at)); + result = cf_quiche_verify_peer(cf, data); if(!result) { - DEBUGF(LOG_CF(data, cf, "peer verified")); + CURL_TRC_CF(data, cf, "peer verified"); + ctx->h3config = quiche_h3_config_new(); + if(!ctx->h3config) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + /* Create a new HTTP/3 connection on the QUIC connection. */ + ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); + if(!ctx->h3c) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } cf->connected = TRUE; cf->conn->alpn = CURL_HTTP_VERSION_3; *done = TRUE; @@ -1433,67 +1477,87 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, else if(quiche_conn_is_draining(ctx->qconn)) { /* When a QUIC server instance is shutting down, it may send us a * CONNECTION_CLOSE right away. Our connection then enters the DRAINING - * state. - * This may be a stopping of the service or it may be that the server - * is reloading and a new instance will start serving soon. - * In any case, we tear down our socket and start over with a new one. - * We re-open the underlying UDP cf right now, but do not start - * connecting until called again. - */ - int reconn_delay_ms = 200; - - DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms", - reconn_delay_ms)); - Curl_conn_cf_close(cf->next, data); - cf_quiche_ctx_clear(ctx); - result = Curl_conn_cf_connect(cf->next, data, FALSE, done); - if(!result && *done) { - *done = FALSE; - ctx->reconnect_at = Curl_now(); - ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000; - Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC); - result = CURLE_OK; - } + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; } out: #ifndef CURL_DISABLE_VERBOSE_STRINGS if(result && result != CURLE_AGAIN) { - const char *r_ip; - int r_port; + struct ip_quadruple ip; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); infof(data, "connect to %s port %u failed: %s", - r_ip, r_port, curl_easy_strerror(result)); + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); } #endif return result; } -static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_quiche_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->shutdown || !ctx || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } - if(ctx) { - if(ctx->qconn) { - (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); - /* flushing the egress is not a failsafe way to deliver all the - outstanding packets, but we also don't want to get stuck here... */ - (void)cf_flush_egress(cf, data); + *done = FALSE; + if(!ctx->shutdown_started) { + int err; + + ctx->shutdown_started = TRUE; + vquic_ctx_update_time(&ctx->q); + err = quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); + if(err) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packet, " + "aborting shutdown", err); + result = CURLE_SEND_ERROR; + goto out; } - cf_quiche_ctx_clear(ctx); + } + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = cf_flush_egress(cf, data); + if(result) + goto out; + } + + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* sent everything, quiche does not seem to support a graceful + * shutdown waiting for a reply, so ware done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + } + else { + CURL_TRC_CF(data, cf, "shutdown sending blocked"); + } + +out: + return result; +} + +static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + if(cf->ctx) { + bool done; + (void)cf_quiche_shutdown(cf, data, &done); + cf_quiche_ctx_close(cf->ctx); } } static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct cf_quiche_ctx *ctx = cf->ctx; - (void)data; - cf_quiche_ctx_clear(ctx); - free(ctx); - cf->ctx = NULL; + if(cf->ctx) { + cf_quiche_ctx_free(cf->ctx); + cf->ctx = NULL; + } } static CURLcode cf_quiche_query(struct Curl_cfilter *cf, @@ -1504,26 +1568,28 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - uint64_t max_streams = CONN_INUSE(cf->conn); + curl_uint64_t max_streams = CONN_ATTACHED(cf->conn); if(!ctx->goaway) { max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn); } - *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams; - DEBUGF(LOG_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1)); + *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; + CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " + "MAX_CONCURRENT -> %d (%u in use)", + cf->conn->connection_id, *pres1, CONN_ATTACHED(cf->conn)); return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->got_first_byte) { - timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); - *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + if(ctx->q.got_first_byte) { + timediff_t ms = curlx_timediff(ctx->q.first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else *pres1 = -1; return CURLE_OK; case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { @@ -1532,10 +1598,13 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, *when = ctx->handshake_at; return CURLE_OK; } + case CF_QUERY_HTTP_VERSION: + *pres1 = 30; + return CURLE_OK; default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1544,24 +1613,34 @@ static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { + struct cf_quiche_ctx *ctx = cf->ctx; bool alive = TRUE; *input_pending = FALSE; + if(!ctx->qconn) + return FALSE; + + if(quiche_conn_is_closed(ctx->qconn)) { + if(quiche_conn_is_timed_out(ctx->qconn)) + CURL_TRC_CF(data, cf, "connection was closed due to idle timeout"); + else + CURL_TRC_CF(data, cf, "connection is closed"); + return FALSE; + } + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) return FALSE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ *input_pending = FALSE; - Curl_attach_connection(data, cf->conn); if(cf_process_ingress(cf, data)) alive = FALSE; else { alive = TRUE; } - Curl_detach_connection(data); } return alive; @@ -1569,13 +1648,14 @@ static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_http3 = { "HTTP/3", - CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, 0, cf_quiche_destroy, cf_quiche_connect, cf_quiche_close, + cf_quiche_shutdown, Curl_cf_def_get_host, - cf_quiche_get_select_socks, + cf_quiche_adjust_pollset, cf_quiche_data_pending, cf_quiche_send, cf_quiche_recv, @@ -1596,11 +1676,12 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, (void)data; (void)conn; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } + cf_quiche_ctx_init(ctx); result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) @@ -1615,12 +1696,12 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, cf->next = udp_cf; out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { if(udp_cf) Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); Curl_safefree(cf); - Curl_safefree(ctx); + cf_quiche_ctx_free(ctx); } return result; @@ -1630,7 +1711,7 @@ bool Curl_conn_is_quiche(const struct Curl_easy *data, const struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; (void)data; for(; cf; cf = cf->next) { diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.h b/Utilities/cmcurl/lib/vquic/curl_quiche.h index bce781c1bcc..9832687bdc1 100644 --- a/Utilities/cmcurl/lib/vquic/curl_quiche.h +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_QUICHE diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.c b/Utilities/cmcurl/lib/vquic/vquic-tls.c new file mode 100644 index 00000000000..2a5be138fc6 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/vquic-tls.c @@ -0,0 +1,200 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#if defined(USE_HTTP3) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +#ifdef USE_OPENSSL +#include +#include "../vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include +#include +#include +#include +#include "../vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#include +#include +#include "../vtls/wolfssl.h" +#endif + +#include "../urldata.h" +#include "../curl_trc.h" +#include "../cfilters.h" +#include "../multiif.h" +#include "../vtls/keylog.h" +#include "../vtls/vtls.h" +#include "../vtls/vtls_scache.h" +#include "vquic-tls.h" + +/* The last 3 #include files should be in this order */ +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" + +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, void *ssl_user_data, + Curl_vquic_session_reuse_cb *session_reuse_cb) +{ + char tls_id[80]; + CURLcode result; + +#ifdef USE_OPENSSL + Curl_ossl_version(tls_id, sizeof(tls_id)); +#elif defined(USE_GNUTLS) + Curl_gtls_version(tls_id, sizeof(tls_id)); +#elif defined(USE_WOLFSSL) + Curl_wssl_version(tls_id, sizeof(tls_id)); +#else +#error "no TLS lib in used, should not happen" + return CURLE_FAILED_INIT; +#endif + (void)session_reuse_cb; + result = Curl_ssl_peer_init(peer, cf, tls_id, TRNSPRT_QUIC); + if(result) + return result; + +#ifdef USE_OPENSSL + (void)result; + return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, alpns, + cb_setup, cb_user_data, NULL, ssl_user_data, + session_reuse_cb); +#elif defined(USE_GNUTLS) + return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, alpns, + cb_setup, cb_user_data, ssl_user_data, + session_reuse_cb); +#elif defined(USE_WOLFSSL) + return Curl_wssl_ctx_init(&ctx->wssl, cf, data, peer, alpns, + cb_setup, cb_user_data, + ssl_user_data, session_reuse_cb); +#else +#error "no TLS lib in used, should not happen" + return CURLE_FAILED_INIT; +#endif +} + +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx) +{ +#ifdef USE_OPENSSL + if(ctx->ossl.ssl) + SSL_free(ctx->ossl.ssl); + if(ctx->ossl.ssl_ctx) + SSL_CTX_free(ctx->ossl.ssl_ctx); +#elif defined(USE_GNUTLS) + if(ctx->gtls.session) + gnutls_deinit(ctx->gtls.session); + Curl_gtls_shared_creds_free(&ctx->gtls.shared_creds); +#elif defined(USE_WOLFSSL) + if(ctx->wssl.ssl) + wolfSSL_free(ctx->wssl.ssl); + if(ctx->wssl.ssl_ctx) + wolfSSL_CTX_free(ctx->wssl.ssl_ctx); +#endif + memset(ctx, 0, sizeof(*ctx)); +} + +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ +#ifdef USE_OPENSSL + if(!ctx->ossl.x509_store_setup) { + CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ossl.ssl_ctx); + if(result) + return result; + ctx->ossl.x509_store_setup = TRUE; + } +#elif defined(USE_WOLFSSL) + if(!ctx->wssl.x509_store_setup) { + CURLcode result = Curl_wssl_setup_x509_store(cf, data, &ctx->wssl); + if(result) + return result; + } +#elif defined(USE_GNUTLS) + if(!ctx->gtls.shared_creds->trust_setup) { + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &ctx->gtls); + if(result) + return result; + } +#else + (void)ctx; (void)cf; (void)data; +#endif + return CURLE_OK; +} + +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer) +{ + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_OK; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + +#ifdef USE_OPENSSL + (void)conn_config; + result = Curl_oss_check_peer_cert(cf, data, &ctx->ossl, peer); +#elif defined(USE_GNUTLS) + if(conn_config->verifyhost) { + result = Curl_gtls_verifyserver(data, ctx->gtls.session, + conn_config, &data->set.ssl, peer, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) + return result; + } +#elif defined(USE_WOLFSSL) + (void)data; + if(conn_config->verifyhost) { + char *snihost = peer->sni ? peer->sni : peer->hostname; + WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.ssl); + if(wolfSSL_X509_check_host(cert, snihost, strlen(snihost), 0, NULL) + == WOLFSSL_FAILURE) { + result = CURLE_PEER_FAILED_VERIFICATION; + } + wolfSSL_X509_free(cert); + } + if(!result) + result = Curl_wssl_verify_pinned(cf, data, &ctx->wssl); +#endif + /* on error, remove any session we might have in the pool */ + if(result) + Curl_ssl_scache_remove_all(cf, data, peer->scache_key); + return result; +} + + +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.h b/Utilities/cmcurl/lib/vquic/vquic-tls.h new file mode 100644 index 00000000000..bf29eecc916 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/vquic-tls.h @@ -0,0 +1,111 @@ +#ifndef HEADER_CURL_VQUIC_TLS_H +#define HEADER_CURL_VQUIC_TLS_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" +#include "../bufq.h" +#include "../vtls/vtls.h" +#include "../vtls/vtls_int.h" +#include "../vtls/openssl.h" + +#if defined(USE_HTTP3) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +#include "../vtls/wolfssl.h" + +struct ssl_peer; +struct Curl_ssl_session; + +struct curl_tls_ctx { +#ifdef USE_OPENSSL + struct ossl_ctx ossl; +#elif defined(USE_GNUTLS) + struct gtls_ctx gtls; +#elif defined(USE_WOLFSSL) + struct wssl_ctx wssl; +#endif +}; + +/** + * Callback passed to `Curl_vquic_tls_init()` that can + * do early initializations on the not otherwise configured TLS + * instances created. This varies by TLS backend: + * - openssl/wolfssl: SSL_CTX* has just been created + * - gnutls: gtls_client_init() has run + */ +typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *cb_user_data); + +typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data); + +/** + * Initialize the QUIC TLS instances based of the SSL configurations + * for the connection filter, transfer and peer. + * @param ctx the TLS context to initialize + * @param cf the connection filter involved + * @param data the transfer involved + * @param peer the peer that will be connected to + * @param alpns the ALPN specifications to negotiate, may be NULL + * @param cb_setup optional callback for early TLS config + * @param cb_user_data user_data param for callback + * @param ssl_user_data optional pointer to set in TLS application context + * @param session_reuse_cb callback to handle session reuse, signal early data + */ +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, + void *ssl_user_data, + Curl_vquic_session_reuse_cb *session_reuse_cb); + +/** + * Cleanup all data that has been initialized. + */ +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx); + +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * After the QUIC basic handshake has been, verify that the peer + * (and its certificate) fulfill our requirements. + */ +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer); + +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ + +#endif /* HEADER_CURL_VQUIC_TLS_H */ diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c index f850029a622..b5dc44f8aa9 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.c +++ b/Utilities/cmcurl/lib/vquic/vquic.c @@ -22,56 +22,58 @@ * ***************************************************************************/ -/* WIP, experimental: use recvmmsg() on linux - * we have no configure check, yet - * and also it is only available for _GNU_SOURCE, which - * we do not use otherwise. -#define HAVE_SENDMMSG - */ -#if defined(HAVE_SENDMMSG) -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#endif - -#include "curl_setup.h" +#include "../curl_setup.h" +#ifdef HAVE_NETINET_UDP_H +#include +#endif #ifdef HAVE_FCNTL_H #include #endif -#include "urldata.h" -#include "bufq.h" -#include "dynbuf.h" -#include "cfilters.h" -#include "curl_log.h" +#include "../urldata.h" +#include "../bufq.h" +#include "../curlx/dynbuf.h" +#include "../cfilters.h" +#include "../curl_trc.h" #include "curl_msh3.h" #include "curl_ngtcp2.h" +#include "curl_osslq.h" #include "curl_quiche.h" +#include "../multiif.h" +#include "../rand.h" #include "vquic.h" #include "vquic_int.h" +#include "../strerror.h" +#include "../curlx/strparse.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" -#ifdef ENABLE_QUIC - -#ifdef O_BINARY -#define QLOGMODE O_WRONLY|O_CREAT|O_BINARY -#else -#define QLOGMODE O_WRONLY|O_CREAT -#endif +#ifdef USE_HTTP3 #define NW_CHUNK_SIZE (64 * 1024) #define NW_SEND_CHUNKS 2 +int Curl_vquic_init(void) +{ +#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2) + if(ngtcp2_crypto_ossl_init()) + return 0; +#endif + + return 1; +} + void Curl_quic_ver(char *p, size_t len) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) Curl_ngtcp2_ver(p, len); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + Curl_osslq_ver(p, len); #elif defined(USE_QUICHE) Curl_quiche_ver(p, len); #elif defined(USE_MSH3) @@ -88,6 +90,17 @@ CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx) #else qctx->no_gso = TRUE; #endif +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_DBG_QUIC_WBLOCK"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) + qctx->wblock_percent = (int)l; + } + } +#endif + vquic_ctx_update_time(qctx); return CURLE_OK; } @@ -97,6 +110,11 @@ void vquic_ctx_free(struct cf_quic_ctx *qctx) Curl_bufq_free(&qctx->sendbuf); } +void vquic_ctx_update_time(struct cf_quic_ctx *qctx) +{ + qctx->last_op = curlx_now(); +} + static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, @@ -119,7 +137,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, #endif *psent = 0; - msg_iov.iov_base = (uint8_t *)pkt; + msg_iov.iov_base = (uint8_t *)CURL_UNCONST(pkt); msg_iov.iov_len = pktlen; msg.msg_iov = &msg_iov; msg.msg_iovlen = 1; @@ -129,8 +147,8 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, /* Only set this, when we need it. macOS, for example, * does not seem to like a msg_control of length 0. */ msg.msg_control = msg_ctrl; - assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); - msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int))); + msg.msg_controllen = CMSG_SPACE(sizeof(int)); cm = CMSG_FIRSTHDR(&msg); cm->cmsg_level = SOL_UDP; cm->cmsg_type = UDP_SEGMENT; @@ -140,28 +158,29 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, #endif - while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) + while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && + SOCKERRNO == SOCKEINTR) ; if(sent == -1) { switch(SOCKERRNO) { case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: +#if EAGAIN != SOCKEWOULDBLOCK + case SOCKEWOULDBLOCK: #endif return CURLE_AGAIN; - case EMSGSIZE: + case SOCKEMSGSIZE: /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ break; case EIO: if(pktlen > gsolen) { /* GSO failure */ - failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, + infof(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, SOCKERRNO); qctx->no_gso = TRUE; return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); } - /* FALLTHROUGH */ + FALLTHROUGH(); default: failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); return CURLE_SEND_ERROR; @@ -178,16 +197,16 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, while((sent = send(qctx->sockfd, (const char *)pkt, (SEND_TYPE_ARG3)pktlen, 0)) == -1 && - SOCKERRNO == EINTR) + SOCKERRNO == SOCKEINTR) ; if(sent == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { return CURLE_AGAIN; } else { failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); - if(SOCKERRNO != EMSGSIZE) { + if(SOCKERRNO != SOCKEMSGSIZE) { return CURLE_SEND_ERROR; } /* UDP datagram is too large; caused by PMTUD. Just let it be @@ -230,11 +249,28 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf, const uint8_t *pkt, size_t pktlen, size_t gsolen, size_t *psent) { + CURLcode result; +#ifdef DEBUGBUILD + /* simulate network blocking/partial writes */ + if(qctx->wblock_percent > 0) { + unsigned char c; + *psent = 0; + Curl_rand(data, &c, 1); + if(c >= ((100-qctx->wblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "vquic_flush() simulate EWOULDBLOCK"); + return CURLE_AGAIN; + } + } +#endif if(qctx->no_gso && pktlen > gsolen) { - return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); + result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); } - - return do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); + else { + result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); + } + if(!result) + qctx->last_io = qctx->last_op; + return result; } CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -253,11 +289,9 @@ CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, blen = qctx->split_len; } - DEBUGF(LOG_CF(data, cf, "vquic_send(len=%zu, gso=%zu)", - blen, gsolen)); result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent); - DEBUGF(LOG_CF(data, cf, "vquic_send(len=%zu, gso=%zu) -> %d, sent=%zu", - blen, gsolen, result, sent)); + CURL_TRC_CF(data, cf, "vquic_send(len=%zu, gso=%zu) -> %d, sent=%zu", + blen, gsolen, result, sent); if(result) { if(result == CURLE_AGAIN) { Curl_bufq_skip(&qctx->sendbuf, sent); @@ -288,12 +322,42 @@ CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len; qctx->split_gsolen = gsolen; qctx->gsolen = tail_gsolen; - DEBUGF(LOG_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]", - qctx->split_len, qctx->split_gsolen, - tail_len, qctx->gsolen)); + CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]", + qctx->split_len, qctx->split_gsolen, + tail_len, qctx->gsolen); return vquic_flush(cf, data, qctx); } +#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) +static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) +{ + int gso_size = 0; +#if defined(__linux__) && defined(UDP_GRO) + struct cmsghdr *cmsg; + + /* Workaround musl CMSG_NXTHDR issue */ +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wcast-align" +#endif + for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic pop +#endif + if(cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) { + memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size)); + + break; + } + } +#endif + (void)msg; + + return (size_t)gso_size; +} +#endif + #ifdef HAVE_SENDMMSG static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -301,20 +365,31 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, size_t max_pkts, vquic_recv_pkt_cb *recv_cb, void *userp) { -#define MMSG_NUM 64 +#define MMSG_NUM 16 struct iovec msg_iov[MMSG_NUM]; struct mmsghdr mmsg[MMSG_NUM]; - uint8_t bufs[MMSG_NUM][2*1024]; + uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))]; struct sockaddr_storage remote_addr[MMSG_NUM]; - size_t total_nread, pkts; + size_t total_nread = 0, pkts = 0; int mcount, i, n; + char errstr[STRERROR_LEN]; CURLcode result = CURLE_OK; + size_t gso_size; + size_t pktlen; + size_t offset, to; + char *sockbuf = NULL; + uint8_t (*bufs)[64*1024] = NULL; DEBUGASSERT(max_pkts > 0); - pkts = 0; + result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * sizeof(bufs[0]), + &sockbuf); + if(result) + goto out; + bufs = (uint8_t (*)[64*1024])sockbuf; + total_nread = 0; while(pkts < max_pkts) { - n = (int)CURLMIN(MMSG_NUM, max_pkts); + n = (int)CURLMIN(CURLMIN(MMSG_NUM, IOV_MAX), max_pkts); memset(&mmsg, 0, sizeof(mmsg)); for(i = 0; i < n; ++i) { msg_iov[i].iov_base = bufs[i]; @@ -323,47 +398,66 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, mmsg[i].msg_hdr.msg_iovlen = 1; mmsg[i].msg_hdr.msg_name = &remote_addr[i]; mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]); + mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))]; + mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(int)); } while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && - SOCKERRNO == EINTR) + SOCKERRNO == SOCKEINTR) ; if(mcount == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { - DEBUGF(LOG_CF(data, cf, "ingress, recvmmsg -> EAGAIN")); + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { + CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN"); goto out; } - if(!cf->connected && SOCKERRNO == ECONNREFUSED) { - const char *r_ip; - int r_port; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", - r_ip, r_port); + ip.remote_ip, ip.remote_port); result = CURLE_COULDNT_CONNECT; goto out; } - failf(data, "QUIC: recvmsg() unexpectedly returned %d (errno=%d)", - mcount, SOCKERRNO); + Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvmsg() unexpectedly returned %d (errno=%d; %s)", + mcount, SOCKERRNO, errstr); result = CURLE_RECV_ERROR; goto out; } - DEBUGF(LOG_CF(data, cf, "recvmmsg() -> %d packets", mcount)); - pkts += mcount; + CURL_TRC_CF(data, cf, "recvmmsg() -> %d packets", mcount); for(i = 0; i < mcount; ++i) { total_nread += mmsg[i].msg_len; - result = recv_cb(bufs[i], mmsg[i].msg_len, - mmsg[i].msg_hdr.msg_name, mmsg[i].msg_hdr.msg_namelen, - 0, userp); - if(result) - goto out; + + gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr); + if(gso_size == 0) { + gso_size = mmsg[i].msg_len; + } + + for(offset = 0; offset < mmsg[i].msg_len; offset = to) { + ++pkts; + + to = offset + gso_size; + if(to > mmsg[i].msg_len) { + pktlen = mmsg[i].msg_len - offset; + } + else { + pktlen = gso_size; + } + + result = recv_cb(bufs[i] + offset, pktlen, mmsg[i].msg_hdr.msg_name, + mmsg[i].msg_hdr.msg_namelen, 0, userp); + if(result) + goto out; + } } } out: - DEBUGF(LOG_CF(data, cf, "recvd %zu packets with %zd bytes -> %d", - pkts, total_nread, result)); + if(total_nread || result) + CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", + pkts, total_nread, result); + Curl_multi_xfer_sockbuf_release(data, sockbuf); return result; } @@ -380,53 +474,78 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, struct sockaddr_storage remote_addr; size_t total_nread, pkts; ssize_t nread; + char errstr[STRERROR_LEN]; CURLcode result = CURLE_OK; - - msg_iov.iov_base = buf; - msg_iov.iov_len = (int)sizeof(buf); - - memset(&msg, 0, sizeof(msg)); - msg.msg_iov = &msg_iov; - msg.msg_iovlen = 1; + uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))]; + size_t gso_size; + size_t pktlen; + size_t offset, to; DEBUGASSERT(max_pkts > 0); for(pkts = 0, total_nread = 0; pkts < max_pkts;) { + /* fully initialise this on each call to `recvmsg()`. There seem to + * operating systems out there that mess with `msg_iov.iov_len`. */ + memset(&msg, 0, sizeof(msg)); + msg_iov.iov_base = buf; + msg_iov.iov_len = (int)sizeof(buf); + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + msg.msg_control = msg_ctrl; msg.msg_name = &remote_addr; msg.msg_namelen = sizeof(remote_addr); + msg.msg_controllen = sizeof(msg_ctrl); + while((nread = recvmsg(qctx->sockfd, &msg, 0)) == -1 && - SOCKERRNO == EINTR) + SOCKERRNO == SOCKEINTR) ; if(nread == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { goto out; } - if(!cf->connected && SOCKERRNO == ECONNREFUSED) { - const char *r_ip; - int r_port; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", - r_ip, r_port); + ip.remote_ip, ip.remote_port); result = CURLE_COULDNT_CONNECT; goto out; } - failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d)", - nread, SOCKERRNO); + Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)", + nread, SOCKERRNO, errstr); result = CURLE_RECV_ERROR; goto out; } - ++pkts; total_nread += (size_t)nread; - result = recv_cb(buf, (size_t)nread, msg.msg_name, msg.msg_namelen, - 0, userp); - if(result) - goto out; + + gso_size = vquic_msghdr_get_udp_gro(&msg); + if(gso_size == 0) { + gso_size = (size_t)nread; + } + + for(offset = 0; offset < (size_t)nread; offset = to) { + ++pkts; + + to = offset + gso_size; + if(to > (size_t)nread) { + pktlen = (size_t)nread - offset; + } + else { + pktlen = gso_size; + } + + result = + recv_cb(buf + offset, pktlen, msg.msg_name, msg.msg_namelen, 0, userp); + if(result) + goto out; + } } out: - DEBUGF(LOG_CF(data, cf, "recvd %zu packets with %zd bytes -> %d", - pkts, total_nread, result)); + if(total_nread || result) + CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", + pkts, total_nread, result); return result; } @@ -443,6 +562,7 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, socklen_t remote_addrlen = sizeof(remote_addr); size_t total_nread, pkts; ssize_t nread; + char errstr[STRERROR_LEN]; CURLcode result = CURLE_OK; DEBUGASSERT(max_pkts > 0); @@ -450,25 +570,24 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, while((nread = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0, (struct sockaddr *)&remote_addr, &remote_addrlen)) == -1 && - SOCKERRNO == EINTR) + SOCKERRNO == SOCKEINTR) ; if(nread == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { - DEBUGF(LOG_CF(data, cf, "ingress, recvfrom -> EAGAIN")); + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { + CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN"); goto out; } - if(!cf->connected && SOCKERRNO == ECONNREFUSED) { - const char *r_ip; - int r_port; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", - r_ip, r_port); + ip.remote_ip, ip.remote_port); result = CURLE_COULDNT_CONNECT; goto out; } - failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d)", - nread, SOCKERRNO); + Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)", + nread, SOCKERRNO, errstr); result = CURLE_RECV_ERROR; goto out; } @@ -482,8 +601,9 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, } out: - DEBUGF(LOG_CF(data, cf, "recvd %zu packets with %zd bytes -> %d", - pkts, total_nread, result)); + if(total_nread || result) + CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", + pkts, total_nread, result); return result; } #endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */ @@ -494,13 +614,22 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf, size_t max_pkts, vquic_recv_pkt_cb *recv_cb, void *userp) { + CURLcode result; #if defined(HAVE_SENDMMSG) - return recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); #elif defined(HAVE_SENDMSG) - return recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); #else - return recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); #endif + if(!result) { + if(!qctx->got_first_byte) { + qctx->got_first_byte = TRUE; + qctx->first_byte_at = qctx->last_op; + } + qctx->last_io = qctx->last_op; + } + return result; } /* @@ -522,25 +651,25 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, struct dynbuf fname; CURLcode result; unsigned int i; - Curl_dyn_init(&fname, DYN_QLOG_NAME); - result = Curl_dyn_add(&fname, qlog_dir); + curlx_dyn_init(&fname, DYN_QLOG_NAME); + result = curlx_dyn_add(&fname, qlog_dir); if(!result) - result = Curl_dyn_add(&fname, "/"); + result = curlx_dyn_add(&fname, "/"); for(i = 0; (i < scidlen) && !result; i++) { char hex[3]; msnprintf(hex, 3, "%02x", scid[i]); - result = Curl_dyn_add(&fname, hex); + result = curlx_dyn_add(&fname, hex); } if(!result) - result = Curl_dyn_add(&fname, ".sqlog"); + result = curlx_dyn_add(&fname, ".sqlog"); if(!result) { - int qlogfd = open(Curl_dyn_ptr(&fname), QLOGMODE, + int qlogfd = open(curlx_dyn_ptr(&fname), O_WRONLY|O_CREAT|CURL_O_BINARY, data->set.new_file_perms); if(qlogfd != -1) *qlogfdp = qlogfd; } - Curl_dyn_free(&fname); + curlx_dyn_free(&fname); if(result) return result; } @@ -558,6 +687,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, DEBUGASSERT(transport == TRNSPRT_QUIC); #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) return Curl_cf_ngtcp2_create(pcf, data, conn, ai); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + return Curl_cf_osslq_create(pcf, data, conn, ai); #elif defined(USE_QUICHE) return Curl_cf_quiche_create(pcf, data, conn, ai); #elif defined(USE_MSH3) @@ -571,27 +702,11 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, #endif } -bool Curl_conn_is_http3(const struct Curl_easy *data, - const struct connectdata *conn, - int sockindex) -{ -#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_conn_is_ngtcp2(data, conn, sockindex); -#elif defined(USE_QUICHE) - return Curl_conn_is_quiche(data, conn, sockindex); -#elif defined(USE_MSH3) - return Curl_conn_is_msh3(data, conn, sockindex); -#else - return ((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (conn->httpversion == 30)); -#endif -} - CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn) { if(conn->transport == TRNSPRT_UNIX) { - /* cannot do QUIC over a unix domain socket */ + /* cannot do QUIC over a Unix domain socket */ return CURLE_QUIC_CONNECT_ERROR; } if(!(conn->handler->flags & PROTOPT_SSL)) { @@ -604,7 +719,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_URL_MALFORMAT; } if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { - failf(data, "HTTP/3 is not supported over a HTTP proxy"); + failf(data, "HTTP/3 is not supported over an HTTP proxy"); return CURLE_URL_MALFORMAT; } #endif @@ -612,7 +727,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_OK; } -#else /* ENABLE_QUIC */ +#else /* USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn) @@ -623,4 +738,4 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_NOT_BUILT_IN; } -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h index dc73957aaf6..dbf63b1f6fe 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.h +++ b/Utilities/cmcurl/lib/vquic/vquic.h @@ -24,15 +24,16 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 struct Curl_cfilter; struct Curl_easy; struct connectdata; struct Curl_addrinfo; void Curl_quic_ver(char *p, size_t len); +int Curl_vquic_init(void); CURLcode Curl_qlogdir(struct Curl_easy *data, unsigned char *scid, @@ -46,17 +47,11 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, const struct Curl_addrinfo *ai, int transport); -bool Curl_conn_is_http3(const struct Curl_easy *data, - const struct connectdata *conn, - int sockindex); - extern struct Curl_cftype Curl_cft_http3; -#else /* ENABLE_QUIC */ - -#define Curl_conn_is_http3(a,b,c) FALSE - -#endif /* !ENABLE_QUIC */ +#else +#define Curl_vquic_init() 1 +#endif /* !USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn); diff --git a/Utilities/cmcurl/lib/vquic/vquic_int.h b/Utilities/cmcurl/lib/vquic/vquic_int.h index 8e08784e7d9..4641c3125b0 100644 --- a/Utilities/cmcurl/lib/vquic/vquic_int.h +++ b/Utilities/cmcurl/lib/vquic/vquic_int.h @@ -24,10 +24,10 @@ * ***************************************************************************/ -#include "curl_setup.h" -#include "bufq.h" +#include "../curl_setup.h" +#include "../bufq.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 #define MAX_PKT_BURST 10 #define MAX_UDP_PAYLOAD_SIZE 1452 @@ -38,15 +38,27 @@ struct cf_quic_ctx { socklen_t local_addrlen; /* length of local address */ struct bufq sendbuf; /* buffer for sending one or more packets */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct curltime last_op; /* last (attempted) send/recv operation */ + struct curltime last_io; /* last successful socket IO */ size_t gsolen; /* length of individual packets in send buf */ size_t split_len; /* if != 0, buffer length after which GSO differs */ size_t split_gsolen; /* length of individual packets after split_len */ - bool no_gso; /* do not use gso on sending */ +#ifdef DEBUGBUILD + int wblock_percent; /* percent of writes doing EAGAIN */ +#endif + BIT(got_first_byte); /* if first byte was received */ + BIT(no_gso); /* do not use gso on sending */ }; +#define H3_STREAM_CTX(ctx,data) \ + (data ? Curl_uint_hash_get(&(ctx)->streams, (data)->mid) : NULL) + CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx); void vquic_ctx_free(struct cf_quic_ctx *qctx); +void vquic_ctx_update_time(struct cf_quic_ctx *qctx); + void vquic_push_blocked_pkt(struct Curl_cfilter *cf, struct cf_quic_ctx *qctx, const uint8_t *pkt, size_t pktlen, size_t gsolen); @@ -77,6 +89,6 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf, size_t max_pkts, vquic_recv_pkt_cb *recv_cb, void *userp); -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ #endif /* HEADER_CURL_VQUIC_QUIC_INT_H */ diff --git a/Utilities/cmcurl/lib/curl_path.c b/Utilities/cmcurl/lib/vssh/curl_path.c similarity index 75% rename from Utilities/cmcurl/lib/curl_path.c rename to Utilities/cmcurl/lib/vssh/curl_path.c index 856423db997..117d2e6009f 100644 --- a/Utilities/cmcurl/lib/curl_path.c +++ b/Utilities/cmcurl/lib/vssh/curl_path.c @@ -22,15 +22,15 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_SSH) -#include -#include "curl_memory.h" #include "curl_path.h" -#include "escape.h" -#include "memdebug.h" +#include +#include "../curl_memory.h" +#include "../escape.h" +#include "../memdebug.h" #define MAX_SSHPATH_LEN 100000 /* arbitrary */ @@ -50,13 +50,13 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, return result; /* new path to switch to in case we need to */ - Curl_dyn_init(&npath, MAX_SSHPATH_LEN); + curlx_dyn_init(&npath, MAX_SSHPATH_LEN); /* Check for /~/, indicating relative to the user's home directory */ if((data->conn->handler->protocol & CURLPROTO_SCP) && (working_path_len > 3) && (!memcmp(working_path, "/~/", 3))) { /* It is referenced to the home directory, so strip the leading '/~/' */ - if(Curl_dyn_addn(&npath, &working_path[3], working_path_len - 3)) { + if(curlx_dyn_addn(&npath, &working_path[3], working_path_len - 3)) { free(working_path); return CURLE_OUT_OF_MEMORY; } @@ -64,7 +64,7 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, else if((data->conn->handler->protocol & CURLPROTO_SFTP) && (!strcmp("/~", working_path) || ((working_path_len > 2) && !memcmp(working_path, "/~/", 3)))) { - if(Curl_dyn_add(&npath, homedir)) { + if(curlx_dyn_add(&npath, homedir)) { free(working_path); return CURLE_OUT_OF_MEMORY; } @@ -73,24 +73,24 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, const char *p; int copyfrom = 3; /* Copy a separating '/' if homedir does not end with one */ - len = Curl_dyn_len(&npath); - p = Curl_dyn_ptr(&npath); + len = curlx_dyn_len(&npath); + p = curlx_dyn_ptr(&npath); if(len && (p[len-1] != '/')) copyfrom = 2; - if(Curl_dyn_addn(&npath, - &working_path[copyfrom], working_path_len - copyfrom)) { + if(curlx_dyn_addn(&npath, &working_path[copyfrom], + working_path_len - copyfrom)) { free(working_path); return CURLE_OUT_OF_MEMORY; } } } - if(Curl_dyn_len(&npath)) { + if(curlx_dyn_len(&npath)) { free(working_path); /* store the pointer for the caller to receive */ - *path = Curl_dyn_ptr(&npath); + *path = curlx_dyn_ptr(&npath); } else *path = working_path; @@ -98,8 +98,8 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, return CURLE_OK; } -/* The get_pathname() function is being borrowed from OpenSSH sftp.c - version 4.6p1. */ +/* The original get_pathname() function came from OpenSSH sftp.c version + 4.6p1. */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -115,38 +115,37 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir) + +#define MAX_PATHLENGTH 65535 /* arbitrary long */ + +CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir) { const char *cp = *cpp, *end; char quot; - unsigned int i, j; - size_t fullPathLength, pathLength; - bool relativePath = false; + unsigned int i; static const char WHITESPACE[] = " \t\r\n"; + struct dynbuf out; + CURLcode result; DEBUGASSERT(homedir); - if(!*cp || !homedir) { - *cpp = NULL; - *path = NULL; + *path = NULL; + *cpp = NULL; + if(!*cp || !homedir) return CURLE_QUOTE_ERROR; - } + + curlx_dyn_init(&out, MAX_PATHLENGTH); + /* Ignore leading whitespace */ cp += strspn(cp, WHITESPACE); - /* Allocate enough space for home directory and filename + separator */ - fullPathLength = strlen(cp) + strlen(homedir) + 2; - *path = malloc(fullPathLength); - if(!*path) - return CURLE_OUT_OF_MEMORY; /* Check for quoted filenames */ if(*cp == '\"' || *cp == '\'') { quot = *cp++; /* Search for terminating quote, unescape some chars */ - for(i = j = 0; i <= strlen(cp); i++) { + for(i = 0; i <= strlen(cp); i++) { if(cp[i] == quot) { /* Found quote */ i++; - (*path)[j] = '\0'; break; } if(cp[i] == '\0') { /* End of string */ @@ -159,40 +158,45 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir) goto fail; } } - (*path)[j++] = cp[i]; + result = curlx_dyn_addn(&out, &cp[i], 1); + if(result) + return result; } - if(j == 0) { + if(!curlx_dyn_len(&out)) goto fail; - } - *cpp = cp + i + strspn(cp + i, WHITESPACE); + + /* return pointer to second parameter if it exists */ + *cpp = &cp[i] + strspn(&cp[i], WHITESPACE); } else { /* Read to end of filename - either to whitespace or terminator */ end = strpbrk(cp, WHITESPACE); if(!end) end = strchr(cp, '\0'); + /* return pointer to second parameter if it exists */ *cpp = end + strspn(end, WHITESPACE); - pathLength = 0; - relativePath = (cp[0] == '/' && cp[1] == '~' && cp[2] == '/'); + /* Handling for relative path - prepend home directory */ - if(relativePath) { - strcpy(*path, homedir); - pathLength = strlen(homedir); - (*path)[pathLength++] = '/'; - (*path)[pathLength] = '\0'; + if(cp[0] == '/' && cp[1] == '~' && cp[2] == '/') { + result = curlx_dyn_add(&out, homedir); + if(!result) + result = curlx_dyn_addn(&out, "/", 1); + if(result) + return result; cp += 3; } /* Copy path name up until first "whitespace" */ - memcpy(&(*path)[pathLength], cp, (int)(end - cp)); - pathLength += (int)(end - cp); - (*path)[pathLength] = '\0'; + result = curlx_dyn_addn(&out, cp, (end - cp)); + if(result) + return result; } + *path = curlx_dyn_ptr(&out); return CURLE_OK; fail: - Curl_safefree(*path); + curlx_dyn_free(&out); return CURLE_QUOTE_ERROR; } diff --git a/Utilities/cmcurl/lib/curl_path.h b/Utilities/cmcurl/lib/vssh/curl_path.h similarity index 76% rename from Utilities/cmcurl/lib/curl_path.h rename to Utilities/cmcurl/lib/vssh/curl_path.h index 9ed09dea835..1c167f96603 100644 --- a/Utilities/cmcurl/lib/curl_path.h +++ b/Utilities/cmcurl/lib/vssh/curl_path.h @@ -24,26 +24,13 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include -#include "urldata.h" - -#ifdef WIN32 -# undef PATH_MAX -# define PATH_MAX MAX_PATH -# ifndef R_OK -# define R_OK 4 -# endif -#endif - -#ifndef PATH_MAX -#define PATH_MAX 1024 /* just an extra precaution since there are systems that - have their definition hidden well */ -#endif +#include "../urldata.h" CURLcode Curl_getworkingpath(struct Curl_easy *data, char *homedir, char **path); -CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir); +CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir); #endif /* HEADER_CURL_PATH_H */ diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c index 1cecb649cb6..86f1e1a568b 100644 --- a/Utilities/cmcurl/lib/vssh/libssh.c +++ b/Utilities/cmcurl/lib/vssh/libssh.c @@ -25,24 +25,18 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_LIBSSH #include -#include -#include - #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -52,29 +46,29 @@ #endif #include -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "progress.h" -#include "transfer.h" -#include "escape.h" -#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "../urldata.h" +#include "../sendf.h" +#include "../hostip.h" +#include "../progress.h" +#include "../transfer.h" +#include "../escape.h" +#include "../http.h" /* for HTTP proxy tunnel stuff */ #include "ssh.h" -#include "url.h" -#include "speedcheck.h" -#include "getinfo.h" -#include "strdup.h" -#include "strcase.h" -#include "vtls/vtls.h" -#include "cfilters.h" -#include "connect.h" -#include "inet_ntop.h" -#include "parsedate.h" /* for the week day and month names */ -#include "sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "strtoofft.h" -#include "multiif.h" -#include "select.h" -#include "warnless.h" +#include "../url.h" +#include "../speedcheck.h" +#include "../getinfo.h" +#include "../strdup.h" +#include "../strcase.h" +#include "../vtls/vtls.h" +#include "../cfilters.h" +#include "../connect.h" +#include "../inet_ntop.h" +#include "../parsedate.h" /* for the week day and month names */ +#include "../sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "../curlx/strparse.h" +#include "../multiif.h" +#include "../select.h" +#include "../curlx/warnless.h" #include "curl_path.h" #ifdef HAVE_SYS_STAT_H @@ -88,16 +82,9 @@ #endif /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* in 0.10.0 or later, ignore deprecated warnings */ -#if defined(__GNUC__) && \ - (LIBSSH_VERSION_MINOR >= 10) || \ - (LIBSSH_VERSION_MAJOR > 0) -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* A recent macro provided by libssh. Or make our own. */ #ifndef SSH_STRING_FREE_CHAR @@ -143,13 +130,19 @@ CURLcode sftp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done); -static void sftp_quote(struct Curl_easy *data); -static void sftp_quote_stat(struct Curl_easy *data); +static void sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp); +static void sftp_quote_stat(struct Curl_easy *data, struct ssh_conn *sshc); static int myssh_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *sock); +static void myssh_block2waitfor(struct connectdata *conn, + struct ssh_conn *sshc, + bool block); static CURLcode myssh_setup_connection(struct Curl_easy *data, struct connectdata *conn); +static void sshc_cleanup(struct ssh_conn *sshc); /* * SCP protocol handler. @@ -169,9 +162,11 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ myssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SCP, /* protocol */ CURLPROTO_SCP, /* family */ @@ -196,9 +191,11 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ myssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SFTP, /* protocol */ CURLPROTO_SFTP, /* family */ @@ -231,23 +228,23 @@ static CURLcode sftp_error_to_CURLE(int err) } #ifndef DEBUGBUILD -#define state(x,y) mystate(x,y) +#define myssh_state(x,y,z) myssh_set_state(x,y,z) #else -#define state(x,y) mystate(x,y, __LINE__) +#define myssh_state(x,y,z) myssh_set_state(x,y,z, __LINE__) #endif /* * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ -static void mystate(struct Curl_easy *data, sshstate nowstate +static void myssh_set_state(struct Curl_easy *data, + struct ssh_conn *sshc, + sshstate nowstate #ifdef DEBUGBUILD - , int lineno + , int lineno #endif ) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char *const names[] = { @@ -320,7 +317,7 @@ static void mystate(struct Curl_easy *data, sshstate nowstate lineno); } #endif - + (void)data; sshc->state = nowstate; } @@ -334,11 +331,9 @@ static void mystate(struct Curl_easy *data, sshstate nowstate * * Returns SSH_OK or SSH_ERROR. */ -static int myssh_is_known(struct Curl_easy *data) +static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) { int rc; - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; ssh_key pubkey; size_t hlen; unsigned char *hash = NULL; @@ -350,17 +345,11 @@ static int myssh_is_known(struct Curl_easy *data) struct curl_khkey *knownkeyp = NULL; curl_sshkeycallback func = data->set.ssh_keyfunc; - -#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) struct ssh_knownhosts_entry *knownhostsentry = NULL; struct curl_khkey knownkey; -#endif -#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,8,0) rc = ssh_get_server_publickey(sshc->ssh_session, &pubkey); -#else - rc = ssh_get_publickey(sshc->ssh_session, &pubkey); -#endif + if(rc != SSH_OK) return rc; @@ -394,28 +383,24 @@ static int myssh_is_known(struct Curl_easy *data) goto cleanup; } - if(data->set.ssl.primary.verifyhost != TRUE) { - rc = SSH_OK; - goto cleanup; - } + if(data->set.str[STRING_SSH_KNOWNHOSTS]) { -#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) - /* Get the known_key from the known hosts file */ - vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, - &knownhostsentry); + /* Get the known_key from the known hosts file */ + vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, + &knownhostsentry); - /* Case an entry was found in a known hosts file */ - if(knownhostsentry) { - if(knownhostsentry->publickey) { - rc = ssh_pki_export_pubkey_base64(knownhostsentry->publickey, - &known_base64); - if(rc != SSH_OK) { - goto cleanup; - } - knownkey.key = known_base64; - knownkey.len = strlen(known_base64); + /* Case an entry was found in a known hosts file */ + if(knownhostsentry) { + if(knownhostsentry->publickey) { + rc = ssh_pki_export_pubkey_base64(knownhostsentry->publickey, + &known_base64); + if(rc != SSH_OK) { + goto cleanup; + } + knownkey.key = known_base64; + knownkey.len = strlen(known_base64); - switch(ssh_key_type(knownhostsentry->publickey)) { + switch(ssh_key_type(knownhostsentry->publickey)) { case SSH_KEYTYPE_RSA: knownkey.keytype = CURLKHTYPE_RSA; break; @@ -437,55 +422,35 @@ static int myssh_is_known(struct Curl_easy *data) default: rc = SSH_ERROR; goto cleanup; + } + knownkeyp = &knownkey; } - knownkeyp = &knownkey; } - } - switch(vstate) { + switch(vstate) { case SSH_KNOWN_HOSTS_OK: keymatch = CURLKHMATCH_OK; break; case SSH_KNOWN_HOSTS_OTHER: - /* fallthrough */ case SSH_KNOWN_HOSTS_NOT_FOUND: - /* fallthrough */ case SSH_KNOWN_HOSTS_UNKNOWN: - /* fallthrough */ case SSH_KNOWN_HOSTS_ERROR: keymatch = CURLKHMATCH_MISSING; break; - default: - keymatch = CURLKHMATCH_MISMATCH; - break; - } - -#else - vstate = ssh_is_server_known(sshc->ssh_session); - switch(vstate) { - case SSH_SERVER_KNOWN_OK: - keymatch = CURLKHMATCH_OK; - break; - case SSH_SERVER_FILE_NOT_FOUND: - /* fallthrough */ - case SSH_SERVER_NOT_KNOWN: - keymatch = CURLKHMATCH_MISSING; - break; - default: + default: keymatch = CURLKHMATCH_MISMATCH; break; - } -#endif + } - if(func) { /* use callback to determine action */ - rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); - if(rc != SSH_OK) - goto cleanup; + if(func) { /* use callback to determine action */ + rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); + if(rc != SSH_OK) + goto cleanup; - foundkey.key = found_base64; - foundkey.len = strlen(found_base64); + foundkey.key = found_base64; + foundkey.len = strlen(found_base64); - switch(ssh_key_type(pubkey)) { + switch(ssh_key_type(pubkey)) { case SSH_KEYTYPE_RSA: foundkey.keytype = CURLKHTYPE_RSA; break; @@ -493,39 +458,31 @@ static int myssh_is_known(struct Curl_easy *data) foundkey.keytype = CURLKHTYPE_RSA1; break; case SSH_KEYTYPE_ECDSA: -#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: -#endif foundkey.keytype = CURLKHTYPE_ECDSA; break; -#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,7,0) case SSH_KEYTYPE_ED25519: foundkey.keytype = CURLKHTYPE_ED25519; break; -#endif case SSH_KEYTYPE_DSS: foundkey.keytype = CURLKHTYPE_DSS; break; default: rc = SSH_ERROR; goto cleanup; - } + } - Curl_set_in_callback(data, true); - rc = func(data, knownkeyp, /* from the knownhosts file */ - &foundkey, /* from the remote host */ - keymatch, data->set.ssh_keyfunc_userp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, TRUE); + rc = func(data, knownkeyp, /* from the knownhosts file */ + &foundkey, /* from the remote host */ + keymatch, data->set.ssh_keyfunc_userp); + Curl_set_in_callback(data, FALSE); - switch(rc) { + switch(rc) { case CURLKHSTAT_FINE_ADD_TO_FILE: -#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,8,0) rc = ssh_session_update_known_hosts(sshc->ssh_session); -#else - rc = ssh_write_knownhost(sshc->ssh_session); -#endif if(rc != SSH_OK) { goto cleanup; } @@ -535,12 +492,13 @@ static int myssh_is_known(struct Curl_easy *data) default: /* REJECT/DEFER */ rc = SSH_ERROR; goto cleanup; + } } - } - else { - if(keymatch != CURLKHMATCH_OK) { - rc = SSH_ERROR; - goto cleanup; + else { + if(keymatch != CURLKHMATCH_OK) { + rc = SSH_ERROR; + goto cleanup; + } } } rc = SSH_OK; @@ -555,22 +513,20 @@ static int myssh_is_known(struct Curl_easy *data) if(hash) ssh_clean_pubkey_hash(&hash); ssh_key_free(pubkey); -#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) if(knownhostsentry) { ssh_knownhosts_entry_free(knownhostsentry); } -#endif return rc; } -#define MOVE_TO_ERROR_STATE(_r) do { \ - state(data, SSH_SESSION_DISCONNECT); \ - sshc->actualcode = _r; \ - rc = SSH_ERROR; \ +#define MOVE_TO_ERROR_STATE(_r) do { \ + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); \ + sshc->actualcode = _r; \ + rc = SSH_ERROR; \ } while(0) #define MOVE_TO_SFTP_CLOSE_STATE() do { \ - state(data, SSH_SFTP_CLOSE); \ + myssh_state(data, sshc, SSH_SFTP_CLOSE); \ sshc->actualcode = \ sftp_error_to_CURLE(sftp_get_error(sshc->sftp_session)); \ rc = SSH_ERROR; \ @@ -579,7 +535,7 @@ static int myssh_is_known(struct Curl_easy *data) #define MOVE_TO_PASSWD_AUTH do { \ if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { \ rc = SSH_OK; \ - state(data, SSH_AUTH_PASS_INIT); \ + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); \ } \ else { \ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); \ @@ -589,7 +545,7 @@ static int myssh_is_known(struct Curl_easy *data) #define MOVE_TO_KEY_AUTH do { \ if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { \ rc = SSH_OK; \ - state(data, SSH_AUTH_KEY_INIT); \ + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); \ } \ else { \ MOVE_TO_PASSWD_AUTH; \ @@ -599,7 +555,7 @@ static int myssh_is_known(struct Curl_easy *data) #define MOVE_TO_GSSAPI_AUTH do { \ if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { \ rc = SSH_OK; \ - state(data, SSH_AUTH_GSSAPI); \ + myssh_state(data, sshc, SSH_AUTH_GSSAPI); \ } \ else { \ MOVE_TO_KEY_AUTH; \ @@ -607,10 +563,10 @@ static int myssh_is_known(struct Curl_easy *data) } while(0) static -int myssh_auth_interactive(struct connectdata *conn) +int myssh_auth_interactive(struct connectdata *conn, + struct ssh_conn *sshc) { int rc; - struct ssh_conn *sshc = &conn->proto.sshc; int nprompts; restart: @@ -631,7 +587,7 @@ int myssh_auth_interactive(struct connectdata *conn) if(rc < 0) return SSH_ERROR; - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: sshc->kbd_state = 1; @@ -671,173 +627,493 @@ int myssh_auth_interactive(struct connectdata *conn) return rc; } -/* - * ssh_statemach_act() runs the SSH state machine as far as it can without - * blocking and without reaching the end. The data the pointer 'block' points - * to will be set to TRUE if the libssh function returns SSH_AGAIN - * meaning it wants to be called again when the socket is ready - */ -static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) +static void myssh_state_init(struct Curl_easy *data, + struct ssh_conn *sshc) { - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct SSHPROTO *protop = data->req.p.ssh; - struct ssh_conn *sshc = &conn->proto.sshc; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int rc = SSH_NO_ERROR, err; - int seekerr = CURL_SEEKFUNC_OK; - const char *err_msg; - *block = 0; /* we're not blocking by default */ - - do { - - switch(sshc->state) { - case SSH_INIT: - sshc->secondCreateDirs = 0; - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_OK; + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_OK; #if 0 - ssh_set_log_level(SSH_LOG_PROTOCOL); + ssh_set_log_level(SSH_LOG_PROTOCOL); #endif - /* Set libssh to non-blocking, since everything internally is - non-blocking */ - ssh_set_blocking(sshc->ssh_session, 0); + /* Set libssh to non-blocking, since everything internally is + non-blocking */ + ssh_set_blocking(sshc->ssh_session, 0); - state(data, SSH_S_STARTUP); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_S_STARTUP); +} - case SSH_S_STARTUP: - rc = ssh_connect(sshc->ssh_session); - if(rc == SSH_AGAIN) - break; +static int myssh_state_startup(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + int rc = ssh_connect(sshc->ssh_session); - if(rc != SSH_OK) { - failf(data, "Failure establishing ssh session"); - MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT); - break; - } + myssh_block2waitfor(conn, sshc, (rc == SSH_AGAIN)); + if(rc == SSH_AGAIN) { + DEBUGF(infof(data, "ssh_connect -> EAGAIN")); + } + else if(rc != SSH_OK) { + failf(data, "Failure establishing ssh session"); + MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT); + } + else + myssh_state(data, sshc, SSH_HOSTKEY); - state(data, SSH_HOSTKEY); + return rc; +} - /* FALLTHROUGH */ - case SSH_HOSTKEY: +static int myssh_state_authlist(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc; + sshc->authed = FALSE; + + rc = ssh_userauth_none(sshc->ssh_session, NULL); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + + if(rc == SSH_AUTH_SUCCESS) { + sshc->authed = TRUE; + infof(data, "Authenticated with none"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return rc; + } + else if(rc == SSH_AUTH_ERROR) { + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + return rc; + } + + sshc->auth_methods = + (unsigned int)ssh_userauth_list(sshc->ssh_session, NULL); + if(sshc->auth_methods) + infof(data, "SSH authentication methods available: %s%s%s%s", + sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY ? + "public key, ": "", + sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC ? + "GSSAPI, " : "", + sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE ? + "keyboard-interactive, " : "", + sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD ? + "password": ""); + if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + myssh_state(data, sshc, SSH_AUTH_PKEY_INIT); + infof(data, "Authentication using SSH public key file"); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { + myssh_state(data, sshc, SSH_AUTH_GSSAPI); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); + } + else { /* unsupported authentication method */ + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + } + return rc; +} + +static int myssh_state_auth_pkey_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc; + if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) { + MOVE_TO_GSSAPI_AUTH; + return 0; + } + + /* Two choices, (1) private key was given on CMD, + * (2) use the "default" keys. */ + if(data->set.str[STRING_SSH_PRIVATE_KEY]) { + if(sshc->pubkey && !data->set.ssl.key_passwd) { + rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, + sshc->pubkey); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; - rc = myssh_is_known(data); if(rc != SSH_OK) { - MOVE_TO_ERROR_STATE(CURLE_PEER_FAILED_VERIFICATION); - break; + MOVE_TO_GSSAPI_AUTH; + return rc; } + } - state(data, SSH_AUTHLIST); - /* FALLTHROUGH */ - case SSH_AUTHLIST:{ - sshc->authed = FALSE; + rc = ssh_pki_import_privkey_file(data-> + set.str[STRING_SSH_PRIVATE_KEY], + data->set.ssl.key_passwd, NULL, + NULL, &sshc->privkey); + if(rc != SSH_OK) { + failf(data, "Could not load private key file %s", + data->set.str[STRING_SSH_PRIVATE_KEY]); + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + return rc; + } - rc = ssh_userauth_none(sshc->ssh_session, NULL); - if(rc == SSH_AUTH_AGAIN) { - rc = SSH_AGAIN; - break; - } + myssh_state(data, sshc, SSH_AUTH_PKEY); + } + else { + rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, + data->set.ssl.key_passwd); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + + if(rc == SSH_AUTH_SUCCESS) { + rc = SSH_OK; + sshc->authed = TRUE; + infof(data, "Completed public key authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return rc; + } - if(rc == SSH_AUTH_SUCCESS) { - sshc->authed = TRUE; - infof(data, "Authenticated with none"); - state(data, SSH_AUTH_DONE); - break; - } - else if(rc == SSH_AUTH_ERROR) { - MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); - break; - } + MOVE_TO_GSSAPI_AUTH; + } + return rc; +} - sshc->auth_methods = ssh_userauth_list(sshc->ssh_session, NULL); - if(sshc->auth_methods) - infof(data, "SSH authentication methods available: %s%s%s%s", - sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY ? - "public key, ": "", - sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC ? - "GSSAPI, " : "", - sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE ? - "keyboard-interactive, " : "", - sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD ? - "password": ""); - if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { - state(data, SSH_AUTH_PKEY_INIT); - infof(data, "Authentication using SSH public key file"); - } - else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { - state(data, SSH_AUTH_GSSAPI); - } - else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { - state(data, SSH_AUTH_KEY_INIT); - } - else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { - state(data, SSH_AUTH_PASS_INIT); - } - else { /* unsupported authentication method */ - MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); - break; +static int myssh_state_upload_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + int flags; + int rc = 0; + + if(data->state.resume_from) { + sftp_attributes attrs; + + if(data->state.resume_from < 0) { + attrs = sftp_stat(sshc->sftp_session, sshp->path); + if(attrs) { + curl_off_t size = attrs->size; + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; } + data->state.resume_from = attrs->size; - break; + sftp_attributes_free(attrs); } - case SSH_AUTH_PKEY_INIT: - if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) { - MOVE_TO_GSSAPI_AUTH; - break; + else { + data->state.resume_from = 0; } + } + } - /* Two choices, (1) private key was given on CMD, - * (2) use the "default" keys. */ - if(data->set.str[STRING_SSH_PRIVATE_KEY]) { - if(sshc->pubkey && !data->set.ssl.key_passwd) { - rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, - sshc->pubkey); - if(rc == SSH_AUTH_AGAIN) { - rc = SSH_AGAIN; - break; - } + if(data->set.remote_append) + /* Try to open for append, but create if nonexisting */ + flags = O_WRONLY|O_CREAT|O_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = O_WRONLY|O_APPEND; + else + /* Clear file before writing (normal behavior) */ + flags = O_WRONLY|O_CREAT|O_TRUNC; + + if(sshc->sftp_file) + sftp_close(sshc->sftp_file); + sshc->sftp_file = + sftp_open(sshc->sftp_session, sshp->path, + flags, (mode_t)data->set.new_file_perms); + if(!sshc->sftp_file) { + int err = sftp_get_error(sshc->sftp_session); + + if(((err == SSH_FX_NO_SUCH_FILE || err == SSH_FX_FAILURE || + err == SSH_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(sshp->path) > 1))) { + /* try to create the path remotely */ + rc = 0; + sshc->secondCreateDirs = 1; + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_INIT); + return rc; + } + else { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } - if(rc != SSH_OK) { - MOVE_TO_GSSAPI_AUTH; - break; - } - } + /* If we have a restart point then we need to seek to the correct + position. */ + if(data->state.resume_from > 0) { + int seekerr = CURL_SEEKFUNC_OK; + /* Let's read off the proper amount of bytes from the input. */ + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); + } - rc = ssh_pki_import_privkey_file(data-> - set.str[STRING_SSH_PRIVATE_KEY], - data->set.ssl.key_passwd, NULL, - NULL, &sshc->privkey); - if(rc != SSH_OK) { - failf(data, "Could not load private key file %s", - data->set.str[STRING_SSH_PRIVATE_KEY]); - MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); - break; + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); + return rc; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->state.fread_func(scratch, 1, + readthisamountnow, data->state.in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); + return rc; } + } while(passed < data->state.resume_from); + } - state(data, SSH_AUTH_PKEY); - break; + /* now, decrease the size of the read */ + if(data->state.infilesize > 0) { + data->state.infilesize -= data->state.resume_from; + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } + if(data->state.infilesize > 0) { + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + /* upload data */ + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->sockfd = data->conn->writesockfd; + + /* store this original bitmask setup to use later on if we cannot + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh sftp send function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_OUT; + + /* since we do not really wait for anything at this point, we want the + state machine to move on as soon as possible so we set a very short + timeout here */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); +#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0) + sshc->sftp_send_state = 0; +#endif + myssh_state(data, sshc, SSH_STOP); + return rc; +} + +static int myssh_state_sftp_download_stat(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + curl_off_t size; + int rc = 0; + sftp_attributes attrs = sftp_fstat(sshc->sftp_file); + if(!attrs || + !(attrs->flags & SSH_FILEXFER_ATTR_SIZE) || + (attrs->size == 0)) { + /* + * sftp_fstat did not return an error, so maybe the server + * just does not support stat() + * OR the server does not return a file size with a stat() + * OR file size is 0 + */ + data->req.size = -1; + data->req.maxdownload = -1; + Curl_pgrsSetDownloadSize(data, -1); + size = 0; + if(attrs) + sftp_attributes_free(attrs); + } + else { + size = attrs->size; + + sftp_attributes_free(attrs); + + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + if(data->state.use_range) { + curl_off_t from, to; + const char *p = data->state.range; + int from_t, to_t; + + from_t = curlx_str_number(&p, &from, CURL_OFF_T_MAX); + if(from_t == STRE_OVERFLOW) { + MOVE_TO_ERROR_STATE(CURLE_RANGE_ERROR); + return rc; + } + curlx_str_passblanks(&p); + (void)curlx_str_single(&p, '-'); + + to_t = curlx_str_numblanks(&p, &to); + if(to_t == STRE_OVERFLOW) + return CURLE_RANGE_ERROR; + + if((to_t == STRE_NO_NUM) || (to >= size)) { + to = size - 1; + to_t = STRE_OK; + } + + if(from_t == STRE_NO_NUM) { + /* from is relative to end of file */ + from = size - to; + to = size - 1; + from_t = STRE_OK; + } + if(from > size) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", from, size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + if(from > to) { + from = to; + size = 0; } else { - rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, - data->set.ssl.key_passwd); - if(rc == SSH_AUTH_AGAIN) { - rc = SSH_AGAIN; - break; - } - if(rc == SSH_AUTH_SUCCESS) { - rc = SSH_OK; - sshc->authed = TRUE; - infof(data, "Completed public key authentication"); - state(data, SSH_AUTH_DONE); - break; + if((to - from) == CURL_OFF_T_MAX) { + MOVE_TO_ERROR_STATE(CURLE_RANGE_ERROR); + return rc; } + size = to - from + 1; + } - MOVE_TO_GSSAPI_AUTH; + rc = sftp_seek64(sshc->sftp_file, from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } + data->req.size = size; + data->req.maxdownload = size; + Curl_pgrsSetDownloadSize(data, size); + } + + /* We can resume if we can seek to the resume position */ + if(data->state.resume_from) { + if(data->state.resume_from < 0) { + /* We are supposed to download the last abs(from) bytes */ + if((curl_off_t)size < -data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", data->state.resume_from, size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + /* download from where? */ + data->state.resume_from += size; + } + else { + if((curl_off_t)size < data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", + data->state.resume_from, size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + } + /* Now store the number of bytes we are expected to download */ + data->req.size = size - data->state.resume_from; + data->req.maxdownload = size - data->state.resume_from; + Curl_pgrsSetDownloadSize(data, + size - data->state.resume_from); + + rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } + + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + myssh_state(data, sshc, SSH_STOP); + return rc; + } + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->writesockfd = data->conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh recv function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_IN; + + sshc->sftp_recv_state = 0; + myssh_state(data, sshc, SSH_STOP); + + return rc; +} + +/* + * ssh_statemach_act() runs the SSH state machine as far as it can without + * blocking and without reaching the end. The data the pointer 'block' points + * to will be set to TRUE if the libssh function returns SSH_AGAIN + * meaning it wants to be called again when the socket is ready + */ +static CURLcode myssh_statemach_act(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *block) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc = SSH_NO_ERROR, err; + const char *err_msg; + *block = 0; /* we are not blocking by default */ + + do { + + switch(sshc->state) { + case SSH_INIT: + myssh_state_init(data, sshc); + FALLTHROUGH(); + + case SSH_S_STARTUP: + rc = myssh_state_startup(data, sshc); + if(rc) + break; + FALLTHROUGH(); + case SSH_HOSTKEY: + rc = myssh_is_known(data, sshc); + if(rc != SSH_OK) { + MOVE_TO_ERROR_STATE(CURLE_PEER_FAILED_VERIFICATION); + break; } + + myssh_state(data, sshc, SSH_AUTHLIST); + FALLTHROUGH(); + case SSH_AUTHLIST: + rc = myssh_state_authlist(data, sshc); + break; + case SSH_AUTH_PKEY_INIT: + rc = myssh_state_auth_pkey_init(data, sshc); break; case SSH_AUTH_PKEY: rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey); @@ -849,7 +1125,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(rc == SSH_AUTH_SUCCESS) { sshc->authed = TRUE; infof(data, "Completed public key authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); break; } else { @@ -874,7 +1150,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) rc = SSH_OK; sshc->authed = TRUE; infof(data, "Completed gssapi authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); break; } @@ -883,7 +1159,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_AUTH_KEY_INIT: if(data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) { - state(data, SSH_AUTH_KEY); + myssh_state(data, sshc, SSH_AUTH_KEY); } else { MOVE_TO_PASSWD_AUTH; @@ -892,14 +1168,14 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_AUTH_KEY: /* keyboard-interactive authentication */ - rc = myssh_auth_interactive(conn); + rc = myssh_auth_interactive(conn, sshc); if(rc == SSH_AGAIN) { break; } if(rc == SSH_OK) { sshc->authed = TRUE; infof(data, "completed keyboard interactive authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); } else { MOVE_TO_PASSWD_AUTH; @@ -911,8 +1187,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); break; } - state(data, SSH_AUTH_PASS); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_AUTH_PASS); + FALLTHROUGH(); case SSH_AUTH_PASS: rc = ssh_userauth_password(sshc->ssh_session, NULL, conn->passwd); @@ -924,7 +1200,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(rc == SSH_AUTH_SUCCESS) { sshc->authed = TRUE; infof(data, "Completed password authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); } else { MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); @@ -949,11 +1225,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) conn->writesockfd = CURL_SOCKET_BAD; if(conn->handler->protocol == CURLPROTO_SFTP) { - state(data, SSH_SFTP_INIT); + myssh_state(data, sshc, SSH_SFTP_INIT); break; } infof(data, "SSH CONNECT phase done"); - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_SFTP_INIT: @@ -974,8 +1250,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(sftp_error_to_CURLE(SSH_FX_FAILURE)); break; } - state(data, SSH_SFTP_REALPATH); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_SFTP_REALPATH); + FALLTHROUGH(); case SSH_SFTP_REALPATH: /* * Get the "home" directory @@ -985,31 +1261,34 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); break; } - data->state.most_recent_ftp_entrypath = sshc->homedir; + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(sshc->homedir); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; /* This is the last step in the SFTP connect phase. Do note that while we get the homedir here, we get the "workingpath" in the DO action since the homedir will remain the same between request but the working path will not. */ DEBUGF(infof(data, "SSH CONNECT phase done")); - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_SFTP_QUOTE_INIT: - result = Curl_getworkingpath(data, sshc->homedir, &protop->path); + result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); if(result) { sshc->actualcode = result; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } if(data->set.quote) { infof(data, "Sending quote commands"); sshc->quote_item = data->set.quote; - state(data, SSH_SFTP_QUOTE); + myssh_state(data, sshc, SSH_SFTP_QUOTE); } else { - state(data, SSH_SFTP_GETINFO); + myssh_state(data, sshc, SSH_SFTP_GETINFO); } break; @@ -1017,16 +1296,16 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(data->set.postquote) { infof(data, "Sending quote commands"); sshc->quote_item = data->set.postquote; - state(data, SSH_SFTP_QUOTE); + myssh_state(data, sshc, SSH_SFTP_QUOTE); } else { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); } break; case SSH_SFTP_QUOTE: /* Send any quote commands */ - sftp_quote(data); + sftp_quote(data, sshc, sshp); break; case SSH_SFTP_NEXT_QUOTE: @@ -1036,21 +1315,21 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->quote_item = sshc->quote_item->next; if(sshc->quote_item) { - state(data, SSH_SFTP_QUOTE); + myssh_state(data, sshc, SSH_SFTP_QUOTE); } else { if(sshc->nextstate != SSH_NO_STATE) { - state(data, sshc->nextstate); + myssh_state(data, sshc, sshc->nextstate); sshc->nextstate = SSH_NO_STATE; } else { - state(data, SSH_SFTP_GETINFO); + myssh_state(data, sshc, SSH_SFTP_GETINFO); } } break; case SSH_SFTP_QUOTE_STAT: - sftp_quote_stat(data); + sftp_quote_stat(data, sshc); break; case SSH_SFTP_QUOTE_SETSTAT: @@ -1061,7 +1340,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path2); failf(data, "Attempt to set SFTP stats failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; /* sshc->actualcode = sftp_error_to_CURLE(err); @@ -1069,7 +1348,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) * the error the libssh2 backend is returning */ break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_SYMLINK: @@ -1080,12 +1359,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path2); failf(data, "symlink command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_MKDIR: @@ -1095,12 +1374,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "mkdir command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_RENAME: @@ -1111,12 +1390,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path2); failf(data, "rename command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_RMDIR: @@ -1125,12 +1404,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "rmdir command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_UNLINK: @@ -1139,12 +1418,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "rm command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_STATVFS: @@ -1156,19 +1435,29 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "statvfs command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } else if(statvfs) { + #ifdef _MSC_VER + #define CURL_LIBSSH_VFS_SIZE_MASK "I64u" + #else + #define CURL_LIBSSH_VFS_SIZE_MASK PRIu64 + #endif char *tmp = aprintf("statvfs:\n" - "f_bsize: %llu\n" "f_frsize: %llu\n" - "f_blocks: %llu\n" "f_bfree: %llu\n" - "f_bavail: %llu\n" "f_files: %llu\n" - "f_ffree: %llu\n" "f_favail: %llu\n" - "f_fsid: %llu\n" "f_flag: %llu\n" - "f_namemax: %llu\n", + "f_bsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_frsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_blocks: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bfree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bavail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_files: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_ffree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_favail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_fsid: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_flag: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_namemax: %" CURL_LIBSSH_VFS_SIZE_MASK "\n", statvfs->f_bsize, statvfs->f_frsize, statvfs->f_blocks, statvfs->f_bfree, statvfs->f_bavail, statvfs->f_files, @@ -1179,7 +1468,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(!tmp) { result = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; break; } @@ -1187,21 +1476,21 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); free(tmp); if(result) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; } } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; } case SSH_SFTP_GETINFO: if(data->set.get_filetime) { - state(data, SSH_SFTP_FILETIME); + myssh_state(data, sshc, SSH_SFTP_FILETIME); } else { - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); } break; @@ -1209,177 +1498,38 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) { sftp_attributes attrs; - attrs = sftp_stat(sshc->sftp_session, protop->path); + attrs = sftp_stat(sshc->sftp_session, sshp->path); if(attrs) { data->info.filetime = attrs->mtime; sftp_attributes_free(attrs); } - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); break; } case SSH_SFTP_TRANS_INIT: if(data->state.upload) - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); else { - if(protop->path[strlen(protop->path)-1] == '/') - state(data, SSH_SFTP_READDIR_INIT); + if(sshp->path[strlen(sshp->path)-1] == '/') + myssh_state(data, sshc, SSH_SFTP_READDIR_INIT); else - state(data, SSH_SFTP_DOWNLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } break; case SSH_SFTP_UPLOAD_INIT: - { - int flags; - - if(data->state.resume_from) { - sftp_attributes attrs; - - if(data->state.resume_from < 0) { - attrs = sftp_stat(sshc->sftp_session, protop->path); - if(attrs) { - curl_off_t size = attrs->size; - if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); - MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); - break; - } - data->state.resume_from = attrs->size; - - sftp_attributes_free(attrs); - } - else { - data->state.resume_from = 0; - } - } - } - - if(data->set.remote_append) - /* Try to open for append, but create if nonexisting */ - flags = O_WRONLY|O_CREAT|O_APPEND; - else if(data->state.resume_from > 0) - /* If we have restart position then open for append */ - flags = O_WRONLY|O_APPEND; - else - /* Clear file before writing (normal behavior) */ - flags = O_WRONLY|O_CREAT|O_TRUNC; - - if(sshc->sftp_file) - sftp_close(sshc->sftp_file); - sshc->sftp_file = - sftp_open(sshc->sftp_session, protop->path, - flags, (mode_t)data->set.new_file_perms); - if(!sshc->sftp_file) { - err = sftp_get_error(sshc->sftp_session); - - if(((err == SSH_FX_NO_SUCH_FILE || err == SSH_FX_FAILURE || - err == SSH_FX_NO_SUCH_PATH)) && - (data->set.ftp_create_missing_dirs && - (strlen(protop->path) > 1))) { - /* try to create the path remotely */ - rc = 0; - sshc->secondCreateDirs = 1; - state(data, SSH_SFTP_CREATE_DIRS_INIT); - break; - } - else { - MOVE_TO_SFTP_CLOSE_STATE(); - break; - } - } - - /* If we have a restart point then we need to seek to the correct - position. */ - if(data->state.resume_from > 0) { - /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_FTP_COULDNT_USE_REST; - } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, - readthisamountnow, data->state.in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Failed to read data"); - MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); - break; - } - } while(passed < data->state.resume_from); - if(rc) - break; - } - - /* now, decrease the size of the read */ - if(data->state.infilesize > 0) { - data->state.infilesize -= data->state.resume_from; - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - } - - rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); - if(rc) { - MOVE_TO_SFTP_CLOSE_STATE(); - break; - } - } - if(data->state.infilesize > 0) { - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - } - /* upload data */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - - /* store this original bitmask setup to use later on if we can't - figure out a "real" bitmask */ - sshc->orig_waitfor = data->req.keepon; - - /* we want to use the _sending_ function even when the socket turns - out readable as the underlying libssh sftp send function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; - - /* since we don't really wait for anything at this point, we want the - state machine to move on as soon as possible so we set a very short - timeout here */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); - - state(data, SSH_STOP); + rc = myssh_state_upload_init(data, sshc, sshp); break; - } case SSH_SFTP_CREATE_DIRS_INIT: - if(strlen(protop->path) > 1) { - sshc->slash_pos = protop->path + 1; /* ignore the leading '/' */ - state(data, SSH_SFTP_CREATE_DIRS); + if(strlen(sshp->path) > 1) { + sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */ + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); } else { - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); } break; @@ -1388,22 +1538,22 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(sshc->slash_pos) { *sshc->slash_pos = 0; - infof(data, "Creating directory '%s'", protop->path); - state(data, SSH_SFTP_CREATE_DIRS_MKDIR); + infof(data, "Creating directory '%s'", sshp->path); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR); break; } - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); break; case SSH_SFTP_CREATE_DIRS_MKDIR: /* 'mode' - parameter is preliminary - default to 0644 */ - rc = sftp_mkdir(sshc->sftp_session, protop->path, + rc = sftp_mkdir(sshc->sftp_session, sshp->path, (mode_t)data->set.new_directory_perms); *sshc->slash_pos = '/'; ++sshc->slash_pos; if(rc < 0) { /* - * Abort if failure wasn't that the dir already exists or the + * Abort if failure was not that the dir already exists or the * permission was denied (creation might succeed further down the * path) - retry on unspecific FAILURE also */ @@ -1416,13 +1566,13 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } rc = 0; /* clear rc and continue */ } - state(data, SSH_SFTP_CREATE_DIRS); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); break; case SSH_SFTP_READDIR_INIT: Curl_pgrsSetDownloadSize(data, -1); if(data->req.no_body) { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } @@ -1431,18 +1581,18 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) * listing */ sshc->sftp_dir = sftp_opendir(sshc->sftp_session, - protop->path); + sshp->path); if(!sshc->sftp_dir) { failf(data, "Could not open directory for reading: %s", ssh_get_error(sshc->ssh_session)); MOVE_TO_SFTP_CLOSE_STATE(); break; } - state(data, SSH_SFTP_READDIR); + myssh_state(data, sshc, SSH_SFTP_READDIR); break; case SSH_SFTP_READDIR: - Curl_dyn_reset(&sshc->readdir_buf); + curlx_dyn_reset(&sshc->readdir_buf); if(sshc->readdir_attrs) sftp_attributes_free(sshc->readdir_attrs); @@ -1457,7 +1607,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) tmpLine = aprintf("%s\n", sshc->readdir_filename); if(!tmpLine) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } @@ -1466,45 +1616,39 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) free(tmpLine); if(result) { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } - /* since this counts what we send to the client, we include the - newline in this counter */ - data->req.bytecount += sshc->readdir_len + 1; - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_OUT, (char *)sshc->readdir_filename, - sshc->readdir_len); } else { - if(Curl_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) { + if(curlx_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) { sshc->actualcode = CURLE_OUT_OF_MEMORY; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } if((sshc->readdir_attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) && ((sshc->readdir_attrs->permissions & SSH_S_IFMT) == SSH_S_IFLNK)) { - sshc->readdir_linkPath = aprintf("%s%s", protop->path, + sshc->readdir_linkPath = aprintf("%s%s", sshp->path, sshc->readdir_filename); if(!sshc->readdir_linkPath) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } - state(data, SSH_SFTP_READDIR_LINK); + myssh_state(data, sshc, SSH_SFTP_READDIR_LINK); break; } - state(data, SSH_SFTP_READDIR_BOTTOM); + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); break; } } else if(sftp_dir_eof(sshc->sftp_dir)) { - state(data, SSH_SFTP_READDIR_DONE); + myssh_state(data, sshc, SSH_SFTP_READDIR_DONE); break; } else { @@ -1546,8 +1690,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->readdir_linkPath); - if(Curl_dyn_addf(&sshc->readdir_buf, " -> %s", - sshc->readdir_filename)) { + if(curlx_dyn_addf(&sshc->readdir_buf, " -> %s", + sshc->readdir_filename)) { sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } @@ -1557,30 +1701,24 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->readdir_filename = NULL; sshc->readdir_longentry = NULL; - state(data, SSH_SFTP_READDIR_BOTTOM); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); + FALLTHROUGH(); case SSH_SFTP_READDIR_BOTTOM: - if(Curl_dyn_addn(&sshc->readdir_buf, "\n", 1)) + if(curlx_dyn_addn(&sshc->readdir_buf, "\n", 1)) result = CURLE_OUT_OF_MEMORY; else result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&sshc->readdir_buf), - Curl_dyn_len(&sshc->readdir_buf)); + curlx_dyn_ptr(&sshc->readdir_buf), + curlx_dyn_len(&sshc->readdir_buf)); - if(!result) { - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_OUT, Curl_dyn_ptr(&sshc->readdir_buf), - Curl_dyn_len(&sshc->readdir_buf)); - data->req.bytecount += Curl_dyn_len(&sshc->readdir_buf); - } ssh_string_free_char(sshc->readdir_tmp); sshc->readdir_tmp = NULL; if(result) { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); } else - state(data, SSH_SFTP_READDIR); + myssh_state(data, sshc, SSH_SFTP_READDIR); break; case SSH_SFTP_READDIR_DONE: @@ -1588,8 +1726,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->sftp_dir = NULL; /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - state(data, SSH_STOP); + Curl_xfer_setup_nop(data); + myssh_state(data, sshc, SSH_STOP); break; case SSH_SFTP_DOWNLOAD_INIT: @@ -1599,7 +1737,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(sshc->sftp_file) sftp_close(sshc->sftp_file); - sshc->sftp_file = sftp_open(sshc->sftp_session, protop->path, + sshc->sftp_file = sftp_open(sshc->sftp_session, sshp->path, O_RDONLY, (mode_t)data->set.new_file_perms); if(!sshc->sftp_file) { failf(data, "Could not open remote file for reading: %s", @@ -1609,161 +1747,19 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } sftp_file_set_nonblocking(sshc->sftp_file); - state(data, SSH_SFTP_DOWNLOAD_STAT); + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); break; case SSH_SFTP_DOWNLOAD_STAT: - { - sftp_attributes attrs; - curl_off_t size; - - attrs = sftp_fstat(sshc->sftp_file); - if(!attrs || - !(attrs->flags & SSH_FILEXFER_ATTR_SIZE) || - (attrs->size == 0)) { - /* - * sftp_fstat didn't return an error, so maybe the server - * just doesn't support stat() - * OR the server doesn't return a file size with a stat() - * OR file size is 0 - */ - data->req.size = -1; - data->req.maxdownload = -1; - Curl_pgrsSetDownloadSize(data, -1); - size = 0; - } - else { - size = attrs->size; - - sftp_attributes_free(attrs); - - if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(data->state.use_range) { - curl_off_t from, to; - char *ptr; - char *ptr2; - CURLofft to_t; - CURLofft from_t; - - from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); - if(from_t == CURL_OFFT_FLOW) { - return CURLE_RANGE_ERROR; - } - while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) - ptr++; - to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); - if(to_t == CURL_OFFT_FLOW) { - return CURLE_RANGE_ERROR; - } - if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ - || (to >= size)) { - to = size - 1; - } - if(from_t) { - /* from is relative to end of file */ - from = size - to; - to = size - 1; - } - if(from > size) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", from, size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(from > to) { - from = to; - size = 0; - } - else { - size = to - from + 1; - } - - rc = sftp_seek64(sshc->sftp_file, from); - if(rc) { - MOVE_TO_SFTP_CLOSE_STATE(); - break; - } - } - data->req.size = size; - data->req.maxdownload = size; - Curl_pgrsSetDownloadSize(data, size); - } - - /* We can resume if we can seek to the resume position */ - if(data->state.resume_from) { - if(data->state.resume_from < 0) { - /* We're supposed to download the last abs(from) bytes */ - if((curl_off_t)size < -data->state.resume_from) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* download from where? */ - data->state.resume_from += size; - } - else { - if((curl_off_t)size < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - } - /* Now store the number of bytes we are expected to download */ - data->req.size = size - data->state.resume_from; - data->req.maxdownload = size - data->state.resume_from; - Curl_pgrsSetDownloadSize(data, - size - data->state.resume_from); - - rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); - if(rc) { - MOVE_TO_SFTP_CLOSE_STATE(); - break; - } - } - } - - /* Setup the actual download */ - if(data->req.size == 0) { - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - infof(data, "File already completely downloaded"); - state(data, SSH_STOP); + rc = myssh_state_sftp_download_stat(data, sshc); break; - } - Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->writesockfd = conn->sockfd; - - /* we want to use the _receiving_ function even when the socket turns - out writableable as the underlying libssh recv function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; - - if(result) { - /* this should never occur; the close state should be entered - at the time the error occurs */ - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result; - } - else { - sshc->sftp_recv_state = 0; - state(data, SSH_STOP); - } - break; case SSH_SFTP_CLOSE: if(sshc->sftp_file) { sftp_close(sshc->sftp_file); sshc->sftp_file = NULL; } - Curl_safefree(protop->path); + Curl_safefree(sshp->path); DEBUGF(infof(data, "SFTP DONE done")); @@ -1772,11 +1768,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) SSH_SFTP_CLOSE to pass the correct result back */ if(sshc->nextstate != SSH_NO_STATE && sshc->nextstate != SSH_SFTP_CLOSE) { - state(data, sshc->nextstate); + myssh_state(data, sshc, sshc->nextstate); sshc->nextstate = SSH_SFTP_CLOSE; } else { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); result = sshc->actualcode; } break; @@ -1785,6 +1781,13 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* during times we get here due to a broken transfer and then the sftp_handle might not have been taken down so make sure that is done before we proceed */ + ssh_set_blocking(sshc->ssh_session, 0); +#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0) + if(sshc->sftp_aio) { + sftp_aio_free(sshc->sftp_aio); + sshc->sftp_aio = NULL; + } +#endif if(sshc->sftp_file) { sftp_close(sshc->sftp_file); @@ -1797,16 +1800,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } SSH_STRING_FREE_CHAR(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; - state(data, SSH_SESSION_DISCONNECT); + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); break; case SSH_SCP_TRANS_INIT: - result = Curl_getworkingpath(data, sshc->homedir, &protop->path); + result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); if(result) { sshc->actualcode = result; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } @@ -1822,13 +1824,13 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } sshc->scp_session = - ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, protop->path); - state(data, SSH_SCP_UPLOAD_INIT); + ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, sshp->path); + myssh_state(data, sshc, SSH_SCP_UPLOAD_INIT); } else { sshc->scp_session = - ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, protop->path); - state(data, SSH_SCP_DOWNLOAD_INIT); + ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, sshp->path); + myssh_state(data, sshc, SSH_SCP_DOWNLOAD_INIT); } if(!sshc->scp_session) { @@ -1849,9 +1851,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } - rc = ssh_scp_push_file(sshc->scp_session, protop->path, - data->state.infilesize, - (int)data->set.new_file_perms); + rc = ssh_scp_push_file64(sshc->scp_session, sshp->path, + (uint64_t)data->state.infilesize, + (int)data->set.new_file_perms); + if(rc != SSH_OK) { err_msg = ssh_get_error(sshc->ssh_session); failf(data, "%s", err_msg); @@ -1860,21 +1863,21 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } /* upload data */ - Curl_setup_transfer(data, -1, data->req.size, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->sockfd = conn->writesockfd; - /* store this original bitmask setup to use later on if we can't + /* store this original bitmask setup to use later on if we cannot figure out a "real" bitmask */ sshc->orig_waitfor = data->req.keepon; /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh scp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; @@ -1887,8 +1890,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); break; } - state(data, SSH_SCP_DOWNLOAD); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_SCP_DOWNLOAD); + FALLTHROUGH(); case SSH_SCP_DOWNLOAD:{ curl_off_t bytecount; @@ -1904,24 +1907,24 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* download data */ bytecount = ssh_scp_request_get_size(sshc->scp_session); data->req.maxdownload = (curl_off_t) bytecount; - Curl_setup_transfer(data, FIRSTSOCKET, bytecount, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->writesockfd = conn->sockfd; /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } case SSH_SCP_DONE: if(data->state.upload) - state(data, SSH_SCP_SEND_EOF); + myssh_state(data, sshc, SSH_SCP_SEND_EOF); else - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_SEND_EOF: @@ -1939,7 +1942,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } } - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_CHANNEL_FREE: @@ -1951,11 +1954,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ssh_set_blocking(sshc->ssh_session, 0); - state(data, SSH_SESSION_DISCONNECT); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); + FALLTHROUGH(); case SSH_SESSION_DISCONNECT: - /* during weird times when we've been prematurely aborted, the channel + /* during weird times when we have been prematurely aborted, the channel is still alive when we reach this state and we MUST kill the channel properly first */ if(sshc->scp_session) { @@ -1963,75 +1966,43 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->scp_session = NULL; } + if(sshc->sftp_file) { + sftp_close(sshc->sftp_file); + sshc->sftp_file = NULL; + } + if(sshc->sftp_session) { + sftp_free(sshc->sftp_session); + sshc->sftp_session = NULL; + } + ssh_disconnect(sshc->ssh_session); if(!ssh_version(SSH_VERSION_INT(0, 10, 0))) { /* conn->sock[FIRSTSOCKET] is closed by ssh_disconnect behind our back, - explicitly mark it as closed with the memdebug macro. This libssh + tell the connection to forget about it. This libssh bug is fixed in 0.10.0. */ - fake_sclose(conn->sock[FIRSTSOCKET]); - conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; + Curl_conn_forget_socket(data, FIRSTSOCKET); } SSH_STRING_FREE_CHAR(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; - state(data, SSH_SESSION_FREE); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_SESSION_FREE); + FALLTHROUGH(); case SSH_SESSION_FREE: - if(sshc->ssh_session) { - ssh_free(sshc->ssh_session); - sshc->ssh_session = NULL; - } - - /* worst-case scenario cleanup */ - - DEBUGASSERT(sshc->ssh_session == NULL); - DEBUGASSERT(sshc->scp_session == NULL); - - if(sshc->readdir_tmp) { - ssh_string_free_char(sshc->readdir_tmp); - sshc->readdir_tmp = NULL; - } - - if(sshc->quote_attrs) - sftp_attributes_free(sshc->quote_attrs); - - if(sshc->readdir_attrs) - sftp_attributes_free(sshc->readdir_attrs); - - if(sshc->readdir_link_attrs) - sftp_attributes_free(sshc->readdir_link_attrs); - - if(sshc->privkey) - ssh_key_free(sshc->privkey); - if(sshc->pubkey) - ssh_key_free(sshc->pubkey); - - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - Curl_dyn_free(&sshc->readdir_buf); - Curl_safefree(sshc->readdir_linkPath); - SSH_STRING_FREE_CHAR(sshc->homedir); - + sshc_cleanup(sshc); /* the code we are about to return */ result = sshc->actualcode; - memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(conn, "SSH session free"); sshc->state = SSH_SESSION_FREE; /* current */ sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } @@ -2067,26 +2038,26 @@ static int myssh_getsock(struct Curl_easy *data, if(!conn->waitfor) bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + DEBUGF(infof(data, "ssh_getsock -> %x", bitmap)); return bitmap; } -static void myssh_block2waitfor(struct connectdata *conn, bool block) +static void myssh_block2waitfor(struct connectdata *conn, + struct ssh_conn *sshc, + bool block) { - struct ssh_conn *sshc = &conn->proto.sshc; - - /* If it didn't block, or nothing was returned by ssh_get_poll_flags + /* If it did not block, or nothing was returned by ssh_get_poll_flags * have the original set */ conn->waitfor = sshc->orig_waitfor; if(block) { int dir = ssh_get_poll_flags(sshc->ssh_session); - if(dir & SSH_READ_PENDING) { - /* translate the libssh define bits into our own bit defines */ - conn->waitfor = KEEP_RECV; - } - else if(dir & SSH_WRITE_PENDING) { - conn->waitfor = KEEP_SEND; - } + conn->waitfor = 0; + /* translate the libssh define bits into our own bit defines */ + if(dir & SSH_READ_PENDING) + conn->waitfor |= KEEP_RECV; + if(dir & SSH_WRITE_PENDING) + conn->waitfor |= KEEP_SEND; } } @@ -2095,30 +2066,35 @@ static CURLcode myssh_multi_statemach(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); bool block; /* we store the status and use that to provide a ssh_getsock() implementation */ - CURLcode result = myssh_statemach_act(data, &block); + CURLcode result; - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; - myssh_block2waitfor(conn, block); + if(!sshc || !sshp) + return CURLE_FAILED_INIT; + result = myssh_statemach_act(data, sshc, sshp, &block); + *done = (sshc->state == SSH_STOP); + myssh_block2waitfor(conn, sshc, block); return result; } static CURLcode myssh_block_statemach(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, bool disconnect) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; while((sshc->state != SSH_STOP) && !result) { bool block; timediff_t left = 1000; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); - result = myssh_statemach_act(data, &block); + result = myssh_statemach_act(data, sshc, sshp, &block); if(result) break; @@ -2149,19 +2125,46 @@ static CURLcode myssh_block_statemach(struct Curl_easy *data, return result; } +static void myssh_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SSHPROTO *sshp = entry; + (void)key; + (void)klen; + Curl_safefree(sshp->path); + free(sshp); +} + +static void myssh_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ssh_conn *sshc = entry; + (void)key; + (void)klen; + sshc_cleanup(sshc); + free(sshc); +} + /* * SSH setup connection */ static CURLcode myssh_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - struct SSHPROTO *ssh; - struct ssh_conn *sshc = &conn->proto.sshc; + struct SSHPROTO *sshp; + struct ssh_conn *sshc; + + sshc = calloc(1, sizeof(*sshc)); + if(!sshc) + return CURLE_OUT_OF_MEMORY; + + curlx_dyn_init(&sshc->readdir_buf, CURL_PATH_MAX * 2); + sshc->initialised = TRUE; + if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, myssh_conn_dtor)) + return CURLE_OUT_OF_MEMORY; - data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); - if(!ssh) + sshp = calloc(1, sizeof(*sshp)); + if(!sshp || + Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; - Curl_dyn_init(&sshc->readdir_buf, PATH_MAX * 2); return CURLE_OK; } @@ -2175,18 +2178,18 @@ static Curl_send scp_send, sftp_send; */ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) { - struct ssh_conn *ssh; CURLcode result; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *ssh = Curl_meta_get(data, CURL_META_SSH_EASY); curl_socket_t sock = conn->sock[FIRSTSOCKET]; int rc; - /* initialize per-handle data if not already */ - if(!data->req.p.ssh) - myssh_setup_connection(data, conn); + if(!sshc || !ssh) + return CURLE_FAILED_INIT; /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ + function to make the reuse checks properly be able to check this bit. */ connkeep(conn, "SSH default"); if(conn->handler->protocol & CURLPROTO_SCP) { @@ -2198,27 +2201,32 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) conn->send[FIRSTSOCKET] = sftp_send; } - ssh = &conn->proto.sshc; - - ssh->ssh_session = ssh_new(); - if(!ssh->ssh_session) { + sshc->ssh_session = ssh_new(); + if(!sshc->ssh_session) { failf(data, "Failure initialising ssh session"); return CURLE_FAILED_INIT; } - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_HOST, conn->host.name); + if(conn->bits.ipv6_ip) { + char ipv6[MAX_IPADR_LEN]; + msnprintf(ipv6, sizeof(ipv6), "[%s]", conn->host.name); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, ipv6); + } + else + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, conn->host.name); + if(rc != SSH_OK) { failf(data, "Could not set remote host"); return CURLE_FAILED_INIT; } - rc = ssh_options_parse_config(ssh->ssh_session, NULL); + rc = ssh_options_parse_config(sshc->ssh_session, NULL); if(rc != SSH_OK) { infof(data, "Could not parse SSH configuration files"); /* ignore */ } - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_FD, &sock); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_FD, &sock); if(rc != SSH_OK) { failf(data, "Could not set socket"); return CURLE_FAILED_INIT; @@ -2226,7 +2234,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) if(conn->user && conn->user[0] != '\0') { infof(data, "User: %s", conn->user); - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_USER, conn->user); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_USER, conn->user); if(rc != SSH_OK) { failf(data, "Could not set user"); return CURLE_FAILED_INIT; @@ -2235,7 +2243,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) if(data->set.str[STRING_SSH_KNOWNHOSTS]) { infof(data, "Known hosts: %s", data->set.str[STRING_SSH_KNOWNHOSTS]); - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_KNOWNHOSTS, + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_KNOWNHOSTS, data->set.str[STRING_SSH_KNOWNHOSTS]); if(rc != SSH_OK) { failf(data, "Could not set known hosts file path"); @@ -2244,7 +2252,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) } if(conn->remote_port) { - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_PORT, + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_PORT, &conn->remote_port); if(rc != SSH_OK) { failf(data, "Could not set remote port"); @@ -2253,7 +2261,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) } if(data->set.ssh_compression) { - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_COMPRESSION, + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_COMPRESSION, "zlib,zlib@openssh.com,none"); if(rc != SSH_OK) { failf(data, "Could not set compression"); @@ -2261,12 +2269,12 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) } } - ssh->privkey = NULL; - ssh->pubkey = NULL; + sshc->privkey = NULL; + sshc->pubkey = NULL; if(data->set.str[STRING_SSH_PUBLIC_KEY]) { rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY], - &ssh->pubkey); + &sshc->pubkey); if(rc != SSH_OK) { failf(data, "Could not load public key file"); return CURLE_FAILED_INIT; @@ -2276,7 +2284,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) /* we do not verify here, we do it at the state machine, * after connection */ - state(data, SSH_INIT); + myssh_state(data, sshc, SSH_INIT); result = myssh_multi_statemach(data, done); @@ -2310,13 +2318,16 @@ CURLcode scp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { CURLcode result = CURLE_OK; + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SCP_TRANS_INIT); + myssh_state(data, sshc, SSH_SCP_TRANS_INIT); result = myssh_multi_statemach(data, dophase_done); @@ -2332,11 +2343,13 @@ CURLcode scp_perform(struct Curl_easy *data, static CURLcode myssh_do_it(struct Curl_easy *data, bool *done) { CURLcode result; - bool connected = 0; + bool connected = FALSE; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); *done = FALSE; /* default to false */ + if(!sshc) + return CURLE_FAILED_INIT; data->req.size = -1; /* make sure this is unknown at this point */ @@ -2357,6 +2370,62 @@ static CURLcode myssh_do_it(struct Curl_easy *data, bool *done) return result; } +static void sshc_cleanup(struct ssh_conn *sshc) +{ + if(sshc->initialised) { + if(sshc->sftp_file) { + sftp_close(sshc->sftp_file); + sshc->sftp_file = NULL; + } + if(sshc->sftp_session) { + sftp_free(sshc->sftp_session); + sshc->sftp_session = NULL; + } + if(sshc->ssh_session) { + ssh_free(sshc->ssh_session); + sshc->ssh_session = NULL; + } + + /* worst-case scenario cleanup */ + DEBUGASSERT(sshc->ssh_session == NULL); + DEBUGASSERT(sshc->scp_session == NULL); + + if(sshc->readdir_tmp) { + ssh_string_free_char(sshc->readdir_tmp); + sshc->readdir_tmp = NULL; + } + if(sshc->quote_attrs) { + sftp_attributes_free(sshc->quote_attrs); + sshc->quote_attrs = NULL; + } + if(sshc->readdir_attrs) { + sftp_attributes_free(sshc->readdir_attrs); + sshc->readdir_attrs = NULL; + } + if(sshc->readdir_link_attrs) { + sftp_attributes_free(sshc->readdir_link_attrs); + sshc->readdir_link_attrs = NULL; + } + if(sshc->privkey) { + ssh_key_free(sshc->privkey); + sshc->privkey = NULL; + } + if(sshc->pubkey) { + ssh_key_free(sshc->pubkey); + sshc->pubkey = NULL; + } + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + curlx_dyn_free(&sshc->readdir_buf); + Curl_safefree(sshc->readdir_linkPath); + SSH_STRING_FREE_CHAR(sshc->homedir); + sshc->initialised = FALSE; + } +} + /* BLOCKING, but the function is using the state machine so the only reason this is still blocking is that the multi interface code has no support for disconnecting operations that takes a while */ @@ -2365,15 +2434,16 @@ static CURLcode scp_disconnect(struct Curl_easy *data, bool dead_connection) { CURLcode result = CURLE_OK; - struct ssh_conn *ssh = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void) dead_connection; - if(ssh->ssh_session) { - /* only if there's a session still around to use! */ + if(sshc && sshc->ssh_session && sshp) { + /* only if there is a session still around to use! */ - state(data, SSH_SESSION_DISCONNECT); + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); - result = myssh_block_statemach(data, TRUE); + result = myssh_block_statemach(data, sshc, sshp, TRUE); } return result; @@ -2381,20 +2451,20 @@ static CURLcode scp_disconnect(struct Curl_easy *data, /* generic done function for both SCP and SFTP called from their specific done functions */ -static CURLcode myssh_done(struct Curl_easy *data, CURLcode status) +static CURLcode myssh_done(struct Curl_easy *data, + struct ssh_conn *sshc, + CURLcode status) { CURLcode result = CURLE_OK; - struct SSHPROTO *protop = data->req.p.ssh; + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); - if(!status) { + if(!status && sshp) { /* run the state-machine */ - result = myssh_block_statemach(data, FALSE); + result = myssh_block_statemach(data, sshc, sshp, FALSE); } else result = status; - if(protop) - Curl_safefree(protop->path); if(Curl_pgrsDone(data)) return CURLE_ABORTED_BY_CALLBACK; @@ -2406,30 +2476,38 @@ static CURLcode myssh_done(struct Curl_easy *data, CURLcode status) static CURLcode scp_done(struct Curl_easy *data, CURLcode status, bool premature) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); (void) premature; /* not used */ + if(!sshc) + return CURLE_FAILED_INIT; if(!status) - state(data, SSH_SCP_DONE); - - return myssh_done(data, status); + myssh_state(data, sshc, SSH_SCP_DONE); + return myssh_done(data, sshc, status); } static ssize_t scp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { int rc; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void) sockindex; /* we only support SCP on the fixed known primary socket */ - (void) err; + (void)eos; - rc = ssh_scp_write(conn->proto.sshc.scp_session, mem, len); + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } + + rc = ssh_scp_write(sshc->scp_session, mem, len); #if 0 /* The following code is misleading, mostly added as wishful thinking * that libssh at some point will implement non-blocking ssh_scp_write/read. * Currently rc can only be number of bytes read or SSH_ERROR. */ - myssh_block2waitfor(conn, (rc == SSH_AGAIN) ? TRUE : FALSE); + myssh_block2waitfor(conn, sshc, (rc == SSH_AGAIN)); if(rc == SSH_AGAIN) { *err = CURLE_AGAIN; @@ -2450,18 +2528,22 @@ static ssize_t scp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; - (void) err; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void) sockindex; /* we only support SCP on the fixed known primary socket */ + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } /* libssh returns int */ - nread = ssh_scp_read(conn->proto.sshc.scp_session, mem, len); + nread = ssh_scp_read(sshc->scp_session, mem, len); #if 0 /* The following code is misleading, mostly added as wishful thinking * that libssh at some point will implement non-blocking ssh_scp_write/read. * Currently rc can only be SSH_OK or SSH_ERROR. */ - myssh_block2waitfor(conn, (nread == SSH_AGAIN) ? TRUE : FALSE); + myssh_block2waitfor(conn, sshc, (nread == SSH_AGAIN)); if(nread == SSH_AGAIN) { *err = CURLE_AGAIN; nread = -1; @@ -2489,14 +2571,17 @@ CURLcode sftp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SFTP_QUOTE_INIT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_INIT); /* run the state-machine */ result = myssh_multi_statemach(data, dophase_done); @@ -2528,51 +2613,98 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); CURLcode result = CURLE_OK; (void) dead_connection; DEBUGF(infof(data, "SSH DISCONNECT starts now")); - if(conn->proto.sshc.ssh_session) { - /* only if there's a session still around to use! */ - state(data, SSH_SFTP_SHUTDOWN); - result = myssh_block_statemach(data, TRUE); + if(sshc && sshc->ssh_session && sshp) { + /* only if there is a session still around to use! */ + myssh_state(data, sshc, SSH_SFTP_SHUTDOWN); + result = myssh_block_statemach(data, sshc, sshp, TRUE); } DEBUGF(infof(data, "SSH DISCONNECT is done")); - return result; - } static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + if(!sshc) + return CURLE_FAILED_INIT; if(!status) { /* Post quote commands are executed after the SFTP_CLOSE state to avoid errors that could happen due to open file handles during POSTQUOTE operation */ if(!premature && data->set.postquote && !conn->bits.retry) sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); } - return myssh_done(data, status); + return myssh_done(data, sshc, status); } /* return number of sent bytes */ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, + CURLcode *err) { ssize_t nwrite; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; + (void)eos; - nwrite = sftp_write(conn->proto.sshc.sftp_file, mem, len); + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } + /* limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + */ + if(len > 32768) + len = 32768; +#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0) + switch(sshc->sftp_send_state) { + case 0: + sftp_file_set_nonblocking(sshc->sftp_file); + if(sftp_aio_begin_write(sshc->sftp_file, mem, len, + &sshc->sftp_aio) == SSH_ERROR) { + *err = CURLE_SEND_ERROR; + return -1; + } + sshc->sftp_send_state = 1; + FALLTHROUGH(); + case 1: + nwrite = sftp_aio_wait_write(&sshc->sftp_aio); + myssh_block2waitfor(conn, sshc, (nwrite == SSH_AGAIN) ? TRUE : FALSE); + if(nwrite == SSH_AGAIN) { + *err = CURLE_AGAIN; + return 0; + } + else if(nwrite < 0) { + *err = CURLE_SEND_ERROR; + return -1; + } + if(sshc->sftp_aio) { + sftp_aio_free(sshc->sftp_aio); + sshc->sftp_aio = NULL; + } + sshc->sftp_send_state = 0; + return nwrite; + default: + /* we never reach here */ + return -1; + } +#else + nwrite = sftp_write(sshc->sftp_file, mem, len); - myssh_block2waitfor(conn, FALSE); + myssh_block2waitfor(conn, sshc, FALSE); #if 0 /* not returned by libssh on write */ if(nwrite == SSH_AGAIN) { @@ -2587,6 +2719,7 @@ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, } return nwrite; +#endif } /* @@ -2598,29 +2731,31 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; DEBUGASSERT(len < CURL_MAX_READ_SIZE); + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } - switch(conn->proto.sshc.sftp_recv_state) { + switch(sshc->sftp_recv_state) { case 0: - conn->proto.sshc.sftp_file_index = - sftp_async_read_begin(conn->proto.sshc.sftp_file, - (uint32_t)len); - if(conn->proto.sshc.sftp_file_index < 0) { + sshc->sftp_file_index = + sftp_async_read_begin(sshc->sftp_file, (uint32_t)len); + if(sshc->sftp_file_index < 0) { *err = CURLE_RECV_ERROR; return -1; } - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: - conn->proto.sshc.sftp_recv_state = 1; - - nread = sftp_async_read(conn->proto.sshc.sftp_file, - mem, (uint32_t)len, - conn->proto.sshc.sftp_file_index); + sshc->sftp_recv_state = 1; + nread = sftp_async_read(sshc->sftp_file, mem, (uint32_t)len, + (uint32_t)sshc->sftp_file_index); - myssh_block2waitfor(conn, (nread == SSH_AGAIN)?TRUE:FALSE); + myssh_block2waitfor(conn, sshc, (nread == SSH_AGAIN)); if(nread == SSH_AGAIN) { *err = CURLE_AGAIN; @@ -2631,7 +2766,7 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, return -1; } - conn->proto.sshc.sftp_recv_state = 0; + sshc->sftp_recv_state = 0; return nread; default: @@ -2640,12 +2775,11 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, } } -static void sftp_quote(struct Curl_easy *data) +static void sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) { const char *cp; - struct connectdata *conn = data->conn; - struct SSHPROTO *protop = data->req.p.ssh; - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result; /* @@ -2657,7 +2791,7 @@ static void sftp_quote(struct Curl_easy *data) /* if a command starts with an asterisk, which a legal SFTP command never can, the command will be allowed to fail without it causing any aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + is successful, whatever the server responds. */ if(cmd[0] == '*') { cmd++; @@ -2666,15 +2800,14 @@ static void sftp_quote(struct Curl_easy *data) if(strcasecompare("pwd", cmd)) { /* output debug output if that is requested */ - char *tmp = aprintf("257 \"%s\" is current directory.\n", - protop->path); + char *tmp = aprintf("257 \"%s\" is current directory.\n", sshp->path); if(!tmp) { sshc->actualcode = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; return; } - Curl_debug(data, CURLINFO_HEADER_OUT, (char *) "PWD\n", 4); + Curl_debug(data, CURLINFO_HEADER_OUT, "PWD\n", 4); Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); /* this sends an FTP-like "header" to the header callback so that the @@ -2683,12 +2816,12 @@ static void sftp_quote(struct Curl_easy *data) result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); free(tmp); if(result) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; } else - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); return; } @@ -2699,7 +2832,7 @@ static void sftp_quote(struct Curl_easy *data) cp = strchr(cmd, ' '); if(!cp) { failf(data, "Syntax error in SFTP command. Supply parameter(s)"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; @@ -2715,23 +2848,23 @@ static void sftp_quote(struct Curl_easy *data) failf(data, "Out of memory"); else failf(data, "Syntax error: Bad first parameter"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; } /* - * SFTP is a binary protocol, so we don't send text commands + * SFTP is a binary protocol, so we do not send text commands * to the server. Instead, we scan for commands used by * OpenSSH's sftp program and call the appropriate libssh * functions. */ - if(strncasecompare(cmd, "chgrp ", 6) || - strncasecompare(cmd, "chmod ", 6) || - strncasecompare(cmd, "chown ", 6) || - strncasecompare(cmd, "atime ", 6) || - strncasecompare(cmd, "mtime ", 6)) { + if(!strncmp(cmd, "chgrp ", 6) || + !strncmp(cmd, "chmod ", 6) || + !strncmp(cmd, "chown ", 6) || + !strncmp(cmd, "atime ", 6) || + !strncmp(cmd, "mtime ", 6)) { /* attribute change */ /* sshc->quote_path1 contains the mode to set */ @@ -2744,17 +2877,17 @@ static void sftp_quote(struct Curl_easy *data) failf(data, "Syntax error in chgrp/chmod/chown/atime/mtime: " "Bad second parameter"); Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; } sshc->quote_attrs = NULL; - state(data, SSH_SFTP_QUOTE_STAT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_STAT); return; } - if(strncasecompare(cmd, "ln ", 3) || - strncasecompare(cmd, "symlink ", 8)) { + if(!strncmp(cmd, "ln ", 3) || + !strncmp(cmd, "symlink ", 8)) { /* symbolic linking */ /* sshc->quote_path1 is the source */ /* get the destination */ @@ -2765,20 +2898,20 @@ static void sftp_quote(struct Curl_easy *data) else failf(data, "Syntax error in ln/symlink: Bad second parameter"); Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; } - state(data, SSH_SFTP_QUOTE_SYMLINK); + myssh_state(data, sshc, SSH_SFTP_QUOTE_SYMLINK); return; } - else if(strncasecompare(cmd, "mkdir ", 6)) { + else if(!strncmp(cmd, "mkdir ", 6)) { /* create dir */ - state(data, SSH_SFTP_QUOTE_MKDIR); + myssh_state(data, sshc, SSH_SFTP_QUOTE_MKDIR); return; } - else if(strncasecompare(cmd, "rename ", 7)) { + else if(!strncmp(cmd, "rename ", 7)) { /* rename file */ /* first param is the source path */ /* second param is the dest. path */ @@ -2789,26 +2922,26 @@ static void sftp_quote(struct Curl_easy *data) else failf(data, "Syntax error in rename: Bad second parameter"); Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; } - state(data, SSH_SFTP_QUOTE_RENAME); + myssh_state(data, sshc, SSH_SFTP_QUOTE_RENAME); return; } - else if(strncasecompare(cmd, "rmdir ", 6)) { + else if(!strncmp(cmd, "rmdir ", 6)) { /* delete dir */ - state(data, SSH_SFTP_QUOTE_RMDIR); + myssh_state(data, sshc, SSH_SFTP_QUOTE_RMDIR); return; } - else if(strncasecompare(cmd, "rm ", 3)) { - state(data, SSH_SFTP_QUOTE_UNLINK); + else if(!strncmp(cmd, "rm ", 3)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_UNLINK); return; } #ifdef HAS_STATVFS_SUPPORT - else if(strncasecompare(cmd, "statvfs ", 8)) { - state(data, SSH_SFTP_QUOTE_STATVFS); + else if(!strncmp(cmd, "statvfs ", 8)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_STATVFS); return; } #endif @@ -2816,22 +2949,21 @@ static void sftp_quote(struct Curl_easy *data) failf(data, "Unknown SFTP command"); Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; } -static void sftp_quote_stat(struct Curl_easy *data) +static void sftp_quote_stat(struct Curl_easy *data, + struct ssh_conn *sshc) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; char *cmd = sshc->quote_item->data; sshc->acceptfail = FALSE; /* if a command starts with an asterisk, which a legal SFTP command never can, the command will be allowed to fail without it causing any aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + is successful, whatever the server responds. */ if(cmd[0] == '*') { cmd++; @@ -2851,59 +2983,63 @@ static void sftp_quote_stat(struct Curl_easy *data) Curl_safefree(sshc->quote_path2); failf(data, "Attempt to get SFTP stats failed: %d", sftp_get_error(sshc->sftp_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } /* Now set the new attributes... */ - if(strncasecompare(cmd, "chgrp", 5)) { - sshc->quote_attrs->gid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); + if(!strncmp(cmd, "chgrp", 5)) { + const char *p = sshc->quote_path1; + curl_off_t gid; + (void)curlx_str_number(&p, &gid, UINT_MAX); + sshc->quote_attrs->gid = (uint32_t)gid; if(sshc->quote_attrs->gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { + !sshc->acceptfail) { Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); failf(data, "Syntax error: chgrp gid not a number"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; } - else if(strncasecompare(cmd, "chmod", 5)) { - mode_t perms; - perms = (mode_t)strtoul(sshc->quote_path1, NULL, 8); - /* permissions are octal */ - if(perms == 0 && !ISDIGIT(sshc->quote_path1[0])) { + else if(!strncmp(cmd, "chmod", 5)) { + curl_off_t perms; + const char *p = sshc->quote_path1; + if(curlx_str_octal(&p, &perms, 07777)) { Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); failf(data, "Syntax error: chmod permissions not a number"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } - sshc->quote_attrs->permissions = perms; + sshc->quote_attrs->permissions = (mode_t)perms; sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_PERMISSIONS; } - else if(strncasecompare(cmd, "chown", 5)) { - sshc->quote_attrs->uid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); + else if(!strncmp(cmd, "chown", 5)) { + const char *p = sshc->quote_path1; + curl_off_t uid; + (void)curlx_str_number(&p, &uid, UINT_MAX); if(sshc->quote_attrs->uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { + !sshc->acceptfail) { Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); failf(data, "Syntax error: chown uid not a number"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; } - else if(strncasecompare(cmd, "atime", 5) || - strncasecompare(cmd, "mtime", 5)) { + else if(!strncmp(cmd, "atime", 5) || + !strncmp(cmd, "mtime", 5)) { time_t date = Curl_getdate_capped(sshc->quote_path1); bool fail = FALSE; if(date == -1) { @@ -2919,12 +3055,12 @@ static void sftp_quote_stat(struct Curl_easy *data) if(fail) { Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } - if(strncasecompare(cmd, "atime", 5)) + if(!strncmp(cmd, "atime", 5)) sshc->quote_attrs->atime = (uint32_t)date; else /* mtime */ sshc->quote_attrs->mtime = (uint32_t)date; @@ -2933,7 +3069,7 @@ static void sftp_quote_stat(struct Curl_easy *data) } /* Now send the completed structure... */ - state(data, SSH_SFTP_QUOTE_SETSTAT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_SETSTAT); return; } diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c index 14c2784fe3c..c16b3ac3886 100644 --- a/Utilities/cmcurl/lib/vssh/libssh2.c +++ b/Utilities/cmcurl/lib/vssh/libssh2.c @@ -24,15 +24,12 @@ /* #define CURL_LIBSSH2_DEBUG */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_LIBSSH2 #include -#include -#include - #ifdef HAVE_FCNTL_H #include #endif @@ -43,9 +40,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -55,57 +49,45 @@ #endif #include -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "progress.h" -#include "transfer.h" -#include "escape.h" -#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "../urldata.h" +#include "../sendf.h" +#include "../hostip.h" +#include "../progress.h" +#include "../transfer.h" +#include "../escape.h" +#include "../http.h" /* for HTTP proxy tunnel stuff */ #include "ssh.h" -#include "url.h" -#include "speedcheck.h" -#include "getinfo.h" -#include "strdup.h" -#include "strcase.h" -#include "vtls/vtls.h" -#include "cfilters.h" -#include "connect.h" -#include "inet_ntop.h" -#include "parsedate.h" /* for the week day and month names */ -#include "sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "strtoofft.h" -#include "multiif.h" -#include "select.h" -#include "warnless.h" +#include "../url.h" +#include "../speedcheck.h" +#include "../getinfo.h" +#include "../strdup.h" +#include "../strcase.h" +#include "../vtls/vtls.h" +#include "../cfilters.h" +#include "../connect.h" +#include "../inet_ntop.h" +#include "../parsedate.h" /* for the week day and month names */ +#include "../sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "../multiif.h" +#include "../select.h" +#include "../curlx/warnless.h" #include "curl_path.h" - -#include /* for base64 encoding/decoding */ -#include - +#include "../curlx/strparse.h" +#include "../curlx/base64.h" /* for base64 encoding/decoding */ +#include "../curl_sha256.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -#if LIBSSH2_VERSION_NUM >= 0x010206 -/* libssh2_sftp_statvfs and friends were added in 1.2.6 */ -#define HAS_STATVFS_SUPPORT 1 -#endif - -#define sftp_libssh2_realpath(s,p,t,m) \ - libssh2_sftp_symlink_ex((s), (p), curlx_uztoui(strlen(p)), \ - (t), (m), LIBSSH2_SFTP_REALPATH) +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* Local functions: */ static const char *sftp_libssh2_strerror(unsigned long err); -#ifdef CURL_LIBSSH2_DEBUG static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc); static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc); static LIBSSH2_FREE_FUNC(my_libssh2_free); -#endif -static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data); +static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, + struct ssh_conn *sshc); static CURLcode ssh_connect(struct Curl_easy *data, bool *done); static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done); static CURLcode ssh_do(struct Curl_easy *data, bool *done); @@ -124,7 +106,8 @@ static int ssh_getsock(struct Curl_easy *data, struct connectdata *conn, static CURLcode ssh_setup_connection(struct Curl_easy *data, struct connectdata *conn); static void ssh_attach(struct Curl_easy *data, struct connectdata *conn); - +static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, + bool block); /* * SCP protocol handler. */ @@ -143,9 +126,11 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ ssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SCP, /* protocol */ CURLPROTO_SCP, /* family */ @@ -172,9 +157,11 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ ssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SFTP, /* protocol */ CURLPROTO_SFTP, /* family */ @@ -206,7 +193,8 @@ kbd_callback(const char *name, int name_len, const char *instruction, if(num_prompts == 1) { struct connectdata *conn = data->conn; responses[0].text = strdup(conn->passwd); - responses[0].length = curlx_uztoui(strlen(conn->passwd)); + responses[0].length = + responses[0].text == NULL ? 0 : curlx_uztoui(strlen(conn->passwd)); } (void)prompts; } /* kbd_callback */ @@ -284,37 +272,37 @@ static CURLcode libssh2_session_error_to_CURLE(int err) return CURLE_SSH; } -#ifdef CURL_LIBSSH2_DEBUG +/* These functions are made to use the libcurl memory functions - NOT the + debugmem functions, as that leads us to trigger on libssh2 memory leaks + that are not ours to care for */ static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc) { (void)abstract; /* arg not used */ - return malloc(count); + return Curl_cmalloc(count); } static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc) { (void)abstract; /* arg not used */ - return realloc(ptr, count); + return Curl_crealloc(ptr, count); } static LIBSSH2_FREE_FUNC(my_libssh2_free) { (void)abstract; /* arg not used */ if(ptr) /* ssh2 agent sometimes call free with null ptr */ - free(ptr); + Curl_cfree(ptr); } -#endif - /* * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ -static void state(struct Curl_easy *data, sshstate nowstate) +static void myssh_state(struct Curl_easy *data, + struct ssh_conn *sshc, + sshstate nowstate) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -381,20 +369,18 @@ static void state(struct Curl_easy *data, sshstate nowstate) }; /* a precaution to make sure the lists are in sync */ - DEBUGASSERT(sizeof(names)/sizeof(names[0]) == SSH_LAST); + DEBUGASSERT(CURL_ARRAYSIZE(names) == SSH_LAST); if(sshc->state != nowstate) { infof(data, "SFTP %p state change from %s to %s", (void *)sshc, names[sshc->state], names[nowstate]); } #endif - + (void)data; sshc->state = nowstate; } - -#ifdef HAVE_LIBSSH2_KNOWNHOST_API -static int sshkeycallback(struct Curl_easy *easy, +static int sshkeycallback(CURL *easy, const struct curl_khkey *knownkey, /* known */ const struct curl_khkey *foundkey, /* found */ enum curl_khmatch match, @@ -406,42 +392,12 @@ static int sshkeycallback(struct Curl_easy *easy, (void)clientp; /* we only allow perfect matches, and we reject everything else */ - return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE; + return (match != CURLKHMATCH_OK) ? CURLKHSTAT_REJECT : CURLKHSTAT_FINE; } -#endif - -/* - * Earlier libssh2 versions didn't have the ability to seek to 64bit positions - * with 32bit size_t. - */ -#ifdef HAVE_LIBSSH2_SFTP_SEEK64 -#define SFTP_SEEK(x,y) libssh2_sftp_seek64(x, (libssh2_uint64_t)y) -#else -#define SFTP_SEEK(x,y) libssh2_sftp_seek(x, (size_t)y) -#endif - -/* - * Earlier libssh2 versions didn't do SCP properly beyond 32bit sizes on 32bit - * architectures so we check of the necessary function is present. - */ -#ifndef HAVE_LIBSSH2_SCP_SEND64 -#define SCP_SEND(a,b,c,d) libssh2_scp_send_ex(a, b, (int)(c), (size_t)d, 0, 0) -#else -#define SCP_SEND(a,b,c,d) libssh2_scp_send64(a, b, (int)(c), \ - (libssh2_uint64_t)d, 0, 0) -#endif -/* - * libssh2 1.2.8 fixed the problem with 32bit ints used for sockets on win64. - */ -#ifdef HAVE_LIBSSH2_SESSION_HANDSHAKE -#define session_startup(x,y) libssh2_session_handshake(x, y) -#else -#define session_startup(x,y) libssh2_session_startup(x, (int)y) -#endif -static int convert_ssh2_keytype(int sshkeytype) +static enum curl_khtype convert_ssh2_keytype(int sshkeytype) { - int keytype = CURLKHTYPE_UNKNOWN; + enum curl_khtype keytype = CURLKHTYPE_UNKNOWN; switch(sshkeytype) { case LIBSSH2_HOSTKEY_TYPE_RSA: keytype = CURLKHTYPE_RSA; @@ -473,18 +429,17 @@ static int convert_ssh2_keytype(int sshkeytype) return keytype; } -static CURLcode ssh_knownhost(struct Curl_easy *data) +static CURLcode ssh_knownhost(struct Curl_easy *data, + struct ssh_conn *sshc) { int sshkeytype = 0; size_t keylen = 0; int rc = 0; CURLcode result = CURLE_OK; -#ifdef HAVE_LIBSSH2_KNOWNHOST_API if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - /* we're asked to verify the host against a file */ + /* we are asked to verify the host against a file */ struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; struct libssh2_knownhost *host = NULL; const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, &keylen, &sshkeytype); @@ -493,8 +448,8 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) if(remotekey) { /* - * A subject to figure out is what host name we need to pass in here. - * What host name does OpenSSH store in its file if an IDN name is + * A subject to figure out is what hostname we need to pass in here. + * What hostname does OpenSSH store in its file if an IDN name is * used? */ enum curl_khmatch keymatch; @@ -532,7 +487,7 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) break; #endif default: - infof(data, "unsupported key type, can't check knownhosts"); + infof(data, "unsupported key type, cannot check knownhosts"); keybit = 0; break; } @@ -540,29 +495,19 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) /* no check means failure! */ rc = CURLKHSTAT_REJECT; else { -#ifdef HAVE_LIBSSH2_KNOWNHOST_CHECKP keycheck = libssh2_knownhost_checkp(sshc->kh, conn->host.name, - (conn->remote_port != PORT_SSH)? - conn->remote_port:-1, + (conn->remote_port != PORT_SSH) ? + conn->remote_port : -1, remotekey, keylen, LIBSSH2_KNOWNHOST_TYPE_PLAIN| LIBSSH2_KNOWNHOST_KEYENC_RAW| keybit, &host); -#else - keycheck = libssh2_knownhost_check(sshc->kh, - conn->host.name, - remotekey, keylen, - LIBSSH2_KNOWNHOST_TYPE_PLAIN| - LIBSSH2_KNOWNHOST_KEYENC_RAW| - keybit, - &host); -#endif infof(data, "SSH host check: %d, key: %s", keycheck, - (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)? - host->key:""); + (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) ? + host->key : ""); /* setup 'knownkey' */ if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { @@ -585,11 +530,11 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) keymatch = (enum curl_khmatch)keycheck; /* Ask the callback how to behave */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); rc = func(data, knownkeyp, /* from the knownhosts file */ &foundkey, /* from the remote host */ keymatch, data->set.ssh_keyfunc_userp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } } else @@ -598,25 +543,23 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) switch(rc) { default: /* unknown return codes will equal reject */ - /* FALLTHROUGH */ case CURLKHSTAT_REJECT: - state(data, SSH_SESSION_FREE); - /* FALLTHROUGH */ + myssh_state(data, sshc, SSH_SESSION_FREE); + FALLTHROUGH(); case CURLKHSTAT_DEFER: /* DEFER means bail out but keep the SSH_HOSTKEY state */ - result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + result = CURLE_PEER_FAILED_VERIFICATION; break; case CURLKHSTAT_FINE_REPLACE: - /* remove old host+key that doesn't match */ + /* remove old host+key that does not match */ if(host) libssh2_knownhost_del(sshc->kh, host); - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLKHSTAT_FINE: - /* FALLTHROUGH */ case CURLKHSTAT_FINE_ADD_TO_FILE: /* proceed */ if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { - /* the found host+key didn't match but has been told to be fine + /* the found host+key did not match but has been told to be fine anyway so we add it in memory */ int addrc = libssh2_knownhost_add(sshc->kh, conn->host.name, NULL, @@ -644,23 +587,19 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) break; } } -#else /* HAVE_LIBSSH2_KNOWNHOST_API */ - (void)data; -#endif return result; } -static CURLcode ssh_check_fingerprint(struct Curl_easy *data) +static CURLcode ssh_check_fingerprint(struct Curl_easy *data, + struct ssh_conn *sshc) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; const char *pubkey_sha256 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]; infof(data, "SSH MD5 public key: %s", - pubkey_md5 != NULL ? pubkey_md5 : "NULL"); + pubkey_md5 != NULL ? pubkey_md5 : "NULL"); infof(data, "SSH SHA256 public key: %s", - pubkey_sha256 != NULL ? pubkey_sha256 : "NULL"); + pubkey_sha256 != NULL ? pubkey_sha256 : "NULL"); if(pubkey_sha256) { const char *fingerprint = NULL; @@ -670,7 +609,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) size_t b64_pos = 0; #ifdef LIBSSH2_HOSTKEY_HASH_SHA256 - /* The fingerprint points to static storage (!), don't free() it. */ + /* The fingerprint points to static storage (!), do not free() it. */ fingerprint = libssh2_hostkey_hash(sshc->ssh_session, LIBSSH2_HOSTKEY_HASH_SHA256); #else @@ -689,25 +628,22 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) failf(data, "Denied establishing ssh session: sha256 fingerprint " "not available"); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } /* The length of fingerprint is 32 bytes for SHA256. * See libssh2_hostkey_hash documentation. */ - if(Curl_base64_encode(fingerprint, 32, &fingerprint_b64, - &fingerprint_b64_len) != CURLE_OK) { - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + if(curlx_base64_encode(fingerprint, 32, &fingerprint_b64, + &fingerprint_b64_len) != CURLE_OK) { + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } if(!fingerprint_b64) { failf(data, "sha256 fingerprint could not be encoded"); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } infof(data, "SSH SHA256 fingerprint: %s", fingerprint_b64); @@ -732,9 +668,8 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) "Denied establishing ssh session: mismatch sha256 fingerprint. " "Remote %s is not equal to %s", fingerprint_b64, pubkey_sha256); free(fingerprint_b64); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } free(fingerprint_b64); @@ -744,13 +679,13 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) if(pubkey_md5) { char md5buffer[33]; - const char *fingerprint = NULL; + const char *fingerprint; fingerprint = libssh2_hostkey_hash(sshc->ssh_session, LIBSSH2_HOSTKEY_HASH_MD5); if(fingerprint) { - /* The fingerprint points to static storage (!), don't free() it. */ + /* The fingerprint points to static storage (!), do not free() it. */ int i; for(i = 0; i < 16; i++) { msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); @@ -772,9 +707,8 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) "Denied establishing ssh session: md5 fingerprint " "not available"); } - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } infof(data, "MD5 checksum match"); } @@ -788,26 +722,24 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, &keylen, &sshkeytype); if(remotekey) { - int keytype = convert_ssh2_keytype(sshkeytype); - Curl_set_in_callback(data, true); + enum curl_khtype keytype = convert_ssh2_keytype(sshkeytype); + Curl_set_in_callback(data, TRUE); rc = data->set.ssh_hostkeyfunc(data->set.ssh_hostkeyfunc_userp, - keytype, remotekey, keylen); - Curl_set_in_callback(data, false); + (int)keytype, remotekey, keylen); + Curl_set_in_callback(data, FALSE); if(rc!= CURLKHMATCH_OK) { - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } } else { - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } return CURLE_OK; } else { - return ssh_knownhost(data); + return ssh_knownhost(data, sshc); } } else { @@ -820,12 +752,11 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) * ssh_force_knownhost_key_type() will check the known hosts file and try to * force a specific public key type from the server if an entry is found. */ -static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) +static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, + struct ssh_conn *sshc) { CURLcode result = CURLE_OK; -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - #ifdef LIBSSH2_KNOWNHOST_KEY_ED25519 static const char * const hostkey_method_ssh_ed25519 = "ssh-ed25519"; @@ -851,14 +782,15 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) const char *hostkey_method = NULL; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; struct libssh2_knownhost* store = NULL; const char *kh_name_end = NULL; size_t kh_name_size = 0; int port = 0; - bool found = false; + bool found = FALSE; - if(sshc->kh && !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { + if(sshc->kh && + !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] && + !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]) { /* lets try to find our host in the known hosts file */ while(!libssh2_knownhost_get(sshc->kh, &store, store)) { /* For non-standard ports, the name will be enclosed in */ @@ -877,24 +809,25 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) kh_name_size = strlen(store->name) - 1 - strlen(kh_name_end); if(strncmp(store->name + 1, conn->host.name, kh_name_size) == 0) { - found = true; + found = TRUE; break; } } } else if(strcmp(store->name, conn->host.name) == 0) { - found = true; + found = TRUE; break; } } else { - found = true; + found = TRUE; break; } } } if(found) { + int rc; infof(data, "Found host %s in %s", conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]); @@ -920,12 +853,10 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) break; #endif case LIBSSH2_KNOWNHOST_KEY_SSHRSA: -#ifdef HAVE_LIBSSH2_VERSION if(libssh2_version(0x010900)) /* since 1.9.0 libssh2_session_method_pref() works as expected */ hostkey_method = hostkey_method_ssh_rsa_all; else -#endif /* old libssh2 which cannot correctly remove unsupported methods due * to bug in src/kex.c or does not support the new methods anyways. */ @@ -944,9 +875,15 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) } infof(data, "Set \"%s\" as SSH hostkey type", hostkey_method); - result = libssh2_session_error_to_CURLE( - libssh2_session_method_pref( - sshc->ssh_session, LIBSSH2_METHOD_HOSTKEY, hostkey_method)); + rc = libssh2_session_method_pref(sshc->ssh_session, + LIBSSH2_METHOD_HOSTKEY, hostkey_method); + if(rc) { + char *errmsg = NULL; + int errlen; + libssh2_session_last_error(sshc->ssh_session, &errmsg, &errlen, 0); + failf(data, "libssh2: %s", errmsg); + result = libssh2_session_error_to_CURLE(rc); + } } else { infof(data, "Did not find host %s in %s", @@ -954,1270 +891,1997 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) } } -#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ - return result; } -/* - * ssh_statemach_act() runs the SSH state machine as far as it can without - * blocking and without reaching the end. The data the pointer 'block' points - * to will be set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN - * meaning it wants to be called again when the socket is ready - */ - -static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) +static CURLcode sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) { + const char *cp; CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct SSHPROTO *sshp = data->req.p.ssh; - struct ssh_conn *sshc = &conn->proto.sshc; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int rc = LIBSSH2_ERROR_NONE; - int ssherr; - unsigned long sftperr; - int seekerr = CURL_SEEKFUNC_OK; - size_t readdir_len; - *block = 0; /* we're not blocking by default */ - do { - switch(sshc->state) { - case SSH_INIT: - sshc->secondCreateDirs = 0; - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_OK; + /* + * Support some of the "FTP" commands + * + * 'sshc->quote_item' is already verified to be non-NULL before it + * switched to this state. + */ + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } - /* Set libssh2 to non-blocking, since everything internally is - non-blocking */ - libssh2_session_set_blocking(sshc->ssh_session, 0); + if(strcasecompare("pwd", cmd)) { + /* output debug output if that is requested */ + char *tmp = aprintf("257 \"%s\" is current directory.\n", sshp->path); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + Curl_debug(data, CURLINFO_HEADER_OUT, "PWD\n", 4); + Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); + + /* this sends an FTP-like "header" to the header callback so that the + current directory can be read very similar to how it is read when + using ordinary FTP. */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(!result) + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return result; + } - result = ssh_force_knownhost_key_type(data); - if(result) { - state(data, SSH_SESSION_FREE); - sshc->actualcode = result; - break; - } + /* + * the arguments following the command must be separated from the + * command with a space so we can check for it unconditionally + */ + cp = strchr(cmd, ' '); + if(!cp) { + failf(data, "Syntax error command '%s', missing parameter", cmd); + return result; + } - state(data, SSH_S_STARTUP); - /* FALLTHROUGH */ + /* + * also, every command takes at least one argument so we get that + * first argument right now + */ + result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error: Bad first parameter to '%s'", cmd); + return result; + } - case SSH_S_STARTUP: - rc = session_startup(sshc->ssh_session, sock); + /* + * SFTP is a binary protocol, so we do not send text commands to the server. + * Instead, we scan for commands used by OpenSSH's sftp program and call the + * appropriate libssh2 functions. + */ + if(!strncmp(cmd, "chgrp ", 6) || + !strncmp(cmd, "chmod ", 6) || + !strncmp(cmd, "chown ", 6) || + !strncmp(cmd, "atime ", 6) || + !strncmp(cmd, "mtime ", 6)) { + /* attribute change */ + + /* sshc->quote_path1 contains the mode to set */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error in %s: Bad second parameter", cmd); + Curl_safefree(sshc->quote_path1); + return result; + } + memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + myssh_state(data, sshc, SSH_SFTP_QUOTE_STAT); + return result; + } + if(!strncmp(cmd, "ln ", 3) || + !strncmp(cmd, "symlink ", 8)) { + /* symbolic linking */ + /* sshc->quote_path1 is the source */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error in ln/symlink: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + return result; + } + myssh_state(data, sshc, SSH_SFTP_QUOTE_SYMLINK); + return result; + } + else if(!strncmp(cmd, "mkdir ", 6)) { + /* create dir */ + myssh_state(data, sshc, SSH_SFTP_QUOTE_MKDIR); + return result; + } + else if(!strncmp(cmd, "rename ", 7)) { + /* rename file */ + /* first param is the source path */ + /* second param is the dest. path */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error in rename: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + return result; + } + myssh_state(data, sshc, SSH_SFTP_QUOTE_RENAME); + return result; + } + else if(!strncmp(cmd, "rmdir ", 6)) { + /* delete dir */ + myssh_state(data, sshc, SSH_SFTP_QUOTE_RMDIR); + return result; + } + else if(!strncmp(cmd, "rm ", 3)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_UNLINK); + return result; + } + else if(!strncmp(cmd, "statvfs ", 8)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_STATVFS); + return result; + } + + failf(data, "Unknown SFTP command"); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + return CURLE_QUOTE_ERROR; +} + +static CURLcode +sftp_upload_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + unsigned long flags; + + /* + * NOTE!!! libssh2 requires that the destination path is a full path + * that includes the destination file and name OR ends in a "/" + * If this is not done the destination file will be named the + * same name as the last directory in the path. + */ + + if(data->state.resume_from) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + if(data->state.resume_from < 0) { + int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); if(rc == LIBSSH2_ERROR_EAGAIN) { - break; + *blockp = TRUE; + return CURLE_OK; } if(rc) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); - failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg); - - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; + data->state.resume_from = 0; } - - state(data, SSH_HOSTKEY); - - /* FALLTHROUGH */ - case SSH_HOSTKEY: - /* - * Before we authenticate we should check the hostkey's fingerprint - * against our known hosts. How that is handled (reading from file, - * whatever) is up to us. - */ - result = ssh_check_fingerprint(data); - if(!result) - state(data, SSH_AUTHLIST); - /* ssh_check_fingerprint sets state appropriately on error */ - break; - - case SSH_AUTHLIST: - /* - * Figure out authentication methods - * NB: As soon as we have provided a username to an openssh server we - * must never change it later. Thus, always specify the correct username - * here, even though the libssh2 docs kind of indicate that it should be - * possible to get a 'generic' list (not user-specific) of authentication - * methods, presumably with a blank username. That won't work in my - * experience. - * So always specify it here. - */ - sshc->authlist = libssh2_userauth_list(sshc->ssh_session, - conn->user, - curlx_uztoui(strlen(conn->user))); - - if(!sshc->authlist) { - if(libssh2_userauth_authenticated(sshc->ssh_session)) { - sshc->authed = TRUE; - infof(data, "SSH user accepted with no authentication"); - state(data, SSH_AUTH_DONE); - break; - } - ssherr = libssh2_session_last_errno(sshc->ssh_session); - if(ssherr == LIBSSH2_ERROR_EAGAIN) - rc = LIBSSH2_ERROR_EAGAIN; - else { - state(data, SSH_SESSION_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssherr); + else { + curl_off_t size = attrs.filesize; + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; } - break; + data->state.resume_from = attrs.filesize; } - infof(data, "SSH authentication methods available: %s", - sshc->authlist); - - state(data, SSH_AUTH_PKEY_INIT); - break; + } + } - case SSH_AUTH_PKEY_INIT: - /* - * Check the supported auth types in the order I feel is most secure - * with the requested type of authentication - */ - sshc->authed = FALSE; + if(data->set.remote_append) + /* Try to open for append, but create if nonexisting */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND; + else + /* Clear file before writing (normal behavior) */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; + + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + flags, (long)data->set.new_file_perms, + LIBSSH2_SFTP_OPENFILE); + + if(!sshc->sftp_handle) { + CURLcode result; + unsigned long sftperr; + int rc = libssh2_session_last_errno(sshc->ssh_session); + + if(LIBSSH2_ERROR_EAGAIN == rc) { + *blockp = TRUE; + return CURLE_OK; + } - if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && - (strstr(sshc->authlist, "publickey") != NULL)) { - bool out_of_memory = FALSE; + if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) + /* only when there was an SFTP protocol error can we extract + the sftp error! */ + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + else + sftperr = LIBSSH2_FX_OK; /* not an sftp error at all */ - sshc->rsa_pub = sshc->rsa = NULL; + if(sshc->secondCreateDirs) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + failf(data, "Creating the dir/file failed: %s", + sftp_libssh2_strerror(sftperr)); + return sftp_libssh2_error_to_CURLE(sftperr); + } + if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) || + (sftperr == LIBSSH2_FX_FAILURE) || + (sftperr == LIBSSH2_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(sshp->path) > 1))) { + /* try to create the path remotely */ + sshc->secondCreateDirs = 1; + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_INIT); + return CURLE_OK; + } + myssh_state(data, sshc, SSH_SFTP_CLOSE); + result = sftp_libssh2_error_to_CURLE(sftperr); + if(!result) { + /* Sometimes, for some reason libssh2_sftp_last_error() returns zero + even though libssh2_sftp_open() failed previously! We need to + work around that! */ + result = CURLE_SSH; + sftperr = LIBSSH2_FX_OK; + } + failf(data, "Upload failed: %s (%lu/%d)", + sftperr != LIBSSH2_FX_OK ? + sftp_libssh2_strerror(sftperr) : "ssh error", + sftperr, rc); + return result; + } - if(data->set.str[STRING_SSH_PRIVATE_KEY]) - sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); - else { - /* To ponder about: should really the lib be messing about with the - HOME environment variable etc? */ - char *home = curl_getenv("HOME"); - - /* If no private key file is specified, try some common paths. */ - if(home) { - /* Try ~/.ssh first. */ - sshc->rsa = aprintf("%s/.ssh/id_rsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - sshc->rsa = aprintf("%s/.ssh/id_dsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - } - } - free(home); - } - if(!out_of_memory && !sshc->rsa) { - /* Nothing found; try the current dir. */ - sshc->rsa = strdup("id_rsa"); - if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - sshc->rsa = strdup("id_dsa"); - if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - /* Out of guesses. Set to the empty string to avoid - * surprising info messages. */ - sshc->rsa = strdup(""); - } - } - } - } + /* If we have a restart point then we need to seek to the correct + position. */ + if(data->state.resume_from > 0) { + int seekerr = CURL_SEEKFUNC_OK; + /* Let's read off the proper amount of bytes from the input. */ + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); + } - /* - * Unless the user explicitly specifies a public key file, let - * libssh2 extract the public key from the private key file. - * This is done by simply passing sshc->rsa_pub = NULL. - */ - if(data->set.str[STRING_SSH_PUBLIC_KEY] - /* treat empty string the same way as NULL */ - && data->set.str[STRING_SSH_PUBLIC_KEY][0]) { - sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]); - if(!sshc->rsa_pub) - out_of_memory = TRUE; - } + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread; + Curl_set_in_callback(data, TRUE); + actuallyread = data->state.fread_func(scratch, 1, + readthisamountnow, + data->state.in); + Curl_set_in_callback(data, FALSE); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed < data->state.resume_from); + } - if(out_of_memory || !sshc->rsa) { - Curl_safefree(sshc->rsa); - Curl_safefree(sshc->rsa_pub); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } + /* now, decrease the size of the read */ + if(data->state.infilesize > 0) { + data->state.infilesize -= data->state.resume_from; + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } - sshc->passphrase = data->set.ssl.key_passwd; - if(!sshc->passphrase) - sshc->passphrase = ""; + libssh2_sftp_seek64(sshc->sftp_handle, + (libssh2_uint64_t)data->state.resume_from); + } + if(data->state.infilesize > 0) { + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + /* upload data */ + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - if(sshc->rsa_pub) - infof(data, "Using SSH public key file '%s'", sshc->rsa_pub); - infof(data, "Using SSH private key file '%s'", sshc->rsa); + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->sockfd = data->conn->writesockfd; - state(data, SSH_AUTH_PKEY); - } - else { - state(data, SSH_AUTH_PASS_INIT); - } - break; + /* store this original bitmask setup to use later on if we cannot + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; - case SSH_AUTH_PKEY: - /* The function below checks if the files exists, no need to stat() here. - */ - rc = libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), - sshc->rsa_pub, - sshc->rsa, sshc->passphrase); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 sftp send function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_OUT; - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); + /* since we do not really wait for anything at this point, we want the + state machine to move on as soon as possible so we set a very short + timeout here */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized SSH public key authentication"); - state(data, SSH_AUTH_DONE); - } - else { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "SSH public key authentication failed: %s", err_msg); - state(data, SSH_AUTH_PASS_INIT); - rc = 0; /* clear rc and continue */ - } - break; + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; +} - case SSH_AUTH_PASS_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && - (strstr(sshc->authlist, "password") != NULL)) { - state(data, SSH_AUTH_PASS); - } - else { - state(data, SSH_AUTH_HOST_INIT); - rc = 0; /* clear rc and continue */ - } - break; +/* make sure that this does not collide with an actual libssh2 error code */ +#define ERROR_LIBBSH2 1 - case SSH_AUTH_PASS: - rc = libssh2_userauth_password_ex(sshc->ssh_session, conn->user, - curlx_uztoui(strlen(conn->user)), - conn->passwd, - curlx_uztoui(strlen(conn->passwd)), - NULL); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized password authentication"); - state(data, SSH_AUTH_DONE); - } - else { - state(data, SSH_AUTH_HOST_INIT); - rc = 0; /* clear rc and continue */ - } - break; +static CURLcode ssh_state_pkey_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Check the supported auth types in the order I feel is most secure + * with the requested type of authentication + */ + sshc->authed = FALSE; - case SSH_AUTH_HOST_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && - (strstr(sshc->authlist, "hostbased") != NULL)) { - state(data, SSH_AUTH_HOST); - } - else { - state(data, SSH_AUTH_AGENT_INIT); - } - break; + if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && + (strstr(sshc->authlist, "publickey") != NULL)) { + bool out_of_memory = FALSE; - case SSH_AUTH_HOST: - state(data, SSH_AUTH_AGENT_INIT); - break; + sshc->rsa_pub = sshc->rsa = NULL; - case SSH_AUTH_AGENT_INIT: -#ifdef HAVE_LIBSSH2_AGENT_API - if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) - && (strstr(sshc->authlist, "publickey") != NULL)) { - - /* Connect to the ssh-agent */ - /* The agent could be shared by a curl thread i believe - but nothing obvious as keys can be added/removed at any time */ - if(!sshc->ssh_agent) { - sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session); - if(!sshc->ssh_agent) { - infof(data, "Could not create agent object"); - - state(data, SSH_AUTH_KEY_INIT); - break; + if(data->set.str[STRING_SSH_PRIVATE_KEY]) + sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); + else { + /* To ponder about: should really the lib be messing about with the + HOME environment variable etc? */ + char *home = curl_getenv("HOME"); + struct_stat sbuf; + + /* If no private key file is specified, try some common paths. */ + if(home) { + /* Try ~/.ssh first. */ + sshc->rsa = aprintf("%s/.ssh/id_rsa", home); + if(!sshc->rsa) + out_of_memory = TRUE; + else if(stat(sshc->rsa, &sbuf)) { + free(sshc->rsa); + sshc->rsa = aprintf("%s/.ssh/id_dsa", home); + if(!sshc->rsa) + out_of_memory = TRUE; + else if(stat(sshc->rsa, &sbuf)) { + Curl_safefree(sshc->rsa); } } - - rc = libssh2_agent_connect(sshc->ssh_agent); - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - if(rc < 0) { - infof(data, "Failure connecting to agent"); - state(data, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } - else { - state(data, SSH_AUTH_AGENT_LIST); + free(home); + } + if(!out_of_memory && !sshc->rsa) { + /* Nothing found; try the current dir. */ + sshc->rsa = strdup("id_rsa"); + if(sshc->rsa && stat(sshc->rsa, &sbuf)) { + free(sshc->rsa); + sshc->rsa = strdup("id_dsa"); + if(sshc->rsa && stat(sshc->rsa, &sbuf)) { + free(sshc->rsa); + /* Out of guesses. Set to the empty string to avoid + * surprising info messages. */ + sshc->rsa = strdup(""); + } } } - else -#endif /* HAVE_LIBSSH2_AGENT_API */ - state(data, SSH_AUTH_KEY_INIT); - break; + } - case SSH_AUTH_AGENT_LIST: -#ifdef HAVE_LIBSSH2_AGENT_API - rc = libssh2_agent_list_identities(sshc->ssh_agent); + /* + * Unless the user explicitly specifies a public key file, let + * libssh2 extract the public key from the private key file. + * This is done by simply passing sshc->rsa_pub = NULL. + */ + if(data->set.str[STRING_SSH_PUBLIC_KEY] + /* treat empty string the same way as NULL */ + && data->set.str[STRING_SSH_PUBLIC_KEY][0]) { + sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]); + if(!sshc->rsa_pub) + out_of_memory = TRUE; + } - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - if(rc < 0) { - infof(data, "Failure requesting identities to agent"); - state(data, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } - else { - state(data, SSH_AUTH_AGENT); - sshc->sshagent_prev_identity = NULL; - } -#endif - break; + if(out_of_memory || !sshc->rsa) { + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->rsa_pub); + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_OUT_OF_MEMORY; + } - case SSH_AUTH_AGENT: -#ifdef HAVE_LIBSSH2_AGENT_API - /* as prev_identity evolves only after an identity user auth finished we - can safely request it again as long as EAGAIN is returned here or by - libssh2_agent_userauth */ - rc = libssh2_agent_get_identity(sshc->ssh_agent, - &sshc->sshagent_identity, - sshc->sshagent_prev_identity); - if(rc == LIBSSH2_ERROR_EAGAIN) - break; + sshc->passphrase = data->set.ssl.key_passwd; + if(!sshc->passphrase) + sshc->passphrase = ""; - if(rc == 0) { - rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, - sshc->sshagent_identity); + if(sshc->rsa_pub) + infof(data, "Using SSH public key file '%s'", sshc->rsa_pub); + infof(data, "Using SSH private key file '%s'", sshc->rsa); - if(rc < 0) { - if(rc != LIBSSH2_ERROR_EAGAIN) { - /* tried and failed? go to next identity */ - sshc->sshagent_prev_identity = sshc->sshagent_identity; - } - break; - } - } + myssh_state(data, sshc, SSH_AUTH_PKEY); + } + else { + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); + } + return 0; +} - if(rc < 0) - infof(data, "Failure requesting identities to agent"); - else if(rc == 1) - infof(data, "No identity would match"); +static CURLcode +sftp_quote_stat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; - if(rc == LIBSSH2_ERROR_NONE) { - sshc->authed = TRUE; - infof(data, "Agent based authentication successful"); - state(data, SSH_AUTH_DONE); - } - else { - state(data, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } -#endif - break; + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any aborts or + cancels etc. It will cause libcurl to act as if the command is + successful, whatever the server responds. */ - case SSH_AUTH_KEY_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) - && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { - state(data, SSH_AUTH_KEY); - } - else { - state(data, SSH_AUTH_DONE); - } - break; + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } - case SSH_AUTH_KEY: - /* Authentication failed. Continue with keyboard-interactive now. */ - rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), - &kbd_callback); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized keyboard interactive authentication"); - } - state(data, SSH_AUTH_DONE); - break; + if(!!strncmp(cmd, "chmod", 5)) { + /* Since chown and chgrp only set owner OR group but libssh2 wants to set + * them both at once, we need to obtain the current ownership first. This + * takes an extra protocol round trip. + */ + int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_STAT, + &sshp->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + *blockp = TRUE; + return CURLE_OK; + } + if(rc && !sshc->acceptfail) { /* get those attributes */ + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Attempt to get SFTP stats failed: %s", + sftp_libssh2_strerror(sftperr)); + goto fail; + } + } + + /* Now set the new attributes... */ + if(!strncmp(cmd, "chgrp", 5)) { + const char *p = sshc->quote_path1; + curl_off_t gid; + (void)curlx_str_number(&p, &gid, ULONG_MAX); + sshp->quote_attrs.gid = (unsigned long)gid; + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshp->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + failf(data, "Syntax error: chgrp gid not a number"); + goto fail; + } + } + else if(!strncmp(cmd, "chmod", 5)) { + curl_off_t perms; + const char *p = sshc->quote_path1; + /* permissions are octal */ + if(curlx_str_octal(&p, &perms, 07777)) { + failf(data, "Syntax error: chmod permissions not a number"); + goto fail; + } + + sshp->quote_attrs.permissions = (unsigned long)perms; + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; + } + else if(!strncmp(cmd, "chown", 5)) { + const char *p = sshc->quote_path1; + curl_off_t uid; + (void)curlx_str_number(&p, &uid, ULONG_MAX); + sshp->quote_attrs.uid = (unsigned long)uid; + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshp->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + failf(data, "Syntax error: chown uid not a number"); + goto fail; + } + } + else if(!strncmp(cmd, "atime", 5) || + !strncmp(cmd, "mtime", 5)) { + time_t date = Curl_getdate_capped(sshc->quote_path1); + bool fail = FALSE; + + if(date == -1) { + failf(data, "incorrect date format for %.*s", 5, cmd); + fail = TRUE; + } +#if SIZEOF_TIME_T > SIZEOF_LONG + if(date > 0xffffffff) { + /* if 'long' cannot old >32-bit, this date cannot be sent */ + failf(data, "date overflow"); + fail = TRUE; + } +#endif + if(fail) + goto fail; + if(!strncmp(cmd, "atime", 5)) + sshp->quote_attrs.atime = (unsigned long)date; + else /* mtime */ + sshp->quote_attrs.mtime = (unsigned long)date; + + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME; + } + + /* Now send the completed structure... */ + myssh_state(data, sshc, SSH_SFTP_QUOTE_SETSTAT); + return CURLE_OK; +fail: + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + return CURLE_QUOTE_ERROR; +} + +static CURLcode +sftp_download_stat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + LIBSSH2_SFTP_ATTRIBUTES attrs; + int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + *blockp = TRUE; + return CURLE_OK; + } + if(rc || + !(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) || + (attrs.filesize == 0)) { + /* + * libssh2_sftp_open() did not return an error, so maybe the server + * just does not support stat() + * OR the server does not return a file size with a stat() + * OR file size is 0 + */ + data->req.size = -1; + data->req.maxdownload = -1; + Curl_pgrsSetDownloadSize(data, -1); + } + else { + curl_off_t size = attrs.filesize; + + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(data->state.use_range) { + curl_off_t from, to; + const char *p = data->state.range; + int to_t, from_t; + + from_t = curlx_str_number(&p, &from, CURL_OFF_T_MAX); + if(from_t == STRE_OVERFLOW) + return CURLE_RANGE_ERROR; + curlx_str_passblanks(&p); + (void)curlx_str_single(&p, '-'); + + to_t = curlx_str_numblanks(&p, &to); + if(to_t == STRE_OVERFLOW) + return CURLE_RANGE_ERROR; + if((to_t == STRE_NO_NUM) /* no "to" value given */ + || (to >= size)) { + to = size - 1; + } + if(from_t) { + /* from is relative to end of file */ + from = size - to; + to = size - 1; + } + if(from > size) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(from > to) { + from = to; + size = 0; + } + else { + if((to - from) == CURL_OFF_T_MAX) + return CURLE_RANGE_ERROR; + size = to - from + 1; + } + + libssh2_sftp_seek64(sshc->sftp_handle, (libssh2_uint64_t)from); + } + data->req.size = size; + data->req.maxdownload = size; + Curl_pgrsSetDownloadSize(data, size); + } + + /* We can resume if we can seek to the resume position */ + if(data->state.resume_from) { + if(data->state.resume_from < 0) { + /* We are supposed to download the last abs(from) bytes */ + if((curl_off_t)attrs.filesize < -data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", + data->state.resume_from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* download from where? */ + data->state.resume_from += attrs.filesize; + } + else { + if((curl_off_t)attrs.filesize < data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", + data->state.resume_from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + } + /* Now store the number of bytes we are expected to download */ + data->req.size = attrs.filesize - data->state.resume_from; + data->req.maxdownload = attrs.filesize - data->state.resume_from; + Curl_pgrsSetDownloadSize(data, + attrs.filesize - data->state.resume_from); + libssh2_sftp_seek64(sshc->sftp_handle, + (libssh2_uint64_t)data->state.resume_from); + } + + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; + } + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->writesockfd = data->conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_IN; + myssh_state(data, sshc, SSH_STOP); + + return CURLE_OK; +} + +static CURLcode sftp_readdir(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + CURLcode result = CURLE_OK; + int rc = libssh2_sftp_readdir_ex(sshc->sftp_handle, + sshp->readdir_filename, CURL_PATH_MAX, + sshp->readdir_longentry, CURL_PATH_MAX, + &sshp->readdir_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + *blockp = TRUE; + return result; + } + if(rc > 0) { + size_t readdir_len = (size_t) rc; + sshp->readdir_filename[readdir_len] = '\0'; + + if(data->set.list_only) { + result = Curl_client_write(data, CLIENTWRITE_BODY, + sshp->readdir_filename, + readdir_len); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(result) + return result; + } + else { + result = curlx_dyn_add(&sshp->readdir, sshp->readdir_longentry); + + if(!result) { + if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && + ((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFLNK)) { + result = curlx_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path, + sshp->readdir_filename); + myssh_state(data, sshc, SSH_SFTP_READDIR_LINK); + } + else { + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); + } + } + return result; + } + } + else if(!rc) { + myssh_state(data, sshc, SSH_SFTP_READDIR_DONE); + } + else { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + result = sftperr ? sftp_libssh2_error_to_CURLE(sftperr) : CURLE_SSH; + failf(data, "Could not open remote file for reading: %s :: %d", + sftp_libssh2_strerror(sftperr), + libssh2_session_last_errno(sshc->ssh_session)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + } + return result; +} + +static CURLcode ssh_state_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + CURLcode result; + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + + /* Set libssh2 to non-blocking, since everything internally is + non-blocking */ + libssh2_session_set_blocking(sshc->ssh_session, 0); + + result = ssh_force_knownhost_key_type(data, sshc); + if(result) + myssh_state(data, sshc, SSH_SESSION_FREE); + else + myssh_state(data, sshc, SSH_S_STARTUP); + return result; +} + +static CURLcode ssh_state_startup(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + int rc = libssh2_session_handshake(sshc->ssh_session, + conn->sock[FIRSTSOCKET]); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); + failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg); + + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_FAILED_INIT; + } + + myssh_state(data, sshc, SSH_HOSTKEY); + return CURLE_OK; +} + +static CURLcode ssh_state_hostkey(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Before we authenticate we should check the hostkey's fingerprint + * against our known hosts. How that is handled (reading from file, + * whatever) is up to us. + */ + CURLcode result = ssh_check_fingerprint(data, sshc); + if(!result) + myssh_state(data, sshc, SSH_AUTHLIST); + return result; +} + +static CURLcode ssh_state_authlist(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Figure out authentication methods + * NB: As soon as we have provided a username to an openssh server we + * must never change it later. Thus, always specify the correct username + * here, even though the libssh2 docs kind of indicate that it should be + * possible to get a 'generic' list (not user-specific) of authentication + * methods, presumably with a blank username. That will not work in my + * experience. + * So always specify it here. + */ + struct connectdata *conn = data->conn; + sshc->authlist = libssh2_userauth_list(sshc->ssh_session, + conn->user, + curlx_uztoui(strlen(conn->user))); + + if(!sshc->authlist) { + int rc; + if(libssh2_userauth_authenticated(sshc->ssh_session)) { + sshc->authed = TRUE; + infof(data, "SSH user accepted with no authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return CURLE_OK; + } + rc = libssh2_session_last_errno(sshc->ssh_session); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + myssh_state(data, sshc, SSH_SESSION_FREE); + return libssh2_session_error_to_CURLE(rc); + } + infof(data, "SSH authentication methods available: %s", + sshc->authlist); + + myssh_state(data, sshc, SSH_AUTH_PKEY_INIT); + return CURLE_OK; +} + +static CURLcode ssh_state_auth_pkey(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* The function below checks if the files exists, no need to stat() here. + */ + struct connectdata *conn = data->conn; + int rc = + libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + sshc->rsa_pub, + sshc->rsa, sshc->passphrase); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized SSH public key authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + } + else { + char *err_msg = NULL; + char unknown[] = "Reason unknown (-1)"; + if(rc == -1) { + /* No error message has been set and the last set error message, if + any, is from a previous error so ignore it. #11837 */ + err_msg = unknown; + } + else { + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + } + infof(data, "SSH public key authentication failed: %s", err_msg); + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_pass_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && + (strstr(sshc->authlist, "password") != NULL)) { + myssh_state(data, sshc, SSH_AUTH_PASS); + } + else { + myssh_state(data, sshc, SSH_AUTH_HOST_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_pass(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + int rc = + libssh2_userauth_password_ex(sshc->ssh_session, conn->user, + curlx_uztoui(strlen(conn->user)), + conn->passwd, + curlx_uztoui(strlen(conn->passwd)), + NULL); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return CURLE_AGAIN; + } + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized password authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + } + else { + myssh_state(data, sshc, SSH_AUTH_HOST_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_host_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && + (strstr(sshc->authlist, "hostbased") != NULL)) { + myssh_state(data, sshc, SSH_AUTH_HOST); + } + else { + myssh_state(data, sshc, SSH_AUTH_AGENT_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_agent_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = 0; + if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) + && (strstr(sshc->authlist, "publickey") != NULL)) { + + /* Connect to the ssh-agent */ + /* The agent could be shared by a curl thread i believe + but nothing obvious as keys can be added/removed at any time */ + if(!sshc->ssh_agent) { + sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session); + if(!sshc->ssh_agent) { + infof(data, "Could not create agent object"); + + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + return CURLE_OK; + } + } + + rc = libssh2_agent_connect(sshc->ssh_agent); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + if(rc < 0) { + infof(data, "Failure connecting to agent"); + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + else { + myssh_state(data, sshc, SSH_AUTH_AGENT_LIST); + } + } + else + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + return CURLE_OK; +} + +static CURLcode ssh_state_auth_agent_list(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_agent_list_identities(sshc->ssh_agent); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + if(rc < 0) { + infof(data, "Failure requesting identities to agent"); + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + else { + myssh_state(data, sshc, SSH_AUTH_AGENT); + sshc->sshagent_prev_identity = NULL; + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_agent(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* as prev_identity evolves only after an identity user auth finished we + can safely request it again as long as EAGAIN is returned here or by + libssh2_agent_userauth */ + int rc = libssh2_agent_get_identity(sshc->ssh_agent, + &sshc->sshagent_identity, + sshc->sshagent_prev_identity); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc == 0) { + struct connectdata *conn = data->conn; + rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, + sshc->sshagent_identity); + + if(rc < 0) { + if(rc != LIBSSH2_ERROR_EAGAIN) { + /* tried and failed? go to next identity */ + sshc->sshagent_prev_identity = sshc->sshagent_identity; + } + return CURLE_OK; + } + } + + if(rc < 0) + infof(data, "Failure requesting identities to agent"); + else if(rc == 1) + infof(data, "No identity would match"); + + if(rc == LIBSSH2_ERROR_NONE) { + sshc->authed = TRUE; + infof(data, "Agent based authentication successful"); + myssh_state(data, sshc, SSH_AUTH_DONE); + } + else { + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_key_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) + && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { + myssh_state(data, sshc, SSH_AUTH_KEY); + } + else { + myssh_state(data, sshc, SSH_AUTH_DONE); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_key(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* Authentication failed. Continue with keyboard-interactive now. */ + struct connectdata *conn = data->conn; + int rc = + libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + &kbd_callback); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized keyboard interactive authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return CURLE_OK; + } + return CURLE_LOGIN_DENIED; +} + +static CURLcode ssh_state_auth_done(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + if(!sshc->authed) { + failf(data, "Authentication failure"); + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_LOGIN_DENIED; + } + + /* + * At this point we have an authenticated ssh session. + */ + infof(data, "Authentication complete"); + + Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ + + conn->sockfd = conn->sock[FIRSTSOCKET]; + conn->writesockfd = CURL_SOCKET_BAD; + + if(conn->handler->protocol == CURLPROTO_SFTP) { + myssh_state(data, sshc, SSH_SFTP_INIT); + return CURLE_OK; + } + infof(data, "SSH CONNECT phase done"); + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Start the libssh2 sftp session + */ + sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); + if(!sshc->sftp_session) { + char *err_msg = NULL; + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + failf(data, "Failure initializing sftp session: %s", err_msg); + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_FAILED_INIT; + } + myssh_state(data, sshc, SSH_SFTP_REALPATH); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_realpath(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* + * Get the "home" directory + */ + int rc = libssh2_sftp_symlink_ex(sshc->sftp_session, + ".", curlx_uztoui(strlen(".")), + sshp->readdir_filename, CURL_PATH_MAX, + LIBSSH2_SFTP_REALPATH); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc > 0) { + /* It seems that this string is not always null-terminated */ + sshp->readdir_filename[rc] = '\0'; + free(sshc->homedir); + sshc->homedir = strdup(sshp->readdir_filename); + if(!sshc->homedir) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return CURLE_OUT_OF_MEMORY; + } + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(sshc->homedir); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; + } + else { + /* Return the error type */ + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + CURLcode result; + if(sftperr) + result = sftp_libssh2_error_to_CURLE(sftperr); + else + /* in this case, the error was not in the SFTP level but for example + a time-out or similar */ + result = CURLE_SSH; + DEBUGF(infof(data, "error = %lu makes libcurl = %d", + sftperr, (int)result)); + myssh_state(data, sshc, SSH_STOP); + return result; + } + + /* This is the last step in the SFTP connect phase. Do note that while + we get the homedir here, we get the "workingpath" in the DO action + since the homedir will remain the same between request but the + working path will not. */ + DEBUGF(infof(data, "SSH CONNECT phase done")); + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + CURLcode result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); + if(result) { + myssh_state(data, sshc, SSH_STOP); + return result; + } + + if(data->set.quote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.quote; + myssh_state(data, sshc, SSH_SFTP_QUOTE); + } + else { + myssh_state(data, sshc, SSH_SFTP_GETINFO); + } + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_postquote_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if(data->set.postquote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.postquote; + myssh_state(data, sshc, SSH_SFTP_QUOTE); + } + else { + myssh_state(data, sshc, SSH_STOP); + } + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* Send quote commands */ + CURLcode result = sftp_quote(data, sshc, sshp); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + } + return result; +} + +static CURLcode ssh_state_sftp_next_quote(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + + sshc->quote_item = sshc->quote_item->next; + + if(sshc->quote_item) { + myssh_state(data, sshc, SSH_SFTP_QUOTE); + } + else { + if(sshc->nextstate != SSH_NO_STATE) { + myssh_state(data, sshc, sshc->nextstate); + sshc->nextstate = SSH_NO_STATE; + } + else { + myssh_state(data, sshc, SSH_SFTP_GETINFO); + } + } + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_stat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + CURLcode result = sftp_quote_stat(data, sshc, sshp, blockp); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + } + return result; +} + +static CURLcode ssh_state_sftp_quote_setstat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + int rc = + libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SETSTAT, + &sshp->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Attempt to set SFTP stats failed: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_symlink(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = + libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SYMLINK); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "symlink command failed: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_mkdir(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + (long)data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "mkdir command failed: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_rename(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = + libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_RENAME_OVERWRITE | + LIBSSH2_SFTP_RENAME_ATOMIC | + LIBSSH2_SFTP_RENAME_NATIVE); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "rename command failed: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_rmdir(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "rmdir command failed: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_unlink(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "rm command failed: %s", sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_statvfs(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + LIBSSH2_SFTP_STATVFS statvfs; + int rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + &statvfs); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "statvfs command failed: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + else if(rc == 0) { +#ifdef _MSC_VER +#define CURL_LIBSSH2_VFS_SIZE_MASK "I64u" +#else +#define CURL_LIBSSH2_VFS_SIZE_MASK "llu" +#endif + CURLcode result; + char *tmp = aprintf("statvfs:\n" + "f_bsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_frsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_blocks: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_bfree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_bavail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_files: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_ffree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_favail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_fsid: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_flag: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_namemax: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n", + statvfs.f_bsize, statvfs.f_frsize, + statvfs.f_blocks, statvfs.f_bfree, + statvfs.f_bavail, statvfs.f_files, + statvfs.f_ffree, statvfs.f_favail, + statvfs.f_fsid, statvfs.f_flag, + statvfs.f_namemax); + if(!tmp) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_OUT_OF_MEMORY; + } + + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return result; + } + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_create_dirs_mkdir(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* 'mode' - parameter is preliminary - default to 0644 */ + int rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + (long)data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + *sshc->slash_pos = '/'; + ++sshc->slash_pos; + if(rc < 0) { + /* + * Abort if failure was not that the dir already exists or the + * permission was denied (creation might succeed further down the + * path) - retry on unspecific FAILURE also + */ + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + if((sftperr != LIBSSH2_FX_FILE_ALREADY_EXISTS) && + (sftperr != LIBSSH2_FX_FAILURE) && + (sftperr != LIBSSH2_FX_PERMISSION_DENIED)) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return sftp_libssh2_error_to_CURLE(sftperr); + } + } + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_readdir_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + Curl_pgrsSetDownloadSize(data, -1); + if(data->req.no_body) { + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; + } + + /* + * This is a directory that we are trying to get, so produce a directory + * listing + */ + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + 0, 0, LIBSSH2_SFTP_OPENDIR); + if(!sshc->sftp_handle) { + unsigned long sftperr; + if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open directory for reading: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return sftp_libssh2_error_to_CURLE(sftperr); + } + myssh_state(data, sshc, SSH_SFTP_READDIR); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_readdir_link(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + CURLcode result; + int rc = + libssh2_sftp_symlink_ex(sshc->sftp_session, + curlx_dyn_ptr(&sshp->readdir_link), + (unsigned int) + curlx_dyn_len(&sshp->readdir_link), + sshp->readdir_filename, + CURL_PATH_MAX, LIBSSH2_SFTP_READLINK); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + curlx_dyn_free(&sshp->readdir_link); + + /* append filename and extra output */ + result = curlx_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename); + if(result) + myssh_state(data, sshc, SSH_SFTP_CLOSE); + else + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); + return result; +} + +static CURLcode ssh_state_scp_download_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + curl_off_t bytecount; + + /* + * We must check the remote file; if it is a directory no values will + * be set in sb + */ + + /* + * If support for >2GB files exists, use it. + */ + + /* get a fresh new channel from the ssh layer */ +#if LIBSSH2_VERSION_NUM < 0x010700 + struct stat sb; + memset(&sb, 0, sizeof(struct stat)); + sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, + sshp->path, &sb); +#else + libssh2_struct_stat sb; + memset(&sb, 0, sizeof(libssh2_struct_stat)); + sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, + sshp->path, &sb); +#endif + + if(!sshc->ssh_channel) { + int ssh_err; + char *err_msg = NULL; + + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; - case SSH_AUTH_DONE: - if(!sshc->authed) { - failf(data, "Authentication failure"); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_LOGIN_DENIED; - break; - } + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(data, "%s", err_msg); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); + return libssh2_session_error_to_CURLE(ssh_err); + } - /* - * At this point we have an authenticated ssh session. - */ - infof(data, "Authentication complete"); + /* download data */ + bytecount = (curl_off_t)sb.st_size; + data->req.maxdownload = (curl_off_t)sb.st_size; + Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE); - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->writesockfd = data->conn->sockfd; - conn->sockfd = sock; - conn->writesockfd = CURL_SOCKET_BAD; + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_IN; - if(conn->handler->protocol == CURLPROTO_SFTP) { - state(data, SSH_SFTP_INIT); - break; - } - infof(data, "SSH CONNECT phase done"); - state(data, SSH_STOP); - break; + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; +} - case SSH_SFTP_INIT: - /* - * Start the libssh2 sftp session - */ - sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); - if(!sshc->sftp_session) { - char *err_msg = NULL; - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } +static CURLcode ssh_state_sftp_close(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + int rc = 0; + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - failf(data, "Failure initializing sftp session: %s", err_msg); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; - } - state(data, SSH_SFTP_REALPATH); - break; + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); + } + sshc->sftp_handle = NULL; + } - case SSH_SFTP_REALPATH: - { - char tempHome[PATH_MAX]; + Curl_safefree(sshp->path); - /* - * Get the "home" directory - */ - rc = sftp_libssh2_realpath(sshc->sftp_session, ".", - tempHome, PATH_MAX-1); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc > 0) { - /* It seems that this string is not always NULL terminated */ - tempHome[rc] = '\0'; - sshc->homedir = strdup(tempHome); - if(!sshc->homedir) { - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - data->state.most_recent_ftp_entrypath = sshc->homedir; - } - else { - /* Return the error type */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - if(sftperr) - result = sftp_libssh2_error_to_CURLE(sftperr); - else - /* in this case, the error wasn't in the SFTP level but for example - a time-out or similar */ - result = CURLE_SSH; - sshc->actualcode = result; - DEBUGF(infof(data, "error = %lu makes libcurl = %d", - sftperr, (int)result)); - state(data, SSH_STOP); - break; - } + DEBUGF(infof(data, "SFTP DONE done")); + + /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT + After nextstate is executed, the control should come back to + SSH_SFTP_CLOSE to pass the correct result back */ + if(sshc->nextstate != SSH_NO_STATE && + sshc->nextstate != SSH_SFTP_CLOSE) { + myssh_state(data, sshc, sshc->nextstate); + sshc->nextstate = SSH_SFTP_CLOSE; + } + else + myssh_state(data, sshc, SSH_STOP); + + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_shutdown(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* during times we get here due to a broken transfer and then the + sftp_handle might not have been taken down so make sure that is done + before we proceed */ + int rc = 0; + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, + NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); } - /* This is the last step in the SFTP connect phase. Do note that while - we get the homedir here, we get the "workingpath" in the DO action - since the homedir will remain the same between request but the - working path will not. */ - DEBUGF(infof(data, "SSH CONNECT phase done")); - state(data, SSH_STOP); - break; + sshc->sftp_handle = NULL; + } + if(sshc->sftp_session) { + rc = libssh2_sftp_shutdown(sshc->sftp_session); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; - case SSH_SFTP_QUOTE_INIT: + if(rc < 0) { + infof(data, "Failed to stop libssh2 sftp subsystem"); + } + sshc->sftp_session = NULL; + } - result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); - if(result) { - sshc->actualcode = result; - state(data, SSH_STOP); + Curl_safefree(sshc->homedir); + + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_download_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* + * Work on getting the specified file + */ + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_FXF_READ, (long)data->set.new_file_perms, + LIBSSH2_SFTP_OPENFILE); + if(!sshc->sftp_handle) { + unsigned long sftperr; + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + return CURLE_AGAIN; + } + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open remote file for reading: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return sftp_libssh2_error_to_CURLE(sftperr); + } + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); + return CURLE_OK; +} + +static CURLcode ssh_state_scp_upload_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* + * libssh2 requires that the destination path is a full path that + * includes the destination file and name OR ends in a "/" . If this is + * not done the destination file will be named the same name as the last + * directory in the path. + */ + sshc->ssh_channel = + libssh2_scp_send64(sshc->ssh_session, sshp->path, + (int)data->set.new_file_perms, + (libssh2_int64_t)data->state.infilesize, 0, 0); + if(!sshc->ssh_channel) { + int ssh_err; + char *err_msg = NULL; + CURLcode result; + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(data, "%s", err_msg); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); + result = libssh2_session_error_to_CURLE(ssh_err); + + /* Map generic errors to upload failed */ + if(result == CURLE_SSH || + result == CURLE_REMOTE_FILE_NOT_FOUND) + result = CURLE_UPLOAD_FAILED; + return result; + } + + /* upload data */ + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->sockfd = data->conn->writesockfd; + + /* store this original bitmask setup to use later on if we cannot + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 scp send function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_OUT; + + myssh_state(data, sshc, SSH_STOP); + + return CURLE_OK; +} + +static CURLcode ssh_state_session_disconnect(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* during weird times when we have been prematurely aborted, the channel + is still alive when we reach this state and we MUST kill the channel + properly first */ + int rc = 0; + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_OK; + + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 scp subsystem: %d %s", + rc, err_msg); + } + sshc->ssh_channel = NULL; + } + + if(sshc->ssh_session) { + rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to disconnect libssh2 session: %d %s", + rc, err_msg); + } + } + + Curl_safefree(sshc->homedir); + + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_OK; +} +/* + * ssh_statemachine() runs the SSH state machine as far as it can without + * blocking and without reaching the end. The data the pointer 'block' points + * to will be set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN + * meaning it wants to be called again when the socket is ready + */ + +static CURLcode ssh_statemachine(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *block) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + *block = 0; /* we are not blocking by default */ + + do { + switch(sshc->state) { + case SSH_INIT: + result = ssh_state_init(data, sshc); + if(result) break; - } + FALLTHROUGH(); - if(data->set.quote) { - infof(data, "Sending quote commands"); - sshc->quote_item = data->set.quote; - state(data, SSH_SFTP_QUOTE); - } - else { - state(data, SSH_SFTP_GETINFO); - } - break; + case SSH_S_STARTUP: + result = ssh_state_startup(data, sshc); + if(result) + break; + FALLTHROUGH(); - case SSH_SFTP_POSTQUOTE_INIT: - if(data->set.postquote) { - infof(data, "Sending quote commands"); - sshc->quote_item = data->set.postquote; - state(data, SSH_SFTP_QUOTE); - } - else { - state(data, SSH_STOP); - } + case SSH_HOSTKEY: + result = ssh_state_hostkey(data, sshc); break; - case SSH_SFTP_QUOTE: - /* Send any quote commands */ - { - const char *cp; + case SSH_AUTHLIST: + result = ssh_state_authlist(data, sshc); + break; - /* - * Support some of the "FTP" commands - * - * 'sshc->quote_item' is already verified to be non-NULL before it - * switched to this state. - */ - char *cmd = sshc->quote_item->data; - sshc->acceptfail = FALSE; + case SSH_AUTH_PKEY_INIT: + result = ssh_state_pkey_init(data, sshc); + break; - /* if a command starts with an asterisk, which a legal SFTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + case SSH_AUTH_PKEY: + result = ssh_state_auth_pkey(data, sshc); + break; - if(cmd[0] == '*') { - cmd++; - sshc->acceptfail = TRUE; - } + case SSH_AUTH_PASS_INIT: + result = ssh_state_auth_pass_init(data, sshc); + break; - if(strcasecompare("pwd", cmd)) { - /* output debug output if that is requested */ - char *tmp = aprintf("257 \"%s\" is current directory.\n", - sshp->path); - if(!tmp) { - result = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - break; - } - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4); - Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); - - /* this sends an FTP-like "header" to the header callback so that the - current directory can be read very similar to how it is read when - using ordinary FTP. */ - result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); - free(tmp); - if(result) { - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - } - else - state(data, SSH_SFTP_NEXT_QUOTE); - break; - } - { - /* - * the arguments following the command must be separated from the - * command with a space so we can check for it unconditionally - */ - cp = strchr(cmd, ' '); - if(!cp) { - failf(data, "Syntax error command '%s', missing parameter", - cmd); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } + case SSH_AUTH_PASS: + result = ssh_state_auth_pass(data, sshc); + break; - /* - * also, every command takes at least one argument so we get that - * first argument right now - */ - result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error: Bad first parameter to '%s'", cmd); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } + case SSH_AUTH_HOST_INIT: + result = ssh_state_auth_host_init(data, sshc); + break; - /* - * SFTP is a binary protocol, so we don't send text commands - * to the server. Instead, we scan for commands used by - * OpenSSH's sftp program and call the appropriate libssh2 - * functions. - */ - if(strncasecompare(cmd, "chgrp ", 6) || - strncasecompare(cmd, "chmod ", 6) || - strncasecompare(cmd, "chown ", 6) || - strncasecompare(cmd, "atime ", 6) || - strncasecompare(cmd, "mtime ", 6)) { - /* attribute change */ - - /* sshc->quote_path1 contains the mode to set */ - /* get the destination */ - result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in %s: Bad second parameter", cmd); - Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); - state(data, SSH_SFTP_QUOTE_STAT); - break; - } - if(strncasecompare(cmd, "ln ", 3) || - strncasecompare(cmd, "symlink ", 8)) { - /* symbolic linking */ - /* sshc->quote_path1 is the source */ - /* get the destination */ - result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, - "Syntax error in ln/symlink: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(data, SSH_SFTP_QUOTE_SYMLINK); - break; - } - else if(strncasecompare(cmd, "mkdir ", 6)) { - /* create dir */ - state(data, SSH_SFTP_QUOTE_MKDIR); - break; - } - else if(strncasecompare(cmd, "rename ", 7)) { - /* rename file */ - /* first param is the source path */ - /* second param is the dest. path */ - result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in rename: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(data, SSH_SFTP_QUOTE_RENAME); - break; - } - else if(strncasecompare(cmd, "rmdir ", 6)) { - /* delete dir */ - state(data, SSH_SFTP_QUOTE_RMDIR); - break; - } - else if(strncasecompare(cmd, "rm ", 3)) { - state(data, SSH_SFTP_QUOTE_UNLINK); - break; - } -#ifdef HAS_STATVFS_SUPPORT - else if(strncasecompare(cmd, "statvfs ", 8)) { - state(data, SSH_SFTP_QUOTE_STATVFS); - break; - } -#endif + case SSH_AUTH_HOST: + myssh_state(data, sshc, SSH_AUTH_AGENT_INIT); + break; - failf(data, "Unknown SFTP command"); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - break; + case SSH_AUTH_AGENT_INIT: + result = ssh_state_auth_agent_init(data, sshc); + break; - case SSH_SFTP_NEXT_QUOTE: - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); + case SSH_AUTH_AGENT_LIST: + result = ssh_state_auth_agent_list(data, sshc); + break; - sshc->quote_item = sshc->quote_item->next; + case SSH_AUTH_AGENT: + result = ssh_state_auth_agent(data, sshc); + break; - if(sshc->quote_item) { - state(data, SSH_SFTP_QUOTE); - } - else { - if(sshc->nextstate != SSH_NO_STATE) { - state(data, sshc->nextstate); - sshc->nextstate = SSH_NO_STATE; - } - else { - state(data, SSH_SFTP_GETINFO); - } - } + case SSH_AUTH_KEY_INIT: + result = ssh_state_auth_key_init(data, sshc); break; - case SSH_SFTP_QUOTE_STAT: - { - char *cmd = sshc->quote_item->data; - sshc->acceptfail = FALSE; + case SSH_AUTH_KEY: + result = ssh_state_auth_key(data, sshc); + break; - /* if a command starts with an asterisk, which a legal SFTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + case SSH_AUTH_DONE: + result = ssh_state_auth_done(data, sshc); + break; - if(cmd[0] == '*') { - cmd++; - sshc->acceptfail = TRUE; - } + case SSH_SFTP_INIT: + result = ssh_state_sftp_init(data, sshc); + break; - if(!strncasecompare(cmd, "chmod", 5)) { - /* Since chown and chgrp only set owner OR group but libssh2 wants to - * set them both at once, we need to obtain the current ownership - * first. This takes an extra protocol round trip. - */ - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_STAT, - &sshp->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { /* get those attributes */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Attempt to get SFTP stats failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } + case SSH_SFTP_REALPATH: + result = ssh_state_sftp_realpath(data, sshc, sshp); + break; - /* Now set the new attributes... */ - if(strncasecompare(cmd, "chgrp", 5)) { - sshp->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - if(sshp->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chgrp gid not a number"); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "chmod", 5)) { - sshp->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8); - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; - /* permissions are octal */ - if(sshp->quote_attrs.permissions == 0 && - !ISDIGIT(sshc->quote_path1[0])) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chmod permissions not a number"); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "chown", 5)) { - sshp->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - if(sshp->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chown uid not a number"); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "atime", 5) || - strncasecompare(cmd, "mtime", 5)) { - time_t date = Curl_getdate_capped(sshc->quote_path1); - bool fail = FALSE; - - if(date == -1) { - failf(data, "incorrect date format for %.*s", 5, cmd); - fail = TRUE; - } -#if SIZEOF_TIME_T > SIZEOF_LONG - if(date > 0xffffffff) { - /* if 'long' can't old >32bit, this date cannot be sent */ - failf(data, "date overflow"); - fail = TRUE; - } -#endif - if(fail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - if(strncasecompare(cmd, "atime", 5)) - sshp->quote_attrs.atime = (unsigned long)date; - else /* mtime */ - sshp->quote_attrs.mtime = (unsigned long)date; + case SSH_SFTP_QUOTE_INIT: + result = ssh_state_sftp_quote_init(data, sshc, sshp); + break; - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME; - } + case SSH_SFTP_POSTQUOTE_INIT: + result = ssh_state_sftp_postquote_init(data, sshc); + break; - /* Now send the completed structure... */ - state(data, SSH_SFTP_QUOTE_SETSTAT); + case SSH_SFTP_QUOTE: + result = ssh_state_sftp_quote(data, sshc, sshp); break; - } - case SSH_SFTP_QUOTE_SETSTAT: - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_SETSTAT, - &sshp->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Attempt to set SFTP stats failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + case SSH_SFTP_NEXT_QUOTE: + result = ssh_state_sftp_next_quote(data, sshc); + break; + + case SSH_SFTP_QUOTE_STAT: + result = ssh_state_sftp_quote_stat(data, sshc, sshp, block); + break; + + case SSH_SFTP_QUOTE_SETSTAT: + result = ssh_state_sftp_quote_setstat(data, sshc, sshp); break; case SSH_SFTP_QUOTE_SYMLINK: - rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_SYMLINK); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "symlink command failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_symlink(data, sshc); break; case SSH_SFTP_QUOTE_MKDIR: - rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - data->set.new_directory_perms); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "mkdir command failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_mkdir(data, sshc); break; case SSH_SFTP_QUOTE_RENAME: - rc = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_RENAME_OVERWRITE | - LIBSSH2_SFTP_RENAME_ATOMIC | - LIBSSH2_SFTP_RENAME_NATIVE); - - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "rename command failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_rename(data, sshc); break; case SSH_SFTP_QUOTE_RMDIR: - rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1))); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "rmdir command failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_rmdir(data, sshc); break; case SSH_SFTP_QUOTE_UNLINK: - rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1))); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "rm command failed: %s", sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_unlink(data, sshc); break; -#ifdef HAS_STATVFS_SUPPORT case SSH_SFTP_QUOTE_STATVFS: - { - LIBSSH2_SFTP_STATVFS statvfs; - rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - &statvfs); - - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "statvfs command failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - else if(rc == 0) { - char *tmp = aprintf("statvfs:\n" - "f_bsize: %llu\n" "f_frsize: %llu\n" - "f_blocks: %llu\n" "f_bfree: %llu\n" - "f_bavail: %llu\n" "f_files: %llu\n" - "f_ffree: %llu\n" "f_favail: %llu\n" - "f_fsid: %llu\n" "f_flag: %llu\n" - "f_namemax: %llu\n", - statvfs.f_bsize, statvfs.f_frsize, - statvfs.f_blocks, statvfs.f_bfree, - statvfs.f_bavail, statvfs.f_files, - statvfs.f_ffree, statvfs.f_favail, - statvfs.f_fsid, statvfs.f_flag, - statvfs.f_namemax); - if(!tmp) { - result = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - break; - } - - result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); - free(tmp); - if(result) { - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - } - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_statvfs(data, sshc); break; - } -#endif + case SSH_SFTP_GETINFO: - { if(data->set.get_filetime) { - state(data, SSH_SFTP_FILETIME); + myssh_state(data, sshc, SSH_SFTP_FILETIME); } else { - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); } break; - } case SSH_SFTP_FILETIME: { LIBSSH2_SFTP_ATTRIBUTES attrs; - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_SFTP_STAT, &attrs); + int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc == 0) { - data->info.filetime = attrs.mtime; + data->info.filetime = (time_t)attrs.mtime; } - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); break; } case SSH_SFTP_TRANS_INIT: if(data->state.upload) - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); else { if(sshp->path[strlen(sshp->path)-1] == '/') - state(data, SSH_SFTP_READDIR_INIT); + myssh_state(data, sshc, SSH_SFTP_READDIR_INIT); else - state(data, SSH_SFTP_DOWNLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } break; case SSH_SFTP_UPLOAD_INIT: - { - unsigned long flags; - /* - * NOTE!!! libssh2 requires that the destination path is a full path - * that includes the destination file and name OR ends in a "/" - * If this is not done the destination file will be named the - * same name as the last directory in the path. - */ - - if(data->state.resume_from) { - LIBSSH2_SFTP_ATTRIBUTES attrs; - if(data->state.resume_from < 0) { - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc) { - data->state.resume_from = 0; - } - else { - curl_off_t size = attrs.filesize; - if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - data->state.resume_from = attrs.filesize; - } - } - } - - if(data->set.remote_append) - /* Try to open for append, but create if nonexisting */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND; - else if(data->state.resume_from > 0) - /* If we have restart position then open for append */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND; - else - /* Clear file before writing (normal behavior) */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; - - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - flags, data->set.new_file_perms, - LIBSSH2_SFTP_OPENFILE); - - if(!sshc->sftp_handle) { - rc = libssh2_session_last_errno(sshc->ssh_session); - - if(LIBSSH2_ERROR_EAGAIN == rc) - break; - - if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) - /* only when there was an SFTP protocol error can we extract - the sftp error! */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - else - sftperr = LIBSSH2_FX_OK; /* not an sftp error at all */ - - if(sshc->secondCreateDirs) { - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = sftperr != LIBSSH2_FX_OK ? - sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH; - failf(data, "Creating the dir/file failed: %s", - sftp_libssh2_strerror(sftperr)); - break; - } - if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) || - (sftperr == LIBSSH2_FX_FAILURE) || - (sftperr == LIBSSH2_FX_NO_SUCH_PATH)) && - (data->set.ftp_create_missing_dirs && - (strlen(sshp->path) > 1))) { - /* try to create the path remotely */ - rc = 0; /* clear rc and continue */ - sshc->secondCreateDirs = 1; - state(data, SSH_SFTP_CREATE_DIRS_INIT); - break; - } - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = sftperr != LIBSSH2_FX_OK ? - sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH; - if(!sshc->actualcode) { - /* Sometimes, for some reason libssh2_sftp_last_error() returns zero - even though libssh2_sftp_open() failed previously! We need to - work around that! */ - sshc->actualcode = CURLE_SSH; - sftperr = LIBSSH2_FX_OK; - } - failf(data, "Upload failed: %s (%lu/%d)", - sftperr != LIBSSH2_FX_OK ? - sftp_libssh2_strerror(sftperr):"ssh error", - sftperr, rc); - break; - } - - /* If we have a restart point then we need to seek to the correct - position. */ - if(data->state.resume_from > 0) { - /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_FTP_COULDNT_USE_REST; - } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread; - Curl_set_in_callback(data, true); - actuallyread = data->state.fread_func(data->state.buffer, 1, - readthisamountnow, - data->state.in); - Curl_set_in_callback(data, false); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Failed to read data"); - return CURLE_FTP_COULDNT_USE_REST; - } - } while(passed < data->state.resume_from); - } - - /* now, decrease the size of the read */ - if(data->state.infilesize > 0) { - data->state.infilesize -= data->state.resume_from; - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - } - - SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); - } - if(data->state.infilesize > 0) { - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - } - /* upload data */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - + result = sftp_upload_init(data, sshc, sshp, block); if(result) { - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result; - } - else { - /* store this original bitmask setup to use later on if we can't - figure out a "real" bitmask */ - sshc->orig_waitfor = data->req.keepon; - - /* we want to use the _sending_ function even when the socket turns - out readable as the underlying libssh2 sftp send function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; - - /* since we don't really wait for anything at this point, we want the - state machine to move on as soon as possible so we set a very short - timeout here */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); - - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; } break; - } case SSH_SFTP_CREATE_DIRS_INIT: if(strlen(sshp->path) > 1) { sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */ - state(data, SSH_SFTP_CREATE_DIRS); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); } else { - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); } break; @@ -2227,619 +2891,121 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) *sshc->slash_pos = 0; infof(data, "Creating directory '%s'", sshp->path); - state(data, SSH_SFTP_CREATE_DIRS_MKDIR); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR); break; } - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); break; case SSH_SFTP_CREATE_DIRS_MKDIR: - /* 'mode' - parameter is preliminary - default to 0644 */ - rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - data->set.new_directory_perms); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - *sshc->slash_pos = '/'; - ++sshc->slash_pos; - if(rc < 0) { - /* - * Abort if failure wasn't that the dir already exists or the - * permission was denied (creation might succeed further down the - * path) - retry on unspecific FAILURE also - */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - if((sftperr != LIBSSH2_FX_FILE_ALREADY_EXISTS) && - (sftperr != LIBSSH2_FX_FAILURE) && - (sftperr != LIBSSH2_FX_PERMISSION_DENIED)) { - result = sftp_libssh2_error_to_CURLE(sftperr); - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - rc = 0; /* clear rc and continue */ - } - state(data, SSH_SFTP_CREATE_DIRS); + result = ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp); break; case SSH_SFTP_READDIR_INIT: - Curl_pgrsSetDownloadSize(data, -1); - if(data->req.no_body) { - state(data, SSH_STOP); - break; - } - - /* - * This is a directory that we are trying to get, so produce a directory - * listing - */ - sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session, - sshp->path, - curlx_uztoui( - strlen(sshp->path)), - 0, 0, LIBSSH2_SFTP_OPENDIR); - if(!sshc->sftp_handle) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - failf(data, "Could not open directory for reading: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - sshp->readdir_filename = malloc(PATH_MAX + 1); - if(!sshp->readdir_filename) { - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - sshp->readdir_longentry = malloc(PATH_MAX + 1); - if(!sshp->readdir_longentry) { - Curl_safefree(sshp->readdir_filename); - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - Curl_dyn_init(&sshp->readdir, PATH_MAX * 2); - state(data, SSH_SFTP_READDIR); + result = ssh_state_sftp_readdir_init(data, sshc, sshp); break; case SSH_SFTP_READDIR: - rc = libssh2_sftp_readdir_ex(sshc->sftp_handle, - sshp->readdir_filename, - PATH_MAX, - sshp->readdir_longentry, - PATH_MAX, - &sshp->readdir_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc > 0) { - readdir_len = (size_t) rc; - sshp->readdir_filename[readdir_len] = '\0'; - - if(data->set.list_only) { - result = Curl_client_write(data, CLIENTWRITE_BODY, - sshp->readdir_filename, - readdir_len); - if(!result) - result = Curl_client_write(data, CLIENTWRITE_BODY, - (char *)"\n", 1); - if(result) { - state(data, SSH_STOP); - break; - } - /* since this counts what we send to the client, we include the - newline in this counter */ - data->req.bytecount += readdir_len + 1; - - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_IN, sshp->readdir_filename, - readdir_len); - Curl_debug(data, CURLINFO_DATA_IN, (char *)"\n", 1); - } - else { - result = Curl_dyn_add(&sshp->readdir, sshp->readdir_longentry); - - if(!result) { - if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && - ((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == - LIBSSH2_SFTP_S_IFLNK)) { - Curl_dyn_init(&sshp->readdir_link, PATH_MAX); - result = Curl_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path, - sshp->readdir_filename); - state(data, SSH_SFTP_READDIR_LINK); - if(!result) - break; - } - else { - state(data, SSH_SFTP_READDIR_BOTTOM); - break; - } - } - sshc->actualcode = result; - state(data, SSH_SFTP_CLOSE); - break; - } - } - else if(rc == 0) { - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); - state(data, SSH_SFTP_READDIR_DONE); - break; - } - else if(rc < 0) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result?result:CURLE_SSH; - failf(data, "Could not open remote file for reading: %s :: %d", - sftp_libssh2_strerror(sftperr), - libssh2_session_last_errno(sshc->ssh_session)); - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); - state(data, SSH_SFTP_CLOSE); - break; + result = sftp_readdir(data, sshc, sshp, block); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); } break; case SSH_SFTP_READDIR_LINK: - rc = - libssh2_sftp_symlink_ex(sshc->sftp_session, - Curl_dyn_ptr(&sshp->readdir_link), - (int)Curl_dyn_len(&sshp->readdir_link), - sshp->readdir_filename, - PATH_MAX, LIBSSH2_SFTP_READLINK); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - Curl_dyn_free(&sshp->readdir_link); - - /* append filename and extra output */ - result = Curl_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename); - - if(result) { - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result; - break; - } - - state(data, SSH_SFTP_READDIR_BOTTOM); + result = ssh_state_sftp_readdir_link(data, sshc, sshp); break; case SSH_SFTP_READDIR_BOTTOM: - result = Curl_dyn_addn(&sshp->readdir, "\n", 1); + result = curlx_dyn_addn(&sshp->readdir, "\n", 1); if(!result) result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&sshp->readdir), - Curl_dyn_len(&sshp->readdir)); + curlx_dyn_ptr(&sshp->readdir), + curlx_dyn_len(&sshp->readdir)); - if(!result) { - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_IN, - Curl_dyn_ptr(&sshp->readdir), - Curl_dyn_len(&sshp->readdir)); - data->req.bytecount += Curl_dyn_len(&sshp->readdir); - } if(result) { - Curl_dyn_free(&sshp->readdir); - state(data, SSH_STOP); + curlx_dyn_free(&sshp->readdir); + myssh_state(data, sshc, SSH_STOP); } else { - Curl_dyn_reset(&sshp->readdir); - state(data, SSH_SFTP_READDIR); + curlx_dyn_reset(&sshp->readdir); + myssh_state(data, sshc, SSH_SFTP_READDIR); } break; case SSH_SFTP_READDIR_DONE: - if(libssh2_sftp_closedir(sshc->sftp_handle) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - sshc->sftp_handle = NULL; - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); + if(libssh2_sftp_closedir(sshc->sftp_handle) == LIBSSH2_ERROR_EAGAIN) + result = CURLE_AGAIN; + else { + sshc->sftp_handle = NULL; - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - state(data, SSH_STOP); + /* no data to transfer */ + Curl_xfer_setup_nop(data); + myssh_state(data, sshc, SSH_STOP); + } break; case SSH_SFTP_DOWNLOAD_INIT: - /* - * Work on getting the specified file - */ - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_FXF_READ, data->set.new_file_perms, - LIBSSH2_SFTP_OPENFILE); - if(!sshc->sftp_handle) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - failf(data, "Could not open remote file for reading: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - state(data, SSH_SFTP_DOWNLOAD_STAT); + result = ssh_state_sftp_download_init(data, sshc, sshp); break; case SSH_SFTP_DOWNLOAD_STAT: - { - LIBSSH2_SFTP_ATTRIBUTES attrs; - - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc || - !(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) || - (attrs.filesize == 0)) { - /* - * libssh2_sftp_open() didn't return an error, so maybe the server - * just doesn't support stat() - * OR the server doesn't return a file size with a stat() - * OR file size is 0 - */ - data->req.size = -1; - data->req.maxdownload = -1; - Curl_pgrsSetDownloadSize(data, -1); - } - else { - curl_off_t size = attrs.filesize; - - if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(data->state.use_range) { - curl_off_t from, to; - char *ptr; - char *ptr2; - CURLofft to_t; - CURLofft from_t; - - from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); - if(from_t == CURL_OFFT_FLOW) - return CURLE_RANGE_ERROR; - while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) - ptr++; - to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); - if(to_t == CURL_OFFT_FLOW) - return CURLE_RANGE_ERROR; - if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ - || (to >= size)) { - to = size - 1; - } - if(from_t) { - /* from is relative to end of file */ - from = size - to; - to = size - 1; - } - if(from > size) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", from, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(from > to) { - from = to; - size = 0; - } - else { - size = to - from + 1; - } - - SFTP_SEEK(sshc->sftp_handle, from); - } - data->req.size = size; - data->req.maxdownload = size; - Curl_pgrsSetDownloadSize(data, size); - } - - /* We can resume if we can seek to the resume position */ - if(data->state.resume_from) { - if(data->state.resume_from < 0) { - /* We're supposed to download the last abs(from) bytes */ - if((curl_off_t)attrs.filesize < -data->state.resume_from) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* download from where? */ - data->state.resume_from += attrs.filesize; - } - else { - if((curl_off_t)attrs.filesize < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - } - /* Now store the number of bytes we are expected to download */ - data->req.size = attrs.filesize - data->state.resume_from; - data->req.maxdownload = attrs.filesize - data->state.resume_from; - Curl_pgrsSetDownloadSize(data, - attrs.filesize - data->state.resume_from); - SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + result = sftp_download_stat(data, sshc, sshp, block); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; } - } - - /* Setup the actual download */ - if(data->req.size == 0) { - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - infof(data, "File already completely downloaded"); - state(data, SSH_STOP); break; - } - Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->writesockfd = conn->sockfd; - - /* we want to use the _receiving_ function even when the socket turns - out writableable as the underlying libssh2 recv function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; - - if(result) { - /* this should never occur; the close state should be entered - at the time the error occurs */ - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result; - } - else { - state(data, SSH_STOP); - } - break; case SSH_SFTP_CLOSE: - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); - } - sshc->sftp_handle = NULL; - } - - Curl_safefree(sshp->path); - - DEBUGF(infof(data, "SFTP DONE done")); - - /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT - After nextstate is executed, the control should come back to - SSH_SFTP_CLOSE to pass the correct result back */ - if(sshc->nextstate != SSH_NO_STATE && - sshc->nextstate != SSH_SFTP_CLOSE) { - state(data, sshc->nextstate); - sshc->nextstate = SSH_SFTP_CLOSE; - } - else { - state(data, SSH_STOP); - result = sshc->actualcode; - } - break; - - case SSH_SFTP_SHUTDOWN: - /* during times we get here due to a broken transfer and then the - sftp_handle might not have been taken down so make sure that is done - before we proceed */ - - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, - NULL, 0); - infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); - } - sshc->sftp_handle = NULL; - } - if(sshc->sftp_session) { - rc = libssh2_sftp_shutdown(sshc->sftp_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - infof(data, "Failed to stop libssh2 sftp subsystem"); - } - sshc->sftp_session = NULL; - } - - Curl_safefree(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; + result = ssh_state_sftp_close(data, sshc, sshp); + break; - state(data, SSH_SESSION_DISCONNECT); + case SSH_SFTP_SHUTDOWN: + result = ssh_state_sftp_shutdown(data, sshc); break; case SSH_SCP_TRANS_INIT: result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); if(result) { - sshc->actualcode = result; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } if(data->state.upload) { if(data->state.infilesize < 0) { failf(data, "SCP requires a known file size for upload"); - sshc->actualcode = CURLE_UPLOAD_FAILED; - state(data, SSH_SCP_CHANNEL_FREE); + result = CURLE_UPLOAD_FAILED; + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; } - state(data, SSH_SCP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SCP_UPLOAD_INIT); } else { - state(data, SSH_SCP_DOWNLOAD_INIT); + myssh_state(data, sshc, SSH_SCP_DOWNLOAD_INIT); } break; case SSH_SCP_UPLOAD_INIT: - /* - * libssh2 requires that the destination path is a full path that - * includes the destination file and name OR ends in a "/" . If this is - * not done the destination file will be named the same name as the last - * directory in the path. - */ - sshc->ssh_channel = - SCP_SEND(sshc->ssh_session, sshp->path, data->set.new_file_perms, - data->state.infilesize); - if(!sshc->ssh_channel) { - int ssh_err; - char *err_msg = NULL; - - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(data, "%s", err_msg); - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); - /* Map generic errors to upload failed */ - if(sshc->actualcode == CURLE_SSH || - sshc->actualcode == CURLE_REMOTE_FILE_NOT_FOUND) - sshc->actualcode = CURLE_UPLOAD_FAILED; - break; - } - - /* upload data */ - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - - if(result) { - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = result; - } - else { - /* store this original bitmask setup to use later on if we can't - figure out a "real" bitmask */ - sshc->orig_waitfor = data->req.keepon; - - /* we want to use the _sending_ function even when the socket turns - out readable as the underlying libssh2 scp send function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; - - state(data, SSH_STOP); - } + result = ssh_state_scp_upload_init(data, sshc, sshp); break; case SSH_SCP_DOWNLOAD_INIT: - { - curl_off_t bytecount; - - /* - * We must check the remote file; if it is a directory no values will - * be set in sb - */ - - /* - * If support for >2GB files exists, use it. - */ - - /* get a fresh new channel from the ssh layer */ -#if LIBSSH2_VERSION_NUM < 0x010700 - struct stat sb; - memset(&sb, 0, sizeof(struct stat)); - sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, - sshp->path, &sb); -#else - libssh2_struct_stat sb; - memset(&sb, 0, sizeof(libssh2_struct_stat)); - sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, - sshp->path, &sb); -#endif - - if(!sshc->ssh_channel) { - int ssh_err; - char *err_msg = NULL; - - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(data, "%s", err_msg); - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); - break; - } - - /* download data */ - bytecount = (curl_off_t)sb.st_size; - data->req.maxdownload = (curl_off_t)sb.st_size; - Curl_setup_transfer(data, FIRSTSOCKET, bytecount, FALSE, -1); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->writesockfd = conn->sockfd; - - /* we want to use the _receiving_ function even when the socket turns - out writableable as the underlying libssh2 recv function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; - - if(result) { - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = result; - } - else - state(data, SSH_STOP); - } - break; + result = ssh_state_scp_download_init(data, sshc, sshp); + break; case SSH_SCP_DONE: if(data->state.upload) - state(data, SSH_SCP_SEND_EOF); + myssh_state(data, sshc, SSH_SCP_SEND_EOF); else - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_SEND_EOF: if(sshc->ssh_channel) { - rc = libssh2_channel_send_eof(sshc->ssh_channel); + int rc = libssh2_channel_send_eof(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc) { @@ -2850,13 +3016,14 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) rc, err_msg); } } - state(data, SSH_SCP_WAIT_EOF); + myssh_state(data, sshc, SSH_SCP_WAIT_EOF); break; case SSH_SCP_WAIT_EOF: if(sshc->ssh_channel) { - rc = libssh2_channel_wait_eof(sshc->ssh_channel); + int rc = libssh2_channel_wait_eof(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc) { @@ -2866,13 +3033,14 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) infof(data, "Failed to get channel EOF: %d %s", rc, err_msg); } } - state(data, SSH_SCP_WAIT_CLOSE); + myssh_state(data, sshc, SSH_SCP_WAIT_CLOSE); break; case SSH_SCP_WAIT_CLOSE: if(sshc->ssh_channel) { - rc = libssh2_channel_wait_closed(sshc->ssh_channel); + int rc = libssh2_channel_wait_closed(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc) { @@ -2882,13 +3050,14 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) infof(data, "Channel failed to close: %d %s", rc, err_msg); } } - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_CHANNEL_FREE: if(sshc->ssh_channel) { - rc = libssh2_channel_free(sshc->ssh_channel); + int rc = libssh2_channel_free(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc < 0) { @@ -2901,142 +3070,38 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) sshc->ssh_channel = NULL; } DEBUGF(infof(data, "SCP DONE phase complete")); -#if 0 /* PREV */ - state(data, SSH_SESSION_DISCONNECT); -#endif - state(data, SSH_STOP); - result = sshc->actualcode; + myssh_state(data, sshc, SSH_STOP); break; case SSH_SESSION_DISCONNECT: - /* during weird times when we've been prematurely aborted, the channel - is still alive when we reach this state and we MUST kill the channel - properly first */ - if(sshc->ssh_channel) { - rc = libssh2_channel_free(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to free libssh2 scp subsystem: %d %s", - rc, err_msg); - } - sshc->ssh_channel = NULL; - } - - if(sshc->ssh_session) { - rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to disconnect libssh2 session: %d %s", - rc, err_msg); - } - } - - Curl_safefree(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; - - state(data, SSH_SESSION_FREE); + result = ssh_state_session_disconnect(data, sshc); break; case SSH_SESSION_FREE: -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - if(sshc->kh) { - libssh2_knownhost_free(sshc->kh); - sshc->kh = NULL; - } -#endif - -#ifdef HAVE_LIBSSH2_AGENT_API - if(sshc->ssh_agent) { - rc = libssh2_agent_disconnect(sshc->ssh_agent); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to disconnect from libssh2 agent: %d %s", - rc, err_msg); - } - libssh2_agent_free(sshc->ssh_agent); - sshc->ssh_agent = NULL; - - /* NB: there is no need to free identities, they are part of internal - agent stuff */ - sshc->sshagent_identity = NULL; - sshc->sshagent_prev_identity = NULL; - } -#endif - - if(sshc->ssh_session) { - rc = libssh2_session_free(sshc->ssh_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg); - } - sshc->ssh_session = NULL; - } - - /* worst-case scenario cleanup */ - - DEBUGASSERT(sshc->ssh_session == NULL); - DEBUGASSERT(sshc->ssh_channel == NULL); - DEBUGASSERT(sshc->sftp_session == NULL); - DEBUGASSERT(sshc->sftp_handle == NULL); -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - DEBUGASSERT(sshc->kh == NULL); -#endif -#ifdef HAVE_LIBSSH2_AGENT_API - DEBUGASSERT(sshc->ssh_agent == NULL); -#endif - - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - Curl_safefree(sshc->homedir); - + result = sshc_cleanup(sshc, data, FALSE); + if(result) + break; /* the code we are about to return */ - result = sshc->actualcode; - memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(conn, "SSH session free"); sshc->state = SSH_SESSION_FREE; /* current */ - sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ - sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } - } while(!rc && (sshc->state != SSH_STOP)); + } while(!result && (sshc->state != SSH_STOP)); - if(rc == LIBSSH2_ERROR_EAGAIN) { + if(result == CURLE_AGAIN) { /* we would block, we need to wait for the socket to be ready (in the right direction too)! */ *block = TRUE; + result = CURLE_OK; } return result; @@ -3066,24 +3131,25 @@ static int ssh_getsock(struct Curl_easy *data, * When one of the libssh2 functions has returned LIBSSH2_ERROR_EAGAIN this * function is used to figure out in what direction and stores this info so * that the multi interface can take advantage of it. Make sure to call this - * function in all cases so that when it _doesn't_ return EAGAIN we can + * function in all cases so that when it _does not_ return EAGAIN we can * restore the default wait bits. */ -static void ssh_block2waitfor(struct Curl_easy *data, bool block) +static void ssh_block2waitfor(struct Curl_easy *data, + struct ssh_conn *sshc, + bool block) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; int dir = 0; if(block) { dir = libssh2_session_block_directions(sshc->ssh_session); if(dir) { /* translate the libssh2 define bits into our own bit defines */ - conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND)?KEEP_RECV:0) | - ((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND)?KEEP_SEND:0); + conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND) ? KEEP_RECV : 0) | + ((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND) ? KEEP_SEND : 0); } } if(!dir) - /* It didn't block or libssh2 didn't reveal in which direction, put back + /* It did not block or libssh2 did not reveal in which direction, put back the original set */ conn->waitfor = sshc->orig_waitfor; } @@ -3092,35 +3158,39 @@ static void ssh_block2waitfor(struct Curl_easy *data, bool block) static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); CURLcode result = CURLE_OK; bool block; /* we store the status and use that to provide a ssh_getsock() implementation */ + if(!sshc || !sshp) + return CURLE_FAILED_INIT; + do { - result = ssh_statemach_act(data, &block); - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; - /* if there's no error, it isn't done and it didn't EWOULDBLOCK, then + result = ssh_statemachine(data, sshc, sshp, &block); + *done = (sshc->state == SSH_STOP); + /* if there is no error, it is not done and it did not EWOULDBLOCK, then try again */ } while(!result && !*done && !block); - ssh_block2waitfor(data, block); + ssh_block2waitfor(data, sshc, block); return result; } static CURLcode ssh_block_statemach(struct Curl_easy *data, - struct connectdata *conn, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, bool disconnect) { - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; - struct curltime dis = Curl_now(); + struct curltime dis = curlx_now(); while((sshc->state != SSH_STOP) && !result) { bool block; timediff_t left = 1000; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); - result = ssh_statemach_act(data, &block); + result = ssh_statemachine(data, sshc, sshp, &block); if(result) break; @@ -3138,7 +3208,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; } } - else if(Curl_timediff(now, dis) > 1000) { + else if(curlx_timediff(now, dis) > 1000) { /* disconnect timeout */ failf(data, "Disconnect timed out"); result = CURLE_OK; @@ -3147,7 +3217,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, if(block) { int dir = libssh2_session_block_directions(sshc->ssh_session); - curl_socket_t sock = conn->sock[FIRSTSOCKET]; + curl_socket_t sock = data->conn->sock[FIRSTSOCKET]; curl_socket_t fd_read = CURL_SOCKET_BAD; curl_socket_t fd_write = CURL_SOCKET_BAD; if(LIBSSH2_SESSION_BLOCK_INBOUND & dir) @@ -3156,24 +3226,58 @@ 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>1000?1000:left); + left > 1000 ? 1000 : left); } } return result; } +static void myssh_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SSHPROTO *sshp = entry; + (void)key; + (void)klen; + Curl_safefree(sshp->path); + curlx_dyn_free(&sshp->readdir); + curlx_dyn_free(&sshp->readdir_link); + free(sshp); +} + +static void myssh_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ssh_conn *sshc = entry; + (void)key; + (void)klen; + sshc_cleanup(sshc, NULL, TRUE); + free(sshc); +} + /* * SSH setup and connection */ static CURLcode ssh_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - struct SSHPROTO *ssh; + struct ssh_conn *sshc; + struct SSHPROTO *sshp; (void)conn; - data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); - if(!ssh) + sshc = calloc(1, sizeof(*sshc)); + if(!sshc) + return CURLE_OUT_OF_MEMORY; + + sshc->initialised = TRUE; + if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, myssh_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + sshp = calloc(1, sizeof(*sshp)); + if(!sshp) + return CURLE_OUT_OF_MEMORY; + + curlx_dyn_init(&sshp->readdir, CURL_PATH_MAX * 2); + curlx_dyn_init(&sshp->readdir_link, CURL_PATH_MAX); + if(Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; return CURLE_OK; @@ -3191,19 +3295,23 @@ static ssize_t ssh_tls_recv(libssh2_socket_t sock, void *buffer, CURLcode result; struct connectdata *conn = data->conn; Curl_recv *backup = conn->recv[0]; - struct ssh_conn *ssh = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + int socknum = Curl_conn_sockindex(data, sock); (void)flags; + if(!sshc) + return -1; + /* swap in the TLS reader function for this call only, and then swap back the SSH one again */ - conn->recv[0] = ssh->tls_recv; - result = Curl_read(data, sock, buffer, length, &nread); + conn->recv[0] = sshc->tls_recv; + result = Curl_conn_recv(data, socknum, buffer, length, &nread); conn->recv[0] = backup; if(result == CURLE_AGAIN) return -EAGAIN; /* magic return code for libssh2 */ else if(result) return -1; /* generic error */ - Curl_debug(data, CURLINFO_DATA_IN, (char *)buffer, (size_t)nread); + Curl_debug(data, CURLINFO_DATA_IN, (const char *)buffer, (size_t)nread); return nread; } @@ -3211,24 +3319,28 @@ static ssize_t ssh_tls_send(libssh2_socket_t sock, const void *buffer, size_t length, int flags, void **abstract) { struct Curl_easy *data = (struct Curl_easy *)*abstract; - ssize_t nwrite; + size_t nwrite; CURLcode result; struct connectdata *conn = data->conn; Curl_send *backup = conn->send[0]; - struct ssh_conn *ssh = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + int socknum = Curl_conn_sockindex(data, sock); (void)flags; + if(!sshc) + return -1; + /* swap in the TLS writer function for this call only, and then swap back the SSH one again */ - conn->send[0] = ssh->tls_send; - result = Curl_write(data, sock, buffer, length, &nwrite); + conn->send[0] = sshc->tls_send; + result = Curl_conn_send(data, socknum, buffer, length, FALSE, &nwrite); conn->send[0] = backup; if(result == CURLE_AGAIN) return -EAGAIN; /* magic return code for libssh2 */ else if(result) return -1; /* error */ - Curl_debug(data, CURLINFO_DATA_OUT, (char *)buffer, (size_t)nwrite); - return nwrite; + Curl_debug(data, CURLINFO_DATA_OUT, (const char *)buffer, nwrite); + return (ssize_t)nwrite; } #endif @@ -3241,57 +3353,98 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) #ifdef CURL_LIBSSH2_DEBUG curl_socket_t sock; #endif - struct ssh_conn *sshc; - CURLcode result; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + CURLcode result; - /* initialize per-handle data if not already */ - if(!data->req.p.ssh) { - result = ssh_setup_connection(data, conn); - if(result) - return result; +#if LIBSSH2_VERSION_NUM >= 0x010b00 + { + const char *crypto_str; + switch(libssh2_crypto_engine()) { + case libssh2_gcrypt: + crypto_str = "libgcrypt"; + break; + case libssh2_mbedtls: + crypto_str = "mbedTLS"; + break; + case libssh2_openssl: + crypto_str = "openssl compatible"; + break; + case libssh2_os400qc3: + crypto_str = "OS400QC3"; + break; + case libssh2_wincng: + crypto_str = "WinCNG"; + break; + default: + crypto_str = NULL; + break; + } + if(crypto_str) + infof(data, "libssh2 cryptography backend: %s", crypto_str); } +#endif + + if(!sshc) + return CURLE_FAILED_INIT; /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ + function to make the reuse checks properly be able to check this bit. */ connkeep(conn, "SSH default"); - sshc = &conn->proto.sshc; - + if(conn->user) + infof(data, "User: '%s'", conn->user); + else + infof(data, "User: NULL"); #ifdef CURL_LIBSSH2_DEBUG - if(conn->user) { - infof(data, "User: %s", conn->user); - } if(conn->passwd) { infof(data, "Password: %s", conn->passwd); } sock = conn->sock[FIRSTSOCKET]; #endif /* CURL_LIBSSH2_DEBUG */ -#ifdef CURL_LIBSSH2_DEBUG + /* libcurl MUST to set custom memory functions so that the kbd_callback + function's memory allocations can be properly freed */ sshc->ssh_session = libssh2_session_init_ex(my_libssh2_malloc, my_libssh2_free, my_libssh2_realloc, data); -#else - sshc->ssh_session = libssh2_session_init_ex(NULL, NULL, NULL, data); -#endif + if(!sshc->ssh_session) { failf(data, "Failure initialising ssh session"); return CURLE_FAILED_INIT; } -#ifdef HAVE_LIBSSH2_VERSION /* Set the packet read timeout if the libssh2 version supports it */ #if LIBSSH2_VERSION_NUM >= 0x010B00 if(data->set.server_response_timeout > 0) { libssh2_session_set_read_timeout(sshc->ssh_session, - data->set.server_response_timeout / 1000); + (long)(data->set.server_response_timeout / 1000)); } #endif -#endif #ifndef CURL_DISABLE_PROXY if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { + /* + Setup libssh2 callbacks to make it read/write TLS from the socket. + + ssize_t + recvcb(libssh2_socket_t sock, void *buffer, size_t length, + int flags, void **abstract); + + ssize_t + sendcb(libssh2_socket_t sock, const void *buffer, size_t length, + int flags, void **abstract); + + */ +#if LIBSSH2_VERSION_NUM >= 0x010b01 + infof(data, "Uses HTTPS proxy"); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_RECV, + (libssh2_cb_generic *)ssh_tls_recv); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_SEND, + (libssh2_cb_generic *)ssh_tls_send); +#else /* * This crazy union dance is here to avoid assigning a void pointer a * function pointer as it is invalid C. The problem is of course that @@ -3312,22 +3465,11 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) sshsend.sendptr = ssh_tls_send; infof(data, "Uses HTTPS proxy"); - /* - Setup libssh2 callbacks to make it read/write TLS from the socket. - - ssize_t - recvcb(libssh2_socket_t sock, void *buffer, size_t length, - int flags, void **abstract); - - ssize_t - sendcb(libssh2_socket_t sock, const void *buffer, size_t length, - int flags, void **abstract); - - */ libssh2_session_callback_set(sshc->ssh_session, LIBSSH2_CALLBACK_RECV, sshrecv.recvp); libssh2_session_callback_set(sshc->ssh_session, LIBSSH2_CALLBACK_SEND, sshsend.sendp); +#endif /* Store the underlying TLS recv/send function pointers to be used when reading from the proxy */ @@ -3345,14 +3487,11 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) conn->send[FIRSTSOCKET] = sftp_send; } - if(data->set.ssh_compression) { -#if LIBSSH2_VERSION_NUM >= 0x010208 - if(libssh2_session_flag(sshc->ssh_session, LIBSSH2_FLAG_COMPRESS, 1) < 0) -#endif - infof(data, "Failed to enable compression for ssh session"); + if(data->set.ssh_compression && + libssh2_session_flag(sshc->ssh_session, LIBSSH2_FLAG_COMPRESS, 1) < 0) { + infof(data, "Failed to enable compression for ssh session"); } -#ifdef HAVE_LIBSSH2_KNOWNHOST_API if(data->set.str[STRING_SSH_KNOWNHOSTS]) { int rc; sshc->kh = libssh2_knownhost_init(sshc->ssh_session); @@ -3370,14 +3509,13 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) infof(data, "Failed to read known hosts from %s", data->set.str[STRING_SSH_KNOWNHOSTS]); } -#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ #ifdef CURL_LIBSSH2_DEBUG libssh2_trace(sshc->ssh_session, ~0); infof(data, "SSH socket: %d", (int)sock); #endif /* CURL_LIBSSH2_DEBUG */ - state(data, SSH_INIT); + myssh_state(data, sshc, SSH_INIT); result = ssh_multi_statemach(data, done); @@ -3398,14 +3536,17 @@ CURLcode scp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SCP_TRANS_INIT); + myssh_state(data, sshc, SSH_SCP_TRANS_INIT); /* run the state-machine */ result = ssh_multi_statemach(data, dophase_done); @@ -3440,15 +3581,15 @@ static CURLcode scp_doing(struct Curl_easy *data, static CURLcode ssh_do(struct Curl_easy *data, bool *done) { CURLcode result; - bool connected = 0; + bool connected = FALSE; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); *done = FALSE; /* default to false */ + if(!sshc) + return CURLE_FAILED_INIT; data->req.size = -1; /* make sure this is unknown at this point */ - - sshc->actualcode = CURLE_OK; /* reset error code */ sshc->secondCreateDirs = 0; /* reset the create dir attempt state variable */ @@ -3465,6 +3606,110 @@ static CURLcode ssh_do(struct Curl_easy *data, bool *done) return result; } +static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, + bool block) +{ + int rc; + + if(sshc->initialised) { + if(sshc->kh) { + libssh2_knownhost_free(sshc->kh); + sshc->kh = NULL; + } + + if(sshc->ssh_agent) { + rc = libssh2_agent_disconnect(sshc->ssh_agent); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to disconnect from libssh2 agent: %d %s", + rc, err_msg); + } + libssh2_agent_free(sshc->ssh_agent); + sshc->ssh_agent = NULL; + + /* NB: there is no need to free identities, they are part of internal + agent stuff */ + sshc->sshagent_identity = NULL; + sshc->sshagent_prev_identity = NULL; + } + + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, + NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); + } + sshc->sftp_handle = NULL; + } + + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 scp subsystem: %d %s", + rc, err_msg); + } + sshc->ssh_channel = NULL; + } + + if(sshc->sftp_session) { + rc = libssh2_sftp_shutdown(sshc->sftp_session); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) + infof(data, "Failed to stop libssh2 sftp subsystem"); + sshc->sftp_session = NULL; + } + + if(sshc->ssh_session) { + rc = libssh2_session_free(sshc->ssh_session); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg); + } + sshc->ssh_session = NULL; + } + + /* worst-case scenario cleanup */ + DEBUGASSERT(sshc->ssh_session == NULL); + DEBUGASSERT(sshc->ssh_channel == NULL); + DEBUGASSERT(sshc->sftp_session == NULL); + DEBUGASSERT(sshc->sftp_handle == NULL); + DEBUGASSERT(sshc->kh == NULL); + DEBUGASSERT(sshc->ssh_agent == NULL); + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + Curl_safefree(sshc->homedir); + sshc->initialised = FALSE; + } + return CURLE_OK; +} + + /* BLOCKING, but the function is using the state machine so the only reason this is still blocking is that the multi interface code has no support for disconnecting operations that takes a while */ @@ -3473,15 +3718,18 @@ static CURLcode scp_disconnect(struct Curl_easy *data, bool dead_connection) { CURLcode result = CURLE_OK; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void) dead_connection; - if(sshc->ssh_session) { - /* only if there's a session still around to use! */ - state(data, SSH_SESSION_DISCONNECT); - result = ssh_block_statemach(data, conn, TRUE); + if(sshc && sshc->ssh_session && sshp) { + /* only if there is a session still around to use! */ + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); + result = ssh_block_statemach(data, sshc, sshp, TRUE); } + if(sshc) + return sshc_cleanup(sshc, data, TRUE); return result; } @@ -3489,21 +3737,19 @@ static CURLcode scp_disconnect(struct Curl_easy *data, done functions */ static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); CURLcode result = CURLE_OK; - struct SSHPROTO *sshp = data->req.p.ssh; - struct connectdata *conn = data->conn; + + if(!sshc || !sshp) + return CURLE_FAILED_INIT; if(!status) /* run the state-machine */ - result = ssh_block_statemach(data, conn, FALSE); + result = ssh_block_statemach(data, sshc, sshp, FALSE); else result = status; - Curl_safefree(sshp->path); - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); - Curl_dyn_free(&sshp->readdir); - if(Curl_pgrsDone(data)) return CURLE_ABORTED_BY_CALLBACK; @@ -3515,27 +3761,32 @@ static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) static CURLcode scp_done(struct Curl_easy *data, CURLcode status, bool premature) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); (void)premature; /* not used */ - if(!status) - state(data, SSH_SCP_DONE); + if(sshc && !status) + myssh_state(data, sshc, SSH_SCP_DONE); return ssh_done(data, status); - } static ssize_t scp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { ssize_t nwrite; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; /* we only support SCP on the fixed known primary socket */ + (void)eos; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } /* libssh2_channel_write() returns int! */ nwrite = (ssize_t) libssh2_channel_write(sshc->ssh_channel, mem, len); - ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, sshc, (nwrite == LIBSSH2_ERROR_EAGAIN)); if(nwrite == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3554,13 +3805,17 @@ static ssize_t scp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; /* we only support SCP on the fixed known primary socket */ + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } /* libssh2_channel_read() returns int */ nread = (ssize_t) libssh2_channel_read(sshc->ssh_channel, mem, len); - ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, sshc, (nread == LIBSSH2_ERROR_EAGAIN)); if(nread == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; nread = -1; @@ -3587,14 +3842,17 @@ CURLcode sftp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SFTP_QUOTE_INIT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_INIT); /* run the state-machine */ result = ssh_multi_statemach(data, dophase_done); @@ -3627,18 +3885,21 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { CURLcode result = CURLE_OK; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void) dead_connection; DEBUGF(infof(data, "SSH DISCONNECT starts now")); - if(sshc->ssh_session) { - /* only if there's a session still around to use! */ - state(data, SSH_SFTP_SHUTDOWN); - result = ssh_block_statemach(data, conn, TRUE); + if(sshc && sshc->ssh_session && sshp) { + /* only if there is a session still around to use! */ + myssh_state(data, sshc, SSH_SFTP_SHUTDOWN); + result = ssh_block_statemach(data, sshc, sshp, TRUE); } DEBUGF(infof(data, "SSH DISCONNECT is done")); + if(sshc) + sshc_cleanup(sshc, data, TRUE); return result; @@ -3648,7 +3909,10 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + + if(!sshc) + return CURLE_FAILED_INIT; if(!status) { /* Post quote commands are executed after the SFTP_CLOSE state to avoid @@ -3656,23 +3920,28 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, operation */ if(!premature && data->set.postquote && !conn->bits.retry) sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); } return ssh_done(data, status); } /* return number of sent bytes */ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { ssize_t nwrite; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; + (void)eos; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } nwrite = libssh2_sftp_write(sshc->sftp_handle, mem, len); - ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, sshc, (nwrite == LIBSSH2_ERROR_EAGAIN)); if(nwrite == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3695,12 +3964,16 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } nread = libssh2_sftp_read(sshc->sftp_handle, mem, len); - ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, sshc, (nread == LIBSSH2_ERROR_EAGAIN)); if(nread == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3781,25 +4054,21 @@ static const char *sftp_libssh2_strerror(unsigned long err) CURLcode Curl_ssh_init(void) { -#ifdef HAVE_LIBSSH2_INIT if(libssh2_init(0)) { DEBUGF(fprintf(stderr, "Error: libssh2_init failed\n")); return CURLE_FAILED_INIT; } -#endif return CURLE_OK; } void Curl_ssh_cleanup(void) { -#ifdef HAVE_LIBSSH2_EXIT (void)libssh2_exit(); -#endif } void Curl_ssh_version(char *buffer, size_t buflen) { - (void)msnprintf(buffer, buflen, "libssh2/%s", CURL_LIBSSH2_VERSION); + (void)msnprintf(buffer, buflen, "libssh2/%s", libssh2_version(0)); } /* The SSH session is associated with the *CONNECTION* but the callback user @@ -3811,8 +4080,8 @@ static void ssh_attach(struct Curl_easy *data, struct connectdata *conn) DEBUGASSERT(data); DEBUGASSERT(conn); if(conn->handler->protocol & PROTO_FAMILY_SSH) { - struct ssh_conn *sshc = &conn->proto.sshc; - if(sshc->ssh_session) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + if(sshc && sshc->ssh_session) { /* only re-attach if the session already exists */ void **abstract = libssh2_session_abstract(sshc->ssh_session); *abstract = data; diff --git a/Utilities/cmcurl/lib/vssh/ssh.h b/Utilities/cmcurl/lib/vssh/ssh.h index 1e1b1379c27..feee886562e 100644 --- a/Utilities/cmcurl/lib/vssh/ssh.h +++ b/Utilities/cmcurl/lib/vssh/ssh.h @@ -24,12 +24,14 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_LIBSSH2) #include #include #elif defined(USE_LIBSSH) +/* in 0.10.0 or later, ignore deprecated warnings */ +#define SSH_SUPPRESS_DEPRECATED #include #include #elif defined(USE_WOLFSSH) @@ -37,6 +39,13 @@ #include #endif +#include "curl_path.h" + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_SSH_EASY "meta:proto:ssh:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_SSH_CONN "meta:proto:ssh:conn" + /**************************************************************************** * SSH unique setup ***************************************************************************/ @@ -107,6 +116,8 @@ typedef enum { SSH_LAST /* never used */ } sshstate; +#define CURL_PATH_MAX 1024 + /* this struct is used in the HandleData struct which is part of the Curl_easy, which means this is used on a per-easy handle basis. Everything that is strictly related to a connection is banned from this @@ -116,8 +127,8 @@ struct SSHPROTO { #ifdef USE_LIBSSH2 struct dynbuf readdir_link; struct dynbuf readdir; - char *readdir_filename; - char *readdir_longentry; + char readdir_filename[CURL_PATH_MAX + 1]; + char readdir_longentry[CURL_PATH_MAX + 1]; LIBSSH2_SFTP_ATTRIBUTES quote_attrs; /* used by the SFTP_QUOTE state */ @@ -135,12 +146,8 @@ struct ssh_conn { const char *passphrase; /* pass-phrase to use */ char *rsa_pub; /* strdup'ed public key file */ char *rsa; /* strdup'ed private key file */ - bool authed; /* the connection has been authenticated fine */ - bool acceptfail; /* used by the SFTP_QUOTE (continue if - quote command fails) */ sshstate state; /* always use ssh.c:state() to change state! */ sshstate nextstate; /* the state to goto after stopping */ - CURLcode actualcode; /* the actual error code */ struct curl_slist *quote_item; /* for the quote option */ char *quote_path1; /* two generic pointers for the QUOTE stuff */ char *quote_path2; @@ -156,6 +163,7 @@ struct ssh_conn { char *slash_pos; /* used by the SFTP_CREATE_DIRS state */ #if defined(USE_LIBSSH) + CURLcode actualcode; /* the actual error code */ char *readdir_linkPath; size_t readdir_len; struct dynbuf readdir_buf; @@ -163,7 +171,7 @@ struct ssh_conn { unsigned kbd_state; /* 0 or 1 */ ssh_key privkey; ssh_key pubkey; - int auth_methods; + unsigned int auth_methods; ssh_session ssh_session; ssh_scp scp_session; sftp_session sftp_session; @@ -171,6 +179,10 @@ struct ssh_conn { sftp_dir sftp_dir; unsigned sftp_recv_state; /* 0 or 1 */ +#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0) + sftp_aio sftp_aio; + unsigned sftp_send_state; /* 0 or 1 */ +#endif int sftp_file_index; /* for async read */ sftp_attributes readdir_attrs; /* used by the SFTP readdir actions */ sftp_attributes readdir_link_attrs; /* used by the SFTP readdir actions */ @@ -191,63 +203,38 @@ struct ssh_conn { Curl_send *tls_send; #endif -#ifdef HAVE_LIBSSH2_AGENT_API LIBSSH2_AGENT *ssh_agent; /* proxy to ssh-agent/pageant */ - struct libssh2_agent_publickey *sshagent_identity, - *sshagent_prev_identity; -#endif - - /* note that HAVE_LIBSSH2_KNOWNHOST_API is a define set in the libssh2.h - header */ -#ifdef HAVE_LIBSSH2_KNOWNHOST_API + struct libssh2_agent_publickey *sshagent_identity; + struct libssh2_agent_publickey *sshagent_prev_identity; LIBSSH2_KNOWNHOSTS *kh; -#endif #elif defined(USE_WOLFSSH) + CURLcode actualcode; /* the actual error code */ WOLFSSH *ssh_session; WOLFSSH_CTX *ctx; word32 handleSz; byte handle[WOLFSSH_MAX_HANDLE]; curl_off_t offset; #endif /* USE_LIBSSH */ + BIT(initialised); + BIT(authed); /* the connection has been authenticated fine */ + BIT(acceptfail); /* used by the SFTP_QUOTE (continue if + quote command fails) */ }; -#if defined(USE_LIBSSH2) - -/* Feature detection based on version numbers to better work with - non-configure platforms */ - -#if !defined(LIBSSH2_VERSION_NUM) || (LIBSSH2_VERSION_NUM < 0x001000) -# error "SCP/SFTP protocols require libssh2 0.16 or later" -#endif - -#if LIBSSH2_VERSION_NUM >= 0x010000 -#define HAVE_LIBSSH2_SFTP_SEEK64 1 -#endif - -#if LIBSSH2_VERSION_NUM >= 0x010100 -#define HAVE_LIBSSH2_VERSION 1 +#ifdef USE_LIBSSH +#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 9, 0) +# error "SCP/SFTP protocols require libssh 0.9.0 or later" #endif - -#if LIBSSH2_VERSION_NUM >= 0x010205 -#define HAVE_LIBSSH2_INIT 1 -#define HAVE_LIBSSH2_EXIT 1 #endif -#if LIBSSH2_VERSION_NUM >= 0x010206 -#define HAVE_LIBSSH2_KNOWNHOST_CHECKP 1 -#define HAVE_LIBSSH2_SCP_SEND64 1 -#endif +#if defined(USE_LIBSSH2) -#if LIBSSH2_VERSION_NUM >= 0x010208 -#define HAVE_LIBSSH2_SESSION_HANDSHAKE 1 -#endif +/* Feature detection based on version numbers to better work with + non-configure platforms */ -#ifdef HAVE_LIBSSH2_VERSION -/* get it run-time if possible */ -#define CURL_LIBSSH2_VERSION libssh2_version(0) -#else -/* use build-time if run-time not possible */ -#define CURL_LIBSSH2_VERSION LIBSSH2_VERSION +#if !defined(LIBSSH2_VERSION_NUM) || (LIBSSH2_VERSION_NUM < 0x010208) +# error "SCP/SFTP protocols require libssh2 1.2.8 or later" +/* 1.2.8 was released on April 5 2011 */ #endif #endif /* USE_LIBSSH2 */ @@ -267,6 +254,7 @@ void Curl_ssh_attach(struct Curl_easy *data, /* for non-SSH builds */ #define Curl_ssh_cleanup() #define Curl_ssh_attach(x,y) +#define Curl_ssh_init() 0 #endif #endif /* HEADER_CURL_SSH_H */ diff --git a/Utilities/cmcurl/lib/vssh/wolfssh.c b/Utilities/cmcurl/lib/vssh/wolfssh.c index 780b6126acc..5097ca02c51 100644 --- a/Utilities/cmcurl/lib/vssh/wolfssh.c +++ b/Utilities/cmcurl/lib/vssh/wolfssh.c @@ -22,31 +22,30 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_WOLFSSH #include -#include -#include -#include "urldata.h" -#include "cfilters.h" -#include "connect.h" -#include "sendf.h" -#include "progress.h" +#include "../urldata.h" +#include "../url.h" +#include "../cfilters.h" +#include "../connect.h" +#include "../sendf.h" +#include "../progress.h" #include "curl_path.h" -#include "strtoofft.h" -#include "transfer.h" -#include "speedcheck.h" -#include "select.h" -#include "multiif.h" -#include "warnless.h" +#include "../transfer.h" +#include "../speedcheck.h" +#include "../select.h" +#include "../multiif.h" +#include "../curlx/warnless.h" +#include "../strdup.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" static CURLcode wssh_connect(struct Curl_easy *data, bool *done); static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done); @@ -72,6 +71,7 @@ static int wssh_getsock(struct Curl_easy *data, curl_socket_t *sock); static CURLcode wssh_setup_connection(struct Curl_easy *data, struct connectdata *conn); +static void wssh_sshc_cleanup(struct ssh_conn *sshc); #if 0 /* @@ -92,9 +92,11 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ wssh_getsock, /* perform_getsock */ wscp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SCP, /* protocol */ PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION @@ -121,9 +123,11 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ wssh_getsock, /* perform_getsock */ wsftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SFTP, /* protocol */ CURLPROTO_SFTP, /* family */ @@ -135,10 +139,10 @@ const struct Curl_handler Curl_handler_sftp = { * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ -static void state(struct Curl_easy *data, sshstate nowstate) +static void wssh_state(struct Curl_easy *data, + struct ssh_conn *sshc, + sshstate nowstate) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -205,25 +209,27 @@ static void state(struct Curl_easy *data, sshstate nowstate) }; /* a precaution to make sure the lists are in sync */ - DEBUGASSERT(sizeof(names)/sizeof(names[0]) == SSH_LAST); + DEBUGASSERT(CURL_ARRAYSIZE(names) == SSH_LAST); if(sshc->state != nowstate) { infof(data, "wolfssh %p state change from %s to %s", (void *)sshc, names[sshc->state], names[nowstate]); } #endif - + (void)data; sshc->state = nowstate; } static ssize_t wscp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, + CURLcode *err) { ssize_t nwrite = 0; (void)data; (void)sockindex; /* we only support SCP on the fixed known primary socket */ (void)mem; (void)len; + (void)eos; (void)err; return nwrite; @@ -244,21 +250,26 @@ static ssize_t wscp_recv(struct Curl_easy *data, int sockindex, /* return number of sent bytes */ static ssize_t wsftp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); word32 offset[2]; int rc; (void)sockindex; + (void)eos; - offset[0] = (word32)sshc->offset&0xFFFFFFFF; - offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } + offset[0] = (word32)sshc->offset & 0xFFFFFFFF; + offset[1] = (word32)(sshc->offset >> 32) & 0xFFFFFFFF; rc = wolfSSH_SFTP_SendWritePacket(sshc->ssh_session, sshc->handle, sshc->handleSz, &offset[0], - (byte *)mem, (word32)len); + (byte *)CURL_UNCONST(mem), (word32)len); if(rc == WS_FATAL_ERROR) rc = wolfSSH_get_error(sshc->ssh_session); @@ -277,7 +288,7 @@ static ssize_t wsftp_send(struct Curl_easy *data, int sockindex, return -1; } DEBUGASSERT(rc == (int)len); - infof(data, "sent %zd bytes SFTP from offset %zd", + infof(data, "sent %zu bytes SFTP from offset %" FMT_OFF_T, len, sshc->offset); sshc->offset += len; return (ssize_t)rc; @@ -292,12 +303,16 @@ static ssize_t wsftp_recv(struct Curl_easy *data, int sockindex, { int rc; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); word32 offset[2]; (void)sockindex; - offset[0] = (word32)sshc->offset&0xFFFFFFFF; - offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } + offset[0] = (word32)sshc->offset & 0xFFFFFFFF; + offset[1] = (word32)(sshc->offset >> 32) & 0xFFFFFFFF; rc = wolfSSH_SFTP_SendReadPacket(sshc->ssh_session, sshc->handle, sshc->handleSz, @@ -327,25 +342,50 @@ static ssize_t wsftp_recv(struct Curl_easy *data, int sockindex, return (ssize_t)rc; } +static void wssh_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SSHPROTO *sshp = entry; + (void)key; + (void)klen; + Curl_safefree(sshp->path); + free(sshp); +} + +static void wssh_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ssh_conn *sshc = entry; + (void)key; + (void)klen; + wssh_sshc_cleanup(sshc); + free(sshc); +} + /* * SSH setup and connection */ static CURLcode wssh_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - struct SSHPROTO *ssh; + struct ssh_conn *sshc; + struct SSHPROTO *sshp; (void)conn; - data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); - if(!ssh) + sshc = calloc(1, sizeof(*sshc)); + if(!sshc) + return CURLE_OUT_OF_MEMORY; + + sshc->initialised = TRUE; + if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, wssh_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + sshp = calloc(1, sizeof(*sshp)); + if(!sshp || + Curl_meta_set(data, CURL_META_SSH_EASY, sshp, wssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } -static Curl_recv wscp_recv, wsftp_recv; -static Curl_send wscp_send, wsftp_send; - static int userauth(byte authtype, WS_UserAuthData* authdata, void *ctx) @@ -365,16 +405,16 @@ static int userauth(byte authtype, static CURLcode wssh_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); curl_socket_t sock = conn->sock[FIRSTSOCKET]; int rc; - /* initialize per-handle data if not already */ - if(!data->req.p.ssh) - wssh_setup_connection(data, conn); + if(!sshc || !sshp) + return CURLE_FAILED_INIT; /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ + function to make the reuse checks properly be able to check this bit. */ connkeep(conn, "SSH default"); if(conn->handler->protocol & CURLPROTO_SCP) { @@ -385,7 +425,6 @@ static CURLcode wssh_connect(struct Curl_easy *data, bool *done) conn->recv[FIRSTSOCKET] = wsftp_recv; conn->send[FIRSTSOCKET] = wsftp_send; } - sshc = &conn->proto.sshc; sshc->ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if(!sshc->ctx) { failf(data, "No wolfSSH context"); @@ -400,7 +439,7 @@ static CURLcode wssh_connect(struct Curl_easy *data, bool *done) rc = wolfSSH_SetUsername(sshc->ssh_session, conn->user); if(rc != WS_SUCCESS) { - failf(data, "wolfSSH failed to set user name"); + failf(data, "wolfSSH failed to set username"); goto error; } @@ -420,38 +459,41 @@ static CURLcode wssh_connect(struct Curl_easy *data, bool *done) *done = TRUE; if(conn->handler->protocol & CURLPROTO_SCP) - state(data, SSH_INIT); + wssh_state(data, sshc, SSH_INIT); else - state(data, SSH_SFTP_INIT); + wssh_state(data, sshc, SSH_SFTP_INIT); return wssh_multi_statemach(data, done); error: - wolfSSH_free(sshc->ssh_session); - wolfSSH_CTX_free(sshc->ctx); + wssh_sshc_cleanup(sshc); return CURLE_FAILED_INIT; } /* * wssh_statemach_act() runs the SSH state machine as far as it can without - * blocking and without reaching the end. The data the pointer 'block' points + * blocking and without reaching the end. The data the pointer 'block' points * to will be set to TRUE if the wolfssh function returns EAGAIN meaning it * wants to be called again when the socket is ready */ -static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) +static CURLcode wssh_statemach_act(struct Curl_easy *data, + struct ssh_conn *sshc, + bool *block) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; - struct SSHPROTO *sftp_scp = data->req.p.ssh; + struct SSHPROTO *sftp_scp = Curl_meta_get(data, CURL_META_SSH_EASY); WS_SFTPNAME *name; int rc = 0; - *block = FALSE; /* we're not blocking by default */ + *block = FALSE; /* we are not blocking by default */ + + if(!sftp_scp) + return CURLE_FAILED_INIT; do { switch(sshc->state) { case SSH_INIT: - state(data, SSH_S_STARTUP); + wssh_state(data, sshc, SSH_S_STARTUP); break; case SSH_S_STARTUP: @@ -469,11 +511,11 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_OK; } else if(rc != WS_SUCCESS) { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return CURLE_SSH; } infof(data, "wolfssh connected"); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; case SSH_STOP: break; @@ -494,7 +536,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) } else if(rc == WS_SUCCESS) { infof(data, "wolfssh SFTP connected"); - state(data, SSH_SFTP_REALPATH); + wssh_state(data, sshc, SSH_SFTP_REALPATH); } else { failf(data, "wolfssh SFTP connect error %d", rc); @@ -502,7 +544,8 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) } break; case SSH_SFTP_REALPATH: - name = wolfSSH_SFTP_RealPath(sshc->ssh_session, (char *)"."); + name = wolfSSH_SFTP_RealPath(sshc->ssh_session, + (char *)CURL_UNCONST(".")); rc = wolfSSH_get_error(sshc->ssh_session); if(rc == WS_WANT_READ) { *block = TRUE; @@ -515,17 +558,11 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_OK; } else if(name && (rc == WS_SUCCESS)) { - sshc->homedir = malloc(name->fSz + 1); - if(!sshc->homedir) { + sshc->homedir = Curl_memdup0(name->fName, name->fSz); + if(!sshc->homedir) sshc->actualcode = CURLE_OUT_OF_MEMORY; - } - else { - memcpy(sshc->homedir, name->fName, name->fSz); - sshc->homedir[name->fSz] = 0; - infof(data, "wolfssh SFTP realpath succeeded"); - } wolfSSH_SFTPNAME_list_free(name); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return CURLE_OK; } failf(data, "wolfssh SFTP realpath %d", rc); @@ -535,35 +572,35 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) result = Curl_getworkingpath(data, sshc->homedir, &sftp_scp->path); if(result) { sshc->actualcode = result; - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; } if(data->set.quote) { infof(data, "Sending quote commands"); sshc->quote_item = data->set.quote; - state(data, SSH_SFTP_QUOTE); + wssh_state(data, sshc, SSH_SFTP_QUOTE); } else { - state(data, SSH_SFTP_GETINFO); + wssh_state(data, sshc, SSH_SFTP_GETINFO); } break; case SSH_SFTP_GETINFO: if(data->set.get_filetime) { - state(data, SSH_SFTP_FILETIME); + wssh_state(data, sshc, SSH_SFTP_FILETIME); } else { - state(data, SSH_SFTP_TRANS_INIT); + wssh_state(data, sshc, SSH_SFTP_TRANS_INIT); } break; case SSH_SFTP_TRANS_INIT: if(data->state.upload) - state(data, SSH_SFTP_UPLOAD_INIT); + wssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); else { if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') - state(data, SSH_SFTP_READDIR_INIT); + wssh_state(data, sshc, SSH_SFTP_READDIR_INIT); else - state(data, SSH_SFTP_DOWNLOAD_INIT); + wssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } break; case SSH_SFTP_UPLOAD_INIT: { @@ -583,7 +620,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) else { curl_off_t size = ((curl_off_t)attrs.sz[1] << 32) | attrs.sz[0]; if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + failf(data, "Bad file size (%" FMT_OFF_T ")", size); return CURLE_BAD_DOWNLOAD_RESUME; } data->state.resume_from = size; @@ -626,18 +663,18 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) failf(data, "wolfssh SFTP upload open failed: %d", rc); return CURLE_SSH; } - state(data, SSH_SFTP_DOWNLOAD_STAT); + wssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); /* If we have a restart point then we need to seek to the correct position. */ if(data->state.resume_from > 0) { /* Let's read off the proper amount of bytes from the input. */ int seekerr = CURL_SEEKFUNC_OK; - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -647,19 +684,20 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) failf(data, "Could not seek stream"); return CURLE_FTP_COULDNT_USE_REST; } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread; - Curl_set_in_callback(data, true); - actuallyread = data->state.fread_func(data->state.buffer, 1, + Curl_set_in_callback(data, TRUE); + actuallyread = data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); passed += actuallyread; if((actuallyread == 0) || (actuallyread > readthisamountnow)) { @@ -685,31 +723,31 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) Curl_pgrsSetUploadSize(data, data->state.infilesize); } /* upload data */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->sockfd = conn->writesockfd; if(result) { - state(data, SSH_SFTP_CLOSE); + wssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = result; } else { - /* store this original bitmask setup to use later on if we can't + /* store this original bitmask setup to use later on if we cannot figure out a "real" bitmask */ sshc->orig_waitfor = data->req.keepon; /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh2 sftp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; - /* since we don't really wait for anything at this point, we want the + /* since we do not really wait for anything at this point, we want the state machine to move on as soon as possible so we set a very short timeout here */ Curl_expire(data, 0, EXPIRE_RUN_NOW); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); } break; } @@ -732,7 +770,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) } else if(rc == WS_SUCCESS) { infof(data, "wolfssh SFTP open succeeded"); - state(data, SSH_SFTP_DOWNLOAD_STAT); + wssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); return CURLE_OK; } @@ -767,13 +805,13 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_SSH; } - size = ((curl_off_t)attrs.sz[1] <<32) | attrs.sz[0]; + size = ((curl_off_t)attrs.sz[1] << 32) | attrs.sz[0]; data->req.size = size; data->req.maxdownload = size; Curl_pgrsSetDownloadSize(data, size); - infof(data, "SFTP download %" CURL_FORMAT_CURL_OFF_T " bytes", size); + infof(data, "SFTP download %" FMT_OFF_T " bytes", size); /* We cannot seek with wolfSSH so resuming and range requests are not possible */ @@ -785,38 +823,42 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) /* Setup the actual download */ if(data->req.size == 0) { /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); infof(data, "File already completely downloaded"); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; } - Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->writesockfd = conn->sockfd; /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh2 recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { /* this should never occur; the close state should be entered at the time the error occurs */ - state(data, SSH_SFTP_CLOSE); + wssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = result; } else { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); } break; } case SSH_SFTP_CLOSE: - if(sshc->handleSz) + if(sshc->handleSz) { rc = wolfSSH_SFTP_Close(sshc->ssh_session, sshc->handle, sshc->handleSz); - else + if(rc != WS_SUCCESS) + rc = wolfSSH_get_error(sshc->ssh_session); + } + else { rc = WS_SUCCESS; /* directory listing */ + } if(rc == WS_WANT_READ) { *block = TRUE; conn->waitfor = KEEP_RECV; @@ -828,7 +870,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_OK; } else if(rc == WS_SUCCESS) { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return CURLE_OK; } @@ -838,10 +880,10 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_SFTP_READDIR_INIT: Curl_pgrsSetDownloadSize(data, -1); if(data->req.no_body) { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; } - state(data, SSH_SFTP_READDIR); + wssh_state(data, sshc, SSH_SFTP_READDIR); break; case SSH_SFTP_READDIR: @@ -869,7 +911,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) data->set.list_only ? name->fName : name->lName); if(!line) { - state(data, SSH_SFTP_CLOSE); + wssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } @@ -883,17 +925,15 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) name = name->next; } wolfSSH_SFTPNAME_list_free(origname); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return result; } failf(data, "wolfssh SFTP ls failed: %d", rc); return CURLE_SSH; case SSH_SFTP_SHUTDOWN: - Curl_safefree(sshc->homedir); - wolfSSH_free(sshc->ssh_session); - wolfSSH_CTX_free(sshc->ctx); - state(data, SSH_STOP); + wssh_sshc_cleanup(sshc); + wssh_state(data, sshc, SSH_STOP); return CURLE_OK; default: break; @@ -906,14 +946,17 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; bool block; /* we store the status and use that to provide a ssh_getsock() implementation */ + if(!sshc) + return CURLE_FAILED_INIT; + do { - result = wssh_statemach_act(data, &block); - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; - /* if there's no error, it isn't done and it didn't EWOULDBLOCK, then + result = wssh_statemach_act(data, sshc, &block); + *done = (sshc->state == SSH_STOP); + /* if there is no error, it is not done and it did not EWOULDBLOCK, then try again */ if(*done) { DEBUGF(infof(data, "wssh_statemach_act says DONE")); @@ -936,6 +979,7 @@ CURLcode wscp_perform(struct Curl_easy *data, static CURLcode wsftp_perform(struct Curl_easy *data, + struct ssh_conn *sshc, bool *connected, bool *dophase_done) { @@ -946,7 +990,7 @@ CURLcode wsftp_perform(struct Curl_easy *data, *dophase_done = FALSE; /* not done yet */ /* start the first command in the DO phase */ - state(data, SSH_SFTP_QUOTE_INIT); + wssh_state(data, sshc, SSH_SFTP_QUOTE_INIT); /* run the state-machine */ result = wssh_multi_statemach(data, dophase_done); @@ -966,11 +1010,14 @@ CURLcode wsftp_perform(struct Curl_easy *data, static CURLcode wssh_do(struct Curl_easy *data, bool *done) { CURLcode result; - bool connected = 0; + bool connected = FALSE; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); *done = FALSE; /* default to false */ + if(!sshc) + return CURLE_FAILED_INIT; + data->req.size = -1; /* make sure this is unknown at this point */ sshc->actualcode = CURLE_OK; /* reset error code */ sshc->secondCreateDirs = 0; /* reset the create dir attempt state @@ -984,24 +1031,24 @@ static CURLcode wssh_do(struct Curl_easy *data, bool *done) if(conn->handler->protocol & CURLPROTO_SCP) result = wscp_perform(data, &connected, done); else - result = wsftp_perform(data, &connected, done); + result = wsftp_perform(data, sshc, &connected, done); return result; } static CURLcode wssh_block_statemach(struct Curl_easy *data, - bool disconnect) + struct ssh_conn *sshc, + bool disconnect) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; while((sshc->state != SSH_STOP) && !result) { bool block; timediff_t left = 1000; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); - result = wssh_statemach_act(data, &block); + result = wssh_statemach_act(data, sshc, &block); if(result) break; @@ -1032,7 +1079,7 @@ static CURLcode wssh_block_statemach(struct Curl_easy *data, /* wait for the socket to become ready */ (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, - left>1000?1000:left); /* ignore result */ + left > 1000 ? 1000 : left); /* ignore result */ } } @@ -1041,20 +1088,19 @@ static CURLcode wssh_block_statemach(struct Curl_easy *data, /* generic done function for both SCP and SFTP called from their specific done functions */ -static CURLcode wssh_done(struct Curl_easy *data, CURLcode status) +static CURLcode wssh_done(struct Curl_easy *data, + struct ssh_conn *sshc, + CURLcode status) { CURLcode result = CURLE_OK; - struct SSHPROTO *sftp_scp = data->req.p.ssh; if(!status) { /* run the state-machine */ - result = wssh_block_statemach(data, FALSE); + result = wssh_block_statemach(data, sshc, FALSE); } else result = status; - if(sftp_scp) - Curl_safefree(sftp_scp->path); if(Curl_pgrsDone(data)) return CURLE_ABORTED_BY_CALLBACK; @@ -1062,6 +1108,19 @@ static CURLcode wssh_done(struct Curl_easy *data, CURLcode status) return result; } +static void wssh_sshc_cleanup(struct ssh_conn *sshc) +{ + if(sshc->ssh_session) { + wolfSSH_free(sshc->ssh_session); + sshc->ssh_session = NULL; + } + if(sshc->ctx) { + wolfSSH_CTX_free(sshc->ctx); + sshc->ctx = NULL; + } + Curl_safefree(sshc->homedir); +} + #if 0 static CURLcode wscp_done(struct Curl_easy *data, CURLcode code, bool premature) @@ -1087,11 +1146,11 @@ static CURLcode wscp_doing(struct Curl_easy *data, static CURLcode wscp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; - (void)data; - (void)conn; (void)dead_connection; - + if(sshc) + wssh_sshc_cleanup(sshc); return result; } #endif @@ -1099,10 +1158,14 @@ static CURLcode wscp_disconnect(struct Curl_easy *data, static CURLcode wsftp_done(struct Curl_easy *data, CURLcode code, bool premature) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); (void)premature; - state(data, SSH_SFTP_CLOSE); + if(!sshc) + return CURLE_FAILED_INIT; + + wssh_state(data, sshc, SSH_SFTP_CLOSE); - return wssh_done(data, code); + return wssh_done(data, sshc, code); } static CURLcode wsftp_doing(struct Curl_easy *data, @@ -1120,17 +1183,20 @@ static CURLcode wsftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; (void)dead; DEBUGF(infof(data, "SSH DISCONNECT starts now")); - if(conn->proto.sshc.ssh_session) { - /* only if there's a session still around to use! */ - state(data, SSH_SFTP_SHUTDOWN); - result = wssh_block_statemach(data, TRUE); + if(sshc && sshc->ssh_session) { + /* only if there is a session still around to use! */ + wssh_state(data, sshc, SSH_SFTP_SHUTDOWN); + result = wssh_block_statemach(data, sshc, TRUE); } + if(sshc) + wssh_sshc_cleanup(sshc); DEBUGF(infof(data, "SSH DISCONNECT is done")); return result; } @@ -1168,6 +1234,7 @@ CURLcode Curl_ssh_init(void) } void Curl_ssh_cleanup(void) { + (void)wolfSSH_Cleanup(); } #endif /* USE_WOLFSSH */ diff --git a/Utilities/cmcurl/lib/vtls/bearssl.c b/Utilities/cmcurl/lib/vtls/bearssl.c index 2b666ca6fea..4b652444510 100644 --- a/Utilities/cmcurl/lib/vtls/bearssl.c +++ b/Utilities/cmcurl/lib/vtls/bearssl.c @@ -21,27 +21,28 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_BEARSSL #include #include "bearssl.h" -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "cipher_suite.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" #include "vtls.h" #include "vtls_int.h" -#include "connect.h" -#include "select.h" -#include "multiif.h" -#include "curl_printf.h" -#include "strcase.h" +#include "vtls_scache.h" +#include "../connect.h" +#include "../select.h" +#include "../multiif.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" struct x509_context { const br_x509_class *vtable; @@ -52,7 +53,7 @@ struct x509_context { int cert_num; }; -struct ssl_backend_data { +struct bearssl_ssl_backend_data { br_ssl_client_context ctx; struct x509_context x509; unsigned char buf[BR_SSL_BUFSIZE_BIDI]; @@ -63,6 +64,7 @@ struct ssl_backend_data { bool active; /* size of pending write, yet to be flushed */ size_t pending_write; + BIT(sent_shutdown); }; struct cafile_parser { @@ -120,9 +122,9 @@ static CURLcode load_cafile(struct cafile_source *source, br_x509_pkey *pkey; FILE *fp = 0; unsigned char buf[BUFSIZ]; - const unsigned char *p; + const unsigned char *p = NULL; const char *name; - size_t n, i, pushed; + size_t n = 0, i, pushed; DEBUGASSERT(source->type == CAFILE_SOURCE_PATH || source->type == CAFILE_SOURCE_BLOB); @@ -151,7 +153,7 @@ static CURLcode load_cafile(struct cafile_source *source, } else if(source->type == CAFILE_SOURCE_BLOB) { n = source->len; - p = (unsigned char *) source->data; + p = (const unsigned char *) source->data; } while(n) { pushed = br_pem_decoder_push(&pc, p, n); @@ -327,7 +329,7 @@ static unsigned x509_end_chain(const br_x509_class **ctx) struct x509_context *x509 = (struct x509_context *)ctx; if(!x509->verifypeer) { - return br_x509_decoder_last_error(&x509->decoder); + return (unsigned)br_x509_decoder_last_error(&x509->decoder); } return x509->minimal.vtable->end_chain(&x509->minimal.vtable); @@ -336,7 +338,7 @@ static unsigned x509_end_chain(const br_x509_class **ctx) static const br_x509_pkey *x509_get_pkey(const br_x509_class *const *ctx, unsigned *usages) { - struct x509_context *x509 = (struct x509_context *)ctx; + struct x509_context *x509 = (struct x509_context *)CURL_UNCONST(ctx); if(!x509->verifypeer) { /* Nothing in the chain is verified, just return the public key of the @@ -360,213 +362,171 @@ static const br_x509_class x509_vtable = { x509_get_pkey }; -struct st_cipher { - const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ - const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ - uint16_t num; /* BearSSL cipher suite */ -}; +static CURLcode +bearssl_set_ssl_version_min_max(struct Curl_easy *data, + br_ssl_engine_context *ssl_eng, + struct ssl_primary_config *conn_config) +{ + unsigned version_min, version_max; -/* Macro to initialize st_cipher data structure */ -#define CIPHER_DEF(num, alias) { #num, alias, BR_##num } + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + version_min = BR_TLS10; + break; + case CURL_SSLVERSION_TLSv1_1: + version_min = BR_TLS11; + break; + case CURL_SSLVERSION_TLSv1_2: + version_min = BR_TLS12; + break; + case CURL_SSLVERSION_TLSv1_3: + failf(data, "BearSSL: does not support TLS 1.3"); + return CURLE_SSL_CONNECT_ERROR; + default: + failf(data, "BearSSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } -static const struct st_cipher ciphertable[] = { + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + case CURL_SSLVERSION_MAX_TLSv1_2: + version_max = BR_TLS12; + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + version_max = BR_TLS11; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + version_max = BR_TLS10; + break; + default: + failf(data, "BearSSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + br_ssl_engine_set_versions(ssl_eng, version_min, version_max); + + return CURLE_OK; +} + +static const uint16_t ciphertable[] = { /* RFC 2246 TLS 1.0 */ - CIPHER_DEF(TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ - "DES-CBC3-SHA"), + BR_TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ /* RFC 3268 TLS 1.0 AES */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ - "AES128-SHA"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ - "AES256-SHA"), + BR_TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ /* RFC 5246 TLS 1.2 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ - "AES128-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ - "AES256-SHA256"), + BR_TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ /* RFC 5288 TLS 1.2 AES GCM */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ - "AES128-GCM-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ - "AES256-GCM-SHA384"), + BR_TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + BR_TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ /* RFC 4492 TLS 1.0 ECC */ - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ - "ECDH-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ - "ECDH-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ - "ECDH-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ - "ECDHE-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ - "ECDHE-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ - "ECDHE-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ - "ECDH-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ - "ECDH-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ - "ECDH-RSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ - "ECDHE-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ - "ECDHE-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ - "ECDHE-RSA-AES256-SHA"), + BR_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ + BR_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + BR_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ + BR_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ /* RFC 5289 TLS 1.2 ECC HMAC SHA256/384 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ - "ECDHE-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ - "ECDHE-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ - "ECDH-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ - "ECDH-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ - "ECDHE-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ - "ECDHE-RSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ - "ECDH-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ - "ECDH-RSA-AES256-SHA384"), + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ /* RFC 5289 TLS 1.2 GCM */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ - "ECDHE-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ - "ECDHE-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ - "ECDH-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ - "ECDH-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ - "ECDHE-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ - "ECDHE-RSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ - "ECDH-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ - "ECDH-RSA-AES256-GCM-SHA384"), -#ifdef BR_TLS_RSA_WITH_AES_128_CCM + BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ + BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ + BR_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ + BR_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ +#ifdef BR_TLS_RSA_WITH_AES_128_CCM /* RFC 6655 TLS 1.2 CCM Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ - "AES128-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ - "AES256-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ - "AES128-CCM8"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ - "AES256-CCM8"), + BR_TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ + BR_TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ + BR_TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ + BR_TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ /* RFC 7251 TLS 1.2 ECC CCM Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ - "ECDHE-ECDSA-AES128-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ - "ECDHE-ECDSA-AES256-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ - "ECDHE-ECDSA-AES128-CCM8"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ - "ECDHE-ECDSA-AES256-CCM8"), + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ #endif /* RFC 7905 TLS 1.2 ChaCha20-Poly1305 Supported since BearSSL 0.2 */ - CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ - "ECDHE-RSA-CHACHA20-POLY1305"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ - "ECDHE-ECDSA-CHACHA20-POLY1305"), + BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ }; -#define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0])) -#define CIPHER_NAME_BUF_LEN 64 - -static bool is_separator(char c) -{ - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; -} +#define NUM_OF_CIPHERS CURL_ARRAYSIZE(ciphertable) static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, br_ssl_engine_context *ssl_eng, const char *ciphers) { - uint16_t selected_ciphers[NUM_OF_CIPHERS]; - size_t selected_count = 0; - char cipher_name[CIPHER_NAME_BUF_LEN]; - const char *cipher_start = ciphers; - const char *cipher_end; - size_t i, j; - - if(!cipher_start) - return CURLE_SSL_CIPHER; - - while(true) { - /* Extract the next cipher name from the ciphers string */ - while(is_separator(*cipher_start)) - ++cipher_start; - if(*cipher_start == '\0') - break; - cipher_end = cipher_start; - while(*cipher_end != '\0' && !is_separator(*cipher_end)) - ++cipher_end; - j = cipher_end - cipher_start < CIPHER_NAME_BUF_LEN - 1 ? - cipher_end - cipher_start : CIPHER_NAME_BUF_LEN - 1; - strncpy(cipher_name, cipher_start, j); - cipher_name[j] = '\0'; - cipher_start = cipher_end; - - /* Lookup the cipher name in the table of available ciphers. If the cipher - name starts with "TLS_" we do the lookup by IANA name. Otherwise, we try - to match cipher name by an (OpenSSL) alias. */ - if(strncasecompare(cipher_name, "TLS_", 4)) { - for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].name); ++i); - } - else { - for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].alias_name); ++i); + uint16_t selected[NUM_OF_CIPHERS]; + size_t count = 0, i; + const char *ptr, *end; + + for(ptr = ciphers; ptr[0] != '\0' && count < NUM_OF_CIPHERS; ptr = end) { + uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < NUM_OF_CIPHERS && ciphertable[i] != id; i++); + if(i == NUM_OF_CIPHERS) + id = 0; } - if(i == NUM_OF_CIPHERS) { - infof(data, "BearSSL: unknown cipher in list: %s", cipher_name); + if(!id) { + if(ptr[0] != '\0') + infof(data, "BearSSL: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); continue; } /* No duplicates allowed */ - for(j = 0; j < selected_count && - selected_ciphers[j] != ciphertable[i].num; j++); - if(j < selected_count) { - infof(data, "BearSSL: duplicate cipher in list: %s", cipher_name); + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "BearSSL: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); continue; } - DEBUGASSERT(selected_count < NUM_OF_CIPHERS); - selected_ciphers[selected_count] = ciphertable[i].num; - ++selected_count; + selected[count++] = id; } - if(selected_count == 0) { + if(count == 0) { failf(data, "BearSSL: no supported cipher in list"); return CURLE_SSL_CIPHER; } - br_ssl_engine_set_suites(ssl_eng, selected_ciphers, selected_count); + br_ssl_engine_set_suites(ssl_eng, selected, count); return CURLE_OK; } @@ -574,100 +534,69 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_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); const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ (ca_info_blob ? NULL : conn_config->CAfile); - const char *hostname = connssl->hostname; + const char *hostname = connssl->peer.hostname; const bool verifypeer = conn_config->verifypeer; const bool verifyhost = conn_config->verifyhost; CURLcode ret; - unsigned version_min, version_max; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif + int session_set = 0; DEBUGASSERT(backend); - - switch(conn_config->version) { - case CURL_SSLVERSION_SSLv2: - failf(data, "BearSSL does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv3: - failf(data, "BearSSL does not support SSLv3"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_0: - version_min = BR_TLS10; - version_max = BR_TLS10; - break; - case CURL_SSLVERSION_TLSv1_1: - version_min = BR_TLS11; - version_max = BR_TLS11; - break; - case CURL_SSLVERSION_TLSv1_2: - version_min = BR_TLS12; - version_max = BR_TLS12; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - version_min = BR_TLS10; - version_max = BR_TLS12; - break; - default: - failf(data, "BearSSL: unknown CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(ca_info_blob) { - struct cafile_source source; - source.type = CAFILE_SOURCE_BLOB; - source.data = ca_info_blob->data; - source.len = ca_info_blob->len; - - ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); - if(ret != CURLE_OK) { - if(verifypeer) { + CURL_TRC_CF(data, cf, "connect_step1"); + + if(verifypeer) { + if(ca_info_blob) { + struct cafile_source source; + source.type = CAFILE_SOURCE_BLOB; + source.data = ca_info_blob->data; + source.len = ca_info_blob->len; + + CURL_TRC_CF(data, cf, "connect_step1, load ca_info_blob"); + ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); + if(ret != CURLE_OK) { failf(data, "error importing CA certificate blob"); return ret; } - /* Only warn if no certificate verification is required. */ - infof(data, "error importing CA certificate blob, continuing anyway"); } - } - if(ssl_cafile) { - struct cafile_source source; - source.type = CAFILE_SOURCE_PATH; - source.data = ssl_cafile; - source.len = 0; + if(ssl_cafile) { + struct cafile_source source; + source.type = CAFILE_SOURCE_PATH; + source.data = ssl_cafile; + source.len = 0; - ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); - if(ret != CURLE_OK) { - if(verifypeer) { + CURL_TRC_CF(data, cf, "connect_step1, load cafile"); + ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); + if(ret != CURLE_OK) { failf(data, "error setting certificate verify locations." " CAfile: %s", ssl_cafile); return ret; } - infof(data, "error setting certificate verify locations," - " continuing anyway:"); } } /* initialize SSL context */ br_ssl_client_init_full(&backend->ctx, &backend->x509.minimal, backend->anchors, backend->anchors_len); - br_ssl_engine_set_versions(&backend->ctx.eng, version_min, version_max); + + ret = bearssl_set_ssl_version_min_max(data, &backend->ctx.eng, conn_config); + if(ret != CURLE_OK) + return ret; + br_ssl_engine_set_buffer(&backend->ctx.eng, backend->buf, sizeof(backend->buf), 1); if(conn_config->cipher_list) { /* Override the ciphers as specified. For the default cipher list see the BearSSL source code of br_ssl_client_init_full() */ + CURL_TRC_CF(data, cf, "connect_step1, set ciphers"); ret = bearssl_set_selected_ciphers(data, &backend->ctx.eng, conn_config->cipher_list); if(ret) @@ -680,15 +609,20 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, backend->x509.verifyhost = verifyhost; br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable); - if(ssl_config->primary.sessionid) { - void *session; + if(ssl_config->primary.cache_session) { + struct Curl_ssl_session *sc_session = NULL; - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &session, NULL)) { + ret = Curl_ssl_scache_take(cf, data, connssl->peer.scache_key, + &sc_session); + if(!ret && sc_session && sc_session->sdata && sc_session->sdata_len) { + const br_ssl_session_parameters *session; + session = (const br_ssl_session_parameters *)sc_session->sdata; br_ssl_engine_set_session_parameters(&backend->ctx.eng, session); - infof(data, "BearSSL: re-using session ID"); + session_set = 1; + infof(data, "BearSSL: reusing session ID"); + /* single use of sessions */ + Curl_ssl_scache_return(cf, data, connssl->peer.scache_key, sc_session); } - Curl_ssl_sessionid_unlock(data); } if(connssl->alpn) { @@ -704,11 +638,7 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } - if((1 == Curl_inet_pton(AF_INET, hostname, &addr)) -#ifdef ENABLE_IPV6 - || (1 == Curl_inet_pton(AF_INET6, hostname, &addr)) -#endif - ) { + if(connssl->peer.type != CURL_SSL_PEER_DNS) { if(verifyhost) { failf(data, "BearSSL: " "host verification of IP address is not supported"); @@ -717,27 +647,27 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, hostname = NULL; } else { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { + if(!connssl->peer.sni) { failf(data, "Failed to set SNI"); return CURLE_SSL_CONNECT_ERROR; } - hostname = snihost; + hostname = connssl->peer.sni; + CURL_TRC_CF(data, cf, "connect_step1, SNI set"); } /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); ret = (*data->set.ssl.fsslctx)(data, &backend->ctx, data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(ret) { failf(data, "BearSSL: error signaled by ssl ctx callback"); return ret; } } - if(!br_ssl_client_reset(&backend->ctx, hostname, 1)) + if(!br_ssl_client_reset(&backend->ctx, hostname, session_set)) return CURLE_FAILED_INIT; backend->active = TRUE; @@ -751,7 +681,8 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, unsigned target) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; unsigned state; unsigned char *buf; size_t len; @@ -761,6 +692,7 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, DEBUGASSERT(backend); + connssl->io_need = CURL_SSL_IO_NEED_NONE; for(;;) { state = br_ssl_engine_current_state(&backend->ctx.eng); if(state & BR_SSL_CLOSED) { @@ -785,7 +717,9 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, failf(data, "SSL: X.509 verification: " "chain could not be linked to a trust anchor"); return CURLE_PEER_FAILED_VERIFICATION; + default:; } + failf(data, "BearSSL: connection error 0x%04x", err); /* X.509 errors are documented to have the range 32..63 */ if(err >= 32 && err < 64) return CURLE_PEER_FAILED_VERIFICATION; @@ -795,8 +729,12 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, return CURLE_OK; if(state & BR_SSL_SENDREC) { buf = br_ssl_engine_sendrec_buf(&backend->ctx.eng, &len); - ret = Curl_conn_cf_send(cf->next, data, (char *)buf, len, &result); + ret = Curl_conn_cf_send(cf->next, data, (const char *)buf, len, FALSE, + &result); + CURL_TRC_CF(data, cf, "ssl_send(len=%zu) -> %zd, %d", len, ret, result); if(ret <= 0) { + if(result == CURLE_AGAIN) + connssl->io_need |= CURL_SSL_IO_NEED_SEND; return result; } br_ssl_engine_sendrec_ack(&backend->ctx.eng, ret); @@ -804,11 +742,14 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, else if(state & BR_SSL_RECVREC) { buf = br_ssl_engine_recvrec_buf(&backend->ctx.eng, &len); ret = Curl_conn_cf_recv(cf->next, data, (char *)buf, len, &result); + CURL_TRC_CF(data, cf, "ssl_recv(len=%zu) -> %zd, %d", len, ret, result); if(ret == 0) { failf(data, "SSL: EOF without close notify"); - return CURLE_READ_ERROR; + return CURLE_RECV_ERROR; } if(ret <= 0) { + if(result == CURLE_AGAIN) + connssl->io_need |= CURL_SSL_IO_NEED_RECV; return result; } br_ssl_engine_recvrec_ack(&backend->ctx.eng, ret); @@ -820,20 +761,45 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + br_ssl_session_parameters session; + char cipher_str[64]; CURLcode ret; DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step2"); ret = bearssl_run_until(cf, data, BR_SSL_SENDAPP | BR_SSL_RECVAPP); if(ret == CURLE_AGAIN) return CURLE_OK; if(ret == CURLE_OK) { + unsigned int tver; + int subver = 0; + if(br_ssl_engine_current_state(&backend->ctx.eng) == BR_SSL_CLOSED) { failf(data, "SSL: connection closed during handshake"); return CURLE_SSL_CONNECT_ERROR; } connssl->connecting_state = ssl_connect_3; + /* Informational message */ + tver = br_ssl_engine_get_version(&backend->ctx.eng); + switch(tver) { + case BR_TLS12: + subver = 2; /* 1.2 */ + break; + case BR_TLS11: + subver = 1; /* 1.1 */ + break; + case BR_TLS10: /* 1.0 */ + default: /* unknown, leave it at zero */ + break; + } + br_ssl_engine_get_session_parameters(&backend->ctx.eng, &session); + Curl_cipher_suite_get_str(session.cipher_suite, cipher_str, + sizeof(cipher_str), TRUE); + infof(data, "BearSSL: TLS v1.%d connection using %s", subver, + cipher_str); } return ret; } @@ -842,42 +808,42 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); CURLcode ret; DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step3"); if(connssl->alpn) { const char *proto; proto = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); - Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, - proto? strlen(proto) : 0); + Curl_alpn_set_negotiated(cf, data, connssl, (const unsigned char *)proto, + proto ? strlen(proto) : 0); } - if(ssl_config->primary.sessionid) { - bool incache; - bool added = FALSE; - void *oldsession; + if(ssl_config->primary.cache_session) { + struct Curl_ssl_session *sc_session; br_ssl_session_parameters *session; session = malloc(sizeof(*session)); if(!session) return CURLE_OUT_OF_MEMORY; br_ssl_engine_get_session_parameters(&backend->ctx.eng, session); - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &oldsession, NULL)); - if(incache) - Curl_ssl_delsessionid(data, oldsession); - ret = Curl_ssl_addsessionid(cf, data, session, 0, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) - free(session); - if(ret) { - return CURLE_OUT_OF_MEMORY; + ret = Curl_ssl_session_create((unsigned char *)session, sizeof(*session), + (int)session->version, + connssl->negotiated.alpn, + 0, 0, &sc_session); + if(!ret) { + ret = Curl_ssl_scache_put(cf, data, connssl->peer.scache_key, + sc_session); + /* took ownership of `sc_session` */ } + if(ret) + return ret; } connssl->connecting_state = ssl_connect_done; @@ -889,7 +855,8 @@ static ssize_t bearssl_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; unsigned char *app; size_t applen; @@ -923,7 +890,8 @@ static ssize_t bearssl_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; unsigned char *app; size_t applen; @@ -943,82 +911,33 @@ static ssize_t bearssl_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return applen; } -static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode bearssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { CURLcode ret; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - timediff_t timeout_ms; - int what; + CURL_TRC_CF(data, cf, "connect()"); /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { + CURL_TRC_CF(data, cf, "connect_common, connected"); *done = TRUE; return CURLE_OK; } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(ssl_connect_1 == connssl->connecting_state) { ret = bearssl_connect_step1(cf, data); if(ret) return ret; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ + if(ssl_connect_2 == connssl->connecting_state) { ret = bearssl_connect_step2(cf, data); - if(ret || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(ret) return ret; } @@ -1032,11 +951,6 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, connssl->state = ssl_connection_complete; *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; return CURLE_OK; } @@ -1050,10 +964,12 @@ static bool bearssl_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct ssl_connect_data *ctx = cf->ctx; + struct bearssl_ssl_backend_data *backend; (void)data; DEBUGASSERT(ctx && ctx->backend); - return br_ssl_engine_current_state(&ctx->backend->ctx.eng) & BR_SSL_RECVAPP; + backend = (struct bearssl_ssl_backend_data *)ctx->backend; + return br_ssl_engine_current_state(&backend->ctx.eng) & BR_SSL_RECVAPP; } static CURLcode bearssl_random(struct Curl_easy *data UNUSED_PARAM, @@ -1076,49 +992,63 @@ static CURLcode bearssl_random(struct Curl_easy *data UNUSED_PARAM, return CURLE_OK; } -static CURLcode bearssl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void *bearssl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) { - CURLcode ret; - bool done = FALSE; - - ret = bearssl_connect_common(cf, data, FALSE, &done); - if(ret) - return ret; - - DEBUGASSERT(done); - - return CURLE_OK; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + return &backend->ctx; } -static CURLcode bearssl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) +static CURLcode bearssl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { - return bearssl_connect_common(cf, data, TRUE, done); -} + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + CURLcode result; -static void *bearssl_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; DEBUGASSERT(backend); - return &backend->ctx; + if(!backend->active || cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + if(!backend->sent_shutdown) { + (void)send_shutdown; /* unknown how to suppress our close notify */ + br_ssl_engine_close(&backend->ctx.eng); + backend->sent_shutdown = TRUE; + } + + result = bearssl_run_until(cf, data, BR_SSL_CLOSED); + if(result == CURLE_OK) { + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "shutdown EAGAIN, io_need=%x", connssl->io_need); + result = CURLE_OK; + } + else + CURL_TRC_CF(data, cf, "shutdown error: %d", result); + + cf->shutdown = (result || *done); + return result; } static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; size_t i; + (void)data; DEBUGASSERT(backend); - if(backend->active) { - backend->active = FALSE; - br_ssl_engine_close(&backend->ctx.eng); - (void)bearssl_run_until(cf, data, BR_SSL_CLOSED); - } + backend->active = FALSE; if(backend->anchors) { for(i = 0; i < backend->anchors_len; ++i) free(backend->anchors[i].dn.data); @@ -1126,11 +1056,6 @@ static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) } } -static void bearssl_session_free(void *ptr) -{ - free(ptr); -} - static CURLcode bearssl_sha256sum(const unsigned char *input, size_t inputlen, unsigned char *sha256sum, @@ -1146,34 +1071,34 @@ static CURLcode bearssl_sha256sum(const unsigned char *input, const struct Curl_ssl Curl_ssl_bearssl = { { CURLSSLBACKEND_BEARSSL, "bearssl" }, /* info */ - SSLSUPP_CAINFO_BLOB | SSLSUPP_SSL_CTX | SSLSUPP_HTTPS_PROXY, - sizeof(struct ssl_backend_data), - Curl_none_init, /* init */ - Curl_none_cleanup, /* cleanup */ + SSLSUPP_CAINFO_BLOB | + SSLSUPP_SSL_CTX | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, + + sizeof(struct bearssl_ssl_backend_data), + + NULL, /* init */ + NULL, /* cleanup */ bearssl_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ + bearssl_shutdown, /* shutdown */ bearssl_data_pending, /* data_pending */ bearssl_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ + NULL, /* cert_status_request */ bearssl_connect, /* connect */ - bearssl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ bearssl_get_internals, /* get_internals */ bearssl_close, /* close_one */ - Curl_none_close_all, /* close_all */ - bearssl_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ + NULL, /* false_start */ bearssl_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ bearssl_recv, /* recv decrypted data */ bearssl_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_BEARSSL */ diff --git a/Utilities/cmcurl/lib/vtls/bearssl.h b/Utilities/cmcurl/lib/vtls/bearssl.h index b3651b092c1..8bb254f7da0 100644 --- a/Utilities/cmcurl/lib/vtls/bearssl.h +++ b/Utilities/cmcurl/lib/vtls/bearssl.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_BEARSSL diff --git a/Utilities/cmcurl/lib/vtls/cipher_suite.c b/Utilities/cmcurl/lib/vtls/cipher_suite.c new file mode 100644 index 00000000000..d058bd3f0e0 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/cipher_suite.c @@ -0,0 +1,891 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "../curl_setup.h" + +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#include "cipher_suite.h" +#include "../curl_printf.h" +#include "../strcase.h" +#include + +/* + * To support the CURLOPT_SSL_CIPHER_LIST option on SSL backends + * that do not support it natively, but do support setting a list of + * IANA ids, we need a list of all supported cipher suite names + * (OpenSSL and IANA) to be able to look up the IANA ids. + * + * To keep the binary size of this list down we compress each entry + * down to 2 + 6 bytes using the C preprocessor. + */ + +/* + * mbedTLS NOTE: mbedTLS has mbedtls_ssl_get_ciphersuite_id() to + * convert a string representation to an IANA id, we do not use that + * because it does not support "standard" OpenSSL cipher suite + * names, nor IANA names. + */ + +/* NOTE: also see tests/unit/unit3205.c */ + +/* Text for cipher suite parts (max 64 entries), + keep indexes below in sync with this! */ +static const char *cs_txt = + "\0" + "TLS" "\0" + "WITH" "\0" + "128" "\0" + "256" "\0" + "3DES" "\0" + "8" "\0" + "AES" "\0" + "AES128" "\0" + "AES256" "\0" + "CBC" "\0" + "CBC3" "\0" + "CCM" "\0" + "CCM8" "\0" + "CHACHA20" "\0" + "DES" "\0" + "DHE" "\0" + "ECDH" "\0" + "ECDHE" "\0" + "ECDSA" "\0" + "EDE" "\0" + "GCM" "\0" + "MD5" "\0" + "NULL" "\0" + "POLY1305" "\0" + "PSK" "\0" + "RSA" "\0" + "SHA" "\0" + "SHA256" "\0" + "SHA384" "\0" +#if defined(USE_MBEDTLS) + "ARIA" "\0" + "ARIA128" "\0" + "ARIA256" "\0" + "CAMELLIA" "\0" + "CAMELLIA128" "\0" + "CAMELLIA256" "\0" +#endif +#if defined(USE_SECTRANSP) + "40" "\0" + "ADH" "\0" + "AECDH" "\0" + "anon" "\0" + "DES40" "\0" + "DH" "\0" + "DSS" "\0" + "EDH" "\0" + "EXP" "\0" + "EXPORT" "\0" + "IDEA" "\0" + "RC2" "\0" + "RC4" "\0" +#endif +; +/* Indexes of above cs_txt */ +enum { + CS_TXT_IDX_, + CS_TXT_IDX_TLS, + CS_TXT_IDX_WITH, + CS_TXT_IDX_128, + CS_TXT_IDX_256, + CS_TXT_IDX_3DES, + CS_TXT_IDX_8, + CS_TXT_IDX_AES, + CS_TXT_IDX_AES128, + CS_TXT_IDX_AES256, + CS_TXT_IDX_CBC, + CS_TXT_IDX_CBC3, + CS_TXT_IDX_CCM, + CS_TXT_IDX_CCM8, + CS_TXT_IDX_CHACHA20, + CS_TXT_IDX_DES, + CS_TXT_IDX_DHE, + CS_TXT_IDX_ECDH, + CS_TXT_IDX_ECDHE, + CS_TXT_IDX_ECDSA, + CS_TXT_IDX_EDE, + CS_TXT_IDX_GCM, + CS_TXT_IDX_MD5, + CS_TXT_IDX_NULL, + CS_TXT_IDX_POLY1305, + CS_TXT_IDX_PSK, + CS_TXT_IDX_RSA, + CS_TXT_IDX_SHA, + CS_TXT_IDX_SHA256, + CS_TXT_IDX_SHA384, +#if defined(USE_MBEDTLS) + CS_TXT_IDX_ARIA, + CS_TXT_IDX_ARIA128, + CS_TXT_IDX_ARIA256, + CS_TXT_IDX_CAMELLIA, + CS_TXT_IDX_CAMELLIA128, + CS_TXT_IDX_CAMELLIA256, +#endif +#if defined(USE_SECTRANSP) + CS_TXT_IDX_40, + CS_TXT_IDX_ADH, + CS_TXT_IDX_AECDH, + CS_TXT_IDX_anon, + CS_TXT_IDX_DES40, + CS_TXT_IDX_DH, + CS_TXT_IDX_DSS, + CS_TXT_IDX_EDH, + CS_TXT_IDX_EXP, + CS_TXT_IDX_EXPORT, + CS_TXT_IDX_IDEA, + CS_TXT_IDX_RC2, + CS_TXT_IDX_RC4, +#endif + CS_TXT_LEN, +}; + +#define CS_ZIP_IDX(a, b, c, d, e, f, g, h) \ +{ \ + (uint8_t) ((((a) << 2) & 0xFF) | ((b) & 0x3F) >> 4), \ + (uint8_t) ((((b) << 4) & 0xFF) | ((c) & 0x3F) >> 2), \ + (uint8_t) ((((c) << 6) & 0xFF) | ((d) & 0x3F)), \ + (uint8_t) ((((e) << 2) & 0xFF) | ((f) & 0x3F) >> 4), \ + (uint8_t) ((((f) << 4) & 0xFF) | ((g) & 0x3F) >> 2), \ + (uint8_t) ((((g) << 6) & 0xFF) | ((h) & 0x3F)) \ +} +#define CS_ENTRY(id, a, b, c, d, e, f, g, h) \ +{ \ + id, \ + CS_ZIP_IDX( \ + CS_TXT_IDX_ ## a, CS_TXT_IDX_ ## b, \ + CS_TXT_IDX_ ## c, CS_TXT_IDX_ ## d, \ + CS_TXT_IDX_ ## e, CS_TXT_IDX_ ## f, \ + CS_TXT_IDX_ ## g, CS_TXT_IDX_ ## h \ + ) \ +} + +struct cs_entry { + uint16_t id; + uint8_t zip[6]; +}; + +/* !checksrc! disable COMMANOSPACE all */ +static const struct cs_entry cs_list [] = { + /* TLS 1.3 ciphers */ +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_RUSTLS) + CS_ENTRY(0x1301, TLS,AES,128,GCM,SHA256,,,), + CS_ENTRY(0x1302, TLS,AES,256,GCM,SHA384,,,), + CS_ENTRY(0x1303, TLS,CHACHA20,POLY1305,SHA256,,,,), + CS_ENTRY(0x1304, TLS,AES,128,CCM,SHA256,,,), + CS_ENTRY(0x1305, TLS,AES,128,CCM,8,SHA256,,), +#endif + /* TLS 1.2 ciphers */ + CS_ENTRY(0xC02B, TLS,ECDHE,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02B, ECDHE,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02C, TLS,ECDHE,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02C, ECDHE,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC02F, TLS,ECDHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02F, ECDHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC030, TLS,ECDHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC030, ECDHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xCCA8, TLS,ECDHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA8, ECDHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCA9, TLS,ECDHE,ECDSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA9, ECDHE,ECDSA,CHACHA20,POLY1305,,,,), +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_BEARSSL) + CS_ENTRY(0x002F, TLS,RSA,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x002F, AES128,SHA,,,,,,), + CS_ENTRY(0x0035, TLS,RSA,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x0035, AES256,SHA,,,,,,), + CS_ENTRY(0x003C, TLS,RSA,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x003C, AES128,SHA256,,,,,,), + CS_ENTRY(0x003D, TLS,RSA,WITH,AES,256,CBC,SHA256,), + CS_ENTRY(0x003D, AES256,SHA256,,,,,,), + CS_ENTRY(0x009C, TLS,RSA,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x009C, AES128,GCM,SHA256,,,,,), + CS_ENTRY(0x009D, TLS,RSA,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x009D, AES256,GCM,SHA384,,,,,), + CS_ENTRY(0xC004, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC004, ECDH,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC005, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC005, ECDH,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC009, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC009, ECDHE,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC00A, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00A, ECDHE,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC00E, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC00E, ECDH,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC00F, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00F, ECDH,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC013, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC013, ECDHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC014, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC014, ECDHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC023, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC023, ECDHE,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC024, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC024, ECDHE,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC025, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC025, ECDH,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC026, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC026, ECDH,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC027, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC027, ECDHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC028, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC028, ECDHE,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC029, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC029, ECDH,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC02A, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC02A, ECDH,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC02D, TLS,ECDH,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02D, ECDH,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02E, TLS,ECDH,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02E, ECDH,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC031, TLS,ECDH,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC031, ECDH,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC032, TLS,ECDH,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC032, ECDH,RSA,AES256,GCM,SHA384,,,), +#endif +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) + CS_ENTRY(0x0001, TLS,RSA,WITH,NULL,MD5,,,), + CS_ENTRY(0x0001, NULL,MD5,,,,,,), + CS_ENTRY(0x0002, TLS,RSA,WITH,NULL,SHA,,,), + CS_ENTRY(0x0002, NULL,SHA,,,,,,), + CS_ENTRY(0x002C, TLS,PSK,WITH,NULL,SHA,,,), + CS_ENTRY(0x002C, PSK,NULL,SHA,,,,,), + CS_ENTRY(0x002D, TLS,DHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002D, DHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0x002E, TLS,RSA,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002E, RSA,PSK,NULL,SHA,,,,), + CS_ENTRY(0x0033, TLS,DHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0033, DHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0x0039, TLS,DHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0039, DHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0x003B, TLS,RSA,WITH,NULL,SHA256,,,), + CS_ENTRY(0x003B, NULL,SHA256,,,,,,), + CS_ENTRY(0x0067, TLS,DHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x0067, DHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0x006B, TLS,DHE,RSA,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006B, DHE,RSA,AES256,SHA256,,,,), + CS_ENTRY(0x008C, TLS,PSK,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x008C, PSK,AES128,CBC,SHA,,,,), + CS_ENTRY(0x008D, TLS,PSK,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x008D, PSK,AES256,CBC,SHA,,,,), + CS_ENTRY(0x0090, TLS,DHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0090, DHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0091, TLS,DHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0091, DHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x0094, TLS,RSA,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0094, RSA,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0095, TLS,RSA,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0095, RSA,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x009E, TLS,DHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x009E, DHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0x009F, TLS,DHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x009F, DHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A8, TLS,PSK,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x00A8, PSK,AES128,GCM,SHA256,,,,), + CS_ENTRY(0x00A9, TLS,PSK,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x00A9, PSK,AES256,GCM,SHA384,,,,), + CS_ENTRY(0x00AA, TLS,DHE,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AA, DHE,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AB, TLS,DHE,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AB, DHE,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AC, TLS,RSA,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AC, RSA,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AD, TLS,RSA,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AD, RSA,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AE, TLS,PSK,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x00AE, PSK,AES128,CBC,SHA256,,,,), + CS_ENTRY(0x00AF, TLS,PSK,WITH,AES,256,CBC,SHA384,), + CS_ENTRY(0x00AF, PSK,AES256,CBC,SHA384,,,,), + CS_ENTRY(0x00B0, TLS,PSK,WITH,NULL,SHA256,,,), + CS_ENTRY(0x00B0, PSK,NULL,SHA256,,,,,), + CS_ENTRY(0x00B1, TLS,PSK,WITH,NULL,SHA384,,,), + CS_ENTRY(0x00B1, PSK,NULL,SHA384,,,,,), + CS_ENTRY(0x00B2, TLS,DHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B2, DHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B3, TLS,DHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B3, DHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B4, TLS,DHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B4, DHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B5, TLS,DHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B5, DHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0x00B6, TLS,RSA,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B6, RSA,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B7, TLS,RSA,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B7, RSA,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B8, TLS,RSA,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B8, RSA,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B9, TLS,RSA,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B9, RSA,PSK,NULL,SHA384,,,,), + CS_ENTRY(0xC001, TLS,ECDH,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC001, ECDH,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC006, TLS,ECDHE,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC006, ECDHE,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC00B, TLS,ECDH,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC00B, ECDH,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC010, TLS,ECDHE,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC010, ECDHE,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC035, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC035, ECDHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0xC036, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC036, ECDHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0xCCAB, TLS,PSK,WITH,CHACHA20,POLY1305,SHA256,,), + CS_ENTRY(0xCCAB, PSK,CHACHA20,POLY1305,,,,,), +#endif +#if defined(USE_SECTRANSP) || defined(USE_BEARSSL) + CS_ENTRY(0x000A, TLS,RSA,WITH,3DES,EDE,CBC,SHA,), + CS_ENTRY(0x000A, DES,CBC3,SHA,,,,,), + CS_ENTRY(0xC003, TLS,ECDH,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC003, ECDH,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC008, TLS,ECDHE,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC008, ECDHE,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC00D, TLS,ECDH,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC00D, ECDH,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC012, TLS,ECDHE,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC012, ECDHE,RSA,DES,CBC3,SHA,,,), +#endif +#if defined(USE_MBEDTLS) || defined(USE_BEARSSL) + CS_ENTRY(0xC09C, TLS,RSA,WITH,AES,128,CCM,,), + CS_ENTRY(0xC09C, AES128,CCM,,,,,,), + CS_ENTRY(0xC09D, TLS,RSA,WITH,AES,256,CCM,,), + CS_ENTRY(0xC09D, AES256,CCM,,,,,,), + CS_ENTRY(0xC0A0, TLS,RSA,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A0, AES128,CCM8,,,,,,), + CS_ENTRY(0xC0A1, TLS,RSA,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A1, AES256,CCM8,,,,,,), + CS_ENTRY(0xC0AC, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC0AC, ECDHE,ECDSA,AES128,CCM,,,,), + CS_ENTRY(0xC0AD, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC0AD, ECDHE,ECDSA,AES256,CCM,,,,), + CS_ENTRY(0xC0AE, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AE, ECDHE,ECDSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0AF, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AF, ECDHE,ECDSA,AES256,CCM8,,,,), +#endif +#if defined(USE_SECTRANSP) + /* entries marked bc are backward compatible aliases for old OpenSSL names */ + CS_ENTRY(0x0003, TLS,RSA,EXPORT,WITH,RC4,40,MD5,), + CS_ENTRY(0x0003, EXP,RC4,MD5,,,,,), + CS_ENTRY(0x0004, TLS,RSA,WITH,RC4,128,MD5,,), + CS_ENTRY(0x0004, RC4,MD5,,,,,,), + CS_ENTRY(0x0005, TLS,RSA,WITH,RC4,128,SHA,,), + CS_ENTRY(0x0005, RC4,SHA,,,,,,), + CS_ENTRY(0x0006, TLS,RSA,EXPORT,WITH,RC2,CBC,40,MD5), + CS_ENTRY(0x0006, EXP,RC2,CBC,MD5,,,,), + CS_ENTRY(0x0007, TLS,RSA,WITH,IDEA,CBC,SHA,,), + CS_ENTRY(0x0007, IDEA,CBC,SHA,,,,,), + CS_ENTRY(0x0008, TLS,RSA,EXPORT,WITH,DES40,CBC,SHA,), + CS_ENTRY(0x0008, EXP,DES,CBC,SHA,,,,), + CS_ENTRY(0x0009, TLS,RSA,WITH,DES,CBC,SHA,,), + CS_ENTRY(0x0009, DES,CBC,SHA,,,,,), + CS_ENTRY(0x000B, TLS,DH,DSS,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x000B, EXP,DH,DSS,DES,CBC,SHA,,), + CS_ENTRY(0x000C, TLS,DH,DSS,WITH,DES,CBC,SHA,), + CS_ENTRY(0x000C, DH,DSS,DES,CBC,SHA,,,), + CS_ENTRY(0x000D, TLS,DH,DSS,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x000D, DH,DSS,DES,CBC3,SHA,,,), + CS_ENTRY(0x000E, TLS,DH,RSA,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x000E, EXP,DH,RSA,DES,CBC,SHA,,), + CS_ENTRY(0x000F, TLS,DH,RSA,WITH,DES,CBC,SHA,), + CS_ENTRY(0x000F, DH,RSA,DES,CBC,SHA,,,), + CS_ENTRY(0x0010, TLS,DH,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0010, DH,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0x0011, TLS,DHE,DSS,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0011, EXP,DHE,DSS,DES,CBC,SHA,,), + CS_ENTRY(0x0011, EXP,EDH,DSS,DES,CBC,SHA,,), /* bc */ + CS_ENTRY(0x0012, TLS,DHE,DSS,WITH,DES,CBC,SHA,), + CS_ENTRY(0x0012, DHE,DSS,DES,CBC,SHA,,,), + CS_ENTRY(0x0012, EDH,DSS,DES,CBC,SHA,,,), /* bc */ + CS_ENTRY(0x0013, TLS,DHE,DSS,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0013, DHE,DSS,DES,CBC3,SHA,,,), + CS_ENTRY(0x0013, EDH,DSS,DES,CBC3,SHA,,,), /* bc */ + CS_ENTRY(0x0014, TLS,DHE,RSA,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0014, EXP,DHE,RSA,DES,CBC,SHA,,), + CS_ENTRY(0x0014, EXP,EDH,RSA,DES,CBC,SHA,,), /* bc */ + CS_ENTRY(0x0015, TLS,DHE,RSA,WITH,DES,CBC,SHA,), + CS_ENTRY(0x0015, DHE,RSA,DES,CBC,SHA,,,), + CS_ENTRY(0x0015, EDH,RSA,DES,CBC,SHA,,,), /* bc */ + CS_ENTRY(0x0016, TLS,DHE,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0016, DHE,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0x0016, EDH,RSA,DES,CBC3,SHA,,,), /* bc */ + CS_ENTRY(0x0017, TLS,DH,anon,EXPORT,WITH,RC4,40,MD5), + CS_ENTRY(0x0017, EXP,ADH,RC4,MD5,,,,), + CS_ENTRY(0x0018, TLS,DH,anon,WITH,RC4,128,MD5,), + CS_ENTRY(0x0018, ADH,RC4,MD5,,,,,), + CS_ENTRY(0x0019, TLS,DH,anon,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0019, EXP,ADH,DES,CBC,SHA,,,), + CS_ENTRY(0x001A, TLS,DH,anon,WITH,DES,CBC,SHA,), + CS_ENTRY(0x001A, ADH,DES,CBC,SHA,,,,), + CS_ENTRY(0x001B, TLS,DH,anon,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x001B, ADH,DES,CBC3,SHA,,,,), + CS_ENTRY(0x0030, TLS,DH,DSS,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0030, DH,DSS,AES128,SHA,,,,), + CS_ENTRY(0x0031, TLS,DH,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0031, DH,RSA,AES128,SHA,,,,), + CS_ENTRY(0x0032, TLS,DHE,DSS,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0032, DHE,DSS,AES128,SHA,,,,), + CS_ENTRY(0x0034, TLS,DH,anon,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0034, ADH,AES128,SHA,,,,,), + CS_ENTRY(0x0036, TLS,DH,DSS,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0036, DH,DSS,AES256,SHA,,,,), + CS_ENTRY(0x0037, TLS,DH,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0037, DH,RSA,AES256,SHA,,,,), + CS_ENTRY(0x0038, TLS,DHE,DSS,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0038, DHE,DSS,AES256,SHA,,,,), + CS_ENTRY(0x003A, TLS,DH,anon,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x003A, ADH,AES256,SHA,,,,,), + CS_ENTRY(0x003E, TLS,DH,DSS,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x003E, DH,DSS,AES128,SHA256,,,,), + CS_ENTRY(0x003F, TLS,DH,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x003F, DH,RSA,AES128,SHA256,,,,), + CS_ENTRY(0x0040, TLS,DHE,DSS,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x0040, DHE,DSS,AES128,SHA256,,,,), + CS_ENTRY(0x0068, TLS,DH,DSS,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x0068, DH,DSS,AES256,SHA256,,,,), + CS_ENTRY(0x0069, TLS,DH,RSA,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x0069, DH,RSA,AES256,SHA256,,,,), + CS_ENTRY(0x006A, TLS,DHE,DSS,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006A, DHE,DSS,AES256,SHA256,,,,), + CS_ENTRY(0x006C, TLS,DH,anon,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x006C, ADH,AES128,SHA256,,,,,), + CS_ENTRY(0x006D, TLS,DH,anon,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006D, ADH,AES256,SHA256,,,,,), + CS_ENTRY(0x008A, TLS,PSK,WITH,RC4,128,SHA,,), + CS_ENTRY(0x008A, PSK,RC4,SHA,,,,,), + CS_ENTRY(0x008B, TLS,PSK,WITH,3DES,EDE,CBC,SHA,), + CS_ENTRY(0x008B, PSK,3DES,EDE,CBC,SHA,,,), + CS_ENTRY(0x008E, TLS,DHE,PSK,WITH,RC4,128,SHA,), + CS_ENTRY(0x008E, DHE,PSK,RC4,SHA,,,,), + CS_ENTRY(0x008F, TLS,DHE,PSK,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x008F, DHE,PSK,3DES,EDE,CBC,SHA,,), + CS_ENTRY(0x0092, TLS,RSA,PSK,WITH,RC4,128,SHA,), + CS_ENTRY(0x0092, RSA,PSK,RC4,SHA,,,,), + CS_ENTRY(0x0093, TLS,RSA,PSK,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0093, RSA,PSK,3DES,EDE,CBC,SHA,,), + CS_ENTRY(0x00A0, TLS,DH,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A0, DH,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A1, TLS,DH,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A1, DH,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A2, TLS,DHE,DSS,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A2, DHE,DSS,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A3, TLS,DHE,DSS,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A3, DHE,DSS,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A4, TLS,DH,DSS,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A4, DH,DSS,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A5, TLS,DH,DSS,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A5, DH,DSS,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A6, TLS,DH,anon,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A6, ADH,AES128,GCM,SHA256,,,,), + CS_ENTRY(0x00A7, TLS,DH,anon,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A7, ADH,AES256,GCM,SHA384,,,,), + CS_ENTRY(0xC002, TLS,ECDH,ECDSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC002, ECDH,ECDSA,RC4,SHA,,,,), + CS_ENTRY(0xC007, TLS,ECDHE,ECDSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC007, ECDHE,ECDSA,RC4,SHA,,,,), + CS_ENTRY(0xC00C, TLS,ECDH,RSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC00C, ECDH,RSA,RC4,SHA,,,,), + CS_ENTRY(0xC011, TLS,ECDHE,RSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC011, ECDHE,RSA,RC4,SHA,,,,), + CS_ENTRY(0xC015, TLS,ECDH,anon,WITH,NULL,SHA,,), + CS_ENTRY(0xC015, AECDH,NULL,SHA,,,,,), + CS_ENTRY(0xC016, TLS,ECDH,anon,WITH,RC4,128,SHA,), + CS_ENTRY(0xC016, AECDH,RC4,SHA,,,,,), + CS_ENTRY(0xC017, TLS,ECDH,anon,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC017, AECDH,DES,CBC3,SHA,,,,), + CS_ENTRY(0xC018, TLS,ECDH,anon,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC018, AECDH,AES128,SHA,,,,,), + CS_ENTRY(0xC019, TLS,ECDH,anon,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC019, AECDH,AES256,SHA,,,,,), +#endif +#if defined(USE_MBEDTLS) + /* entries marked ns are "non-standard", they are not in OpenSSL */ + CS_ENTRY(0x0041, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA,), + CS_ENTRY(0x0041, CAMELLIA128,SHA,,,,,,), + CS_ENTRY(0x0045, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA), + CS_ENTRY(0x0045, DHE,RSA,CAMELLIA128,SHA,,,,), + CS_ENTRY(0x0084, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA,), + CS_ENTRY(0x0084, CAMELLIA256,SHA,,,,,,), + CS_ENTRY(0x0088, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA), + CS_ENTRY(0x0088, DHE,RSA,CAMELLIA256,SHA,,,,), + CS_ENTRY(0x00BA, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0x00BA, CAMELLIA128,SHA256,,,,,,), + CS_ENTRY(0x00BE, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0x00BE, DHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0x00C0, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA256,), + CS_ENTRY(0x00C0, CAMELLIA256,SHA256,,,,,,), + CS_ENTRY(0x00C4, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA256), + CS_ENTRY(0x00C4, DHE,RSA,CAMELLIA256,SHA256,,,,), + CS_ENTRY(0xC037, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC037, ECDHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0xC038, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC038, ECDHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0xC039, TLS,ECDHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0xC039, ECDHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0xC03A, TLS,ECDHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0xC03A, ECDHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0xC03B, TLS,ECDHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0xC03B, ECDHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0xC03C, TLS,RSA,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC03C, ARIA128,SHA256,,,,,,), /* ns */ + CS_ENTRY(0xC03D, TLS,RSA,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC03D, ARIA256,SHA384,,,,,,), /* ns */ + CS_ENTRY(0xC044, TLS,DHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC044, DHE,RSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC045, TLS,DHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC045, DHE,RSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC048, TLS,ECDHE,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC048, ECDHE,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC049, TLS,ECDHE,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC049, ECDHE,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04A, TLS,ECDH,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04A, ECDH,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC04B, TLS,ECDH,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04B, ECDH,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04C, TLS,ECDHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04C, ECDHE,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04D, TLS,ECDHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04D, ECDHE,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC04E, TLS,ECDH,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04E, ECDH,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04F, TLS,ECDH,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04F, ECDH,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC050, TLS,RSA,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC050, ARIA128,GCM,SHA256,,,,,), + CS_ENTRY(0xC051, TLS,RSA,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC051, ARIA256,GCM,SHA384,,,,,), + CS_ENTRY(0xC052, TLS,DHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC052, DHE,RSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC053, TLS,DHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC053, DHE,RSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05C, TLS,ECDHE,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05C, ECDHE,ECDSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC05D, TLS,ECDHE,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05D, ECDHE,ECDSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05E, TLS,ECDH,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05E, ECDH,ECDSA,ARIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC05F, TLS,ECDH,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05F, ECDH,ECDSA,ARIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC060, TLS,ECDHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC060, ECDHE,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC061, TLS,ECDHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC061, ECDHE,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC062, TLS,ECDH,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC062, ECDH,ARIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC063, TLS,ECDH,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC063, ECDH,ARIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC064, TLS,PSK,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC064, PSK,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC065, TLS,PSK,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC065, PSK,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC066, TLS,DHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC066, DHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC067, TLS,DHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC067, DHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC068, TLS,RSA,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC068, RSA,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC069, TLS,RSA,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC069, RSA,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC06A, TLS,PSK,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC06A, PSK,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC06B, TLS,PSK,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC06B, PSK,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC06C, TLS,DHE,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06C, DHE,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06D, TLS,DHE,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06D, DHE,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC06E, TLS,RSA,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06E, RSA,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06F, TLS,RSA,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06F, RSA,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC070, TLS,ECDHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC070, ECDHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC071, TLS,ECDHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC071, ECDHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC072, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC072, ECDHE,ECDSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC073, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC073, ECDHE,ECDSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC074, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC074, ECDH,ECDSA,CAMELLIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC075, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC075, ECDH,ECDSA,CAMELLIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC076, TLS,ECDHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC076, ECDHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC077, TLS,ECDHE,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC077, ECDHE,RSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC078, TLS,ECDH,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC078, ECDH,CAMELLIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC079, TLS,ECDH,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC079, ECDH,CAMELLIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07A, TLS,RSA,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC07A, CAMELLIA128,GCM,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC07B, TLS,RSA,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC07B, CAMELLIA256,GCM,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07C, TLS,DHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC07C, DHE,RSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC07D, TLS,DHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC07D, DHE,RSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC086, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC086, ECDHE,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC087, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC087, ECDHE,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC088, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC088, ECDH,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC089, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC089, ECDH,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC08A, TLS,ECDHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08A, ECDHE,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08B, TLS,ECDHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08B, ECDHE,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08C, TLS,ECDH,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08C, ECDH,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08D, TLS,ECDH,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08D, ECDH,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08E, TLS,PSK,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC08E, PSK,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08F, TLS,PSK,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC08F, PSK,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC090, TLS,DHE,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC090, DHE,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC091, TLS,DHE,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC091, DHE,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC092, TLS,RSA,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC092, RSA,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC093, TLS,RSA,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC093, RSA,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC094, TLS,PSK,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0xC094, PSK,CAMELLIA128,SHA256,,,,,), + CS_ENTRY(0xC095, TLS,PSK,WITH,CAMELLIA,256,CBC,SHA384,), + CS_ENTRY(0xC095, PSK,CAMELLIA256,SHA384,,,,,), + CS_ENTRY(0xC096, TLS,DHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC096, DHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC097, TLS,DHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC097, DHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC098, TLS,RSA,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC098, RSA,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC099, TLS,RSA,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC099, RSA,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09A, TLS,ECDHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC09A, ECDHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC09B, TLS,ECDHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC09B, ECDHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09E, TLS,DHE,RSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC09E, DHE,RSA,AES128,CCM,,,,), + CS_ENTRY(0xC09F, TLS,DHE,RSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC09F, DHE,RSA,AES256,CCM,,,,), + CS_ENTRY(0xC0A2, TLS,DHE,RSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0A2, DHE,RSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0A3, TLS,DHE,RSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0A3, DHE,RSA,AES256,CCM8,,,,), + CS_ENTRY(0xC0A4, TLS,PSK,WITH,AES,128,CCM,,), + CS_ENTRY(0xC0A4, PSK,AES128,CCM,,,,,), + CS_ENTRY(0xC0A5, TLS,PSK,WITH,AES,256,CCM,,), + CS_ENTRY(0xC0A5, PSK,AES256,CCM,,,,,), + CS_ENTRY(0xC0A6, TLS,DHE,PSK,WITH,AES,128,CCM,), + CS_ENTRY(0xC0A6, DHE,PSK,AES128,CCM,,,,), + CS_ENTRY(0xC0A7, TLS,DHE,PSK,WITH,AES,256,CCM,), + CS_ENTRY(0xC0A7, DHE,PSK,AES256,CCM,,,,), + CS_ENTRY(0xC0A8, TLS,PSK,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A8, PSK,AES128,CCM8,,,,,), + CS_ENTRY(0xC0A9, TLS,PSK,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A9, PSK,AES256,CCM8,,,,,), + CS_ENTRY(0xC0AA, TLS,PSK,DHE,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AA, DHE,PSK,AES128,CCM8,,,,), + CS_ENTRY(0xC0AB, TLS,PSK,DHE,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AB, DHE,PSK,AES256,CCM8,,,,), + CS_ENTRY(0xCCAA, TLS,DHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAA, DHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAC, TLS,ECDHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAC, ECDHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAD, TLS,DHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAD, DHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAE, TLS,RSA,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAE, RSA,PSK,CHACHA20,POLY1305,,,,), +#endif +}; +#define CS_LIST_LEN CURL_ARRAYSIZE(cs_list) + +static int cs_str_to_zip(const char *cs_str, size_t cs_len, + uint8_t zip[6]) +{ + uint8_t indexes[8] = {0}; + const char *entry, *cur; + const char *nxt = cs_str; + const char *end = cs_str + cs_len; + char separator = '-'; + int idx, i = 0; + size_t len; + + /* split the cipher string by '-' or '_' */ + if(strncasecompare(cs_str, "TLS", 3)) + separator = '_'; + + do { + if(i == 8) + return -1; + + /* determine the length of the part */ + cur = nxt; + for(; nxt < end && *nxt != '\0' && *nxt != separator; nxt++); + len = nxt - cur; + + /* lookup index for the part (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < CS_TXT_LEN; idx++) { + size_t elen = strlen(entry); + if(elen == len && strncasecompare(entry, cur, len)) + break; + entry += elen + 1; + } + if(idx == CS_TXT_LEN) + return -1; + + indexes[i++] = (uint8_t) idx; + } while(nxt < end && *(nxt++) != '\0'); + + /* zip the 8 indexes into 48 bits */ + zip[0] = (uint8_t) (indexes[0] << 2 | (indexes[1] & 0x3F) >> 4); + zip[1] = (uint8_t) (indexes[1] << 4 | (indexes[2] & 0x3F) >> 2); + zip[2] = (uint8_t) (indexes[2] << 6 | (indexes[3] & 0x3F)); + zip[3] = (uint8_t) (indexes[4] << 2 | (indexes[5] & 0x3F) >> 4); + zip[4] = (uint8_t) (indexes[5] << 4 | (indexes[6] & 0x3F) >> 2); + zip[5] = (uint8_t) (indexes[6] << 6 | (indexes[7] & 0x3F)); + + return 0; +} + +static int cs_zip_to_str(const uint8_t zip[6], + char *buf, size_t buf_size) +{ + uint8_t indexes[8] = {0}; + const char *entry; + char separator = '-'; + int idx, i, r; + size_t len = 0; + + /* unzip the 8 indexes */ + indexes[0] = zip[0] >> 2; + indexes[1] = (uint8_t)(((zip[0] << 4) & 0x3F) | zip[1] >> 4); + indexes[2] = (uint8_t)(((zip[1] << 2) & 0x3F) | zip[2] >> 6); + indexes[3] = ((zip[2] << 0) & 0x3F); + indexes[4] = zip[3] >> 2; + indexes[5] = (uint8_t)(((zip[3] << 4) & 0x3F) | zip[4] >> 4); + indexes[6] = (uint8_t)(((zip[4] << 2) & 0x3F) | zip[5] >> 6); + indexes[7] = ((zip[5] << 0) & 0x3F); + + if(indexes[0] == CS_TXT_IDX_TLS) + separator = '_'; + + for(i = 0; i < 8 && indexes[i] != 0 && len < buf_size; i++) { + if(indexes[i] >= CS_TXT_LEN) + return -1; + + /* lookup the part string for the index (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < indexes[i]; idx++) { + size_t elen = strlen(entry); + entry += elen + 1; + } + + /* append the part string to the buffer */ + if(i > 0) + r = msnprintf(&buf[len], buf_size - len, "%c%s", separator, entry); + else + r = msnprintf(&buf[len], buf_size - len, "%s", entry); + + if(r < 0) + return -1; + len += r; + } + + return 0; +} + +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len) +{ + size_t i; + uint8_t zip[6]; + + if(cs_len > 0 && cs_str_to_zip(cs_str, cs_len, zip) == 0) { + for(i = 0; i < CS_LIST_LEN; i++) { + if(memcmp(cs_list[i].zip, zip, sizeof(zip)) == 0) + return cs_list[i].id; + } + } + + return 0; +} + +static bool cs_is_separator(char c) +{ + switch(c) { + case ' ': + case '\t': + case ':': + case ',': + case ';': + return TRUE; + default:; + } + return FALSE; +} + +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end) +{ + /* move string pointer to first non-separator or end of string */ + for(; cs_is_separator(*str[0]); (*str)++); + + /* move end pointer to next separator or end of string */ + for(*end = *str; *end[0] != '\0' && !cs_is_separator(*end[0]); (*end)++); + + return Curl_cipher_suite_lookup_id(*str, *end - *str); +} + +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + size_t i, j = CS_LIST_LEN; + int r = -1; + + for(i = 0; i < CS_LIST_LEN; i++) { + if(cs_list[i].id != id) + continue; + if((cs_list[i].zip[0] >> 2 != CS_TXT_IDX_TLS) == !prefer_rfc) { + j = i; + break; + } + if(j == CS_LIST_LEN) + j = i; + } + + if(j < CS_LIST_LEN) + r = cs_zip_to_str(cs_list[j].zip, buf, buf_size); + + if(r < 0) + msnprintf(buf, buf_size, "TLS_UNKNOWN_0x%04x", id); + + return r; +} + +#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) */ diff --git a/Utilities/cmcurl/lib/vtls/cipher_suite.h b/Utilities/cmcurl/lib/vtls/cipher_suite.h new file mode 100644 index 00000000000..cd556db10f6 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/cipher_suite.h @@ -0,0 +1,48 @@ +#ifndef HEADER_CURL_CIPHER_SUITE_H +#define HEADER_CURL_CIPHER_SUITE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "../curl_setup.h" + +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#include + +/* Lookup IANA id for cipher suite string, returns 0 if not recognized */ +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len); + +/* Walk over cipher suite string, update str and end pointers to next + cipher suite in string, returns IANA id of that suite if recognized */ +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end); + +/* Copy openssl or RFC name for cipher suite in supplied buffer. + Caller is responsible to supply sufficiently large buffer (size + of 64 should suffice), excess bytes are silently truncated. */ +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc); + +#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) */ +#endif /* HEADER_CURL_CIPHER_SUITE_H */ diff --git a/Utilities/cmcurl/lib/vtls/gskit.c b/Utilities/cmcurl/lib/vtls/gskit.c deleted file mode 100644 index 749dc916ad0..00000000000 --- a/Utilities/cmcurl/lib/vtls/gskit.c +++ /dev/null @@ -1,1327 +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" - -#ifdef USE_GSKIT - -#include -#include -#undef HAVE_SOCKETPAIR /* because the native one isn't good enough */ -#include "socketpair.h" -#include "strerror.h" - -/* Some symbols are undefined/unsupported on OS400 versions < V7R1. */ -#ifndef GSK_SSL_EXTN_SERVERNAME_REQUEST -#define GSK_SSL_EXTN_SERVERNAME_REQUEST 230 -#endif - -#ifndef GSK_TLSV10_CIPHER_SPECS -#define GSK_TLSV10_CIPHER_SPECS 236 -#endif - -#ifndef GSK_TLSV11_CIPHER_SPECS -#define GSK_TLSV11_CIPHER_SPECS 237 -#endif - -#ifndef GSK_TLSV12_CIPHER_SPECS -#define GSK_TLSV12_CIPHER_SPECS 238 -#endif - -#ifndef GSK_PROTOCOL_TLSV11 -#define GSK_PROTOCOL_TLSV11 437 -#endif - -#ifndef GSK_PROTOCOL_TLSV12 -#define GSK_PROTOCOL_TLSV12 438 -#endif - -#ifndef GSK_FALSE -#define GSK_FALSE 0 -#endif - -#ifndef GSK_TRUE -#define GSK_TRUE 1 -#endif - - -#include - -#include -#include "urldata.h" -#include "sendf.h" -#include "gskit.h" -#include "vtls.h" -#include "vtls_int.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" -#include "timediff.h" -#include "x509asn1.h" -#include "curl_printf.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - - -/* Directions. */ -#define SOS_READ 0x01 -#define SOS_WRITE 0x02 - -/* SSL version flags. */ -#define CURL_GSKPROTO_SSLV2 0 -#define CURL_GSKPROTO_SSLV2_MASK (1 << CURL_GSKPROTO_SSLV2) -#define CURL_GSKPROTO_SSLV3 1 -#define CURL_GSKPROTO_SSLV3_MASK (1 << CURL_GSKPROTO_SSLV3) -#define CURL_GSKPROTO_TLSV10 2 -#define CURL_GSKPROTO_TLSV10_MASK (1 << CURL_GSKPROTO_TLSV10) -#define CURL_GSKPROTO_TLSV11 3 -#define CURL_GSKPROTO_TLSV11_MASK (1 << CURL_GSKPROTO_TLSV11) -#define CURL_GSKPROTO_TLSV12 4 -#define CURL_GSKPROTO_TLSV12_MASK (1 << CURL_GSKPROTO_TLSV12) -#define CURL_GSKPROTO_LAST 5 - -struct ssl_backend_data { - gsk_handle handle; - int iocport; - int localfd; - int remotefd; -}; - -#define BACKEND connssl->backend - -/* Supported ciphers. */ -struct gskit_cipher { - const char *name; /* Cipher name. */ - const char *gsktoken; /* Corresponding token for GSKit String. */ - unsigned int versions; /* SSL version flags. */ -}; - -static const struct gskit_cipher ciphertable[] = { - { "null-md5", "01", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "null-sha", "02", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "exp-rc4-md5", "03", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK }, - { "rc4-md5", "04", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "rc4-sha", "05", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "exp-rc2-cbc-md5", "06", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK }, - { "exp-des-cbc-sha", "09", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK }, - { "des-cbc3-sha", "0A", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-sha", "2F", - CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK | - CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-sha", "35", - CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK | - CURL_GSKPROTO_TLSV12_MASK }, - { "null-sha256", "3B", CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-sha256", "3C", CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-sha256", "3D", CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-gcm-sha256", - "9C", CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-gcm-sha384", - "9D", CURL_GSKPROTO_TLSV12_MASK }, - { "rc4-md5", "1", CURL_GSKPROTO_SSLV2_MASK }, - { "exp-rc4-md5", "2", CURL_GSKPROTO_SSLV2_MASK }, - { "rc2-md5", "3", CURL_GSKPROTO_SSLV2_MASK }, - { "exp-rc2-md5", "4", CURL_GSKPROTO_SSLV2_MASK }, - { "des-cbc-md5", "6", CURL_GSKPROTO_SSLV2_MASK }, - { "des-cbc3-md5", "7", CURL_GSKPROTO_SSLV2_MASK }, - { (const char *) NULL, (const char *) NULL, 0 } -}; - - -static bool is_separator(char c) -{ - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; -} - - -static CURLcode gskit_status(struct Curl_easy *data, int rc, - const char *procname, CURLcode defcode) -{ - char buffer[STRERROR_LEN]; - /* Process GSKit status and map it to a CURLcode. */ - switch(rc) { - case GSK_OK: - case GSK_OS400_ASYNCHRONOUS_SOC_INIT: - return CURLE_OK; - case GSK_KEYRING_OPEN_ERROR: - case GSK_OS400_ERROR_NO_ACCESS: - return CURLE_SSL_CACERT_BADFILE; - case GSK_INSUFFICIENT_STORAGE: - return CURLE_OUT_OF_MEMORY; - case GSK_ERROR_BAD_V2_CIPHER: - case GSK_ERROR_BAD_V3_CIPHER: - case GSK_ERROR_NO_CIPHERS: - return CURLE_SSL_CIPHER; - case GSK_OS400_ERROR_NOT_TRUSTED_ROOT: - case GSK_ERROR_CERT_VALIDATION: - return CURLE_PEER_FAILED_VERIFICATION; - case GSK_OS400_ERROR_TIMED_OUT: - return CURLE_OPERATION_TIMEDOUT; - case GSK_WOULD_BLOCK: - return CURLE_AGAIN; - case GSK_OS400_ERROR_NOT_REGISTERED: - break; - case GSK_ERROR_IO: - switch(errno) { - case ENOMEM: - return CURLE_OUT_OF_MEMORY; - default: - failf(data, "%s I/O error: %s", procname, - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - } - break; - default: - failf(data, "%s: %s", procname, gsk_strerror(rc)); - break; - } - return defcode; -} - - -static CURLcode set_enum(struct Curl_easy *data, gsk_handle h, - GSK_ENUM_ID id, GSK_ENUM_VALUE value, bool unsupported_ok) -{ - char buffer[STRERROR_LEN]; - int rc = gsk_attribute_set_enum(h, id, value); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_enum() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - case GSK_ATTRIBUTE_INVALID_ID: - if(unsupported_ok) - return CURLE_UNSUPPORTED_PROTOCOL; - default: - failf(data, "gsk_attribute_set_enum(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_buffer(struct Curl_easy *data, gsk_handle h, - GSK_BUF_ID id, const char *buf, bool unsupported_ok) -{ - char buffer[STRERROR_LEN]; - int rc = gsk_attribute_set_buffer(h, id, buf, 0); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_buffer() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - case GSK_ATTRIBUTE_INVALID_ID: - if(unsupported_ok) - return CURLE_UNSUPPORTED_PROTOCOL; - default: - failf(data, "gsk_attribute_set_buffer(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_numeric(struct Curl_easy *data, - gsk_handle h, GSK_NUM_ID id, int value) -{ - char buffer[STRERROR_LEN]; - int rc = gsk_attribute_set_numeric_value(h, id, value); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_numeric_value() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - default: - failf(data, "gsk_attribute_set_numeric_value(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_ciphers(struct Curl_cfilter *cf, struct Curl_easy *data, - gsk_handle h, unsigned int *protoflags) -{ - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct connectdata *conn = data->conn; - const char *cipherlist = conn_config->cipher_list; - const char *clp; - const struct gskit_cipher *ctp; - int i; - int l; - bool unsupported; - CURLcode result; - struct { - char *buf; - char *ptr; - } ciphers[CURL_GSKPROTO_LAST]; - - /* Compile cipher list into GSKit-compatible cipher lists. */ - - if(!cipherlist) - return CURLE_OK; - while(is_separator(*cipherlist)) /* Skip initial separators. */ - cipherlist++; - if(!*cipherlist) - return CURLE_OK; - - /* We allocate GSKit buffers of the same size as the input string: since - GSKit tokens are always shorter than their cipher names, allocated buffers - will always be large enough to accommodate the result. */ - l = strlen(cipherlist) + 1; - memset(ciphers, 0, sizeof(ciphers)); - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - ciphers[i].buf = malloc(l); - if(!ciphers[i].buf) { - while(i--) - free(ciphers[i].buf); - return CURLE_OUT_OF_MEMORY; - } - ciphers[i].ptr = ciphers[i].buf; - *ciphers[i].ptr = '\0'; - } - - /* Process each cipher in input string. */ - unsupported = FALSE; - result = CURLE_OK; - for(;;) { - for(clp = cipherlist; *cipherlist && !is_separator(*cipherlist);) - cipherlist++; - l = cipherlist - clp; - if(!l) - break; - /* Search the cipher in our table. */ - for(ctp = ciphertable; ctp->name; ctp++) - if(strncasecompare(ctp->name, clp, l) && !ctp->name[l]) - break; - if(!ctp->name) { - failf(data, "Unknown cipher %.*s", l, clp); - result = CURLE_SSL_CIPHER; - } - else { - unsupported |= !(ctp->versions & (CURL_GSKPROTO_SSLV2_MASK | - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK)); - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - if(ctp->versions & (1 << i)) { - strcpy(ciphers[i].ptr, ctp->gsktoken); - ciphers[i].ptr += strlen(ctp->gsktoken); - } - } - } - - /* Advance to next cipher name or end of string. */ - while(is_separator(*cipherlist)) - cipherlist++; - } - - /* Disable protocols with empty cipher lists. */ - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - if(!(*protoflags & (1 << i)) || !ciphers[i].buf[0]) { - *protoflags &= ~(1 << i); - ciphers[i].buf[0] = '\0'; - } - } - - /* Try to set-up TLSv1.1 and TLSv2.1 ciphers. */ - if(*protoflags & CURL_GSKPROTO_TLSV11_MASK) { - result = set_buffer(data, h, GSK_TLSV11_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV11].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(unsupported) { - failf(data, "TLSv1.1-only ciphers are not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result && (*protoflags & CURL_GSKPROTO_TLSV12_MASK)) { - result = set_buffer(data, h, GSK_TLSV12_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV12].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(unsupported) { - failf(data, "TLSv1.2-only ciphers are not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - - /* Try to set-up TLSv1.0 ciphers. If not successful, concatenate them to - the SSLv3 ciphers. OS/400 prior to version 7.1 will understand it. */ - if(!result && (*protoflags & CURL_GSKPROTO_TLSV10_MASK)) { - result = set_buffer(data, h, GSK_TLSV10_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV10].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - strcpy(ciphers[CURL_GSKPROTO_SSLV3].ptr, - ciphers[CURL_GSKPROTO_TLSV10].ptr); - } - } - - /* Set-up other ciphers. */ - if(!result && (*protoflags & CURL_GSKPROTO_SSLV3_MASK)) - result = set_buffer(data, h, GSK_V3_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_SSLV3].buf, FALSE); - if(!result && (*protoflags & CURL_GSKPROTO_SSLV2_MASK)) - result = set_buffer(data, h, GSK_V2_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_SSLV2].buf, FALSE); - - /* Clean-up. */ - for(i = 0; i < CURL_GSKPROTO_LAST; i++) - free(ciphers[i].buf); - - return result; -} - - -static int gskit_init(void) -{ - /* No initialization needed. */ - return 1; -} - - -static void gskit_cleanup(void) -{ - /* Nothing to do. */ -} - - -static CURLcode init_environment(struct Curl_easy *data, - gsk_handle *envir, const char *appid, - const char *file, const char *label, - const char *password) -{ - int rc; - CURLcode result; - gsk_handle h; - - /* Creates the GSKit environment. */ - - rc = gsk_environment_open(&h); - switch(rc) { - case GSK_OK: - break; - case GSK_INSUFFICIENT_STORAGE: - return CURLE_OUT_OF_MEMORY; - default: - failf(data, "gsk_environment_open(): %s", gsk_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - - result = set_enum(data, h, GSK_SESSION_TYPE, GSK_CLIENT_SESSION, FALSE); - if(!result && appid) - result = set_buffer(data, h, GSK_OS400_APPLICATION_ID, appid, FALSE); - if(!result && file) - result = set_buffer(data, h, GSK_KEYRING_FILE, file, FALSE); - if(!result && label) - result = set_buffer(data, h, GSK_KEYRING_LABEL, label, FALSE); - if(!result && password) - result = set_buffer(data, h, GSK_KEYRING_PW, password, FALSE); - - if(!result) { - /* Locate CAs, Client certificate and key according to our settings. - Note: this call may be blocking for some tenths of seconds. */ - result = gskit_status(data, gsk_environment_init(h), - "gsk_environment_init()", CURLE_SSL_CERTPROBLEM); - if(!result) { - *envir = h; - return result; - } - } - /* Error: rollback. */ - gsk_environment_close(&h); - return result; -} - - -static void cancel_async_handshake(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - Qso_OverlappedIO_t cstat; - - (void)data; - DEBUGASSERT(BACKEND); - - if(QsoCancelOperation(Curl_conn_cf_get_socket(cf, data), 0) > 0) - QsoWaitForIOCompletion(BACKEND->iocport, &cstat, (struct timeval *) NULL); -} - - -static void close_async_handshake(struct ssl_connect_data *connssl) -{ - DEBUGASSERT(BACKEND); - QsoDestroyIOCompletionPort(BACKEND->iocport); - BACKEND->iocport = -1; -} - -static int pipe_ssloverssl(struct Curl_cfilter *cf, struct Curl_easy *data, - int directions) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next); - struct ssl_connect_data *connssl_next = cf_ssl_next? - cf_ssl_next->ctx : NULL; - struct pollfd fds[2]; - int n; - int m; - int i; - int ret = 0; - char buf[CURL_MAX_WRITE_SIZE]; - - DEBUGASSERT(BACKEND); - - if(!connssl_next) - return 0; /* No SSL over SSL: OK. */ - - DEBUGASSERT(connssl_next->backend); - n = 1; - fds[0].fd = BACKEND->remotefd; - fds[1].fd = Curl_conn_cf_get_socket(cf, data); - - if(directions & SOS_READ) { - fds[0].events |= POLLOUT; - } - if(directions & SOS_WRITE) { - n = 2; - fds[0].events |= POLLIN; - fds[1].events |= POLLOUT; - } - i = Curl_poll(fds, n, 0); - if(i < 0) - return -1; /* Select error. */ - - if(fds[0].revents & POLLOUT) { - /* Try getting data from HTTPS proxy and pipe it upstream. */ - n = 0; - i = gsk_secure_soc_read(connssl_next->backend->handle, - buf, sizeof(buf), &n); - switch(i) { - case GSK_OK: - if(n) { - i = write(BACKEND->remotefd, buf, n); - if(i < 0) - return -1; - ret = 1; - } - break; - case GSK_OS400_ERROR_TIMED_OUT: - case GSK_WOULD_BLOCK: - break; - default: - return -1; - } - } - - if((fds[0].revents & POLLIN) && (fds[1].revents & POLLOUT)) { - /* Pipe data to HTTPS proxy. */ - n = read(BACKEND->remotefd, buf, sizeof(buf)); - if(n < 0) - return -1; - if(n) { - i = gsk_secure_soc_write(connssl_next->backend->handle, buf, n, &m); - if(i != GSK_OK || n != m) - return -1; - ret = 1; - } - } - - return ret; /* OK */ -} - - -static void close_one(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - - DEBUGASSERT(BACKEND); - if(BACKEND->handle) { - gskit_status(data, gsk_secure_soc_close(&BACKEND->handle), - "gsk_secure_soc_close()", 0); - /* Last chance to drain output. */ - while(pipe_ssloverssl(cf, data, SOS_WRITE) > 0) - ; - BACKEND->handle = (gsk_handle) NULL; - if(BACKEND->localfd >= 0) { - close(BACKEND->localfd); - BACKEND->localfd = -1; - } - if(BACKEND->remotefd >= 0) { - close(BACKEND->remotefd); - BACKEND->remotefd = -1; - } - } - if(BACKEND->iocport >= 0) - close_async_handshake(connssl); -} - - -static ssize_t gskit_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *mem, size_t len, CURLcode *curlcode) -{ - struct connectdata *conn = cf->conn; - struct ssl_connect_data *connssl = cf->ctx; - CURLcode cc = CURLE_SEND_ERROR; - int written; - - DEBUGASSERT(BACKEND); - - if(pipe_ssloverssl(cf, data, SOS_WRITE) >= 0) { - cc = gskit_status(data, - gsk_secure_soc_write(BACKEND->handle, - (char *) mem, (int) len, &written), - "gsk_secure_soc_write()", CURLE_SEND_ERROR); - if(cc == CURLE_OK) - if(pipe_ssloverssl(cf, data, SOS_WRITE) < 0) - cc = CURLE_SEND_ERROR; - } - if(cc != CURLE_OK) { - *curlcode = cc; - written = -1; - } - return (ssize_t) written; /* number of bytes */ -} - - -static ssize_t gskit_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - char *buf, size_t buffersize, CURLcode *curlcode) -{ - struct connectdata *conn = cf->conn; - struct ssl_connect_data *connssl = cf->ctx; - int nread; - CURLcode cc = CURLE_RECV_ERROR; - - (void)data; - DEBUGASSERT(BACKEND); - - if(pipe_ssloverssl(cf, data, SOS_READ) >= 0) { - int buffsize = buffersize > (size_t) INT_MAX? INT_MAX: (int) buffersize; - cc = gskit_status(data, gsk_secure_soc_read(BACKEND->handle, - buf, buffsize, &nread), - "gsk_secure_soc_read()", CURLE_RECV_ERROR); - } - switch(cc) { - case CURLE_OK: - break; - case CURLE_OPERATION_TIMEDOUT: - cc = CURLE_AGAIN; - default: - *curlcode = cc; - nread = -1; - break; - } - return (ssize_t) nread; -} - -static CURLcode -set_ssl_version_min_max(unsigned int *protoflags, - struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct connectdata *conn = data->conn; - long ssl_version = conn_config->version; - long ssl_version_max = conn_config->version_max; - long i = ssl_version; - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = CURL_SSLVERSION_TLSv1_2; - break; - } - for(; i <= (ssl_version_max >> 16); ++i) { - switch(i) { - case CURL_SSLVERSION_TLSv1_0: - *protoflags |= CURL_GSKPROTO_TLSV10_MASK; - break; - case CURL_SSLVERSION_TLSv1_1: - *protoflags |= CURL_GSKPROTO_TLSV11_MASK; - break; - case CURL_SSLVERSION_TLSv1_2: - *protoflags |= CURL_GSKPROTO_TLSV11_MASK; - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "GSKit: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - } - } - - return CURLE_OK; -} - -static CURLcode gskit_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_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next); - struct ssl_connect_data *connssl_next = cf_ssl_next? - cf_ssl_next->ctx : NULL; - gsk_handle envir; - CURLcode result; - const char * const keyringfile = conn_config->CAfile; - const char * const keyringpwd = ssl_config->key_passwd; - const char * const keyringlabel = ssl_config->primary.clientcert; - const long int ssl_version = conn_config->version; - const bool verifypeer = conn_config->verifypeer; - const char *hostname = connssl->hostname; - const char *sni; - unsigned int protoflags = 0; - Qso_OverlappedIO_t commarea; - int sockpair[2]; - static const int sobufsize = CURL_MAX_WRITE_SIZE; - - /* Create SSL environment, start (preferably asynchronous) handshake. */ - DEBUGASSERT(BACKEND); - - BACKEND->handle = (gsk_handle) NULL; - BACKEND->iocport = -1; - BACKEND->localfd = -1; - BACKEND->remotefd = -1; - - /* GSKit supports two ways of specifying an SSL context: either by - * application identifier (that should have been defined at the system - * level) or by keyring file, password and certificate label. - * Local certificate name (CURLOPT_SSLCERT) is used to hold either the - * application identifier of the certificate label. - * Key password (CURLOPT_KEYPASSWD) holds the keyring password. - * It is not possible to have different keyrings for the CAs and the - * local certificate. We thus use the CA file (CURLOPT_CAINFO) to identify - * the keyring file. - * If no key password is given and the keyring is the system keyring, - * application identifier mode is tried first, as recommended in IBM doc. - */ - - envir = (gsk_handle) NULL; - - if(keyringlabel && *keyringlabel && !keyringpwd && - !strcmp(keyringfile, CURL_CA_BUNDLE)) { - /* Try application identifier mode. */ - init_environment(data, &envir, keyringlabel, (const char *) NULL, - (const char *) NULL, (const char *) NULL); - } - - if(!envir) { - /* Use keyring mode. */ - result = init_environment(data, &envir, (const char *) NULL, - keyringfile, keyringlabel, keyringpwd); - if(result) - return result; - } - - /* Create secure session. */ - result = gskit_status(data, gsk_secure_soc_open(envir, &BACKEND->handle), - "gsk_secure_soc_open()", CURLE_SSL_CONNECT_ERROR); - gsk_environment_close(&envir); - if(result) - return result; - - /* Establish a pipelining socket pair for SSL over SSL. */ - if(connssl_next) { - if(Curl_socketpair(0, 0, 0, sockpair)) - return CURLE_SSL_CONNECT_ERROR; - BACKEND->localfd = sockpair[0]; - BACKEND->remotefd = sockpair[1]; - setsockopt(BACKEND->localfd, SOL_SOCKET, SO_RCVBUF, - (void *) &sobufsize, sizeof(sobufsize)); - setsockopt(BACKEND->remotefd, SOL_SOCKET, SO_RCVBUF, - (void *) &sobufsize, sizeof(sobufsize)); - setsockopt(BACKEND->localfd, SOL_SOCKET, SO_SNDBUF, - (void *) &sobufsize, sizeof(sobufsize)); - setsockopt(BACKEND->remotefd, SOL_SOCKET, SO_SNDBUF, - (void *) &sobufsize, sizeof(sobufsize)); - curlx_nonblock(BACKEND->localfd, TRUE); - curlx_nonblock(BACKEND->remotefd, TRUE); - } - - /* Determine which SSL/TLS version should be enabled. */ - sni = hostname; - switch(ssl_version) { - case CURL_SSLVERSION_SSLv2: - protoflags = CURL_GSKPROTO_SSLV2_MASK; - sni = NULL; - break; - case CURL_SSLVERSION_SSLv3: - protoflags = CURL_GSKPROTO_SSLV3_MASK; - sni = NULL; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - protoflags = CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK; - break; - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - result = set_ssl_version_min_max(&protoflags, cf, data); - if(result != CURLE_OK) - return result; - break; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Process SNI. Ignore if not supported (on OS400 < V7R1). */ - if(sni) { - char *snihost = Curl_ssl_snihost(data, sni, NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - result = set_buffer(data, BACKEND->handle, - GSK_SSL_EXTN_SERVERNAME_REQUEST, snihost, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) - result = CURLE_OK; - } - - /* Set session parameters. */ - if(!result) { - /* Compute the handshake timeout. Since GSKit granularity is 1 second, - we round up the required value. */ - timediff_t timeout = Curl_timeleft(data, NULL, TRUE); - if(timeout < 0) - result = CURLE_OPERATION_TIMEDOUT; - else - result = set_numeric(data, BACKEND->handle, GSK_HANDSHAKE_TIMEOUT, - (timeout + 999) / 1000); - } - if(!result) - result = set_numeric(data, BACKEND->handle, GSK_OS400_READ_TIMEOUT, 1); - if(!result) - result = set_numeric(data, BACKEND->handle, GSK_FD, BACKEND->localfd >= 0? - BACKEND->localfd: Curl_conn_cf_get_socket(cf, data)); - if(!result) - result = set_ciphers(cf, data, BACKEND->handle, &protoflags); - if(!protoflags) { - failf(data, "No SSL protocol/cipher combination enabled"); - result = CURLE_SSL_CIPHER; - } - if(!result) - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_SSLV2, - (protoflags & CURL_GSKPROTO_SSLV2_MASK)? - GSK_PROTOCOL_SSLV2_ON: GSK_PROTOCOL_SSLV2_OFF, FALSE); - if(!result) - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_SSLV3, - (protoflags & CURL_GSKPROTO_SSLV3_MASK)? - GSK_PROTOCOL_SSLV3_ON: GSK_PROTOCOL_SSLV3_OFF, FALSE); - if(!result) - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV1, - (protoflags & CURL_GSKPROTO_TLSV10_MASK)? - GSK_PROTOCOL_TLSV1_ON: GSK_PROTOCOL_TLSV1_OFF, FALSE); - if(!result) { - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV11, - (protoflags & CURL_GSKPROTO_TLSV11_MASK)? - GSK_TRUE: GSK_FALSE, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(protoflags == CURL_GSKPROTO_TLSV11_MASK) { - failf(data, "TLS 1.1 not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result) { - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV12, - (protoflags & CURL_GSKPROTO_TLSV12_MASK)? - GSK_TRUE: GSK_FALSE, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(protoflags == CURL_GSKPROTO_TLSV12_MASK) { - failf(data, "TLS 1.2 not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result) - result = set_enum(data, BACKEND->handle, GSK_SERVER_AUTH_TYPE, - verifypeer? GSK_SERVER_AUTH_FULL: - GSK_SERVER_AUTH_PASSTHRU, FALSE); - - if(!result) { - /* Start handshake. Try asynchronous first. */ - memset(&commarea, 0, sizeof(commarea)); - BACKEND->iocport = QsoCreateIOCompletionPort(); - if(BACKEND->iocport != -1) { - result = gskit_status(data, - gsk_secure_soc_startInit(BACKEND->handle, - BACKEND->iocport, - &commarea), - "gsk_secure_soc_startInit()", - CURLE_SSL_CONNECT_ERROR); - if(!result) { - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; - } - else - close_async_handshake(connssl); - } - else if(errno != ENOBUFS) - result = gskit_status(data, GSK_ERROR_IO, - "QsoCreateIOCompletionPort()", 0); - else if(connssl_next) { - /* Cannot pipeline while handshaking synchronously. */ - result = CURLE_SSL_CONNECT_ERROR; - } - else { - /* No more completion port available. Use synchronous IO. */ - result = gskit_status(data, gsk_secure_soc_init(BACKEND->handle), - "gsk_secure_soc_init()", CURLE_SSL_CONNECT_ERROR); - if(!result) { - connssl->connecting_state = ssl_connect_3; - return CURLE_OK; - } - } - } - - /* Error: rollback. */ - close_one(cf, data); - return result; -} - - -static CURLcode gskit_connect_step2(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking) -{ - struct ssl_connect_data *connssl = cf->ctx; - Qso_OverlappedIO_t cstat; - struct timeval stmv; - CURLcode result; - - /* Poll or wait for end of SSL asynchronous handshake. */ - DEBUGASSERT(BACKEND); - - for(;;) { - timediff_t timeout_ms = nonblocking? 0: Curl_timeleft(data, NULL, TRUE); - stmv.tv_sec = 0; - stmv.tv_usec = 0; - if(timeout_ms < 0) - timeout_ms = 0; - switch(QsoWaitForIOCompletion(BACKEND->iocport, &cstat, - curlx_mstotv(&stmv, timeout_ms))) { - case 1: /* Operation complete. */ - break; - case -1: /* An error occurred: handshake still in progress. */ - if(errno == EINTR) { - if(nonblocking) - return CURLE_OK; - continue; /* Retry. */ - } - if(errno != ETIME) { - char buffer[STRERROR_LEN]; - failf(data, "QsoWaitForIOCompletion() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - cancel_async_handshake(cf, data); - close_async_handshake(connssl); - return CURLE_SSL_CONNECT_ERROR; - } - /* FALL INTO... */ - case 0: /* Handshake in progress, timeout occurred. */ - if(nonblocking) - return CURLE_OK; - cancel_async_handshake(cf, data); - close_async_handshake(connssl); - return CURLE_OPERATION_TIMEDOUT; - } - break; - } - result = gskit_status(data, cstat.returnValue, "SSL handshake", - CURLE_SSL_CONNECT_ERROR); - if(!result) - connssl->connecting_state = ssl_connect_3; - close_async_handshake(connssl); - return result; -} - - -static CURLcode gskit_connect_step3(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - const gsk_cert_data_elem *cdev; - int cdec; - const gsk_cert_data_elem *p; - const char *cert = (const char *) NULL; - const char *certend = (const char *) NULL; - const char *ptr; - CURLcode result; - - /* SSL handshake done: gather certificate info and verify host. */ - DEBUGASSERT(BACKEND); - - if(gskit_status(data, gsk_attribute_get_cert_info(BACKEND->handle, - GSK_PARTNER_CERT_INFO, - &cdev, &cdec), - "gsk_attribute_get_cert_info()", CURLE_SSL_CONNECT_ERROR) == - CURLE_OK) { - int i; - - infof(data, "Server certificate:"); - p = cdev; - for(i = 0; i++ < cdec; p++) - switch(p->cert_data_id) { - case CERT_BODY_DER: - cert = p->cert_data_p; - certend = cert + cdev->cert_data_l; - break; - case CERT_DN_PRINTABLE: - infof(data, "\t subject: %.*s", p->cert_data_l, p->cert_data_p); - break; - case CERT_ISSUER_DN_PRINTABLE: - infof(data, "\t issuer: %.*s", p->cert_data_l, p->cert_data_p); - break; - case CERT_VALID_FROM: - infof(data, "\t start date: %.*s", p->cert_data_l, p->cert_data_p); - break; - case CERT_VALID_TO: - infof(data, "\t expire date: %.*s", p->cert_data_l, p->cert_data_p); - break; - } - } - - /* Verify host. */ - result = Curl_verifyhost(cf, data, cert, certend); - if(result) - return result; - - /* The only place GSKit can get the whole CA chain is a validation - callback where no user data pointer is available. Therefore it's not - possible to copy this chain into our structures for CAINFO. - However the server certificate may be available, thus we can return - info about it. */ - if(data->set.ssl.certinfo) { - result = Curl_ssl_init_certinfo(data, 1); - if(result) - return result; - - if(cert) { - result = Curl_extract_certinfo(data, 0, cert, certend); - if(result) - return result; - } - } - - /* Check pinned public key. */ - ptr = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: - data->set.str[STRING_SSL_PINNEDPUBLICKEY]; - if(!result && ptr) { - struct Curl_X509certificate x509; - struct Curl_asn1Element *p; - - memset(&x509, 0, sizeof(x509)); - if(Curl_parseX509(&x509, cert, certend)) - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - p = &x509.subjectPublicKeyInfo; - result = Curl_pin_peer_pubkey(data, ptr, p->header, p->end - p->header); - if(result) { - failf(data, "SSL: public key does not match pinned public key"); - return result; - } - } - - connssl->connecting_state = ssl_connect_done; - return CURLE_OK; -} - - -static CURLcode gskit_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, bool *done) -{ - struct ssl_connect_data *connssl = cf->ctx; - timediff_t timeout_ms; - CURLcode result = CURLE_OK; - - *done = connssl->state == ssl_connection_complete; - if(*done) - return CURLE_OK; - - /* Step 1: create session, start handshake. */ - if(connssl->connecting_state == ssl_connect_1) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; - } - else - result = gskit_connect_step1(cf, data); - } - - /* Handle handshake pipelining. */ - if(!result) - if(pipe_ssloverssl(cf, data, SOS_READ | SOS_WRITE) < 0) - result = CURLE_SSL_CONNECT_ERROR; - - /* Step 2: check if handshake is over. */ - if(!result && connssl->connecting_state == ssl_connect_2) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; - } - else - result = gskit_connect_step2(cf, data, nonblocking); - } - - /* Handle handshake pipelining. */ - if(!result) - if(pipe_ssloverssl(cf, data, SOS_READ | SOS_WRITE) < 0) - result = CURLE_SSL_CONNECT_ERROR; - - /* Step 3: gather certificate info, verify host. */ - if(!result && connssl->connecting_state == ssl_connect_3) - result = gskit_connect_step3(cf, data); - - if(result) - close_one(cf, data); - else if(connssl->connecting_state == ssl_connect_done) { - connssl->state = ssl_connection_complete; - connssl->connecting_state = ssl_connect_1; - *done = TRUE; - } - - return result; -} - - -static CURLcode gskit_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - struct ssl_connect_data *connssl = cf->ctx; - CURLcode result; - - result = gskit_connect_common(cf, data, TRUE, done); - if(*done || result) - connssl->connecting_state = ssl_connect_1; - return result; -} - - -static CURLcode gskit_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - CURLcode result; - bool done; - - connssl->connecting_state = ssl_connect_1; - result = gskit_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - - -static void gskit_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - close_one(cf, data); -} - - -static int gskit_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - int what; - int rc; - char buf[120]; - int loop = 10; /* don't get stuck */ - - DEBUGASSERT(BACKEND); - - if(!BACKEND->handle) - return 0; - -#ifndef CURL_DISABLE_FTP - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; -#endif - - close_one(cf, data); - rc = 0; - what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), - SSL_SHUTDOWN_TIMEOUT); - - while(loop--) { - ssize_t nread; - - if(what < 0) { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - rc = -1; - break; - } - - if(!what) { /* timeout */ - failf(data, "SSL shutdown timeout"); - break; - } - - /* Something to read, let's do it and hope that it is the close - notify alert from the server. No way to gsk_secure_soc_read() now, so - use read(). */ - - nread = read(Curl_conn_cf_get_socket(cf, data), buf, sizeof(buf)); - - if(nread < 0) { - char buffer[STRERROR_LEN]; - failf(data, "read: %s", Curl_strerror(errno, buffer, sizeof(buffer))); - rc = -1; - } - - if(nread <= 0) - break; - - what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); - } - - return rc; -} - - -static size_t gskit_version(char *buffer, size_t size) -{ - return msnprintf(buffer, size, "GSKit"); -} - - -static int gskit_check_cxn(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - int err; - int errlen; - - (void)data; - /* The only thing that can be tested here is at the socket level. */ - DEBUGASSERT(BACKEND); - - if(!BACKEND->handle) - return 0; /* connection has been closed */ - - err = 0; - errlen = sizeof(err); - - if(getsockopt(Curl_conn_cf_get_socket(cf, data), SOL_SOCKET, SO_ERROR, - (unsigned char *) &err, &errlen) || - errlen != sizeof(err) || err) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -static void *gskit_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - (void)info; - DEBUGASSERT(BACKEND); - return BACKEND->handle; -} - -const struct Curl_ssl Curl_ssl_gskit = { - { CURLSSLBACKEND_GSKIT, "gskit" }, /* info */ - - SSLSUPP_CERTINFO | - SSLSUPP_PINNEDPUBKEY, - - sizeof(struct ssl_backend_data), - - gskit_init, /* init */ - gskit_cleanup, /* cleanup */ - gskit_version, /* version */ - gskit_check_cxn, /* check_cxn */ - gskit_shutdown, /* shutdown */ - Curl_none_data_pending, /* data_pending */ - Curl_none_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - gskit_connect, /* connect */ - gskit_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ - gskit_get_internals, /* get_internals */ - gskit_close, /* close_one */ - Curl_none_close_all, /* close_all */ - /* No session handling for GSKit */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - NULL, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ - gskit_recv, /* recv decrypted data */ - gskit_send, /* send data to encrypt */ -}; - -#endif /* USE_GSKIT */ diff --git a/Utilities/cmcurl/lib/vtls/gskit.h b/Utilities/cmcurl/lib/vtls/gskit.h deleted file mode 100644 index c71e6a01170..00000000000 --- a/Utilities/cmcurl/lib/vtls/gskit.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef HEADER_CURL_GSKIT_H -#define HEADER_CURL_GSKIT_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" - -/* - * This header should only be needed to get included by vtls.c and gskit.c - */ - -#include "urldata.h" - -#ifdef USE_GSKIT - -extern const struct Curl_ssl Curl_ssl_gskit; - -#endif /* USE_GSKIT */ - -#endif /* HEADER_CURL_GSKIT_H */ diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c index 3d1906ea4c3..74c36fed943 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.c +++ b/Utilities/cmcurl/lib/vtls/gtls.c @@ -26,11 +26,11 @@ * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code * but vtls.c should ever call or use these functions. * - * Note: don't use the GnuTLS' *_t variable type names in this source code, + * Note: do not use the GnuTLS' *_t variable type names in this source code, * since they were not present in 1.0.X. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_GNUTLS @@ -40,24 +40,28 @@ #include #include -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" +#include "keylog.h" #include "gtls.h" #include "vtls.h" #include "vtls_int.h" -#include "vauth/vauth.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" -#include "warnless.h" +#include "vtls_scache.h" +#include "../vauth/vauth.h" +#include "../parsedate.h" +#include "../connect.h" /* for the connect timeout */ +#include "../progress.h" +#include "../select.h" +#include "../strcase.h" +#include "../strdup.h" +#include "../curlx/warnless.h" #include "x509asn1.h" -#include "multiif.h" -#include "curl_printf.h" -#include "curl_memory.h" +#include "../multiif.h" +#include "../curl_printf.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" +#include "../memdebug.h" /* Enable GnuTLS debugging by defining GTLSDEBUG */ /*#define GTLSDEBUG */ @@ -76,23 +80,29 @@ static bool gtls_inited = FALSE; # include -struct ssl_backend_data { - struct gtls_instance gtls; +struct gtls_ssl_backend_data { + struct gtls_ctx gtls; }; static ssize_t gtls_push(void *s, const void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &result); + CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %zd, err=%d", + blen, nwritten, result); + backend->gtls.io_result = result; if(nwritten < 0) { - gnutls_transport_set_errno(connssl->backend->gtls.session, - (CURLE_AGAIN == result)? EAGAIN : EINVAL); + /* !checksrc! disable ERRNOVAR 1 */ + gnutls_transport_set_errno(backend->gtls.session, + (CURLE_AGAIN == result) ? EAGAIN : EINVAL); nwritten = -1; } return nwritten; @@ -102,17 +112,35 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result; DEBUGASSERT(data); + if(!backend->gtls.shared_creds->trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) { + /* !checksrc! disable ERRNOVAR 1 */ + gnutls_transport_set_errno(backend->gtls.session, EINVAL); + backend->gtls.io_result = result; + return -1; + } + } + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); + CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %zd, err=%d", + blen, nread, result); + backend->gtls.io_result = result; if(nread < 0) { - gnutls_transport_set_errno(connssl->backend->gtls.session, - (CURLE_AGAIN == result)? EAGAIN : EINVAL); + /* !checksrc! disable ERRNOVAR 1 */ + gnutls_transport_set_errno(backend->gtls.session, + (CURLE_AGAIN == result) ? EAGAIN : EINVAL); nread = -1; } + else if(nread == 0) + connssl->peer_closed = TRUE; return nread; } @@ -127,7 +155,7 @@ static int gtls_init(void) { int ret = 1; if(!gtls_inited) { - ret = gnutls_global_init()?0:1; + ret = gnutls_global_init() ? 0 : 1; #ifdef GTLSDEBUG gnutls_global_set_log_function(tls_log_func); gnutls_global_set_log_level(2); @@ -161,7 +189,7 @@ static void showtime(struct Curl_easy *data, sizeof(str), " %s: %s, %02d %s %4d %02d:%02d:%02d GMT", text, - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], tm->tm_mday, Curl_month[tm->tm_mon], tm->tm_year + 1900, @@ -205,106 +233,73 @@ static void unload_file(gnutls_datum_t data) } -/* this function does a SSL/TLS (re-)handshake */ +/* this function does an SSL/TLS (re-)handshake */ static CURLcode handshake(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool duringconnect, - bool nonblocking) + struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; gnutls_session_t session; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + int rc; DEBUGASSERT(backend); session = backend->gtls.session; - for(;;) { - timediff_t timeout_ms; - int rc; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + backend->gtls.io_result = CURLE_OK; + rc = gnutls_handshake(session); - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, duringconnect); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - int what; - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0: - timeout_ms?timeout_ms:1000); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) - return CURLE_OK; - else if(timeout_ms) { - /* timeout */ - failf(data, "SSL connection timeout at %ld", (long)timeout_ms); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } + if(!backend->gtls.shared_creds->trust_setup) { + /* After having send off the ClientHello, we prepare the trust + * store to verify the coming certificate from the server */ + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) + return result; + } - rc = gnutls_handshake(session); + if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { + connssl->io_need = + gnutls_record_get_direction(session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + return CURLE_AGAIN; + } + else if((rc < 0) && !gnutls_error_is_fatal(rc)) { + const char *strerr = NULL; - if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { - connssl->connecting_state = - gnutls_record_get_direction(session)? - ssl_connect_2_writing:ssl_connect_2_reading; - continue; + if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { + gnutls_alert_description_t alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); } - else if((rc < 0) && !gnutls_error_is_fatal(rc)) { - const char *strerr = NULL; - if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); - strerr = gnutls_alert_get_name(alert); - } + if(!strerr) + strerr = gnutls_strerror(rc); - if(!strerr) - strerr = gnutls_strerror(rc); + infof(data, "gnutls_handshake() warning: %s", strerr); + return CURLE_AGAIN; + } + else if((rc < 0) && backend->gtls.io_result) { + return backend->gtls.io_result; + } + else if(rc < 0) { + const char *strerr = NULL; - infof(data, "gnutls_handshake() warning: %s", strerr); - continue; + if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { + gnutls_alert_description_t alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); } - else if(rc < 0) { - const char *strerr = NULL; - - if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); - strerr = gnutls_alert_get_name(alert); - } - if(!strerr) - strerr = gnutls_strerror(rc); + if(!strerr) + strerr = gnutls_strerror(rc); - failf(data, "gnutls_handshake() failed: %s", strerr); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - return CURLE_OK; + failf(data, "GnuTLS, handshake failed: %s", strerr); + return CURLE_SSL_CONNECT_ERROR; } + + return CURLE_OK; } -static gnutls_x509_crt_fmt_t do_file_type(const char *type) +static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) { if(!type || !type[0]) return GNUTLS_X509_FMT_PEM; @@ -321,11 +316,18 @@ static gnutls_x509_crt_fmt_t do_file_type(const char *type) */ #define GNUTLS_SRP "+SRP" +#define QUIC_PRIORITY \ + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ + "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ + "%DISABLE_TLS13_COMPAT_MODE" + static CURLcode -set_ssl_version_min_max(struct Curl_easy *data, - struct ssl_primary_config *conn_config, - const char **prioritylist, - const char *tls13support) +gnutls_set_ssl_version_min_max(struct Curl_easy *data, + struct ssl_peer *peer, + struct ssl_primary_config *conn_config, + const char **prioritylist, + bool tls13support) { long ssl_version = conn_config->version; long ssl_version_max = conn_config->version_max; @@ -335,8 +337,19 @@ set_ssl_version_min_max(struct Curl_easy *data, ssl_version = CURL_SSLVERSION_TLSv1_0; if(ssl_version_max == CURL_SSLVERSION_MAX_NONE) ssl_version_max = CURL_SSLVERSION_MAX_DEFAULT; + + if(peer->transport == TRNSPRT_QUIC) { + if((ssl_version_max != CURL_SSLVERSION_MAX_DEFAULT) && + (ssl_version_max < CURL_SSLVERSION_MAX_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } + *prioritylist = QUIC_PRIORITY; + return CURLE_OK; + } + if(!tls13support) { - /* If the running GnuTLS doesn't support TLS 1.3, we must not specify a + /* If the running GnuTLS does not support TLS 1.3, we must not specify a prioritylist involving that since it will make GnuTLS return an en error back at us */ if((ssl_version_max == CURL_SSLVERSION_MAX_TLSv1_3) || @@ -394,31 +407,457 @@ set_ssl_version_min_max(struct Curl_easy *data, return CURLE_SSL_CONNECT_ERROR; } -CURLcode gtls_client_init(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - const char *hostname, - struct gtls_instance *gtls, - long *pverifyresult) +CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, + struct gtls_shared_creds **pcreds) +{ + struct gtls_shared_creds *shared; + int rc; + + *pcreds = NULL; + shared = calloc(1, sizeof(*shared)); + if(!shared) + return CURLE_OUT_OF_MEMORY; + + rc = gnutls_certificate_allocate_credentials(&shared->creds); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); + free(shared); + return CURLE_SSL_CONNECT_ERROR; + } + + shared->refcount = 1; + shared->time = curlx_now(); + *pcreds = shared; + return CURLE_OK; +} + +CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds) +{ + DEBUGASSERT(creds); + if(creds->refcount < SIZE_T_MAX) { + ++creds->refcount; + return CURLE_OK; + } + return CURLE_BAD_FUNCTION_ARGUMENT; +} + +void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds) +{ + struct gtls_shared_creds *shared = *pcreds; + *pcreds = NULL; + if(shared) { + --shared->refcount; + if(!shared->refcount) { + gnutls_certificate_free_credentials(shared->creds); + free(shared->CAfile); + free(shared); + } + } +} + +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); + int rc; + + if(config->verifypeer) { + bool imported_native_ca = FALSE; + + if(ssl_config->native_ca_store) { + rc = gnutls_certificate_set_x509_system_trust(creds); + if(rc < 0) + infof(data, "error reading native ca store (%s), continuing anyway", + gnutls_strerror(rc)); + else { + infof(data, "found %d certificates in native ca store", rc); + if(rc > 0) + imported_native_ca = TRUE; + } + } + + if(config->CAfile) { + /* set the trusted CA cert bundle file */ + gnutls_certificate_set_verify_flags(creds, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + + rc = gnutls_certificate_set_x509_trust_file(creds, + config->CAfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CAfile, gnutls_strerror(rc), + (imported_native_ca ? ", continuing anyway" : "")); + if(!imported_native_ca) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CAfile); + } + + if(config->CApath) { + /* set the trusted CA cert directory */ + rc = gnutls_certificate_set_x509_trust_dir(creds, config->CApath, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CApath, gnutls_strerror(rc), + (imported_native_ca ? ", continuing anyway" : "")); + if(!imported_native_ca) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CApath); + } + } + + if(config->CRLfile) { + /* set the CRL list file */ + rc = gnutls_certificate_set_x509_crl_file(creds, config->CRLfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + failf(data, "error reading crl file %s (%s)", + config->CRLfile, gnutls_strerror(rc)); + return CURLE_SSL_CRL_BADFILE; + } + else + infof(data, "found %d CRL in %s", rc, config->CRLfile); + } + + return CURLE_OK; +} + +/* key to use at `multi->proto_hash` */ +#define MPROTO_GTLS_X509_KEY "tls:gtls:x509:share" + +static bool gtls_shared_creds_expired(const struct Curl_easy *data, + const struct gtls_shared_creds *sc) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + struct curltime now = curlx_now(); + timediff_t elapsed_ms = curlx_timediff(now, sc->time); + timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + + if(timeout_ms < 0) + return FALSE; + + return elapsed_ms >= timeout_ms; +} + +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); + if(!sc->CAfile || !conn_config->CAfile) + return sc->CAfile != conn_config->CAfile; + + return strcmp(sc->CAfile, conn_config->CAfile); +} + +static struct gtls_shared_creds* +gtls_get_cached_creds(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct gtls_shared_creds *shared_creds; + + if(data->multi) { + shared_creds = Curl_hash_pick(&data->multi->proto_hash, + CURL_UNCONST(MPROTO_GTLS_X509_KEY), + sizeof(MPROTO_GTLS_X509_KEY)-1); + if(shared_creds && shared_creds->creds && + !gtls_shared_creds_expired(data, shared_creds) && + !gtls_shared_creds_different(cf, shared_creds)) { + return shared_creds; + } + } + return NULL; +} + +static void gtls_shared_creds_hash_free(void *key, size_t key_len, void *p) +{ + struct gtls_shared_creds *sc = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_GTLS_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_GTLS_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + Curl_gtls_shared_creds_free(&sc); /* down reference */ +} + +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); + + DEBUGASSERT(sc); + DEBUGASSERT(sc->creds); + DEBUGASSERT(!sc->CAfile); + DEBUGASSERT(sc->refcount == 1); + if(!data->multi) + return; + + if(conn_config->CAfile) { + sc->CAfile = strdup(conn_config->CAfile); + if(!sc->CAfile) + return; + } + + if(Curl_gtls_shared_creds_up_ref(sc)) + return; + + if(!Curl_hash_add2(&data->multi->proto_hash, + CURL_UNCONST(MPROTO_GTLS_X509_KEY), + sizeof(MPROTO_GTLS_X509_KEY)-1, + sc, gtls_shared_creds_hash_free)) { + Curl_gtls_shared_creds_free(&sc); /* down reference again */ + return; + } +} + +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 gtls_shared_creds *cached_creds = NULL; + bool cache_criteria_met; + CURLcode result; + int rc; + + + /* 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) && + conn_config->verifypeer && + !conn_config->CApath && + !conn_config->ca_info_blob && + !ssl_config->primary.CRLfile && + !ssl_config->native_ca_store && + !conn_config->clientcert; /* GnuTLS adds client cert to its credentials! */ + + if(cache_criteria_met) + cached_creds = gtls_get_cached_creds(cf, data); + + if(cached_creds && !Curl_gtls_shared_creds_up_ref(cached_creds)) { + CURL_TRC_CF(data, cf, "using shared trust anchors and CRLs"); + Curl_gtls_shared_creds_free(>ls->shared_creds); + gtls->shared_creds = cached_creds; + rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, + gtls->shared_creds->creds); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + CURL_TRC_CF(data, cf, "loading trust anchors and CRLs"); + result = gtls_populate_creds(cf, data, gtls->shared_creds->creds); + if(result) + return result; + gtls->shared_creds->trust_setup = TRUE; + if(cache_criteria_met) + gtls_set_cached_creds(cf, data, gtls->shared_creds); + } + return CURLE_OK; +} + +CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + gnutls_session_t session, + curl_off_t valid_until, + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct Curl_ssl_session *sc_session; + unsigned char *sdata, *qtp_clone = NULL; + size_t sdata_len = 0; + size_t earlydata_max = 0; + CURLcode result = CURLE_OK; + + if(!ssl_config->primary.cache_session) + return CURLE_OK; + + /* we always unconditionally get the session id here, as even if we + already got it from the cache and asked to use it in the connection, it + might've been rejected and then a new one is in use now and we need to + detect that. */ + + /* get the session ID data size */ + gnutls_session_get_data(session, NULL, &sdata_len); + if(!sdata_len) /* gnutls does this for some version combinations */ + return CURLE_OK; + + sdata = malloc(sdata_len); /* get a buffer for it */ + if(!sdata) + return CURLE_OUT_OF_MEMORY; + + /* extract session ID to the allocated buffer */ + gnutls_session_get_data(session, sdata, &sdata_len); + earlydata_max = gnutls_record_get_max_early_data_size(session); + + CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s, earlymax=%zu) " + "and store in cache", sdata_len, alpn ? alpn : "-", + earlydata_max); + if(quic_tp && quic_tp_len) { + qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len); + if(!qtp_clone) { + free(sdata); + return CURLE_OUT_OF_MEMORY; + } + } + + result = Curl_ssl_session_create2(sdata, sdata_len, + Curl_glts_get_ietf_proto(session), + alpn, valid_until, earlydata_max, + qtp_clone, quic_tp_len, + &sc_session); + /* call took ownership of `sdata` and `qtp_clone` */ + if(!result) { + result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); + /* took ownership of `sc_session` */ + } + return result; +} + +int Curl_glts_get_ietf_proto(gnutls_session_t session) +{ + switch(gnutls_protocol_get_version(session)) { + case GNUTLS_SSL3: + return CURL_IETF_PROTO_SSL3; + case GNUTLS_TLS1_0: + return CURL_IETF_PROTO_TLS1; + case GNUTLS_TLS1_1: + return CURL_IETF_PROTO_TLS1_1; + case GNUTLS_TLS1_2: + return CURL_IETF_PROTO_TLS1_2; + case GNUTLS_TLS1_3: + return CURL_IETF_PROTO_TLS1_3; + default: + return CURL_IETF_PROTO_UNKNOWN; + } +} + +static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session) +{ + struct ssl_connect_data *connssl = cf->ctx; + return Curl_gtls_cache_session(cf, data, connssl->peer.scache_key, + session, 0, connssl->negotiated.alpn, + NULL, 0); +} + +static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + struct Curl_cfilter *cf = gnutls_session_get_ptr(session); + + (void)msg; + (void)incoming; + if(when) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + if(data) { + CURL_TRC_CF(data, cf, "handshake: %s message type %d", + incoming ? "incoming" : "outgoing", htype); + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + cf_gtls_update_session_id(cf, data, session); + break; + } + default: + break; + } + } + } + return 0; +} + +static CURLcode gtls_set_priority(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls, + const char *priority) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct dynbuf buf; + const char *err = NULL; + CURLcode result = CURLE_OK; + int rc; + + curlx_dyn_init(&buf, 4096); + +#ifdef USE_GNUTLS_SRP + if(conn_config->username) { + /* Only add SRP to the cipher list if SRP is requested. Otherwise + * GnuTLS will disable TLS 1.3 support. */ + result = curlx_dyn_add(&buf, priority); + if(!result) + result = curlx_dyn_add(&buf, ":" GNUTLS_SRP); + if(result) + goto out; + priority = curlx_dyn_ptr(&buf); + } +#endif + + if(conn_config->cipher_list) { + if((conn_config->cipher_list[0] == '+') || + (conn_config->cipher_list[0] == '-') || + (conn_config->cipher_list[0] == '!')) { + /* add it to out own */ + if(!curlx_dyn_len(&buf)) { /* not added yet */ + result = curlx_dyn_add(&buf, priority); + if(result) + goto out; + } + result = curlx_dyn_addf(&buf, ":%s", conn_config->cipher_list); + if(result) + goto out; + priority = curlx_dyn_ptr(&buf); + } + else /* replace our own completely */ + priority = conn_config->cipher_list; + } + + infof(data, "GnuTLS priority: %s", priority); + rc = gnutls_priority_set_direct(gtls->session, priority, &err); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "Error %d setting GnuTLS priority: %s", rc, err); + result = CURLE_SSL_CONNECT_ERROR; + } + +out: + curlx_dyn_free(&buf); + return result; +} + +static CURLcode gtls_client_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + 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); unsigned int init_flags; int rc; bool sni = TRUE; /* default is SNI enabled */ -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif const char *prioritylist; - const char *err = NULL; - const char *tls13support; + bool tls13support; CURLcode result; if(!gtls_inited) gtls_init(); - *pverifyresult = 0; - if(config->version == CURL_SSLVERSION_SSLv2) { failf(data, "GnuTLS does not support SSLv2"); return CURLE_SSL_CONNECT_ERROR; @@ -426,12 +865,10 @@ CURLcode gtls_client_init(struct Curl_easy *data, else if(config->version == CURL_SSLVERSION_SSLv3) sni = FALSE; /* SSLv3 has no SNI */ - /* allocate a cred struct */ - rc = gnutls_certificate_allocate_credentials(>ls->cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } + /* allocate a shared creds struct */ + result = Curl_gtls_shared_creds_create(data, >ls->shared_creds); + if(result) + return result; #ifdef USE_GNUTLS_SRP if(config->username && Curl_auth_allowed_to_host(data)) { @@ -455,92 +892,50 @@ CURLcode gtls_client_init(struct Curl_easy *data, } #endif - if(config->CAfile) { - /* set the trusted CA cert bundle file */ - gnutls_certificate_set_verify_flags(gtls->cred, - GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - - rc = gnutls_certificate_set_x509_trust_file(gtls->cred, - config->CAfile, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)", - config->CAfile, gnutls_strerror(rc)); - if(config->verifypeer) { - *pverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found %d certificates in %s", rc, config->CAfile); - } - - if(config->CApath) { - /* set the trusted CA cert directory */ - rc = gnutls_certificate_set_x509_trust_dir(gtls->cred, - config->CApath, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)", - config->CApath, gnutls_strerror(rc)); - if(config->verifypeer) { - *pverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found %d certificates in %s", rc, config->CApath); - } - -#ifdef CURL_CA_FALLBACK - /* use system ca certificate store as fallback */ - if(config->verifypeer && !(config->CAfile || config->CApath)) { - /* this ignores errors on purpose */ - gnutls_certificate_set_x509_system_trust(gtls->cred); - } -#endif - - if(config->CRLfile) { - /* set the CRL list file */ - rc = gnutls_certificate_set_x509_crl_file(gtls->cred, - config->CRLfile, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - failf(data, "error reading crl file %s (%s)", - config->CRLfile, gnutls_strerror(rc)); - return CURLE_SSL_CRL_BADFILE; - } - else - infof(data, "found %d CRL in %s", rc, config->CRLfile); - } + ssl_config->certverifyresult = 0; /* Initialize TLS session as a client */ init_flags = GNUTLS_CLIENT; + if(peer->transport == TRNSPRT_QUIC && earlydata_max > 0) + init_flags |= GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_END_OF_EARLY_DATA; + else if(earlydata_max > 0 && earlydata_max != 0xFFFFFFFFUL) + /* See https://gitlab.com/gnutls/gnutls/-/issues/1619 + * We cannot differentiate between a session announcing no earldata + * and one announcing 0xFFFFFFFFUL. On TCP+TLS, this is unlikely, but + * on QUIC this is common. */ + init_flags |= GNUTLS_ENABLE_EARLY_DATA; #if defined(GNUTLS_FORCE_CLIENT_CERT) init_flags |= GNUTLS_FORCE_CLIENT_CERT; #endif -#if defined(GNUTLS_NO_TICKETS) - /* Disable TLS session tickets */ - init_flags |= GNUTLS_NO_TICKETS; +#if defined(GNUTLS_NO_TICKETS_TLS12) + init_flags |= GNUTLS_NO_TICKETS_TLS12; +#elif defined(GNUTLS_NO_TICKETS) + /* Disable TLS session tickets for non 1.3 connections */ + if((config->version != CURL_SSLVERSION_TLSv1_3) && + (config->version != CURL_SSLVERSION_DEFAULT)) + init_flags |= GNUTLS_NO_TICKETS; +#endif + +#if defined(GNUTLS_NO_STATUS_REQUEST) + if(!config->verifystatus) + /* Disable the "status_request" TLS extension, enabled by default since + GnuTLS 3.8.0. */ + init_flags |= GNUTLS_NO_STATUS_REQUEST; #endif + CURL_TRC_CF(data, cf, "gnutls_init(flags=%x), earlydata=%zu", + init_flags, earlydata_max); rc = gnutls_init(>ls->session, init_flags); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_init() failed: %d", rc); return CURLE_SSL_CONNECT_ERROR; } - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, hostname, &snilen); - if(!snihost || gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS, - snihost, snilen) < 0) { + if(sni && peer->sni) { + if(gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS, + peer->sni, strlen(peer->sni)) < 0) { failf(data, "Failed to set SNI"); return CURLE_SSL_CONNECT_ERROR; } @@ -552,10 +947,10 @@ CURLcode gtls_client_init(struct Curl_easy *data, return CURLE_SSL_CONNECT_ERROR; /* "In GnuTLS 3.6.5, TLS 1.3 is enabled by default" */ - tls13support = gnutls_check_version("3.6.5"); + tls13support = !!gnutls_check_version("3.6.5"); /* Ensure +SRP comes at the *end* of all relevant strings so that it can be - * removed if a run-time error indicates that SRP is not supported by this + * removed if a runtime error indicates that SRP is not supported by this * GnuTLS version */ if(config->version == CURL_SSLVERSION_SSLv2 || @@ -572,74 +967,54 @@ CURLcode gtls_client_init(struct Curl_easy *data, } /* At this point we know we have a supported TLS version, so set it */ - result = set_ssl_version_min_max(data, config, &prioritylist, tls13support); + result = gnutls_set_ssl_version_min_max(data, peer, + config, &prioritylist, tls13support); if(result) return result; -#ifdef USE_GNUTLS_SRP - /* Only add SRP to the cipher list if SRP is requested. Otherwise - * GnuTLS will disable TLS 1.3 support. */ - if(config->username) { - size_t len = strlen(prioritylist); - - char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1); - if(!prioritysrp) - return CURLE_OUT_OF_MEMORY; - strcpy(prioritysrp, prioritylist); - strcpy(prioritysrp + len, ":" GNUTLS_SRP); - rc = gnutls_priority_set_direct(gtls->session, prioritysrp, &err); - free(prioritysrp); - - if((rc == GNUTLS_E_INVALID_REQUEST) && err) { - infof(data, "This GnuTLS does not support SRP"); - } - } - else { -#endif - infof(data, "GnuTLS ciphers: %s", prioritylist); - rc = gnutls_priority_set_direct(gtls->session, prioritylist, &err); -#ifdef USE_GNUTLS_SRP - } -#endif - - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "Error %d setting GnuTLS cipher list starting with %s", - rc, err); - return CURLE_SSL_CONNECT_ERROR; - } + result = gtls_set_priority(cf, data, gtls, prioritylist); + if(result) + return result; if(config->clientcert) { - if(ssl_config->key_passwd) { + if(!gtls->shared_creds->trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, gtls); + if(result) + return result; + } + if(ssl_config->cert_type && strcasecompare(ssl_config->cert_type, "P12")) { + rc = gnutls_certificate_set_x509_simple_pkcs12_file( + gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER, + ssl_config->key_passwd ? ssl_config->key_passwd : ""); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, + "error reading X.509 potentially-encrypted key or certificate " + "file: %s", + gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { const unsigned int supported_key_encryption_algorithms = GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR | GNUTLS_PKCS_USE_PKCS12_RC2_40 | GNUTLS_PKCS_USE_PBES2_3DES | GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 | GNUTLS_PKCS_USE_PBES2_AES_256; rc = gnutls_certificate_set_x509_key_file2( - gtls->cred, + gtls->shared_creds->creds, config->clientcert, ssl_config->key ? ssl_config->key : config->clientcert, - do_file_type(ssl_config->cert_type), + gnutls_do_file_type(ssl_config->cert_type), ssl_config->key_passwd, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { failf(data, - "error reading X.509 potentially-encrypted key file: %s", + "error reading X.509 %skey file: %s", + ssl_config->key_passwd ? "potentially-encrypted " : "", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } } - else { - if(gnutls_certificate_set_x509_key_file( - gtls->cred, - config->clientcert, - ssl_config->key ? ssl_config->key : config->clientcert, - do_file_type(ssl_config->cert_type) ) != - GNUTLS_E_SUCCESS) { - failf(data, "error reading X.509 key or certificate file"); - return CURLE_SSL_CONNECT_ERROR; - } - } } #ifdef USE_GNUTLS_SRP @@ -656,7 +1031,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, #endif { rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, - gtls->cred); + gtls->shared_creds->creds); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; @@ -675,14 +1050,172 @@ CURLcode gtls_client_init(struct Curl_easy *data, return CURLE_OK; } -static CURLcode -gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +static int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) +{ + gnutls_datum_t crandom; + gnutls_datum_t srandom; + + gnutls_session_get_random(session, &crandom, &srandom); + if(crandom.size != 32) { + return -1; + } + + Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + return 0; +} + +static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + + *do_early_data = FALSE; + connssl->earlydata_max = + gnutls_record_get_max_early_data_size(backend->gtls.session); + if((!connssl->earlydata_max || connssl->earlydata_max == 0xFFFFFFFFUL)) { + /* Seems to be GnuTLS way to signal no EarlyData in session */ + CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); + } + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { + CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data"); + } + else { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", connssl->earlydata_max, scs->alpn); + connssl->earlydata_state = ssl_earlydata_await; + connssl->state = ssl_connection_deferred; + result = Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)scs->alpn, + scs->alpn ? strlen(scs->alpn) : 0); + *do_early_data = !result; + } + return result; +} + +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + 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_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - long * const pverifyresult = &ssl_config->certverifyresult; + struct Curl_ssl_session *scs = NULL; + gnutls_datum_t gtls_alpns[ALPN_ENTRIES_MAX]; + size_t gtls_alpns_count = 0; + bool gtls_session_setup = FALSE; + struct alpn_spec alpns; + CURLcode result = CURLE_OK; + int rc; + + DEBUGASSERT(gctx); + Curl_alpn_copy(&alpns, alpns_requested); + + /* This might be a reconnect, so we check for a session ID in the cache + to speed up things. We need to do this before constructing the gnutls + session since we need to set flags depending on the kind of reuse. */ + if(conn_config->cache_session) { + result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs); + if(result) + goto out; + + if(scs && scs->sdata && scs->sdata_len && + (!scs->alpn || Curl_alpn_contains_proto(&alpns, scs->alpn))) { + /* we got a cached session, use it! */ + + result = gtls_client_init(cf, data, peer, scs->earlydata_max, gctx); + if(result) + goto out; + gtls_session_setup = TRUE; + + rc = gnutls_session_set_data(gctx->session, scs->sdata, scs->sdata_len); + if(rc < 0) + infof(data, "SSL session not accepted by GnuTLS, continuing without"); + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + if(ssl_config->earlydata && scs->alpn && + !cf->conn->connect_only && + (gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3)) { + bool do_early_data = FALSE; + if(sess_reuse_cb) { + result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data); + if(result) + goto out; + } + if(do_early_data) { + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + Curl_alpn_restrict_to(&alpns, scs->alpn); + } + } + } + } + } + + if(!gtls_session_setup) { + result = gtls_client_init(cf, data, peer, 0, gctx); + if(result) + goto out; + } + + gnutls_session_set_ptr(gctx->session, ssl_user_data); + + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + goto out; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + gnutls_session_set_keylog_function(gctx->session, keylog_callback); + } + + /* convert the ALPN string from our arguments to a list of strings that + * gnutls wants and will convert internally back to this string for sending + * to the server. nice. */ + if(!gtls_alpns_count && alpns.count) { + size_t i; + DEBUGASSERT(CURL_ARRAYSIZE(gtls_alpns) >= alpns.count); + for(i = 0; i < alpns.count; ++i) { + gtls_alpns[i].data = (unsigned char *)alpns.entries[i]; + gtls_alpns[i].size = (unsigned int)strlen(alpns.entries[i]); + } + gtls_alpns_count = alpns.count; + } + + if(gtls_alpns_count && + gnutls_alpn_set_protocols(gctx->session, + gtls_alpns, (unsigned int)gtls_alpns_count, + GNUTLS_ALPN_MANDATORY)) { + failf(data, "failed setting ALPN"); + result = CURLE_SSL_CONNECT_ERROR; + } + +out: + Curl_ssl_scache_return(cf, data, peer->scache_key, scs); + return result; +} + +static CURLcode +gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; CURLcode result; DEBUGASSERT(backend); @@ -692,47 +1225,22 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) same connection */ return CURLE_OK; - result = gtls_client_init(data, conn_config, ssl_config, - connssl->hostname, - &backend->gtls, pverifyresult); + result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer, + connssl->alpn, NULL, NULL, cf, + gtls_on_session_reuse); if(result) return result; - if(connssl->alpn) { + if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { struct alpn_proto_buf proto; - gnutls_datum_t alpn[ALPN_ENTRIES_MAX]; - size_t i; - - for(i = 0; i < connssl->alpn->count; ++i) { - alpn[i].data = (unsigned char *)connssl->alpn->entries[i]; - alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]); - } - if(gnutls_alpn_set_protocols(backend->gtls.session, alpn, - (unsigned)connssl->alpn->count, 0)) { - failf(data, "failed setting ALPN"); - return CURLE_SSL_CONNECT_ERROR; - } + memset(&proto, 0, sizeof(proto)); Curl_alpn_to_proto_str(&proto, connssl->alpn); infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } - /* This might be a reconnect, so we check for a session ID in the cache - to speed up things */ - if(conn_config->sessionid) { - void *ssl_sessionid; - size_t ssl_idsize; - - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, &ssl_idsize)) { - /* we got a session id, use it! */ - gnutls_session_set_data(backend->gtls.session, - ssl_sessionid, ssl_idsize); - - /* Informational message */ - infof(data, "SSL re-using session ID"); - } - Curl_ssl_sessionid_unlock(data); - } + gnutls_handshake_set_hook_function(backend->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + gtls_handshake_cb); /* register callback functions and handle to send and receive data. */ gnutls_transport_set_ptr(backend->gtls.session, cf); @@ -755,7 +1263,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, /* Result is returned to caller */ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -805,8 +1313,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, gnutls_session_t session, struct ssl_primary_config *config, struct ssl_config_data *ssl_config, - const char *hostname, - const char *dispname, + struct ssl_peer *peer, const char *pinned_key) { unsigned int cert_list_size; @@ -818,16 +1325,17 @@ Curl_gtls_verifyserver(struct Curl_easy *data, char certname[65] = ""; /* limited to 64 chars by ASN.1 */ size_t size; time_t certclock; - const char *ptr; int rc; CURLcode result = CURLE_OK; #ifndef CURL_DISABLE_VERBOSE_STRINGS - unsigned int algo; + const char *ptr; + int algo; unsigned int bits; gnutls_protocol_t version = gnutls_protocol_get_version(session); #endif long * const certverifyresult = &ssl_config->certverifyresult; +#ifndef CURL_DISABLE_VERBOSE_STRINGS /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */ ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(session), gnutls_cipher_get(session), @@ -835,6 +1343,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, infof(data, "SSL connection using %s / %s", gnutls_protocol_get_name(version), ptr); +#endif /* This function will return the peer's raw certificate (chain) as sent by the peer. These certificates are in raw format (DER encoded for @@ -862,13 +1371,13 @@ Curl_gtls_verifyserver(struct Curl_easy *data, } #endif } - infof(data, " common name: WARNING couldn't obtain"); + infof(data, " common name: WARNING could not obtain"); } if(data->set.ssl.certinfo && chainp) { unsigned int i; - result = Curl_ssl_init_certinfo(data, cert_list_size); + result = Curl_ssl_init_certinfo(data, (int)cert_list_size); if(result) return result; @@ -876,7 +1385,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, const char *beg = (const char *) chainp[i].data; const char *end = beg + chainp[i].size; - result = Curl_extract_certinfo(data, i, beg, end); + result = Curl_extract_certinfo(data, (int)i, beg, end); if(result) return result; } @@ -902,9 +1411,18 @@ Curl_gtls_verifyserver(struct Curl_easy *data, /* verify_status is a bitmask of gnutls_certificate_status bits */ if(verify_status & GNUTLS_CERT_INVALID) { if(config->verifypeer) { - failf(data, "server certificate verification failed. CAfile: %s " - "CRLfile: %s", config->CAfile ? config->CAfile: - "none", + const char *cause = "certificate error, no details available"; + if(verify_status & GNUTLS_CERT_EXPIRED) + cause = "certificate has expired"; + else if(verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND) + cause = "certificate signer not trusted"; + else if(verify_status & GNUTLS_CERT_INSECURE_ALGORITHM) + cause = "certificate uses insecure algorithm"; + else if(verify_status & GNUTLS_CERT_INVALID_OCSP_STATUS) + cause = "attached OCSP status response is invalid"; + failf(data, "server verification failed: %s. (CAfile: %s " + "CRLfile: %s)", cause, + config->CAfile ? config->CAfile : "none", ssl_config->primary.CRLfile ? ssl_config->primary.CRLfile : "none"); return CURLE_PEER_FAILED_VERIFICATION; @@ -919,104 +1437,97 @@ Curl_gtls_verifyserver(struct Curl_easy *data, infof(data, " server certificate verification SKIPPED"); if(config->verifystatus) { - if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) { - gnutls_datum_t status_request; - gnutls_ocsp_resp_t ocsp_resp; + gnutls_datum_t status_request; + gnutls_ocsp_resp_t ocsp_resp; + gnutls_ocsp_cert_status_t status; + gnutls_x509_crl_reason_t reason; - gnutls_ocsp_cert_status_t status; - gnutls_x509_crl_reason_t reason; + rc = gnutls_ocsp_status_request_get(session, &status_request); - rc = gnutls_ocsp_status_request_get(session, &status_request); + if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + failf(data, "No OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - infof(data, " server certificate status verification FAILED"); + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { - failf(data, "No OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + gnutls_ocsp_resp_init(&ocsp_resp); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - gnutls_ocsp_resp_init(&ocsp_resp); + (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, + &status, NULL, NULL, NULL, &reason); - rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + switch(status) { + case GNUTLS_OCSP_CERT_GOOD: + break; - (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, - &status, NULL, NULL, NULL, &reason); + case GNUTLS_OCSP_CERT_REVOKED: { + const char *crl_reason; - switch(status) { - case GNUTLS_OCSP_CERT_GOOD: + switch(reason) { + default: + case GNUTLS_X509_CRLREASON_UNSPECIFIED: + crl_reason = "unspecified reason"; 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_KEYCOMPROMISE: + crl_reason = "private key compromised"; + break; - case GNUTLS_X509_CRLREASON_SUPERSEDED: - crl_reason = "certificate superseded"; - break; + case GNUTLS_X509_CRLREASON_CACOMPROMISE: + crl_reason = "CA compromised"; + break; - case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: - crl_reason = "operation has ceased"; - break; + case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: + crl_reason = "affiliation has changed"; + break; - case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: - crl_reason = "certificate is on hold"; - break; + case GNUTLS_X509_CRLREASON_SUPERSEDED: + crl_reason = "certificate superseded"; + break; - case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: - crl_reason = "will be removed from delta CRL"; - break; + case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: + crl_reason = "operation has ceased"; + break; - case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: - crl_reason = "privilege withdrawn"; - break; + case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: + crl_reason = "certificate is on hold"; + break; - case GNUTLS_X509_CRLREASON_AACOMPROMISE: - crl_reason = "AA compromised"; - break; - } + case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: + crl_reason = "will be removed from delta CRL"; + break; - failf(data, "Server certificate was revoked: %s", crl_reason); + case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: + crl_reason = "privilege withdrawn"; break; - } - default: - case GNUTLS_OCSP_CERT_UNKNOWN: - failf(data, "Server certificate status is unknown"); + case GNUTLS_X509_CRLREASON_AACOMPROMISE: + crl_reason = "AA compromised"; break; } - gnutls_ocsp_resp_deinit(ocsp_resp); + failf(data, "Server certificate was revoked: %s", crl_reason); + break; + } - return CURLE_SSL_INVALIDCERTSTATUS; + default: + case GNUTLS_OCSP_CERT_UNKNOWN: + failf(data, "Server certificate status is unknown"); + break; } - else - infof(data, " server certificate status verification OK"); + + gnutls_ocsp_resp_deinit(ocsp_resp); + if(status != GNUTLS_OCSP_CERT_GOOD) + return CURLE_SSL_INVALIDCERTSTATUS; } else infof(data, " server certificate status verification SKIPPED"); @@ -1033,17 +1544,17 @@ Curl_gtls_verifyserver(struct Curl_easy *data, gnutls_x509_crt_init(&x509_issuer); issuerp = load_file(config->issuercert); gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); - rc = gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); + rc = (int)gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); gnutls_x509_crt_deinit(x509_issuer); unload_file(issuerp); if(rc <= 0) { failf(data, "server certificate issuer check failed (IssuerCert: %s)", - config->issuercert?config->issuercert:"none"); + config->issuercert ? config->issuercert : "none"); gnutls_x509_crt_deinit(x509_cert); return CURLE_SSL_ISSUER_ERROR; } infof(data, " server certificate issuer check OK (Issuer Cert: %s)", - config->issuercert?config->issuercert:"none"); + config->issuercert ? config->issuercert : "none"); } size = sizeof(certname); @@ -1062,12 +1573,18 @@ Curl_gtls_verifyserver(struct Curl_easy *data, in RFC2818 (HTTPS), which takes into account wildcards, and the subject alternative name PKIX extension. Returns non zero on success, and zero on failure. */ - rc = gnutls_x509_crt_check_hostname(x509_cert, hostname); + + /* This function does not handle trailing dots, so if we have an SNI name + use that and fallback to the hostname only if there is no SNI (like for + IP addresses) */ + rc = (int)gnutls_x509_crt_check_hostname(x509_cert, + peer->sni ? peer->sni : + peer->hostname); #if GNUTLS_VERSION_NUMBER < 0x030306 - /* Before 3.3.6, gnutls_x509_crt_check_hostname() didn't check IP + /* Before 3.3.6, gnutls_x509_crt_check_hostname() did not check IP addresses. */ if(!rc) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 #define use_addr in6_addr #else #define use_addr in_addr @@ -1075,10 +1592,10 @@ Curl_gtls_verifyserver(struct Curl_easy *data, unsigned char addrbuf[sizeof(struct use_addr)]; size_t addrlen = 0; - if(Curl_inet_pton(AF_INET, hostname, addrbuf) > 0) + if(curlx_inet_pton(AF_INET, peer->hostname, addrbuf) > 0) addrlen = 4; -#ifdef ENABLE_IPV6 - else if(Curl_inet_pton(AF_INET6, hostname, addrbuf) > 0) +#ifdef USE_IPV6 + else if(curlx_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0) addrlen = 16; #endif @@ -1090,7 +1607,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, size_t certaddrlen = sizeof(certaddr); int ret = gnutls_x509_crt_get_subject_alt_name(x509_cert, i, certaddr, &certaddrlen, NULL); - /* If this happens, it wasn't an IP address. */ + /* If this happens, it was not an IP address. */ if(ret == GNUTLS_E_SHORT_MEMORY_BUFFER) continue; if(ret < 0) @@ -1108,13 +1625,13 @@ Curl_gtls_verifyserver(struct Curl_easy *data, if(!rc) { if(config->verifyhost) { failf(data, "SSL: certificate subject name (%s) does not match " - "target host name '%s'", certname, dispname); + "target hostname '%s'", certname, peer->dispname); gnutls_x509_crt_deinit(x509_cert); return CURLE_PEER_FAILED_VERIFICATION; } else infof(data, " common name: %s (does not match '%s')", - certname, dispname); + certname, peer->dispname); } else infof(data, " common name: %s (matched)", certname); @@ -1197,7 +1714,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, /* public key algorithm's parameters */ algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); infof(data, " certificate public key: %s", - gnutls_pk_algorithm_get_name(algo)); + gnutls_pk_algorithm_get_name((gnutls_pk_algorithm_t)algo)); /* version of the X.509 certificate. */ infof(data, " certificate version: #%d", @@ -1241,70 +1758,64 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, 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); - const char *pinned_key = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + const char *pinned_key = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char *pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif CURLcode result; result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config, - connssl->hostname, connssl->dispname, - pinned_key); + &connssl->peer, pinned_key); if(result) goto out; - if(connssl->alpn) { - gnutls_datum_t proto; - int rc; + /* Only on TLSv1.2 or lower do we have the session id now. For + * TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */ + if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3) + result = cf_gtls_update_session_id(cf, data, session); - rc = gnutls_alpn_get_selected_protocol(session, &proto); - if(rc == 0) - Curl_alpn_set_negotiated(cf, data, proto.data, proto.size); - else - Curl_alpn_set_negotiated(cf, data, NULL, 0); - } - - if(ssl_config->primary.sessionid) { - /* we always unconditionally get the session id here, as even if we - already got it from the cache and asked to use it in the connection, it - might've been rejected and then a new one is in use now and we need to - detect that. */ - void *connect_sessionid; - size_t connect_idsize = 0; - - /* get the session ID data size */ - gnutls_session_get_data(session, NULL, &connect_idsize); - connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ - - if(connect_sessionid) { - bool incache; - bool added = FALSE; - void *ssl_sessionid; - - /* extract session ID to the allocated buffer */ - gnutls_session_get_data(session, connect_sessionid, &connect_idsize); - - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)); - if(incache) { - /* there was one before in the cache, so instead of risking that the - previous one was rejected, we just kill that and store the new */ - Curl_ssl_delsessionid(data, ssl_sessionid); - } +out: + return result; +} - /* store this session id */ - result = Curl_ssl_addsessionid(cf, data, connect_sessionid, - connect_idsize, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) - free(connect_sessionid); - if(result) { - result = CURLE_OUT_OF_MEMORY; - } +static CURLcode gtls_send_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen; + ssize_t n; + + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending); + backend->gtls.io_result = CURLE_OK; + while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) { + n = gnutls_record_send_early_data(backend->gtls.session, buf, blen); + CURL_TRC_CF(data, cf, "gtls_send_earlydata(len=%zu) -> %zd", + blen, n); + if(n < 0) { + if(n == GNUTLS_E_AGAIN) + result = CURLE_AGAIN; + else + result = backend->gtls.io_result ? + backend->gtls.io_result : CURLE_SEND_ERROR; + goto out; + } + else if(!n) { + /* gnutls is buggy, it *SHOULD* return the amount of bytes it took in. + * Instead it returns 0 if everything was written. */ + n = (ssize_t)blen; } - else - result = CURLE_OUT_OF_MEMORY; - } + Curl_bufq_skip(&connssl->earlydata, (size_t)n); + } + /* sent everything there was */ + infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip); out: return result; } @@ -1315,223 +1826,273 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, */ /* We use connssl->connecting_state to keep track of the connection status; there are three states: 'ssl_connect_1' (not started yet or complete), - 'ssl_connect_2_reading' (waiting for data from server), and - 'ssl_connect_2_writing' (waiting to be able to write). + 'ssl_connect_2' (doing handshake with the server), and + 'ssl_connect_3' (verifying and getting stats). */ -static CURLcode -gtls_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, - bool *done) -{ +static CURLcode gtls_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { struct ssl_connect_data *connssl = cf->ctx; - int rc; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; CURLcode result = CURLE_OK; + DEBUGASSERT(backend); + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + /* Initiate the connection, if not already done */ - if(ssl_connect_1 == connssl->connecting_state) { - rc = gtls_connect_step1(cf, data); - if(rc) { - result = rc; + if(connssl->connecting_state == ssl_connect_1) { + result = gtls_connect_step1(cf, data); + if(result) goto out; - } + connssl->connecting_state = ssl_connect_2; } - rc = handshake(cf, data, TRUE, nonblocking); - if(rc) { - /* handshake() sets its own error message with failf() */ - result = rc; - goto out; + if(connssl->connecting_state == ssl_connect_2) { + if(connssl->earlydata_state == ssl_earlydata_await) { + goto out; + } + else if(connssl->earlydata_state == ssl_earlydata_sending) { + result = gtls_send_earlydata(cf, data); + if(result) + goto out; + connssl->earlydata_state = ssl_earlydata_sent; + } + DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) || + (connssl->earlydata_state == ssl_earlydata_sent)); + + result = handshake(cf, data); + if(result) + goto out; + connssl->connecting_state = ssl_connect_3; } /* Finish connecting once the handshake is done */ - if(ssl_connect_1 == connssl->connecting_state) { - struct ssl_backend_data *backend = connssl->backend; - gnutls_session_t session; - DEBUGASSERT(backend); - session = backend->gtls.session; - rc = gtls_verifyserver(cf, data, session); - if(rc) { - result = rc; + if(connssl->connecting_state == ssl_connect_3) { + gnutls_datum_t proto; + int rc; + result = gtls_verifyserver(cf, data, backend->gtls.session); + if(result) goto out; - } + connssl->state = ssl_connection_complete; + + rc = gnutls_alpn_get_selected_protocol(backend->gtls.session, &proto); + if(rc) { /* No ALPN from server */ + proto.data = NULL; + proto.size = 0; + } + + result = Curl_alpn_set_negotiated(cf, data, connssl, + proto.data, proto.size); + if(result) + goto out; + + if(connssl->earlydata_state > ssl_earlydata_none) { + /* We should be in this state by now */ + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent); + connssl->earlydata_state = + (gnutls_session_get_flags(backend->gtls.session) & + GNUTLS_SFLAGS_EARLY_DATA) ? + ssl_earlydata_accepted : ssl_earlydata_rejected; + } + connssl->connecting_state = ssl_connect_done; } -out: - *done = ssl_connect_1 == connssl->connecting_state; + if(connssl->connecting_state == ssl_connect_done) + DEBUGASSERT(connssl->state == ssl_connection_complete); +out: + if(result == CURLE_AGAIN) { + *done = FALSE; + return CURLE_OK; + } + *done = ((connssl->state == ssl_connection_complete) || + (connssl->state == ssl_connection_deferred)); + CURL_TRC_CF(data, cf, "gtls_connect_common() -> %d, done=%d", result, *done); return result; } -static CURLcode gtls_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return gtls_connect_common(cf, data, TRUE, done); -} - static CURLcode gtls_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + bool *done) { - CURLcode result; - bool done = FALSE; - - result = gtls_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + if((connssl->state == ssl_connection_deferred) && + (connssl->earlydata_state == ssl_earlydata_await)) { + /* We refuse to be pushed, we are waiting for someone to send/recv. */ + *done = TRUE; + return CURLE_OK; + } + return gtls_connect_common(cf, data, done); } static bool gtls_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct ssl_connect_data *ctx = cf->ctx; + struct gtls_ssl_backend_data *backend; (void)data; DEBUGASSERT(ctx && ctx->backend); - if(ctx->backend->gtls.session && - 0 != gnutls_record_check_pending(ctx->backend->gtls.session)) + backend = (struct gtls_ssl_backend_data *)ctx->backend; + if(backend->gtls.session && + 0 != gnutls_record_check_pending(backend->gtls.session)) return TRUE; return FALSE; } static ssize_t gtls_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *mem, - size_t len, + const void *buf, + size_t blen, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; ssize_t rc; + size_t nwritten, total_written = 0; (void)data; DEBUGASSERT(backend); - rc = gnutls_record_send(backend->gtls.session, mem, len); - if(rc < 0) { - *curlcode = (rc == GNUTLS_E_AGAIN) - ? CURLE_AGAIN - : CURLE_SEND_ERROR; + while(blen) { + backend->gtls.io_result = CURLE_OK; + rc = gnutls_record_send(backend->gtls.session, buf, blen); + + if(rc < 0) { + if(total_written && (rc == GNUTLS_E_AGAIN)) { + *curlcode = CURLE_OK; + rc = (ssize_t)total_written; + goto out; + } + *curlcode = (rc == GNUTLS_E_AGAIN) ? + CURLE_AGAIN : + (backend->gtls.io_result ? backend->gtls.io_result : CURLE_SEND_ERROR); - rc = -1; + rc = -1; + goto out; + } + nwritten = (size_t)rc; + total_written += nwritten; + DEBUGASSERT(nwritten <= blen); + buf = (char *)CURL_UNCONST(buf) + nwritten; + blen -= nwritten; } + rc = total_written; +out: return rc; } -static void gtls_close(struct Curl_cfilter *cf, - struct Curl_easy *data) +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +static CURLcode gtls_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + char buf[1024]; + CURLcode result = CURLE_OK; + ssize_t nread; + size_t i; - (void) data; DEBUGASSERT(backend); + /* If we have no handshaked connection or already shut down */ + if(!backend->gtls.session || cf->shutdown || + connssl->state != ssl_connection_complete) { + *done = TRUE; + goto out; + } - if(backend->gtls.session) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); - gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); - gnutls_deinit(backend->gtls.session); - backend->gtls.session = NULL; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->gtls.sent_shutdown) { + /* do this only once */ + backend->gtls.sent_shutdown = TRUE; + if(send_shutdown) { + int ret = gnutls_bye(backend->gtls.session, GNUTLS_SHUT_RDWR); + if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { + CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye EAGAIN"); + connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + backend->gtls.sent_shutdown = FALSE; + result = CURLE_OK; + goto out; + } + if(ret != GNUTLS_E_SUCCESS) { + CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye error: '%s'(%d)", + gnutls_strerror((int)ret), (int)ret); + result = CURLE_RECV_ERROR; + goto out; + } + } } - if(backend->gtls.cred) { - gnutls_certificate_free_credentials(backend->gtls.cred); - backend->gtls.cred = NULL; + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + for(i = 0; i < 10; ++i) { + nread = gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); + if(nread <= 0) + break; } -#ifdef USE_GNUTLS_SRP - if(backend->gtls.srp_client_cred) { - gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); - backend->gtls.srp_client_cred = NULL; + if(nread > 0) { + /* still data coming in? */ } -#endif + else if(nread == 0) { + /* We got the close notify alert and are done. */ + *done = TRUE; + } + else if((nread == GNUTLS_E_AGAIN) || (nread == GNUTLS_E_INTERRUPTED)) { + connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + } + else { + CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", + gnutls_strerror((int)nread), (int)nread); + result = CURLE_RECV_ERROR; + } + +out: + cf->shutdown = (result || *done); + return result; } -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int gtls_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void gtls_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_backend_data *backend = connssl->backend; - int retval = 0; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + (void) data; DEBUGASSERT(backend); - -#ifndef CURL_DISABLE_FTP - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); -#endif - + CURL_TRC_CF(data, cf, "close"); if(backend->gtls.session) { - ssize_t result; - bool done = FALSE; - char buf[120]; - - while(!done) { - int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), - SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - result = gnutls_record_recv(backend->gtls.session, - buf, sizeof(buf)); - switch(result) { - case 0: - /* This is the expected response. There was no data but only - the close notify alert */ - done = TRUE; - break; - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED"); - break; - default: - retval = -1; - done = TRUE; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = TRUE; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = TRUE; - } - } gnutls_deinit(backend->gtls.session); + backend->gtls.session = NULL; + } + if(backend->gtls.shared_creds) { + Curl_gtls_shared_creds_free(&backend->gtls.shared_creds); } - gnutls_certificate_free_credentials(backend->gtls.cred); - #ifdef USE_GNUTLS_SRP - if(ssl_config->primary.username) + if(backend->gtls.srp_client_cred) { gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); + backend->gtls.srp_client_cred = NULL; + } #endif - - backend->gtls.cred = NULL; - backend->gtls.session = NULL; - - return retval; } static ssize_t gtls_recv(struct Curl_cfilter *cf, @@ -1541,7 +2102,8 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; ssize_t ret; (void)data; @@ -1557,9 +2119,8 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, if(ret == GNUTLS_E_REHANDSHAKE) { /* BLOCKING call, this is bad but a work-around for now. Fixing this "the proper way" takes a whole lot of work. */ - CURLcode result = handshake(cf, data, FALSE, FALSE); + CURLcode result = handshake(cf, data); if(result) - /* handshake() writes error message on its own */ *curlcode = result; else *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */ @@ -1569,9 +2130,9 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, if(ret < 0) { failf(data, "GnuTLS recv error (%d): %s", - (int)ret, gnutls_strerror((int)ret)); - *curlcode = CURLE_RECV_ERROR; + *curlcode = backend->gtls.io_result ? + backend->gtls.io_result : CURLE_RECV_ERROR; ret = -1; goto out; } @@ -1580,12 +2141,7 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, return ret; } -static void gtls_session_free(void *ptr) -{ - free(ptr); -} - -static size_t gtls_version(char *buffer, size_t size) +size_t Curl_gtls_version(char *buffer, size_t size) { return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); } @@ -1597,7 +2153,7 @@ static CURLcode gtls_random(struct Curl_easy *data, int rc; (void)data; rc = gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); - return rc?CURLE_FAILED_INIT:CURLE_OK; + return rc ? CURLE_FAILED_INIT : CURLE_OK; } static CURLcode gtls_sha256sum(const unsigned char *tmp, /* input */ @@ -1620,7 +2176,8 @@ static bool gtls_cert_status_request(void) static void *gtls_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) { - struct ssl_backend_data *backend = connssl->backend; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; (void)info; DEBUGASSERT(backend); return backend->gtls.session; @@ -1632,35 +2189,32 @@ const struct Curl_ssl Curl_ssl_gnutls = { SSLSUPP_CA_PATH | SSLSUPP_CERTINFO | SSLSUPP_PINNEDPUBKEY | - SSLSUPP_HTTPS_PROXY, + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST | + SSLSUPP_CA_CACHE, - sizeof(struct ssl_backend_data), + sizeof(struct gtls_ssl_backend_data), gtls_init, /* init */ gtls_cleanup, /* cleanup */ - gtls_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ + Curl_gtls_version, /* version */ gtls_shutdown, /* shutdown */ gtls_data_pending, /* data_pending */ gtls_random, /* random */ gtls_cert_status_request, /* cert_status_request */ gtls_connect, /* connect */ - gtls_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ gtls_get_internals, /* get_internals */ gtls_close, /* close_one */ - Curl_none_close_all, /* close_all */ - gtls_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ + NULL, /* false_start */ gtls_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ gtls_recv, /* recv decrypted data */ gtls_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_GNUTLS */ diff --git a/Utilities/cmcurl/lib/vtls/gtls.h b/Utilities/cmcurl/lib/vtls/gtls.h index ac141e1c61b..35af9db1398 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.h +++ b/Utilities/cmcurl/lib/vtls/gtls.h @@ -24,12 +24,13 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include #ifdef USE_GNUTLS #include +#include "../curlx/timeval.h" #ifdef HAVE_GNUTLS_SRP /* the function exists */ @@ -41,33 +42,80 @@ struct Curl_easy; struct Curl_cfilter; +struct alpn_spec; struct ssl_primary_config; struct ssl_config_data; +struct ssl_peer; +struct ssl_connect_data; +struct Curl_ssl_session; -struct gtls_instance { +int Curl_glts_get_ietf_proto(gnutls_session_t session); + +struct gtls_shared_creds { + gnutls_certificate_credentials_t creds; + char *CAfile; /* CAfile path used to generate X509 store */ + struct curltime time; /* when the shared creds was created */ + size_t refcount; + BIT(trust_setup); /* x509 anchors + CRLs have been set up */ +}; + +CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, + struct gtls_shared_creds **pcreds); +CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds); +void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds); + +struct gtls_ctx { gnutls_session_t session; - gnutls_certificate_credentials_t cred; + struct gtls_shared_creds *shared_creds; #ifdef USE_GNUTLS_SRP gnutls_srp_client_credentials_t srp_client_cred; #endif + CURLcode io_result; /* result of last IO cfilter operation */ + BIT(sent_shutdown); }; -CURLcode -gtls_client_init(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - const char *hostname, - struct gtls_instance *gtls, - long *pverifyresult); - -CURLcode -Curl_gtls_verifyserver(struct Curl_easy *data, - gnutls_session_t session, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - const char *hostname, - const char *dispname, - const char *pinned_key); +size_t Curl_gtls_version(char *buffer, size_t size); + +typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); + +typedef CURLcode Curl_gtls_init_session_reuse_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data); + +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data, + Curl_gtls_init_session_reuse_cb *sess_reuse_cb); + +CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls); + +CURLcode Curl_gtls_verifyserver(struct Curl_easy *data, + gnutls_session_t session, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + struct ssl_peer *peer, + const char *pinned_key); + +/* Extract TLS session and place in cache, if configured. */ +CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + gnutls_session_t session, + curl_off_t valid_until, + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len); extern const struct Curl_ssl Curl_ssl_gnutls; diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.c b/Utilities/cmcurl/lib/vtls/hostcheck.c index d061c6356f9..8ca69941e82 100644 --- a/Utilities/cmcurl/lib/vtls/hostcheck.c +++ b/Utilities/cmcurl/lib/vtls/hostcheck.c @@ -22,10 +22,9 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(USE_OPENSSL) \ - || defined(USE_GSKIT) \ +#if defined(USE_OPENSSL) \ || defined(USE_SCHANNEL) /* these backends use functions from this file */ @@ -35,15 +34,14 @@ #ifdef HAVE_NETINET_IN6_H #include #endif -#include "curl_memrchr.h" - +#include "../curl_memrchr.h" #include "hostcheck.h" -#include "strcase.h" -#include "hostip.h" +#include "../strcase.h" +#include "../hostip.h" -#include "curl_memory.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" +#include "../memdebug.h" /* check the two input strings with given length, but do not assume they end in nul-bytes */ @@ -63,7 +61,7 @@ static bool pmatch(const char *hostname, size_t hostlen, * We use the matching rule described in RFC6125, section 6.4.3. * https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3 * - * In addition: ignore trailing dots in the host names and wildcards, so that + * In addition: ignore trailing dots in the hostnames and wildcards, so that * the names are used normalized. This is what the browsers do. * * Do not allow wildcard matching on IP numbers. There are apparently @@ -133,4 +131,4 @@ bool Curl_cert_hostcheck(const char *match, size_t matchlen, return FALSE; } -#endif /* OPENSSL, GSKIT or schannel+wince */ +#endif /* OPENSSL or SCHANNEL */ diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.h b/Utilities/cmcurl/lib/vtls/hostcheck.h index 22a1ac2e563..6b4e3796443 100644 --- a/Utilities/cmcurl/lib/vtls/hostcheck.h +++ b/Utilities/cmcurl/lib/vtls/hostcheck.h @@ -26,7 +26,7 @@ #include -/* returns TRUE if there's a match */ +/* returns TRUE if there is a match */ bool Curl_cert_hostcheck(const char *match_pattern, size_t matchlen, const char *hostname, size_t hostlen); diff --git a/Utilities/cmcurl/lib/vtls/keylog.c b/Utilities/cmcurl/lib/vtls/keylog.c index d37bb183e7c..8487d43e8c6 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.c +++ b/Utilities/cmcurl/lib/vtls/keylog.c @@ -21,26 +21,22 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" + +#if defined(USE_OPENSSL) || \ + defined(USE_GNUTLS) || \ + defined(USE_WOLFSSL) || \ + (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + defined(USE_QUICHE) || \ + defined(USE_RUSTLS) #include "keylog.h" #include +#include "../escape.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) - -#define CLIENT_RANDOM_SIZE 32 - -/* - * The master secret in TLS 1.2 and before is always 48 bytes. In TLS 1.3, the - * secret size depends on the cipher suite's hash function which is 32 bytes - * for SHA-256 and 48 bytes for SHA-384. - */ -#define SECRET_MAXLEN 48 - +#include "../curl_memory.h" +#include "../memdebug.h" /* The fp for the open SSLKEYLOGFILE, or NULL if not open */ static FILE *keylog_file_fp; @@ -55,7 +51,7 @@ Curl_tls_keylog_open(void) if(keylog_file_name) { keylog_file_fp = fopen(keylog_file_name, FOPEN_APPENDTEXT); if(keylog_file_fp) { -#ifdef WIN32 +#ifdef _WIN32 if(setvbuf(keylog_file_fp, NULL, _IONBF, 0)) #else if(setvbuf(keylog_file_fp, NULL, _IOLBF, 4096)) @@ -93,13 +89,13 @@ Curl_tls_keylog_write_line(const char *line) char buf[256]; if(!keylog_file_fp || !line) { - return false; + return FALSE; } linelen = strlen(line); if(linelen == 0 || linelen > sizeof(buf) - 2) { - /* Empty line or too big to fit in a LF and NUL. */ - return false; + /* Empty line or too big to fit in an LF and NUL. */ + return FALSE; } memcpy(buf, line, linelen); @@ -111,7 +107,7 @@ Curl_tls_keylog_write_line(const char *line) /* Using fputs here instead of fprintf since libcurl's fprintf replacement may not be thread-safe. */ fputs(buf, keylog_file_fp); - return true; + return TRUE; } bool @@ -119,19 +115,18 @@ Curl_tls_keylog_write(const char *label, const unsigned char client_random[CLIENT_RANDOM_SIZE], const unsigned char *secret, size_t secretlen) { - const char *hex = "0123456789ABCDEF"; size_t pos, i; - char line[KEYLOG_LABEL_MAXLEN + 1 + 2 * CLIENT_RANDOM_SIZE + 1 + - 2 * SECRET_MAXLEN + 1 + 1]; + unsigned char line[KEYLOG_LABEL_MAXLEN + 1 + 2 * CLIENT_RANDOM_SIZE + 1 + + 2 * SECRET_MAXLEN + 1 + 1]; if(!keylog_file_fp) { - return false; + return FALSE; } pos = strlen(label); if(pos > KEYLOG_LABEL_MAXLEN || !secretlen || secretlen > SECRET_MAXLEN) { /* Should never happen - sanity check anyway. */ - return false; + return FALSE; } memcpy(line, label, pos); @@ -139,21 +134,23 @@ Curl_tls_keylog_write(const char *label, /* Client Random */ for(i = 0; i < CLIENT_RANDOM_SIZE; i++) { - line[pos++] = hex[client_random[i] >> 4]; - line[pos++] = hex[client_random[i] & 0xF]; + Curl_hexbyte(&line[pos], client_random[i], FALSE); + pos += 2; } line[pos++] = ' '; /* Secret */ for(i = 0; i < secretlen; i++) { - line[pos++] = hex[secret[i] >> 4]; - line[pos++] = hex[secret[i] & 0xF]; + Curl_hexbyte(&line[pos], secret[i], FALSE); + pos += 2; } line[pos++] = '\n'; line[pos] = '\0'; /* Using fputs here instead of fprintf since libcurl's fprintf replacement may not be thread-safe. */ - fputs(line, keylog_file_fp); - return true; + fputs((char *)line, keylog_file_fp); + return TRUE; } + +#endif /* TLS or QUIC backend */ diff --git a/Utilities/cmcurl/lib/vtls/keylog.h b/Utilities/cmcurl/lib/vtls/keylog.h index eff5bf38f35..ec82abf547f 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.h +++ b/Utilities/cmcurl/lib/vtls/keylog.h @@ -23,7 +23,18 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" + +#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) + +#define CLIENT_RANDOM_SIZE 32 + +/* + * The master secret in TLS 1.2 and before is always 48 bytes. In TLS 1.3, the + * secret size depends on the cipher suite's hash function which is 32 bytes + * for SHA-256 and 48 bytes for SHA-384. + */ +#define SECRET_MAXLEN 48 /* * Opens the TLS key log file if requested by the user. The SSLKEYLOGFILE @@ -50,7 +61,7 @@ bool Curl_tls_keylog_write(const char *label, const unsigned char *secret, size_t secretlen); /* - * Appends a line to the key log file, ensure it is terminated by a LF. + * Appends a line to the key log file, ensure it is terminated by an LF. * Returns true iff the key log file is open and a valid line was provided. */ bool Curl_tls_keylog_write_line(const char *line); diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c index d95888c36e2..7af207caa77 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls.c @@ -29,7 +29,7 @@ * */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MBEDTLS @@ -54,34 +54,36 @@ # ifdef MBEDTLS_DEBUG # include # endif -#endif +#endif /* MBEDTLS_VERSION_MAJOR >= 2 */ -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "cipher_suite.h" +#include "../strcase.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" #include "mbedtls.h" #include "vtls.h" #include "vtls_int.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "multiif.h" +#include "vtls_scache.h" +#include "x509asn1.h" +#include "../parsedate.h" +#include "../connect.h" /* for the connect timeout */ +#include "../select.h" +#include "../multiif.h" #include "mbedtls_threadlock.h" +#include "../strdup.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* ALPN for http2 */ -#ifdef USE_HTTP2 -# undef HAS_ALPN -# ifdef MBEDTLS_SSL_ALPN -# define HAS_ALPN -# endif +#if defined(USE_HTTP2) && defined(MBEDTLS_SSL_ALPN) +# define HAS_ALPN_MBEDTLS #endif -struct ssl_backend_data { +struct mbed_ssl_backend_data { mbedtls_ctr_drbg_context ctr_drbg; mbedtls_entropy_context entropy; mbedtls_ssl_context ssl; @@ -92,26 +94,44 @@ struct ssl_backend_data { #endif mbedtls_pk_context pk; mbedtls_ssl_config config; -#ifdef HAS_ALPN +#ifdef HAS_ALPN_MBEDTLS const char *protocols[3]; #endif + int *ciphersuites; + size_t send_blocked_len; + BIT(initialized); /* mbedtls_ssl_context is initialized */ + BIT(sent_shutdown); + BIT(send_blocked); }; /* apply threading? */ -#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) -#define THREADING_SUPPORT +#if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ + defined(_WIN32) +#define HAS_THREADING_SUPPORT #endif #ifndef MBEDTLS_ERROR_C #define mbedtls_strerror(a,b,c) b[0] = 0 #endif -#if defined(THREADING_SUPPORT) +/* PSA can be used independently of TLS 1.3 */ +#if defined(MBEDTLS_USE_PSA_CRYPTO) && MBEDTLS_VERSION_NUMBER >= 0x03060000 +#define HAS_PSA_SUPPORT +#endif + +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && MBEDTLS_VERSION_NUMBER >= 0x03060000 +#define HAS_TLS13_SUPPORT +#endif + +#if defined(HAS_TLS13_SUPPORT) && defined(MBEDTLS_SSL_SESSION_TICKETS) +#define HAS_SESSION_TICKETS +#endif + +#ifdef HAS_THREADING_SUPPORT static mbedtls_entropy_context ts_entropy; static int entropy_init_initialized = 0; -/* start of entropy_init_mutex() */ static void entropy_init_mutex(mbedtls_entropy_context *ctx) { /* lock 0 = entropy_init_mutex() */ @@ -122,9 +142,18 @@ static void entropy_init_mutex(mbedtls_entropy_context *ctx) } Curl_mbedtlsthreadlock_unlock_function(0); } -/* end of entropy_init_mutex() */ -/* start of entropy_func_mutex() */ +static void entropy_cleanup_mutex(mbedtls_entropy_context *ctx) +{ + /* lock 0 = use same lock as init */ + Curl_mbedtlsthreadlock_lock_function(0); + if(entropy_init_initialized == 1) { + mbedtls_entropy_free(ctx); + entropy_init_initialized = 0; + } + Curl_mbedtlsthreadlock_unlock_function(0); +} + static int entropy_func_mutex(void *data, unsigned char *output, size_t len) { int ret; @@ -135,28 +164,30 @@ static int entropy_func_mutex(void *data, unsigned char *output, size_t len) return ret; } -/* end of entropy_func_mutex() */ -#endif /* THREADING_SUPPORT */ +#endif /* HAS_THREADING_SUPPORT */ #ifdef MBEDTLS_DEBUG static void mbed_debug(void *context, int level, const char *f_name, int line_nb, const char *line) { - struct Curl_easy *data = NULL; - - if(!context) - return; - - data = (struct Curl_easy *)context; - - infof(data, "%s", line); + struct Curl_easy *data = (struct Curl_easy *)context; (void) level; + (void) line_nb; + (void) f_name; + + if(data) { + size_t len = strlen(line); + if(len && (line[len - 1] == '\n')) + /* discount any trailing newline */ + len--; + infof(data, "%.*s", (int)len, line); + } } -#else #endif -static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen) +static int mbedtls_bio_cf_write(void *bio, + const unsigned char *buf, size_t blen) { struct Curl_cfilter *cf = bio; struct Curl_easy *data = CF_DATA_CURRENT(cf); @@ -164,16 +195,20 @@ static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen) CURLcode result; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result); - DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%zu) -> %zd, err=%d", - blen, nwritten, result)); + if(!data) + return 0; + + nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, blen, FALSE, + &result); + CURL_TRC_CF(data, cf, "mbedtls_bio_cf_out_write(len=%zu) -> %zd, err=%d", + blen, nwritten, result); if(nwritten < 0 && CURLE_AGAIN == result) { nwritten = MBEDTLS_ERR_SSL_WANT_WRITE; } return (int)nwritten; } -static int bio_cf_read(void *bio, unsigned char *buf, size_t blen) +static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) { struct Curl_cfilter *cf = bio; struct Curl_easy *data = CF_DATA_CURRENT(cf); @@ -181,13 +216,15 @@ static int bio_cf_read(void *bio, unsigned char *buf, size_t blen) CURLcode result; DEBUGASSERT(data); + if(!data) + return 0; /* OpenSSL catches this case, so should we. */ if(!buf) return 0; nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &result); - DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%zu) -> %zd, err=%d", - blen, nread, result)); + CURL_TRC_CF(data, cf, "mbedtls_bio_cf_in_read(len=%zu) -> %zd, err=%d", + blen, nread, result); if(nread < 0 && CURLE_AGAIN == result) { nread = MBEDTLS_ERR_SSL_WANT_READ; } @@ -211,8 +248,8 @@ static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = 1024, /* RSA min key len */ }; -/* See https://tls.mbed.org/discussions/generic/ - howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der +/* See https://web.archive.org/web/20200921194007/tls.mbed.org/discussions/ + generic/howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der */ #define RSA_PUB_DER_MAX_BYTES (38 + 2 * MBEDTLS_MPI_MAX_SIZE) #define ECP_PUB_DER_MAX_BYTES (30 + 2 * MBEDTLS_ECP_MAX_BYTES) @@ -220,94 +257,329 @@ static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = #define PUB_DER_MAX_BYTES (RSA_PUB_DER_MAX_BYTES > ECP_PUB_DER_MAX_BYTES ? \ RSA_PUB_DER_MAX_BYTES : ECP_PUB_DER_MAX_BYTES) -static CURLcode mbedtls_version_from_curl(int *mbedver, long version) +static CURLcode +mbed_set_ssl_version_min_max(struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + struct ssl_primary_config *conn_config) { -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - switch(version) { - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - break; + /* TLS 1.0 and TLS 1.1 were dropped with mbedTLS 3.0.0 (2021). So, since + * then, and before the introduction of TLS 1.3 in 3.6.0 (2024), this + * function basically always sets TLS 1.2 as min/max, unless given + * unsupported option values. */ + +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + int ver_min = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ + int ver_max = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ +#else + /* mbedTLS 3.2.0 (2022) introduced new methods for setting TLS version */ + mbedtls_ssl_protocol_version ver_min = MBEDTLS_SSL_VERSION_TLS1_2; + mbedtls_ssl_protocol_version ver_max = +#ifdef HAS_TLS13_SUPPORT + MBEDTLS_SSL_VERSION_TLS1_3 +#else + MBEDTLS_SSL_VERSION_TLS1_2 +#endif + ; +#endif + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + ver_min = MBEDTLS_SSL_MINOR_VERSION_1; + break; + case CURL_SSLVERSION_TLSv1_1: + ver_min = MBEDTLS_SSL_MINOR_VERSION_2; + break; +#else + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: +#endif + case CURL_SSLVERSION_TLSv1_2: +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + ver_min = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ +#else + ver_min = MBEDTLS_SSL_VERSION_TLS1_2; +#endif + break; + case CURL_SSLVERSION_TLSv1_3: +#ifdef HAS_TLS13_SUPPORT + ver_min = MBEDTLS_SSL_VERSION_TLS1_3; + break; +#endif + default: + failf(data, "mbedTLS: unsupported minimum TLS version value: %x", + conn_config->version); + return CURLE_SSL_CONNECT_ERROR; } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: +#ifdef HAS_TLS13_SUPPORT + ver_max = MBEDTLS_SSL_VERSION_TLS1_3; + break; +#endif + case CURL_SSLVERSION_MAX_TLSv1_2: +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + ver_max = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ #else - switch(version) { - case CURL_SSLVERSION_TLSv1_0: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_1; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_1: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_2; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_2: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - break; + ver_max = MBEDTLS_SSL_VERSION_TLS1_2; +#endif + break; +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + case CURL_SSLVERSION_MAX_TLSv1_1: + ver_max = MBEDTLS_SSL_MINOR_VERSION_2; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + ver_max = MBEDTLS_SSL_MINOR_VERSION_1; + break; +#else + case CURL_SSLVERSION_MAX_TLSv1_1: + case CURL_SSLVERSION_MAX_TLSv1_0: +#endif + default: + failf(data, "mbedTLS: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; } + +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + ver_min); + mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + ver_max); +#else + mbedtls_ssl_conf_min_tls_version(&backend->config, ver_min); + mbedtls_ssl_conf_max_tls_version(&backend->config, ver_max); #endif - return CURLE_SSL_CONNECT_ERROR; + return CURLE_OK; } +/* TLS_ECJPAKE_WITH_AES_128_CCM_8 (0xC0FF) is marked experimental + in mbedTLS. The number is not reserved by IANA nor is the + cipher suite present in other SSL implementations. Provide + provisional support for specifying the cipher suite here. */ +#ifdef MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8 +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 +static int +mbed_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + if(id == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + msnprintf(buf, buf_size, "%s", "TLS_ECJPAKE_WITH_AES_128_CCM_8"); + else + return Curl_cipher_suite_get_str(id, buf, buf_size, prefer_rfc); + return 0; +} +#endif + +static uint16_t +mbed_cipher_suite_walk_str(const char **str, const char **end) +{ + uint16_t id = Curl_cipher_suite_walk_str(str, end); + size_t len = *end - *str; + + if(!id) { + if(strncasecompare("TLS_ECJPAKE_WITH_AES_128_CCM_8", *str, len)) + id = MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8; + } + return id; +} +#else +#define mbed_cipher_suite_get_str Curl_cipher_suite_get_str +#define mbed_cipher_suite_walk_str Curl_cipher_suite_walk_str +#endif + static CURLcode -set_ssl_version_min_max(struct Curl_cfilter *cf, struct Curl_easy *data) +mbed_set_selected_ciphers(struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + const char *ciphers12, + const char *ciphers13) { - 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); -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_3; - int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_3; + const char *ciphers = ciphers12; + const int *supported; + int *selected; + size_t supported_len, count = 0, default13_count = 0, i, j; + const char *ptr, *end; + + supported = mbedtls_ssl_list_ciphersuites(); + for(i = 0; supported[i] != 0; i++); + supported_len = i; + + selected = malloc(sizeof(int) * (supported_len + 1)); + if(!selected) + return CURLE_OUT_OF_MEMORY; + +#ifndef HAS_TLS13_SUPPORT + (void) ciphers13, (void) j; #else - int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_1; - int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_1; + if(!ciphers13) { + /* Add default TLSv1.3 ciphers to selection */ + for(j = 0; j < supported_len; j++) { + uint16_t id = (uint16_t) supported[j]; + if(strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6) != 0) + continue; + + selected[count++] = id; + } + + default13_count = count; + } + else + ciphers = ciphers13; + +add_ciphers: #endif - long ssl_version = conn_config->version; - long ssl_version_max = conn_config->version_max; - CURLcode result = CURLE_OK; + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = mbed_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len && supported[i] != id; i++); + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "mbedTLS: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } - DEBUGASSERT(backend); + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + if(i >= default13_count) + infof(data, "mbedTLS: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - ssl_version = CURL_SSLVERSION_TLSv1_0; - break; + selected[count++] = id; } - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; - break; +#ifdef HAS_TLS13_SUPPORT + if(ciphers == ciphers13 && ciphers12) { + ciphers = ciphers12; + goto add_ciphers; } - result = mbedtls_version_from_curl(&mbedtls_ver_min, ssl_version); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; + if(!ciphers12) { + /* Add default TLSv1.2 ciphers to selection */ + for(j = 0; j < supported_len; j++) { + uint16_t id = (uint16_t) supported[j]; + if(strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6) == 0) + continue; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) + continue; + + selected[count++] = id; + } } - result = mbedtls_version_from_curl(&mbedtls_ver_max, ssl_version_max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; +#endif + + selected[count] = 0; + + if(count == 0) { + free(selected); + failf(data, "mbedTLS: no supported cipher in list"); + return CURLE_SSL_CIPHER; } - mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - mbedtls_ver_min); - mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - mbedtls_ver_max); + /* mbedtls_ssl_conf_ciphersuites(): The ciphersuites array is not copied. + It must remain valid for the lifetime of the SSL configuration */ + backend->ciphersuites = selected; + mbedtls_ssl_conf_ciphersuites(&backend->config, backend->ciphersuites); + return CURLE_OK; +} - return result; +static void +mbed_dump_cert_info(struct Curl_easy *data, const mbedtls_x509_crt *crt) +{ +#if defined(CURL_DISABLE_VERBOSE_STRINGS) || \ + (MBEDTLS_VERSION_NUMBER >= 0x03000000 && defined(MBEDTLS_X509_REMOVE_INFO)) + (void) data, (void) crt; +#else + const size_t bufsize = 16384; + char *p, *buffer = malloc(bufsize); + + if(buffer && mbedtls_x509_crt_info(buffer, bufsize, " ", crt) > 0) { + infof(data, "Server certificate:"); + for(p = buffer; *p; p += *p != '\0') { + size_t s = strcspn(p, "\n"); + infof(data, "%.*s", (int) s, p); + p += s; + } + } + else + infof(data, "Unable to dump certificate information"); + + free(buffer); +#endif +} + +static void +mbed_extract_certinfo(struct Curl_easy *data, const mbedtls_x509_crt *crt) +{ + CURLcode result; + const mbedtls_x509_crt *cur; + int i; + + for(i = 0, cur = crt; cur; ++i, cur = cur->next); + result = Curl_ssl_init_certinfo(data, i); + + 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); + } +} + +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 Curl_easy *data = CF_DATA_CURRENT(cf); + + if(depth == 0) { + if(data->set.verbose) + mbed_dump_cert_info(data, crt); + if(data->set.ssl.certinfo) + mbed_extract_certinfo(data, crt); + } + + if(!conn_config->verifypeer) + *flags = 0; + else if(!conn_config->verifyhost) + *flags &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; + + if(*flags) { +#if MBEDTLS_VERSION_NUMBER < 0x03000000 || !defined(MBEDTLS_X509_REMOVE_INFO) + char buf[128]; + mbedtls_x509_crt_verify_info(buf, sizeof(buf), "", *flags); + failf(data, "mbedTLS: %s", buf); +#else + failf(data, "mbedTLS: certificate verification error 0x%08x", *flags); +#endif + } + + return 0; } static CURLcode mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + 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); 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); @@ -319,11 +591,12 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *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_crlfile = ssl_config->primary.CRLfile; - const char *hostname = connssl->hostname; + const char *hostname = connssl->peer.hostname; int ret = -1; char errorbuf[128]; DEBUGASSERT(backend); + DEBUGASSERT(!backend->initialized); if((conn_config->version == CURL_SSLVERSION_SSLv2) || (conn_config->version == CURL_SSLVERSION_SSLv3)) { @@ -331,8 +604,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_NOT_BUILT_IN; } -#ifdef THREADING_SUPPORT - entropy_init_mutex(&ts_entropy); +#ifdef HAS_THREADING_SUPPORT mbedtls_ctr_drbg_init(&backend->ctr_drbg); ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, entropy_func_mutex, @@ -355,7 +627,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) -ret, errorbuf); return CURLE_FAILED_INIT; } -#endif /* THREADING_SUPPORT */ +#endif /* HAS_THREADING_SUPPORT */ /* Load the trusted CA */ mbedtls_x509_crt_init(&backend->cacert); @@ -364,15 +636,14 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null terminated even when provided the exact length, forcing us to waste extra memory here. */ - unsigned char *newblob = malloc(ca_info_blob->len + 1); + unsigned char *newblob = Curl_memdup0(ca_info_blob->data, + ca_info_blob->len); if(!newblob) return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ca_info_blob->data, ca_info_blob->len); - newblob[ca_info_blob->len] = 0; /* null terminate */ ret = mbedtls_x509_crt_parse(&backend->cacert, newblob, ca_info_blob->len + 1); free(newblob); - if(ret<0) { + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "Error importing ca cert blob - mbedTLS: (-0x%04X) %s", -ret, errorbuf); @@ -384,7 +655,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef MBEDTLS_FS_IO ret = mbedtls_x509_crt_parse_file(&backend->cacert, ssl_cafile); - if(ret<0) { + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "Error reading ca cert file %s - mbedTLS: (-0x%04X) %s", ssl_cafile, -ret, errorbuf); @@ -400,7 +671,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef MBEDTLS_FS_IO ret = mbedtls_x509_crt_parse_path(&backend->cacert, ssl_capath); - if(ret<0) { + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "Error reading ca cert path %s - mbedTLS: (-0x%04X) %s", ssl_capath, -ret, errorbuf); @@ -438,18 +709,17 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null terminated even when provided the exact length, forcing us to waste extra memory here. */ - unsigned char *newblob = malloc(ssl_cert_blob->len + 1); + unsigned char *newblob = Curl_memdup0(ssl_cert_blob->data, + ssl_cert_blob->len); if(!newblob) return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ssl_cert_blob->data, ssl_cert_blob->len); - newblob[ssl_cert_blob->len] = 0; /* null terminate */ ret = mbedtls_x509_crt_parse(&backend->clicert, newblob, ssl_cert_blob->len + 1); free(newblob); if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s", + failf(data, "Error reading client cert data %s - mbedTLS: (-0x%04X) %s", ssl_config->key, -ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } @@ -470,6 +740,9 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, ssl_config->key_passwd); #endif + if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || + mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) + ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); @@ -498,6 +771,9 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) (const unsigned char *)passwd, passwd ? strlen(passwd) : 0); #endif + if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || + mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) + ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); @@ -506,10 +782,6 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CERTPROBLEM; } } - - if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || - mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) - ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; } /* Load the CRL */ @@ -539,7 +811,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #endif - infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->port); + infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->peer.port); mbedtls_ssl_config_init(&backend->config); ret = mbedtls_ssl_config_defaults(&backend->config, @@ -551,49 +823,66 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; } +#if defined(HAS_SESSION_TICKETS) && MBEDTLS_VERSION_NUMBER >= 0x03060100 + /* New in mbedTLS 3.6.1, need to enable, default is now disabled */ + mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(&backend->config, + MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED); +#endif + + /* Always let mbedTLS verify certificates, if verifypeer or verifyhost are + * disabled we clear the corresponding error flags in the verify callback + * function. That is also where we log verification errors. */ + mbedtls_ssl_conf_verify(&backend->config, mbed_verify_cb, cf); + mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_init(&backend->ssl); - if(mbedtls_ssl_setup(&backend->ssl, &backend->config)) { - failf(data, "mbedTLS: ssl_init failed"); - return CURLE_SSL_CONNECT_ERROR; - } + backend->initialized = TRUE; /* new profile with RSA min key len = 1024 ... */ mbedtls_ssl_conf_cert_profile(&backend->config, &mbedtls_x509_crt_profile_fr); - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if MBEDTLS_VERSION_NUMBER < 0x03000000 - mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_1); - infof(data, "mbedTLS: Set min SSL version to TLS 1.0"); - break; -#endif - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(cf, data); - if(result != CURLE_OK) - return result; - break; - } - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL); + ret = mbed_set_ssl_version_min_max(data, backend, conn_config); + if(ret != CURLE_OK) + return ret; mbedtls_ssl_conf_rng(&backend->config, mbedtls_ctr_drbg_random, &backend->ctr_drbg); - mbedtls_ssl_set_bio(&backend->ssl, cf, bio_cf_write, bio_cf_read, + + ret = mbedtls_ssl_setup(&backend->ssl, &backend->config); + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_setup failed - mbedTLS: (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CONNECT_ERROR; + } + + mbedtls_ssl_set_bio(&backend->ssl, cf, + mbedtls_bio_cf_write, + mbedtls_bio_cf_read, NULL /* rev_timeout() */); - mbedtls_ssl_conf_ciphersuites(&backend->config, - mbedtls_ssl_list_ciphersuites()); +#ifndef HAS_TLS13_SUPPORT + if(conn_config->cipher_list) { + CURLcode result = mbed_set_selected_ciphers(data, backend, + conn_config->cipher_list, + NULL); +#else + if(conn_config->cipher_list || conn_config->cipher_list13) { + CURLcode result = mbed_set_selected_ciphers(data, backend, + conn_config->cipher_list, + conn_config->cipher_list13); +#endif + if(result != CURLE_OK) { + failf(data, "mbedTLS: failed to set cipher suites"); + return result; + } + } + else { + mbedtls_ssl_conf_ciphersuites(&backend->config, + mbedtls_ssl_list_ciphersuites()); + } + #if defined(MBEDTLS_SSL_RENEGOTIATION) mbedtls_ssl_conf_renegotiation(&backend->config, @@ -605,21 +894,32 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) MBEDTLS_SSL_SESSION_TICKETS_DISABLED); #endif - /* Check if there's a cached ID we can/should use here! */ - if(ssl_config->primary.sessionid) { - void *old_session = NULL; + /* Check if there is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { + struct Curl_ssl_session *sc_session = NULL; + CURLcode result; + + result = Curl_ssl_scache_take(cf, data, connssl->peer.scache_key, + &sc_session); + if(!result && sc_session && sc_session->sdata && sc_session->sdata_len) { + mbedtls_ssl_session session; - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &old_session, NULL)) { - ret = mbedtls_ssl_set_session(&backend->ssl, old_session); + mbedtls_ssl_session_init(&session); + ret = mbedtls_ssl_session_load(&session, sc_session->sdata, + sc_session->sdata_len); if(ret) { - Curl_ssl_sessionid_unlock(data); - failf(data, "mbedtls_ssl_set_session returned -0x%x", -ret); - return CURLE_SSL_CONNECT_ERROR; + failf(data, "SSL session error loading: -0x%x", -ret); + } + else { + ret = mbedtls_ssl_set_session(&backend->ssl, &session); + if(ret) + failf(data, "SSL session error setting: -0x%x", -ret); + else + infof(data, "SSL reusing session ID"); } - infof(data, "mbedTLS re-using session"); + mbedtls_ssl_session_free(&session); } - Curl_ssl_sessionid_unlock(data); + Curl_ssl_scache_return(cf, data, connssl->peer.scache_key, sc_session); } mbedtls_ssl_conf_ca_chain(&backend->config, @@ -634,18 +934,17 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) mbedtls_ssl_conf_own_cert(&backend->config, &backend->clicert, &backend->pk); } - { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || mbedtls_ssl_set_hostname(&backend->ssl, snihost)) { - /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and - the name to set in the SNI extension. So even if curl connects to a - host specified as an IP address, this function must be used. */ - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } + + if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname)) { + /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and + the name to set in the SNI extension. So even if curl connects to a + host specified as an IP address, this function must be used. */ + failf(data, "Failed to set SNI"); + return CURLE_SSL_CONNECT_ERROR; } -#ifdef HAS_ALPN +#ifdef HAS_ALPN_MBEDTLS if(connssl->alpn) { struct alpn_proto_buf proto; size_t i; @@ -653,7 +952,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) for(i = 0; i < connssl->alpn->count; ++i) { backend->protocols[i] = connssl->alpn->entries[i]; } - /* this function doesn't clone the protocols array, which is why we need + /* this function does not clone the protocols array, which is why we need to keep it around */ if(mbedtls_ssl_conf_alpn_protocols(&backend->config, &backend->protocols[0])) { @@ -679,11 +978,11 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* give application a chance to interfere with mbedTLS set up. */ if(data->set.ssl.fsslctx) { - ret = (*data->set.ssl.fsslctx)(data, &backend->config, - data->set.ssl.fsslctxp); - if(ret) { + CURLcode result = (*data->set.ssl.fsslctx)(data, &backend->config, + data->set.ssl.fsslctxp); + if(result != CURLE_OK) { failf(data, "error signaled by ssl ctx callback"); - return ret; + return result; } } @@ -697,84 +996,67 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { int ret; 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); - const mbedtls_x509_crt *peercert; - const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; +#ifndef CURL_DISABLE_PROXY + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif DEBUGASSERT(backend); ret = mbedtls_ssl_handshake(&backend->ssl); if(ret == MBEDTLS_ERR_SSL_WANT_READ) { - connssl->connecting_state = ssl_connect_2_reading; + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - connssl->connecting_state = ssl_connect_2_writing; + connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_OK; } + else if(ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { + failf(data, "peer certificate could not be verified"); + return CURLE_PEER_FAILED_VERIFICATION; + } else if(ret) { char errorbuf[128]; +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + CURL_TRC_CF(data, cf, "TLS version %04X", + mbedtls_ssl_get_version_number(&backend->ssl)); +#endif mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "ssl_handshake returned - mbedTLS: (-0x%04X) %s", + failf(data, "ssl_handshake returned: (-0x%04X) %s", -ret, errorbuf); return CURLE_SSL_CONNECT_ERROR; } - infof(data, "mbedTLS: Handshake complete, cipher is %s", - mbedtls_ssl_get_ciphersuite(&backend->ssl)); - - ret = mbedtls_ssl_get_verify_result(&backend->ssl); - - if(!conn_config->verifyhost) - /* Ignore hostname errors if verifyhost is disabled */ - ret &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; - - if(ret && conn_config->verifypeer) { - if(ret & MBEDTLS_X509_BADCERT_EXPIRED) - failf(data, "Cert verify failed: BADCERT_EXPIRED"); - - else if(ret & MBEDTLS_X509_BADCERT_REVOKED) - failf(data, "Cert verify failed: BADCERT_REVOKED"); - - else if(ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) - failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); - - else if(ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED) - failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); - - else if(ret & MBEDTLS_X509_BADCERT_FUTURE) - failf(data, "Cert verify failed: BADCERT_FUTURE"); - - return CURLE_PEER_FAILED_VERIFICATION; - } - - peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); - - if(peercert && data->set.verbose) { - const size_t bufsize = 16384; - char *buffer = malloc(bufsize); - - if(!buffer) - return CURLE_OUT_OF_MEMORY; - - if(mbedtls_x509_crt_info(buffer, bufsize, "* ", peercert) > 0) - infof(data, "Dumping cert info: %s", buffer); - else - infof(data, "Unable to dump certificate information"); - - free(buffer); +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + { + char cipher_str[64]; + uint16_t cipher_id; + cipher_id = (uint16_t) + mbedtls_ssl_get_ciphersuite_id_from_ssl(&backend->ssl); + mbed_cipher_suite_get_str(cipher_id, cipher_str, sizeof(cipher_str), TRUE); + infof(data, "mbedTLS: %s Handshake complete, cipher is %s", + mbedtls_ssl_get_version(&backend->ssl), cipher_str); } +#else + infof(data, "mbedTLS: %s Handshake complete", + mbedtls_ssl_get_version(&backend->ssl)); +#endif if(pinnedpubkey) { int size; CURLcode result; + const mbedtls_x509_crt *peercert; mbedtls_x509_crt *p = NULL; unsigned char *pubkey = NULL; + peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); #if MBEDTLS_VERSION_NUMBER == 0x03000000 if(!peercert || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p) || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len)) { @@ -801,7 +1083,7 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der needs a non-const key, for now. - https://github.com/ARMmbed/mbedtls/issues/396 */ + https://github.com/Mbed-TLS/mbedtls/issues/396 */ #if MBEDTLS_VERSION_NUMBER == 0x03000000 if(mbedtls_x509_crt_parse_der(p, peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p), @@ -840,12 +1122,12 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) } } -#ifdef HAS_ALPN +#ifdef HAS_ALPN_MBEDTLS if(connssl->alpn) { const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl); - Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, - proto? strlen(proto) : 0); + Curl_alpn_set_negotiated(cf, data, connssl, (const unsigned char *)proto, + proto ? strlen(proto) : 0); } #endif @@ -856,58 +1138,71 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) } static CURLcode -mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) +mbed_new_session(struct Curl_cfilter *cf, struct Curl_easy *data) { - CURLcode retcode = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + 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); + mbedtls_ssl_session session; + bool msession_alloced = FALSE; + struct Curl_ssl_session *sc_session = NULL; + unsigned char *sdata = NULL; + size_t slen = 0; + int ietf_tls_id; + CURLcode result = CURLE_OK; + int ret; - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); DEBUGASSERT(backend); + if(!ssl_config->primary.cache_session) + return CURLE_OK; - if(ssl_config->primary.sessionid) { - int ret; - mbedtls_ssl_session *our_ssl_sessionid; - void *old_ssl_sessionid = NULL; - bool added = FALSE; - - our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session)); - if(!our_ssl_sessionid) - return CURLE_OUT_OF_MEMORY; - - mbedtls_ssl_session_init(our_ssl_sessionid); + mbedtls_ssl_session_init(&session); + ret = mbedtls_ssl_get_session(&backend->ssl, &session); + msession_alloced = (ret != MBEDTLS_ERR_SSL_ALLOC_FAILED); + if(ret) { + failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } - ret = mbedtls_ssl_get_session(&backend->ssl, our_ssl_sessionid); - if(ret) { - if(ret != MBEDTLS_ERR_SSL_ALLOC_FAILED) - mbedtls_ssl_session_free(our_ssl_sessionid); - free(our_ssl_sessionid); - failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret); - return CURLE_SSL_CONNECT_ERROR; - } + mbedtls_ssl_session_save(&session, NULL, 0, &slen); + if(!slen) { + failf(data, "failed to serialize session: length is 0"); + goto out; + } - /* If there's already a matching session in the cache, delete it */ - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)) - Curl_ssl_delsessionid(data, old_ssl_sessionid); - - retcode = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, - 0, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) { - mbedtls_ssl_session_free(our_ssl_sessionid); - free(our_ssl_sessionid); - } - if(retcode) { - failf(data, "failed to store ssl session"); - return retcode; - } + sdata = malloc(slen); + if(!sdata) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - connssl->connecting_state = ssl_connect_done; + ret = mbedtls_ssl_session_save(&session, sdata, slen, &slen); + if(ret) { + failf(data, "failed to serialize session: -0x%x", -ret); + goto out; + } - return CURLE_OK; +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + ietf_tls_id = mbedtls_ssl_get_version_number(&backend->ssl); +#else + ietf_tls_id = CURL_IETF_PROTO_UNKNOWN; +#endif + result = Curl_ssl_session_create(sdata, slen, + ietf_tls_id, + connssl->negotiated.alpn, 0, 0, + &sc_session); + sdata = NULL; /* call took ownership */ + if(!result) + result = Curl_ssl_scache_put(cf, data, connssl->peer.scache_key, + sc_session); + +out: + if(msession_alloced) + mbedtls_ssl_session_free(&session); + free(sdata); + return result; } static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -915,52 +1210,161 @@ static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; int ret = -1; (void)data; DEBUGASSERT(backend); - ret = mbedtls_ssl_write(&backend->ssl, (unsigned char *)mem, len); + /* mbedtls is picky when a mbedtls_ssl_write) was previously blocked. + * It requires to be called with the same amount of bytes again, or it + * will lose bytes, e.g. reporting all was sent but they were not. + * Remember the blocked length and use that when set. */ + if(backend->send_blocked) { + DEBUGASSERT(backend->send_blocked_len <= len); + 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; + } + + ret = mbedtls_ssl_write(&backend->ssl, (const unsigned char *)mem, len); if(ret < 0) { - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_WRITE) ? - CURLE_AGAIN : CURLE_SEND_ERROR; + CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> -0x%04X", + len, -ret); + *curlcode = ((ret == MBEDTLS_ERR_SSL_WANT_WRITE) +#ifdef HAS_TLS13_SUPPORT + || (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) +#endif + ) ? CURLE_AGAIN : CURLE_SEND_ERROR; ret = -1; + if((*curlcode == CURLE_AGAIN) && !backend->send_blocked) { + backend->send_blocked = TRUE; + backend->send_blocked_len = len; + } + } + else { + CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> %d", len, ret); + backend->send_blocked = FALSE; } return ret; } -static void mbedtls_close_all(struct Curl_easy *data) +static CURLcode mbedtls_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { - (void)data; + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + unsigned char buf[1024]; + CURLcode result = CURLE_OK; + int ret; + size_t i; + + DEBUGASSERT(backend); + + if(!backend->initialized || cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->sent_shutdown) { + /* do this only once */ + backend->sent_shutdown = TRUE; + if(send_shutdown) { + ret = mbedtls_ssl_close_notify(&backend->ssl); + switch(ret) { + case 0: /* we sent it, receive from the server */ + break; + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: /* server also closed */ + *done = TRUE; + goto out; + case MBEDTLS_ERR_SSL_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + goto out; + case MBEDTLS_ERR_SSL_WANT_WRITE: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + default: + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + result = CURLE_RECV_ERROR; + goto out; + } + } + } + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + for(i = 0; i < 10; ++i) { + ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf)); + /* This seems to be a bug in mbedTLS TLSv1.3 where it reports + * WANT_READ, but has not encountered an EAGAIN. */ + if(ret == MBEDTLS_ERR_SSL_WANT_READ) + ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf)); +#ifdef HAS_TLS13_SUPPORT + if(ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) + continue; +#endif + if(ret <= 0) + break; + } + + if(ret > 0) { + /* still data coming in? */ + CURL_TRC_CF(data, cf, "mbedtls_shutdown, still getting data"); + } + else if(ret == 0 || (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) { + /* We got the close notify alert and are done. */ + CURL_TRC_CF(data, cf, "mbedtls_shutdown done"); + *done = TRUE; + } + else if(ret == MBEDTLS_ERR_SSL_WANT_READ) { + CURL_TRC_CF(data, cf, "mbedtls_shutdown, need RECV"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + CURL_TRC_CF(data, cf, "mbedtls_shutdown, need SEND"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + } + else { + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + result = CURLE_RECV_ERROR; + } + +out: + cf->shutdown = (result || *done); + return result; } static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - char buf[32]; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; (void)data; DEBUGASSERT(backend); - - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, sizeof(buf)); - - mbedtls_pk_free(&backend->pk); - mbedtls_x509_crt_free(&backend->clicert); - mbedtls_x509_crt_free(&backend->cacert); + if(backend->initialized) { + mbedtls_pk_free(&backend->pk); + mbedtls_x509_crt_free(&backend->clicert); + mbedtls_x509_crt_free(&backend->cacert); #ifdef MBEDTLS_X509_CRL_PARSE_C - mbedtls_x509_crl_free(&backend->crl); + mbedtls_x509_crl_free(&backend->crl); #endif - mbedtls_ssl_config_free(&backend->config); - mbedtls_ssl_free(&backend->ssl); - mbedtls_ctr_drbg_free(&backend->ctr_drbg); -#ifndef THREADING_SUPPORT - mbedtls_entropy_free(&backend->entropy); -#endif /* THREADING_SUPPORT */ + Curl_safefree(backend->ciphersuites); + mbedtls_ssl_config_free(&backend->config); + mbedtls_ssl_free(&backend->ssl); + mbedtls_ctr_drbg_free(&backend->ctr_drbg); +#ifndef HAS_THREADING_SUPPORT + mbedtls_entropy_free(&backend->entropy); +#endif /* HAS_THREADING_SUPPORT */ + backend->initialized = FALSE; + } } static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -968,34 +1372,43 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; int ret = -1; - ssize_t len = -1; (void)data; DEBUGASSERT(backend); ret = mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, buffersize); - if(ret <= 0) { - if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) - return 0; - - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_READ) ? - CURLE_AGAIN : CURLE_RECV_ERROR; - return -1; + CURL_TRC_CF(data, cf, "mbedtls_ssl_read(len=%zu) -> -0x%04X", + buffersize, -ret); + switch(ret) { +#ifdef HAS_SESSION_TICKETS + case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: + mbed_new_session(cf, data); + FALLTHROUGH(); +#endif + case MBEDTLS_ERR_SSL_WANT_READ: + *curlcode = CURLE_AGAIN; + ret = -1; + break; + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + *curlcode = CURLE_OK; + ret = 0; + break; + default: { + char errorbuf[128]; + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_read returned: (-0x%04X) %s", -ret, errorbuf); + *curlcode = CURLE_RECV_ERROR; + ret = -1; + break; + } + } } - - len = ret; - - return len; -} - -static void mbedtls_session_free(void *ptr) -{ - mbedtls_ssl_session_free(ptr); - free(ptr); + return (ssize_t)ret; } static size_t mbedtls_version(char *buffer, size_t size) @@ -1003,42 +1416,31 @@ static size_t mbedtls_version(char *buffer, size_t size) #ifdef MBEDTLS_VERSION_C /* if mbedtls_version_get_number() is available it is better */ unsigned int version = mbedtls_version_get_number(); - return msnprintf(buffer, size, "mbedTLS/%u.%u.%u", version>>24, - (version>>16)&0xff, (version>>8)&0xff); + return msnprintf(buffer, size, "mbedTLS/%u.%u.%u", version >> 24, + (version >> 16) & 0xff, (version >> 8) & 0xff); #else return msnprintf(buffer, size, "mbedTLS/%s", MBEDTLS_VERSION_STRING); #endif } +/* 'data' might be NULL */ static CURLcode mbedtls_random(struct Curl_easy *data, unsigned char *entropy, size_t length) { #if defined(MBEDTLS_CTR_DRBG_C) - int ret = -1; - char errorbuf[128]; + int ret; mbedtls_entropy_context ctr_entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_entropy_init(&ctr_entropy); mbedtls_ctr_drbg_init(&ctr_drbg); + (void)data; ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &ctr_entropy, NULL, 0); - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", - -ret, errorbuf); - } - else { + if(!ret) ret = mbedtls_ctr_drbg_random(&ctr_drbg, entropy, length); - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_random returned (-0x%04X) %s", - -ret, errorbuf); - } - } - mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_entropy_free(&ctr_entropy); @@ -1054,16 +1456,12 @@ static CURLcode mbedtls_random(struct Curl_easy *data, #endif } -static CURLcode -mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode mbedtls_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { CURLcode retcode; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - timediff_t timeout_ms; - int what; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -1071,117 +1469,44 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OK; } - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + if(ssl_connect_1 == connssl->connecting_state) { retcode = mbed_connect_step1(cf, data); if(retcode) return retcode; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking ? 0 : timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ + if(ssl_connect_2 == connssl->connecting_state) { retcode = mbed_connect_step2(cf, data); - if(retcode || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(retcode) return retcode; - - } /* repeat step2 until all transactions are done. */ + } if(ssl_connect_3 == connssl->connecting_state) { - retcode = mbed_connect_step3(cf, data); - if(retcode) - return retcode; + /* For tls1.3 we get notified about new sessions */ +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + struct ssl_connect_data *ctx = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)ctx->backend; + + if(mbedtls_ssl_get_version_number(&backend->ssl) <= + MBEDTLS_SSL_VERSION_TLS1_2) { +#else + { /* no TLSv1.3 supported here */ +#endif + retcode = mbed_new_session(cf, data); + if(retcode) + return retcode; + } + connssl->connecting_state = ssl_connect_done; } if(ssl_connect_done == connssl->connecting_state) { connssl->state = ssl_connection_complete; *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode mbedtls_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return mbed_connect_common(cf, data, TRUE, done); -} - - -static CURLcode mbedtls_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode retcode; - bool done = FALSE; - - retcode = mbed_connect_common(cf, data, FALSE, &done); - if(retcode) - return retcode; - - DEBUGASSERT(done); return CURLE_OK; } @@ -1192,11 +1517,33 @@ static CURLcode mbedtls_connect(struct Curl_cfilter *cf, */ static int mbedtls_init(void) { - return Curl_mbedtlsthreadlock_thread_setup(); + if(!Curl_mbedtlsthreadlock_thread_setup()) + return 0; +#ifdef HAS_THREADING_SUPPORT + entropy_init_mutex(&ts_entropy); +#endif +#ifdef HAS_PSA_SUPPORT + { + int ret; +#ifdef HAS_THREADING_SUPPORT + Curl_mbedtlsthreadlock_lock_function(0); +#endif + ret = psa_crypto_init(); +#ifdef HAS_THREADING_SUPPORT + Curl_mbedtlsthreadlock_unlock_function(0); +#endif + if(ret != PSA_SUCCESS) + return 0; + } +#endif /* HAS_PSA_SUPPORT */ + return 1; } static void mbedtls_cleanup(void) { +#ifdef HAS_THREADING_SUPPORT + entropy_cleanup_mutex(&ts_entropy); +#endif (void)Curl_mbedtlsthreadlock_thread_cleanup(); } @@ -1204,10 +1551,12 @@ static bool mbedtls_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct ssl_connect_data *ctx = cf->ctx; + struct mbed_ssl_backend_data *backend; (void)data; DEBUGASSERT(ctx && ctx->backend); - return mbedtls_ssl_get_bytes_avail(&ctx->backend->ssl) != 0; + backend = (struct mbed_ssl_backend_data *)ctx->backend; + return mbedtls_ssl_get_bytes_avail(&backend->ssl) != 0; } static CURLcode mbedtls_sha256sum(const unsigned char *input, @@ -1215,7 +1564,6 @@ static CURLcode mbedtls_sha256sum(const unsigned char *input, unsigned char *sha256sum, size_t sha256len UNUSED_PARAM) { - /* TODO: explain this for different mbedtls 2.x vs 3 version */ (void)sha256len; #if MBEDTLS_VERSION_NUMBER < 0x02070000 mbedtls_sha256(input, inputlen, sha256sum, 0); @@ -1234,7 +1582,8 @@ static CURLcode mbedtls_sha256sum(const unsigned char *input, static void *mbedtls_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) { - struct ssl_backend_data *backend = connssl->backend; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; (void)info; DEBUGASSERT(backend); return &backend->ssl; @@ -1245,37 +1594,37 @@ const struct Curl_ssl Curl_ssl_mbedtls = { SSLSUPP_CA_PATH | SSLSUPP_CAINFO_BLOB | + SSLSUPP_CERTINFO | SSLSUPP_PINNEDPUBKEY | SSLSUPP_SSL_CTX | - SSLSUPP_HTTPS_PROXY, +#ifdef HAS_TLS13_SUPPORT + SSLSUPP_TLS13_CIPHERSUITES | +#endif + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, - sizeof(struct ssl_backend_data), + sizeof(struct mbed_ssl_backend_data), mbedtls_init, /* init */ mbedtls_cleanup, /* cleanup */ mbedtls_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ + mbedtls_shutdown, /* shutdown */ mbedtls_data_pending, /* data_pending */ mbedtls_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ + NULL, /* cert_status_request */ mbedtls_connect, /* connect */ - mbedtls_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ mbedtls_get_internals, /* get_internals */ mbedtls_close, /* close_one */ - mbedtls_close_all, /* close_all */ - mbedtls_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ + NULL, /* false_start */ mbedtls_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ mbed_recv, /* recv decrypted data */ mbed_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_MBEDTLS */ diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.h b/Utilities/cmcurl/lib/vtls/mbedtls.h index d8a0a06eb6a..3876fe36fc5 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.h +++ b/Utilities/cmcurl/lib/vtls/mbedtls.h @@ -24,7 +24,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MBEDTLS diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c index bcb7106a636..682c221852c 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c @@ -22,24 +22,24 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_MBEDTLS) && \ ((defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ - defined(USE_THREADS_WIN32)) + defined(_WIN32)) #if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) # include # define MBEDTLS_MUTEX_T pthread_mutex_t -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) # define MBEDTLS_MUTEX_T HANDLE #endif #include "mbedtls_threadlock.h" -#include "curl_printf.h" -#include "curl_memory.h" +#include "../curl_printf.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" +#include "../memdebug.h" /* number of thread locks */ #define NUMT 2 @@ -51,7 +51,7 @@ int Curl_mbedtlsthreadlock_thread_setup(void) { int i; - mutex_buf = calloc(NUMT * sizeof(MBEDTLS_MUTEX_T), 1); + mutex_buf = calloc(1, NUMT * sizeof(MBEDTLS_MUTEX_T)); if(!mutex_buf) return 0; /* error, no number of threads defined */ @@ -59,7 +59,7 @@ int Curl_mbedtlsthreadlock_thread_setup(void) #if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) if(pthread_mutex_init(&mutex_buf[i], NULL)) return 0; /* pthread_mutex_init failed */ -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) mutex_buf[i] = CreateMutex(0, FALSE, 0); if(mutex_buf[i] == 0) return 0; /* CreateMutex failed */ @@ -80,7 +80,7 @@ int Curl_mbedtlsthreadlock_thread_cleanup(void) #if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) if(pthread_mutex_destroy(&mutex_buf[i])) return 0; /* pthread_mutex_destroy failed */ -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) if(!CloseHandle(mutex_buf[i])) return 0; /* CloseHandle failed */ #endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ @@ -100,7 +100,7 @@ int Curl_mbedtlsthreadlock_lock_function(int n) "Error: mbedtlsthreadlock_lock_function failed\n")); return 0; /* pthread_mutex_lock failed */ } -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) if(WaitForSingleObject(mutex_buf[n], INFINITE) == WAIT_FAILED) { DEBUGF(fprintf(stderr, "Error: mbedtlsthreadlock_lock_function failed\n")); @@ -120,7 +120,7 @@ int Curl_mbedtlsthreadlock_unlock_function(int n) "Error: mbedtlsthreadlock_unlock_function failed\n")); return 0; /* pthread_mutex_unlock failed */ } -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) if(!ReleaseMutex(mutex_buf[n])) { DEBUGF(fprintf(stderr, "Error: mbedtlsthreadlock_unlock_function failed\n")); diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h index 2b0bd41c8b9..9402af6e415 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h @@ -24,12 +24,12 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MBEDTLS #if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ - defined(USE_THREADS_WIN32) + defined(_WIN32) int Curl_mbedtlsthreadlock_thread_setup(void); int Curl_mbedtlsthreadlock_thread_cleanup(void); @@ -43,7 +43,7 @@ int Curl_mbedtlsthreadlock_unlock_function(int n); #define Curl_mbedtlsthreadlock_lock_function(x) 1 #define Curl_mbedtlsthreadlock_unlock_function(x) 1 -#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */ +#endif /* (USE_THREADS_POSIX && HAVE_PTHREAD_H) || _WIN32 */ #endif /* USE_MBEDTLS */ diff --git a/Utilities/cmcurl/lib/vtls/nss.c b/Utilities/cmcurl/lib/vtls/nss.c deleted file mode 100644 index 5e5dbb7448b..00000000000 --- a/Utilities/cmcurl/lib/vtls/nss.c +++ /dev/null @@ -1,2522 +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 - * - ***************************************************************************/ - -/* - * Source file for all NSS-specific code for the TLS/SSL layer. No code - * but vtls.c should ever call or use these functions. - */ - -#include "curl_setup.h" - -#ifdef USE_NSS - -#include "urldata.h" -#include "sendf.h" -#include "formdata.h" /* for the boundary function */ -#include "url.h" /* for the ssl config check function */ -#include "connect.h" -#include "strcase.h" -#include "select.h" -#include "vtls.h" -#include "vtls_int.h" -#include "llist.h" -#include "multiif.h" -#include "curl_printf.h" -#include "nssg.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include /* for SECKEY_DestroyPublicKey() */ -#include /* for PR_ImportTCPSocket */ - -#define NSSVERNUM ((NSS_VMAJOR<<16)|(NSS_VMINOR<<8)|NSS_VPATCH) - -#if NSSVERNUM >= 0x030f00 /* 3.15.0 */ -#include -#endif - -#include "warnless.h" -#include "x509asn1.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#define SSL_DIR "/etc/pki/nssdb" - -/* enough to fit the string "PEM Token #[0|1]" */ -#define SLOTSIZE 13 - -struct ssl_backend_data { - PRFileDesc *handle; - char *client_nickname; - struct Curl_easy *data; - struct Curl_llist obj_list; - PK11GenericObject *obj_clicert; -}; - -static PRLock *nss_initlock = NULL; -static PRLock *nss_crllock = NULL; -static PRLock *nss_findslot_lock = NULL; -static PRLock *nss_trustload_lock = NULL; -static struct Curl_llist nss_crl_list; -static NSSInitContext *nss_context = NULL; -static volatile int initialized = 0; - -/* type used to wrap pointers as list nodes */ -struct ptr_list_wrap { - void *ptr; - struct Curl_llist_element node; -}; - -struct cipher_s { - const char *name; - int num; -}; - -#define PK11_SETATTRS(_attr, _idx, _type, _val, _len) do { \ - CK_ATTRIBUTE *ptr = (_attr) + ((_idx)++); \ - ptr->type = (_type); \ - ptr->pValue = (_val); \ - ptr->ulValueLen = (_len); \ -} while(0) - -#define CERT_NewTempCertificate __CERT_NewTempCertificate - -#define NUM_OF_CIPHERS sizeof(cipherlist)/sizeof(cipherlist[0]) -static const struct cipher_s cipherlist[] = { - /* SSL2 cipher suites */ - {"rc4", SSL_EN_RC4_128_WITH_MD5}, - {"rc4-md5", SSL_EN_RC4_128_WITH_MD5}, - {"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5}, - {"rc2", SSL_EN_RC2_128_CBC_WITH_MD5}, - {"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5}, - {"des", SSL_EN_DES_64_CBC_WITH_MD5}, - {"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5}, - /* SSL3/TLS cipher suites */ - {"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5}, - {"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA}, - {"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA}, - {"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA}, - {"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5}, - {"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5}, - {"rsa_null_md5", SSL_RSA_WITH_NULL_MD5}, - {"rsa_null_sha", SSL_RSA_WITH_NULL_SHA}, - {"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA}, - {"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA}, - {"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA}, - {"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA}, - {"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA}, - {"dhe_rsa_3des_sha", SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA}, - {"dhe_dss_3des_sha", SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA}, - {"dhe_rsa_des_sha", SSL_DHE_RSA_WITH_DES_CBC_SHA}, - {"dhe_dss_des_sha", SSL_DHE_DSS_WITH_DES_CBC_SHA}, - /* TLS 1.0: Exportable 56-bit Cipher Suites. */ - {"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA}, - {"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA}, - /* Ephemeral DH with RC4 bulk encryption */ - {"dhe_dss_rc4_128_sha", TLS_DHE_DSS_WITH_RC4_128_SHA}, - /* AES ciphers. */ - {"dhe_dss_aes_128_cbc_sha", TLS_DHE_DSS_WITH_AES_128_CBC_SHA}, - {"dhe_dss_aes_256_cbc_sha", TLS_DHE_DSS_WITH_AES_256_CBC_SHA}, - {"dhe_rsa_aes_128_cbc_sha", TLS_DHE_RSA_WITH_AES_128_CBC_SHA}, - {"dhe_rsa_aes_256_cbc_sha", TLS_DHE_RSA_WITH_AES_256_CBC_SHA}, - {"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA}, - {"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA}, - /* ECC ciphers. */ - {"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA}, - {"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA}, - {"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA}, - {"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}, - {"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA}, - {"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA}, - {"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA}, - {"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA}, - {"ecdhe_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA}, - {"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA}, - {"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, - {"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA}, - {"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA}, - {"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA}, - {"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA}, -#ifdef TLS_RSA_WITH_NULL_SHA256 - /* new HMAC-SHA256 cipher suites specified in RFC */ - {"rsa_null_sha_256", TLS_RSA_WITH_NULL_SHA256}, - {"rsa_aes_128_cbc_sha_256", TLS_RSA_WITH_AES_128_CBC_SHA256}, - {"rsa_aes_256_cbc_sha_256", TLS_RSA_WITH_AES_256_CBC_SHA256}, - {"dhe_rsa_aes_128_cbc_sha_256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256}, - {"dhe_rsa_aes_256_cbc_sha_256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256}, - {"ecdhe_ecdsa_aes_128_cbc_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, - {"ecdhe_rsa_aes_128_cbc_sha_256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256}, -#endif -#ifdef TLS_RSA_WITH_AES_128_GCM_SHA256 - /* AES GCM cipher suites in RFC 5288 and RFC 5289 */ - {"rsa_aes_128_gcm_sha_256", TLS_RSA_WITH_AES_128_GCM_SHA256}, - {"dhe_rsa_aes_128_gcm_sha_256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, - {"dhe_dss_aes_128_gcm_sha_256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256}, - {"ecdhe_ecdsa_aes_128_gcm_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - {"ecdh_ecdsa_aes_128_gcm_sha_256", TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256}, - {"ecdhe_rsa_aes_128_gcm_sha_256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - {"ecdh_rsa_aes_128_gcm_sha_256", TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256}, -#endif -#ifdef TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - /* cipher suites using SHA384 */ - {"rsa_aes_256_gcm_sha_384", TLS_RSA_WITH_AES_256_GCM_SHA384}, - {"dhe_rsa_aes_256_gcm_sha_384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384}, - {"dhe_dss_aes_256_gcm_sha_384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384}, - {"ecdhe_ecdsa_aes_256_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384}, - {"ecdhe_rsa_aes_256_sha_384", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384}, - {"ecdhe_ecdsa_aes_256_gcm_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, - {"ecdhe_rsa_aes_256_gcm_sha_384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, -#endif -#ifdef TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - /* chacha20-poly1305 cipher suites */ - {"ecdhe_rsa_chacha20_poly1305_sha_256", - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, - {"ecdhe_ecdsa_chacha20_poly1305_sha_256", - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256}, - {"dhe_rsa_chacha20_poly1305_sha_256", - TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, -#endif -#ifdef TLS_AES_256_GCM_SHA384 - {"aes_128_gcm_sha_256", TLS_AES_128_GCM_SHA256}, - {"aes_256_gcm_sha_384", TLS_AES_256_GCM_SHA384}, - {"chacha20_poly1305_sha_256", TLS_CHACHA20_POLY1305_SHA256}, -#endif -#ifdef TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 - /* AES CBC cipher suites in RFC 5246. Introduced in NSS release 3.20 */ - {"dhe_dss_aes_128_sha_256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256}, - {"dhe_dss_aes_256_sha_256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256}, -#endif -#ifdef TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA - /* Camellia cipher suites in RFC 4132/5932. - Introduced in NSS release 3.12 */ - {"dhe_rsa_camellia_128_sha", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA}, - {"dhe_dss_camellia_128_sha", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA}, - {"dhe_rsa_camellia_256_sha", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA}, - {"dhe_dss_camellia_256_sha", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA}, - {"rsa_camellia_128_sha", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA}, - {"rsa_camellia_256_sha", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA}, -#endif -#ifdef TLS_RSA_WITH_SEED_CBC_SHA - /* SEED cipher suite in RFC 4162. Introduced in NSS release 3.12.3 */ - {"rsa_seed_sha", TLS_RSA_WITH_SEED_CBC_SHA}, -#endif -}; - -#if defined(WIN32) -static const char *pem_library = "nsspem.dll"; -static const char *trust_library = "nssckbi.dll"; -#elif defined(__APPLE__) -static const char *pem_library = "libnsspem.dylib"; -static const char *trust_library = "libnssckbi.dylib"; -#else -static const char *pem_library = "libnsspem.so"; -static const char *trust_library = "libnssckbi.so"; -#endif - -static SECMODModule *pem_module = NULL; -static SECMODModule *trust_module = NULL; - -/* NSPR I/O layer we use to detect blocking direction during SSL handshake */ -static PRDescIdentity nspr_io_identity = PR_INVALID_IO_LAYER; -static PRIOMethods nspr_io_methods; - -static const char *nss_error_to_name(PRErrorCode code) -{ - const char *name = PR_ErrorToName(code); - if(name) - return name; - - return "unknown error"; -} - -static void nss_print_error_message(struct Curl_easy *data, PRUint32 err) -{ - failf(data, "%s", PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT)); -} - -static char *nss_sslver_to_name(PRUint16 nssver) -{ - switch(nssver) { - case SSL_LIBRARY_VERSION_2: - return strdup("SSLv2"); - case SSL_LIBRARY_VERSION_3_0: - return strdup("SSLv3"); - case SSL_LIBRARY_VERSION_TLS_1_0: - return strdup("TLSv1.0"); -#ifdef SSL_LIBRARY_VERSION_TLS_1_1 - case SSL_LIBRARY_VERSION_TLS_1_1: - return strdup("TLSv1.1"); -#endif -#ifdef SSL_LIBRARY_VERSION_TLS_1_2 - case SSL_LIBRARY_VERSION_TLS_1_2: - return strdup("TLSv1.2"); -#endif -#ifdef SSL_LIBRARY_VERSION_TLS_1_3 - case SSL_LIBRARY_VERSION_TLS_1_3: - return strdup("TLSv1.3"); -#endif - default: - return curl_maprintf("0x%04x", nssver); - } -} - -/* the longest cipher name this supports */ -#define MAX_CIPHER_LENGTH 128 - -static SECStatus set_ciphers(struct Curl_easy *data, PRFileDesc *model, - const char *cipher_list) -{ - unsigned int i; - const char *cipher; - - /* use accessors to avoid dynamic linking issues after an update of NSS */ - const PRUint16 num_implemented_ciphers = SSL_GetNumImplementedCiphers(); - const PRUint16 *implemented_ciphers = SSL_GetImplementedCiphers(); - if(!implemented_ciphers) - return SECFailure; - - /* First disable all ciphers. This uses a different max value in case - * NSS adds more ciphers later we don't want them available by - * accident - */ - for(i = 0; i < num_implemented_ciphers; i++) { - SSL_CipherPrefSet(model, implemented_ciphers[i], PR_FALSE); - } - - cipher = cipher_list; - - while(cipher && cipher[0]) { - const char *end; - char name[MAX_CIPHER_LENGTH + 1]; - size_t len; - bool found = FALSE; - while((*cipher) && (ISBLANK(*cipher))) - ++cipher; - - end = strpbrk(cipher, ":, "); - if(end) - len = end - cipher; - else - len = strlen(cipher); - - if(len > MAX_CIPHER_LENGTH) { - failf(data, "Bad cipher list"); - return SECFailure; - } - else if(len) { - memcpy(name, cipher, len); - name[len] = 0; - - for(i = 0; i. - */ -static PK11SlotInfo* nss_find_slot_by_name(const char *slot_name) -{ - PK11SlotInfo *slot; - PR_Lock(nss_findslot_lock); - slot = PK11_FindSlotByName(slot_name); - PR_Unlock(nss_findslot_lock); - return slot; -} - -/* wrap 'ptr' as list node and tail-insert into 'list' */ -static CURLcode insert_wrapped_ptr(struct Curl_llist *list, void *ptr) -{ - struct ptr_list_wrap *wrap = malloc(sizeof(*wrap)); - if(!wrap) - return CURLE_OUT_OF_MEMORY; - - wrap->ptr = ptr; - Curl_llist_insert_next(list, list->tail, wrap, &wrap->node); - return CURLE_OK; -} - -/* Call PK11_CreateGenericObject() with the given obj_class and filename. If - * the call succeeds, append the object handle to the list of objects so that - * the object can be destroyed in nss_close(). */ -static CURLcode nss_create_object(struct ssl_connect_data *connssl, - CK_OBJECT_CLASS obj_class, - const char *filename, bool cacert) -{ - PK11SlotInfo *slot; - PK11GenericObject *obj; - CK_BBOOL cktrue = CK_TRUE; - CK_BBOOL ckfalse = CK_FALSE; - CK_ATTRIBUTE attrs[/* max count of attributes */ 4]; - int attr_cnt = 0; - CURLcode result = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - const int slot_id = (cacert) ? 0 : 1; - char *slot_name = aprintf("PEM Token #%d", slot_id); - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(!slot_name) - return CURLE_OUT_OF_MEMORY; - - slot = nss_find_slot_by_name(slot_name); - free(slot_name); - if(!slot) - return result; - - PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class)); - PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL)); - PK11_SETATTRS(attrs, attr_cnt, CKA_LABEL, (unsigned char *)filename, - (CK_ULONG)strlen(filename) + 1); - - if(CKO_CERTIFICATE == obj_class) { - CK_BBOOL *pval = (cacert) ? (&cktrue) : (&ckfalse); - PK11_SETATTRS(attrs, attr_cnt, CKA_TRUST, pval, sizeof(*pval)); - } - - /* PK11_CreateManagedGenericObject() was introduced in NSS 3.34 because - * PK11_DestroyGenericObject() does not release resources allocated by - * PK11_CreateGenericObject() early enough. */ - obj = -#ifdef HAVE_PK11_CREATEMANAGEDGENERICOBJECT - PK11_CreateManagedGenericObject -#else - PK11_CreateGenericObject -#endif - (slot, attrs, attr_cnt, PR_FALSE); - - PK11_FreeSlot(slot); - if(!obj) - return result; - - if(insert_wrapped_ptr(&backend->obj_list, obj) != CURLE_OK) { - PK11_DestroyGenericObject(obj); - return CURLE_OUT_OF_MEMORY; - } - - if(!cacert && CKO_CERTIFICATE == obj_class) - /* store reference to a client certificate */ - backend->obj_clicert = obj; - - return CURLE_OK; -} - -/* Destroy the NSS object whose handle is given by ptr. This function is - * a callback of Curl_llist_alloc() used by Curl_llist_destroy() to destroy - * NSS objects in nss_close() */ -static void nss_destroy_object(void *user, void *ptr) -{ - struct ptr_list_wrap *wrap = (struct ptr_list_wrap *) ptr; - PK11GenericObject *obj = (PK11GenericObject *) wrap->ptr; - (void) user; - PK11_DestroyGenericObject(obj); - free(wrap); -} - -/* same as nss_destroy_object() but for CRL items */ -static void nss_destroy_crl_item(void *user, void *ptr) -{ - struct ptr_list_wrap *wrap = (struct ptr_list_wrap *) ptr; - SECItem *crl_der = (SECItem *) wrap->ptr; - (void) user; - SECITEM_FreeItem(crl_der, PR_TRUE); - free(wrap); -} - -static CURLcode nss_load_cert(struct ssl_connect_data *ssl, - const char *filename, PRBool cacert) -{ - CURLcode result = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - /* libnsspem.so leaks memory if the requested file does not exist. For more - * details, go to . */ - if(is_file(filename)) - result = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert); - - if(!result && !cacert) { - /* we have successfully loaded a client certificate */ - char *nickname = NULL; - char *n = strrchr(filename, '/'); - if(n) - n++; - - /* The following undocumented magic helps to avoid a SIGSEGV on call - * of PK11_ReadRawAttribute() from SelectClientCert() when using an - * immature version of libnsspem.so. For more details, go to - * . */ - nickname = aprintf("PEM Token #1:%s", n); - if(nickname) { - CERTCertificate *cert = PK11_FindCertFromNickname(nickname, NULL); - if(cert) - CERT_DestroyCertificate(cert); - - free(nickname); - } - } - - return result; -} - -/* add given CRL to cache if it is not already there */ -static CURLcode nss_cache_crl(SECItem *crl_der) -{ - CERTCertDBHandle *db = CERT_GetDefaultCertDB(); - CERTSignedCrl *crl = SEC_FindCrlByDERCert(db, crl_der, 0); - if(crl) { - /* CRL already cached */ - SEC_DestroyCrl(crl); - SECITEM_FreeItem(crl_der, PR_TRUE); - return CURLE_OK; - } - - /* acquire lock before call of CERT_CacheCRL() and accessing nss_crl_list */ - PR_Lock(nss_crllock); - - if(SECSuccess != CERT_CacheCRL(db, crl_der)) { - /* unable to cache CRL */ - SECITEM_FreeItem(crl_der, PR_TRUE); - PR_Unlock(nss_crllock); - return CURLE_SSL_CRL_BADFILE; - } - - /* store the CRL item so that we can free it in nss_cleanup() */ - if(insert_wrapped_ptr(&nss_crl_list, crl_der) != CURLE_OK) { - if(SECSuccess == CERT_UncacheCRL(db, crl_der)) - SECITEM_FreeItem(crl_der, PR_TRUE); - PR_Unlock(nss_crllock); - return CURLE_OUT_OF_MEMORY; - } - - /* we need to clear session cache, so that the CRL could take effect */ - SSL_ClearSessionCache(); - PR_Unlock(nss_crllock); - return CURLE_OK; -} - -static CURLcode nss_load_crl(const char *crlfilename) -{ - PRFileDesc *infile; - PRFileInfo info; - SECItem filedata = { 0, NULL, 0 }; - SECItem *crl_der = NULL; - char *body; - - infile = PR_Open(crlfilename, PR_RDONLY, 0); - if(!infile) - return CURLE_SSL_CRL_BADFILE; - - if(PR_SUCCESS != PR_GetOpenFileInfo(infile, &info)) - goto fail; - - if(!SECITEM_AllocItem(NULL, &filedata, info.size + /* zero ended */ 1)) - goto fail; - - if(info.size != PR_Read(infile, filedata.data, info.size)) - goto fail; - - crl_der = SECITEM_AllocItem(NULL, NULL, 0U); - if(!crl_der) - goto fail; - - /* place a trailing zero right after the visible data */ - body = (char *)filedata.data; - body[--filedata.len] = '\0'; - - body = strstr(body, "-----BEGIN"); - if(body) { - /* assume ASCII */ - char *trailer; - char *begin = PORT_Strchr(body, '\n'); - if(!begin) - begin = PORT_Strchr(body, '\r'); - if(!begin) - goto fail; - - trailer = strstr(++begin, "-----END"); - if(!trailer) - goto fail; - - /* retrieve DER from ASCII */ - *trailer = '\0'; - if(ATOB_ConvertAsciiToItem(crl_der, begin)) - goto fail; - - SECITEM_FreeItem(&filedata, PR_FALSE); - } - else - /* assume DER */ - *crl_der = filedata; - - PR_Close(infile); - return nss_cache_crl(crl_der); - -fail: - PR_Close(infile); - SECITEM_FreeItem(crl_der, PR_TRUE); - SECITEM_FreeItem(&filedata, PR_FALSE); - return CURLE_SSL_CRL_BADFILE; -} - -static CURLcode nss_load_key(struct Curl_cfilter *cf, - struct Curl_easy *data, - char *key_file) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - PK11SlotInfo *slot, *tmp; - SECStatus status; - CURLcode result; - - (void)data; - result = nss_create_object(connssl, CKO_PRIVATE_KEY, key_file, FALSE); - if(result) { - PR_SetError(SEC_ERROR_BAD_KEY, 0); - return result; - } - - slot = nss_find_slot_by_name("PEM Token #1"); - if(!slot) - return CURLE_SSL_CERTPROBLEM; - - /* This will force the token to be seen as re-inserted */ - tmp = SECMOD_WaitForAnyTokenEvent(pem_module, 0, 0); - if(tmp) - PK11_FreeSlot(tmp); - if(!PK11_IsPresent(slot)) { - PK11_FreeSlot(slot); - return CURLE_SSL_CERTPROBLEM; - } - - status = PK11_Authenticate(slot, PR_TRUE, ssl_config->key_passwd); - PK11_FreeSlot(slot); - - return (SECSuccess == status) ? CURLE_OK : CURLE_SSL_CERTPROBLEM; -} - -static int display_error(struct Curl_easy *data, PRInt32 err, - const char *filename) -{ - switch(err) { - case SEC_ERROR_BAD_PASSWORD: - failf(data, "Unable to load client key: Incorrect password"); - return 1; - case SEC_ERROR_UNKNOWN_CERT: - failf(data, "Unable to load certificate %s", filename); - return 1; - default: - break; - } - return 0; /* The caller will print a generic error */ -} - -static CURLcode cert_stuff(struct Curl_cfilter *cf, - struct Curl_easy *data, - char *cert_file, char *key_file) -{ - struct ssl_connect_data *connssl = cf->ctx; - CURLcode result; - - if(cert_file) { - result = nss_load_cert(connssl, cert_file, PR_FALSE); - if(result) { - const PRErrorCode err = PR_GetError(); - if(!display_error(data, err, cert_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client cert: %d (%s)", err, err_name); - } - - return result; - } - } - - if(key_file || (is_file(cert_file))) { - if(key_file) - result = nss_load_key(cf, data, key_file); - else - /* In case the cert file also has the key */ - result = nss_load_key(cf, data, cert_file); - if(result) { - const PRErrorCode err = PR_GetError(); - if(!display_error(data, err, key_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client key: %d (%s)", err, err_name); - } - - return result; - } - } - - return CURLE_OK; -} - -static char *nss_get_password(PK11SlotInfo *slot, PRBool retry, void *arg) -{ - (void)slot; /* unused */ - - if(retry || !arg) - return NULL; - else - return (char *)PORT_Strdup((char *)arg); -} - -/* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ -static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig, - PRBool isServer) -{ - struct Curl_cfilter *cf = (struct Curl_cfilter *)arg; - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_easy *data = connssl->backend->data; - - DEBUGASSERT(data); -#ifdef SSL_ENABLE_OCSP_STAPLING - if(conn_config->verifystatus) { - SECStatus cacheResult; - - const SECItemArray *csa = SSL_PeerStapledOCSPResponses(fd); - if(!csa) { - failf(data, "Invalid OCSP response"); - return SECFailure; - } - - if(csa->len == 0) { - failf(data, "No OCSP response received"); - return SECFailure; - } - - cacheResult = CERT_CacheOCSPResponseFromSideChannel( - CERT_GetDefaultCertDB(), SSL_PeerCertificate(fd), - PR_Now(), &csa->items[0], arg - ); - - if(cacheResult != SECSuccess) { - failf(data, "Invalid OCSP response"); - return cacheResult; - } - } -#endif - - if(!conn_config->verifypeer) { - infof(data, "skipping SSL peer certificate verification"); - return SECSuccess; - } - - return SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer); -} - -/** - * Inform the application that the handshake is complete. - */ -static void HandshakeCallback(PRFileDesc *sock, void *arg) -{ - struct Curl_cfilter *cf = (struct Curl_cfilter *)arg; - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->backend->data; - unsigned int buflenmax = 50; - unsigned char buf[50]; - unsigned int buflen; - SSLNextProtoState state; - - DEBUGASSERT(data); - if(!connssl->alpn) { - return; - } - - if(SSL_GetNextProto(sock, &state, buf, &buflen, buflenmax) == SECSuccess) { - - switch(state) { -#if NSSVERNUM >= 0x031a00 /* 3.26.0 */ - /* used by NSS internally to implement 0-RTT */ - case SSL_NEXT_PROTO_EARLY_VALUE: - /* fall through! */ -#endif - case SSL_NEXT_PROTO_NO_SUPPORT: - case SSL_NEXT_PROTO_NO_OVERLAP: - Curl_alpn_set_negotiated(cf, data, NULL, 0); - return; -#ifdef SSL_ENABLE_ALPN - case SSL_NEXT_PROTO_SELECTED: - Curl_alpn_set_negotiated(cf, data, buf, buflen); - break; -#endif - default: - /* ignore SSL_NEXT_PROTO_NEGOTIATED */ - break; - } - - } -} - -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ -static SECStatus CanFalseStartCallback(PRFileDesc *sock, void *client_data, - PRBool *canFalseStart) -{ - struct Curl_easy *data = (struct Curl_easy *)client_data; - - SSLChannelInfo channelInfo; - SSLCipherSuiteInfo cipherInfo; - - SECStatus rv; - PRBool negotiatedExtension; - - *canFalseStart = PR_FALSE; - - if(SSL_GetChannelInfo(sock, &channelInfo, sizeof(channelInfo)) != SECSuccess) - return SECFailure; - - if(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, - sizeof(cipherInfo)) != SECSuccess) - return SECFailure; - - /* Prevent version downgrade attacks from TLS 1.2, and avoid False Start for - * TLS 1.3 and later. See https://bugzilla.mozilla.org/show_bug.cgi?id=861310 - */ - if(channelInfo.protocolVersion != SSL_LIBRARY_VERSION_TLS_1_2) - goto end; - - /* Only allow ECDHE key exchange algorithm. - * See https://bugzilla.mozilla.org/show_bug.cgi?id=952863 */ - if(cipherInfo.keaType != ssl_kea_ecdh) - goto end; - - /* Prevent downgrade attacks on the symmetric cipher. We do not allow CBC - * mode due to BEAST, POODLE, and other attacks on the MAC-then-Encrypt - * design. See https://bugzilla.mozilla.org/show_bug.cgi?id=1109766 */ - if(cipherInfo.symCipher != ssl_calg_aes_gcm) - goto end; - - /* Enforce ALPN to do False Start, as an indicator of server - compatibility. */ - rv = SSL_HandshakeNegotiatedExtension(sock, ssl_app_layer_protocol_xtn, - &negotiatedExtension); - if(rv != SECSuccess || !negotiatedExtension) { - rv = SSL_HandshakeNegotiatedExtension(sock, ssl_next_proto_nego_xtn, - &negotiatedExtension); - } - - if(rv != SECSuccess || !negotiatedExtension) - goto end; - - *canFalseStart = PR_TRUE; - - infof(data, "Trying TLS False Start"); - -end: - return SECSuccess; -} -#endif - -static void display_cert_info(struct Curl_easy *data, - CERTCertificate *cert) -{ - char *subject, *issuer, *common_name; - PRExplodedTime printableTime; - char timeString[256]; - PRTime notBefore, notAfter; - - subject = CERT_NameToAscii(&cert->subject); - issuer = CERT_NameToAscii(&cert->issuer); - common_name = CERT_GetCommonName(&cert->subject); - infof(data, "subject: %s", subject); - - CERT_GetCertTimes(cert, ¬Before, ¬After); - PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, " start date: %s", timeString); - PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, " expire date: %s", timeString); - infof(data, " common name: %s", common_name); - infof(data, " issuer: %s", issuer); - - PR_Free(subject); - PR_Free(issuer); - PR_Free(common_name); -} - -/* A number of certs that will never occur in a real server handshake */ -#define TOO_MANY_CERTS 300 - -static CURLcode display_conn_info(struct Curl_easy *data, PRFileDesc *sock) -{ - CURLcode result = CURLE_OK; - SSLChannelInfo channel; - SSLCipherSuiteInfo suite; - CERTCertificate *cert; - CERTCertificate *cert2; - CERTCertificate *cert3; - PRTime now; - - if(SSL_GetChannelInfo(sock, &channel, sizeof(channel)) == - SECSuccess && channel.length == sizeof(channel) && - channel.cipherSuite) { - if(SSL_GetCipherSuiteInfo(channel.cipherSuite, - &suite, sizeof(suite)) == SECSuccess) { - infof(data, "SSL connection using %s", suite.cipherSuiteName); - } - } - - cert = SSL_PeerCertificate(sock); - if(cert) { - infof(data, "Server certificate:"); - - if(!data->set.ssl.certinfo) { - display_cert_info(data, cert); - CERT_DestroyCertificate(cert); - } - else { - /* Count certificates in chain. */ - int i = 1; - now = PR_Now(); - if(!cert->isRoot) { - cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA); - while(cert2) { - i++; - if(i >= TOO_MANY_CERTS) { - CERT_DestroyCertificate(cert2); - failf(data, "certificate loop"); - return CURLE_SSL_CERTPROBLEM; - } - if(cert2->isRoot) { - CERT_DestroyCertificate(cert2); - break; - } - cert3 = CERT_FindCertIssuer(cert2, now, certUsageSSLCA); - CERT_DestroyCertificate(cert2); - cert2 = cert3; - } - } - - result = Curl_ssl_init_certinfo(data, i); - if(!result) { - for(i = 0; cert; cert = cert2) { - result = Curl_extract_certinfo(data, i++, (char *)cert->derCert.data, - (char *)cert->derCert.data + - cert->derCert.len); - if(result) - break; - - if(cert->isRoot) { - CERT_DestroyCertificate(cert); - break; - } - - cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA); - CERT_DestroyCertificate(cert); - } - } - } - } - - return result; -} - -static SECStatus BadCertHandler(void *arg, PRFileDesc *sock) -{ - struct Curl_cfilter *cf = (struct Curl_cfilter *)arg; - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->backend->data; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config; - PRErrorCode err = PR_GetError(); - CERTCertificate *cert; - - DEBUGASSERT(data); - ssl_config = Curl_ssl_cf_get_config(cf, data); - /* remember the cert verification result */ - ssl_config->certverifyresult = err; - - if(err == SSL_ERROR_BAD_CERT_DOMAIN && !conn_config->verifyhost) - /* we are asked not to verify the host name */ - return SECSuccess; - - /* print only info about the cert, the error is printed off the callback */ - cert = SSL_PeerCertificate(sock); - if(cert) { - infof(data, "Server certificate:"); - display_cert_info(data, cert); - CERT_DestroyCertificate(cert); - } - - return SECFailure; -} - -/** - * - * Check that the Peer certificate's issuer certificate matches the one found - * by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the - * issuer check, so we provide comments that mimic the OpenSSL - * X509_check_issued function (in x509v3/v3_purp.c) - */ -static SECStatus check_issuer_cert(PRFileDesc *sock, - char *issuer_nickname) -{ - CERTCertificate *cert, *cert_issuer, *issuer; - SECStatus res = SECSuccess; - void *proto_win = NULL; - - cert = SSL_PeerCertificate(sock); - cert_issuer = CERT_FindCertIssuer(cert, PR_Now(), certUsageObjectSigner); - - proto_win = SSL_RevealPinArg(sock); - issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win); - - if((!cert_issuer) || (!issuer)) - res = SECFailure; - else if(SECITEM_CompareItem(&cert_issuer->derCert, - &issuer->derCert) != SECEqual) - res = SECFailure; - - CERT_DestroyCertificate(cert); - CERT_DestroyCertificate(issuer); - CERT_DestroyCertificate(cert_issuer); - return res; -} - -static CURLcode cmp_peer_pubkey(struct ssl_connect_data *connssl, - const char *pinnedpubkey) -{ - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - struct ssl_backend_data *backend = connssl->backend; - struct Curl_easy *data = NULL; - CERTCertificate *cert; - - DEBUGASSERT(backend); - data = backend->data; - - if(!pinnedpubkey) - /* no pinned public key specified */ - return CURLE_OK; - - /* get peer certificate */ - cert = SSL_PeerCertificate(backend->handle); - if(cert) { - /* extract public key from peer certificate */ - SECKEYPublicKey *pubkey = CERT_ExtractPublicKey(cert); - if(pubkey) { - /* encode the public key as DER */ - SECItem *cert_der = PK11_DEREncodePublicKey(pubkey); - if(cert_der) { - /* compare the public key with the pinned public key */ - result = Curl_pin_peer_pubkey(data, pinnedpubkey, cert_der->data, - cert_der->len); - SECITEM_FreeItem(cert_der, PR_TRUE); - } - SECKEY_DestroyPublicKey(pubkey); - } - CERT_DestroyCertificate(cert); - } - - /* report the resulting status */ - switch(result) { - case CURLE_OK: - infof(data, "pinned public key verified successfully"); - break; - case CURLE_SSL_PINNEDPUBKEYNOTMATCH: - failf(data, "failed to verify pinned public key"); - break; - default: - /* OOM, etc. */ - break; - } - - return result; -} - -/** - * - * Callback to pick the SSL client certificate. - */ -static SECStatus SelectClientCert(void *arg, PRFileDesc *sock, - struct CERTDistNamesStr *caNames, - struct CERTCertificateStr **pRetCert, - struct SECKEYPrivateKeyStr **pRetKey) -{ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg; - struct ssl_backend_data *backend = connssl->backend; - struct Curl_easy *data = NULL; - const char *nickname = NULL; - static const char pem_slotname[] = "PEM Token #1"; - - DEBUGASSERT(backend); - - data = backend->data; - nickname = backend->client_nickname; - - if(backend->obj_clicert) { - /* use the cert/key provided by PEM reader */ - SECItem cert_der = { 0, NULL, 0 }; - void *proto_win = SSL_RevealPinArg(sock); - struct CERTCertificateStr *cert; - struct SECKEYPrivateKeyStr *key; - - PK11SlotInfo *slot = nss_find_slot_by_name(pem_slotname); - if(!slot) { - failf(data, "NSS: PK11 slot not found: %s", pem_slotname); - return SECFailure; - } - - if(PK11_ReadRawAttribute(PK11_TypeGeneric, backend->obj_clicert, CKA_VALUE, - &cert_der) != SECSuccess) { - failf(data, "NSS: CKA_VALUE not found in PK11 generic object"); - PK11_FreeSlot(slot); - return SECFailure; - } - - cert = PK11_FindCertFromDERCertItem(slot, &cert_der, proto_win); - SECITEM_FreeItem(&cert_der, PR_FALSE); - if(!cert) { - failf(data, "NSS: client certificate from file not found"); - PK11_FreeSlot(slot); - return SECFailure; - } - - key = PK11_FindPrivateKeyFromCert(slot, cert, NULL); - PK11_FreeSlot(slot); - if(!key) { - failf(data, "NSS: private key from file not found"); - CERT_DestroyCertificate(cert); - return SECFailure; - } - - infof(data, "NSS: client certificate from file"); - display_cert_info(data, cert); - - *pRetCert = cert; - *pRetKey = key; - return SECSuccess; - } - - /* use the default NSS hook */ - if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames, - pRetCert, pRetKey) - || !*pRetCert) { - - if(!nickname) - failf(data, "NSS: client certificate not found (nickname not " - "specified)"); - else - failf(data, "NSS: client certificate not found: %s", nickname); - - return SECFailure; - } - - /* get certificate nickname if any */ - nickname = (*pRetCert)->nickname; - if(!nickname) - nickname = "[unknown]"; - - if(!strncmp(nickname, pem_slotname, sizeof(pem_slotname) - 1U)) { - failf(data, "NSS: refusing previously loaded certificate from file: %s", - nickname); - return SECFailure; - } - - if(!*pRetKey) { - failf(data, "NSS: private key not found for certificate: %s", nickname); - return SECFailure; - } - - infof(data, "NSS: using client certificate: %s", nickname); - display_cert_info(data, *pRetCert); - return SECSuccess; -} - -/* update blocking direction in case of PR_WOULD_BLOCK_ERROR */ -static void nss_update_connecting_state(ssl_connect_state state, void *secret) -{ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)secret; - if(PR_GetError() != PR_WOULD_BLOCK_ERROR) - /* an unrelated error is passing by */ - return; - - switch(connssl->connecting_state) { - case ssl_connect_2: - case ssl_connect_2_reading: - case ssl_connect_2_writing: - break; - default: - /* we are not called from an SSL handshake */ - return; - } - - /* update the state accordingly */ - connssl->connecting_state = state; -} - -/* recv() wrapper we use to detect blocking direction during SSL handshake */ -static PRInt32 nspr_io_recv(PRFileDesc *fd, void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - const PRRecvFN recv_fn = fd->lower->methods->recv; - const PRInt32 rv = recv_fn(fd->lower, buf, amount, flags, timeout); - if(rv < 0) - /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ - nss_update_connecting_state(ssl_connect_2_reading, fd->secret); - return rv; -} - -/* send() wrapper we use to detect blocking direction during SSL handshake */ -static PRInt32 nspr_io_send(PRFileDesc *fd, const void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - const PRSendFN send_fn = fd->lower->methods->send; - const PRInt32 rv = send_fn(fd->lower, buf, amount, flags, timeout); - if(rv < 0) - /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ - nss_update_connecting_state(ssl_connect_2_writing, fd->secret); - return rv; -} - -/* close() wrapper to avoid assertion failure due to fd->secret != NULL */ -static PRStatus nspr_io_close(PRFileDesc *fd) -{ - const PRCloseFN close_fn = PR_GetDefaultIOMethods()->close; - fd->secret = NULL; - return close_fn(fd); -} - -/* load a PKCS #11 module */ -static CURLcode nss_load_module(SECMODModule **pmod, const char *library, - const char *name) -{ - char *config_string; - SECMODModule *module = *pmod; - if(module) - /* already loaded */ - return CURLE_OK; - - config_string = aprintf("library=%s name=%s", library, name); - if(!config_string) - return CURLE_OUT_OF_MEMORY; - - module = SECMOD_LoadUserModule(config_string, NULL, PR_FALSE); - free(config_string); - - if(module && module->loaded) { - /* loaded successfully */ - *pmod = module; - return CURLE_OK; - } - - if(module) - SECMOD_DestroyModule(module); - return CURLE_FAILED_INIT; -} - -/* unload a PKCS #11 module */ -static void nss_unload_module(SECMODModule **pmod) -{ - SECMODModule *module = *pmod; - if(!module) - /* not loaded */ - return; - - if(SECMOD_UnloadUserModule(module) != SECSuccess) - /* unload failed */ - return; - - SECMOD_DestroyModule(module); - *pmod = NULL; -} - -/* data might be NULL */ -static CURLcode nss_init_core(struct Curl_easy *data, const char *cert_dir) -{ - NSSInitParameters initparams; - PRErrorCode err; - const char *err_name; - - if(nss_context) - return CURLE_OK; - - memset((void *) &initparams, '\0', sizeof(initparams)); - initparams.length = sizeof(initparams); - - if(cert_dir) { - char *certpath = aprintf("sql:%s", cert_dir); - if(!certpath) - return CURLE_OUT_OF_MEMORY; - - infof(data, "Initializing NSS with certpath: %s", certpath); - nss_context = NSS_InitContext(certpath, "", "", "", &initparams, - NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); - free(certpath); - - if(nss_context) - return CURLE_OK; - - err = PR_GetError(); - err_name = nss_error_to_name(err); - infof(data, "Unable to initialize NSS database: %d (%s)", err, err_name); - } - - infof(data, "Initializing NSS with certpath: none"); - nss_context = NSS_InitContext("", "", "", "", &initparams, NSS_INIT_READONLY - | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN - | NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD); - if(nss_context) - return CURLE_OK; - - err = PR_GetError(); - err_name = nss_error_to_name(err); - failf(data, "Unable to initialize NSS: %d (%s)", err, err_name); - return CURLE_SSL_CACERT_BADFILE; -} - -/* data might be NULL */ -static CURLcode nss_setup(struct Curl_easy *data) -{ - char *cert_dir; - struct_stat st; - CURLcode result; - - if(initialized) - return CURLE_OK; - - /* list of all CRL items we need to destroy in nss_cleanup() */ - Curl_llist_init(&nss_crl_list, nss_destroy_crl_item); - - /* First we check if $SSL_DIR points to a valid dir */ - cert_dir = getenv("SSL_DIR"); - if(cert_dir) { - if((stat(cert_dir, &st) != 0) || - (!S_ISDIR(st.st_mode))) { - cert_dir = NULL; - } - } - - /* Now we check if the default location is a valid dir */ - if(!cert_dir) { - if((stat(SSL_DIR, &st) == 0) && - (S_ISDIR(st.st_mode))) { - cert_dir = (char *)SSL_DIR; - } - } - - if(nspr_io_identity == PR_INVALID_IO_LAYER) { - /* allocate an identity for our own NSPR I/O layer */ - nspr_io_identity = PR_GetUniqueIdentity("libcurl"); - if(nspr_io_identity == PR_INVALID_IO_LAYER) - return CURLE_OUT_OF_MEMORY; - - /* the default methods just call down to the lower I/O layer */ - memcpy(&nspr_io_methods, PR_GetDefaultIOMethods(), - sizeof(nspr_io_methods)); - - /* override certain methods in the table by our wrappers */ - nspr_io_methods.recv = nspr_io_recv; - nspr_io_methods.send = nspr_io_send; - nspr_io_methods.close = nspr_io_close; - } - - result = nss_init_core(data, cert_dir); - if(result) - return result; - - if(!any_cipher_enabled()) - NSS_SetDomesticPolicy(); - - initialized = 1; - - return CURLE_OK; -} - -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -static int nss_init(void) -{ - /* curl_global_init() is not thread-safe so this test is ok */ - if(!nss_initlock) { - PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); - nss_initlock = PR_NewLock(); - nss_crllock = PR_NewLock(); - nss_findslot_lock = PR_NewLock(); - nss_trustload_lock = PR_NewLock(); - } - - /* We will actually initialize NSS later */ - - return 1; -} - -/* data might be NULL */ -CURLcode Curl_nss_force_init(struct Curl_easy *data) -{ - CURLcode result; - if(!nss_initlock) { - if(data) - failf(data, "unable to initialize NSS, curl_global_init() should have " - "been called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL"); - return CURLE_FAILED_INIT; - } - - PR_Lock(nss_initlock); - result = nss_setup(data); - PR_Unlock(nss_initlock); - - return result; -} - -/* Global cleanup */ -static void nss_cleanup(void) -{ - /* This function isn't required to be threadsafe and this is only done - * as a safety feature. - */ - PR_Lock(nss_initlock); - if(initialized) { - /* Free references to client certificates held in the SSL session cache. - * Omitting this hampers destruction of the security module owning - * the certificates. */ - SSL_ClearSessionCache(); - - nss_unload_module(&pem_module); - nss_unload_module(&trust_module); - NSS_ShutdownContext(nss_context); - nss_context = NULL; - } - - /* destroy all CRL items */ - Curl_llist_destroy(&nss_crl_list, NULL); - - PR_Unlock(nss_initlock); - - PR_DestroyLock(nss_initlock); - PR_DestroyLock(nss_crllock); - PR_DestroyLock(nss_findslot_lock); - PR_DestroyLock(nss_trustload_lock); - nss_initlock = NULL; - - initialized = 0; -} - -static void close_one(struct ssl_connect_data *connssl) -{ - /* before the cleanup, check whether we are using a client certificate */ - struct ssl_backend_data *backend = connssl->backend; - bool client_cert = true; - - DEBUGASSERT(backend); - - client_cert = (backend->client_nickname != NULL) - || (backend->obj_clicert != NULL); - - if(backend->handle) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)PR_Recv(backend->handle, buf, (int)sizeof(buf), 0, - PR_INTERVAL_NO_WAIT); - } - - free(backend->client_nickname); - backend->client_nickname = NULL; - - /* destroy all NSS objects in order to avoid failure of NSS shutdown */ - Curl_llist_destroy(&backend->obj_list, NULL); - backend->obj_clicert = NULL; - - if(backend->handle) { - if(client_cert) - /* A server might require different authentication based on the - * particular path being requested by the client. To support this - * scenario, we must ensure that a connection will never reuse the - * authentication data from a previous connection. */ - SSL_InvalidateSession(backend->handle); - - PR_Close(backend->handle); - backend->handle = NULL; - } -} - -/* - * This function is called when an SSL connection is closed. - */ -static void nss_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - (void)data; - DEBUGASSERT(backend); - - if(backend->handle) { - /* NSS closes the socket we previously handed to it, so we must mark it - as closed to avoid double close */ - fake_sclose(cf->conn->sock[cf->sockindex]); - cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; - } - - close_one(connssl); -} - -/* return true if NSS can provide error code (and possibly msg) for the - error */ -static bool is_nss_error(CURLcode err) -{ - switch(err) { - case CURLE_PEER_FAILED_VERIFICATION: - case CURLE_SSL_CERTPROBLEM: - case CURLE_SSL_CONNECT_ERROR: - case CURLE_SSL_ISSUER_ERROR: - return true; - - default: - return false; - } -} - -/* return true if the given error code is related to a client certificate */ -static bool is_cc_error(PRInt32 err) -{ - switch(err) { - case SSL_ERROR_BAD_CERT_ALERT: - case SSL_ERROR_EXPIRED_CERT_ALERT: - case SSL_ERROR_REVOKED_CERT_ALERT: - return true; - - default: - return false; - } -} - -static CURLcode nss_load_ca_certificates(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); - const char *cafile = conn_config->CAfile; - const char *capath = conn_config->CApath; - bool use_trust_module; - CURLcode result = CURLE_OK; - - /* treat empty string as unset */ - if(cafile && !cafile[0]) - cafile = NULL; - if(capath && !capath[0]) - capath = NULL; - - infof(data, " CAfile: %s", cafile ? cafile : "none"); - infof(data, " CApath: %s", capath ? capath : "none"); - - /* load libnssckbi.so if no other trust roots were specified */ - use_trust_module = !cafile && !capath; - - PR_Lock(nss_trustload_lock); - if(use_trust_module && !trust_module) { - /* libnssckbi.so needed but not yet loaded --> load it! */ - result = nss_load_module(&trust_module, trust_library, "trust"); - infof(data, "%s %s", (result) ? "failed to load" : "loaded", - trust_library); - if(result == CURLE_FAILED_INIT) - /* If libnssckbi.so is not available (or fails to load), one can still - use CA certificates stored in NSS database. Ignore the failure. */ - result = CURLE_OK; - } - else if(!use_trust_module && trust_module) { - /* libnssckbi.so not needed but already loaded --> unload it! */ - infof(data, "unloading %s", trust_library); - nss_unload_module(&trust_module); - } - PR_Unlock(nss_trustload_lock); - - if(cafile) - result = nss_load_cert(connssl, cafile, PR_TRUE); - - if(result) - return result; - - if(capath) { - struct_stat st; - if(stat(capath, &st) == -1) - return CURLE_SSL_CACERT_BADFILE; - - if(S_ISDIR(st.st_mode)) { - PRDirEntry *entry; - PRDir *dir = PR_OpenDir(capath); - if(!dir) - return CURLE_SSL_CACERT_BADFILE; - - while((entry = - PR_ReadDir(dir, (PRDirFlags)(PR_SKIP_BOTH | PR_SKIP_HIDDEN)))) { - char *fullpath = aprintf("%s/%s", capath, entry->name); - if(!fullpath) { - PR_CloseDir(dir); - return CURLE_OUT_OF_MEMORY; - } - - if(CURLE_OK != nss_load_cert(connssl, fullpath, PR_TRUE)) - /* This is purposefully tolerant of errors so non-PEM files can - * be in the same directory */ - infof(data, "failed to load '%s' from CURLOPT_CAPATH", fullpath); - - free(fullpath); - } - - PR_CloseDir(dir); - } - else - infof(data, "WARNING: CURLOPT_CAPATH not a directory (%s)", capath); - } - - return CURLE_OK; -} - -static CURLcode nss_sslver_from_curl(PRUint16 *nssver, long version) -{ - switch(version) { - case CURL_SSLVERSION_SSLv2: - *nssver = SSL_LIBRARY_VERSION_2; - return CURLE_OK; - - case CURL_SSLVERSION_SSLv3: - return CURLE_NOT_BUILT_IN; - - case CURL_SSLVERSION_TLSv1_0: - *nssver = SSL_LIBRARY_VERSION_TLS_1_0; - return CURLE_OK; - - case CURL_SSLVERSION_TLSv1_1: -#ifdef SSL_LIBRARY_VERSION_TLS_1_1 - *nssver = SSL_LIBRARY_VERSION_TLS_1_1; - return CURLE_OK; -#else - return CURLE_SSL_CONNECT_ERROR; -#endif - - case CURL_SSLVERSION_TLSv1_2: -#ifdef SSL_LIBRARY_VERSION_TLS_1_2 - *nssver = SSL_LIBRARY_VERSION_TLS_1_2; - return CURLE_OK; -#else - return CURLE_SSL_CONNECT_ERROR; -#endif - - case CURL_SSLVERSION_TLSv1_3: -#ifdef SSL_LIBRARY_VERSION_TLS_1_3 - *nssver = SSL_LIBRARY_VERSION_TLS_1_3; - return CURLE_OK; -#else - return CURLE_SSL_CONNECT_ERROR; -#endif - - default: - return CURLE_SSL_CONNECT_ERROR; - } -} - -static CURLcode nss_init_sslver(SSLVersionRange *sslver, - struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - CURLcode result; - const long min = conn_config->version; - const long max = conn_config->version_max; - SSLVersionRange vrange; - - switch(min) { - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_DEFAULT: - /* Bump our minimum TLS version if NSS has stricter requirements. */ - if(SSL_VersionRangeGetDefault(ssl_variant_stream, &vrange) != SECSuccess) - return CURLE_SSL_CONNECT_ERROR; - if(sslver->min < vrange.min) - sslver->min = vrange.min; - break; - default: - result = nss_sslver_from_curl(&sslver->min, min); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; - } - } - - switch(max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - break; - default: - result = nss_sslver_from_curl(&sslver->max, max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; - } - } - - return CURLE_OK; -} - -static CURLcode nss_fail_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - CURLcode curlerr) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(is_nss_error(curlerr)) { - /* read NSPR error code */ - PRErrorCode err = PR_GetError(); - if(is_cc_error(err)) - curlerr = CURLE_SSL_CERTPROBLEM; - - /* print the error number and error string */ - infof(data, "NSS error %d (%s)", err, nss_error_to_name(err)); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - } - - /* cleanup on connection failure */ - Curl_llist_destroy(&backend->obj_list, NULL); - - return curlerr; -} - -/* Switch the SSL socket into blocking or non-blocking mode. */ -static CURLcode nss_set_blocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool blocking) -{ - struct ssl_connect_data *connssl = cf->ctx; - PRSocketOptionData sock_opt; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - sock_opt.option = PR_SockOpt_Nonblocking; - sock_opt.value.non_blocking = !blocking; - - if(PR_SetSocketOption(backend->handle, &sock_opt) != PR_SUCCESS) - return nss_fail_connect(cf, data, CURLE_SSL_CONNECT_ERROR); - - return CURLE_OK; -} - -static CURLcode nss_setup_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - PRFileDesc *model = NULL; - PRFileDesc *nspr_io = NULL; - PRFileDesc *nspr_io_stub = NULL; - PRBool ssl_no_cache; - PRBool ssl_cbc_random_iv; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next); - struct ssl_connect_data *connssl_next = cf_ssl_next? - cf_ssl_next->ctx : NULL; - CURLcode result; - bool second_layer = FALSE; - SSLVersionRange sslver_supported; - SSLVersionRange sslver = { - SSL_LIBRARY_VERSION_TLS_1_0, /* min */ -#ifdef SSL_LIBRARY_VERSION_TLS_1_3 - SSL_LIBRARY_VERSION_TLS_1_3 /* max */ -#elif defined SSL_LIBRARY_VERSION_TLS_1_2 - SSL_LIBRARY_VERSION_TLS_1_2 -#elif defined SSL_LIBRARY_VERSION_TLS_1_1 - SSL_LIBRARY_VERSION_TLS_1_1 -#else - SSL_LIBRARY_VERSION_TLS_1_0 -#endif - }; - const char *hostname = connssl->hostname; - char *snihost; - - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - - DEBUGASSERT(backend); - - backend->data = data; - - /* list of all NSS objects we need to destroy in nss_do_close() */ - Curl_llist_init(&backend->obj_list, nss_destroy_object); - - PR_Lock(nss_initlock); - result = nss_setup(data); - if(result) { - PR_Unlock(nss_initlock); - goto error; - } - - PK11_SetPasswordFunc(nss_get_password); - - result = nss_load_module(&pem_module, pem_library, "PEM"); - PR_Unlock(nss_initlock); - if(result == CURLE_FAILED_INIT) - infof(data, "WARNING: failed to load NSS PEM library %s. Using " - "OpenSSL PEM certificates will not work.", pem_library); - else if(result) - goto error; - - result = CURLE_SSL_CONNECT_ERROR; - - model = PR_NewTCPSocket(); - if(!model) - goto error; - model = SSL_ImportFD(NULL, model); - - if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) - goto error; - - /* do not use SSL cache if disabled or we are not going to verify peer */ - ssl_no_cache = (ssl_config->primary.sessionid - && conn_config->verifypeer) ? PR_FALSE : PR_TRUE; - if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess) - goto error; - - /* enable/disable the requested SSL version(s) */ - if(nss_init_sslver(&sslver, cf, data) != CURLE_OK) - goto error; - if(SSL_VersionRangeGetSupported(ssl_variant_stream, - &sslver_supported) != SECSuccess) - goto error; - if(sslver_supported.max < sslver.max && sslver_supported.max >= sslver.min) { - char *sslver_req_str, *sslver_supp_str; - sslver_req_str = nss_sslver_to_name(sslver.max); - sslver_supp_str = nss_sslver_to_name(sslver_supported.max); - if(sslver_req_str && sslver_supp_str) - infof(data, "Falling back from %s to max supported SSL version (%s)", - sslver_req_str, sslver_supp_str); - free(sslver_req_str); - free(sslver_supp_str); - sslver.max = sslver_supported.max; - } - if(SSL_VersionRangeSet(model, &sslver) != SECSuccess) - goto error; - - ssl_cbc_random_iv = !ssl_config->enable_beast; -#ifdef SSL_CBC_RANDOM_IV - /* unless the user explicitly asks to allow the protocol vulnerability, we - use the work-around */ - if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess) - infof(data, "WARNING: failed to set SSL_CBC_RANDOM_IV = %d", - ssl_cbc_random_iv); -#else - if(ssl_cbc_random_iv) - infof(data, "WARNING: support for SSL_CBC_RANDOM_IV not compiled in"); -#endif - - if(conn_config->cipher_list) { - if(set_ciphers(data, model, conn_config->cipher_list) != SECSuccess) { - result = CURLE_SSL_CIPHER; - goto error; - } - } - - if(!conn_config->verifypeer && conn_config->verifyhost) - infof(data, "WARNING: ignoring value of ssl.verifyhost"); - - /* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ - if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, cf) != SECSuccess) - goto error; - - /* not checked yet */ - ssl_config->certverifyresult = 0; - - if(SSL_BadCertHook(model, BadCertHandler, cf) != SECSuccess) - goto error; - - if(SSL_HandshakeCallback(model, HandshakeCallback, cf) != SECSuccess) - goto error; - - { - const CURLcode rv = nss_load_ca_certificates(cf, data); - if((rv == CURLE_SSL_CACERT_BADFILE) && !conn_config->verifypeer) - /* not a fatal error because we are not going to verify the peer */ - infof(data, "WARNING: CA certificates failed to load"); - else if(rv) { - result = rv; - goto error; - } - } - - if(ssl_config->primary.CRLfile) { - const CURLcode rv = nss_load_crl(ssl_config->primary.CRLfile); - if(rv) { - result = rv; - goto error; - } - infof(data, " CRLfile: %s", ssl_config->primary.CRLfile); - } - - if(ssl_config->primary.clientcert) { - char *nickname = dup_nickname(data, ssl_config->primary.clientcert); - if(nickname) { - /* we are not going to use libnsspem.so to read the client cert */ - backend->obj_clicert = NULL; - } - else { - CURLcode rv = cert_stuff(cf, data, - ssl_config->primary.clientcert, - ssl_config->key); - if(rv) { - /* failf() is already done in cert_stuff() */ - result = rv; - goto error; - } - } - - /* store the nickname for SelectClientCert() called during handshake */ - backend->client_nickname = nickname; - } - else - backend->client_nickname = NULL; - - if(SSL_GetClientAuthDataHook(model, SelectClientCert, - (void *)connssl) != SECSuccess) { - result = CURLE_SSL_CERTPROBLEM; - goto error; - } - - /* Is there an SSL filter "in front" of us or are we writing directly - * to the socket? */ - if(connssl_next) { - /* The filter should be connected by now, with full handshake */ - DEBUGASSERT(connssl_next->backend->handle); - DEBUGASSERT(ssl_connection_complete == connssl_next->state); - /* We tell our NSS instance to use do IO with the 'next' NSS - * instance. This NSS instance will take ownership of the next - * one, including its destruction. We therefore need to `disown` - * the next filter's handle, once import succeeds. */ - nspr_io = connssl_next->backend->handle; - second_layer = TRUE; - } - else { - /* wrap OS file descriptor by NSPR's file descriptor abstraction */ - nspr_io = PR_ImportTCPSocket(sockfd); - if(!nspr_io) - goto error; - } - - /* create our own NSPR I/O layer */ - nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods); - if(!nspr_io_stub) { - if(!second_layer) - PR_Close(nspr_io); - goto error; - } - - /* make the per-connection data accessible from NSPR I/O callbacks */ - nspr_io_stub->secret = (void *)connssl; - - /* push our new layer to the NSPR I/O stack */ - if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) { - if(!second_layer) - PR_Close(nspr_io); - PR_Close(nspr_io_stub); - goto error; - } - - /* import our model socket onto the current I/O stack */ - backend->handle = SSL_ImportFD(model, nspr_io); - if(!backend->handle) { - if(!second_layer) - PR_Close(nspr_io); - goto error; - } - - PR_Close(model); /* We don't need this any more */ - model = NULL; - if(connssl_next) /* steal the NSS handle we just imported successfully */ - connssl_next->backend->handle = NULL; - - /* This is the password associated with the cert that we're using */ - if(ssl_config->key_passwd) { - SSL_SetPKCS11PinArg(backend->handle, ssl_config->key_passwd); - } - -#ifdef SSL_ENABLE_OCSP_STAPLING - if(conn_config->verifystatus) { - if(SSL_OptionSet(backend->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE) - != SECSuccess) - goto error; - } -#endif - -#ifdef SSL_ENABLE_ALPN - if(SSL_OptionSet(backend->handle, SSL_ENABLE_ALPN, - connssl->alpn ? PR_TRUE : PR_FALSE) - != SECSuccess) - goto error; -#endif - -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ - if(data->set.ssl.falsestart) { - if(SSL_OptionSet(backend->handle, SSL_ENABLE_FALSE_START, PR_TRUE) - != SECSuccess) - goto error; - - if(SSL_SetCanFalseStartCallback(backend->handle, CanFalseStartCallback, - data) != SECSuccess) - goto error; - } -#endif - -#if defined(SSL_ENABLE_ALPN) - if(connssl->alpn) { - struct alpn_proto_buf proto; - - result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); - if(result || SSL_SetNextProtoNego(backend->handle, proto.data, proto.len) - != SECSuccess) { - failf(data, "Error setting ALPN"); - goto error; - } - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); - } -#endif - - - /* Force handshake on next I/O */ - if(SSL_ResetHandshake(backend->handle, /* asServer */ PR_FALSE) - != SECSuccess) - goto error; - - /* propagate hostname to the TLS layer */ - if(SSL_SetURL(backend->handle, snihost) != SECSuccess) - goto error; - - /* prevent NSS from re-using the session for a different hostname */ - if(SSL_SetSockPeerID(backend->handle, snihost) != SECSuccess) - goto error; - - return CURLE_OK; - -error: - if(model) - PR_Close(model); - - return nss_fail_connect(cf, data, result); -} - -static CURLcode nss_do_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - CURLcode result = CURLE_SSL_CONNECT_ERROR; - PRUint32 timeout; - - /* check timeout situation */ - const timediff_t time_left = Curl_timeleft(data, NULL, TRUE); - if(time_left < 0) { - failf(data, "timed out before SSL handshake"); - result = CURLE_OPERATION_TIMEDOUT; - goto error; - } - - DEBUGASSERT(backend); - - /* Force the handshake now */ - timeout = PR_MillisecondsToInterval((PRUint32) time_left); - if(SSL_ForceHandshakeWithTimeout(backend->handle, timeout) != SECSuccess) { - if(PR_GetError() == PR_WOULD_BLOCK_ERROR) - /* blocking direction is updated by nss_update_connecting_state() */ - return CURLE_AGAIN; - else if(ssl_config->certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) - result = CURLE_PEER_FAILED_VERIFICATION; - else if(ssl_config->certverifyresult) - result = CURLE_PEER_FAILED_VERIFICATION; - goto error; - } - - result = display_conn_info(data, backend->handle); - if(result) - goto error; - - if(conn_config->issuercert) { - SECStatus ret = SECFailure; - char *nickname = dup_nickname(data, conn_config->issuercert); - if(nickname) { - /* we support only nicknames in case of issuercert for now */ - ret = check_issuer_cert(backend->handle, nickname); - free(nickname); - } - - if(SECFailure == ret) { - infof(data, "SSL certificate issuer check failed"); - result = CURLE_SSL_ISSUER_ERROR; - goto error; - } - else { - infof(data, "SSL certificate issuer check ok"); - } - } - - result = cmp_peer_pubkey(connssl, Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); - if(result) - /* status already printed */ - goto error; - - return CURLE_OK; - -error: - return nss_fail_connect(cf, data, result); -} - -static CURLcode nss_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - struct ssl_connect_data *connssl = cf->ctx; - const bool blocking = (done == NULL); - CURLcode result; - - if(connssl->state == ssl_connection_complete) { - if(!blocking) - *done = TRUE; - return CURLE_OK; - } - - if(connssl->connecting_state == ssl_connect_1) { - result = nss_setup_connect(cf, data); - if(result) - /* we do not expect CURLE_AGAIN from nss_setup_connect() */ - return result; - - connssl->connecting_state = ssl_connect_2; - } - - /* enable/disable blocking mode before handshake */ - result = nss_set_blocking(cf, data, blocking); - if(result) - return result; - - result = nss_do_connect(cf, data); - switch(result) { - case CURLE_OK: - break; - case CURLE_AGAIN: - /* CURLE_AGAIN in non-blocking mode is not an error */ - if(!blocking) - return CURLE_OK; - else - return result; - default: - return result; - } - - if(blocking) { - /* in blocking mode, set NSS non-blocking mode _after_ SSL handshake */ - result = nss_set_blocking(cf, data, /* blocking */ FALSE); - if(result) - return result; - } - else - /* signal completed SSL handshake */ - *done = TRUE; - - connssl->state = ssl_connection_complete; - - /* ssl_connect_done is never used outside, go back to the initial state */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode nss_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - return nss_connect_common(cf, data, /* blocking */ NULL); -} - -static CURLcode nss_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return nss_connect_common(cf, data, done); -} - -static ssize_t nss_send(struct Curl_cfilter *cf, - struct Curl_easy *data, /* transfer */ - const void *mem, /* send this data */ - size_t len, /* amount to write */ - CURLcode *curlcode) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - ssize_t rc; - - (void)data; - DEBUGASSERT(backend); - - /* The SelectClientCert() hook uses this for infof() and failf() but the - handle stored in nss_setup_connect() could have already been freed. */ - backend->data = data; - - rc = PR_Send(backend->handle, mem, (int)len, 0, PR_INTERVAL_NO_WAIT); - if(rc < 0) { - PRInt32 err = PR_GetError(); - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(data, "SSL write: error %d (%s)", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_SEND_ERROR; - } - - return -1; - } - - return rc; /* number of bytes */ -} - -static bool -nss_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - PRFileDesc *fd = connssl->backend->handle->lower; - char buf; - - (void) data; - - /* Returns true in case of error to force reading. */ - return PR_Recv(fd, (void *) &buf, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT) != 0; -} - -static ssize_t nss_recv(struct Curl_cfilter *cf, - struct Curl_easy *data, /* transfer */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - ssize_t nread; - - (void)data; - DEBUGASSERT(backend); - - /* The SelectClientCert() hook uses this for infof() and failf() but the - handle stored in nss_setup_connect() could have already been freed. */ - backend->data = data; - - nread = PR_Recv(backend->handle, buf, (int)buffersize, 0, - PR_INTERVAL_NO_WAIT); - if(nread < 0) { - /* failed SSL read */ - PRInt32 err = PR_GetError(); - - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(data, "SSL read: errno %d (%s)", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_RECV_ERROR; - } - - return -1; - } - - return nread; -} - -static size_t nss_version(char *buffer, size_t size) -{ - return msnprintf(buffer, size, "NSS/%s", NSS_GetVersion()); -} - -/* data might be NULL */ -static int Curl_nss_seed(struct Curl_easy *data) -{ - /* make sure that NSS is initialized */ - return !!Curl_nss_force_init(data); -} - -/* data might be NULL */ -static CURLcode nss_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length) -{ - Curl_nss_seed(data); /* Initiate the seed if not already done */ - - if(SECSuccess != PK11_GenerateRandom(entropy, curlx_uztosi(length))) - /* signal a failure */ - return CURLE_FAILED_INIT; - - return CURLE_OK; -} - -static CURLcode nss_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len) -{ - PK11Context *SHA256pw = PK11_CreateDigestContext(SEC_OID_SHA256); - unsigned int SHA256out; - - if(!SHA256pw) - return CURLE_NOT_BUILT_IN; - - PK11_DigestOp(SHA256pw, tmp, curlx_uztoui(tmplen)); - PK11_DigestFinal(SHA256pw, sha256sum, &SHA256out, curlx_uztoui(sha256len)); - PK11_DestroyContext(SHA256pw, PR_TRUE); - - return CURLE_OK; -} - -static bool nss_cert_status_request(void) -{ -#ifdef SSL_ENABLE_OCSP_STAPLING - return TRUE; -#else - return FALSE; -#endif -} - -static bool nss_false_start(void) -{ -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ - return TRUE; -#else - return FALSE; -#endif -} - -static void *nss_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - (void)info; - DEBUGASSERT(backend); - return backend->handle; -} - -static bool nss_attach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - - if(!connssl->backend->data) - connssl->backend->data = data; - return TRUE; -} - -static void nss_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - - if(connssl->backend->data == data) - connssl->backend->data = NULL; -} - -const struct Curl_ssl Curl_ssl_nss = { - { CURLSSLBACKEND_NSS, "nss" }, /* info */ - - SSLSUPP_CA_PATH | - SSLSUPP_CERTINFO | - SSLSUPP_PINNEDPUBKEY | - SSLSUPP_HTTPS_PROXY, - - sizeof(struct ssl_backend_data), - - nss_init, /* init */ - nss_cleanup, /* cleanup */ - nss_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - /* NSS has no shutdown function provided and thus always fail */ - Curl_none_shutdown, /* shutdown */ - nss_data_pending, /* data_pending */ - nss_random, /* random */ - nss_cert_status_request, /* cert_status_request */ - nss_connect, /* connect */ - nss_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ - nss_get_internals, /* get_internals */ - nss_close, /* close_one */ - Curl_none_close_all, /* close_all */ - /* NSS has its own session ID cache */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - nss_false_start, /* false_start */ - nss_sha256sum, /* sha256sum */ - nss_attach_data, /* associate_connection */ - nss_detach_data, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ - nss_recv, /* recv decrypted data */ - nss_send, /* send data to encrypt */ -}; - -#endif /* USE_NSS */ diff --git a/Utilities/cmcurl/lib/vtls/nssg.h b/Utilities/cmcurl/lib/vtls/nssg.h deleted file mode 100644 index ad7eef58012..00000000000 --- a/Utilities/cmcurl/lib/vtls/nssg.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef HEADER_CURL_NSSG_H -#define HEADER_CURL_NSSG_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" - -#ifdef USE_NSS -/* - * This header should only be needed to get included by vtls.c and nss.c - */ - -#include "urldata.h" - -/* initialize NSS library if not already */ -CURLcode Curl_nss_force_init(struct Curl_easy *data); - -extern const struct Curl_ssl Curl_ssl_nss; - -#endif /* USE_NSS */ -#endif /* HEADER_CURL_NSSG_H */ diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c index 1c6c786d374..76919b611c0 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.c +++ b/Utilities/cmcurl/lib/vtls/openssl.c @@ -27,14 +27,14 @@ * but vtls.c should ever call or use these functions. */ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_QUICHE) || defined(USE_OPENSSL) #include /* Wincrypt must be included before anything that could include OpenSSL. */ -#if defined(USE_WIN32_CRYPTO) +#ifdef USE_WIN32_CRYPTO #include /* Undefine wincrypt conflicting symbols for BoringSSL. */ #undef X509_NAME @@ -45,24 +45,27 @@ #undef OCSP_RESPONSE #endif -#include "urldata.h" -#include "sendf.h" -#include "formdata.h" /* for the boundary function */ -#include "url.h" /* for the ssl config check function */ -#include "inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../formdata.h" /* for the boundary function */ +#include "../url.h" /* for the ssl config check function */ +#include "../curlx/inet_pton.h" #include "openssl.h" -#include "connect.h" -#include "slist.h" -#include "select.h" +#include "../connect.h" +#include "../slist.h" +#include "../select.h" #include "vtls.h" #include "vtls_int.h" -#include "vauth/vauth.h" +#include "vtls_scache.h" +#include "../vauth/vauth.h" #include "keylog.h" -#include "strcase.h" +#include "../strcase.h" #include "hostcheck.h" -#include "multiif.h" -#include "strerror.h" -#include "curl_printf.h" +#include "../multiif.h" +#include "../curlx/strparse.h" +#include "../strdup.h" +#include "../strerror.h" +#include "../curl_printf.h" #include #include @@ -79,23 +82,51 @@ #include #include #include +#include +#include -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP) +#ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST +#define USE_ECH_OPENSSL +#endif + +#ifdef USE_ECH_OPENSSL +# if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) +# include +# endif +#endif /* USE_ECH_OPENSSL */ + +#ifndef OPENSSL_NO_OCSP #include #endif -#if (OPENSSL_VERSION_NUMBER >= 0x0090700fL) && /* 0.9.7 or later */ \ - !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE) +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE) #define USE_OPENSSL_ENGINE #include #endif -#include "warnless.h" +#ifdef LIBRESSL_VERSION_NUMBER +# /* As of LibreSSL 2.0.0-4.0.0: OPENSSL_VERSION_NUMBER == 0x20000000L */ +# if LIBRESSL_VERSION_NUMBER < 0x2090100fL /* 2019-04-13 */ +# error "LibreSSL 2.9.1 or later required" +# endif +#elif OPENSSL_VERSION_NUMBER < 0x1000201fL /* 2015-03-19 */ +# error "OpenSSL 1.0.2a or later required" +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL && !defined(OPENSSL_NO_UI_CONSOLE) +#include +#include +/* this is used in the following conditions to make them easier to read */ +#define OPENSSL_HAS_PROVIDERS -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +static void ossl_provider_cleanup(struct Curl_easy *data); +#endif +#include "../curlx/warnless.h" + +/* The last #include files should be: */ +#include "../curl_memory.h" +#include "../memdebug.h" /* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS renegotiations when built with BoringSSL. Renegotiating is non-compliant @@ -108,57 +139,28 @@ #error "OPENSSL_VERSION_NUMBER not defined" #endif -#ifdef USE_OPENSSL_ENGINE +#if defined(USE_OPENSSL_ENGINE) || defined(OPENSSL_HAS_PROVIDERS) #include #endif -#if OPENSSL_VERSION_NUMBER >= 0x00909000L -#define SSL_METHOD_QUAL const +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +#define OSSL_UI_METHOD_CAST(x) (x) #else -#define SSL_METHOD_QUAL -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10000000L) -#define HAVE_ERR_REMOVE_THREAD_STATE 1 +#define OSSL_UI_METHOD_CAST(x) CURL_UNCONST(x) #endif -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && /* OpenSSL 1.1.0+ */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) -#define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER +#if OPENSSL_VERSION_NUMBER >= 0x10100000L /* OpenSSL 1.1.0+ and LibreSSL */ #define HAVE_X509_GET0_EXTENSIONS 1 /* added in 1.1.0 -pre1 */ #define HAVE_OPAQUE_EVP_PKEY 1 /* since 1.1.0 -pre3 */ #define HAVE_OPAQUE_RSA_DSA_DH 1 /* since 1.1.0 -pre5 */ -#define CONST_EXTS const #define HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED 1 - -/* funny typecast define due to difference in API */ -#ifdef LIBRESSL_VERSION_NUMBER -#define ARG2_X509_signature_print (X509_ALGOR *) -#else -#define ARG2_X509_signature_print -#endif - #else /* For OpenSSL before 1.1.0 */ #define ASN1_STRING_get0_data(x) ASN1_STRING_data(x) #define X509_get0_notBefore(x) X509_get_notBefore(x) #define X509_get0_notAfter(x) X509_get_notAfter(x) -#define CONST_EXTS /* nope */ -#ifndef LIBRESSL_VERSION_NUMBER #define OpenSSL_version_num() SSLeay() #endif -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* 1.0.2 or later */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) -#define HAVE_X509_GET0_SIGNATURE 1 -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) /* 1.0.2 or later */ -#define HAVE_SSL_GET_SHUTDOWN 1 -#endif #if OPENSSL_VERSION_NUMBER >= 0x10002003L && \ OPENSSL_VERSION_NUMBER <= 0x10002FFFL && \ @@ -166,15 +168,8 @@ #define HAVE_SSL_COMP_FREE_COMPRESSION_METHODS 1 #endif -#if (OPENSSL_VERSION_NUMBER < 0x0090808fL) -/* not present in older OpenSSL */ -#define OPENSSL_load_builtin_modules(x) -#endif - #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) #define HAVE_EVP_PKEY_GET_PARAMS 1 -#else -#define SSL_get1_peer_certificate SSL_get_peer_certificate #endif #ifdef HAVE_EVP_PKEY_GET_PARAMS @@ -186,64 +181,59 @@ #define FREE_PKEY_PARAM_BIGNUM(name) #endif -/* - * Whether SSL_CTX_set_keylog_callback is available. - * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 - * BoringSSL: supported since d28f59c27bac (committed 2015-11-19) - * LibreSSL: unsupported in at least 2.7.2 (explicitly check for it since it - * lies and pretends to be OpenSSL 2.0.0). - */ -#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ - !defined(LIBRESSL_VERSION_NUMBER)) || \ - defined(OPENSSL_IS_BORINGSSL) -#define HAVE_KEYLOG_CALLBACK -#endif - /* Whether SSL_CTX_set_ciphersuites is available. * OpenSSL: supported since 1.1.1 (commit a53b5be6a05) * BoringSSL: no - * LibreSSL: no + * LibreSSL: supported since 3.4.1 (released 2021-10-14) */ -#if ((OPENSSL_VERSION_NUMBER >= 0x10101000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) && \ - !defined(OPENSSL_IS_BORINGSSL)) +#if ((OPENSSL_VERSION_NUMBER >= 0x10101000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER >= 0x3040100fL)) && \ + !defined(OPENSSL_IS_BORINGSSL) #define HAVE_SSL_CTX_SET_CIPHERSUITES - #if !defined(OPENSSL_IS_AWSLC) + #ifndef OPENSSL_IS_AWSLC #define HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH #endif #endif -/* - * Whether SSL_CTX_set1_curves_list is available. - * OpenSSL: supported since 1.0.2, see - * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_groups.html - * BoringSSL: supported since 5fd1807d95f7 (committed 2016-09-30) - * LibreSSL: since 2.5.3 (April 12, 2017) +/* Whether SSL_CTX_set1_sigalgs_list is available + * OpenSSL: supported since 1.0.2 (commit 0b362de5f575) + * BoringSSL: supported since 0.20240913.0 (commit 826ce15) + * LibreSSL: no */ -#if ((OPENSSL_VERSION_NUMBER >= 0x10002000L) && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20503000L)) || \ - defined(OPENSSL_IS_BORINGSSL) -#define HAVE_SSL_CTX_SET_EC_CURVES +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) + #define HAVE_SSL_CTX_SET1_SIGALGS #endif -#if defined(LIBRESSL_VERSION_NUMBER) +#ifdef LIBRESSL_VERSION_NUMBER #define OSSL_PACKAGE "LibreSSL" #elif defined(OPENSSL_IS_BORINGSSL) #define OSSL_PACKAGE "BoringSSL" #elif defined(OPENSSL_IS_AWSLC) #define OSSL_PACKAGE "AWS-LC" +#elif (defined(USE_NGTCP2) && defined(USE_NGHTTP3) && \ + !defined(OPENSSL_QUIC_API2)) || defined(USE_MSH3) +#define OSSL_PACKAGE "quictls" #else #define OSSL_PACKAGE "OpenSSL" #endif +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef size_t numcert_t; +#else +typedef int numcert_t; +#endif +#define ossl_valsize_t numcert_t + #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* up2date versions of OpenSSL maintain reasonably secure defaults without * breaking compatibility, so it is better not to override the defaults in curl */ #define DEFAULT_CIPHER_SELECTION NULL #else -/* ... but it is not the case with old versions of OpenSSL */ +/* not the case with old versions of OpenSSL */ #define DEFAULT_CIPHER_SELECTION \ "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH" #endif @@ -261,14 +251,12 @@ #endif #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x2070100fL) && \ !defined(OPENSSL_IS_BORINGSSL) && \ !defined(OPENSSL_IS_AWSLC) #define HAVE_OPENSSL_VERSION #endif -#ifdef OPENSSL_IS_BORINGSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) typedef uint32_t sslerr_t; #else typedef unsigned long sslerr_t; @@ -283,64 +271,47 @@ typedef unsigned long sslerr_t; #define HAVE_SSL_X509_STORE_SHARE #endif -/* FIXME: On a specific machine using LCC 1.23, OpenSSL 2.0.0 - * is found but does not seem to have X509_STORE_up_ref. */ -#if defined(__LCC__) && defined(__EDG__) && (__LCC__ == 123) -#undef HAVE_SSL_X509_STORE_SHARE +#if (OPENSSL_VERSION_NUMBER > 0x10100000L) /* OpenSSL > 1.1.0 */ +#define HAVE_SSL_X509_GET_SIGNATURE_NID #endif -/* What API version do we use? */ -#if defined(LIBRESSL_VERSION_NUMBER) -#define USE_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f) -#else /* !LIBRESSL_VERSION_NUMBER */ -#define USE_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) -#endif /* !LIBRESSL_VERSION_NUMBER */ - -struct ssl_backend_data { - /* these ones requires specific SSL-types */ - SSL_CTX* ctx; - SSL* handle; - X509* server_cert; - BIO_METHOD *bio_method; - CURLcode io_result; /* result of last BIO cfilter operation */ -#ifndef HAVE_KEYLOG_CALLBACK - /* Set to true once a valid keylog entry has been created to avoid dupes. */ - bool keylog_done; +/* FIXME: On LCC <= 1.23, OpenSSL 2.0.0 may be found but does not seem to + * have X509_STORE_up_ref or X509_get_signature_nid. */ +#if defined(__LCC__) && defined(__EDG__) && (__LCC__ <= 123) +#undef HAVE_SSL_X509_STORE_SHARE +#undef HAVE_SSL_X509_GET_SIGNATURE_NID #endif - bool x509_store_setup; /* x509 store has been set up */ -}; -#if defined(HAVE_SSL_X509_STORE_SHARE) -struct multi_ssl_backend_data { - char *CAfile; /* CAfile 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 */ -}; -#endif /* HAVE_SSL_X509_STORE_SHARE */ +static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl); -#define push_certinfo(_label, _num) \ -do { \ - long info_len = BIO_get_mem_data(mem, &ptr); \ - Curl_ssl_push_certinfo_len(data, _num, _label, ptr, info_len); \ - if(1 != BIO_reset(mem)) \ - break; \ -} while(0) +static CURLcode push_certinfo(struct Curl_easy *data, + BIO *mem, const char *label, int num) + WARN_UNUSED_RESULT; -static void pubkey_show(struct Curl_easy *data, - BIO *mem, - int num, - const char *type, - const char *name, - const BIGNUM *bn) +static CURLcode push_certinfo(struct Curl_easy *data, + BIO *mem, const char *label, int num) { char *ptr; + long len = BIO_get_mem_data(mem, &ptr); + CURLcode result = Curl_ssl_push_certinfo_len(data, num, label, ptr, len); + (void)BIO_reset(mem); + return result; +} + +static CURLcode pubkey_show(struct Curl_easy *data, + BIO *mem, + int num, + const char *type, + const char *name, + const BIGNUM *bn) +{ char namebuf[32]; msnprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); if(bn) BN_print(mem, bn); - push_certinfo(namebuf, num); + return push_certinfo(data, mem, namebuf, num); } #ifdef HAVE_OPAQUE_RSA_DSA_DH @@ -372,25 +343,32 @@ static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) return 0; } -static void X509V3_ext(struct Curl_easy *data, - int certnum, - CONST_EXTS STACK_OF(X509_EXTENSION) *exts) +static CURLcode X509V3_ext(struct Curl_easy *data, + int certnum, + const STACK_OF(X509_EXTENSION) *extsarg) { int i; + CURLcode result = CURLE_OK; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER) + const STACK_OF(X509_EXTENSION) *exts = extsarg; +#else + STACK_OF(X509_EXTENSION) *exts = CURL_UNCONST(extsarg); +#endif if((int)sk_X509_EXTENSION_num(exts) <= 0) /* no extensions, bail out */ - return; + return result; for(i = 0; i < (int)sk_X509_EXTENSION_num(exts); i++) { ASN1_OBJECT *obj; - X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i); + X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, (ossl_valsize_t)i); BUF_MEM *biomem; char namebuf[128]; BIO *bio_out = BIO_new(BIO_s_mem()); if(!bio_out) - return; + return result; obj = X509_EXTENSION_get_object(ext); @@ -400,19 +378,16 @@ static void X509V3_ext(struct Curl_easy *data, ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext)); BIO_get_mem_ptr(bio_out, &biomem); - Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data, - biomem->length); + result = Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data, + biomem->length); BIO_free(bio_out); + if(result) + break; } + return result; } -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) -typedef size_t numcert_t; -#else -typedef int numcert_t; -#endif - -CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) +static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) { CURLcode result; STACK_OF(X509) *sk; @@ -430,40 +405,45 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) numcerts = sk_X509_num(sk); result = Curl_ssl_init_certinfo(data, (int)numcerts); - if(result) { + if(result) return result; - } mem = BIO_new(BIO_s_mem()); - if(!mem) { - return CURLE_OUT_OF_MEMORY; - } + if(!mem) + result = CURLE_OUT_OF_MEMORY; - for(i = 0; i < (int)numcerts; i++) { + for(i = 0; !result && (i < (int)numcerts); i++) { ASN1_INTEGER *num; - X509 *x = sk_X509_value(sk, i); + X509 *x = sk_X509_value(sk, (ossl_valsize_t)i); EVP_PKEY *pubkey = NULL; int j; - char *ptr; const ASN1_BIT_STRING *psig = NULL; X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Subject", i); + result = push_certinfo(data, mem, "Subject", i); + if(result) + break; X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Issuer", i); + result = push_certinfo(data, mem, "Issuer", i); + if(result) + break; BIO_printf(mem, "%lx", X509_get_version(x)); - push_certinfo("Version", i); + result = push_certinfo(data, mem, "Version", i); + if(result) + break; num = X509_get_serialNumber(x); if(num->type == V_ASN1_NEG_INTEGER) BIO_puts(mem, "-"); for(j = 0; j < num->length; j++) BIO_printf(mem, "%02x", num->data[j]); - push_certinfo("Serial Number", i); + result = push_certinfo(data, mem, "Serial Number", i); + if(result) + break; -#if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS) +#ifdef HAVE_X509_GET0_EXTENSIONS { const X509_ALGOR *sigalg = NULL; X509_PUBKEY *xpubkey = NULL; @@ -471,8 +451,12 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) X509_get0_signature(&psig, &sigalg, x); if(sigalg) { - i2a_ASN1_OBJECT(mem, sigalg->algorithm); - push_certinfo("Signature Algorithm", i); + const ASN1_OBJECT *sigalgoid = NULL; + X509_ALGOR_get0(&sigalgoid, NULL, NULL, sigalg); + i2a_ASN1_OBJECT(mem, sigalgoid); + result = push_certinfo(data, mem, "Signature Algorithm", i); + if(result) + break; } xpubkey = X509_get_X509_PUBKEY(x); @@ -480,11 +464,15 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) X509_PUBKEY_get0_param(&pubkeyoid, NULL, NULL, NULL, xpubkey); if(pubkeyoid) { i2a_ASN1_OBJECT(mem, pubkeyoid); - push_certinfo("Public Key Algorithm", i); + result = push_certinfo(data, mem, "Public Key Algorithm", i); + if(result) + break; } } - X509V3_ext(data, i, X509_get0_extensions(x)); + result = X509V3_ext(data, i, X509_get0_extensions(x)); + if(result) + break; } #else { @@ -492,22 +480,32 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) X509_CINF *cinf = x->cert_info; i2a_ASN1_OBJECT(mem, cinf->signature->algorithm); - push_certinfo("Signature Algorithm", i); + result = push_certinfo(data, mem, "Signature Algorithm", i); + + if(!result) { + i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); + result = push_certinfo(data, mem, "Public Key Algorithm", i); + } - i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); - push_certinfo("Public Key Algorithm", i); + if(!result) + result = X509V3_ext(data, i, cinf->extensions); - X509V3_ext(data, i, cinf->extensions); + if(result) + break; psig = x->signature; } #endif ASN1_TIME_print(mem, X509_get0_notBefore(x)); - push_certinfo("Start date", i); + result = push_certinfo(data, mem, "Start date", i); + if(result) + break; ASN1_TIME_print(mem, X509_get0_notAfter(x)); - push_certinfo("Expire date", i); + result = push_certinfo(data, mem, "Expire date", i); + if(result) + break; pubkey = X509_get_pubkey(x); if(!pubkey) @@ -520,8 +518,7 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) pktype = pubkey->type; #endif switch(pktype) { - case EVP_PKEY_RSA: - { + case EVP_PKEY_RSA: { #ifndef HAVE_EVP_PKEY_GET_PARAMS RSA *rsa; #ifdef HAVE_OPAQUE_EVP_PKEY @@ -541,11 +538,13 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) #else RSA_get0_key(rsa, &n, &e, NULL); #endif /* HAVE_EVP_PKEY_GET_PARAMS */ - BIO_printf(mem, "%d", BN_num_bits(n)); + BIO_printf(mem, "%d", n ? BN_num_bits(n) : 0); #else - BIO_printf(mem, "%d", BN_num_bits(rsa->n)); + BIO_printf(mem, "%d", rsa->n ? BN_num_bits(rsa->n) : 0); #endif /* HAVE_OPAQUE_RSA_DSA_DH */ - push_certinfo("RSA Public Key", i); + result = push_certinfo(data, mem, "RSA Public Key", i); + if(result) + break; print_pubkey_BN(rsa, n, i); print_pubkey_BN(rsa, e, i); FREE_PKEY_PARAM_BIGNUM(n); @@ -593,8 +592,7 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) #endif /* !OPENSSL_NO_DSA */ break; } - case EVP_PKEY_DH: - { + case EVP_PKEY_DH: { #ifndef HAVE_EVP_PKEY_GET_PARAMS DH *dh; #ifdef HAVE_OPAQUE_EVP_PKEY @@ -637,54 +635,58 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) EVP_PKEY_free(pubkey); } - if(psig) { + if(!result && psig) { for(j = 0; j < psig->length; j++) BIO_printf(mem, "%02x:", psig->data[j]); - push_certinfo("Signature", i); + result = push_certinfo(data, mem, "Signature", i); } - PEM_write_bio_X509(mem, x); - push_certinfo("Cert", i); + if(!result) { + PEM_write_bio_X509(mem, x); + result = push_certinfo(data, mem, "Cert", i); + } } BIO_free(mem); - return CURLE_OK; + if(result) + /* cleanup all leftovers */ + Curl_ssl_free_certinfo(data); + + return result; } #endif /* quiche or OpenSSL */ #ifdef USE_OPENSSL -#if USE_PRE_1_1_API -#if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x2070000fL +#if OPENSSL_VERSION_NUMBER < 0x10100000L #define BIO_set_init(x,v) ((x)->init=(v)) #define BIO_get_data(x) ((x)->ptr) #define BIO_set_data(x,v) ((x)->ptr=(v)) -#endif #define BIO_get_shutdown(x) ((x)->shutdown) #define BIO_set_shutdown(x,v) ((x)->shutdown=(v)) -#endif /* USE_PRE_1_1_API */ +#endif /* HAVE_PRE_1_1_API */ -static int bio_cf_create(BIO *bio) +static int ossl_bio_cf_create(BIO *bio) { BIO_set_shutdown(bio, 1); BIO_set_init(bio, 1); -#if USE_PRE_1_1_API +#if OPENSSL_VERSION_NUMBER < 0x10100000L bio->num = -1; #endif BIO_set_data(bio, NULL); return 1; } -static int bio_cf_destroy(BIO *bio) +static int ossl_bio_cf_destroy(BIO *bio) { if(!bio) return 0; return 1; } -static long bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) +static long ossl_bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) { struct Curl_cfilter *cf = BIO_get_data(bio); long ret = 1; @@ -707,9 +709,11 @@ static long bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) ret = 1; break; #ifdef BIO_CTRL_EOF - case BIO_CTRL_EOF: + case BIO_CTRL_EOF: { /* EOF has been reached on input? */ - return (!cf->next || !cf->next->connected); + struct ssl_connect_data *connssl = cf->ctx; + return connssl->peer_closed; + } #endif default: ret = 0; @@ -718,20 +722,25 @@ static long bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) return ret; } -static int bio_cf_out_write(BIO *bio, const char *buf, int blen) +static int ossl_bio_cf_out_write(BIO *bio, const char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result = CURLE_SEND_ERROR; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); - DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%d) -> %d, err=%d", - blen, (int)nwritten, result)); + if(blen < 0) + return 0; + + nwritten = Curl_conn_cf_send(cf->next, data, buf, (size_t)blen, FALSE, + &result); + CURL_TRC_CF(data, cf, "ossl_bio_cf_out_write(len=%d) -> %d, err=%d", + blen, (int)nwritten, result); BIO_clear_retry_flags(bio); - connssl->backend->io_result = result; + octx->io_result = result; if(nwritten < 0) { if(CURLE_AGAIN == result) BIO_set_retry_write(bio); @@ -739,10 +748,11 @@ static int bio_cf_out_write(BIO *bio, const char *buf, int blen) return (int)nwritten; } -static int bio_cf_in_read(BIO *bio, char *buf, int blen) +static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_RECV_ERROR; @@ -751,69 +761,74 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen) /* OpenSSL catches this case, so should we. */ if(!buf) return 0; + if(blen < 0) + return 0; - nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); - DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%d) -> %d, err=%d", - blen, (int)nread, result)); + nread = Curl_conn_cf_recv(cf->next, data, buf, (size_t)blen, &result); + CURL_TRC_CF(data, cf, "ossl_bio_cf_in_read(len=%d) -> %d, err=%d", + blen, (int)nread, result); BIO_clear_retry_flags(bio); - connssl->backend->io_result = result; + octx->io_result = result; if(nread < 0) { if(CURLE_AGAIN == result) BIO_set_retry_read(bio); } + else if(nread == 0) { + connssl->peer_closed = TRUE; + } /* Before returning server replies to the SSL instance, we need * to have setup the x509 store or verification will fail. */ - if(!connssl->backend->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, connssl->backend->ctx); + if(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) { - connssl->backend->io_result = result; + octx->io_result = result; return -1; } - connssl->backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } return (int)nread; } -#if USE_PRE_1_1_API +#if OPENSSL_VERSION_NUMBER < 0x10100000L -static BIO_METHOD bio_cf_meth_1_0 = { +static BIO_METHOD ossl_bio_cf_meth_1_0 = { BIO_TYPE_MEM, "OpenSSL CF BIO", - bio_cf_out_write, - bio_cf_in_read, + ossl_bio_cf_out_write, + ossl_bio_cf_in_read, NULL, /* puts is never called */ NULL, /* gets is never called */ - bio_cf_ctrl, - bio_cf_create, - bio_cf_destroy, + ossl_bio_cf_ctrl, + ossl_bio_cf_create, + ossl_bio_cf_destroy, NULL }; -static BIO_METHOD *bio_cf_method_create(void) +static BIO_METHOD *ossl_bio_cf_method_create(void) { - return &bio_cf_meth_1_0; + return &ossl_bio_cf_meth_1_0; } -#define bio_cf_method_free(m) Curl_nop_stmt +#define ossl_bio_cf_method_free(m) Curl_nop_stmt #else -static BIO_METHOD *bio_cf_method_create(void) +static BIO_METHOD *ossl_bio_cf_method_create(void) { BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO"); if(m) { - BIO_meth_set_write(m, &bio_cf_out_write); - BIO_meth_set_read(m, &bio_cf_in_read); - BIO_meth_set_ctrl(m, &bio_cf_ctrl); - BIO_meth_set_create(m, &bio_cf_create); - BIO_meth_set_destroy(m, &bio_cf_destroy); + BIO_meth_set_write(m, &ossl_bio_cf_out_write); + BIO_meth_set_read(m, &ossl_bio_cf_in_read); + BIO_meth_set_ctrl(m, &ossl_bio_cf_ctrl); + BIO_meth_set_create(m, &ossl_bio_cf_create); + BIO_meth_set_destroy(m, &ossl_bio_cf_destroy); } return m; } -static void bio_cf_method_free(BIO_METHOD *m) +static void ossl_bio_cf_method_free(BIO_METHOD *m) { if(m) BIO_meth_free(m); @@ -822,14 +837,6 @@ static void bio_cf_method_free(BIO_METHOD *m) #endif -/* - * Number of bytes to read from the random number seed file. This must be - * a finite value (because some entropy "files" like /dev/urandom have - * an infinite length), but must be large enough to provide enough - * entropy to properly seed OpenSSL's PRNG. - */ -#define RAND_LOAD_LENGTH 1024 - #ifdef HAVE_KEYLOG_CALLBACK static void ossl_keylog_callback(const SSL *ssl, const char *line) { @@ -840,7 +847,7 @@ static void ossl_keylog_callback(const SSL *ssl, const char *line) #else /* * ossl_log_tls12_secret is called by libcurl to make the CLIENT_RANDOMs if the - * OpenSSL being used doesn't have native support for doing that. + * OpenSSL being used does not have native support for doing that. */ static void ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) @@ -853,10 +860,8 @@ ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) if(!session || *keylog_done) return; -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) - /* ssl->s3 is not checked in openssl 1.1.0-pre6, but let's assume that +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + /* ssl->s3 is not checked in OpenSSL 1.1.0-pre6, but let's assume that * we have a valid SSL context if we have a non-NULL session. */ SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE); master_key_length = (int) @@ -875,7 +880,7 @@ ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) if(master_key_length <= 0) return; - *keylog_done = true; + *keylog_done = TRUE; Curl_tls_keylog_write("CLIENT_RANDOM", client_random, master_key, master_key_length); } @@ -902,15 +907,15 @@ static const char *SSL_ERROR_to_str(int err) return "SSL_ERROR_WANT_CONNECT"; case SSL_ERROR_WANT_ACCEPT: return "SSL_ERROR_WANT_ACCEPT"; -#if defined(SSL_ERROR_WANT_ASYNC) +#ifdef SSL_ERROR_WANT_ASYNC case SSL_ERROR_WANT_ASYNC: return "SSL_ERROR_WANT_ASYNC"; #endif -#if defined(SSL_ERROR_WANT_ASYNC_JOB) +#ifdef SSL_ERROR_WANT_ASYNC_JOB case SSL_ERROR_WANT_ASYNC_JOB: return "SSL_ERROR_WANT_ASYNC_JOB"; #endif -#if defined(SSL_ERROR_WANT_EARLY) +#ifdef SSL_ERROR_WANT_EARLY case SSL_ERROR_WANT_EARLY: return "SSL_ERROR_WANT_EARLY"; #endif @@ -919,8 +924,6 @@ static const char *SSL_ERROR_to_str(int err) } } -static size_t ossl_version(char *buffer, size_t size); - /* Return error string for last OpenSSL error */ static char *ossl_strerror(unsigned long error, char *buf, size_t size) @@ -929,7 +932,7 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size) DEBUGASSERT(size); *buf = '\0'; - len = ossl_version(buf, size); + len = Curl_ossl_version(buf, size); DEBUGASSERT(len < (size - 2)); if(len < (size - 2)) { buf += len; @@ -946,22 +949,23 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size) #endif if(!*buf) { - strncpy(buf, (error ? "Unknown error" : "No error"), size); - buf[size - 1] = '\0'; + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); } return buf; } static int passwd_callback(char *buf, int num, int encrypting, - void *global_passwd) + void *password) { DEBUGASSERT(0 == encrypting); - if(!encrypting) { - int klen = curlx_uztosi(strlen((char *)global_passwd)); + if(!encrypting && num >= 0 && password) { + int klen = curlx_uztosi(strlen((char *)password)); if(num > klen) { - memcpy(buf, global_passwd, klen + 1); + memcpy(buf, password, klen + 1); return klen; } } @@ -973,7 +977,7 @@ static int passwd_callback(char *buf, int num, int encrypting, */ static bool rand_enough(void) { - return (0 != RAND_status()) ? TRUE : FALSE; + return 0 != RAND_status(); } static CURLcode ossl_seed(struct Curl_easy *data) @@ -994,26 +998,6 @@ static CURLcode ossl_seed(struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; #else -#ifdef RANDOM_FILE - RAND_load_file(RANDOM_FILE, RAND_LOAD_LENGTH); - if(rand_enough()) - return CURLE_OK; -#endif - -#if defined(HAVE_RAND_EGD) && defined(EGD_SOCKET) - /* available in OpenSSL 0.9.5 and later */ - /* EGD_SOCKET is set at configure time or not at all */ - { - /* If there's an option and a define, the option overrides the - define */ - int ret = RAND_egd(EGD_SOCKET); - if(-1 != ret) { - if(rand_enough()) - return CURLE_OK; - } - } -#endif - /* fallback to a custom seeding of the PRNG using a hash based on a current time */ do { @@ -1021,28 +1005,35 @@ static CURLcode ossl_seed(struct Curl_easy *data) size_t len = sizeof(randb); size_t i, i_max; for(i = 0, i_max = len / sizeof(struct curltime); i < i_max; ++i) { - struct curltime tv = Curl_now(); + struct curltime tv = curlx_now(); Curl_wait_ms(1); - tv.tv_sec *= i + 1; - tv.tv_usec *= (unsigned int)i + 2; - tv.tv_sec ^= ((Curl_now().tv_sec + Curl_now().tv_usec) * - (i + 3)) << 8; - tv.tv_usec ^= (unsigned int) ((Curl_now().tv_sec + - Curl_now().tv_usec) * - (i + 4)) << 16; + tv.tv_sec *= (time_t)i + 1; + tv.tv_usec *= (int)i + 2; + tv.tv_sec ^= ((curlx_now().tv_sec + (time_t)curlx_now().tv_usec) * + (time_t)(i + 3)) << 8; + tv.tv_usec ^= (int) ((curlx_now().tv_sec + (time_t)curlx_now().tv_usec) * + (time_t)(i + 4)) << 16; memcpy(&randb[i * sizeof(struct curltime)], &tv, sizeof(struct curltime)); } RAND_add(randb, (int)len, (double)len/2); } while(!rand_enough()); + /* + * Number of bytes to read from the random number seed file. This must be + * a finite value (because some entropy "files" like /dev/urandom have + * an infinite length), but must be large enough to provide enough + * entropy to properly seed OpenSSL's PRNG. + */ +# define RAND_LOAD_LENGTH 1024 + { /* generates a default path for the random seed file */ char fname[256]; fname[0] = 0; /* blank it first */ RAND_file_name(fname, sizeof(fname)); if(fname[0]) { - /* we got a file name to try */ + /* we got a filename to try */ RAND_load_file(fname, RAND_LOAD_LENGTH); if(rand_enough()) return CURLE_OK; @@ -1050,8 +1041,8 @@ static CURLcode ossl_seed(struct Curl_easy *data) } infof(data, "libcurl is now using a weak random seed"); - return (rand_enough() ? CURLE_OK : - CURLE_SSL_CONNECT_ERROR /* confusing error code */); + return rand_enough() ? CURLE_OK : + CURLE_SSL_CONNECT_ERROR; /* confusing error code */ #endif } @@ -1061,7 +1052,10 @@ static CURLcode ossl_seed(struct Curl_easy *data) #ifndef SSL_FILETYPE_PKCS12 #define SSL_FILETYPE_PKCS12 43 #endif -static int do_file_type(const char *type) +#ifndef SSL_FILETYPE_PROVIDER +#define SSL_FILETYPE_PROVIDER 44 +#endif +static int ossl_do_file_type(const char *type) { if(!type || !type[0]) return SSL_FILETYPE_PEM; @@ -1069,6 +1063,8 @@ static int do_file_type(const char *type) return SSL_FILETYPE_PEM; if(strcasecompare(type, "DER")) return SSL_FILETYPE_ASN1; + if(strcasecompare(type, "PROV")) + return SSL_FILETYPE_PROVIDER; if(strcasecompare(type, "ENG")) return SSL_FILETYPE_ENGINE; if(strcasecompare(type, "P12")) @@ -1076,7 +1072,7 @@ static int do_file_type(const char *type) return -1; } -#ifdef USE_OPENSSL_ENGINE +#if defined(USE_OPENSSL_ENGINE) || defined(OPENSSL_HAS_PROVIDERS) /* * Supply default password to the engine user interface conversation. * The password is passed by OpenSSL engine from ENGINE_load_private_key() @@ -1093,6 +1089,7 @@ static int ssl_ui_reader(UI *ui, UI_STRING *uis) UI_set_result(ui, uis, password); return 1; } + FALLTHROUGH(); default: break; } @@ -1111,6 +1108,7 @@ static int ssl_ui_writer(UI *ui, UI_STRING *uis) (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) { return 1; } + FALLTHROUGH(); default: break; } @@ -1122,16 +1120,19 @@ static int ssl_ui_writer(UI *ui, UI_STRING *uis) */ static bool is_pkcs11_uri(const char *string) { - return (string && strncasecompare(string, "pkcs11:", 7)); + return string && strncasecompare(string, "pkcs11:", 7); } #endif static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine); +#if defined(OPENSSL_HAS_PROVIDERS) +static CURLcode ossl_set_provider(struct Curl_easy *data, + const char *provider); +#endif -static int -SSL_CTX_use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, - int type, const char *key_passwd) +static int use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, + int type, const char *key_passwd) { int ret = 0; X509 *x = NULL; @@ -1148,7 +1149,7 @@ SSL_CTX_use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, else if(type == SSL_FILETYPE_PEM) { /* ERR_R_PEM_LIB; */ x = PEM_read_bio_X509(in, NULL, - passwd_callback, (void *)key_passwd); + passwd_callback, CURL_UNCONST(key_passwd)); } else { ret = 0; @@ -1167,9 +1168,8 @@ SSL_CTX_use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, return ret; } -static int -SSL_CTX_use_PrivateKey_blob(SSL_CTX *ctx, const struct curl_blob *blob, - int type, const char *key_passwd) +static int use_privatekey_blob(SSL_CTX *ctx, const struct curl_blob *blob, + int type, const char *key_passwd) { int ret = 0; EVP_PKEY *pkey = NULL; @@ -1179,17 +1179,15 @@ SSL_CTX_use_PrivateKey_blob(SSL_CTX *ctx, const struct curl_blob *blob, if(type == SSL_FILETYPE_PEM) pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, - (void *)key_passwd); + CURL_UNCONST(key_passwd)); else if(type == SSL_FILETYPE_ASN1) pkey = d2i_PrivateKey_bio(in, NULL); - else { - ret = 0; + else goto end; - } - if(!pkey) { - ret = 0; + + if(!pkey) goto end; - } + ret = SSL_CTX_use_PrivateKey(ctx, pkey); EVP_PKEY_free(pkey); end: @@ -1198,16 +1196,11 @@ SSL_CTX_use_PrivateKey_blob(SSL_CTX *ctx, const struct curl_blob *blob, } static int -SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, - const char *key_passwd) +use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, + const char *key_passwd) { -/* SSL_CTX_add1_chain_cert introduced in OpenSSL 1.0.2 */ -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* OpenSSL 1.0.2 or later */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - (LIBRESSL_VERSION_NUMBER < 0x2090100fL)) /* LibreSSL 2.9.1 or later */ int ret = 0; X509 *x = NULL; - void *passwd_callback_userdata = (void *)key_passwd; BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); if(!in) return CURLE_OUT_OF_MEMORY; @@ -1215,12 +1208,9 @@ SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, ERR_clear_error(); x = PEM_read_bio_X509_AUX(in, NULL, - passwd_callback, (void *)key_passwd); - - if(!x) { - ret = 0; + passwd_callback, CURL_UNCONST(key_passwd)); + if(!x) goto end; - } ret = SSL_CTX_use_certificate(ctx, x); @@ -1237,7 +1227,7 @@ SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, } while((ca = PEM_read_bio_X509(in, NULL, passwd_callback, - passwd_callback_userdata)) + CURL_UNCONST(key_passwd))) != NULL) { if(!SSL_CTX_add0_chain_cert(ctx, ca)) { @@ -1259,12 +1249,6 @@ SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, X509_free(x); BIO_free(in); return ret; -#else - (void)ctx; /* unused */ - (void)blob; /* unused */ - (void)key_passwd; /* unused */ - return 0; -#endif } static @@ -1281,9 +1265,10 @@ int cert_stuff(struct Curl_easy *data, char error_buffer[256]; bool check_privkey = TRUE; - int file_type = do_file_type(cert_type); + int file_type = ossl_do_file_type(cert_type); - if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE)) { + if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE) || + (file_type == SSL_FILETYPE_PROVIDER)) { SSL *ssl; X509 *x509; int cert_done = 0; @@ -1301,7 +1286,7 @@ int cert_stuff(struct Curl_easy *data, case SSL_FILETYPE_PEM: /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ cert_use_result = cert_blob ? - SSL_CTX_use_certificate_chain_blob(ctx, cert_blob, key_passwd) : + use_certificate_chain_blob(ctx, cert_blob, key_passwd) : SSL_CTX_use_certificate_chain_file(ctx, cert_file); if(cert_use_result != 1) { failf(data, @@ -1321,8 +1306,7 @@ int cert_stuff(struct Curl_easy *data, ASN1 files. */ cert_use_result = cert_blob ? - SSL_CTX_use_certificate_blob(ctx, cert_blob, - file_type, key_passwd) : + use_certificate_blob(ctx, cert_blob, file_type, key_passwd) : SSL_CTX_use_certificate_file(ctx, cert_file, file_type); if(cert_use_result != 1) { failf(data, @@ -1360,7 +1344,7 @@ int cert_stuff(struct Curl_easy *data, /* Does the engine supports LOAD_CERT_CTRL ? */ if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME, - 0, (void *)cmd_name, NULL)) { + 0, CURL_UNCONST(cmd_name), NULL)) { failf(data, "ssl engine does not support loading certificates"); return 0; } @@ -1376,7 +1360,7 @@ int cert_stuff(struct Curl_easy *data, } if(!params.cert) { - failf(data, "ssl engine didn't initialized the certificate " + failf(data, "ssl engine did not initialized the certificate " "properly."); return 0; } @@ -1387,17 +1371,78 @@ int cert_stuff(struct Curl_easy *data, sizeof(error_buffer))); return 0; } - X509_free(params.cert); /* we don't need the handle any more... */ + X509_free(params.cert); /* we do not need the handle any more... */ } else { - failf(data, "crypto engine not set, can't load certificate"); + failf(data, "crypto engine not set, cannot load certificate"); + return 0; + } + } + break; +#endif +#if defined(OPENSSL_HAS_PROVIDERS) + /* fall through to compatible provider */ + case SSL_FILETYPE_PROVIDER: + { + /* Implicitly use pkcs11 provider if none was provided and the + * cert_file is a PKCS#11 URI */ + if(!data->state.provider) { + if(is_pkcs11_uri(cert_file)) { + if(ossl_set_provider(data, "pkcs11") != CURLE_OK) { + return 0; + } + } + } + + if(data->state.provider) { + /* Load the certificate from the provider */ + OSSL_STORE_INFO *info = NULL; + X509 *cert = NULL; + OSSL_STORE_CTX *store = + OSSL_STORE_open_ex(cert_file, data->state.libctx, + NULL, NULL, NULL, NULL, NULL, NULL); + if(!store) { + failf(data, "Failed to open OpenSSL store: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return 0; + } + if(OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) != 1) { + failf(data, "Failed to set store preference. Ignoring the error: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + } + + info = OSSL_STORE_load(store); + if(info) { + int ossl_type = OSSL_STORE_INFO_get_type(info); + + if(ossl_type == OSSL_STORE_INFO_CERT) + cert = OSSL_STORE_INFO_get1_CERT(info); + OSSL_STORE_INFO_free(info); + } + OSSL_STORE_close(store); + if(!cert) { + failf(data, "No cert found in the openssl store: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return 0; + } + + if(SSL_CTX_use_certificate(ctx, cert) != 1) { + failf(data, "unable to set client certificate [%s]", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return 0; + } + X509_free(cert); /* we do not need the handle any more... */ + } + else { + failf(data, "crypto provider not set, cannot load certificate"); return 0; } } break; -#else - failf(data, "file type ENG for certificate not implemented"); - return 0; #endif case SSL_FILETYPE_PKCS12: @@ -1446,8 +1491,7 @@ int cert_stuff(struct Curl_easy *data, PKCS12_PBE_add(); - if(!PKCS12_parse(p12, key_passwd, &pri, &x509, - &ca)) { + if(!PKCS12_parse(p12, key_passwd, &pri, &x509, &ca)) { failf(data, "could not parse PKCS12 file, check password, " OSSL_PACKAGE " error %s", @@ -1486,7 +1530,7 @@ int cert_stuff(struct Curl_easy *data, * Note that sk_X509_pop() is used below to make sure the cert is * removed from the stack properly before getting passed to * SSL_CTX_add_extra_chain_cert(), which takes ownership. Previously - * we used sk_X509_value() instead, but then we'd clean it in the + * we used sk_X509_value() instead, but then we would clean it in the * subsequent sk_X509_pop_free() call. */ X509 *x = sk_X509_pop(ca); @@ -1522,20 +1566,21 @@ int cert_stuff(struct Curl_easy *data, key_blob = cert_blob; } else - file_type = do_file_type(key_type); + file_type = ossl_do_file_type(key_type); switch(file_type) { case SSL_FILETYPE_PEM: if(cert_done) break; - /* FALLTHROUGH */ + FALLTHROUGH(); case SSL_FILETYPE_ASN1: cert_use_result = key_blob ? - SSL_CTX_use_PrivateKey_blob(ctx, key_blob, file_type, key_passwd) : + use_privatekey_blob(ctx, key_blob, file_type, key_passwd) : SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type); if(cert_use_result != 1) { failf(data, "unable to set private key file: '%s' type %s", - key_file?key_file:"(memory blob)", key_type?key_type:"PEM"); + key_file ? key_file : "(memory blob)", + key_type ? key_type : "PEM"); return 0; } break; @@ -1556,7 +1601,7 @@ int cert_stuff(struct Curl_easy *data, if(data->state.engine) { UI_METHOD *ui_method = - UI_create_method((char *)"curl user interface"); + UI_create_method(OSSL_UI_METHOD_CAST("curl user interface")); if(!ui_method) { failf(data, "unable do create " OSSL_PACKAGE " user-interface method"); @@ -1566,11 +1611,9 @@ int cert_stuff(struct Curl_easy *data, UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL())); UI_method_set_reader(ui_method, ssl_ui_reader); UI_method_set_writer(ui_method, ssl_ui_writer); - /* the typecast below was added to please mingw32 */ - priv_key = (EVP_PKEY *) - ENGINE_load_private_key(data->state.engine, key_file, - ui_method, - key_passwd); + priv_key = ENGINE_load_private_key(data->state.engine, key_file, + ui_method, + key_passwd); UI_destroy_method(ui_method); if(!priv_key) { failf(data, "failed to load private key from crypto engine"); @@ -1581,18 +1624,95 @@ int cert_stuff(struct Curl_easy *data, EVP_PKEY_free(priv_key); return 0; } - EVP_PKEY_free(priv_key); /* we don't need the handle any more... */ + EVP_PKEY_free(priv_key); /* we do not need the handle any more... */ } else { - failf(data, "crypto engine not set, can't load private key"); + failf(data, "crypto engine not set, cannot load private key"); + return 0; + } + } + break; +#endif +#if defined(OPENSSL_HAS_PROVIDERS) + /* fall through to compatible provider */ + case SSL_FILETYPE_PROVIDER: + { + /* Implicitly use pkcs11 provider if none was provided and the + * key_file is a PKCS#11 URI */ + if(!data->state.provider) { + if(is_pkcs11_uri(key_file)) { + if(ossl_set_provider(data, "pkcs11") != CURLE_OK) { + return 0; + } + } + } + + if(data->state.provider) { + /* Load the private key from the provider */ + EVP_PKEY *priv_key = NULL; + OSSL_STORE_CTX *store = NULL; + OSSL_STORE_INFO *info = NULL; + UI_METHOD *ui_method = + UI_create_method(OSSL_UI_METHOD_CAST("curl user interface")); + if(!ui_method) { + failf(data, "unable do create " OSSL_PACKAGE + " user-interface method"); + return 0; + } + UI_method_set_opener(ui_method, UI_method_get_opener(UI_OpenSSL())); + UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL())); + UI_method_set_reader(ui_method, ssl_ui_reader); + UI_method_set_writer(ui_method, ssl_ui_writer); + + store = OSSL_STORE_open_ex(key_file, data->state.libctx, + data->state.propq, ui_method, NULL, NULL, + NULL, NULL); + if(!store) { + failf(data, "Failed to open OpenSSL store: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return 0; + } + if(OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) != 1) { + failf(data, "Failed to set store preference. Ignoring the error: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + } + + info = OSSL_STORE_load(store); + if(info) { + int ossl_type = OSSL_STORE_INFO_get_type(info); + + if(ossl_type == OSSL_STORE_INFO_PKEY) + priv_key = OSSL_STORE_INFO_get1_PKEY(info); + OSSL_STORE_INFO_free(info); + } + OSSL_STORE_close(store); + UI_destroy_method(ui_method); + if(!priv_key) { + failf(data, "No private key found in the openssl store: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return 0; + } + + if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) { + failf(data, "unable to set private key [%s]", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + EVP_PKEY_free(priv_key); + return 0; + } + EVP_PKEY_free(priv_key); /* we do not need the handle any more... */ + } + else { + failf(data, "crypto provider not set, cannot load private key"); return 0; } } break; -#else - failf(data, "file type ENG for private key not supported"); - return 0; #endif + case SSL_FILETYPE_PKCS12: if(!cert_done) { failf(data, "file type P12 for private key not supported"); @@ -1623,8 +1743,8 @@ int cert_stuff(struct Curl_easy *data, #if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_IS_BORINGSSL) && \ !defined(OPENSSL_NO_DEPRECATED_3_0) { - /* If RSA is used, don't check the private key if its flags indicate - * it doesn't support it. */ + /* If RSA is used, do not check the private key if its flags indicate + * it does not support it. */ EVP_PKEY *priv_key = SSL_get_privatekey(ssl); int pktype; #ifdef HAVE_OPAQUE_EVP_PKEY @@ -1658,46 +1778,24 @@ int cert_stuff(struct Curl_easy *data, return 1; } -CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, SSL_CTX *ctx, - char *cert_file, - const struct curl_blob *cert_blob, - const char *cert_type, char *key_file, - const struct curl_blob *key_blob, - const char *key_type, char *key_passwd) -{ - int rv = cert_stuff(data, ctx, cert_file, cert_blob, cert_type, key_file, - key_blob, key_type, key_passwd); - if(rv != 1) { - return CURLE_SSL_CERTPROBLEM; - } - - return CURLE_OK; -} - /* returns non-zero on failure */ -static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) +static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d) { BIO *bio_out = BIO_new(BIO_s_mem()); BUF_MEM *biomem; int rc; - - if(!bio_out) - return 1; /* alloc failed! */ - - rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); - BIO_get_mem_ptr(bio_out, &biomem); - - if((size_t)biomem->length < size) - size = biomem->length; - else - size--; /* don't overwrite the buffer end */ - - memcpy(buf, biomem->data, size); - buf[size] = 0; - - BIO_free(bio_out); - - return !rc; + CURLcode result = CURLE_OUT_OF_MEMORY; + + if(bio_out) { + curlx_dyn_reset(d); + rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); + if(rc != -1) { + BIO_get_mem_ptr(bio_out, &biomem); + result = curlx_dyn_addn(d, biomem->data, biomem->length); + BIO_free(bio_out); + } + } + return result; } /** @@ -1708,8 +1806,7 @@ static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) */ static int ossl_init(void) { -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L const uint64_t flags = #ifdef OPENSSL_INIT_ENGINE_ALL_BUILTIN /* not present in BoringSSL */ @@ -1759,8 +1856,7 @@ static int ossl_init(void) /* Global cleanup */ static void ossl_cleanup(void) { -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L /* OpenSSL 1.1 deprecates all these cleanup functions and turns them into no-ops in OpenSSL 1.0 compatibility mode */ #else @@ -1776,11 +1872,7 @@ static void ossl_cleanup(void) ERR_free_strings(); /* Free thread local error state, destroying hash upon zero refcount */ -#ifdef HAVE_ERR_REMOVE_THREAD_STATE ERR_remove_thread_state(NULL); -#else - ERR_remove_state(0); -#endif /* Free all memory allocated by all configuration modules */ CONF_modules_free(); @@ -1793,47 +1885,39 @@ static void ossl_cleanup(void) Curl_tls_keylog_close(); } -/* Selects an OpenSSL crypto engine +/* Selects an OpenSSL crypto engine or provider. */ -static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine) +static CURLcode ossl_set_engine(struct Curl_easy *data, const char *name) { #ifdef USE_OPENSSL_ENGINE - ENGINE *e; - -#if OPENSSL_VERSION_NUMBER >= 0x00909000L - e = ENGINE_by_id(engine); -#else - /* avoid memory leak */ - for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { - const char *e_id = ENGINE_get_id(e); - if(!strcmp(engine, e_id)) - break; - } -#endif - - if(!e) { - failf(data, "SSL Engine '%s' not found", engine); - return CURLE_SSL_ENGINE_NOTFOUND; - } + CURLcode result = CURLE_SSL_ENGINE_NOTFOUND; + ENGINE *e = ENGINE_by_id(name); - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; - } - if(!ENGINE_init(e)) { - char buf[256]; + if(e) { - ENGINE_free(e); - failf(data, "Failed to initialise SSL Engine '%s': %s", - engine, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); - return CURLE_SSL_ENGINE_INITFAILED; + if(data->state.engine) { + ENGINE_finish(data->state.engine); + ENGINE_free(data->state.engine); + data->state.engine = NULL; + } + if(!ENGINE_init(e)) { + char buf[256]; + + ENGINE_free(e); + failf(data, "Failed to initialise SSL Engine '%s': %s", + name, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); + result = CURLE_SSL_ENGINE_INITFAILED; + e = NULL; + } + data->state.engine = e; + return result; } - data->state.engine = e; - return CURLE_OK; +#endif +#ifdef OPENSSL_HAS_PROVIDERS + return ossl_set_provider(data, name); #else - (void)engine; - failf(data, "SSL Engine not supported"); + (void)name; + failf(data, "OpenSSL engine not found"); return CURLE_SSL_ENGINE_NOTFOUND; #endif } @@ -1882,151 +1966,239 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data) return list; } -static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +#if defined(OPENSSL_HAS_PROVIDERS) + +static void ossl_provider_cleanup(struct Curl_easy *data) { - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + if(data->state.baseprov) { + OSSL_PROVIDER_unload(data->state.baseprov); + data->state.baseprov = NULL; + } + if(data->state.provider) { + OSSL_PROVIDER_unload(data->state.provider); + data->state.provider = NULL; + } + OSSL_LIB_CTX_free(data->state.libctx); + data->state.libctx = NULL; + Curl_safefree(data->state.propq); + data->state.provider_loaded = FALSE; +} - (void)data; - DEBUGASSERT(backend); +#define MAX_PROVIDER_LEN 128 /* reasonable */ - if(backend->handle) { - if(cf->next && cf->next->connected) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)SSL_read(backend->handle, buf, (int)sizeof(buf)); +/* Selects an OpenSSL crypto provider. + * + * A provider might need an associated property, a string passed on to + * OpenSSL. Specify this as [PROVIDER][:PROPERTY]: separate the name and the + * property with a colon. No colon means no property is set. + * + * An example provider + property looks like "tpm2:?provider=tpm2". + */ +static CURLcode ossl_set_provider(struct Curl_easy *data, const char *iname) +{ + char name[MAX_PROVIDER_LEN + 1]; + struct Curl_str prov; + const char *propq = NULL; - (void)SSL_shutdown(backend->handle); - SSL_set_connect_state(backend->handle); + if(!iname) { + /* clear and cleanup provider use */ + ossl_provider_cleanup(data); + return CURLE_OK; + } + if(curlx_str_until(&iname, &prov, MAX_PROVIDER_LEN, ':')) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(!curlx_str_single(&iname, ':')) + /* there was a colon, get the propq until the end of string */ + propq = iname; + + /* we need the name in a buffer, null-terminated */ + memcpy(name, curlx_str(&prov), curlx_strlen(&prov)); + name[curlx_strlen(&prov)] = 0; + + if(!data->state.libctx) { + OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new(); + if(!libctx) + return CURLE_OUT_OF_MEMORY; + if(propq) { + data->state.propq = strdup(propq); + if(!data->state.propq) { + OSSL_LIB_CTX_free(libctx); + return CURLE_OUT_OF_MEMORY; + } } + data->state.libctx = libctx; + } - SSL_free(backend->handle); - backend->handle = NULL; + if(OSSL_PROVIDER_available(data->state.libctx, name)) { + /* already loaded through the configuration - no action needed */ + data->state.provider_loaded = TRUE; + return CURLE_OK; } - if(backend->ctx) { - SSL_CTX_free(backend->ctx); - backend->ctx = NULL; - backend->x509_store_setup = FALSE; + + data->state.provider = + OSSL_PROVIDER_try_load(data->state.libctx, name, 1); + if(!data->state.provider) { + char error_buffer[256]; + failf(data, "Failed to initialize provider: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + ossl_provider_cleanup(data); + return CURLE_SSL_ENGINE_NOTFOUND; } - if(backend->bio_method) { - bio_cf_method_free(backend->bio_method); - backend->bio_method = NULL; + + /* load the base provider as well */ + data->state.baseprov = + OSSL_PROVIDER_try_load(data->state.libctx, "base", 1); + if(!data->state.baseprov) { + ossl_provider_cleanup(data); + failf(data, "Failed to load base"); + return CURLE_SSL_ENGINE_NOTFOUND; } + else + data->state.provider_loaded = TRUE; + return CURLE_OK; } +#endif -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int ossl_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) + +static CURLcode ossl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { - int retval = 0; struct ssl_connect_data *connssl = cf->ctx; - char buf[256]; /* We will use this for the OpenSSL error buffer, so it has - to be at least 256 bytes long. */ - unsigned long sslerror; - int nread; - int buffsize; - int err; - bool done = FALSE; - struct ssl_backend_data *backend = connssl->backend; - int loop = 10; - - DEBUGASSERT(backend); - -#ifndef CURL_DISABLE_FTP - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - (void)SSL_shutdown(backend->handle); -#endif - - if(backend->handle) { - buffsize = (int)sizeof(buf); - while(!done && loop--) { - int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), - SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - ERR_clear_error(); - - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - nread = SSL_read(backend->handle, buf, buffsize); - err = SSL_get_error(backend->handle, nread); - - switch(err) { - case SSL_ERROR_NONE: /* this is not an error */ - case SSL_ERROR_ZERO_RETURN: /* no more data */ - /* This is the expected response. There was no data but only - the close notify alert */ - done = TRUE; - break; - case SSL_ERROR_WANT_READ: - /* there's data pending, re-invoke SSL_read() */ - infof(data, "SSL_ERROR_WANT_READ"); - break; - case SSL_ERROR_WANT_WRITE: - /* SSL wants a write. Really odd. Let's bail out. */ - infof(data, "SSL_ERROR_WANT_WRITE"); - done = TRUE; - break; - default: - /* openssl/ssl.h says "look at error stack/return value/errno" */ - sslerror = ERR_get_error(); - failf(data, OSSL_PACKAGE " SSL_read on shutdown: %s, errno %d", - (sslerror ? - ossl_strerror(sslerror, buf, sizeof(buf)) : - SSL_ERROR_to_str(err)), - SOCKERRNO); - done = TRUE; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = TRUE; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = TRUE; - } - } /* while()-loop for the select() */ + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + char buf[1024]; + int nread = -1, err; + unsigned long sslerr; + size_t i; - if(data->set.verbose) { -#ifdef HAVE_SSL_GET_SHUTDOWN - switch(SSL_get_shutdown(backend->handle)) { - case SSL_SENT_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN"); - break; - case SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN"); - break; - case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|" - "SSL_RECEIVED__SHUTDOWN"); + DEBUGASSERT(octx); + if(!octx->ssl || cf->shutdown) { + *done = TRUE; + goto out; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + if(!(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) { + /* We have not started the shutdown from our side yet. Check + * if the server already sent us one. */ + ERR_clear_error(); + for(i = 0; i < 10; ++i) { + nread = SSL_read(octx->ssl, buf, (int)sizeof(buf)); + CURL_TRC_CF(data, cf, "SSL shutdown not sent, read -> %d", nread); + if(nread <= 0) break; + } + err = SSL_get_error(octx->ssl, nread); + if(!nread && err == SSL_ERROR_ZERO_RETURN) { + bool input_pending; + /* Yes, it did. */ + if(!send_shutdown) { + CURL_TRC_CF(data, cf, "SSL shutdown received, not sending"); + *done = TRUE; + goto out; } -#endif + else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) { + /* Server closed the connection after its closy notify. It + * seems not interested to see our close notify, so do not + * send it. We are done. */ + connssl->peer_closed = TRUE; + CURL_TRC_CF(data, cf, "peer closed connection"); + *done = TRUE; + goto out; + } + } + } + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + if(send_shutdown && !(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) { + ERR_clear_error(); + CURL_TRC_CF(data, cf, "send SSL close notify"); + if(SSL_shutdown(octx->ssl) == 1) { + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + *done = TRUE; + goto out; + } + if(SSL_ERROR_WANT_WRITE == SSL_get_error(octx->ssl, nread)) { + CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; } + /* Having sent the close notify, we use SSL_read() to get the + * missing close notify from the server. */ + } - SSL_free(backend->handle); - backend->handle = NULL; + for(i = 0; i < 10; ++i) { + ERR_clear_error(); + nread = SSL_read(octx->ssl, buf, (int)sizeof(buf)); + CURL_TRC_CF(data, cf, "SSL shutdown read -> %d", nread); + if(nread <= 0) + break; + } + err = SSL_get_error(octx->ssl, nread); + switch(err) { + case SSL_ERROR_ZERO_RETURN: /* no more data */ + if(SSL_shutdown(octx->ssl) == 1) + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + else + CURL_TRC_CF(data, cf, "SSL shutdown not received, but closed"); + *done = TRUE; + break; + case SSL_ERROR_NONE: /* just did not get anything */ + case SSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + break; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + break; + default: + /* Server seems to have closed the connection without sending us + * a close notify. */ + sslerr = ERR_get_error(); + CURL_TRC_CF(data, cf, "SSL shutdown, ignore recv error: '%s', errno %d", + (sslerr ? + ossl_strerror(sslerr, buf, sizeof(buf)) : + SSL_ERROR_to_str(err)), + SOCKERRNO); + *done = TRUE; + result = CURLE_OK; + break; } - return retval; + +out: + cf->shutdown = (result || *done); + return result; } -static void ossl_session_free(void *ptr) +static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - /* free the ID */ - SSL_SESSION_free(ptr); + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + + (void)data; + DEBUGASSERT(octx); + + if(octx->ssl) { + SSL_free(octx->ssl); + octx->ssl = NULL; + } + if(octx->ssl_ctx) { + SSL_CTX_free(octx->ssl_ctx); + octx->ssl_ctx = NULL; + octx->x509_store_setup = FALSE; + } + if(octx->bio_method) { + ossl_bio_cf_method_free(octx->bio_method); + octx->bio_method = NULL; + } } /* @@ -2044,8 +2216,10 @@ static void ossl_close_all(struct Curl_easy *data) #else (void)data; #endif -#if !defined(HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED) && \ - defined(HAVE_ERR_REMOVE_THREAD_STATE) +#ifdef OPENSSL_HAS_PROVIDERS + ossl_provider_cleanup(data); +#endif +#ifndef HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED /* OpenSSL 1.0.1 and 1.0.2 build an error queue that is stored per-thread so we need to clean it here in case the thread will be killed. All OpenSSL code should extract the error in association with the error so clearing @@ -2057,7 +2231,7 @@ static void ossl_close_all(struct Curl_easy *data) /* ====================================================== */ /* - * Match subjectAltName against the host name. + * Match subjectAltName against the hostname. */ static bool subj_alt_hostcheck(struct Curl_easy *data, const char *match_pattern, @@ -2078,22 +2252,6 @@ static bool subj_alt_hostcheck(struct Curl_easy *data, return FALSE; } -static CURLcode -ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert, const char *hostname, - const char *dispname); - -CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert) -{ - const char *hostname, *dispname; - int port; - - (void)conn; - Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port); - return ossl_verifyhost(data, conn, server_cert, hostname, dispname); -} - /* Quote from RFC2818 section 3.1 "Server Identity" If a subjectAltName extension of type dNSName is present, that MUST @@ -2103,7 +2261,7 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, Certification Authorities are encouraged to use the dNSName instead. Matching is performed using the matching rules specified by - [RFC2459]. If more than one identity of a given type is present in + [RFC2459]. If more than one identity of a given type is present in the certificate (e.g., more than one dNSName name, a match in any one of the set is considered acceptable.) Names may contain the wildcard character * which is considered to match any single domain name @@ -2116,45 +2274,49 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, This function is now used from ngtcp2 (QUIC) as well. */ -static CURLcode -ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert, const char *hostname, - const char *dispname) +static CURLcode ossl_verifyhost(struct Curl_easy *data, + struct connectdata *conn, + struct ssl_peer *peer, X509 *server_cert) { bool matched = FALSE; - int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ + int target; /* target type, GEN_DNS or GEN_IPADD */ size_t addrlen = 0; STACK_OF(GENERAL_NAME) *altnames; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr addr; #else struct in_addr addr; #endif CURLcode result = CURLE_OK; bool dNSName = FALSE; /* if a dNSName field exists in the cert */ - bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */ + bool iPAddress = FALSE; /* if an iPAddress field exists in the cert */ size_t hostlen; (void)conn; - hostlen = strlen(hostname); - -#ifndef ENABLE_IPV6 - /* Silence compiler warnings for unused params */ - (void) conn; -#endif - -#ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, hostname, &addr)) { + hostlen = strlen(peer->hostname); + switch(peer->type) { + case CURL_SSL_PEER_IPV4: + if(!curlx_inet_pton(AF_INET, peer->hostname, &addr)) + return CURLE_PEER_FAILED_VERIFICATION; + target = GEN_IPADD; + addrlen = sizeof(struct in_addr); + break; +#ifdef USE_IPV6 + case CURL_SSL_PEER_IPV6: + if(!curlx_inet_pton(AF_INET6, peer->hostname, &addr)) + return CURLE_PEER_FAILED_VERIFICATION; target = GEN_IPADD; addrlen = sizeof(struct in6_addr); - } - else + break; #endif - if(Curl_inet_pton(AF_INET, hostname, &addr)) { - target = GEN_IPADD; - addrlen = sizeof(struct in_addr); - } + case CURL_SSL_PEER_DNS: + target = GEN_DNS; + break; + default: + DEBUGASSERT(0); + failf(data, "unexpected ssl peer type: %d", peer->type); + return CURLE_PEER_FAILED_VERIFICATION; + } /* get a "list" of alternative names */ altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL); @@ -2171,7 +2333,7 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, bool ipmatched = FALSE; /* get amount of alternatives, RFC2459 claims there MUST be at least - one, but we don't depend on it... */ + one, but we do not depend on it... */ numalts = sk_GENERAL_NAME_num(altnames); /* loop through all alternatives - until a dnsmatch */ @@ -2187,12 +2349,12 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, /* only check alternatives of the same type the target is */ if(check->type == target) { /* get data and length */ - const char *altptr = (char *)ASN1_STRING_get0_data(check->d.ia5); + const char *altptr = (const char *)ASN1_STRING_get0_data(check->d.ia5); size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5); switch(target) { case GEN_DNS: /* name/pattern comparison */ - /* The OpenSSL man page explicitly says: "In general it cannot be + /* The OpenSSL manpage explicitly says: "In general it cannot be assumed that the data returned by ASN1_STRING_data() is null terminated or does not contain embedded nulls." But also that "The actual format of the data will depend on the actual string @@ -2202,11 +2364,11 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, is always null-terminated. */ if((altlen == strlen(altptr)) && - /* if this isn't true, there was an embedded zero in the name + /* if this is not true, there was an embedded zero in the name string and we cannot match it. */ - subj_alt_hostcheck(data, - altptr, - altlen, hostname, hostlen, dispname)) { + subj_alt_hostcheck(data, altptr, altlen, + peer->hostname, hostlen, + peer->dispname)) { dnsmatched = TRUE; } break; @@ -2218,7 +2380,7 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, ipmatched = TRUE; infof(data, " subjectAltName: host \"%s\" matched cert's IP address!", - dispname); + peer->dispname); } break; } @@ -2234,17 +2396,21 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, /* an alternative name matched */ ; else if(dNSName || iPAddress) { - infof(data, " subjectAltName does not match %s", dispname); + const char *tname = (peer->type == CURL_SSL_PEER_DNS) ? "hostname" : + (peer->type == CURL_SSL_PEER_IPV4) ? + "ipv4 address" : "ipv6 address"; + infof(data, " subjectAltName does not match %s %s", tname, peer->dispname); failf(data, "SSL: no alternative certificate subject name matches " - "target host name '%s'", dispname); + "target %s '%s'", tname, peer->dispname); result = CURLE_PEER_FAILED_VERIFICATION; } else { /* we have to look to the last occurrence of a commonName in the distinguished one to get the most significant one. */ int i = -1; - unsigned char *peer_CN = NULL; - int peerlen = 0; + unsigned char *cn = NULL; + int cnlen = 0; + bool free_cn = FALSE; /* The following is done because of a bug in 0.9.6b */ X509_NAME *name = X509_get_subject_name(server_cert); @@ -2268,21 +2434,17 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, conditional in the future when OpenSSL has been fixed. */ if(tmp) { if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { - peerlen = ASN1_STRING_length(tmp); - if(peerlen >= 0) { - peer_CN = OPENSSL_malloc(peerlen + 1); - if(peer_CN) { - memcpy(peer_CN, ASN1_STRING_get0_data(tmp), peerlen); - peer_CN[peerlen] = '\0'; - } - else - result = CURLE_OUT_OF_MEMORY; - } + cnlen = ASN1_STRING_length(tmp); + cn = (unsigned char *)CURL_UNCONST(ASN1_STRING_get0_data(tmp)); + } + else { /* not a UTF8 name */ + cnlen = ASN1_STRING_to_UTF8(&cn, tmp); + free_cn = TRUE; } - else /* not a UTF8 name */ - peerlen = ASN1_STRING_to_UTF8(&peer_CN, tmp); - if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != peerlen)) { + if((cnlen <= 0) || !cn) + result = CURLE_OUT_OF_MEMORY; + else if((size_t)cnlen != strlen((char *)cn)) { /* there was a terminating zero before the end of string, this cannot match and we return failure! */ failf(data, "SSL: illegal cert name field"); @@ -2294,42 +2456,44 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, if(result) /* error already detected, pass through */ ; - else if(!peer_CN) { + else if(!cn) { failf(data, "SSL: unable to obtain common name from peer certificate"); result = CURLE_PEER_FAILED_VERIFICATION; } - else if(!Curl_cert_hostcheck((const char *)peer_CN, - peerlen, hostname, hostlen)) { + else if(!Curl_cert_hostcheck((const char *)cn, cnlen, + peer->hostname, hostlen)) { failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", peer_CN, dispname); + "target hostname '%s'", cn, peer->dispname); result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " common name: %s (matched)", peer_CN); + infof(data, " common name: %s (matched)", cn); } - if(peer_CN) - OPENSSL_free(peer_CN); + if(free_cn) + OPENSSL_free(cn); } return result; } -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) static CURLcode verifystatus(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct ossl_ctx *octx) { - struct ssl_connect_data *connssl = cf->ctx; int i, ocsp_status; +#ifdef OPENSSL_IS_AWSLC + const uint8_t *status; +#else unsigned char *status; +#endif const unsigned char *p; CURLcode result = CURLE_OK; OCSP_RESPONSE *rsp = NULL; OCSP_BASICRESP *br = NULL; X509_STORE *st = NULL; STACK_OF(X509) *ch = NULL; - struct ssl_backend_data *backend = connssl->backend; X509 *cert; OCSP_CERTID *id = NULL; int cert_status, crl_reason; @@ -2337,9 +2501,10 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, int ret; long len; - DEBUGASSERT(backend); + (void)cf; + DEBUGASSERT(octx); - len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status); + len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &status); if(!status) { failf(data, "No OCSP response received"); @@ -2369,41 +2534,13 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, goto end; } - ch = SSL_get_peer_cert_chain(backend->handle); + ch = SSL_get_peer_cert_chain(octx->ssl); if(!ch) { failf(data, "Could not get peer certificate chain"); result = CURLE_SSL_INVALIDCERTSTATUS; goto end; } - st = SSL_CTX_get_cert_store(backend->ctx); - -#if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \ - (defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER <= 0x2040200fL)) - /* The authorized responder cert in the OCSP response MUST be signed by the - peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert, - no problem, but if it's an intermediate cert OpenSSL has a bug where it - expects this issuer to be present in the chain embedded in the OCSP - response. So we add it if necessary. */ - - /* First make sure the peer cert chain includes both a peer and an issuer, - and the OCSP response contains a responder cert. */ - if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) { - X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1); - - /* Find issuer of responder cert and add it to the OCSP response chain */ - for(i = 0; i < sk_X509_num(ch); i++) { - X509 *issuer = sk_X509_value(ch, i); - if(X509_check_issued(issuer, responder) == X509_V_OK) { - if(!OCSP_basic_add1_cert(br, issuer)) { - failf(data, "Could not add issuer cert to OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - } - } - } -#endif + st = SSL_CTX_get_cert_store(octx->ssl_ctx); if(OCSP_basic_verify(br, ch, st, 0) <= 0) { failf(data, "OCSP response verification failed"); @@ -2412,15 +2549,15 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, } /* Compute the certificate's ID */ - cert = SSL_get1_peer_certificate(backend->handle); + cert = SSL_get1_peer_certificate(octx->ssl); if(!cert) { failf(data, "Error getting peer certificate"); result = CURLE_SSL_INVALIDCERTSTATUS; goto end; } - for(i = 0; i < sk_X509_num(ch); i++) { - X509 *issuer = sk_X509_value(ch, i); + for(i = 0; i < (int)sk_X509_num(ch); i++) { + X509 *issuer = sk_X509_value(ch, (ossl_valsize_t)i); if(X509_check_issued(issuer, cert) == X509_V_OK) { id = OCSP_cert_to_id(EVP_sha1(), cert, issuer); break; @@ -2481,7 +2618,7 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, #endif /* USE_OPENSSL */ -/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions +/* The SSL_CTRL_SET_MSG_CALLBACK does not exist in ancient OpenSSL versions and thus this cannot be done there. */ #ifdef SSL_CTRL_SET_MSG_CALLBACK @@ -2666,7 +2803,7 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, ssl_ver >>= 8; /* check the upper 8 bits only below */ - /* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL + /* SSLv2 does not seem to have TLS record-type headers, so OpenSSL * always pass-up content-type as 0. But the interesting message-type * is at 'buf[0]'. */ @@ -2676,29 +2813,27 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, tls_rt_name = ""; if(content_type == SSL3_RT_CHANGE_CIPHER_SPEC) { - msg_type = *(char *)buf; + msg_type = *(const char *)buf; msg_name = "Change cipher spec"; } else if(content_type == SSL3_RT_ALERT) { - msg_type = (((char *)buf)[0] << 8) + ((char *)buf)[1]; + msg_type = (((const char *)buf)[0] << 8) + ((const char *)buf)[1]; msg_name = SSL_alert_desc_string_long(msg_type); } else { - msg_type = *(char *)buf; + msg_type = *(const char *)buf; msg_name = ssl_msg_type(ssl_ver, msg_type); } txt_len = msnprintf(ssl_buf, sizeof(ssl_buf), "%s (%s), %s, %s (%d):\n", - verstr, direction?"OUT":"IN", + verstr, direction ? "OUT" : "IN", tls_rt_name, msg_name, msg_type); - if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) { - Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len); - } + Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len); } Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : - CURLINFO_SSL_DATA_IN, (char *)buf, len); + CURLINFO_SSL_DATA_IN, (const char *)buf, len); (void) ssl; } #endif @@ -2706,17 +2841,9 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, #ifdef USE_OPENSSL /* ====================================================== */ -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -# define use_sni(x) sni = (x) -#else -# define use_sni(x) Curl_nop_stmt -#endif - -/* Check for OpenSSL 1.0.2 which has ALPN support. */ -#undef HAS_ALPN -#if OPENSSL_VERSION_NUMBER >= 0x10002000L \ - && !defined(OPENSSL_NO_TLSEXT) -# define HAS_ALPN 1 +/* Check for ALPN support. */ +#ifndef OPENSSL_NO_TLSEXT +# define HAS_ALPN_OPENSSL #endif /* Check for OpenSSL 1.1.0 which has set_{min,max}_proto_version(). */ @@ -2729,7 +2856,7 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, #ifdef HAS_MODERN_SET_PROTO_VER static CURLcode -set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) +ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); /* first, TLS min version... */ @@ -2767,7 +2894,7 @@ set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) } /* CURL_SSLVERSION_DEFAULT means that no option was selected. - We don't want to pass 0 to SSL_CTX_set_min_proto_version as + We do not want to pass 0 to SSL_CTX_set_min_proto_version as it would enable all versions down to the lowest supported by the library. So we skip this, and stay with the library default @@ -2779,7 +2906,7 @@ set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) } /* ... then, TLS max version */ - curl_ssl_version_max = conn_config->version_max; + curl_ssl_version_max = (long)conn_config->version_max; /* convert curl max SSL version option to OpenSSL constant */ switch(curl_ssl_version_max) { @@ -2820,29 +2947,33 @@ set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) typedef uint32_t ctx_option_t; #elif OPENSSL_VERSION_NUMBER >= 0x30000000L typedef uint64_t ctx_option_t; +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER) +typedef unsigned long ctx_option_t; #else typedef long ctx_option_t; #endif #if !defined(HAS_MODERN_SET_PROTO_VER) static CURLcode -set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, - struct Curl_cfilter *cf, - struct Curl_easy *data) +ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, + struct Curl_cfilter *cf, + struct Curl_easy *data) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); long ssl_version = conn_config->version; long ssl_version_max = conn_config->version_max; - (void) data; /* In case it's unused. */ + (void) data; /* In case it is unused. */ switch(ssl_version) { case CURL_SSLVERSION_TLSv1_3: #ifdef TLS1_3_VERSION { struct ssl_connect_data *connssl = cf->ctx; - DEBUGASSERT(connssl->backend); - SSL_CTX_set_max_proto_version(connssl->backend->ctx, TLS1_3_VERSION); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); + SSL_CTX_set_max_proto_version(octx->ssl_ctx, TLS1_3_VERSION); *ctx_options |= SSL_OP_NO_TLSv1_2; } #else @@ -2850,23 +2981,13 @@ set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); return CURLE_NOT_BUILT_IN; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_2: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.2 support"); - return CURLE_NOT_BUILT_IN; -#endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_1: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.1 support"); - return CURLE_NOT_BUILT_IN; -#endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_0: case CURL_SSLVERSION_TLSv1: break; @@ -2874,15 +2995,11 @@ set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, switch(ssl_version_max) { case CURL_SSLVERSION_MAX_TLSv1_0: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; -#endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_1: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_2; -#endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_2: #ifdef TLS1_3_VERSION *ctx_options |= SSL_OP_NO_TLSv1_3; @@ -2900,61 +3017,91 @@ set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, } #endif /* ! HAS_MODERN_SET_PROTO_VER */ -/* 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. - */ -static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + SSL_SESSION *session, + int ietf_tls_id, + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len) { - int res = 0; - struct Curl_easy *data; - struct Curl_cfilter *cf; const struct ssl_config_data *config; - struct ssl_connect_data *connssl; - bool isproxy; + unsigned char *der_session_buf = NULL; + unsigned char *qtp_clone = NULL; + CURLcode result = CURLE_OK; - cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); - connssl = cf? cf->ctx : NULL; - data = connssl? CF_DATA_CURRENT(cf) : NULL; - /* The sockindex has been stored as a pointer to an array element */ if(!cf || !data) - return 0; - - isproxy = Curl_ssl_cf_is_proxy(cf); + goto out; config = Curl_ssl_cf_get_config(cf, data); - if(config->primary.sessionid) { - bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; - - Curl_ssl_sessionid_lock(data); - if(isproxy) - incache = FALSE; - else - incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } + if(config->primary.cache_session) { + struct Curl_ssl_session *sc_session = NULL; + size_t der_session_size; + unsigned char *der_session_ptr; + size_t earlydata_max = 0; + + der_session_size = i2d_SSL_SESSION(session, NULL); + if(der_session_size == 0) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - if(!incache) { - if(!Curl_ssl_addsessionid(cf, data, ssl_sessionid, - 0 /* unknown size */, &added)) { - if(added) { - /* the session has been put into the session cache */ - res = 1; - } + der_session_buf = der_session_ptr = malloc(der_session_size); + if(!der_session_buf) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + der_session_size = i2d_SSL_SESSION(session, &der_session_ptr); + if(der_session_size == 0) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + +#ifdef HAVE_OPENSSL_EARLYDATA + earlydata_max = SSL_SESSION_get_max_early_data(session); +#endif + if(quic_tp && quic_tp_len) { + qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len); + if(!qtp_clone) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - else - failf(data, "failed to store ssl session"); } - Curl_ssl_sessionid_unlock(data); + + result = Curl_ssl_session_create2(der_session_buf, der_session_size, + ietf_tls_id, alpn, + (curl_off_t)time(NULL) + + SSL_SESSION_get_timeout(session), + earlydata_max, qtp_clone, quic_tp_len, + &sc_session); + der_session_buf = NULL; /* took ownership of sdata */ + if(!result) { + result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); + /* took ownership of `sc_session` */ + } } - return res; +out: + free(der_session_buf); + return result; +} + +/* 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. + */ +static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +{ + struct Curl_cfilter *cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); + if(cf) { + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct ssl_connect_data *connssl = cf->ctx; + Curl_ossl_add_session(cf, data, connssl->peer.scache_key, ssl_sessionid, + SSL_version(ssl), connssl->negotiated.alpn, + NULL, 0); + } + return 0; } static CURLcode load_cacert_from_memory(X509_STORE *store, @@ -2983,7 +3130,7 @@ static CURLcode load_cacert_from_memory(X509_STORE *store, /* add each entry from PEM file to x509_store */ for(i = 0; i < (int)sk_X509_INFO_num(inf); ++i) { - itmp = sk_X509_INFO_value(inf, i); + itmp = sk_X509_INFO_value(inf, (ossl_valsize_t)i); if(itmp->x509) { if(X509_STORE_add_cert(store, itmp->x509)) { ++count; @@ -3009,189 +3156,220 @@ static CURLcode load_cacert_from_memory(X509_STORE *store, sk_X509_INFO_pop_free(inf, X509_INFO_free); BIO_free(cbio); - /* if we didn't end up importing anything, treat that as an error */ + /* if we did not end up importing anything, treat that as an error */ return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE; } -static CURLcode populate_x509_store(struct Curl_cfilter *cf, - struct Curl_easy *data, - X509_STORE *store) +#ifdef USE_WIN32_CRYPTO +static CURLcode import_windows_cert_store(struct Curl_easy *data, + const char *name, + X509_STORE *store, + bool *imported) { - 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); CURLcode result = CURLE_OK; - X509_LOOKUP *lookup = NULL; - const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; - const char * const ssl_cafile = - /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ - (ca_info_blob ? NULL : conn_config->CAfile); - const char * const ssl_capath = conn_config->CApath; - const char * const ssl_crlfile = ssl_config->primary.CRLfile; - const bool verifypeer = conn_config->verifypeer; - bool imported_native_ca = false; - bool imported_ca_info_blob = false; - - if(!store) - return CURLE_OUT_OF_MEMORY; + HCERTSTORE hStore; + + *imported = FALSE; + + hStore = CertOpenSystemStoreA(0, name); + if(hStore) { + PCCERT_CONTEXT pContext = NULL; + /* The array of enhanced key usage OIDs will vary per certificate and + is declared outside of the loop so that rather than malloc/free each + iteration we can grow it with realloc, when necessary. */ + CERT_ENHKEY_USAGE *enhkey_usage = NULL; + DWORD enhkey_usage_size = 0; + + /* This loop makes a best effort to import all valid certificates from + the MS root store. If a certificate cannot be imported it is + skipped. 'result' is used to store only hard-fail conditions (such + as out of memory) that cause an early break. */ + result = CURLE_OK; + for(;;) { + X509 *x509; + FILETIME now; + BYTE key_usage[2]; + DWORD req_size; + const unsigned char *encoded_cert; + pContext = CertEnumCertificatesInStore(hStore, pContext); + if(!pContext) + break; - if(verifypeer) { -#if defined(USE_WIN32_CRYPTO) - /* Import certificates from the Windows root certificate store if - requested. - https://stackoverflow.com/questions/9507184/ - https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 - https://datatracker.ietf.org/doc/html/rfc5280 */ - if(ssl_config->native_ca_store) { - HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT")); - - if(hStore) { - PCCERT_CONTEXT pContext = NULL; - /* The array of enhanced key usage OIDs will vary per certificate and - is declared outside of the loop so that rather than malloc/free each - iteration we can grow it with realloc, when necessary. */ - CERT_ENHKEY_USAGE *enhkey_usage = NULL; - DWORD enhkey_usage_size = 0; - - /* This loop makes a best effort to import all valid certificates from - the MS root store. If a certificate cannot be imported it is - skipped. 'result' is used to store only hard-fail conditions (such - as out of memory) that cause an early break. */ - result = CURLE_OK; - for(;;) { - X509 *x509; - FILETIME now; - BYTE key_usage[2]; - DWORD req_size; - const unsigned char *encoded_cert; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - char cert_name[256]; + else { + char cert_name[256]; + if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + NULL, cert_name, sizeof(cert_name))) + infof(data, "SSL: unknown cert name"); + else + infof(data, "SSL: Checking cert \"%s\"", cert_name); + } #endif + encoded_cert = (const unsigned char *)pContext->pbCertEncoded; + if(!encoded_cert) + continue; + + GetSystemTimeAsFileTime(&now); + if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || + CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) + continue; + + /* If key usage exists check for signing attribute */ + if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, + pContext->pCertInfo, + key_usage, sizeof(key_usage))) { + if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + continue; + } + else if(GetLastError()) + continue; + + /* If enhanced key usage exists check for server auth attribute. + * + * Note "In a Microsoft environment, a certificate might also have + * EKU extended properties that specify valid uses for the + * certificate." The call below checks both, and behavior varies + * depending on what is found. For more details see + * CertGetEnhancedKeyUsage doc. + */ + if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { + if(req_size && req_size > enhkey_usage_size) { + void *tmp = realloc(enhkey_usage, req_size); - pContext = CertEnumCertificatesInStore(hStore, pContext); - if(!pContext) + if(!tmp) { + failf(data, "SSL: Out of memory allocating for OID list"); + result = CURLE_OUT_OF_MEMORY; break; - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, - NULL, cert_name, sizeof(cert_name))) { - strcpy(cert_name, "Unknown"); } - infof(data, "SSL: Checking cert \"%s\"", cert_name); -#endif - encoded_cert = (const unsigned char *)pContext->pbCertEncoded; - if(!encoded_cert) - continue; - GetSystemTimeAsFileTime(&now); - if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || - CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) - continue; + enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; + enhkey_usage_size = req_size; + } - /* If key usage exists check for signing attribute */ - if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, - pContext->pCertInfo, - key_usage, sizeof(key_usage))) { - if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { + if(!enhkey_usage->cUsageIdentifier) { + /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate + is good for all uses. If it returns zero, the certificate + has no valid uses." */ + if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) continue; } - else if(GetLastError()) - continue; - - /* If enhanced key usage exists check for server auth attribute. - * - * Note "In a Microsoft environment, a certificate might also have - * EKU extended properties that specify valid uses for the - * certificate." The call below checks both, and behavior varies - * depending on what is found. For more details see - * CertGetEnhancedKeyUsage doc. - */ - if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { - if(req_size && req_size > enhkey_usage_size) { - void *tmp = realloc(enhkey_usage, req_size); - - if(!tmp) { - failf(data, "SSL: Out of memory allocating for OID list"); - result = CURLE_OUT_OF_MEMORY; + else { + DWORD i; + bool found = FALSE; + + for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { + if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, + enhkey_usage->rgpszUsageIdentifier[i])) { + found = TRUE; break; } - - enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; - enhkey_usage_size = req_size; } - if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { - if(!enhkey_usage->cUsageIdentifier) { - /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate - is good for all uses. If it returns zero, the certificate - has no valid uses." */ - if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) - continue; - } - else { - DWORD i; - bool found = false; - - for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { - if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, - enhkey_usage->rgpszUsageIdentifier[i])) { - found = true; - break; - } - } - - if(!found) - continue; - } - } - else + if(!found) continue; } - else - continue; - - x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); - if(!x509) - continue; - - /* Try to import the certificate. This may fail for legitimate - reasons such as duplicate certificate, which is allowed by MS but - not OpenSSL. */ - if(X509_STORE_add_cert(store, x509) == 1) { -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - infof(data, "SSL: Imported cert \"%s\"", cert_name); -#endif - imported_native_ca = true; - } - X509_free(x509); } - - free(enhkey_usage); - CertFreeCertificateContext(pContext); - CertCloseStore(hStore, 0); - - if(result) - return result; + else + continue; } - if(imported_native_ca) - infof(data, "successfully imported Windows CA store"); else - infof(data, "error importing Windows CA store, continuing anyway"); - } + continue; + + x509 = d2i_X509(NULL, &encoded_cert, (long)pContext->cbCertEncoded); + if(!x509) + continue; + + /* Try to import the certificate. This may fail for legitimate + reasons such as duplicate certificate, which is allowed by MS but + not OpenSSL. */ + if(X509_STORE_add_cert(store, x509) == 1) { +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + infof(data, "SSL: Imported cert"); #endif - if(ca_info_blob) { - result = load_cacert_from_memory(store, ca_info_blob); - if(result) { - failf(data, "error importing CA certificate blob"); - return result; - } - else { - imported_ca_info_blob = true; - infof(data, "successfully imported CA certificate blob"); + *imported = TRUE; + } + X509_free(x509); + } + + free(enhkey_usage); + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + if(result) + return result; + } + + return result; +} +#endif + +static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + 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); + CURLcode result = CURLE_OK; + X509_LOOKUP *lookup = NULL; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const char * const ssl_capath = conn_config->CApath; + const char * const ssl_crlfile = ssl_config->primary.CRLfile; + const bool verifypeer = conn_config->verifypeer; + bool imported_native_ca = FALSE; + bool imported_ca_info_blob = FALSE; + + CURL_TRC_CF(data, cf, "ossl_populate_x509_store, path=%s, blob=%d", + ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); + if(!store) + return CURLE_OUT_OF_MEMORY; + + if(verifypeer) { +#ifdef USE_WIN32_CRYPTO + /* Import certificates from the Windows root certificate store if + requested. + https://stackoverflow.com/questions/9507184/ + https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 + https://datatracker.ietf.org/doc/html/rfc5280 */ + if(ssl_config->native_ca_store) { + const char *storeNames[] = { + "ROOT", /* Trusted Root Certification Authorities */ + "CA" /* Intermediate Certification Authorities */ + }; + size_t i; + for(i = 0; i < CURL_ARRAYSIZE(storeNames); ++i) { + bool imported = FALSE; + result = import_windows_cert_store(data, storeNames[i], store, + &imported); + if(result) + return result; + if(imported) { + infof(data, "successfully imported Windows %s store", storeNames[i]); + imported_native_ca = TRUE; + } + else + infof(data, "error importing Windows %s store, continuing anyway", + storeNames[i]); + } + } +#endif + if(ca_info_blob) { + result = load_cacert_from_memory(store, ca_info_blob); + if(result) { + failf(data, "error importing CA certificate blob"); + return result; + } + else { + imported_ca_info_blob = TRUE; + infof(data, "successfully imported CA certificate blob"); } } if(ssl_cafile || ssl_capath) { -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) { if(!imported_native_ca && !imported_ca_info_blob) { @@ -3236,8 +3414,8 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, #ifdef CURL_CA_FALLBACK if(!ssl_cafile && !ssl_capath && !imported_native_ca && !imported_ca_info_blob) { - /* verifying the peer without any CA certificates won't - work so use openssl's built-in default as fallback */ + /* verifying the peer without any CA certificates will not + work so use OpenSSL's built-in default as fallback */ X509_STORE_set_default_paths(store); } #endif @@ -3262,12 +3440,13 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, if(verifypeer) { /* Try building a chain using issuers in the trusted store first to avoid - problems with server-sent legacy intermediates. Newer versions of + problems with server-sent legacy intermediates. Newer versions of OpenSSL do alternate chain checking by default but we do not know how to determine that in a reliable manner. - https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest + https://web.archive.org/web/20190422050538/ + rt.openssl.org/Ticket/Display.html?id=3621 */ -#if defined(X509_V_FLAG_TRUSTED_FIRST) +#ifdef X509_V_FLAG_TRUSTED_FIRST X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); #endif #ifdef X509_V_FLAG_PARTIAL_CHAIN @@ -3288,24 +3467,50 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, return result; } -#if defined(HAVE_SSL_X509_STORE_SHARE) -static bool cached_x509_store_expired(const struct Curl_easy *data, - const struct multi_ssl_backend_data *mb) +#ifdef HAVE_SSL_X509_STORE_SHARE + +/* key to use at `multi->proto_hash` */ +#define MPROTO_OSSL_X509_KEY "tls:ossl:x509:share" + +struct ossl_x509_share { + char *CAfile; /* CAfile 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 */ +}; + +static void oss_x509_share_free(void *key, size_t key_len, void *p) { - const struct ssl_general_config *cfg = &data->set.general_ssl; - struct curltime now = Curl_now(); - timediff_t elapsed_ms = Curl_timediff(now, mb->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + struct ossl_x509_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_OSSL_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_OSSL_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->store) { + X509_STORE_free(share->store); + } + free(share->CAfile); + free(share); +} - if(timeout_ms < 0) - return false; +static bool +ossl_cached_x509_store_expired(const 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) + return FALSE; + else { + struct curltime now = curlx_now(); + timediff_t elapsed_ms = curlx_timediff(now, mb->time); + timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; - return elapsed_ms >= timeout_ms; + return elapsed_ms >= timeout_ms; + } } -static bool cached_x509_store_different( - struct Curl_cfilter *cf, - const struct multi_ssl_backend_data *mb) +static bool +ossl_cached_x509_store_different(struct Curl_cfilter *cf, + const struct ossl_x509_share *mb) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); if(!mb->CAfile || !conn_config->CAfile) @@ -3314,42 +3519,54 @@ static bool cached_x509_store_different( return strcmp(mb->CAfile, conn_config->CAfile); } -static X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf, - const struct Curl_easy *data) +static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct Curl_multi *multi = data->multi; + struct ossl_x509_share *share; X509_STORE *store = NULL; - if(multi && - multi->ssl_backend_data && - multi->ssl_backend_data->store && - !cached_x509_store_expired(data, multi->ssl_backend_data) && - !cached_x509_store_different(cf, multi->ssl_backend_data)) { - store = multi->ssl_backend_data->store; + DEBUGASSERT(multi); + share = multi ? Curl_hash_pick(&multi->proto_hash, + CURL_UNCONST(MPROTO_OSSL_X509_KEY), + sizeof(MPROTO_OSSL_X509_KEY)-1) : NULL; + if(share && share->store && + !ossl_cached_x509_store_expired(data, share) && + !ossl_cached_x509_store_different(cf, share)) { + store = share->store; } return store; } -static void set_cached_x509_store(struct Curl_cfilter *cf, - const struct Curl_easy *data, - X509_STORE *store) +static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + X509_STORE *store) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; - struct multi_ssl_backend_data *mbackend; + struct Curl_multi *multi = data->multi; + struct ossl_x509_share *share; + DEBUGASSERT(multi); if(!multi) return; + share = Curl_hash_pick(&multi->proto_hash, + CURL_UNCONST(MPROTO_OSSL_X509_KEY), + sizeof(MPROTO_OSSL_X509_KEY)-1); - if(!multi->ssl_backend_data) { - multi->ssl_backend_data = calloc(1, sizeof(struct multi_ssl_backend_data)); - if(!multi->ssl_backend_data) + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) + return; + if(!Curl_hash_add2(&multi->proto_hash, + CURL_UNCONST(MPROTO_OSSL_X509_KEY), + sizeof(MPROTO_OSSL_X509_KEY)-1, + share, oss_x509_share_free)) { + free(share); return; + } } - mbackend = multi->ssl_backend_data; - if(X509_STORE_up_ref(store)) { char *CAfile = NULL; @@ -3361,14 +3578,14 @@ static void set_cached_x509_store(struct Curl_cfilter *cf, } } - if(mbackend->store) { - X509_STORE_free(mbackend->store); - free(mbackend->CAfile); + if(share->store) { + X509_STORE_free(share->store); + free(share->CAfile); } - mbackend->time = Curl_now(); - mbackend->store = store; - mbackend->CAfile = CAfile; + share->time = curlx_now(); + share->store = store; + share->CAfile = CAfile; } } @@ -3383,7 +3600,7 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, bool cache_criteria_met; /* 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 + 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) && conn_config->verifypeer && @@ -3392,16 +3609,16 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, !ssl_config->primary.CRLfile && !ssl_config->native_ca_store; - cached_store = get_cached_x509_store(cf, data); + cached_store = ossl_get_cached_x509_store(cf, data); if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) { SSL_CTX_set_cert_store(ssl_ctx, cached_store); } else { X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); - result = populate_x509_store(cf, data, store); + result = ossl_populate_x509_store(cf, data, store); if(result == CURLE_OK && cache_criteria_met) { - set_cached_x509_store(cf, data, store); + ossl_set_cached_x509_store(cf, data, store); } } @@ -3414,43 +3631,375 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, { X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); - return populate_x509_store(cf, data, store); + return ossl_populate_x509_store(cf, data, store); } #endif /* HAVE_SSL_X509_STORE_SHARE */ -static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, - struct Curl_easy *data) + +static CURLcode +ossl_init_session_and_alpns(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb) { - CURLcode result = CURLE_OK; - char *ciphers; - SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; - struct ssl_connect_data *connssl = cf->ctx; - ctx_option_t ctx_options = 0; - void *ssl_sessionid = NULL; - 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); - BIO *bio; + struct alpn_spec alpns; + char error_buffer[256]; + CURLcode result; + + Curl_alpn_copy(&alpns, alpns_requested); + + octx->reused_session = FALSE; + if(ssl_config->primary.cache_session) { + struct Curl_ssl_session *scs = NULL; + + result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs); + if(!result && scs && scs->sdata && scs->sdata_len) { + const unsigned char *der_sessionid = scs->sdata; + size_t der_sessionid_size = scs->sdata_len; + SSL_SESSION *ssl_session = NULL; + + /* If OpenSSL does not accept the session from the cache, this + * is not an error. We just continue without it. */ + ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid, + (long)der_sessionid_size); + if(ssl_session) { + if(!SSL_set_session(octx->ssl, ssl_session)) { + infof(data, "SSL: SSL_set_session not accepted, " + "continuing without: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + } + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + octx->reused_session = TRUE; +#ifdef HAVE_OPENSSL_EARLYDATA + if(ssl_config->earlydata && scs->alpn && + SSL_SESSION_get_max_early_data(ssl_session) && + !cf->conn->connect_only && + (SSL_version(octx->ssl) == TLS1_3_VERSION)) { + bool do_early_data = FALSE; + if(sess_reuse_cb) { + result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data); + if(result) + return result; + } + if(do_early_data) { + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + Curl_alpn_restrict_to(&alpns, scs->alpn); + } + } +#else + (void)sess_reuse_cb; +#endif + } + SSL_SESSION_free(ssl_session); + } + else { + infof(data, "SSL session not accepted by OpenSSL, continuing without"); + } + } + Curl_ssl_scache_return(cf, data, peer->scache_key, scs); + } + +#ifdef HAS_ALPN_OPENSSL + if(alpns.count) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); + result = Curl_alpn_to_proto_buf(&proto, &alpns); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + if(SSL_set_alpn_protos(octx->ssl, proto.data, (int)proto.len)) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif + + return CURLE_OK; +} + +#ifdef USE_ECH_OPENSSL +static CURLcode ossl_init_ech(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer) +{ + unsigned char *ech_config = NULL; + size_t ech_config_len = 0; + char *outername = data->set.str[STRING_ECH_PUBLIC]; + int trying_ech_now = 0; + CURLcode result; + + if(!ECH_ENABLED(data)) + return CURLE_OK; + + if(data->set.tls_ech & CURLECH_GREASE) { + infof(data, "ECH: will GREASE ClientHello"); +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + SSL_set_enable_ech_grease(octx->ssl, 1); +# else + SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); +# endif + } + else if(data->set.tls_ech & CURLECH_CLA_CFG) { +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + /* have to do base64 decode here for BoringSSL */ + const char *b64 = data->set.str[STRING_ECH_CONFIG]; + if(!b64) { + 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); + if(result || !ech_config) { + infof(data, "ECH: cannot base64 decode ECHConfig from command line"); + if(data->set.tls_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) { + free(ech_config); + return CURLE_SSL_CONNECT_ERROR; + } + } + free(ech_config); + trying_ech_now = 1; +# else + ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG]; + if(!ech_config) { + infof(data, "ECH: ECHConfig from command line empty"); + return CURLE_SSL_CONNECT_ERROR; + } + ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]); + 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) + return CURLE_SSL_CONNECT_ERROR; + } + else + trying_ech_now = 1; +# endif + infof(data, "ECH: ECHConfig from command line"); + } + else { + struct Curl_dns_entry *dns = NULL; + + if(peer->hostname) + dns = Curl_dnscache_get(data, peer->hostname, peer->port, + cf->conn->ip_version); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); + if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_set1_ech_config_list failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %zu", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + Curl_resolv_unlink(data, &dns); + } + } +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + if(trying_ech_now && outername) { + infof(data, "ECH: setting public_name not supported with BoringSSL"); + return CURLE_SSL_CONNECT_ERROR; + } +# else + if(trying_ech_now && outername) { + infof(data, "ECH: inner: '%s', outer: '%s'", + peer->hostname ? peer->hostname : "NULL", outername); + result = SSL_ech_set1_server_names(octx->ssl, + peer->hostname, outername, + 0 /* do send outer */); + if(result != 1) { + infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result); + return CURLE_SSL_CONNECT_ERROR; + } + } +# endif /* OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC */ + if(trying_ech_now + && SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) { + infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); + return CURLE_SSL_CONNECT_ERROR; + } + + return CURLE_OK; +} +#endif /* USE_ECH_OPENSSL */ + + +static CURLcode ossl_init_ssl(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + void *ssl_user_data, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb) +{ + /* Let's make an SSL structure */ + if(octx->ssl) + SSL_free(octx->ssl); + octx->ssl = SSL_new(octx->ssl_ctx); + if(!octx->ssl) { + failf(data, "SSL: could not create a context (handle)"); + return CURLE_OUT_OF_MEMORY; + } + + SSL_set_app_data(octx->ssl, ssl_user_data); + +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) + if(Curl_ssl_cf_get_primary_config(cf)->verifystatus) + SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp); +#endif + +#if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \ + defined(ALLOW_RENEG) + SSL_set_renegotiate_mode(octx->ssl, ssl_renegotiate_freely); +#endif + + SSL_set_connect_state(octx->ssl); + + octx->server_cert = NULL; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - bool sni; - const char *hostname = connssl->hostname; + if(peer->sni) { + if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) { + failf(data, "Failed set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + } + +#ifdef USE_ECH_OPENSSL + { + CURLcode result = ossl_init_ech(octx, cf, data, peer); + if(result) + return result; + } +#endif /* USE_ECH_OPENSSL */ -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; #endif + + return ossl_init_session_and_alpns(octx, cf, data, peer, + alpns_requested, sess_reuse_cb); +} + + +static CURLcode ossl_init_method(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const SSL_METHOD **pmethod, + unsigned int *pssl_version_min) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + + *pmethod = NULL; + *pssl_version_min = conn_config->version; + switch(peer->transport) { + case TRNSPRT_TCP: + /* check to see if we have been told to use an explicit SSL/TLS version */ + switch(*pssl_version_min) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + /* it will be handled later with the context options */ + #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + *pmethod = TLS_client_method(); + #else + *pmethod = SSLv23_client_method(); + #endif + break; + case CURL_SSLVERSION_SSLv2: + failf(data, "No SSLv2 support"); + return CURLE_NOT_BUILT_IN; + case CURL_SSLVERSION_SSLv3: + failf(data, "No SSLv3 support"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + break; + case TRNSPRT_QUIC: + *pssl_version_min = CURL_SSLVERSION_TLSv1_3; + if(conn_config->version_max && + (conn_config->version_max != CURL_SSLVERSION_MAX_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef USE_OPENSSL_QUIC + *pmethod = OSSL_QUIC_client_method(); +#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L) + *pmethod = TLS_method(); +#else + *pmethod = SSLv23_client_method(); #endif - const long int ssl_version = conn_config->version; + break; + default: + failf(data, "unsupported transport %d in SSL init", peer->transport); + return CURLE_SSL_CONNECT_ERROR; + } + + return *pmethod ? CURLE_OK : CURLE_SSL_CONNECT_ERROR; +} + + +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb) +{ + CURLcode result = CURLE_OK; + 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->cert_type; const bool verifypeer = conn_config->verifypeer; + unsigned int ssl_version_min; char error_buffer[256]; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); - DEBUGASSERT(backend); /* Make funny stuff to get random input */ result = ossl_seed(data); @@ -3459,68 +4008,47 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, ssl_config->certverifyresult = !X509_V_OK; - /* check to see if we've been told to use an explicit SSL/TLS version */ + result = ossl_init_method(cf, data, peer, &req_method, &ssl_version_min); + if(result) + return result; + DEBUGASSERT(req_method); - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - /* it will be handled later with the context options */ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - req_method = TLS_client_method(); -#else - req_method = SSLv23_client_method(); + DEBUGASSERT(!octx->ssl_ctx); + octx->ssl_ctx = +#ifdef OPENSSL_HAS_PROVIDERS + data->state.libctx ? + SSL_CTX_new_ex(data->state.libctx, data->state.propq, req_method): #endif - use_sni(TRUE); - break; - case CURL_SSLVERSION_SSLv2: - failf(data, "No SSLv2 support"); - return CURLE_NOT_BUILT_IN; - case CURL_SSLVERSION_SSLv3: - failf(data, "No SSLv3 support"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(backend->ctx) { - /* This happens when an error was encountered before in this - * step and we are called to do it again. Get rid of any leftover - * from the previous call. */ - ossl_close(cf, data); - } - backend->ctx = SSL_CTX_new(req_method); + SSL_CTX_new(req_method); - if(!backend->ctx) { - failf(data, "SSL: couldn't create a context: %s", + if(!octx->ssl_ctx) { + failf(data, "SSL: could not create a context: %s", ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); return CURLE_OUT_OF_MEMORY; } -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS); -#endif + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + return result; + } #ifdef SSL_CTRL_SET_MSG_CALLBACK if(data->set.fdebug && data->set.verbose) { /* the SSL trace callback is only used for verbose logging */ - SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); - SSL_CTX_set_msg_callback_arg(backend->ctx, cf); + SSL_CTX_set_msg_callback(octx->ssl_ctx, ossl_trace); + SSL_CTX_set_msg_callback_arg(octx->ssl_ctx, cf); } #endif /* OpenSSL contains code to work around lots of bugs and flaws in various SSL-implementations. SSL_CTX_set_options() is used to enabled those - work-arounds. The man page for this option states that SSL_OP_ALL enables + work-arounds. The manpage for this option states that SSL_OP_ALL enables all the work-arounds and that "It is usually safe to use SSL_OP_ALL to enable the bug workaround options if compatibility with somewhat broken implementations is desired." - The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to + The "-no_ticket" option was introduced in OpenSSL 0.9.8j. it is a flag to disable "rfc4507bis session ticket support". rfc4507bis was later turned into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077 @@ -3541,12 +4069,12 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, CVE-2010-4180 when using previous OpenSSL versions we no longer enable this option regardless of OpenSSL version and SSL_OP_ALL definition. - OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability - (https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to - SSL_OP_ALL that _disables_ that work-around despite the fact that - SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to - keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit - must not be set. + OpenSSL added a work-around for an SSL 3.0/TLS 1.0 CBC vulnerability: + https://web.archive.org/web/20240114184648/openssl.org/~bodo/tls-cbc.txt. + In 0.9.6e they added a bit to SSL_OP_ALL that _disables_ that work-around + despite the fact that SSL_OP_ALL is documented to do "rather harmless" + workarounds. In order to keep the secure work-around, the + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit must not be set. */ ctx_options = SSL_OP_ALL; @@ -3561,17 +4089,17 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG /* mitigate CVE-2010-4180 */ - ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; + ctx_options &= ~(ctx_option_t)SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; #endif #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS /* unless the user explicitly asks to allow the protocol vulnerability we use the work-around */ if(!ssl_config->enable_beast) - ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; #endif - switch(ssl_version) { + switch(ssl_version_min) { case CURL_SSLVERSION_SSLv2: case CURL_SSLVERSION_SSLv3: return CURLE_NOT_BUILT_IN; @@ -3589,9 +4117,9 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, ctx_options |= SSL_OP_NO_SSLv3; #if HAS_MODERN_SET_PROTO_VER /* 1.1.0 */ - result = set_ssl_version_min_max(cf, backend->ctx); + result = ossl_set_ssl_version_min_max(cf, octx->ssl_ctx); #else - result = set_ssl_version_min_max_legacy(&ctx_options, cf, data); + result = ossl_set_ssl_version_min_max_legacy(&ctx_options, cf, data); #endif if(result != CURLE_OK) return result; @@ -3602,40 +4130,18 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - SSL_CTX_set_options(backend->ctx, ctx_options); + SSL_CTX_set_options(octx->ssl_ctx, ctx_options); -#ifdef HAS_ALPN - if(connssl->alpn) { - struct alpn_proto_buf proto; - - result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); - if(result || - SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) { - failf(data, "Error setting ALPN"); - return CURLE_SSL_CONNECT_ERROR; - } - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); - } +#ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER + /* We do retry writes sometimes from another buffer address */ + SSL_CTX_set_mode(octx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif - if(ssl_cert || ssl_cert_blob || ssl_cert_type) { - if(!result && - !cert_stuff(data, backend->ctx, - ssl_cert, ssl_cert_blob, ssl_cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd)) - result = CURLE_SSL_CERTPROBLEM; - if(result) - /* failf() is already done in cert_stuff() */ - return result; - } - ciphers = conn_config->cipher_list; - if(!ciphers) - ciphers = (char *)DEFAULT_CIPHER_SELECTION; - if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { + if(!ciphers && (peer->transport != TRNSPRT_QUIC)) + ciphers = DEFAULT_CIPHER_SELECTION; + if(ciphers && (ssl_version_min < CURL_SSLVERSION_TLSv1_3)) { + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, ciphers)) { failf(data, "failed setting cipher list: %s", ciphers); return CURLE_SSL_CIPHER; } @@ -3644,9 +4150,11 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES { - char *ciphers13 = conn_config->cipher_list13; - if(ciphers13) { - if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) { + const char *ciphers13 = conn_config->cipher_list13; + if(ciphers13 && + (!conn_config->version_max || + (conn_config->version_max >= CURL_SSLVERSION_MAX_TLSv1_3))) { + if(!SSL_CTX_set_ciphersuites(octx->ssl_ctx, ciphers13)) { failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13); return CURLE_SSL_CIPHER; } @@ -3655,21 +4163,51 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } #endif + if(ssl_cert || ssl_cert_blob || ssl_cert_type) { + if(!result && + !cert_stuff(data, octx->ssl_ctx, + ssl_cert, ssl_cert_blob, ssl_cert_type, + ssl_config->key, ssl_config->key_blob, + ssl_config->key_type, ssl_config->key_passwd)) + result = CURLE_SSL_CERTPROBLEM; + if(result) + /* failf() is already done in cert_stuff() */ + return result; + } + #ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH /* OpenSSL 1.1.1 requires clients to opt-in for PHA */ - SSL_CTX_set_post_handshake_auth(backend->ctx, 1); + SSL_CTX_set_post_handshake_auth(octx->ssl_ctx, 1); #endif -#ifdef HAVE_SSL_CTX_SET_EC_CURVES { - char *curves = conn_config->curves; + const char *curves = conn_config->curves; if(curves) { - if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +#define OSSL_CURVE_CAST(x) (x) +#else +#define OSSL_CURVE_CAST(x) (char *)CURL_UNCONST(x) +#endif + if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, OSSL_CURVE_CAST(curves))) { failf(data, "failed setting curves list: '%s'", curves); return CURLE_SSL_CIPHER; } } } + +#ifdef HAVE_SSL_CTX_SET1_SIGALGS +#define OSSL_SIGALG_CAST(x) OSSL_CURVE_CAST(x) + { + const char *signature_algorithms = conn_config->signature_algorithms; + if(signature_algorithms) { + if(!SSL_CTX_set1_sigalgs_list(octx->ssl_ctx, + OSSL_SIGALG_CAST(signature_algorithms))) { + failf(data, "failed setting signature algorithms: '%s'", + signature_algorithms); + return CURLE_SSL_CIPHER; + } + } + } #endif #ifdef USE_OPENSSL_SRP @@ -3678,18 +4216,18 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, char * const ssl_password = ssl_config->primary.password; infof(data, "Using TLS-SRP username: %s", ssl_username); - if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) { - failf(data, "Unable to set SRP user name"); + if(!SSL_CTX_set_srp_username(octx->ssl_ctx, ssl_username)) { + failf(data, "Unable to set SRP username"); return CURLE_BAD_FUNCTION_ARGUMENT; } - if(!SSL_CTX_set_srp_password(backend->ctx, ssl_password)) { + if(!SSL_CTX_set_srp_password(octx->ssl_ctx, ssl_password)) { failf(data, "failed setting SRP password"); return CURLE_BAD_FUNCTION_ARGUMENT; } if(!conn_config->cipher_list) { infof(data, "Setting cipher list SRP"); - if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) { + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, "SRP")) { failf(data, "failed setting SRP cipher list"); return CURLE_SSL_CIPHER; } @@ -3701,99 +4239,104 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, * fail to connect if the verification fails, or if it should continue * anyway. In the latter case the result of the verification is checked with * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(backend->ctx, + SSL_CTX_set_verify(octx->ssl_ctx, verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ #ifdef HAVE_KEYLOG_CALLBACK if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback); + SSL_CTX_set_keylog_callback(octx->ssl_ctx, ossl_keylog_callback); } #endif - /* Enable the session cache because it's a prerequisite for the "new session" - * callback. Use the "external storage" mode to prevent OpenSSL from creating - * an internal session cache. - */ - SSL_CTX_set_session_cache_mode(backend->ctx, - SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb); + if(cb_new_session) { + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(octx->ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(octx->ssl_ctx, cb_new_session); + } /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, backend->ctx, + /* When a user callback is installed to modify the SSL_CTX, + * we need to do the full initialization before calling it. + * See: #11800 */ + if(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); + if(result) + return result; + octx->x509_store_setup = TRUE; + } + Curl_set_in_callback(data, TRUE); + result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(result) { failf(data, "error signaled by ssl ctx callback"); return result; } } - /* Let's make an SSL structure */ - if(backend->handle) - SSL_free(backend->handle); - backend->handle = SSL_new(backend->ctx); - if(!backend->handle) { - failf(data, "SSL: couldn't create a context (handle)"); - return CURLE_OUT_OF_MEMORY; - } - - SSL_set_app_data(backend->handle, cf); - -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) - if(conn_config->verifystatus) - SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp); -#endif - -#if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \ - defined(ALLOW_RENEG) - SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely); -#endif + return ossl_init_ssl(octx, cf, data, peer, alpns_requested, + ssl_user_data, sess_reuse_cb); +} - SSL_set_connect_state(backend->handle); +static CURLcode ossl_on_session_reuse(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data) +{ + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result = CURLE_OK; - backend->server_cert = 0x0; -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni) { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || !SSL_set_tlsext_host_name(backend->handle, snihost)) { - failf(data, "Failed set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } + *do_early_data = FALSE; + connssl->earlydata_max = scs->earlydata_max; + if(!connssl->earlydata_max) { + CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); } -#endif + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { + CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data"); + } + else { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", connssl->earlydata_max, scs->alpn); + connssl->earlydata_state = ssl_earlydata_await; + connssl->state = ssl_connection_deferred; + result = Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)scs->alpn, + scs->alpn ? strlen(scs->alpn) : 0); + *do_early_data = !result; + } + return result; +} + +static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + BIO *bio; + CURLcode result; - SSL_set_app_data(backend->handle, cf); + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(octx); - if(ssl_config->primary.sessionid) { - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { - /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { - Curl_ssl_sessionid_unlock(data); - failf(data, "SSL: SSL_set_session failed: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof(data, "SSL re-using session ID"); - } - Curl_ssl_sessionid_unlock(data); - } + result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer, + connssl->alpn, NULL, NULL, + ossl_new_session_cb, cf, + ossl_on_session_reuse); + if(result) + return result; - backend->bio_method = bio_cf_method_create(); - if(!backend->bio_method) + octx->bio_method = ossl_bio_cf_method_create(); + if(!octx->bio_method) return CURLE_OUT_OF_MEMORY; - bio = BIO_new(backend->bio_method); + bio = BIO_new(octx->bio_method); if(!bio) return CURLE_OUT_OF_MEMORY; @@ -3802,76 +4345,152 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, /* with OpenSSL v1.1.1 we get an alternative to SSL_set_bio() that works * without backward compat quirks. Every call takes one reference, so we * up it and pass. SSL* then owns it and will free. - * We check on the function in configure, since libressl and friends + * We check on the function in configure, since LibreSSL and friends * each have their own versions to add support for this. */ BIO_up_ref(bio); - SSL_set0_rbio(backend->handle, bio); - SSL_set0_wbio(backend->handle, bio); + SSL_set0_rbio(octx->ssl, bio); + SSL_set0_wbio(octx->ssl, bio); #else - SSL_set_bio(backend->handle, bio, bio); + SSL_set_bio(octx->ssl, bio, bio); #endif - connssl->connecting_state = ssl_connect_2; +#ifdef HAS_ALPN_OPENSSL + if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + connssl->connecting_state = ssl_connect_2; return CURLE_OK; } +#ifdef USE_ECH_OPENSSL +/* If we have retry configs, then trace those out */ +static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl, + int reason) +{ + CURLcode result = CURLE_OK; + size_t rcl = 0; + int rv = 1; +# if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) + char *inner = NULL; + unsigned char *rcs = NULL; + char *outer = NULL; +# else + const char *inner = NULL; + const uint8_t *rcs = NULL; + const char *outer = NULL; + size_t out_name_len = 0; + int servername_type = 0; +# endif + + /* nothing to trace if not doing ECH */ + if(!ECH_ENABLED(data)) + return; +# if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) + rv = SSL_ech_get1_retry_config(ssl, &rcs, &rcl); +# else + SSL_get0_ech_retry_configs(ssl, &rcs, &rcl); + rv = (int)rcl; +# endif + + if(rv && rcs) { + char *b64str = NULL; + size_t blen = 0; + + result = curlx_base64_encode((const char *)rcs, rcl, &b64str, &blen); + if(!result && b64str) { + infof(data, "ECH: retry_configs %s", b64str); + free(b64str); +#if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) + rv = SSL_ech_get1_status(ssl, &inner, &outer); + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#else + rv = SSL_ech_accepted(ssl); + servername_type = SSL_get_servername_type(ssl); + inner = SSL_get_servername(ssl, servername_type); + SSL_get0_ech_name_override(ssl, &outer, &out_name_len); + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#endif + } + } + else + infof(data, "ECH: no retry_configs (rv = %d)", rv); +# if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) + OPENSSL_free((void *)rcs); +# endif + return; +} + +#endif + static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { int err; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); - DEBUGASSERT(backend); + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state); + DEBUGASSERT(octx); + connssl->io_need = CURL_SSL_IO_NEED_NONE; ERR_clear_error(); - err = SSL_connect(backend->handle); + err = SSL_connect(octx->ssl); - if(!backend->x509_store_setup) { + if(!octx->x509_store_setup) { /* After having send off the ClientHello, we prepare the x509 * store to verify the coming certificate from the server */ - CURLcode result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + CURLcode result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) return result; - backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } #ifndef HAVE_KEYLOG_CALLBACK - if(Curl_tls_keylog_enabled()) { - /* If key logging is enabled, wait for the handshake to complete and then - * proceed with logging secrets (for TLS 1.2 or older). - */ - ossl_log_tls12_secret(backend->handle, &backend->keylog_done); - } + /* If key logging is enabled, wait for the handshake to complete and then + * proceed with logging secrets (for TLS 1.2 or older). + */ + if(Curl_tls_keylog_enabled() && !octx->keylog_done) + ossl_log_tls12_secret(octx->ssl, &octx->keylog_done); #endif /* 1 is fine 0 is "not successful but was shut down controlled" <0 is "handshake was not successful, because a fatal error occurred" */ if(1 != err) { - int detail = SSL_get_error(backend->handle, err); + int detail = SSL_get_error(octx->ssl, err); + CURL_TRC_CF(data, cf, "SSL_connect() -> err=%d, detail=%d", err, detail); if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; + CURL_TRC_CF(data, cf, "SSL_connect() -> want recv"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_AGAIN; } if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; + CURL_TRC_CF(data, cf, "SSL_connect() -> want send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_AGAIN; } #ifdef SSL_ERROR_WANT_ASYNC if(SSL_ERROR_WANT_ASYNC == detail) { - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; + CURL_TRC_CF(data, cf, "SSL_connect() -> want async"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_AGAIN; } #endif - else if(backend->io_result == CURLE_AGAIN) { - return CURLE_OK; +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + if(SSL_ERROR_WANT_RETRY_VERIFY == detail) { + CURL_TRC_CF(data, cf, "SSL_connect() -> want retry_verify"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_AGAIN; } +#endif else { /* untreated error */ sslerr_t errdetail; @@ -3881,7 +4500,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, int lib; int reason; - /* the connection failed, we're not waiting for anything else. */ + /* the connection failed, we are not waiting for anything else. */ connssl->connecting_state = ssl_connect_2; /* Get the earliest error code from the thread's error queue and remove @@ -3897,23 +4516,16 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { result = CURLE_PEER_FAILED_VERIFICATION; - lerr = SSL_get_verify_result(backend->handle); + lerr = SSL_get_verify_result(octx->ssl); if(lerr != X509_V_OK) { ssl_config->certverifyresult = lerr; - msnprintf(error_buffer, sizeof(error_buffer), - "SSL certificate problem: %s", - X509_verify_cert_error_string(lerr)); + failf(data, "SSL certificate problem: %s", + X509_verify_cert_error_string(lerr)); } else - /* strcpy() is fine here as long as the string fits within - error_buffer */ - strcpy(error_buffer, "SSL certificate verification failed"); + failf(data, "%s", "SSL certificate verification failed"); } -#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ - !defined(LIBRESSL_VERSION_NUMBER) && \ - !defined(OPENSSL_IS_BORINGSSL) && \ - !defined(OPENSSL_IS_AWSLC)) - +#ifdef SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on OpenSSL version above v1.1.1, not LibreSSL, BoringSSL, or AWS-LC */ else if((lib == ERR_LIB_SSL) && @@ -3921,17 +4533,35 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, /* If client certificate is required, communicate the error to client */ result = CURLE_SSL_CLIENTCERT; - ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + failf(data, "TLS cert problem: %s", + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); + } +#endif +#ifdef USE_ECH_OPENSSL + else if((lib == ERR_LIB_SSL) && +# if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) + (reason == SSL_R_ECH_REQUIRED)) { +# else + (reason == SSL_R_ECH_REJECTED)) { +# endif + + /* trace retry_configs if we got some */ + ossl_trace_ech_retry_configs(data, octx->ssl, reason); + + result = CURLE_ECH_REQUIRED; + failf(data, "ECH required: %s", + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); } #endif else { result = CURLE_SSL_CONNECT_ERROR; - ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + failf(data, "TLS connect error: %s", + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); } /* detail is already set to the SSL error above */ - /* If we e.g. use SSLv2 request-method and the server doesn't like us + /* If we e.g. use SSLv2 request-method and the server does not like us * (RST connection, etc.), OpenSSL gives no explanation whatsoever and * the SO_ERROR is also lost. */ @@ -3943,35 +4573,108 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, Curl_strerror(sockerr, extramsg, sizeof(extramsg)); failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%d ", extramsg[0] ? extramsg : SSL_ERROR_to_str(detail), - connssl->hostname, connssl->port); - return result; + connssl->peer.hostname, connssl->peer.port); } - /* Could be a CERT problem */ - failf(data, "%s", error_buffer); - return result; } } else { - /* we connected fine, we're not waiting for anything else. */ + int psigtype_nid = NID_undef; + const char *negotiated_group_name = NULL; + + /* we connected fine, we are not waiting for anything else. */ connssl->connecting_state = ssl_connect_3; - /* Informational message */ - infof(data, "SSL connection using %s / %s", - SSL_get_version(backend->handle), - SSL_get_cipher(backend->handle)); +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + SSL_get_peer_signature_type_nid(octx->ssl, &psigtype_nid); +#if (OPENSSL_VERSION_NUMBER >= 0x30200000L) + negotiated_group_name = SSL_get0_group_name(octx->ssl); +#else + negotiated_group_name = + OBJ_nid2sn(SSL_get_negotiated_group(octx->ssl) & 0x0000FFFF); +#endif +#endif -#ifdef HAS_ALPN + /* Informational message */ + infof(data, "SSL connection using %s / %s / %s / %s", + SSL_get_version(octx->ssl), + SSL_get_cipher(octx->ssl), + negotiated_group_name ? negotiated_group_name : "[blank]", + OBJ_nid2sn(psigtype_nid)); + +#ifdef USE_ECH_OPENSSL +# if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) + if(ECH_ENABLED(data)) { + char *inner = NULL, *outer = NULL; + const char *status = NULL; + int rv; + + rv = SSL_ech_get1_status(octx->ssl, &inner, &outer); + switch(rv) { + case SSL_ECH_STATUS_SUCCESS: + status = "succeeded"; + break; + case SSL_ECH_STATUS_GREASE_ECH: + status = "sent GREASE, got retry-configs"; + break; + case SSL_ECH_STATUS_GREASE: + status = "sent GREASE"; + break; + case SSL_ECH_STATUS_NOT_TRIED: + status = "not attempted"; + break; + case SSL_ECH_STATUS_NOT_CONFIGURED: + status = "not configured"; + break; + case SSL_ECH_STATUS_BACKEND: + status = "backend (unexpected)"; + break; + case SSL_ECH_STATUS_FAILED: + status = "failed"; + break; + case SSL_ECH_STATUS_BAD_CALL: + status = "bad call (unexpected)"; + break; + case SSL_ECH_STATUS_BAD_NAME: + status = "bad name (unexpected)"; + break; + default: + status = "unexpected status"; + infof(data, "ECH: unexpected status %d",rv); + } + infof(data, "ECH: result: status is %s, inner is %s, outer is %s", + (status ? status : "NULL"), + (inner ? inner : "NULL"), + (outer ? outer : "NULL")); + OPENSSL_free(inner); + 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); + } + if(rv != SSL_ECH_STATUS_SUCCESS + && data->set.tls_ech & CURLECH_HARD) { + infof(data, "ECH: ech-hard failed"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + infof(data, "ECH: result: status is not attempted"); + } +# endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ +#endif /* USE_ECH_OPENSSL */ + +#ifdef HAS_ALPN_OPENSSL /* Sets data and len to negotiated protocol, len is 0 if no protocol was * negotiated */ if(connssl->alpn) { const unsigned char *neg_protocol; unsigned int len; - SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len); + SSL_get0_alpn_selected(octx->ssl, &neg_protocol, &len); - return Curl_alpn_set_negotiated(cf, data, neg_protocol, len); + return Curl_alpn_set_negotiated(cf, data, connssl, neg_protocol, len); } #endif @@ -3983,8 +4686,8 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, * Heavily modified from: * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL */ -static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, - const char *pinnedpubkey) +static CURLcode ossl_pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, + const char *pinnedpubkey) { /* Scratch */ int len1 = 0, len2 = 0; @@ -3993,7 +4696,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, /* Result is returned to caller */ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -4014,12 +4717,12 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, if(!buff1) break; /* failed */ - /* https://www.openssl.org/docs/crypto/d2i_X509.html */ + /* https://docs.openssl.org/master/man3/d2i_X509/ */ len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp); /* * These checks are verifying we got back the same values as when we - * sized the buffer. It's pretty weak since they should always be the + * sized the buffer. it is pretty weak since they should always be the * same. But it gives us something to test. */ if((len1 != len2) || !temp || ((temp - buff1) != len1)) @@ -4037,34 +4740,98 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, return result; } -/* - * Get the server cert, verify it and show it, etc., only call failf() if the - * 'strict' argument is TRUE as otherwise all this is for informational - * purposes only! - * - * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack. - */ -static CURLcode servercert(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool strict) +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x3060000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(OPENSSL_IS_AWSLC) && \ + !defined(CURL_DISABLE_VERBOSE_STRINGS) +static void infof_certstack(struct Curl_easy *data, const SSL *ssl) +{ + STACK_OF(X509) *certstack; + long verify_result; + int num_cert_levels; + int cert_level; + + verify_result = SSL_get_verify_result(ssl); + if(verify_result != X509_V_OK) + certstack = SSL_get_peer_cert_chain(ssl); + else + certstack = SSL_get0_verified_chain(ssl); + num_cert_levels = sk_X509_num(certstack); + + for(cert_level = 0; cert_level < num_cert_levels; cert_level++) { + char cert_algorithm[80] = ""; + char group_name_final[80] = ""; + const X509_ALGOR *palg_cert = NULL; + const ASN1_OBJECT *paobj_cert = NULL; + X509 *current_cert; + EVP_PKEY *current_pkey; + int key_bits; + int key_sec_bits; + int get_group_name; + const char *type_name; + + current_cert = sk_X509_value(certstack, cert_level); + + X509_get0_signature(NULL, &palg_cert, current_cert); + X509_ALGOR_get0(&paobj_cert, NULL, NULL, palg_cert); + OBJ_obj2txt(cert_algorithm, sizeof(cert_algorithm), paobj_cert, 0); + + current_pkey = X509_get0_pubkey(current_cert); + key_bits = EVP_PKEY_bits(current_pkey); +#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define EVP_PKEY_get_security_bits EVP_PKEY_security_bits +#endif + key_sec_bits = EVP_PKEY_get_security_bits(current_pkey); +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + { + char group_name[80] = ""; + get_group_name = EVP_PKEY_get_group_name(current_pkey, group_name, + sizeof(group_name), NULL); + msnprintf(group_name_final, sizeof(group_name_final), "/%s", group_name); + } + type_name = current_pkey ? EVP_PKEY_get0_type_name(current_pkey) : NULL; +#else + get_group_name = 0; + type_name = NULL; +#endif + + infof(data, + " Certificate level %d: " + "Public key type %s%s (%d/%d Bits/secBits), signed using %s", + cert_level, type_name ? type_name : "?", + get_group_name == 0 ? "" : group_name_final, + key_bits, key_sec_bits, cert_algorithm); + } +} +#else +#define infof_certstack(data, ssl) +#endif + +#define MAX_CERT_NAME_LENGTH 2048 + +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer) { struct connectdata *conn = cf->conn; - struct ssl_connect_data *connssl = cf->ctx; 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); CURLcode result = CURLE_OK; - int rc; long lerr; X509 *issuer; BIO *fp = NULL; char error_buffer[256]=""; - char buffer[2048]; const char *ptr; BIO *mem = BIO_new(BIO_s_mem()); - struct ssl_backend_data *backend = connssl->backend; + bool strict = (conn_config->verifypeer || conn_config->verifyhost); + struct dynbuf dname; - DEBUGASSERT(backend); + DEBUGASSERT(octx); + + curlx_dyn_init(&dname, MAX_CERT_NAME_LENGTH); if(!mem) { failf(data, @@ -4077,36 +4844,37 @@ static CURLcode servercert(struct Curl_cfilter *cf, if(data->set.ssl.certinfo) /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, connssl->backend->handle); + (void)ossl_certchain(data, octx->ssl); - backend->server_cert = SSL_get1_peer_certificate(backend->handle); - if(!backend->server_cert) { + octx->server_cert = SSL_get1_peer_certificate(octx->ssl); + if(!octx->server_cert) { BIO_free(mem); if(!strict) return CURLE_OK; - failf(data, "SSL: couldn't get peer certificate"); + failf(data, "SSL: could not get peer certificate"); return CURLE_PEER_FAILED_VERIFICATION; } infof(data, "%s certificate:", - Curl_ssl_cf_is_proxy(cf)? "Proxy" : "Server"); + Curl_ssl_cf_is_proxy(cf) ? "Proxy" : "Server"); - rc = x509_name_oneline(X509_get_subject_name(backend->server_cert), - buffer, sizeof(buffer)); - infof(data, " subject: %s", rc?"[NONE]":buffer); + result = x509_name_oneline(X509_get_subject_name(octx->server_cert), + &dname); + infof(data, " subject: %s", result ? "[NONE]" : curlx_dyn_ptr(&dname)); #ifndef CURL_DISABLE_VERBOSE_STRINGS { + char *buf; long len; - ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert)); - len = BIO_get_mem_data(mem, (char **) &ptr); - infof(data, " start date: %.*s", (int)len, ptr); + ASN1_TIME_print(mem, X509_get0_notBefore(octx->server_cert)); + len = BIO_get_mem_data(mem, (char **) &buf); + infof(data, " start date: %.*s", (int)len, buf); (void)BIO_reset(mem); - ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert)); - len = BIO_get_mem_data(mem, (char **) &ptr); - infof(data, " expire date: %.*s", (int)len, ptr); + ASN1_TIME_print(mem, X509_get0_notAfter(octx->server_cert)); + len = BIO_get_mem_data(mem, (char **) &buf); + infof(data, " expire date: %.*s", (int)len, buf); (void)BIO_reset(mem); } #endif @@ -4114,24 +4882,25 @@ static CURLcode servercert(struct Curl_cfilter *cf, BIO_free(mem); if(conn_config->verifyhost) { - result = ossl_verifyhost(data, conn, backend->server_cert, - connssl->hostname, connssl->dispname); + result = ossl_verifyhost(data, conn, peer, octx->server_cert); if(result) { - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; + curlx_dyn_free(&dname); return result; } } - rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert), - buffer, sizeof(buffer)); - if(rc) { + result = x509_name_oneline(X509_get_issuer_name(octx->server_cert), + &dname); + if(result) { if(strict) - failf(data, "SSL: couldn't get X509-issuer name"); + failf(data, "SSL: could not get X509-issuer name"); result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " issuer: %s", buffer); + infof(data, " issuer: %s", curlx_dyn_ptr(&dname)); + curlx_dyn_free(&dname); /* We could do all sorts of certificate verification stuff here before deallocating the certificate. */ @@ -4147,8 +4916,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, " error %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_OUT_OF_MEMORY; } } @@ -4160,8 +4929,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, " error %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_OUT_OF_MEMORY; } @@ -4170,8 +4939,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, failf(data, "SSL: Unable to open issuer cert (%s)", conn_config->issuercert); BIO_free(fp); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } } @@ -4183,19 +4952,19 @@ static CURLcode servercert(struct Curl_cfilter *cf, conn_config->issuercert); BIO_free(fp); X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } - if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) { + if(X509_check_issued(issuer, octx->server_cert) != X509_V_OK) { if(strict) failf(data, "SSL: Certificate issuer check failed (%s)", conn_config->issuercert); BIO_free(fp); X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } @@ -4205,7 +4974,7 @@ static CURLcode servercert(struct Curl_cfilter *cf, X509_free(issuer); } - lerr = SSL_get_verify_result(backend->handle); + lerr = SSL_get_verify_result(octx->ssl); ssl_config->certverifyresult = lerr; if(lerr != X509_V_OK) { if(conn_config->verifypeer) { @@ -4224,35 +4993,39 @@ static CURLcode servercert(struct Curl_cfilter *cf, else infof(data, " SSL certificate verify ok."); } + infof_certstack(data, octx->ssl); -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) - if(conn_config->verifystatus) { - result = verifystatus(cf, data); +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) + if(conn_config->verifystatus && !octx->reused_session) { + /* do not do this after Session ID reuse */ + result = verifystatus(cf, data, octx); if(result) { - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } } #endif if(!strict) - /* when not strict, we don't bother about the verify cert problems */ + /* when not strict, we do not bother about the verify cert problems */ result = CURLE_OK; - ptr = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + ptr = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif if(!result && ptr) { - result = pkp_pin_peer_pubkey(data, backend->server_cert, ptr); + result = ossl_pkp_pin_peer_pubkey(data, octx->server_cert, ptr); if(result) failf(data, "SSL: public key does not match pinned public key"); } - X509_free(backend->server_cert); - backend->server_cert = NULL; - connssl->connecting_state = ssl_connect_done; + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } @@ -4262,35 +5035,110 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); /* * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to + * man-in-the-middle attack; NEVERTHELESS, if we are told explicitly not to * verify the peer, ignore faults and failures from the server cert * operations. */ - result = servercert(cf, data, conn_config->verifypeer || - conn_config->verifyhost); + result = Curl_oss_check_peer_cert(cf, data, octx, &connssl->peer); + if(result) + /* on error, remove sessions we might have in the pool */ + Curl_ssl_scache_remove_all(cf, data, connssl->peer.scache_key); + + return result; +} - if(!result) - connssl->connecting_state = ssl_connect_done; +#ifdef HAVE_OPENSSL_EARLYDATA +static CURLcode ossl_send_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen, nwritten; + int rc; + + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending); + octx->io_result = CURLE_OK; + while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) { + nwritten = 0; + rc = SSL_write_early_data(octx->ssl, buf, blen, &nwritten); + CURL_TRC_CF(data, cf, "SSL_write_early_data(len=%zu) -> %d, %zu", + blen, rc, nwritten); + if(rc <= 0) { + long sslerror; + char error_buffer[256]; + int err = SSL_get_error(octx->ssl, rc); + + switch(err) { + case SSL_ERROR_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_AGAIN; + goto out; + case SSL_ERROR_WANT_WRITE: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_AGAIN; + goto out; + case SSL_ERROR_SYSCALL: { + int sockerr = SOCKERRNO; + if(octx->io_result == CURLE_AGAIN) { + result = CURLE_AGAIN; + goto out; + } + sslerror = ERR_get_error(); + if(sslerror) + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); + else if(sockerr) + Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); + + failf(data, OSSL_PACKAGE " SSL_write:early_data: %s, errno %d", + error_buffer, sockerr); + result = CURLE_SEND_ERROR; + goto out; + } + case SSL_ERROR_SSL: { + /* A failure in the SSL library occurred, usually a protocol error. + The OpenSSL error queue contains more information on the error. */ + sslerror = ERR_get_error(); + failf(data, "SSL_write_early_data() error: %s", + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); + result = CURLE_SEND_ERROR; + goto out; + } + default: + /* a true error */ + failf(data, OSSL_PACKAGE " SSL_write_early_data: %s, errno %d", + SSL_ERROR_to_str(err), SOCKERRNO); + result = CURLE_SEND_ERROR; + goto out; + } + } + Curl_bufq_skip(&connssl->earlydata, nwritten); + } + /* sent everything there was */ + infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip); +out: return result; } +#endif /* HAVE_OPENSSL_EARLYDATA */ -static CURLcode ossl_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode ossl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - int what; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -4298,127 +5146,79 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, return CURLE_OK; } - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time is already up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(ssl_connect_1 == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "ossl_connect, step1"); result = ossl_connect_step1(cf, data); if(result) goto out; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; + if(ssl_connect_2 == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "ossl_connect, step2"); +#ifdef HAVE_OPENSSL_EARLYDATA + if(connssl->earlydata_state == ssl_earlydata_await) { goto out; } - - /* if ssl is expecting something, check if it's available. */ - if(!nonblocking && - (connssl->connecting_state == ssl_connect_2_reading || - connssl->connecting_state == ssl_connect_2_writing)) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - if(0 == what) { - /* timeout */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; + else if(connssl->earlydata_state == ssl_earlydata_sending) { + result = ossl_send_earlydata(cf, data); + if(result) goto out; - } - /* socket is readable or writable */ + connssl->earlydata_state = ssl_earlydata_sent; } +#endif + DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) || + (connssl->earlydata_state == ssl_earlydata_sent)); - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ result = ossl_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(result) goto out; - - } /* repeat step2 until all transactions are done. */ + } if(ssl_connect_3 == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "ossl_connect, step3"); result = ossl_connect_step3(cf, data); if(result) goto out; + connssl->connecting_state = ssl_connect_done; +#ifdef HAVE_OPENSSL_EARLYDATA + if(connssl->earlydata_state > ssl_earlydata_none) { + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + /* We should be in this state by now */ + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent); + connssl->earlydata_state = + (SSL_get_early_data_status(octx->ssl) == SSL_EARLY_DATA_ACCEPTED) ? + ssl_earlydata_accepted : ssl_earlydata_rejected; + } +#endif } if(ssl_connect_done == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "ossl_connect, done"); connssl->state = ssl_connection_complete; - *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; out: + if(result == CURLE_AGAIN) { + *done = FALSE; + return CURLE_OK; + } + *done = ((connssl->state == ssl_connection_complete) || + (connssl->state == ssl_connection_deferred)); return result; } -static CURLcode ossl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return ossl_connect_common(cf, data, TRUE, done); -} - -static CURLcode ossl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - - result = ossl_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - static bool ossl_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct ssl_connect_data *ctx = cf->ctx; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(ctx && ctx->backend); - if(ctx->backend->handle && SSL_pending(ctx->backend->handle)) + DEBUGASSERT(connssl && octx); + if(octx->ssl && SSL_pending(octx->ssl)) return TRUE; return FALSE; } @@ -4437,25 +5237,27 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, int memlen; int rc; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(backend); + DEBUGASSERT(octx); ERR_clear_error(); + connssl->io_need = CURL_SSL_IO_NEED_NONE; memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - rc = SSL_write(backend->handle, mem, memlen); + rc = SSL_write(octx->ssl, mem, memlen); if(rc <= 0) { - err = SSL_get_error(backend->handle, rc); + err = SSL_get_error(octx->ssl, rc); switch(err) { case SSL_ERROR_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + *curlcode = CURLE_AGAIN; + rc = -1; + goto out; case SSL_ERROR_WANT_WRITE: - /* The operation did not complete; the same TLS/SSL I/O function - should be called again later. This is basically an EWOULDBLOCK - equivalent. */ *curlcode = CURLE_AGAIN; rc = -1; goto out; @@ -4463,7 +5265,7 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, { int sockerr = SOCKERRNO; - if(backend->io_result == CURLE_AGAIN) { + if(octx->io_result == CURLE_AGAIN) { *curlcode = CURLE_AGAIN; rc = -1; goto out; @@ -4473,10 +5275,10 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); else if(sockerr) Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); + failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", error_buffer, sockerr); *curlcode = CURLE_SEND_ERROR; @@ -4486,22 +5288,9 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, case SSL_ERROR_SSL: { /* A failure in the SSL library occurred, usually a protocol error. The OpenSSL error queue contains more information on the error. */ - struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next); - struct ssl_connect_data *connssl_next = cf_ssl_next? - cf_ssl_next->ctx : NULL; sslerror = ERR_get_error(); - if(ERR_GET_LIB(sslerror) == ERR_LIB_SSL && - ERR_GET_REASON(sslerror) == SSL_R_BIO_NOT_SET && - connssl->state == ssl_connection_complete && - (connssl_next && connssl_next->state == ssl_connection_complete) - ) { - char ver[120]; - (void)ossl_version(ver, sizeof(ver)); - failf(data, "Error: %s does not support double SSL tunneling.", ver); - } - else - failf(data, "SSL_write() error: %s", - ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); + failf(data, "SSL_write() error: %s", + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); *curlcode = CURLE_SEND_ERROR; rc = -1; goto out; @@ -4533,19 +5322,20 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, int buffsize; struct connectdata *conn = cf->conn; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(backend); + DEBUGASSERT(octx); ERR_clear_error(); + connssl->io_need = CURL_SSL_IO_NEED_NONE; buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - nread = (ssize_t)SSL_read(backend->handle, buf, buffsize); + nread = (ssize_t)SSL_read(octx->ssl, buf, buffsize); if(nread <= 0) { /* failed SSL_read */ - int err = SSL_get_error(backend->handle, (int)nread); + int err = SSL_get_error(octx->ssl, (int)nread); switch(err) { case SSL_ERROR_NONE: /* this is not an error */ @@ -4558,16 +5348,19 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, connclose(conn, "TLS close_notify"); break; case SSL_ERROR_WANT_READ: + *curlcode = CURLE_AGAIN; + nread = -1; + goto out; case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ + connssl->io_need = CURL_SSL_IO_NEED_SEND; *curlcode = CURLE_AGAIN; nread = -1; goto out; default: /* openssl/ssl.h for SSL_ERROR_SYSCALL says "look at error stack/return value/errno" */ - /* https://www.openssl.org/docs/crypto/ERR_get_error.html */ - if(backend->io_result == CURLE_AGAIN) { + /* https://docs.openssl.org/master/man3/ERR_get_error/ */ + if(octx->io_result == CURLE_AGAIN) { *curlcode = CURLE_AGAIN; nread = -1; goto out; @@ -4581,10 +5374,9 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); else if(sockerr && err == SSL_ERROR_SYSCALL) Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d", error_buffer, sockerr); *curlcode = CURLE_RECV_ERROR; @@ -4594,7 +5386,7 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, /* For debug builds be a little stricter and error on any SSL_ERROR_SYSCALL. For example a server may have closed the connection abruptly without a close_notify alert. For compatibility with older - peers we don't do this by default. #4624 + peers we do not do this by default. #4624 We can use this to gauge how many users may be affected, and if it goes ok eventually transition to allow in dev and release with @@ -4623,12 +5415,95 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, return nread; } -static size_t ossl_version(char *buffer, size_t size) +static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding) +{ + /* required for X509_get_signature_nid support */ +#ifdef HAVE_SSL_X509_GET_SIGNATURE_NID + X509 *cert; + int algo_nid; + const EVP_MD *algo_type; + const char *algo_name; + unsigned int length; + unsigned char buf[EVP_MAX_MD_SIZE]; + + const char prefix[] = "tls-server-end-point:"; + struct connectdata *conn = data->conn; + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + struct ossl_ctx *octx = NULL; + + do { + const struct Curl_cftype *cft = cf->cft; + struct ssl_connect_data *connssl = cf->ctx; + + if(cft->name && !strcmp(cft->name, "SSL")) { + octx = (struct ossl_ctx *)connssl->backend; + break; + } + + if(cf->next) + cf = cf->next; + + } while(cf->next); + + if(!octx) { + failf(data, "Failed to find the SSL filter"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + cert = SSL_get1_peer_certificate(octx->ssl); + if(!cert) { + /* No server certificate, don't do channel binding */ + return CURLE_OK; + } + + if(!OBJ_find_sigid_algs(X509_get_signature_nid(cert), &algo_nid, NULL)) { + failf(data, + "Unable to find digest NID for certificate signature algorithm"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + /* https://datatracker.ietf.org/doc/html/rfc5929#section-4.1 */ + if(algo_nid == NID_md5 || algo_nid == NID_sha1) { + algo_type = EVP_sha256(); + } + else { + algo_type = EVP_get_digestbynid(algo_nid); + if(!algo_type) { + algo_name = OBJ_nid2sn(algo_nid); + failf(data, "Could not find digest algorithm %s (NID %d)", + algo_name ? algo_name : "(null)", algo_nid); + return CURLE_SSL_INVALIDCERTSTATUS; + } + } + + if(!X509_digest(cert, algo_type, buf, &length)) { + failf(data, "X509_digest() failed"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + /* Append "tls-server-end-point:" */ + if(curlx_dyn_addn(binding, prefix, sizeof(prefix) - 1) != CURLE_OK) + return CURLE_OUT_OF_MEMORY; + /* Append digest */ + if(curlx_dyn_addn(binding, buf, length)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +#else + /* No X509_get_signature_nid support */ + (void)data; /* unused */ + (void)sockindex; /* unused */ + (void)binding; /* unused */ + return CURLE_OK; +#endif +} + +size_t Curl_ossl_version(char *buffer, size_t size) { #ifdef LIBRESSL_VERSION_NUMBER -#ifdef HAVE_OPENSSL_VERSION char *p; - int count; + size_t count; const char *ver = OpenSSL_version(OPENSSL_VERSION); const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ if(strncasecompare(ver, expected, sizeof(expected) - 1)) { @@ -4640,13 +5515,6 @@ static size_t ossl_version(char *buffer, size_t size) *p = '_'; } return count; -#else - return msnprintf(buffer, size, "%s/%lx.%lx.%lx", - OSSL_PACKAGE, - (LIBRESSL_VERSION_NUMBER>>28)&0xf, - (LIBRESSL_VERSION_NUMBER>>20)&0xff, - (LIBRESSL_VERSION_NUMBER>>12)&0xff); -#endif #elif defined(OPENSSL_IS_BORINGSSL) #ifdef CURL_BORINGSSL_VERSION return msnprintf(buffer, size, "%s/%s", @@ -4670,25 +5538,19 @@ static size_t ossl_version(char *buffer, size_t size) sub[2]='\0'; sub[1]='\0'; ssleay_value = OpenSSL_version_num(); - if(ssleay_value < 0x906000) { - ssleay_value = SSLEAY_VERSION_NUMBER; - sub[0]='\0'; - } - else { - if(ssleay_value&0xff0) { - int minor_ver = (ssleay_value >> 4) & 0xff; - if(minor_ver > 26) { - /* handle extended version introduced for 0.9.8za */ - sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1); - sub[0] = 'z'; - } - else { - sub[0] = (char) (minor_ver + 'a' - 1); - } + if(ssleay_value&0xff0) { + int minor_ver = (ssleay_value >> 4) & 0xff; + if(minor_ver > 26) { + /* handle extended version introduced for 0.9.8za */ + sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1); + sub[0] = 'z'; + } + else { + sub[0] = (char) (minor_ver + 'a' - 1); } - else - sub[0]='\0'; } + else + sub[0]='\0'; return msnprintf(buffer, size, "%s/%lx.%lx.%lx%s" #ifdef OPENSSL_FIPS @@ -4696,9 +5558,9 @@ static size_t ossl_version(char *buffer, size_t size) #endif , OSSL_PACKAGE, - (ssleay_value>>28)&0xf, - (ssleay_value>>20)&0xff, - (ssleay_value>>12)&0xff, + (ssleay_value >> 28) & 0xf, + (ssleay_value >> 20) & 0xff, + (ssleay_value >> 12) & 0xff, sub); #endif /* OPENSSL_IS_BORINGSSL */ } @@ -4710,18 +5572,18 @@ static CURLcode ossl_random(struct Curl_easy *data, int rc; if(data) { if(ossl_seed(data)) /* Initiate the seed if not already done */ - return CURLE_FAILED_INIT; /* couldn't seed for some reason */ + return CURLE_FAILED_INIT; /* could not seed for some reason */ } else { if(!rand_enough()) return CURLE_FAILED_INIT; } /* RAND_bytes() returns 1 on success, 0 otherwise. */ - rc = RAND_bytes(entropy, curlx_uztosi(length)); - return (rc == 1 ? CURLE_OK : CURLE_FAILED_INIT); + rc = RAND_bytes(entropy, (ossl_valsize_t)curlx_uztosi(length)); + return rc == 1 ? CURLE_OK : CURLE_FAILED_INIT; } -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) +#ifndef OPENSSL_NO_SHA256 static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ size_t tmplen, unsigned char *sha256sum /* output */, @@ -4734,7 +5596,10 @@ static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ mdctx = EVP_MD_CTX_create(); if(!mdctx) return CURLE_OUT_OF_MEMORY; - EVP_DigestInit(mdctx, EVP_sha256()); + if(!EVP_DigestInit(mdctx, EVP_sha256())) { + EVP_MD_CTX_destroy(mdctx); + return CURLE_FAILED_INIT; + } EVP_DigestUpdate(mdctx, tmp, tmplen); EVP_DigestFinal_ex(mdctx, sha256sum, &len); EVP_MD_CTX_destroy(mdctx); @@ -4744,8 +5609,7 @@ static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ static bool ossl_cert_status_request(void) { -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) return TRUE; #else return FALSE; @@ -4756,24 +5620,10 @@ static void *ossl_get_internals(struct ssl_connect_data *connssl, CURLINFO info) { /* Legacy: CURLINFO_TLS_SESSION must return an SSL_CTX pointer. */ - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); return info == CURLINFO_TLS_SESSION ? - (void *)backend->ctx : (void *)backend->handle; -} - -static void ossl_free_multi_ssl_backend_data( - struct multi_ssl_backend_data *mbackend) -{ -#if defined(HAVE_SSL_X509_STORE_SHARE) - if(mbackend->store) { - X509_STORE_free(mbackend->store); - } - free(mbackend->CAfile); - free(mbackend); -#else /* HAVE_SSL_X509_STORE_SHARE */ - (void)mbackend; -#endif /* HAVE_SSL_X509_STORE_SHARE */ + (void *)octx->ssl_ctx : (void *)octx->ssl; } const struct Curl_ssl Curl_ssl_openssl = { @@ -4787,39 +5637,42 @@ const struct Curl_ssl Curl_ssl_openssl = { #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES SSLSUPP_TLS13_CIPHERSUITES | #endif - SSLSUPP_HTTPS_PROXY, +#ifdef HAVE_SSL_CTX_SET1_SIGALGS + SSLSUPP_SIGNATURE_ALGORITHMS | +#endif +#ifdef USE_ECH_OPENSSL + SSLSUPP_ECH | +#endif + SSLSUPP_CA_CACHE | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, - sizeof(struct ssl_backend_data), + sizeof(struct ossl_ctx), ossl_init, /* init */ ossl_cleanup, /* cleanup */ - ossl_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ + Curl_ossl_version, /* version */ ossl_shutdown, /* shutdown */ ossl_data_pending, /* data_pending */ ossl_random, /* random */ ossl_cert_status_request, /* cert_status_request */ ossl_connect, /* connect */ - ossl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks,/* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ ossl_get_internals, /* get_internals */ ossl_close, /* close_one */ ossl_close_all, /* close_all */ - ossl_session_free, /* session_free */ - ossl_set_engine, /* set_engine */ + ossl_set_engine, /* set_engine or provider */ ossl_set_engine_default, /* set_engine_default */ ossl_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) + NULL, /* false_start */ +#ifndef OPENSSL_NO_SHA256 ossl_sha256sum, /* sha256sum */ #else NULL, /* sha256sum */ #endif - NULL, /* use of data in this connection */ - NULL, /* remote of data from this connection */ - ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */ ossl_recv, /* recv decrypted data */ ossl_send, /* send data to encrypt */ + ossl_get_channel_binding /* get_channel_binding */ }; #endif /* USE_OPENSSL */ diff --git a/Utilities/cmcurl/lib/vtls/openssl.h b/Utilities/cmcurl/lib/vtls/openssl.h index 950faab8892..8d063e25acc 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.h +++ b/Utilities/cmcurl/lib/vtls/openssl.h @@ -24,37 +24,88 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_OPENSSL /* * This header should only be needed to get included by vtls.c, openssl.c * and ngtcp2.c */ +#include +#include #include -#include "urldata.h" +#include "../urldata.h" /* - * In an effort to avoid using 'X509 *' here, we instead use the struct - * x509_st version of the type so that we can forward-declare it here without - * having to include . Including that header causes name - * conflicts when libcurl is built with both Schannel and OpenSSL support. + * Whether SSL_CTX_set_keylog_callback is available. + * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 + * BoringSSL: supported since d28f59c27bac (committed 2015-11-19) + * LibreSSL: not supported. 3.5.0+ has a stub function that does nothing. */ -struct x509_st; -CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - struct x509_st *server_cert); -extern const struct Curl_ssl Curl_ssl_openssl; +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || \ + defined(OPENSSL_IS_BORINGSSL) +#define HAVE_KEYLOG_CALLBACK +#endif + +/* Check for OpenSSL 1.1.1 which has early data support. */ +#undef HAVE_OPENSSL_EARLYDATA +#if OPENSSL_VERSION_NUMBER >= 0x10100010L && defined(TLS1_3_VERSION) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) +#define HAVE_OPENSSL_EARLYDATA +#endif + +struct alpn_spec; +struct ssl_peer; +struct Curl_ssl_session; + +/* Struct to hold a curl OpenSSL instance */ +struct ossl_ctx { + /* these ones requires specific SSL-types */ + SSL_CTX* ssl_ctx; + SSL* ssl; + X509* server_cert; + BIO_METHOD *bio_method; + CURLcode io_result; /* result of last BIO cfilter operation */ +#ifndef HAVE_KEYLOG_CALLBACK + /* Set to true once a valid keylog entry has been created to avoid dupes. + This is a bool and not a bitfield because it is passed by address. */ + bool keylog_done; +#endif + BIT(x509_store_setup); /* x509 store has been set up */ + BIT(reused_session); /* session-ID was reused for this */ +}; + +size_t Curl_ossl_version(char *buffer, size_t size); + +typedef CURLcode Curl_ossl_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); + +typedef int Curl_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid); +typedef CURLcode Curl_ossl_init_session_reuse_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data); + +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb); -struct ssl_ctx_st; -CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, - struct ssl_ctx_st *ctx, char *cert_file, - const struct curl_blob *cert_blob, - const char *cert_type, char *key_file, - const struct curl_blob *key_blob, - const char *key_type, char *key_passwd); +#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif -CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl); +extern const struct Curl_ssl Curl_ssl_openssl; /** * Setup the OpenSSL X509_STORE in `ssl_ctx` for the cfilter `cf` and @@ -65,5 +116,31 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, SSL_CTX *ssl_ctx); +CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx); + +/* + * Add a new session to the cache. Takes ownership of the session. + */ +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + SSL_SESSION *ssl_sessionid, + int ietf_tls_id, + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len); + +/* + * Get the server cert, verify it and show it, etc., only call failf() if + * ssl config verifypeer or -host is set. Otherwise all this is for + * informational purposes only! + */ +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer); + #endif /* USE_OPENSSL */ #endif /* HEADER_CURL_SSLUSE_H */ diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c index 097c58ce1c7..cb9fd6230a5 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.c +++ b/Utilities/cmcurl/lib/vtls/rustls.c @@ -7,6 +7,8 @@ * * Copyright (C) Jacob Hoffman-Andrews, * + * Copyright (C) kpcyrd, + * Copyright (C) Daniel McCarney, * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -22,33 +24,36 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_RUSTLS -#include "curl_printf.h" +#include "../curl_printf.h" -#include #include -#include "inet_pton.h" -#include "urldata.h" -#include "sendf.h" +#include "../curlx/inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" #include "vtls.h" #include "vtls_int.h" -#include "select.h" -#include "strerror.h" -#include "multiif.h" +#include "rustls.h" +#include "keylog.h" +#include "../strerror.h" +#include "cipher_suite.h" +#include "x509asn1.h" -struct ssl_backend_data +struct rustls_ssl_backend_data { const struct rustls_client_config *config; struct rustls_connection *conn; - bool data_pending; + size_t plain_out_buffered; + BIT(data_in_pending); + BIT(sent_shutdown); }; /* For a given rustls_result error code, return the best-matching CURLcode. */ -static CURLcode map_error(rustls_result r) +static CURLcode map_error(const rustls_result r) { if(rustls_result_is_cert_error(r)) { return CURLE_PEER_FAILED_VERIFICATION; @@ -59,26 +64,29 @@ static CURLcode map_error(rustls_result r) case RUSTLS_RESULT_NULL_PARAMETER: return CURLE_BAD_FUNCTION_ARGUMENT; default: - return CURLE_READ_ERROR; + return CURLE_RECV_ERROR; } } +static void +rustls_failf(struct Curl_easy *data, const rustls_result rr, const char *msg) +{ + char errorbuf[STRERROR_LEN]; + size_t errorlen; + rustls_error(rr, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "%s: %.*s", msg, (int)errorlen, errorbuf); +} + static bool cr_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct ssl_connect_data *ctx = cf->ctx; + const struct ssl_connect_data *ctx = cf->ctx; + struct rustls_ssl_backend_data *backend; (void)data; DEBUGASSERT(ctx && ctx->backend); - return ctx->backend->data_pending; -} - -static CURLcode -cr_connect(struct Curl_cfilter *cf UNUSED_PARAM, - struct Curl_easy *data UNUSED_PARAM) -{ - infof(data, "rustls_connect: unimplemented"); - return CURLE_SSL_CONNECT_ERROR; + backend = (struct rustls_ssl_backend_data *)ctx->backend; + return backend->data_in_pending; } struct io_ctx { @@ -89,34 +97,37 @@ struct io_ctx { static int read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) { - struct io_ctx *io_ctx = userdata; + const struct io_ctx *io_ctx = userdata; + struct ssl_connect_data *const connssl = io_ctx->cf->ctx; CURLcode result; int ret = 0; ssize_t nread = Curl_conn_cf_recv(io_ctx->cf->next, io_ctx->data, (char *)buf, len, &result); if(nread < 0) { nread = 0; + /* !checksrc! disable ERRNOVAR 4 */ if(CURLE_AGAIN == result) ret = EAGAIN; else ret = EINVAL; } - *out_n = (int)nread; - /* - DEBUGF(LOG_CF(io_ctx->data, io_ctx->cf, "cf->next recv(len=%zu) -> %zd, %d", - len, nread, result)); - */ + else if(nread == 0) + connssl->peer_closed = TRUE; + *out_n = (uintptr_t)nread; + CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next recv(len=%zu) -> %zd, %d", + len, nread, result); return ret; } static int write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) { - struct io_ctx *io_ctx = userdata; + const struct io_ctx *io_ctx = userdata; CURLcode result; int ret = 0; ssize_t nwritten = Curl_conn_cf_send(io_ctx->cf->next, io_ctx->data, - (const char *)buf, len, &result); + (const char *)buf, len, FALSE, + &result); if(nwritten < 0) { nwritten = 0; if(CURLE_AGAIN == result) @@ -124,19 +135,18 @@ write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) else ret = EINVAL; } - *out_n = (int)nwritten; - /* - DEBUGF(LOG_CF(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d", - len, nwritten, result)); - */ + *out_n = (uintptr_t)nwritten; + CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d", + len, nwritten, result); return ret; } static ssize_t tls_recv_more(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct ssl_connect_data *const connssl = cf->ctx; - struct ssl_backend_data *const backend = connssl->backend; + const struct ssl_connect_data *const connssl = cf->ctx; + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; struct io_ctx io_ctx; size_t tls_bytes_read = 0; rustls_io_result io_error; @@ -154,44 +164,41 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, char buffer[STRERROR_LEN]; failf(data, "reading from socket: %s", Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_READ_ERROR; + *err = CURLE_RECV_ERROR; return -1; } rresult = rustls_connection_process_new_packets(backend->conn); if(rresult != RUSTLS_RESULT_OK) { - char errorbuf[255]; - size_t errorlen; - rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_process_new_packets: %.*s", - errorlen, errorbuf); + rustls_failf(data, rresult, "rustls_connection_process_new_packets"); *err = map_error(rresult); return -1; } - backend->data_pending = TRUE; + backend->data_in_pending = TRUE; *err = CURLE_OK; return (ssize_t)tls_bytes_read; } /* * On each run: - * - Read a chunk of bytes from the socket into rustls' TLS input buffer. - * - Tell rustls to process any new packets. - * - Read out as many plaintext bytes from rustls as possible, until hitting + * - Read a chunk of bytes from the socket into Rustls' TLS input buffer. + * - Tell Rustls to process any new packets. + * - Read out as many plaintext bytes from Rustls as possible, until hitting * error, EOF, or EAGAIN/EWOULDBLOCK, or plainbuf/plainlen is filled up. * - * It's okay to call this function with plainbuf == NULL and plainlen == 0. - * In that case, it will copy bytes from the socket into rustls' TLS input - * buffer, and process packets, but won't consume bytes from rustls' plaintext - * output buffer. + * it is okay to call this function with plainbuf == NULL and plainlen == 0. In + * that case, it will copy bytes from the socket into Rustls' TLS input + * buffer, and process packets, but will not consume bytes from Rustls' + * plaintext output buffer. */ static ssize_t cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *plainbuf, size_t plainlen, CURLcode *err) { - struct ssl_connect_data *const connssl = cf->ctx; - struct ssl_backend_data *const backend = connssl->backend; + const struct ssl_connect_data *const connssl = cf->ctx; + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; struct rustls_connection *rconn = NULL; size_t n = 0; size_t plain_bytes_copied = 0; @@ -203,7 +210,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, rconn = backend->conn; while(plain_bytes_copied < plainlen) { - if(!backend->data_pending) { + if(!backend->data_in_pending) { if(tls_recv_more(cf, data, err) < 0) { if(*err != CURLE_AGAIN) { nread = -1; @@ -214,26 +221,23 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } rresult = rustls_connection_read(rconn, - (uint8_t *)plainbuf + plain_bytes_copied, - plainlen - plain_bytes_copied, - &n); + (uint8_t *)plainbuf + plain_bytes_copied, + plainlen - plain_bytes_copied, + &n); if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { - backend->data_pending = FALSE; + backend->data_in_pending = FALSE; } else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { failf(data, "rustls: peer closed TCP connection " - "without first closing TLS connection"); - *err = CURLE_READ_ERROR; + "without first closing TLS connection"); + *err = CURLE_RECV_ERROR; nread = -1; goto out; } else if(rresult != RUSTLS_RESULT_OK) { - /* n always equals 0 in this case, don't need to check it */ - char errorbuf[255]; - size_t errorlen; - rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf); - *err = CURLE_READ_ERROR; + /* n always equals 0 in this case, do not need to check it */ + rustls_failf(data, rresult, "rustls_connection_read"); + *err = CURLE_RECV_ERROR; nread = -1; goto out; } @@ -263,51 +267,101 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } out: - DEBUGF(LOG_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", - plainlen, nread, *err)); + CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", + plainlen, nread, *err); return nread; } +static CURLcode cr_flush_out(struct Curl_cfilter *cf, struct Curl_easy *data, + struct rustls_connection *rconn) +{ + struct io_ctx io_ctx; + rustls_io_result io_error; + size_t tlswritten = 0; + size_t tlswritten_total = 0; + + io_ctx.cf = cf; + io_ctx.data = data; + + while(rustls_connection_wants_write(rconn)) { + io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, + &tlswritten); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", + tlswritten_total); + return CURLE_AGAIN; + } + else if(io_error) { + char buffer[STRERROR_LEN]; + failf(data, "writing to socket: %s", + Curl_strerror(io_error, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + if(tlswritten == 0) { + failf(data, "EOF in swrite"); + return CURLE_SEND_ERROR; + } + CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); + tlswritten_total += tlswritten; + } + return CURLE_OK; +} + /* * On each call: - * - Copy `plainlen` bytes into rustls' plaintext input buffer (if > 0). - * - Fully drain rustls' plaintext output buffer into the socket until + * - Copy `plainlen` bytes into Rustls' plaintext input buffer (if > 0). + * - Fully drain Rustls' plaintext output buffer into the socket until * we get either an error or EAGAIN/EWOULDBLOCK. * - * It's okay to call this function with plainbuf == NULL and plainlen == 0. - * In that case, it won't read anything into rustls' plaintext input buffer. - * It will only drain rustls' plaintext output buffer into the socket. + * it is okay to call this function with plainbuf == NULL and plainlen == 0. + * In that case, it will not read anything into Rustls' plaintext input buffer. + * It will only drain Rustls' plaintext output buffer into the socket. */ static ssize_t cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *plainbuf, size_t plainlen, CURLcode *err) { - struct ssl_connect_data *const connssl = cf->ctx; - struct ssl_backend_data *const backend = connssl->backend; + const struct ssl_connect_data *const connssl = cf->ctx; + struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; struct rustls_connection *rconn = NULL; - struct io_ctx io_ctx; size_t plainwritten = 0; - size_t tlswritten = 0; - size_t tlswritten_total = 0; - rustls_result rresult; - rustls_io_result io_error; - char errorbuf[256]; - size_t errorlen; + const unsigned char *buf = plainbuf; + size_t blen = plainlen; + ssize_t nwritten = 0; DEBUGASSERT(backend); rconn = backend->conn; + DEBUGASSERT(rconn); + + CURL_TRC_CF(data, cf, "cf_send(len=%zu)", plainlen); + + /* If a previous send blocked, we already added its plain bytes + * to rustsls and must not do that again. Flush the TLS bytes and, + * if successful, deduct the previous plain bytes from the current + * send. */ + if(backend->plain_out_buffered) { + *err = cr_flush_out(cf, data, rconn); + CURL_TRC_CF(data, cf, "cf_send: flushing %zu previously added bytes -> %d", + backend->plain_out_buffered, *err); + if(*err) + return -1; + if(blen > backend->plain_out_buffered) { + blen -= backend->plain_out_buffered; + buf += backend->plain_out_buffered; + } + else + blen = 0; + nwritten += (ssize_t)backend->plain_out_buffered; + backend->plain_out_buffered = 0; + } - DEBUGF(LOG_CF(data, cf, "cf_send: %ld plain bytes", plainlen)); - - io_ctx.cf = cf; - io_ctx.data = data; - - if(plainlen > 0) { - rresult = rustls_connection_write(rconn, plainbuf, plainlen, - &plainwritten); + if(blen > 0) { + rustls_result rresult; + CURL_TRC_CF(data, cf, "cf_send: adding %zu plain bytes to Rustls", blen); + rresult = rustls_connection_write(rconn, buf, blen, &plainwritten); if(rresult != RUSTLS_RESULT_OK) { - rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf); + rustls_failf(data, rresult, "rustls_connection_write"); *err = CURLE_WRITE_ERROR; return -1; } @@ -318,245 +372,895 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, } } - while(rustls_connection_wants_write(rconn)) { - io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, - &tlswritten); - if(io_error == EAGAIN || io_error == EWOULDBLOCK) { - DEBUGF(LOG_CF(data, cf, "cf_send: EAGAIN after %zu bytes", - tlswritten_total)); - *err = CURLE_AGAIN; - return -1; - } - else if(io_error) { - char buffer[STRERROR_LEN]; - failf(data, "writing to socket: %s", - Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_WRITE_ERROR; - return -1; - } - if(tlswritten == 0) { - failf(data, "EOF in swrite"); - *err = CURLE_WRITE_ERROR; - return -1; + *err = cr_flush_out(cf, data, rconn); + if(*err) { + if(CURLE_AGAIN == *err) { + /* The TLS bytes may have been partially written, but we fail the + * complete send() and remember how much we already added to Rustls. */ + CURL_TRC_CF(data, cf, "cf_send: EAGAIN, remember we added %zu plain" + " bytes already to Rustls", blen); + backend->plain_out_buffered = plainwritten; + if(nwritten) { + *err = CURLE_OK; + return (ssize_t)nwritten; + } } - DEBUGF(LOG_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten)); - tlswritten_total += tlswritten; + return -1; } + else + nwritten += (ssize_t)plainwritten; - return plainwritten; + CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %d, %zd", + plainlen, *err, nwritten); + return nwritten; } -/* A server certificate verify callback for rustls that always returns +/* A server certificate verify callback for Rustls that always returns RUSTLS_RESULT_OK, or in other words disable certificate verification. */ -static enum rustls_result +static uint32_t cr_verify_none(void *userdata UNUSED_PARAM, const rustls_verify_server_cert_params *params UNUSED_PARAM) { return RUSTLS_RESULT_OK; } -static bool -cr_hostname_is_ip(const char *hostname) +static int +read_file_into(const char *filename, + struct dynbuf *out) +{ + FILE *f = fopen(filename, FOPEN_READTEXT); + if(!f) { + return 0; + } + + while(!feof(f)) { + uint8_t buf[256]; + const size_t rr = fread(buf, 1, sizeof(buf), f); + if(rr == 0 || + CURLE_OK != curlx_dyn_addn(out, buf, rr)) { + fclose(f); + return 0; + } + } + + return fclose(f) == 0; +} + +static void +cr_get_selected_ciphers(struct Curl_easy *data, + const char *ciphers12, + const char *ciphers13, + const struct rustls_supported_ciphersuite **selected, + size_t *selected_size) +{ + const size_t supported_len = *selected_size; + const size_t default_len = rustls_default_crypto_provider_ciphersuites_len(); + const struct rustls_supported_ciphersuite *entry; + const char *ciphers = ciphers12; + size_t count = 0, default13_count = 0, i, j; + const char *ptr, *end; + + DEBUGASSERT(default_len <= supported_len); + + if(!ciphers13) { + /* Add default TLSv1.3 ciphers to selection */ + for(j = 0; j < default_len; j++) { + entry = rustls_default_crypto_provider_ciphersuites_get(j); + if(rustls_supported_ciphersuite_protocol_version(entry) != + RUSTLS_TLS_VERSION_TLSV1_3) + continue; + + selected[count++] = entry; + } + + default13_count = count; + + if(!ciphers) + ciphers = ""; + } + else + ciphers = ciphers13; + +add_ciphers: + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len; i++) { + entry = rustls_default_crypto_provider_ciphersuites_get(i); + if(rustls_supported_ciphersuite_get_suite(entry) == id) + break; + } + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "rustls: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != entry; i++); + if(i < count) { + if(i >= default13_count) + infof(data, "rustls: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + selected[count++] = entry; + } + + if(ciphers == ciphers13 && ciphers12) { + ciphers = ciphers12; + goto add_ciphers; + } + + if(!ciphers12) { + /* Add default TLSv1.2 ciphers to selection */ + for(j = 0; j < default_len; j++) { + entry = rustls_default_crypto_provider_ciphersuites_get(j); + if(rustls_supported_ciphersuite_protocol_version(entry) == + RUSTLS_TLS_VERSION_TLSV1_3) + continue; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != entry; i++); + if(i < count) + continue; + + selected[count++] = entry; + } + } + + *selected_size = count; +} + +static void +cr_keylog_log_cb(struct rustls_str label, + const uint8_t *client_random, size_t client_random_len, + const uint8_t *secret, size_t secret_len) +{ + char clabel[KEYLOG_LABEL_MAXLEN]; + (void)client_random_len; + DEBUGASSERT(client_random_len == CLIENT_RANDOM_SIZE); + /* Turning a "rustls_str" into a null delimited "c" string */ + msnprintf(clabel, label.len + 1, "%.*s", (int)label.len, label.data); + Curl_tls_keylog_write(clabel, client_random, secret, secret_len); +} + +static CURLcode +init_config_builder(struct Curl_easy *data, + const struct ssl_primary_config *conn_config, + struct rustls_client_config_builder **config_builder) +{ + const struct rustls_supported_ciphersuite **cipher_suites = NULL; + struct rustls_crypto_provider_builder *custom_provider_builder = NULL; + const struct rustls_crypto_provider *custom_provider = NULL; + + uint16_t tls_versions[2] = { + RUSTLS_TLS_VERSION_TLSV1_2, + RUSTLS_TLS_VERSION_TLSV1_3, + }; + size_t tls_versions_len = 2; + size_t cipher_suites_len = + rustls_default_crypto_provider_ciphersuites_len(); + + CURLcode result = CURLE_OK; + rustls_result rr; + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + break; + case CURL_SSLVERSION_TLSv1_3: + tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; + tls_versions_len = 1; + break; + default: + failf(data, "rustls: unsupported minimum TLS version value"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto cleanup; + } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + break; + case CURL_SSLVERSION_MAX_TLSv1_2: + if(tls_versions[0] == RUSTLS_TLS_VERSION_TLSV1_2) { + tls_versions_len = 1; + break; + } + FALLTHROUGH(); + case CURL_SSLVERSION_MAX_TLSv1_1: + case CURL_SSLVERSION_MAX_TLSv1_0: + default: + failf(data, "rustls: unsupported maximum TLS version value"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto cleanup; + } + +#if defined(USE_ECH) + if(ECH_ENABLED(data)) { + tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; + tls_versions_len = 1; + infof(data, "rustls: ECH enabled, forcing TLSv1.3"); + } +#endif /* USE_ECH */ + + cipher_suites = malloc(sizeof(cipher_suites) * (cipher_suites_len)); + if(!cipher_suites) { + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + + cr_get_selected_ciphers(data, + conn_config->cipher_list, + conn_config->cipher_list13, + cipher_suites, &cipher_suites_len); + if(cipher_suites_len == 0) { + failf(data, "rustls: no supported cipher in list"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = rustls_crypto_provider_builder_new_from_default( + &custom_provider_builder); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, + "failed to create crypto provider builder from default"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = + rustls_crypto_provider_builder_set_cipher_suites( + custom_provider_builder, + cipher_suites, + cipher_suites_len); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, + "failed to set ciphersuites for crypto provider builder"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = rustls_crypto_provider_builder_build( + custom_provider_builder, &custom_provider); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build custom crypto provider"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = rustls_client_config_builder_new_custom(custom_provider, + tls_versions, + tls_versions_len, + config_builder); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to create client config builder"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + +cleanup: + if(cipher_suites) { + free(cipher_suites); + } + if(custom_provider_builder) { + rustls_crypto_provider_builder_free(custom_provider_builder); + } + if(custom_provider) { + rustls_crypto_provider_free(custom_provider); + } + return result; +} + +static void +init_config_builder_alpn(struct Curl_easy *data, + const struct ssl_connect_data *connssl, + struct rustls_client_config_builder *config_builder) { + struct alpn_proto_buf proto; + rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; + alpn[i].len = strlen(connssl->alpn->entries[i]); + } + rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); +} + +static CURLcode +init_config_builder_verifier_crl( + struct Curl_easy *data, + const struct ssl_primary_config *conn_config, + struct rustls_web_pki_server_cert_verifier_builder *builder) { - struct in_addr in; -#ifdef ENABLE_IPV6 - struct in6_addr in6; - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) { - return true; + CURLcode result = CURLE_OK; + struct dynbuf crl_contents; + rustls_result rr; + + curlx_dyn_init(&crl_contents, DYN_CRLFILE_SIZE); + if(!read_file_into(conn_config->CRLfile, &crl_contents)) { + failf(data, "rustls: failed to read revocation list file"); + result = CURLE_SSL_CRL_BADFILE; + goto cleanup; + } + + rr = rustls_web_pki_server_cert_verifier_builder_add_crl( + builder, + curlx_dyn_uptr(&crl_contents), + curlx_dyn_len(&crl_contents)); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to parse revocation list"); + result = CURLE_SSL_CRL_BADFILE; + goto cleanup; + } + +cleanup: + curlx_dyn_free(&crl_contents); + return result; +} + +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 curl_blob *ca_info_blob, + const char * const ssl_cafile) { + const struct rustls_root_cert_store *roots = NULL; + struct rustls_root_cert_store_builder *roots_builder = NULL; + struct rustls_web_pki_server_cert_verifier_builder *verifier_builder = NULL; + struct rustls_server_cert_verifier *server_cert_verifier = NULL; + rustls_result rr = RUSTLS_RESULT_OK; + CURLcode result = CURLE_OK; + + roots_builder = rustls_root_cert_store_builder_new(); + if(ca_info_blob) { + rr = rustls_root_cert_store_builder_add_pem(roots_builder, + ca_info_blob->data, + ca_info_blob->len, + 1); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to parse trusted certificates from blob"); + + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + } + else if(ssl_cafile) { + rr = rustls_root_cert_store_builder_load_roots_from_file(roots_builder, + ssl_cafile, + 1); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to load trusted certificates"); + + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } } -#endif /* ENABLE_IPV6 */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { - return true; + + rr = rustls_root_cert_store_builder_build(roots_builder, &roots); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build trusted root certificate store"); + result = CURLE_SSL_CACERT_BADFILE; + } + + verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(roots); + + if(conn_config->CRLfile) { + result = init_config_builder_verifier_crl(data, + conn_config, + verifier_builder); + if(result != CURLE_OK) { + goto cleanup; + } } - return false; + + rr = rustls_web_pki_server_cert_verifier_builder_build( + verifier_builder, &server_cert_verifier); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build certificate verifier"); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + rustls_client_config_builder_set_server_verifier(builder, + server_cert_verifier); +cleanup: + if(roots_builder) { + rustls_root_cert_store_builder_free(roots_builder); + } + if(roots) { + rustls_root_cert_store_free(roots); + } + if(verifier_builder) { + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); + } + if(server_cert_verifier) { + rustls_server_cert_verifier_free(server_cert_verifier); + } + + return result; } +static CURLcode +init_config_builder_platform_verifier( + struct Curl_easy *data, + struct rustls_client_config_builder *builder) +{ + struct rustls_server_cert_verifier *server_cert_verifier = NULL; + CURLcode result = CURLE_OK; + rustls_result rr; + + rr = rustls_platform_server_cert_verifier(&server_cert_verifier); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to create platform certificate verifier"); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + rustls_client_config_builder_set_server_verifier(builder, + server_cert_verifier); + +cleanup: + if(server_cert_verifier) { + rustls_server_cert_verifier_free(server_cert_verifier); + } + return result; +} + +static CURLcode +init_config_builder_keylog(struct Curl_easy *data, + struct rustls_client_config_builder *builder) +{ + rustls_result rr; + + Curl_tls_keylog_open(); + if(!Curl_tls_keylog_enabled()) { + return CURLE_OK; + } + + rr = rustls_client_config_builder_set_key_log(builder, + cr_keylog_log_cb, + NULL); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls_client_config_builder_set_key_log"); + Curl_tls_keylog_close(); + return map_error(rr); + } + + return CURLE_OK; +} + +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, + struct rustls_client_config_builder *builder) +{ + struct dynbuf cert_contents; + struct dynbuf key_contents; + rustls_result rr; + const struct rustls_certified_key *certified_key = NULL; + CURLcode result = CURLE_OK; + + if(conn_config->clientcert && !ssl_config->key) { + failf(data, "rustls: must provide key with certificate '%s'", + conn_config->clientcert); + return CURLE_SSL_CERTPROBLEM; + } + else if(!conn_config->clientcert && ssl_config->key) { + failf(data, "rustls: must provide certificate with key '%s'", + conn_config->clientcert); + 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)) { + 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->key, &key_contents)) { + failf(data, "rustls: failed to read key file: '%s'", ssl_config->key); + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + + rr = rustls_certified_key_build(curlx_dyn_uptr(&cert_contents), + curlx_dyn_len(&cert_contents), + curlx_dyn_uptr(&key_contents), + curlx_dyn_len(&key_contents), + &certified_key); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls: failed to build certified key"); + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + + rr = rustls_certified_key_keys_match(certified_key); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, + rr, + "rustls: client certificate and keypair files do not match:"); + + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + + rr = rustls_client_config_builder_set_certified_key(builder, + &certified_key, + 1); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls: failed to set certified key"); + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + +cleanup: + curlx_dyn_free(&cert_contents); + curlx_dyn_free(&key_contents); + if(certified_key) { + rustls_certified_key_free(certified_key); + } + return result; +} + +#if defined(USE_ECH) +static CURLcode +init_config_builder_ech(struct Curl_easy *data, + const struct ssl_connect_data *connssl, + struct rustls_client_config_builder *builder) +{ + const rustls_hpke *hpke = rustls_supported_hpke(); + unsigned char *ech_config = NULL; + size_t ech_config_len = 0; + struct Curl_dns_entry *dns = NULL; + struct Curl_https_rrinfo *rinfo = NULL; + CURLcode result = CURLE_OK; + rustls_result rr; + + if(!hpke) { + failf(data, + "rustls: ECH unavailable, rustls-ffi built without " + "HPKE compatible crypto provider"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + + if(data->set.str[STRING_ECH_PUBLIC]) { + failf(data, "rustls: ECH outername not supported"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + + if(data->set.tls_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"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + return CURLE_OK; + } + + if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { + const char *b64 = data->set.str[STRING_ECH_CONFIG]; + size_t decode_result; + if(!b64) { + infof(data, "rustls: ECHConfig from command line empty"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + /* rustls-ffi expects the raw TLS encoded ECHConfigList bytes */ + decode_result = curlx_base64_decode(b64, &ech_config, &ech_config_len); + if(decode_result || !ech_config) { + infof(data, "rustls: cannot base64 decode ECHConfig from command line"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + } + else { + if(connssl->peer.hostname) { + dns = Curl_dnscache_get(data, connssl->peer.hostname, + connssl->peer.port, data->conn->ip_version); + } + if(!dns) { + failf(data, "rustls: ECH requested but no DNS info available"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + rinfo = dns->hinfo; + if(!rinfo || !rinfo->echconfiglist) { + failf(data, "rustls: ECH requested but no ECHConfig available"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + ech_config = rinfo->echconfiglist; + ech_config_len = rinfo->echconfiglist_len; + } + + rr = rustls_client_config_builder_enable_ech(builder, + ech_config, + ech_config_len, + hpke); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls: failed to configure ECH"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } +cleanup: + /* if we base64 decoded, we can free now */ + if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { + free(ech_config); + } + if(dns) { + Curl_resolv_unlink(data, &dns); + } + return result; +} +#endif /* USE_ECH */ + static CURLcode cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, - struct ssl_backend_data *const backend) + struct rustls_ssl_backend_data *const backend) { - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + 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); struct rustls_connection *rconn = NULL; struct rustls_client_config_builder *config_builder = NULL; - struct rustls_root_cert_store *roots = NULL; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ (ca_info_blob ? NULL : conn_config->CAfile); - const bool verifypeer = conn_config->verifypeer; - const char *hostname = connssl->hostname; - char errorbuf[256]; - size_t errorlen; - int result; + CURLcode result = CURLE_OK; + rustls_result rr; DEBUGASSERT(backend); rconn = backend->conn; - config_builder = rustls_client_config_builder_new(); - if(connssl->alpn) { - struct alpn_proto_buf proto; - rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; - size_t i; + result = init_config_builder(data, conn_config, &config_builder); + if(result != CURLE_OK) { + return result; + } - for(i = 0; i < connssl->alpn->count; ++i) { - alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; - alpn[i].len = strlen(connssl->alpn->entries[i]); - } - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, - connssl->alpn->count); - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + if(connssl->alpn) { + init_config_builder_alpn(data, connssl, config_builder); } - if(!verifypeer) { + + if(!conn_config->verifypeer) { rustls_client_config_builder_dangerous_set_certificate_verifier( config_builder, cr_verify_none); - /* rustls doesn't support IP addresses (as of 0.19.0), and will reject - * connections created with an IP address, even when certificate - * verification is turned off. Set a placeholder hostname and disable - * SNI. */ - if(cr_hostname_is_ip(hostname)) { - rustls_client_config_builder_set_enable_sni(config_builder, false); - hostname = "example.invalid"; - } - } - else if(ca_info_blob) { - roots = rustls_root_cert_store_new(); - - /* Enable strict parsing only if verification isn't disabled. */ - result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data, - ca_info_blob->len, verifypeer); - if(result != RUSTLS_RESULT_OK) { - failf(data, "rustls: failed to parse trusted certificates from blob"); - rustls_root_cert_store_free(roots); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); - return CURLE_SSL_CACERT_BADFILE; - } - - result = rustls_client_config_builder_use_roots(config_builder, roots); - rustls_root_cert_store_free(roots); - if(result != RUSTLS_RESULT_OK) { - failf(data, "rustls: failed to load trusted certificates"); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); - return CURLE_SSL_CACERT_BADFILE; + } + else if(ssl_config->native_ca_store) { + result = init_config_builder_platform_verifier(data, config_builder); + if(result != CURLE_OK) { + rustls_client_config_builder_free(config_builder); + return result; } } - else if(ssl_cafile) { - result = rustls_client_config_builder_load_roots_from_file( - config_builder, ssl_cafile); - if(result != RUSTLS_RESULT_OK) { - failf(data, "rustls: failed to load trusted certificates"); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); - return CURLE_SSL_CACERT_BADFILE; + else if(ca_info_blob || ssl_cafile) { + result = init_config_builder_verifier(data, + config_builder, + conn_config, + ca_info_blob, + ssl_cafile); + if(result != CURLE_OK) { + rustls_client_config_builder_free(config_builder); + return result; } } - backend->config = rustls_client_config_builder_build(config_builder); - DEBUGASSERT(rconn == NULL); - { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { - failf(data, "rustls: failed to get SNI"); - return CURLE_SSL_CONNECT_ERROR; + if(conn_config->clientcert || ssl_config->key) { + result = init_config_builder_client_auth(data, + conn_config, + ssl_config, + config_builder); + if(result != CURLE_OK) { + rustls_client_config_builder_free(config_builder); + return result; } - result = rustls_client_connection_new(backend->config, snihost, &rconn); } - if(result != RUSTLS_RESULT_OK) { - rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf); + +#if defined(USE_ECH) + if(ECH_ENABLED(data)) { + result = init_config_builder_ech(data, connssl, config_builder); + if(result != CURLE_OK && data->set.tls_ech & CURLECH_HARD) { + rustls_client_config_builder_free(config_builder); + return result; + } + } +#endif /* USE_ECH */ + + result = init_config_builder_keylog(data, config_builder); + if(result != CURLE_OK) { + rustls_client_config_builder_free(config_builder); + return result; + } + + rr = rustls_client_config_builder_build( + config_builder, + &backend->config); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build client config"); + rustls_client_config_builder_free(config_builder); + rustls_client_config_free(backend->config); + return CURLE_SSL_CONNECT_ERROR; + } + + DEBUGASSERT(rconn == NULL); + rr = rustls_client_connection_new(backend->config, + connssl->peer.hostname, + &rconn); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, result, "rustls_client_connection_new"); return CURLE_COULDNT_CONNECT; } + DEBUGASSERT(rconn); rustls_connection_set_userdata(rconn, backend); backend->conn = rconn; - return CURLE_OK; + + return result; } static void cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data, const struct rustls_connection *rconn) { + struct ssl_connect_data *const connssl = cf->ctx; const uint8_t *protocol = NULL; size_t len = 0; rustls_connection_get_alpn_protocol(rconn, &protocol, &len); - Curl_alpn_set_negotiated(cf, data, protocol, len); + Curl_alpn_set_negotiated(cf, data, connssl, protocol, len); } +/* Given an established network connection, do a TLS handshake. + * + * This function will set `*done` to true once the handshake is complete. + * This function never reads the value of `*done*`. + */ static CURLcode -cr_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, bool *done) +cr_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct ssl_connect_data *const connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - struct ssl_backend_data *const backend = connssl->backend; - struct rustls_connection *rconn = NULL; + const struct rustls_ssl_backend_data *const backend = + (struct rustls_ssl_backend_data *)connssl->backend; + const struct rustls_connection *rconn = NULL; CURLcode tmperr = CURLE_OK; int result; - int what; bool wants_read; bool wants_write; - curl_socket_t writefd; - curl_socket_t readfd; DEBUGASSERT(backend); - if(ssl_connection_none == connssl->state) { - result = cr_init_backend(cf, data, connssl->backend); + CURL_TRC_CF(data, cf, "cr_connect, state=%d", connssl->state); + *done = FALSE; + + if(!backend->conn) { + result = cr_init_backend(cf, data, + (struct rustls_ssl_backend_data *)connssl->backend); + CURL_TRC_CF(data, cf, "cr_connect, init backend -> %d", result); if(result != CURLE_OK) { return result; } connssl->state = ssl_connection_negotiating; } - rconn = backend->conn; /* Read/write data until the handshake is done or the socket would block. */ for(;;) { /* - * Connection has been established according to rustls. Set send/recv + * Connection has been established according to Rustls. Set send/recv * handlers, and update the state machine. */ + connssl->io_need = CURL_SSL_IO_NEED_NONE; if(!rustls_connection_is_handshaking(rconn)) { - infof(data, "Done handshaking"); - /* Done with the handshake. Set up callbacks to send/receive data. */ - connssl->state = ssl_connection_complete; - + /* Rustls claims it is no longer handshaking *before* it has + * send its FINISHED message off. We attempt to let it write + * one more time. Oh my. + */ cr_set_negotiated_alpn(cf, data, rconn); + cr_send(cf, data, NULL, 0, &tmperr); + if(tmperr == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_OK; + } + else if(tmperr != CURLE_OK) { + return tmperr; + } + /* REALLY Done with the handshake. */ + { + const uint16_t proto = + rustls_connection_get_protocol_version(rconn); + const uint16_t cipher = + rustls_connection_get_negotiated_ciphersuite(rconn); + char buf[64] = ""; + const char *ver = "TLS version unknown"; + if(proto == RUSTLS_TLS_VERSION_TLSV1_3) + ver = "TLSv1.3"; + if(proto == RUSTLS_TLS_VERSION_TLSV1_2) + ver = "TLSv1.2"; + Curl_cipher_suite_get_str(cipher, buf, sizeof(buf), TRUE); + infof(data, "rustls: handshake complete, %s, cipher: %s", + ver, buf); + } + if(data->set.ssl.certinfo) { + size_t num_certs = 0; + while(rustls_connection_get_peer_certificate(rconn, (int)num_certs)) { + num_certs++; + } + result = Curl_ssl_init_certinfo(data, (int)num_certs); + if(result) + return result; + for(size_t i = 0; i < num_certs; i++) { + const rustls_certificate *cert; + const unsigned char *der_data; + size_t der_len; + rustls_result rresult = RUSTLS_RESULT_OK; + cert = rustls_connection_get_peer_certificate(rconn, i); + DEBUGASSERT(cert); /* Should exist since we counted already */ + rresult = rustls_certificate_get_der(cert, &der_data, &der_len); + if(rresult != RUSTLS_RESULT_OK) { + char errorbuf[255]; + size_t errorlen; + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, + "Failed getting DER of server certificate #%ld: %.*s", i, + (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; + } + } + } + connssl->state = ssl_connection_complete; *done = TRUE; return CURLE_OK; } + connssl->connecting_state = ssl_connect_2; wants_read = rustls_connection_wants_read(rconn); - wants_write = rustls_connection_wants_write(rconn); + wants_write = rustls_connection_wants_write(rconn) || + backend->plain_out_buffered; DEBUGASSERT(wants_read || wants_write); - writefd = wants_write?sockfd:CURL_SOCKET_BAD; - readfd = wants_read?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, 0); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - if(0 == what) { - infof(data, "Curl_socket_check: %s would block", - wants_read&&wants_write ? "writing and reading" : - wants_write ? "writing" : "reading"); - *done = FALSE; - return CURLE_OK; - } - /* socket is readable or writable */ if(wants_write) { - infof(data, "rustls_connection wants us to write_tls."); + CURL_TRC_CF(data, cf, "rustls_connection wants us to write_tls."); cr_send(cf, data, NULL, 0, &tmperr); if(tmperr == CURLE_AGAIN) { - infof(data, "writing would block"); - /* fall through */ + CURL_TRC_CF(data, cf, "writing would block"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_OK; } else if(tmperr != CURLE_OK) { return tmperr; @@ -564,14 +1268,14 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, } if(wants_read) { - infof(data, "rustls_connection wants us to read_tls."); - + CURL_TRC_CF(data, cf, "rustls_connection wants us to read_tls."); if(tls_recv_more(cf, data, &tmperr) < 0) { if(tmperr == CURLE_AGAIN) { - infof(data, "reading would block"); - /* fall through */ + CURL_TRC_CF(data, cf, "reading would block"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_OK; } - else if(tmperr == CURLE_READ_ERROR) { + else if(tmperr == CURLE_RECV_ERROR) { return CURLE_SSL_CONNECT_ERROR; } else { @@ -582,63 +1286,99 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, } /* We should never fall through the loop. We should return either because - the handshake is done or because we can't read/write without blocking. */ - DEBUGASSERT(false); + the handshake is done or because we cannot read/write without blocking. */ + DEBUGASSERT(FALSE); } -/* returns a bitmap of flags for this connection's first socket indicating - whether we want to read or write */ -static int -cr_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) +static void * +cr_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) { - struct ssl_connect_data *const connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - struct ssl_backend_data *const backend = connssl->backend; - struct rustls_connection *rconn = NULL; + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + DEBUGASSERT(backend); + return &backend->conn; +} + +static CURLcode +cr_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + const bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + ssize_t nwritten, nread; + size_t i; - (void)data; DEBUGASSERT(backend); - rconn = backend->conn; + if(!backend->conn || cf->shutdown) { + *done = TRUE; + goto out; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; - if(rustls_connection_wants_write(rconn)) { - socks[0] = sockfd; - return GETSOCK_WRITESOCK(0); + if(!backend->sent_shutdown) { + /* do this only once */ + backend->sent_shutdown = TRUE; + if(send_shutdown) { + rustls_connection_send_close_notify(backend->conn); + } } - if(rustls_connection_wants_read(rconn)) { - socks[0] = sockfd; - return GETSOCK_READSOCK(0); + + nwritten = cr_send(cf, data, NULL, 0, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + } + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown send failed: %d", result); + goto out; } - return GETSOCK_BLANK; -} + for(i = 0; i < 10; ++i) { + char buf[1024]; + nread = cr_recv(cf, data, buf, (int)sizeof(buf), &result); + if(nread <= 0) + break; + } -static void * -cr_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - return &backend->conn; + if(nread > 0) { + /* still data coming in? */ + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_OK; + } + else { + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown, error: %d", result); + } + +out: + cf->shutdown = (result || *done); + return result; } static void cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - CURLcode tmperr = CURLE_OK; - ssize_t n = 0; + const struct ssl_connect_data *connssl = cf->ctx; + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + (void)data; DEBUGASSERT(backend); - if(backend->conn) { - rustls_connection_send_close_notify(backend->conn); - n = cr_send(cf, data, NULL, 0, &tmperr); - if(n < 0) { - failf(data, "rustls: error sending close_notify: %d", tmperr); - } - rustls_connection_free(backend->conn); backend->conn = NULL; } @@ -650,42 +1390,55 @@ cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) static size_t cr_version(char *buffer, size_t size) { - struct rustls_str ver = rustls_version(); + const struct rustls_str ver = rustls_version(); return msnprintf(buffer, size, "%.*s", (int)ver.len, ver.data); } +static CURLcode +cr_random(struct Curl_easy *data, unsigned char *entropy, size_t length) +{ + rustls_result rresult = 0; + (void)data; + rresult = + rustls_default_crypto_provider_random(entropy, length); + return map_error(rresult); +} + +static void cr_cleanup(void) +{ + Curl_tls_keylog_close(); +} + const struct Curl_ssl Curl_ssl_rustls = { { CURLSSLBACKEND_RUSTLS, "rustls" }, SSLSUPP_CAINFO_BLOB | /* supports */ + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST | SSLSUPP_TLS13_CIPHERSUITES | - SSLSUPP_HTTPS_PROXY, - sizeof(struct ssl_backend_data), + SSLSUPP_CERTINFO | + SSLSUPP_ECH, + sizeof(struct rustls_ssl_backend_data), - Curl_none_init, /* init */ - Curl_none_cleanup, /* cleanup */ + NULL, /* init */ + cr_cleanup, /* cleanup */ cr_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ + cr_shutdown, /* shutdown */ cr_data_pending, /* data_pending */ - Curl_none_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ + cr_random, /* random */ + NULL, /* cert_status_request */ cr_connect, /* connect */ - cr_connect_nonblocking, /* connect_nonblocking */ - cr_get_select_socks, /* get_select_socks */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ cr_get_internals, /* get_internals */ cr_close, /* close_one */ - Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ + NULL, /* false_start */ NULL, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ cr_recv, /* recv decrypted data */ cr_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_RUSTLS */ diff --git a/Utilities/cmcurl/lib/vtls/rustls.h b/Utilities/cmcurl/lib/vtls/rustls.h index bfbe23de3e6..74d39d4d115 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.h +++ b/Utilities/cmcurl/lib/vtls/rustls.h @@ -25,7 +25,7 @@ #ifndef HEADER_CURL_RUSTLS_H #define HEADER_CURL_RUSTLS_H -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_RUSTLS diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c index 513811d2d80..bea8eef8c0d 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.c +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -29,64 +29,48 @@ * but vtls.c should ever call or use these functions. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SCHANNEL -#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS - #ifndef USE_WINDOWS_SSPI -# error "Can't compile SCHANNEL support without SSPI." +# error "cannot compile SCHANNEL support without SSPI." #endif #include "schannel.h" +#include "schannel_int.h" #include "vtls.h" #include "vtls_int.h" -#include "strcase.h" -#include "sendf.h" -#include "connect.h" /* for the connect timeout */ -#include "strerror.h" -#include "select.h" /* for the socket readiness */ -#include "inet_pton.h" /* for IP addr SNI check */ -#include "curl_multibyte.h" -#include "warnless.h" +#include "vtls_scache.h" +#include "../strcase.h" +#include "../sendf.h" +#include "../connect.h" /* for the connect timeout */ +#include "../strerror.h" +#include "../select.h" /* for the socket readiness */ +#include "../curlx/inet_pton.h" /* for IP addr SNI check */ +#include "../curlx/multibyte.h" +#include "../curlx/warnless.h" #include "x509asn1.h" -#include "curl_printf.h" -#include "multiif.h" -#include "version_win32.h" -#include "rand.h" +#include "../curl_printf.h" +#include "../multiif.h" +#include "../system_win32.h" +#include "../curlx/version_win32.h" +#include "../rand.h" +#include "../curlx/strparse.h" /* The last #include file should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -/* ALPN requires version 8.1 of the Windows SDK, which was - shipped with Visual Studio 2013, aka _MSC_VER 1800: - - https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx -*/ -#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_) -# define HAS_ALPN 1 -#endif +#include "../curl_memory.h" +#include "../memdebug.h" -#ifndef UNISP_NAME_A -#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME_W -#define UNISP_NAME_W L"Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME -#ifdef UNICODE -#define UNISP_NAME UNISP_NAME_W +/* Some verbose debug messages are wrapped by SCH_DEV() instead of DEBUGF() + * and only shown if CURL_SCHANNEL_DEV_DEBUG was defined at build time. These + * messages are extra verbose and intended for curl developers debugging + * Schannel recv decryption. + */ +#ifdef CURL_SCHANNEL_DEV_DEBUG +#define SCH_DEV(x) x #else -#define UNISP_NAME UNISP_NAME_A -#endif -#endif - -#ifndef BCRYPT_CHACHA20_POLY1305_ALGORITHM -#define BCRYPT_CHACHA20_POLY1305_ALGORITHM L"CHACHA20_POLY1305" +#define SCH_DEV(x) do { } while(0) #endif #ifndef BCRYPT_CHAIN_MODE_CCM @@ -109,13 +93,6 @@ #define BCRYPT_SHA384_ALGORITHM L"SHA384" #endif -/* Workaround broken compilers like MinGW. - Return the number of elements in a statically sized array. -*/ -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif - #ifdef HAS_CLIENT_CERT_PATH #ifdef UNICODE #define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W @@ -124,18 +101,6 @@ #endif #endif -#ifndef SP_PROT_SSL2_CLIENT -#define SP_PROT_SSL2_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_SSL3_CLIENT -#define SP_PROT_SSL3_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_TLS1_CLIENT -#define SP_PROT_TLS1_CLIENT 0x00000080 -#endif - #ifndef SP_PROT_TLS1_0_CLIENT #define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT #endif @@ -173,12 +138,12 @@ */ #ifndef CALG_SHA_256 -# define CALG_SHA_256 0x0000800c +#define CALG_SHA_256 0x0000800c #endif -/* Work around typo in classic MinGW's w32api up to version 5.0, - see https://osdn.net/projects/mingw/ticket/38391 */ -#if !defined(ALG_CLASS_DHASH) && defined(ALG_CLASS_HASH) +/* Work around typo in CeGCC (as of 0.59.1) w32api headers */ +#if defined(__MINGW32CE__) && \ + !defined(ALG_CLASS_DHASH) && defined(ALG_CLASS_HASH) #define ALG_CLASS_DHASH ALG_CLASS_HASH #endif @@ -186,9 +151,24 @@ #define PKCS12_NO_PERSIST_KEY 0x00008000 #endif -static CURLcode pkp_pin_peer_pubkey(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *pinnedpubkey); +#ifndef CERT_FIND_HAS_PRIVATE_KEY +#define CERT_FIND_HAS_PRIVATE_KEY (21 << CERT_COMPARE_SHIFT) +#endif + +/* ALPN requires version 8.1 of the Windows SDK, which was + shipped with Visual Studio 2013, aka _MSC_VER 1800: + https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx + Or mingw-w64 9.0 or upper. +*/ +#if (defined(__MINGW64_VERSION_MAJOR) && __MINGW64_VERSION_MAJOR >= 9) || \ + (defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_)) +#define HAS_ALPN_SCHANNEL +static bool s_win_has_alpn; +#endif + +static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *pinnedpubkey); static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType, void *BufDataPtr, unsigned long BufByteSize) @@ -207,13 +187,13 @@ static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr, } static CURLcode -set_ssl_version_min_max(DWORD *enabled_protocols, - struct Curl_cfilter *cf, - struct Curl_easy *data) +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); long ssl_version = conn_config->version; - long ssl_version_max = conn_config->version_max; + long ssl_version_max = (long)conn_config->version_max; long i = ssl_version; switch(ssl_version_max) { @@ -262,8 +242,6 @@ set_ssl_version_min_max(DWORD *enabled_protocols, return CURLE_OK; } -/* longest is 26, buffer is slightly bigger */ -#define LONGEST_ALG_ID 32 #define CIPHEROPTION(x) {#x, x} struct algo { @@ -380,9 +358,9 @@ static const struct algo algs[]= { }; static int -get_alg_id_by_name(char *name) +get_alg_id_by_name(const char *name) { - char *nameEnd = strchr(name, ':'); + const char *nameEnd = strchr(name, ':'); size_t n = nameEnd ? (size_t)(nameEnd - name) : strlen(name); int i; @@ -399,14 +377,15 @@ static CURLcode set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, ALG_ID *algIds) { - char *startCur = ciphers; + const char *startCur = ciphers; int algCount = 0; while(startCur && (0 != *startCur) && (algCount < NUM_CIPHERS)) { - long alg = strtol(startCur, 0, 0); - if(!alg) + curl_off_t alg; + if(curlx_str_number(&startCur, &alg, INT_MAX) || !alg) alg = get_alg_id_by_name(startCur); + if(alg) - algIds[algCount++] = alg; + algIds[algCount++] = (ALG_ID)alg; else if(!strncmp(startCur, "USE_STRONG_CRYPTO", sizeof("USE_STRONG_CRYPTO") - 1) || !strncmp(startCur, "SCH_USE_STRONG_CRYPTO", @@ -419,7 +398,7 @@ set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, startCur++; } schannel_cred->palgSupportedAlgs = algIds; - schannel_cred->cSupportedAlgs = algCount; + schannel_cred->cSupportedAlgs = (DWORD)algCount; return CURLE_OK; } @@ -481,6 +460,7 @@ get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path, return CURLE_OK; } #endif + static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -500,7 +480,8 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, DWORD flags = 0; DWORD enabled_protocols = 0; - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)(connssl->backend); DEBUGASSERT(backend); @@ -548,7 +529,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, } if(!ssl_config->auto_client_cert) { - flags &= ~SCH_CRED_USE_DEFAULT_CREDS; + flags &= ~(DWORD)SCH_CRED_USE_DEFAULT_CREDS; flags |= SCH_CRED_NO_DEFAULT_CREDS; infof(data, "schannel: disabled automatic use of client certificate"); } @@ -563,7 +544,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, case CURL_SSLVERSION_TLSv1_2: case CURL_SSLVERSION_TLSv1_3: { - result = set_ssl_version_min_max(&enabled_protocols, cf, data); + result = schannel_set_ssl_version_min_max(&enabled_protocols, cf, data); if(result != CURLE_OK) return result; break; @@ -634,6 +615,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, WCHAR* pszPassword; size_t pwd_len = 0; int str_w_len = 0; + int cert_find_flags; const char *cert_showfilename_error = blob ? "(memory blob)" : data->set.ssl.primary.clientcert; curlx_unicodefree(cert_path); @@ -682,8 +664,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, else pszPassword[0] = 0; - if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) + if(Curl_isVistaOrGreater) cert_store = PFXImportCertStore(&datablob, pszPassword, PKCS12_NO_PERSIST_KEY); else @@ -701,18 +682,26 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, cert_showfilename_error); else failf(data, "schannel: Failed to import cert file %s, " - "last error is 0x%x", + "last error is 0x%lx", cert_showfilename_error, errorcode); return CURLE_SSL_CERTPROBLEM; } + /* CERT_FIND_HAS_PRIVATE_KEY is only available in Windows 8 / Server + 2012, (NT v6.2). For earlier versions we use CURL_FIND_ANY. */ + if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + cert_find_flags = CERT_FIND_HAS_PRIVATE_KEY; + else + cert_find_flags = CERT_FIND_ANY; + client_certs[0] = CertFindCertificateInStore( cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, - CERT_FIND_ANY, NULL, NULL); + cert_find_flags, NULL, NULL); if(!client_certs[0]) { failf(data, "schannel: Failed to get certificate from file %s" - ", last error is 0x%x", + ", last error is 0x%lx", cert_showfilename_error, GetLastError()); CertCloseStore(cert_store, 0); return CURLE_SSL_CERTPROBLEM; @@ -725,10 +714,15 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name, cert_store_path); if(!cert_store) { - failf(data, "schannel: Failed to open cert store %x %s, " - "last error is 0x%x", - cert_store_name, cert_store_path, GetLastError()); + char *path_utf8 = + curlx_convert_tchar_to_UTF8(cert_store_path); + failf(data, "schannel: Failed to open cert store %lx %s, " + "last error is 0x%lx", + cert_store_name, + (path_utf8 ? path_utf8 : "(unknown)"), + GetLastError()); free(cert_store_path); + curlx_unicodefree(path_utf8); curlx_unicodefree(cert_path); return CURLE_SSL_CERTPROBLEM; } @@ -769,7 +763,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, } #endif - /* allocate memory for the re-usable credential handle */ + /* allocate memory for the reusable credential handle */ backend->cred = (struct Curl_schannel_cred *) calloc(1, sizeof(struct Curl_schannel_cred)); if(!backend->cred) { @@ -793,199 +787,21 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, backend->cred->client_cert_store = client_cert_store; #endif - /* Windows 10, 1809 (a.k.a. Windows 10 build 17763) */ - if(curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT, + /* We support TLS 1.3 starting in Windows 10 version 1809 (OS build 17763) as + long as the user did not set a legacy algorithm list + (CURLOPT_SSL_CIPHER_LIST). */ + if(!conn_config->cipher_list && + curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { - char *ciphers13 = 0; - - bool disable_aes_gcm_sha384 = FALSE; - bool disable_aes_gcm_sha256 = FALSE; - bool disable_chacha_poly = FALSE; - bool disable_aes_ccm_8_sha256 = FALSE; - bool disable_aes_ccm_sha256 = FALSE; - SCH_CREDENTIALS credentials = { 0 }; TLS_PARAMETERS tls_parameters = { 0 }; - CRYPTO_SETTINGS crypto_settings[4] = { 0 }; - UNICODE_STRING blocked_ccm_modes[1] = { 0 }; - UNICODE_STRING blocked_gcm_modes[1] = { 0 }; - - int crypto_settings_idx = 0; - - - /* If TLS 1.3 ciphers are explicitly listed, then - * disable all the ciphers and re-enable which - * ciphers the user has provided. - */ - ciphers13 = conn_config->cipher_list13; - if(ciphers13) { - const int remaining_ciphers = 5; - - /* detect which remaining ciphers to enable - and then disable everything else. - */ - - char *startCur = ciphers13; - int algCount = 0; - char tmp[LONGEST_ALG_ID] = { 0 }; - char *nameEnd; - size_t n; - - disable_aes_gcm_sha384 = TRUE; - disable_aes_gcm_sha256 = TRUE; - disable_chacha_poly = TRUE; - disable_aes_ccm_8_sha256 = TRUE; - disable_aes_ccm_sha256 = TRUE; - - while(startCur && (0 != *startCur) && (algCount < remaining_ciphers)) { - nameEnd = strchr(startCur, ':'); - n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur); - - /* reject too-long cipher names */ - if(n > (LONGEST_ALG_ID - 1)) { - failf(data, "Cipher name too long, not checked."); - return CURLE_SSL_CIPHER; - } - - strncpy(tmp, startCur, n); - tmp[n] = 0; - - if(disable_aes_gcm_sha384 - && !strcmp("TLS_AES_256_GCM_SHA384", tmp)) { - disable_aes_gcm_sha384 = FALSE; - } - else if(disable_aes_gcm_sha256 - && !strcmp("TLS_AES_128_GCM_SHA256", tmp)) { - disable_aes_gcm_sha256 = FALSE; - } - else if(disable_chacha_poly - && !strcmp("TLS_CHACHA20_POLY1305_SHA256", tmp)) { - disable_chacha_poly = FALSE; - } - else if(disable_aes_ccm_8_sha256 - && !strcmp("TLS_AES_128_CCM_8_SHA256", tmp)) { - disable_aes_ccm_8_sha256 = FALSE; - } - else if(disable_aes_ccm_sha256 - && !strcmp("TLS_AES_128_CCM_SHA256", tmp)) { - disable_aes_ccm_sha256 = FALSE; - } - else { - failf(data, "Passed in an unknown TLS 1.3 cipher."); - return CURLE_SSL_CIPHER; - } - - startCur = nameEnd; - if(startCur) - startCur++; - - algCount++; - } - } - - if(disable_aes_gcm_sha384 && disable_aes_gcm_sha256 - && disable_chacha_poly && disable_aes_ccm_8_sha256 - && disable_aes_ccm_sha256) { - failf(data, "All available TLS 1.3 ciphers were disabled."); - return CURLE_SSL_CIPHER; - } - - /* Disable TLS_AES_128_CCM_8_SHA256 and/or TLS_AES_128_CCM_SHA256 */ - if(disable_aes_ccm_8_sha256 || disable_aes_ccm_sha256) { - /* - Disallow AES_CCM algorithm. - */ - blocked_ccm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_CCM); - blocked_ccm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_CCM); - blocked_ccm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_CCM; - - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageCipher; - crypto_settings[crypto_settings_idx].rgstrChainingModes = - blocked_ccm_modes; - crypto_settings[crypto_settings_idx].cChainingModes = - ARRAYSIZE(blocked_ccm_modes); - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)BCRYPT_AES_ALGORITHM; - - /* only disabling one of the CCM modes */ - if(disable_aes_ccm_8_sha256 != disable_aes_ccm_sha256) { - if(disable_aes_ccm_8_sha256) - crypto_settings[crypto_settings_idx].dwMinBitLength = 128; - else /* disable_aes_ccm_sha256 */ - crypto_settings[crypto_settings_idx].dwMaxBitLength = 64; - } - - crypto_settings_idx++; - } - - /* Disable TLS_AES_256_GCM_SHA384 and/or TLS_AES_128_GCM_SHA256 */ - if(disable_aes_gcm_sha384 || disable_aes_gcm_sha256) { - - /* - Disallow AES_GCM algorithm - */ - blocked_gcm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_GCM); - blocked_gcm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_GCM); - blocked_gcm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_GCM; - - /* if only one is disabled, then explicitly disable the - digest cipher suite (sha384 or sha256) */ - if(disable_aes_gcm_sha384 != disable_aes_gcm_sha256) { - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageDigest; - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(disable_aes_gcm_sha384 ? - BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(disable_aes_gcm_sha384 ? - BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)(disable_aes_gcm_sha384 ? - BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); - } - else { /* Disable both AES_GCM ciphers */ - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageCipher; - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)BCRYPT_AES_ALGORITHM; - } - - crypto_settings[crypto_settings_idx].rgstrChainingModes = - blocked_gcm_modes; - crypto_settings[crypto_settings_idx].cChainingModes = 1; - - crypto_settings_idx++; - } - - /* - Disable ChaCha20-Poly1305. - */ - if(disable_chacha_poly) { - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageCipher; - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)BCRYPT_CHACHA20_POLY1305_ALGORITHM; - crypto_settings_idx++; - } + CRYPTO_SETTINGS crypto_settings[1] = { { 0 } }; tls_parameters.pDisabledCrypto = crypto_settings; /* The number of blocked suites */ - tls_parameters.cDisabledCrypto = crypto_settings_idx; + tls_parameters.cDisabledCrypto = (DWORD)0; credentials.pTlsParameters = &tls_parameters; credentials.cTlsParameters = 1; @@ -1003,14 +819,16 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, #endif sspi_status = - s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, - SECPKG_CRED_OUTBOUND, NULL, - &credentials, NULL, NULL, - &backend->cred->cred_handle, - &backend->cred->time_stamp); + Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(UNISP_NAME), + SECPKG_CRED_OUTBOUND, NULL, + &credentials, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); } else { - /* Pre-Windows 10 1809 */ + /* Pre-Windows 10 1809 or the user set a legacy algorithm list. + Schannel will not negotiate TLS 1.3 when SCHANNEL_CRED is used. */ ALG_ID algIds[NUM_CIPHERS]; char *ciphers = conn_config->cipher_list; SCHANNEL_CRED schannel_cred = { 0 }; @@ -1019,9 +837,14 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, schannel_cred.grbitEnabledProtocols = enabled_protocols; if(ciphers) { + if((enabled_protocols & SP_PROT_TLS1_3_CLIENT)) { + infof(data, "schannel: WARNING: This version of Schannel " + "negotiates a less-secure TLS version than TLS 1.3 because the " + "user set an algorithm cipher list."); + } result = set_ssl_ciphers(&schannel_cred, ciphers, algIds); if(CURLE_OK != result) { - failf(data, "Unable to set ciphers to from connection ssl config"); + failf(data, "schannel: Failed setting algorithm cipher list"); return result; } } @@ -1037,11 +860,12 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, #endif sspi_status = - s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, - SECPKG_CRED_OUTBOUND, NULL, - &schannel_cred, NULL, NULL, - &backend->cred->cred_handle, - &backend->cred->time_stamp); + Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(UNISP_NAME), + SECPKG_CRED_OUTBOUND, NULL, + &schannel_cred, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); } #ifdef HAS_CLIENT_CERT_PATH @@ -1075,29 +899,26 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { ssize_t written = -1; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; +#ifndef UNDER_CE struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); +#endif struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); SecBuffer outbuf; SecBufferDesc outbuf_desc; SecBuffer inbuf; SecBufferDesc inbuf_desc; -#ifdef HAS_ALPN +#ifdef HAS_ALPN_SCHANNEL unsigned char alpn_buffer[128]; #endif SECURITY_STATUS sspi_status = SEC_E_OK; - struct Curl_schannel_cred *old_cred = NULL; - struct in_addr addr; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; -#endif CURLcode result; - const char *hostname = connssl->hostname; DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 1/3)", - hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(curlx_verify_windows_version(5, 1, 0, PLATFORM_WINNT, VERSION_LESS_THAN_EQUAL)) { @@ -1107,32 +928,26 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) "connect to some servers due to lack of SNI, algorithms, etc."); } -#ifdef HAS_ALPN - /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above. - Also it doesn't seem to be supported for Wine, see curl bug #983. */ - backend->use_alpn = connssl->alpn && - !GetProcAddress(GetModuleHandle(TEXT("ntdll")), - "wine_get_version") && - curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL); +#ifdef HAS_ALPN_SCHANNEL + backend->use_alpn = connssl->alpn && s_win_has_alpn; #else - backend->use_alpn = false; + backend->use_alpn = FALSE; #endif -#ifdef _WIN32_WCE +#ifdef UNDER_CE #ifdef HAS_MANUAL_VERIFY_API - /* certificate validation on CE doesn't seem to work right; we'll + /* certificate validation on Windows CE does not seem to work right; we will * do it following a more manual process. */ - backend->use_manual_cred_validation = true; + backend->use_manual_cred_validation = TRUE; #else -#error "compiler too old to support requisite manual cert verify for Win CE" +#error "compiler too old to support Windows CE requisite manual cert verify" #endif #else #ifdef HAS_MANUAL_VERIFY_API if(conn_config->CAfile || conn_config->ca_info_blob) { if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { - backend->use_manual_cred_validation = true; + backend->use_manual_cred_validation = TRUE; } else { failf(data, "schannel: this version of Windows is too old to support " @@ -1141,7 +956,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } } else - backend->use_manual_cred_validation = false; + backend->use_manual_cred_validation = FALSE; #else if(conn_config->CAfile || conn_config->ca_info_blob) { failf(data, "schannel: CA cert support not built in"); @@ -1152,12 +967,14 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) backend->cred = NULL; - /* check for an existing re-usable credential handle */ - if(ssl_config->primary.sessionid) { - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)) { + /* check for an existing reusable credential handle */ + if(ssl_config->primary.cache_session) { + struct Curl_schannel_cred *old_cred; + Curl_ssl_scache_lock(data); + old_cred = Curl_ssl_scache_get_obj(cf, data, connssl->peer.scache_key); + if(old_cred) { backend->cred = old_cred; - DEBUGF(infof(data, "schannel: re-using existing credential handle")); + DEBUGF(infof(data, "schannel: reusing existing credential handle")); /* increment the reference counter of the credential/session handle */ backend->cred->refcount++; @@ -1165,39 +982,31 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) "schannel: incremented credential handle refcount = %d", backend->cred->refcount)); } - Curl_ssl_sessionid_unlock(data); + Curl_ssl_scache_unlock(data); } if(!backend->cred) { char *snihost; result = schannel_acquire_credential_handle(cf, data); - if(result) + if(result || !backend->cred) return result; /* schannel_acquire_credential_handle() sets backend->cred accordingly or it returns error otherwise. */ /* A hostname associated with the credential is needed by InitializeSecurityContext for SNI and other reasons. */ - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } + snihost = connssl->peer.sni ? connssl->peer.sni : connssl->peer.hostname; backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost); if(!backend->cred->sni_hostname) return CURLE_OUT_OF_MEMORY; } /* Warn if SNI is disabled due to use of an IP address */ - if(Curl_inet_pton(AF_INET, hostname, &addr) -#ifdef ENABLE_IPV6 - || Curl_inet_pton(AF_INET6, hostname, &addr6) -#endif - ) { + if(connssl->peer.type != CURL_SSL_PEER_DNS) { infof(data, "schannel: using IP address, SNI is not supported by OS."); } -#ifdef HAS_ALPN +#ifdef HAS_ALPN_SCHANNEL if(backend->use_alpn) { int cur = 0; int list_start_index = 0; @@ -1232,9 +1041,8 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) cur += proto.len; *list_len = curlx_uitous(cur - list_start_index); - *extension_len = *list_len + - (unsigned short)sizeof(unsigned int) + - (unsigned short)sizeof(unsigned short); + *extension_len = (unsigned int)(*list_len + + sizeof(unsigned int) + sizeof(unsigned short)); InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); @@ -1246,7 +1054,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); } -#else /* HAS_ALPN */ +#else /* HAS_ALPN_SCHANNEL */ InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); #endif @@ -1275,11 +1083,11 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Schannel InitializeSecurityContext: https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx - At the moment we don't pass inbuf unless we're using ALPN since we only - use it for that, and Wine (for which we currently disable ALPN) is giving + At the moment we do not pass inbuf unless we are using ALPN since we only + use it for that, and WINE (for which we currently disable ALPN) is giving us problems with inbuf regardless. https://github.com/curl/curl/issues/983 */ - sspi_status = s_pSecFn->InitializeSecurityContext( + sspi_status = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, NULL, backend->cred->sni_hostname, backend->req_flags, 0, 0, (backend->use_alpn ? &inbuf_desc : NULL), @@ -1321,9 +1129,9 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* send initial handshake data which is now stored in output buffer */ written = Curl_conn_cf_send(cf->next, data, - outbuf.pvBuffer, outbuf.cbBuffer, + outbuf.pvBuffer, outbuf.cbBuffer, FALSE, &result); - s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { failf(data, "schannel: failed to send initial handshake data: " "sent %zd of %lu bytes", written, outbuf.cbBuffer); @@ -1334,10 +1142,10 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) "sent %zd bytes", written)); backend->recv_unrecoverable_err = CURLE_OK; - backend->recv_sspi_close_notify = false; - backend->recv_connection_closed = false; - backend->recv_renegotiating = false; - backend->encdata_is_incomplete = false; + backend->recv_sspi_close_notify = FALSE; + backend->recv_connection_closed = FALSE; + backend->recv_renegotiating = FALSE; + backend->encdata_is_incomplete = FALSE; /* continue to second handshake step */ connssl->connecting_state = ssl_connect_2; @@ -1349,7 +1157,8 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + 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); int i; ssize_t nread = -1, written = -1; @@ -1365,11 +1174,12 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(backend); - doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE; + doread = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? FALSE : TRUE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 2/3)", - connssl->hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(!backend->cred || !backend->ctxt) return CURLE_SSL_CONNECT_ERROR; @@ -1387,7 +1197,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* buffer to store previously received and encrypted data */ if(!backend->encdata_buffer) { - backend->encdata_is_incomplete = false; + backend->encdata_is_incomplete = FALSE; backend->encdata_offset = 0; backend->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; backend->encdata_buffer = malloc(backend->encdata_length); @@ -1426,8 +1236,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) backend->encdata_offset, &result); if(result == CURLE_AGAIN) { - if(connssl->connecting_state != ssl_connect_2_writing) - connssl->connecting_state = ssl_connect_2_reading; + connssl->io_need = CURL_SSL_IO_NEED_RECV; DEBUGF(infof(data, "schannel: failed to receive handshake, " "need more data")); return CURLE_OK; @@ -1440,13 +1249,13 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* increase encrypted data buffer offset */ backend->encdata_offset += nread; - backend->encdata_is_incomplete = false; - DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); + backend->encdata_is_incomplete = FALSE; + SCH_DEV(infof(data, "schannel: encrypted data got %zd", nread)); } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* setup input buffers */ InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(backend->encdata_offset), @@ -1469,7 +1278,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) memcpy(inbuf[0].pvBuffer, backend->encdata_buffer, backend->encdata_offset); - sspi_status = s_pSecFn->InitializeSecurityContext( + sspi_status = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, &backend->ctxt->ctxt_handle, backend->cred->sni_hostname, backend->req_flags, 0, 0, &inbuf_desc, 0, NULL, @@ -1480,8 +1289,8 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* check if the handshake was incomplete */ if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { - backend->encdata_is_incomplete = true; - connssl->connecting_state = ssl_connect_2_reading; + backend->encdata_is_incomplete = TRUE; + connssl->io_need = CURL_SSL_IO_NEED_RECV; DEBUGF(infof(data, "schannel: received incomplete message, need more data")); return CURLE_OK; @@ -1493,7 +1302,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS && !(backend->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) { backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; - connssl->connecting_state = ssl_connect_2_writing; + connssl->io_need = CURL_SSL_IO_NEED_SEND; DEBUGF(infof(data, "schannel: a client certificate has been requested")); return CURLE_OK; @@ -1510,7 +1319,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* send handshake token to server */ written = Curl_conn_cf_send(cf->next, data, outbuf[i].pvBuffer, outbuf[i].cbBuffer, - &result); + FALSE, &result); if((result != CURLE_OK) || (outbuf[i].cbBuffer != (size_t) written)) { failf(data, "schannel: failed to send next handshake data: " @@ -1521,7 +1330,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* free obsolete buffer */ if(outbuf[i].pvBuffer) { - s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); + Curl_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); } } } @@ -1560,11 +1369,11 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* check if there was additional remaining encrypted data */ if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) { - DEBUGF(infof(data, "schannel: encrypted data length: %lu", - inbuf[1].cbBuffer)); + SCH_DEV(infof(data, "schannel: encrypted data length: %lu", + inbuf[1].cbBuffer)); /* There are two cases where we could be getting extra data here: - 1) If we're renegotiating a connection and the handshake is already + 1) If we are renegotiating a connection and the handshake is already complete (from the server perspective), it can encrypted app data (not handshake data) in an extra buffer at this point. 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a @@ -1593,7 +1402,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* check if the handshake needs to be continued */ if(sspi_status == SEC_I_CONTINUE_NEEDED) { - connssl->connecting_state = ssl_connect_2_reading; + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } @@ -1603,11 +1412,15 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGF(infof(data, "schannel: SSL/TLS handshake complete")); } - pubkey_ptr = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + pubkey_ptr = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + pubkey_ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif if(pubkey_ptr) { - result = pkp_pin_peer_pubkey(cf, data, pubkey_ptr); + result = schannel_pkp_pin_peer_pubkey(cf, data, pubkey_ptr); if(result) { failf(data, "SSL: public key does not match pinned public key"); return result; @@ -1616,10 +1429,16 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef HAS_MANUAL_VERIFY_API if(conn_config->verifypeer && backend->use_manual_cred_validation) { + /* Certificate verification also verifies the hostname if verifyhost */ return Curl_verify_certificate(cf, data); } #endif + /* Verify the hostname manually when certificate verification is disabled, + because in that case Schannel will not verify it. */ + if(!conn_config->verifypeer && conn_config->verifyhost) + return Curl_verify_host(cf, data); + return CURLE_OK; } @@ -1632,30 +1451,44 @@ valid_cert_encoding(const CERT_CONTEXT *cert_context) (cert_context->cbCertEncoded > 0); } -typedef bool(*Read_crt_func)(const CERT_CONTEXT *ccert_context, void *arg); +typedef bool(*Read_crt_func)(const CERT_CONTEXT *ccert_context, + bool reverse_order, void *arg); static void traverse_cert_store(const CERT_CONTEXT *context, Read_crt_func func, void *arg) { const CERT_CONTEXT *current_context = NULL; - bool should_continue = true; + bool should_continue = TRUE; + bool first = TRUE; + bool reverse_order = FALSE; while(should_continue && (current_context = CertEnumCertificatesInStore( context->hCertStore, - current_context)) != NULL) - should_continue = func(current_context, arg); + current_context)) != NULL) { + /* Windows 11 22H2 OS Build 22621.674 or higher enumerates certificates in + leaf-to-root order while all previous versions of Windows enumerate + certificates in root-to-leaf order. Determine the order of enumeration + by comparing SECPKG_ATTR_REMOTE_CERT_CONTEXT's pbCertContext with the + first certificate's pbCertContext. */ + if(first && context->pbCertEncoded != current_context->pbCertEncoded) + reverse_order = TRUE; + should_continue = func(current_context, reverse_order, arg); + first = FALSE; + } if(current_context) CertFreeCertificateContext(current_context); } static bool -cert_counter_callback(const CERT_CONTEXT *ccert_context, void *certs_count) +cert_counter_callback(const CERT_CONTEXT *ccert_context, bool reverse_order, + void *certs_count) { + (void)reverse_order; /* unused */ if(valid_cert_encoding(ccert_context)) (*(int *)certs_count)++; - return true; + return TRUE; } struct Adder_args @@ -1667,14 +1500,16 @@ struct Adder_args }; static bool -add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, void *raw_arg) +add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, bool reverse_order, + void *raw_arg) { 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 = (args->certs_count - 1) - args->idx; + int insert_index = reverse_order ? (args->certs_count - 1) - args->idx : + args->idx; args->result = Curl_extract_certinfo(args->data, insert_index, beg, end); args->idx++; @@ -1682,16 +1517,38 @@ add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, void *raw_arg) return args->result == CURLE_OK; } +static void schannel_session_free(void *sessionid) +{ + /* this is expected to be called under sessionid lock */ + struct Curl_schannel_cred *cred = sessionid; + + if(cred) { + cred->refcount--; + if(cred->refcount == 0) { + Curl_pSecFn->FreeCredentialsHandle(&cred->cred_handle); + curlx_unicodefree(cred->sni_hostname); +#ifdef HAS_CLIENT_CERT_PATH + if(cred->client_cert_store) { + CertCloseStore(cred->client_cert_store, 0); + cred->client_cert_store = NULL; + } +#endif + Curl_safefree(cred); + } + } +} + static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + 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); CURLcode result = CURLE_OK; SECURITY_STATUS sspi_status = SEC_E_OK; CERT_CONTEXT *ccert_context = NULL; -#ifdef HAS_ALPN +#ifdef HAS_ALPN_SCHANNEL SecPkgContext_ApplicationProtocol alpn_result; #endif @@ -1700,7 +1557,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 3/3)", - connssl->hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(!backend->cred) return CURLE_SSL_CONNECT_ERROR; @@ -1720,10 +1577,10 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; } -#ifdef HAS_ALPN +#ifdef HAS_ALPN_SCHANNEL if(backend->use_alpn) { sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); @@ -1736,7 +1593,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) SecApplicationProtocolNegotiationStatus_Success) { unsigned char prev_alpn = cf->conn->alpn; - Curl_alpn_set_negotiated(cf, data, alpn_result.ProtocolId, + Curl_alpn_set_negotiated(cf, data, connssl, alpn_result.ProtocolId, alpn_result.ProtocolIdSize); if(backend->recv_renegotiating) { if(prev_alpn != cf->conn->alpn && @@ -1750,51 +1607,27 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) } else { if(!backend->recv_renegotiating) - Curl_alpn_set_negotiated(cf, data, NULL, 0); + Curl_alpn_set_negotiated(cf, data, connssl, NULL, 0); } } #endif - /* save the current session data for possible re-use */ - if(ssl_config->primary.sessionid) { - bool incache; - bool added = FALSE; - struct Curl_schannel_cred *old_cred = NULL; - - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)); - if(incache) { - if(old_cred != backend->cred) { - DEBUGF(infof(data, - "schannel: old credential handle is stale, removing")); - /* we're not taking old_cred ownership here, no refcount++ is needed */ - Curl_ssl_delsessionid(data, (void *)old_cred); - incache = FALSE; - } - } - if(!incache) { - result = Curl_ssl_addsessionid(cf, data, backend->cred, - sizeof(struct Curl_schannel_cred), - &added); - if(result) { - Curl_ssl_sessionid_unlock(data); - failf(data, "schannel: failed to store credential handle"); - return result; - } - else if(added) { - /* this cred session is now also referenced by sessionid cache */ - backend->cred->refcount++; - DEBUGF(infof(data, - "schannel: stored credential handle in session cache")); - } - } - Curl_ssl_sessionid_unlock(data); + /* save the current session data for possible reuse */ + if(ssl_config->primary.cache_session) { + Curl_ssl_scache_lock(data); + /* Up ref count since call takes ownership */ + backend->cred->refcount++; + result = Curl_ssl_scache_add_obj(cf, data, connssl->peer.scache_key, + backend->cred, schannel_session_free); + Curl_ssl_scache_unlock(data); + if(result) + return result; } if(data->set.ssl.certinfo) { int certs_count = 0; sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &ccert_context); @@ -1811,6 +1644,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) args.data = data; args.idx = 0; args.certs_count = certs_count; + args.result = CURLE_OK; traverse_cert_store(ccert_context, add_cert_to_certinfo, &args); result = args.result; } @@ -1824,16 +1658,12 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_OK; } -static CURLcode -schannel_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, bool *done) +static CURLcode schannel_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - timediff_t timeout_ms; - int what; + CURLcode result; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -1841,79 +1671,19 @@ schannel_connect_common(struct Curl_cfilter *cf, return CURLE_OK; } - if(ssl_connect_1 == connssl->connecting_state) { - /* check out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + *done = FALSE; + if(ssl_connect_1 == connssl->connecting_state) { result = schannel_connect_step1(cf, data); if(result) return result; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking ? 0 : timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ + if(ssl_connect_2 == connssl->connecting_state) { result = schannel_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(result) return result; - - } /* repeat step2 until all transactions are done. */ + } if(ssl_connect_3 == connssl->connecting_state) { result = schannel_connect_step3(cf, data); @@ -1931,7 +1701,8 @@ schannel_connect_common(struct Curl_cfilter *cf, * Available on Windows 7 or later. */ { - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; DEBUGASSERT(backend); cf->conn->sslContext = &backend->ctxt->ctxt_handle; } @@ -1939,11 +1710,6 @@ schannel_connect_common(struct Curl_cfilter *cf, *done = TRUE; } - else - *done = FALSE; - - /* reset our connection state machine */ - connssl->connecting_state = ssl_connect_1; return CURLE_OK; } @@ -1960,13 +1726,14 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, SecBufferDesc outbuf_desc; SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; DEBUGASSERT(backend); /* check if the maximum stream sizes were queried */ if(backend->stream_sizes.cbMaximumMessage == 0) { - sspi_status = s_pSecFn->QueryContextAttributes( + sspi_status = Curl_pSecFn->QueryContextAttributes( &backend->ctxt->ctxt_handle, SECPKG_ATTR_STREAM_SIZES, &backend->stream_sizes); @@ -2005,7 +1772,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, memcpy(outbuf[1].pvBuffer, buf, len); /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ - sspi_status = s_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0, + sspi_status = Curl_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0, &outbuf_desc, 0); /* check if the message was encrypted */ @@ -2016,10 +1783,10 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; /* - It's important to send the full message which includes the header, - encrypted payload, and trailer. Until the client receives all the + it is important to send the full message which includes the header, + encrypted payload, and trailer. Until the client receives all the data a coherent message has not been delivered and the client - can't read any of it. + cannot read any of it. If we wanted to buffer the unwritten encrypted bytes, we would tell the client that all data it has requested to be sent has been @@ -2065,7 +1832,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, this_write = Curl_conn_cf_send(cf->next, data, ptr + written, len - written, - &result); + FALSE, &result); if(result == CURLE_AGAIN) continue; else if(result != CURLE_OK) { @@ -2110,13 +1877,20 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* we want the length of the encrypted buffer to be at least large enough that it can hold all the bytes requested and some TLS record overhead. */ size_t min_encdata_length = len + CURL_SCHANNEL_BUFFER_FREE_SIZE; - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; DEBUGASSERT(backend); /**************************************************************************** - * Don't return or set backend->recv_unrecoverable_err unless in the cleanup. - * The pattern for return error is set *err, optional infof, goto cleanup. + * Do not return or set backend->recv_unrecoverable_err unless in the + * cleanup. The pattern for return error is set *err, optional infof, goto + * cleanup. + * + * Some verbose debug messages are wrapped by SCH_DEV() instead of DEBUGF() + * and only shown if CURL_SCHANNEL_DEV_DEBUG was defined at build time. These + * messages are extra verbose and intended for curl developers debugging + * Schannel recv decryption. * * Our priority is to always return as much decrypted data to the caller as * possible, even if an error occurs. The state of the decrypted buffer must @@ -2124,11 +1898,12 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, * handled in the cleanup. */ - DEBUGF(infof(data, "schannel: client wants to read %zu bytes", len)); + SCH_DEV(infof(data, "schannel: client wants to read %zu bytes", len)); *err = CURLE_OK; if(len && len <= backend->decdata_offset) { - infof(data, "schannel: enough decrypted data is already available"); + SCH_DEV(infof(data, + "schannel: enough decrypted data is already available")); goto cleanup; } else if(backend->recv_unrecoverable_err) { @@ -2141,8 +1916,7 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, infof(data, "schannel: server indicated shutdown in a prior call"); goto cleanup; } - - /* It's debatable what to return when !len. Regardless we can't return + /* it is debatable what to return when !len. Regardless we cannot return immediately because there may be data to decrypt (in the case we want to decrypt all encrypted cached data) so handle !len later in cleanup. */ @@ -2167,13 +1941,13 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->encdata_buffer = reallocated_buffer; backend->encdata_length = reallocated_length; size = backend->encdata_length - backend->encdata_offset; - DEBUGF(infof(data, "schannel: encdata_buffer resized %zu", - backend->encdata_length)); + SCH_DEV(infof(data, "schannel: encdata_buffer resized %zu", + backend->encdata_length)); } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* read encrypted data from socket */ nread = Curl_conn_cf_recv(cf->next, data, @@ -2181,29 +1955,26 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->encdata_offset), size, err); if(*err) { - nread = -1; if(*err == CURLE_AGAIN) - DEBUGF(infof(data, - "schannel: recv returned CURLE_AGAIN")); + SCH_DEV(infof(data, "schannel: recv returned CURLE_AGAIN")); else if(*err == CURLE_RECV_ERROR) infof(data, "schannel: recv returned CURLE_RECV_ERROR"); else infof(data, "schannel: recv returned error %d", *err); } else if(nread == 0) { - backend->recv_connection_closed = true; + backend->recv_connection_closed = TRUE; DEBUGF(infof(data, "schannel: server closed the connection")); } else if(nread > 0) { backend->encdata_offset += (size_t)nread; - backend->encdata_is_incomplete = false; - DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); + backend->encdata_is_incomplete = FALSE; + SCH_DEV(infof(data, "schannel: encrypted data got %zd", nread)); } } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* decrypt loop */ while(backend->encdata_offset > 0 && sspi_status == SEC_E_OK && @@ -2221,7 +1992,7 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */ - sspi_status = s_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle, + sspi_status = Curl_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle, &inbuf_desc, 0, NULL); /* check if everything went fine (server may want to renegotiate @@ -2231,8 +2002,8 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* check for successfully decrypted data, even before actual renegotiation or shutdown of the connection context */ if(inbuf[1].BufferType == SECBUFFER_DATA) { - DEBUGF(infof(data, "schannel: decrypted data length: %lu", - inbuf[1].cbBuffer)); + SCH_DEV(infof(data, "schannel: decrypted data length: %lu", + inbuf[1].cbBuffer)); /* increase buffer in order to fit the received amount of data */ size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ? @@ -2264,16 +2035,16 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->decdata_offset += size; } - DEBUGF(infof(data, "schannel: decrypted data added: %zu", size)); - DEBUGF(infof(data, - "schannel: decrypted cached: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); + SCH_DEV(infof(data, "schannel: decrypted data added: %zu", size)); + SCH_DEV(infof(data, + "schannel: decrypted cached: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); } /* check for remaining encrypted data */ if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) { - DEBUGF(infof(data, "schannel: encrypted data length: %lu", - inbuf[3].cbBuffer)); + SCH_DEV(infof(data, "schannel: encrypted data length: %lu", + inbuf[3].cbBuffer)); /* check if the remaining data is less than the total amount * and therefore begins after the already processed data @@ -2287,9 +2058,9 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->encdata_offset = inbuf[3].cbBuffer; } - DEBUGF(infof(data, - "schannel: encrypted cached: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, + "schannel: encrypted cached: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); } else { /* reset encrypted buffer offset, because there is no data remaining */ @@ -2300,17 +2071,18 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(sspi_status == SEC_I_RENEGOTIATE) { infof(data, "schannel: remote party requests renegotiation"); if(*err && *err != CURLE_AGAIN) { - infof(data, "schannel: can't renegotiate, an error is pending"); + infof(data, "schannel: cannot renegotiate, an error is pending"); goto cleanup; } /* begin renegotiation */ infof(data, "schannel: renegotiating SSL/TLS connection"); connssl->state = ssl_connection_negotiating; - connssl->connecting_state = ssl_connect_2_writing; - backend->recv_renegotiating = true; - *err = schannel_connect_common(cf, data, FALSE, &done); - backend->recv_renegotiating = false; + connssl->connecting_state = ssl_connect_2; + connssl->io_need = CURL_SSL_IO_NEED_SEND; + backend->recv_renegotiating = TRUE; + *err = schannel_connect(cf, data, &done); + backend->recv_renegotiating = FALSE; if(*err) { infof(data, "schannel: renegotiation failed"); goto cleanup; @@ -2324,53 +2096,56 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, else if(sspi_status == SEC_I_CONTEXT_EXPIRED) { /* In Windows 2000 SEC_I_CONTEXT_EXPIRED (close_notify) is not returned so we have to work around that in cleanup. */ - backend->recv_sspi_close_notify = true; - if(!backend->recv_connection_closed) { - backend->recv_connection_closed = true; - infof(data, "schannel: server closed the connection"); - } + backend->recv_sspi_close_notify = TRUE; + if(!backend->recv_connection_closed) + backend->recv_connection_closed = TRUE; + /* We received the close notify just fine, any error we got + * from the lower filters afterwards (e.g. the socket), is not + * an error on the TLS data stream. That one ended here. */ + if(*err == CURLE_RECV_ERROR) + *err = CURLE_OK; + infof(data, + "schannel: server close notification received (close_notify)"); goto cleanup; } } else if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { - backend->encdata_is_incomplete = true; + backend->encdata_is_incomplete = TRUE; if(!*err) *err = CURLE_AGAIN; - infof(data, "schannel: failed to decrypt data, need more data"); + SCH_DEV(infof(data, "schannel: failed to decrypt data, need more data")); goto cleanup; } else { #ifndef CURL_DISABLE_VERBOSE_STRINGS char buffer[STRERROR_LEN]; + failf(data, "schannel: failed to read data from server: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); #endif *err = CURLE_RECV_ERROR; - infof(data, "schannel: failed to read data from server: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); goto cleanup; } } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); - DEBUGF(infof(data, - "schannel: decrypted data buffer: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); + SCH_DEV(infof(data, "schannel: decrypted data buffer: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); cleanup: /* Warning- there is no guarantee the encdata state is valid at this point */ - DEBUGF(infof(data, "schannel: schannel_recv cleanup")); + SCH_DEV(infof(data, "schannel: schannel_recv cleanup")); /* Error if the connection has closed without a close_notify. - The behavior here is a matter of debate. We don't want to be vulnerable - to a truncation attack however there's some browser precedent for + The behavior here is a matter of debate. We do not want to be vulnerable + to a truncation attack however there is some browser precedent for ignoring the close_notify for compatibility reasons. Additionally, Windows 2000 (v5.0) is a special case since it seems it - doesn't return close_notify. In that case if the connection was closed we - assume it was graceful (close_notify) since there doesn't seem to be a + does not return close_notify. In that case if the connection was closed we + assume it was graceful (close_notify) since there does not seem to be a way to tell. */ if(len && !backend->decdata_offset && backend->recv_connection_closed && @@ -2379,10 +2154,10 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, VERSION_EQUAL); if(isWin2k && sspi_status == SEC_E_OK) - backend->recv_sspi_close_notify = true; + backend->recv_sspi_close_notify = TRUE; else { *err = CURLE_RECV_ERROR; - infof(data, "schannel: server closed abruptly (missing close_notify)"); + failf(data, "schannel: server closed abruptly (missing close_notify)"); } } @@ -2396,10 +2171,10 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, memmove(backend->decdata_buffer, backend->decdata_buffer + size, backend->decdata_offset - size); backend->decdata_offset -= size; - DEBUGF(infof(data, "schannel: decrypted data returned %zu", size)); - DEBUGF(infof(data, - "schannel: decrypted data buffer: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); + SCH_DEV(infof(data, "schannel: decrypted data returned %zu", size)); + SCH_DEV(infof(data, + "schannel: decrypted data buffer: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); *err = CURLE_OK; return (ssize_t)size; } @@ -2407,7 +2182,7 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(!*err && !backend->recv_connection_closed) *err = CURLE_AGAIN; - /* It's debatable what to return when !len. We could return whatever error + /* it is debatable what to return when !len. We could return whatever error we got from decryption but instead we override here so the return is consistent. */ @@ -2417,111 +2192,90 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return *err ? -1 : 0; } -static CURLcode schannel_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return schannel_connect_common(cf, data, TRUE, done); -} - -static CURLcode schannel_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - - result = schannel_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - static bool schannel_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { const struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; (void)data; DEBUGASSERT(backend); - if(connssl->backend->ctxt) /* SSL/TLS is in use */ - return (backend->decdata_offset > 0 || - (backend->encdata_offset > 0 && !backend->encdata_is_incomplete)); + if(backend->ctxt) /* SSL/TLS is in use */ + return backend->decdata_offset > 0 || + (backend->encdata_offset > 0 && !backend->encdata_is_incomplete) || + backend->recv_connection_closed || + backend->recv_sspi_close_notify || + backend->recv_unrecoverable_err; else return FALSE; } -static void schannel_session_free(void *ptr) -{ - /* this is expected to be called under sessionid lock */ - struct Curl_schannel_cred *cred = ptr; - - if(cred) { - cred->refcount--; - if(cred->refcount == 0) { - s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); - curlx_unicodefree(cred->sni_hostname); -#ifdef HAS_CLIENT_CERT_PATH - if(cred->client_cert_store) { - CertCloseStore(cred->client_cert_store, 0); - cred->client_cert_store = NULL; - } -#endif - Curl_safefree(cred); - } - } -} - /* shut down the SSL connection and clean up related memory. this function can be called multiple times on the same connection including if the SSL connection failed (eg connection made but failed handshake). */ -static int schannel_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode schannel_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { /* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx * Shutting Down an Schannel Connection */ struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + + if(cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } DEBUGASSERT(data); DEBUGASSERT(backend); - if(connssl->backend->ctxt) { + /* Not supported in schannel */ + (void)send_shutdown; + + *done = FALSE; + if(backend->ctxt) { infof(data, "schannel: shutting down SSL/TLS connection with %s port %d", - connssl->hostname, connssl->port); + connssl->peer.hostname, connssl->peer.port); } - if(backend->cred && backend->ctxt) { + if(!backend->ctxt || cf->shutdown) { + *done = TRUE; + goto out; + } + + if(backend->cred && backend->ctxt && !backend->sent_shutdown) { SecBufferDesc BuffDesc; SecBuffer Buffer; SECURITY_STATUS sspi_status; SecBuffer outbuf; SecBufferDesc outbuf_desc; - CURLcode result; DWORD dwshut = SCHANNEL_SHUTDOWN; InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut)); InitSecBufferDesc(&BuffDesc, &Buffer, 1); - sspi_status = s_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle, + sspi_status = Curl_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle, &BuffDesc); if(sspi_status != SEC_E_OK) { char buffer[STRERROR_LEN]; failf(data, "schannel: ApplyControlToken failure: %s", Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + result = CURLE_SEND_ERROR; + goto out; } /* setup output buffer */ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, &outbuf, 1); - sspi_status = s_pSecFn->InitializeSecurityContext( + sspi_status = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, &backend->ctxt->ctxt_handle, backend->cred->sni_hostname, @@ -2539,27 +2293,88 @@ static int schannel_shutdown(struct Curl_cfilter *cf, /* send close message which is in output buffer */ ssize_t written = Curl_conn_cf_send(cf->next, data, outbuf.pvBuffer, outbuf.cbBuffer, - &result); - s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); - if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { - infof(data, "schannel: failed to send close msg: %s" - " (bytes written: %zd)", curl_easy_strerror(result), written); + FALSE, &result); + Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + if(!result) { + if(written < (ssize_t)outbuf.cbBuffer) { + failf(data, "schannel: failed to send close msg: %s" + " (bytes written: %zd)", curl_easy_strerror(result), written); + result = CURLE_SEND_ERROR; + goto out; + } + backend->sent_shutdown = TRUE; + *done = TRUE; } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + } + else { + if(!backend->recv_connection_closed) { + failf(data, "schannel: error sending close msg: %d", result); + result = CURLE_SEND_ERROR; + goto out; + } + /* Looks like server already closed the connection. + * An error to send our close notify is not a failure. */ + *done = TRUE; + result = CURLE_OK; + } + } + } + + /* If the connection seems open and we have not seen the close notify + * from the server yet, try to receive it. */ + if(backend->cred && backend->ctxt && + !backend->recv_sspi_close_notify && !backend->recv_connection_closed) { + char buffer[1024]; + ssize_t nread; + + nread = schannel_recv(cf, data, buffer, sizeof(buffer), &result); + if(nread > 0) { + /* still data coming in? */ + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + backend->recv_connection_closed = TRUE; + *done = TRUE; + } + else if(nread < 0 && result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else { + CURL_TRC_CF(data, cf, "SSL shutdown, error %d", result); + result = CURLE_RECV_ERROR; } } +out: + cf->shutdown = (result || *done); + return result; +} + +static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(data); + DEBUGASSERT(backend); + /* free SSPI Schannel API security context handle */ if(backend->ctxt) { DEBUGF(infof(data, "schannel: clear security context handle")); - s_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); + Curl_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); Curl_safefree(backend->ctxt); } /* free SSPI Schannel API credential handle */ if(backend->cred) { - Curl_ssl_sessionid_lock(data); + Curl_ssl_scache_lock(data); schannel_session_free(backend->cred); - Curl_ssl_sessionid_unlock(data); + Curl_ssl_scache_unlock(data); backend->cred = NULL; } @@ -2568,7 +2383,7 @@ static int schannel_shutdown(struct Curl_cfilter *cf, Curl_safefree(backend->encdata_buffer); backend->encdata_length = 0; backend->encdata_offset = 0; - backend->encdata_is_incomplete = false; + backend->encdata_is_incomplete = FALSE; } /* free internal buffer for received decrypted data */ @@ -2577,18 +2392,39 @@ static int schannel_shutdown(struct Curl_cfilter *cf, backend->decdata_length = 0; backend->decdata_offset = 0; } - - return CURLE_OK; -} - -static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - schannel_shutdown(cf, data); } static int schannel_init(void) { - return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0); +#if defined(HAS_ALPN_SCHANNEL) && !defined(UNDER_CE) + bool wine = FALSE; + bool wine_has_alpn = FALSE; + +#ifndef CURL_WINDOWS_UWP + typedef const char *(APIENTRY *WINE_GET_VERSION_FN)(void); + /* GetModuleHandle() not available for UWP. + Assume no WINE because WINE has no UWP support. */ + WINE_GET_VERSION_FN p_wine_get_version = + CURLX_FUNCTION_CAST(WINE_GET_VERSION_FN, + (GetProcAddress(GetModuleHandleA("ntdll"), + "wine_get_version"))); + wine = !!p_wine_get_version; + if(wine) { + const char *wine_version = p_wine_get_version(); /* e.g. "6.0.2" */ + /* Assume ALPN support with WINE 6.0 or upper */ + wine_has_alpn = wine_version && atoi(wine_version) >= 6; + } +#endif + if(wine) + s_win_has_alpn = wine_has_alpn; + else { + /* ALPN is supported on Windows 8.1 / Server 2012 R2 and above. */ + s_win_has_alpn = curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL); + } +#endif /* HAS_ALPN_SCHANNEL && !UNDER_CE */ + + return Curl_sspi_global_init() == CURLE_OK ? 1 : 0; } static void schannel_cleanup(void) @@ -2598,9 +2434,7 @@ static void schannel_cleanup(void) static size_t schannel_version(char *buffer, size_t size) { - size = msnprintf(buffer, size, "Schannel"); - - return size; + return msnprintf(buffer, size, "Schannel"); } static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, @@ -2611,12 +2445,13 @@ static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, return Curl_win32_random(entropy, length); } -static CURLcode pkp_pin_peer_pubkey(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *pinnedpubkey) +static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *pinnedpubkey) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; CERT_CONTEXT *pCertContextServer = NULL; /* Result is returned to caller */ @@ -2624,7 +2459,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_cfilter *cf, DEBUGASSERT(backend); - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -2636,7 +2471,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_cfilter *cf, struct Curl_asn1Element *pubkey; sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pCertContextServer); @@ -2686,6 +2521,13 @@ static void schannel_checksum(const unsigned char *input, DWORD provType, const unsigned int algId) { +#ifdef CURL_WINDOWS_UWP + (void)input; + (void)inputlen; + (void)provType; + (void)algId; + memset(checksum, 0, checksumlen); +#else HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; DWORD cbHashSize = 0; @@ -2705,8 +2547,12 @@ static void schannel_checksum(const unsigned char *input, if(!CryptCreateHash(hProv, algId, 0, 0, &hHash)) break; /* failed */ - /* workaround for original MinGW, should be (const BYTE*) */ - if(!CryptHashData(hHash, (BYTE*)input, (DWORD)inputlen, 0)) +#ifdef __MINGW32CE__ + /* workaround for CeGCC, should be (const BYTE*) */ + if(!CryptHashData(hHash, (BYTE*)CURL_UNCONST(input), (DWORD)inputlen, 0)) +#else + if(!CryptHashData(hHash, input, (DWORD)inputlen, 0)) +#endif break; /* failed */ /* get hash size */ @@ -2727,6 +2573,7 @@ static void schannel_checksum(const unsigned char *input, if(hProv) CryptReleaseContext(hProv, 0); +#endif } static CURLcode schannel_sha256sum(const unsigned char *input, @@ -2742,12 +2589,156 @@ static CURLcode schannel_sha256sum(const unsigned char *input, static void *schannel_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) { - struct ssl_backend_data *backend = connssl->backend; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; (void)info; DEBUGASSERT(backend); return &backend->ctxt->ctxt_handle; } +HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_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; + timediff_t elapsed_ms; + struct curltime now; + unsigned char info_blob_digest[CURL_SHA256_DIGEST_LENGTH]; + + DEBUGASSERT(multi); + + if(!multi) { + return NULL; + } + + share = Curl_hash_pick(&multi->proto_hash, + CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); + if(!share || !share->cert_store) { + return NULL; + } + + /* zero ca_cache_timeout completely disables caching */ + if(!cfg->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; + if(timeout_ms >= 0) { + now = curlx_now(); + elapsed_ms = curlx_timediff(now, share->time); + if(elapsed_ms >= timeout_ms) { + return NULL; + } + } + + if(ca_info_blob) { + if(share->CAinfo_blob_size != ca_info_blob->len) { + return NULL; + } + schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + info_blob_digest, + CURL_SHA256_DIGEST_LENGTH); + if(memcmp(share->CAinfo_blob_digest, info_blob_digest, + CURL_SHA256_DIGEST_LENGTH)) { + return NULL; + } + } + else { + if(!conn_config->CAfile || !share->CAfile || + strcmp(share->CAfile, conn_config->CAfile)) { + return NULL; + } + } + + return share->cert_store; +} + +static void schannel_cert_share_free(void *key, size_t key_len, void *p) +{ + struct schannel_cert_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_SCHANNEL_CERT_SHARE_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->cert_store) { + CertCloseStore(share->cert_store, 0); + } + free(share->CAfile); + free(share); +} + +bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + HCERTSTORE cert_store) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_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; + size_t CAinfo_blob_size = 0; + char *CAfile = NULL; + + DEBUGASSERT(multi); + + if(!multi) { + return FALSE; + } + + share = Curl_hash_pick(&multi->proto_hash, + CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) { + return FALSE; + } + if(!Curl_hash_add2(&multi->proto_hash, + CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1, + share, schannel_cert_share_free)) { + free(share); + return FALSE; + } + } + + if(ca_info_blob) { + schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + share->CAinfo_blob_digest, + CURL_SHA256_DIGEST_LENGTH); + CAinfo_blob_size = ca_info_blob->len; + } + else { + if(conn_config->CAfile) { + CAfile = strdup(conn_config->CAfile); + if(!CAfile) { + return FALSE; + } + } + } + + /* free old cache data */ + if(share->cert_store) { + CertCloseStore(share->cert_store, 0); + } + free(share->CAfile); + + share->time = curlx_now(); + share->cert_store = cert_store; + share->CAinfo_blob_size = CAinfo_blob_size; + share->CAfile = CAfile; + return TRUE; +} + const struct Curl_ssl Curl_ssl_schannel = { { CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */ @@ -2755,37 +2746,35 @@ const struct Curl_ssl Curl_ssl_schannel = { #ifdef HAS_MANUAL_VERIFY_API SSLSUPP_CAINFO_BLOB | #endif +#ifndef CURL_WINDOWS_UWP SSLSUPP_PINNEDPUBKEY | - SSLSUPP_TLS13_CIPHERSUITES | - SSLSUPP_HTTPS_PROXY, +#endif + SSLSUPP_CA_CACHE | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, - sizeof(struct ssl_backend_data), + sizeof(struct schannel_ssl_backend_data), schannel_init, /* init */ schannel_cleanup, /* cleanup */ schannel_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ schannel_shutdown, /* shutdown */ schannel_data_pending, /* data_pending */ schannel_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ + NULL, /* cert_status_request */ schannel_connect, /* connect */ - schannel_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ schannel_get_internals, /* get_internals */ schannel_close, /* close_one */ - Curl_none_close_all, /* close_all */ - schannel_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ + NULL, /* false_start */ schannel_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ schannel_recv, /* recv decrypted data */ schannel_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_SCHANNEL */ diff --git a/Utilities/cmcurl/lib/vtls/schannel.h b/Utilities/cmcurl/lib/vtls/schannel.h index 7fae39fa0a5..c3512dd1323 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.h +++ b/Utilities/cmcurl/lib/vtls/schannel.h @@ -24,15 +24,13 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SCHANNEL -#define SCHANNEL_USE_BLACKLISTS 1 - #ifdef _MSC_VER #pragma warning(push) -#pragma warning(disable: 4201) +#pragma warning(disable:4201) #endif #include #ifdef _MSC_VER @@ -52,10 +50,10 @@ #include #include -#include "curl_sspi.h" +#include "../curl_sspi.h" -#include "cfilters.h" -#include "urldata.h" +#include "../cfilters.h" +#include "../urldata.h" /* has been included via the above . * Or in case of ldap.c, it was included via . @@ -70,7 +68,7 @@ * BoringSSL's : So just undefine those defines here * (and only here). */ -#if defined(HAVE_BORINGSSL) || defined(OPENSSL_IS_BORINGSSL) +#if defined(OPENSSL_IS_BORINGSSL) # undef X509_NAME # undef X509_CERT_PAIR # undef X509_EXTENSIONS @@ -78,122 +76,11 @@ extern const struct Curl_ssl Curl_ssl_schannel; +CURLcode Curl_verify_host(struct Curl_cfilter *cf, + struct Curl_easy *data); + CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, struct Curl_easy *data); -/* structs to expose only in schannel.c and schannel_verify.c */ -#ifdef EXPOSE_SCHANNEL_INTERNAL_STRUCTS - -#ifdef __MINGW32__ -#ifdef __MINGW64_VERSION_MAJOR -#define HAS_MANUAL_VERIFY_API -#endif -#else -#ifdef CERT_CHAIN_REVOCATION_CHECK_CHAIN -#define HAS_MANUAL_VERIFY_API -#endif -#endif - -#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX) \ - && !defined(DISABLE_SCHANNEL_CLIENT_CERT) -#define HAS_CLIENT_CERT_PATH -#endif - -#ifndef SCH_CREDENTIALS_VERSION - -#define SCH_CREDENTIALS_VERSION 0x00000005 - -typedef enum _eTlsAlgorithmUsage -{ - TlsParametersCngAlgUsageKeyExchange, - TlsParametersCngAlgUsageSignature, - TlsParametersCngAlgUsageCipher, - TlsParametersCngAlgUsageDigest, - TlsParametersCngAlgUsageCertSig -} eTlsAlgorithmUsage; - -typedef struct _CRYPTO_SETTINGS -{ - eTlsAlgorithmUsage eAlgorithmUsage; - UNICODE_STRING strCngAlgId; - DWORD cChainingModes; - PUNICODE_STRING rgstrChainingModes; - DWORD dwMinBitLength; - DWORD dwMaxBitLength; -} CRYPTO_SETTINGS, * PCRYPTO_SETTINGS; - -typedef struct _TLS_PARAMETERS -{ - DWORD cAlpnIds; - PUNICODE_STRING rgstrAlpnIds; - DWORD grbitDisabledProtocols; - DWORD cDisabledCrypto; - PCRYPTO_SETTINGS pDisabledCrypto; - DWORD dwFlags; -} TLS_PARAMETERS, * PTLS_PARAMETERS; - -typedef struct _SCH_CREDENTIALS -{ - DWORD dwVersion; - DWORD dwCredFormat; - DWORD cCreds; - PCCERT_CONTEXT* paCred; - HCERTSTORE hRootStore; - - DWORD cMappers; - struct _HMAPPER **aphMappers; - - DWORD dwSessionLifespan; - DWORD dwFlags; - DWORD cTlsParameters; - PTLS_PARAMETERS pTlsParameters; -} SCH_CREDENTIALS, * PSCH_CREDENTIALS; - -#define SCH_CRED_MAX_SUPPORTED_PARAMETERS 16 -#define SCH_CRED_MAX_SUPPORTED_ALPN_IDS 16 -#define SCH_CRED_MAX_SUPPORTED_CRYPTO_SETTINGS 16 -#define SCH_CRED_MAX_SUPPORTED_CHAINING_MODES 16 - -#endif - -struct Curl_schannel_cred { - CredHandle cred_handle; - TimeStamp time_stamp; - TCHAR *sni_hostname; -#ifdef HAS_CLIENT_CERT_PATH - HCERTSTORE client_cert_store; -#endif - int refcount; -}; - -struct Curl_schannel_ctxt { - CtxtHandle ctxt_handle; - TimeStamp time_stamp; -}; - -struct ssl_backend_data { - struct Curl_schannel_cred *cred; - struct Curl_schannel_ctxt *ctxt; - SecPkgContext_StreamSizes stream_sizes; - size_t encdata_length, decdata_length; - size_t encdata_offset, decdata_offset; - unsigned char *encdata_buffer, *decdata_buffer; - /* encdata_is_incomplete: if encdata contains only a partial record that - can't be decrypted without another recv() (that is, status is - SEC_E_INCOMPLETE_MESSAGE) then set this true. after an recv() adds - more bytes into encdata then set this back to false. */ - bool encdata_is_incomplete; - unsigned long req_flags, ret_flags; - CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ - bool recv_sspi_close_notify; /* true if connection closed by close_notify */ - bool recv_connection_closed; /* true if connection closed, regardless how */ - bool recv_renegotiating; /* true if recv is doing renegotiation */ - bool use_alpn; /* true if ALPN is used for this connection */ -#ifdef HAS_MANUAL_VERIFY_API - bool use_manual_cred_validation; /* true if manual cred validation is used */ -#endif -}; -#endif /* EXPOSE_SCHANNEL_INTERNAL_STRUCTS */ - #endif /* USE_SCHANNEL */ #endif /* HEADER_CURL_SCHANNEL_H */ diff --git a/Utilities/cmcurl/lib/vtls/schannel_int.h b/Utilities/cmcurl/lib/vtls/schannel_int.h new file mode 100644 index 00000000000..fe101254567 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/schannel_int.h @@ -0,0 +1,198 @@ +#ifndef HEADER_CURL_SCHANNEL_INT_H +#define HEADER_CURL_SCHANNEL_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Marc Hoersken, , et al. + * 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" + +#ifdef USE_SCHANNEL + +#include "vtls.h" + +#if (defined(__MINGW32__) || defined(CERT_CHAIN_REVOCATION_CHECK_CHAIN)) \ + && !defined(CURL_WINDOWS_UWP) +#define HAS_MANUAL_VERIFY_API +#endif + +#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX) \ + && !defined(DISABLE_SCHANNEL_CLIENT_CERT) +#define HAS_CLIENT_CERT_PATH +#endif + +#ifndef CRYPT_DECODE_NOCOPY_FLAG +#define CRYPT_DECODE_NOCOPY_FLAG 0x1 +#endif + +#ifndef CRYPT_DECODE_ALLOC_FLAG +#define CRYPT_DECODE_ALLOC_FLAG 0x8000 +#endif + +#ifndef CERT_ALT_NAME_DNS_NAME +#define CERT_ALT_NAME_DNS_NAME 3 +#endif + +#ifndef CERT_ALT_NAME_IP_ADDRESS +#define CERT_ALT_NAME_IP_ADDRESS 8 +#endif + +#if defined(_MSC_VER) && (_MSC_VER <= 1600) +/* Workaround for warning: + 'type cast' : conversion from 'int' to 'LPCSTR' of greater size */ +#undef CERT_STORE_PROV_MEMORY +#undef CERT_STORE_PROV_SYSTEM_A +#undef CERT_STORE_PROV_SYSTEM_W +#define CERT_STORE_PROV_MEMORY ((LPCSTR)(size_t)2) +#define CERT_STORE_PROV_SYSTEM_A ((LPCSTR)(size_t)9) +#define CERT_STORE_PROV_SYSTEM_W ((LPCSTR)(size_t)10) +#endif + +#ifndef SCH_CREDENTIALS_VERSION + +#define SCH_CREDENTIALS_VERSION 0x00000005 + +typedef enum _eTlsAlgorithmUsage +{ + TlsParametersCngAlgUsageKeyExchange, + TlsParametersCngAlgUsageSignature, + TlsParametersCngAlgUsageCipher, + TlsParametersCngAlgUsageDigest, + TlsParametersCngAlgUsageCertSig +} eTlsAlgorithmUsage; + +typedef struct _CRYPTO_SETTINGS +{ + eTlsAlgorithmUsage eAlgorithmUsage; + UNICODE_STRING strCngAlgId; + DWORD cChainingModes; + PUNICODE_STRING rgstrChainingModes; + DWORD dwMinBitLength; + DWORD dwMaxBitLength; +} CRYPTO_SETTINGS, * PCRYPTO_SETTINGS; + +typedef struct _TLS_PARAMETERS +{ + DWORD cAlpnIds; + PUNICODE_STRING rgstrAlpnIds; + DWORD grbitDisabledProtocols; + DWORD cDisabledCrypto; + PCRYPTO_SETTINGS pDisabledCrypto; + DWORD dwFlags; +} TLS_PARAMETERS, * PTLS_PARAMETERS; + +typedef struct _SCH_CREDENTIALS +{ + DWORD dwVersion; + DWORD dwCredFormat; + DWORD cCreds; + PCCERT_CONTEXT* paCred; + HCERTSTORE hRootStore; + + DWORD cMappers; + struct _HMAPPER **aphMappers; + + DWORD dwSessionLifespan; + DWORD dwFlags; + DWORD cTlsParameters; + PTLS_PARAMETERS pTlsParameters; +} SCH_CREDENTIALS, * PSCH_CREDENTIALS; + +#define SCH_CRED_MAX_SUPPORTED_PARAMETERS 16 +#define SCH_CRED_MAX_SUPPORTED_ALPN_IDS 16 +#define SCH_CRED_MAX_SUPPORTED_CRYPTO_SETTINGS 16 +#define SCH_CRED_MAX_SUPPORTED_CHAINING_MODES 16 + +#endif /* SCH_CREDENTIALS_VERSION */ + +struct Curl_schannel_cred { + CredHandle cred_handle; + TimeStamp time_stamp; + TCHAR *sni_hostname; +#ifdef HAS_CLIENT_CERT_PATH + HCERTSTORE client_cert_store; +#endif + int refcount; +}; + +struct Curl_schannel_ctxt { + CtxtHandle ctxt_handle; + TimeStamp time_stamp; +}; + +struct schannel_ssl_backend_data { + struct Curl_schannel_cred *cred; + struct Curl_schannel_ctxt *ctxt; + SecPkgContext_StreamSizes stream_sizes; + size_t encdata_length, decdata_length; + size_t encdata_offset, decdata_offset; + unsigned char *encdata_buffer, *decdata_buffer; + /* 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 an recv() adds + more bytes into encdata then set this back to false. */ + unsigned long req_flags, ret_flags; + CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ + BIT(recv_sspi_close_notify); /* true if connection closed by close_notify */ + BIT(recv_connection_closed); /* true if connection closed, regardless how */ + BIT(recv_renegotiating); /* true if recv is doing renegotiation */ + BIT(use_alpn); /* true if ALPN is used for this connection */ +#ifdef HAS_MANUAL_VERIFY_API + BIT(use_manual_cred_validation); /* true if manual cred validation is used */ +#endif + BIT(sent_shutdown); + BIT(encdata_is_incomplete); +}; + +/* key to use at `multi->proto_hash` */ +#define MPROTO_SCHANNEL_CERT_SHARE_KEY "tls:schannel:cert:share" + +struct schannel_cert_share { + unsigned char CAinfo_blob_digest[CURL_SHA256_DIGEST_LENGTH]; + size_t CAinfo_blob_size; /* CA info blob size */ + char *CAfile; /* CAfile path used to generate + certificate store */ + HCERTSTORE cert_store; /* cached certificate store or + NULL if none */ + struct curltime time; /* when the cached store was created */ +}; + +/* +* size of the structure: 20 bytes. +*/ +struct num_ip_data { + DWORD size; /* 04 bytes */ + union { + struct in_addr ia; /* 04 bytes */ + struct in6_addr ia6; /* 16 bytes */ + } bData; +}; + +HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data); + +bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + HCERTSTORE cert_store); + +#endif /* USE_SCHANNEL */ +#endif /* HEADER_CURL_SCHANNEL_INT_H */ diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c index d75ee8dfe7b..f843342b9ce 100644 --- a/Utilities/cmcurl/lib/vtls/schannel_verify.c +++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c @@ -29,32 +29,50 @@ * only be invoked by code in schannel.c. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SCHANNEL #ifndef USE_WINDOWS_SSPI -# error "Can't compile SCHANNEL support without SSPI." +# error "cannot compile SCHANNEL support without SSPI." #endif -#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS #include "schannel.h" +#include "schannel_int.h" -#ifdef HAS_MANUAL_VERIFY_API - +#include "../curlx/inet_pton.h" #include "vtls.h" #include "vtls_int.h" -#include "sendf.h" -#include "strerror.h" -#include "curl_multibyte.h" -#include "curl_printf.h" +#include "../sendf.h" +#include "../strerror.h" +#include "../curlx/winapi.h" +#include "../curlx/multibyte.h" +#include "../curl_printf.h" #include "hostcheck.h" -#include "version_win32.h" +#include "../curlx/version_win32.h" /* The last #include file should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" + +#define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend) -#define BACKEND connssl->backend +#ifdef HAS_MANUAL_VERIFY_API + +#ifdef __MINGW32CE__ +#define CERT_QUERY_OBJECT_BLOB 0x00000002 +#define CERT_QUERY_CONTENT_CERT 1 +#define CERT_QUERY_CONTENT_FLAG_CERT (1 << CERT_QUERY_CONTENT_CERT) +#define CERT_QUERY_FORMAT_BINARY 1 +#define CERT_QUERY_FORMAT_BASE64_ENCODED 2 +#define CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED 3 +#define CERT_QUERY_FORMAT_FLAG_ALL \ + (1 << CERT_QUERY_FORMAT_BINARY) | \ + (1 << CERT_QUERY_FORMAT_BASE64_ENCODED) | \ + (1 << CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED) +#define CERT_CHAIN_REVOCATION_CHECK_CHAIN 0x20000000 +#define CERT_NAME_DISABLE_IE4_UTF8_FLAG 0x00010000 +#define CERT_TRUST_IS_OFFLINE_REVOCATION 0x01000000 +#endif /* __MINGW32CE__ */ #define MAX_CAFILE_SIZE 1048576 /* 1 MiB */ #define BEGIN_CERT "-----BEGIN CERTIFICATE-----" @@ -75,14 +93,15 @@ struct cert_chain_engine_config_win7 { HCERTSTORE hExclusiveTrustedPeople; }; +#ifndef UNDER_CE static int is_cr_or_lf(char c) { return c == '\r' || c == '\n'; } /* Search the substring needle,needlelen into string haystack,haystacklen - * Strings don't need to be terminated by a '\0'. - * Similar of OSX/Linux memmem (not available on Visual Studio). + * Strings do not need to be terminated by a '\0'. + * Similar of macOS/Linux memmem (not available on Visual Studio). * Return position of beginning of first occurrence or NULL if not found */ static const char *c_memmem(const void *haystack, size_t haystacklen, @@ -115,7 +134,7 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, const char *current_ca_file_ptr = ca_buffer; const char *ca_buffer_limit = ca_buffer + ca_buffer_size; - while(more_certs && (current_ca_file_ptrpCertInfo; - if(!cert_info) { - failf(data, "schannel: Null certificate info."); - return actual_length; - } - - extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, - cert_info->cExtension, - cert_info->rgExtension); - if(!extension) { - failf(data, "schannel: CertFindExtension() returned no extension."); - return actual_length; - } - - decode_para.cbSize = sizeof(CRYPT_DECODE_PARA); - - ret_val = - CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - szOID_SUBJECT_ALT_NAME2, - extension->Value.pbData, - extension->Value.cbData, - CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, - &decode_para, - &alt_name_info, - &alt_name_info_size); - if(!ret_val) { - failf(data, - "schannel: CryptDecodeObjectEx() returned no alternate name " - "information."); - return actual_length; - } - current_pos = host_names; /* Iterate over the alternate names and populate host_names. */ @@ -438,14 +433,14 @@ static DWORD cert_get_name_string(struct Curl_easy *data, } /* Sanity check to prevent buffer overrun. */ if((actual_length + current_length) > length) { - failf(data, "schannel: Not enough memory to list all host names."); + failf(data, "schannel: Not enough memory to list all hostnames."); break; } dns_w = entry->pwszDNSName; - /* pwszDNSName is in ia5 string format and hence doesn't contain any - * non-ascii characters. */ + /* pwszDNSName is in ia5 string format and hence does not contain any + * non-ASCII characters. */ while(*dns_w != '\0') { - *current_pos++ = (char)(*dns_w++); + *current_pos++ = (TCHAR)(*dns_w++); } *current_pos++ = '\0'; actual_length += (DWORD)current_length; @@ -454,115 +449,304 @@ static DWORD cert_get_name_string(struct Curl_easy *data, /* Last string has double null-terminator. */ *current_pos = '\0'; } +#endif return actual_length; } -static CURLcode verify_host(struct Curl_easy *data, - CERT_CONTEXT *pCertContextServer, - const char *conn_hostname) +/* +* Returns TRUE if the hostname is a numeric IPv4/IPv6 Address, +* and populates the buffer with IPv4/IPv6 info. +*/ + +static bool get_num_host_info(struct num_ip_data *ip_blob, + LPCSTR hostname) { - CURLcode result = CURLE_PEER_FAILED_VERIFICATION; - TCHAR *cert_hostname_buff = NULL; - size_t cert_hostname_buff_index = 0; - size_t hostlen = strlen(conn_hostname); - DWORD len = 0; - DWORD actual_len = 0; + struct in_addr ia; + struct in6_addr ia6; + bool result = FALSE; + + int res = curlx_inet_pton(AF_INET, hostname, &ia); + if(res) { + ip_blob->size = sizeof(struct in_addr); + memcpy(&ip_blob->bData.ia, &ia, sizeof(struct in_addr)); + result = TRUE; + } + else { + res = curlx_inet_pton(AF_INET6, hostname, &ia6); + if(res) { + ip_blob->size = sizeof(struct in6_addr); + memcpy(&ip_blob->bData.ia6, &ia6, sizeof(struct in6_addr)); + result = TRUE; + } + } + return result; +} - /* Determine the size of the string needed for the cert hostname */ - len = cert_get_name_string(data, pCertContextServer, NULL, 0); - if(len == 0) { - failf(data, - "schannel: CertGetNameString() returned no " - "certificate name information"); - result = CURLE_PEER_FAILED_VERIFICATION; - goto cleanup; +static bool get_alt_name_info(struct Curl_easy *data, + PCCERT_CONTEXT ctx, + PCERT_ALT_NAME_INFO *alt_name_info, + LPDWORD alt_name_info_size) +{ + bool result = FALSE; +#if defined(CURL_WINDOWS_UWP) + (void)data; + (void)ctx; + (void)alt_name_info; + (void)alt_name_info_size; +#else + PCERT_INFO cert_info = NULL; + PCERT_EXTENSION extension = NULL; + CRYPT_DECODE_PARA decode_para = { sizeof(CRYPT_DECODE_PARA), NULL, NULL }; + + if(!ctx) { + failf(data, "schannel: Null certificate context."); + return result; } - /* CertGetNameString guarantees that the returned name will not contain - * embedded null bytes. This appears to be undocumented behavior. - */ - cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); - if(!cert_hostname_buff) { - result = CURLE_OUT_OF_MEMORY; - goto cleanup; + cert_info = ctx->pCertInfo; + if(!cert_info) { + failf(data, "schannel: Null certificate info."); + return result; } - actual_len = cert_get_name_string( - data, pCertContextServer, (LPTSTR)cert_hostname_buff, len); - /* Sanity check */ - if(actual_len != len) { + extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, + cert_info->cExtension, + cert_info->rgExtension); + if(!extension) { + failf(data, "schannel: CertFindExtension() returned no extension."); + return result; + } + + if(!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_SUBJECT_ALT_NAME2, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + alt_name_info, + alt_name_info_size)) { failf(data, - "schannel: CertGetNameString() returned certificate " - "name information of unexpected size"); - result = CURLE_PEER_FAILED_VERIFICATION; - goto cleanup; + "schannel: CryptDecodeObjectEx() returned no alternate name " + "information."); + return result; } + result = TRUE; +#endif + return result; +} +#endif /* !UNDER_CE */ - /* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output - * will contain all DNS names, where each name is null-terminated - * and the last DNS name is double null-terminated. Due to this - * encoding, use the length of the buffer to iterate over all names. +/* Verify the server's hostname */ +CURLcode Curl_verify_host(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result = CURLE_PEER_FAILED_VERIFICATION; + struct ssl_connect_data *connssl = cf->ctx; + CERT_CONTEXT *pCertContextServer = NULL; +#ifdef UNDER_CE + TCHAR cert_hostname_buff[256]; + DWORD len; + + /* This code does not support certificates with multiple alternative names. + * Right now we are only asking for the first preferred alternative name. + * Instead we would need to do all via CERT_NAME_SEARCH_ALL_NAMES_FLAG + * (If Windows CE supports that?) and run this section in a loop for each. + * https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086.aspx + * curl: (51) schannel: CertGetNameString() certificate hostname + * (.google.com) did not match connection (google.com) */ - result = CURLE_PEER_FAILED_VERIFICATION; - while(cert_hostname_buff_index < len && - cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && - result == CURLE_PEER_FAILED_VERIFICATION) { - - char *cert_hostname; - + len = CertGetNameString(pCertContextServer, + CERT_NAME_DNS_TYPE, + CERT_NAME_DISABLE_IE4_UTF8_FLAG, + NULL, + cert_hostname_buff, + 256); + if(len > 0) { /* Comparing the cert name and the connection hostname encoded as UTF-8 * is acceptable since both values are assumed to use ASCII * (or some equivalent) encoding */ - cert_hostname = curlx_convert_tchar_to_UTF8( - &cert_hostname_buff[cert_hostname_buff_index]); + char *cert_hostname = curlx_convert_tchar_to_UTF8(cert_hostname_buff); if(!cert_hostname) { result = CURLE_OUT_OF_MEMORY; } - else { + else{ + const char *conn_hostname = connssl->peer.hostname; if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), - conn_hostname, hostlen)) { + conn_hostname, strlen(conn_hostname))) { infof(data, "schannel: connection hostname (%s) validated " - "against certificate name (%s)", + "against certificate name (%s)\n", conn_hostname, cert_hostname); result = CURLE_OK; } - else { - size_t cert_hostname_len; - - infof(data, - "schannel: connection hostname (%s) did not match " - "against certificate name (%s)", + else{ + failf(data, + "schannel: connection hostname (%s) " + "does not match certificate name (%s)", conn_hostname, cert_hostname); + } + Curl_safefree(cert_hostname); + } + } + else { + failf(data, + "schannel: CertGetNameString did not provide any " + "certificate name information"); + } +#else + SECURITY_STATUS sspi_status; + TCHAR *cert_hostname_buff = NULL; + size_t cert_hostname_buff_index = 0; + const char *conn_hostname = connssl->peer.hostname; + size_t hostlen = strlen(conn_hostname); + DWORD len = 0; + DWORD actual_len = 0; + PCERT_ALT_NAME_INFO alt_name_info = NULL; + DWORD alt_name_info_size = 0; + struct num_ip_data ip_blob = { 0 }; + bool Win8_compat; + struct num_ip_data *p = &ip_blob; + DWORD i; - cert_hostname_len = - _tcslen(&cert_hostname_buff[cert_hostname_buff_index]); + sspi_status = + Curl_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); - /* Move on to next cert name */ - cert_hostname_buff_index += cert_hostname_len + 1; + if((sspi_status != SEC_E_OK) || !pCertContextServer) { + char buffer[WINAPI_ERROR_LEN]; + failf(data, "schannel: Failed to read remote certificate context: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + goto cleanup; + } - result = CURLE_PEER_FAILED_VERIFICATION; + Win8_compat = curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL); + if(get_num_host_info(p, conn_hostname) || !Win8_compat) { + if(!get_alt_name_info(data, pCertContextServer, + &alt_name_info, &alt_name_info_size)) { + goto cleanup; + } + } + + if(p->size && alt_name_info) { + for(i = 0; i < alt_name_info->cAltEntry; ++i) { + PCERT_ALT_NAME_ENTRY entry = &alt_name_info->rgAltEntry[i]; + if(entry->dwAltNameChoice == CERT_ALT_NAME_IP_ADDRESS) { + if(entry->IPAddress.cbData == p->size) { + if(!memcmp(entry->IPAddress.pbData, &p->bData, + entry->IPAddress.cbData)) { + result = CURLE_OK; + infof(data, + "schannel: connection hostname (%s) matched cert's IP address!", + conn_hostname); + break; + } + } } - curlx_unicodefree(cert_hostname); } } + else { + /* Determine the size of the string needed for the cert hostname */ + len = cert_get_name_string(data, pCertContextServer, + NULL, 0, alt_name_info, Win8_compat); + if(len == 0) { + failf(data, + "schannel: CertGetNameString() returned no " + "certificate name information"); + goto cleanup; + } - if(result == CURLE_PEER_FAILED_VERIFICATION) { - failf(data, - "schannel: CertGetNameString() failed to match " - "connection hostname (%s) against server certificate names", - conn_hostname); + /* CertGetNameString guarantees that the returned name will not contain + * embedded null bytes. This appears to be undocumented behavior. + */ + cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); + if(!cert_hostname_buff) { + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + actual_len = cert_get_name_string(data, pCertContextServer, + (LPTSTR)cert_hostname_buff, len, alt_name_info, Win8_compat); + + /* Sanity check */ + if(actual_len != len) { + failf(data, + "schannel: CertGetNameString() returned certificate " + "name information of unexpected size"); + goto cleanup; + } + + /* cert_hostname_buff contains all DNS names, where each name is + * null-terminated and the last DNS name is double null-terminated. Due to + * this encoding, use the length of the buffer to iterate over all names. + */ + while(cert_hostname_buff_index < len && + cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && + result == CURLE_PEER_FAILED_VERIFICATION) { + + char *cert_hostname; + + /* Comparing the cert name and the connection hostname encoded as UTF-8 + * is acceptable since both values are assumed to use ASCII + * (or some equivalent) encoding + */ + cert_hostname = curlx_convert_tchar_to_UTF8( + &cert_hostname_buff[cert_hostname_buff_index]); + if(!cert_hostname) { + result = CURLE_OUT_OF_MEMORY; + } + else { + if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), + conn_hostname, hostlen)) { + infof(data, + "schannel: connection hostname (%s) validated " + "against certificate name (%s)", + conn_hostname, cert_hostname); + result = CURLE_OK; + } + else { + size_t cert_hostname_len; + + infof(data, + "schannel: connection hostname (%s) did not match " + "against certificate name (%s)", + conn_hostname, cert_hostname); + + cert_hostname_len = + _tcslen(&cert_hostname_buff[cert_hostname_buff_index]); + + /* Move on to next cert name */ + cert_hostname_buff_index += cert_hostname_len + 1; + + result = CURLE_PEER_FAILED_VERIFICATION; + } + curlx_unicodefree(cert_hostname); + } + } + + if(result == CURLE_PEER_FAILED_VERIFICATION) { + failf(data, + "schannel: CertGetNameString() failed to match " + "connection hostname (%s) against server certificate names", + conn_hostname); + } + else if(result != CURLE_OK) + failf(data, "schannel: server certificate name verification failed"); } - else if(result != CURLE_OK) - failf(data, "schannel: server certificate name verification failed"); cleanup: Curl_safefree(cert_hostname_buff); + if(pCertContextServer) + CertFreeCertificateContext(pCertContextServer); +#endif /* !UNDER_CE */ + return result; } +#ifdef HAS_MANUAL_VERIFY_API +/* Verify the server's certificate and hostname */ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -574,22 +758,26 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CERT_CONTEXT *pCertContextServer = NULL; const CERT_CHAIN_CONTEXT *pChainContext = NULL; HCERTCHAINENGINE cert_chain_engine = NULL; +#ifndef UNDER_CE HCERTSTORE trust_store = NULL; + HCERTSTORE own_trust_store = NULL; +#endif /* !UNDER_CE */ DEBUGASSERT(BACKEND); sspi_status = - s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &pCertContextServer); + Curl_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); if((sspi_status != SEC_E_OK) || !pCertContextServer) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: Failed to read remote certificate context: %s", Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); result = CURLE_PEER_FAILED_VERIFICATION; } +#ifndef UNDER_CE if(result == CURLE_OK && (conn_config->CAfile || conn_config->ca_info_blob) && BACKEND->use_manual_cred_validation) { @@ -605,31 +793,46 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, result = CURLE_SSL_CACERT_BADFILE; } else { - /* Open the certificate store */ - trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, - 0, - (HCRYPTPROV)NULL, - CERT_STORE_CREATE_NEW_FLAG, - NULL); - if(!trust_store) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: failed to create certificate store: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; + /* try cache */ + trust_store = Curl_schannel_get_cached_cert_store(cf, data); + + if(trust_store) { + infof(data, "schannel: reusing certificate store from cache"); } else { - const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; - if(ca_info_blob) { - result = add_certs_data_to_store(trust_store, - (const char *)ca_info_blob->data, - ca_info_blob->len, - "(memory blob)", - data); + /* Open the certificate store */ + trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, + 0, + (HCRYPTPROV)NULL, + CERT_STORE_CREATE_NEW_FLAG, + NULL); + if(!trust_store) { + char buffer[WINAPI_ERROR_LEN]; + failf(data, "schannel: failed to create certificate store: %s", + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; } else { - result = add_certs_file_to_store(trust_store, - conn_config->CAfile, - data); + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + own_trust_store = trust_store; + + if(ca_info_blob) { + result = add_certs_data_to_store(trust_store, + (const char *)ca_info_blob->data, + ca_info_blob->len, + "(memory blob)", + data); + } + else { + result = add_certs_file_to_store(trust_store, + conn_config->CAfile, + data); + } + if(result == CURLE_OK) { + if(Curl_schannel_set_cached_cert_store(cf, data, trust_store)) { + own_trust_store = NULL; + } + } } } } @@ -651,14 +854,15 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CertCreateCertificateChainEngine( (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine); if(!create_engine_result) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to create certificate chain engine: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; } } } +#endif /* !UNDER_CE */ if(result == CURLE_OK) { CERT_CHAIN_PARA ChainPara; @@ -675,9 +879,9 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CERT_CHAIN_REVOCATION_CHECK_CHAIN), NULL, &pChainContext)) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: CertGetCertificateChain failed: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); pChainContext = NULL; result = CURLE_PEER_FAILED_VERIFICATION; } @@ -712,7 +916,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, failf(data, "schannel: CertGetCertificateChain trust error" " CERT_TRUST_REVOCATION_STATUS_UNKNOWN"); else - failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", + failf(data, "schannel: CertGetCertificateChain error mask: 0x%08lx", dwTrustErrorMask); result = CURLE_PEER_FAILED_VERIFICATION; } @@ -721,17 +925,19 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, if(result == CURLE_OK) { if(conn_config->verifyhost) { - result = verify_host(data, pCertContextServer, connssl->hostname); + result = Curl_verify_host(cf, data); } } +#ifndef UNDER_CE if(cert_chain_engine) { CertFreeCertificateChainEngine(cert_chain_engine); } - if(trust_store) { - CertCloseStore(trust_store, 0); + if(own_trust_store) { + CertCloseStore(own_trust_store, 0); } +#endif /* !UNDER_CE */ if(pChainContext) CertFreeCertificateChain(pChainContext); diff --git a/Utilities/cmcurl/lib/vtls/sectransp.c b/Utilities/cmcurl/lib/vtls/sectransp.c index c9f02f2d5b0..2ae2ef35a2b 100644 --- a/Utilities/cmcurl/lib/vtls/sectransp.c +++ b/Utilities/cmcurl/lib/vtls/sectransp.c @@ -24,30 +24,37 @@ ***************************************************************************/ /* - * Source file for all iOS and macOS SecureTransport-specific code for the + * Source file for all iOS and macOS Secure Transport-specific code for the * TLS/SSL layer. No code but vtls.c should ever call or use these functions. */ -#include "curl_setup.h" - -#include "urldata.h" /* for the Curl_easy definition */ -#include "curl_base64.h" -#include "strtok.h" -#include "multiif.h" -#include "strcase.h" -#include "x509asn1.h" -#include "strerror.h" +#include "../curl_setup.h" #ifdef USE_SECTRANSP +#include "../urldata.h" /* for the Curl_easy definition */ +#include "../curlx/base64.h" +#include "../curlx/strparse.h" +#include "../multiif.h" +#include "../strcase.h" +#include "x509asn1.h" +#include "vtls_scache.h" +#include "../strerror.h" +#include "cipher_suite.h" + #ifdef __clang__ #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#pragma clang diagnostic ignored "-Wunreachable-code" #endif /* __clang__ */ #ifdef __GNUC__ +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Waddress" -#pragma GCC diagnostic ignored "-Wundef" +#endif + +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #include @@ -70,7 +77,7 @@ #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) #if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 -#error "The Secure Transport back-end requires Leopard or later." +#error "The Secure Transport backend requires Leopard or later." #endif /* MAC_OS_X_VERSION_MAX_ALLOWED < 1050 */ #define CURL_BUILD_IOS 0 @@ -120,668 +127,90 @@ #define CURL_SUPPORT_MAC_10_9 0 #else -#error "The Secure Transport back-end requires iOS or macOS." +#error "The Secure Transport backend requires iOS or macOS." #endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */ #if CURL_BUILD_MAC #include #endif /* CURL_BUILD_MAC */ -#include "sendf.h" -#include "inet_pton.h" -#include "connect.h" -#include "select.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" +#include "../connect.h" +#include "../select.h" #include "vtls.h" #include "vtls_int.h" #include "sectransp.h" -#include "curl_printf.h" -#include "strdup.h" +#include "../curl_printf.h" +#include "../strdup.h" -#include "curl_memory.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" +#include "../memdebug.h" -/* From MacTypes.h (which we can't include because it isn't present in iOS: */ +/* From MacTypes.h (which we cannot include because it is not present in + iOS: */ #define ioErr -36 #define paramErr -50 -struct ssl_backend_data { +struct st_ssl_backend_data { SSLContextRef ssl_ctx; bool ssl_direction; /* true if writing, false if reading */ size_t ssl_write_buffered_length; + BIT(sent_shutdown); }; -struct st_cipher { - const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ - const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ - SSLCipherSuite num; /* Cipher suite code/number defined in IANA registry */ - bool weak; /* Flag to mark cipher as weak based on previous implementation - of Secure Transport back-end by CURL */ -}; - -/* Macro to initialize st_cipher data structure: stringify id to name, cipher - number/id, 'weak' suite flag - */ -#define CIPHER_DEF(num, alias, weak) \ - { #num, alias, num, weak } - -/* - Macro to initialize st_cipher data structure with name, code (IANA cipher - number/id value), and 'weak' suite flag. The first 28 cipher suite numbers - have the same IANA code for both SSL and TLS standards: numbers 0x0000 to - 0x001B. They have different names though. The first 4 letters of the cipher - suite name are the protocol name: "SSL_" or "TLS_", rest of the IANA name is - the same for both SSL and TLS cipher suite name. - The second part of the problem is that macOS/iOS SDKs don't define all TLS - codes but only 12 of them. The SDK defines all SSL codes though, i.e. SSL_NUM - constant is always defined for those 28 ciphers while TLS_NUM is defined only - for 12 of the first 28 ciphers. Those 12 TLS cipher codes match to - corresponding SSL enum value and represent the same cipher suite. Therefore - we'll use the SSL enum value for those cipher suites because it is defined - for all 28 of them. - We make internal data consistent and based on TLS names, i.e. all st_cipher - item names start with the "TLS_" prefix. - Summarizing all the above, those 28 first ciphers are presented in our table - with both TLS and SSL names. Their cipher numbers are assigned based on the - SDK enum value for the SSL cipher, which matches to IANA TLS number. +/* Create the list of default ciphers to use by making an intersection of the + * ciphers supported by Secure Transport and the list below, using the order + * of the former. + * This list is based on TLS recommendations by Mozilla, balancing between + * security and wide compatibility: "Most ciphers that are not clearly broken + * and dangerous to use are supported" */ -#define CIPHER_DEF_SSLTLS(num_wo_prefix, alias, weak) \ - { "TLS_" #num_wo_prefix, alias, SSL_##num_wo_prefix, weak } - -/* - Cipher suites were marked as weak based on the following: - RC4 encryption - rfc7465, the document contains a list of deprecated ciphers. - Marked in the code below as weak. - RC2 encryption - many mentions, was found vulnerable to a relatively easy - attack https://link.springer.com/chapter/10.1007%2F3-540-69710-1_14 - Marked in the code below as weak. - DES and IDEA encryption - rfc5469, has a list of deprecated ciphers. - Marked in the code below as weak. - Anonymous Diffie-Hellman authentication and anonymous elliptic curve - Diffie-Hellman - vulnerable to a man-in-the-middle attack. Deprecated by - RFC 4346 aka TLS 1.1 (section A.5, page 60) - Null bulk encryption suites - not encrypted communication - Export ciphers, i.e. ciphers with restrictions to be used outside the US for - software exported to some countries, they were excluded from TLS 1.1 - version. More precisely, they were noted as ciphers which MUST NOT be - negotiated in RFC 4346 aka TLS 1.1 (section A.5, pages 60 and 61). - All of those filters were considered weak because they contain a weak - algorithm like DES, RC2 or RC4, and already considered weak by other - criteria. - 3DES - NIST deprecated it and is going to retire it by 2023 - https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA - OpenSSL https://www.openssl.org/blog/blog/2016/08/24/sweet32/ also - deprecated those ciphers. Some other libraries also consider it - vulnerable or at least not strong enough. - - CBC ciphers are vulnerable with SSL3.0 and TLS1.0: - https://www.cisco.com/c/en/us/support/docs/security/email-security-appliance - /118518-technote-esa-00.html - We don't take care of this issue because it is resolved by later TLS - versions and for us, it requires more complicated checks, we need to - check a protocol version also. Vulnerability doesn't look very critical - and we do not filter out those cipher suites. - */ - -#define CIPHER_WEAK_NOT_ENCRYPTED TRUE -#define CIPHER_WEAK_RC_ENCRYPTION TRUE -#define CIPHER_WEAK_DES_ENCRYPTION TRUE -#define CIPHER_WEAK_IDEA_ENCRYPTION TRUE -#define CIPHER_WEAK_ANON_AUTH TRUE -#define CIPHER_WEAK_3DES_ENCRYPTION TRUE -#define CIPHER_STRONG_ENOUGH FALSE - -/* Please do not change the order of the first ciphers available for SSL. - Do not insert and do not delete any of them. Code below - depends on their order and continuity. - If you add a new cipher, please maintain order by number, i.e. - insert in between existing items to appropriate place based on - cipher suite IANA number -*/ -static const struct st_cipher ciphertable[] = { - /* SSL version 3.0 and initial TLS 1.0 cipher suites. - Defined since SDK 10.2.8 */ - CIPHER_DEF_SSLTLS(NULL_WITH_NULL_NULL, /* 0x0000 */ - NULL, - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_WITH_NULL_MD5, /* 0x0001 */ - "NULL-MD5", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_WITH_NULL_SHA, /* 0x0002 */ - "NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC4_40_MD5, /* 0x0003 */ - "EXP-RC4-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_MD5, /* 0x0004 */ - "RC4-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_SHA, /* 0x0005 */ - "RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* 0x0006 */ - "EXP-RC2-CBC-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_IDEA_CBC_SHA, /* 0x0007 */ - "IDEA-CBC-SHA", - CIPHER_WEAK_IDEA_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0008 */ - "EXP-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_DES_CBC_SHA, /* 0x0009 */ - "DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ - "DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x000B */ - "EXP-DH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_WITH_DES_CBC_SHA, /* 0x000C */ - "DH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x000D */ - "DH-DSS-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x000E */ - "EXP-DH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_WITH_DES_CBC_SHA, /* 0x000F */ - "DH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0010 */ - "DH-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x0011 */ - "EXP-EDH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_WITH_DES_CBC_SHA, /* 0x0012 */ - "EDH-DSS-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x0013 */ - "DHE-DSS-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0014 */ - "EXP-EDH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_WITH_DES_CBC_SHA, /* 0x0015 */ - "EDH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0016 */ - "DHE-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_RC4_40_MD5, /* 0x0017 */ - "EXP-ADH-RC4-MD5", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_RC4_128_MD5, /* 0x0018 */ - "ADH-RC4-MD5", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_DES40_CBC_SHA, /* 0x0019 */ - "EXP-ADH-DES-CBC-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_DES_CBC_SHA, /* 0x001A */ - "ADH-DES-CBC-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_3DES_EDE_CBC_SHA, /* 0x001B */ - "ADH-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_NULL_SHA, /* 0x001C */ - NULL, - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, /* 0x001D */ - NULL, - CIPHER_STRONG_ENOUGH), - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */ - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA, /* 0x002C */ - "PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA, /* 0x002D */ - "DHE-PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA, /* 0x002E */ - "RSA-PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - - /* TLS addenda using AES, per RFC 3268. Defined since SDK 10.4u */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ - "AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA, /* 0x0030 */ - "DH-DSS-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA, /* 0x0031 */ - "DH-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* 0x0032 */ - "DHE-DSS-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* 0x0033 */ - "DHE-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA, /* 0x0034 */ - "ADH-AES128-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ - "AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA, /* 0x0036 */ - "DH-DSS-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA, /* 0x0037 */ - "DH-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* 0x0038 */ - "DHE-DSS-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* 0x0039 */ - "DHE-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA, /* 0x003A */ - "ADH-AES256-SHA", - CIPHER_WEAK_ANON_AUTH), - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* TLS 1.2 addenda, RFC 5246 */ - /* Server provided RSA certificate for key exchange. */ - CIPHER_DEF(TLS_RSA_WITH_NULL_SHA256, /* 0x003B */ - "NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ - "AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ - "AES256-SHA256", - CIPHER_STRONG_ENOUGH), - /* Server-authenticated (and optionally client-authenticated) - Diffie-Hellman. */ - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA256, /* 0x003E */ - "DH-DSS-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA256, /* 0x003F */ - "DH-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, /* 0x0040 */ - "DHE-DSS-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - - /* TLS 1.2 addenda, RFC 5246 */ - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */ - "DHE-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA256, /* 0x0068 */ - "DH-DSS-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA256, /* 0x0069 */ - "DH-RSA-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, /* 0x006A */ - "DHE-DSS-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */ - "DHE-RSA-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA256, /* 0x006C */ - "ADH-AES128-SHA256", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA256, /* 0x006D */ - "ADH-AES256-SHA256", - CIPHER_WEAK_ANON_AUTH), -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* Addendum from RFC 4279, TLS PSK */ - CIPHER_DEF(TLS_PSK_WITH_RC4_128_SHA, /* 0x008A */ - "PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008B */ - "PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA, /* 0x008C */ - "PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA, /* 0x008D */ - "PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_RC4_128_SHA, /* 0x008E */ - "DHE-PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008F */ - "DHE-PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA, /* 0x0090 */ - "DHE-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA, /* 0x0091 */ - "DHE-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_RC4_128_SHA, /* 0x0092 */ - "RSA-PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x0093 */ - "RSA-PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA, /* 0x0094 */ - "RSA-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA, /* 0x0095 */ - "RSA-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites - for TLS. */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ - "AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ - "AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */ - "DHE-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */ - "DHE-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_GCM_SHA256, /* 0x00A0 */ - "DH-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_GCM_SHA384, /* 0x00A1 */ - "DH-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A2 */ - "DHE-DSS-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A3 */ - "DHE-DSS-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A4 */ - "DH-DSS-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A5 */ - "DH-DSS-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_GCM_SHA256, /* 0x00A6 */ - "ADH-AES128-GCM-SHA256", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_GCM_SHA384, /* 0x00A7 */ - "ADH-AES256-GCM-SHA384", - CIPHER_WEAK_ANON_AUTH), -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* RFC 5487 - PSK with SHA-256/384 and AES GCM */ - CIPHER_DEF(TLS_PSK_WITH_AES_128_GCM_SHA256, /* 0x00A8 */ - "PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_GCM_SHA384, /* 0x00A9 */ - "PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AA */ - "DHE-PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AB */ - "DHE-PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AC */ - "RSA-PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AD */ - "RSA-PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA256, /* 0x00AE */ - "PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA384, /* 0x00AF */ - "PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA256, /* 0x00B0 */ - "PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA384, /* 0x00B1 */ - "PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B2 */ - "DHE-PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B3 */ - "DHE-PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA256, /* 0x00B4 */ - "DHE-PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA384, /* 0x00B5 */ - "DHE-PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B6 */ - "RSA-PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B7 */ - "RSA-PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA256, /* 0x00B8 */ - "RSA-PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA384, /* 0x00B9 */ - "RSA-PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - - /* RFC 5746 - Secure Renegotiation. This is not a real suite, - it is a response to initiate negotiation again */ - CIPHER_DEF(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, /* 0x00FF */ - NULL, - CIPHER_STRONG_ENOUGH), - -#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 - /* TLS 1.3 standard cipher suites for ChaCha20+Poly1305. - Note: TLS 1.3 ciphersuites do not specify the key exchange - algorithm -- they only specify the symmetric ciphers. - Cipher alias name matches to OpenSSL cipher name, and for - TLS 1.3 ciphers */ - CIPHER_DEF(TLS_AES_128_GCM_SHA256, /* 0x1301 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_256_GCM_SHA384, /* 0x1302 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_128_CCM_SHA256, /* 0x1304 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_128_CCM_8_SHA256, /* 0x1305 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ +static const uint16_t default_ciphers[] = { + TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ + TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ #if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS - /* ECDSA addenda, RFC 4492 */ - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_NULL_SHA, /* 0xC001 */ - "ECDH-ECDSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_RC4_128_SHA, /* 0xC002 */ - "ECDH-ECDSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ - "ECDH-ECDSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ - "ECDH-ECDSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ - "ECDH-ECDSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_NULL_SHA, /* 0xC006 */ - "ECDHE-ECDSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, /* 0xC007 */ - "ECDHE-ECDSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ - "ECDHE-ECDSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ - "ECDHE-ECDSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ - "ECDHE-ECDSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_NULL_SHA, /* 0xC00B */ - "ECDH-RSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDH_RSA_WITH_RC4_128_SHA, /* 0xC00C */ - "ECDH-RSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ - "ECDH-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ - "ECDH-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ - "ECDH-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_NULL_SHA, /* 0xC010 */ - "ECDHE-RSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_RC4_128_SHA, /* 0xC011 */ - "ECDHE-RSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ - "ECDHE-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ - "ECDHE-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ - "ECDHE-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_anon_WITH_NULL_SHA, /* 0xC015 */ - "AECDH-NULL-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_RC4_128_SHA, /* 0xC016 */ - "AECDH-RC4-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, /* 0xC017 */ - "AECDH-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_anon_WITH_AES_128_CBC_SHA, /* 0xC018 */ - "AECDH-AES128-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_AES_256_CBC_SHA, /* 0xC019 */ - "AECDH-AES256-SHA", - CIPHER_WEAK_ANON_AUTH), + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ #endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with - HMAC SHA-256/384. */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ - "ECDHE-ECDSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ - "ECDHE-ECDSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ - "ECDH-ECDSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ - "ECDH-ECDSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ - "ECDHE-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ - "ECDHE-RSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ - "ECDH-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ - "ECDH-RSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with - SHA-256/384 and AES Galois Counter Mode (GCM) */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ - "ECDHE-ECDSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ - "ECDHE-ECDSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ - "ECDH-ECDSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ - "ECDH-ECDSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ - "ECDHE-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ - "ECDHE-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ - "ECDH-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ - "ECDH-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), + TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */ + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */ + TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */ + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */ + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ -#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 - /* ECDHE_PSK Cipher Suites for Transport Layer Security (TLS), RFC 5489 */ - CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, /* 0xC035 */ - "ECDHE-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, /* 0xC036 */ - "ECDHE-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ - #if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 - /* Addenda from rfc 7905 ChaCha20-Poly1305 Cipher Suites for - Transport Layer Security (TLS). */ - CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ - "ECDHE-RSA-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ - "ECDHE-ECDSA-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ + + /* TLSv1.3 is not supported by Secure Transport, but there is also other + * code referencing TLSv1.3, like: kTLSProtocol13 ? */ + TLS_AES_128_GCM_SHA256, /* 0x1301 */ + TLS_AES_256_GCM_SHA384, /* 0x1302 */ + TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */ #endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ - -#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 - /* ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS), - RFC 7905 */ - CIPHER_DEF(TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCAB */ - "PSK-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ - - /* Tags for SSL 2 cipher kinds which are not specified for SSL 3. - Defined since SDK 10.2.8 */ - CIPHER_DEF(SSL_RSA_WITH_RC2_CBC_MD5, /* 0xFF80 */ - NULL, - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_IDEA_CBC_MD5, /* 0xFF81 */ - NULL, - CIPHER_WEAK_IDEA_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_DES_CBC_MD5, /* 0xFF82 */ - NULL, - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_3DES_EDE_CBC_MD5, /* 0xFF83 */ - NULL, - CIPHER_WEAK_3DES_ENCRYPTION), }; -#define NUM_OF_CIPHERS sizeof(ciphertable)/sizeof(ciphertable[0]) - - /* pinned public key support tests */ /* version 1 supports macOS 10.12+ and iOS 10+ */ @@ -790,7 +219,7 @@ static const struct st_cipher ciphertable[] = { #define SECTRANSP_PINNEDPUBKEY_V1 1 #endif -/* version 2 supports MacOSX 10.7+ */ +/* version 2 supports macOS 10.7+ */ #if (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) #define SECTRANSP_PINNEDPUBKEY_V2 1 #endif @@ -814,7 +243,7 @@ static const unsigned char rsa2048SpkiHeader[] = { 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00}; #ifdef SECTRANSP_PINNEDPUBKEY_V1 -/* the *new* version doesn't return DER encoded ecdsa certs like the old... */ +/* the *new* version does not return DER encoded ecdsa certs like the old... */ static const unsigned char ecDsaSecp256r1SpkiHeader[] = { 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, @@ -830,13 +259,14 @@ static const unsigned char ecDsaSecp384r1SpkiHeader[] = { #endif /* SECTRANSP_PINNEDPUBKEY_V1 */ #endif /* SECTRANSP_PINNEDPUBKEY */ -static OSStatus bio_cf_in_read(SSLConnectionRef connection, - void *buf, - size_t *dataLength) /* IN/OUT */ +static OSStatus sectransp_bio_cf_in_read(SSLConnectionRef connection, + void *buf, + size_t *dataLength) /* IN/OUT */ { - struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; + const struct Curl_cfilter *cf = (const struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result; @@ -844,14 +274,14 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection, DEBUGASSERT(data); nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result); - DEBUGF(LOG_CF(data, cf, "bio_read(len=%zu) -> %zd, result=%d", - *dataLength, nread, result)); + CURL_TRC_CF(data, cf, "bio_read(len=%zu) -> %zd, result=%d", + *dataLength, nread, result); if(nread < 0) { switch(result) { case CURLE_OK: case CURLE_AGAIN: rtn = errSSLWouldBlock; - backend->ssl_direction = false; + backend->ssl_direction = FALSE; break; default: rtn = ioErr; @@ -859,6 +289,9 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection, } nread = 0; } + else if(nread == 0) { + rtn = errSSLClosedGraceful; + } else if((size_t)nread < *dataLength) { rtn = errSSLWouldBlock; } @@ -866,26 +299,28 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection, return rtn; } -static OSStatus bio_cf_out_write(SSLConnectionRef connection, - const void *buf, - size_t *dataLength) /* IN/OUT */ +static OSStatus sectransp_bio_cf_out_write(SSLConnectionRef connection, + const void *buf, + size_t *dataLength) /* IN/OUT */ { - struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; + const struct Curl_cfilter *cf = (const struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result; OSStatus rtn = noErr; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result); - DEBUGF(LOG_CF(data, cf, "bio_send(len=%zu) -> %zd, result=%d", - *dataLength, nwritten, result)); + nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, FALSE, + &result); + CURL_TRC_CF(data, cf, "bio_send(len=%zu) -> %zd, result=%d", + *dataLength, nwritten, result); if(nwritten <= 0) { if(result == CURLE_AGAIN) { rtn = errSSLWouldBlock; - backend->ssl_direction = true; + backend->ssl_direction = TRUE; } else { rtn = ioErr; @@ -899,80 +334,57 @@ static OSStatus bio_cf_out_write(SSLConnectionRef connection, return rtn; } -#ifndef CURL_DISABLE_VERBOSE_STRINGS -CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher) -{ - /* The first ciphers in the ciphertable are continuous. Here we do small - optimization and instead of loop directly get SSL name by cipher number. - */ - size_t i; - if(cipher <= SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) { - return ciphertable[cipher].name; - } - /* Iterate through the rest of the ciphers */ - for(i = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA + 1; i < NUM_OF_CIPHERS; - ++i) { - if(ciphertable[i].num == cipher) { - return ciphertable[i].name; - } - } - return ciphertable[SSL_NULL_WITH_NULL_NULL].name; -} -#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ - #if CURL_BUILD_MAC CF_INLINE void GetDarwinVersionNumber(int *major, int *minor) { int mib[2]; - char *os_version; size_t os_version_len; - char *os_version_major, *os_version_minor; - char *tok_buf; + char buf[256]; /* Get the Darwin kernel version from the kernel using sysctl(): */ mib[0] = CTL_KERN; mib[1] = KERN_OSRELEASE; if(sysctl(mib, 2, NULL, &os_version_len, NULL, 0) == -1) return; - os_version = malloc(os_version_len*sizeof(char)); - if(!os_version) - return; - if(sysctl(mib, 2, os_version, &os_version_len, NULL, 0) == -1) { - free(os_version); - return; + if(os_version_len < sizeof(buf)) { + if(sysctl(mib, 2, buf, &os_version_len, NULL, 0) != -1) { + const char *os = buf; + curl_off_t fnum; + curl_off_t snum; + /* Parse the version: */ + if(!curlx_str_number(&os, &fnum, INT_MAX) && + !curlx_str_single(&os, '.') && + !curlx_str_number(&os, &snum, INT_MAX)) { + *major = (int)fnum; + *minor = (int)snum; + } + } } - - /* Parse the version: */ - os_version_major = strtok_r(os_version, ".", &tok_buf); - os_version_minor = strtok_r(NULL, ".", &tok_buf); - *major = atoi(os_version_major); - *minor = atoi(os_version_minor); - free(os_version); } #endif /* CURL_BUILD_MAC */ /* Apple provides a myriad of ways of getting information about a certificate - into a string. Some aren't available under iOS or newer cats. So here's - a unified function for getting a string describing the certificate that - ought to work in all cats starting with Leopard. */ + into a string. Some are not available under iOS or newer cats. Here's a + unified function for getting a string describing the certificate that ought + to work in all cats starting with Leopard. */ CF_INLINE CFStringRef getsubject(SecCertificateRef cert) { CFStringRef server_cert_summary = CFSTR("(null)"); #if CURL_BUILD_IOS - /* iOS: There's only one way to do this. */ + /* iOS: There is only one way to do this. */ server_cert_summary = SecCertificateCopySubjectSummary(cert); #else #if CURL_BUILD_MAC_10_7 /* Lion & later: Get the long description if we can. */ - if(SecCertificateCopyLongDescription) + if(&SecCertificateCopyLongDescription) server_cert_summary = SecCertificateCopyLongDescription(NULL, cert, NULL); else #endif /* CURL_BUILD_MAC_10_7 */ #if CURL_BUILD_MAC_10_6 /* Snow Leopard: Get the certificate summary. */ - if(SecCertificateCopySubjectSummary) + if(&SecCertificateCopySubjectSummary) server_cert_summary = SecCertificateCopySubjectSummary(cert); else #endif /* CURL_BUILD_MAC_10_6 */ @@ -1008,9 +420,9 @@ static CURLcode CopyCertSubject(struct Curl_easy *data, } else { size_t cbuf_size = ((size_t)CFStringGetLength(c) * 4) + 1; - cbuf = calloc(cbuf_size, 1); + cbuf = calloc(1, cbuf_size); if(cbuf) { - if(!CFStringGetCString(c, cbuf, cbuf_size, + if(!CFStringGetCString(c, cbuf, (CFIndex)cbuf_size, kCFStringEncodingUTF8)) { failf(data, "SSL: invalid CA certificate subject"); result = CURLE_PEER_FAILED_VERIFICATION; @@ -1020,7 +432,7 @@ static CURLcode CopyCertSubject(struct Curl_easy *data, *certp = cbuf; } else { - failf(data, "SSL: couldn't allocate %zu bytes of memory", cbuf_size); + failf(data, "SSL: could not allocate %zu bytes of memory", cbuf_size); result = CURLE_OUT_OF_MEMORY; } } @@ -1032,7 +444,7 @@ static CURLcode CopyCertSubject(struct Curl_easy *data, #if CURL_SUPPORT_MAC_10_6 /* The SecKeychainSearch API was deprecated in Lion, and using it will raise - deprecation warnings, so let's not compile this unless it's necessary: */ + deprecation warnings, so let's not compile this unless it is necessary: */ static OSStatus CopyIdentityWithLabelOldSchool(char *label, SecIdentityRef *out_c_a_k) { @@ -1081,12 +493,11 @@ static OSStatus CopyIdentityWithLabel(char *label, CFArrayRef keys_list; CFIndex keys_list_count; CFIndex i; - CFStringRef common_name; /* SecItemCopyMatching() was introduced in iOS and Snow Leopard. kSecClassIdentity was introduced in Lion. If both exist, let's use them to find the certificate. */ - if(SecItemCopyMatching && kSecClassIdentity) { + if(&SecItemCopyMatching && kSecClassIdentity) { CFTypeRef keys[5]; CFTypeRef values[5]; CFDictionaryRef query_dict; @@ -1102,9 +513,9 @@ static OSStatus CopyIdentityWithLabel(char *label, * label matching below worked correctly */ keys[2] = kSecMatchLimit; /* identity searches need a SecPolicyRef in order to work */ - values[3] = SecPolicyCreateSSL(false, NULL); + values[3] = SecPolicyCreateSSL(FALSE, NULL); keys[3] = kSecMatchPolicy; - /* match the name of the certificate (doesn't work in macOS 10.12.1) */ + /* match the name of the certificate (does not work in macOS 10.12.1) */ values[4] = label_cf; keys[4] = kSecAttrLabel; query_dict = CFDictionaryCreate(NULL, (const void **)keys, @@ -1116,19 +527,20 @@ static OSStatus CopyIdentityWithLabel(char *label, /* Do we have a match? */ status = SecItemCopyMatching(query_dict, (CFTypeRef *) &keys_list); - /* Because kSecAttrLabel matching doesn't work with kSecClassIdentity, + /* Because kSecAttrLabel matching does not work with kSecClassIdentity, * we need to find the correct identity ourselves */ if(status == noErr) { keys_list_count = CFArrayGetCount(keys_list); *out_cert_and_key = NULL; status = 1; - for(i = 0; idata, blob->len); + (const unsigned char *)blob->data, + (CFIndex)blob->len); status = (pkcs_data != NULL) ? errSecSuccess : errSecAllocate; resource_imported = (pkcs_data != NULL); } @@ -1196,7 +610,7 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath, pkcs_url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)cPath, - strlen(cPath), false); + (CFIndex)strlen(cPath), FALSE); resource_imported = CFURLCreateDataAndPropertiesFromResource(NULL, pkcs_url, &pkcs_data, @@ -1225,7 +639,7 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath, /* On macOS SecPKCS12Import will always add the client certificate to * the Keychain. * - * As this doesn't match iOS, and apps may not want to see their client + * As this does not match iOS, and apps may not want to see their client * certificate saved in the user's keychain, we use SecItemImport * with a NULL keychain to avoid importing it. * @@ -1252,22 +666,22 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath, count = CFArrayGetCount(items); for(i = 0; i < count; i++) { - CFTypeRef item = (CFTypeRef) CFArrayGetValueAtIndex(items, i); - CFTypeID itemID = CFGetTypeID(item); + const CFTypeRef item = CFArrayGetValueAtIndex(items, i); + CFTypeID itemID = CFGetTypeID(item); if(itemID == CFDictionaryGetTypeID()) { - CFTypeRef identity = (CFTypeRef) CFDictionaryGetValue( - (CFDictionaryRef) item, - kSecImportItemIdentity); + const CFTypeRef identity = CFDictionaryGetValue( + (CFDictionaryRef)item, + kSecImportItemIdentity); CFRetain(identity); - *out_cert_and_key = (SecIdentityRef) identity; + *out_cert_and_key = (SecIdentityRef)CURL_UNCONST(identity); break; } #if CURL_BUILD_MAC_10_7 else if(itemID == SecCertificateGetTypeID()) { status = SecIdentityCreateWithCertificate(NULL, - (SecCertificateRef) item, - out_cert_and_key); + (SecCertificateRef)CURL_UNCONST(item), + out_cert_and_key); break; } #endif @@ -1288,7 +702,7 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath, /* This code was borrowed from nss.c, with some modifications: * Determine whether the nickname passed in is a filename that needs to - * be loaded as a PEM or a regular NSS nickname. + * be loaded as a PEM or a nickname. * * returns 1 for a file * returns 0 for not a file @@ -1298,342 +712,327 @@ CF_INLINE bool is_file(const char *filename) struct_stat st; if(!filename) - return false; + return FALSE; if(stat(filename, &st) == 0) return S_ISREG(st.st_mode); - return false; + return FALSE; } -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS -static CURLcode sectransp_version_from_curl(SSLProtocol *darwinver, - long ssl_version) +static CURLcode +sectransp_set_ssl_version_min_max(struct Curl_easy *data, + struct st_ssl_backend_data *backend, + struct ssl_primary_config *conn_config) { - switch(ssl_version) { +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + OSStatus err; + SSLProtocol ver_min; + SSLProtocol ver_max; + +#if CURL_SUPPORT_MAC_10_7 + if(!&SSLSetProtocolVersionMax) + goto legacy; +#endif + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: case CURL_SSLVERSION_TLSv1_0: - *darwinver = kTLSProtocol1; - return CURLE_OK; + ver_min = kTLSProtocol1; + break; case CURL_SSLVERSION_TLSv1_1: - *darwinver = kTLSProtocol11; - return CURLE_OK; + ver_min = kTLSProtocol11; + break; case CURL_SSLVERSION_TLSv1_2: - *darwinver = kTLSProtocol12; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - /* TLS 1.3 support first appeared in iOS 11 and macOS 10.13 */ -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - *darwinver = kTLSProtocol13; - return CURLE_OK; - } -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ + ver_min = kTLSProtocol12; break; + case CURL_SSLVERSION_TLSv1_3: + default: + failf(data, "SSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; } - return CURLE_SSL_CONNECT_ERROR; -} -#endif -static CURLcode set_ssl_version_min_max(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - long ssl_version = conn_config->version; - long ssl_version_max = conn_config->version_max; - long max_supported_version_by_os; - - DEBUGASSERT(backend); + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + case CURL_SSLVERSION_MAX_TLSv1_2: + ver_max = kTLSProtocol12; + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + ver_max = kTLSProtocol11; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + ver_max = kTLSProtocol1; + break; + default: + failf(data, "SSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } - /* macOS 10.5-10.7 supported TLS 1.0 only. - macOS 10.8 and later, and iOS 5 and later, added TLS 1.1 and 1.2. - macOS 10.13 and later, and iOS 11 and later, added TLS 1.3. */ -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_3; + err = SSLSetProtocolVersionMin(backend->ssl_ctx, ver_min); + if(err != noErr) { + failf(data, "SSL: failed to set minimum TLS version"); + return CURLE_SSL_CONNECT_ERROR; } - else { - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; + err = SSLSetProtocolVersionMax(backend->ssl_ctx, ver_max); + if(err != noErr) { + failf(data, "SSL: failed to set maximum TLS version"); + return CURLE_SSL_CONNECT_ERROR; } -#else - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ - switch(ssl_version) { + return CURLE_OK; +#endif +#if CURL_SUPPORT_MAC_10_7 + goto legacy; +legacy: + switch(conn_config->version) { case CURL_SSLVERSION_DEFAULT: case CURL_SSLVERSION_TLSv1: - ssl_version = CURL_SSLVERSION_TLSv1_0; - break; - } - - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = max_supported_version_by_os; + case CURL_SSLVERSION_TLSv1_0: break; + default: + failf(data, "SSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; } -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLSetProtocolVersionMax) { - SSLProtocol darwin_ver_min = kTLSProtocol1; - SSLProtocol darwin_ver_max = kTLSProtocol1; - CURLcode result = sectransp_version_from_curl(&darwin_ver_min, - ssl_version); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; - } - result = sectransp_version_from_curl(&darwin_ver_max, - ssl_version_max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; - } + /* only TLS 1.0 is supported, disable SSL 3.0 and SSL 2.0 */ + SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, FALSE); + SSLSetProtocolVersionEnabled(backend->ssl_ctx, kTLSProtocol1, TRUE); - (void)SSLSetProtocolVersionMin(backend->ssl_ctx, darwin_ver_min); - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, darwin_ver_max); - return result; - } - else { -#if CURL_SUPPORT_MAC_10_8 - long i = ssl_version; - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kSSLProtocolAll, - false); - for(; i <= (ssl_version_max >> 16); i++) { - switch(i) { - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - break; - case CURL_SSLVERSION_TLSv1_1: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol11, - true); - break; - case CURL_SSLVERSION_TLSv1_2: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "Your version of the OS does not support TLSv1.3"); - return CURLE_SSL_CONNECT_ERROR; - } - } - return CURLE_OK; -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - failf(data, "Secure Transport: cannot set SSL protocol"); - return CURLE_SSL_CONNECT_ERROR; + return CURLE_OK; +#endif } -static bool is_cipher_suite_strong(SSLCipherSuite suite_num) +static int sectransp_cipher_suite_get_str(uint16_t id, char *buf, + size_t buf_size, bool prefer_rfc) { - size_t i; - for(i = 0; i < NUM_OF_CIPHERS; ++i) { - if(ciphertable[i].num == suite_num) { - return !ciphertable[i].weak; - } - } - /* If the cipher is not in our list, assume it is a new one - and therefore strong. Previous implementation was the same, - if cipher suite is not in the list, it was considered strong enough */ - return true; + /* are these fortezza suites even supported ? */ + if(id == SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) + msnprintf(buf, buf_size, "%s", "SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA"); + else if(id == SSL_FORTEZZA_DMS_WITH_NULL_SHA) + msnprintf(buf, buf_size, "%s", "SSL_FORTEZZA_DMS_WITH_NULL_SHA"); + /* can TLS_EMPTY_RENEGOTIATION_INFO_SCSV even be set ? */ + else if(id == TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + msnprintf(buf, buf_size, "%s", "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"); + /* do we still need to support these SSL2-only ciphers ? */ + else if(id == SSL_RSA_WITH_RC2_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_RC2_CBC_MD5"); + else if(id == SSL_RSA_WITH_IDEA_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_IDEA_CBC_MD5"); + else if(id == SSL_RSA_WITH_DES_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_DES_CBC_MD5"); + else if(id == SSL_RSA_WITH_3DES_EDE_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_3DES_EDE_CBC_MD5"); + else + return Curl_cipher_suite_get_str(id, buf, buf_size, prefer_rfc); + return 0; } -static bool is_separator(char c) +static uint16_t sectransp_cipher_suite_walk_str(const char **str, + const char **end) { - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; + uint16_t id = Curl_cipher_suite_walk_str(str, end); + size_t len = *end - *str; + + if(!id) { + /* are these fortezza suites even supported ? */ + if(strncasecompare("SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA", *str, len)) + id = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA; + else if(strncasecompare("SSL_FORTEZZA_DMS_WITH_NULL_SHA", *str, len)) + id = SSL_FORTEZZA_DMS_WITH_NULL_SHA; + /* can TLS_EMPTY_RENEGOTIATION_INFO_SCSV even be set ? */ + else if(strncasecompare("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", *str, len)) + id = TLS_EMPTY_RENEGOTIATION_INFO_SCSV; + /* do we still need to support these SSL2-only ciphers ? */ + else if(strncasecompare("SSL_RSA_WITH_RC2_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_RC2_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_IDEA_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_IDEA_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_DES_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_DES_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_3DES_EDE_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_3DES_EDE_CBC_MD5; + } + return id; } -static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, - SSLContextRef ssl_ctx) +/* allocated memory must be freed */ +static SSLCipherSuite * sectransp_get_supported_ciphers(SSLContextRef ssl_ctx, + size_t *len) { - size_t all_ciphers_count = 0UL, allowed_ciphers_count = 0UL, i; - SSLCipherSuite *all_ciphers = NULL, *allowed_ciphers = NULL; + SSLCipherSuite *ciphers = NULL; OSStatus err = noErr; + *len = 0; -#if CURL_BUILD_MAC - int darwinver_maj = 0, darwinver_min = 0; + err = SSLGetNumberSupportedCiphers(ssl_ctx, len); + if(err != noErr) + goto failed; - GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); -#endif /* CURL_BUILD_MAC */ + ciphers = malloc(*len * sizeof(SSLCipherSuite)); + if(!ciphers) + goto failed; + + err = SSLGetSupportedCiphers(ssl_ctx, ciphers, len); + if(err != noErr) + goto failed; - /* Disable cipher suites that ST supports but are not safe. These ciphers - are unlikely to be used in any case since ST gives other ciphers a much - higher priority, but it's probably better that we not connect at all than - to give the user a false sense of security if the server only supports - insecure ciphers. (Note: We don't care about SSLv2-only ciphers.) */ - err = SSLGetNumberSupportedCiphers(ssl_ctx, &all_ciphers_count); - if(err != noErr) { - failf(data, "SSL: SSLGetNumberSupportedCiphers() failed: OSStatus %d", - err); - return CURLE_SSL_CIPHER; - } - all_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - if(!all_ciphers) { - failf(data, "SSL: Failed to allocate memory for all ciphers"); - return CURLE_OUT_OF_MEMORY; - } - allowed_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - if(!allowed_ciphers) { - Curl_safefree(all_ciphers); - failf(data, "SSL: Failed to allocate memory for allowed ciphers"); - return CURLE_OUT_OF_MEMORY; - } - err = SSLGetSupportedCiphers(ssl_ctx, all_ciphers, - &all_ciphers_count); - if(err != noErr) { - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); - return CURLE_SSL_CIPHER; - } - for(i = 0UL ; i < all_ciphers_count ; i++) { #if CURL_BUILD_MAC - /* There's a known bug in early versions of Mountain Lion where ST's ECC - ciphers (cipher suite 0xC001 through 0xC032) simply do not work. - Work around the problem here by disabling those ciphers if we are - running in an affected version of OS X. */ - if(darwinver_maj == 12 && darwinver_min <= 3 && - all_ciphers[i] >= 0xC001 && all_ciphers[i] <= 0xC032) { - continue; + { + int maj = 0, min = 0; + GetDarwinVersionNumber(&maj, &min); + /* There is a known bug in early versions of Mountain Lion where ST's ECC + ciphers (cipher suite 0xC001 through 0xC032) simply do not work. + Work around the problem here by disabling those ciphers if we are + running in an affected version of macOS. */ + if(maj == 12 && min <= 3) { + size_t i = 0, j = 0; + for(; i < *len; i++) { + if(ciphers[i] >= 0xC001 && ciphers[i] <= 0xC032) + continue; + ciphers[j++] = ciphers[i]; + } + *len = j; } -#endif /* CURL_BUILD_MAC */ - if(is_cipher_suite_strong(all_ciphers[i])) { - allowed_ciphers[allowed_ciphers_count++] = all_ciphers[i]; + } +#endif + + return ciphers; +failed: + *len = 0; + Curl_safefree(ciphers); + return NULL; +} + +static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, + SSLContextRef ssl_ctx) +{ + CURLcode ret = CURLE_SSL_CIPHER; + size_t count = 0, i, j; + OSStatus err; + size_t supported_len; + SSLCipherSuite *ciphers = NULL; + + ciphers = sectransp_get_supported_ciphers(ssl_ctx, &supported_len); + if(!ciphers) { + failf(data, "SSL: Failed to get supported ciphers"); + goto failed; + } + + /* Intersect the ciphers supported by Secure Transport with the default + * ciphers, using the order of the former. */ + for(i = 0; i < supported_len; i++) { + for(j = 0; j < CURL_ARRAYSIZE(default_ciphers); j++) { + if(default_ciphers[j] == ciphers[i]) { + ciphers[count++] = ciphers[i]; + break; + } } } - err = SSLSetEnabledCiphers(ssl_ctx, allowed_ciphers, - allowed_ciphers_count); - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); + + if(count == 0) { + failf(data, "SSL: no supported default ciphers"); + goto failed; + } + + err = SSLSetEnabledCiphers(ssl_ctx, ciphers, count); if(err != noErr) { failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); - return CURLE_SSL_CIPHER; + goto failed; } - return CURLE_OK; + + ret = CURLE_OK; +failed: + Curl_safefree(ciphers); + return ret; } static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, SSLContextRef ssl_ctx, const char *ciphers) { - size_t ciphers_count = 0; - const char *cipher_start = ciphers; - OSStatus err = noErr; - SSLCipherSuite selected_ciphers[NUM_OF_CIPHERS]; + CURLcode ret = CURLE_SSL_CIPHER; + size_t count = 0, i; + const char *ptr, *end; + OSStatus err; + size_t supported_len; + SSLCipherSuite *supported = NULL; + SSLCipherSuite *selected = NULL; - if(!ciphers) - return CURLE_OK; + supported = sectransp_get_supported_ciphers(ssl_ctx, &supported_len); + if(!supported) { + failf(data, "SSL: Failed to get supported ciphers"); + goto failed; + } - while(is_separator(*ciphers)) /* Skip initial separators. */ - ciphers++; - if(!*ciphers) - return CURLE_OK; + selected = malloc(supported_len * sizeof(SSLCipherSuite)); + if(!selected) { + failf(data, "SSL: Failed to allocate memory"); + goto failed; + } - cipher_start = ciphers; - while(*cipher_start && ciphers_count < NUM_OF_CIPHERS) { - bool cipher_found = FALSE; - size_t cipher_len = 0; - const char *cipher_end = NULL; - bool tls_name = FALSE; - size_t i; - - /* Skip separators */ - while(is_separator(*cipher_start)) - cipher_start++; - if(*cipher_start == '\0') { - break; - } - /* Find last position of a cipher in the ciphers string */ - cipher_end = cipher_start; - while(*cipher_end != '\0' && !is_separator(*cipher_end)) { - ++cipher_end; - } + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = sectransp_cipher_suite_walk_str(&ptr, &end); - /* IANA cipher names start with the TLS_ or SSL_ prefix. - If the 4th symbol of the cipher is '_' we look for a cipher in the - table by its (TLS) name. - Otherwise, we try to match cipher by an alias. */ - if(cipher_start[3] == '_') { - tls_name = TRUE; - } - /* Iterate through the cipher table and look for the cipher, starting - the cipher number 0x01 because the 0x00 is not the real cipher */ - cipher_len = cipher_end - cipher_start; - for(i = 1; i < NUM_OF_CIPHERS; ++i) { - const char *table_cipher_name = NULL; - if(tls_name) { - table_cipher_name = ciphertable[i].name; - } - else if(ciphertable[i].alias_name) { - table_cipher_name = ciphertable[i].alias_name; - } - else { - continue; - } - /* Compare a part of the string between separators with a cipher name - in the table and make sure we matched the whole cipher name */ - if(strncmp(cipher_start, table_cipher_name, cipher_len) == 0 - && table_cipher_name[cipher_len] == '\0') { - selected_ciphers[ciphers_count] = ciphertable[i].num; - ++ciphers_count; - cipher_found = TRUE; - break; - } - } - if(!cipher_found) { - /* It would be more human-readable if we print the wrong cipher name - but we don't want to allocate any additional memory and copy the name - into it, then add it into logs. - Also, we do not modify an original cipher list string. We just point - to positions where cipher starts and ends in the cipher list string. - The message is a bit cryptic and longer than necessary but can be - understood by humans. */ - failf(data, "SSL: cipher string \"%s\" contains unsupported cipher name" - " starting position %d and ending position %d", - ciphers, - cipher_start - ciphers, - cipher_end - ciphers); - return CURLE_SSL_CIPHER; + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len && supported[i] != id; i++); + if(i == supported_len) + id = 0; } - if(*cipher_end) { - cipher_start = cipher_end + 1; + if(!id) { + if(ptr[0] != '\0') + infof(data, "SSL: unknown cipher in list: \"%.*s\"", (int) (end - ptr), + ptr); + continue; } - else { - break; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "SSL: duplicate cipher in list: \"%.*s\"", (int) (end - ptr), + ptr); + continue; } + + selected[count++] = id; } - /* All cipher suites in the list are found. Report to logs as-is */ - infof(data, "SSL: Setting cipher suites list \"%s\"", ciphers); - err = SSLSetEnabledCiphers(ssl_ctx, selected_ciphers, ciphers_count); + if(count == 0) { + failf(data, "SSL: no supported cipher in list"); + goto failed; + } + + err = SSLSetEnabledCiphers(ssl_ctx, selected, count); if(err != noErr) { failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); - return CURLE_SSL_CIPHER; + goto failed; } - return CURLE_OK; + + ret = CURLE_OK; +failed: + Curl_safefree(supported); + Curl_safefree(selected); + return ret; +} + +static void sectransp_session_free(void *sessionid) +{ + /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a + cached session ID inside the Security framework. There is a private + function that does this, but I do not want to have to explain to you why I + got your application rejected from the App Store due to the use of a + private API, so the best we can do is free up our own char array that we + created way back in sectransp_connect_step1... */ + Curl_safefree(sessionid); } static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_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); const struct curl_blob *ssl_cablob = conn_config->ca_info_blob; @@ -1643,40 +1042,36 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, const bool verifypeer = conn_config->verifypeer; char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif /* ENABLE_IPV6 */ char *ciphers; OSStatus err = noErr; + CURLcode result; #if CURL_BUILD_MAC int darwinver_maj = 0, darwinver_min = 0; DEBUGASSERT(backend); - DEBUGF(LOG_CF(data, cf, "connect_step1")); + CURL_TRC_CF(data, cf, "connect_step1"); GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); #endif /* CURL_BUILD_MAC */ #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext) { /* use the newer API if available */ + if(&SSLCreateContext) { /* use the newer API if available */ if(backend->ssl_ctx) CFRelease(backend->ssl_ctx); backend->ssl_ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); if(!backend->ssl_ctx) { - failf(data, "SSL: couldn't create a context"); + failf(data, "SSL: could not create a context"); return CURLE_OUT_OF_MEMORY; } } else { - /* The old ST API does not exist under iOS, so don't compile it: */ + /* The old ST API does not exist under iOS, so do not compile it: */ #if CURL_SUPPORT_MAC_10_8 if(backend->ssl_ctx) (void)SSLDisposeContext(backend->ssl_ctx); - err = SSLNewContext(false, &(backend->ssl_ctx)); + err = SSLNewContext(FALSE, &(backend->ssl_ctx)); if(err != noErr) { - failf(data, "SSL: couldn't create a context: OSStatus %d", err); + failf(data, "SSL: could not create a context: OSStatus %d", err); return CURLE_OUT_OF_MEMORY; } #endif /* CURL_SUPPORT_MAC_10_8 */ @@ -1684,127 +1079,25 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, #else if(backend->ssl_ctx) (void)SSLDisposeContext(backend->ssl_ctx); - err = SSLNewContext(false, &(backend->ssl_ctx)); + err = SSLNewContext(FALSE, &(backend->ssl_ctx)); if(err != noErr) { - failf(data, "SSL: couldn't create a context: OSStatus %d", err); + failf(data, "SSL: could not create a context: OSStatus %d", err); return CURLE_OUT_OF_MEMORY; } #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ backend->ssl_write_buffered_length = 0UL; /* reset buffered write length */ - /* check to see if we've been told to use an explicit SSL/TLS version */ -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLSetProtocolVersionMax) { - switch(conn_config->version) { - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionMin(backend->ssl_ctx, kTLSProtocol1); -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol13); - } - else { - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); - } -#else - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(cf, data); - if(result != CURLE_OK) - return result; - break; - } - case CURL_SSLVERSION_SSLv3: - case CURL_SSLVERSION_SSLv2: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { -#if CURL_SUPPORT_MAC_10_8 - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kSSLProtocolAll, - false); - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol11, - true); - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(cf, data); - if(result != CURLE_OK) - return result; - break; - } - case CURL_SSLVERSION_SSLv3: - case CURL_SSLVERSION_SSLv2: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#else - if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) { - failf(data, "Your version of the OS does not support to set maximum" - " SSL/TLS version"); - return CURLE_SSL_CONNECT_ERROR; - } - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, false); - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - break; - case CURL_SSLVERSION_TLSv1_1: - failf(data, "Your version of the OS does not support TLSv1.1"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_2: - failf(data, "Your version of the OS does not support TLSv1.2"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "Your version of the OS does not support TLSv1.3"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv2: - case CURL_SSLVERSION_SSLv3: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + result = sectransp_set_ssl_version_min_max(data, backend, conn_config); + if(result != CURLE_OK) + return result; -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 if(connssl->alpn) { +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 +#ifdef HAVE_BUILTIN_AVAILABLE if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { +#else + if(&SSLSetALPNProtocols && &SSLCopyALPNProtocols) { +#endif struct alpn_proto_buf proto; size_t i; CFStringRef cstr; @@ -1826,8 +1119,8 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, Curl_alpn_to_proto_str(&proto, connssl->alpn); infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ } -#endif if(ssl_config->key) { infof(data, "WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure " @@ -1871,7 +1164,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, err = SecIdentityCopyCertificate(cert_and_key, &cert); if(err == noErr) { char *certp; - CURLcode result = CopyCertSubject(data, cert, &certp); + result = CopyCertSubject(data, cert, &certp); if(!result) { infof(data, "Client certificate: %s", certp); free(certp); @@ -1914,11 +1207,11 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, cert_showfilename_error); break; case errSecItemNotFound: - failf(data, "SSL: Can't find the certificate \"%s\" and its private " + failf(data, "SSL: cannot find the certificate \"%s\" and its private " "key in the Keychain.", cert_showfilename_error); break; default: - failf(data, "SSL: Can't load the certificate \"%s\" and its private " + failf(data, "SSL: cannot load the certificate \"%s\" and its private " "key: OSStatus %d", cert_showfilename_error, err); break; } @@ -1933,12 +1226,11 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, #if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS /* Snow Leopard introduced the SSLSetSessionOption() function, but due to a library bug with the way the kSSLSessionOptionBreakOnServerAuth flag - works, it doesn't work as expected under Snow Leopard, Lion or + works, it does not work as expected under Snow Leopard, Lion or Mountain Lion. So we need to call SSLSetEnableCertVerify() on those older cats in order to disable certificate validation if the user turned that off. - (SecureTransport will always validate the certificate chain by - default.) + (Secure Transport always validates the certificate chain by default.) Note: Darwin 11.x.x is Lion (10.7) Darwin 12.x.x is Mountain Lion (10.8) @@ -1947,9 +1239,9 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, Darwin 15.x.x is El Capitan (10.11) */ #if CURL_BUILD_MAC - if(SSLSetSessionOption && darwinver_maj >= 13) { + if(&SSLSetSessionOption && darwinver_maj >= 13) { #else - if(SSLSetSessionOption) { + if(&SSLSetSessionOption) { #endif /* CURL_BUILD_MAC */ bool break_on_auth = !conn_config->verifypeer || ssl_cafile || ssl_cablob; @@ -1964,7 +1256,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, else { #if CURL_SUPPORT_MAC_10_8 err = SSLSetEnableCertVerify(backend->ssl_ctx, - conn_config->verifypeer?true:false); + conn_config->verifypeer ? true : FALSE); if(err != noErr) { failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; @@ -1973,7 +1265,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, } #else err = SSLSetEnableCertVerify(backend->ssl_ctx, - conn_config->verifypeer?true:false); + conn_config->verifypeer ? true : FALSE); if(err != noErr) { failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; @@ -1985,7 +1277,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, bool is_cert_file = (!is_cert_data) && is_file(ssl_cafile); if(!(is_cert_file || is_cert_data)) { - failf(data, "SSL: can't load CA certificate file %s", + failf(data, "SSL: cannot load CA certificate file %s", ssl_cafile ? ssl_cafile : "(blob memory)"); return CURLE_SSL_CACERT_BADFILE; } @@ -1995,13 +1287,9 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, * Both hostname check and SNI require SSLSetPeerDomainName(). * Also: the verifyhost setting influences SNI usage */ if(conn_config->verifyhost) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, connssl->hostname, &snilen); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - err = SSLSetPeerDomainName(backend->ssl_ctx, snihost, snilen); + char *server = connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname; + err = SSLSetPeerDomainName(backend->ssl_ctx, server, strlen(server)); if(err != noErr) { failf(data, "SSL: SSLSetPeerDomainName() failed: OSStatus %d", @@ -2009,11 +1297,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - if((Curl_inet_pton(AF_INET, connssl->hostname, &addr)) - #ifdef ENABLE_IPV6 - || (Curl_inet_pton(AF_INET6, connssl->hostname, &addr)) - #endif - ) { + if(connssl->peer.type != CURL_SSL_PEER_DNS) { infof(data, "WARNING: using IP address, SNI is being disabled by " "the OS."); } @@ -2024,21 +1308,21 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, ciphers = conn_config->cipher_list; if(ciphers) { - err = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers); + result = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers); } else { - err = sectransp_set_default_ciphers(data, backend->ssl_ctx); + result = sectransp_set_default_ciphers(data, backend->ssl_ctx); } - if(err != noErr) { + if(result != CURLE_OK) { failf(data, "SSL: Unable to set ciphers for SSL/TLS handshake. " - "Error code: %d", err); + "Error code: %d", (int)result); return CURLE_SSL_CIPHER; } #if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 /* We want to enable 1/n-1 when using a CBC cipher unless the user - specifically doesn't want us doing that: */ - if(SSLSetSessionOption) { + specifically does not want us doing that: */ + if(&SSLSetSessionOption) { SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionSendOneByteRecord, !ssl_config->enable_beast); SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionFalseStart, @@ -2046,53 +1330,57 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, } #endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - /* Check if there's a cached ID we can/should use here! */ - if(ssl_config->primary.sessionid) { + /* Check if there is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { char *ssl_sessionid; size_t ssl_sessionid_len; - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, (void **)&ssl_sessionid, - &ssl_sessionid_len)) { + Curl_ssl_scache_lock(data); + ssl_sessionid = Curl_ssl_scache_get_obj(cf, data, + connssl->peer.scache_key); + if(ssl_sessionid) { /* we got a session id, use it! */ - err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); - Curl_ssl_sessionid_unlock(data); + err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, + strlen(ssl_sessionid)); + Curl_ssl_scache_unlock(data); if(err != noErr) { failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; } - /* Informational message */ - infof(data, "SSL re-using session ID"); + else + infof(data, "SSL reusing session ID"); } - /* If there isn't one, then let's make one up! This has to be done prior + /* If there is not one, then let's make one up! This has to be done prior to starting the handshake. */ else { - CURLcode result; ssl_sessionid = aprintf("%s:%d:%d:%s:%d", ssl_cafile ? ssl_cafile : "(blob memory)", - verifypeer, conn_config->verifyhost, connssl->hostname, - connssl->port); + verifypeer, conn_config->verifyhost, connssl->peer.hostname, + connssl->peer.port); ssl_sessionid_len = strlen(ssl_sessionid); err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); if(err != noErr) { - Curl_ssl_sessionid_unlock(data); + Curl_ssl_scache_unlock(data); failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; } - result = Curl_ssl_addsessionid(cf, data, ssl_sessionid, - ssl_sessionid_len, NULL); - Curl_ssl_sessionid_unlock(data); - if(result) { - failf(data, "failed to store ssl session"); + /* This is all a bit weird, as we have not handshaked yet. + * I hope this backend will go away soon. */ + result = Curl_ssl_scache_add_obj(cf, data, connssl->peer.scache_key, + (void *)ssl_sessionid, + sectransp_session_free); + Curl_ssl_scache_unlock(data); + if(result) return result; - } } } - err = SSLSetIOFuncs(backend->ssl_ctx, bio_cf_in_read, bio_cf_out_write); + err = SSLSetIOFuncs(backend->ssl_ctx, + sectransp_bio_cf_in_read, + sectransp_bio_cf_out_write); if(err != noErr) { failf(data, "SSL: SSLSetIOFuncs() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; @@ -2113,7 +1401,7 @@ static long pem_to_der(const char *in, unsigned char **out, size_t *outlen) char *sep_start, *sep_end, *cert_start, *cert_end; size_t i, j, err; size_t len; - unsigned char *b64; + char *b64; /* Jump through the separators at the beginning of the certificate. */ sep_start = strstr(in, "-----"); @@ -2147,7 +1435,7 @@ static long pem_to_der(const char *in, unsigned char **out, size_t *outlen) } b64[j] = '\0'; - err = Curl_base64_decode((const char *)b64, out, outlen); + err = curlx_base64_decode((const char *)b64, out, outlen); free(b64); if(err) { free(*out); @@ -2166,7 +1454,7 @@ static int read_cert(const char *file, unsigned char **out, size_t *outlen) unsigned char buf[512]; struct dynbuf certs; - Curl_dyn_init(&certs, MAX_CERTS_SIZE); + curlx_dyn_init(&certs, MAX_CERTS_SIZE); fd = open(file, 0); if(fd < 0) @@ -2178,32 +1466,32 @@ static int read_cert(const char *file, unsigned char **out, size_t *outlen) break; if(n < 0) { close(fd); - Curl_dyn_free(&certs); + curlx_dyn_free(&certs); return -1; } - if(Curl_dyn_addn(&certs, buf, n)) { + if(curlx_dyn_addn(&certs, buf, n)) { close(fd); return -1; } } close(fd); - *out = Curl_dyn_uptr(&certs); - *outlen = Curl_dyn_len(&certs); + *out = curlx_dyn_uptr(&certs); + *outlen = curlx_dyn_len(&certs); return 0; } -static int append_cert_to_array(struct Curl_easy *data, - const unsigned char *buf, size_t buflen, - CFMutableArrayRef array) +static CURLcode append_cert_to_array(struct Curl_easy *data, + const unsigned char *buf, size_t buflen, + CFMutableArrayRef array) { char *certp; CURLcode result; SecCertificateRef cacert; CFDataRef certdata; - certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen); + certdata = CFDataCreate(kCFAllocatorDefault, buf, (CFIndex)buflen); if(!certdata) { failf(data, "SSL: failed to allocate array for CA certificate"); return CURLE_OUT_OF_MEMORY; @@ -2222,9 +1510,11 @@ static int append_cert_to_array(struct Curl_easy *data, case CURLE_OK: break; case CURLE_PEER_FAILED_VERIFICATION: + CFRelease(cacert); return CURLE_SSL_CACERT_BADFILE; case CURLE_OUT_OF_MEMORY: default: + CFRelease(cacert); return result; } free(certp); @@ -2240,7 +1530,8 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, const unsigned char *certbuf, size_t buflen, SSLContextRef ctx) { - int n = 0, rc; + int n = 0; + CURLcode rc; long res; unsigned char *der; size_t derlen, offset = 0; @@ -2286,7 +1577,7 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, /* This is not a PEM file, probably a certificate in DER format. */ rc = append_cert_to_array(data, certbuf, buflen, array); if(rc != CURLE_OK) { - DEBUGF(LOG_CF(data, cf, "append_cert for CA failed")); + CURL_TRC_CF(data, cf, "append_cert for CA failed"); result = rc; goto out; } @@ -2300,7 +1591,7 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, rc = append_cert_to_array(data, der, derlen, array); free(der); if(rc != CURLE_OK) { - DEBUGF(LOG_CF(data, cf, "append_cert for CA failed")); + CURL_TRC_CF(data, cf, "append_cert for CA failed"); result = rc; goto out; } @@ -2316,13 +1607,13 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, goto out; } - DEBUGF(LOG_CF(data, cf, "setting %d trust anchors", n)); + CURL_TRC_CF(data, cf, "setting %d trust anchors", n); ret = SecTrustSetAnchorCertificates(trust, array); if(ret != noErr) { failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret); goto out; } - ret = SecTrustSetAnchorCertificatesOnly(trust, true); + ret = SecTrustSetAnchorCertificatesOnly(trust, TRUE); if(ret != noErr) { failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret); goto out; @@ -2338,11 +1629,11 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, switch(trust_eval) { case kSecTrustResultUnspecified: /* what does this really mean? */ - DEBUGF(LOG_CF(data, cf, "trust result: Unspecified")); + CURL_TRC_CF(data, cf, "trust result: Unspecified"); result = CURLE_OK; goto out; case kSecTrustResultProceed: - DEBUGF(LOG_CF(data, cf, "trust result: Proceed")); + CURL_TRC_CF(data, cf, "trust result: Proceed"); result = CURLE_OK; goto out; @@ -2370,32 +1661,30 @@ static CURLcode verify_cert(struct Curl_cfilter *cf, const struct curl_blob *ca_info_blob, SSLContextRef ctx) { - int result; + CURLcode result; unsigned char *certbuf; size_t buflen; + bool free_certbuf = FALSE; if(ca_info_blob) { - DEBUGF(LOG_CF(data, cf, "verify_peer, CA from config blob")); - certbuf = (unsigned char *)malloc(ca_info_blob->len + 1); - if(!certbuf) { - return CURLE_OUT_OF_MEMORY; - } + CURL_TRC_CF(data, cf, "verify_peer, CA from config blob"); + certbuf = ca_info_blob->data; buflen = ca_info_blob->len; - memcpy(certbuf, ca_info_blob->data, ca_info_blob->len); - certbuf[ca_info_blob->len]='\0'; } else if(cafile) { - DEBUGF(LOG_CF(data, cf, "verify_peer, CA from file '%s'", cafile)); + CURL_TRC_CF(data, cf, "verify_peer, CA from file '%s'", cafile); if(read_cert(cafile, &certbuf, &buflen) < 0) { failf(data, "SSL: failed to read or invalid CA certificate"); return CURLE_SSL_CACERT_BADFILE; } + free_certbuf = TRUE; } else return CURLE_SSL_CACERT_BADFILE; result = verify_cert_buf(cf, data, certbuf, buflen, ctx); - free(certbuf); + if(free_certbuf) + free(certbuf); return result; } @@ -2406,14 +1695,15 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, const char *pinnedpubkey) { /* Scratch */ size_t pubkeylen, realpubkeylen, spkiHeaderLength = 24; - unsigned char *pubkey = NULL, *realpubkey = NULL; + const unsigned char *pubkey = NULL; + unsigned char *realpubkey = NULL; const unsigned char *spkiHeader = NULL; CFDataRef publicKeyBits = NULL; /* Result is returned to caller */ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -2425,7 +1715,6 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, SecTrustRef trust; OSStatus ret; SecKeyRef keyRef; - OSStatus success; ret = SSLCopyPeerTrust(ctx, &trust); if(ret != noErr || !trust) @@ -2445,16 +1734,19 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, #elif SECTRANSP_PINNEDPUBKEY_V2 - success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, - &publicKeyBits); - CFRelease(keyRef); - if(success != errSecSuccess || !publicKeyBits) - break; + { + OSStatus success; + success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, + &publicKeyBits); + CFRelease(keyRef); + if(success != errSecSuccess || !publicKeyBits) + break; + } #endif /* SECTRANSP_PINNEDPUBKEY_V2 */ - pubkeylen = CFDataGetLength(publicKeyBits); - pubkey = (unsigned char *)CFDataGetBytePtr(publicKeyBits); + pubkeylen = (size_t)CFDataGetLength(publicKeyBits); + pubkey = (const unsigned char *)CFDataGetBytePtr(publicKeyBits); switch(pubkeylen) { case 526: @@ -2477,7 +1769,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, spkiHeaderLength = 23; break; default: - infof(data, "SSL: unhandled public key length: %d", pubkeylen); + infof(data, "SSL: unhandled public key length: %zu", pubkeylen); #elif SECTRANSP_PINNEDPUBKEY_V2 default: /* ecDSA secp256r1 pubkeylen == 91 header already included? @@ -2515,30 +1807,30 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); OSStatus err; SSLCipherSuite cipher; SSLProtocol protocol = 0; - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state); DEBUGASSERT(backend); - DEBUGF(LOG_CF(data, cf, "connect_step2")); + CURL_TRC_CF(data, cf, "connect_step2"); /* Here goes nothing: */ check_handshake: + connssl->io_need = CURL_SSL_IO_NEED_NONE; err = SSLHandshake(backend->ssl_ctx); if(err != noErr) { switch(err) { - case errSSLWouldBlock: /* they're not done with us yet */ - connssl->connecting_state = backend->ssl_direction ? - ssl_connect_2_writing : ssl_connect_2_reading; + case errSSLWouldBlock: /* they are not done with us yet */ + connssl->io_need = backend->ssl_direction ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; return CURLE_OK; - /* The below is errSSLServerAuthCompleted; it's not defined in + /* The below is errSSLServerAuthCompleted; it is not defined in Leopard's headers */ case -9841: if((conn_config->CAfile || conn_config->ca_info_blob) && @@ -2648,11 +1940,11 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, "authority"); break; - /* This error is raised if the server's cert didn't match the server's - host name: */ + /* This error is raised if the server's cert did not match the server's + hostname: */ case errSSLHostNameMismatch: failf(data, "SSL certificate peer verification failed, the " - "certificate did not match \"%s\"\n", connssl->dispname); + "certificate did not match \"%s\"\n", connssl->peer.dispname); return CURLE_PEER_FAILED_VERIFICATION; /* Problem with SSL / TLS negotiation */ @@ -2744,13 +2036,14 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, default: /* May also return codes listed in Security Framework Result Codes */ failf(data, "Unknown SSL protocol error in connection to %s:%d", - connssl->hostname, err); + connssl->peer.hostname, err); break; } return CURLE_SSL_CONNECT_ERROR; } else { - /* we have been connected fine, we're not waiting for anything else. */ + char cipher_str[64]; + /* we have been connected fine, we are not waiting for anything else. */ connssl->connecting_state = ssl_connect_3; #ifdef SECTRANSP_PINNEDPUBKEY @@ -2768,33 +2061,30 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, /* Informational message */ (void)SSLGetNegotiatedCipher(backend->ssl_ctx, &cipher); (void)SSLGetNegotiatedProtocolVersion(backend->ssl_ctx, &protocol); + + sectransp_cipher_suite_get_str((uint16_t) cipher, cipher_str, + sizeof(cipher_str), TRUE); switch(protocol) { case kSSLProtocol2: - infof(data, "SSL 2.0 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "SSL 2.0 connection using %s", cipher_str); break; case kSSLProtocol3: - infof(data, "SSL 3.0 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "SSL 3.0 connection using %s", cipher_str); break; case kTLSProtocol1: - infof(data, "TLS 1.0 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.0 connection using %s", cipher_str); break; #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS case kTLSProtocol11: - infof(data, "TLS 1.1 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.1 connection using %s", cipher_str); break; case kTLSProtocol12: - infof(data, "TLS 1.2 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.2 connection using %s", cipher_str); break; #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ #if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 case kTLSProtocol13: - infof(data, "TLS 1.3 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.3 connection using %s", cipher_str); break; #endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ default: @@ -2802,9 +2092,13 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, break; } -#if(CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 if(connssl->alpn) { +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 +#ifdef HAVE_BUILTIN_AVAILABLE if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { +#else + if(&SSLSetALPNProtocols && &SSLCopyALPNProtocols) { +#endif CFArrayRef alpnArr = NULL; CFStringRef chosenProtocol = NULL; err = SSLCopyALPNProtocols(backend->ssl_ctx, &alpnArr); @@ -2826,16 +2120,13 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, else infof(data, VTLS_INFOF_NO_ALPN); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - /* chosenProtocol is a reference to the string within alpnArr - and doesn't need to be freed separately */ + and does not need to be freed separately */ if(alpnArr) CFRelease(alpnArr); } +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ } -#endif return CURLE_OK; } @@ -2843,7 +2134,7 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, static CURLcode add_cert_to_certinfo(struct Curl_easy *data, - SecCertificateRef server_cert, + const SecCertificateRef server_cert, int idx) { CURLcode result = CURLE_OK; @@ -2863,7 +2154,7 @@ add_cert_to_certinfo(struct Curl_easy *data, static CURLcode collect_server_cert_single(struct Curl_cfilter *cf, struct Curl_easy *data, - SecCertificateRef server_cert, + const SecCertificateRef server_cert, CFIndex idx) { CURLcode result = CURLE_OK; @@ -2890,13 +2181,14 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, #ifndef CURL_DISABLE_VERBOSE_STRINGS const bool show_verbose_server_cert = data->set.verbose; #else - const bool show_verbose_server_cert = false; + const bool show_verbose_server_cert = FALSE; #endif struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); CURLcode result = ssl_config->certinfo ? CURLE_PEER_FAILED_VERIFICATION : CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; CFArrayRef server_certs = NULL; SecCertificateRef server_cert; OSStatus err; @@ -2931,10 +2223,10 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, /* SSLCopyPeerCertificates() is deprecated as of Mountain Lion. The function SecTrustGetCertificateAtIndex() is officially present in Lion, but it is unfortunately also present in Snow Leopard as - private API and doesn't work as expected. So we have to look for + private API and does not work as expected. So we have to look for a different symbol to make sure this code is only executed under Lion or later. */ - if(SecTrustCopyPublicKey) { + if(&SecTrustCopyPublicKey) { #pragma unused(server_certs) err = SSLCopyPeerTrust(backend->ssl_ctx, &trust); /* For some reason, SSLCopyPeerTrust() can return noErr and yet return @@ -2959,8 +2251,8 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, if(ssl_config->certinfo) result = Curl_ssl_init_certinfo(data, (int)count); for(i = 0L ; !result && (i < count) ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, - i); + const void *item = CFArrayGetValueAtIndex(server_certs, i); + server_cert = (SecCertificateRef)CURL_UNCONST(item); result = collect_server_cert_single(cf, data, server_cert, i); } CFRelease(server_certs); @@ -2976,7 +2268,8 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, if(ssl_config->certinfo) result = Curl_ssl_init_certinfo(data, (int)count); for(i = 0L ; !result && (i < count) ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, i); + const void *item = CFArrayGetValueAtIndex(server_certs, i); + server_cert = (SecCertificateRef)CURL_UNCONST(item); result = collect_server_cert_single(cf, data, server_cert, i); } CFRelease(server_certs); @@ -2991,7 +2284,7 @@ static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; CURLcode result; - DEBUGF(LOG_CF(data, cf, "connect_step3")); + CURL_TRC_CF(data, cf, "connect_step3"); /* There is no step 3! * Well, okay, let's collect server certificates, and if verbose mode is on, * let's print the details of the server certificates. */ @@ -3003,15 +2296,12 @@ static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf, return CURLE_OK; } -static CURLcode -sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode sectransp_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - int what; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -3019,79 +2309,20 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OK; } - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(ssl_connect_1 == connssl->connecting_state) { result = sectransp_connect_step1(cf, data); if(result) return result; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading || - connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking ? 0 : timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ + if(ssl_connect_2 == connssl->connecting_state) { result = sectransp_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(result) return result; - - } /* repeat step2 until all transactions are done. */ - + } if(ssl_connect_3 == connssl->connecting_state) { result = sectransp_connect_step3(cf, data); @@ -3100,56 +2331,114 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, } if(ssl_connect_done == connssl->connecting_state) { - DEBUGF(LOG_CF(data, cf, "connected")); + CURL_TRC_CF(data, cf, "connected"); connssl->state = ssl_connection_complete; *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; return CURLE_OK; } -static CURLcode sectransp_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return sectransp_connect_common(cf, data, TRUE, done); -} +static ssize_t sectransp_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, + size_t buffersize, + CURLcode *curlcode); -static CURLcode sectransp_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode sectransp_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { - CURLcode result; - bool done = FALSE; + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + ssize_t nread = 0; + char buf[1024]; + size_t i; - result = sectransp_connect_common(cf, data, FALSE, &done); + DEBUGASSERT(backend); + if(!backend->ssl_ctx || cf->shutdown) { + *done = TRUE; + goto out; + } - if(result) - return result; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; - DEBUGASSERT(done); + if(send_shutdown && !backend->sent_shutdown) { + OSStatus err; - return CURLE_OK; + CURL_TRC_CF(data, cf, "shutdown, send close notify"); + err = SSLClose(backend->ssl_ctx); + switch(err) { + case noErr: + backend->sent_shutdown = TRUE; + break; + case errSSLWouldBlock: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + default: + CURL_TRC_CF(data, cf, "shutdown, error: %d", (int)err); + result = CURLE_SEND_ERROR; + goto out; + } + } + + for(i = 0; i < 10; ++i) { + if(!backend->sent_shutdown) { + nread = sectransp_recv(cf, data, buf, (int)sizeof(buf), &result); + } + else { + /* We would like to read the close notify from the server using + * Secure Transport, however SSLRead() no longer works after we + * sent the notify from our side. So, we just read from the + * underlying filter and hope it will end. */ + nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result); + } + CURL_TRC_CF(data, cf, "shutdown read -> %zd, %d", nread, result); + if(nread <= 0) + break; + } + + if(nread > 0) { + /* still data coming in? */ + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + CURL_TRC_CF(data, cf, "shutdown done"); + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_OK; + } + else { + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown, error: %d", result); + } + +out: + cf->shutdown = (result || *done); + return result; } static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; (void) data; DEBUGASSERT(backend); if(backend->ssl_ctx) { - DEBUGF(LOG_CF(data, cf, "close")); - (void)SSLClose(backend->ssl_ctx); + CURL_TRC_CF(data, cf, "close"); #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext) + if(&SSLCreateContext) CFRelease(backend->ssl_ctx); #if CURL_SUPPORT_MAC_10_8 else @@ -3162,79 +2451,6 @@ static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data) } } -static int sectransp_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - ssize_t nread; - int what; - int rc; - char buf[120]; - int loop = 10; /* avoid getting stuck */ - CURLcode result; - - DEBUGASSERT(backend); - - if(!backend->ssl_ctx) - return 0; - -#ifndef CURL_DISABLE_FTP - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; -#endif - - sectransp_close(cf, data); - - rc = 0; - - what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), - SSL_SHUTDOWN_TIMEOUT); - - DEBUGF(LOG_CF(data, cf, "shutdown")); - while(loop--) { - if(what < 0) { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - rc = -1; - break; - } - - if(!what) { /* timeout */ - failf(data, "SSL shutdown timeout"); - break; - } - - /* Something to read, let's do it and hope that it is the close - notify alert from the server. No way to SSL_Read now, so use read(). */ - - nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result); - - if(nread < 0) { - failf(data, "read: %s", curl_easy_strerror(result)); - rc = -1; - } - - if(nread <= 0) - break; - - what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); - } - - return rc; -} - -static void sectransp_session_free(void *ptr) -{ - /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a - cached session ID inside the Security framework. There is a private - function that does this, but I don't want to have to explain to you why I - got your application rejected from the App Store due to the use of a - private API, so the best we can do is free up our own char array that we - created way back in sectransp_connect_step1... */ - Curl_safefree(ptr); -} - static size_t sectransp_version(char *buffer, size_t size) { return msnprintf(buffer, size, "SecureTransport"); @@ -3244,7 +2460,8 @@ static bool sectransp_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { const struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; OSStatus err; size_t buffer; @@ -3252,20 +2469,20 @@ static bool sectransp_data_pending(struct Curl_cfilter *cf, DEBUGASSERT(backend); if(backend->ssl_ctx) { /* SSL is in use */ - DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending")); + CURL_TRC_CF((struct Curl_easy *)CURL_UNCONST(data), cf, "data_pending"); err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer); if(err == noErr) return buffer > 0UL; - return false; + return FALSE; } else - return false; + return FALSE; } static CURLcode sectransp_random(struct Curl_easy *data UNUSED_PARAM, unsigned char *entropy, size_t length) { - /* arc4random_buf() isn't available on cats older than Lion, so let's + /* arc4random_buf() is not available on cats older than Lion, so let's do this manually for the benefit of the older cats. */ size_t i; u_int32_t random_number = 0; @@ -3287,6 +2504,7 @@ static CURLcode sectransp_sha256sum(const unsigned char *tmp, /* input */ unsigned char *sha256sum, /* output */ size_t sha256len) { + (void)sha256len; assert(sha256len >= CURL_SHA256_DIGEST_LENGTH); (void)CC_SHA256(tmp, (CC_LONG)tmplen, sha256sum); return CURLE_OK; @@ -3295,7 +2513,7 @@ static CURLcode sectransp_sha256sum(const unsigned char *tmp, /* input */ static bool sectransp_false_start(void) { #if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - if(SSLSetSessionOption) + if(&SSLSetSessionOption) return TRUE; #endif return FALSE; @@ -3308,7 +2526,8 @@ static ssize_t sectransp_send(struct Curl_cfilter *cf, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; size_t processed = 0UL; OSStatus err; @@ -3321,7 +2540,7 @@ static ssize_t sectransp_send(struct Curl_cfilter *cf, Now, one could interpret that as "written to the socket," but actually, it returns the amount of data that was written to a buffer internal to - the SSLContextRef instead. So it's possible for SSLWrite() to return + the SSLContextRef instead. So it is possible for SSLWrite() to return errSSLWouldBlock and a number of bytes "written" because those bytes were encrypted and written to a buffer, not to the socket. @@ -3334,7 +2553,7 @@ static ssize_t sectransp_send(struct Curl_cfilter *cf, err = SSLWrite(backend->ssl_ctx, NULL, 0UL, &processed); switch(err) { case noErr: - /* processed is always going to be 0 because we didn't write to + /* processed is always going to be 0 because we did not write to the buffer, so return how much was written to the socket */ processed = backend->ssl_write_buffered_length; backend->ssl_write_buffered_length = 0UL; @@ -3349,7 +2568,7 @@ static ssize_t sectransp_send(struct Curl_cfilter *cf, } } else { - /* We've got new data to write: */ + /* We have got new data to write: */ err = SSLWrite(backend->ssl_ctx, mem, len, &processed); if(err != noErr) { switch(err) { @@ -3376,7 +2595,8 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); size_t processed = 0UL; OSStatus err; @@ -3395,7 +2615,6 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, } *curlcode = CURLE_AGAIN; return -1L; - break; /* errSSLClosedGraceful - server gracefully shut down the SSL session errSSLClosedNoNotify - server hung up on us instead of sending a @@ -3405,9 +2624,8 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, case errSSLClosedNoNotify: *curlcode = CURLE_OK; return 0; - break; - /* The below is errSSLPeerAuthCompleted; it's not defined in + /* The below is errSSLPeerAuthCompleted; it is not defined in Leopard's headers */ case -9841: if((conn_config->CAfile || conn_config->ca_info_blob) && @@ -3425,7 +2643,6 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, failf(data, "SSLRead() return error %d", err); *curlcode = CURLE_RECV_ERROR; return -1L; - break; } } return (ssize_t)processed; @@ -3434,7 +2651,8 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, static void *sectransp_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) { - struct ssl_backend_data *backend = connssl->backend; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; (void)info; DEBUGASSERT(backend); return backend->ssl_ctx; @@ -3448,37 +2666,41 @@ const struct Curl_ssl Curl_ssl_sectransp = { #ifdef SECTRANSP_PINNEDPUBKEY SSLSUPP_PINNEDPUBKEY | #endif /* SECTRANSP_PINNEDPUBKEY */ - SSLSUPP_HTTPS_PROXY, + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, - sizeof(struct ssl_backend_data), + sizeof(struct st_ssl_backend_data), - Curl_none_init, /* init */ - Curl_none_cleanup, /* cleanup */ + NULL, /* init */ + NULL, /* cleanup */ sectransp_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ sectransp_shutdown, /* shutdown */ sectransp_data_pending, /* data_pending */ sectransp_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ + NULL, /* cert_status_request */ sectransp_connect, /* connect */ - sectransp_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ sectransp_get_internals, /* get_internals */ sectransp_close, /* close_one */ - Curl_none_close_all, /* close_all */ - sectransp_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ sectransp_false_start, /* false_start */ sectransp_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ sectransp_recv, /* recv decrypted data */ sectransp_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; +#if defined(__GNUC__) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + #ifdef __clang__ #pragma clang diagnostic pop #endif diff --git a/Utilities/cmcurl/lib/vtls/sectransp.h b/Utilities/cmcurl/lib/vtls/sectransp.h index 0f1085ad91b..c82dc184456 100644 --- a/Utilities/cmcurl/lib/vtls/sectransp.h +++ b/Utilities/cmcurl/lib/vtls/sectransp.h @@ -24,7 +24,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SECTRANSP diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c index a4ff7d61a61..c6fb60d0a4f 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.c +++ b/Utilities/cmcurl/lib/vtls/vtls.c @@ -38,7 +38,7 @@ https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef HAVE_SYS_TYPES_H #include @@ -50,35 +50,46 @@ #include #endif -#include "urldata.h" -#include "cfilters.h" +#include "../urldata.h" +#include "../cfilters.h" #include "vtls.h" /* generic SSL protos etc */ #include "vtls_int.h" -#include "slist.h" -#include "sendf.h" -#include "strcase.h" -#include "url.h" -#include "progress.h" -#include "share.h" -#include "multiif.h" -#include "timeval.h" -#include "curl_md5.h" -#include "warnless.h" -#include "curl_base64.h" -#include "curl_printf.h" -#include "strdup.h" +#include "vtls_scache.h" + +#include "openssl.h" /* OpenSSL versions */ +#include "gtls.h" /* GnuTLS versions */ +#include "wolfssl.h" /* wolfSSL versions */ +#include "schannel.h" /* Schannel SSPI version */ +#include "sectransp.h" /* Secure Transport (Darwin) version */ +#include "mbedtls.h" /* mbedTLS versions */ +#include "bearssl.h" /* BearSSL versions */ +#include "rustls.h" /* Rustls versions */ + +#include "../slist.h" +#include "../sendf.h" +#include "../strcase.h" +#include "../url.h" +#include "../progress.h" +#include "../share.h" +#include "../multiif.h" +#include "../curlx/timeval.h" +#include "../curl_md5.h" +#include "../curl_sha256.h" +#include "../curlx/warnless.h" +#include "../curlx/base64.h" +#include "../curl_printf.h" +#include "../curlx/inet_pton.h" +#include "../connect.h" +#include "../select.h" +#include "../strdup.h" +#include "../rand.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" -/* convenience macro to check if this handle is using a shared SSL session */ -#define SSLSESSION_SHARED(data) (data->share && \ - (data->share->specifier & \ - (1<var) { \ @@ -102,7 +113,7 @@ static CURLcode blobdup(struct curl_blob **dest, DEBUGASSERT(dest); DEBUGASSERT(!*dest); if(src) { - /* only if there's data to dupe! */ + /* only if there is data to dupe! */ struct curl_blob *d; d = malloc(sizeof(struct curl_blob) + src->len); if(!d) @@ -131,74 +142,111 @@ static bool blobcmp(struct curl_blob *first, struct curl_blob *second) } #ifdef USE_SSL -static const struct alpn_spec ALPN_SPEC_H10 = { - { ALPN_HTTP_1_0 }, 1 -}; static const struct alpn_spec ALPN_SPEC_H11 = { { ALPN_HTTP_1_1 }, 1 }; #ifdef USE_HTTP2 +static const struct alpn_spec ALPN_SPEC_H2 = { + { ALPN_H2 }, 1 +}; static const struct alpn_spec ALPN_SPEC_H2_H11 = { { ALPN_H2, ALPN_HTTP_1_1 }, 2 }; #endif -static const struct alpn_spec *alpn_get_spec(int httpwant, bool use_alpn) +static const struct alpn_spec * +alpn_get_spec(http_majors allowed, bool use_alpn) { if(!use_alpn) return NULL; - if(httpwant == CURL_HTTP_VERSION_1_0) - return &ALPN_SPEC_H10; #ifdef USE_HTTP2 - if(httpwant >= CURL_HTTP_VERSION_2) - return &ALPN_SPEC_H2_H11; + if(allowed & CURL_HTTP_V2x) { + if(allowed & CURL_HTTP_V1x) + return &ALPN_SPEC_H2_H11; + return &ALPN_SPEC_H2; + } +#else + (void)allowed; #endif + /* Use the ALPN protocol "http/1.1" for HTTP/1.x. + Avoid "http/1.0" because some servers do not support it. */ return &ALPN_SPEC_H11; } #endif /* USE_SSL */ -bool -Curl_ssl_config_matches(struct ssl_primary_config *data, - struct ssl_primary_config *needle) -{ - if((data->version == needle->version) && - (data->version_max == needle->version_max) && - (data->ssl_options == needle->ssl_options) && - (data->verifypeer == needle->verifypeer) && - (data->verifyhost == needle->verifyhost) && - (data->verifystatus == needle->verifystatus) && - blobcmp(data->cert_blob, needle->cert_blob) && - blobcmp(data->ca_info_blob, needle->ca_info_blob) && - blobcmp(data->issuercert_blob, needle->issuercert_blob) && - Curl_safecmp(data->CApath, needle->CApath) && - Curl_safecmp(data->CAfile, needle->CAfile) && - Curl_safecmp(data->issuercert, needle->issuercert) && - Curl_safecmp(data->clientcert, needle->clientcert) && +void Curl_ssl_easy_config_init(struct Curl_easy *data) +{ + /* + * libcurl 7.10 introduced SSL verification *by default*! This needs to be + * switched off unless wanted. + */ + data->set.ssl.primary.verifypeer = TRUE; + data->set.ssl.primary.verifyhost = TRUE; + data->set.ssl.primary.cache_session = TRUE; /* caching by default */ +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl = data->set.ssl; +#endif +} + +static bool +match_ssl_primary_config(struct Curl_easy *data, + struct ssl_primary_config *c1, + struct ssl_primary_config *c2) +{ + (void)data; + if((c1->version == c2->version) && + (c1->version_max == c2->version_max) && + (c1->ssl_options == c2->ssl_options) && + (c1->verifypeer == c2->verifypeer) && + (c1->verifyhost == c2->verifyhost) && + (c1->verifystatus == c2->verifystatus) && + blobcmp(c1->cert_blob, c2->cert_blob) && + blobcmp(c1->ca_info_blob, c2->ca_info_blob) && + blobcmp(c1->issuercert_blob, c2->issuercert_blob) && + Curl_safecmp(c1->CApath, c2->CApath) && + Curl_safecmp(c1->CAfile, c2->CAfile) && + Curl_safecmp(c1->issuercert, c2->issuercert) && + Curl_safecmp(c1->clientcert, c2->clientcert) && #ifdef USE_TLS_SRP - !Curl_timestrcmp(data->username, needle->username) && - !Curl_timestrcmp(data->password, needle->password) && + !Curl_timestrcmp(c1->username, c2->username) && + !Curl_timestrcmp(c1->password, c2->password) && #endif - strcasecompare(data->cipher_list, needle->cipher_list) && - strcasecompare(data->cipher_list13, needle->cipher_list13) && - strcasecompare(data->curves, needle->curves) && - strcasecompare(data->CRLfile, needle->CRLfile) && - strcasecompare(data->pinned_key, needle->pinned_key)) + strcasecompare(c1->cipher_list, c2->cipher_list) && + strcasecompare(c1->cipher_list13, c2->cipher_list13) && + strcasecompare(c1->curves, c2->curves) && + strcasecompare(c1->signature_algorithms, c2->signature_algorithms) && + strcasecompare(c1->CRLfile, c2->CRLfile) && + strcasecompare(c1->pinned_key, c2->pinned_key)) return TRUE; return FALSE; } -bool -Curl_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest) +bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct connectdata *candidate, + bool proxy) +{ +#ifndef CURL_DISABLE_PROXY + if(proxy) + return match_ssl_primary_config(data, &data->set.proxy_ssl.primary, + &candidate->proxy_ssl_config); +#else + (void)proxy; +#endif + return match_ssl_primary_config(data, &data->set.ssl.primary, + &candidate->ssl_config); +} + +static bool clone_ssl_primary_config(struct ssl_primary_config *source, + struct ssl_primary_config *dest) { dest->version = source->version; dest->version_max = source->version_max; dest->verifypeer = source->verifypeer; dest->verifyhost = source->verifyhost; dest->verifystatus = source->verifystatus; - dest->sessionid = source->sessionid; + dest->cache_session = source->cache_session; dest->ssl_options = source->ssl_options; CLONE_BLOB(cert_blob); @@ -212,6 +260,7 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source, CLONE_STRING(cipher_list13); CLONE_STRING(pinned_key); CLONE_STRING(curves); + CLONE_STRING(signature_algorithms); CLONE_STRING(CRLfile); #ifdef USE_TLS_SRP CLONE_STRING(username); @@ -221,7 +270,7 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source, return TRUE; } -void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) +static void free_primary_ssl_config(struct ssl_primary_config *sslc) { Curl_safefree(sslc->CApath); Curl_safefree(sslc->CAfile); @@ -234,6 +283,7 @@ void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) Curl_safefree(sslc->ca_info_blob); Curl_safefree(sslc->issuercert_blob); Curl_safefree(sslc->curves); + Curl_safefree(sslc->signature_algorithms); Curl_safefree(sslc->CRLfile); #ifdef USE_TLS_SRP Curl_safefree(sslc->username); @@ -241,6 +291,113 @@ void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) #endif } +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) +{ + data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH]; + data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE]; + data->set.ssl.primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; + data->set.ssl.primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; + data->set.ssl.primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; + data->set.ssl.primary.cipher_list = + data->set.str[STRING_SSL_CIPHER_LIST]; + data->set.ssl.primary.cipher_list13 = + data->set.str[STRING_SSL_CIPHER13_LIST]; + data->set.ssl.primary.signature_algorithms = + data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS]; + data->set.ssl.primary.pinned_key = + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT]; + data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; + data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES]; +#ifdef USE_TLS_SRP + data->set.ssl.primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; + data->set.ssl.primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; +#endif + data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE]; + data->set.ssl.key = data->set.str[STRING_KEY]; + data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE]; + data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD]; + data->set.ssl.primary.clientcert = data->set.str[STRING_CERT]; + data->set.ssl.key_blob = data->set.blobs[BLOB_KEY]; + +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; + data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; + data->set.proxy_ssl.primary.cipher_list = + data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; + data->set.proxy_ssl.primary.cipher_list13 = + data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; + data->set.proxy_ssl.primary.pinned_key = + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; + data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; + data->set.proxy_ssl.primary.ca_info_blob = + data->set.blobs[BLOB_CAINFO_PROXY]; + data->set.proxy_ssl.primary.issuercert = + data->set.str[STRING_SSL_ISSUERCERT_PROXY]; + data->set.proxy_ssl.primary.issuercert_blob = + data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; + data->set.proxy_ssl.primary.CRLfile = + data->set.str[STRING_SSL_CRLFILE_PROXY]; + data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; + data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY]; + data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; + data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; + data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY]; + data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY]; +#ifdef USE_TLS_SRP + data->set.proxy_ssl.primary.username = + data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; + data->set.proxy_ssl.primary.password = + data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; +#endif +#endif /* CURL_DISABLE_PROXY */ + + return CURLE_OK; +} + +CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Clone "primary" SSL configurations from the esay 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)) + return CURLE_OUT_OF_MEMORY; +#ifndef CURL_DISABLE_PROXY + if(!clone_ssl_primary_config(&data->set.proxy_ssl.primary, + &conn->proxy_ssl_config)) + return CURLE_OUT_OF_MEMORY; +#endif + return CURLE_OK; +} + +void Curl_ssl_conn_config_cleanup(struct connectdata *conn) +{ + free_primary_ssl_config(&conn->ssl_config); +#ifndef CURL_DISABLE_PROXY + free_primary_ssl_config(&conn->proxy_ssl_config); +#endif +} + +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; +#ifndef CURL_DISABLE_PROXY + src = for_proxy ? &data->set.proxy_ssl.primary : &data->set.ssl.primary; + dest = for_proxy ? &data->conn->proxy_ssl_config : &data->conn->ssl_config; +#else + (void)for_proxy; + src = &data->set.ssl.primary; + dest = &data->conn->ssl_config; +#endif + dest->verifyhost = src->verifyhost; + dest->verifypeer = src->verifypeer; + dest->verifystatus = src->verifystatus; + } +} + #ifdef USE_SSL static int multissl_setup(const struct Curl_ssl *backend); #endif @@ -273,24 +430,9 @@ int Curl_ssl_init(void) return 1; init_ssl = TRUE; /* never again */ - return Curl_ssl->init(); -} - -#if defined(CURL_WITH_MULTI_SSL) -static const struct Curl_ssl Curl_ssl_multi; -#endif - -/* Global cleanup */ -void Curl_ssl_cleanup(void) -{ - if(init_ssl) { - /* only cleanup if we did a previous init */ - Curl_ssl->cleanup(); -#if defined(CURL_WITH_MULTI_SSL) - Curl_ssl = &Curl_ssl_multi; -#endif - init_ssl = FALSE; - } + if(Curl_ssl->init) + return Curl_ssl->init(); + return 1; } static bool ssl_prefs_check(struct Curl_easy *data) @@ -318,7 +460,7 @@ static bool ssl_prefs_check(struct Curl_easy *data) } static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, - const struct alpn_spec *alpn) + const struct alpn_spec *alpn) { struct ssl_connect_data *ctx; @@ -327,8 +469,10 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, if(!ctx) return NULL; + ctx->ssl_impl = Curl_ssl; ctx->alpn = alpn; - ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data); + Curl_bufq_init2(&ctx->earlydata, CURL_SSL_EARLY_MAX, 1, BUFQ_OPT_NO_SPARES); + ctx->backend = calloc(1, ctx->ssl_impl->sizeof_ssl_backend_data); if(!ctx->backend) { free(ctx); return NULL; @@ -339,358 +483,73 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, static void cf_ctx_free(struct ssl_connect_data *ctx) { if(ctx) { + Curl_safefree(ctx->negotiated.alpn); + Curl_bufq_free(&ctx->earlydata); free(ctx->backend); free(ctx); } } -static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data) +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding) { - struct ssl_connect_data *connssl = cf->ctx; - CURLcode result; - - if(!ssl_prefs_check(data)) - return CURLE_SSL_CONNECT_ERROR; - - /* mark this is being ssl-enabled from here on. */ - connssl->state = ssl_connection_negotiating; - - result = Curl_ssl->connect_blocking(cf, data); - - if(!result) { - DEBUGASSERT(connssl->state == ssl_connection_complete); - } - - return result; -} - -static CURLcode -ssl_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data, - bool *done) -{ - if(!ssl_prefs_check(data)) - return CURLE_SSL_CONNECT_ERROR; - - /* mark this is being ssl requested from here on. */ - return Curl_ssl->connect_nonblocking(cf, data, done); -} - -/* - * Lock shared SSL session data - */ -void Curl_ssl_sessionid_lock(struct Curl_easy *data) -{ - if(SSLSESSION_SHARED(data)) - Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); -} - -/* - * Unlock shared SSL session data - */ -void Curl_ssl_sessionid_unlock(struct Curl_easy *data) -{ - if(SSLSESSION_SHARED(data)) - Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); -} - -/* - * Check if there's a session ID for the given connection in the cache, and if - * there's one suitable, it is provided. Returns TRUE when no entry matched. - */ -bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, - struct Curl_easy *data, - void **ssl_sessionid, - size_t *idsize) /* set 0 if unknown */ -{ - 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 Curl_ssl_session *check; - size_t i; - long *general_age; - bool no_match = TRUE; - - *ssl_sessionid = NULL; - if(!ssl_config) - return TRUE; - - DEBUGASSERT(ssl_config->primary.sessionid); - - if(!ssl_config->primary.sessionid || !data->state.session) - /* session ID re-use is disabled or the session cache has not been - setup */ - return TRUE; - - /* Lock if shared */ - if(SSLSESSION_SHARED(data)) - general_age = &data->share->sessionage; - else - general_age = &data->state.sessionage; - - for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { - check = &data->state.session[i]; - if(!check->sessionid) - /* not session ID means blank entry */ - continue; - if(strcasecompare(connssl->hostname, check->name) && - ((!cf->conn->bits.conn_to_host && !check->conn_to_host) || - (cf->conn->bits.conn_to_host && check->conn_to_host && - strcasecompare(cf->conn->conn_to_host.name, check->conn_to_host))) && - ((!cf->conn->bits.conn_to_port && check->conn_to_port == -1) || - (cf->conn->bits.conn_to_port && check->conn_to_port != -1 && - cf->conn->conn_to_port == check->conn_to_port)) && - (connssl->port == check->remote_port) && - strcasecompare(cf->conn->handler->scheme, check->scheme) && - Curl_ssl_config_matches(conn_config, &check->ssl_config)) { - /* yes, we have a session ID! */ - (*general_age)++; /* increase general age */ - check->age = *general_age; /* set this as used in this age */ - *ssl_sessionid = check->sessionid; - if(idsize) - *idsize = check->idsize; - no_match = FALSE; - break; - } - } - - DEBUGF(infof(data, DMSG(data, "%s Session ID in cache for %s %s://%s:%d"), - no_match? "Didn't find": "Found", - Curl_ssl_cf_is_proxy(cf) ? "proxy" : "host", - cf->conn->handler->scheme, connssl->hostname, connssl->port)); - return no_match; -} - -/* - * Kill a single session ID entry in the cache. - */ -void Curl_ssl_kill_session(struct Curl_ssl_session *session) -{ - if(session->sessionid) { - /* defensive check */ - - /* free the ID the SSL-layer specific way */ - Curl_ssl->session_free(session->sessionid); - - session->sessionid = NULL; - session->age = 0; /* fresh */ - - Curl_free_primary_ssl_config(&session->ssl_config); - - Curl_safefree(session->name); - Curl_safefree(session->conn_to_host); - } -} - -/* - * Delete the given session ID from the cache. - */ -void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid) -{ - size_t i; - - for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { - struct Curl_ssl_session *check = &data->state.session[i]; - - if(check->sessionid == ssl_sessionid) { - Curl_ssl_kill_session(check); - break; - } - } -} - -/* - * Store session id in the session cache. The ID passed on to this function - * must already have been extracted and allocated the proper way for the SSL - * layer. Curl_XXXX_session_free() will be called to free/kill the session ID - * later on. - */ -CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, - struct Curl_easy *data, - void *ssl_sessionid, - size_t idsize, - bool *added) -{ - struct ssl_connect_data *connssl = cf->ctx; - 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); - size_t i; - struct Curl_ssl_session *store; - long oldest_age; - char *clone_host; - char *clone_conn_to_host; - int conn_to_port; - long *general_age; - - if(added) - *added = FALSE; - - if(!data->state.session) - return CURLE_OK; - - store = &data->state.session[0]; - oldest_age = data->state.session[0].age; /* zero if unused */ - (void)ssl_config; - DEBUGASSERT(ssl_config->primary.sessionid); - - clone_host = strdup(connssl->hostname); - if(!clone_host) - return CURLE_OUT_OF_MEMORY; /* bail out */ - - if(cf->conn->bits.conn_to_host) { - clone_conn_to_host = strdup(cf->conn->conn_to_host.name); - if(!clone_conn_to_host) { - free(clone_host); - return CURLE_OUT_OF_MEMORY; /* bail out */ - } - } - else - clone_conn_to_host = NULL; - - if(cf->conn->bits.conn_to_port) - conn_to_port = cf->conn->conn_to_port; - else - conn_to_port = -1; - - /* Now we should add the session ID and the host name to the cache, (remove - the oldest if necessary) */ - - /* If using shared SSL session, lock! */ - if(SSLSESSION_SHARED(data)) { - general_age = &data->share->sessionage; - } - else { - general_age = &data->state.sessionage; - } - - /* find an empty slot for us, or find the oldest */ - for(i = 1; (i < data->set.general_ssl.max_ssl_sessions) && - data->state.session[i].sessionid; i++) { - if(data->state.session[i].age < oldest_age) { - oldest_age = data->state.session[i].age; - store = &data->state.session[i]; - } - } - if(i == data->set.general_ssl.max_ssl_sessions) - /* cache is full, we must "kill" the oldest entry! */ - Curl_ssl_kill_session(store); - else - store = &data->state.session[i]; /* use this slot */ - - /* now init the session struct wisely */ - store->sessionid = ssl_sessionid; - store->idsize = idsize; - store->age = *general_age; /* set current age */ - /* free it if there's one already present */ - free(store->name); - free(store->conn_to_host); - store->name = clone_host; /* clone host name */ - store->conn_to_host = clone_conn_to_host; /* clone connect to host name */ - store->conn_to_port = conn_to_port; /* connect to port number */ - /* port number */ - store->remote_port = connssl->port; - store->scheme = cf->conn->handler->scheme; - - if(!Curl_clone_primary_ssl_config(conn_config, &store->ssl_config)) { - Curl_free_primary_ssl_config(&store->ssl_config); - store->sessionid = NULL; /* let caller free sessionid */ - free(clone_host); - free(clone_conn_to_host); - return CURLE_OUT_OF_MEMORY; - } - - if(added) - *added = TRUE; - - DEBUGF(infof(data, DMSG(data, "Added Session ID to cache for %s://%s:%d" - " [%s]"), store->scheme, store->name, store->remote_port, - Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server")); + if(Curl_ssl->get_channel_binding) + return Curl_ssl->get_channel_binding(data, sockindex, binding); return CURLE_OK; } -void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend) -{ - if(Curl_ssl->free_multi_ssl_backend_data && mbackend) - Curl_ssl->free_multi_ssl_backend_data(mbackend); -} - void Curl_ssl_close_all(struct Curl_easy *data) { - /* kill the session ID cache if not shared */ - if(data->state.session && !SSLSESSION_SHARED(data)) { - size_t i; - for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) - /* the single-killer function handles empty table slots */ - Curl_ssl_kill_session(&data->state.session[i]); - - /* free the cache data */ - Curl_safefree(data->state.session); - } - - Curl_ssl->close_all(data); + if(Curl_ssl->close_all) + Curl_ssl->close_all(data); } -int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) +void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps) { struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); - if(sock != CURL_SOCKET_BAD) { - if(connssl->connecting_state == ssl_connect_2_writing) { - /* write mode */ - socks[0] = sock; - return GETSOCK_WRITESOCK(0); - } - if(connssl->connecting_state == ssl_connect_2_reading) { - /* read mode */ - socks[0] = sock; - return GETSOCK_READSOCK(0); + if(connssl->io_need) { + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); + if(sock != CURL_SOCKET_BAD) { + if(connssl->io_need & CURL_SSL_IO_NEED_SEND) { + Curl_pollset_set_out_only(data, ps, sock); + CURL_TRC_CF(data, cf, "adjust_pollset, POLLOUT fd=%" FMT_SOCKET_T, + sock); + } + else { + Curl_pollset_set_in_only(data, ps, sock); + CURL_TRC_CF(data, cf, "adjust_pollset, POLLIN fd=%" FMT_SOCKET_T, + sock); + } } } - return GETSOCK_BLANK; } /* Selects an SSL crypto engine */ CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine) { - return Curl_ssl->set_engine(data, engine); + if(Curl_ssl->set_engine) + return Curl_ssl->set_engine(data, engine); + return CURLE_NOT_BUILT_IN; } /* Selects the default SSL crypto engine */ CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data) { - return Curl_ssl->set_engine_default(data); + if(Curl_ssl->set_engine_default) + return Curl_ssl->set_engine_default(data); + return CURLE_NOT_BUILT_IN; } /* Return list of OpenSSL crypto engine names. */ struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data) { - return Curl_ssl->engines_list(data); -} - -/* - * This sets up a session ID cache to the specified size. Make sure this code - * is agnostic to what underlying SSL technology we use. - */ -CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount) -{ - struct Curl_ssl_session *session; - - if(data->state.session) - /* this is just a precaution to prevent multiple inits */ - return CURLE_OK; - - session = calloc(amount, sizeof(struct Curl_ssl_session)); - if(!session) - return CURLE_OUT_OF_MEMORY; - - /* store the info in the SSL section */ - data->set.general_ssl.max_ssl_sessions = amount; - data->state.session = session; - data->state.sessionage = 1; /* this is brand new */ - return CURLE_OK; + if(Curl_ssl->engines_list) + return Curl_ssl->engines_list(data); + return NULL; } static size_t multissl_version(char *buffer, size_t size); @@ -711,7 +570,7 @@ void Curl_ssl_free_certinfo(struct Curl_easy *data) if(ci->num_of_certs) { /* free all individual lists used */ int i; - for(i = 0; inum_of_certs; i++) { + for(i = 0; i < ci->num_of_certs; i++) { curl_slist_free_all(ci->certinfo[i]); ci->certinfo[i] = NULL; } @@ -751,28 +610,23 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, size_t valuelen) { struct curl_certinfo *ci = &data->info.certs; - char *output; struct curl_slist *nl; CURLcode result = CURLE_OK; - size_t labellen = strlen(label); - size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */ - - output = malloc(outlen); - if(!output) - return CURLE_OUT_OF_MEMORY; + struct dynbuf build; - /* sprintf the label and colon */ - msnprintf(output, outlen, "%s:", label); + DEBUGASSERT(certnum < ci->num_of_certs); - /* memcpy the value (it might not be null-terminated) */ - memcpy(&output[labellen + 1], value, valuelen); + curlx_dyn_init(&build, CURL_X509_STR_MAX); - /* null-terminate the output */ - output[labellen + 1 + valuelen] = 0; + if(curlx_dyn_add(&build, label) || + curlx_dyn_addn(&build, ":", 1) || + curlx_dyn_addn(&build, value, valuelen)) + return CURLE_OUT_OF_MEMORY; - nl = Curl_slist_append_nodup(ci->certinfo[certnum], output); + nl = Curl_slist_append_nodup(ci->certinfo[certnum], + curlx_dyn_ptr(&build)); if(!nl) { - free(output); + curlx_dyn_free(&build); curl_slist_free_all(ci->certinfo[certnum]); result = CURLE_OUT_OF_MEMORY; } @@ -781,37 +635,16 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, return result; } +/* get length bytes of randomness */ CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *entropy, size_t length) { - return Curl_ssl->random(data, entropy, length); -} - -/* - * Curl_ssl_snihost() converts the input host name to a suitable SNI name put - * in data->state.buffer. Returns a pointer to the name (or NULL if a problem) - * and stores the new length in 'olen'. - * - * SNI fields must not have any trailing dot and while RFC 6066 section 3 says - * the SNI field is case insensitive, browsers always send the data lowercase - * and subsequently there are numerous servers out there that don't work - * unless the name is lowercased. - */ - -char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen) -{ - size_t len = strlen(host); - if(len && (host[len-1] == '.')) - len--; - if(len >= data->set.buffer_size) - return NULL; - - Curl_strntolower(data->state.buffer, host, len); - data->state.buffer[len] = 0; - if(olen) - *olen = len; - return data->state.buffer; + DEBUGASSERT(length == sizeof(int)); + if(Curl_ssl->random) + return Curl_ssl->random(data, entropy, length); + else + return CURLE_NOT_BUILT_IN; } /* @@ -821,14 +654,17 @@ char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen) static CURLcode pubkey_pem_to_der(const char *pem, unsigned char **der, size_t *der_len) { - char *stripped_pem, *begin_pos, *end_pos; - size_t pem_count, stripped_pem_count = 0, pem_len; + char *begin_pos, *end_pos; + size_t pem_count, pem_len; CURLcode result; + struct dynbuf pbuf; /* if no pem, exit. */ if(!pem) return CURLE_BAD_CONTENT_ENCODING; + curlx_dyn_init(&pbuf, MAX_PINNED_PUBKEY_SIZE); + begin_pos = strstr(pem, "-----BEGIN PUBLIC KEY-----"); if(!begin_pos) return CURLE_BAD_CONTENT_ENCODING; @@ -848,26 +684,26 @@ static CURLcode pubkey_pem_to_der(const char *pem, pem_len = end_pos - pem; - stripped_pem = malloc(pem_len - pem_count + 1); - if(!stripped_pem) - return CURLE_OUT_OF_MEMORY; - /* * Here we loop through the pem array one character at a time between the * correct indices, and place each character that is not '\n' or '\r' * into the stripped_pem array, which should represent the raw base64 string */ while(pem_count < pem_len) { - if('\n' != pem[pem_count] && '\r' != pem[pem_count]) - stripped_pem[stripped_pem_count++] = pem[pem_count]; + if('\n' != pem[pem_count] && '\r' != pem[pem_count]) { + result = curlx_dyn_addn(&pbuf, &pem[pem_count], 1); + if(result) + return result; + } ++pem_count; } - /* Place the null terminator in the correct place */ - stripped_pem[stripped_pem_count] = '\0'; - - result = Curl_base64_decode(stripped_pem, der, der_len); - Curl_safefree(stripped_pem); + if(curlx_dyn_len(&pbuf)) { + result = curlx_base64_decode(curlx_dyn_ptr(&pbuf), der, der_len); + curlx_dyn_free(&pbuf); + } + else + result = CURLE_BAD_CONTENT_ENCODING; return result; } @@ -880,21 +716,22 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, const char *pinnedpubkey, const unsigned char *pubkey, size_t pubkeylen) { - FILE *fp; - unsigned char *buf = NULL, *pem_ptr = NULL; CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; if(!pubkey || !pubkeylen) return result; /* only do this if pinnedpubkey starts with "sha256//", length 8 */ - if(strncmp(pinnedpubkey, "sha256//", 8) == 0) { + if(!strncmp(pinnedpubkey, "sha256//", 8)) { CURLcode encode; - size_t encodedlen, pinkeylen; - char *encoded, *pinkeycopy, *begin_pos, *end_pos; + size_t encodedlen = 0; + char *encoded = NULL, *pinkeycopy, *begin_pos, *end_pos; unsigned char *sha256sumdigest; if(!Curl_ssl->sha256sum) { @@ -907,14 +744,12 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, if(!sha256sumdigest) return CURLE_OUT_OF_MEMORY; encode = Curl_ssl->sha256sum(pubkey, pubkeylen, - sha256sumdigest, CURL_SHA256_DIGEST_LENGTH); - - if(encode != CURLE_OK) - return encode; + sha256sumdigest, CURL_SHA256_DIGEST_LENGTH); - encode = Curl_base64_encode((char *)sha256sumdigest, - CURL_SHA256_DIGEST_LENGTH, &encoded, - &encodedlen); + if(!encode) + encode = curlx_base64_encode((char *)sha256sumdigest, + CURL_SHA256_DIGEST_LENGTH, &encoded, + &encodedlen); Curl_safefree(sha256sumdigest); if(encode) @@ -923,20 +758,18 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, infof(data, " public key hash: sha256//%s", encoded); /* it starts with sha256//, copy so we can modify it */ - pinkeylen = strlen(pinnedpubkey) + 1; - pinkeycopy = malloc(pinkeylen); + pinkeycopy = strdup(pinnedpubkey); if(!pinkeycopy) { Curl_safefree(encoded); return CURLE_OUT_OF_MEMORY; } - memcpy(pinkeycopy, pinnedpubkey, pinkeylen); /* point begin_pos to the copy, and start extracting keys */ begin_pos = pinkeycopy; do { end_pos = strstr(begin_pos, ";sha256//"); /* - * if there is an end_pos, null terminate, - * otherwise it'll go to the end of the original string + * if there is an end_pos, null-terminate, otherwise it will go to the + * end of the original string */ if(end_pos) end_pos[0] = '\0'; @@ -959,75 +792,78 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, } while(end_pos && begin_pos); Curl_safefree(encoded); Curl_safefree(pinkeycopy); - return result; } - - fp = fopen(pinnedpubkey, "rb"); - if(!fp) - return result; - - do { + else { long filesize; size_t size, pem_len; CURLcode pem_read; + struct dynbuf buf; + char unsigned *pem_ptr = NULL; + size_t left; + FILE *fp = fopen(pinnedpubkey, "rb"); + if(!fp) + return result; + + curlx_dyn_init(&buf, MAX_PINNED_PUBKEY_SIZE); /* Determine the file's size */ if(fseek(fp, 0, SEEK_END)) - break; + goto end; filesize = ftell(fp); if(fseek(fp, 0, SEEK_SET)) - break; + goto end; if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE) - break; + goto end; /* * if the size of our certificate is bigger than the file - * size then it can't match + * size then it cannot match */ size = curlx_sotouz((curl_off_t) filesize); if(pubkeylen > size) - break; + goto end; /* - * Allocate buffer for the pinned key - * With 1 additional byte for null terminator in case of PEM key + * Read the file into the dynbuf */ - buf = malloc(size + 1); - if(!buf) - break; - - /* Returns number of elements read, which should be 1 */ - if((int) fread(buf, size, 1, fp) != 1) - break; - - /* If the sizes are the same, it can't be base64 encoded, must be der */ + left = size; + do { + char buffer[1024]; + size_t want = left > sizeof(buffer) ? sizeof(buffer) : left; + if(want != fread(buffer, 1, want, fp)) + goto end; + if(curlx_dyn_addn(&buf, buffer, want)) + goto end; + left -= want; + } while(left); + + /* If the sizes are the same, it cannot be base64 encoded, must be der */ if(pubkeylen == size) { - if(!memcmp(pubkey, buf, pubkeylen)) + if(!memcmp(pubkey, curlx_dyn_ptr(&buf), pubkeylen)) result = CURLE_OK; - break; + goto end; } /* - * Otherwise we will assume it's PEM and try to decode it - * after placing null terminator + * Otherwise we will assume it is PEM and try to decode it after placing + * null-terminator */ - buf[size] = '\0'; - pem_read = pubkey_pem_to_der((const char *)buf, &pem_ptr, &pem_len); - /* if it wasn't read successfully, exit */ + pem_read = pubkey_pem_to_der(curlx_dyn_ptr(&buf), &pem_ptr, &pem_len); + /* if it was not read successfully, exit */ if(pem_read) - break; + goto end; /* - * if the size of our certificate doesn't match the size of - * the decoded file, they can't be the same, otherwise compare + * if the size of our certificate does not match the size of + * the decoded file, they cannot be the same, otherwise compare */ if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen)) result = CURLE_OK; - } while(0); - - Curl_safefree(buf); - Curl_safefree(pem_ptr); - fclose(fp); +end: + curlx_dyn_free(&buf); + Curl_safefree(pem_ptr); + fclose(fp); + } return result; } @@ -1037,100 +873,18 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, */ bool Curl_ssl_cert_status_request(void) { - return Curl_ssl->cert_status_request(); + if(Curl_ssl->cert_status_request) + return Curl_ssl->cert_status_request(); + return FALSE; } /* * Check whether the SSL backend supports false start. */ -bool Curl_ssl_false_start(struct Curl_easy *data) -{ - (void)data; - return Curl_ssl->false_start(); -} - -/* - * Default implementations for unsupported functions. - */ - -int Curl_none_init(void) -{ - return 1; -} - -void Curl_none_cleanup(void) -{ } - -int Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM, - struct Curl_easy *data UNUSED_PARAM) -{ - (void)data; - (void)cf; - return 0; -} - -int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - (void)cf; - (void)data; - return -1; -} - -CURLcode Curl_none_random(struct Curl_easy *data UNUSED_PARAM, - unsigned char *entropy UNUSED_PARAM, - size_t length UNUSED_PARAM) -{ - (void)data; - (void)entropy; - (void)length; - return CURLE_NOT_BUILT_IN; -} - -void Curl_none_close_all(struct Curl_easy *data UNUSED_PARAM) -{ - (void)data; -} - -void Curl_none_session_free(void *ptr UNUSED_PARAM) -{ - (void)ptr; -} - -bool Curl_none_data_pending(struct Curl_cfilter *cf UNUSED_PARAM, - const struct Curl_easy *data UNUSED_PARAM) -{ - (void)cf; - (void)data; - return 0; -} - -bool Curl_none_cert_status_request(void) -{ - return FALSE; -} - -CURLcode Curl_none_set_engine(struct Curl_easy *data UNUSED_PARAM, - const char *engine UNUSED_PARAM) -{ - (void)data; - (void)engine; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_none_set_engine_default(struct Curl_easy *data UNUSED_PARAM) -{ - (void)data; - return CURLE_NOT_BUILT_IN; -} - -struct curl_slist *Curl_none_engines_list(struct Curl_easy *data UNUSED_PARAM) -{ - (void)data; - return (struct curl_slist *)NULL; -} - -bool Curl_none_false_start(void) +bool Curl_ssl_false_start(void) { + if(Curl_ssl->false_start) + return Curl_ssl->false_start(); return FALSE; } @@ -1138,33 +892,26 @@ static int multissl_init(void) { if(multissl_setup(NULL)) return 1; - return Curl_ssl->init(); + if(Curl_ssl->init) + return Curl_ssl->init(); + return 1; } static CURLcode multissl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - if(multissl_setup(NULL)) - return CURLE_FAILED_INIT; - return Curl_ssl->connect_blocking(cf, data); -} - -static CURLcode multissl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) + struct Curl_easy *data, bool *done) { if(multissl_setup(NULL)) return CURLE_FAILED_INIT; - return Curl_ssl->connect_nonblocking(cf, data, done); + return Curl_ssl->do_connect(cf, data, done); } -static int multissl_get_select_socks(struct Curl_cfilter *cf, +static void multissl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { if(multissl_setup(NULL)) - return 0; - return Curl_ssl->get_select_socks(cf, data, socks); + return; + Curl_ssl->adjust_pollset(cf, data, ps); } static void *multissl_get_internals(struct ssl_connect_data *connssl, @@ -1207,30 +954,25 @@ static const struct Curl_ssl Curl_ssl_multi = { (size_t)-1, /* something insanely large to be on the safe side */ multissl_init, /* init */ - Curl_none_cleanup, /* cleanup */ + NULL, /* cleanup */ multissl_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ - Curl_none_data_pending, /* data_pending */ - Curl_none_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ + NULL, /* shutdown */ + NULL, /* data_pending */ + NULL, /* random */ + NULL, /* cert_status_request */ multissl_connect, /* connect */ - multissl_connect_nonblocking, /* connect_nonblocking */ - multissl_get_select_socks, /* getsock */ + multissl_adjust_pollset, /* adjust_pollset */ multissl_get_internals, /* get_internals */ multissl_close, /* close_one */ - Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ + NULL, /* false_start */ NULL, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ multissl_recv_plain, /* recv decrypted data */ multissl_send_plain, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; const struct Curl_ssl *Curl_ssl = @@ -1238,20 +980,16 @@ const struct Curl_ssl *Curl_ssl = &Curl_ssl_multi; #elif defined(USE_WOLFSSL) &Curl_ssl_wolfssl; -#elif defined(USE_SECTRANSP) - &Curl_ssl_sectransp; #elif defined(USE_GNUTLS) &Curl_ssl_gnutls; -#elif defined(USE_GSKIT) - &Curl_ssl_gskit; #elif defined(USE_MBEDTLS) &Curl_ssl_mbedtls; -#elif defined(USE_NSS) - &Curl_ssl_nss; #elif defined(USE_RUSTLS) &Curl_ssl_rustls; #elif defined(USE_OPENSSL) &Curl_ssl_openssl; +#elif defined(USE_SECTRANSP) + &Curl_ssl_sectransp; #elif defined(USE_SCHANNEL) &Curl_ssl_schannel; #elif defined(USE_BEARSSL) @@ -1264,24 +1002,18 @@ static const struct Curl_ssl *available_backends[] = { #if defined(USE_WOLFSSL) &Curl_ssl_wolfssl, #endif -#if defined(USE_SECTRANSP) - &Curl_ssl_sectransp, -#endif #if defined(USE_GNUTLS) &Curl_ssl_gnutls, #endif -#if defined(USE_GSKIT) - &Curl_ssl_gskit, -#endif #if defined(USE_MBEDTLS) &Curl_ssl_mbedtls, #endif -#if defined(USE_NSS) - &Curl_ssl_nss, -#endif #if defined(USE_OPENSSL) &Curl_ssl_openssl, #endif +#if defined(USE_SECTRANSP) + &Curl_ssl_sectransp, +#endif #if defined(USE_SCHANNEL) &Curl_ssl_schannel, #endif @@ -1291,8 +1023,22 @@ static const struct Curl_ssl *available_backends[] = { #if defined(USE_RUSTLS) &Curl_ssl_rustls, #endif - NULL -}; + NULL +}; + +/* Global cleanup */ +void Curl_ssl_cleanup(void) +{ + if(init_ssl) { + /* only cleanup if we did a previous init */ + if(Curl_ssl->cleanup) + Curl_ssl->cleanup(); +#if defined(CURL_WITH_MULTI_SSL) + Curl_ssl = &Curl_ssl_multi; +#endif + init_ssl = FALSE; + } +} static size_t multissl_version(char *buffer, size_t size) { @@ -1325,23 +1071,19 @@ static size_t multissl_version(char *buffer, size_t size) backends_len = p - backends; } - if(!size) - return 0; - - if(size <= backends_len) { - strncpy(buffer, backends, size - 1); - buffer[size - 1] = '\0'; - return size - 1; + if(size) { + if(backends_len < size) + strcpy(buffer, backends); + else + *buffer = 0; /* did not fit */ } - - strcpy(buffer, backends); - return backends_len; + return 0; } static int multissl_setup(const struct Curl_ssl *backend) { - const char *env; - char *env_tmp; + int i; + char *env; if(Curl_ssl != &Curl_ssl_multi) return 1; @@ -1354,25 +1096,31 @@ static int multissl_setup(const struct Curl_ssl *backend) if(!available_backends[0]) return 1; - env = env_tmp = curl_getenv("CURL_SSL_BACKEND"); -#ifdef CURL_DEFAULT_SSL_BACKEND - if(!env) - env = CURL_DEFAULT_SSL_BACKEND; -#endif + env = curl_getenv("CURL_SSL_BACKEND"); if(env) { - int i; for(i = 0; available_backends[i]; i++) { if(strcasecompare(env, available_backends[i]->info.name)) { Curl_ssl = available_backends[i]; - free(env_tmp); + free(env); return 0; } } } +#ifdef CURL_DEFAULT_SSL_BACKEND + for(i = 0; available_backends[i]; i++) { + if(strcasecompare(CURL_DEFAULT_SSL_BACKEND, + available_backends[i]->info.name)) { + Curl_ssl = available_backends[i]; + free(env); + return 0; + } + } +#endif + /* Fall back to first available backend */ Curl_ssl = available_backends[0]; - free(env_tmp); + free(env); return 0; } @@ -1421,71 +1169,118 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, #ifdef USE_SSL -static void free_hostname(struct ssl_connect_data *connssl) +void Curl_ssl_peer_cleanup(struct ssl_peer *peer) { - if(connssl->dispname != connssl->hostname) - free(connssl->dispname); - free(connssl->hostname); - connssl->hostname = connssl->dispname = NULL; + Curl_safefree(peer->sni); + if(peer->dispname != peer->hostname) + free(peer->dispname); + peer->dispname = NULL; + Curl_safefree(peer->hostname); + Curl_safefree(peer->scache_key); + peer->type = CURL_SSL_PEER_DNS; } static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; if(connssl) { - Curl_ssl->close(cf, data); + connssl->ssl_impl->close(cf, data); connssl->state = ssl_connection_none; - free_hostname(connssl); + Curl_ssl_peer_cleanup(&connssl->peer); } cf->connected = FALSE; } -static CURLcode reinit_hostname(struct Curl_cfilter *cf) +static ssl_peer_type get_peer_type(const char *hostname) { - struct ssl_connect_data *connssl = cf->ctx; - const char *ehostname, *edispname; - int eport; + if(hostname && hostname[0]) { +#ifdef USE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + if(curlx_inet_pton(AF_INET, hostname, &addr)) + return CURL_SSL_PEER_IPV4; +#ifdef USE_IPV6 + else if(curlx_inet_pton(AF_INET6, hostname, &addr)) { + return CURL_SSL_PEER_IPV6; + } +#endif + } + return CURL_SSL_PEER_DNS; +} - /* We need the hostname for SNI negotiation. Once handshaked, this - * remains the SNI hostname for the TLS connection. But when the - * connection is reused, the settings in cf->conn might change. - * So we keep a copy of the hostname we use for SNI. +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, + struct Curl_cfilter *cf, + const char *tls_id, + int transport) +{ + const char *ehostname, *edispname; + CURLcode result = CURLE_OUT_OF_MEMORY; + + /* We expect a clean struct, e.g. called only ONCE */ + DEBUGASSERT(peer); + DEBUGASSERT(!peer->hostname); + DEBUGASSERT(!peer->dispname); + DEBUGASSERT(!peer->sni); + /* We need the hostname for SNI negotiation. Once handshaked, this remains + * the SNI hostname for the TLS connection. When the connection is reused, + * the settings in cf->conn might change. We keep a copy of the hostname we + * use for SNI. */ + peer->transport = transport; #ifndef CURL_DISABLE_PROXY if(Curl_ssl_cf_is_proxy(cf)) { ehostname = cf->conn->http_proxy.host.name; edispname = cf->conn->http_proxy.host.dispname; - eport = cf->conn->http_proxy.port; + peer->port = cf->conn->http_proxy.port; } else #endif { ehostname = cf->conn->host.name; edispname = cf->conn->host.dispname; - eport = cf->conn->remote_port; + peer->port = cf->conn->remote_port; } - /* change if ehostname changed */ - if(ehostname && (!connssl->hostname - || strcmp(ehostname, connssl->hostname))) { - free_hostname(connssl); - connssl->hostname = strdup(ehostname); - if(!connssl->hostname) { - free_hostname(connssl); - return CURLE_OUT_OF_MEMORY; - } - if(!edispname || !strcmp(ehostname, edispname)) - connssl->dispname = connssl->hostname; - else { - connssl->dispname = strdup(edispname); - if(!connssl->dispname) { - free_hostname(connssl); - return CURLE_OUT_OF_MEMORY; - } + /* hostname MUST exist and not be empty */ + if(!ehostname || !ehostname[0]) { + result = CURLE_FAILED_INIT; + goto out; + } + + peer->hostname = strdup(ehostname); + if(!peer->hostname) + goto out; + if(!edispname || !strcmp(ehostname, edispname)) + peer->dispname = peer->hostname; + else { + peer->dispname = strdup(edispname); + if(!peer->dispname) + goto out; + } + peer->type = get_peer_type(peer->hostname); + if(peer->type == CURL_SSL_PEER_DNS) { + /* not an IP address, normalize according to RCC 6066 ch. 3, + * max len of SNI is 2^16-1, no trailing dot */ + size_t len = strlen(peer->hostname); + if(len && (peer->hostname[len-1] == '.')) + len--; + if(len < USHRT_MAX) { + peer->sni = calloc(1, len + 1); + if(!peer->sni) + goto out; + Curl_strntolower(peer->sni, peer->hostname, len); + peer->sni[len] = 0; } } - connssl->port = eport; - return CURLE_OK; + + result = Curl_ssl_peer_key_make(cf, peer, tls_id, &peer->scache_key); + +out: + if(result) + Curl_ssl_peer_cleanup(peer); + return result; } static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -1506,65 +1301,153 @@ static void ssl_cf_close(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); cf_close(cf, data); - cf->next->cft->close(cf->next, data); + if(cf->next) + cf->next->cft->do_close(cf->next, data); CF_DATA_RESTORE(cf, save); } static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; CURLcode result; - if(cf->connected) { + if(cf->connected && (connssl->state != ssl_connection_deferred)) { *done = TRUE; return CURLE_OK; } + if(!cf->next) { + *done = FALSE; + return CURLE_FAILED_INIT; + } + + if(!cf->next->connected) { + result = cf->next->cft->do_connect(cf->next, data, done); + if(result || !*done) + return result; + } + CF_DATA_SAVE(save, cf, data); - (void)connssl; - DEBUGASSERT(data->conn); - DEBUGASSERT(data->conn == cf->conn); + CURL_TRC_CF(data, cf, "cf_connect()"); DEBUGASSERT(connssl); - DEBUGASSERT(cf->conn->host.name); - - result = cf->next->cft->connect(cf->next, data, blocking, done); - if(result || !*done) - goto out; *done = FALSE; - result = reinit_hostname(cf); - if(result) - goto out; - - if(blocking) { - result = ssl_connect(cf, data); - *done = (result == CURLE_OK); + if(!connssl->peer.hostname) { + char tls_id[80]; + connssl->ssl_impl->version(tls_id, sizeof(tls_id) - 1); + result = Curl_ssl_peer_init(&connssl->peer, cf, tls_id, TRNSPRT_TCP); + if(result) + goto out; } - else { - result = ssl_connect_nonblocking(cf, data, done); + + if(!connssl->prefs_checked) { + if(!ssl_prefs_check(data)) + return CURLE_SSL_CONNECT_ERROR; + connssl->prefs_checked = TRUE; } + result = connssl->ssl_impl->do_connect(cf, data, done); + if(!result && *done) { cf->connected = TRUE; - connssl->handshake_done = Curl_now(); - DEBUGASSERT(connssl->state == ssl_connection_complete); + if(connssl->state == ssl_connection_complete) + connssl->handshake_done = curlx_now(); + /* Connection can be deferred when sending early data */ + DEBUGASSERT(connssl->state == ssl_connection_complete || + connssl->state == ssl_connection_deferred); + DEBUGASSERT(connssl->state != ssl_connection_deferred || + connssl->earlydata_state > ssl_earlydata_none); } out: + CURL_TRC_CF(data, cf, "cf_connect() -> %d, done=%d", result, *done); CF_DATA_RESTORE(cf, save); return result; } +static CURLcode ssl_cf_set_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t blen) +{ + struct ssl_connect_data *connssl = cf->ctx; + ssize_t nwritten = 0; + CURLcode result = CURLE_OK; + + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_await); + DEBUGASSERT(Curl_bufq_is_empty(&connssl->earlydata)); + if(blen) { + if(blen > connssl->earlydata_max) + blen = connssl->earlydata_max; + nwritten = Curl_bufq_write(&connssl->earlydata, buf, blen, &result); + CURL_TRC_CF(data, cf, "ssl_cf_set_earlydata(len=%zu) -> %zd", + blen, nwritten); + if(nwritten < 0) + return result; + } + return CURLE_OK; +} + +static CURLcode ssl_cf_connect_deferred(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t blen, + bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result = CURLE_OK; + + DEBUGASSERT(connssl->state == ssl_connection_deferred); + *done = FALSE; + if(connssl->earlydata_state == ssl_earlydata_await) { + result = ssl_cf_set_earlydata(cf, data, buf, blen); + if(result) + return result; + /* we buffered any early data we'd like to send. Actually + * do the connect now which sends it and performs the handshake. */ + connssl->earlydata_state = ssl_earlydata_sending; + connssl->earlydata_skip = Curl_bufq_len(&connssl->earlydata); + } + + result = ssl_cf_connect(cf, data, done); + + if(!result && *done) { + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + switch(connssl->earlydata_state) { + case ssl_earlydata_none: + break; + case ssl_earlydata_accepted: + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsEarlyData(data, (curl_off_t)connssl->earlydata_skip); + infof(data, "Server accepted %zu bytes of TLS early data.", + connssl->earlydata_skip); + break; + case ssl_earlydata_rejected: + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsEarlyData(data, -(curl_off_t)connssl->earlydata_skip); + infof(data, "Server rejected TLS early data."); + connssl->earlydata_skip = 0; + break; + default: + /* This should not happen. Either we do not use early data or we + * should know if it was accepted or not. */ + DEBUGASSERT(NULL); + break; + } + } + return result; +} + static bool ssl_cf_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { + struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; bool result; CF_DATA_SAVE(save, cf, data); - if(Curl_ssl->data_pending(cf, data)) + if(connssl->ssl_impl->data_pending && + connssl->ssl_impl->data_pending(cf, data)) result = TRUE; else result = cf->next->cft->has_data_pending(cf->next, data); @@ -1573,15 +1456,56 @@ static bool ssl_cf_data_pending(struct Curl_cfilter *cf, } static ssize_t ssl_cf_send(struct Curl_cfilter *cf, - struct Curl_easy *data, const void *buf, size_t len, - CURLcode *err) + struct Curl_easy *data, + const void *buf, size_t blen, + bool eos, CURLcode *err) { + struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; - ssize_t nwritten; + ssize_t nwritten = 0, early_written = 0; - CF_DATA_SAVE(save, cf, data); + (void)eos; *err = CURLE_OK; - nwritten = Curl_ssl->send_plain(cf, data, buf, len, err); + CF_DATA_SAVE(save, cf, data); + + if(connssl->state == ssl_connection_deferred) { + bool done = FALSE; + *err = ssl_cf_connect_deferred(cf, data, buf, blen, &done); + if(*err) { + nwritten = -1; + goto out; + } + else if(!done) { + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + DEBUGASSERT(connssl->state == ssl_connection_complete); + } + + if(connssl->earlydata_skip) { + if(connssl->earlydata_skip >= blen) { + connssl->earlydata_skip -= blen; + *err = CURLE_OK; + nwritten = (ssize_t)blen; + goto out; + } + else { + early_written = connssl->earlydata_skip; + buf = ((const char *)buf) + connssl->earlydata_skip; + blen -= connssl->earlydata_skip; + connssl->earlydata_skip = 0; + } + } + + /* OpenSSL and maybe other TLS libs do not like 0-length writes. Skip. */ + if(blen > 0) + nwritten = connssl->ssl_impl->send_plain(cf, data, buf, blen, err); + + if(nwritten >= 0) + nwritten += early_written; + +out: CF_DATA_RESTORE(cf, save); return nwritten; } @@ -1590,11 +1514,28 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { + struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; ssize_t nread; CF_DATA_SAVE(save, cf, data); - nread = Curl_ssl->recv_plain(cf, data, buf, len, err); + *err = CURLE_OK; + if(connssl->state == ssl_connection_deferred) { + bool done = FALSE; + *err = ssl_cf_connect_deferred(cf, data, NULL, 0, &done); + if(*err) { + nread = -1; + goto out; + } + else if(!done) { + *err = CURLE_AGAIN; + nread = -1; + goto out; + } + DEBUGASSERT(connssl->state == ssl_connection_complete); + } + + nread = connssl->ssl_impl->recv_plain(cf, data, buf, len, err); if(nread > 0) { DEBUGASSERT((size_t)nread <= len); } @@ -1602,51 +1543,46 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, /* eof */ *err = CURLE_OK; } - DEBUGF(LOG_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, nread, *err)); + +out: + CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, + nread, *err); CF_DATA_RESTORE(cf, save); return nread; } -static int ssl_cf_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - struct cf_call_data save; - int result; + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result = CURLE_OK; - CF_DATA_SAVE(save, cf, data); - result = Curl_ssl->get_select_socks(cf, data, socks); - CF_DATA_RESTORE(cf, save); + *done = TRUE; + /* If we have done the SSL handshake, shut down the connection cleanly */ + if(cf->connected && (connssl->state == ssl_connection_complete) && + !cf->shutdown && Curl_ssl->shut_down) { + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = connssl->ssl_impl->shut_down(cf, data, TRUE, done); + CURL_TRC_CF(data, cf, "cf_shutdown -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + } return result; } -static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, - struct Curl_easy *data, - int event, int arg1, void *arg2) +static void ssl_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { + struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; - (void)arg1; - (void)arg2; - switch(event) { - case CF_CTRL_DATA_ATTACH: - if(Curl_ssl->attach_data) { - CF_DATA_SAVE(save, cf, data); - Curl_ssl->attach_data(cf, data); - CF_DATA_RESTORE(cf, save); - } - break; - case CF_CTRL_DATA_DETACH: - if(Curl_ssl->detach_data) { - CF_DATA_SAVE(save, cf, data); - Curl_ssl->detach_data(cf, data); - CF_DATA_RESTORE(cf, save); - } - break; - default: - break; - } - return CURLE_OK; + CF_DATA_SAVE(save, cf, data); + connssl->ssl_impl->adjust_pollset(cf, data, ps); + CF_DATA_RESTORE(cf, save); } static CURLcode ssl_cf_query(struct Curl_cfilter *cf, @@ -1665,7 +1601,7 @@ static CURLcode ssl_cf_query(struct Curl_cfilter *cf, default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1673,29 +1609,10 @@ static CURLcode ssl_cf_query(struct Curl_cfilter *cf, static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { - struct cf_call_data save; - int result; /* * This function tries to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown */ - CF_DATA_SAVE(save, cf, data); - result = Curl_ssl->check_cxn(cf, data); - CF_DATA_RESTORE(cf, save); - if(result > 0) { - *input_pending = TRUE; - return TRUE; - } - if(result == 0) { - *input_pending = FALSE; - return FALSE; - } - /* ssl backend does not know */ - return cf->next? + return cf->next ? cf->next->cft->is_alive(cf->next, data, input_pending) : FALSE; /* pessimistic in absence of data */ } @@ -1703,39 +1620,45 @@ static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, struct Curl_cftype Curl_cft_ssl = { "SSL", CF_TYPE_SSL, - CURL_LOG_DEFAULT, + CURL_LOG_LVL_NONE, ssl_cf_destroy, ssl_cf_connect, ssl_cf_close, + ssl_cf_shutdown, Curl_cf_def_get_host, - ssl_cf_get_select_socks, + ssl_cf_adjust_pollset, ssl_cf_data_pending, ssl_cf_send, ssl_cf_recv, - ssl_cf_cntrl, + Curl_cf_def_cntrl, cf_ssl_is_alive, Curl_cf_def_conn_keep_alive, ssl_cf_query, }; +#ifndef CURL_DISABLE_PROXY + struct Curl_cftype Curl_cft_ssl_proxy = { "SSL-PROXY", - CF_TYPE_SSL, - CURL_LOG_DEFAULT, + CF_TYPE_SSL|CF_TYPE_PROXY, + CURL_LOG_LVL_NONE, ssl_cf_destroy, ssl_cf_connect, ssl_cf_close, + ssl_cf_shutdown, Curl_cf_def_get_host, - ssl_cf_get_select_socks, + ssl_cf_adjust_pollset, ssl_cf_data_pending, ssl_cf_send, ssl_cf_recv, - ssl_cf_cntrl, + Curl_cf_def_cntrl, cf_ssl_is_alive, Curl_cf_def_conn_keep_alive, Curl_cf_def_query, }; +#endif /* !CURL_DISABLE_PROXY */ + static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn) @@ -1746,8 +1669,14 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, DEBUGASSERT(data->conn); - ctx = cf_ctx_new(data, alpn_get_spec(data->state.httpwant, +#ifdef CURL_DISABLE_HTTP + /* We only support ALPN for HTTP so far. */ + DEBUGASSERT(!conn->bits.tls_enable_alpn); + ctx = cf_ctx_new(data, NULL); +#else + ctx = cf_ctx_new(data, alpn_get_spec(data->state.http_neg.wanted, conn->bits.tls_enable_alpn)); +#endif if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1758,7 +1687,7 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, out: if(result) cf_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } @@ -1797,16 +1726,16 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, struct ssl_connect_data *ctx; CURLcode result; bool use_alpn = conn->bits.tls_enable_alpn; - int httpwant = CURL_HTTP_VERSION_1_1; + http_majors allowed = CURL_HTTP_V1x; #ifdef USE_HTTP2 if(conn->http_proxy.proxytype == CURLPROXY_HTTPS2) { use_alpn = TRUE; - httpwant = CURL_HTTP_VERSION_2; + allowed = (CURL_HTTP_V1x|CURL_HTTP_V2x); } #endif - ctx = cf_ctx_new(data, alpn_get_spec(httpwant, use_alpn)); + ctx = cf_ctx_new(data, alpn_get_spec(allowed, use_alpn)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1816,7 +1745,7 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, out: if(result) cf_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } @@ -1834,12 +1763,26 @@ CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, #endif /* !CURL_DISABLE_PROXY */ -bool Curl_ssl_supports(struct Curl_easy *data, int option) +bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option) { (void)data; - return (Curl_ssl->supports & option)? TRUE : FALSE; + return (Curl_ssl->supports & ssl_option); +} + +static struct Curl_cfilter *get_ssl_filter(struct Curl_cfilter *cf) +{ + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_ssl) + return cf; +#ifndef CURL_DISABLE_PROXY + if(cf->cft == &Curl_cft_ssl_proxy) + return cf; +#endif + } + return NULL; } + void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, CURLINFO info, int n) { @@ -1847,57 +1790,99 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, (void)n; if(data->conn) { struct Curl_cfilter *cf; - /* get first filter in chain, if any is present */ - cf = Curl_ssl_cf_get_ssl(data->conn->cfilter[sockindex]); + /* get first SSL filter in chain, if any is present */ + cf = get_ssl_filter(data->conn->cfilter[sockindex]); if(cf) { + struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - result = Curl_ssl->get_internals(cf->ctx, info); + result = connssl->ssl_impl->get_internals(cf->ctx, info); CF_DATA_RESTORE(cf, save); } } return result; } +static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + int what, loop = 10; + + if(cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + CF_DATA_SAVE(save, cf, data); + + *done = FALSE; + while(!result && !*done && loop--) { + timeout_ms = Curl_shutdown_timeleft(cf->conn, cf->sockindex, NULL); + + if(timeout_ms < 0) { + /* no need to continue if time is already up */ + failf(data, "SSL shutdown timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + result = connssl->ssl_impl->shut_down(cf, data, send_shutdown, done); + if(result ||*done) + goto out; + + if(connssl->io_need) { + what = Curl_conn_cf_poll(cf, data, timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + result = CURLE_RECV_ERROR; + goto out; + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + /* socket is readable or writable */ + } + } +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex) + int sockindex, bool send_shutdown) { struct Curl_cfilter *cf, *head; CURLcode result = CURLE_OK; - (void)data; - head = data->conn? data->conn->cfilter[sockindex] : NULL; + head = data->conn ? data->conn->cfilter[sockindex] : NULL; for(cf = head; cf; cf = cf->next) { if(cf->cft == &Curl_cft_ssl) { - if(Curl_ssl->shut_down(cf, data)) + bool done; + CURL_TRC_CF(data, cf, "shutdown and remove SSL, start"); + Curl_shutdown_start(data, sockindex, 0, NULL); + result = vtls_shutdown_blocking(cf, data, send_shutdown, &done); + Curl_shutdown_clear(data, sockindex); + if(!result && !done) /* blocking failed? */ result = CURLE_SSL_SHUTDOWN_FAILED; Curl_conn_cf_discard_sub(head, cf, data, FALSE); + CURL_TRC_CF(data, cf, "shutdown and remove SSL, done -> %d", result); break; } } return result; } -static struct Curl_cfilter *get_ssl_cf_engaged(struct connectdata *conn, - int sockindex) -{ - struct Curl_cfilter *cf, *lowest_ssl_cf = NULL; - - for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) { - lowest_ssl_cf = cf; - if(cf->connected || (cf->next && cf->next->connected)) { - /* connected or about to start */ - return cf; - } - } - } - return lowest_ssl_cf; -} - bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) { - return (cf->cft == &Curl_cft_ssl_proxy); + return (cf->cft->flags & CF_TYPE_SSL) && (cf->cft->flags & CF_TYPE_PROXY); } struct ssl_config_data * @@ -1907,41 +1892,21 @@ Curl_ssl_cf_get_config(struct Curl_cfilter *cf, struct Curl_easy *data) (void)cf; return &data->set.ssl; #else - return Curl_ssl_cf_is_proxy(cf)? &data->set.proxy_ssl : &data->set.ssl; + return Curl_ssl_cf_is_proxy(cf) ? &data->set.proxy_ssl : &data->set.ssl; #endif } -struct ssl_config_data * -Curl_ssl_get_config(struct Curl_easy *data, int sockindex) -{ - struct Curl_cfilter *cf; - - (void)data; - DEBUGASSERT(data->conn); - cf = get_ssl_cf_engaged(data->conn, sockindex); - return cf? Curl_ssl_cf_get_config(cf, data) : &data->set.ssl; -} - struct ssl_primary_config * Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf) { #ifdef CURL_DISABLE_PROXY return &cf->conn->ssl_config; #else - return Curl_ssl_cf_is_proxy(cf)? + return Curl_ssl_cf_is_proxy(cf) ? &cf->conn->proxy_ssl_config : &cf->conn->ssl_config; #endif } -struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf) -{ - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) - return cf; - } - return NULL; -} - CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, const struct alpn_spec *spec) { @@ -1988,64 +1953,132 @@ CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, return CURLE_OK; } +bool Curl_alpn_contains_proto(const struct alpn_spec *spec, + const char *proto) +{ + size_t i, plen = proto ? strlen(proto) : 0; + for(i = 0; spec && plen && i < spec->count; ++i) { + size_t slen = strlen(spec->entries[i]); + if((slen == plen) && !memcmp(proto, spec->entries[i], plen)) + return TRUE; + } + return FALSE; +} + +void Curl_alpn_restrict_to(struct alpn_spec *spec, const char *proto) +{ + size_t plen = strlen(proto); + DEBUGASSERT(plen < sizeof(spec->entries[0])); + if(plen < sizeof(spec->entries[0])) { + memcpy(spec->entries[0], proto, plen + 1); + spec->count = 1; + } +} + +void Curl_alpn_copy(struct alpn_spec *dest, const struct alpn_spec *src) +{ + if(src) + memcpy(dest, src, sizeof(*dest)); + else + memset(dest, 0, sizeof(*dest)); +} + CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ssl_connect_data *connssl, const unsigned char *proto, size_t proto_len) { - int can_multi = 0; + CURLcode result = CURLE_OK; unsigned char *palpn = #ifndef CURL_DISABLE_PROXY - (cf->conn->bits.tunnel_proxy && Curl_ssl_cf_is_proxy(cf))? + (cf->conn->bits.tunnel_proxy && Curl_ssl_cf_is_proxy(cf)) ? &cf->conn->proxy_alpn : &cf->conn->alpn #else &cf->conn->alpn #endif ; + if(connssl->negotiated.alpn) { + /* When we ask for a specific ALPN protocol, we need the confirmation + * of it by the server, as we have installed protocol handler and + * connection filter chain for exactly this protocol. */ + if(!proto_len) { + failf(data, "ALPN: asked for '%s' from previous session, " + "but server did not confirm it. Refusing to continue.", + connssl->negotiated.alpn); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + else if((strlen(connssl->negotiated.alpn) != proto_len) || + memcmp(connssl->negotiated.alpn, proto, proto_len)) { + failf(data, "ALPN: asked for '%s' from previous session, but server " + "selected '%.*s'. Refusing to continue.", + connssl->negotiated.alpn, (int)proto_len, proto); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + /* ALPN is exactly what we asked for, done. */ + infof(data, "ALPN: server confirmed to use '%s'", + connssl->negotiated.alpn); + goto out; + } + + if(proto && proto_len) { + if(memchr(proto, '\0', proto_len)) { + failf(data, "ALPN: server selected protocol contains NUL. " + "Refusing to continue."); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + connssl->negotiated.alpn = malloc(proto_len + 1); + if(!connssl->negotiated.alpn) + return CURLE_OUT_OF_MEMORY; + memcpy(connssl->negotiated.alpn, proto, proto_len); + connssl->negotiated.alpn[proto_len] = 0; + } + if(proto && proto_len) { if(proto_len == ALPN_HTTP_1_1_LENGTH && !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) { *palpn = CURL_HTTP_VERSION_1_1; } - else if(proto_len == ALPN_HTTP_1_0_LENGTH && - !memcmp(ALPN_HTTP_1_0, proto, ALPN_HTTP_1_0_LENGTH)) { - *palpn = CURL_HTTP_VERSION_1_0; - } #ifdef USE_HTTP2 else if(proto_len == ALPN_H2_LENGTH && !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) { *palpn = CURL_HTTP_VERSION_2; - can_multi = 1; } #endif #ifdef USE_HTTP3 else if(proto_len == ALPN_H3_LENGTH && !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) { *palpn = CURL_HTTP_VERSION_3; - can_multi = 1; } #endif else { *palpn = CURL_HTTP_VERSION_NONE; failf(data, "unsupported ALPN protocol: '%.*s'", (int)proto_len, proto); - /* TODO: do we want to fail this? Previous code just ignored it and - * some vtls backends even ignore the return code of this function. */ + /* Previous code just ignored it and some vtls backends even ignore the + * return code of this function. */ /* return CURLE_NOT_BUILT_IN; */ goto out; } - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, (int)proto_len, proto); + + if(connssl->state == ssl_connection_deferred) + infof(data, VTLS_INFOF_ALPN_DEFERRED, (int)proto_len, proto); + else + infof(data, VTLS_INFOF_ALPN_ACCEPTED, (int)proto_len, proto); } else { *palpn = CURL_HTTP_VERSION_NONE; - infof(data, VTLS_INFOF_NO_ALPN); + if(connssl->state == ssl_connection_deferred) + infof(data, VTLS_INFOF_NO_ALPN_DEFERRED); + else + infof(data, VTLS_INFOF_NO_ALPN); } out: - if(!Curl_ssl_cf_is_proxy(cf)) - Curl_multiuse_state(data, can_multi? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - return CURLE_OK; + return result; } #endif /* USE_SSL */ diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h index 3516247301d..0bb333b987a 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.h +++ b/Utilities/cmcurl/lib/vtls/vtls.h @@ -23,12 +23,14 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" struct connectdata; struct ssl_config_data; struct ssl_primary_config; -struct Curl_ssl_session; +struct Curl_cfilter; +struct Curl_easy; +struct dynbuf; #define SSLSUPP_CA_PATH (1<<0) /* supports CAPATH */ #define SSLSUPP_CERTINFO (1<<1) /* supports CURLOPT_CERTINFO */ @@ -37,22 +39,58 @@ struct Curl_ssl_session; #define SSLSUPP_HTTPS_PROXY (1<<4) /* supports access via HTTPS proxies */ #define SSLSUPP_TLS13_CIPHERSUITES (1<<5) /* supports TLS 1.3 ciphersuites */ #define SSLSUPP_CAINFO_BLOB (1<<6) +#define SSLSUPP_ECH (1<<7) +#define SSLSUPP_CA_CACHE (1<<8) +#define SSLSUPP_CIPHER_LIST (1<<9) /* supports TLS 1.0-1.2 ciphersuites */ +#define SSLSUPP_SIGNATURE_ALGORITHMS (1<<10) /* supports TLS sigalgs */ + +#ifdef USE_ECH +# include "../curlx/base64.h" +# define ECH_ENABLED(__data__) \ + (__data__->set.tls_ech && \ + !(__data__->set.tls_ech & CURLECH_DISABLE)\ + ) +#endif /* USE_ECH */ #define ALPN_ACCEPTED "ALPN: server accepted " -#define VTLS_INFOF_NO_ALPN \ +#define VTLS_INFOF_NO_ALPN \ "ALPN: server did not agree on a protocol. Uses default." -#define VTLS_INFOF_ALPN_OFFER_1STR \ - "ALPN: offers %s" -#define VTLS_INFOF_ALPN_ACCEPTED_1STR \ - ALPN_ACCEPTED "%s" -#define VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR \ +#define VTLS_INFOF_ALPN_OFFER_1STR \ + "ALPN: curl offers %s" +#define VTLS_INFOF_ALPN_ACCEPTED \ ALPN_ACCEPTED "%.*s" -/* Curl_multi SSL backend-specific data; declared differently by each SSL - backend */ -struct multi_ssl_backend_data; -struct Curl_cfilter; +#define VTLS_INFOF_NO_ALPN_DEFERRED \ + "ALPN: deferred handshake for early data without specific protocol." +#define VTLS_INFOF_ALPN_DEFERRED \ + "ALPN: deferred handshake for early data using '%.*s'." + +/* IETF defined version numbers used in TLS protocol negotiation */ +#define CURL_IETF_PROTO_UNKNOWN 0x0 +#define CURL_IETF_PROTO_SSL3 0x0300 +#define CURL_IETF_PROTO_TLS1 0x0301 +#define CURL_IETF_PROTO_TLS1_1 0x0302 +#define CURL_IETF_PROTO_TLS1_2 0x0303 +#define CURL_IETF_PROTO_TLS1_3 0x0304 +#define CURL_IETF_PROTO_DTLS1 0xFEFF +#define CURL_IETF_PROTO_DTLS1_2 0xFEFD + +typedef enum { + CURL_SSL_PEER_DNS, + CURL_SSL_PEER_IPV4, + CURL_SSL_PEER_IPV6 +} ssl_peer_type; + +struct ssl_peer { + char *hostname; /* hostname for verification */ + char *dispname; /* display version of hostname */ + char *sni; /* SNI version of hostname or NULL if not usable */ + char *scache_key; /* for lookups in session cache */ + ssl_peer_type type; /* type of the peer information */ + int port; /* port we are talking to */ + int transport; /* one of TRNSPRT_* defines */ +}; CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail); @@ -65,15 +103,57 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, #define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ #endif -char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen); -bool Curl_ssl_config_matches(struct ssl_primary_config *data, - struct ssl_primary_config *needle); -bool Curl_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest); -void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc); - curl_sslbackend Curl_ssl_backend(void); +/** + * Init ssl config for a new easy handle. + */ +void Curl_ssl_easy_config_init(struct Curl_easy *data); + +/** + * Init the `data->set.ssl` and `data->set.proxy_ssl` for + * connection matching use. + */ +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data); + +/** + * 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); + +/** + * Free allocated resources in SSL configs (main + proxy) for + * the given connection. + */ +void Curl_ssl_conn_config_cleanup(struct connectdata *conn); + +/** + * Return TRUE iff SSL configuration from `data` 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 connectdata *candidate, + bool proxy); + +/* Update certain connection SSL config flags after they have + * been changed on the easy handle. Will work for `verifypeer`, + * `verifyhost` and `verifystatus`. */ +void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy); + +/** + * Init SSL peer information for filter. Can be called repeatedly. + */ +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, + struct Curl_cfilter *cf, + const char *tls_id, + int transport); +/** + * Free all allocated data and reset peer information. + */ +void Curl_ssl_peer_cleanup(struct ssl_peer *peer); + #ifdef USE_SSL int Curl_ssl_init(void); void Curl_ssl_cleanup(void); @@ -85,11 +165,10 @@ CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine); CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data); struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data); -/* init the SSL session ID cache */ -CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t); void Curl_ssl_version(char *buffer, size_t size); /* Certificate information list handling. */ +#define CURL_X509_STR_MAX 100000 void Curl_ssl_free_certinfo(struct Curl_easy *data); CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num); @@ -101,33 +180,6 @@ CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data, int certnum, /* Functions to be used by SSL library adaptation functions */ -/* Lock session cache mutex. - * Call this before calling other Curl_ssl_*session* functions - * Caller should unlock this mutex as soon as possible, as it may block - * other SSL connection from making progress. - * The purpose of explicitly locking SSL session cache data is to allow - * individual SSL engines to manage session lifetime in their specific way. - */ -void Curl_ssl_sessionid_lock(struct Curl_easy *data); - -/* Unlock session cache mutex */ -void Curl_ssl_sessionid_unlock(struct Curl_easy *data); - -/* Kill a single session ID entry in the cache - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * This will call engine-specific curlssl_session_free function, which must - * take sessionid object ownership from sessionid cache - * (e.g. decrement refcount). - */ -void Curl_ssl_kill_session(struct Curl_ssl_session *session); -/* delete a session from the cache - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * This will call engine-specific curlssl_session_free function, which must - * take sessionid object ownership from sessionid cache - * (e.g. decrement refcount). - */ -void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid); - /* get N random bytes into the buffer */ CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *buffer, size_t length); @@ -138,9 +190,26 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, bool Curl_ssl_cert_status_request(void); -bool Curl_ssl_false_start(struct Curl_easy *data); +bool Curl_ssl_false_start(void); -void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend); +/* The maximum size of the SSL channel binding is 85 bytes, as defined in + * RFC 5929, Section 4.1. The 'tls-server-end-point:' prefix is 21 bytes long, + * and SHA-512 is the longest supported hash algorithm, with a digest length of + * 64 bytes. + * The maximum size of the channel binding is therefore 21 + 64 = 85 bytes. + */ +#define SSL_CB_MAX_SIZE 85 + +/* Return the tls-server-end-point channel binding, including the + * 'tls-server-end-point:' prefix. + * If successful, the data is written to the dynbuf, and CURLE_OK is returned. + * The dynbuf MUST HAVE a minimum toobig size of SSL_CB_MAX_SIZE. + * If the dynbuf is too small, CURLE_OUT_OF_MEMORY is returned. + * If channel binding is not supported, binding stays empty and CURLE_OK is + * returned. + */ +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding); #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ @@ -152,31 +221,19 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data); CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex); + int sockindex, bool send_shutdown); #ifndef CURL_DISABLE_PROXY CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data); #endif /* !CURL_DISABLE_PROXY */ -/** - * Get the SSL configuration that is used on the connection. - * This returns NULL if no SSL is configured. - * Otherwise it returns the config of the first (highest) one that is - * either connected, in handshake or about to start - * (e.g. all filters below it are connected). If SSL filters are present, - * but neither can start operating, return the config of the lowest one - * that will first come into effect when connecting. - */ -struct ssl_config_data *Curl_ssl_get_config(struct Curl_easy *data, - int sockindex); - /** * True iff the underlying SSL implementation supports the option. * Option is one of the defined SSLSUPP_* values. * `data` maybe NULL for the features of the default implementation. */ -bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option); +bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option); /** * Get the internal ssl instance (like OpenSSL's SSL*) from the filter @@ -188,8 +245,22 @@ bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option); void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, CURLINFO info, int n); +/** + * 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 primary config relevant for the filter from its connection. + */ +struct ssl_primary_config * + Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf); + extern struct Curl_cftype Curl_cft_ssl; +#ifndef CURL_DISABLE_PROXY extern struct Curl_cftype Curl_cft_ssl_proxy; +#endif #else /* if not USE_SSL */ @@ -200,17 +271,16 @@ extern struct Curl_cftype Curl_cft_ssl_proxy; #define Curl_ssl_set_engine(x,y) CURLE_NOT_BUILT_IN #define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN #define Curl_ssl_engines_list(x) NULL -#define Curl_ssl_initsessions(x,y) CURLE_OK #define Curl_ssl_free_certinfo(x) Curl_nop_stmt -#define Curl_ssl_kill_session(x) Curl_nop_stmt #define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN) #define Curl_ssl_cert_status_request() FALSE -#define Curl_ssl_false_start(a) FALSE +#define Curl_ssl_false_start() FALSE #define Curl_ssl_get_internals(a,b,c,d) NULL #define Curl_ssl_supports(a,b) FALSE #define Curl_ssl_cfilter_add(a,b,c) CURLE_NOT_BUILT_IN -#define Curl_ssl_get_config(a,b) NULL -#define Curl_ssl_cfilter_remove(a,b) CURLE_OK +#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 #endif #endif /* HEADER_CURL_VTLS_H */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_int.h b/Utilities/cmcurl/lib/vtls/vtls_int.h index ed49339e474..0632a07657d 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_int.h +++ b/Utilities/cmcurl/lib/vtls/vtls_int.h @@ -23,17 +23,19 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" -#include "cfilters.h" -#include "urldata.h" +#include "../curl_setup.h" +#include "../cfilters.h" +#include "../urldata.h" +#include "vtls.h" #ifdef USE_SSL +struct Curl_ssl; +struct ssl_connect_data; + /* see https://www.iana.org/assignments/tls-extensiontype-values/ */ #define ALPN_HTTP_1_1_LENGTH 8 #define ALPN_HTTP_1_1 "http/1.1" -#define ALPN_HTTP_1_0_LENGTH 8 -#define ALPN_HTTP_1_0 "http/1.0" #define ALPN_H2_LENGTH 2 #define ALPN_H2 "h2" #define ALPN_H3_LENGTH 2 @@ -47,7 +49,7 @@ #define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1)) struct alpn_spec { - const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; + char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; size_t count; /* number of entries */ }; @@ -60,27 +62,75 @@ CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, const struct alpn_spec *spec); CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, const struct alpn_spec *spec); +void Curl_alpn_restrict_to(struct alpn_spec *spec, const char *proto); +void Curl_alpn_copy(struct alpn_spec *dest, const struct alpn_spec *src); CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ssl_connect_data *connssl, const unsigned char *proto, size_t proto_len); +bool Curl_alpn_contains_proto(const struct alpn_spec *spec, + const char *proto); + +/* enum for the nonblocking SSL connection state machine */ +typedef enum { + ssl_connect_1, + ssl_connect_2, + ssl_connect_3, + ssl_connect_done +} ssl_connect_state; + +typedef enum { + ssl_connection_none, + ssl_connection_deferred, + ssl_connection_negotiating, + ssl_connection_complete +} ssl_connection_state; + +typedef enum { + ssl_earlydata_none, + ssl_earlydata_await, + ssl_earlydata_sending, + ssl_earlydata_sent, + ssl_earlydata_accepted, + ssl_earlydata_rejected +} ssl_earlydata_state; + +#define CURL_SSL_IO_NEED_NONE (0) +#define CURL_SSL_IO_NEED_RECV (1<<0) +#define CURL_SSL_IO_NEED_SEND (1<<1) + +/* Max earlydata payload we want to send */ +#define CURL_SSL_EARLY_MAX (64*1024) + /* Information in each SSL cfilter context: cf->ctx */ struct ssl_connect_data { - ssl_connection_state state; - ssl_connect_state connecting_state; - char *hostname; /* hostname for verification */ - char *dispname; /* display version of hostname */ + const struct Curl_ssl *ssl_impl; /* TLS backend for this filter */ + struct ssl_peer peer; /* peer the filter talks to */ const struct alpn_spec *alpn; /* ALPN to use or NULL for none */ - struct ssl_backend_data *backend; /* vtls backend specific props */ + void *backend; /* vtls backend specific props */ struct cf_call_data call_data; /* data handle used in current call */ struct curltime handshake_done; /* time when handshake finished */ - int port; /* remote port at origin */ + struct { + char *alpn; /* ALPN value or NULL */ + } negotiated; + struct bufq earlydata; /* earlydata to be send to peer */ + size_t earlydata_max; /* max earlydata allowed by peer */ + size_t earlydata_skip; /* sending bytes to skip when earlydata + * is accepted by peer */ + ssl_connection_state state; + ssl_connect_state connecting_state; + ssl_earlydata_state earlydata_state; + int io_need; /* TLS signals special SEND/RECV needs */ BIT(use_alpn); /* if ALPN shall be used in handshake */ + BIT(peer_closed); /* peer has closed connection */ + BIT(prefs_checked); /* SSL preferences have been checked */ }; +#undef CF_CTX_CALL_DATA #define CF_CTX_CALL_DATA(cf) \ ((struct ssl_connect_data *)(cf)->ctx)->call_data @@ -100,9 +150,8 @@ struct Curl_ssl { void (*cleanup)(void); size_t (*version)(char *buffer, size_t size); - int (*check_cxn)(struct Curl_cfilter *cf, struct Curl_easy *data); - int (*shut_down)(struct Curl_cfilter *cf, - struct Curl_easy *data); + CURLcode (*shut_down)(struct Curl_cfilter *cf, struct Curl_easy *data, + bool send_shutdown, bool *done); bool (*data_pending)(struct Curl_cfilter *cf, const struct Curl_easy *data); @@ -111,24 +160,16 @@ struct Curl_ssl { size_t length); bool (*cert_status_request)(void); - CURLcode (*connect_blocking)(struct Curl_cfilter *cf, - struct Curl_easy *data); - CURLcode (*connect_nonblocking)(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done); - - /* If the SSL backend wants to read or write on this connection during a - handshake, set socks[0] to the connection's FIRSTSOCKET, and return - a bitmap indicating read or write with GETSOCK_WRITESOCK(0) or - GETSOCK_READSOCK(0). Otherwise return GETSOCK_BLANK. - Mandatory. */ - int (*get_select_socks)(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks); + CURLcode (*do_connect)(struct Curl_cfilter *cf, struct Curl_easy *data, + bool *done); + /* During handshake/shutdown, adjust the pollset to include the socket + * for POLLOUT or POLLIN as needed. Mandatory. */ + void (*adjust_pollset)(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps); void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info); void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data); void (*close_all)(struct Curl_easy *data); - void (*session_free)(void *ptr); CURLcode (*set_engine)(struct Curl_easy *data, const char *engine); CURLcode (*set_engine_default)(struct Curl_easy *data); @@ -137,94 +178,26 @@ struct Curl_ssl { bool (*false_start)(void); CURLcode (*sha256sum)(const unsigned char *input, size_t inputlen, unsigned char *sha256sum, size_t sha256sumlen); - - bool (*attach_data)(struct Curl_cfilter *cf, struct Curl_easy *data); - void (*detach_data)(struct Curl_cfilter *cf, struct Curl_easy *data); - - void (*free_multi_ssl_backend_data)(struct multi_ssl_backend_data *mbackend); - ssize_t (*recv_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *code); ssize_t (*send_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, size_t len, CURLcode *code); + CURLcode (*get_channel_binding)(struct Curl_easy *data, int sockindex, + struct dynbuf *binding); + }; extern const struct Curl_ssl *Curl_ssl; - -int Curl_none_init(void); -void Curl_none_cleanup(void); -int Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data); -int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data); -CURLcode Curl_none_random(struct Curl_easy *data, unsigned char *entropy, - size_t length); -void Curl_none_close_all(struct Curl_easy *data); -void Curl_none_session_free(void *ptr); -bool Curl_none_data_pending(struct Curl_cfilter *cf, - const struct Curl_easy *data); -bool Curl_none_cert_status_request(void); -CURLcode Curl_none_set_engine(struct Curl_easy *data, const char *engine); -CURLcode Curl_none_set_engine_default(struct Curl_easy *data); -struct curl_slist *Curl_none_engines_list(struct Curl_easy *data); -bool Curl_none_false_start(void); -int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks); - -/** - * 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 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 first SSL filter in the chain starting with `cf`, or NULL. - */ -struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf); +void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps); /** * Get the SSL filter below the given one or NULL if there is none. */ bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf); -/* extract a session ID - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * Caller must make sure that the ownership of returned sessionid object - * is properly taken (e.g. its refcount is incremented - * under sessionid mutex). - */ -bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, - struct Curl_easy *data, - void **ssl_sessionid, - size_t *idsize); /* set 0 if unknown */ -/* add a new session ID - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * Caller must ensure that it has properly shared ownership of this sessionid - * object with cache (e.g. incrementing refcount on success) - */ -CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, - struct Curl_easy *data, - void *ssl_sessionid, - size_t idsize, - bool *added); - -#include "openssl.h" /* OpenSSL versions */ -#include "gtls.h" /* GnuTLS versions */ -#include "nssg.h" /* NSS versions */ -#include "gskit.h" /* Global Secure ToolKit versions */ -#include "wolfssl.h" /* wolfSSL versions */ -#include "schannel.h" /* Schannel SSPI version */ -#include "sectransp.h" /* SecureTransport (Darwin) version */ -#include "mbedtls.h" /* mbedTLS versions */ -#include "bearssl.h" /* BearSSL versions */ -#include "rustls.h" /* rustls versions */ - #endif /* USE_SSL */ #endif /* HEADER_CURL_VTLS_INT_H */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.c b/Utilities/cmcurl/lib/vtls/vtls_scache.c new file mode 100644 index 00000000000..1fe4b5bbdee --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/vtls_scache.c @@ -0,0 +1,1235 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef USE_SSL + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif + +#include "../urldata.h" +#include "../cfilters.h" + +#include "vtls.h" /* generic SSL protos etc */ +#include "vtls_int.h" +#include "vtls_scache.h" +#include "vtls_spack.h" + +#include "../strcase.h" +#include "../url.h" +#include "../llist.h" +#include "../share.h" +#include "../curl_trc.h" +#include "../curl_sha256.h" +#include "../rand.h" +#include "../curlx/warnless.h" +#include "../curl_printf.h" +#include "../strdup.h" + +/* The last #include files should be: */ +#include "../curl_memory.h" +#include "../memdebug.h" + + +static bool cf_ssl_peer_key_is_global(const char *peer_key); + +/* a peer+tls-config we cache sessions for */ +struct Curl_ssl_scache_peer { + char *ssl_peer_key; /* id for peer + relevant TLS configuration */ + char *clientcert; + char *srp_username; + char *srp_password; + struct Curl_llist sessions; + void *sobj; /* object instance or NULL */ + Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */ + unsigned char key_salt[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */ + unsigned char key_hmac[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */ + size_t max_sessions; + long age; /* just a number, the higher the more recent */ + BIT(hmac_set); /* if key_salt and key_hmac are present */ + BIT(exportable); /* sessions for this peer can be exported */ +}; + +#define CURL_SCACHE_MAGIC 0x000e1551 + +#define GOOD_SCACHE(x) ((x) && (x)->magic == CURL_SCACHE_MAGIC) + +struct Curl_ssl_scache { + unsigned int magic; + struct Curl_ssl_scache_peer *peers; + size_t peer_count; + int default_lifetime_secs; + long age; +}; + +static struct Curl_ssl_scache *cf_ssl_scache_get(struct Curl_easy *data) +{ + struct Curl_ssl_scache *scache = NULL; + /* If a share is present, its ssl_scache has preference over the multi */ + if(data->share && data->share->ssl_scache) + scache = data->share->ssl_scache; + else if(data->multi && data->multi->ssl_scache) + scache = data->multi->ssl_scache; + if(scache && !GOOD_SCACHE(scache)) { + failf(data, "transfer would use an invalid scache at %p, denied", + (void *)scache); + DEBUGASSERT(0); + return NULL; + } + return scache; +} + +static void cf_ssl_scache_session_ldestroy(void *udata, void *obj) +{ + struct Curl_ssl_session *s = obj; + (void)udata; + free(CURL_UNCONST(s->sdata)); + free(CURL_UNCONST(s->quic_tp)); + free((void *)s->alpn); + free(s); +} + +CURLcode +Curl_ssl_session_create(void *sdata, size_t sdata_len, + int ietf_tls_id, const char *alpn, + curl_off_t valid_until, size_t earlydata_max, + struct Curl_ssl_session **psession) +{ + return Curl_ssl_session_create2(sdata, sdata_len, ietf_tls_id, alpn, + valid_until, earlydata_max, + NULL, 0, psession); +} + +CURLcode +Curl_ssl_session_create2(void *sdata, size_t sdata_len, + int ietf_tls_id, const char *alpn, + curl_off_t valid_until, size_t earlydata_max, + unsigned char *quic_tp, size_t quic_tp_len, + struct Curl_ssl_session **psession) +{ + struct Curl_ssl_session *s; + + if(!sdata || !sdata_len) { + free(sdata); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + *psession = NULL; + s = calloc(1, sizeof(*s)); + if(!s) { + free(sdata); + free(quic_tp); + return CURLE_OUT_OF_MEMORY; + } + + s->ietf_tls_id = ietf_tls_id; + s->valid_until = valid_until; + s->earlydata_max = earlydata_max; + s->sdata = sdata; + s->sdata_len = sdata_len; + s->quic_tp = quic_tp; + s->quic_tp_len = quic_tp_len; + if(alpn) { + s->alpn = strdup(alpn); + if(!s->alpn) { + cf_ssl_scache_session_ldestroy(NULL, s); + return CURLE_OUT_OF_MEMORY; + } + } + *psession = s; + return CURLE_OK; +} + +void Curl_ssl_session_destroy(struct Curl_ssl_session *s) +{ + if(s) { + /* if in the list, the list destructor takes care of it */ + if(Curl_node_llist(&s->list)) + Curl_node_remove(&s->list); + else { + cf_ssl_scache_session_ldestroy(NULL, s); + } + } +} + +static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer) +{ + Curl_llist_destroy(&peer->sessions, NULL); + if(peer->sobj) { + DEBUGASSERT(peer->sobj_free); + if(peer->sobj_free) + peer->sobj_free(peer->sobj); + peer->sobj = NULL; + } + peer->sobj_free = NULL; + Curl_safefree(peer->clientcert); +#ifdef USE_TLS_SRP + Curl_safefree(peer->srp_username); + Curl_safefree(peer->srp_password); +#endif + Curl_safefree(peer->ssl_peer_key); + peer->age = 0; + peer->hmac_set = FALSE; +} + +static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer, + void *sobj, + Curl_ssl_scache_obj_dtor *sobj_free) +{ + DEBUGASSERT(peer); + if(peer->sobj_free) { + peer->sobj_free(peer->sobj); + } + peer->sobj = sobj; + peer->sobj_free = sobj_free; +} + +static void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer) +{ + /* The sessions of this peer are exportable if + * - it has no confidential information + * - its peer key is not yet known, because sessions were + * imported using only the salt+hmac + * - the peer key is global, e.g. carrying no relative paths */ + peer->exportable = (!peer->clientcert && !peer->srp_username && + !peer->srp_password && + (!peer->ssl_peer_key || + cf_ssl_peer_key_is_global(peer->ssl_peer_key))); +} + +static CURLcode +cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, + const char *ssl_peer_key, + const char *clientcert, + const char *srp_username, + const char *srp_password, + const unsigned char *salt, + const unsigned char *hmac) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + + DEBUGASSERT(!peer->ssl_peer_key); + if(ssl_peer_key) { + peer->ssl_peer_key = strdup(ssl_peer_key); + if(!peer->ssl_peer_key) + goto out; + peer->hmac_set = FALSE; + } + else if(salt && hmac) { + memcpy(peer->key_salt, salt, sizeof(peer->key_salt)); + memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac)); + peer->hmac_set = TRUE; + } + else { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(clientcert) { + peer->clientcert = strdup(clientcert); + if(!peer->clientcert) + goto out; + } + if(srp_username) { + peer->srp_username = strdup(srp_username); + if(!peer->srp_username) + goto out; + } + if(srp_password) { + peer->srp_password = strdup(srp_password); + if(!peer->srp_password) + goto out; + } + + cf_ssl_cache_peer_update(peer); + result = CURLE_OK; +out: + if(result) + cf_ssl_scache_clear_peer(peer); + return result; +} + +static void cf_scache_session_remove(struct Curl_ssl_scache_peer *peer, + struct Curl_ssl_session *s) +{ + (void)peer; + DEBUGASSERT(Curl_node_llist(&s->list) == &peer->sessions); + Curl_ssl_session_destroy(s); +} + +static bool cf_scache_session_expired(struct Curl_ssl_session *s, + curl_off_t now) +{ + return (s->valid_until > 0) && (s->valid_until < now); +} + +static void cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer *peer, + curl_off_t now) +{ + struct Curl_llist_node *n = Curl_llist_head(&peer->sessions); + while(n) { + struct Curl_ssl_session *s = Curl_node_elem(n); + n = Curl_node_next(n); + if(cf_scache_session_expired(s, now)) + cf_scache_session_remove(peer, s); + } +} + +static void cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer *peer) +{ + struct Curl_llist_node *n = Curl_llist_head(&peer->sessions); + while(n) { + struct Curl_ssl_session *s = Curl_node_elem(n); + n = Curl_node_next(n); + if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) + cf_scache_session_remove(peer, s); + } +} + +CURLcode Curl_ssl_scache_create(size_t max_peers, + size_t max_sessions_per_peer, + struct Curl_ssl_scache **pscache) +{ + struct Curl_ssl_scache *scache; + struct Curl_ssl_scache_peer *peers; + size_t i; + + *pscache = NULL; + peers = calloc(max_peers, sizeof(*peers)); + if(!peers) + return CURLE_OUT_OF_MEMORY; + + scache = calloc(1, sizeof(*scache)); + if(!scache) { + free(peers); + return CURLE_OUT_OF_MEMORY; + } + + scache->magic = CURL_SCACHE_MAGIC; + scache->default_lifetime_secs = (24*60*60); /* 1 day */ + scache->peer_count = max_peers; + scache->peers = peers; + scache->age = 1; + for(i = 0; i < scache->peer_count; ++i) { + scache->peers[i].max_sessions = max_sessions_per_peer; + Curl_llist_init(&scache->peers[i].sessions, + cf_ssl_scache_session_ldestroy); + } + + *pscache = scache; + return CURLE_OK; +} + +void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache) +{ + if(scache && GOOD_SCACHE(scache)) { + size_t i; + scache->magic = 0; + for(i = 0; i < scache->peer_count; ++i) { + cf_ssl_scache_clear_peer(&scache->peers[i]); + } + free(scache->peers); + free(scache); + } +} + +/* Lock shared SSL session data */ +void Curl_ssl_scache_lock(struct Curl_easy *data) +{ + if(CURL_SHARE_ssl_scache(data)) + Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); +} + +/* Unlock shared SSL session data */ +void Curl_ssl_scache_unlock(struct Curl_easy *data) +{ + if(CURL_SHARE_ssl_scache(data)) + Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); +} + +static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf, + const char *name, + char *path, + bool *is_local) +{ + if(path && path[0]) { + /* We try to add absolute paths, so that the session key can stay + * valid when used in another process with different CWD. However, + * when a path does not exist, this does not work. Then, we add + * the path as is. */ +#ifdef UNDER_CE + (void)is_local; + return curlx_dyn_addf(buf, ":%s-%s", name, path); +#elif defined(_WIN32) + char abspath[_MAX_PATH]; + if(_fullpath(abspath, path, _MAX_PATH)) + return curlx_dyn_addf(buf, ":%s-%s", name, abspath); + *is_local = TRUE; +#elif defined(HAVE_REALPATH) + if(path[0] != '/') { + char *abspath = realpath(path, NULL); + if(abspath) { + CURLcode r = curlx_dyn_addf(buf, ":%s-%s", name, abspath); + (free)(abspath); /* allocated by libc, free without memdebug */ + return r; + } + *is_local = TRUE; + } +#endif + return curlx_dyn_addf(buf, ":%s-%s", name, path); + } + return CURLE_OK; +} + +static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf, + const char *name, + struct curl_blob *blob) +{ + CURLcode r = CURLE_OK; + if(blob && blob->len) { + unsigned char hash[CURL_SHA256_DIGEST_LENGTH]; + size_t i; + + r = curlx_dyn_addf(buf, ":%s-", name); + if(r) + goto out; + r = Curl_sha256it(hash, blob->data, blob->len); + if(r) + goto out; + for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) { + r = curlx_dyn_addf(buf, "%02x", hash[i]); + if(r) + goto out; + } + } +out: + return r; +} + +#define CURL_SSLS_LOCAL_SUFFIX ":L" +#define CURL_SSLS_GLOBAL_SUFFIX ":G" + +static bool cf_ssl_peer_key_is_global(const char *peer_key) +{ + size_t len = peer_key ? strlen(peer_key) : 0; + return (len > 2) && + (peer_key[len - 1] == 'G') && + (peer_key[len - 2] == ':'); +} + +CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, + const struct ssl_peer *peer, + const char *tls_id, + char **ppeer_key) +{ + struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); + struct dynbuf buf; + size_t key_len; + bool is_local = FALSE; + CURLcode r; + + *ppeer_key = NULL; + curlx_dyn_init(&buf, 10 * 1024); + + r = curlx_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port); + if(r) + goto out; + + switch(peer->transport) { + case TRNSPRT_TCP: + break; + case TRNSPRT_UDP: + r = curlx_dyn_add(&buf, ":UDP"); + break; + case TRNSPRT_QUIC: + r = curlx_dyn_add(&buf, ":QUIC"); + break; + case TRNSPRT_UNIX: + r = curlx_dyn_add(&buf, ":UNIX"); + break; + default: + r = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); + break; + } + if(r) + goto out; + + if(!ssl->verifypeer) { + r = curlx_dyn_add(&buf, ":NO-VRFY-PEER"); + if(r) + goto out; + } + if(!ssl->verifyhost) { + r = curlx_dyn_add(&buf, ":NO-VRFY-HOST"); + if(r) + goto out; + } + if(ssl->verifystatus) { + r = curlx_dyn_add(&buf, ":VRFY-STATUS"); + if(r) + goto out; + } + if(!ssl->verifypeer || !ssl->verifyhost) { + if(cf->conn->bits.conn_to_host) { + r = curlx_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name); + if(r) + goto out; + } + if(cf->conn->bits.conn_to_port) { + r = curlx_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port); + if(r) + goto out; + } + } + + if(ssl->version || ssl->version_max) { + r = curlx_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version, + (ssl->version_max >> 16)); + if(r) + goto out; + } + if(ssl->ssl_options) { + r = curlx_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); + if(r) + goto out; + } + if(ssl->cipher_list) { + r = curlx_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); + if(r) + goto out; + } + if(ssl->cipher_list13) { + r = curlx_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); + if(r) + goto out; + } + if(ssl->curves) { + r = curlx_dyn_addf(&buf, ":CURVES-%s", ssl->curves); + if(r) + goto out; + } + if(ssl->verifypeer) { + r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile, &is_local); + if(r) + goto out; + r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local); + if(r) + goto out; + r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local); + if(r) + goto out; + r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local); + if(r) + goto out; + if(ssl->cert_blob) { + r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob); + if(r) + goto out; + } + if(ssl->ca_info_blob) { + r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob); + if(r) + goto out; + } + if(ssl->issuercert_blob) { + r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob); + if(r) + goto out; + } + } + if(ssl->pinned_key && ssl->pinned_key[0]) { + r = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); + if(r) + goto out; + } + + if(ssl->clientcert && ssl->clientcert[0]) { + r = curlx_dyn_add(&buf, ":CCERT"); + if(r) + goto out; + } +#ifdef USE_TLS_SRP + if(ssl->username || ssl->password) { + r = curlx_dyn_add(&buf, ":SRP-AUTH"); + if(r) + goto out; + } +#endif + + if(!tls_id || !tls_id[0]) { + r = CURLE_FAILED_INIT; + goto out; + } + r = curlx_dyn_addf(&buf, ":IMPL-%s", tls_id); + if(r) + goto out; + + r = curlx_dyn_addf(&buf, is_local ? + CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX); + if(r) + goto out; + + *ppeer_key = curlx_dyn_take(&buf, &key_len); + /* we just added printable char, and dynbuf always null-terminates, no need + * to track length */ + +out: + curlx_dyn_free(&buf); + return r; +} + +static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, + struct ssl_primary_config *conn_config) +{ + if(!conn_config) { + if(peer->clientcert) + return FALSE; +#ifdef USE_TLS_SRP + if(peer->srp_username || peer->srp_password) + return FALSE; +#endif + return TRUE; + } + else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert)) + return FALSE; +#ifdef USE_TLS_SRP + if(Curl_timestrcmp(peer->srp_username, conn_config->username) || + Curl_timestrcmp(peer->srp_password, conn_config->password)) + return FALSE; +#endif + return TRUE; +} + +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 Curl_ssl_scache_peer **ppeer) +{ + size_t i, peer_key_len = 0; + CURLcode result = CURLE_OK; + + *ppeer = NULL; + if(!GOOD_SCACHE(scache)) { + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + CURL_TRC_SSLS(data, "find peer slot for %s among %zu slots", + ssl_peer_key, scache->peer_count); + + /* check for entries with known peer_key */ + for(i = 0; scache && i < scache->peer_count; i++) { + if(scache->peers[i].ssl_peer_key && + strcasecompare(ssl_peer_key, scache->peers[i].ssl_peer_key) && + cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) { + /* yes, we have a cached session for this! */ + *ppeer = &scache->peers[i]; + goto out; + } + } + /* check for entries with HMAC set but no known peer_key */ + for(i = 0; scache && i < scache->peer_count; i++) { + if(!scache->peers[i].ssl_peer_key && + scache->peers[i].hmac_set && + cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) { + /* possible entry with unknown peer_key, check hmac */ + unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH]; + if(!peer_key_len) /* we are lazy */ + peer_key_len = strlen(ssl_peer_key); + result = Curl_hmacit(&Curl_HMAC_SHA256, + scache->peers[i].key_salt, + sizeof(scache->peers[i].key_salt), + (const unsigned char *)ssl_peer_key, + peer_key_len, + my_hmac); + if(result) + goto out; + if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) { + /* remember peer_key for future lookups */ + CURL_TRC_SSLS(data, "peer entry %zu key recovered: %s", + i, ssl_peer_key); + scache->peers[i].ssl_peer_key = strdup(ssl_peer_key); + if(!scache->peers[i].ssl_peer_key) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_ssl_cache_peer_update(&scache->peers[i]); + *ppeer = &scache->peers[i]; + goto out; + } + } + } + CURL_TRC_SSLS(data, "peer not found for %s", ssl_peer_key); +out: + return result; +} + +static struct Curl_ssl_scache_peer * +cf_ssl_get_free_peer(struct Curl_ssl_scache *scache) +{ + struct Curl_ssl_scache_peer *peer = NULL; + size_t i; + + /* find empty or oldest peer */ + for(i = 0; i < scache->peer_count; ++i) { + /* free peer entry? */ + if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) { + peer = &scache->peers[i]; + break; + } + /* peer without sessions and obj */ + if(!scache->peers[i].sobj && + !Curl_llist_count(&scache->peers[i].sessions)) { + peer = &scache->peers[i]; + break; + } + /* remember "oldest" peer */ + if(!peer || (scache->peers[i].age < peer->age)) { + peer = &scache->peers[i]; + } + } + DEBUGASSERT(peer); + if(peer) + cf_ssl_scache_clear_peer(peer); + return 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 Curl_ssl_scache_peer **ppeer) +{ + struct Curl_ssl_scache_peer *peer = NULL; + CURLcode result = CURLE_OK; + + *ppeer = NULL; + if(ssl_peer_key) { + result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config, + &peer); + if(result || !scache->peer_count) + return result; + } + + if(peer) { + *ppeer = peer; + return CURLE_OK; + } + + peer = cf_ssl_get_free_peer(scache); + if(peer) { + const char *ccert = conn_config ? conn_config->clientcert : NULL; + const char *username = NULL, *password = NULL; +#ifdef USE_TLS_SRP + username = conn_config ? conn_config->username : NULL; + password = conn_config ? conn_config->password : NULL; +#endif + result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert, + username, password, NULL, NULL); + if(result) + goto out; + /* all ready */ + *ppeer = peer; + result = CURLE_OK; + } + +out: + if(result) { + cf_ssl_scache_clear_peer(peer); + } + return result; +} + +static void cf_scache_peer_add_session(struct Curl_ssl_scache_peer *peer, + struct Curl_ssl_session *s, + curl_off_t now) +{ + /* A session not from TLSv1.3 replaces all other. */ + if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) { + Curl_llist_destroy(&peer->sessions, NULL); + Curl_llist_append(&peer->sessions, s, &s->list); + } + else { + /* Expire existing, append, trim from head to obey max_sessions */ + cf_scache_peer_remove_expired(peer, now); + cf_scache_peer_remove_non13(peer); + Curl_llist_append(&peer->sessions, s, &s->list); + while(Curl_llist_count(&peer->sessions) > peer->max_sessions) { + Curl_node_remove(Curl_llist_head(&peer->sessions)); + } + } +} + +static CURLcode cf_scache_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct Curl_ssl_scache *scache, + const char *ssl_peer_key, + 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); + CURLcode result = CURLE_OUT_OF_MEMORY; + curl_off_t now = (curl_off_t)time(NULL); + curl_off_t max_lifetime; + + if(!scache || !scache->peer_count) { + Curl_ssl_session_destroy(s); + return CURLE_OK; + } + + if(s->valid_until <= 0) + s->valid_until = now + scache->default_lifetime_secs; + + max_lifetime = (s->ietf_tls_id == CURL_IETF_PROTO_TLS1_3) ? + CURL_SCACHE_MAX_13_LIFETIME_SEC : + CURL_SCACHE_MAX_12_LIFETIME_SEC; + if(s->valid_until > (now + max_lifetime)) + s->valid_until = now + max_lifetime; + + if(cf_scache_session_expired(s, now)) { + CURL_TRC_SSLS(data, "add, session already expired"); + Curl_ssl_session_destroy(s); + return CURLE_OK; + } + + result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer); + if(result || !peer) { + CURL_TRC_SSLS(data, "unable to add scache peer: %d", result); + Curl_ssl_session_destroy(s); + goto out; + } + + cf_scache_peer_add_session(peer, s, now); + +out: + if(result) { + failf(data, "[SCACHE] failed to add session for %s, error=%d", + ssl_peer_key, result); + } + else + CURL_TRC_SSLS(data, "added session for %s [proto=0x%x, " + "valid_secs=%" FMT_OFF_T ", alpn=%s, earlydata=%zu, " + "quic_tp=%s], peer has %zu sessions now", + ssl_peer_key, s->ietf_tls_id, s->valid_until - now, + s->alpn, s->earlydata_max, s->quic_tp ? "yes" : "no", + peer ? Curl_llist_count(&peer->sessions) : 0); + return result; +} + +CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + 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); + CURLcode result; + DEBUGASSERT(ssl_config); + + if(!scache || !ssl_config->primary.cache_session) { + Curl_ssl_session_destroy(s); + return CURLE_OK; + } + + Curl_ssl_scache_lock(data); + result = cf_scache_add_session(cf, data, scache, ssl_peer_key, s); + Curl_ssl_scache_unlock(data); + return result; +} + +void Curl_ssl_scache_return(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + struct Curl_ssl_session *s) +{ + /* See RFC 8446 C.4: + * "Clients SHOULD NOT reuse a ticket for multiple connections." */ + if(s && s->ietf_tls_id < 0x304) + (void)Curl_ssl_scache_put(cf, data, ssl_peer_key, s); + else + Curl_ssl_session_destroy(s); +} + +CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + 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 Curl_ssl_scache_peer *peer = NULL; + struct Curl_llist_node *n; + struct Curl_ssl_session *s = NULL; + CURLcode result; + + *ps = NULL; + if(!scache) + return CURLE_OK; + + Curl_ssl_scache_lock(data); + result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config, + &peer); + if(!result && peer) { + cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL)); + n = Curl_llist_head(&peer->sessions); + if(n) { + s = Curl_node_take_elem(n); + (scache->age)++; /* increase general age */ + peer->age = scache->age; /* set this as used in this age */ + } + } + Curl_ssl_scache_unlock(data); + if(s) { + *ps = s; + CURL_TRC_SSLS(data, "took session for %s [proto=0x%x, " + "alpn=%s, earlydata=%zu, quic_tp=%s], %zu sessions remain", + ssl_peer_key, s->ietf_tls_id, s->alpn, + s->earlydata_max, s->quic_tp ? "yes" : "no", + Curl_llist_count(&peer->sessions)); + } + else { + CURL_TRC_SSLS(data, "no cached session for %s", ssl_peer_key); + } + return result; +} + +CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + void *sobj, + Curl_ssl_scache_obj_dtor *sobj_free) +{ + struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_ssl_scache_peer *peer = NULL; + CURLcode result; + + DEBUGASSERT(sobj); + DEBUGASSERT(sobj_free); + + if(!scache) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + + result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer); + if(result || !peer) { + CURL_TRC_SSLS(data, "unable to add scache peer: %d", result); + goto out; + } + + cf_ssl_scache_peer_set_obj(peer, sobj, sobj_free); + sobj = NULL; /* peer took ownership */ + +out: + if(sobj && sobj_free) + sobj_free(sobj); + return result; +} + +void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, + struct Curl_easy *data, + 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 Curl_ssl_scache_peer *peer = NULL; + CURLcode result; + void *sobj; + + if(!scache) + return NULL; + + result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config, + &peer); + if(result) + return NULL; + + sobj = peer ? peer->sobj : NULL; + + CURL_TRC_SSLS(data, "%s cached session for '%s'", + sobj ? "Found" : "No", ssl_peer_key); + return sobj; +} + +void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, + struct Curl_easy *data, + 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 Curl_ssl_scache_peer *peer = NULL; + CURLcode result; + + (void)cf; + if(!scache) + return; + + Curl_ssl_scache_lock(data); + result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config, + &peer); + if(!result && peer) + cf_ssl_scache_clear_peer(peer); + Curl_ssl_scache_unlock(data); +} + +#ifdef USE_SSLS_EXPORT + +#define CURL_SSL_TICKET_MAX (16*1024) + +static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer) +{ + CURLcode result; + + DEBUGASSERT(peer); + if(!peer->ssl_peer_key) + return CURLE_BAD_FUNCTION_ARGUMENT; + + result = Curl_rand(NULL, peer->key_salt, sizeof(peer->key_salt)); + if(result) + return result; + + result = Curl_hmacit(&Curl_HMAC_SHA256, + peer->key_salt, sizeof(peer->key_salt), + (const unsigned char *)peer->ssl_peer_key, + strlen(peer->ssl_peer_key), + peer->key_hmac); + if(!result) + peer->hmac_set = TRUE; + return result; +} + +static CURLcode +cf_ssl_find_peer_by_hmac(struct Curl_ssl_scache *scache, + const unsigned char *salt, + const unsigned char *hmac, + struct Curl_ssl_scache_peer **ppeer) +{ + size_t i; + CURLcode result = CURLE_OK; + + *ppeer = NULL; + if(!GOOD_SCACHE(scache)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* look for an entry that matches salt+hmac exactly or has a known + * ssl_peer_key which salt+hmac's to the same. */ + for(i = 0; scache && i < scache->peer_count; i++) { + struct Curl_ssl_scache_peer *peer = &scache->peers[i]; + if(!cf_ssl_scache_match_auth(peer, NULL)) + continue; + if(scache->peers[i].hmac_set && + !memcmp(peer->key_salt, salt, sizeof(peer->key_salt)) && + !memcmp(peer->key_hmac, hmac, sizeof(peer->key_hmac))) { + /* found exact match, return */ + *ppeer = peer; + goto out; + } + else if(peer->ssl_peer_key) { + unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH]; + /* compute hmac for the passed salt */ + result = Curl_hmacit(&Curl_HMAC_SHA256, + salt, sizeof(peer->key_salt), + (const unsigned char *)peer->ssl_peer_key, + strlen(peer->ssl_peer_key), + my_hmac); + if(result) + goto out; + if(!memcmp(my_hmac, hmac, sizeof(my_hmac))) { + /* cryptohash match, take over salt+hmac if no set and return */ + if(!peer->hmac_set) { + memcpy(peer->key_salt, salt, sizeof(peer->key_salt)); + memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac)); + peer->hmac_set = TRUE; + } + *ppeer = peer; + goto out; + } + } + } +out: + return result; +} + +CURLcode Curl_ssl_session_import(struct Curl_easy *data, + const char *ssl_peer_key, + const unsigned char *shmac, size_t shmac_len, + const void *sdata, size_t sdata_len) +{ + struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); + struct Curl_ssl_scache_peer *peer = NULL; + struct Curl_ssl_session *s = NULL; + bool locked = FALSE; + CURLcode r; + + if(!scache) { + r = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(!ssl_peer_key && (!shmac || !shmac_len)) { + r = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + + r = Curl_ssl_session_unpack(data, sdata, sdata_len, &s); + if(r) + goto out; + + Curl_ssl_scache_lock(data); + locked = TRUE; + + if(ssl_peer_key) { + r = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer); + if(r) + goto out; + } + else if(shmac_len != (sizeof(peer->key_salt) + sizeof(peer->key_hmac))) { + /* Either salt+hmac was garbled by caller or is from a curl version + * that does things differently */ + r = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + else { + const unsigned char *salt = shmac; + const unsigned char *hmac = shmac + sizeof(peer->key_salt); + + r = cf_ssl_find_peer_by_hmac(scache, salt, hmac, &peer); + if(r) + goto out; + if(!peer) { + peer = cf_ssl_get_free_peer(scache); + if(peer) { + r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL, + NULL, NULL, salt, hmac); + if(r) + goto out; + } + } + } + + if(peer) { + cf_scache_peer_add_session(peer, s, time(NULL)); + s = NULL; /* peer is now owner */ + CURL_TRC_SSLS(data, "successfully imported ticket for peer %s, now " + "with %zu tickets", + peer->ssl_peer_key ? peer->ssl_peer_key : "without key", + Curl_llist_count(&peer->sessions)); + } + +out: + if(locked) + Curl_ssl_scache_unlock(data); + Curl_ssl_session_destroy(s); + return r; +} + +CURLcode Curl_ssl_session_export(struct Curl_easy *data, + curl_ssls_export_cb *export_fn, + void *userptr) +{ + struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); + struct Curl_ssl_scache_peer *peer; + struct dynbuf sbuf, hbuf; + struct Curl_llist_node *n; + size_t i, npeers = 0, ntickets = 0; + curl_off_t now = time(NULL); + CURLcode r = CURLE_OK; + + if(!export_fn) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(!scache) + return CURLE_OK; + + Curl_ssl_scache_lock(data); + + curlx_dyn_init(&hbuf, (CURL_SHA256_DIGEST_LENGTH * 2) + 1); + curlx_dyn_init(&sbuf, CURL_SSL_TICKET_MAX); + + for(i = 0; scache && i < scache->peer_count; i++) { + peer = &scache->peers[i]; + if(!peer->ssl_peer_key && !peer->hmac_set) + continue; /* skip free entry */ + if(!peer->exportable) + continue; + + curlx_dyn_reset(&hbuf); + cf_scache_peer_remove_expired(peer, now); + n = Curl_llist_head(&peer->sessions); + if(n) + ++npeers; + while(n) { + struct Curl_ssl_session *s = Curl_node_elem(n); + if(!peer->hmac_set) { + r = cf_ssl_scache_peer_set_hmac(peer); + if(r) + goto out; + } + if(!curlx_dyn_len(&hbuf)) { + r = curlx_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt)); + if(r) + goto out; + r = curlx_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac)); + if(r) + goto out; + } + curlx_dyn_reset(&sbuf); + r = Curl_ssl_session_pack(data, s, &sbuf); + if(r) + goto out; + + r = export_fn(data, userptr, peer->ssl_peer_key, + curlx_dyn_uptr(&hbuf), curlx_dyn_len(&hbuf), + curlx_dyn_uptr(&sbuf), curlx_dyn_len(&sbuf), + s->valid_until, s->ietf_tls_id, + s->alpn, s->earlydata_max); + if(r) + goto out; + ++ntickets; + n = Curl_node_next(n); + } + + } + r = CURLE_OK; + CURL_TRC_SSLS(data, "exported %zu session tickets for %zu peers", + ntickets, npeers); + +out: + Curl_ssl_scache_unlock(data); + curlx_dyn_free(&hbuf); + curlx_dyn_free(&sbuf); + return r; +} + +#endif /* USE_SSLS_EXPORT */ + +#endif /* USE_SSL */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.h b/Utilities/cmcurl/lib/vtls/vtls_scache.h new file mode 100644 index 00000000000..eef50805e4e --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/vtls_scache.h @@ -0,0 +1,217 @@ +#ifndef HEADER_CURL_VTLS_SCACHE_H +#define HEADER_CURL_VTLS_SCACHE_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" +#include "../cfilters.h" +#include "../urldata.h" + +#ifdef USE_SSL + +struct Curl_cfilter; +struct Curl_easy; +struct Curl_ssl_scache; +struct Curl_ssl_session; +struct ssl_peer; + +/* RFC 8446 (TLSv1.3) restrict lifetime to one week max, for + * other, less secure versions, we restrict it to a day */ +#define CURL_SCACHE_MAX_13_LIFETIME_SEC (60*60*24*7) +#define CURL_SCACHE_MAX_12_LIFETIME_SEC (60*60*24) + +/* Create a session cache for up to max_peers endpoints with a total + * of up to max_sessions SSL sessions per peer */ +CURLcode Curl_ssl_scache_create(size_t max_peers, + size_t max_sessions_per_peer, + struct Curl_ssl_scache **pscache); + +void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache); + +/* Create a key from peer and TLS configuration information that is + * unique for how the connection filter wants to establish a TLS + * connection to the peer. + * If the filter is a TLS proxy filter, it will use the proxy relevant + * information. + * @param cf the connection filter wanting to use it + * @param peer the peer the filter wants to talk to + * @param tls_id identifier of TLS implementation for sessions. Should + * include full version if session data from other versions + * is to be avoided. + * @param ppeer_key on successful return, the key generated + */ +CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, + const struct ssl_peer *peer, + const char *tls_id, + char **ppeer_key); + +/* Lock session cache mutex. + * Call this before calling other Curl_ssl_*session* functions + * Caller should unlock this mutex as soon as possible, as it may block + * other SSL connection from making progress. + * The purpose of explicitly locking SSL session cache data is to allow + * individual SSL engines to manage session lifetime in their specific way. + */ +void Curl_ssl_scache_lock(struct Curl_easy *data); + +/* Unlock session cache mutex */ +void Curl_ssl_scache_unlock(struct Curl_easy *data); + +/* Get TLS session object from the cache for the ssl_peer_ey. + * scache mutex must be locked (see Curl_ssl_scache_lock). + * Caller must make sure that the ownership of returned session object + * is properly taken (e.g. its refcount is incremented + * under scache mutex). + * @param cf the connection filter wanting to use it + * @param data the transfer involved + * @param ssl_peer_key the key for lookup + * @retval sobj the object for the peer key or NULL + */ +void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key); + +typedef void Curl_ssl_scache_obj_dtor(void *sobj); + +/* Add a TLS session related object to the cache. + * Replaces an existing object with the same peer_key. + * scache mutex must be locked (see Curl_ssl_scache_lock). + * Call takes ownership of `sobj`, using `sobj_dtor_cb` + * to deallocate it. Is called in all outcomes, either right away or + * later when the session cache is cleaned up. + * Caller must ensure that it has properly shared ownership of `sobj` + * with cache (e.g. incrementing refcount on success) + * @param cf the connection filter wanting to use it + * @param data the transfer involved + * @param ssl_peer_key the key for lookup + * @param sobj the TLS session object + * @param sobj_free_cb callback to free the session objectt + */ +CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + void *sobj, + Curl_ssl_scache_obj_dtor *sobj_dtor_cb); + +/* All about an SSL session ticket */ +struct Curl_ssl_session { + const void *sdata; /* session ticket data, plain bytes */ + size_t sdata_len; /* number of bytes in sdata */ + curl_off_t valid_until; /* seconds since EPOCH until ticket expires */ + int ietf_tls_id; /* TLS protocol identifier negotiated */ + char *alpn; /* APLN TLS negotiated protocol string */ + size_t earlydata_max; /* max 0-RTT data supported by peer */ + const unsigned char *quic_tp; /* Optional QUIC transport param bytes */ + size_t quic_tp_len; /* number of bytes in quic_tp */ + struct Curl_llist_node list; /* internal storage handling */ +}; + +/* Create a `session` instance. Does NOT need locking. + * Takes ownership of `sdata` and `sobj` regardless of return code. + * @param sdata bytes of SSL session data or NULL (sobj then required) + * @param sdata_len amount of session data bytes + * @param ietf_tls_id IETF protocol version, e.g. 0x304 for TLSv1.3 + * @param alpn ALPN protocol selected or NULL + * @param valid_until seconds since EPOCH when session expires, pass 0 + * in case this is not known. + * @param psession on return the scached session instance created + */ +CURLcode +Curl_ssl_session_create(void *sdata, size_t sdata_len, + int ietf_tls_id, const char *alpn, + curl_off_t valid_until, + size_t earlydata_max, + struct Curl_ssl_session **psession); + +/* Variation of session creation with quic transport parameter bytes, + * Takes ownership of `quic_tp` regardless of return code. */ +CURLcode +Curl_ssl_session_create2(void *sdata, size_t sdata_len, + int ietf_tls_id, const char *alpn, + curl_off_t valid_until, + size_t earlydata_max, + unsigned char *quic_tp, size_t quic_tp_len, + struct Curl_ssl_session **psession); + +/* Destroy a `session` instance. Can be called with NULL. + * Does NOT need locking. */ +void Curl_ssl_session_destroy(struct Curl_ssl_session *s); + +/* Put the scache session into the cache. Does NOT need locking. + * Call takes ownership of `s` in all outcomes. + * @param cf the connection filter wanting to use it + * @param data the transfer involved + * @param ssl_peer_key the key for lookup + * @param s the scache session object + */ +CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + struct Curl_ssl_session *s); + +/* Take a matching scache session from the cache. Does NOT need locking. + * @param cf the connection filter wanting to use it + * @param data the transfer involved + * @param ssl_peer_key the key for lookup + * @param s on return, the scache session object or NULL + */ +CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + struct Curl_ssl_session **ps); + +/* Return a taken scache session to the cache. Does NOT need locking. + * Depending on TLS version and other criteria, it may cache it again + * or destroy it. Maybe called with a NULL session. + */ +void Curl_ssl_scache_return(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + struct Curl_ssl_session *s); + +/* Remove all sessions and obj for the peer_key. Does NOT need locking. */ +void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key); + +#ifdef USE_SSLS_EXPORT + +CURLcode Curl_ssl_session_import(struct Curl_easy *data, + const char *ssl_peer_key, + const unsigned char *shmac, size_t shmac_len, + const void *sdata, size_t sdata_len); + +CURLcode Curl_ssl_session_export(struct Curl_easy *data, + curl_ssls_export_cb *export_fn, + void *userptr); + +#endif /* USE_SSLS_EXPORT */ + +#else /* USE_SSL */ + +#define Curl_ssl_scache_create(x,y,z) ((void)x, CURLE_OK) +#define Curl_ssl_scache_destroy(x) do {} while(0) + +#endif /* USE_SSL (else) */ + +#endif /* HEADER_CURL_VTLS_SCACHE_H */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_spack.c b/Utilities/cmcurl/lib/vtls/vtls_spack.c new file mode 100644 index 00000000000..152fad7eb30 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/vtls_spack.c @@ -0,0 +1,346 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef USE_SSLS_EXPORT + +#include "../urldata.h" +#include "../curl_trc.h" +#include "vtls_scache.h" +#include "vtls_spack.h" +#include "../strdup.h" + +/* The last #include files should be: */ +#include "../curl_memory.h" +#include "../memdebug.h" + +#ifdef _MSC_VER +#if _MSC_VER >= 1600 +#include +#else +typedef unsigned char uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#endif +#endif /* _MSC_VER */ + +#ifndef UINT16_MAX +#define UINT16_MAX 0xffff +#endif +#ifndef UINT32_MAX +#define UINT32_MAX 0xffffffff +#endif + +#define CURL_SPACK_VERSION 0x01 +#define CURL_SPACK_IETF_ID 0x02 +#define CURL_SPACK_VALID_UNTIL 0x03 +#define CURL_SPACK_TICKET 0x04 +#define CURL_SPACK_ALPN 0x05 +#define CURL_SPACK_EARLYDATA 0x06 +#define CURL_SPACK_QUICTP 0x07 + +static CURLcode spack_enc8(struct dynbuf *buf, uint8_t b) +{ + return curlx_dyn_addn(buf, &b, 1); +} + +static CURLcode +spack_dec8(uint8_t *val, const uint8_t **src, const uint8_t *end) +{ + if(end - *src < 1) + return CURLE_READ_ERROR; + *val = **src; + *src += 1; + return CURLE_OK; +} + +static CURLcode spack_enc16(struct dynbuf *buf, uint16_t val) +{ + uint8_t nval[2]; + nval[0] = (uint8_t)(val >> 8); + nval[1] = (uint8_t)val; + return curlx_dyn_addn(buf, nval, sizeof(nval)); +} + +static CURLcode +spack_dec16(uint16_t *val, const uint8_t **src, const uint8_t *end) +{ + if(end - *src < 2) + return CURLE_READ_ERROR; + *val = (uint16_t)((*src)[0] << 8 | (*src)[1]); + *src += 2; + return CURLE_OK; +} + +static CURLcode spack_enc32(struct dynbuf *buf, uint32_t val) +{ + uint8_t nval[4]; + nval[0] = (uint8_t)(val >> 24); + nval[1] = (uint8_t)(val >> 16); + nval[2] = (uint8_t)(val >> 8); + nval[3] = (uint8_t)val; + return curlx_dyn_addn(buf, nval, sizeof(nval)); +} + +static CURLcode +spack_dec32(uint32_t *val, const uint8_t **src, const uint8_t *end) +{ + if(end - *src < 4) + return CURLE_READ_ERROR; + *val = (uint32_t)(*src)[0] << 24 | (uint32_t)(*src)[1] << 16 | + (uint32_t)(*src)[2] << 8 | (*src)[3]; + *src += 4; + return CURLE_OK; +} + +static CURLcode spack_enc64(struct dynbuf *buf, uint64_t val) +{ + uint8_t nval[8]; + nval[0] = (uint8_t)(val >> 56); + nval[1] = (uint8_t)(val >> 48); + nval[2] = (uint8_t)(val >> 40); + nval[3] = (uint8_t)(val >> 32); \ + nval[4] = (uint8_t)(val >> 24); + nval[5] = (uint8_t)(val >> 16); + nval[6] = (uint8_t)(val >> 8); + nval[7] = (uint8_t)val; + return curlx_dyn_addn(buf, nval, sizeof(nval)); +} + +static CURLcode +spack_dec64(uint64_t *val, const uint8_t **src, const uint8_t *end) +{ + if(end - *src < 8) + return CURLE_READ_ERROR; + *val = (uint64_t)(*src)[0] << 56 | (uint64_t)(*src)[1] << 48 | + (uint64_t)(*src)[2] << 40 | (uint64_t)(*src)[3] << 32 | + (uint64_t)(*src)[4] << 24 | (uint64_t)(*src)[5] << 16 | + (uint64_t)(*src)[6] << 8 | (*src)[7]; + *src += 8; + return CURLE_OK; +} + +static CURLcode spack_encstr16(struct dynbuf *buf, const char *s) +{ + size_t slen = strlen(s); + CURLcode r; + if(slen > UINT16_MAX) + return CURLE_BAD_FUNCTION_ARGUMENT; + r = spack_enc16(buf, (uint16_t)slen); + if(!r) { + r = curlx_dyn_addn(buf, s, slen); + } + return r; +} + +static CURLcode +spack_decstr16(char **val, const uint8_t **src, const uint8_t *end) +{ + uint16_t slen; + CURLcode r; + + *val = NULL; + r = spack_dec16(&slen, src, end); + if(r) + return r; + if(end - *src < slen) + return CURLE_READ_ERROR; + *val = Curl_memdup0((const char *)(*src), slen); + *src += slen; + return *val ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} + +static CURLcode spack_encdata16(struct dynbuf *buf, + const uint8_t *data, size_t data_len) +{ + CURLcode r; + if(data_len > UINT16_MAX) + return CURLE_BAD_FUNCTION_ARGUMENT; + r = spack_enc16(buf, (uint16_t)data_len); + if(!r) { + r = curlx_dyn_addn(buf, data, data_len); + } + return r; +} + +static CURLcode +spack_decdata16(uint8_t **val, size_t *val_len, + const uint8_t **src, const uint8_t *end) +{ + uint16_t data_len; + CURLcode r; + + *val = NULL; + r = spack_dec16(&data_len, src, end); + if(r) + return r; + if(end - *src < data_len) + return CURLE_READ_ERROR; + *val = Curl_memdup0((const char *)(*src), data_len); + *val_len = data_len; + *src += data_len; + return *val ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} + +CURLcode Curl_ssl_session_pack(struct Curl_easy *data, + struct Curl_ssl_session *s, + struct dynbuf *buf) +{ + CURLcode r; + DEBUGASSERT(s->sdata); + DEBUGASSERT(s->sdata_len); + + if(s->valid_until < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + + r = spack_enc8(buf, CURL_SPACK_VERSION); + if(!r) + r = spack_enc8(buf, CURL_SPACK_TICKET); + if(!r) + r = spack_encdata16(buf, s->sdata, s->sdata_len); + if(!r) + r = spack_enc8(buf, CURL_SPACK_IETF_ID); + if(!r) + r = spack_enc16(buf, (uint16_t)s->ietf_tls_id); + if(!r) + r = spack_enc8(buf, CURL_SPACK_VALID_UNTIL); + if(!r) + r = spack_enc64(buf, (uint64_t)s->valid_until); + if(!r && s->alpn) { + r = spack_enc8(buf, CURL_SPACK_ALPN); + if(!r) + r = spack_encstr16(buf, s->alpn); + } + if(!r && s->earlydata_max) { + if(s->earlydata_max > UINT32_MAX) + r = CURLE_BAD_FUNCTION_ARGUMENT; + if(!r) + r = spack_enc8(buf, CURL_SPACK_EARLYDATA); + if(!r) + r = spack_enc32(buf, (uint32_t)s->earlydata_max); + } + if(!r && s->quic_tp && s->quic_tp_len) { + r = spack_enc8(buf, CURL_SPACK_QUICTP); + if(!r) + r = spack_encdata16(buf, s->quic_tp, s->quic_tp_len); + } + + if(r) + CURL_TRC_SSLS(data, "error packing data: %d", r); + return r; +} + +CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, + const void *bufv, size_t buflen, + struct Curl_ssl_session **ps) +{ + struct Curl_ssl_session *s = NULL; + const unsigned char *buf = (const unsigned char *)bufv; + const unsigned char *end = buf + buflen; + uint8_t val8, *pval8; + uint16_t val16; + uint32_t val32; + uint64_t val64; + CURLcode r; + + DEBUGASSERT(buf); + DEBUGASSERT(buflen); + *ps = NULL; + + r = spack_dec8(&val8, &buf, end); + if(r) + goto out; + if(val8 != CURL_SPACK_VERSION) { + r = CURLE_READ_ERROR; + goto out; + } + + s = calloc(1, sizeof(*s)); + if(!s) { + r = CURLE_OUT_OF_MEMORY; + goto out; + } + + while(buf < end) { + r = spack_dec8(&val8, &buf, end); + if(r) + goto out; + + switch(val8) { + case CURL_SPACK_ALPN: + r = spack_decstr16(&s->alpn, &buf, end); + if(r) + goto out; + break; + case CURL_SPACK_EARLYDATA: + r = spack_dec32(&val32, &buf, end); + if(r) + goto out; + s->earlydata_max = val32; + break; + case CURL_SPACK_IETF_ID: + r = spack_dec16(&val16, &buf, end); + if(r) + goto out; + s->ietf_tls_id = val16; + break; + case CURL_SPACK_QUICTP: { + r = spack_decdata16(&pval8, &s->quic_tp_len, &buf, end); + if(r) + goto out; + s->quic_tp = pval8; + break; + } + case CURL_SPACK_TICKET: { + r = spack_decdata16(&pval8, &s->sdata_len, &buf, end); + if(r) + goto out; + s->sdata = pval8; + break; + } + case CURL_SPACK_VALID_UNTIL: + r = spack_dec64(&val64, &buf, end); + if(r) + goto out; + s->valid_until = (curl_off_t)val64; + break; + default: /* unknown tag */ + r = CURLE_READ_ERROR; + goto out; + } + } + +out: + if(r) { + CURL_TRC_SSLS(data, "error unpacking data: %d", r); + Curl_ssl_session_destroy(s); + } + else + *ps = s; + return r; +} + +#endif /* USE_SSLS_EXPORT */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_spack.h b/Utilities/cmcurl/lib/vtls/vtls_spack.h new file mode 100644 index 00000000000..4cdabae30e6 --- /dev/null +++ b/Utilities/cmcurl/lib/vtls/vtls_spack.h @@ -0,0 +1,43 @@ +#ifndef HEADER_CURL_VTLS_SPACK_H +#define HEADER_CURL_VTLS_SPACK_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" + +#ifdef USE_SSLS_EXPORT + +struct dynbuf; +struct Curl_ssl_session; + +CURLcode Curl_ssl_session_pack(struct Curl_easy *data, + struct Curl_ssl_session *s, + struct dynbuf *buf); + +CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, + const void *bufv, size_t buflen, + struct Curl_ssl_session **ps); + +#endif /* USE_SSLS_EXPORT */ + +#endif /* HEADER_CURL_VTLS_SPACK_H */ diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c index 292872878cf..9c6f5182605 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.c +++ b/Utilities/cmcurl/lib/vtls/wolfssl.c @@ -28,13 +28,18 @@ * */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_WOLFSSL #define WOLFSSL_OPTIONS_IGNORE_SYS -#include #include +#include + + +#if LIBWOLFSSL_VERSION_HEX < 0x03004006 /* wolfSSL 3.4.6 (2015) */ +#error "wolfSSL version should be at least 3.4.6" +#endif /* To determine what functions are available we rely on one or both of: - the user's options.h generated by wolfSSL @@ -51,28 +56,34 @@ #include -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" #include "vtls.h" #include "vtls_int.h" +#include "vtls_scache.h" #include "keylog.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" +#include "../parsedate.h" +#include "../connect.h" /* for the connect timeout */ +#include "../progress.h" +#include "../select.h" +#include "../strcase.h" +#include "../strdup.h" #include "x509asn1.h" -#include "curl_printf.h" -#include "multiif.h" +#include "../curl_printf.h" +#include "../multiif.h" -#include #include #include #include "wolfssl.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" + +#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG +#define USE_ECH_WOLFSSL +#endif /* KEEP_PEER_CERT is a product of the presence of build time symbol OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is @@ -85,32 +96,46 @@ #endif #endif -#if defined(HAVE_WOLFSSL_FULL_BIO) && HAVE_WOLFSSL_FULL_BIO +#ifdef HAVE_WOLFSSL_BIO_NEW #define USE_BIO_CHAIN -#else +#ifdef HAVE_WOLFSSL_BIO_SET_SHUTDOWN +#define USE_FULL_BIO +#else /* HAVE_WOLFSSL_BIO_SET_SHUTDOWN */ +#undef USE_FULL_BIO +#endif +/* wolfSSL 5.7.4 and older do not have these symbols, but only the + * OpenSSL ones. */ +#ifndef WOLFSSL_BIO_CTRL_GET_CLOSE +#define WOLFSSL_BIO_CTRL_GET_CLOSE BIO_CTRL_GET_CLOSE +#define WOLFSSL_BIO_CTRL_SET_CLOSE BIO_CTRL_SET_CLOSE +#define WOLFSSL_BIO_CTRL_FLUSH BIO_CTRL_FLUSH +#define WOLFSSL_BIO_CTRL_DUP BIO_CTRL_DUP +#define wolfSSL_BIO_set_retry_write BIO_set_retry_write +#define wolfSSL_BIO_set_retry_read BIO_set_retry_read +#endif /* !WOLFSSL_BIO_CTRL_GET_CLOSE */ + +#else /* HAVE_WOLFSSL_BIO_NEW */ #undef USE_BIO_CHAIN #endif -struct ssl_backend_data { - SSL_CTX* ctx; - SSL* handle; - CURLcode io_result; /* result of last BIO cfilter operation */ -}; +static CURLcode wssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); #ifdef OPENSSL_EXTRA /* * Availability note: * The TLS 1.3 secret callback (wolfSSL_set_tls13_secret_cb) was added in - * WolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that + * wolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that * option is not set, then TLS 1.3 will not be logged. * For TLS 1.2 and before, we use wolfSSL_get_keys(). - * SSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA + * wolfSSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA * (--enable-opensslextra or --enable-all). */ #if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) static int -wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, - int secretSz, void *ctx) +wssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, + int secretSz, void *ctx) { const char *label; unsigned char client_random[SSL3_RANDOM_SIZE]; @@ -146,7 +171,7 @@ wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, return 0; } - if(SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE) == 0) { + if(wolfSSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE) == 0) { /* Should never happen as wolfSSL_KeepArrays() was called before. */ return 0; } @@ -156,8 +181,7 @@ wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, } #endif /* defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) */ -static void -wolfssl_log_tls12_secret(SSL *ssl) +static void wssl_log_tls12_secret(WOLFSSL *ssl) { unsigned char *ms, *sr, *cr; unsigned int msLen, srLen, crLen, i, x = 0; @@ -180,7 +204,8 @@ wolfssl_log_tls12_secret(SSL *ssl) } #endif - if(SSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != SSL_SUCCESS) { + if(wolfSSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != + WOLFSSL_SUCCESS) { return; } @@ -198,77 +223,87 @@ wolfssl_log_tls12_secret(SSL *ssl) } #endif /* OPENSSL_EXTRA */ -static int do_file_type(const char *type) +static int wssl_do_file_type(const char *type) { if(!type || !type[0]) - return SSL_FILETYPE_PEM; + return WOLFSSL_FILETYPE_PEM; if(strcasecompare(type, "PEM")) - return SSL_FILETYPE_PEM; + return WOLFSSL_FILETYPE_PEM; if(strcasecompare(type, "DER")) - return SSL_FILETYPE_ASN1; + return WOLFSSL_FILETYPE_ASN1; return -1; } -#ifdef HAVE_LIBOQS +#ifdef WOLFSSL_HAVE_KYBER struct group_name_map { const word16 group; const char *name; }; static const struct group_name_map gnm[] = { - { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" }, - { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" }, - { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" }, - { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" }, - { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" }, - { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" }, + { WOLFSSL_ML_KEM_512, "ML_KEM_512" }, + { WOLFSSL_ML_KEM_768, "ML_KEM_768" }, + { WOLFSSL_ML_KEM_1024, "ML_KEM_1024" }, + { WOLFSSL_P256_ML_KEM_512, "P256_ML_KEM_512" }, + { WOLFSSL_P384_ML_KEM_768, "P384_ML_KEM_768" }, + { WOLFSSL_P521_ML_KEM_1024, "P521_ML_KEM_1024" }, { 0, NULL } }; #endif #ifdef USE_BIO_CHAIN -static int bio_cf_create(WOLFSSL_BIO *bio) +static int wssl_bio_cf_create(WOLFSSL_BIO *bio) { +#ifdef USE_FULL_BIO wolfSSL_BIO_set_shutdown(bio, 1); - wolfSSL_BIO_set_init(bio, 1); +#endif wolfSSL_BIO_set_data(bio, NULL); return 1; } -static int bio_cf_destroy(WOLFSSL_BIO *bio) +static int wssl_bio_cf_destroy(WOLFSSL_BIO *bio) { if(!bio) return 0; return 1; } -static long bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) +static long wssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) { - struct Curl_cfilter *cf = BIO_get_data(bio); + struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); long ret = 1; (void)cf; (void)ptr; + (void)num; switch(cmd) { - case BIO_CTRL_GET_CLOSE: + case WOLFSSL_BIO_CTRL_GET_CLOSE: +#ifdef USE_FULL_BIO ret = (long)wolfSSL_BIO_get_shutdown(bio); +#else + ret = 0; +#endif break; - case BIO_CTRL_SET_CLOSE: + case WOLFSSL_BIO_CTRL_SET_CLOSE: +#ifdef USE_FULL_BIO wolfSSL_BIO_set_shutdown(bio, (int)num); +#endif break; - case BIO_CTRL_FLUSH: + case WOLFSSL_BIO_CTRL_FLUSH: /* we do no delayed writes, but if we ever would, this * needs to trigger it. */ ret = 1; break; - case BIO_CTRL_DUP: + case WOLFSSL_BIO_CTRL_DUP: ret = 1; break; -#ifdef BIO_CTRL_EOF - case BIO_CTRL_EOF: +#ifdef WOLFSSL_BIO_CTRL_EOF + case WOLFSSL_BIO_CTRL_EOF: { /* EOF has been reached on input? */ - return (!cf->next || !cf->next->connected); + struct ssl_connect_data *connssl = cf->ctx; + return connssl->peer_closed; + } #endif default: ret = 0; @@ -277,29 +312,49 @@ static long bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) return ret; } -static int bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen) +static int wssl_bio_cf_out_write(WOLFSSL_BIO *bio, + const char *buf, int blen) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); - ssize_t nwritten; + ssize_t nwritten, skiplen = 0; CURLcode result = CURLE_OK; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); - connssl->backend->io_result = result; - DEBUGF(LOG_CF(data, cf, "bio_write(len=%d) -> %zd, %d", - blen, nwritten, result)); + if(wssl->shutting_down && wssl->io_send_blocked_len && + (wssl->io_send_blocked_len < blen)) { + /* bug in wolfSSL: + * It adds the close notify message again every time we retry + * sending during shutdown. */ + CURL_TRC_CF(data, cf, "bio_write, shutdown restrict send of %d" + " to %d bytes", blen, wssl->io_send_blocked_len); + skiplen = (ssize_t)(blen - wssl->io_send_blocked_len); + blen = wssl->io_send_blocked_len; + } + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &result); + wssl->io_result = result; + CURL_TRC_CF(data, cf, "bio_write(len=%d) -> %zd, %d", + blen, nwritten, result); +#ifdef USE_FULL_BIO wolfSSL_BIO_clear_retry_flags(bio); - if(nwritten < 0 && CURLE_AGAIN == result) - BIO_set_retry_read(bio); +#endif + if(nwritten < 0 && CURLE_AGAIN == result) { + wolfSSL_BIO_set_retry_write(bio); + if(wssl->shutting_down && !wssl->io_send_blocked_len) + wssl->io_send_blocked_len = blen; + } + else if(!result && skiplen) + nwritten += skiplen; return (int)nwritten; } -static int bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) +static int wssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_OK; @@ -309,205 +364,317 @@ static int bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) if(!buf) return 0; + if((connssl->connecting_state == ssl_connect_2) && + !wssl->x509_store_setup) { + /* During handshake, init the x509 store before receiving the + * server response. This allows sending of ClientHello without delay. */ + result = Curl_wssl_setup_x509_store(cf, data, wssl); + if(result) { + CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", result); + wssl->io_result = result; + return -1; + } + } + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); - connssl->backend->io_result = result; - DEBUGF(LOG_CF(data, cf, "bio_read(len=%d) -> %zd, %d", - blen, nread, result)); + wssl->io_result = result; + CURL_TRC_CF(data, cf, "bio_read(len=%d) -> %zd, %d", blen, nread, result); +#ifdef USE_FULL_BIO wolfSSL_BIO_clear_retry_flags(bio); +#endif if(nread < 0 && CURLE_AGAIN == result) - BIO_set_retry_read(bio); + wolfSSL_BIO_set_retry_read(bio); + else if(nread == 0) + connssl->peer_closed = TRUE; return (int)nread; } -static WOLFSSL_BIO_METHOD *bio_cf_method = NULL; +static WOLFSSL_BIO_METHOD *wssl_bio_cf_method = NULL; -static void bio_cf_init_methods(void) +static void wssl_bio_cf_init_methods(void) { - bio_cf_method = wolfSSL_BIO_meth_new(BIO_TYPE_MEM, "wolfSSL CF BIO"); - wolfSSL_BIO_meth_set_write(bio_cf_method, &bio_cf_out_write); - wolfSSL_BIO_meth_set_read(bio_cf_method, &bio_cf_in_read); - wolfSSL_BIO_meth_set_ctrl(bio_cf_method, &bio_cf_ctrl); - wolfSSL_BIO_meth_set_create(bio_cf_method, &bio_cf_create); - wolfSSL_BIO_meth_set_destroy(bio_cf_method, &bio_cf_destroy); + wssl_bio_cf_method = wolfSSL_BIO_meth_new(WOLFSSL_BIO_MEMORY, + "wolfSSL CF BIO"); + wolfSSL_BIO_meth_set_write(wssl_bio_cf_method, &wssl_bio_cf_out_write); + 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); } -static void bio_cf_free_methods(void) +static void wssl_bio_cf_free_methods(void) { - wolfSSL_BIO_meth_free(bio_cf_method); + wolfSSL_BIO_meth_free(wssl_bio_cf_method); } #else /* USE_BIO_CHAIN */ -#define bio_cf_init_methods() Curl_nop_stmt -#define bio_cf_free_methods() Curl_nop_stmt +#define wssl_bio_cf_init_methods() Curl_nop_stmt +#define wssl_bio_cf_free_methods() Curl_nop_stmt #endif /* !USE_BIO_CHAIN */ -/* - * This function loads all the client/CA certificates and CRLs. Setup the TLS - * layer and do all necessary magic. - */ -static CURLcode -wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + WOLFSSL_SESSION *session, + int ietf_tls_id, + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len) { - char *ciphers, *curves; - 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); - const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - SSL_METHOD* req_method = NULL; -#ifdef HAVE_LIBOQS - word16 oqsAlg = 0; - size_t idx = 0; -#endif -#ifdef HAVE_SNI - bool sni = FALSE; -#define use_sni(x) sni = (x) -#else -#define use_sni(x) Curl_nop_stmt + CURLcode result = CURLE_OK; + struct Curl_ssl_session *sc_session = NULL; + unsigned char *sdata = NULL, *qtp_clone = NULL; + unsigned int sdata_len; + unsigned int earlydata_max = 0; + + if(!session) + goto out; + + sdata_len = wolfSSL_i2d_SSL_SESSION(session, NULL); + if(sdata_len <= 0) { + CURL_TRC_CF(data, cf, "fail to assess session length: %u", sdata_len); + result = CURLE_FAILED_INIT; + goto out; + } + sdata = calloc(1, sdata_len); + if(!sdata) { + failf(data, "unable to allocate session buffer of %u bytes", sdata_len); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + sdata_len = wolfSSL_i2d_SSL_SESSION(session, &sdata); + if(sdata_len <= 0) { + CURL_TRC_CF(data, cf, "fail to serialize session: %u", sdata_len); + result = CURLE_FAILED_INIT; + goto out; + } + if(quic_tp && quic_tp_len) { + qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len); + if(!qtp_clone) { + free(sdata); + return CURLE_OUT_OF_MEMORY; + } + } +#ifdef WOLFSSL_EARLY_DATA + earlydata_max = wolfSSL_SESSION_get_max_early_data(session); #endif - DEBUGASSERT(backend); + result = Curl_ssl_session_create2(sdata, sdata_len, + ietf_tls_id, alpn, + (curl_off_t)time(NULL) + + wolfSSL_SESSION_get_timeout(session), + earlydata_max, qtp_clone, quic_tp_len, + &sc_session); + sdata = NULL; /* took ownership of sdata */ + if(!result) { + result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); + /* took ownership of `sc_session` */ + } - if(connssl->state == ssl_connection_complete) - return CURLE_OK; +out: + free(sdata); + return result; +} - if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) { - failf(data, "wolfSSL does not support to set maximum SSL/TLS version"); - return CURLE_SSL_CONNECT_ERROR; +static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) +{ + struct Curl_cfilter *cf; + + cf = (struct Curl_cfilter*)wolfSSL_get_app_data(ssl); + DEBUGASSERT(cf != NULL); + if(cf && session) { + struct ssl_connect_data *connssl = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(connssl); + DEBUGASSERT(data); + if(connssl && data) { + (void)Curl_wssl_cache_session(cf, data, connssl->peer.scache_key, + session, wolfSSL_version(ssl), + connssl->negotiated.alpn, NULL, 0); + } } + return 0; +} - /* check to see if we've been told to use an explicit SSL/TLS version */ - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if LIBWOLFSSL_VERSION_HEX >= 0x03003000 /* >= 3.3.0 */ - /* minimum protocol version is set later after the CTX object is created */ - req_method = SSLv23_client_method(); -#else - infof(data, "wolfSSL <3.3.0 cannot be configured to use TLS 1.0-1.2, " - "TLS 1.0 is used exclusively"); - req_method = TLSv1_client_method(); -#endif - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_0: -#if defined(WOLFSSL_ALLOW_TLSV10) && !defined(NO_OLD_TLS) - req_method = TLSv1_client_method(); - use_sni(TRUE); -#else - failf(data, "wolfSSL does not support TLS 1.0"); - return CURLE_NOT_BUILT_IN; -#endif - break; - case CURL_SSLVERSION_TLSv1_1: -#ifndef NO_OLD_TLS - req_method = TLSv1_1_client_method(); - use_sni(TRUE); -#else - failf(data, "wolfSSL does not support TLS 1.1"); - return CURLE_NOT_BUILT_IN; -#endif - break; - case CURL_SSLVERSION_TLSv1_2: - req_method = TLSv1_2_client_method(); - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_3: -#ifdef WOLFSSL_TLS13 - req_method = wolfTLSv1_3_client_method(); - use_sni(TRUE); - break; +static CURLcode wssl_on_session_reuse(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + + *do_early_data = FALSE; +#ifdef WOLFSSL_EARLY_DATA + connssl->earlydata_max = wolfSSL_SESSION_get_max_early_data( + wolfSSL_get_session(wssl->ssl)); #else - failf(data, "wolfSSL: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; + (void)wssl; + connssl->earlydata_max = 0; #endif - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - if(!req_method) { - failf(data, "SSL: couldn't create a method"); - return CURLE_OUT_OF_MEMORY; + if(!connssl->earlydata_max) { + /* Seems to be GnuTLS way to signal no EarlyData in session */ + CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); } - - if(backend->ctx) - SSL_CTX_free(backend->ctx); - backend->ctx = SSL_CTX_new(req_method); - - if(!backend->ctx) { - failf(data, "SSL: couldn't create a context"); - return CURLE_OUT_OF_MEMORY; + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { + CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data"); } + else { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", connssl->earlydata_max, scs->alpn); + connssl->earlydata_state = ssl_earlydata_await; + connssl->state = ssl_connection_deferred; + result = Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)scs->alpn, + scs->alpn ? strlen(scs->alpn) : 0); + *do_early_data = !result; + } + return result; +} - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if LIBWOLFSSL_VERSION_HEX > 0x03004006 /* > 3.4.6 */ - /* Versions 3.3.0 to 3.4.6 we know the minimum protocol version is - * whatever minimum version of TLS was built in and at least TLS 1.0. For - * later library versions that could change (eg TLS 1.0 built in but - * defaults to TLS 1.1) so we have this short circuit evaluation to find - * the minimum supported TLS version. - */ - if((wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1) != 1) && - (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_1) != 1) && - (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_2) != 1) -#ifdef WOLFSSL_TLS13 - && (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_3) != 1) +static CURLcode +wssl_setup_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wss, + struct alpn_spec *alpns, + const char *ssl_peer_key, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct Curl_ssl_session *scs = NULL; + CURLcode result; + + result = Curl_ssl_scache_take(cf, data, ssl_peer_key, &scs); + if(!result && scs && scs->sdata && scs->sdata_len && + (!scs->alpn || Curl_alpn_contains_proto(alpns, scs->alpn))) { + WOLFSSL_SESSION *session; + /* wolfSSL changes the passed pointer for whatever reasons, yikes */ + const unsigned char *sdata = scs->sdata; + session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)scs->sdata_len); + if(session) { + int ret = wolfSSL_set_session(wss->ssl, session); + if(ret != WOLFSSL_SUCCESS) { + Curl_ssl_session_destroy(scs); + scs = NULL; + infof(data, "cached session not accepted (%d), " + "removing from cache", ret); + } + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + if(ssl_config->earlydata && + !cf->conn->connect_only && + !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) { + bool do_early_data = FALSE; + if(sess_reuse_cb) { + result = sess_reuse_cb(cf, data, alpns, scs, &do_early_data); + if(result) + goto out; + } +#ifdef WOLFSSL_EARLY_DATA + if(do_early_data) { + unsigned int edmax = (scs->earlydata_max < UINT_MAX) ? + (unsigned int)scs->earlydata_max : UINT_MAX; + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + Curl_alpn_restrict_to(alpns, scs->alpn); + wolfSSL_set_max_early_data(wss->ssl, edmax); + } +#else + /* Should never enable when not supported */ + DEBUGASSERT(!do_early_data); #endif - ) { - failf(data, "SSL: couldn't set the minimum protocol version"); - return CURLE_SSL_CONNECT_ERROR; + } + } + wolfSSL_SESSION_free(session); } -#endif - break; - } - - ciphers = conn_config->cipher_list; - if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { - failf(data, "failed setting cipher list: %s", ciphers); - return CURLE_SSL_CIPHER; + else { + failf(data, "could not decode previous session"); } - infof(data, "Cipher selection: %s", ciphers); } +out: + Curl_ssl_scache_return(cf, data, ssl_peer_key, scs); + return result; +} - curves = conn_config->curves; - if(curves) { +static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + WOLFSSL_X509_STORE *store, + struct wssl_ctx *wssl) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_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 */ + (ca_info_blob ? NULL : conn_config->CAfile); + const char * const ssl_capath = conn_config->CApath; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + bool imported_native_ca = FALSE; + bool imported_ca_info_blob = FALSE; + + /* We do not want to do this again, no matter the outcome */ + wssl->x509_store_setup = TRUE; -#ifdef HAVE_LIBOQS - for(idx = 0; gnm[idx].name != NULL; idx++) { - if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) { - oqsAlg = gnm[idx].group; - break; - } +#ifndef NO_FILESYSTEM + /* load native CA certificates */ + if(ssl_config->native_ca_store) { +#ifdef WOLFSSL_SYS_CA_CERTS + if(wolfSSL_CTX_load_system_CA_certs(wssl->ssl_ctx) != WOLFSSL_SUCCESS) { + infof(data, "error importing native CA store, continuing anyway"); } - - if(oqsAlg == 0) + else { + imported_native_ca = TRUE; + infof(data, "successfully imported native CA store"); + } +#else + infof(data, "ignoring native CA option because wolfSSL was built without " + "native CA support"); #endif - { - if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { - failf(data, "failed setting curves list: '%s'", curves); - return CURLE_SSL_CIPHER; - } + } +#endif /* !NO_FILESYSTEM */ + + /* load certificate blob */ + if(ca_info_blob) { + if(wolfSSL_CTX_load_verify_buffer(wssl->ssl_ctx, ca_info_blob->data, + (long)ca_info_blob->len, + WOLFSSL_FILETYPE_PEM) != + WOLFSSL_SUCCESS) { + failf(data, "error importing CA certificate blob"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + imported_ca_info_blob = TRUE; + infof(data, "successfully imported CA certificate blob"); } } + #ifndef NO_FILESYSTEM - /* load trusted cacert */ - if(conn_config->CAfile) { - if(1 != SSL_CTX_load_verify_locations(backend->ctx, - conn_config->CAfile, - conn_config->CApath)) { - if(conn_config->verifypeer) { + /* load trusted cacert from file if not blob */ + + CURL_TRC_CF(data, cf, "wssl_populate_x509_store, path=%s, blob=%d", + ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); + if(!store) + return CURLE_OUT_OF_MEMORY; + + if(ssl_cafile || ssl_capath) { + int rc = + wolfSSL_CTX_load_verify_locations_ex(wssl->ssl_ctx, + ssl_cafile, + ssl_capath, + WOLFSSL_LOAD_FLAG_IGNORE_ERR); + if(WOLFSSL_SUCCESS != rc) { + if(conn_config->verifypeer && + !imported_native_ca && !imported_ca_info_blob) { /* Fail if we insist on successfully verifying the server. */ failf(data, "error setting certificate verify locations:" " CAfile: %s CApath: %s", - conn_config->CAfile? - conn_config->CAfile: "none", - conn_config->CApath? - conn_config->CApath : "none"); + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); return CURLE_SSL_CACERT_BADFILE; } else { @@ -521,303 +688,868 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Everything is fine. */ infof(data, "successfully set certificate verify locations:"); } - infof(data, " CAfile: %s", - conn_config->CAfile ? conn_config->CAfile : "none"); - infof(data, " CApath: %s", - conn_config->CApath ? conn_config->CApath : "none"); + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); } +#endif + (void)store; + return CURLE_OK; +} - /* Load the client certificate, and private key */ - if(ssl_config->primary.clientcert && ssl_config->key) { - int file_type = do_file_type(ssl_config->cert_type); - - if(SSL_CTX_use_certificate_file(backend->ctx, - ssl_config->primary.clientcert, - file_type) != 1) { - failf(data, "unable to use client certificate (no key or wrong pass" - " phrase?)"); - return CURLE_SSL_CONNECT_ERROR; - } +/* key to use at `multi->proto_hash` */ +#define MPROTO_WSSL_X509_KEY "tls:wssl:x509:share" - file_type = do_file_type(ssl_config->key_type); - if(SSL_CTX_use_PrivateKey_file(backend->ctx, ssl_config->key, - file_type) != 1) { - failf(data, "unable to set private key"); - return CURLE_SSL_CONNECT_ERROR; - } +struct wssl_x509_share { + char *CAfile; /* CAfile path used to generate X509 store */ + WOLFSSL_X509_STORE *store; /* cached X509 store or NULL if none */ + struct curltime time; /* when the cached store was created */ +}; + +static void wssl_x509_share_free(void *key, size_t key_len, void *p) +{ + struct wssl_x509_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_WSSL_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_WSSL_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->store) { + wolfSSL_X509_STORE_free(share->store); } -#endif /* !NO_FILESYSTEM */ + free(share->CAfile); + free(share); +} - /* SSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(backend->ctx, - conn_config->verifypeer?SSL_VERIFY_PEER: - SSL_VERIFY_NONE, - NULL); +static bool +wssl_cached_x509_store_expired(const struct Curl_easy *data, + const struct wssl_x509_share *mb) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + struct curltime now = curlx_now(); + timediff_t elapsed_ms = curlx_timediff(now, mb->time); + timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; -#ifdef HAVE_SNI - if(sni) { - struct in_addr addr4; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; -#endif - size_t hostname_len = strlen(connssl->hostname); + if(timeout_ms < 0) + return FALSE; - if((hostname_len < USHRT_MAX) && - !Curl_inet_pton(AF_INET, connssl->hostname, &addr4) -#ifdef ENABLE_IPV6 - && !Curl_inet_pton(AF_INET6, connssl->hostname, &addr6) -#endif - ) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, connssl->hostname, &snilen); - if(!snihost || - wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, snihost, - (unsigned short)snilen) != 1) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - } - } -#endif + return elapsed_ms >= timeout_ms; +} - /* 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(result) { - failf(data, "error signaled by ssl ctx callback"); - return result; - } - } -#ifdef NO_FILESYSTEM - else if(conn_config->verifypeer) { - failf(data, "SSL: Certificates can't be loaded because wolfSSL was built" - " with \"no filesystem\". Either disable peer verification" - " (insecure) or if you are building an application with libcurl you" - " can load certificates via CURLOPT_SSL_CTX_FUNCTION."); - return CURLE_SSL_CONNECT_ERROR; - } -#endif +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); + if(!mb->CAfile || !conn_config->CAfile) + return mb->CAfile != conn_config->CAfile; - /* Let's make an SSL structure */ - if(backend->handle) - SSL_free(backend->handle); - backend->handle = SSL_new(backend->ctx); - if(!backend->handle) { - failf(data, "SSL: couldn't create a handle"); - return CURLE_OUT_OF_MEMORY; - } + return strcmp(mb->CAfile, conn_config->CAfile); +} -#ifdef HAVE_LIBOQS - if(oqsAlg) { - if(wolfSSL_UseKeyShare(backend->handle, oqsAlg) != WOLFSSL_SUCCESS) { - failf(data, "unable to use oqs KEM"); - } +static WOLFSSL_X509_STORE *wssl_get_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct Curl_multi *multi = data->multi; + struct wssl_x509_share *share; + WOLFSSL_X509_STORE *store = NULL; + + DEBUGASSERT(multi); + share = multi ? Curl_hash_pick(&multi->proto_hash, + CURL_UNCONST(MPROTO_WSSL_X509_KEY), + sizeof(MPROTO_WSSL_X509_KEY)-1) : NULL; + if(share && share->store && + !wssl_cached_x509_store_expired(data, share) && + !wssl_cached_x509_store_different(cf, share)) { + store = share->store; } -#endif - -#ifdef HAVE_ALPN - if(connssl->alpn) { - struct alpn_proto_buf proto; - CURLcode result; - result = Curl_alpn_to_proto_str(&proto, connssl->alpn); - if(result || - wolfSSL_UseALPN(backend->handle, (char *)proto.data, proto.len, - WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) { - failf(data, "SSL: failed setting ALPN protocols"); - return CURLE_SSL_CONNECT_ERROR; - } - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); - } -#endif /* HAVE_ALPN */ + return store; +} -#ifdef OPENSSL_EXTRA - if(Curl_tls_keylog_enabled()) { - /* Ensure the Client Random is preserved. */ - wolfSSL_KeepArrays(backend->handle); -#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) - wolfSSL_set_tls13_secret_cb(backend->handle, - wolfssl_tls13_secret_callback, NULL); -#endif - } -#endif /* OPENSSL_EXTRA */ +static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + WOLFSSL_X509_STORE *store) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi; + struct wssl_x509_share *share; -#ifdef HAVE_SECURE_RENEGOTIATION - if(wolfSSL_UseSecureRenegotiation(backend->handle) != SSL_SUCCESS) { - failf(data, "SSL: failed setting secure renegotiation"); - return CURLE_SSL_CONNECT_ERROR; + DEBUGASSERT(multi); + if(!multi) + return; + share = Curl_hash_pick(&multi->proto_hash, + CURL_UNCONST(MPROTO_WSSL_X509_KEY), + sizeof(MPROTO_WSSL_X509_KEY)-1); + + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) + return; + if(!Curl_hash_add2(&multi->proto_hash, + CURL_UNCONST(MPROTO_WSSL_X509_KEY), + sizeof(MPROTO_WSSL_X509_KEY)-1, + share, wssl_x509_share_free)) { + free(share); + return; + } } -#endif /* HAVE_SECURE_RENEGOTIATION */ - /* Check if there's a cached ID we can/should use here! */ - if(ssl_config->primary.sessionid) { - void *ssl_sessionid = NULL; + if(wolfSSL_X509_STORE_up_ref(store)) { + char *CAfile = NULL; - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { - /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { - Curl_ssl_delsessionid(data, ssl_sessionid); - infof(data, "Can't use session ID, going on without"); + if(conn_config->CAfile) { + CAfile = strdup(conn_config->CAfile); + if(!CAfile) { + wolfSSL_X509_STORE_free(store); + return; } - else - infof(data, "SSL re-using session ID"); } - Curl_ssl_sessionid_unlock(data); - } -#ifdef USE_BIO_CHAIN - { - WOLFSSL_BIO *bio; + if(share->store) { + wolfSSL_X509_STORE_free(share->store); + free(share->CAfile); + } - bio = BIO_new(bio_cf_method); - if(!bio) + share->time = curlx_now(); + share->store = store; + share->CAfile = CAfile; + } +} + +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); + CURLcode result = CURLE_OK; + WOLFSSL_X509_STORE *cached_store; + bool cache_criteria_met; + + /* 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) && + conn_config->verifypeer && + !conn_config->CApath && + !conn_config->ca_info_blob && + !ssl_config->primary.CRLfile && + !ssl_config->native_ca_store; + + cached_store = cache_criteria_met ? wssl_get_cached_x509_store(cf, data) + : NULL; + if(cached_store && + wolfSSL_CTX_get_cert_store(wssl->ssl_ctx) == cached_store) { + /* The cached store is already in use, do nothing. */ + } + else if(cached_store && wolfSSL_X509_STORE_up_ref(cached_store)) { + wolfSSL_CTX_set_cert_store(wssl->ssl_ctx, cached_store); + } + else if(cache_criteria_met) { + /* wolfSSL's initial store in CTX is not shareable by default. + * Make a new one, suitable for adding to the cache. See #14278 */ + WOLFSSL_X509_STORE *store = wolfSSL_X509_STORE_new(); + if(!store) { + failf(data, "SSL: could not create a X509 store"); return CURLE_OUT_OF_MEMORY; + } + wolfSSL_CTX_set_cert_store(wssl->ssl_ctx, store); - wolfSSL_BIO_set_data(bio, cf); - wolfSSL_set_bio(backend->handle, bio, bio); + result = wssl_populate_x509_store(cf, data, store, wssl); + if(!result) { + wssl_set_cached_x509_store(cf, data, store); + } } -#else /* USE_BIO_CHAIN */ - /* pass the raw socket into the SSL layer */ - if(!SSL_set_fd(backend->handle, (int)Curl_conn_cf_get_socket(cf, data))) { - failf(data, "SSL: SSL_set_fd failed"); - return CURLE_SSL_CONNECT_ERROR; + else { + /* We never share the CTX's store, use it. */ + WOLFSSL_X509_STORE *store = wolfSSL_CTX_get_cert_store(wssl->ssl_ctx); + result = wssl_populate_x509_store(cf, data, store, wssl); } -#endif /* !USE_BIO_CHAIN */ - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; + return result; } - +#ifdef WOLFSSL_TLS13 static CURLcode -wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) +wssl_add_default_ciphers(bool tls13, struct dynbuf *buf) { - int ret = -1; - 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); - const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: - data->set.str[STRING_SSL_PINNEDPUBLICKEY]; - - DEBUGASSERT(backend); - - ERR_clear_error(); + int i; + char *str; + + for(i = 0; (str = wolfSSL_get_cipher_list(i)) != NULL; i++) { + size_t n; + if((strncmp(str, "TLS13", 5) == 0) != tls13) + continue; + + /* if there already is data in the string, add colon separator */ + if(curlx_dyn_len(buf)) { + CURLcode result = curlx_dyn_addn(buf, ":", 1); + if(result) + return result; + } - /* Enable RFC2818 checks */ - if(conn_config->verifyhost) { - char *snihost = Curl_ssl_snihost(data, connssl->hostname, NULL); - if(!snihost || - (wolfSSL_check_domain_name(backend->handle, snihost) == SSL_FAILURE)) - return CURLE_SSL_CONNECT_ERROR; + n = strlen(str); + if(curlx_dyn_addn(buf, str, n)) + return CURLE_OUT_OF_MEMORY; } - ret = SSL_connect(backend->handle); + return CURLE_OK; +} +#endif + +/* 4.2.0 (2019) */ +#if LIBWOLFSSL_VERSION_HEX < 0x04002000 || !defined(OPENSSL_EXTRA) +static int +wssl_legacy_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) +{ + int res; + switch(version) { + default: + case TLS1_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1); + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_1_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_1); + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_2_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_2); +#ifdef WOLFSSL_TLS13 + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_3_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_3); +#endif + } + return res; +} +static int +wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) +{ + (void) ctx, (void) version; + return WOLFSSL_NOT_IMPLEMENTED; +} +#define wolfSSL_CTX_set_min_proto_version wssl_legacy_CTX_set_min_proto_version +#define wolfSSL_CTX_set_max_proto_version wssl_legacy_CTX_set_max_proto_version +#endif + +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:P-384:P-521" + +CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + Curl_wssl_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_primary_config *conn_config; + WOLFSSL_METHOD* req_method = NULL; + struct alpn_spec alpns; + int res; + char *curves; +#ifdef WOLFSSL_HAVE_KYBER + word16 pqkem = 0; + size_t idx = 0; +#endif + CURLcode result = CURLE_FAILED_INIT; + + DEBUGASSERT(!wctx->ssl_ctx); + DEBUGASSERT(!wctx->ssl); + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) { + result = CURLE_FAILED_INIT; + goto out; + } + Curl_alpn_copy(&alpns, alpns_requested); + +#if LIBWOLFSSL_VERSION_HEX < 0x04002000 /* 4.2.0 (2019) */ + req_method = wolfSSLv23_client_method(); +#else + req_method = wolfTLS_client_method(); +#endif + if(!req_method) { + failf(data, "wolfSSL: could not create a client method"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(wctx->ssl_ctx) + wolfSSL_CTX_free(wctx->ssl_ctx); + + wctx->ssl_ctx = wolfSSL_CTX_new(req_method); + if(!wctx->ssl_ctx) { + failf(data, "wolfSSL: could not create a context"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_VERSION); + break; + case CURL_SSLVERSION_TLSv1_1: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); + break; + case CURL_SSLVERSION_TLSv1_2: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); + break; +#ifdef WOLFSSL_TLS13 + case CURL_SSLVERSION_TLSv1_3: + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); + break; +#endif + default: + failf(data, "wolfSSL: unsupported minimum TLS version value"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the minimum TLS version"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + + switch(conn_config->version_max) { +#ifdef WOLFSSL_TLS13 + case CURL_SSLVERSION_MAX_TLSv1_3: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); + break; +#endif + case CURL_SSLVERSION_MAX_TLSv1_2: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_VERSION); + break; + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + res = WOLFSSL_SUCCESS; + break; + default: + failf(data, "wolfSSL: unsupported maximum TLS version value"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the maximum TLS version"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + +#ifndef WOLFSSL_TLS13 + { + char *ciphers = conn_config->cipher_list; + if(ciphers) { + if(!SSL_CTX_set_cipher_list(wctx->ssl_ctx, ciphers)) { + failf(data, "failed setting cipher list: %s", ciphers); + result = CURLE_SSL_CIPHER; + goto out; + } + infof(data, "Cipher selection: %s", ciphers); + } + } +#else +#define MAX_CIPHER_LEN 4096 + if(conn_config->cipher_list || conn_config->cipher_list13) { + const char *ciphers12 = conn_config->cipher_list; + const char *ciphers13 = conn_config->cipher_list13; + struct dynbuf c; + curlx_dyn_init(&c, MAX_CIPHER_LEN); + + if(ciphers13) + result = curlx_dyn_add(&c, ciphers13); + else + result = wssl_add_default_ciphers(TRUE, &c); + + if(!result) { + if(ciphers12) { + if(curlx_dyn_len(&c)) + result = curlx_dyn_addn(&c, ":", 1); + if(!result) + result = curlx_dyn_add(&c, ciphers12); + } + else + result = wssl_add_default_ciphers(FALSE, &c); + } + if(result) + goto out; + + if(!wolfSSL_CTX_set_cipher_list(wctx->ssl_ctx, curlx_dyn_ptr(&c))) { + failf(data, "failed setting cipher list: %s", curlx_dyn_ptr(&c)); + curlx_dyn_free(&c); + result = CURLE_SSL_CIPHER; + goto out; + } + infof(data, "Cipher selection: %s", curlx_dyn_ptr(&c)); + curlx_dyn_free(&c); + } +#endif + + curves = conn_config->curves; + if(!curves && cf->conn->transport == TRNSPRT_QUIC) + curves = (char *)CURL_UNCONST(QUIC_GROUPS); + + if(curves) { +#ifdef WOLFSSL_HAVE_KYBER + for(idx = 0; gnm[idx].name != NULL; idx++) { + if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) { + pqkem = gnm[idx].group; + break; + } + } + + if(pqkem == 0) +#endif + { + if(!wolfSSL_CTX_set1_curves_list(wctx->ssl_ctx, curves)) { + failf(data, "failed setting curves list: '%s'", curves); + result = CURLE_SSL_CIPHER; + goto out; + } + } + } + + /* 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->key; + const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; + const struct curl_blob *key_blob = ssl_config->key_blob; + int file_type = wssl_do_file_type(ssl_config->cert_type); + int rc; + + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, + cert_blob->data, + (long)cert_blob->len) : + wolfSSL_CTX_use_certificate_chain_file(wctx->ssl_ctx, cert_file); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, + (long)cert_blob->len, file_type) : + wolfSSL_CTX_use_certificate_file(wctx->ssl_ctx, cert_file, file_type); + break; + default: + failf(data, "unknown cert type"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(rc != 1) { + failf(data, "unable to use client certificate"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + + if(!key_blob && !key_file) { + key_blob = cert_blob; + key_file = cert_file; + } + else + file_type = wssl_do_file_type(ssl_config->key_type); + + rc = key_blob ? + wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, + (long)key_blob->len, file_type) : + wolfSSL_CTX_use_PrivateKey_file(wctx->ssl_ctx, key_file, file_type); + if(rc != 1) { + failf(data, "unable to set private key"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + } +#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->key_blob; + int file_type = wssl_do_file_type(ssl_config->cert_type); + int rc; + + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, + cert_blob->data, + (long)cert_blob->len); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, + (long)cert_blob->len, file_type); + break; + default: + failf(data, "unknown cert type"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + if(rc != 1) { + failf(data, "unable to use client certificate"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + + if(!key_blob) + key_blob = cert_blob; + else + file_type = wssl_do_file_type(ssl_config->key_type); + + if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, + (long)key_blob->len, + file_type) != 1) { + failf(data, "unable to set private key"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + } +#endif /* !NO_FILESYSTEM */ + + /* SSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + wolfSSL_CTX_set_verify(wctx->ssl_ctx, + conn_config->verifypeer ? WOLFSSL_VERIFY_PEER : + WOLFSSL_VERIFY_NONE, NULL); + +#ifdef HAVE_SNI + if(peer->sni) { + size_t sni_len = strlen(peer->sni); + if((sni_len < USHRT_MAX)) { + if(wolfSSL_CTX_UseSNI(wctx->ssl_ctx, WOLFSSL_SNI_HOST_NAME, + peer->sni, (unsigned short)sni_len) != 1) { + failf(data, "Failed to set SNI"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + CURL_TRC_CF(data, cf, "set SNI '%s'", peer->sni); + } + } +#endif + + if(ssl_config->primary.cache_session && + cf->conn->transport != TRNSPRT_QUIC) { + /* Register to get notified when a new session is received */ + wolfSSL_CTX_sess_set_new_cb(wctx->ssl_ctx, wssl_vtls_new_session_cb); + } + + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + goto out; + } + + /* give application a chance to interfere with SSL set up. */ + 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); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + goto out; + } + } +#ifdef NO_FILESYSTEM + else if(conn_config->verifypeer) { + failf(data, "SSL: Certificates cannot be loaded because wolfSSL was built" + " with \"no filesystem\". Either disable peer verification" + " (insecure) or if you are building an application with libcurl you" + " can load certificates via CURLOPT_SSL_CTX_FUNCTION."); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } +#endif + + /* Let's make an SSL structure */ + wctx->ssl = wolfSSL_new(wctx->ssl_ctx); + if(!wctx->ssl) { + failf(data, "SSL: could not create a handle"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + wolfSSL_set_app_data(wctx->ssl, ssl_user_data); +#ifdef WOLFSSL_QUIC + if(cf->conn->transport == TRNSPRT_QUIC) + wolfSSL_set_quic_use_legacy_codepoint(wctx->ssl, 0); +#endif + +#ifdef WOLFSSL_HAVE_KYBER + if(pqkem) { + if(wolfSSL_UseKeyShare(wctx->ssl, pqkem) != WOLFSSL_SUCCESS) { + failf(data, "unable to use PQ KEM"); + } + } +#endif + + /* Check if there is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { + /* Set session from cache if there is one */ + (void)wssl_setup_session(cf, data, wctx, &alpns, + peer->scache_key, sess_reuse_cb); + } + +#ifdef HAVE_ALPN + if(alpns.count) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); + Curl_alpn_to_proto_str(&proto, &alpns); + + if(wolfSSL_UseALPN(wctx->ssl, (char *)proto.data, + (unsigned int)proto.len, + WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != WOLFSSL_SUCCESS) { + failf(data, "SSL: failed setting ALPN protocols"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + CURL_TRC_CF(data, cf, "set ALPN: %s", proto.data); + } +#endif /* HAVE_ALPN */ #ifdef OPENSSL_EXTRA if(Curl_tls_keylog_enabled()) { - /* If key logging is enabled, wait for the handshake to complete and then - * proceed with logging secrets (for TLS 1.2 or older). - * - * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits - * for the server response. At that point the master secret is not yet - * available, so we must not try to read it. - * To log the secret on completion with a handshake failure, detect - * completion via the observation that there is nothing to read or write. - * Note that OpenSSL SSL_want_read() is always true here. If wolfSSL ever - * changes, the worst case is that no key is logged on error. - */ - if(ret == SSL_SUCCESS || - (!wolfSSL_want_read(backend->handle) && - !wolfSSL_want_write(backend->handle))) { - wolfssl_log_tls12_secret(backend->handle); - /* Client Random and master secrets are no longer needed, erase these. - * Ignored while the handshake is still in progress. */ - wolfSSL_FreeArrays(backend->handle); - } + /* Ensure the Client Random is preserved. */ + wolfSSL_KeepArrays(wctx->ssl); +#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) + wolfSSL_set_tls13_secret_cb(wctx->ssl, + wssl_tls13_secret_callback, NULL); +#endif } -#endif /* OPENSSL_EXTRA */ +#endif /* OPENSSL_EXTRA */ - if(ret != 1) { - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; - int detail = SSL_get_error(backend->handle, ret); +#ifdef HAVE_SECURE_RENEGOTIATION + if(wolfSSL_UseSecureRenegotiation(wctx->ssl) != SSL_SUCCESS) { + failf(data, "SSL: failed setting secure renegotiation"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } +#endif /* HAVE_SECURE_RENEGOTIATION */ - if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; +#ifdef USE_ECH_WOLFSSL + if(ECH_ENABLED(data)) { + int trying_ech_now = 0; + + if(data->set.str[STRING_ECH_PUBLIC]) { + infof(data, "ECH: outername not (yet) supported with wolfSSL"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; } - else if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; + if(data->set.tls_ech == CURLECH_GREASE) { + infof(data, "ECH: GREASE is done by default by wolfSSL: no need to ask"); } - /* There is no easy way to override only the CN matching. - * This will enable the override of both mismatching SubjectAltNames - * as also mismatching CN fields */ - else if(DOMAIN_NAME_MISMATCH == detail) { -#if 1 - failf(data, " subject alt name(s) or common name do not match \"%s\"", - connssl->dispname); - return CURLE_PEER_FAILED_VERIFICATION; -#else - /* When the wolfssl_check_domain_name() is used and you desire to - * continue on a DOMAIN_NAME_MISMATCH, i.e. 'ssl_config.verifyhost - * == 0', CyaSSL version 2.4.0 will fail with an INCOMPLETE_DATA - * error. The only way to do this is currently to switch the - * Wolfssl_check_domain_name() in and out based on the - * 'ssl_config.verifyhost' value. */ - if(conn_config->verifyhost) { - failf(data, - " subject alt name(s) or common name do not match \"%s\"\n", - connssl->dispname); - return CURLE_PEER_FAILED_VERIFICATION; + if(data->set.tls_ech & CURLECH_CLA_CFG + && data->set.str[STRING_ECH_CONFIG]) { + char *b64val = data->set.str[STRING_ECH_CONFIG]; + word32 b64len = 0; + + b64len = (word32) strlen(b64val); + if(b64len + && wolfSSL_SetEchConfigsBase64(wctx->ssl, b64val, b64len) + != WOLFSSL_SUCCESS) { + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } } else { - infof(data, - " subject alt name(s) and/or common name do not match \"%s\"", - connssl->dispname); - return CURLE_OK; + trying_ech_now = 1; + infof(data, "ECH: ECHConfig from command line"); } -#endif } -#if LIBWOLFSSL_VERSION_HEX >= 0x02007000 /* 2.7.0 */ - else if(ASN_NO_SIGNER_E == detail) { - if(conn_config->verifypeer) { - failf(data, " CA signer not available for verification"); - return CURLE_SSL_CACERT_BADFILE; + else { + struct ssl_connect_data *connssl = cf->ctx; + struct Curl_dns_entry *dns = NULL; + + dns = Curl_dnscache_get(data, connssl->peer.hostname, connssl->peer.port, + cf->conn->ip_version); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } } else { - /* Just continue with a warning if no strict certificate - verification is required. */ - infof(data, "CA signer not available for verification, " - "continuing anyway"); + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); + if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32) elen) != + WOLFSSL_SUCCESS) { + infof(data, "ECH: wolfSSL_SetEchConfigs failed"); + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + } + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %ld", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + } + Curl_resolv_unlink(data, &dns); } } -#endif - else if(backend->io_result == CURLE_AGAIN) { - return CURLE_OK; + + if(trying_ech_now && wolfSSL_set_min_proto_version(wctx->ssl, + TLS1_3_VERSION) != 1) { + infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); + result = CURLE_SSL_CONNECT_ERROR; + goto out; } - else { - failf(data, "SSL_connect failed with error %d: %s", detail, - ERR_error_string(detail, error_buffer)); + + } +#endif /* USE_ECH_WOLFSSL */ + + result = CURLE_OK; + +out: + if(result && wctx->ssl) { + wolfSSL_free(wctx->ssl); + wctx->ssl = NULL; + } + if(result && wctx->ssl_ctx) { + wolfSSL_CTX_free(wctx->ssl_ctx); + wctx->ssl_ctx = NULL; + } + return result; +} + +/* + * This function loads all the client/CA certificates and CRLs. Setup the TLS + * layer and do all necessary magic. + */ +static CURLcode +wssl_connect_step1(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); + CURLcode result; + + DEBUGASSERT(wssl); + + if(connssl->state == ssl_connection_complete) + return CURLE_OK; + + result = Curl_wssl_ctx_init(wssl, cf, data, &connssl->peer, + connssl->alpn, NULL, NULL, cf, + wssl_on_session_reuse); + if(result) + return result; + +#ifdef HAVE_ALPN + if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + + /* Enable RFC2818 checks */ + if(conn_config->verifyhost) { + char *snihost = connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname; + if(wolfSSL_check_domain_name(wssl->ssl, snihost) != + WOLFSSL_SUCCESS) { return CURLE_SSL_CONNECT_ERROR; } } +#ifdef USE_BIO_CHAIN + { + WOLFSSL_BIO *bio; + + bio = wolfSSL_BIO_new(wssl_bio_cf_method); + if(!bio) + return CURLE_OUT_OF_MEMORY; + + wolfSSL_BIO_set_data(bio, cf); + wolfSSL_set_bio(wssl->ssl, bio, bio); + } +#else /* USE_BIO_CHAIN */ + /* pass the raw socket into the SSL layer */ + if(!wolfSSL_set_fd(wssl->ssl, + (int)Curl_conn_cf_get_socket(cf, data))) { + failf(data, "SSL: wolfSSL_set_fd failed"); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* !USE_BIO_CHAIN */ + + return CURLE_OK; +} + + +static char *wssl_strerror(unsigned long error, char *buf, + unsigned long size) +{ + DEBUGASSERT(size > 40); + *buf = '\0'; + + wolfSSL_ERR_error_string_n(error, buf, size); + + if(!*buf) { + const char *msg = error ? "Unknown error" : "No error"; + /* the string fits because the assert above assures this */ + strcpy(buf, msg); + } + + return buf; +} + +CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wssl) +{ +#ifndef CURL_DISABLE_PROXY + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif + if(pinnedpubkey) { #ifdef KEEP_PEER_CERT - X509 *x509; + WOLFSSL_X509 *x509; const char *x509_der; int x509_der_len; struct Curl_X509certificate x509_parsed; struct Curl_asn1Element *pubkey; CURLcode result; - x509 = SSL_get_peer_certificate(backend->handle); + x509 = wolfSSL_get_peer_certificate(wssl->ssl); if(!x509) { failf(data, "SSL: failed retrieving server certificate"); return CURLE_SSL_PINNEDPUBKEYNOTMATCH; @@ -843,6 +1575,7 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) pinnedpubkey, (const unsigned char *)pubkey->header, (size_t)(pubkey->end - pubkey->header)); + wolfSSL_FreeX509(x509); if(result) { failf(data, "SSL: public key does not match pinned public key"); return result; @@ -852,226 +1585,468 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_NOT_BUILT_IN; #endif } + return CURLE_OK; +} -#ifdef HAVE_ALPN - if(connssl->alpn) { - int rc; - char *protocol = NULL; - unsigned short protocol_len = 0; +#ifdef WOLFSSL_EARLY_DATA +static CURLcode wssl_send_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen; + + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending); + wssl->io_result = CURLE_OK; + while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) { + int nwritten = 0, rc; + + wolfSSL_ERR_clear_error(); + rc = wolfSSL_write_early_data(wssl->ssl, buf, (int)blen, &nwritten); + CURL_TRC_CF(data, cf, "wolfSSL_write_early_data(len=%zu) -> %d, %d", + blen, rc, nwritten); + if(rc < 0) { + int err = wolfSSL_get_error(wssl->ssl, rc); + switch(err) { + case WOLFSSL_ERROR_NONE: /* just did not get anything */ + case WOLFSSL_ERROR_WANT_READ: + case WOLFSSL_ERROR_WANT_WRITE: + result = CURLE_AGAIN; + break; + default: { + char error_buffer[256]; + int detail = wolfSSL_get_error(wssl->ssl, err); + CURL_TRC_CF(data, cf, "SSL send early data, error: '%s'(%d)", + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + detail); + result = CURLE_SEND_ERROR; + break; + } + } + goto out; + } + + Curl_bufq_skip(&connssl->earlydata, (size_t)nwritten); + } + /* sent everything there was */ + connssl->earlydata_state = ssl_earlydata_sent; + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsEarlyData(data, (curl_off_t)connssl->earlydata_skip); + infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip); +out: + return result; +} +#endif /* WOLFSSL_EARLY_DATA */ + +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); + int ret = -1, detail; + CURLcode result; - rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len); + DEBUGASSERT(wssl); + connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(rc == SSL_SUCCESS) { - Curl_alpn_set_negotiated(cf, data, (const unsigned char *)protocol, - protocol_len); +#ifdef WOLFSSL_EARLY_DATA + if(connssl->earlydata_state == ssl_earlydata_sending) { + result = wssl_send_earlydata(cf, data); + if(result) + return result; + } + DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) || + (connssl->earlydata_state == ssl_earlydata_sent)); +#else + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_none); +#endif /* WOLFSSL_EARLY_DATA */ + + wolfSSL_ERR_clear_error(); + ret = wolfSSL_connect(wssl->ssl); + + if(!wssl->x509_store_setup) { + /* After having send off the ClientHello, we prepare the x509 + * store to verify the coming certificate from the server */ + result = Curl_wssl_setup_x509_store(cf, data, wssl); + if(result) { + CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", result); + return result; } - else if(rc == SSL_ALPN_NOT_FOUND) - Curl_alpn_set_negotiated(cf, data, NULL, 0); - else { - failf(data, "ALPN, failure getting protocol, error %d", rc); - return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef OPENSSL_EXTRA + if(Curl_tls_keylog_enabled()) { + /* If key logging is enabled, wait for the handshake to complete and then + * proceed with logging secrets (for TLS 1.2 or older). + * + * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits + * for the server response. At that point the master secret is not yet + * available, so we must not try to read it. + * To log the secret on completion with a handshake failure, detect + * completion via the observation that there is nothing to read or write. + * Note that OpenSSL SSL_want_read() is always true here. If wolfSSL ever + * changes, the worst case is that no key is logged on error. + */ + if(ret == WOLFSSL_SUCCESS || + (!wolfSSL_want_read(wssl->ssl) && + !wolfSSL_want_write(wssl->ssl))) { + wssl_log_tls12_secret(wssl->ssl); + /* Client Random and master secrets are no longer needed, erase these. + * Ignored while the handshake is still in progress. */ + wolfSSL_FreeArrays(wssl->ssl); } } -#endif /* HAVE_ALPN */ +#endif /* OPENSSL_EXTRA */ - connssl->connecting_state = ssl_connect_3; -#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010) - infof(data, "SSL connection using %s / %s", - wolfSSL_get_version(backend->handle), - wolfSSL_get_cipher_name(backend->handle)); -#else - infof(data, "SSL connected"); -#endif + detail = wolfSSL_get_error(wssl->ssl, ret); + CURL_TRC_CF(data, cf, "wolfSSL_connect() -> %d, detail=%d", ret, detail); - return CURLE_OK; + if(ret == WOLFSSL_SUCCESS) { + return CURLE_OK; + } + else { + if(WOLFSSL_ERROR_WANT_READ == detail) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_AGAIN; + } + else if(WOLFSSL_ERROR_WANT_WRITE == detail) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_AGAIN; + } + else if(DOMAIN_NAME_MISMATCH == detail) { + /* There is no easy way to override only the CN matching. + * This will enable the override of both mismatching SubjectAltNames + * as also mismatching CN fields */ + failf(data, " subject alt name(s) or common name do not match \"%s\"", + connssl->peer.dispname); + return CURLE_PEER_FAILED_VERIFICATION; + } + else if(ASN_NO_SIGNER_E == detail) { + if(conn_config->verifypeer) { + failf(data, " CA signer not available for verification"); + return CURLE_SSL_CACERT_BADFILE; + } + /* Just continue with a warning if no strict certificate + verification is required. */ + infof(data, "CA signer not available for verification, " + "continuing anyway"); + return CURLE_OK; + } + else if(ASN_AFTER_DATE_E == detail) { + failf(data, "server verification failed: certificate has expired."); + return CURLE_PEER_FAILED_VERIFICATION; + } + else if(ASN_BEFORE_DATE_E == detail) { + failf(data, "server verification failed: certificate not valid yet."); + return CURLE_PEER_FAILED_VERIFICATION; + } + else if(wssl->io_result) { + switch(wssl->io_result) { + case CURLE_SEND_ERROR: + case CURLE_RECV_ERROR: + return CURLE_SSL_CONNECT_ERROR; + default: + return wssl->io_result; + } + } +#ifdef USE_ECH_WOLFSSL + else if(-1 == detail) { + /* try access a retry_config ECHConfigList for tracing */ + byte echConfigs[1000]; + word32 echConfigsLen = 1000; + int rv = 0; + + /* this currently does not produce the retry_configs */ + rv = wolfSSL_GetEchConfigs(wssl->ssl, echConfigs, + &echConfigsLen); + if(rv != WOLFSSL_SUCCESS) { + infof(data, "Failed to get ECHConfigs"); + } + else { + char *b64str = NULL; + size_t blen = 0; + + result = curlx_base64_encode((const char *)echConfigs, echConfigsLen, + &b64str, &blen); + if(!result && b64str) + infof(data, "ECH: (not yet) retry_configs %s", b64str); + free(b64str); + } + return CURLE_SSL_CONNECT_ERROR; + } +#endif + else { + char error_buffer[256]; + failf(data, "SSL_connect failed with error %d: %s", detail, + wssl_strerror((unsigned long)detail, error_buffer, + sizeof(error_buffer))); + return CURLE_SSL_CONNECT_ERROR; + } + } } - -static CURLcode -wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) +static ssize_t wssl_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t blen, + CURLcode *curlcode) { - CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - DEBUGASSERT(backend); - - if(ssl_config->primary.sessionid) { - bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; - /* SSL_get1_session allocates memory that has to be freed. */ - SSL_SESSION *our_ssl_sessionid = SSL_get1_session(backend->handle); - - if(our_ssl_sessionid) { - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } - } + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + size_t total_written = 0; + ssize_t nwritten = -1; + DEBUGASSERT(wssl); - if(!incache) { - result = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, 0, NULL); - if(result) { - Curl_ssl_sessionid_unlock(data); - SSL_SESSION_free(our_ssl_sessionid); - failf(data, "failed to store ssl session"); - return result; + wolfSSL_ERR_clear_error(); + + if(blen) { + int memlen = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; + int rc; + + rc = wolfSSL_write(wssl->ssl, buf, memlen); + if(rc <= 0) { + int err = wolfSSL_get_error(wssl->ssl, rc); + + switch(err) { + case WOLFSSL_ERROR_WANT_READ: + case WOLFSSL_ERROR_WANT_WRITE: + /* there is data pending, re-invoke wolfSSL_write() */ + if(total_written) { + *curlcode = CURLE_OK; + nwritten = total_written; + goto out; } - else { - added = TRUE; + *curlcode = CURLE_AGAIN; + nwritten = -1; + goto out; + + default: + if(wssl->io_result == CURLE_AGAIN) { + if(total_written) { + *curlcode = CURLE_OK; + nwritten = total_written; + goto out; + } + *curlcode = CURLE_AGAIN; + nwritten = -1; + goto out; } - } - Curl_ssl_sessionid_unlock(data); - - if(!added) { - /* If the session info wasn't added to the cache, free our copy. */ - SSL_SESSION_free(our_ssl_sessionid); + { + char error_buffer[256]; + failf(data, "SSL write: %s, errno %d", + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + SOCKERRNO); + } + *curlcode = CURLE_SEND_ERROR; + nwritten = -1; + goto out; } } + else + total_written += rc; } - connssl->connecting_state = ssl_connect_done; - - return result; + *curlcode = CURLE_OK; + nwritten = total_written; +out: + CURL_TRC_CF(data, cf, "wssl_send(len=%zu) -> %" FMT_OFF_T ", %d", + blen, nwritten, *curlcode); + return nwritten; } - -static ssize_t wolfssl_send(struct Curl_cfilter *cf, - struct Curl_easy *data, - const void *mem, - size_t len, - CURLcode *curlcode) +static CURLcode wssl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; - int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - int rc; - - DEBUGASSERT(backend); - - ERR_clear_error(); - - rc = SSL_write(backend->handle, mem, memlen); - if(rc <= 0) { - int err = SSL_get_error(backend->handle, rc); + struct wssl_ctx *wctx = (struct wssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + char buf[1024]; + char error_buffer[256]; + int nread = -1, err; + size_t i; + int detail; + + DEBUGASSERT(wctx); + if(!wctx->ssl || cf->shutdown) { + *done = TRUE; + goto out; + } - switch(err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_write() */ - DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len)); - *curlcode = CURLE_AGAIN; - return -1; - default: - if(backend->io_result == CURLE_AGAIN) { - DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len)); - *curlcode = CURLE_AGAIN; - return -1; + wctx->shutting_down = TRUE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + if(!(wolfSSL_get_shutdown(wctx->ssl) & WOLFSSL_SENT_SHUTDOWN)) { + /* We have not started the shutdown from our side yet. Check + * if the server already sent us one. */ + wolfSSL_ERR_clear_error(); + nread = wolfSSL_read(wctx->ssl, buf, (int)sizeof(buf)); + err = wolfSSL_get_error(wctx->ssl, nread); + CURL_TRC_CF(data, cf, "wolfSSL_read, nread=%d, err=%d", nread, err); + if(!nread && err == WOLFSSL_ERROR_ZERO_RETURN) { + bool input_pending; + /* Yes, it did. */ + if(!send_shutdown) { + CURL_TRC_CF(data, cf, "SSL shutdown received, not sending"); + *done = TRUE; + goto out; + } + else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) { + /* Server closed the connection after its closy notify. It + * seems not interested to see our close notify, so do not + * send it. We are done. */ + CURL_TRC_CF(data, cf, "peer closed connection"); + connssl->peer_closed = TRUE; + *done = TRUE; + goto out; } - DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> %d, %d", - len, rc, err)); - failf(data, "SSL write: %s, errno %d", - ERR_error_string(err, error_buffer), - SOCKERRNO); - *curlcode = CURLE_SEND_ERROR; - return -1; } } - DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> %d", len, rc)); - return rc; + + /* wolfSSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + if(send_shutdown) { + wolfSSL_ERR_clear_error(); + if(wolfSSL_shutdown(wctx->ssl) == 1) { + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + *done = TRUE; + goto out; + } + if(WOLFSSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->ssl, nread)) { + CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + } + /* Having sent the close notify, we use wolfSSL_read() to get the + * missing close notify from the server. */ + } + + for(i = 0; i < 10; ++i) { + wolfSSL_ERR_clear_error(); + nread = wolfSSL_read(wctx->ssl, buf, (int)sizeof(buf)); + if(nread <= 0) + break; + } + err = wolfSSL_get_error(wctx->ssl, nread); + switch(err) { + case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ + CURL_TRC_CF(data, cf, "SSL shutdown received"); + *done = TRUE; + break; + case WOLFSSL_ERROR_NONE: /* just did not get anything */ + case WOLFSSL_ERROR_WANT_READ: + /* wolfSSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + break; + case WOLFSSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + break; + default: + detail = wolfSSL_get_error(wctx->ssl, err); + CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + detail); + result = CURLE_RECV_ERROR; + break; + } + +out: + cf->shutdown = (result || *done); + return result; } -static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static void wssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; (void) data; - DEBUGASSERT(backend); + DEBUGASSERT(wssl); - if(backend->handle) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)SSL_read(backend->handle, buf, (int)sizeof(buf)); - (void)SSL_shutdown(backend->handle); - SSL_free(backend->handle); - backend->handle = NULL; + if(wssl->ssl) { + wolfSSL_free(wssl->ssl); + wssl->ssl = NULL; } - if(backend->ctx) { - SSL_CTX_free(backend->ctx); - backend->ctx = NULL; + if(wssl->ssl_ctx) { + wolfSSL_CTX_free(wssl->ssl_ctx); + wssl->ssl_ctx = NULL; } } -static ssize_t wolfssl_recv(struct Curl_cfilter *cf, - struct Curl_easy *data, - char *buf, size_t blen, - CURLcode *curlcode) +static ssize_t wssl_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t blen, + CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; int buffsize = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; int nread; - DEBUGASSERT(backend); + DEBUGASSERT(wssl); - ERR_clear_error(); + wolfSSL_ERR_clear_error(); *curlcode = CURLE_OK; - nread = SSL_read(backend->handle, buf, buffsize); + nread = wolfSSL_read(wssl->ssl, buf, buffsize); if(nread <= 0) { - int err = SSL_get_error(backend->handle, nread); + int err = wolfSSL_get_error(wssl->ssl, nread); switch(err) { - case SSL_ERROR_ZERO_RETURN: /* no more data */ - DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen)); + case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen); *curlcode = CURLE_OK; return 0; - case SSL_ERROR_NONE: - /* FALLTHROUGH */ - case SSL_ERROR_WANT_READ: - /* FALLTHROUGH */ - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ - DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen)); + case WOLFSSL_ERROR_NONE: + case WOLFSSL_ERROR_WANT_READ: + case WOLFSSL_ERROR_WANT_WRITE: + if(!wssl->io_result && connssl->peer_closed) { + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen); + *curlcode = CURLE_OK; + return 0; + } + /* there is data pending, re-invoke wolfSSL_read() */ + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen); *curlcode = CURLE_AGAIN; return -1; default: - if(backend->io_result == CURLE_AGAIN) { - DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen)); + if(wssl->io_result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen); *curlcode = CURLE_AGAIN; return -1; } - failf(data, "SSL read: %s, errno %d", - ERR_error_string(err, error_buffer), SOCKERRNO); + else if(!wssl->io_result && connssl->peer_closed) { + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen); + *curlcode = CURLE_OK; + return 0; + } + else { + char error_buffer[256]; + failf(data, "SSL read: %s, errno %d", + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + SOCKERRNO); + } *curlcode = CURLE_RECV_ERROR; return -1; } } - DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> %d", blen, nread)); + + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> %d", blen, nread); return nread; } - -static void wolfssl_session_free(void *ptr) -{ - SSL_SESSION_free(ptr); -} - - -static size_t wolfssl_version(char *buffer, size_t size) +size_t Curl_wssl_version(char *buffer, size_t size) { #if LIBWOLFSSL_VERSION_HEX >= 0x03006000 return msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version()); @@ -1081,22 +2056,22 @@ static size_t wolfssl_version(char *buffer, size_t size) } -static int wolfssl_init(void) +static int wssl_init(void) { int ret; #ifdef OPENSSL_EXTRA Curl_tls_keylog_open(); #endif - ret = (wolfSSL_Init() == SSL_SUCCESS); - bio_cf_init_methods(); + ret = (wolfSSL_Init() == WOLFSSL_SUCCESS); + wssl_bio_cf_init_methods(); return ret; } -static void wolfssl_cleanup(void) +static void wssl_cleanup(void) { - bio_cf_free_methods(); + wssl_bio_cf_free_methods(); wolfSSL_Cleanup(); #ifdef OPENSSL_EXTRA Curl_tls_keylog_close(); @@ -1104,52 +2079,29 @@ static void wolfssl_cleanup(void) } -static bool wolfssl_data_pending(struct Curl_cfilter *cf, - const struct Curl_easy *data) +static bool wssl_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { struct ssl_connect_data *ctx = cf->ctx; + struct wssl_ctx *wssl; (void)data; DEBUGASSERT(ctx && ctx->backend); - if(ctx->backend->handle) /* SSL is in use */ - return (0 != SSL_pending(ctx->backend->handle)) ? TRUE : FALSE; + + wssl = (struct wssl_ctx *)ctx->backend; + if(wssl->ssl) /* wolfSSL is in use */ + return wolfSSL_pending(wssl->ssl); else return FALSE; } - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int wolfssl_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode wssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - struct ssl_connect_data *ctx = cf->ctx; - int retval = 0; - - (void)data; - DEBUGASSERT(ctx && ctx->backend); - - if(ctx->backend->handle) { - ERR_clear_error(); - SSL_free(ctx->backend->handle); - ctx->backend->handle = NULL; - } - return retval; -} - - -static CURLcode -wolfssl_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, - bool *done) -{ - CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - int what; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -1157,124 +2109,108 @@ wolfssl_connect_common(struct Curl_cfilter *cf, return CURLE_OK; } - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; - result = wolfssl_connect_step1(cf, data); + if(ssl_connect_1 == connssl->connecting_state) { + result = wssl_connect_step1(cf, data); if(result) return result; + connssl->connecting_state = ssl_connect_2; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; + if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->earlydata_state == ssl_earlydata_await) { + /* We defer the handshake until request data arrives. */ + DEBUGASSERT(connssl->state == ssl_connection_deferred); + goto out; } + result = wssl_handshake(cf, data); + if(result == CURLE_AGAIN) + goto out; + wssl->hs_result = result; + connssl->connecting_state = ssl_connect_3; + } - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { + if(ssl_connect_3 == connssl->connecting_state) { + /* Once the handshake has errored, it stays in that state and will + * error again on every call. */ + if(wssl->hs_result) { + result = wssl->hs_result; + goto out; + } + result = Curl_wssl_verify_pinned(cf, data, wssl); + if(result) { + wssl->hs_result = result; + goto out; + } + /* handhshake was done without errors */ +#ifdef HAVE_ALPN + if(connssl->alpn) { + int rc; + char *protocol = NULL; + unsigned short protocol_len = 0; - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + rc = wolfSSL_ALPN_GetProtocol(wssl->ssl, &protocol, &protocol_len); - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; + if(rc == WOLFSSL_SUCCESS) { + Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)protocol, + protocol_len); } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + else if(rc == WOLFSSL_ALPN_NOT_FOUND) + Curl_alpn_set_negotiated(cf, data, connssl, NULL, 0); + else { + failf(data, "ALPN, failure getting protocol, error %d", rc); + wssl->hs_result = result = CURLE_SSL_CONNECT_ERROR; + goto out; } - /* socket is readable or writable */ } +#endif /* HAVE_ALPN */ - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ - result = wolfssl_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return result; - } /* repeat step2 until all transactions are done. */ +#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010) + infof(data, "SSL connection using %s / %s", + wolfSSL_get_version(wssl->ssl), + wolfSSL_get_cipher_name(wssl->ssl)); +#else + infof(data, "SSL connected"); +#endif - if(ssl_connect_3 == connssl->connecting_state) { - result = wolfssl_connect_step3(cf, data); - if(result) - return result; + connssl->connecting_state = ssl_connect_done; + connssl->state = ssl_connection_complete; + +#ifdef WOLFSSL_EARLY_DATA + if(connssl->earlydata_state > ssl_earlydata_none) { + /* We should be in this state by now */ + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent); + connssl->earlydata_state = + (wolfSSL_get_early_data_status(wssl->ssl) == + WOLFSSL_EARLY_DATA_REJECTED) ? + ssl_earlydata_rejected : ssl_earlydata_accepted; + } +#endif /* WOLFSSL_EARLY_DATA */ } - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; + if((connssl->connecting_state == ssl_connect_done) || + (connssl->state == ssl_connection_deferred)) { *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - - -static CURLcode wolfssl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return wolfssl_connect_common(cf, data, TRUE, done); -} - - -static CURLcode wolfssl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - result = wolfssl_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; +out: + if(result) { + *done = FALSE; + if(result == CURLE_AGAIN) + return CURLE_OK; + } + else if((connssl->connecting_state == ssl_connect_done) || + (connssl->state == ssl_connection_deferred)) { + *done = TRUE; + } + return result; } -static CURLcode wolfssl_random(struct Curl_easy *data, - unsigned char *entropy, size_t length) +static CURLcode wssl_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) { WC_RNG rng; (void)data; @@ -1289,30 +2225,31 @@ static CURLcode wolfssl_random(struct Curl_easy *data, return CURLE_OK; } -static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum /* output */, - size_t unused) +static CURLcode wssl_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum /* output */, + size_t unused) { wc_Sha256 SHA256pw; (void)unused; - wc_InitSha256(&SHA256pw); + if(wc_InitSha256(&SHA256pw)) + return CURLE_FAILED_INIT; wc_Sha256Update(&SHA256pw, tmp, (word32)tmplen); wc_Sha256Final(&SHA256pw, sha256sum); return CURLE_OK; } -static void *wolfssl_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) +static void *wssl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) { - struct ssl_backend_data *backend = connssl->backend; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; (void)info; - DEBUGASSERT(backend); - return backend->handle; + DEBUGASSERT(wssl); + return wssl->ssl; } const struct Curl_ssl Curl_ssl_wolfssl = { - { CURLSSLBACKEND_WOLFSSL, "WolfSSL" }, /* info */ + { CURLSSLBACKEND_WOLFSSL, "wolfssl" }, /* info */ #ifdef KEEP_PEER_CERT SSLSUPP_PINNEDPUBKEY | @@ -1320,35 +2257,40 @@ const struct Curl_ssl Curl_ssl_wolfssl = { #ifdef USE_BIO_CHAIN SSLSUPP_HTTPS_PROXY | #endif - SSLSUPP_SSL_CTX, - - sizeof(struct ssl_backend_data), - - wolfssl_init, /* init */ - wolfssl_cleanup, /* cleanup */ - wolfssl_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - wolfssl_shutdown, /* shutdown */ - wolfssl_data_pending, /* data_pending */ - wolfssl_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - wolfssl_connect, /* connect */ - wolfssl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ - wolfssl_get_internals, /* get_internals */ - wolfssl_close, /* close_one */ - Curl_none_close_all, /* close_all */ - wolfssl_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - wolfssl_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ - wolfssl_recv, /* recv decrypted data */ - wolfssl_send, /* send data to encrypt */ + SSLSUPP_CA_PATH | + SSLSUPP_CAINFO_BLOB | +#ifdef USE_ECH_WOLFSSL + SSLSUPP_ECH | +#endif + SSLSUPP_SSL_CTX | +#ifdef WOLFSSL_TLS13 + SSLSUPP_TLS13_CIPHERSUITES | +#endif + SSLSUPP_CA_CACHE | + SSLSUPP_CIPHER_LIST, + + sizeof(struct wssl_ctx), + + wssl_init, /* init */ + wssl_cleanup, /* cleanup */ + Curl_wssl_version, /* version */ + wssl_shutdown, /* shutdown */ + wssl_data_pending, /* data_pending */ + wssl_random, /* random */ + NULL, /* cert_status_request */ + wssl_connect, /* connect */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + wssl_get_internals, /* get_internals */ + wssl_close, /* close_one */ + NULL, /* close_all */ + NULL, /* set_engine */ + NULL, /* set_engine_default */ + NULL, /* engines_list */ + NULL, /* false_start */ + wssl_sha256sum, /* sha256sum */ + wssl_recv, /* recv decrypted data */ + wssl_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.h b/Utilities/cmcurl/lib/vtls/wolfssl.h index a5ed8480996..0ddbee9ed9e 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.h +++ b/Utilities/cmcurl/lib/vtls/wolfssl.h @@ -23,11 +23,71 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_WOLFSSL +#include "../urldata.h" + +struct alpn_spec; +struct ssl_peer; +struct Curl_ssl_session; + +struct WOLFSSL; +struct WOLFSSL_CTX; +struct WOLFSSL_SESSION; + extern const struct Curl_ssl Curl_ssl_wolfssl; +struct wssl_ctx { + struct WOLFSSL_CTX *ssl_ctx; + struct WOLFSSL *ssl; + CURLcode io_result; /* result of last BIO cfilter operation */ + CURLcode hs_result; /* result of handshake */ + int io_send_blocked_len; /* length of last BIO write that EAGAINed */ + BIT(x509_store_setup); /* x509 store has been set up */ + BIT(shutting_down); /* TLS is being shut down */ +}; + +size_t Curl_wssl_version(char *buffer, size_t size); + +typedef CURLcode Curl_wssl_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); + +typedef CURLcode Curl_wssl_init_session_reuse_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data); + +CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns, + Curl_wssl_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb); + +CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wssl); + +CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key, + struct WOLFSSL_SESSION *session, + int ietf_tls_id, + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len); + +CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wssl); + + #endif /* USE_WOLFSSL */ #endif /* HEADER_CURL_WOLFSSL_H */ diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.c b/Utilities/cmcurl/lib/vtls/x509asn1.c index acf8bdb2ab5..c6246cbd096 100644 --- a/Utilities/cmcurl/lib/vtls/x509asn1.c +++ b/Utilities/cmcurl/lib/vtls/x509asn1.c @@ -22,43 +22,39 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) || defined(USE_RUSTLS) -#if defined(USE_GSKIT) || defined(USE_WOLFSSL) || defined(USE_SCHANNEL) +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) || defined(USE_WOLFSSL) || defined(USE_RUSTLS) #define WANT_PARSEX509 /* uses Curl_parseX509() */ #endif -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_SCHANNEL) || defined(USE_SECTRANSP) +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) || defined(USE_RUSTLS) #define WANT_EXTRACT_CERTINFO /* uses Curl_extract_certinfo() */ -#define WANT_PARSEX509 /* ... uses Curl_parseX509() */ -#endif - -#if defined(USE_GSKIT) -#define WANT_VERIFYHOST /* uses Curl_verifyhost () */ -#define WANT_PARSEX509 /* ... uses Curl_parseX509() */ #endif #include -#include "urldata.h" -#include "strcase.h" -#include "curl_ctype.h" +#include "../urldata.h" +#include "../strcase.h" +#include "../curl_ctype.h" #include "hostcheck.h" -#include "vtls/vtls.h" -#include "vtls/vtls_int.h" -#include "sendf.h" -#include "inet_pton.h" -#include "curl_base64.h" +#include "vtls.h" +#include "vtls_int.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" +#include "../curlx/base64.h" #include "x509asn1.h" -#include "dynbuf.h" +#include "../curlx/dynbuf.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Constants. @@ -68,10 +64,10 @@ #define CURL_ASN1_MAX ((size_t) 0x40000) /* 256K */ /* ASN.1 classes. */ -#define CURL_ASN1_UNIVERSAL 0 -#define CURL_ASN1_APPLICATION 1 -#define CURL_ASN1_CONTEXT_SPECIFIC 2 -#define CURL_ASN1_PRIVATE 3 +/* #define CURL_ASN1_UNIVERSAL 0 */ +/* #define CURL_ASN1_APPLICATION 1 */ +/* #define CURL_ASN1_CONTEXT_SPECIFIC 2 */ +/* #define CURL_ASN1_PRIVATE 3 */ /* ASN.1 types. */ #define CURL_ASN1_BOOLEAN 1 @@ -80,29 +76,30 @@ #define CURL_ASN1_OCTET_STRING 4 #define CURL_ASN1_NULL 5 #define CURL_ASN1_OBJECT_IDENTIFIER 6 -#define CURL_ASN1_OBJECT_DESCRIPTOR 7 -#define CURL_ASN1_INSTANCE_OF 8 -#define CURL_ASN1_REAL 9 +/* #define CURL_ASN1_OBJECT_DESCRIPTOR 7 */ +/* #define CURL_ASN1_INSTANCE_OF 8 */ +/* #define CURL_ASN1_REAL 9 */ #define CURL_ASN1_ENUMERATED 10 -#define CURL_ASN1_EMBEDDED 11 +/* #define CURL_ASN1_EMBEDDED 11 */ #define CURL_ASN1_UTF8_STRING 12 -#define CURL_ASN1_RELATIVE_OID 13 -#define CURL_ASN1_SEQUENCE 16 -#define CURL_ASN1_SET 17 +/* #define CURL_ASN1_RELATIVE_OID 13 */ +/* #define CURL_ASN1_SEQUENCE 16 */ +/* #define CURL_ASN1_SET 17 */ #define CURL_ASN1_NUMERIC_STRING 18 #define CURL_ASN1_PRINTABLE_STRING 19 #define CURL_ASN1_TELETEX_STRING 20 -#define CURL_ASN1_VIDEOTEX_STRING 21 +/* #define CURL_ASN1_VIDEOTEX_STRING 21 */ #define CURL_ASN1_IA5_STRING 22 #define CURL_ASN1_UTC_TIME 23 #define CURL_ASN1_GENERALIZED_TIME 24 -#define CURL_ASN1_GRAPHIC_STRING 25 +/* #define CURL_ASN1_GRAPHIC_STRING 25 */ #define CURL_ASN1_VISIBLE_STRING 26 -#define CURL_ASN1_GENERAL_STRING 27 +/* #define CURL_ASN1_GENERAL_STRING 27 */ #define CURL_ASN1_UNIVERSAL_STRING 28 -#define CURL_ASN1_CHARACTER_STRING 29 +/* #define CURL_ASN1_CHARACTER_STRING 29 */ #define CURL_ASN1_BMP_STRING 30 + #ifdef WANT_EXTRACT_CERTINFO /* ASN.1 OID table entry. */ struct Curl_OID { @@ -111,15 +108,16 @@ struct Curl_OID { }; /* ASN.1 OIDs. */ -static const char cnOID[] = "2.5.4.3"; /* Common name. */ -static const char sanOID[] = "2.5.29.17"; /* Subject alternative name. */ - static const struct Curl_OID OIDtable[] = { { "1.2.840.10040.4.1", "dsa" }, { "1.2.840.10040.4.3", "dsa-with-sha1" }, { "1.2.840.10045.2.1", "ecPublicKey" }, { "1.2.840.10045.3.0.1", "c2pnb163v1" }, { "1.2.840.10045.4.1", "ecdsa-with-SHA1" }, + { "1.2.840.10045.4.3.1", "ecdsa-with-SHA224" }, + { "1.2.840.10045.4.3.2", "ecdsa-with-SHA256" }, + { "1.2.840.10045.4.3.3", "ecdsa-with-SHA384" }, + { "1.2.840.10045.4.3.4", "ecdsa-with-SHA512" }, { "1.2.840.10046.2.1", "dhpublicnumber" }, { "1.2.840.113549.1.1.1", "rsaEncryption" }, { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, @@ -133,7 +131,7 @@ static const struct Curl_OID OIDtable[] = { { "1.2.840.113549.2.2", "md2" }, { "1.2.840.113549.2.5", "md5" }, { "1.3.14.3.2.26", "sha1" }, - { cnOID, "CN" }, + { "2.5.4.3", "CN" }, { "2.5.4.4", "SN" }, { "2.5.4.5", "serialNumber" }, { "2.5.4.6", "C" }, @@ -154,13 +152,14 @@ static const struct Curl_OID OIDtable[] = { { "2.5.4.65", "pseudonym" }, { "1.2.840.113549.1.9.1", "emailAddress" }, { "2.5.4.72", "role" }, - { sanOID, "subjectAltName" }, + { "2.5.29.17", "subjectAltName" }, { "2.5.29.18", "issuerAltName" }, { "2.5.29.19", "basicConstraints" }, { "2.16.840.1.101.3.4.2.4", "sha224" }, { "2.16.840.1.101.3.4.2.1", "sha256" }, { "2.16.840.1.101.3.4.2.2", "sha384" }, { "2.16.840.1.101.3.4.2.3", "sha512" }, + { "1.2.840.113549.1.9.2", "unstructuredName" }, { (const char *) NULL, (const char *) NULL } }; @@ -179,8 +178,11 @@ static const char *getASN1Element(struct Curl_asn1Element *elem, const char *beg, const char *end) WARN_UNUSED_RESULT; -static const char *getASN1Element(struct Curl_asn1Element *elem, - const char *beg, const char *end) +#define CURL_ASN1_MAX_RECURSIONS 16 + +static const char *getASN1Element_(struct Curl_asn1Element *elem, + const char *beg, const char *end, + size_t lvl) { unsigned char b; size_t len; @@ -191,7 +193,8 @@ static const char *getASN1Element(struct Curl_asn1Element *elem, Returns a pointer in source string after the parsed element, or NULL if an error occurs. */ if(!beg || !end || beg >= end || !*beg || - (size_t)(end - beg) > CURL_ASN1_MAX) + ((size_t)(end - beg) > CURL_ASN1_MAX) || + lvl >= CURL_ASN1_MAX_RECURSIONS) return NULL; /* Process header byte. */ @@ -217,7 +220,7 @@ static const char *getASN1Element(struct Curl_asn1Element *elem, return NULL; elem->beg = beg; while(beg < end && *beg) { - beg = getASN1Element(&lelem, beg, end); + beg = getASN1Element_(&lelem, beg, end, lvl + 1); if(!beg) return NULL; } @@ -244,10 +247,16 @@ static const char *getASN1Element(struct Curl_asn1Element *elem, return elem->end; } +static const char *getASN1Element(struct Curl_asn1Element *elem, + const char *beg, const char *end) +{ + return getASN1Element_(elem, beg, end, 0); +} + #ifdef WANT_EXTRACT_CERTINFO /* - * Search the null terminated OID or OID identifier in local table. + * Search the null-terminated OID or OID identifier in local table. * Return the table entry pointer or NULL if not found. */ static const struct Curl_OID *searchOID(const char *oid) @@ -260,62 +269,73 @@ static const struct Curl_OID *searchOID(const char *oid) return NULL; } +#ifdef UNITTESTS +/* used by unit1657.c */ +CURLcode Curl_x509_getASN1Element(struct Curl_asn1Element *elem, + const char *beg, const char *end) +{ + if(getASN1Element(elem, beg, end)) + return CURLE_OK; + return CURLE_BAD_FUNCTION_ARGUMENT; +} +#endif + /* - * Convert an ASN.1 Boolean value into its string representation. Return the - * dynamically allocated string, or NULL if source is not an ASN.1 Boolean - * value. + * Convert an ASN.1 Boolean value into its string representation. + * + * Return error code. */ -static const char *bool2str(const char *beg, const char *end) +static CURLcode bool2str(struct dynbuf *store, + const char *beg, const char *end) { if(end - beg != 1) - return NULL; - return strdup(*beg? "TRUE": "FALSE"); + return CURLE_BAD_FUNCTION_ARGUMENT; + return curlx_dyn_add(store, *beg ? "TRUE": "FALSE"); } /* * Convert an ASN.1 octet string to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error code. */ -static const char *octet2str(const char *beg, const char *end) +static CURLcode octet2str(struct dynbuf *store, + const char *beg, const char *end) { - struct dynbuf buf; - CURLcode result; - - Curl_dyn_init(&buf, 3 * CURL_ASN1_MAX + 1); - result = Curl_dyn_addn(&buf, "", 0); + CURLcode result = CURLE_OK; while(!result && beg < end) - result = Curl_dyn_addf(&buf, "%02x:", (unsigned char) *beg++); + result = curlx_dyn_addf(store, "%02x:", (unsigned char) *beg++); - return Curl_dyn_ptr(&buf); + return result; } -static const char *bit2str(const char *beg, const char *end) +static CURLcode bit2str(struct dynbuf *store, + const char *beg, const char *end) { - /* Convert an ASN.1 bit string to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ + /* Convert an ASN.1 bit string to a printable string. */ if(++beg > end) - return NULL; - return octet2str(beg, end); + return CURLE_BAD_FUNCTION_ARGUMENT; + return octet2str(store, beg, end); } /* * Convert an ASN.1 integer value into its string representation. - * Return the dynamically allocated string, or NULL if source is not an - * ASN.1 integer value. + * + * Returns error. */ -static const char *int2str(const char *beg, const char *end) +static CURLcode int2str(struct dynbuf *store, + const char *beg, const char *end) { unsigned int val = 0; size_t n = end - beg; if(!n) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; if(n > 4) - return octet2str(beg, end); + return octet2str(store, beg, end); /* Represent integers <= 32-bit as a single value. */ if(*beg & 0x80) @@ -324,25 +344,24 @@ static const char *int2str(const char *beg, const char *end) do val = (val << 8) | *(const unsigned char *) beg++; while(beg < end); - return curl_maprintf("%s%x", val >= 10? "0x": "", val); + return curlx_dyn_addf(store, "%s%x", val >= 10 ? "0x" : "", val); } /* - * Perform a lazy conversion from an ASN.1 typed string to UTF8. Allocate the - * destination buffer dynamically. The allocation size will normally be too - * large: this is to avoid buffer overflows. - * Terminate the string with a nul byte and return the converted - * string length. + * Convert from an ASN.1 typed string to UTF8. + * + * The result is stored in a dynbuf that is inited by the user of this + * function. + * + * Returns error. */ -static ssize_t -utf8asn1str(char **to, int type, const char *from, const char *end) +static CURLcode +utf8asn1str(struct dynbuf *to, int type, const char *from, const char *end) { size_t inlength = end - from; int size = 1; - size_t outlength; - char *buf; + CURLcode result = CURLE_OK; - *to = NULL; switch(type) { case CURL_ASN1_BMP_STRING: size = 2; @@ -358,133 +377,84 @@ utf8asn1str(char **to, int type, const char *from, const char *end) case CURL_ASN1_UTF8_STRING: break; default: - return -1; /* Conversion not supported. */ + return CURLE_BAD_FUNCTION_ARGUMENT; /* Conversion not supported. */ } if(inlength % size) - return -1; /* Length inconsistent with character size. */ - if(inlength / size > (SIZE_T_MAX - 1) / 4) - return -1; /* Too big. */ - buf = malloc(4 * (inlength / size) + 1); - if(!buf) - return -1; /* Not enough memory. */ + /* Length inconsistent with character size. */ + return CURLE_BAD_FUNCTION_ARGUMENT; if(type == CURL_ASN1_UTF8_STRING) { /* Just copy. */ - outlength = inlength; - if(outlength) - memcpy(buf, from, outlength); + if(inlength) + result = curlx_dyn_addn(to, from, inlength); } else { - for(outlength = 0; from < end;) { - int charsize; - unsigned int wc; + while(!result && (from < end)) { + char buf[4]; /* decode buffer */ + size_t charsize = 1; + unsigned int wc = 0; - wc = 0; switch(size) { case 4: wc = (wc << 8) | *(const unsigned char *) from++; wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ + FALLTHROUGH(); case 2: wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* case 1: */ wc = (wc << 8) | *(const unsigned char *) from++; } - charsize = 1; if(wc >= 0x00000080) { if(wc >= 0x00000800) { if(wc >= 0x00010000) { if(wc >= 0x00200000) { - free(buf); - return -1; /* Invalid char. size for target encoding. */ + /* Invalid char. size for target encoding. */ + return CURLE_WEIRD_SERVER_REPLY; } - buf[outlength + 3] = (char) (0x80 | (wc & 0x3F)); + buf[3] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x00010000; charsize++; } - buf[outlength + 2] = (char) (0x80 | (wc & 0x3F)); + buf[2] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x00000800; charsize++; } - buf[outlength + 1] = (char) (0x80 | (wc & 0x3F)); + buf[1] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x000000C0; charsize++; } - buf[outlength] = (char) wc; - outlength += charsize; + buf[0] = (char) wc; + result = curlx_dyn_addn(to, buf, charsize); } } - buf[outlength] = '\0'; - *to = buf; - return outlength; -} - -/* - * Convert an ASN.1 String into its UTF-8 string representation. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *string2str(int type, const char *beg, const char *end) -{ - char *buf; - if(utf8asn1str(&buf, type, beg, end) < 0) - return NULL; - return buf; -} - -/* - * Decimal ASCII encode unsigned integer `x' into the buflen sized buffer at - * buf. Return the total number of encoded digits, even if larger than - * `buflen'. - */ -static size_t encodeUint(char *buf, size_t buflen, unsigned int x) -{ - size_t i = 0; - unsigned int y = x / 10; - - if(y) { - i = encodeUint(buf, buflen, y); - x -= y * 10; - } - if(i < buflen) - buf[i] = (char) ('0' + x); - i++; - if(i < buflen) - buf[i] = '\0'; /* Store a terminator if possible. */ - return i; + return result; } /* * Convert an ASN.1 OID into its dotted string representation. - * Store the result in th `n'-byte buffer at `buf'. - * Return the converted string length, or 0 on errors. + * + * Return error code. */ -static size_t encodeOID(char *buf, size_t buflen, - const char *beg, const char *end) +static CURLcode encodeOID(struct dynbuf *store, + const char *beg, const char *end) { - size_t i; unsigned int x; unsigned int y; + CURLcode result = CURLE_OK; /* Process the first two numbers. */ y = *(const unsigned char *) beg++; x = y / 40; y -= x * 40; - i = encodeUint(buf, buflen, x); - if(i < buflen) - buf[i] = '.'; - i++; - if(i >= buflen) - i += encodeUint(NULL, 0, y); - else - i += encodeUint(buf + i, buflen - i, y); + + result = curlx_dyn_addf(store, "%u.%u", x, y); + if(result) + return result; /* Process the trailing numbers. */ while(beg < end) { - if(i < buflen) - buf[i] = '.'; - i++; x = 0; do { if(x & 0xFF000000) @@ -492,46 +462,44 @@ static size_t encodeOID(char *buf, size_t buflen, y = *(const unsigned char *) beg++; x = (x << 7) | (y & 0x7F); } while(y & 0x80); - if(i >= buflen) - i += encodeUint(NULL, 0, x); - else - i += encodeUint(buf + i, buflen - i, x); + result = curlx_dyn_addf(store, ".%u", x); } - if(i < buflen) - buf[i] = '\0'; - return i; + return result; } /* * Convert an ASN.1 OID into its dotted or symbolic string representation. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error code. */ -static const char *OID2str(const char *beg, const char *end, bool symbolic) +static CURLcode OID2str(struct dynbuf *store, + const char *beg, const char *end, bool symbolic) { - char *buf = NULL; + CURLcode result = CURLE_OK; if(beg < end) { - size_t buflen = encodeOID(NULL, 0, beg, end); - if(buflen) { - buf = malloc(buflen + 1); /* one extra for the zero byte */ - if(buf) { - encodeOID(buf, buflen, beg, end); - buf[buflen] = '\0'; - - if(symbolic) { - const struct Curl_OID *op = searchOID(buf); - if(op) { - free(buf); - buf = strdup(op->textoid); - } - } + if(symbolic) { + struct dynbuf buf; + curlx_dyn_init(&buf, CURL_X509_STR_MAX); + result = encodeOID(&buf, beg, end); + + if(!result) { + const struct Curl_OID *op = searchOID(curlx_dyn_ptr(&buf)); + if(op) + result = curlx_dyn_add(store, op->textoid); + else + result = curlx_dyn_add(store, curlx_dyn_ptr(&buf)); + curlx_dyn_free(&buf); } } + else + result = encodeOID(store, beg, end); } - return buf; + return result; } -static const char *GTime2str(const char *beg, const char *end) +static CURLcode GTime2str(struct dynbuf *store, + const char *beg, const char *end) { const char *tzp; const char *fracp; @@ -543,7 +511,7 @@ static const char *GTime2str(const char *beg, const char *end) /* Convert an ASN.1 Generalized time to a printable string. Return the dynamically allocated string, or NULL if an error occurs. */ - for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++) + for(fracp = beg; fracp < end && ISDIGIT(*fracp); fracp++) ; /* Get seconds digits. */ @@ -554,52 +522,76 @@ static const char *GTime2str(const char *beg, const char *end) break; case 2: sec1 = fracp[-2]; - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: sec2 = fracp[-1]; break; default: - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; } - /* Scan for timezone, measure fractional seconds. */ + /* timezone follows optional fractional seconds. */ tzp = fracp; - fracl = 0; + fracl = 0; /* no fractional seconds detected so far */ if(fracp < end && (*fracp == '.' || *fracp == ',')) { - fracp++; - do + /* Have fractional seconds, e.g. "[.,]\d+". How many? */ + fracp++; /* should be a digit char or BAD ARGUMENT */ + tzp = fracp; + while(tzp < end && ISDIGIT(*tzp)) tzp++; - while(tzp < end && *tzp >= '0' && *tzp <= '9'); - /* Strip leading zeroes in fractional seconds. */ - for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--) - ; + if(tzp == fracp) /* never looped, no digit after [.,] */ + return CURLE_BAD_FUNCTION_ARGUMENT; + fracl = tzp - fracp; /* number of fractional sec digits */ + DEBUGASSERT(fracl > 0); + /* Strip trailing zeroes in fractional seconds. + * May reduce fracl to 0 if only '0's are present. */ + while(fracl && fracp[fracl - 1] == '0') + fracl--; } /* Process timezone. */ - if(tzp >= end) - ; /* Nothing to do. */ + if(tzp >= end) { + tzp = ""; + tzl = 0; + } else if(*tzp == 'Z') { - tzp = " GMT"; - end = tzp + 4; + sep = " "; + tzp = "GMT"; + tzl = 3; + } + else if((*tzp == '+') || (*tzp == '-')) { + sep = " UTC"; + tzl = end - tzp; } else { sep = " "; - tzp++; + tzl = end - tzp; } - tzl = end - tzp; - return curl_maprintf("%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", - beg, beg + 4, beg + 6, - beg + 8, beg + 10, sec1, sec2, - fracl? ".": "", (int)fracl, fracp, - sep, (int)tzl, tzp); + return curlx_dyn_addf(store, + "%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", + beg, beg + 4, beg + 6, + beg + 8, beg + 10, sec1, sec2, + fracl ? ".": "", (int)fracl, fracp, + sep, (int)tzl, tzp); +} + +#ifdef UNITTESTS +/* used by unit1656.c */ +CURLcode Curl_x509_GTime2str(struct dynbuf *store, + const char *beg, const char *end) +{ + return GTime2str(store, beg, end); } +#endif /* - * Convert an ASN.1 UTC time to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * Convert an ASN.1 UTC time to a printable string. + * + * Return error code. */ -static const char *UTime2str(const char *beg, const char *end) +static CURLcode UTime2str(struct dynbuf *store, + const char *beg, const char *end) { const char *tzp; size_t tzl; @@ -612,15 +604,16 @@ static const char *UTime2str(const char *beg, const char *end) switch(tzp - sec) { case 0: sec = "00"; + FALLTHROUGH(); case 2: break; default: - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; } /* Process timezone. */ if(tzp >= end) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; if(*tzp == 'Z') { tzp = "GMT"; end = tzp + 3; @@ -629,42 +622,53 @@ static const char *UTime2str(const char *beg, const char *end) tzp++; tzl = end - tzp; - return curl_maprintf("%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", - 20 - (*beg >= '5'), beg, beg + 2, beg + 4, - beg + 6, beg + 8, sec, - (int)tzl, tzp); + return curlx_dyn_addf(store, "%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", + 20 - (*beg >= '5'), beg, beg + 2, beg + 4, + beg + 6, beg + 8, sec, + (int)tzl, tzp); } /* * Convert an ASN.1 element to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error */ -static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) +static CURLcode ASN1tostr(struct dynbuf *store, + struct Curl_asn1Element *elem, int type) { + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; if(elem->constructed) - return NULL; /* No conversion of structured elements. */ + return result; /* No conversion of structured elements. */ if(!type) type = elem->tag; /* Type not forced: use element tag as type. */ switch(type) { case CURL_ASN1_BOOLEAN: - return bool2str(elem->beg, elem->end); + result = bool2str(store, elem->beg, elem->end); + break; case CURL_ASN1_INTEGER: case CURL_ASN1_ENUMERATED: - return int2str(elem->beg, elem->end); + result = int2str(store, elem->beg, elem->end); + break; case CURL_ASN1_BIT_STRING: - return bit2str(elem->beg, elem->end); + result = bit2str(store, elem->beg, elem->end); + break; case CURL_ASN1_OCTET_STRING: - return octet2str(elem->beg, elem->end); + result = octet2str(store, elem->beg, elem->end); + break; case CURL_ASN1_NULL: - return strdup(""); + result = curlx_dyn_addn(store, "", 1); + break; case CURL_ASN1_OBJECT_IDENTIFIER: - return OID2str(elem->beg, elem->end, TRUE); + result = OID2str(store, elem->beg, elem->end, TRUE); + break; case CURL_ASN1_UTC_TIME: - return UTime2str(elem->beg, elem->end); + result = UTime2str(store, elem->beg, elem->end); + break; case CURL_ASN1_GENERALIZED_TIME: - return GTime2str(elem->beg, elem->end); + result = GTime2str(store, elem->beg, elem->end); + break; case CURL_ASN1_UTF8_STRING: case CURL_ASN1_NUMERIC_STRING: case CURL_ASN1_PRINTABLE_STRING: @@ -673,87 +677,101 @@ static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) case CURL_ASN1_VISIBLE_STRING: case CURL_ASN1_UNIVERSAL_STRING: case CURL_ASN1_BMP_STRING: - return string2str(type, elem->beg, elem->end); + result = utf8asn1str(store, type, elem->beg, elem->end); + break; } - return NULL; /* Unsupported. */ + return result; } /* - * ASCII encode distinguished name at `dn' into the `buflen'-sized buffer at - * `buf'. + * ASCII encode distinguished name at `dn' into the store dynbuf. * - * Returns the total string length, even if larger than `buflen' or -1 on - * error. + * Returns error. */ -static ssize_t encodeDN(char *buf, size_t buflen, struct Curl_asn1Element *dn) +static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) { struct Curl_asn1Element rdn; struct Curl_asn1Element atv; struct Curl_asn1Element oid; struct Curl_asn1Element value; - size_t l = 0; const char *p1; const char *p2; const char *p3; const char *str; + CURLcode result = CURLE_OK; + bool added = FALSE; + struct dynbuf temp; + curlx_dyn_init(&temp, CURL_X509_STR_MAX); for(p1 = dn->beg; p1 < dn->end;) { p1 = getASN1Element(&rdn, p1, dn->end); - if(!p1) - return -1; + if(!p1) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } for(p2 = rdn.beg; p2 < rdn.end;) { p2 = getASN1Element(&atv, p2, rdn.end); - if(!p2) - return -1; + if(!p2) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } p3 = getASN1Element(&oid, atv.beg, atv.end); - if(!p3) - return -1; - if(!getASN1Element(&value, p3, atv.end)) - return -1; - str = ASN1tostr(&oid, 0); - if(!str) - return -1; + if(!p3) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + if(!getASN1Element(&value, p3, atv.end)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + curlx_dyn_reset(&temp); + result = ASN1tostr(&temp, &oid, 0); + if(result) + goto error; + + str = curlx_dyn_ptr(&temp); + + if(!str) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } /* Encode delimiter. If attribute has a short uppercase name, delimiter is ", ". */ - if(l) { - for(p3 = str; ISUPPER(*p3); p3++) - ; - for(p3 = (*p3 || p3 - str > 2)? "/": ", "; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } + for(p3 = str; ISUPPER(*p3); p3++) + ; + if(added) { + if(p3 - str > 2) + result = curlx_dyn_addn(store, "/", 1); + else + result = curlx_dyn_addn(store, ", ", 2); + if(result) + goto error; } /* Encode attribute name. */ - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); + result = curlx_dyn_add(store, str); + if(result) + goto error; /* Generate equal sign. */ - if(l < buflen) - buf[l] = '='; - l++; + result = curlx_dyn_addn(store, "=", 1); + if(result) + goto error; /* Generate value. */ - str = ASN1tostr(&value, 0); - if(!str) - return -1; - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); + result = ASN1tostr(store, &value, 0); + if(result) + goto error; + curlx_dyn_reset(&temp); + added = TRUE; /* use separator for next */ } } +error: + curlx_dyn_free(&temp); - return l; + return result; } #endif /* WANT_EXTRACT_CERTINFO */ @@ -882,25 +900,9 @@ int Curl_parseX509(struct Curl_X509certificate *cert, #ifdef WANT_EXTRACT_CERTINFO -/* - * Copy at most 64-characters, terminate with a newline and returns the - * effective number of stored characters. - */ -static size_t copySubstring(char *to, const char *from) -{ - size_t i; - for(i = 0; i < 64; i++) { - to[i] = *from; - if(!*from++) - break; - } - - to[i++] = '\n'; - return i; -} - -static const char *dumpAlgo(struct Curl_asn1Element *param, - const char *beg, const char *end) +static CURLcode dumpAlgo(struct dynbuf *store, + struct Curl_asn1Element *param, + const char *beg, const char *end) { struct Curl_asn1Element oid; @@ -908,14 +910,16 @@ static const char *dumpAlgo(struct Curl_asn1Element *param, beg = getASN1Element(&oid, beg, end); if(!beg) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; param->header = NULL; param->tag = 0; param->beg = param->end = end; - if(beg < end) - if(!getASN1Element(param, beg, end)) - return NULL; - return OID2str(oid.beg, oid.end, TRUE); + if(beg < end) { + const char *p = getASN1Element(param, beg, end); + if(!p) + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return OID2str(store, oid.beg, oid.end, TRUE); } /* @@ -932,24 +936,47 @@ static CURLcode ssl_push_certinfo(struct Curl_easy *data, return Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen); } -/* return 0 on success, 1 on error */ -static int do_pubkey_field(struct Curl_easy *data, int certnum, - const char *label, struct Curl_asn1Element *elem) +/* + * This is a convenience function for push_certinfo_len that takes a + * dynbuf value. + * + * It also does the verbose output if !certnum. + */ +static CURLcode ssl_push_certinfo_dyn(struct Curl_easy *data, + int certnum, + const char *label, + struct dynbuf *ptr) { - const char *output; - CURLcode result = CURLE_OK; + size_t valuelen = curlx_dyn_len(ptr); + char *value = curlx_dyn_ptr(ptr); + + CURLcode result = Curl_ssl_push_certinfo_len(data, certnum, label, + value, valuelen); + + if(!certnum && !result) + infof(data, " %s: %s", label, value); + + return result; +} + +static CURLcode do_pubkey_field(struct Curl_easy *data, int certnum, + const char *label, + struct Curl_asn1Element *elem) +{ + CURLcode result; + struct dynbuf out; + + curlx_dyn_init(&out, CURL_X509_STR_MAX); /* Generate a certificate information record for the public key. */ - output = ASN1tostr(elem, 0); - if(output) { + result = ASN1tostr(&out, elem, 0); + if(!result) { if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, label, output); - if(!certnum && !result) - infof(data, " %s: %s", label, output); - free((char *) output); + result = ssl_push_certinfo_dyn(data, certnum, label, &out); + curlx_dyn_free(&out); } - return result ? 1 : 0; + return result; } /* return 0 on success, 1 on error */ @@ -970,14 +997,15 @@ static int do_pubkey(struct Curl_easy *data, int certnum, */ const size_t len = ((pubkey->end - pubkey->beg - 2) * 4); if(!certnum) - infof(data, " ECC Public Key (%lu bits)", len); + infof(data, " ECC Public Key (%zu bits)", len); if(data->set.ssl.certinfo) { char q[sizeof(len) * 8 / 3 + 1]; - (void)msnprintf(q, sizeof(q), "%lu", len); + (void)msnprintf(q, sizeof(q), "%zu", len); if(ssl_push_certinfo(data, certnum, "ECC Public Key", q)) return 1; } - return do_pubkey_field(data, certnum, "ecPublicKey", pubkey); + return do_pubkey_field(data, certnum, "ecPublicKey", pubkey) == CURLE_OK + ? 0 : 1; } /* Get the public key (single element). */ @@ -998,16 +1026,16 @@ static int do_pubkey(struct Curl_easy *data, int certnum, len = ((elem.end - q) * 8); if(len) { unsigned int i; - for(i = *(unsigned char *) q; !(i & 0x80); i <<= 1) + for(i = *(const unsigned char *) q; !(i & 0x80); i <<= 1) len--; } if(len > 32) elem.beg = q; /* Strip leading zero bytes. */ if(!certnum) - infof(data, " RSA Public Key (%lu bits)", len); + infof(data, " RSA Public Key (%zu bits)", len); if(data->set.ssl.certinfo) { char r[sizeof(len) * 8 / 3 + 1]; - msnprintf(r, sizeof(r), "%lu", len); + msnprintf(r, sizeof(r), "%zu", len); if(ssl_push_certinfo(data, certnum, "RSA Public Key", r)) return 1; } @@ -1055,24 +1083,12 @@ static int do_pubkey(struct Curl_easy *data, int certnum, /* * Convert an ASN.1 distinguished name into a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * Return error. */ -static const char *DNtostr(struct Curl_asn1Element *dn) +static CURLcode DNtostr(struct dynbuf *store, + struct Curl_asn1Element *dn) { - char *buf = NULL; - ssize_t buflen = encodeDN(NULL, 0, dn); - - if(buflen >= 0) { - buf = malloc(buflen + 1); - if(buf) { - if(encodeDN(buf, buflen + 1, dn) == -1) { - free(buf); - return NULL; - } - buf[buflen] = '\0'; - } - } - return buf; + return encodeDN(store, dn); } CURLcode Curl_extract_certinfo(struct Curl_easy *data, @@ -1082,19 +1098,19 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, { struct Curl_X509certificate cert; struct Curl_asn1Element param; - const char *ccp; - char *cp1; - size_t cl1; - char *cp2; + char *certptr; + size_t clen; + struct dynbuf out; CURLcode result = CURLE_OK; unsigned int version; - size_t i; - size_t j; + const char *ptr; + int rc; if(!data->set.ssl.certinfo) if(certnum) return CURLE_OK; + curlx_dyn_init(&out, CURL_X509_STR_MAX); /* Prepare the certificate information for curl_easy_getinfo(). */ /* Extract the certificate ASN.1 elements. */ @@ -1102,135 +1118,126 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, return CURLE_PEER_FAILED_VERIFICATION; /* Subject. */ - ccp = DNtostr(&cert.subject); - if(!ccp) - return CURLE_OUT_OF_MEMORY; + result = DNtostr(&out, &cert.subject); + if(result) + goto done; if(data->set.ssl.certinfo) { - result = ssl_push_certinfo(data, certnum, "Subject", ccp); + result = ssl_push_certinfo_dyn(data, certnum, "Subject", &out); if(result) - return result; + goto done; } - if(!certnum) - infof(data, "%2d Subject: %s", certnum, ccp); - free((char *) ccp); + curlx_dyn_reset(&out); /* Issuer. */ - ccp = DNtostr(&cert.issuer); - if(!ccp) - return CURLE_OUT_OF_MEMORY; + result = DNtostr(&out, &cert.issuer); + if(result) + goto done; if(data->set.ssl.certinfo) { - result = ssl_push_certinfo(data, certnum, "Issuer", ccp); + result = ssl_push_certinfo_dyn(data, certnum, "Issuer", &out); + if(result) + goto done; } - if(!certnum) - infof(data, " Issuer: %s", ccp); - free((char *) ccp); - if(result) - return result; + curlx_dyn_reset(&out); /* Version (always fits in less than 32 bits). */ version = 0; - for(ccp = cert.version.beg; ccp < cert.version.end; ccp++) - version = (version << 8) | *(const unsigned char *) ccp; + for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) + version = (version << 8) | *(const unsigned char *) ptr; if(data->set.ssl.certinfo) { - ccp = curl_maprintf("%x", version); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - result = ssl_push_certinfo(data, certnum, "Version", ccp); - free((char *) ccp); + result = curlx_dyn_addf(&out, "%x", version); + if(result) + goto done; + result = ssl_push_certinfo_dyn(data, certnum, "Version", &out); if(result) - return result; + goto done; + curlx_dyn_reset(&out); } - if(!certnum) - infof(data, " Version: %u (0x%x)", version + 1, version); /* Serial number. */ - ccp = ASN1tostr(&cert.serialNumber, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Serial Number", ccp); - if(!certnum) - infof(data, " Serial Number: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.serialNumber, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Serial Number", &out); + if(result) + goto done; + } + curlx_dyn_reset(&out); /* Signature algorithm .*/ - ccp = dumpAlgo(¶m, cert.signatureAlgorithm.beg, - cert.signatureAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp); - if(!certnum) - infof(data, " Signature Algorithm: %s", ccp); - free((char *) ccp); + result = dumpAlgo(&out, ¶m, cert.signatureAlgorithm.beg, + cert.signatureAlgorithm.end); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature Algorithm", + &out); + if(result) + goto done; + } + curlx_dyn_reset(&out); /* Start Date. */ - ccp = ASN1tostr(&cert.notBefore, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Start Date", ccp); - if(!certnum) - infof(data, " Start Date: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.notBefore, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Start Date", &out); + if(result) + goto done; + } + curlx_dyn_reset(&out); /* Expire Date. */ - ccp = ASN1tostr(&cert.notAfter, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Expire Date", ccp); - if(!certnum) - infof(data, " Expire Date: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.notAfter, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Expire Date", &out); + if(result) + goto done; + } + curlx_dyn_reset(&out); /* Public Key Algorithm. */ - ccp = dumpAlgo(¶m, cert.subjectPublicKeyAlgorithm.beg, - cert.subjectPublicKeyAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Public Key Algorithm", - ccp); - if(!result) { - int ret; - if(!certnum) - infof(data, " Public Key Algorithm: %s", ccp); - ret = do_pubkey(data, certnum, ccp, ¶m, &cert.subjectPublicKey); - if(ret) - result = CURLE_OUT_OF_MEMORY; /* the most likely error */ - } - free((char *) ccp); + result = dumpAlgo(&out, ¶m, cert.subjectPublicKeyAlgorithm.beg, + cert.subjectPublicKeyAlgorithm.end); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Public Key Algorithm", + &out); + if(result) + goto done; + } + + rc = do_pubkey(data, certnum, curlx_dyn_ptr(&out), + ¶m, &cert.subjectPublicKey); + if(rc) { + result = CURLE_OUT_OF_MEMORY; /* the most likely error */ + goto done; + } + curlx_dyn_reset(&out); /* Signature. */ - ccp = ASN1tostr(&cert.signature, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Signature", ccp); - if(!certnum) - infof(data, " Signature: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.signature, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature", &out); + if(result) + goto done; + } + curlx_dyn_reset(&out); /* Generate PEM certificate. */ - result = Curl_base64_encode(cert.certificate.beg, - cert.certificate.end - cert.certificate.beg, - &cp1, &cl1); + result = curlx_base64_encode(cert.certificate.beg, + cert.certificate.end - cert.certificate.beg, + &certptr, &clen); if(result) - return result; - /* Compute the number of characters in final certificate string. Format is: + goto done; + + /* Generate the final output certificate string. Format is: -----BEGIN CERTIFICATE-----\n \n . @@ -1238,208 +1245,37 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, . -----END CERTIFICATE-----\n */ - i = 28 + cl1 + (cl1 + 64 - 1) / 64 + 26; - cp2 = malloc(i + 1); - if(!cp2) { - free(cp1); - return CURLE_OUT_OF_MEMORY; - } - /* Build the certificate string. */ - i = copySubstring(cp2, "-----BEGIN CERTIFICATE-----"); - for(j = 0; j < cl1; j += 64) - i += copySubstring(cp2 + i, cp1 + j); - i += copySubstring(cp2 + i, "-----END CERTIFICATE-----"); - cp2[i] = '\0'; - free(cp1); - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Cert", cp2); - if(!certnum) - infof(data, "%s", cp2); - free(cp2); - return result; -} - -#endif /* WANT_EXTRACT_CERTINFO */ - -#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL - * or USE_SECTRANSP */ - -#ifdef WANT_VERIFYHOST - -static const char *checkOID(const char *beg, const char *end, - const char *oid) -{ - struct Curl_asn1Element e; - const char *ccp; - const char *p; - bool matched; - - /* Check if first ASN.1 element at `beg' is the given OID. - Return a pointer in the source after the OID if found, else NULL. */ - - ccp = getASN1Element(&e, beg, end); - if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER) - return NULL; - - p = OID2str(e.beg, e.end, FALSE); - if(!p) - return NULL; - - matched = !strcmp(p, oid); - free((char *) p); - return matched? ccp: NULL; -} - -CURLcode Curl_verifyhost(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *beg, const char *end) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_X509certificate cert; - struct Curl_asn1Element dn; - struct Curl_asn1Element elem; - struct Curl_asn1Element ext; - struct Curl_asn1Element name; - const char *p; - const char *q; - char *dnsname; - int matched = -1; - size_t addrlen = (size_t) -1; - ssize_t len; - size_t hostlen; - -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - - /* Verify that connection server matches info in X509 certificate at - `beg'..`end'. */ - - if(!conn_config->verifyhost) - return CURLE_OK; - - if(Curl_parseX509(&cert, beg, end)) - return CURLE_PEER_FAILED_VERIFICATION; - - hostlen = strlen(connssl->hostname); - - /* Get the server IP address. */ -#ifdef ENABLE_IPV6 - if(cf->conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, connssl->hostname, &addr)) - addrlen = sizeof(struct in6_addr); - else -#endif - if(Curl_inet_pton(AF_INET, connssl->hostname, &addr)) - addrlen = sizeof(struct in_addr); - /* Process extensions. */ - for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) { - p = getASN1Element(&ext, p, cert.extensions.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - - /* Check if extension is a subjectAlternativeName. */ - ext.beg = checkOID(ext.beg, ext.end, sanOID); - if(ext.beg) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - /* Skip critical if present. */ - if(elem.tag == CURL_ASN1_BOOLEAN) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - } - /* Parse the octet string contents: is a single sequence. */ - if(!getASN1Element(&elem, elem.beg, elem.end)) - return CURLE_PEER_FAILED_VERIFICATION; - /* Check all GeneralNames. */ - for(q = elem.beg; matched != 1 && q < elem.end;) { - q = getASN1Element(&name, q, elem.end); - if(!q) - break; - switch(name.tag) { - case 2: /* DNS name. */ - len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING, - name.beg, name.end); - if(len > 0 && (size_t)len == strlen(dnsname)) - matched = Curl_cert_hostcheck(dnsname, (size_t)len, - connssl->hostname, hostlen); - else - matched = 0; - free(dnsname); - break; - - case 7: /* IP address. */ - matched = (size_t)(name.end - name.beg) == addrlen && - !memcmp(&addr, name.beg, addrlen); - break; - } - } - } - } + curlx_dyn_reset(&out); - switch(matched) { - case 1: - /* an alternative name matched the server hostname */ - infof(data, " subjectAltName: %s matched", connssl->dispname); - return CURLE_OK; - case 0: - /* an alternative name field existed, but didn't match and then - we MUST fail */ - infof(data, " subjectAltName does not match %s", connssl->dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - - /* Process subject. */ - name.header = NULL; - name.beg = name.end = ""; - q = cert.subject.beg; - /* we have to look to the last occurrence of a commonName in the - distinguished one to get the most significant one. */ - while(q < cert.subject.end) { - q = getASN1Element(&dn, q, cert.subject.end); - if(!q) - break; - for(p = dn.beg; p < dn.end;) { - p = getASN1Element(&elem, p, dn.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - /* We have a DN's AttributeTypeAndValue: check it in case it's a CN. */ - elem.beg = checkOID(elem.beg, elem.end, cnOID); - if(elem.beg) - name = elem; /* Latch CN. */ - } - } - - /* Check the CN if found. */ - if(!getASN1Element(&elem, name.beg, name.end)) - failf(data, "SSL: unable to obtain common name from peer certificate"); - else { - len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end); - if(len < 0) { - free(dnsname); - return CURLE_OUT_OF_MEMORY; - } - if(strlen(dnsname) != (size_t) len) /* Nul byte in string ? */ - failf(data, "SSL: illegal cert name field"); - else if(Curl_cert_hostcheck((const char *) dnsname, - len, connssl->hostname, hostlen)) { - infof(data, " common name: %s (matched)", dnsname); - free(dnsname); - return CURLE_OK; + /* Build the certificate string. */ + result = curlx_dyn_add(&out, "-----BEGIN CERTIFICATE-----\n"); + if(!result) { + size_t j = 0; + + while(!result && (j < clen)) { + size_t chunksize = (clen - j) > 64 ? 64 : (clen - j); + result = curlx_dyn_addn(&out, &certptr[j], chunksize); + if(!result) + result = curlx_dyn_addn(&out, "\n", 1); + j += chunksize; } - else - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", dnsname, connssl->dispname); - free(dnsname); + if(!result) + result = curlx_dyn_add(&out, "-----END CERTIFICATE-----\n"); } + free(certptr); + if(!result) + if(data->set.ssl.certinfo) + result = ssl_push_certinfo_dyn(data, certnum, "Cert", &out); - return CURLE_PEER_FAILED_VERIFICATION; +done: + if(result) + failf(data, "Failed extracting certificate chain"); + curlx_dyn_free(&out); + return result; } -#endif /* WANT_VERIFYHOST */ +#endif /* WANT_EXTRACT_CERTINFO */ + +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP + or USE_MBEDTLS or USE_RUSTLS */ diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.h b/Utilities/cmcurl/lib/vtls/x509asn1.h index 5496de40e40..1c9c35c3d4e 100644 --- a/Utilities/cmcurl/lib/vtls/x509asn1.h +++ b/Utilities/cmcurl/lib/vtls/x509asn1.h @@ -25,13 +25,14 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) || defined(USE_RUSTLS) -#include "cfilters.h" -#include "urldata.h" +#include "../cfilters.h" +#include "../urldata.h" /* * Types. @@ -44,7 +45,7 @@ struct Curl_asn1Element { const char *end; /* Pointer to 1st byte after element. */ unsigned char class; /* ASN.1 element class. */ unsigned char tag; /* ASN.1 element tag. */ - bool constructed; /* Element is constructed. */ + BIT(constructed); /* Element is constructed. */ }; /* X509 certificate: RFC 5280. */ @@ -76,6 +77,20 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, const char *beg, const char *end); CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data, const char *beg, const char *end); -#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL - * or USE_SECTRANSP */ + +#ifdef UNITTESTS +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) || defined(USE_RUSTLS) + +/* used by unit1656.c */ +CURLcode Curl_x509_GTime2str(struct dynbuf *store, + const char *beg, const char *end); +/* used by unit1657.c */ +CURLcode Curl_x509_getASN1Element(struct Curl_asn1Element *elem, + const char *beg, const char *end); +#endif +#endif + +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP + or USE_MBEDTLS or USE_RUSTLS */ #endif /* HEADER_CURL_X509ASN1_H */ diff --git a/Utilities/cmcurl/lib/warnless.c b/Utilities/cmcurl/lib/warnless.c deleted file mode 100644 index 10c91fb28f3..00000000000 --- a/Utilities/cmcurl/lib/warnless.c +++ /dev/null @@ -1,431 +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" - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -#ifdef HAVE_NETINET_IN_H -# include -#endif -#ifdef HAVE_ARPA_INET_H -# include -#endif - -#endif /* __INTEL_COMPILER && __unix__ */ - -#define BUILDING_WARNLESS_C 1 - -#include "warnless.h" - -#include - -#define CURL_MASK_UCHAR ((unsigned char)~0) -#define CURL_MASK_SCHAR (CURL_MASK_UCHAR >> 1) - -#define CURL_MASK_USHORT ((unsigned short)~0) -#define CURL_MASK_SSHORT (CURL_MASK_USHORT >> 1) - -#define CURL_MASK_UINT ((unsigned int)~0) -#define CURL_MASK_SINT (CURL_MASK_UINT >> 1) - -#define CURL_MASK_ULONG ((unsigned long)~0) -#define CURL_MASK_SLONG (CURL_MASK_ULONG >> 1) - -#define CURL_MASK_UCOFFT ((unsigned CURL_TYPEOF_CURL_OFF_T)~0) -#define CURL_MASK_SCOFFT (CURL_MASK_UCOFFT >> 1) - -#define CURL_MASK_USIZE_T ((size_t)~0) -#define CURL_MASK_SSIZE_T (CURL_MASK_USIZE_T >> 1) - -/* -** unsigned long to unsigned short -*/ - -unsigned short curlx_ultous(unsigned long ulnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_USHORT); - return (unsigned short)(ulnum & (unsigned long) CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** 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 - - DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_UCHAR); - return (unsigned char)(ulnum & (unsigned long) CURL_MASK_UCHAR); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned size_t to signed curl_off_t -*/ - -curl_off_t curlx_uztoso(size_t uznum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#elif defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable:4310) /* cast truncates constant value */ -#endif - - DEBUGASSERT(uznum <= (size_t) CURL_MASK_SCOFFT); - return (curl_off_t)(uznum & (size_t) CURL_MASK_SCOFFT); - -#if defined(__INTEL_COMPILER) || defined(_MSC_VER) -# pragma warning(pop) -#endif -} - -/* -** unsigned size_t to signed int -*/ - -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 -} - -/* -** unsigned size_t to unsigned long -*/ - -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_T_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 -} - -/* -** unsigned size_t to unsigned int -*/ - -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_T_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 -} - -/* -** signed long to signed int -*/ - -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 -} - -/* -** signed long to unsigned int -*/ - -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 -} - -/* -** signed long to unsigned short -*/ - -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 -} - -/* -** unsigned size_t to signed ssize_t -*/ - -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 -} - -/* -** signed curl_off_t to unsigned size_t -*/ - -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 -} - -/* -** signed ssize_t to signed int -*/ - -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_T_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 -} - -/* -** unsigned int to unsigned short -*/ - -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 -} - -/* -** signed int to unsigned size_t -*/ - -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 -} - -#ifdef USE_WINSOCK - -/* -** curl_socket_t to signed int -*/ - -int curlx_sktosi(curl_socket_t s) -{ - return (int)((ssize_t) s); -} - -/* -** signed int to curl_socket_t -*/ - -curl_socket_t curlx_sitosk(int i) -{ - return (curl_socket_t)((ssize_t) i); -} - -#endif /* USE_WINSOCK */ - -#if defined(WIN32) - -ssize_t curlx_read(int fd, void *buf, size_t count) -{ - return (ssize_t)read(fd, buf, curlx_uztoui(count)); -} - -ssize_t curlx_write(int fd, const void *buf, size_t count) -{ - return (ssize_t)write(fd, buf, curlx_uztoui(count)); -} - -#endif /* WIN32 */ - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -int curlx_FD_ISSET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - return FD_ISSET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_SET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - FD_SET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_ZERO(fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:593) /* variable was set but never used */ - FD_ZERO(fdset); - #pragma warning(pop) -} - -unsigned short curlx_htons(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return htons(usnum); - #pragma warning(pop) -#endif -} - -unsigned short curlx_ntohs(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return ntohs(usnum); - #pragma warning(pop) -#endif -} - -#endif /* __INTEL_COMPILER && __unix__ */ diff --git a/Utilities/cmcurl/lib/warnless.h b/Utilities/cmcurl/lib/warnless.h deleted file mode 100644 index 99b243379e0..00000000000 --- a/Utilities/cmcurl/lib/warnless.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef HEADER_CURL_WARNLESS_H -#define HEADER_CURL_WARNLESS_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" - -#ifdef USE_WINSOCK -#include /* for curl_socket_t */ -#endif - -#define CURLX_FUNCTION_CAST(target_type, func) \ - (target_type)(void (*) (void))(func) - -unsigned short curlx_ultous(unsigned long ulnum); - -unsigned char curlx_ultouc(unsigned long ulnum); - -int curlx_uztosi(size_t uznum); - -curl_off_t curlx_uztoso(size_t uznum); - -unsigned long curlx_uztoul(size_t uznum); - -unsigned int curlx_uztoui(size_t uznum); - -int curlx_sltosi(long slnum); - -unsigned int curlx_sltoui(long slnum); - -unsigned short curlx_sltous(long slnum); - -ssize_t curlx_uztosz(size_t uznum); - -size_t curlx_sotouz(curl_off_t sonum); - -int curlx_sztosi(ssize_t sznum); - -unsigned short curlx_uitous(unsigned int uinum); - -size_t curlx_sitouz(int sinum); - -#ifdef USE_WINSOCK - -int curlx_sktosi(curl_socket_t s); - -curl_socket_t curlx_sitosk(int i); - -#endif /* USE_WINSOCK */ - -#if defined(WIN32) - -ssize_t curlx_read(int fd, void *buf, size_t count); - -ssize_t curlx_write(int fd, const void *buf, size_t count); - -#ifndef BUILDING_WARNLESS_C -# undef read -# define read(fd, buf, count) curlx_read(fd, buf, count) -# undef write -# define write(fd, buf, count) curlx_write(fd, buf, count) -#endif - -#endif /* WIN32 */ - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -int curlx_FD_ISSET(int fd, fd_set *fdset); - -void curlx_FD_SET(int fd, fd_set *fdset); - -void curlx_FD_ZERO(fd_set *fdset); - -unsigned short curlx_htons(unsigned short usnum); - -unsigned short curlx_ntohs(unsigned short usnum); - -#endif /* __INTEL_COMPILER && __unix__ */ - -#endif /* HEADER_CURL_WARNLESS_H */ diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c index c60bbc95b38..fc02025f1d2 100644 --- a/Utilities/cmcurl/lib/ws.c +++ b/Utilities/cmcurl/lib/ws.c @@ -24,20 +24,23 @@ #include "curl_setup.h" #include -#ifdef USE_WEBSOCKETS +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) #include "urldata.h" +#include "url.h" #include "bufq.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "rand.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "connect.h" #include "sendf.h" #include "multiif.h" #include "ws.h" #include "easyif.h" #include "transfer.h" -#include "nonblock.h" +#include "select.h" +#include "curlx/nonblock.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -45,12 +48,26 @@ #include "memdebug.h" -#define WSBIT_FIN 0x80 -#define WSBIT_OPCODE_CONT 0 -#define WSBIT_OPCODE_TEXT (1) -#define WSBIT_OPCODE_BIN (2) -#define WSBIT_OPCODE_CLOSE (8) -#define WSBIT_OPCODE_PING (9) +/*** + RFC 6455 Section 5.2 + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-------+ + |F|R|R|R| opcode| + |I|S|S|S| (4) | + |N|V|V|V| | + | |1|2|3| | +*/ +#define WSBIT_FIN (0x80) +#define WSBIT_RSV1 (0x40) +#define WSBIT_RSV2 (0x20) +#define WSBIT_RSV3 (0x10) +#define WSBIT_RSV_MASK (WSBIT_RSV1 | WSBIT_RSV2 | WSBIT_RSV3) +#define WSBIT_OPCODE_CONT (0x0) +#define WSBIT_OPCODE_TEXT (0x1) +#define WSBIT_OPCODE_BIN (0x2) +#define WSBIT_OPCODE_CLOSE (0x8) +#define WSBIT_OPCODE_PING (0x9) #define WSBIT_OPCODE_PONG (0xa) #define WSBIT_OPCODE_MASK (0xf) @@ -60,51 +77,206 @@ #define WS_CHUNK_SIZE 65535 #define WS_CHUNK_COUNT 2 -struct ws_frame_meta { - char proto_opcode; - int flags; - const char *name; + +/* a client-side WS frame decoder, parsing frame headers and + * payload, keeping track of current position and stats */ +enum ws_dec_state { + WS_DEC_INIT, + WS_DEC_HEAD, + WS_DEC_PAYLOAD +}; + +struct ws_decoder { + int frame_age; /* zero */ + int frame_flags; /* See the CURLWS_* defines */ + curl_off_t payload_offset; /* the offset parsing is at */ + curl_off_t payload_len; + unsigned char head[10]; + int head_len, head_total; + enum ws_dec_state state; + int cont_flags; +}; + +/* a client-side WS frame encoder, generating frame headers and + * converting payloads, tracking remaining data in current frame */ +struct ws_encoder { + curl_off_t payload_len; /* payload length of current frame */ + curl_off_t payload_remain; /* remaining payload of current */ + unsigned int xori; /* xor index */ + unsigned char mask[4]; /* 32-bit mask for this connection */ + unsigned char firstbyte; /* first byte of frame we encode */ + BIT(contfragment); /* set TRUE if the previous fragment sent was not final */ }; -static struct ws_frame_meta WS_FRAMES[] = { - { WSBIT_OPCODE_CONT, CURLWS_CONT, "CONT" }, - { WSBIT_OPCODE_TEXT, CURLWS_TEXT, "TEXT" }, - { WSBIT_OPCODE_BIN, CURLWS_BINARY, "BIN" }, - { WSBIT_OPCODE_CLOSE, CURLWS_CLOSE, "CLOSE" }, - { WSBIT_OPCODE_PING, CURLWS_PING, "PING" }, - { WSBIT_OPCODE_PONG, CURLWS_PONG, "PONG" }, +/* A websocket connection with en- and decoder that treat frames + * and keep track of boundaries. */ +struct websocket { + struct Curl_easy *data; /* used for write callback handling */ + struct ws_decoder dec; /* decode of we frames */ + struct ws_encoder enc; /* decode of we frames */ + struct bufq recvbuf; /* raw data from the server */ + struct bufq sendbuf; /* raw data to be sent to the server */ + struct curl_ws_frame frame; /* the current WS FRAME received */ + size_t sendbuf_payload; /* number of payload bytes in sendbuf */ }; -static const char *ws_frame_name_of_op(unsigned char proto_opcode) + +static const char *ws_frame_name_of_op(unsigned char firstbyte) { - unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK; - size_t i; - for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { - if(WS_FRAMES[i].proto_opcode == opcode) - return WS_FRAMES[i].name; + switch(firstbyte & WSBIT_OPCODE_MASK) { + case WSBIT_OPCODE_CONT: + return "CONT"; + case WSBIT_OPCODE_TEXT: + return "TEXT"; + case WSBIT_OPCODE_BIN: + return "BIN"; + case WSBIT_OPCODE_CLOSE: + return "CLOSE"; + case WSBIT_OPCODE_PING: + return "PING"; + case WSBIT_OPCODE_PONG: + return "PONG"; + default: + return "???"; } - return "???"; } -static int ws_frame_op2flags(unsigned char proto_opcode) +static int ws_frame_firstbyte2flags(struct Curl_easy *data, + unsigned char firstbyte, int cont_flags) { - unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK; - size_t i; - for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { - if(WS_FRAMES[i].proto_opcode == opcode) - return WS_FRAMES[i].flags; + switch(firstbyte) { + /* 0x00 - intermediate TEXT/BINARY fragment */ + case WSBIT_OPCODE_CONT: + if(!(cont_flags & CURLWS_CONT)) { + failf(data, "[WS] no ongoing fragmented message to resume"); + return 0; + } + return cont_flags | CURLWS_CONT; + /* 0x80 - final TEXT/BIN fragment */ + case (WSBIT_OPCODE_CONT | WSBIT_FIN): + if(!(cont_flags & CURLWS_CONT)) { + failf(data, "[WS] no ongoing fragmented message to resume"); + return 0; + } + return cont_flags & ~CURLWS_CONT; + /* 0x01 - first TEXT fragment */ + case WSBIT_OPCODE_TEXT: + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new TEXT msg"); + return 0; + } + return CURLWS_TEXT | CURLWS_CONT; + /* 0x81 - unfragmented TEXT msg */ + case (WSBIT_OPCODE_TEXT | WSBIT_FIN): + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new TEXT msg"); + return 0; + } + return CURLWS_TEXT; + /* 0x02 - first BINARY fragment */ + case WSBIT_OPCODE_BIN: + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new BINARY msg"); + return 0; + } + return CURLWS_BINARY | CURLWS_CONT; + /* 0x82 - unfragmented BINARY msg */ + case (WSBIT_OPCODE_BIN | WSBIT_FIN): + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new BINARY msg"); + return 0; + } + return CURLWS_BINARY; + /* 0x08 - first CLOSE fragment */ + case WSBIT_OPCODE_CLOSE: + failf(data, "[WS] invalid fragmented CLOSE frame"); + return 0; + /* 0x88 - unfragmented CLOSE */ + case (WSBIT_OPCODE_CLOSE | WSBIT_FIN): + return CURLWS_CLOSE; + /* 0x09 - first PING fragment */ + case WSBIT_OPCODE_PING: + failf(data, "[WS] invalid fragmented PING frame"); + return 0; + /* 0x89 - unfragmented PING */ + case (WSBIT_OPCODE_PING | WSBIT_FIN): + return CURLWS_PING; + /* 0x0a - first PONG fragment */ + case WSBIT_OPCODE_PONG: + failf(data, "[WS] invalid fragmented PONG frame"); + return 0; + /* 0x8a - unfragmented PONG */ + case (WSBIT_OPCODE_PONG | WSBIT_FIN): + return CURLWS_PONG; + /* invalid first byte */ + default: + if(firstbyte & WSBIT_RSV_MASK) + /* any of the reserved bits 0x40/0x20/0x10 are set */ + failf(data, "[WS] invalid reserved bits: %02x", firstbyte); + else + /* any of the reserved opcodes 0x3-0x7 or 0xb-0xf is used */ + failf(data, "[WS] invalid opcode: %02x", firstbyte); + return 0; } - return 0; } -static unsigned char ws_frame_flags2op(int flags) +static unsigned char ws_frame_flags2firstbyte(struct Curl_easy *data, + unsigned int flags, + bool contfragment, + CURLcode *err) { - size_t i; - for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { - if(WS_FRAMES[i].flags & flags) - return WS_FRAMES[i].proto_opcode; + switch(flags & ~CURLWS_OFFSET) { + case 0: + if(contfragment) { + infof(data, "[WS] no flags given; interpreting as continuation " + "fragment for compatibility"); + return (WSBIT_OPCODE_CONT | WSBIT_FIN); + } + failf(data, "[WS] no flags given"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_CONT: + if(contfragment) { + infof(data, "[WS] setting CURLWS_CONT flag without message type is " + "supported for compatibility but highly discouraged"); + return WSBIT_OPCODE_CONT; + } + failf(data, "[WS] No ongoing fragmented message to continue"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_TEXT: + return contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN) + : (WSBIT_OPCODE_TEXT | WSBIT_FIN); + case (CURLWS_TEXT | CURLWS_CONT): + return contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_TEXT; + case CURLWS_BINARY: + return contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN) + : (WSBIT_OPCODE_BIN | WSBIT_FIN); + case (CURLWS_BINARY | CURLWS_CONT): + return contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_BIN; + case CURLWS_CLOSE: + return WSBIT_OPCODE_CLOSE | WSBIT_FIN; + case (CURLWS_CLOSE | CURLWS_CONT): + failf(data, "[WS] CLOSE frame must not be fragmented"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_PING: + return WSBIT_OPCODE_PING | WSBIT_FIN; + case (CURLWS_PING | CURLWS_CONT): + failf(data, "[WS] PING frame must not be fragmented"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_PONG: + return WSBIT_OPCODE_PONG | WSBIT_FIN; + case (CURLWS_PONG | CURLWS_CONT): + failf(data, "[WS] PONG frame must not be fragmented"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + default: + failf(data, "[WS] unknown flags: %x", flags); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; } - return 0; } static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, @@ -114,27 +286,31 @@ static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, case 0: break; case 1: - infof(data, "WS-DEC: %s [%s%s]", msg, - ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL"); + CURL_TRC_WS(data, "decoded %s [%s%s]", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL"); break; default: if(dec->head_len < dec->head_total) { - infof(data, "WS-DEC: %s [%s%s](%d/%d)", msg, - ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", - dec->head_len, dec->head_total); + CURL_TRC_WS(data, "decoded %s [%s%s](%d/%d)", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL", + dec->head_len, dec->head_total); } else { - infof(data, "WS-DEC: %s [%s%s payload=%zd/%zd]", msg, - ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", - dec->payload_offset, dec->payload_len); + CURL_TRC_WS(data, "decoded %s [%s%s payload=%" + FMT_OFF_T "/%" FMT_OFF_T "]", + msg, ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL", + dec->payload_offset, dec->payload_len); } break; } } +static CURLcode ws_send_raw_blocking(CURL *data, struct websocket *ws, + const char *buffer, size_t buflen); + typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen, int frame_age, int frame_flags, curl_off_t payload_offset, @@ -142,6 +318,16 @@ typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen, void *userp, CURLcode *err); +static void ws_dec_next_frame(struct ws_decoder *dec) +{ + dec->frame_age = 0; + dec->frame_flags = 0; + dec->payload_offset = 0; + dec->payload_len = 0; + dec->head_len = dec->head_total = 0; + dec->state = WS_DEC_INIT; + /* dec->cont_flags must be carried over to next frame */ +} static void ws_dec_reset(struct ws_decoder *dec) { @@ -151,6 +337,7 @@ static void ws_dec_reset(struct ws_decoder *dec) dec->payload_len = 0; dec->head_len = dec->head_total = 0; dec->state = WS_DEC_INIT; + dec->cont_flags = 0; } static void ws_dec_init(struct ws_decoder *dec) @@ -170,12 +357,19 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->head[0] = *inbuf; Curl_bufq_skip(inraw, 1); - dec->frame_flags = ws_frame_op2flags(dec->head[0]); + dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0], + dec->cont_flags); if(!dec->frame_flags) { - failf(data, "WS: unknown opcode: %x", dec->head[0]); ws_dec_reset(dec); return CURLE_RECV_ERROR; } + + /* fragmentation only applies to data frames (text/binary); + * control frames (close/ping/pong) do not affect the CONT status */ + if(dec->frame_flags & (CURLWS_TEXT | CURLWS_BINARY)) { + dec->cont_flags = dec->frame_flags; + } + dec->head_len = 1; /* ws_dec_info(dec, data, "seeing opcode"); */ continue; @@ -187,10 +381,30 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, if(dec->head[1] & WSBIT_MASK) { /* A client MUST close a connection if it detects a masked frame. */ - failf(data, "WS: masked input frame"); + failf(data, "[WS] masked input frame"); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + if(dec->frame_flags & CURLWS_PING && dec->head[1] > 125) { + /* The maximum valid size of PING frames is 125 bytes. + Accepting overlong pings would mean sending equivalent pongs! */ + failf(data, "[WS] received PING frame is too big"); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + if(dec->frame_flags & CURLWS_PONG && dec->head[1] > 125) { + /* The maximum valid size of PONG frames is 125 bytes. */ + failf(data, "[WS] received PONG frame is too big"); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > 125) { + /* The maximum valid size of CLOSE frames is 125 bytes. */ + failf(data, "[WS] received CLOSE frame is too big"); ws_dec_reset(dec); return CURLE_RECV_ERROR; } + /* How long is the frame head? */ if(dec->head[1] == 126) { dec->head_total = 4; @@ -224,6 +438,10 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->payload_len = (dec->head[2] << 8) | dec->head[3]; break; case 10: + if(dec->head[2] > 127) { + failf(data, "[WS] frame length longer than 64 signed not supported"); + return CURLE_RECV_ERROR; + } dec->payload_len = ((curl_off_t)dec->head[2] << 56) | (curl_off_t)dec->head[3] << 48 | (curl_off_t)dec->head[4] << 40 | @@ -236,7 +454,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, default: /* this should never happen */ DEBUGASSERT(0); - failf(data, "WS: unexpected frame header length"); + failf(data, "[WS] unexpected frame header length"); return CURLE_RECV_ERROR; } @@ -272,11 +490,11 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, Curl_bufq_skip(inraw, (size_t)nwritten); dec->payload_offset += (curl_off_t)nwritten; remain = dec->payload_len - dec->payload_offset; - /* infof(data, "WS-DEC: passed %zd bytes payload, %zd remain", - nwritten, remain); */ + CURL_TRC_WS(data, "passed %zd bytes payload, %" + FMT_OFF_T " remain", nwritten, remain); } - return remain? CURLE_AGAIN : CURLE_OK; + return remain ? CURLE_AGAIN : CURLE_OK; } static CURLcode ws_dec_pass(struct ws_decoder *dec, @@ -292,14 +510,14 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, switch(dec->state) { case WS_DEC_INIT: - ws_dec_reset(dec); + ws_dec_next_frame(dec); dec->state = WS_DEC_HEAD; - /* FALLTHROUGH */ + FALLTHROUGH(); case WS_DEC_HEAD: result = ws_dec_read_head(dec, data, inraw); if(result) { if(result != CURLE_AGAIN) { - infof(data, "WS: decode error %d", (int)result); + infof(data, "[WS] decode error %d", (int)result); break; /* real error */ } /* incomplete ws frame head */ @@ -319,7 +537,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, dec->state = WS_DEC_INIT; break; } - /* FALLTHROUGH */ + FALLTHROUGH(); case WS_DEC_PAYLOAD: result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx); ws_dec_info(dec, data, "passing"); @@ -341,22 +559,157 @@ static void update_meta(struct websocket *ws, curl_off_t payload_len, size_t cur_len) { + curl_off_t bytesleft = (payload_len - payload_offset - cur_len); + ws->frame.age = frame_age; ws->frame.flags = frame_flags; ws->frame.offset = payload_offset; ws->frame.len = cur_len; - ws->frame.bytesleft = (payload_len - payload_offset - cur_len); + ws->frame.bytesleft = bytesleft; +} + +/* WebSockets decoding client writer */ +struct ws_cw_ctx { + struct Curl_cwriter super; + struct bufq buf; +}; + +static CURLcode ws_cw_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = writer->ctx; + (void)data; + Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = writer->ctx; + (void) data; + Curl_bufq_free(&ctx->buf); +} + +struct ws_cw_dec_ctx { + struct Curl_easy *data; + struct websocket *ws; + struct Curl_cwriter *next_writer; + int cw_type; +}; + +static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + void *user_data, + CURLcode *err) +{ + 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; + curl_off_t remain = (payload_len - (payload_offset + buflen)); + + (void)frame_age; + + if(auto_pong && (frame_flags & CURLWS_PING) && !remain) { + /* auto-respond to PINGs, only works for single-frame payloads atm */ + size_t bytes; + infof(data, "[WS] auto-respond to PING with a PONG"); + /* send back the exact same content as a PONG */ + *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); + if(*err) + return -1; + } + else if(buflen || !remain) { + /* forward the decoded frame to the next client writer. */ + update_meta(ws, frame_age, frame_flags, payload_offset, + payload_len, buflen); + + *err = Curl_cwriter_write(data, ctx->next_writer, ctx->cw_type, + (const char *)buf, buflen); + if(*err) + return -1; + } + *err = CURLE_OK; + return (ssize_t)buflen; +} + +static CURLcode ws_cw_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + struct ws_cw_ctx *ctx = writer->ctx; + struct websocket *ws; + CURLcode result; + + if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] not a websocket transfer"); + return CURLE_FAILED_INIT; + } + + if(nbytes) { + ssize_t nwritten; + nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf, + nbytes, &result); + if(nwritten < 0) { + infof(data, "[WS] error adding data to buffer %d", result); + return result; + } + } + + while(!Curl_bufq_is_empty(&ctx->buf)) { + struct ws_cw_dec_ctx pass_ctx; + pass_ctx.data = data; + pass_ctx.ws = ws; + pass_ctx.next_writer = writer->next; + pass_ctx.cw_type = type; + result = ws_dec_pass(&ws->dec, data, &ctx->buf, + ws_cw_dec_next, &pass_ctx); + if(result == CURLE_AGAIN) { + /* insufficient amount of data, keep it for later. + * we pretend to have written all since we have a copy */ + CURL_TRC_WS(data, "buffered incomplete frame head"); + return CURLE_OK; + } + else if(result) { + infof(data, "[WS] decode error %d", (int)result); + return result; + } + } + + if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { + failf(data, "[WS] decode ending with %zd frame bytes remaining", + Curl_bufq_len(&ctx->buf)); + return CURLE_RECV_ERROR; + } + + return CURLE_OK; } +/* WebSocket payload decoding client writer. */ +static const struct Curl_cwtype ws_cw_decode = { + "ws-decode", + NULL, + ws_cw_init, + ws_cw_write, + ws_cw_close, + sizeof(struct ws_cw_ctx) +}; + + static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data, const char *msg) { - infof(data, "WS-ENC: %s [%s%s%s payload=%zd/%zd]", msg, - ws_frame_name_of_op(enc->firstbyte), - (enc->firstbyte & WSBIT_OPCODE_MASK) == WSBIT_OPCODE_CONT ? - " CONT" : "", - (enc->firstbyte & WSBIT_FIN)? "" : " NON-FIN", - enc->payload_len - enc->payload_remain, enc->payload_len); + CURL_TRC_WS(data, "WS-ENC: %s [%s%s payload=%" + FMT_OFF_T "/%" FMT_OFF_T "]", + msg, ws_frame_name_of_op(enc->firstbyte), + (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN", + enc->payload_len - enc->payload_remain, enc->payload_len); } static void ws_enc_reset(struct ws_encoder *enc) @@ -402,45 +755,53 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, CURLcode *err) { unsigned char firstbyte = 0; - unsigned char opcode; unsigned char head[14]; size_t hlen; ssize_t n; + if(payload_len < 0) { + failf(data, "[WS] starting new frame with negative payload length %" + FMT_OFF_T, payload_len); + *err = CURLE_SEND_ERROR; + return -1; + } + if(enc->payload_remain > 0) { /* trying to write a new frame before the previous one is finished */ - failf(data, "WS: starting new frame with %zd bytes from last one" + failf(data, "[WS] starting new frame with %zd bytes from last one " "remaining to be sent", (ssize_t)enc->payload_remain); *err = CURLE_SEND_ERROR; return -1; } - opcode = ws_frame_flags2op(flags); - if(!opcode) { - failf(data, "WS: provided flags not recognized '%x'", flags); - *err = CURLE_SEND_ERROR; + firstbyte = ws_frame_flags2firstbyte(data, flags, enc->contfragment, err); + if(*err) { return -1; } - if(!(flags & CURLWS_CONT)) { - if(!enc->contfragment) - /* not marked as continuing, this is the final fragment */ - firstbyte |= WSBIT_FIN | opcode; - else - /* marked as continuing, this is the final fragment; set CONT - opcode and FIN bit */ - firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT; + /* fragmentation only applies to data frames (text/binary); + * control frames (close/ping/pong) do not affect the CONT status */ + if(flags & (CURLWS_TEXT | CURLWS_BINARY)) { + enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE; + } - enc->contfragment = FALSE; + if(flags & CURLWS_PING && payload_len > 125) { + /* The maximum valid size of PING frames is 125 bytes. */ + failf(data, "[WS] given PING frame is too big"); + *err = CURLE_TOO_LARGE; + return -1; } - else if(enc->contfragment) { - /* the previous fragment was not a final one and this isn't either, keep a - CONT opcode and no FIN bit */ - firstbyte |= WSBIT_OPCODE_CONT; + if(flags & CURLWS_PONG && payload_len > 125) { + /* The maximum valid size of PONG frames is 125 bytes. */ + failf(data, "[WS] given PONG frame is too big"); + *err = CURLE_TOO_LARGE; + return -1; } - else { - firstbyte = opcode; - enc->contfragment = TRUE; + if(flags & CURLWS_CLOSE && payload_len > 125) { + /* The maximum valid size of CLOSE frames is 125 bytes. */ + failf(data, "[WS] given CLOSE frame is too big"); + *err = CURLE_TOO_LARGE; + return -1; } head[0] = enc->firstbyte = firstbyte; @@ -528,7 +889,7 @@ struct wsfield { const char *val; }; -CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) +CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) { unsigned int i; CURLcode result = CURLE_OK; @@ -541,18 +902,18 @@ CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) { /* The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword. */ - "Upgrade:", "websocket" + "Upgrade", "websocket" }, { /* The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token. */ - "Connection:", "Upgrade", + "Connection", "Upgrade", }, { /* The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13. */ - "Sec-WebSocket-Version:", "13", + "Sec-WebSocket-Version", "13", }, { /* The request MUST include a header field with the name @@ -560,7 +921,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) consisting of a randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection. */ - "Sec-WebSocket-Key:", NULL, + "Sec-WebSocket-Key", NULL, } }; heads[3].val = &keyval[0]; @@ -569,32 +930,36 @@ CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) result = Curl_rand(data, (unsigned char *)rand, sizeof(rand)); if(result) return result; - result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen); + result = curlx_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen); if(result) return result; DEBUGASSERT(randlen < sizeof(keyval)); - if(randlen >= sizeof(keyval)) + if(randlen >= sizeof(keyval)) { + free(randstr); return CURLE_FAILED_INIT; + } strcpy(keyval, randstr); free(randstr); - for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) { - if(!Curl_checkheaders(data, STRCONST(heads[i].name))) { -#ifdef USE_HYPER - char field[128]; - msnprintf(field, sizeof(field), "%s %s", heads[i].name, - heads[i].val); - result = Curl_hyper_header(data, req, field); -#else - (void)data; - result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name, - heads[i].val); -#endif + for(i = 0; !result && (i < CURL_ARRAYSIZE(heads)); i++) { + if(!Curl_checkheaders(data, heads[i].name, strlen(heads[i].name))) { + result = curlx_dyn_addf(req, "%s: %s\r\n", heads[i].name, + heads[i].val); } } k->upgr101 = UPGR101_WS; return result; } +static void ws_conn_dtor(void *key, size_t klen, void *entry) +{ + struct websocket *ws = entry; + (void)key; + (void)klen; + Curl_bufq_free(&ws->recvbuf); + Curl_bufq_free(&ws->sendbuf); + free(ws); +} + /* * 'nread' is number of bytes of websocket data already in the buffer at * 'mem'. @@ -604,20 +969,37 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, { struct SingleRequest *k = &data->req; struct websocket *ws; + struct Curl_cwriter *ws_dec_writer; CURLcode result; DEBUGASSERT(data->conn); - ws = data->conn->proto.ws; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); if(!ws) { + size_t chunk_size = WS_CHUNK_SIZE; ws = calloc(1, sizeof(*ws)); if(!ws) return CURLE_OUT_OF_MEMORY; - data->conn->proto.ws = ws; - Curl_bufq_init(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT); - Curl_bufq_init2(&ws->sendbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT, +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_WS_CHUNK_SIZE"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 1*1024*1024)) + chunk_size = (size_t)l; + } + } +#endif + CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size); + Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT, + BUFQ_OPT_SOFT_LIMIT); + Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT, BUFQ_OPT_SOFT_LIMIT); ws_dec_init(&ws->dec); ws_enc_init(&ws->enc); + result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN, + ws, ws_conn_dtor); + if(result) + return result; } else { Curl_bufq_reset(&ws->recvbuf); @@ -649,9 +1031,28 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, sizeof(ws->enc.mask)); if(result) return result; - infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x", + +#ifdef DEBUGBUILD + if(getenv("CURL_WS_FORCE_ZERO_MASK")) + /* force the bit mask to 0x00000000, effectively disabling masking */ + memset(ws->enc.mask, 0, sizeof(ws->enc.mask)); +#endif + + infof(data, "[WS] Received 101, switch to WebSocket; mask %02x%02x%02x%02x", ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]); + /* Install our client writer that decodes WS frames payload */ + result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ws_dec_writer); + if(result) { + Curl_cwriter_free(data, ws_dec_writer); + return result; + } + if(data->set.connect_only) { ssize_t nwritten; /* In CONNECT_ONLY setup, the payloads from `mem` need to be received @@ -661,112 +1062,22 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, nread, &result); if(nwritten < 0) return result; - infof(data, "%zu bytes websocket payload", nread); - } - k->upgr101 = UPGR101_RECEIVED; - - return result; -} - -static ssize_t ws_client_write(const unsigned char *buf, size_t buflen, - int frame_age, int frame_flags, - curl_off_t payload_offset, - curl_off_t payload_len, - void *userp, - CURLcode *err) -{ - struct Curl_easy *data = userp; - struct websocket *ws; - size_t wrote; - curl_off_t remain = (payload_len - (payload_offset + buflen)); - - (void)frame_age; - if(!data->conn || !data->conn->proto.ws) { - *err = CURLE_FAILED_INIT; - return -1; - } - ws = data->conn->proto.ws; - - if((frame_flags & CURLWS_PING) && !remain) { - /* auto-respond to PINGs, only works for single-frame payloads atm */ - size_t bytes; - infof(data, "WS: auto-respond to PING with a PONG"); - /* send back the exact same content as a PONG */ - *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); - if(*err) - return -1; + CURL_TRC_WS(data, "%zu bytes payload", nread); } - else if(buflen || !remain) { - /* deliver the decoded frame to the user callback. The application - * may invoke curl_ws_meta() to access frame information. */ - update_meta(ws, frame_age, frame_flags, payload_offset, - payload_len, buflen); - Curl_set_in_callback(data, true); - wrote = data->set.fwrite_func((char *)buf, 1, - buflen, data->set.out); - Curl_set_in_callback(data, false); - if(wrote != buflen) { - *err = CURLE_RECV_ERROR; - return -1; + else { /* !connect_only */ + /* And pass any additional data to the writers */ + if(nread) { + result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread); } } - *err = CURLE_OK; - return (ssize_t)buflen; -} - -/* Curl_ws_writecb() is the write callback for websocket traffic. The - websocket data is provided to this raw, in chunks. This function should - handle/decode the data and call the "real" underlying callback accordingly. -*/ -size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, - size_t nitems, void *userp) -{ - struct Curl_easy *data = userp; - - if(data->set.ws_raw_mode) - return data->set.fwrite_func(buffer, size, nitems, data->set.out); - else if(nitems) { - struct websocket *ws; - CURLcode result; - - if(!data->conn || !data->conn->proto.ws) { - failf(data, "WS: not a websocket transfer"); - return nitems - 1; - } - ws = data->conn->proto.ws; - - if(buffer) { - ssize_t nwritten; - - nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)buffer, - nitems, &result); - if(nwritten < 0) { - infof(data, "WS: error adding data to buffer %d", (int)result); - return nitems - 1; - } - buffer = NULL; - } - - while(!Curl_bufq_is_empty(&ws->recvbuf)) { + k->upgr101 = UPGR101_RECEIVED; - result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, - ws_client_write, data); - if(result == CURLE_AGAIN) - /* insufficient amount of data, keep it for later. - * we pretend to have written all since we have a copy */ - return nitems; - else if(result) { - infof(data, "WS: decode error %d", (int)result); - return nitems - 1; - } - } - } - return nitems; + return result; } struct ws_collect { struct Curl_easy *data; - void *buffer; + unsigned char *buffer; size_t buflen; size_t bufidx; int frame_age; @@ -784,6 +1095,8 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, CURLcode *err) { struct ws_collect *ctx = userp; + struct Curl_easy *data = ctx->data; + bool auto_pong = !data->set.ws_no_auto_pong; size_t nwritten; curl_off_t remain = (payload_len - (payload_offset + buflen)); @@ -795,10 +1108,10 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, ctx->payload_len = payload_len; } - if((frame_flags & CURLWS_PING) && !remain) { + if(auto_pong && (frame_flags & CURLWS_PING) && !remain) { /* auto-respond to PINGs, only works for single-frame payloads atm */ size_t bytes; - infof(ctx->data, "WS: auto-respond to PING with a PONG"); + infof(ctx->data, "[WS] auto-respond to PING with a PONG"); /* send back the exact same content as a PONG */ *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG); if(*err) @@ -818,7 +1131,7 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, return -1; } *err = CURLE_OK; - memcpy(ctx->buffer, buf, nwritten); + memcpy(ctx->buffer + ctx->bufidx, buf, nwritten); ctx->bufidx += nwritten; } return nwritten; @@ -837,48 +1150,46 @@ static ssize_t nw_in_recv(void *reader_ctx, return (ssize_t)nread; } -CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, +CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer, size_t buflen, size_t *nread, - struct curl_ws_frame **metap) + const struct curl_ws_frame **metap) { + struct Curl_easy *data = d; struct connectdata *conn = data->conn; struct websocket *ws; - bool done = FALSE; /* not filled passed buffer yet */ struct ws_collect ctx; - CURLcode result; + + *nread = 0; + *metap = NULL; if(!conn) { /* Unhappy hack with lifetimes of transfers and connection */ if(!data->set.connect_only) { - failf(data, "CONNECT_ONLY is required"); + failf(data, "[WS] CONNECT_ONLY is required"); return CURLE_UNSUPPORTED_PROTOCOL; } Curl_getconnectinfo(data, &conn); if(!conn) { - failf(data, "connection not found"); + failf(data, "[WS] connection not found"); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ws = conn->proto.ws; + ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN); if(!ws) { - failf(data, "connection is not setup for websocket"); + failf(data, "[WS] connection is not setup for websocket"); return CURLE_BAD_FUNCTION_ARGUMENT; } - *nread = 0; - *metap = NULL; - /* get a download buffer */ - result = Curl_preconnect(data); - if(result) - return result; memset(&ctx, 0, sizeof(ctx)); ctx.data = data; ctx.buffer = buffer; ctx.buflen = buflen; - while(!done) { + while(1) { + CURLcode result; + /* receive more when our buffer is empty */ if(Curl_bufq_is_empty(&ws->recvbuf)) { ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result); @@ -887,11 +1198,11 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, } else if(n == 0) { /* connection closed */ - infof(data, "connection expectedly closed?"); + infof(data, "[WS] connection expectedly closed?"); return CURLE_GOT_NOTHING; } - DEBUGF(infof(data, "curl_ws_recv, added %zu bytes from network", - Curl_bufq_len(&ws->recvbuf))); + CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network", + Curl_bufq_len(&ws->recvbuf)); } result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, @@ -901,7 +1212,6 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, ws_dec_info(&ws->dec, data, "need more input"); continue; /* nothing written, try more input */ } - done = TRUE; break; } else if(result) { @@ -911,7 +1221,6 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, /* The decoded frame is passed back to our caller. * There are frames like PING were we auto-respond to and * that we do not return. For these `ctx.written` is not set. */ - done = TRUE; break; } } @@ -921,182 +1230,379 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, ctx.payload_len, ctx.bufidx); *metap = &ws->frame; *nread = ws->frame.len; - /* infof(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %zd, %zd left)", - buflen, *nread, ws->frame.offset, ws->frame.bytesleft); */ + CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" + FMT_OFF_T ", %" FMT_OFF_T " left)", + buflen, *nread, ws->frame.offset, ws->frame.bytesleft); return CURLE_OK; } static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, - bool complete) + bool blocking) { if(!Curl_bufq_is_empty(&ws->sendbuf)) { CURLcode result; const unsigned char *out; - size_t outlen; - ssize_t n; + size_t outlen, n; +#ifdef DEBUGBUILD + /* Simulate a blocking send after this chunk has been sent */ + bool eagain_next = FALSE; + size_t chunk_egain = 0; + const char *p = getenv("CURL_WS_CHUNK_EAGAIN"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 1*1024*1024)) + chunk_egain = (size_t)l; + } +#endif while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) { - if(data->set.connect_only) +#ifdef DEBUGBUILD + if(eagain_next) + return CURLE_AGAIN; + if(chunk_egain && (outlen > chunk_egain)) { + outlen = chunk_egain; + eagain_next = TRUE; + } +#endif + if(blocking) { + result = ws_send_raw_blocking(data, ws, (const char *)out, outlen); + n = result ? 0 : outlen; + } + else if(data->set.connect_only || Curl_is_in_callback(data)) result = Curl_senddata(data, out, outlen, &n); - else - result = Curl_write(data, data->conn->writesockfd, out, outlen, &n); - if(result) { - if(result == CURLE_AGAIN) { - if(!complete) { - infof(data, "WS: flush EAGAIN, %zu bytes remain in buffer", - Curl_bufq_len(&ws->sendbuf)); - return result; - } - /* TODO: the current design does not allow for buffered writes. - * We need to flush the buffer now. There is no ws_flush() later */ - n = 0; - continue; - } - else if(result) { - failf(data, "WS: flush, write error %d", result); - return result; - } + else { + result = Curl_xfer_send(data, out, outlen, FALSE, &n); + if(!result && !n && outlen) + result = CURLE_AGAIN; + } + + if(result == CURLE_AGAIN) { + CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer", + Curl_bufq_len(&ws->sendbuf)); + return result; + } + else if(result) { + failf(data, "[WS] flush, write error %d", result); + return result; } else { - infof(data, "WS: flushed %zu bytes", (size_t)n); - Curl_bufq_skip(&ws->sendbuf, (size_t)n); + CURL_TRC_WS(data, "flushed %zu bytes", n); + Curl_bufq_skip(&ws->sendbuf, n); } } } return CURLE_OK; } -CURL_EXTERN CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer, - size_t buflen, size_t *sent, - curl_off_t totalsize, - unsigned int sendflags) +static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws, + const char *buffer, size_t buflen) +{ + CURLcode result = CURLE_OK; + size_t nwritten; + struct Curl_easy *data = d; + + (void)ws; + while(buflen) { + result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten); + if(result) + return result; + DEBUGASSERT(nwritten <= buflen); + buffer += nwritten; + buflen -= nwritten; + if(buflen) { + curl_socket_t sock = data->conn->sock[FIRSTSOCKET]; + timediff_t left_ms; + int ev; + + CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send", + buflen); + left_ms = Curl_timeleft(data, NULL, FALSE); + if(left_ms < 0) { + failf(data, "[WS] Timeout waiting for socket becoming writable"); + return CURLE_SEND_ERROR; + } + + /* POLLOUT socket */ + if(sock == CURL_SOCKET_BAD) + return CURLE_SEND_ERROR; + ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock, + left_ms ? left_ms : 500); + if(ev < 0) { + failf(data, "[WS] Error while waiting for socket becoming writable"); + return CURLE_SEND_ERROR; + } + } + } + return result; +} + +static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer, + size_t buflen, size_t *pnwritten) { struct websocket *ws; - ssize_t nwritten, n; - size_t space; CURLcode result; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] Not a websocket transfer"); + return CURLE_SEND_ERROR; + } + if(!buflen) + return CURLE_OK; + + if(Curl_is_in_callback(data)) { + /* When invoked from inside callbacks, we do a blocking send as the + * callback will probably not implement partial writes that may then + * mess up the ws framing subsequently. + * We need any pending data to be flushed before sending. */ + result = ws_flush(data, ws, TRUE); + if(result) + return result; + result = ws_send_raw_blocking(data, ws, buffer, buflen); + } + else { + /* We need any pending data to be sent or EAGAIN this call. */ + result = ws_flush(data, ws, FALSE); + if(result) + return result; + result = Curl_senddata(data, buffer, buflen, pnwritten); + } + + CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu", + buflen, result, *pnwritten); + return result; +} + +CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, + size_t buflen, size_t *sent, + curl_off_t fragsize, + unsigned int flags) +{ + struct websocket *ws; + const unsigned char *buffer = buffer_arg; + ssize_t n; + CURLcode result = CURLE_OK; + struct Curl_easy *data = d; + + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x), raw=%d", + buflen, fragsize, flags, data->set.ws_raw_mode); *sent = 0; if(!data->conn && data->set.connect_only) { result = Curl_connect_only_attach(data); if(result) - return result; + goto out; } if(!data->conn) { - failf(data, "No associated connection"); - return CURLE_SEND_ERROR; + failf(data, "[WS] No associated connection"); + result = CURLE_SEND_ERROR; + goto out; } - if(!data->conn->proto.ws) { - failf(data, "Not a websocket transfer on connection #%ld", - data->conn->connection_id); - return CURLE_SEND_ERROR; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] Not a websocket transfer"); + result = CURLE_SEND_ERROR; + goto out; } - ws = data->conn->proto.ws; if(data->set.ws_raw_mode) { - if(totalsize || sendflags) + /* In raw mode, we write directly to the connection */ + /* try flushing any content still waiting to be sent. */ + result = ws_flush(data, ws, FALSE); + if(result) + goto out; + + if(fragsize || flags) { + failf(data, "[WS] fragsize and flags must be zero in raw mode"); return CURLE_BAD_FUNCTION_ARGUMENT; - if(!buflen) - /* nothing to do */ - return CURLE_OK; - /* raw mode sends exactly what was requested, and this is from within - the write callback */ - if(Curl_is_in_callback(data)) { - result = Curl_write(data, data->conn->writesockfd, buffer, buflen, - &nwritten); } - else - result = Curl_senddata(data, buffer, buflen, &nwritten); - - infof(data, "WS: wanted to send %zu bytes, sent %zu bytes", - buflen, nwritten); - *sent = (nwritten >= 0)? (size_t)nwritten : 0; - return result; + result = ws_send_raw(data, buffer, buflen, sent); + goto out; } /* Not RAW mode, buf we do the frame encoding */ - result = ws_flush(data, ws, FALSE); - if(result) - return result; - - /* TODO: the current design does not allow partial writes, afaict. - * It is not clear who the application is supposed to react. */ - space = Curl_bufq_space(&ws->sendbuf); - DEBUGF(infof(data, "curl_ws_send(len=%zu), sendbuf len=%zu space %zu", - buflen, Curl_bufq_len(&ws->sendbuf), space)); - if(space < 14) - return CURLE_AGAIN; - if(sendflags & CURLWS_OFFSET) { - if(totalsize) { - /* a frame series 'totalsize' bytes big, this is the first */ - n = ws_enc_write_head(data, &ws->enc, sendflags, totalsize, - &ws->sendbuf, &result); - if(n < 0) - return result; + if(ws->enc.payload_remain || !Curl_bufq_is_empty(&ws->sendbuf)) { + /* a frame is ongoing with payload buffered or more payload + * that needs to be encoded into the buffer */ + if(buflen < ws->sendbuf_payload) { + /* We have been called with LESS buffer data than before. This + * is not how it's supposed too work. */ + failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than " + "bytes already buffered in previous call, %zu vs %zu", + buflen, ws->sendbuf_payload); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - else { - if((curl_off_t)buflen > ws->enc.payload_remain) { - infof(data, "WS: unaligned frame size (sending %zu instead of %zd)", - buflen, ws->enc.payload_remain); - } + if((curl_off_t)buflen > + (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) { + /* too large buflen beyond payload length of frame */ + failf(data, "[WS] unaligned frame size (sending %zu instead of %" + FMT_OFF_T ")", + buflen, ws->enc.payload_remain + ws->sendbuf_payload); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } } - else if(!ws->enc.payload_remain) { - n = ws_enc_write_head(data, &ws->enc, sendflags, (curl_off_t)buflen, + else { + /* starting a new frame, we want a clean sendbuf */ + curl_off_t payload_len = (flags & CURLWS_OFFSET) ? + fragsize : (curl_off_t)buflen; + result = ws_flush(data, ws, Curl_is_in_callback(data)); + if(result) + goto out; + + n = ws_enc_write_head(data, &ws->enc, flags, payload_len, &ws->sendbuf, &result); if(n < 0) - return result; + goto out; } - n = ws_enc_write_payload(&ws->enc, data, - buffer, buflen, &ws->sendbuf, &result); - if(n < 0) - return result; - - *sent = (size_t)n; - return ws_flush(data, ws, TRUE); -} + /* While there is either sendbuf to flush OR more payload to encode... */ + while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) { + /* Try to add more payload to sendbuf */ + if(buflen > ws->sendbuf_payload) { + size_t prev_len = Curl_bufq_len(&ws->sendbuf); + n = ws_enc_write_payload(&ws->enc, data, + buffer + ws->sendbuf_payload, + buflen - ws->sendbuf_payload, + &ws->sendbuf, &result); + if(n < 0 && (result != CURLE_AGAIN)) + goto out; + ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len; + if(!ws->sendbuf_payload) { + result = CURLE_AGAIN; + goto out; + } + } -static void ws_free(struct connectdata *conn) -{ - if(conn && conn->proto.ws) { - Curl_bufq_free(&conn->proto.ws->recvbuf); - Curl_bufq_free(&conn->proto.ws->sendbuf); - Curl_safefree(conn->proto.ws); + /* flush, blocking when in callback */ + result = ws_flush(data, ws, Curl_is_in_callback(data)); + if(!result && ws->sendbuf_payload > 0) { + *sent += ws->sendbuf_payload; + buffer += ws->sendbuf_payload; + buflen -= ws->sendbuf_payload; + ws->sendbuf_payload = 0; + } + else if(result == CURLE_AGAIN) { + if(ws->sendbuf_payload > Curl_bufq_len(&ws->sendbuf)) { + /* blocked, part of payload bytes remain, report length + * that we managed to send. */ + size_t flushed = (ws->sendbuf_payload - Curl_bufq_len(&ws->sendbuf)); + *sent += flushed; + ws->sendbuf_payload -= flushed; + result = CURLE_OK; + goto out; + } + else { + /* blocked before sending headers or 1st payload byte. We cannot report + * OK on 0-length send (caller counts only payload) and EAGAIN */ + CURL_TRC_WS(data, "EAGAIN flushing sendbuf, payload_encoded: %zu/%zu", + ws->sendbuf_payload, buflen); + DEBUGASSERT(*sent == 0); + result = CURLE_AGAIN; + goto out; + } + } + else + goto out; /* real error sending the data */ } -} -void Curl_ws_done(struct Curl_easy *data) -{ - (void)data; +out: + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x, raw=%d) -> %d, %zu", + buflen, fragsize, flags, data->set.ws_raw_mode, result, *sent); + return result; } -CURLcode Curl_ws_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) +static CURLcode ws_setup_conn(struct Curl_easy *data, + struct connectdata *conn) { - (void)data; - (void)dead_connection; - ws_free(conn); - return CURLE_OK; + /* WebSockets is 1.1 only (for now) */ + data->state.http_neg.accept_09 = FALSE; + data->state.http_neg.only_10 = FALSE; + data->state.http_neg.wanted = CURL_HTTP_V1x; + data->state.http_neg.allowed = CURL_HTTP_V1x; + return Curl_http_setup_conn(data, conn); } -CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) + +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *d) { /* we only return something for websocket, called from within the callback when not using raw mode */ - if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn && - data->conn->proto.ws && !data->set.ws_raw_mode) - return &data->conn->proto.ws->frame; + struct Curl_easy *data = d; + if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && + data->conn && !data->set.ws_raw_mode) { + struct websocket *ws; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(ws) + return &ws->frame; + + } return NULL; } +const struct Curl_handler Curl_handler_ws = { + "WS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ + PORT_HTTP, /* defport */ + CURLPROTO_WS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; + +#ifdef USE_SSL +const struct Curl_handler Curl_handler_wss = { + "WSS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + NULL, /* connecting */ + ZERO_NULL, /* doing */ + NULL, /* proto_getsock */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ + PORT_HTTPS, /* defport */ + CURLPROTO_WSS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; +#endif + + #else CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, size_t *nread, - struct curl_ws_frame **metap) + const struct curl_ws_frame **metap) { (void)curl; (void)buffer; @@ -1108,21 +1614,21 @@ CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, size_t buflen, size_t *sent, - curl_off_t framesize, - unsigned int sendflags) + curl_off_t fragsize, + unsigned int flags) { (void)curl; (void)buffer; (void)buflen; (void)sent; - (void)framesize; - (void)sendflags; + (void)fragsize; + (void)flags; return CURLE_NOT_BUILT_IN; } -CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *data) { (void)data; return NULL; } -#endif /* USE_WEBSOCKETS */ +#endif /* !CURL_DISABLE_WEBSOCKETS */ diff --git a/Utilities/cmcurl/lib/ws.h b/Utilities/cmcurl/lib/ws.h index 0308a42545b..b7655abbce7 100644 --- a/Utilities/cmcurl/lib/ws.h +++ b/Utilities/cmcurl/lib/ws.h @@ -25,64 +25,22 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef USE_WEBSOCKETS +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) -#ifdef USE_HYPER -#define REQTYPE void -#else -#define REQTYPE struct dynbuf -#endif - -/* a client-side WS frame decoder, parsing frame headers and - * payload, keeping track of current position and stats */ -enum ws_dec_state { - WS_DEC_INIT, - WS_DEC_HEAD, - WS_DEC_PAYLOAD -}; +/* meta key for storing protocol meta at connection */ +#define CURL_META_PROTO_WS_CONN "meta:proto:ws:conn" -struct ws_decoder { - int frame_age; /* zero */ - int frame_flags; /* See the CURLWS_* defines */ - curl_off_t payload_offset; /* the offset parsing is at */ - curl_off_t payload_len; - unsigned char head[10]; - int head_len, head_total; - enum ws_dec_state state; -}; +CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req); +CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len); -/* a client-side WS frame encoder, generating frame headers and - * converting payloads, tracking remaining data in current frame */ -struct ws_encoder { - curl_off_t payload_len; /* payload length of current frame */ - curl_off_t payload_remain; /* remaining payload of current */ - unsigned int xori; /* xor index */ - unsigned char mask[4]; /* 32 bit mask for this connection */ - unsigned char firstbyte; /* first byte of frame we encode */ - bool contfragment; /* set TRUE if the previous fragment sent was not final */ -}; +extern const struct Curl_handler Curl_handler_ws; +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_wss; +#endif -/* A websocket connection with en- and decoder that treat frames - * and keep track of boundaries. */ -struct websocket { - struct Curl_easy *data; /* used for write callback handling */ - struct ws_decoder dec; /* decode of we frames */ - struct ws_encoder enc; /* decode of we frames */ - struct bufq recvbuf; /* raw data from the server */ - struct bufq sendbuf; /* raw data to be sent to the server */ - struct curl_ws_frame frame; /* the current WS FRAME received */ -}; -CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req); -CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len); -size_t Curl_ws_writecb(char *buffer, size_t size, size_t nitems, void *userp); -void Curl_ws_done(struct Curl_easy *data); -CURLcode Curl_ws_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection); #else #define Curl_ws_request(x,y) CURLE_OK -#define Curl_ws_done(x) Curl_nop_stmt #define Curl_ws_free(x) Curl_nop_stmt #endif diff --git a/Utilities/cmexpat/COPYING b/Utilities/cmexpat/COPYING index 3c0142e71c8..ce9e5939291 100644 --- a/Utilities/cmexpat/COPYING +++ b/Utilities/cmexpat/COPYING @@ -1,5 +1,5 @@ Copyright (c) 1998-2000 Thai Open Source Software Center Ltd and Clark Cooper -Copyright (c) 2001-2019 Expat maintainers +Copyright (c) 2001-2022 Expat maintainers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/Utilities/cmexpat/README.md b/Utilities/cmexpat/README.md index 959c4a6e94a..3c20adbee91 100644 --- a/Utilities/cmexpat/README.md +++ b/Utilities/cmexpat/README.md @@ -1,13 +1,14 @@ -[![Run Linux Travis CI tasks](https://github.com/libexpat/libexpat/actions/workflows/linux.yml/badge.svg)](https://github.com/libexpat/libexpat/actions/workflows/linux.yml) +[![Run Linux CI tasks](https://github.com/libexpat/libexpat/actions/workflows/linux.yml/badge.svg)](https://github.com/libexpat/libexpat/actions/workflows/linux.yml) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/libexpat/libexpat?svg=true)](https://ci.appveyor.com/project/libexpat/libexpat) [![Packaging status](https://repology.org/badge/tiny-repos/expat.svg)](https://repology.org/metapackage/expat/versions) [![Downloads SourceForge](https://img.shields.io/sourceforge/dt/expat?label=Downloads%20SourceForge)](https://sourceforge.net/projects/expat/files/) [![Downloads GitHub](https://img.shields.io/github/downloads/libexpat/libexpat/total?label=Downloads%20GitHub)](https://github.com/libexpat/libexpat/releases) -# Expat, Release 2.4.6 +# Expat, Release 2.6.2 -This is Expat, a C library for parsing XML, started by +This is Expat, a C99 library for parsing +[XML 1.0 Fourth Edition](https://www.w3.org/TR/2006/REC-xml-20060816/), started by [James Clark](https://en.wikipedia.org/wiki/James_Clark_%28programmer%29) in 1997. Expat is a stream-oriented XML parser. This means that you register handlers with the parser before starting the parse. These handlers @@ -222,37 +223,37 @@ CMAKE_INSTALL_PREFIX:PATH=/usr/local // Path to a program. DOCBOOK_TO_MAN:FILEPATH=/usr/bin/docbook2x-man -// build man page for xmlwf +// Build man page for xmlwf EXPAT_BUILD_DOCS:BOOL=ON -// build the examples for expat library +// Build the examples for expat library EXPAT_BUILD_EXAMPLES:BOOL=ON -// build fuzzers for the expat library +// Build fuzzers for the expat library EXPAT_BUILD_FUZZERS:BOOL=OFF -// build pkg-config file +// Build pkg-config file EXPAT_BUILD_PKGCONFIG:BOOL=ON -// build the tests for expat library +// Build the tests for expat library EXPAT_BUILD_TESTS:BOOL=ON -// build the xmlwf tool for expat library +// Build the xmlwf tool for expat library EXPAT_BUILD_TOOLS:BOOL=ON // Character type to use (char|ushort|wchar_t) [default=char] EXPAT_CHAR_TYPE:STRING=char -// install expat files in cmake install target +// Install expat files in cmake install target EXPAT_ENABLE_INSTALL:BOOL=ON // Use /MT flag (static CRT) when compiling in MSVC EXPAT_MSVC_STATIC_CRT:BOOL=OFF -// build fuzzers via ossfuzz for the expat library +// Build fuzzers via ossfuzz for the expat library EXPAT_OSSFUZZ_BUILD:BOOL=OFF -// build a shared expat library +// Build a shared expat library EXPAT_SHARED_LIBS:BOOL=ON // Treat all compiler warnings as errors @@ -261,7 +262,7 @@ EXPAT_WARNINGS_AS_ERRORS:BOOL=OFF // Make use of getrandom function (ON|OFF|AUTO) [default=AUTO] EXPAT_WITH_GETRANDOM:STRING=AUTO -// utilize libbsd (for arc4random_buf) +// Utilize libbsd (for arc4random_buf) EXPAT_WITH_LIBBSD:BOOL=OFF // Make use of syscall SYS_getrandom (ON|OFF|AUTO) [default=AUTO] diff --git a/Utilities/cmexpat/expat_config.h.cmake b/Utilities/cmexpat/expat_config.h.cmake index e91861ecf56..c82053338e1 100644 --- a/Utilities/cmexpat/expat_config.h.cmake +++ b/Utilities/cmexpat/expat_config.h.cmake @@ -1,5 +1,8 @@ /* expat_config.h.cmake. Based upon generated expat_config.h.in. */ +#ifndef EXPAT_CONFIG_H +#define EXPAT_CONFIG_H 1 + /* 1234 = LIL_ENDIAN, 4321 = BIGENDIAN */ #cmakedefine BYTEORDER @BYTEORDER@ @@ -58,7 +61,9 @@ #cmakedefine HAVE_UNISTD_H /* Define to 1 if you have the ANSI C header files. */ +#ifndef STDC_HEADERS #cmakedefine STDC_HEADERS +#endif /* whether byteorder is bigendian */ #cmakedefine WORDS_BIGENDIAN @@ -68,17 +73,20 @@ #cmakedefine XML_ATTR_INFO /* Define to specify how much context to retain around the current parse - point. */ + point, 0 to disable. */ #define XML_CONTEXT_BYTES 1024 #if ! defined(_WIN32) /* Define to include code reading entropy from `/dev/urandom'. */ - #cmakedefine XML_DEV_URANDOM +#cmakedefine XML_DEV_URANDOM #endif /* Define to make parameter entity parsing functionality available. */ /* #undef XML_DTD */ +/* Define as 1/0 to enable/disable support for general entities. */ +#define XML_GE 0 + /* Define to make XML Namespaces functionality available. */ /* #undef XML_NS */ @@ -86,3 +94,5 @@ #ifdef _MSC_VER # define __func__ __FUNCTION__ #endif + +#endif // ndef EXPAT_CONFIG_H diff --git a/Utilities/cmexpat/lib/expat.h b/Utilities/cmexpat/lib/expat.h index 46a0e1bcd22..c2770be3897 100644 --- a/Utilities/cmexpat/lib/expat.h +++ b/Utilities/cmexpat/lib/expat.h @@ -11,10 +11,14 @@ Copyright (c) 2000-2005 Fred L. Drake, Jr. Copyright (c) 2001-2002 Greg Stein Copyright (c) 2002-2016 Karl Waclawek - Copyright (c) 2016-2022 Sebastian Pipping + Copyright (c) 2016-2024 Sebastian Pipping Copyright (c) 2016 Cristian Rodríguez Copyright (c) 2016 Thomas Beutlich Copyright (c) 2017 Rhodri James + Copyright (c) 2022 Thijs Schreijer + Copyright (c) 2023 Hanno Böck + Copyright (c) 2023 Sony Corporation / Snild Dolkow + Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -174,8 +178,10 @@ struct XML_cp { }; /* This is called for an element declaration. See above for - description of the model argument. It's the caller's responsibility - to free model when finished with it. + description of the model argument. It's the user code's responsibility + to free model when finished with it. See XML_FreeContentModel. + There is no need to free the model from the handler, it can be kept + around and freed at a later stage. */ typedef void(XMLCALL *XML_ElementDeclHandler)(void *userData, const XML_Char *name, @@ -237,6 +243,17 @@ XML_ParserCreate(const XML_Char *encoding); and the local part will be concatenated without any separator. It is a programming error to use the separator '\0' with namespace triplets (see XML_SetReturnNSTriplet). + If a namespace separator is chosen that can be part of a URI or + part of an XML name, splitting an expanded name back into its + 1, 2 or 3 original parts on application level in the element handler + may end up vulnerable, so these are advised against; sane choices for + a namespace separator are e.g. '\n' (line feed) and '|' (pipe). + + Note that Expat does not validate namespace URIs (beyond encoding) + against RFC 3986 today (and is not required to do so with regard to + the XML 1.0 namespaces specification) but it may start doing that + in future releases. Before that, an application using Expat must + be ready to receive namespace URIs containing non-URI characters. */ XMLPARSEAPI(XML_Parser) XML_ParserCreateNS(const XML_Char *encoding, XML_Char namespaceSeparator); @@ -255,7 +272,7 @@ XML_ParserCreate_MM(const XML_Char *encoding, const XML_Memory_Handling_Suite *memsuite, const XML_Char *namespaceSeparator); -/* Prepare a parser object to be re-used. This is particularly +/* Prepare a parser object to be reused. This is particularly valuable when memory allocation overhead is disproportionately high, such as when a large number of small documnents need to be parsed. All handlers are cleared from the parser, except for the @@ -317,7 +334,7 @@ typedef void(XMLCALL *XML_StartDoctypeDeclHandler)(void *userData, const XML_Char *pubid, int has_internal_subset); -/* This is called for the start of the DOCTYPE declaration when the +/* This is called for the end of the DOCTYPE declaration when the closing > is encountered, but after processing any external subset. */ @@ -937,7 +954,7 @@ XMLPARSEAPI(XML_Index) XML_GetCurrentByteIndex(XML_Parser parser); XMLPARSEAPI(int) XML_GetCurrentByteCount(XML_Parser parser); -/* If XML_CONTEXT_BYTES is defined, returns the input buffer, sets +/* If XML_CONTEXT_BYTES is >=1, returns the input buffer, sets the integer pointed to by offset to the offset within this buffer of the current parse position, and sets the integer pointed to by size to the size of this buffer (the number of input bytes). Otherwise @@ -1011,7 +1028,9 @@ enum XML_FeatureEnum { XML_FEATURE_ATTR_INFO, /* Added in Expat 2.4.0. */ XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT, - XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT + XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT, + /* Added in Expat 2.6.0. */ + XML_FEATURE_GE /* Additional features must be added to the end of this enum. */ }; @@ -1024,24 +1043,30 @@ typedef struct { XMLPARSEAPI(const XML_Feature *) XML_GetFeatureList(void); -#ifdef XML_DTD -/* Added in Expat 2.4.0. */ +#if defined(XML_DTD) || (defined(XML_GE) && XML_GE == 1) +/* Added in Expat 2.4.0 for XML_DTD defined and + * added in Expat 2.6.0 for XML_GE == 1. */ XMLPARSEAPI(XML_Bool) XML_SetBillionLaughsAttackProtectionMaximumAmplification( XML_Parser parser, float maximumAmplificationFactor); -/* Added in Expat 2.4.0. */ +/* Added in Expat 2.4.0 for XML_DTD defined and + * added in Expat 2.6.0 for XML_GE == 1. */ XMLPARSEAPI(XML_Bool) XML_SetBillionLaughsAttackProtectionActivationThreshold( XML_Parser parser, unsigned long long activationThresholdBytes); #endif +/* Added in Expat 2.6.0. */ +XMLPARSEAPI(XML_Bool) +XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled); + /* Expat follows the semantic versioning convention. - See http://semver.org. + See https://semver.org */ #define XML_MAJOR_VERSION 2 -#define XML_MINOR_VERSION 4 -#define XML_MICRO_VERSION 6 +#define XML_MINOR_VERSION 6 +#define XML_MICRO_VERSION 2 #ifdef __cplusplus } diff --git a/Utilities/cmexpat/lib/internal.h b/Utilities/cmexpat/lib/internal.h index 444eba0fb03..167ec36804a 100644 --- a/Utilities/cmexpat/lib/internal.h +++ b/Utilities/cmexpat/lib/internal.h @@ -28,9 +28,11 @@ Copyright (c) 2002-2003 Fred L. Drake, Jr. Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2003 Greg Stein - Copyright (c) 2016-2021 Sebastian Pipping + Copyright (c) 2016-2024 Sebastian Pipping Copyright (c) 2018 Yury Gribov Copyright (c) 2019 David Loffredo + Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow + Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -107,7 +109,9 @@ #include // ULONG_MAX -#if defined(_WIN32) && ! defined(__USE_MINGW_ANSI_STDIO) +#if defined(_WIN32) \ + && (! defined(__USE_MINGW_ANSI_STDIO) \ + || (1 - __USE_MINGW_ANSI_STDIO - 1 == 0)) # define EXPAT_FMT_ULL(midpart) "%" midpart "I64u" # if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW # define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d" @@ -152,12 +156,21 @@ extern "C" { void _INTERNAL_trim_to_complete_utf8_characters(const char *from, const char **fromLimRef); -#if defined(XML_DTD) +#if defined(XML_GE) && XML_GE == 1 unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser); unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser); const char *unsignedCharToPrintable(unsigned char c); #endif +extern +#if ! defined(XML_TESTING) + const +#endif + XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c +#if defined(XML_TESTING) +extern unsigned int g_bytesScanned; // used for testing only +#endif + #ifdef __cplusplus } #endif diff --git a/Utilities/cmexpat/lib/siphash.h b/Utilities/cmexpat/lib/siphash.h index 952f1c88b7e..c4f6fe91260 100644 --- a/Utilities/cmexpat/lib/siphash.h +++ b/Utilities/cmexpat/lib/siphash.h @@ -117,7 +117,7 @@ * if this code is included and compiled as C++; related GCC warning is: * warning: use of C++11 long long integer constant [-Wlong-long] */ -#define _SIP_ULL(high, low) (((uint64_t)high << 32) | low) +#define SIP_ULL(high, low) ((((uint64_t)high) << 32) | (low)) #define SIP_ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) @@ -201,10 +201,10 @@ sip_round(struct siphash *H, const int rounds) { static struct siphash * sip24_init(struct siphash *H, const struct sipkey *key) { - H->v0 = _SIP_ULL(0x736f6d65U, 0x70736575U) ^ key->k[0]; - H->v1 = _SIP_ULL(0x646f7261U, 0x6e646f6dU) ^ key->k[1]; - H->v2 = _SIP_ULL(0x6c796765U, 0x6e657261U) ^ key->k[0]; - H->v3 = _SIP_ULL(0x74656462U, 0x79746573U) ^ key->k[1]; + H->v0 = SIP_ULL(0x736f6d65U, 0x70736575U) ^ key->k[0]; + H->v1 = SIP_ULL(0x646f7261U, 0x6e646f6dU) ^ key->k[1]; + H->v2 = SIP_ULL(0x6c796765U, 0x6e657261U) ^ key->k[0]; + H->v3 = SIP_ULL(0x74656462U, 0x79746573U) ^ key->k[1]; H->p = H->buf; H->c = 0; diff --git a/Utilities/cmexpat/lib/winconfig.h b/Utilities/cmexpat/lib/winconfig.h index a689db69b9b..bc7d33335c7 100644 --- a/Utilities/cmexpat/lib/winconfig.h +++ b/Utilities/cmexpat/lib/winconfig.h @@ -9,7 +9,8 @@ Copyright (c) 2000 Clark Cooper Copyright (c) 2002 Greg Stein Copyright (c) 2005 Karl Waclawek - Copyright (c) 2017-2021 Sebastian Pipping + Copyright (c) 2017-2023 Sebastian Pipping + Copyright (c) 2023 Orgad Shaneh Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -35,7 +36,9 @@ #ifndef WINCONFIG_H #define WINCONFIG_H -#define WIN32_LEAN_AND_MEAN +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif #include #undef WIN32_LEAN_AND_MEAN diff --git a/Utilities/cmexpat/lib/xmlparse.c b/Utilities/cmexpat/lib/xmlparse.c index 7db28d07acb..2951fec70c5 100644 --- a/Utilities/cmexpat/lib/xmlparse.c +++ b/Utilities/cmexpat/lib/xmlparse.c @@ -1,4 +1,4 @@ -/* a30d2613dcfdef81475a9d1a349134d2d42722172fdaa7d5bb12ed2aa74b9596 (2.4.6+) +/* 2a14271ad4d35e82bde8ba210b4edb7998794bcbae54deab114046a300f9639a (2.6.2+) __ __ _ ___\ \/ /_ __ __ _| |_ / _ \\ /| '_ \ / _` | __| @@ -13,13 +13,13 @@ Copyright (c) 2002-2016 Karl Waclawek Copyright (c) 2005-2009 Steven Solie Copyright (c) 2016 Eric Rahm - Copyright (c) 2016-2022 Sebastian Pipping + Copyright (c) 2016-2024 Sebastian Pipping Copyright (c) 2016 Gaurav Copyright (c) 2016 Thomas Beutlich Copyright (c) 2016 Gustavo Grieco Copyright (c) 2016 Pascal Cuoq Copyright (c) 2016 Ed Schouten - Copyright (c) 2017-2018 Rhodri James + Copyright (c) 2017-2022 Rhodri James Copyright (c) 2017 Václav Slavík Copyright (c) 2017 Viktor Szakats Copyright (c) 2017 Chanho Park @@ -32,8 +32,13 @@ Copyright (c) 2019 David Loffredo Copyright (c) 2019-2020 Ben Wagner Copyright (c) 2019 Vadim Zeitlin - Copyright (c) 2021 Dong-hee Na + Copyright (c) 2021 Donghee Na Copyright (c) 2022 Samanta Navarro + Copyright (c) 2022 Jeffrey Walton + Copyright (c) 2022 Jann Horn + Copyright (c) 2022 Sean McBride + Copyright (c) 2023 Owain Davies + Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -58,10 +63,25 @@ #define XML_BUILDING_EXPAT 1 -#include +#include "expat_config.h" -#if ! defined(_GNU_SOURCE) -# define _GNU_SOURCE 1 /* syscall prototype */ +#if ! defined(XML_GE) || (1 - XML_GE - 1 == 2) || (XML_GE < 0) || (XML_GE > 1) +# error XML_GE (for general entities) must be defined, non-empty, either 1 or 0 (0 to disable, 1 to enable; 1 is a common default) +#endif + +#if defined(XML_DTD) && XML_GE == 0 +# error Either undefine XML_DTD or define XML_GE to 1. +#endif + +#if ! defined(XML_CONTEXT_BYTES) || (1 - XML_CONTEXT_BYTES - 1 == 2) \ + || (XML_CONTEXT_BYTES + 0 < 0) +# error XML_CONTEXT_BYTES must be defined, non-empty and >=0 (0 to disable, >=1 to enable; 1024 is a common default) +#endif + +#if defined(HAVE_SYSCALL_GETRANDOM) +# if ! defined(_GNU_SOURCE) +# define _GNU_SOURCE 1 /* syscall prototype */ +# endif #endif #ifdef _WIN32 @@ -71,6 +91,7 @@ # endif #endif +#include #include #include /* memset(), memcpy() */ #include @@ -129,11 +150,11 @@ Your options include: \ * Linux >=3.17 + glibc >=2.25 (getrandom): HAVE_GETRANDOM, \ * Linux >=3.17 + glibc (including <2.25) (syscall SYS_getrandom): HAVE_SYSCALL_GETRANDOM, \ - * BSD / macOS >=10.7 (arc4random_buf): HAVE_ARC4RANDOM_BUF, \ - * BSD / macOS (including <10.7) (arc4random): HAVE_ARC4RANDOM, \ + * BSD / macOS >=10.7 / glibc >=2.36 (arc4random_buf): HAVE_ARC4RANDOM_BUF, \ + * BSD / macOS (including <10.7) / glibc >=2.36 (arc4random): HAVE_ARC4RANDOM, \ * libbsd (arc4random_buf): HAVE_ARC4RANDOM_BUF + HAVE_LIBBSD, \ * libbsd (arc4random): HAVE_ARC4RANDOM + HAVE_LIBBSD, \ - * Linux (including <3.17) / BSD / macOS (including <10.7) (/dev/urandom): XML_DEV_URANDOM, \ + * Linux (including <3.17) / BSD / macOS (including <10.7) / Solaris >=8 (/dev/urandom): XML_DEV_URANDOM, \ * Windows >=Vista (rand_s): _WIN32. \ \ If insist on not using any of these, bypass this error by defining \ @@ -189,11 +210,13 @@ typedef char ICHAR; #endif /* Round up n to be a multiple of sz, where sz is a power of 2. */ -#define ROUND_UP(n, sz) (((n) + ((sz)-1)) & ~((sz)-1)) +#define ROUND_UP(n, sz) (((n) + ((sz) - 1)) & ~((sz) - 1)) /* Do safe (NULL-aware) pointer arithmetic */ #define EXPAT_SAFE_PTR_DIFF(p, q) (((p) && (q)) ? ((p) - (q)) : 0) +#define EXPAT_MIN(a, b) (((a) < (b)) ? (a) : (b)) + #include "internal.h" #include "xmltok.h" #include "xmlrole.h" @@ -225,7 +248,7 @@ static void copy_salt_to_sipkey(XML_Parser parser, struct sipkey *key); it odd, since odd numbers are always relative prime to a power of 2. */ #define SECOND_HASH(hash, mask, power) \ - ((((hash) & ~(mask)) >> ((power)-1)) & ((mask) >> 2)) + ((((hash) & ~(mask)) >> ((power) - 1)) & ((mask) >> 2)) #define PROBE_STEP(hash, mask, power) \ ((unsigned char)((SECOND_HASH(hash, mask, power)) | 1)) @@ -277,7 +300,7 @@ typedef struct { XML_Parse()/XML_ParseBuffer(), the buffer is re-allocated to contain the 'raw' name as well. - A parser re-uses these structures, maintaining a list of allocated + A parser reuses these structures, maintaining a list of allocated TAG objects in a free list. */ typedef struct tag { @@ -406,12 +429,12 @@ enum XML_Account { XML_ACCOUNT_NONE /* i.e. do not account, was accounted already */ }; -#ifdef XML_DTD +#if XML_GE == 1 typedef unsigned long long XmlBigCount; typedef struct accounting { XmlBigCount countBytesDirect; XmlBigCount countBytesIndirect; - int debugLevel; + unsigned long debugLevel; float maximumAmplificationFactor; // >=1.0 unsigned long long activationThresholdBytes; } ACCOUNTING; @@ -420,9 +443,9 @@ typedef struct entity_stats { unsigned int countEverOpened; unsigned int currentDepth; unsigned int maximumDepthSeen; - int debugLevel; + unsigned long debugLevel; } ENTITY_STATS; -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ typedef enum XML_Error PTRCALL Processor(XML_Parser parser, const char *start, const char *end, const char **endPtr); @@ -462,41 +485,47 @@ static enum XML_Error doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, const char *start, const char *end, const char **endPtr, XML_Bool haveMore, enum XML_Account account); -static enum XML_Error doCdataSection(XML_Parser parser, const ENCODING *, +static enum XML_Error doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, const char *end, const char **nextPtr, XML_Bool haveMore, enum XML_Account account); #ifdef XML_DTD -static enum XML_Error doIgnoreSection(XML_Parser parser, const ENCODING *, +static enum XML_Error doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, const char *end, const char **nextPtr, XML_Bool haveMore); #endif /* XML_DTD */ static void freeBindings(XML_Parser parser, BINDING *bindings); -static enum XML_Error storeAtts(XML_Parser parser, const ENCODING *, - const char *s, TAG_NAME *tagNamePtr, +static enum XML_Error storeAtts(XML_Parser parser, const ENCODING *enc, + const char *attStr, TAG_NAME *tagNamePtr, BINDING **bindingsPtr, enum XML_Account account); static enum XML_Error addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, const XML_Char *uri, BINDING **bindingsPtr); -static int defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *, XML_Bool isCdata, - XML_Bool isId, const XML_Char *dfltValue, - XML_Parser parser); -static enum XML_Error storeAttributeValue(XML_Parser parser, const ENCODING *, - XML_Bool isCdata, const char *, - const char *, STRING_POOL *, +static int defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, + XML_Bool isCdata, XML_Bool isId, + const XML_Char *value, XML_Parser parser); +static enum XML_Error storeAttributeValue(XML_Parser parser, + const ENCODING *enc, XML_Bool isCdata, + const char *ptr, const char *end, + STRING_POOL *pool, enum XML_Account account); -static enum XML_Error appendAttributeValue(XML_Parser parser, const ENCODING *, - XML_Bool isCdata, const char *, - const char *, STRING_POOL *, +static enum XML_Error appendAttributeValue(XML_Parser parser, + const ENCODING *enc, + XML_Bool isCdata, const char *ptr, + const char *end, STRING_POOL *pool, enum XML_Account account); static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc, const char *start, const char *end); -static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *); +static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *elementType); +#if XML_GE == 1 static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc, const char *start, const char *end, enum XML_Account account); +#else +static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity); +#endif static int reportProcessingInstruction(XML_Parser parser, const ENCODING *enc, const char *start, const char *end); static int reportComment(XML_Parser parser, const ENCODING *enc, @@ -516,21 +545,22 @@ static void dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms); static int dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, const XML_Memory_Handling_Suite *ms); -static int copyEntityTable(XML_Parser oldParser, HASH_TABLE *, STRING_POOL *, - const HASH_TABLE *); +static int copyEntityTable(XML_Parser oldParser, HASH_TABLE *newTable, + STRING_POOL *newPool, const HASH_TABLE *oldTable); static NAMED *lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize); -static void FASTCALL hashTableInit(HASH_TABLE *, +static void FASTCALL hashTableInit(HASH_TABLE *table, const XML_Memory_Handling_Suite *ms); -static void FASTCALL hashTableClear(HASH_TABLE *); -static void FASTCALL hashTableDestroy(HASH_TABLE *); -static void FASTCALL hashTableIterInit(HASH_TABLE_ITER *, const HASH_TABLE *); -static NAMED *FASTCALL hashTableIterNext(HASH_TABLE_ITER *); +static void FASTCALL hashTableClear(HASH_TABLE *table); +static void FASTCALL hashTableDestroy(HASH_TABLE *table); +static void FASTCALL hashTableIterInit(HASH_TABLE_ITER *iter, + const HASH_TABLE *table); +static NAMED *FASTCALL hashTableIterNext(HASH_TABLE_ITER *iter); -static void FASTCALL poolInit(STRING_POOL *, +static void FASTCALL poolInit(STRING_POOL *pool, const XML_Memory_Handling_Suite *ms); -static void FASTCALL poolClear(STRING_POOL *); -static void FASTCALL poolDestroy(STRING_POOL *); +static void FASTCALL poolClear(STRING_POOL *pool); +static void FASTCALL poolDestroy(STRING_POOL *pool); static XML_Char *poolAppend(STRING_POOL *pool, const ENCODING *enc, const char *ptr, const char *end); static XML_Char *poolStoreString(STRING_POOL *pool, const ENCODING *enc, @@ -560,7 +590,7 @@ static XML_Parser parserCreate(const XML_Char *encodingName, static void parserInit(XML_Parser parser, const XML_Char *encodingName); -#ifdef XML_DTD +#if XML_GE == 1 static float accountingGetCurrentAmplification(XML_Parser rootParser); static void accountingReportStats(XML_Parser originParser, const char *epilog); static void accountingOnAbort(XML_Parser originParser); @@ -583,13 +613,12 @@ static void entityTrackingOnClose(XML_Parser parser, ENTITY *entity, static XML_Parser getRootParserOf(XML_Parser parser, unsigned int *outLevelDiff); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ static unsigned long getDebugLevel(const char *variableName, unsigned long defaultDebugLevel); #define poolStart(pool) ((pool)->start) -#define poolEnd(pool) ((pool)->ptr) #define poolLength(pool) ((pool)->ptr - (pool)->start) #define poolChop(pool) ((void)--(pool->ptr)) #define poolLastChar(pool) (((pool)->ptr)[-1]) @@ -600,21 +629,41 @@ static unsigned long getDebugLevel(const char *variableName, ? 0 \ : ((*((pool)->ptr)++ = c), 1)) +#if ! defined(XML_TESTING) +const +#endif + XML_Bool g_reparseDeferralEnabledDefault + = XML_TRUE; // write ONLY in runtests.c +#if defined(XML_TESTING) +unsigned int g_bytesScanned = 0; // used for testing only +#endif + struct XML_ParserStruct { /* The first member must be m_userData so that the XML_GetUserData macro works. */ void *m_userData; void *m_handlerArg; - char *m_buffer; + + // How the four parse buffer pointers below relate in time and space: + // + // m_buffer <= m_bufferPtr <= m_bufferEnd <= m_bufferLim + // | | | | + // <--parsed-->| | | + // <---parsing--->| | + // <--unoccupied-->| + // <---------total-malloced/realloced-------->| + + char *m_buffer; // malloc/realloc base pointer of parse buffer const XML_Memory_Handling_Suite m_mem; - /* first character to be parsed */ - const char *m_bufferPtr; - /* past last character to be parsed */ - char *m_bufferEnd; - /* allocated end of m_buffer */ - const char *m_bufferLim; + const char *m_bufferPtr; // first character to be parsed + char *m_bufferEnd; // past last character to be parsed + const char *m_bufferLim; // allocated end of m_buffer + XML_Index m_parseEndByteIndex; const char *m_parseEndPtr; + size_t m_partialTokenBytesBefore; /* used in heuristic to avoid O(n^2) */ + XML_Bool m_reparseDeferralEnabled; + int m_lastBufferRequestSize; XML_Char *m_dataBuf; XML_Char *m_dataBufEnd; XML_StartElementHandler m_startElementHandler; @@ -701,7 +750,7 @@ struct XML_ParserStruct { enum XML_ParamEntityParsing m_paramEntityParsing; #endif unsigned long m_hash_secret_salt; -#ifdef XML_DTD +#if XML_GE == 1 ACCOUNTING m_accounting; ENTITY_STATS m_entity_stats; #endif @@ -722,6 +771,7 @@ XML_ParserCreateNS(const XML_Char *encodingName, XML_Char nsSep) { return XML_ParserCreate_MM(encodingName, NULL, tmp); } +// "xml=http://www.w3.org/XML/1998/namespace" static const XML_Char implicitContext[] = {ASCII_x, ASCII_m, ASCII_l, ASCII_EQUALS, ASCII_h, ASCII_t, ASCII_t, ASCII_p, ASCII_COLON, ASCII_SLASH, @@ -945,6 +995,49 @@ get_hash_secret_salt(XML_Parser parser) { return parser->m_hash_secret_salt; } +static enum XML_Error +callProcessor(XML_Parser parser, const char *start, const char *end, + const char **endPtr) { + const size_t have_now = EXPAT_SAFE_PTR_DIFF(end, start); + + if (parser->m_reparseDeferralEnabled + && ! parser->m_parsingStatus.finalBuffer) { + // Heuristic: don't try to parse a partial token again until the amount of + // available data has increased significantly. + const size_t had_before = parser->m_partialTokenBytesBefore; + // ...but *do* try anyway if we're close to causing a reallocation. + size_t available_buffer + = EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer); +#if XML_CONTEXT_BYTES > 0 + available_buffer -= EXPAT_MIN(available_buffer, XML_CONTEXT_BYTES); +#endif + available_buffer + += EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd); + // m_lastBufferRequestSize is never assigned a value < 0, so the cast is ok + const bool enough + = (have_now >= 2 * had_before) + || ((size_t)parser->m_lastBufferRequestSize > available_buffer); + + if (! enough) { + *endPtr = start; // callers may expect this to be set + return XML_ERROR_NONE; + } + } +#if defined(XML_TESTING) + g_bytesScanned += (unsigned)have_now; +#endif + const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr); + if (ret == XML_ERROR_NONE) { + // if we consumed nothing, remember what we had on this parse attempt. + if (*endPtr == start) { + parser->m_partialTokenBytesBefore = have_now; + } else { + parser->m_partialTokenBytesBefore = 0; + } + } + return ret; +} + static XML_Bool /* only valid for root parser */ startParsing(XML_Parser parser) { /* hash functions must be initialized before setContext() is called */ @@ -1066,6 +1159,14 @@ parserCreate(const XML_Char *encodingName, parserInit(parser, encodingName); if (encodingName && ! parser->m_protocolEncodingName) { + if (dtd) { + // We need to stop the upcoming call to XML_ParserFree from happily + // destroying parser->m_dtd because the DTD is shared with the parent + // parser and the only guard that keeps XML_ParserFree from destroying + // parser->m_dtd is parser->m_isParamEntity but it will be set to + // XML_TRUE only later in XML_ExternalEntityParserCreate (or not at all). + parser->m_dtd = NULL; + } XML_ParserFree(parser); return NULL; } @@ -1118,6 +1219,9 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { parser->m_bufferEnd = parser->m_buffer; parser->m_parseEndByteIndex = 0; parser->m_parseEndPtr = NULL; + parser->m_partialTokenBytesBefore = 0; + parser->m_reparseDeferralEnabled = g_reparseDeferralEnabledDefault; + parser->m_lastBufferRequestSize = 0; parser->m_declElementType = NULL; parser->m_declAttributeId = NULL; parser->m_declEntity = NULL; @@ -1152,7 +1256,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { #endif parser->m_hash_secret_salt = 0; -#ifdef XML_DTD +#if XML_GE == 1 memset(&parser->m_accounting, 0, sizeof(ACCOUNTING)); parser->m_accounting.debugLevel = getDebugLevel("EXPAT_ACCOUNTING_DEBUG", 0u); parser->m_accounting.maximumAmplificationFactor @@ -1287,6 +1391,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, to worry which hash secrets each table has. */ unsigned long oldhash_secret_salt; + XML_Bool oldReparseDeferralEnabled; /* Validate the oldParser parameter before we pull everything out of it */ if (oldParser == NULL) @@ -1331,6 +1436,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, to worry which hash secrets each table has. */ oldhash_secret_salt = parser->m_hash_secret_salt; + oldReparseDeferralEnabled = parser->m_reparseDeferralEnabled; #ifdef XML_DTD if (! context) @@ -1383,6 +1489,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, parser->m_defaultExpandInternalEntities = oldDefaultExpandInternalEntities; parser->m_ns_triplets = oldns_triplets; parser->m_hash_secret_salt = oldhash_secret_salt; + parser->m_reparseDeferralEnabled = oldReparseDeferralEnabled; parser->m_parentParser = oldParser; #ifdef XML_DTD parser->m_paramEntityParsing = oldParamEntityParsing; @@ -1837,55 +1944,8 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { parser->m_parsingStatus.parsing = XML_PARSING; } - if (len == 0) { - parser->m_parsingStatus.finalBuffer = (XML_Bool)isFinal; - if (! isFinal) - return XML_STATUS_OK; - parser->m_positionPtr = parser->m_bufferPtr; - parser->m_parseEndPtr = parser->m_bufferEnd; - - /* If data are left over from last buffer, and we now know that these - data are the final chunk of input, then we have to check them again - to detect errors based on that fact. - */ - parser->m_errorCode - = parser->m_processor(parser, parser->m_bufferPtr, - parser->m_parseEndPtr, &parser->m_bufferPtr); - - if (parser->m_errorCode == XML_ERROR_NONE) { - switch (parser->m_parsingStatus.parsing) { - case XML_SUSPENDED: - /* It is hard to be certain, but it seems that this case - * cannot occur. This code is cleaning up a previous parse - * with no new data (since len == 0). Changing the parsing - * state requires getting to execute a handler function, and - * there doesn't seem to be an opportunity for that while in - * this circumstance. - * - * Given the uncertainty, we retain the code but exclude it - * from coverage tests. - * - * LCOV_EXCL_START - */ - XmlUpdatePosition(parser->m_encoding, parser->m_positionPtr, - parser->m_bufferPtr, &parser->m_position); - parser->m_positionPtr = parser->m_bufferPtr; - return XML_STATUS_SUSPENDED; - /* LCOV_EXCL_STOP */ - case XML_INITIALIZED: - case XML_PARSING: - parser->m_parsingStatus.parsing = XML_FINISHED; - /* fall through */ - default: - return XML_STATUS_OK; - } - } - parser->m_eventEndPtr = parser->m_eventPtr; - parser->m_processor = errorProcessor; - return XML_STATUS_ERROR; - } -#ifndef XML_CONTEXT_BYTES - else if (parser->m_bufferPtr == parser->m_bufferEnd) { +#if XML_CONTEXT_BYTES == 0 + if (parser->m_bufferPtr == parser->m_bufferEnd) { const char *end; int nLeftOver; enum XML_Status result; @@ -1896,12 +1956,15 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { parser->m_processor = errorProcessor; return XML_STATUS_ERROR; } + // though this isn't a buffer request, we assume that `len` is the app's + // preferred buffer fill size, and therefore save it here. + parser->m_lastBufferRequestSize = len; parser->m_parseEndByteIndex += len; parser->m_positionPtr = s; parser->m_parsingStatus.finalBuffer = (XML_Bool)isFinal; parser->m_errorCode - = parser->m_processor(parser, s, parser->m_parseEndPtr = s + len, &end); + = callProcessor(parser, s, parser->m_parseEndPtr = s + len, &end); if (parser->m_errorCode != XML_ERROR_NONE) { parser->m_eventEndPtr = parser->m_eventPtr; @@ -1928,23 +1991,25 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { &parser->m_position); nLeftOver = s + len - end; if (nLeftOver) { - if (parser->m_buffer == NULL - || nLeftOver > parser->m_bufferLim - parser->m_buffer) { - /* avoid _signed_ integer overflow */ - char *temp = NULL; - const int bytesToAllocate = (int)((unsigned)len * 2U); - if (bytesToAllocate > 0) { - temp = (char *)REALLOC(parser, parser->m_buffer, bytesToAllocate); - } - if (temp == NULL) { - parser->m_errorCode = XML_ERROR_NO_MEMORY; - parser->m_eventPtr = parser->m_eventEndPtr = NULL; - parser->m_processor = errorProcessor; - return XML_STATUS_ERROR; - } - parser->m_buffer = temp; - parser->m_bufferLim = parser->m_buffer + bytesToAllocate; + // Back up and restore the parsing status to avoid XML_ERROR_SUSPENDED + // (and XML_ERROR_FINISHED) from XML_GetBuffer. + const enum XML_Parsing originalStatus = parser->m_parsingStatus.parsing; + parser->m_parsingStatus.parsing = XML_PARSING; + void *const temp = XML_GetBuffer(parser, nLeftOver); + parser->m_parsingStatus.parsing = originalStatus; + // GetBuffer may have overwritten this, but we want to remember what the + // app requested, not how many bytes were left over after parsing. + parser->m_lastBufferRequestSize = len; + if (temp == NULL) { + // NOTE: parser->m_errorCode has already been set by XML_GetBuffer(). + parser->m_eventPtr = parser->m_eventEndPtr = NULL; + parser->m_processor = errorProcessor; + return XML_STATUS_ERROR; } + // Since we know that the buffer was empty and XML_CONTEXT_BYTES is 0, we + // don't have any data to preserve, and can copy straight into the start + // of the buffer rather than the GetBuffer return pointer (which may be + // pointing further into the allocated buffer). memcpy(parser->m_buffer, end, nLeftOver); } parser->m_bufferPtr = parser->m_buffer; @@ -1955,16 +2020,15 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { parser->m_eventEndPtr = parser->m_bufferPtr; return result; } -#endif /* not defined XML_CONTEXT_BYTES */ - else { - void *buff = XML_GetBuffer(parser, len); - if (buff == NULL) - return XML_STATUS_ERROR; - else { - memcpy(buff, s, len); - return XML_ParseBuffer(parser, len, isFinal); - } +#endif /* XML_CONTEXT_BYTES == 0 */ + void *buff = XML_GetBuffer(parser, len); + if (buff == NULL) + return XML_STATUS_ERROR; + if (len > 0) { + assert(s != NULL); // make sure s==NULL && len!=0 was rejected above + memcpy(buff, s, len); } + return XML_ParseBuffer(parser, len, isFinal); } enum XML_Status XMLCALL @@ -2004,8 +2068,8 @@ XML_ParseBuffer(XML_Parser parser, int len, int isFinal) { parser->m_parseEndByteIndex += len; parser->m_parsingStatus.finalBuffer = (XML_Bool)isFinal; - parser->m_errorCode = parser->m_processor( - parser, start, parser->m_parseEndPtr, &parser->m_bufferPtr); + parser->m_errorCode = callProcessor(parser, start, parser->m_parseEndPtr, + &parser->m_bufferPtr); if (parser->m_errorCode != XML_ERROR_NONE) { parser->m_eventEndPtr = parser->m_eventPtr; @@ -2050,10 +2114,14 @@ XML_GetBuffer(XML_Parser parser, int len) { default:; } - if (len > EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd)) { -#ifdef XML_CONTEXT_BYTES + // whether or not the request succeeds, `len` seems to be the app's preferred + // buffer fill size; remember it. + parser->m_lastBufferRequestSize = len; + if (len > EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd) + || parser->m_buffer == NULL) { +#if XML_CONTEXT_BYTES > 0 int keep; -#endif /* defined XML_CONTEXT_BYTES */ +#endif /* XML_CONTEXT_BYTES > 0 */ /* Do not invoke signed arithmetic overflow: */ int neededSize = (int)((unsigned)len + (unsigned)EXPAT_SAFE_PTR_DIFF( @@ -2062,7 +2130,7 @@ XML_GetBuffer(XML_Parser parser, int len) { parser->m_errorCode = XML_ERROR_NO_MEMORY; return NULL; } -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 keep = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer); if (keep > XML_CONTEXT_BYTES) keep = XML_CONTEXT_BYTES; @@ -2072,10 +2140,11 @@ XML_GetBuffer(XML_Parser parser, int len) { return NULL; } neededSize += keep; -#endif /* defined XML_CONTEXT_BYTES */ - if (neededSize - <= EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_buffer)) { -#ifdef XML_CONTEXT_BYTES +#endif /* XML_CONTEXT_BYTES > 0 */ + if (parser->m_buffer && parser->m_bufferPtr + && neededSize + <= EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_buffer)) { +#if XML_CONTEXT_BYTES > 0 if (keep < EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer)) { int offset = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer) @@ -2088,19 +2157,17 @@ XML_GetBuffer(XML_Parser parser, int len) { parser->m_bufferPtr -= offset; } #else - if (parser->m_buffer && parser->m_bufferPtr) { - memmove(parser->m_buffer, parser->m_bufferPtr, - EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr)); - parser->m_bufferEnd - = parser->m_buffer - + EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr); - parser->m_bufferPtr = parser->m_buffer; - } -#endif /* not defined XML_CONTEXT_BYTES */ + memmove(parser->m_buffer, parser->m_bufferPtr, + EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr)); + parser->m_bufferEnd + = parser->m_buffer + + EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr); + parser->m_bufferPtr = parser->m_buffer; +#endif /* XML_CONTEXT_BYTES > 0 */ } else { char *newBuf; int bufferSize - = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferPtr); + = (int)EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_buffer); if (bufferSize == 0) bufferSize = INIT_BUFFER_SIZE; do { @@ -2117,7 +2184,7 @@ XML_GetBuffer(XML_Parser parser, int len) { return NULL; } parser->m_bufferLim = newBuf + bufferSize; -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 if (parser->m_bufferPtr) { memcpy(newBuf, &parser->m_bufferPtr[-keep], EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr) @@ -2147,7 +2214,7 @@ XML_GetBuffer(XML_Parser parser, int len) { parser->m_bufferEnd = newBuf; } parser->m_bufferPtr = parser->m_buffer = newBuf; -#endif /* not defined XML_CONTEXT_BYTES */ +#endif /* XML_CONTEXT_BYTES > 0 */ } parser->m_eventPtr = parser->m_eventEndPtr = NULL; parser->m_positionPtr = NULL; @@ -2197,7 +2264,7 @@ XML_ResumeParser(XML_Parser parser) { } parser->m_parsingStatus.parsing = XML_PARSING; - parser->m_errorCode = parser->m_processor( + parser->m_errorCode = callProcessor( parser, parser->m_bufferPtr, parser->m_parseEndPtr, &parser->m_bufferPtr); if (parser->m_errorCode != XML_ERROR_NONE) { @@ -2261,7 +2328,7 @@ XML_GetCurrentByteCount(XML_Parser parser) { const char *XMLCALL XML_GetInputContext(XML_Parser parser, int *offset, int *size) { -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 if (parser == NULL) return NULL; if (parser->m_eventPtr && parser->m_buffer) { @@ -2275,7 +2342,7 @@ XML_GetInputContext(XML_Parser parser, int *offset, int *size) { (void)parser; (void)offset; (void)size; -#endif /* defined XML_CONTEXT_BYTES */ +#endif /* XML_CONTEXT_BYTES > 0 */ return (const char *)0; } @@ -2495,7 +2562,7 @@ XML_GetFeatureList(void) { #ifdef XML_DTD {XML_FEATURE_DTD, XML_L("XML_DTD"), 0}, #endif -#ifdef XML_CONTEXT_BYTES +#if XML_CONTEXT_BYTES > 0 {XML_FEATURE_CONTEXT_BYTES, XML_L("XML_CONTEXT_BYTES"), XML_CONTEXT_BYTES}, #endif @@ -2511,8 +2578,9 @@ XML_GetFeatureList(void) { #ifdef XML_ATTR_INFO {XML_FEATURE_ATTR_INFO, XML_L("XML_ATTR_INFO"), 0}, #endif -#ifdef XML_DTD - /* Added in Expat 2.4.0. */ +#if XML_GE == 1 + /* Added in Expat 2.4.0 for XML_DTD defined and + * added in Expat 2.6.0 for XML_GE == 1. */ {XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT, XML_L("XML_BLAP_MAX_AMP"), (long int) @@ -2520,13 +2588,15 @@ XML_GetFeatureList(void) { {XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT, XML_L("XML_BLAP_ACT_THRES"), EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT}, + /* Added in Expat 2.6.0. */ + {XML_FEATURE_GE, XML_L("XML_GE"), 0}, #endif {XML_FEATURE_END, NULL, 0}}; return features; } -#ifdef XML_DTD +#if XML_GE == 1 XML_Bool XMLCALL XML_SetBillionLaughsAttackProtectionMaximumAmplification( XML_Parser parser, float maximumAmplificationFactor) { @@ -2548,7 +2618,16 @@ XML_SetBillionLaughsAttackProtectionActivationThreshold( parser->m_accounting.activationThresholdBytes = activationThresholdBytes; return XML_TRUE; } -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ + +XML_Bool XMLCALL +XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled) { + if (parser != NULL && (enabled == XML_TRUE || enabled == XML_FALSE)) { + parser->m_reparseDeferralEnabled = enabled; + return XML_TRUE; + } + return XML_FALSE; +} /* Initially tag->rawName always points into the parse buffer; for those TAG instances opened while the current parse buffer was @@ -2570,7 +2649,7 @@ storeRawNames(XML_Parser parser) { */ if (tag->rawName == rawNameBuf) break; - /* For re-use purposes we need to ensure that the + /* For reuse purposes we need to ensure that the size of tag->buf is a multiple of sizeof(XML_Char). */ rawNameLen = ROUND_UP(tag->rawNameLength, sizeof(XML_Char)); @@ -2634,13 +2713,13 @@ externalEntityInitProcessor2(XML_Parser parser, const char *start, int tok = XmlContentTok(parser->m_encoding, start, end, &next); switch (tok) { case XML_TOK_BOM: -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, start, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ /* If we are at the end of the buffer, this would cause the next stage, i.e. externalEntityInitProcessor3, to pass control directly to @@ -2754,7 +2833,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, for (;;) { const char *next = s; /* XmlContentTok doesn't always set the last arg */ int tok = XmlContentTok(enc, s, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 const char *accountAfter = ((tok == XML_TOK_TRAILING_RSQB) || (tok == XML_TOK_TRAILING_CR)) ? (haveMore ? s /* i.e. 0 bytes */ : end) @@ -2820,14 +2899,14 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, XML_Char ch = (XML_Char)XmlPredefinedEntityName( enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar); if (ch) { -#ifdef XML_DTD +#if XML_GE == 1 /* NOTE: We are replacing 4-6 characters original input for 1 character * so there is no amplification and hence recording without * protection. */ accountingDiffTolerated(parser, tok, (char *)&ch, ((char *)&ch) + sizeof(XML_Char), __LINE__, XML_ACCOUNT_ENTITY_EXPANSION); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ if (parser->m_characterDataHandler) parser->m_characterDataHandler(parser->m_handlerArg, &ch, 1); else if (parser->m_defaultHandler) @@ -3009,9 +3088,6 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, int len; const char *rawName; TAG *tag = parser->m_tagStack; - parser->m_tagStack = tag->parent; - tag->parent = parser->m_freeTagList; - parser->m_freeTagList = tag; rawName = s + enc->minBytesPerChar * 2; len = XmlNameLength(enc, rawName); if (len != tag->rawNameLength @@ -3019,6 +3095,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, *eventPP = rawName; return XML_ERROR_TAG_MISMATCH; } + parser->m_tagStack = tag->parent; + tag->parent = parser->m_freeTagList; + parser->m_freeTagList = tag; --parser->m_tagLevel; if (parser->m_endElementHandler) { const XML_Char *localPart; @@ -3028,13 +3107,13 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, if (parser->m_ns && localPart) { /* localPart and prefix may have been overwritten in tag->name.str, since this points to the binding->uri - buffer which gets re-used; so we have to add them again + buffer which gets reused; so we have to add them again */ uri = (XML_Char *)tag->name.str + tag->name.uriLen; /* don't need to check for space - already done in storeAtts() */ while (*localPart) *uri++ = *localPart++; - prefix = (XML_Char *)tag->name.prefix; + prefix = tag->name.prefix; if (parser->m_ns_triplets && prefix) { *uri++ = parser->m_namespaceSeparator; while (*prefix) @@ -3101,7 +3180,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, However, now we have a start/endCdataSectionHandler, so it seems easier to let the user deal with this. */ - else if (0 && parser->m_characterDataHandler) + else if ((0) && parser->m_characterDataHandler) parser->m_characterDataHandler(parser->m_handlerArg, parser->m_dataBuf, 0); /* END disabled code */ @@ -3130,8 +3209,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, (int)(dataPtr - (ICHAR *)parser->m_dataBuf)); } else parser->m_characterDataHandler( - parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)end - (XML_Char *)s)); + parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)end - (const XML_Char *)s)); } else if (parser->m_defaultHandler) reportDefault(parser, enc, s, end); /* We are at the end of the final buffer, should we check for @@ -3164,8 +3243,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, *eventPP = s; } } else - charDataHandler(parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)next - (XML_Char *)s)); + charDataHandler(parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)next - (const XML_Char *)s)); } else if (parser->m_defaultHandler) reportDefault(parser, enc, s, next); } break; @@ -3704,12 +3783,124 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, return XML_ERROR_NONE; } +static XML_Bool +is_rfc3986_uri_char(XML_Char candidate) { + // For the RFC 3986 ANBF grammar see + // https://datatracker.ietf.org/doc/html/rfc3986#appendix-A + + switch (candidate) { + // From rule "ALPHA" (uppercase half) + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + + // From rule "ALPHA" (lowercase half) + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + + // From rule "DIGIT" + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + + // From rule "pct-encoded" + case '%': + + // From rule "unreserved" + case '-': + case '.': + case '_': + case '~': + + // From rule "gen-delims" + case ':': + case '/': + case '?': + case '#': + case '[': + case ']': + case '@': + + // From rule "sub-delims" + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + return XML_TRUE; + + default: + return XML_FALSE; + } +} + /* addBinding() overwrites the value of prefix->binding without checking. Therefore one must keep track of the old value outside of addBinding(). */ static enum XML_Error addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, const XML_Char *uri, BINDING **bindingsPtr) { + // "http://www.w3.org/XML/1998/namespace" static const XML_Char xmlNamespace[] = {ASCII_h, ASCII_t, ASCII_t, ASCII_p, ASCII_COLON, ASCII_SLASH, ASCII_SLASH, ASCII_w, ASCII_w, ASCII_w, @@ -3720,6 +3911,7 @@ addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, ASCII_e, ASCII_s, ASCII_p, ASCII_a, ASCII_c, ASCII_e, '\0'}; static const int xmlLen = (int)sizeof(xmlNamespace) / sizeof(XML_Char) - 1; + // "http://www.w3.org/2000/xmlns/" static const XML_Char xmlnsNamespace[] = {ASCII_h, ASCII_t, ASCII_t, ASCII_p, ASCII_COLON, ASCII_SLASH, ASCII_SLASH, ASCII_w, ASCII_w, ASCII_w, ASCII_PERIOD, ASCII_w, @@ -3760,14 +3952,26 @@ addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, && (len > xmlnsLen || uri[len] != xmlnsNamespace[len])) isXMLNS = XML_FALSE; - // NOTE: While Expat does not validate namespace URIs against RFC 3986, - // we have to at least make sure that the XML processor on top of - // Expat (that is splitting tag names by namespace separator into - // 2- or 3-tuples (uri-local or uri-local-prefix)) cannot be confused - // by an attacker putting additional namespace separator characters - // into namespace declarations. That would be ambiguous and not to - // be expected. - if (parser->m_ns && (uri[len] == parser->m_namespaceSeparator)) { + // NOTE: While Expat does not validate namespace URIs against RFC 3986 + // today (and is not REQUIRED to do so with regard to the XML 1.0 + // namespaces specification) we have to at least make sure, that + // the application on top of Expat (that is likely splitting expanded + // element names ("qualified names") of form + // "[uri sep] local [sep prefix] '\0'" back into 1, 2 or 3 pieces + // in its element handler code) cannot be confused by an attacker + // putting additional namespace separator characters into namespace + // declarations. That would be ambiguous and not to be expected. + // + // While the HTML API docs of function XML_ParserCreateNS have been + // advising against use of a namespace separator character that can + // appear in a URI for >20 years now, some widespread applications + // are using URI characters (':' (colon) in particular) for a + // namespace separator, in practice. To keep these applications + // functional, we only reject namespaces URIs containing the + // application-chosen namespace separator if the chosen separator + // is a non-URI character with regard to RFC 3986. + if (parser->m_ns && (uri[len] == parser->m_namespaceSeparator) + && ! is_rfc3986_uri_char(uri[len])) { return XML_ERROR_SYNTAX; } } @@ -3904,7 +4108,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, for (;;) { const char *next = s; /* in case of XML_TOK_NONE or XML_TOK_PARTIAL */ int tok = XmlCdataSectionTok(enc, s, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { accountingOnAbort(parser); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; @@ -3919,7 +4123,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, parser->m_endCdataSectionHandler(parser->m_handlerArg); /* BEGIN disabled code */ /* see comment under XML_TOK_CDATA_SECT_OPEN */ - else if (0 && parser->m_characterDataHandler) + else if ((0) && parser->m_characterDataHandler) parser->m_characterDataHandler(parser->m_handlerArg, parser->m_dataBuf, 0); /* END disabled code */ @@ -3955,8 +4159,8 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, *eventPP = s; } } else - charDataHandler(parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)next - (XML_Char *)s)); + charDataHandler(parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)next - (const XML_Char *)s)); } else if (parser->m_defaultHandler) reportDefault(parser, enc, s, next); } break; @@ -4056,7 +4260,7 @@ doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, *eventPP = s; *startPtr = NULL; tok = XmlIgnoreSectionTok(enc, s, end, &next); -# ifdef XML_DTD +# if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -4144,11 +4348,11 @@ processXmlDecl(XML_Parser parser, int isGeneralTextEntity, const char *s, const XML_Char *storedEncName = NULL; const ENCODING *newEncoding = NULL; const char *version = NULL; - const char *versionend; + const char *versionend = NULL; const XML_Char *storedversion = NULL; int standalone = -1; -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, XML_TOK_XML_DECL, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -4346,16 +4550,16 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, parser->m_processor = entityValueProcessor; return entityValueProcessor(parser, next, end, nextPtr); } - /* If we are at the end of the buffer, this would cause XmlPrologTok to - return XML_TOK_NONE on the next call, which would then cause the - function to exit with *nextPtr set to s - that is what we want for other - tokens, but not for the BOM - we would rather like to skip it; - then, when this routine is entered the next time, XmlPrologTok will - return XML_TOK_INVALID, since the BOM is still in the buffer + /* XmlPrologTok has now set the encoding based on the BOM it found, and we + must move s and nextPtr forward to consume the BOM. + + If we didn't, and got XML_TOK_NONE from the next XmlPrologTok call, we + would leave the BOM in the buffer and return. On the next call to this + function, our XmlPrologTok call would return XML_TOK_INVALID, since it + is not valid to have multiple BOMs. */ - else if (tok == XML_TOK_BOM && next == end - && ! parser->m_parsingStatus.finalBuffer) { -# ifdef XML_DTD + else if (tok == XML_TOK_BOM) { +# if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -4364,7 +4568,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, # endif *nextPtr = next; - return XML_ERROR_NONE; + s = next; } /* If we get this token, we have the start of what might be a normal tag, but not a declaration (i.e. it doesn't begin with @@ -4571,11 +4775,13 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } } role = XmlTokenRole(&parser->m_prologState, tok, s, next, enc); -#ifdef XML_DTD +#if XML_GE == 1 switch (role) { case XML_ROLE_INSTANCE_START: // bytes accounted in contentProcessor case XML_ROLE_XML_DECL: // bytes accounted in processXmlDecl - case XML_ROLE_TEXT_DECL: // bytes accounted in processXmlDecl +# ifdef XML_DTD + case XML_ROLE_TEXT_DECL: // bytes accounted in processXmlDecl +# endif break; default: if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { @@ -4848,10 +5054,10 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, parser->m_handlerArg, parser->m_declElementType->name, parser->m_declAttributeId->name, parser->m_declAttributeType, 0, role == XML_ROLE_REQUIRED_ATTRIBUTE_VALUE); - poolClear(&parser->m_tempPool); handleDefault = XML_FALSE; } } + poolClear(&parser->m_tempPool); break; case XML_ROLE_DEFAULT_ATTRIBUTE_VALUE: case XML_ROLE_FIXED_ATTRIBUTE_VALUE: @@ -4893,6 +5099,9 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, break; case XML_ROLE_ENTITY_VALUE: if (dtd->keepProcessing) { +#if XML_GE == 1 + // This will store the given replacement text in + // parser->m_declEntity->textPtr. enum XML_Error result = storeEntityValue(parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar, XML_ACCOUNT_NONE); @@ -4913,6 +5122,25 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, poolDiscard(&dtd->entityValuePool); if (result != XML_ERROR_NONE) return result; +#else + // This will store "&entity123;" in parser->m_declEntity->textPtr + // to end up as "&entity123;" in the handler. + if (parser->m_declEntity != NULL) { + const enum XML_Error result + = storeSelfEntityValue(parser, parser->m_declEntity); + if (result != XML_ERROR_NONE) + return result; + + if (parser->m_entityDeclHandler) { + *eventEndPP = s; + parser->m_entityDeclHandler( + parser->m_handlerArg, parser->m_declEntity->name, + parser->m_declEntity->is_param, parser->m_declEntity->textPtr, + parser->m_declEntity->textLen, parser->m_curBase, 0, 0, 0); + handleDefault = XML_FALSE; + } + } +#endif } break; case XML_ROLE_DOCTYPE_SYSTEM_ID: @@ -4971,6 +5199,16 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } break; case XML_ROLE_ENTITY_COMPLETE: +#if XML_GE == 0 + // This will store "&entity123;" in entity->textPtr + // to end up as "&entity123;" in the handler. + if (parser->m_declEntity != NULL) { + const enum XML_Error result + = storeSelfEntityValue(parser, parser->m_declEntity); + if (result != XML_ERROR_NONE) + return result; + } +#endif if (dtd->keepProcessing && parser->m_declEntity && parser->m_entityDeclHandler) { *eventEndPP = s; @@ -5259,7 +5497,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, * * If 'standalone' is false, the DTD must have no * parameter entities or we wouldn't have passed the outer - * 'if' statement. That measn the only entity in the hash + * 'if' statement. That means the only entity in the hash * table is the external subset name "#" which cannot be * given as a parameter entity name in XML syntax, so the * lookup must have returned NULL and we don't even reach @@ -5512,7 +5750,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, for (;;) { const char *next = NULL; int tok = XmlPrologTok(parser->m_encoding, s, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { accountingOnAbort(parser); @@ -5592,7 +5830,7 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { return XML_ERROR_NO_MEMORY; } entity->open = XML_TRUE; -#ifdef XML_DTD +#if XML_GE == 1 entityTrackingOnOpen(parser, entity, __LINE__); #endif entity->processed = 0; @@ -5625,10 +5863,10 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) { entity->processed = (int)(next - textStart); parser->m_processor = internalEntityProcessor; - } else { -#ifdef XML_DTD + } else if (parser->m_openInternalEntities->entity == entity) { +#if XML_GE == 1 entityTrackingOnClose(parser, entity, __LINE__); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ entity->open = XML_FALSE; parser->m_openInternalEntities = openEntity->next; /* put openEntity back in list of free instances */ @@ -5671,19 +5909,27 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, if (result != XML_ERROR_NONE) return result; - else if (textEnd != next - && parser->m_parsingStatus.parsing == XML_SUSPENDED) { + + if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) { entity->processed = (int)(next - (const char *)entity->textPtr); return result; - } else { -#ifdef XML_DTD - entityTrackingOnClose(parser, entity, __LINE__); + } + +#if XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); #endif - entity->open = XML_FALSE; - parser->m_openInternalEntities = openEntity->next; - /* put openEntity back in list of free instances */ - openEntity->next = parser->m_freeInternalEntities; - parser->m_freeInternalEntities = openEntity; + entity->open = XML_FALSE; + parser->m_openInternalEntities = openEntity->next; + /* put openEntity back in list of free instances */ + openEntity->next = parser->m_freeInternalEntities; + parser->m_freeInternalEntities = openEntity; + + // If there are more open entities we want to stop right here and have the + // upcoming call to XML_ResumeParser continue with entity content, or it would + // be ignored altogether. + if (parser->m_openInternalEntities != NULL + && parser->m_parsingStatus.parsing == XML_SUSPENDED) { + return XML_ERROR_NONE; } #ifdef XML_DTD @@ -5699,10 +5945,15 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, { parser->m_processor = contentProcessor; /* see externalEntityContentProcessor vs contentProcessor */ - return doContent(parser, parser->m_parentParser ? 1 : 0, parser->m_encoding, - s, end, nextPtr, - (XML_Bool)! parser->m_parsingStatus.finalBuffer, - XML_ACCOUNT_DIRECT); + result = doContent(parser, parser->m_parentParser ? 1 : 0, + parser->m_encoding, s, end, nextPtr, + (XML_Bool)! parser->m_parsingStatus.finalBuffer, + XML_ACCOUNT_DIRECT); + if (result == XML_ERROR_NONE) { + if (! storeRawNames(parser)) + return XML_ERROR_NO_MEMORY; + } + return result; } } @@ -5743,7 +5994,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, const char *next = ptr; /* XmlAttributeValueTok doesn't always set the last arg */ int tok = XmlAttributeValueTok(enc, ptr, end, &next); -#ifdef XML_DTD +#if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, ptr, next, __LINE__, account)) { accountingOnAbort(parser); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; @@ -5808,14 +6059,14 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, XML_Char ch = (XML_Char)XmlPredefinedEntityName( enc, ptr + enc->minBytesPerChar, next - enc->minBytesPerChar); if (ch) { -#ifdef XML_DTD +#if XML_GE == 1 /* NOTE: We are replacing 4-6 characters original input for 1 character * so there is no amplification and hence recording without * protection. */ accountingDiffTolerated(parser, tok, (char *)&ch, ((char *)&ch) + sizeof(XML_Char), __LINE__, XML_ACCOUNT_ENTITY_EXPANSION); -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ if (! poolAppendChar(pool, ch)) return XML_ERROR_NO_MEMORY; break; @@ -5893,14 +6144,14 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, enum XML_Error result; const XML_Char *textEnd = entity->textPtr + entity->textLen; entity->open = XML_TRUE; -#ifdef XML_DTD +#if XML_GE == 1 entityTrackingOnOpen(parser, entity, __LINE__); #endif result = appendAttributeValue(parser, parser->m_internalEncoding, isCdata, (const char *)entity->textPtr, (const char *)textEnd, pool, XML_ACCOUNT_ENTITY_EXPANSION); -#ifdef XML_DTD +#if XML_GE == 1 entityTrackingOnClose(parser, entity, __LINE__); #endif entity->open = XML_FALSE; @@ -5930,6 +6181,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, /* not reached */ } +#if XML_GE == 1 static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc, const char *entityTextPtr, const char *entityTextEnd, @@ -5937,12 +6189,12 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, DTD *const dtd = parser->m_dtd; /* save one level of indirection */ STRING_POOL *pool = &(dtd->entityValuePool); enum XML_Error result = XML_ERROR_NONE; -#ifdef XML_DTD +# ifdef XML_DTD int oldInEntityValue = parser->m_prologState.inEntityValue; parser->m_prologState.inEntityValue = 1; -#else +# else UNUSED_P(account); -#endif /* XML_DTD */ +# endif /* XML_DTD */ /* never return Null for the value argument in EntityDeclHandler, since this would indicate an external entity; therefore we have to make sure that entityValuePool.start is not null */ @@ -5956,18 +6208,16 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */ int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next); -#ifdef XML_DTD if (! accountingDiffTolerated(parser, tok, entityTextPtr, next, __LINE__, account)) { accountingOnAbort(parser); result = XML_ERROR_AMPLIFICATION_LIMIT_BREACH; goto endEntityValue; } -#endif switch (tok) { case XML_TOK_PARAM_ENTITY_REF: -#ifdef XML_DTD +# ifdef XML_DTD if (parser->m_isParamEntity || enc != parser->m_encoding) { const XML_Char *name; ENTITY *entity; @@ -5990,7 +6240,7 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, dtd->keepProcessing = dtd->standalone; goto endEntityValue; } - if (entity->open) { + if (entity->open || (entity == parser->m_declEntity)) { if (enc == parser->m_encoding) parser->m_eventPtr = entityTextPtr; result = XML_ERROR_RECURSIVE_ENTITY_REF; @@ -6029,7 +6279,7 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, } break; } -#endif /* XML_DTD */ +# endif /* XML_DTD */ /* In the internal subset, PE references are not legal within markup declarations, e.g entity values in this case. */ parser->m_eventPtr = entityTextPtr; @@ -6110,12 +6360,38 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, entityTextPtr = next; } endEntityValue: -#ifdef XML_DTD +# ifdef XML_DTD parser->m_prologState.inEntityValue = oldInEntityValue; -#endif /* XML_DTD */ +# endif /* XML_DTD */ return result; } +#else /* XML_GE == 0 */ + +static enum XML_Error +storeSelfEntityValue(XML_Parser parser, ENTITY *entity) { + // This will store "&entity123;" in entity->textPtr + // to end up as "&entity123;" in the handler. + const char *const entity_start = "&"; + const char *const entity_end = ";"; + + STRING_POOL *const pool = &(parser->m_dtd->entityValuePool); + if (! poolAppendString(pool, entity_start) + || ! poolAppendString(pool, entity->name) + || ! poolAppendString(pool, entity_end)) { + poolDiscard(pool); + return XML_ERROR_NO_MEMORY; + } + + entity->textPtr = poolStart(pool); + entity->textLen = (int)(poolLength(pool)); + poolFinish(pool); + + return XML_ERROR_NONE; +} + +#endif /* XML_GE == 0 */ + static void FASTCALL normalizeLines(XML_Char *s) { XML_Char *p; @@ -6226,8 +6502,9 @@ reportDefault(XML_Parser parser, const ENCODING *enc, const char *s, } while ((convert_res != XML_CONVERT_COMPLETED) && (convert_res != XML_CONVERT_INPUT_INCOMPLETE)); } else - parser->m_defaultHandler(parser->m_handlerArg, (XML_Char *)s, - (int)((XML_Char *)end - (XML_Char *)s)); + parser->m_defaultHandler( + parser->m_handlerArg, (const XML_Char *)s, + (int)((const XML_Char *)end - (const XML_Char *)s)); } static int @@ -6331,7 +6608,7 @@ getAttributeId(XML_Parser parser, const ENCODING *enc, const char *start, name = poolStoreString(&dtd->pool, enc, start, end); if (! name) return NULL; - /* skip quotation mark - its storage will be re-used (like in name[-1]) */ + /* skip quotation mark - its storage will be reused (like in name[-1]) */ ++name; id = (ATTRIBUTE_ID *)lookup(parser, &dtd->attributeIds, name, sizeof(ATTRIBUTE_ID)); @@ -6481,6 +6758,10 @@ getContext(XML_Parser parser) { static XML_Bool setContext(XML_Parser parser, const XML_Char *context) { + if (context == NULL) { + return XML_FALSE; + } + DTD *const dtd = parser->m_dtd; /* save one level of indirection */ const XML_Char *s = context; @@ -7071,7 +7352,7 @@ poolAppend(STRING_POOL *pool, const ENCODING *enc, const char *ptr, return NULL; for (;;) { const enum XML_Convert_Result convert_res = XmlConvert( - enc, &ptr, end, (ICHAR **)&(pool->ptr), (ICHAR *)pool->end); + enc, &ptr, end, (ICHAR **)&(pool->ptr), (const ICHAR *)pool->end); if ((convert_res == XML_CONVERT_COMPLETED) || (convert_res == XML_CONVERT_INPUT_INCOMPLETE)) break; @@ -7502,10 +7783,12 @@ copyString(const XML_Char *s, const XML_Memory_Handling_Suite *memsuite) { return result; } -#ifdef XML_DTD +#if XML_GE == 1 static float accountingGetCurrentAmplification(XML_Parser rootParser) { + // 1.........1.........12 => 22 + const size_t lenOfShortestInclude = sizeof("") - 1; const XmlBigCount countBytesOutput = rootParser->m_accounting.countBytesDirect + rootParser->m_accounting.countBytesIndirect; @@ -7513,7 +7796,9 @@ accountingGetCurrentAmplification(XML_Parser rootParser) { = rootParser->m_accounting.countBytesDirect ? (countBytesOutput / (float)(rootParser->m_accounting.countBytesDirect)) - : 1.0f; + : ((lenOfShortestInclude + + rootParser->m_accounting.countBytesIndirect) + / (float)lenOfShortestInclude); assert(! rootParser->m_parentParser); return amplificationFactor; } @@ -7523,7 +7808,7 @@ accountingReportStats(XML_Parser originParser, const char *epilog) { const XML_Parser rootParser = getRootParserOf(originParser, NULL); assert(! rootParser->m_parentParser); - if (rootParser->m_accounting.debugLevel < 1) { + if (rootParser->m_accounting.debugLevel == 0u) { return; } @@ -7560,7 +7845,7 @@ accountingReportDiff(XML_Parser rootParser, /* Note: Performance is of no concern here */ const char *walker = before; - if ((rootParser->m_accounting.debugLevel >= 3) + if ((rootParser->m_accounting.debugLevel >= 3u) || (after - before) <= (ptrdiff_t)(contextLength + ellipsisLength + contextLength)) { for (; walker < after; walker++) { @@ -7625,7 +7910,7 @@ accountingDiffTolerated(XML_Parser originParser, int tok, const char *before, || (amplificationFactor <= rootParser->m_accounting.maximumAmplificationFactor); - if (rootParser->m_accounting.debugLevel >= 2) { + if (rootParser->m_accounting.debugLevel >= 2u) { accountingReportStats(rootParser, ""); accountingReportDiff(rootParser, levelsAwayFromRootParser, before, after, bytesMore, source_line, account); @@ -7652,7 +7937,7 @@ static void entityTrackingReportStats(XML_Parser rootParser, ENTITY *entity, const char *action, int sourceLine) { assert(! rootParser->m_parentParser); - if (rootParser->m_entity_stats.debugLevel < 1) + if (rootParser->m_entity_stats.debugLevel == 0u) return; # if defined(XML_UNICODE) @@ -8233,7 +8518,7 @@ unsignedCharToPrintable(unsigned char c) { assert(0); /* never gets here */ } -#endif /* XML_DTD */ +#endif /* XML_GE == 1 */ static unsigned long getDebugLevel(const char *variableName, unsigned long defaultDebugLevel) { @@ -8244,9 +8529,9 @@ getDebugLevel(const char *variableName, unsigned long defaultDebugLevel) { const char *const value = valueOrNull; errno = 0; - char *afterValue = (char *)value; + char *afterValue = NULL; unsigned long debugLevel = strtoul(value, &afterValue, 10); - if ((errno != 0) || (afterValue[0] != '\0')) { + if ((errno != 0) || (afterValue == value) || (afterValue[0] != '\0')) { errno = 0; return defaultDebugLevel; } diff --git a/Utilities/cmexpat/lib/xmlrole.c b/Utilities/cmexpat/lib/xmlrole.c index 3f0f5c150c6..2c48bf40867 100644 --- a/Utilities/cmexpat/lib/xmlrole.c +++ b/Utilities/cmexpat/lib/xmlrole.c @@ -12,10 +12,10 @@ Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2002-2003 Fred L. Drake, Jr. Copyright (c) 2005-2009 Steven Solie - Copyright (c) 2016-2021 Sebastian Pipping + Copyright (c) 2016-2023 Sebastian Pipping Copyright (c) 2017 Rhodri James Copyright (c) 2019 David Loffredo - Copyright (c) 2021 Dong-hee Na + Copyright (c) 2021 Donghee Na Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -38,7 +38,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include "expat_config.h" #include diff --git a/Utilities/cmexpat/lib/xmlrole.h b/Utilities/cmexpat/lib/xmlrole.h index d6e1fa150a1..a7904274c91 100644 --- a/Utilities/cmexpat/lib/xmlrole.h +++ b/Utilities/cmexpat/lib/xmlrole.h @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper Copyright (c) 2002 Karl Waclawek Copyright (c) 2002 Fred L. Drake, Jr. - Copyright (c) 2017 Sebastian Pipping + Copyright (c) 2017-2024 Sebastian Pipping Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -127,9 +127,9 @@ typedef struct prolog_state { #endif /* XML_DTD */ } PROLOG_STATE; -void XmlPrologStateInit(PROLOG_STATE *); +void XmlPrologStateInit(PROLOG_STATE *state); #ifdef XML_DTD -void XmlPrologStateInitExternalEntity(PROLOG_STATE *); +void XmlPrologStateInitExternalEntity(PROLOG_STATE *state); #endif /* XML_DTD */ #define XmlTokenRole(state, tok, ptr, end, enc) \ diff --git a/Utilities/cmexpat/lib/xmltok.c b/Utilities/cmexpat/lib/xmltok.c index c659983b400..29a66d72cee 100644 --- a/Utilities/cmexpat/lib/xmltok.c +++ b/Utilities/cmexpat/lib/xmltok.c @@ -12,7 +12,7 @@ Copyright (c) 2002 Greg Stein Copyright (c) 2002-2016 Karl Waclawek Copyright (c) 2005-2009 Steven Solie - Copyright (c) 2016-2022 Sebastian Pipping + Copyright (c) 2016-2024 Sebastian Pipping Copyright (c) 2016 Pascal Cuoq Copyright (c) 2016 Don Lewis Copyright (c) 2017 Rhodri James @@ -20,7 +20,10 @@ Copyright (c) 2017 Benbuck Nason Copyright (c) 2017 José Gutiérrez de la Concha Copyright (c) 2019 David Loffredo - Copyright (c) 2021 Dong-hee Na + Copyright (c) 2021 Donghee Na + Copyright (c) 2022 Martin Ettl + Copyright (c) 2022 Sean McBride + Copyright (c) 2023 Hanno Böck Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -43,7 +46,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include "expat_config.h" #include #include /* memcpy */ @@ -75,7 +78,7 @@ #define VTABLE VTABLE1, PREFIX(toUtf8), PREFIX(toUtf16) #define UCS2_GET_NAMING(pages, hi, lo) \ - (namingBitmap[(pages[hi] << 3) + ((lo) >> 5)] & (1u << ((lo)&0x1F))) + (namingBitmap[(pages[hi] << 3) + ((lo) >> 5)] & (1u << ((lo) & 0x1F))) /* A 2 byte UTF-8 representation splits the characters 11 bits between the bottom 5 and 6 bits of the bytes. We need 8 bits to index into @@ -99,7 +102,7 @@ & (1u << (((byte)[2]) & 0x1F))) /* Detection of invalid UTF-8 sequences is based on Table 3.1B - of Unicode 3.2: http://www.unicode.org/unicode/reports/tr28/ + of Unicode 3.2: https://www.unicode.org/unicode/reports/tr28/ with the additional restriction of not allowing the Unicode code points 0xFFFF and 0xFFFE (sequences EF,BF,BF and EF,BF,BE). Implementation details: @@ -224,7 +227,7 @@ struct normal_encoding { /* isNmstrt2 */ NULL, /* isNmstrt3 */ NULL, /* isNmstrt4 */ NULL, \ /* isInvalid2 */ NULL, /* isInvalid3 */ NULL, /* isInvalid4 */ NULL -static int FASTCALL checkCharRefNumber(int); +static int FASTCALL checkCharRefNumber(int result); #include "xmltok_impl.h" #include "ascii.h" @@ -242,7 +245,7 @@ static int FASTCALL checkCharRefNumber(int); #endif #define SB_BYTE_TYPE(enc, p) \ - (((struct normal_encoding *)(enc))->type[(unsigned char)*(p)]) + (((const struct normal_encoding *)(enc))->type[(unsigned char)*(p)]) #ifdef XML_MIN_SIZE static int PTRFASTCALL @@ -296,7 +299,7 @@ sb_charMatches(const ENCODING *enc, const char *p, int c) { } #else /* c is an ASCII character */ -# define CHAR_MATCHES(enc, p, c) (*(p) == c) +# define CHAR_MATCHES(enc, p, c) (*(p) == (c)) #endif #define PREFIX(ident) normal_##ident @@ -406,7 +409,7 @@ utf8_toUtf16(const ENCODING *enc, const char **fromP, const char *fromLim, unsigned short *to = *toP; const char *from = *fromP; while (from < fromLim && to < toLim) { - switch (((struct normal_encoding *)enc)->type[(unsigned char)*from]) { + switch (SB_BYTE_TYPE(enc, from)) { case BT_LEAD2: if (fromLim - from < 2) { res = XML_CONVERT_INPUT_INCOMPLETE; @@ -714,33 +717,28 @@ unicode_byte_type(char hi, char lo) { return res; \ } -#define SET2(ptr, ch) (((ptr)[0] = ((ch)&0xff)), ((ptr)[1] = ((ch) >> 8))) #define GET_LO(ptr) ((unsigned char)(ptr)[0]) #define GET_HI(ptr) ((unsigned char)(ptr)[1]) DEFINE_UTF16_TO_UTF8(little2_) DEFINE_UTF16_TO_UTF16(little2_) -#undef SET2 #undef GET_LO #undef GET_HI -#define SET2(ptr, ch) (((ptr)[0] = ((ch) >> 8)), ((ptr)[1] = ((ch)&0xFF))) #define GET_LO(ptr) ((unsigned char)(ptr)[1]) #define GET_HI(ptr) ((unsigned char)(ptr)[0]) DEFINE_UTF16_TO_UTF8(big2_) DEFINE_UTF16_TO_UTF16(big2_) -#undef SET2 #undef GET_LO #undef GET_HI #define LITTLE2_BYTE_TYPE(enc, p) \ - ((p)[1] == 0 ? ((struct normal_encoding *)(enc))->type[(unsigned char)*(p)] \ - : unicode_byte_type((p)[1], (p)[0])) + ((p)[1] == 0 ? SB_BYTE_TYPE(enc, p) : unicode_byte_type((p)[1], (p)[0])) #define LITTLE2_BYTE_TO_ASCII(p) ((p)[1] == 0 ? (p)[0] : -1) -#define LITTLE2_CHAR_MATCHES(p, c) ((p)[1] == 0 && (p)[0] == c) +#define LITTLE2_CHAR_MATCHES(p, c) ((p)[1] == 0 && (p)[0] == (c)) #define LITTLE2_IS_NAME_CHAR_MINBPC(p) \ UCS2_GET_NAMING(namePages, (unsigned char)p[1], (unsigned char)p[0]) #define LITTLE2_IS_NMSTRT_CHAR_MINBPC(p) \ @@ -871,11 +869,9 @@ static const struct normal_encoding internal_little2_encoding #endif #define BIG2_BYTE_TYPE(enc, p) \ - ((p)[0] == 0 \ - ? ((struct normal_encoding *)(enc))->type[(unsigned char)(p)[1]] \ - : unicode_byte_type((p)[0], (p)[1])) + ((p)[0] == 0 ? SB_BYTE_TYPE(enc, p + 1) : unicode_byte_type((p)[0], (p)[1])) #define BIG2_BYTE_TO_ASCII(p) ((p)[0] == 0 ? (p)[1] : -1) -#define BIG2_CHAR_MATCHES(p, c) ((p)[0] == 0 && (p)[1] == c) +#define BIG2_CHAR_MATCHES(p, c) ((p)[0] == 0 && (p)[1] == (c)) #define BIG2_IS_NAME_CHAR_MINBPC(p) \ UCS2_GET_NAMING(namePages, (unsigned char)p[0], (unsigned char)p[1]) #define BIG2_IS_NMSTRT_CHAR_MINBPC(p) \ diff --git a/Utilities/cmexpat/lib/xmltok.h b/Utilities/cmexpat/lib/xmltok.h index 6f630c2f9ba..c51fce1ec15 100644 --- a/Utilities/cmexpat/lib/xmltok.h +++ b/Utilities/cmexpat/lib/xmltok.h @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper Copyright (c) 2002 Fred L. Drake, Jr. Copyright (c) 2002-2005 Karl Waclawek - Copyright (c) 2016-2017 Sebastian Pipping + Copyright (c) 2016-2024 Sebastian Pipping Copyright (c) 2017 Rhodri James Licensed under the MIT license: @@ -289,7 +289,8 @@ int XmlParseXmlDecl(int isGeneralTextEntity, const ENCODING *enc, const char **encodingNamePtr, const ENCODING **namedEncodingPtr, int *standalonePtr); -int XmlInitEncoding(INIT_ENCODING *, const ENCODING **, const char *name); +int XmlInitEncoding(INIT_ENCODING *p, const ENCODING **encPtr, + const char *name); const ENCODING *XmlGetUtf8InternalEncoding(void); const ENCODING *XmlGetUtf16InternalEncoding(void); int FASTCALL XmlUtf8Encode(int charNumber, char *buf); @@ -307,7 +308,8 @@ int XmlParseXmlDeclNS(int isGeneralTextEntity, const ENCODING *enc, const char **encodingNamePtr, const ENCODING **namedEncodingPtr, int *standalonePtr); -int XmlInitEncodingNS(INIT_ENCODING *, const ENCODING **, const char *name); +int XmlInitEncodingNS(INIT_ENCODING *p, const ENCODING **encPtr, + const char *name); const ENCODING *XmlGetUtf8InternalEncodingNS(void); const ENCODING *XmlGetUtf16InternalEncodingNS(void); ENCODING *XmlInitUnknownEncodingNS(void *mem, int *table, CONVERTER convert, diff --git a/Utilities/cmexpat/lib/xmltok_impl.c b/Utilities/cmexpat/lib/xmltok_impl.c index 4072b06497d..239a2d06c45 100644 --- a/Utilities/cmexpat/lib/xmltok_impl.c +++ b/Utilities/cmexpat/lib/xmltok_impl.c @@ -16,6 +16,7 @@ Copyright (c) 2018 Anton Maklakov Copyright (c) 2019 David Loffredo Copyright (c) 2020 Boris Kolpackov + Copyright (c) 2022 Martin Ettl Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -96,7 +97,7 @@ # define CHECK_NMSTRT_CASE(n, enc, ptr, end, nextTokPtr) \ case BT_LEAD##n: \ - if (end - ptr < n) \ + if ((end) - (ptr) < (n)) \ return XML_TOK_PARTIAL_CHAR; \ if (IS_INVALID_CHAR(enc, ptr, n) || ! IS_NMSTRT_CHAR(enc, ptr, n)) { \ *nextTokPtr = ptr; \ @@ -124,7 +125,8 @@ # define PREFIX(ident) ident # endif -# define HAS_CHARS(enc, ptr, end, count) (end - ptr >= count * MINBPC(enc)) +# define HAS_CHARS(enc, ptr, end, count) \ + ((end) - (ptr) >= ((count) * MINBPC(enc))) # define HAS_CHAR(enc, ptr, end) HAS_CHARS(enc, ptr, end, 1) diff --git a/Utilities/cmexpat/lib/xmltok_impl.h b/Utilities/cmexpat/lib/xmltok_impl.h index c518aada013..3469c4ae138 100644 --- a/Utilities/cmexpat/lib/xmltok_impl.h +++ b/Utilities/cmexpat/lib/xmltok_impl.h @@ -45,7 +45,7 @@ enum { BT_LF, /* line feed = "\n" */ BT_GT, /* greater than = ">" */ BT_QUOT, /* quotation character = "\"" */ - BT_APOS, /* aposthrophe = "'" */ + BT_APOS, /* apostrophe = "'" */ BT_EQUALS, /* equal sign = "=" */ BT_QUEST, /* question mark = "?" */ BT_EXCL, /* exclamation mark = "!" */ diff --git a/Utilities/cmjsoncpp/include/json/allocator.h b/Utilities/cmjsoncpp/include/json/allocator.h index 3718df1da8c..eda2677016a 100644 --- a/Utilities/cmjsoncpp/include/json/allocator.h +++ b/Utilities/cmjsoncpp/include/json/allocator.h @@ -71,7 +71,9 @@ template class SecureAllocator { // Boilerplate SecureAllocator() {} template SecureAllocator(const SecureAllocator&) {} - template struct rebind { using other = SecureAllocator; }; + template struct rebind { + using other = SecureAllocator; + }; }; template diff --git a/Utilities/cmjsoncpp/include/json/reader.h b/Utilities/cmjsoncpp/include/json/reader.h index 0d444ad2142..a79d0eabf4f 100644 --- a/Utilities/cmjsoncpp/include/json/reader.h +++ b/Utilities/cmjsoncpp/include/json/reader.h @@ -53,12 +53,12 @@ class JSON_API Reader { }; /** \brief Constructs a Reader allowing all features for parsing. - * deprecated Use CharReader and CharReaderBuilder. + * deprecated Use CharReader and CharReaderBuilder. */ Reader(); /** \brief Constructs a Reader allowing the specified feature set for parsing. - * deprecated Use CharReader and CharReaderBuilder. + * deprecated Use CharReader and CharReaderBuilder. */ Reader(const Features& features); @@ -192,6 +192,7 @@ class JSON_API Reader { using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); bool match(const Char* pattern, int patternLength); bool readComment(); @@ -223,7 +224,6 @@ class JSON_API Reader { int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static bool containsNewLine(Location begin, Location end); static String normalizeEOL(Location begin, Location end); @@ -246,6 +246,12 @@ class JSON_API Reader { */ class JSON_API CharReader { public: + struct JSON_API StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + virtual ~CharReader() = default; /** \brief Read a Value from a JSON * document. The document must be a UTF-8 encoded string containing the @@ -264,7 +270,12 @@ class JSON_API CharReader { * error occurred. */ virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) = 0; + String* errs); + + /** \brief Returns a vector of structured errors encountered while parsing. + * Each parse call resets the stored list of errors. + */ + std::vector getStructuredErrors() const; class JSON_API Factory { public: @@ -274,7 +285,21 @@ class JSON_API CharReader { */ virtual CharReader* newCharReader() const = 0; }; // Factory -}; // CharReader + +protected: + class Impl { + public: + virtual ~Impl() = default; + virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) = 0; + virtual std::vector getStructuredErrors() const = 0; + }; + + explicit CharReader(std::unique_ptr impl) : _impl(std::move(impl)) {} + +private: + std::unique_ptr _impl; +}; // CharReader /** \brief Build a CharReader implementation. * @@ -362,6 +387,12 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { * snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode */ static void strictMode(Json::Value* settings); + /** ECMA-404 mode. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderECMA404Mode + */ + static void ecma404Mode(Json::Value* settings); }; /** Consume entire stream and use its begin/end. diff --git a/Utilities/cmjsoncpp/include/json/value.h b/Utilities/cmjsoncpp/include/json/value.h index f6ac9e3d81d..da788699cd4 100644 --- a/Utilities/cmjsoncpp/include/json/value.h +++ b/Utilities/cmjsoncpp/include/json/value.h @@ -3,8 +3,8 @@ // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef JSON_H_INCLUDED -#define JSON_H_INCLUDED +#ifndef JSON_VALUE_H_INCLUDED +#define JSON_VALUE_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) #include "forwards.h" @@ -33,6 +33,10 @@ #if __clang_major__ == 3 && __clang_minor__ <= 8 #define JSONCPP_TEMPLATE_DELETE #endif +#elif defined(__EDG__) && defined(__LCC__) +#if __LCC__ < 123 +#define JSONCPP_TEMPLATE_DELETE +#endif #endif #if !defined(JSONCPP_TEMPLATE_DELETE) #define JSONCPP_TEMPLATE_DELETE = delete @@ -439,7 +443,7 @@ class JSON_API Value { /// \post type() is arrayValue void resize(ArrayIndex newSize); - //@{ + ///@{ /// Access an array element (zero based index). If the array contains less /// than index element, then null value are inserted in the array so that /// its size is index+1. @@ -447,15 +451,15 @@ class JSON_API Value { /// this from the operator[] which takes a string.) Value& operator[](ArrayIndex index); Value& operator[](int index); - //@} + ///@} - //@{ + ///@{ /// Access an array element (zero based index). /// (You may need to say 'value[0u]' to get your compiler to distinguish /// this from the operator[] which takes a string.) const Value& operator[](ArrayIndex index) const; const Value& operator[](int index) const; - //@} + ///@} /// If the array contains at least index+1 elements, returns the element /// value, otherwise returns defaultValue. @@ -515,6 +519,9 @@ class JSON_API Value { /// and operator[]const /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + Value const* find(const String& key) const; /// Most general and efficient version of object-mutators. /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. @@ -587,6 +594,26 @@ class JSON_API Value { iterator begin(); iterator end(); + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + const Value& front() const; + + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& front(); + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that value holds an array or json object, with at least one + /// element. + const Value& back() const; + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& back(); + // Accessors for the [start, limit) range of bytes within the JSON text from // which this value was parsed, if any. void setOffsetStart(ptrdiff_t start); @@ -927,6 +954,14 @@ class JSON_API ValueIterator : public ValueIteratorBase { inline void swap(Value& a, Value& b) { a.swap(b); } +inline const Value& Value::front() const { return *begin(); } + +inline Value& Value::front() { return *begin(); } + +inline const Value& Value::back() const { return *(--end()); } + +inline Value& Value::back() { return *(--end()); } + } // namespace Json #if !defined(__SUNPRO_CC) diff --git a/Utilities/cmjsoncpp/include/json/version.h b/Utilities/cmjsoncpp/include/json/version.h index e931d0383e3..38faedf11d7 100644 --- a/Utilities/cmjsoncpp/include/json/version.h +++ b/Utilities/cmjsoncpp/include/json/version.h @@ -9,19 +9,18 @@ // 3. /CMakeLists.txt // IMPORTANT: also update the SOVERSION!! -#define JSONCPP_VERSION_STRING "1.9.5" +#define JSONCPP_VERSION_STRING "1.9.6" #define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 5 +#define JSONCPP_VERSION_PATCH 6 #define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_HEXA \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ (JSONCPP_VERSION_PATCH << 8)) -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY -#endif +#if !defined(JSONCPP_USE_SECURE_MEMORY) #define JSONCPP_USING_SECURE_MEMORY 0 +#endif // If non-zero, the library zeroes any memory that it has allocated before // it frees its memory. diff --git a/Utilities/cmjsoncpp/include/json/writer.h b/Utilities/cmjsoncpp/include/json/writer.h index 2a47d5e27d9..fac86c74029 100644 --- a/Utilities/cmjsoncpp/include/json/writer.h +++ b/Utilities/cmjsoncpp/include/json/writer.h @@ -66,7 +66,7 @@ class JSON_API StreamWriter { */ virtual StreamWriter* newStreamWriter() const = 0; }; // Factory -}; // StreamWriter +}; // StreamWriter /** \brief Write into stringstream, then return string, for convenience. * A StreamWriter will be created from the factory, used, and then deleted. @@ -170,8 +170,7 @@ class JSON_API Writer { #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSON_API FastWriter - : public Writer { +class JSON_API FastWriter : public Writer { public: FastWriter(); ~FastWriter() override = default; @@ -230,8 +229,7 @@ class JSON_API FastWriter #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSON_API - StyledWriter : public Writer { +class JSON_API StyledWriter : public Writer { public: StyledWriter(); ~StyledWriter() override = default; @@ -299,8 +297,7 @@ class JSON_API #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSON_API - StyledStreamWriter { +class JSON_API StyledStreamWriter { public: /** * \param indentation Each level will be indented by this amount extra. @@ -356,6 +353,7 @@ String JSON_API valueToString( PrecisionType precisionType = PrecisionType::significantDigits); String JSON_API valueToString(bool value); String JSON_API valueToQuotedString(const char* value); +String JSON_API valueToQuotedString(const char* value, size_t length); /// \brief Output using the StyledStreamWriter. /// \see Json::operator>>() diff --git a/Utilities/cmjsoncpp/src/lib_json/json_reader.cpp b/Utilities/cmjsoncpp/src/lib_json/json_reader.cpp index 5cc718de4e7..c504bfb46f4 100644 --- a/Utilities/cmjsoncpp/src/lib_json/json_reader.cpp +++ b/Utilities/cmjsoncpp/src/lib_json/json_reader.cpp @@ -129,7 +129,7 @@ bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool successful = readValue(); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); if (features_.strictRoot_) { @@ -157,7 +157,7 @@ bool Reader::readValue() { throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -225,14 +225,14 @@ bool Reader::readValue() { return successful; } -void Reader::skipCommentTokens(Token& token) { +bool Reader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool Reader::readToken(Token& token) { @@ -446,12 +446,7 @@ bool Reader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object return true; name.clear(); @@ -480,15 +475,11 @@ bool Reader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -518,10 +509,7 @@ bool Reader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -599,8 +587,7 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; - String buffer(token.start_, token.end_); - IStringStream is(buffer); + IStringStream is(String(token.start_, token.end_)); if (!(is >> value)) { if (value == std::numeric_limits::max()) value = std::numeric_limits::infinity(); @@ -608,7 +595,7 @@ bool Reader::decodeDouble(Token& token, Value& decoded) { value = -std::numeric_limits::infinity(); else if (!std::isinf(value)) return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + "'" + String(token.start_, token.end_) + "' is not a number.", token); } decoded = value; return true; @@ -773,7 +760,7 @@ void Reader::getLocationLineAndColumn(Location location, int& line, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -890,17 +877,12 @@ class OurReader { public: using Char = char; using Location = const Char*; - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - String message; - }; explicit OurReader(OurFeatures const& features); bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); String getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; + std::vector getStructuredErrors() const; private: OurReader(OurReader const&); // no impl @@ -943,6 +925,7 @@ class OurReader { using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); void skipBom(bool skipBom); bool match(const Char* pattern, int patternLength); @@ -976,7 +959,6 @@ class OurReader { int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static String normalizeEOL(Location begin, Location end); static bool containsNewLine(Location begin, Location end); @@ -1030,7 +1012,7 @@ bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, bool successful = readValue(); nodes_.pop(); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { addError("Extra non-whitespace after JSON value.", token); return false; @@ -1058,7 +1040,7 @@ bool OurReader::readValue() { if (nodes_.size() > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -1145,14 +1127,14 @@ bool OurReader::readValue() { return successful; } -void OurReader::skipCommentTokens(Token& token) { +bool OurReader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool OurReader::readToken(Token& token) { @@ -1449,12 +1431,7 @@ bool OurReader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma @@ -1491,15 +1468,11 @@ bool OurReader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -1533,10 +1506,7 @@ bool OurReader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -1651,8 +1621,7 @@ bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; - const String buffer(token.start_, token.end_); - IStringStream is(buffer); + IStringStream is(String(token.start_, token.end_)); if (!(is >> value)) { if (value == std::numeric_limits::max()) value = std::numeric_limits::infinity(); @@ -1660,7 +1629,7 @@ bool OurReader::decodeDouble(Token& token, Value& decoded) { value = -std::numeric_limits::infinity(); else if (!std::isinf(value)) return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + "'" + String(token.start_, token.end_) + "' is not a number.", token); } decoded = value; return true; @@ -1826,7 +1795,7 @@ void OurReader::getLocationLineAndColumn(Location location, int& line, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -1861,10 +1830,11 @@ String OurReader::getFormattedErrorMessages() const { return formattedMessage; } -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; +std::vector +OurReader::getStructuredErrors() const { + std::vector allErrors; for (const auto& error : errors_) { - OurReader::StructuredError structured; + CharReader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; structured.message = error.message_; @@ -1874,20 +1844,36 @@ std::vector OurReader::getStructuredErrors() const { } class OurCharReader : public CharReader { - bool const collectComments_; - OurReader reader_; public: OurCharReader(bool collectComments, OurFeatures const& features) - : collectComments_(collectComments), reader_(features) {} - bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) override { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); + : CharReader( + std::unique_ptr(new OurImpl(collectComments, features))) {} + +protected: + class OurImpl : public Impl { + public: + OurImpl(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + + bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) override { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; } - return ok; - } + + std::vector + getStructuredErrors() const override { + return reader_.getStructuredErrors(); + } + + private: + bool const collectComments_; + OurReader reader_; + }; }; CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } @@ -1976,6 +1962,32 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) { (*settings)["skipBom"] = true; //! [CharReaderBuilderDefaults] } +// static +void CharReaderBuilder::ecma404Mode(Json::Value* settings) { + //! [CharReaderBuilderECMA404Mode] + (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = false; + //! [CharReaderBuilderECMA404Mode] +} + +std::vector +CharReader::getStructuredErrors() const { + return _impl->getStructuredErrors(); +} + +bool CharReader::parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) { + return _impl->parse(beginDoc, endDoc, root, errs); +} ////////////////////////////////// // global functions @@ -1984,7 +1996,7 @@ bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, String* errs) { OStringStream ssin; ssin << sin.rdbuf(); - String doc = ssin.str(); + String doc = std::move(ssin).str(); char const* begin = doc.data(); char const* end = begin + doc.size(); // Note that we do not actually need a null-terminator. diff --git a/Utilities/cmjsoncpp/src/lib_json/json_value.cpp b/Utilities/cmjsoncpp/src/lib_json/json_value.cpp index b496ddff8a3..ec7ae704f19 100644 --- a/Utilities/cmjsoncpp/src/lib_json/json_value.cpp +++ b/Utilities/cmjsoncpp/src/lib_json/json_value.cpp @@ -1092,6 +1092,9 @@ Value const* Value::find(char const* begin, char const* end) const { return nullptr; return &(*it).second; } +Value const* Value::find(const String& key) const { + return find(key.data(), key.data() + key.length()); +} Value* Value::demand(char const* begin, char const* end) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::demand(begin, end): requires " @@ -1105,7 +1108,7 @@ const Value& Value::operator[](const char* key) const { return *found; } Value const& Value::operator[](const String& key) const { - Value const* found = find(key.data(), key.data() + key.length()); + Value const* found = find(key); if (!found) return nullSingleton(); return *found; @@ -1205,7 +1208,7 @@ bool Value::removeIndex(ArrayIndex index, Value* removed) { return false; } if (removed) - *removed = it->second; + *removed = std::move(it->second); ArrayIndex oldSize = size(); // shift left all items left, into the place of the "removed" for (ArrayIndex i = index; i < (oldSize - 1); ++i) { @@ -1410,9 +1413,8 @@ void Value::setComment(String comment, CommentPlacement placement) { // Always discard trailing newline, to aid indentation. comment.pop_back(); } - JSON_ASSERT(!comment.empty()); JSON_ASSERT_MESSAGE( - comment[0] == '\0' || comment[0] == '/', + comment.empty() || comment[0] == '/', "in Json::Value::setComment(): Comments must start with /"); comments_.set(placement, std::move(comment)); } diff --git a/Utilities/cmjsoncpp/src/lib_json/json_writer.cpp b/Utilities/cmjsoncpp/src/lib_json/json_writer.cpp index 0dd160e452e..ee45c43ba15 100644 --- a/Utilities/cmjsoncpp/src/lib_json/json_writer.cpp +++ b/Utilities/cmjsoncpp/src/lib_json/json_writer.cpp @@ -132,8 +132,9 @@ String valueToString(double value, bool useSpecialFloats, if (!isfinite(value)) { static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, {"null", "-1e+9999", "1e+9999"}}; - return reps[useSpecialFloats ? 0 : 1] - [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + return reps[useSpecialFloats ? 0 : 1][isnan(value) ? 0 + : (value < 0) ? 1 + : 2]; } String buffer(size_t(36), '\0'); @@ -353,6 +354,10 @@ String valueToQuotedString(const char* value) { return valueToQuotedStringN(value, strlen(value)); } +String valueToQuotedString(const char* value, size_t length) { + return valueToQuotedStringN(value, length); +} + // Class Writer // ////////////////////////////////////////////////////////////////// Writer::~Writer() = default; @@ -490,7 +495,7 @@ void StyledWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); document_ += " : "; writeValue(childValue); if (++it == members.end()) { @@ -708,7 +713,7 @@ void StyledStreamWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); *document_ << " : "; writeValue(childValue); if (++it == members.end()) { @@ -1246,7 +1251,7 @@ String writeString(StreamWriter::Factory const& factory, Value const& root) { OStringStream sout; StreamWriterPtr const writer(factory.newStreamWriter()); writer->write(root, &sout); - return sout.str(); + return std::move(sout).str(); } OStream& operator<<(OStream& sout, Value const& root) { diff --git a/Utilities/cmlibarchive/CMakeLists.txt b/Utilities/cmlibarchive/CMakeLists.txt index 027de5c5893..e6ab5de99bf 100644 --- a/Utilities/cmlibarchive/CMakeLists.txt +++ b/Utilities/cmlibarchive/CMakeLists.txt @@ -1,14 +1,8 @@ # IF(0) # CMake handles policy settings in its own build. -CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12 FATAL_ERROR) -if(POLICY CMP0065) - cmake_policy(SET CMP0065 NEW) #3.4 don't use `-rdynamic` with executables -endif() -if(POLICY CMP0074) - cmake_policy(SET CMP0074 NEW) #3.12.0 `find_package()`` uses ``_ROOT`` variables. -endif() +cmake_minimum_required(VERSION 3.17 FATAL_ERROR) ENDIF() -# + PROJECT(libarchive C) # SET(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake") @@ -23,6 +17,7 @@ IF(0) # CMake handles build type selection in its own build. # Release : Release build # RelWithDebInfo : Release build with Debug Info # MinSizeRel : Release Min Size build +# None : No build type IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build Type" FORCE) ENDIF(NOT CMAKE_BUILD_TYPE) @@ -33,13 +28,15 @@ IF("${cached_type}" STREQUAL "UNINITIALIZED") SET(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Build Type" FORCE) ENDIF("${cached_type}" STREQUAL "UNINITIALIZED") # Check the Build Type. -IF(NOT "${CMAKE_BUILD_TYPE}" - MATCHES "^(Debug|Release|RelWithDebInfo|MinSizeRel)\$") +# Convert the CMAKE_BUILD_TYPE to uppercase to perform a case-insensitive comparison. +string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER) +IF(NOT "${CMAKE_BUILD_TYPE_UPPER}" + MATCHES "^(DEBUG|RELEASE|RELWITHDEBINFO|MINSIZEREL|NONE)\$") MESSAGE(FATAL_ERROR "Unknown keyword for CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}\n" - "Acceptable keywords: Debug,Release,RelWithDebInfo,MinSizeRel") -ENDIF(NOT "${CMAKE_BUILD_TYPE}" - MATCHES "^(Debug|Release|RelWithDebInfo|MinSizeRel)\$") + "Acceptable keywords: Debug, Release, RelWithDebInfo, MinSizeRel, None") +ENDIF(NOT "${CMAKE_BUILD_TYPE_UPPER}" + MATCHES "^(DEBUG|RELEASE|RELWITHDEBINFO|MINSIZEREL|NONE)\$") ENDIF() # On MacOS, prefer MacPorts libraries to system libraries. @@ -69,6 +66,7 @@ SET(VERSION "${_major}.${_trimmed_minor}.${_trimmed_revision} SET(BSDCPIO_VERSION_STRING "${VERSION}") SET(BSDTAR_VERSION_STRING "${VERSION}") SET(BSDCAT_VERSION_STRING "${VERSION}") +SET(BSDUNZIP_VERSION_STRING "${VERSION}") SET(LIBARCHIVE_VERSION_NUMBER "${_version_number}") SET(LIBARCHIVE_VERSION_STRING "${VERSION}") @@ -80,9 +78,21 @@ SET(LIBARCHIVE_VERSION_STRING "${VERSION}") # libarchive 3.1 == interface version 13 math(EXPR INTERFACE_VERSION "13 + ${_minor}") -# Set SOVERSION == Interface version -# ?? Should there be more here ?? -SET(SOVERSION "${INTERFACE_VERSION}") +# Set SOVERSION so it matches libtool's conventions +# libtool accepts a string "current:revision:age"; in libarchive, that's set to +# - current: ${INTERFACE_VERSION} = 13 + ${_minor} +# - revision: ${_revision} +# - age: ${_minor} +# Since libtool computes SOVERSION as "current - age", it's just '13' again +math(EXPR SOVERSION "${INTERFACE_VERSION} - ${_minor}") +set(SOVERSION_FULL "${SOVERSION}.${_trimmed_minor}.${_trimmed_revision}") + +# Override CMake's default shared library versioning scheme, which uses SOVERSION and VERSION, +# to match libtool's conventions (see https://github.com/mesonbuild/meson/issues/1451) +# - compatibility version: current + 1 = ${INTERFACE_VERSION} + 1 +# - current version: ${current + 1}.${revision} +math(EXPR MACHO_COMPATIBILITY_VERSION "${INTERFACE_VERSION} + 1") +set(MACHO_CURRENT_VERSION "${MACHO_COMPATIBILITY_VERSION}.${_revision}") # Enable CMAKE_PUSH_CHECK_STATE() and CMAKE_POP_CHECK_STATE() macros # saving and restoring the state of the variables. @@ -124,7 +134,7 @@ endif () # aggressive about diagnosing build problems; this can get # relaxed somewhat in final shipping versions. IF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR - CMAKE_C_COMPILER_ID MATCHES "^Clang$") + CMAKE_C_COMPILER_ID MATCHES "^Clang$" AND NOT MSVC) SET(CMAKE_REQUIRED_FLAGS "-Wall -Wformat -Wformat-security") ################################################################# # Set compile flags for all build types. @@ -147,8 +157,6 @@ IF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR # either of the following two, yet neither is supported as of 3.0.2 # - check_linker_flag - does not exist # - try_compile - does not support linker flags - # - # The CI fails with this on MacOS IF(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin") # Place the functions and data into separate sections, allowing the linker # to garbage collect the unused ones. @@ -158,9 +166,12 @@ IF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR # Printing the discarded section is "too much", so enable on demand. #SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -Wl,--print-gc-sections") #SET(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -Wl,--print-gc-sections") + ELSE() + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-dead_strip") ENDIF(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin") ENDIF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR - CMAKE_C_COMPILER_ID MATCHES "^Clang$") + CMAKE_C_COMPILER_ID MATCHES "^Clang$" AND NOT MSVC) IF (CMAKE_C_COMPILER_ID MATCHES "^XL$") SET(CMAKE_C_COMPILER "xlc_r") SET(CMAKE_REQUIRED_FLAGS "-qflag=e:e -qformat=sec") @@ -224,6 +235,8 @@ ENDIF() # Enable CTest/CDash support include(CTest) +option(BUILD_SHARED_LIBS "Build shared libraries" ON) + OPTION(ENABLE_MBEDTLS "Enable use of mbed TLS" OFF) OPTION(ENABLE_NETTLE "Enable use of Nettle" OFF) OPTION(ENABLE_OPENSSL "Enable use of OpenSSL" ON) @@ -238,6 +251,7 @@ OPTION(ENABLE_BZip2 "Enable the use of the system BZip2 library if found" ON) OPTION(ENABLE_LIBXML2 "Enable the use of the system libxml2 library if found" ON) OPTION(ENABLE_EXPAT "Enable the use of the system EXPAT library if found" ON) OPTION(ENABLE_PCREPOSIX "Enable the use of the system PCREPOSIX library if found" ON) +OPTION(ENABLE_PCRE2POSIX "Enable the use of the system PCRE2POSIX library if found" ON) OPTION(ENABLE_LIBGCC "Enable the use of the system LibGCC library if found" ON) # CNG is used for encrypt/decrypt Zip archives on Windows. OPTION(ENABLE_CNG "Enable the use of CNG(Crypto Next Generation)" ON) @@ -248,6 +262,13 @@ OPTION(ENABLE_CPIO "Enable cpio building" ON) OPTION(ENABLE_CPIO_SHARED "Enable dynamic build of cpio" FALSE) OPTION(ENABLE_CAT "Enable cat building" ON) OPTION(ENABLE_CAT_SHARED "Enable dynamic build of cat" FALSE) +IF(WIN32 AND NOT CYGWIN) + SET(ENABLE_UNZIP FALSE) + SET(ENABLE_UNZIP_SHARED FALSE) +ELSE() + OPTION(ENABLE_UNZIP "Enable unzip building" ON) + OPTION(ENABLE_UNZIP_SHARED "Enable dynamic build of unzip" FALSE) +ENDIF() OPTION(ENABLE_XATTR "Enable extended attribute support" ON) OPTION(ENABLE_ACL "Enable ACL support" ON) OPTION(ENABLE_ICONV "Enable iconv support" ON) @@ -284,18 +305,6 @@ IF(WIN32) SET(NTDDI_VERSION 0x06000100) SET(_WIN32_WINNT 0x0600) SET(WINVER 0x0600) - ELSEIF(WINDOWS_VERSION STREQUAL "VISTA") - SET(NTDDI_VERSION 0x06000000) - SET(_WIN32_WINNT 0x0600) - SET(WINVER 0x0600) - ELSEIF(WINDOWS_VERSION STREQUAL "WS03") - SET(NTDDI_VERSION 0x05020000) - SET(_WIN32_WINNT 0x0502) - SET(WINVER 0x0502) - ELSEIF(WINDOWS_VERSION STREQUAL "WINXP") - SET(NTDDI_VERSION 0x05010000) - SET(_WIN32_WINNT 0x0501) - SET(WINVER 0x0501) ELSE(WINDOWS_VERSION STREQUAL "WIN10") # Default to Windows Server 2003 API if we don't recognize the specifier SET(NTDDI_VERSION 0x05020000) @@ -324,6 +333,7 @@ ENDIF() IF(MINGW) ADD_DEFINITIONS(-D__USE_MINGW_ANSI_STDIO) + ADD_DEFINITIONS(-D__MINGW_USE_VC2005_COMPAT) ENDIF() # @@ -394,7 +404,11 @@ MACRO (TRY_MACRO_FOR_LIBRARY INCLUDES LIBRARIES IF("${TRY_TYPE}" MATCHES "COMPILES") CHECK_C_SOURCE_COMPILES("${SAMPLE_SOURCE}" ${VAR}) ELSEIF("${TRY_TYPE}" MATCHES "RUNS") - CHECK_C_SOURCE_RUNS("${SAMPLE_SOURCE}" ${VAR}) + IF(CMAKE_CROSSCOMPILING) + MESSAGE(WARNING "Cannot test run \"${VAR}\" when cross-compiling") + ELSE(CMAKE_CROSSCOMPILING) + CHECK_C_SOURCE_RUNS("${SAMPLE_SOURCE}" ${VAR}) + ENDIF(CMAKE_CROSSCOMPILING) ELSE("${TRY_TYPE}" MATCHES "COMPILES") MESSAGE(FATAL_ERROR "UNKNOWN KEYWORD \"${TRY_TYPE}\" FOR TRY_TYPE") ENDIF("${TRY_TYPE}" MATCHES "COMPILES") @@ -533,15 +547,19 @@ IF(LIBLZMA_FOUND) COMPILES "#include \nint main() {return (int)lzma_version_number(); }" "WITHOUT_LZMA_API_STATIC;LZMA_API_STATIC") + CHECK_C_SOURCE_COMPILES( + "#include \n#if LZMA_VERSION < 50020000\n#error unsupported\n#endif\nint main(void){int ignored __attribute__((unused)); ignored = lzma_stream_encoder_mt(0, 0); return 0;}" + HAVE_LZMA_STREAM_ENCODER_MT) IF(NOT WITHOUT_LZMA_API_STATIC AND LZMA_API_STATIC) ADD_DEFINITIONS(-DLZMA_API_STATIC) - ENDIF(NOT WITHOUT_LZMA_API_STATIC AND LZMA_API_STATIC) + ENDIF() ELSE() ADD_DEFINITIONS(-DLZMA_API_STATIC) ENDIF() CMAKE_POP_CHECK_STATE() ELSE(LIBLZMA_FOUND) # LZMA not found and will not be used. + SET(HAVE_LZMA_STREAM_ENCODER_MT 0) ENDIF(LIBLZMA_FOUND) # # Find LZO2 @@ -590,6 +608,7 @@ IF(LIBB2_FOUND) SET(HAVE_BLAKE2_H 1) SET(ARCHIVE_BLAKE2 FALSE) LIST(APPEND ADDITIONAL_LIBS ${LIBB2_LIBRARY}) + INCLUDE_DIRECTORIES(${LIBB2_INCLUDE_DIR}) CMAKE_PUSH_CHECK_STATE() SET(CMAKE_REQUIRED_LIBRARIES ${LIBB2_LIBRARY}) SET(CMAKE_REQUIRED_INCLUDES ${LIBB2_INCLUDE_DIR}) @@ -655,13 +674,13 @@ IF(ZSTD_FOUND) INCLUDE_DIRECTORIES(${ZSTD_INCLUDE_DIR}) LIST(APPEND ADDITIONAL_LIBS ${ZSTD_LIBRARY}) SET(HAVE_LIBZSTD 1) - SET(HAVE_LIBZSTD_COMPRESSOR 1) + SET(HAVE_ZSTD_compressStream 1) IF(0) # CMake expects the zstd library to work. CMAKE_PUSH_CHECK_STATE() SET(CMAKE_REQUIRED_LIBRARIES ${ZSTD_LIBRARY}) SET(CMAKE_REQUIRED_INCLUDES ${ZSTD_INCLUDE_DIR}) CHECK_FUNCTION_EXISTS(ZSTD_decompressStream HAVE_LIBZSTD) - CHECK_FUNCTION_EXISTS(ZSTD_compressStream HAVE_LIBZSTD_COMPRESSOR) + CHECK_FUNCTION_EXISTS(ZSTD_compressStream HAVE_ZSTD_compressStream) # # TODO: test for static library. # @@ -708,6 +727,7 @@ CHECK_C_SOURCE_COMPILES("#include int main(void) { return EXT2_IOC_GETFLAGS; }" HAVE_WORKING_EXT2_IOC_GETFLAGS) LA_CHECK_INCLUDE_FILE("fcntl.h" HAVE_FCNTL_H) +LA_CHECK_INCLUDE_FILE("fnmatch.h" HAVE_FNMATCH_H) LA_CHECK_INCLUDE_FILE("grp.h" HAVE_GRP_H) LA_CHECK_INCLUDE_FILE("io.h" HAVE_IO_H) LA_CHECK_INCLUDE_FILE("langinfo.h" HAVE_LANGINFO_H) @@ -764,9 +784,9 @@ LA_CHECK_INCLUDE_FILE("wchar.h" HAVE_WCHAR_H) LA_CHECK_INCLUDE_FILE("wctype.h" HAVE_WCTYPE_H) LA_CHECK_INCLUDE_FILE("windows.h" HAVE_WINDOWS_H) IF(ENABLE_CNG) - LA_CHECK_INCLUDE_FILE("Bcrypt.h" HAVE_BCRYPT_H) + LA_CHECK_INCLUDE_FILE("bcrypt.h" HAVE_BCRYPT_H) IF(HAVE_BCRYPT_H) - LIST(APPEND ADDITIONAL_LIBS "Bcrypt") + LIST(APPEND ADDITIONAL_LIBS "bcrypt") ENDIF(HAVE_BCRYPT_H) ELSE(ENABLE_CNG) UNSET(HAVE_BCRYPT_H CACHE) @@ -842,6 +862,11 @@ IF(ENABLE_OPENSSL AND NOT CMAKE_SYSTEM_NAME MATCHES "Darwin") IF(OPENSSL_FOUND) SET(HAVE_LIBCRYPTO 1) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + list(APPEND ADDITIONAL_LIBS OpenSSL::Crypto) + set(CMAKE_REQUIRED_LIBRARIES OpenSSL::Crypto) + SET(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + LA_CHECK_INCLUDE_FILE("openssl/evp.h" HAVE_OPENSSL_EVP_H) + CHECK_FUNCTION_EXISTS(PKCS5_PBKDF2_HMAC_SHA1 HAVE_PKCS5_PBKDF2_HMAC_SHA1) ENDIF(OPENSSL_FOUND) ELSE() SET(OPENSSL_FOUND FALSE) # Override cached value @@ -859,6 +884,14 @@ IF(NOT OPENSSL_FOUND) ENDIF(LIBMD_FOUND) ENDIF(NOT OPENSSL_FOUND) +# libbsd for readpassphrase on Haiku +IF("${CMAKE_SYSTEM_NAME}" MATCHES "Haiku") + MESSAGE(STATUS "Adding libbsd for Haiku") + SET(CMAKE_REQUIRED_LIBRARIES "bsd") + FIND_LIBRARY(LIBBSD_LIBRARY NAMES bsd) + LIST(APPEND ADDITIONAL_LIBS ${LIBBSD_LIBRARY}) +ENDIF("${CMAKE_SYSTEM_NAME}" MATCHES "Haiku") + # # How to prove that CRYPTO functions, which have several names on various # platforms, just see if archive_digest.c can compile and link against @@ -960,9 +993,8 @@ main(int argc, char **argv) # was found on this platform. IF (ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) IF ("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND OPENSSL_FOUND) - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) - LIST(APPEND ADDITIONAL_LIBS ${OPENSSL_LIBRARIES}) - LIST(REMOVE_DUPLICATES ADDITIONAL_LIBS) + LIST(APPEND ADDITIONAL_LIBS OpenSSL::Crypto) + LIST(REMOVE_DUPLICATES ADDITIONAL_LIBS) ENDIF ("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND OPENSSL_FOUND) ENDIF (ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) ENDIF(NOT ARCHIVE_CRYPTO_${ALGORITHM}) @@ -1194,7 +1226,7 @@ ENDIF(ENABLE_ICONV) # # Find Libxml2 # -IF(ENABLE_LIBXML2) +IF(ENABLE_LIBXML2 AND HAVE_ICONV) FIND_PACKAGE(LibXml2) ELSE() SET(LIBXML2_FOUND FALSE) @@ -1353,6 +1385,68 @@ IF(NOT FOUND_POSIX_REGEX_LIB AND POSIX_REGEX_LIB MATCHES "^(AUTO|LIBPCREPOSIX)$" MARK_AS_ADVANCED(CLEAR LIBGCC_LIBRARIES) ENDIF(NOT FOUND_POSIX_REGEX_LIB AND POSIX_REGEX_LIB MATCHES "^(AUTO|LIBPCREPOSIX)$") +IF(NOT FOUND_POSIX_REGEX_LIB AND POSIX_REGEX_LIB MATCHES "^(AUTO|LIBPCRE2POSIX)$") + # + # If requested, try finding library for PCRE2POSIX + # + IF(ENABLE_LIBGCC) + FIND_PACKAGE(LIBGCC) + ELSE() + MESSAGE(FATAL_ERROR "libgcc not found.") + SET(LIBGCC_FOUND FALSE) # Override cached value + ENDIF() + IF(ENABLE_PCRE2POSIX) + FIND_PACKAGE(PCRE2POSIX) + ELSE() + SET(PCRE2POSIX_FOUND FALSE) # Override cached value + ENDIF() + IF(PCRE2POSIX_FOUND) + INCLUDE_DIRECTORIES(${PCRE2_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${PCRE2POSIX_LIBRARIES}) + # Test if a macro is needed for the library. + TRY_MACRO_FOR_LIBRARY( + "${PCRE2_INCLUDE_DIR}" "${PCRE2POSIX_LIBRARIES}" + COMPILES + "#include \nint main() {regex_t r;return pcre2_regcomp(&r, \"\", 0);}" + "WITHOUT_PCRE2_STATIC;PCRE2_STATIC") + IF(NOT WITHOUT_PCRE2_STATIC AND PCRE2_STATIC) + ADD_DEFINITIONS(-DPCRE2_STATIC) + ELSEIF(NOT WITHOUT_PCRE2_STATIC AND NOT PCRE2_STATIC AND PCRE2_FOUND) + # Determine if pcre2 static libraries are to be used. + LIST(APPEND ADDITIONAL_LIBS ${PCRE2_LIBRARIES}) + SET(TMP_LIBRARIES ${PCRE2POSIX_LIBRARIES} ${PCRE2_LIBRARIES}) + MESSAGE(STATUS "trying again with -lpcre2-8 included") + TRY_MACRO_FOR_LIBRARY( + "${PCRE2_INCLUDE_DIR}" "${TMP_LIBRARIES}" + COMPILES + "#include \nint main() {regex_t r;return pcre2_regcomp(&r, \"\", 0);}" + "WITHOUT_PCRE2_STATIC;PCRE2_STATIC") + IF(NOT WITHOUT_PCRE2_STATIC AND PCRE2_STATIC) + ADD_DEFINITIONS(-DPCRE2_STATIC) + ELSEIF(NOT WITHOUT_PCRE2_STATIC AND NOT PCRE2_STATIC AND MSVC AND LIBGCC_FOUND) + # When doing a Visual Studio build using pcre2 static libraries + # built using the mingw toolchain, -lgcc is needed to resolve + # ___chkstk_ms. + MESSAGE(STATUS "Visual Studio build detected, trying again with -lgcc included") + LIST(APPEND ADDITIONAL_LIBS ${LIBGCC_LIBRARIES}) + SET(TMP_LIBRARIES ${PCRE2POSIX_LIBRARIES} ${PCRE2_LIBRARIES} ${LIBGCC_LIBRARIES}) + TRY_MACRO_FOR_LIBRARY( + "${PCRE2_INCLUDE_DIR}" "${TMP_LIBRARIES}" + COMPILES + "#include \nint main() {regex_t r;return pcre2_regcomp(&r, \"\", 0);}" + "WITHOUT_PCRE2_STATIC;PCRE2_STATIC") + IF(NOT WITHOUT_PCRE2_STATIC AND PCRE2_STATIC) + ADD_DEFINITIONS(-DPCRE2_STATIC) + ENDIF(NOT WITHOUT_PCRE2_STATIC AND PCRE2_STATIC) + ENDIF(NOT WITHOUT_PCRE2_STATIC AND PCRE2_STATIC) + ENDIF(NOT WITHOUT_PCRE2_STATIC AND PCRE2_STATIC) + ENDIF(PCRE2POSIX_FOUND) + MARK_AS_ADVANCED(CLEAR PCRE2_INCLUDE_DIR) + MARK_AS_ADVANCED(CLEAR PCRE2POSIX_LIBRARIES) + MARK_AS_ADVANCED(CLEAR PCRE2_LIBRARIES) + MARK_AS_ADVANCED(CLEAR LIBGCC_LIBRARIES) +ENDIF(NOT FOUND_POSIX_REGEX_LIB AND POSIX_REGEX_LIB MATCHES "^(AUTO|LIBPCRE2POSIX)$") + # # Check functions # @@ -1379,6 +1473,7 @@ CHECK_FUNCTION_EXISTS_GLIBC(fchmod HAVE_FCHMOD) CHECK_FUNCTION_EXISTS_GLIBC(fchown HAVE_FCHOWN) CHECK_FUNCTION_EXISTS_GLIBC(fcntl HAVE_FCNTL) CHECK_FUNCTION_EXISTS_GLIBC(fdopendir HAVE_FDOPENDIR) +CHECK_FUNCTION_EXISTS_GLIBC(fnmatch HAVE_FNMATCH) CHECK_FUNCTION_EXISTS_GLIBC(fork HAVE_FORK) CHECK_FUNCTION_EXISTS_GLIBC(fstat HAVE_FSTAT) CHECK_FUNCTION_EXISTS_GLIBC(fstatat HAVE_FSTATAT) @@ -1391,13 +1486,16 @@ CHECK_FUNCTION_EXISTS_GLIBC(futimesat HAVE_FUTIMESAT) CHECK_FUNCTION_EXISTS_GLIBC(geteuid HAVE_GETEUID) CHECK_FUNCTION_EXISTS_GLIBC(getgrgid_r HAVE_GETGRGID_R) CHECK_FUNCTION_EXISTS_GLIBC(getgrnam_r HAVE_GETGRNAM_R) +CHECK_FUNCTION_EXISTS_GLIBC(getline HAVE_GETLINE) CHECK_FUNCTION_EXISTS_GLIBC(getpwnam_r HAVE_GETPWNAM_R) CHECK_FUNCTION_EXISTS_GLIBC(getpwuid_r HAVE_GETPWUID_R) CHECK_FUNCTION_EXISTS_GLIBC(getpid HAVE_GETPID) CHECK_FUNCTION_EXISTS_GLIBC(getvfsbyname HAVE_GETVFSBYNAME) CHECK_FUNCTION_EXISTS_GLIBC(gmtime_r HAVE_GMTIME_R) CHECK_FUNCTION_EXISTS_GLIBC(lchflags HAVE_LCHFLAGS) -CHECK_FUNCTION_EXISTS_GLIBC(lchmod HAVE_LCHMOD) +if(NOT CMAKE_C_COMPILER_ID STREQUAL "LCC" OR NOT CMAKE_C_COMPILER_VERSION VERSION_LESS_EQUAL "1.23") + CHECK_FUNCTION_EXISTS_GLIBC(lchmod HAVE_LCHMOD) +endif() CHECK_FUNCTION_EXISTS_GLIBC(lchown HAVE_LCHOWN) CHECK_FUNCTION_EXISTS_GLIBC(link HAVE_LINK) CHECK_FUNCTION_EXISTS_GLIBC(linkat HAVE_LINKAT) @@ -1430,6 +1528,9 @@ CHECK_FUNCTION_EXISTS_GLIBC(strncpy_s HAVE_STRNCPY_S) CHECK_FUNCTION_EXISTS_GLIBC(strnlen HAVE_STRNLEN) CHECK_FUNCTION_EXISTS_GLIBC(strrchr HAVE_STRRCHR) CHECK_FUNCTION_EXISTS_GLIBC(symlink HAVE_SYMLINK) +CHECK_FUNCTION_EXISTS_GLIBC(sysconf HAVE_SYSCONF) +CHECK_FUNCTION_EXISTS_GLIBC(tcgetattr HAVE_TCGETATTR) +CHECK_FUNCTION_EXISTS_GLIBC(tcsetattr HAVE_TCSETATTR) CHECK_FUNCTION_EXISTS_GLIBC(timegm HAVE_TIMEGM) CHECK_FUNCTION_EXISTS_GLIBC(tzset HAVE_TZSET) CHECK_FUNCTION_EXISTS_GLIBC(unlinkat HAVE_UNLINKAT) @@ -1439,16 +1540,16 @@ CHECK_FUNCTION_EXISTS_GLIBC(utimes HAVE_UTIMES) CHECK_FUNCTION_EXISTS_GLIBC(utimensat HAVE_UTIMENSAT) CHECK_FUNCTION_EXISTS_GLIBC(vfork HAVE_VFORK) CHECK_FUNCTION_EXISTS_GLIBC(wcrtomb HAVE_WCRTOMB) -CHECK_FUNCTION_EXISTS_GLIBC(wcscmp HAVE_WCSCMP) -CHECK_FUNCTION_EXISTS_GLIBC(wcscpy HAVE_WCSCPY) -CHECK_FUNCTION_EXISTS_GLIBC(wcslen HAVE_WCSLEN) +check_symbol_exists(wcscmp wchar.h HAVE_WCSCMP) +check_symbol_exists(wcscpy wchar.h HAVE_WCSCPY) +check_symbol_exists(wcslen wchar.h HAVE_WCSLEN) CHECK_FUNCTION_EXISTS_GLIBC(wctomb HAVE_WCTOMB) -CHECK_FUNCTION_EXISTS_GLIBC(_ctime64_s HAVE__CTIME64_S) CHECK_FUNCTION_EXISTS_GLIBC(_fseeki64 HAVE__FSEEKI64) CHECK_FUNCTION_EXISTS_GLIBC(_get_timezone HAVE__GET_TIMEZONE) -CHECK_FUNCTION_EXISTS_GLIBC(_gmtime64_s HAVE__GMTIME64_S) -CHECK_FUNCTION_EXISTS_GLIBC(_localtime64_s HAVE__LOCALTIME64_S) -CHECK_FUNCTION_EXISTS_GLIBC(_mkgmtime64 HAVE__MKGMTIME64) +CHECK_SYMBOL_EXISTS(ctime_s "time.h" HAVE_CTIME_S) +CHECK_SYMBOL_EXISTS(gmtime_s "time.h" HAVE_GMTIME_S) +CHECK_SYMBOL_EXISTS(localtime_s "time.h" HAVE_LOCALTIME_S) +CHECK_SYMBOL_EXISTS(_mkgmtime "time.h" HAVE__MKGMTIME) SET(CMAKE_REQUIRED_LIBRARIES "") CHECK_FUNCTION_EXISTS(cygwin_conv_path HAVE_CYGWIN_CONV_PATH) @@ -1491,7 +1592,6 @@ CHECK_C_SOURCE_COMPILES( "#include \n#include \nint main() {char buf[10]; return readlinkat(AT_FDCWD, \"\", buf, 0);}" HAVE_READLINKAT) - # To verify major(), we need to both include the header # of interest and verify that the result can be linked. # CHECK_FUNCTION_EXISTS doesn't accept a header argument, @@ -1503,20 +1603,6 @@ CHECK_C_SOURCE_COMPILES( "#include \nint main() { return major(256); }" MAJOR_IN_SYSMACROS) -IF(ENABLE_LZMA) -CMAKE_PUSH_CHECK_STATE() -SET(CMAKE_REQUIRED_LIBRARIES ${LIBLZMA_LIBRARIES}) -SET(CMAKE_REQUIRED_INCLUDES ${LIBLZMA_INCLUDE_DIR}) - -CHECK_C_SOURCE_COMPILES( - "#include \n#if LZMA_VERSION < 50020000\n#error unsupported\n#endif\nint main(void){lzma_stream_encoder_mt(0, 0); return 0;}" - HAVE_LZMA_STREAM_ENCODER_MT) - -CMAKE_POP_CHECK_STATE() -ELSE() - SET(HAVE_LZMA_STREAM_ENCODER_MT 0) -ENDIF(ENABLE_LZMA) - IF(HAVE_STRERROR_R) SET(HAVE_DECL_STRERROR_R 1) ENDIF(HAVE_STRERROR_R) @@ -1578,7 +1664,7 @@ ENDIF() # # CHECK_STRUCT_HAS_MEMBER("struct tm" tm_sec - "sys/types.h;sys/time.h;time.h" TIME_WITH_SYS_TIME) + "sys/types.h;sys/time.h;time.h" HAVE_SYS_TIME_H) CHECK_TYPE_SIZE(dev_t DEV_T) IF(NOT HAVE_DEV_T) @@ -2057,10 +2143,10 @@ IF(MSVC) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE) ENDIF(MSVC) -IF(APPLE) - # CC_MD5_Init() functions are deprecated on macOS 10.15, but we want to use them - ADD_DEFINITIONS(-Wno-deprecated-declarations) -ENDIF(APPLE) +OPTION(DONT_FAIL_ON_CRC_ERROR "Ignore CRC errors during parsing (For fuzzing)" OFF) +IF(DONT_FAIL_ON_CRC_ERROR) + ADD_DEFINITIONS(-DDONT_FAIL_ON_CRC_ERROR=1) +ENDIF(DONT_FAIL_ON_CRC_ERROR) IF(ENABLE_TEST) ADD_CUSTOM_TARGET(run_all_tests) @@ -2076,6 +2162,7 @@ IF(0) # CMake does not build libarchive's command-line tools. add_subdirectory(cat) add_subdirectory(tar) add_subdirectory(cpio) +add_subdirectory(unzip) ENDIF() install(FILES COPYING DESTINATION ${CMAKE_DOC_DIR}/cmlibarchive) diff --git a/Utilities/cmlibarchive/build/cmake/FindMbedTLS.cmake b/Utilities/cmlibarchive/build/cmake/FindMbedTLS.cmake index a9163958921..aa40485c3ec 100644 --- a/Utilities/cmlibarchive/build/cmake/FindMbedTLS.cmake +++ b/Utilities/cmlibarchive/build/cmake/FindMbedTLS.cmake @@ -7,7 +7,7 @@ find_library(MBEDCRYPTO_LIBRARY mbedcrypto) set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MBEDTLS DEFAULT_MSG +find_package_handle_standard_args(MbedTLS DEFAULT_MSG MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) diff --git a/Utilities/cmlibarchive/build/cmake/FindPCRE2POSIX.cmake b/Utilities/cmlibarchive/build/cmake/FindPCRE2POSIX.cmake new file mode 100644 index 00000000000..76bb7a455f3 --- /dev/null +++ b/Utilities/cmlibarchive/build/cmake/FindPCRE2POSIX.cmake @@ -0,0 +1,34 @@ +# - Find pcre2posix +# Find the native PCRE2-8 and PCRE2-POSIX include and libraries +# +# PCRE2_INCLUDE_DIR - where to find pcre2posix.h, etc. +# PCRE2POSIX_LIBRARIES - List of libraries when using libpcre2-posix. +# PCRE2_LIBRARIES - List of libraries when using libpcre2-8. +# PCRE2POSIX_FOUND - True if libpcre2-posix found. +# PCRE2_FOUND - True if libpcre2-8 found. + +IF (PCRE2_INCLUDE_DIR) + # Already in cache, be silent + SET(PCRE2_FIND_QUIETLY TRUE) +ENDIF (PCRE2_INCLUDE_DIR) + +FIND_PATH(PCRE2_INCLUDE_DIR pcre2posix.h) +FIND_LIBRARY(PCRE2POSIX_LIBRARY NAMES pcre2-posix libpcre2-posix pcre2-posix-static) +FIND_LIBRARY(PCRE2_LIBRARY NAMES pcre2-8 libpcre2-8 pcre2-8-static) + +# handle the QUIETLY and REQUIRED arguments and set PCRE2POSIX_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE2POSIX DEFAULT_MSG PCRE2POSIX_LIBRARY PCRE2_INCLUDE_DIR) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE2 DEFAULT_MSG PCRE2_LIBRARY) + +IF(PCRE2POSIX_FOUND) + SET(PCRE2POSIX_LIBRARIES ${PCRE2POSIX_LIBRARY}) + SET(HAVE_LIBPCRE2POSIX 1) + SET(HAVE_PCRE2POSIX_H 1) +ENDIF(PCRE2POSIX_FOUND) + +IF(PCRE2_FOUND) + SET(PCRE2_LIBRARIES ${PCRE2_LIBRARY}) + SET(HAVE_LIBPCRE2 1) +ENDIF(PCRE2_FOUND) diff --git a/Utilities/cmlibarchive/build/cmake/LibarchiveCodeCoverage.cmake b/Utilities/cmlibarchive/build/cmake/LibarchiveCodeCoverage.cmake index 297b886ccf0..efcf4ad4d24 100644 --- a/Utilities/cmlibarchive/build/cmake/LibarchiveCodeCoverage.cmake +++ b/Utilities/cmlibarchive/build/cmake/LibarchiveCodeCoverage.cmake @@ -19,13 +19,13 @@ # xdg-open coverage/index.html ################################################################# -# Find programs we need +# Find programs we need FIND_PROGRAM(LCOV_EXECUTABLE lcov DOC "Full path to lcov executable") FIND_PROGRAM(GENHTML_EXECUTABLE genhtml DOC "Full path to genhtml executable") MARK_AS_ADVANCED(LCOV_EXECUTABLE GENHTML_EXECUTABLE) # Check, compiler, build types and programs are available -IF(NOT CMAKE_COMPILER_IS_GNUCC) +IF(NOT CMAKE_C_COMPILER_ID STREQUAL "GNU") MESSAGE(FATAL_ERROR "Coverage can only be built on GCC") ELSEIF(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") MESSAGE(FATAL_ERROR "Coverage can only be built in Debug mode") @@ -33,7 +33,7 @@ ELSEIF(NOT LCOV_EXECUTABLE) MESSAGE(FATAL_ERROR "lcov executable not found") ELSEIF(NOT GENHTML_EXECUTABLE) MESSAGE(FATAL_ERROR "genhtml executable not found") -ENDIF(NOT CMAKE_COMPILER_IS_GNUCC) +ENDIF() # Enable testing if not already done SET(ENABLE_TEST ON) diff --git a/Utilities/cmlibarchive/build/cmake/config.h.in b/Utilities/cmlibarchive/build/cmake/config.h.in index e44a5140803..faef2de758f 100644 --- a/Utilities/cmlibarchive/build/cmake/config.h.in +++ b/Utilities/cmlibarchive/build/cmake/config.h.in @@ -38,6 +38,9 @@ /* MD5 via ARCHIVE_CRYPTO_MD5_LIBSYSTEM supported. */ #cmakedefine ARCHIVE_CRYPTO_MD5_LIBSYSTEM 1 +/* MD5 via ARCHIVE_CRYPTO_MD5_MBEDTLS supported. */ +#cmakedefine ARCHIVE_CRYPTO_MD5_MBEDTLS 1 + /* MD5 via ARCHIVE_CRYPTO_MD5_NETTLE supported. */ #cmakedefine ARCHIVE_CRYPTO_MD5_NETTLE 1 @@ -53,6 +56,9 @@ /* RMD160 via ARCHIVE_CRYPTO_RMD160_NETTLE supported. */ #cmakedefine ARCHIVE_CRYPTO_RMD160_NETTLE 1 +/* RMD160 via ARCHIVE_CRYPTO_RMD160_MBEDTLS supported. */ +#cmakedefine ARCHIVE_CRYPTO_RMD160_MBEDTLS 1 + /* RMD160 via ARCHIVE_CRYPTO_RMD160_OPENSSL supported. */ #cmakedefine ARCHIVE_CRYPTO_RMD160_OPENSSL 1 @@ -62,6 +68,9 @@ /* SHA1 via ARCHIVE_CRYPTO_SHA1_LIBSYSTEM supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA1_LIBSYSTEM 1 +/* SHA1 via ARCHIVE_CRYPTO_SHA1_MBEDTLS supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA1_MBEDTLS 1 + /* SHA1 via ARCHIVE_CRYPTO_SHA1_NETTLE supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA1_NETTLE 1 @@ -83,6 +92,9 @@ /* SHA256 via ARCHIVE_CRYPTO_SHA256_LIBSYSTEM supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA256_LIBSYSTEM 1 +/* SHA256 via ARCHIVE_CRYPTO_SHA256_MBEDTLS supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_MBEDTLS 1 + /* SHA256 via ARCHIVE_CRYPTO_SHA256_NETTLE supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA256_NETTLE 1 @@ -104,6 +116,9 @@ /* SHA384 via ARCHIVE_CRYPTO_SHA384_LIBSYSTEM supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA384_LIBSYSTEM 1 +/* SHA384 via ARCHIVE_CRYPTO_SHA384_MBEDTLS supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_MBEDTLS 1 + /* SHA384 via ARCHIVE_CRYPTO_SHA384_NETTLE supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA384_NETTLE 1 @@ -125,6 +140,9 @@ /* SHA512 via ARCHIVE_CRYPTO_SHA512_LIBSYSTEM supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA512_LIBSYSTEM 1 +/* SHA512 via ARCHIVE_CRYPTO_SHA512_MBEDTLS supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_MBEDTLS 1 + /* SHA512 via ARCHIVE_CRYPTO_SHA512_NETTLE supported. */ #cmakedefine ARCHIVE_CRYPTO_SHA512_NETTLE 1 @@ -155,6 +173,9 @@ /* Version number of bsdcat */ #cmakedefine BSDCAT_VERSION_STRING "@BSDCAT_VERSION_STRING@" +/* Version number of bsdunzip */ +#cmakedefine BSDUNZIP_VERSION_STRING "@BSDUNZIP_VERSION_STRING@" + /* Define to 1 if you have the `acl_create_entry' function. */ #cmakedefine HAVE_ACL_CREATE_ENTRY 1 @@ -197,7 +218,7 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ATTR_XATTR_H 1 -/* Define to 1 if you have the header file. */ +/* Define to 1 if you have the header file. */ #cmakedefine HAVE_BCRYPT_H 1 /* Define to 1 if you have the header file. */ @@ -357,6 +378,12 @@ /* Define to 1 if you have the `flistxattr' function. */ #cmakedefine HAVE_FLISTXATTR 1 +/* Define to 1 if you have the `fnmatch' function. */ +#cmakedefine HAVE_FNMATCH 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_FNMATCH_H 1 + /* Define to 1 if you have the `fork' function. */ #cmakedefine HAVE_FORK 1 @@ -405,6 +432,9 @@ /* Define to 1 if you have the `getgrnam_r' function. */ #cmakedefine HAVE_GETGRNAM_R 1 +/* Define to 1 if you have the `getline' function. */ +#cmakedefine HAVE_GETLINE 1 + /* Define to 1 if you have the `getpid' function. */ #cmakedefine HAVE_GETPID 1 @@ -489,9 +519,6 @@ /* Define to 1 if you have the `lzma' library (-llzma). */ #cmakedefine HAVE_LIBLZMA 1 -/* Define to 1 if you have the `lzmadec' library (-llzmadec). */ -#cmakedefine HAVE_LIBLZMADEC 1 - /* Define to 1 if you have the `lzo2' library (-llzo2). */ #cmakedefine HAVE_LIBLZO2 1 @@ -507,6 +534,12 @@ /* Define to 1 if you have the `pcreposix' library (-lpcreposix). */ #cmakedefine HAVE_LIBPCREPOSIX 1 +/* Define to 1 if you have the `pcre2-8' library (-lpcre2-8). */ +#cmakedefine HAVE_LIBPCRE2 1 + +/* Define to 1 if you have the `pcreposix' library (-lpcre2posix). */ +#cmakedefine HAVE_LIBPCRE2POSIX 1 + /* Define to 1 if you have the `xml2' library (-lxml2). */ #cmakedefine HAVE_LIBXML2 1 @@ -522,9 +555,8 @@ /* Define to 1 if you have the `zstd' library (-lzstd). */ #cmakedefine HAVE_LIBZSTD 1 -/* Define to 1 if you have the `zstd' library (-lzstd) with compression - support. */ -#cmakedefine HAVE_LIBZSTD_COMPRESSOR 1 +/* Define to 1 if you have the ZSTD_compressStream function. */ +#cmakedefine HAVE_ZSTD_compressStream 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LIMITS_H 1 @@ -596,9 +628,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LZ4_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_LZMADEC_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LZMA_H 1 @@ -611,6 +640,15 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LZO_LZOCONF_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_MBEDTLS_AES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_MBEDTLS_MD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_MBEDTLS_PKCS5_H 1 + /* Define to 1 if you have the `mbrtowc' function. */ #cmakedefine HAVE_MBRTOWC 1 @@ -662,12 +700,18 @@ /* Define to 1 if you have the `openat' function. */ #cmakedefine HAVE_OPENAT 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_EVP_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PATHS_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PCREPOSIX_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PCRE2POSIX_H 1 + /* Define to 1 if you have the `pipe' function. */ #cmakedefine HAVE_PIPE 1 @@ -771,6 +815,12 @@ /* Define to 1 if you have the `strrchr' function. */ #cmakedefine HAVE_STRRCHR 1 +/* Define to 1 if the system has the type `struct statfs'. */ +#cmakedefine HAVE_STRUCT_STATFS 1 + +/* Define to 1 if `f_iosize' is a member of `struct statfs'. */ +#cmakedefine HAVE_STRUCT_STATFS_F_IOSIZE 1 + /* Define to 1 if `f_namemax' is a member of `struct statfs'. */ #cmakedefine HAVE_STRUCT_STATFS_F_NAMEMAX 1 @@ -819,6 +869,9 @@ /* Define to 1 if you have the `symlink' function. */ #cmakedefine HAVE_SYMLINK 1 +/* Define to 1 if you have the `sysconf' function. */ +#cmakedefine HAVE_SYSCONF 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_ACL_H 1 @@ -894,6 +947,12 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_XATTR_H 1 +/* Define to 1 if you have the `tcgetattr' function. */ +#cmakedefine HAVE_TCGETATTR 1 + +/* Define to 1 if you have the `tcsetattr' function. */ +#cmakedefine HAVE_TCSETATTR 1 + /* Define to 1 if you have the `timegm' function. */ #cmakedefine HAVE_TIMEGM 1 @@ -993,8 +1052,8 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ZSTD_H 1 -/* Define to 1 if you have the `_ctime64_s' function. */ -#cmakedefine HAVE__CTIME64_S 1 +/* Define to 1 if you have the `ctime_s' function. */ +#cmakedefine HAVE_CTIME_S 1 /* Define to 1 if you have the `_fseeki64' function. */ #cmakedefine HAVE__FSEEKI64 1 @@ -1002,14 +1061,14 @@ /* Define to 1 if you have the `_get_timezone' function. */ #cmakedefine HAVE__GET_TIMEZONE 1 -/* Define to 1 if you have the `_gmtime64_s' function. */ -#cmakedefine HAVE__GMTIME64_S 1 +/* Define to 1 if you have the `gmtime_s' function. */ +#cmakedefine HAVE_GMTIME_S 1 -/* Define to 1 if you have the `_localtime64_s' function. */ -#cmakedefine HAVE__LOCALTIME64_S 1 +/* Define to 1 if you have the `localtime_s' function. */ +#cmakedefine HAVE_LOCALTIME_S 1 -/* Define to 1 if you have the `_mkgmtime64' function. */ -#cmakedefine HAVE__MKGMTIME64 1 +/* Define to 1 if you have the `_mkgmtime' function. */ +#cmakedefine HAVE__MKGMTIME 1 /* Define as const if the declaration of iconv() needs const. */ #define ICONV_CONST @ICONV_CONST@ diff --git a/Utilities/cmlibarchive/build/utils/gen_archive_string_composition_h.sh b/Utilities/cmlibarchive/build/utils/gen_archive_string_composition_h.sh index 558e9c0c7cb..93012fe68ff 100755 --- a/Utilities/cmlibarchive/build/utils/gen_archive_string_composition_h.sh +++ b/Utilities/cmlibarchive/build/utils/gen_archive_string_composition_h.sh @@ -39,9 +39,6 @@ cat > ${outfile} <") +endif() + IF(0) # CMake does not build libarchive's full package. # Libarchive is a shared library -ADD_LIBRARY(archive SHARED ${libarchive_SOURCES} ${include_HEADERS}) -TARGET_INCLUDE_DIRECTORIES(archive PUBLIC .) -TARGET_LINK_LIBRARIES(archive ${ADDITIONAL_LIBS}) -SET_TARGET_PROPERTIES(archive PROPERTIES SOVERSION ${SOVERSION}) +IF(BUILD_SHARED_LIBS) + ADD_LIBRARY(archive SHARED ${libarchive_SOURCES} ${include_HEADERS}) + TARGET_INCLUDE_DIRECTORIES(archive PUBLIC .) + TARGET_LINK_LIBRARIES(archive ${ADDITIONAL_LIBS}) + SET_TARGET_PROPERTIES(archive PROPERTIES + VERSION ${SOVERSION_FULL} + SOVERSION ${SOVERSION} + MACHO_COMPATIBILITY_VERSION ${MACHO_COMPATIBILITY_VERSION} + MACHO_CURRENT_VERSION ${MACHO_CURRENT_VERSION}) +ENDIF(BUILD_SHARED_LIBS) # archive_static is a static library ADD_LIBRARY(archive_static STATIC ${libarchive_SOURCES} ${include_HEADERS}) +TARGET_INCLUDE_DIRECTORIES(archive_static PUBLIC .) TARGET_LINK_LIBRARIES(archive_static ${ADDITIONAL_LIBS}) SET_TARGET_PROPERTIES(archive_static PROPERTIES COMPILE_DEFINITIONS LIBARCHIVE_STATIC) # On Posix systems, libarchive.so and libarchive.a can co-exist. -IF(NOT WIN32 OR CYGWIN) +IF(NOT WIN32 OR CYGWIN OR NOT BUILD_SHARED_LIBS) SET_TARGET_PROPERTIES(archive_static PROPERTIES OUTPUT_NAME archive) -ENDIF(NOT WIN32 OR CYGWIN) +ENDIF(NOT WIN32 OR CYGWIN OR NOT BUILD_SHARED_LIBS) IF(ENABLE_INSTALL) # How to install the libraries - INSTALL(TARGETS archive archive_static + IF(BUILD_SHARED_LIBS) + INSTALL(TARGETS archive + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) + ENDIF(BUILD_SHARED_LIBS) + INSTALL(TARGETS archive_static RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) diff --git a/Utilities/cmlibarchive/libarchive/archive.h b/Utilities/cmlibarchive/libarchive/archive.h index 180f3e4cbde..895411a5d46 100644 --- a/Utilities/cmlibarchive/libarchive/archive.h +++ b/Utilities/cmlibarchive/libarchive/archive.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: src/lib/libarchive/archive.h.in,v 1.50 2008/05/26 17:00:22 kientzle Exp $ */ #ifndef ARCHIVE_H_INCLUDED @@ -36,7 +34,7 @@ * assert that ARCHIVE_VERSION_NUMBER >= 2012108. */ /* Note: Compiler will complain if this does not match archive_entry.h! */ -#define ARCHIVE_VERSION_NUMBER 3006002 +#define ARCHIVE_VERSION_NUMBER 3007009 #include #include /* for wchar_t */ @@ -154,7 +152,7 @@ __LA_DECL int archive_version_number(void); /* * Textual name/version of the library, useful for version displays. */ -#define ARCHIVE_VERSION_ONLY_STRING "3.6.2" +#define ARCHIVE_VERSION_ONLY_STRING "3.7.9" #define ARCHIVE_VERSION_STRING "libarchive " ARCHIVE_VERSION_ONLY_STRING __LA_DECL const char * archive_version_string(void); @@ -532,6 +530,10 @@ __LA_DECL int archive_read_open_filenames(struct archive *, const char **_filenames, size_t _block_size); __LA_DECL int archive_read_open_filename_w(struct archive *, const wchar_t *_filename, size_t _block_size); +#if defined(_WIN32) && !defined(__CYGWIN__) +__LA_DECL int archive_read_open_filenames_w(struct archive *, + const wchar_t **_filenames, size_t _block_size); +#endif /* archive_read_open_file() is a deprecated synonym for ..._open_filename(). */ __LA_DECL int archive_read_open_file(struct archive *, const char *_filename, size_t _block_size) __LA_DEPRECATED; @@ -890,7 +892,7 @@ __LA_DECL int archive_write_set_options(struct archive *_a, const char *opts); /* - * Set a encryption passphrase. + * Set an encryption passphrase. */ __LA_DECL int archive_write_set_passphrase(struct archive *_a, const char *p); __LA_DECL int archive_write_set_passphrase_callback(struct archive *, diff --git a/Utilities/cmlibarchive/libarchive/archive_acl.c b/Utilities/cmlibarchive/libarchive/archive_acl.c index da471a506d6..2acbad180fe 100644 --- a/Utilities/cmlibarchive/libarchive/archive_acl.c +++ b/Utilities/cmlibarchive/libarchive/archive_acl.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -61,7 +60,7 @@ static int archive_acl_add_entry_len_l(struct archive_acl *acl, int type, int permset, int tag, int id, const char *name, size_t len, struct archive_string_conv *sc); static int archive_acl_text_want_type(struct archive_acl *acl, int flags); -static ssize_t archive_acl_text_len(struct archive_acl *acl, int want_type, +static size_t archive_acl_text_len(struct archive_acl *acl, int want_type, int flags, int wide, struct archive *a, struct archive_string_conv *sc); static int isint_w(const wchar_t *start, const wchar_t *end, int *result); @@ -81,7 +80,7 @@ static int is_nfs4_flags(const char *start, const char *end, int *result); static int is_nfs4_perms(const char *start, const char *end, int *result); -static void next_field(const char **p, const char **start, +static void next_field(const char **p, size_t *l, const char **start, const char **end, char *sep); static void append_entry(char **p, const char *prefix, int type, int tag, int flags, const char *name, int perm, int id); @@ -351,7 +350,7 @@ acl_new_entry(struct archive_acl *acl, } /* Add a new entry to the end of the list. */ - ap = (struct archive_acl_entry *)calloc(1, sizeof(*ap)); + ap = calloc(1, sizeof(*ap)); if (ap == NULL) return (NULL); if (aq == NULL) @@ -533,14 +532,14 @@ archive_acl_text_want_type(struct archive_acl *acl, int flags) /* * Calculate ACL text string length */ -static ssize_t +static size_t archive_acl_text_len(struct archive_acl *acl, int want_type, int flags, int wide, struct archive *a, struct archive_string_conv *sc) { struct archive_acl_entry *ap; const char *name; const wchar_t *wname; int count, idlen, tmp, r; - ssize_t length; + size_t length; size_t len; count = 0; @@ -669,7 +668,7 @@ archive_acl_to_text_w(struct archive_acl *acl, ssize_t *text_len, int flags, struct archive *a) { int count; - ssize_t length; + size_t length; size_t len; const wchar_t *wname; const wchar_t *prefix; @@ -698,7 +697,7 @@ archive_acl_to_text_w(struct archive_acl *acl, ssize_t *text_len, int flags, separator = L'\n'; /* Now, allocate the string and actually populate it. */ - wp = ws = (wchar_t *)malloc(length * sizeof(wchar_t)); + wp = ws = malloc(length * sizeof(*wp)); if (wp == NULL) { if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -760,7 +759,7 @@ archive_acl_to_text_w(struct archive_acl *acl, ssize_t *text_len, int flags, len = wcslen(ws); - if ((ssize_t)len > (length - 1)) + if (len > length - 1) __archive_errx(1, "Buffer overrun"); if (text_len != NULL) @@ -902,7 +901,7 @@ archive_acl_to_text_l(struct archive_acl *acl, ssize_t *text_len, int flags, struct archive_string_conv *sc) { int count; - ssize_t length; + size_t length; size_t len; const char *name; const char *prefix; @@ -931,7 +930,7 @@ archive_acl_to_text_l(struct archive_acl *acl, ssize_t *text_len, int flags, separator = '\n'; /* Now, allocate the string and actually populate it. */ - p = s = (char *)malloc(length * sizeof(char)); + p = s = malloc(length * sizeof(*p)); if (p == NULL) { if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -995,7 +994,7 @@ archive_acl_to_text_l(struct archive_acl *acl, ssize_t *text_len, int flags, len = strlen(s); - if ((ssize_t)len > (length - 1)) + if (len > length - 1) __archive_errx(1, "Buffer overrun"); if (text_len != NULL) @@ -1627,6 +1626,13 @@ next_field_w(const wchar_t **wp, const wchar_t **start, int archive_acl_from_text_l(struct archive_acl *acl, const char *text, int want_type, struct archive_string_conv *sc) +{ + return archive_acl_from_text_nl(acl, text, strlen(text), want_type, sc); +} + +int +archive_acl_from_text_nl(struct archive_acl *acl, const char *text, + size_t length, int want_type, struct archive_string_conv *sc) { struct { const char *start; @@ -1657,7 +1663,7 @@ archive_acl_from_text_l(struct archive_acl *acl, const char *text, ret = ARCHIVE_OK; types = 0; - while (text != NULL && *text != '\0') { + while (text != NULL && length > 0 && *text != '\0') { /* * Parse the fields out of the next entry, * advance 'text' to start of next entry. @@ -1665,7 +1671,7 @@ archive_acl_from_text_l(struct archive_acl *acl, const char *text, fields = 0; do { const char *start, *end; - next_field(&text, &start, &end, &sep); + next_field(&text, &length, &start, &end, &sep); if (fields < numfields) { field[fields].start = start; field[fields].end = end; @@ -2058,7 +2064,7 @@ is_nfs4_flags(const char *start, const char *end, int *permset) } /* - * Match "[:whitespace:]*(.*)[:whitespace:]*[:,\n]". *wp is updated + * Match "[:whitespace:]*(.*)[:whitespace:]*[:,\n]". *p is updated * to point to just after the separator. *start points to the first * character of the matched text and *end just after the last * character of the matched identifier. In particular *end - *start @@ -2066,42 +2072,42 @@ is_nfs4_flags(const char *start, const char *end, int *permset) * whitespace. */ static void -next_field(const char **p, const char **start, +next_field(const char **p, size_t *l, const char **start, const char **end, char *sep) { /* Skip leading whitespace to find start of field. */ - while (**p == ' ' || **p == '\t' || **p == '\n') { + while (*l > 0 && (**p == ' ' || **p == '\t' || **p == '\n')) { (*p)++; + (*l)--; } *start = *p; - /* Scan for the separator. */ - while (**p != '\0' && **p != ',' && **p != ':' && **p != '\n' && - **p != '#') { + /* Locate end of field, trim trailing whitespace if necessary */ + while (*l > 0 && **p != ' ' && **p != '\t' && **p != '\n' && **p != ',' && **p != ':' && **p != '#') { (*p)++; + (*l)--; } - *sep = **p; + *end = *p; - /* Locate end of field, trim trailing whitespace if necessary */ - if (*p == *start) { - *end = *p; - } else { - *end = *p - 1; - while (**end == ' ' || **end == '\t' || **end == '\n') { - (*end)--; - } - (*end)++; + /* Scan for the separator. */ + while (*l > 0 && **p != ',' && **p != ':' && **p != '\n' && **p != '#') { + (*p)++; + (*l)--; } + *sep = **p; /* Handle in-field comments */ if (*sep == '#') { - while (**p != '\0' && **p != ',' && **p != '\n') { + while (*l > 0 && **p != ',' && **p != '\n') { (*p)++; + (*l)--; } *sep = **p; } - /* Adjust scanner location. */ - if (**p != '\0') + /* Skip separator. */ + if (*l > 0) { (*p)++; + (*l)--; + } } diff --git a/Utilities/cmlibarchive/libarchive/archive_acl_private.h b/Utilities/cmlibarchive/libarchive/archive_acl_private.h index af108162c66..2c9b5053430 100644 --- a/Utilities/cmlibarchive/libarchive/archive_acl_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_acl_private.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ #ifndef ARCHIVE_ACL_PRIVATE_H_INCLUDED @@ -79,5 +77,7 @@ int archive_acl_from_text_w(struct archive_acl *, const wchar_t * /* wtext */, int /* type */); int archive_acl_from_text_l(struct archive_acl *, const char * /* text */, int /* type */, struct archive_string_conv *); +int archive_acl_from_text_nl(struct archive_acl *, const char * /* text */, + size_t /* size of text */, int /* type */, struct archive_string_conv *); #endif /* ARCHIVE_ENTRY_PRIVATE_H_INCLUDED */ diff --git a/Utilities/cmlibarchive/libarchive/archive_check_magic.c b/Utilities/cmlibarchive/libarchive/archive_check_magic.c index 1f40072f81d..d12f0c496e2 100644 --- a/Utilities/cmlibarchive/libarchive/archive_check_magic.c +++ b/Utilities/cmlibarchive/libarchive/archive_check_magic.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_check_magic.c 201089 2009-12-28 02:20:23Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include @@ -62,7 +61,7 @@ errmsg(const char *m) } } -static __LA_DEAD void +static __LA_NORETURN void diediedie(void) { #if defined(_WIN32) && !defined(__CYGWIN__) && defined(_DEBUG) @@ -99,13 +98,12 @@ archive_handle_type_name(unsigned m) } } - -static char * +static void write_all_states(char *buff, unsigned int states) { unsigned int lowbit; - buff[0] = '\0'; + *buff = '\0'; /* A trick for computing the lowest set bit. */ while ((lowbit = states & (1 + ~states)) != 0) { @@ -114,7 +112,6 @@ write_all_states(char *buff, unsigned int states) if (states != 0) strcat(buff, "/"); } - return buff; } /* @@ -160,16 +157,19 @@ __archive_check_magic(struct archive *a, unsigned int magic, if ((a->state & state) == 0) { /* If we're already FATAL, don't overwrite the error. */ - if (a->state != ARCHIVE_STATE_FATAL) + if (a->state != ARCHIVE_STATE_FATAL) { + write_all_states(states1, a->state); + write_all_states(states2, state); archive_set_error(a, -1, "INTERNAL ERROR: Function '%s' invoked with" " archive structure in state '%s'," " should be in state '%s'", function, - write_all_states(states1, a->state), - write_all_states(states2, state)); + states1, + states2); + } a->state = ARCHIVE_STATE_FATAL; return (ARCHIVE_FATAL); } - return ARCHIVE_OK; + return (ARCHIVE_OK); } diff --git a/Utilities/cmlibarchive/libarchive/archive_cmdline.c b/Utilities/cmlibarchive/libarchive/archive_cmdline.c index 5c519cd17f1..2e5428cae51 100644 --- a/Utilities/cmlibarchive/libarchive/archive_cmdline.c +++ b/Utilities/cmlibarchive/libarchive/archive_cmdline.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_STRING_H # include #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_cmdline_private.h b/Utilities/cmlibarchive/libarchive/archive_cmdline_private.h index 57a19494fd7..7495dfed55d 100644 --- a/Utilities/cmlibarchive/libarchive/archive_cmdline_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_cmdline_private.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ #ifndef ARCHIVE_CMDLINE_PRIVATE_H diff --git a/Utilities/cmlibarchive/libarchive/archive_crc32.h b/Utilities/cmlibarchive/libarchive/archive_crc32.h index 4f1aed30593..d86a507ce78 100644 --- a/Utilities/cmlibarchive/libarchive/archive_crc32.h +++ b/Utilities/cmlibarchive/libarchive/archive_crc32.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_crc32.h 201102 2009-12-28 03:11:36Z kientzle $ */ #ifndef ARCHIVE_CRC32_H @@ -32,6 +30,8 @@ #error This header is only to be used internally to libarchive. #endif +#include + /* * When zlib is unavailable, we should still be able to validate * uncompressed zip archives. That requires us to be able to compute @@ -48,6 +48,9 @@ crc32(unsigned long crc, const void *_p, size_t len) static volatile int crc_tbl_inited = 0; static unsigned long crc_tbl[256]; + if (_p == NULL) + return (0); + if (!crc_tbl_inited) { for (b = 0; b < 256; ++b) { crc2 = b; diff --git a/Utilities/cmlibarchive/libarchive/archive_cryptor.c b/Utilities/cmlibarchive/libarchive/archive_cryptor.c index 112baf16134..1825af4dc51 100644 --- a/Utilities/cmlibarchive/libarchive/archive_cryptor.c +++ b/Utilities/cmlibarchive/libarchive/archive_cryptor.c @@ -57,7 +57,7 @@ pbkdf2_sha1(const char *pw, size_t pw_len, const uint8_t *salt, return 0; } -#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) +#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA #ifdef _MSC_VER #pragma comment(lib, "Bcrypt.lib") #endif @@ -197,7 +197,7 @@ aes_ctr_release(archive_crypto_ctx *ctx) return 0; } -#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) +#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA static int aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, size_t key_len) @@ -424,8 +424,8 @@ static int aes_ctr_release(archive_crypto_ctx *ctx) { EVP_CIPHER_CTX_free(ctx->ctx); - memset(ctx->key, 0, ctx->key_len); - memset(ctx->nonce, 0, sizeof(ctx->nonce)); + OPENSSL_cleanse(ctx->key, ctx->key_len); + OPENSSL_cleanse(ctx->nonce, sizeof(ctx->nonce)); return 0; } diff --git a/Utilities/cmlibarchive/libarchive/archive_cryptor_private.h b/Utilities/cmlibarchive/libarchive/archive_cryptor_private.h index 16b6d16ff23..c13f29260a0 100644 --- a/Utilities/cmlibarchive/libarchive/archive_cryptor_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_cryptor_private.h @@ -62,7 +62,7 @@ typedef struct { unsigned encr_pos; } archive_crypto_ctx; -#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) +#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA #include /* Common in other bcrypt implementations, but missing from VS2008. */ diff --git a/Utilities/cmlibarchive/libarchive/archive_digest.c b/Utilities/cmlibarchive/libarchive/archive_digest.c index 3361b19ada8..33518740833 100644 --- a/Utilities/cmlibarchive/libarchive/archive_digest.c +++ b/Utilities/cmlibarchive/libarchive/archive_digest.c @@ -36,6 +36,11 @@ #error Cannot use both OpenSSL and libmd. #endif +/* Common in other bcrypt implementations, but missing from VS2008. */ +#ifndef BCRYPT_SUCCESS +#define BCRYPT_SUCCESS(r) ((NTSTATUS)(r) == STATUS_SUCCESS) +#endif + /* * Message digest functions for Windows platform. */ @@ -48,6 +53,26 @@ /* * Initialize a Message digest. */ +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA +static int +win_crypto_init(Digest_CTX *ctx, const WCHAR *algo) +{ + NTSTATUS status; + ctx->valid = 0; + + status = BCryptOpenAlgorithmProvider(&ctx->hAlg, algo, NULL, 0); + if (!BCRYPT_SUCCESS(status)) + return (ARCHIVE_FAILED); + status = BCryptCreateHash(ctx->hAlg, &ctx->hHash, NULL, 0, NULL, 0, 0); + if (!BCRYPT_SUCCESS(status)) { + BCryptCloseAlgorithmProvider(ctx->hAlg, 0); + return (ARCHIVE_FAILED); + } + + ctx->valid = 1; + return (ARCHIVE_OK); +} +#else static int win_crypto_init(Digest_CTX *ctx, DWORD prov, ALG_ID algId) { @@ -70,6 +95,7 @@ win_crypto_init(Digest_CTX *ctx, DWORD prov, ALG_ID algId) ctx->valid = 1; return (ARCHIVE_OK); } +#endif /* * Update a Message digest. @@ -81,23 +107,37 @@ win_crypto_Update(Digest_CTX *ctx, const unsigned char *buf, size_t len) if (!ctx->valid) return (ARCHIVE_FAILED); +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + BCryptHashData(ctx->hHash, + (PUCHAR)(uintptr_t)buf, + (ULONG)len, 0); +#else CryptHashData(ctx->hash, (unsigned char *)(uintptr_t)buf, (DWORD)len, 0); +#endif return (ARCHIVE_OK); } static int win_crypto_Final(unsigned char *buf, size_t bufsize, Digest_CTX *ctx) { +#if !(defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA) DWORD siglen = (DWORD)bufsize; +#endif if (!ctx->valid) return (ARCHIVE_FAILED); +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + BCryptFinishHash(ctx->hHash, buf, (ULONG)bufsize, 0); + BCryptDestroyHash(ctx->hHash); + BCryptCloseAlgorithmProvider(ctx->hAlg, 0); +#else CryptGetHashParam(ctx->hash, HP_HASHVAL, buf, &siglen, 0); CryptDestroyHash(ctx->hash); CryptReleaseContext(ctx->cryptProv, 0); +#endif ctx->valid = 0; return (ARCHIVE_OK); } @@ -156,6 +196,13 @@ __archive_md5final(archive_md5_ctx *ctx, void *md) #elif defined(ARCHIVE_CRYPTO_MD5_LIBSYSTEM) +// These functions are available in macOS 10.4 and later, but deprecated from 10.15 onwards. +// We need to continue supporting this feature regardless, so suppress the warnings. +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + static int __archive_md5init(archive_md5_ctx *ctx) { @@ -178,6 +225,10 @@ __archive_md5final(archive_md5_ctx *ctx, void *md) return (ARCHIVE_OK); } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + #elif defined(ARCHIVE_CRYPTO_MD5_MBEDTLS) static int @@ -276,7 +327,11 @@ __archive_md5final(archive_md5_ctx *ctx, void *md) static int __archive_md5init(archive_md5_ctx *ctx) { +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + return (win_crypto_init(ctx, BCRYPT_MD5_ALGORITHM)); +#else return (win_crypto_init(ctx, PROV_RSA_FULL, CALG_MD5)); +#endif } static int @@ -659,7 +714,11 @@ __archive_sha1final(archive_sha1_ctx *ctx, void *md) static int __archive_sha1init(archive_sha1_ctx *ctx) { +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + return (win_crypto_init(ctx, BCRYPT_SHA1_ALGORITHM)); +#else return (win_crypto_init(ctx, PROV_RSA_FULL, CALG_SHA1)); +#endif } static int @@ -919,7 +978,11 @@ __archive_sha256final(archive_sha256_ctx *ctx, void *md) static int __archive_sha256init(archive_sha256_ctx *ctx) { +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + return (win_crypto_init(ctx, BCRYPT_SHA256_ALGORITHM)); +#else return (win_crypto_init(ctx, PROV_RSA_AES, CALG_SHA_256)); +#endif } static int @@ -1155,7 +1218,11 @@ __archive_sha384final(archive_sha384_ctx *ctx, void *md) static int __archive_sha384init(archive_sha384_ctx *ctx) { +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + return (win_crypto_init(ctx, BCRYPT_SHA384_ALGORITHM)); +#else return (win_crypto_init(ctx, PROV_RSA_AES, CALG_SHA_384)); +#endif } static int @@ -1415,7 +1482,11 @@ __archive_sha512final(archive_sha512_ctx *ctx, void *md) static int __archive_sha512init(archive_sha512_ctx *ctx) { +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + return (win_crypto_init(ctx, BCRYPT_SHA512_ALGORITHM)); +#else return (win_crypto_init(ctx, PROV_RSA_AES, CALG_SHA_512)); +#endif } static int diff --git a/Utilities/cmlibarchive/libarchive/archive_digest_private.h b/Utilities/cmlibarchive/libarchive/archive_digest_private.h index 9b3bd6621bf..339b4edca48 100644 --- a/Utilities/cmlibarchive/libarchive/archive_digest_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_digest_private.h @@ -164,6 +164,15 @@ defined(ARCHIVE_CRYPTO_SHA256_WIN) ||\ defined(ARCHIVE_CRYPTO_SHA384_WIN) ||\ defined(ARCHIVE_CRYPTO_SHA512_WIN) +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA +/* don't use bcrypt when XP needs to be supported */ +#include +typedef struct { + int valid; + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_HASH_HANDLE hHash; +} Digest_CTX; +#else #include #include typedef struct { @@ -172,6 +181,7 @@ typedef struct { HCRYPTHASH hash; } Digest_CTX; #endif +#endif /* typedefs */ #if defined(ARCHIVE_CRYPTO_MD5_LIBC) diff --git a/Utilities/cmlibarchive/libarchive/archive_endian.h b/Utilities/cmlibarchive/libarchive/archive_endian.h index e6d3f2ce5e7..83b2efa531e 100644 --- a/Utilities/cmlibarchive/libarchive/archive_endian.h +++ b/Utilities/cmlibarchive/libarchive/archive_endian.h @@ -23,8 +23,6 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: head/lib/libarchive/archive_endian.h 201085 2009-12-28 02:17:15Z kientzle $ - * * Borrowed from FreeBSD's */ diff --git a/Utilities/cmlibarchive/libarchive/archive_entry.3 b/Utilities/cmlibarchive/libarchive/archive_entry.3 index 2f62a4be233..0fc0f8cc2fe 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry.3 +++ b/Utilities/cmlibarchive/libarchive/archive_entry.3 @@ -23,8 +23,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_ENTRY 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_entry.c b/Utilities/cmlibarchive/libarchive/archive_entry.c index ae6dc333619..4ebfc5fa632 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry.c 201096 2009-12-28 02:41:27Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -119,7 +118,7 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_entry.c 201096 2009-12-28 02:41: static char * ae_fflagstostr(unsigned long bitset, unsigned long bitclear); static const wchar_t *ae_wcstofflags(const wchar_t *stringp, unsigned long *setp, unsigned long *clrp); -static const char *ae_strtofflags(const char *stringp, +static const char *ae_strtofflags(const char *stringp, size_t length, unsigned long *setp, unsigned long *clrp); #ifndef HAVE_WCSCPY @@ -158,10 +157,9 @@ archive_entry_clear(struct archive_entry *entry) return (NULL); archive_mstring_clean(&entry->ae_fflags_text); archive_mstring_clean(&entry->ae_gname); - archive_mstring_clean(&entry->ae_hardlink); + archive_mstring_clean(&entry->ae_linkname); archive_mstring_clean(&entry->ae_pathname); archive_mstring_clean(&entry->ae_sourcepath); - archive_mstring_clean(&entry->ae_symlink); archive_mstring_clean(&entry->ae_uname); archive_entry_copy_mac_metadata(entry, NULL, 0); archive_acl_clear(&entry->acl); @@ -196,10 +194,9 @@ archive_entry_clone(struct archive_entry *entry) * character sets are different? XXX */ archive_mstring_copy(&entry2->ae_fflags_text, &entry->ae_fflags_text); archive_mstring_copy(&entry2->ae_gname, &entry->ae_gname); - archive_mstring_copy(&entry2->ae_hardlink, &entry->ae_hardlink); + archive_mstring_copy(&entry2->ae_linkname, &entry->ae_linkname); archive_mstring_copy(&entry2->ae_pathname, &entry->ae_pathname); archive_mstring_copy(&entry2->ae_sourcepath, &entry->ae_sourcepath); - archive_mstring_copy(&entry2->ae_symlink, &entry->ae_symlink); entry2->ae_set = entry->ae_set; archive_mstring_copy(&entry2->ae_uname, &entry->ae_uname); @@ -266,7 +263,7 @@ archive_entry_new2(struct archive *a) { struct archive_entry *entry; - entry = (struct archive_entry *)calloc(1, sizeof(*entry)); + entry = calloc(1, sizeof(*entry)); if (entry == NULL) return (NULL); entry->archive = a; @@ -372,6 +369,12 @@ archive_entry_filetype(struct archive_entry *entry) return (AE_IFMT & entry->acl.mode); } +int +archive_entry_filetype_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_FILETYPE); +} + void archive_entry_fflags(struct archive_entry *entry, unsigned long *set, unsigned long *clear) @@ -425,6 +428,12 @@ archive_entry_gid(struct archive_entry *entry) return (entry->ae_stat.aest_gid); } +int +archive_entry_gid_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_GID); +} + const char * archive_entry_gname(struct archive_entry *entry) { @@ -466,6 +475,15 @@ _archive_entry_gname_l(struct archive_entry *entry, return (archive_mstring_get_mbs_l(entry->archive, &entry->ae_gname, p, len, sc)); } +void +archive_entry_set_link_to_hardlink(struct archive_entry *entry) +{ + if ((entry->ae_set & AE_SET_SYMLINK) != 0) { + entry->ae_set &= ~AE_SET_SYMLINK; + } + entry->ae_set |= AE_SET_HARDLINK; +} + const char * archive_entry_hardlink(struct archive_entry *entry) { @@ -473,7 +491,7 @@ archive_entry_hardlink(struct archive_entry *entry) if ((entry->ae_set & AE_SET_HARDLINK) == 0) return (NULL); if (archive_mstring_get_mbs( - entry->archive, &entry->ae_hardlink, &p) == 0) + entry->archive, &entry->ae_linkname, &p) == 0) return (p); if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -487,7 +505,7 @@ archive_entry_hardlink_utf8(struct archive_entry *entry) if ((entry->ae_set & AE_SET_HARDLINK) == 0) return (NULL); if (archive_mstring_get_utf8( - entry->archive, &entry->ae_hardlink, &p) == 0) + entry->archive, &entry->ae_linkname, &p) == 0) return (p); if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -501,13 +519,19 @@ archive_entry_hardlink_w(struct archive_entry *entry) if ((entry->ae_set & AE_SET_HARDLINK) == 0) return (NULL); if (archive_mstring_get_wcs( - entry->archive, &entry->ae_hardlink, &p) == 0) + entry->archive, &entry->ae_linkname, &p) == 0) return (p); if (errno == ENOMEM) __archive_errx(1, "No memory"); return (NULL); } +int +archive_entry_hardlink_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_HARDLINK) != 0; +} + int _archive_entry_hardlink_l(struct archive_entry *entry, const char **p, size_t *len, struct archive_string_conv *sc) @@ -517,7 +541,7 @@ _archive_entry_hardlink_l(struct archive_entry *entry, *len = 0; return (0); } - return (archive_mstring_get_mbs_l(entry->archive, &entry->ae_hardlink, p, len, sc)); + return (archive_mstring_get_mbs_l(entry->archive, &entry->ae_linkname, p, len, sc)); } la_int64_t @@ -631,32 +655,56 @@ archive_entry_perm(struct archive_entry *entry) return (~AE_IFMT & entry->acl.mode); } +int +archive_entry_perm_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_PERM); +} + +int +archive_entry_rdev_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_RDEV); +} + dev_t archive_entry_rdev(struct archive_entry *entry) { - if (entry->ae_stat.aest_rdev_is_broken_down) - return ae_makedev(entry->ae_stat.aest_rdevmajor, - entry->ae_stat.aest_rdevminor); - else - return (entry->ae_stat.aest_rdev); + if (archive_entry_rdev_is_set(entry)) { + if (entry->ae_stat.aest_rdev_is_broken_down) + return ae_makedev(entry->ae_stat.aest_rdevmajor, + entry->ae_stat.aest_rdevminor); + else + return (entry->ae_stat.aest_rdev); + } else { + return 0; + } } dev_t archive_entry_rdevmajor(struct archive_entry *entry) { - if (entry->ae_stat.aest_rdev_is_broken_down) - return (entry->ae_stat.aest_rdevmajor); - else - return major(entry->ae_stat.aest_rdev); + if (archive_entry_rdev_is_set(entry)) { + if (entry->ae_stat.aest_rdev_is_broken_down) + return (entry->ae_stat.aest_rdevmajor); + else + return major(entry->ae_stat.aest_rdev); + } else { + return 0; + } } dev_t archive_entry_rdevminor(struct archive_entry *entry) { - if (entry->ae_stat.aest_rdev_is_broken_down) - return (entry->ae_stat.aest_rdevminor); - else - return minor(entry->ae_stat.aest_rdev); + if (archive_entry_rdev_is_set(entry)) { + if (entry->ae_stat.aest_rdev_is_broken_down) + return (entry->ae_stat.aest_rdevminor); + else + return minor(entry->ae_stat.aest_rdev); + } else { + return 0; + } } la_int64_t @@ -700,13 +748,22 @@ archive_entry_symlink(struct archive_entry *entry) if ((entry->ae_set & AE_SET_SYMLINK) == 0) return (NULL); if (archive_mstring_get_mbs( - entry->archive, &entry->ae_symlink, &p) == 0) + entry->archive, &entry->ae_linkname, &p) == 0) return (p); if (errno == ENOMEM) __archive_errx(1, "No memory"); return (NULL); } +void +archive_entry_set_link_to_symlink(struct archive_entry *entry) +{ + if ((entry->ae_set & AE_SET_HARDLINK) != 0) { + entry->ae_set &= ~AE_SET_HARDLINK; + } + entry->ae_set |= AE_SET_SYMLINK; +} + int archive_entry_symlink_type(struct archive_entry *entry) { @@ -720,7 +777,7 @@ archive_entry_symlink_utf8(struct archive_entry *entry) if ((entry->ae_set & AE_SET_SYMLINK) == 0) return (NULL); if (archive_mstring_get_utf8( - entry->archive, &entry->ae_symlink, &p) == 0) + entry->archive, &entry->ae_linkname, &p) == 0) return (p); if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -734,7 +791,7 @@ archive_entry_symlink_w(struct archive_entry *entry) if ((entry->ae_set & AE_SET_SYMLINK) == 0) return (NULL); if (archive_mstring_get_wcs( - entry->archive, &entry->ae_symlink, &p) == 0) + entry->archive, &entry->ae_linkname, &p) == 0) return (p); if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -750,7 +807,7 @@ _archive_entry_symlink_l(struct archive_entry *entry, *len = 0; return (0); } - return (archive_mstring_get_mbs_l(entry->archive, &entry->ae_symlink, p, len, sc)); + return (archive_mstring_get_mbs_l(entry->archive, &entry->ae_linkname, p, len, sc)); } la_int64_t @@ -759,6 +816,12 @@ archive_entry_uid(struct archive_entry *entry) return (entry->ae_stat.aest_uid); } +int +archive_entry_uid_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_UID); +} + const char * archive_entry_uname(struct archive_entry *entry) { @@ -827,6 +890,7 @@ archive_entry_set_filetype(struct archive_entry *entry, unsigned int type) entry->stat_valid = 0; entry->acl.mode &= ~AE_IFMT; entry->acl.mode |= AE_IFMT & type; + entry->ae_set |= AE_SET_FILETYPE; } void @@ -840,10 +904,17 @@ archive_entry_set_fflags(struct archive_entry *entry, const char * archive_entry_copy_fflags_text(struct archive_entry *entry, - const char *flags) + const char *flags) { - archive_mstring_copy_mbs(&entry->ae_fflags_text, flags); - return (ae_strtofflags(flags, + return archive_entry_copy_fflags_text_len(entry, flags, strlen(flags)); +} + +const char * +archive_entry_copy_fflags_text_len(struct archive_entry *entry, + const char *flags, size_t flags_length) +{ + archive_mstring_copy_mbs_len(&entry->ae_fflags_text, flags, flags_length); + return (ae_strtofflags(flags, flags_length, &entry->ae_fflags_set, &entry->ae_fflags_clear)); } @@ -859,8 +930,12 @@ archive_entry_copy_fflags_text_w(struct archive_entry *entry, void archive_entry_set_gid(struct archive_entry *entry, la_int64_t g) { + if (g < 0) { + g = 0; + } entry->stat_valid = 0; entry->ae_stat.aest_gid = g; + entry->ae_set |= AE_SET_GID; } void @@ -908,6 +983,9 @@ _archive_entry_copy_gname_l(struct archive_entry *entry, void archive_entry_set_ino(struct archive_entry *entry, la_int64_t ino) { + if (ino < 0) { + ino = 0; + } entry->stat_valid = 0; entry->ae_set |= AE_SET_INO; entry->ae_stat.aest_ino = ino; @@ -916,6 +994,9 @@ archive_entry_set_ino(struct archive_entry *entry, la_int64_t ino) void archive_entry_set_ino64(struct archive_entry *entry, la_int64_t ino) { + if (ino < 0) { + ino = 0; + } entry->stat_valid = 0; entry->ae_set |= AE_SET_INO; entry->ae_stat.aest_ino = ino; @@ -924,17 +1005,24 @@ archive_entry_set_ino64(struct archive_entry *entry, la_int64_t ino) void archive_entry_set_hardlink(struct archive_entry *entry, const char *target) { - archive_mstring_copy_mbs(&entry->ae_hardlink, target); - if (target != NULL) - entry->ae_set |= AE_SET_HARDLINK; - else + if (target == NULL) { entry->ae_set &= ~AE_SET_HARDLINK; + if (entry->ae_set & AE_SET_SYMLINK) { + return; + } + } else { + entry->ae_set |= AE_SET_HARDLINK; + } + entry->ae_set &= ~AE_SET_SYMLINK; + archive_mstring_copy_mbs(&entry->ae_linkname, target); } void archive_entry_set_hardlink_utf8(struct archive_entry *entry, const char *target) { - archive_mstring_copy_utf8(&entry->ae_hardlink, target); + if (target == NULL && (entry->ae_set & AE_SET_SYMLINK)) + return; + archive_mstring_copy_utf8(&entry->ae_linkname, target); if (target != NULL) entry->ae_set |= AE_SET_HARDLINK; else @@ -944,7 +1032,9 @@ archive_entry_set_hardlink_utf8(struct archive_entry *entry, const char *target) void archive_entry_copy_hardlink(struct archive_entry *entry, const char *target) { - archive_mstring_copy_mbs(&entry->ae_hardlink, target); + if (target == NULL && (entry->ae_set & AE_SET_SYMLINK)) + return; + archive_mstring_copy_mbs(&entry->ae_linkname, target); if (target != NULL) entry->ae_set |= AE_SET_HARDLINK; else @@ -954,7 +1044,9 @@ archive_entry_copy_hardlink(struct archive_entry *entry, const char *target) void archive_entry_copy_hardlink_w(struct archive_entry *entry, const wchar_t *target) { - archive_mstring_copy_wcs(&entry->ae_hardlink, target); + if (target == NULL && (entry->ae_set & AE_SET_SYMLINK)) + return; + archive_mstring_copy_wcs(&entry->ae_linkname, target); if (target != NULL) entry->ae_set |= AE_SET_HARDLINK; else @@ -964,12 +1056,14 @@ archive_entry_copy_hardlink_w(struct archive_entry *entry, const wchar_t *target int archive_entry_update_hardlink_utf8(struct archive_entry *entry, const char *target) { + if (target == NULL && (entry->ae_set & AE_SET_SYMLINK)) + return (0); if (target != NULL) entry->ae_set |= AE_SET_HARDLINK; else entry->ae_set &= ~AE_SET_HARDLINK; if (archive_mstring_update_utf8(entry->archive, - &entry->ae_hardlink, target) == 0) + &entry->ae_linkname, target) == 0) return (1); if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -982,7 +1076,9 @@ _archive_entry_copy_hardlink_l(struct archive_entry *entry, { int r; - r = archive_mstring_copy_mbs_len_l(&entry->ae_hardlink, + if (target == NULL && (entry->ae_set & AE_SET_SYMLINK)) + return (0); + r = archive_mstring_copy_mbs_len_l(&entry->ae_linkname, target, len, sc); if (target != NULL && r == 0) entry->ae_set |= AE_SET_HARDLINK; @@ -1073,51 +1169,50 @@ archive_entry_set_devminor(struct archive_entry *entry, dev_t m) void archive_entry_set_link(struct archive_entry *entry, const char *target) { - if (entry->ae_set & AE_SET_SYMLINK) - archive_mstring_copy_mbs(&entry->ae_symlink, target); - else - archive_mstring_copy_mbs(&entry->ae_hardlink, target); + archive_mstring_copy_mbs(&entry->ae_linkname, target); + if ((entry->ae_set & AE_SET_SYMLINK) == 0) { + entry->ae_set |= AE_SET_HARDLINK; + } } void archive_entry_set_link_utf8(struct archive_entry *entry, const char *target) { - if (entry->ae_set & AE_SET_SYMLINK) - archive_mstring_copy_utf8(&entry->ae_symlink, target); - else - archive_mstring_copy_utf8(&entry->ae_hardlink, target); + archive_mstring_copy_utf8(&entry->ae_linkname, target); + if ((entry->ae_set & AE_SET_SYMLINK) == 0) { + entry->ae_set |= AE_SET_HARDLINK; + } } /* Set symlink if symlink is already set, else set hardlink. */ void archive_entry_copy_link(struct archive_entry *entry, const char *target) { - if (entry->ae_set & AE_SET_SYMLINK) - archive_mstring_copy_mbs(&entry->ae_symlink, target); - else - archive_mstring_copy_mbs(&entry->ae_hardlink, target); + archive_mstring_copy_mbs(&entry->ae_linkname, target); + if ((entry->ae_set & AE_SET_SYMLINK) == 0) { + entry->ae_set |= AE_SET_HARDLINK; + } } /* Set symlink if symlink is already set, else set hardlink. */ void archive_entry_copy_link_w(struct archive_entry *entry, const wchar_t *target) { - if (entry->ae_set & AE_SET_SYMLINK) - archive_mstring_copy_wcs(&entry->ae_symlink, target); - else - archive_mstring_copy_wcs(&entry->ae_hardlink, target); + archive_mstring_copy_wcs(&entry->ae_linkname, target); + if ((entry->ae_set & AE_SET_SYMLINK) == 0) { + entry->ae_set |= AE_SET_HARDLINK; + } } int archive_entry_update_link_utf8(struct archive_entry *entry, const char *target) { int r; - if (entry->ae_set & AE_SET_SYMLINK) - r = archive_mstring_update_utf8(entry->archive, - &entry->ae_symlink, target); - else - r = archive_mstring_update_utf8(entry->archive, - &entry->ae_hardlink, target); + r = archive_mstring_update_utf8(entry->archive, + &entry->ae_linkname, target); + if ((entry->ae_set & AE_SET_SYMLINK) == 0) { + entry->ae_set |= AE_SET_HARDLINK; + } if (r == 0) return (1); if (errno == ENOMEM) @@ -1131,12 +1226,11 @@ _archive_entry_copy_link_l(struct archive_entry *entry, { int r; - if (entry->ae_set & AE_SET_SYMLINK) - r = archive_mstring_copy_mbs_len_l(&entry->ae_symlink, - target, len, sc); - else - r = archive_mstring_copy_mbs_len_l(&entry->ae_hardlink, + r = archive_mstring_copy_mbs_len_l(&entry->ae_linkname, target, len, sc); + if ((entry->ae_set & AE_SET_SYMLINK) == 0) { + entry->ae_set |= AE_SET_HARDLINK; + } return (r); } @@ -1145,6 +1239,7 @@ archive_entry_set_mode(struct archive_entry *entry, mode_t m) { entry->stat_valid = 0; entry->acl.mode = m; + entry->ae_set |= AE_SET_PERM | AE_SET_FILETYPE; } void @@ -1220,6 +1315,7 @@ archive_entry_set_perm(struct archive_entry *entry, mode_t p) entry->stat_valid = 0; entry->acl.mode &= AE_IFMT; entry->acl.mode |= ~AE_IFMT & p; + entry->ae_set |= AE_SET_PERM; } void @@ -1228,6 +1324,9 @@ archive_entry_set_rdev(struct archive_entry *entry, dev_t m) entry->stat_valid = 0; entry->ae_stat.aest_rdev = m; entry->ae_stat.aest_rdev_is_broken_down = 0; + entry->ae_stat.aest_rdevmajor = 0; + entry->ae_stat.aest_rdevminor = 0; + entry->ae_set |= AE_SET_RDEV; } void @@ -1235,7 +1334,9 @@ archive_entry_set_rdevmajor(struct archive_entry *entry, dev_t m) { entry->stat_valid = 0; entry->ae_stat.aest_rdev_is_broken_down = 1; + entry->ae_stat.aest_rdev = 0; entry->ae_stat.aest_rdevmajor = m; + entry->ae_set |= AE_SET_RDEV; } void @@ -1243,12 +1344,17 @@ archive_entry_set_rdevminor(struct archive_entry *entry, dev_t m) { entry->stat_valid = 0; entry->ae_stat.aest_rdev_is_broken_down = 1; + entry->ae_stat.aest_rdev = 0; entry->ae_stat.aest_rdevminor = m; + entry->ae_set |= AE_SET_RDEV; } void archive_entry_set_size(struct archive_entry *entry, la_int64_t s) { + if (s < 0) { + s = 0; + } entry->stat_valid = 0; entry->ae_stat.aest_size = s; entry->ae_set |= AE_SET_SIZE; @@ -1276,11 +1382,14 @@ archive_entry_copy_sourcepath_w(struct archive_entry *entry, const wchar_t *path void archive_entry_set_symlink(struct archive_entry *entry, const char *linkname) { - archive_mstring_copy_mbs(&entry->ae_symlink, linkname); - if (linkname != NULL) - entry->ae_set |= AE_SET_SYMLINK; - else + if (linkname == NULL && (entry->ae_set & AE_SET_HARDLINK)) + return; + archive_mstring_copy_mbs(&entry->ae_linkname, linkname); + entry->ae_set &= ~AE_SET_HARDLINK; + if (linkname == NULL) entry->ae_set &= ~AE_SET_SYMLINK; + else + entry->ae_set |= AE_SET_SYMLINK; } void @@ -1292,42 +1401,54 @@ archive_entry_set_symlink_type(struct archive_entry *entry, int type) void archive_entry_set_symlink_utf8(struct archive_entry *entry, const char *linkname) { - archive_mstring_copy_utf8(&entry->ae_symlink, linkname); - if (linkname != NULL) - entry->ae_set |= AE_SET_SYMLINK; - else + if (linkname == NULL && (entry->ae_set & AE_SET_HARDLINK)) + return; + archive_mstring_copy_utf8(&entry->ae_linkname, linkname); + entry->ae_set &= ~AE_SET_HARDLINK; + if (linkname == NULL) entry->ae_set &= ~AE_SET_SYMLINK; + else + entry->ae_set |= AE_SET_SYMLINK; } void archive_entry_copy_symlink(struct archive_entry *entry, const char *linkname) { - archive_mstring_copy_mbs(&entry->ae_symlink, linkname); - if (linkname != NULL) - entry->ae_set |= AE_SET_SYMLINK; - else + if (linkname == NULL && (entry->ae_set & AE_SET_HARDLINK)) + return; + archive_mstring_copy_mbs(&entry->ae_linkname, linkname); + entry->ae_set &= ~AE_SET_HARDLINK; + if (linkname == NULL) entry->ae_set &= ~AE_SET_SYMLINK; + else + entry->ae_set |= AE_SET_SYMLINK; } void archive_entry_copy_symlink_w(struct archive_entry *entry, const wchar_t *linkname) { - archive_mstring_copy_wcs(&entry->ae_symlink, linkname); - if (linkname != NULL) - entry->ae_set |= AE_SET_SYMLINK; - else + if (linkname == NULL && (entry->ae_set & AE_SET_HARDLINK)) + return; + archive_mstring_copy_wcs(&entry->ae_linkname, linkname); + entry->ae_set &= ~AE_SET_HARDLINK; + if (linkname == NULL) entry->ae_set &= ~AE_SET_SYMLINK; + else + entry->ae_set |= AE_SET_SYMLINK; } int archive_entry_update_symlink_utf8(struct archive_entry *entry, const char *linkname) { - if (linkname != NULL) - entry->ae_set |= AE_SET_SYMLINK; - else + if (linkname == NULL && (entry->ae_set & AE_SET_HARDLINK)) + return (0); + entry->ae_set &= ~AE_SET_HARDLINK; + if (linkname == NULL) entry->ae_set &= ~AE_SET_SYMLINK; + else + entry->ae_set |= AE_SET_SYMLINK; if (archive_mstring_update_utf8(entry->archive, - &entry->ae_symlink, linkname) == 0) + &entry->ae_linkname, linkname) == 0) return (1); if (errno == ENOMEM) __archive_errx(1, "No memory"); @@ -1340,20 +1461,27 @@ _archive_entry_copy_symlink_l(struct archive_entry *entry, { int r; - r = archive_mstring_copy_mbs_len_l(&entry->ae_symlink, + if (linkname == NULL && (entry->ae_set & AE_SET_HARDLINK)) + return (0); + entry->ae_set &= ~AE_SET_HARDLINK; + r = archive_mstring_copy_mbs_len_l(&entry->ae_linkname, linkname, len, sc); - if (linkname != NULL && r == 0) - entry->ae_set |= AE_SET_SYMLINK; - else + if (linkname == NULL || r != 0) entry->ae_set &= ~AE_SET_SYMLINK; + else + entry->ae_set |= AE_SET_SYMLINK; return (r); } void archive_entry_set_uid(struct archive_entry *entry, la_int64_t u) { + if (u < 0) { + u = 0; + } entry->stat_valid = 0; entry->ae_stat.aest_uid = u; + entry->ae_set |= AE_SET_UID; } void @@ -1967,7 +2095,7 @@ ae_fflagstostr(unsigned long bitset, unsigned long bitclear) if (length == 0) return (NULL); - string = (char *)malloc(length); + string = malloc(length); if (string == NULL) return (NULL); @@ -2003,7 +2131,7 @@ ae_fflagstostr(unsigned long bitset, unsigned long bitclear) * provided string. */ static const char * -ae_strtofflags(const char *s, unsigned long *setp, unsigned long *clrp) +ae_strtofflags(const char *s, size_t l, unsigned long *setp, unsigned long *clrp) { const char *start, *end; const struct flag *flag; @@ -2014,15 +2142,19 @@ ae_strtofflags(const char *s, unsigned long *setp, unsigned long *clrp) start = s; failed = NULL; /* Find start of first token. */ - while (*start == '\t' || *start == ' ' || *start == ',') + while (l > 0 && (*start == '\t' || *start == ' ' || *start == ',')) { start++; - while (*start != '\0') { + l--; + } + while (l > 0) { size_t length; /* Locate end of token. */ end = start; - while (*end != '\0' && *end != '\t' && - *end != ' ' && *end != ',') + while (l > 0 && *end != '\t' && + *end != ' ' && *end != ',') { end++; + l--; + } length = end - start; for (flag = fileflags; flag->name != NULL; flag++) { size_t flag_length = strlen(flag->name); @@ -2046,8 +2178,10 @@ ae_strtofflags(const char *s, unsigned long *setp, unsigned long *clrp) /* Find start of next token. */ start = end; - while (*start == '\t' || *start == ' ' || *start == ',') + while (l > 0 && (*start == '\t' || *start == ' ' || *start == ',')) { start++; + l--; + } } diff --git a/Utilities/cmlibarchive/libarchive/archive_entry.h b/Utilities/cmlibarchive/libarchive/archive_entry.h index 91ef0c97e40..6ab05715d70 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry.h +++ b/Utilities/cmlibarchive/libarchive/archive_entry.h @@ -22,15 +22,13 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_entry.h 201096 2009-12-28 02:41:27Z kientzle $ */ #ifndef ARCHIVE_ENTRY_H_INCLUDED #define ARCHIVE_ENTRY_H_INCLUDED /* Note: Compiler will complain if this does not match archive.h! */ -#define ARCHIVE_VERSION_NUMBER 3006002 +#define ARCHIVE_VERSION_NUMBER 3007009 /* * Note: archive_entry.h is for use outside of libarchive; the @@ -248,17 +246,21 @@ __LA_DECL int archive_entry_dev_is_set(struct archive_entry *); __LA_DECL dev_t archive_entry_devmajor(struct archive_entry *); __LA_DECL dev_t archive_entry_devminor(struct archive_entry *); __LA_DECL __LA_MODE_T archive_entry_filetype(struct archive_entry *); +__LA_DECL int archive_entry_filetype_is_set(struct archive_entry *); __LA_DECL void archive_entry_fflags(struct archive_entry *, unsigned long * /* set */, unsigned long * /* clear */); __LA_DECL const char *archive_entry_fflags_text(struct archive_entry *); __LA_DECL la_int64_t archive_entry_gid(struct archive_entry *); +__LA_DECL int archive_entry_gid_is_set(struct archive_entry *); __LA_DECL const char *archive_entry_gname(struct archive_entry *); __LA_DECL const char *archive_entry_gname_utf8(struct archive_entry *); __LA_DECL const wchar_t *archive_entry_gname_w(struct archive_entry *); +__LA_DECL void archive_entry_set_link_to_hardlink(struct archive_entry *); __LA_DECL const char *archive_entry_hardlink(struct archive_entry *); __LA_DECL const char *archive_entry_hardlink_utf8(struct archive_entry *); __LA_DECL const wchar_t *archive_entry_hardlink_w(struct archive_entry *); +__LA_DECL int archive_entry_hardlink_is_set(struct archive_entry *); __LA_DECL la_int64_t archive_entry_ino(struct archive_entry *); __LA_DECL la_int64_t archive_entry_ino64(struct archive_entry *); __LA_DECL int archive_entry_ino_is_set(struct archive_entry *); @@ -271,6 +273,8 @@ __LA_DECL const char *archive_entry_pathname(struct archive_entry *); __LA_DECL const char *archive_entry_pathname_utf8(struct archive_entry *); __LA_DECL const wchar_t *archive_entry_pathname_w(struct archive_entry *); __LA_DECL __LA_MODE_T archive_entry_perm(struct archive_entry *); +__LA_DECL int archive_entry_perm_is_set(struct archive_entry *); +__LA_DECL int archive_entry_rdev_is_set(struct archive_entry *); __LA_DECL dev_t archive_entry_rdev(struct archive_entry *); __LA_DECL dev_t archive_entry_rdevmajor(struct archive_entry *); __LA_DECL dev_t archive_entry_rdevminor(struct archive_entry *); @@ -279,11 +283,13 @@ __LA_DECL const wchar_t *archive_entry_sourcepath_w(struct archive_entry *); __LA_DECL la_int64_t archive_entry_size(struct archive_entry *); __LA_DECL int archive_entry_size_is_set(struct archive_entry *); __LA_DECL const char *archive_entry_strmode(struct archive_entry *); +__LA_DECL void archive_entry_set_link_to_symlink(struct archive_entry *); __LA_DECL const char *archive_entry_symlink(struct archive_entry *); __LA_DECL const char *archive_entry_symlink_utf8(struct archive_entry *); __LA_DECL int archive_entry_symlink_type(struct archive_entry *); __LA_DECL const wchar_t *archive_entry_symlink_w(struct archive_entry *); __LA_DECL la_int64_t archive_entry_uid(struct archive_entry *); +__LA_DECL int archive_entry_uid_is_set(struct archive_entry *); __LA_DECL const char *archive_entry_uname(struct archive_entry *); __LA_DECL const char *archive_entry_uname_utf8(struct archive_entry *); __LA_DECL const wchar_t *archive_entry_uname_w(struct archive_entry *); @@ -319,6 +325,8 @@ __LA_DECL void archive_entry_set_fflags(struct archive_entry *, /* Note that all recognized tokens are processed, regardless. */ __LA_DECL const char *archive_entry_copy_fflags_text(struct archive_entry *, const char *); +__LA_DECL const char *archive_entry_copy_fflags_text_len(struct archive_entry *, + const char *, size_t); __LA_DECL const wchar_t *archive_entry_copy_fflags_text_w(struct archive_entry *, const wchar_t *); __LA_DECL void archive_entry_set_gid(struct archive_entry *, la_int64_t); diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_acl.3 b/Utilities/cmlibarchive/libarchive/archive_entry_acl.3 index 50dd642c20c..4d0d8b50ed0 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_acl.3 +++ b/Utilities/cmlibarchive/libarchive/archive_entry_acl.3 @@ -383,7 +383,7 @@ Prefix each default ACL entry with the word The mask and other ACLs don not contain a double colon. .El .Pp -The following flags are effecive only on NFSv4 ACL: +The following flags are effective only on NFSv4 ACL: .Bl -tag -offset indent -compact -width ARCHIV .It Dv ARCHIVE_ENTRY_ACL_STYLE_COMPACT Do not output minus characters for unset permissions and flags in NFSv4 ACL diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_copy_bhfi.c b/Utilities/cmlibarchive/libarchive/archive_entry_copy_bhfi.c index 77bf38e450f..d5317a5eab5 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_copy_bhfi.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry_copy_bhfi.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #include "archive_private.h" #include "archive_entry.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_copy_stat.c b/Utilities/cmlibarchive/libarchive/archive_entry_copy_stat.c index ac83868e8f8..f9c2e8469b1 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_copy_stat.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry_copy_stat.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_copy_stat.c 189466 2009-03-07 00:52:02Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_link_resolver.c b/Utilities/cmlibarchive/libarchive/archive_entry_link_resolver.c index c7d59497a7c..c2fd6895f21 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_link_resolver.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry_link_resolver.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_link_resolver.c 201100 2009-12-28 03:05:31Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -202,16 +201,26 @@ archive_entry_linkify(struct archive_entry_linkresolver *res, le = find_entry(res, *e); if (le != NULL) { archive_entry_unset_size(*e); +#if defined(_WIN32) && !defined(__CYGWIN__) + archive_entry_copy_hardlink_w(*e, + archive_entry_pathname_w(le->canonical)); +#else archive_entry_copy_hardlink(*e, archive_entry_pathname(le->canonical)); +#endif } else insert_entry(res, *e); return; case ARCHIVE_ENTRY_LINKIFY_LIKE_MTREE: le = find_entry(res, *e); if (le != NULL) { +#if defined(_WIN32) && !defined(__CYGWIN__) + archive_entry_copy_hardlink_w(*e, + archive_entry_pathname_w(le->canonical)); +#else archive_entry_copy_hardlink(*e, archive_entry_pathname(le->canonical)); +#endif } else insert_entry(res, *e); return; @@ -230,8 +239,13 @@ archive_entry_linkify(struct archive_entry_linkresolver *res, le->entry = t; /* Make the old entry into a hardlink. */ archive_entry_unset_size(*e); +#if defined(_WIN32) && !defined(__CYGWIN__) + archive_entry_copy_hardlink_w(*e, + archive_entry_pathname_w(le->canonical)); +#else archive_entry_copy_hardlink(*e, archive_entry_pathname(le->canonical)); +#endif /* If we ran out of links, return the * final entry as well. */ if (le->links == 0) { diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_locale.h b/Utilities/cmlibarchive/libarchive/archive_entry_locale.h index 803c0368bb6..1b90c57eabf 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_locale.h +++ b/Utilities/cmlibarchive/libarchive/archive_entry_locale.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ #ifndef ARCHIVE_ENTRY_LOCALE_H_INCLUDED diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_perms.3 b/Utilities/cmlibarchive/libarchive/archive_entry_perms.3 index 0291b7b4988..4bfbfc3c781 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_perms.3 +++ b/Utilities/cmlibarchive/libarchive/archive_entry_perms.3 @@ -150,6 +150,7 @@ character strings at the same time. .Fn archive_entry_set_XXX is an alias for .Fn archive_entry_copy_XXX . +The strings are copied, and don't need to outlive the call. .Ss File Flags File flags are transparently converted between a bitmap representation and a textual format. diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_private.h b/Utilities/cmlibarchive/libarchive/archive_entry_private.h index cf4deb24ec8..15f2a8ee284 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_entry_private.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_entry_private.h 201096 2009-12-28 02:41:27Z kientzle $ */ #ifndef ARCHIVE_ENTRY_PRIVATE_H_INCLUDED @@ -147,6 +145,11 @@ struct archive_entry { #define AE_SET_SIZE 64 #define AE_SET_INO 128 #define AE_SET_DEV 256 +#define AE_SET_PERM 512 +#define AE_SET_FILETYPE 1024 +#define AE_SET_UID 2048 +#define AE_SET_GID 4096 +#define AE_SET_RDEV 8192 /* * Use aes here so that we get transparent mbs<->wcs conversions. @@ -155,9 +158,8 @@ struct archive_entry { unsigned long ae_fflags_set; /* Bitmap fflags */ unsigned long ae_fflags_clear; struct archive_mstring ae_gname; /* Name of owning group */ - struct archive_mstring ae_hardlink; /* Name of target for hardlink */ + struct archive_mstring ae_linkname; /* Name of target for hardlink or symlink */ struct archive_mstring ae_pathname; /* Name of entry */ - struct archive_mstring ae_symlink; /* symlink contents */ struct archive_mstring ae_uname; /* Name of owner */ /* Not used within libarchive; useful for some clients. */ diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_sparse.c b/Utilities/cmlibarchive/libarchive/archive_entry_sparse.c index 74917b37b80..c430896119f 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_sparse.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry_sparse.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #include "archive.h" #include "archive_entry.h" @@ -77,7 +76,7 @@ archive_entry_sparse_add_entry(struct archive_entry *entry, } } - if ((sp = (struct ae_sparse *)malloc(sizeof(*sp))) == NULL) + if ((sp = malloc(sizeof(*sp))) == NULL) /* XXX Error XXX */ return; diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_stat.c b/Utilities/cmlibarchive/libarchive/archive_entry_stat.c index 71a407b1f8b..c4906838ed0 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_stat.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry_stat.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_stat.c 201100 2009-12-28 03:05:31Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_strmode.c b/Utilities/cmlibarchive/libarchive/archive_entry_strmode.c index af2517a3219..5faa2faeefa 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_strmode.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry_strmode.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry_strmode.c,v 1.4 2008/06/15 05:14:01 kientzle Exp $"); #ifdef HAVE_SYS_STAT_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_time.3 b/Utilities/cmlibarchive/libarchive/archive_entry_time.3 index d0563eaef43..0f1dbb02512 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_time.3 +++ b/Utilities/cmlibarchive/libarchive/archive_entry_time.3 @@ -23,8 +23,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_ENTRY_TIME 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_entry_xattr.c b/Utilities/cmlibarchive/libarchive/archive_entry_xattr.c index 5fe726b99d0..b92e1878bc4 100644 --- a/Utilities/cmlibarchive/libarchive/archive_entry_xattr.c +++ b/Utilities/cmlibarchive/libarchive/archive_entry_xattr.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_xattr.c 201096 2009-12-28 02:41:27Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -91,7 +90,7 @@ archive_entry_xattr_add_entry(struct archive_entry *entry, { struct ae_xattr *xp; - if ((xp = (struct ae_xattr *)malloc(sizeof(struct ae_xattr))) == NULL) + if ((xp = malloc(sizeof(struct ae_xattr))) == NULL) __archive_errx(1, "Out of memory"); if ((xp->name = strdup(name)) == NULL) diff --git a/Utilities/cmlibarchive/libarchive/archive_getdate.c b/Utilities/cmlibarchive/libarchive/archive_getdate.c index 5b0b7754020..e9c6545d7c9 100644 --- a/Utilities/cmlibarchive/libarchive/archive_getdate.c +++ b/Utilities/cmlibarchive/libarchive/archive_getdate.c @@ -30,10 +30,6 @@ #ifndef CM_GET_DATE #include "archive_platform.h" #endif -#ifdef __FreeBSD__ -#include -__FBSDID("$FreeBSD$"); -#endif #include #include @@ -700,13 +696,9 @@ Convert(time_t Month, time_t Day, time_t Year, time_t Julian; int i; struct tm *ltime; -#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S) +#if defined(HAVE_LOCALTIME_R) || defined(HAVE_LOCALTIME_S) struct tm tmbuf; #endif -#if defined(HAVE__LOCALTIME64_S) - errno_t terr; - __time64_t tmptime; -#endif if (Year < 69) Year += 2000; @@ -733,15 +725,10 @@ Convert(time_t Month, time_t Day, time_t Year, Julian *= DAY; Julian += Timezone; Julian += Hours * HOUR + Minutes * MINUTE + Seconds; -#if defined(HAVE_LOCALTIME_R) +#if defined(HAVE_LOCALTIME_S) + ltime = localtime_s(&tmbuf, &Julian) ? NULL : &tmbuf; +#elif defined(HAVE_LOCALTIME_R) ltime = localtime_r(&Julian, &tmbuf); -#elif defined(HAVE__LOCALTIME64_S) - tmptime = Julian; - terr = _localtime64_s(&tmbuf, &tmptime); - if (terr) - ltime = NULL; - else - ltime = &tmbuf; #else ltime = localtime(&Julian); #endif @@ -757,36 +744,21 @@ DSTcorrect(time_t Start, time_t Future) time_t StartDay; time_t FutureDay; struct tm *ltime; -#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S) +#if defined(HAVE_LOCALTIME_R) || defined(HAVE_LOCALTIME_S) struct tm tmbuf; #endif -#if defined(HAVE__LOCALTIME64_S) - errno_t terr; - __time64_t tmptime; -#endif - -#if defined(HAVE_LOCALTIME_R) +#if defined(HAVE_LOCALTIME_S) + ltime = localtime_s(&tmbuf, &Start) ? NULL : &tmbuf; +#elif defined(HAVE_LOCALTIME_R) ltime = localtime_r(&Start, &tmbuf); -#elif defined(HAVE__LOCALTIME64_S) - tmptime = Start; - terr = _localtime64_s(&tmbuf, &tmptime); - if (terr) - ltime = NULL; - else - ltime = &tmbuf; #else ltime = localtime(&Start); #endif StartDay = (ltime->tm_hour + 1) % 24; -#if defined(HAVE_LOCALTIME_R) +#if defined(HAVE_LOCALTIME_S) + ltime = localtime_s(&tmbuf, &Future) ? NULL : &tmbuf; +#elif defined(HAVE_LOCALTIME_R) ltime = localtime_r(&Future, &tmbuf); -#elif defined(HAVE__LOCALTIME64_S) - tmptime = Future; - terr = _localtime64_s(&tmbuf, &tmptime); - if (terr) - ltime = NULL; - else - ltime = &tmbuf; #else ltime = localtime(&Future); #endif @@ -801,24 +773,15 @@ RelativeDate(time_t Start, time_t zone, int dstmode, { struct tm *tm; time_t t, now; -#if defined(HAVE_GMTIME_R) || defined(HAVE__GMTIME64_S) +#if defined(HAVE_GMTIME_R) || defined(HAVE_GMTIME_S) struct tm tmbuf; #endif -#if defined(HAVE__GMTIME64_S) - errno_t terr; - __time64_t tmptime; -#endif t = Start - zone; -#if defined(HAVE_GMTIME_R) +#if defined(HAVE_GMTIME_S) + tm = gmtime_s(&tmbuf, &t) ? NULL : &tmbuf; +#elif defined(HAVE_GMTIME_R) tm = gmtime_r(&t, &tmbuf); -#elif defined(HAVE__GMTIME64_S) - tmptime = t; - terr = _gmtime64_s(&tmbuf, &tmptime); - if (terr) - tm = NULL; - else - tm = &tmbuf; #else tm = gmtime(&t); #endif @@ -837,25 +800,16 @@ RelativeMonth(time_t Start, time_t Timezone, time_t RelMonth) struct tm *tm; time_t Month; time_t Year; -#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S) +#if defined(HAVE_LOCALTIME_R) || defined(HAVE_LOCALTIME_S) struct tm tmbuf; #endif -#if defined(HAVE__LOCALTIME64_S) - errno_t terr; - __time64_t tmptime; -#endif if (RelMonth == 0) return 0; -#if defined(HAVE_LOCALTIME_R) +#if defined(HAVE_LOCALTIME_S) + tm = localtime_s(&tmbuf, &Start) ? NULL : &tmbuf; +#elif defined(HAVE_LOCALTIME_R) tm = localtime_r(&Start, &tmbuf); -#elif defined(HAVE__LOCALTIME64_S) - tmptime = Start; - terr = _localtime64_s(&tmbuf, &tmptime); - if (terr) - tm = NULL; - else - tm = &tmbuf; #else tm = localtime(&Start); #endif @@ -995,10 +949,6 @@ __archive_get_date(time_t now, const char *p) time_t Start; time_t tod; long tzone; -#if defined(HAVE__LOCALTIME64_S) || defined(HAVE__GMTIME64_S) - errno_t terr; - __time64_t tmptime; -#endif /* Clear out the parsed token array. */ memset(tokens, 0, sizeof(tokens)); @@ -1007,36 +957,26 @@ __archive_get_date(time_t now, const char *p) gds = &_gds; /* Look up the current time. */ -#if defined(HAVE_LOCALTIME_R) +#if defined(HAVE_LOCALTIME_S) + tm = localtime_s(&local, &now) ? NULL : &local; +#elif defined(HAVE_LOCALTIME_R) tm = localtime_r(&now, &local); -#elif defined(HAVE__LOCALTIME64_S) - tmptime = now; - terr = _localtime64_s(&local, &tmptime); - if (terr) - tm = NULL; - else - tm = &local; #else memset(&local, 0, sizeof(local)); tm = localtime(&now); #endif if (tm == NULL) return -1; -#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE__LOCALTIME64_S) +#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE_LOCALTIME_S) local = *tm; #endif /* Look up UTC if we can and use that to determine the current * timezone offset. */ -#if defined(HAVE_GMTIME_R) +#if defined(HAVE_GMTIME_S) + gmt_ptr = gmtime_s(&gmt, &now) ? NULL : &gmt; +#elif defined(HAVE_GMTIME_R) gmt_ptr = gmtime_r(&now, &gmt); -#elif defined(HAVE__GMTIME64_S) - tmptime = now; - terr = _gmtime64_s(&gmt, &tmptime); - if (terr) - gmt_ptr = NULL; - else - gmt_ptr = &gmt; #else memset(&gmt, 0, sizeof(gmt)); gmt_ptr = gmtime(&now); @@ -1078,15 +1018,10 @@ __archive_get_date(time_t now, const char *p) * time components instead of the local timezone. */ if (gds->HaveZone && gmt_ptr != NULL) { now -= gds->Timezone; -#if defined(HAVE_GMTIME_R) +#if defined(HAVE_GMTIME_S) + gmt_ptr = gmtime_s(&gmt, &now) ? NULL : &gmt; +#elif defined(HAVE_GMTIME_R) gmt_ptr = gmtime_r(&now, &gmt); -#elif defined(HAVE__GMTIME64_S) - tmptime = now; - terr = _gmtime64_s(&gmt, &tmptime); - if (terr) - gmt_ptr = NULL; - else - gmt_ptr = &gmt; #else gmt_ptr = gmtime(&now); #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_getdate.h b/Utilities/cmlibarchive/libarchive/archive_getdate.h index 900a8f692e9..cfd49ddf7eb 100644 --- a/Utilities/cmlibarchive/libarchive/archive_getdate.h +++ b/Utilities/cmlibarchive/libarchive/archive_getdate.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ #ifndef ARCHIVE_GETDATE_H_INCLUDED diff --git a/Utilities/cmlibarchive/libarchive/archive_hmac.c b/Utilities/cmlibarchive/libarchive/archive_hmac.c index 012fe159621..210cca70744 100644 --- a/Utilities/cmlibarchive/libarchive/archive_hmac.c +++ b/Utilities/cmlibarchive/libarchive/archive_hmac.c @@ -74,7 +74,7 @@ __hmac_sha1_cleanup(archive_hmac_sha1_ctx *ctx) memset(ctx, 0, sizeof(*ctx)); } -#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) +#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA #ifndef BCRYPT_HASH_REUSABLE_FLAG # define BCRYPT_HASH_REUSABLE_FLAG 0x00000020 @@ -231,15 +231,20 @@ static int __hmac_sha1_init(archive_hmac_sha1_ctx *ctx, const uint8_t *key, size_t key_len) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L - OSSL_PARAM params[2]; + EVP_MAC *mac; - EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL); + char sha1[] = "SHA1"; + OSSL_PARAM params[] = { + OSSL_PARAM_utf8_string("digest", sha1, sizeof(sha1) - 1), + OSSL_PARAM_END + }; + + mac = EVP_MAC_fetch(NULL, "HMAC", NULL); *ctx = EVP_MAC_CTX_new(mac); + EVP_MAC_free(mac); if (*ctx == NULL) return -1; - EVP_MAC_free(mac); - params[0] = OSSL_PARAM_construct_utf8_string("digest", "SHA1", 0); - params[1] = OSSL_PARAM_construct_end(); + EVP_MAC_init(*ctx, key, key_len, params); #else *ctx = HMAC_CTX_new(); diff --git a/Utilities/cmlibarchive/libarchive/archive_hmac_private.h b/Utilities/cmlibarchive/libarchive/archive_hmac_private.h index 50044a045e3..1b24ddd1bc9 100644 --- a/Utilities/cmlibarchive/libarchive/archive_hmac_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_hmac_private.h @@ -52,7 +52,7 @@ int __libarchive_hmac_build_hack(void); typedef CCHmacContext archive_hmac_sha1_ctx; -#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) +#elif defined(_WIN32) && !defined(__CYGWIN__) && defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA #include typedef struct { @@ -77,6 +77,8 @@ typedef struct hmac_sha1_ctx archive_hmac_sha1_ctx; #include #include #if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include + typedef EVP_MAC_CTX *archive_hmac_sha1_ctx; #else diff --git a/Utilities/cmlibarchive/libarchive/archive_match.c b/Utilities/cmlibarchive/libarchive/archive_match.c index 2de00458be9..f74de99c7ab 100644 --- a/Utilities/cmlibarchive/libarchive/archive_match.c +++ b/Utilities/cmlibarchive/libarchive/archive_match.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -47,7 +46,7 @@ __FBSDID("$FreeBSD$"); struct match { struct match *next; - int matches; + int matched; struct archive_mstring pattern; }; @@ -221,7 +220,7 @@ archive_match_new(void) { struct archive_match *a; - a = (struct archive_match *)calloc(1, sizeof(*a)); + a = calloc(1, sizeof(*a)); if (a == NULL) return (NULL); a->archive.magic = ARCHIVE_MATCH_MAGIC; @@ -600,17 +599,14 @@ add_pattern_from_file(struct archive_match *a, struct match_list *mlist, int64_t offset; int r; - ar = archive_read_new(); + ar = archive_read_new(); if (ar == NULL) { archive_set_error(&(a->archive), ENOMEM, "No memory"); return (ARCHIVE_FATAL); } r = archive_read_support_format_raw(ar); -#ifdef __clang_analyzer__ - /* Tolerate deadcode.DeadStores to avoid modifying upstream. */ - (void)r; -#endif - r = archive_read_support_format_empty(ar); + if (r == ARCHIVE_OK) + r = archive_read_support_format_empty(ar); if (r != ARCHIVE_OK) { archive_copy_error(&(a->archive), ar); archive_read_free(ar); @@ -729,12 +725,12 @@ path_excluded(struct archive_match *a, int mbs, const void *pathname) matched = NULL; for (match = a->inclusions.first; match != NULL; match = match->next){ - if (match->matches == 0 && + if (!match->matched && (r = match_path_inclusion(a, match, mbs, pathname)) != 0) { if (r < 0) return (r); a->inclusions.unmatched_count--; - match->matches++; + match->matched = 1; matched = match; } } @@ -757,11 +753,10 @@ path_excluded(struct archive_match *a, int mbs, const void *pathname) for (match = a->inclusions.first; match != NULL; match = match->next){ /* We looked at previously-unmatched inclusions already. */ - if (match->matches > 0 && + if (match->matched && (r = match_path_inclusion(a, match, mbs, pathname)) != 0) { if (r < 0) return (r); - match->matches++; return (0); } } @@ -884,7 +879,7 @@ match_list_unmatched_inclusions_next(struct archive_match *a, for (m = list->unmatched_next; m != NULL; m = m->next) { int r; - if (m->matches) + if (m->matched) continue; if (mbs) { const char *p; @@ -1321,7 +1316,7 @@ cmp_node_mbs(const struct archive_rb_node *n1, return (-1); return (strcmp(p1, p2)); } - + static int cmp_key_mbs(const struct archive_rb_node *n, const void *key) { @@ -1350,7 +1345,7 @@ cmp_node_wcs(const struct archive_rb_node *n1, return (-1); return (wcscmp(p1, p2)); } - + static int cmp_key_wcs(const struct archive_rb_node *n, const void *key) { @@ -1798,7 +1793,7 @@ match_owner_name_mbs(struct archive_match *a, struct match_list *list, < 0 && errno == ENOMEM) return (error_nomem(a)); if (p != NULL && strcmp(p, name) == 0) { - m->matches++; + m->matched = 1; return (1); } } @@ -1819,7 +1814,7 @@ match_owner_name_wcs(struct archive_match *a, struct match_list *list, < 0 && errno == ENOMEM) return (error_nomem(a)); if (p != NULL && wcscmp(p, name) == 0) { - m->matches++; + m->matched = 1; return (1); } } diff --git a/Utilities/cmlibarchive/libarchive/archive_openssl_evp_private.h b/Utilities/cmlibarchive/libarchive/archive_openssl_evp_private.h index ebb06702d0c..d5ba7b0d60d 100644 --- a/Utilities/cmlibarchive/libarchive/archive_openssl_evp_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_openssl_evp_private.h @@ -33,12 +33,13 @@ #include #include -#if OPENSSL_VERSION_NUMBER < 0x10100000L +#if OPENSSL_VERSION_NUMBER < 0x10100000L || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) #include /* malloc, free */ #include /* memset */ static inline EVP_MD_CTX *EVP_MD_CTX_new(void) { - EVP_MD_CTX *ctx = (EVP_MD_CTX *)calloc(1, sizeof(EVP_MD_CTX)); + EVP_MD_CTX *ctx = calloc(1, sizeof(EVP_MD_CTX)); return ctx; } diff --git a/Utilities/cmlibarchive/libarchive/archive_openssl_hmac_private.h b/Utilities/cmlibarchive/libarchive/archive_openssl_hmac_private.h index 25c8dda654f..8ed76260dde 100644 --- a/Utilities/cmlibarchive/libarchive/archive_openssl_hmac_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_openssl_hmac_private.h @@ -39,7 +39,7 @@ #include /* memset */ static inline HMAC_CTX *HMAC_CTX_new(void) { - HMAC_CTX *ctx = (HMAC_CTX *)calloc(1, sizeof(HMAC_CTX)); + HMAC_CTX *ctx = calloc(1, sizeof(HMAC_CTX)); return ctx; } diff --git a/Utilities/cmlibarchive/libarchive/archive_options.c b/Utilities/cmlibarchive/libarchive/archive_options.c index 6496025a5f6..92647c9b41c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_options.c +++ b/Utilities/cmlibarchive/libarchive/archive_options.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_options_private.h b/Utilities/cmlibarchive/libarchive/archive_options_private.h index 9a7f8080d2f..3e49222dabd 100644 --- a/Utilities/cmlibarchive/libarchive/archive_options_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_options_private.h @@ -27,8 +27,6 @@ #define ARCHIVE_OPTIONS_PRIVATE_H_INCLUDED #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #include "archive_private.h" typedef int (*option_handler)(struct archive *a, diff --git a/Utilities/cmlibarchive/libarchive/archive_pack_dev.c b/Utilities/cmlibarchive/libarchive/archive_pack_dev.c index d95444d979f..3c6209b9840 100644 --- a/Utilities/cmlibarchive/libarchive/archive_pack_dev.c +++ b/Utilities/cmlibarchive/libarchive/archive_pack_dev.c @@ -33,13 +33,6 @@ #include "archive_platform.h" -#if HAVE_SYS_CDEFS_H -#include -#endif -#if !defined(lint) -__RCSID("$NetBSD$"); -#endif /* not lint */ - #ifdef HAVE_LIMITS_H #include #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_pathmatch.c b/Utilities/cmlibarchive/libarchive/archive_pathmatch.c index 0867a268eef..19e0889ffe5 100644 --- a/Utilities/cmlibarchive/libarchive/archive_pathmatch.c +++ b/Utilities/cmlibarchive/libarchive/archive_pathmatch.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_STRING_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_pathmatch.h b/Utilities/cmlibarchive/libarchive/archive_pathmatch.h index 9995142921e..3f406ff7435 100644 --- a/Utilities/cmlibarchive/libarchive/archive_pathmatch.h +++ b/Utilities/cmlibarchive/libarchive/archive_pathmatch.h @@ -22,8 +22,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ #ifndef ARCHIVE_PATHMATCH_H diff --git a/Utilities/cmlibarchive/libarchive/archive_platform.h b/Utilities/cmlibarchive/libarchive/archive_platform.h index 63b255ee9db..625d2e7818c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_platform.h +++ b/Utilities/cmlibarchive/libarchive/archive_platform.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_platform.h 201090 2009-12-28 02:22:04Z kientzle $ */ /* !!ONLY FOR USE INTERNALLY TO LIBARCHIVE!! */ @@ -63,6 +61,11 @@ # endif #endif +/* For cygwin, to avoid missing LONG, ULONG, PUCHAR, ... definitions */ +#ifdef __CYGWIN__ +#include +#endif + /* It should be possible to get rid of this by extending the feature-test * macros to cover Windows API functions, probably along with non-trivial * refactoring of code to find structures that sit more cleanly on top of @@ -87,19 +90,6 @@ * headers as required. */ -/* Get a real definition for __FBSDID or __RCSID if we can */ -#if HAVE_SYS_CDEFS_H -#include -#endif - -/* If not, define them so as to avoid dangling semicolons. */ -#ifndef __FBSDID -#define __FBSDID(a) struct _undefined_hack -#endif -#ifndef __RCSID -#define __RCSID(a) struct _undefined_hack -#endif - /* Old glibc mbsnrtowcs fails assertions in our use case. */ #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ <= 1 # undef HAVE_MBSNRTOWCS diff --git a/Utilities/cmlibarchive/libarchive/archive_platform_acl.h b/Utilities/cmlibarchive/libarchive/archive_platform_acl.h index 264e6de375a..48556f87fee 100644 --- a/Utilities/cmlibarchive/libarchive/archive_platform_acl.h +++ b/Utilities/cmlibarchive/libarchive/archive_platform_acl.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ /* !!ONLY FOR USE INTERNALLY TO LIBARCHIVE!! */ diff --git a/Utilities/cmlibarchive/libarchive/archive_platform_xattr.h b/Utilities/cmlibarchive/libarchive/archive_platform_xattr.h index ad4b90ab7b2..2ae222f61ef 100644 --- a/Utilities/cmlibarchive/libarchive/archive_platform_xattr.h +++ b/Utilities/cmlibarchive/libarchive/archive_platform_xattr.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ /* !!ONLY FOR USE INTERNALLY TO LIBARCHIVE!! */ diff --git a/Utilities/cmlibarchive/libarchive/archive_ppmd7.c b/Utilities/cmlibarchive/libarchive/archive_ppmd7.c index cc3f778203f..f0369835b83 100644 --- a/Utilities/cmlibarchive/libarchive/archive_ppmd7.c +++ b/Utilities/cmlibarchive/libarchive/archive_ppmd7.c @@ -138,7 +138,7 @@ static Bool Ppmd7_Alloc(CPpmd7 *p, UInt32 size) #else 4 - (size & 3); #endif - if ((p->Base = (Byte *)malloc(p->AlignOffset + size + if ((p->Base = malloc(p->AlignOffset + size #ifndef PPMD_32BIT + UNIT_SIZE #endif @@ -287,9 +287,14 @@ static void *AllocUnits(CPpmd7 *p, unsigned indx) return AllocUnitsRare(p, indx); } -#define MyMem12Cpy(dest, src, num) \ - { UInt32 *d = (UInt32 *)dest; const UInt32 *s = (const UInt32 *)src; UInt32 n = num; \ - do { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; s += 3; d += 3; } while(--n); } +#define MyMem12Cpy(dest, src, num) do { \ + UInt32 *d = (UInt32 *)dest; \ + const UInt32 *s = (const UInt32 *)src; \ + UInt32 n = num; \ + do { \ + d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; s += 3; d += 3; \ + } while(--n); \ +} while (0) static void *ShrinkUnits(CPpmd7 *p, void *oldPtr, unsigned oldNU, unsigned newNU) { diff --git a/Utilities/cmlibarchive/libarchive/archive_ppmd8.c b/Utilities/cmlibarchive/libarchive/archive_ppmd8.c index 272ca4c4f13..9eca95ac9ad 100644 --- a/Utilities/cmlibarchive/libarchive/archive_ppmd8.c +++ b/Utilities/cmlibarchive/libarchive/archive_ppmd8.c @@ -111,7 +111,7 @@ Bool Ppmd8_Alloc(CPpmd8 *p, UInt32 size) #else 4 - (size & 3); #endif - if ((p->Base = (Byte *)malloc(p->AlignOffset + size)) == 0) + if ((p->Base = malloc(p->AlignOffset + size)) == 0) return False; p->Size = size; } @@ -246,9 +246,14 @@ static void *AllocUnits(CPpmd8 *p, unsigned indx) return AllocUnitsRare(p, indx); } -#define MyMem12Cpy(dest, src, num) \ - { UInt32 *d = (UInt32 *)dest; const UInt32 *z = (const UInt32 *)src; UInt32 n = num; \ - do { d[0] = z[0]; d[1] = z[1]; d[2] = z[2]; z += 3; d += 3; } while (--n); } +#define MyMem12Cpy(dest, src, num) do { \ + UInt32 *d = (UInt32 *)dest; \ + const UInt32 *z = (const UInt32 *)src; \ + UInt32 n = num; \ + do { \ + d[0] = z[0]; d[1] = z[1]; d[2] = z[2]; z += 3; d += 3; \ + } while (--n); \ +} while (0) static void *ShrinkUnits(CPpmd8 *p, void *oldPtr, unsigned oldNU, unsigned newNU) { @@ -348,7 +353,9 @@ static void SetSuccessor(CPpmd_State *p, CPpmd_Void_Ref v) (p)->SuccessorHigh = (UInt16)(((UInt32)(v) >> 16) & 0xFFFF); } -#define RESET_TEXT(offs) { p->Text = p->Base + p->AlignOffset + (offs); } +#define RESET_TEXT(offs) do { \ + p->Text = p->Base + p->AlignOffset + (offs); \ +} while (0) static void RestartModel(CPpmd8 *p) { @@ -683,7 +690,7 @@ static CTX_PTR CreateSuccessors(CPpmd8 *p, Bool skip, CPpmd_State *s1, CTX_PTR c upState.Freq = (Byte)(1 + ((2 * cf <= s0) ? (5 * cf > s0) : ((cf + 2 * s0 - 3) / s0))); } - do + while (numPs != 0) { /* Create Child */ CTX_PTR c1; /* = AllocContext(p); */ @@ -704,8 +711,7 @@ static CTX_PTR CreateSuccessors(CPpmd8 *p, Bool skip, CPpmd_State *s1, CTX_PTR c SetSuccessor(ps[--numPs], REF(c1)); c = c1; } - while (numPs != 0); - + return c; } diff --git a/Utilities/cmlibarchive/libarchive/archive_ppmd_private.h b/Utilities/cmlibarchive/libarchive/archive_ppmd_private.h index 582803e5fd0..bc525b47e52 100644 --- a/Utilities/cmlibarchive/libarchive/archive_ppmd_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_ppmd_private.h @@ -109,8 +109,12 @@ typedef struct Byte Count; /* Count to next change of Shift */ } CPpmd_See; -#define Ppmd_See_Update(p) if ((p)->Shift < PPMD_PERIOD_BITS && --(p)->Count == 0) \ - { (p)->Summ <<= 1; (p)->Count = (Byte)(3 << (p)->Shift++); } +#define Ppmd_See_Update(p) do { \ + if ((p)->Shift < PPMD_PERIOD_BITS && --(p)->Count == 0) { \ + (p)->Summ <<= 1; \ + (p)->Count = (Byte)(3 << (p)->Shift++); \ + } \ +} while (0) typedef struct { @@ -144,8 +148,12 @@ typedef #endif CPpmd_Byte_Ref; -#define PPMD_SetAllBitsIn256Bytes(p) \ - { unsigned j; for (j = 0; j < 256 / sizeof(p[0]); j += 8) { \ - p[j+7] = p[j+6] = p[j+5] = p[j+4] = p[j+3] = p[j+2] = p[j+1] = p[j+0] = ~(size_t)0; }} +#define PPMD_SetAllBitsIn256Bytes(p) do { \ + unsigned j; \ + for (j = 0; j < 256 / sizeof(p[0]); j += 8) { \ + p[j+7] = p[j+6] = p[j+5] = p[j+4] = \ + p[j+3] = p[j+2] = p[j+1] = p[j+0] = ~(size_t)0; \ + } \ +} while (0) #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_private.h b/Utilities/cmlibarchive/libarchive/archive_private.h index b2a2cda250e..050fc63c0b2 100644 --- a/Utilities/cmlibarchive/libarchive/archive_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_private.h @@ -21,16 +21,16 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_private.h 201098 2009-12-28 02:58:14Z kientzle $ */ #ifndef ARCHIVE_PRIVATE_H_INCLUDED #define ARCHIVE_PRIVATE_H_INCLUDED #ifndef __LIBARCHIVE_BUILD +#ifndef __LIBARCHIVE_TEST #error This header is only to be used internally to libarchive. #endif +#endif #if HAVE_ICONV_H #include @@ -40,10 +40,12 @@ #include "archive_string.h" #if defined(__GNUC__) && (__GNUC__ > 2 || \ - (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) -#define __LA_DEAD __attribute__((__noreturn__)) + (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) +#define __LA_NORETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) +#define __LA_NORETURN __declspec(noreturn) #else -#define __LA_DEAD +#define __LA_NORETURN #endif #if defined(__GNUC__) && (__GNUC__ > 2 || \ @@ -153,14 +155,14 @@ int __archive_check_magic(struct archive *, unsigned int magic, return ARCHIVE_FATAL; \ } while (0) -void __archive_errx(int retvalue, const char *msg) __LA_DEAD; +__LA_NORETURN void __archive_errx(int retvalue, const char *msg); void __archive_ensure_cloexec_flag(int fd); int __archive_mktemp(const char *tmpdir); #if defined(_WIN32) && !defined(__CYGWIN__) -int __archive_mkstemp(wchar_t *template); +int __archive_mkstemp(wchar_t *templates); #else -int __archive_mkstemp(char *template); +int __archive_mkstemp(char *templates); #endif int __archive_clean(struct archive *); diff --git a/Utilities/cmlibarchive/libarchive/archive_random.c b/Utilities/cmlibarchive/libarchive/archive_random.c index 9d1aa493f0c..8c48d2d3b5a 100644 --- a/Utilities/cmlibarchive/libarchive/archive_random.c +++ b/Utilities/cmlibarchive/libarchive/archive_random.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_STDLIB_H #include @@ -51,16 +50,27 @@ __FBSDID("$FreeBSD$"); #include #endif -static void arc4random_buf(void *, size_t); +static void la_arc4random_buf(void *, size_t); #endif /* HAVE_ARC4RANDOM_BUF */ #include "archive.h" #include "archive_random_private.h" -#if defined(HAVE_WINCRYPT_H) && !defined(__CYGWIN__) +#if defined(_WIN32) && !defined(__CYGWIN__) +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA +/* don't use bcrypt when XP needs to be supported */ +#include + +/* Common in other bcrypt implementations, but missing from VS2008. */ +#ifndef BCRYPT_SUCCESS +#define BCRYPT_SUCCESS(r) ((NTSTATUS)(r) == STATUS_SUCCESS) +#endif + +#elif defined(HAVE_WINCRYPT_H) #include #endif +#endif #ifndef O_CLOEXEC #define O_CLOEXEC 0 @@ -75,6 +85,20 @@ int archive_random(void *buf, size_t nbytes) { #if defined(_WIN32) && !defined(__CYGWIN__) +# if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + NTSTATUS status; + BCRYPT_ALG_HANDLE hAlg; + + status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RNG_ALGORITHM, NULL, 0); + if (!BCRYPT_SUCCESS(status)) + return ARCHIVE_FAILED; + status = BCryptGenRandom(hAlg, buf, (ULONG)nbytes, 0); + BCryptCloseAlgorithmProvider(hAlg, 0); + if (!BCRYPT_SUCCESS(status)) + return ARCHIVE_FAILED; + + return ARCHIVE_OK; +# else HCRYPTPROV hProv; BOOL success; @@ -92,6 +116,10 @@ archive_random(void *buf, size_t nbytes) } /* TODO: Does this case really happen? */ return ARCHIVE_FAILED; +# endif +#elif !defined(HAVE_ARC4RANDOM_BUF) && (!defined(_WIN32) || defined(__CYGWIN__)) + la_arc4random_buf(buf, nbytes); + return ARCHIVE_OK; #else arc4random_buf(buf, nbytes); return ARCHIVE_OK; @@ -256,7 +284,7 @@ arc4_getbyte(void) } static void -arc4random_buf(void *_buf, size_t n) +la_arc4random_buf(void *_buf, size_t n) { uint8_t *buf = (uint8_t *)_buf; _ARC4_LOCK(); diff --git a/Utilities/cmlibarchive/libarchive/archive_read.3 b/Utilities/cmlibarchive/libarchive/archive_read.3 index cbedd0a1912..c81c98be275 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read.c b/Utilities/cmlibarchive/libarchive/archive_read.c index 45a38aed02b..822c534b868 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read.c +++ b/Utilities/cmlibarchive/libarchive/archive_read.c @@ -32,7 +32,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read.c 201157 2009-12-29 05:30:23Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -93,7 +92,7 @@ archive_read_new(void) { struct archive_read *a; - a = (struct archive_read *)calloc(1, sizeof(*a)); + a = calloc(1, sizeof(*a)); if (a == NULL) return (NULL); a->archive.magic = ARCHIVE_READ_MAGIC; @@ -583,7 +582,7 @@ choose_filters(struct archive_read *a) } filter - = (struct archive_read_filter *)calloc(1, sizeof(*filter)); + = calloc(1, sizeof(*filter)); if (filter == NULL) return (ARCHIVE_FATAL); filter->bidder = best_bidder; @@ -1383,7 +1382,7 @@ __archive_read_filter_ahead(struct archive_read_filter *filter, if (filter->client_avail <= 0) { if (filter->end_of_file) { if (avail != NULL) - *avail = 0; + *avail = filter->avail; return (NULL); } bytes_read = (filter->vtable->read)(filter, @@ -1452,7 +1451,7 @@ __archive_read_filter_ahead(struct archive_read_filter *filter, s = t; } /* Now s >= min, so allocate a new buffer. */ - p = (char *)malloc(s); + p = malloc(s); if (p == NULL) { archive_set_error( &filter->archive->archive, diff --git a/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.3 b/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.3 index ca60d4fc62f..c35cfeb34cd 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd September 14, 2014 .Dt ARCHIVE_READ_ADD_PASSPHRASE 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.c b/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.c index f0b1ab93300..c67d1df3d1b 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_add_passphrase.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_read_append_filter.c b/Utilities/cmlibarchive/libarchive/archive_read_append_filter.c index 25dc4b2a2b7..cd88df11990 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_append_filter.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_append_filter.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -97,6 +96,10 @@ archive_read_append_filter(struct archive *_a, int code) strcpy(str, "lzip"); r1 = archive_read_support_filter_lzip(_a); break; + case ARCHIVE_FILTER_LZOP: + strcpy(str, "lzop"); + r1 = archive_read_support_filter_lzop(_a); + break; case ARCHIVE_FILTER_LRZIP: strcpy(str, "lrzip"); r1 = archive_read_support_filter_lrzip(_a); @@ -112,7 +115,7 @@ archive_read_append_filter(struct archive *_a, int code) number_bidders = sizeof(a->bidders) / sizeof(a->bidders[0]); bidder = a->bidders; - for (i = 0; i < number_bidders; i++, bidder++) + for (i = 1; i < number_bidders; i++, bidder++) { if (!bidder->name || !strcmp(bidder->name, str)) break; @@ -124,8 +127,7 @@ archive_read_append_filter(struct archive *_a, int code) return (ARCHIVE_FATAL); } - filter - = (struct archive_read_filter *)calloc(1, sizeof(*filter)); + filter = calloc(1, sizeof(*filter)); if (filter == NULL) { archive_set_error(&a->archive, ENOMEM, "Out of memory"); @@ -181,8 +183,7 @@ archive_read_append_filter_program_signature(struct archive *_a, return (ARCHIVE_FATAL); } - filter - = (struct archive_read_filter *)calloc(1, sizeof(*filter)); + filter = calloc(1, sizeof(*filter)); if (filter == NULL) { archive_set_error(&a->archive, ENOMEM, "Out of memory"); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_data.3 b/Utilities/cmlibarchive/libarchive/archive_read_data.3 index 78c0c900041..694f29264ec 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_data.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_data.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ_DATA 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_data_into_fd.c b/Utilities/cmlibarchive/libarchive/archive_read_data_into_fd.c index b4398f1ecce..8fd5e12442b 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_data_into_fd.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_data_into_fd.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_data_into_fd.c,v 1.16 2008/05/23 05:01:29 cperciva Exp $"); #ifdef HAVE_SYS_TYPES_H #include @@ -95,8 +94,13 @@ archive_read_data_into_fd(struct archive *a, int fd) "archive_read_data_into_fd"); can_lseek = (fstat(fd, &st) == 0) && S_ISREG(st.st_mode); - if (!can_lseek) + if (!can_lseek) { nulls = calloc(1, nulls_size); + if (!nulls) { + r = ARCHIVE_FATAL; + goto cleanup; + } + } while ((r = archive_read_data_block(a, &buff, &size, &target_offset)) == ARCHIVE_OK) { diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk.3 b/Utilities/cmlibarchive/libarchive/archive_read_disk.3 index 8b568d7b056..990c1514c4d 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_disk.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_disk.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd April 3, 2017 .Dt ARCHIVE_READ_DISK 3 .Os @@ -290,11 +288,11 @@ calls. If matched based on calls to .Tn archive_match_time_excluded , or .Tn archive_match_owner_excluded , -then the callback function specified by the _excluded_func parameter will execute. This function will recieve data provided to the fourth parameter, void *_client_data. +then the callback function specified by the _excluded_func parameter will execute. This function will receive data provided to the fourth parameter, void *_client_data. .It Fn archive_read_disk_set_metadata_filter_callback Allows the caller to set a callback function during calls to .Xr archive_read_header 3 -to filter out metadata for each entry. The callback function recieves the +to filter out metadata for each entry. The callback function receives the .Tn struct archive object, void* custom filter data, and the .Tn struct archive_entry . diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk_entry_from_file.c b/Utilities/cmlibarchive/libarchive/archive_read_disk_entry_from_file.c index ab0270bc285..19d049770b7 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_disk_entry_from_file.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_entry_from_file.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD"); /* This is the tree-walking code for POSIX systems. */ #if !defined(_WIN32) || defined(__CYGWIN__) @@ -521,6 +520,7 @@ setup_xattr(struct archive_read_disk *a, if (size == -1) { archive_set_error(&a->archive, errno, "Couldn't read extended attribute"); + free(value); return (ARCHIVE_WARN); } @@ -888,7 +888,7 @@ setup_sparse_fiemap(struct archive_read_disk *a, count = (sizeof(buff) - sizeof(*fm))/sizeof(*fe); fm = (struct fiemap *)buff; fm->fm_start = 0; - fm->fm_length = ~0ULL;; + fm->fm_length = ~0ULL; fm->fm_flags = FIEMAP_FLAG_SYNC; fm->fm_extent_count = count; do_fiemap = 1; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c b/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c index c964d3f31d7..4cf008016f9 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c @@ -29,7 +29,6 @@ #if !defined(_WIN32) || defined(__CYGWIN__) #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_PARAM_H #include @@ -456,7 +455,7 @@ archive_read_disk_new(void) { struct archive_read_disk *a; - a = (struct archive_read_disk *)calloc(1, sizeof(*a)); + a = calloc(1, sizeof(*a)); if (a == NULL) return (NULL); a->archive.magic = ARCHIVE_READ_DISK_MAGIC; @@ -1678,6 +1677,11 @@ setup_current_filesystem(struct archive_read_disk *a) else t->current_filesystem->name_max = nm; #endif + if (t->current_filesystem->name_max == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Cannot determine name_max"); + return (ARCHIVE_FAILED); + } #endif /* USE_READDIR_R */ return (ARCHIVE_OK); } @@ -1868,7 +1872,16 @@ setup_current_filesystem(struct archive_read_disk *a) #if defined(USE_READDIR_R) /* Set maximum filename length. */ +#if defined(HAVE_STATVFS) + t->current_filesystem->name_max = svfs.f_namemax; +#else t->current_filesystem->name_max = sfs.f_namelen; +#endif + if (t->current_filesystem->name_max == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Cannot determine name_max"); + return (ARCHIVE_FAILED); + } #endif return (ARCHIVE_OK); } @@ -1950,6 +1963,11 @@ setup_current_filesystem(struct archive_read_disk *a) #if defined(USE_READDIR_R) /* Set maximum filename length. */ t->current_filesystem->name_max = svfs.f_namemax; + if (t->current_filesystem->name_max == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Cannot determine name_max"); + return (ARCHIVE_FAILED); + } #endif return (ARCHIVE_OK); } @@ -2004,6 +2022,11 @@ setup_current_filesystem(struct archive_read_disk *a) else t->current_filesystem->name_max = nm; # endif /* _PC_NAME_MAX */ + if (t->current_filesystem->name_max == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Cannot determine name_max"); + return (ARCHIVE_FAILED); + } #endif /* USE_READDIR_R */ return (ARCHIVE_OK); } @@ -2554,7 +2577,11 @@ tree_current_lstat(struct tree *t) #else if (tree_enter_working_dir(t) != 0) return NULL; +#ifdef HAVE_LSTAT if (lstat(tree_current_access_path(t), &t->lst) != 0) +#else + if (la_stat(tree_current_access_path(t), &t->lst) != 0) +#endif #endif return NULL; t->flags |= hasLstat; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk_private.h b/Utilities/cmlibarchive/libarchive/archive_read_disk_private.h index bc8abc15d15..cf8da99a029 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_disk_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_private.h @@ -22,8 +22,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_read_disk_private.h 201105 2009-12-28 03:20:54Z kientzle $ */ #ifndef ARCHIVE_READ_DISK_PRIVATE_H_INCLUDED diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk_set_standard_lookup.c b/Utilities/cmlibarchive/libarchive/archive_read_disk_set_standard_lookup.c index c7fd2471ecb..3512d343f65 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_disk_set_standard_lookup.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_set_standard_lookup.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_disk_set_standard_lookup.c 201109 2009-12-28 03:30:31Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c b/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c index f9d139557ee..cb59b516069 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c @@ -25,7 +25,6 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #if defined(_WIN32) && !defined(__CYGWIN__) @@ -50,6 +49,8 @@ __FBSDID("$FreeBSD$"); /* Old SDKs do not provide IO_REPARSE_TAG_SYMLINK */ #define IO_REPARSE_TAG_SYMLINK 0xA000000CL #endif +/* To deal with absolute symlink isuues */ +#define START_ABSOLUTE_SYMLINK_REPARSE L"\\??\\" /*- * This is a new directory-walking system that addresses a number @@ -376,7 +377,7 @@ la_linkname_from_handle(HANDLE h, wchar_t **linkname, int *linktype) return (-1); } - tbuf = malloc(len + 1 * sizeof(wchar_t)); + tbuf = malloc(len + sizeof(wchar_t)); if (tbuf == NULL) { free(indata); return (-1); @@ -387,18 +388,21 @@ la_linkname_from_handle(HANDLE h, wchar_t **linkname, int *linktype) free(indata); tbuf[len / sizeof(wchar_t)] = L'\0'; + if (wcsncmp(tbuf, START_ABSOLUTE_SYMLINK_REPARSE, 4) == 0) { + /* Absolute symlink, so we'll change the NT path into a verbatim one */ + tbuf[1] = L'\\'; + } else { + /* Relative symlink, so we can translate backslashes to slashes */ + wchar_t *temp = tbuf; + do { + if (*temp == L'\\') + *temp = L'/'; + temp++; + } while(*temp != L'\0'); + } *linkname = tbuf; - /* - * Translate backslashes to slashes for libarchive internal use - */ - while(*tbuf != L'\0') { - if (*tbuf == L'\\') - *tbuf = L'/'; - tbuf++; - } - if ((st.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) *linktype = AE_SYMLINK_TYPE_FILE; else @@ -418,9 +422,19 @@ la_linkname_from_pathw(const wchar_t *path, wchar_t **outbuf, int *linktype) FILE_FLAG_OPEN_REPARSE_POINT; int ret; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = flag; + h = CreateFile2(path, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + OPEN_EXISTING, &createExParams); +#else h = CreateFileW(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flag, NULL); +#endif if (h == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); return (-1); @@ -529,7 +543,7 @@ archive_read_disk_new(void) { struct archive_read_disk *a; - a = (struct archive_read_disk *)calloc(1, sizeof(*a)); + a = calloc(1, sizeof(*a)); if (a == NULL) return (NULL); a->archive.magic = ARCHIVE_READ_DISK_MAGIC; @@ -1067,16 +1081,29 @@ next_entry(struct archive_read_disk *a, struct tree *t, if (archive_entry_filetype(entry) == AE_IFREG && archive_entry_size(entry) > 0) { DWORD flags = FILE_FLAG_BACKUP_SEMANTICS; +#if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif if (t->async_io) flags |= FILE_FLAG_OVERLAPPED; if (t->direct_io) flags |= FILE_FLAG_NO_BUFFERING; else flags |= FILE_FLAG_SEQUENTIAL_SCAN; +#if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = flags; + t->entry_fh = CreateFile2(tree_current_access_path(t), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + OPEN_EXISTING, &createExParams); +#else t->entry_fh = CreateFileW(tree_current_access_path(t), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flags, NULL); +#endif if (t->entry_fh == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); archive_set_error(&a->archive, errno, @@ -1547,6 +1574,9 @@ close_and_restore_time(HANDLE h, struct tree *t, struct restore_time *rt) { HANDLE handle; int r = 0; +#if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif if (h == INVALID_HANDLE_VALUE && AE_IFLNK == rt->filetype) return (0); @@ -1560,8 +1590,16 @@ close_and_restore_time(HANDLE h, struct tree *t, struct restore_time *rt) if ((t->flags & needsRestoreTimes) == 0) return (r); +#if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS; + handle = CreateFile2(rt->full_path, FILE_WRITE_ATTRIBUTES, + 0, OPEN_EXISTING, &createExParams); +#else handle = CreateFileW(rt->full_path, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); +#endif if (handle == INVALID_HANDLE_VALUE) { errno = EINVAL; return (-1); @@ -1922,6 +1960,8 @@ tree_dir_next_windows(struct tree *t, const wchar_t *pattern) t->visit_type = r != 0 ? r : TREE_ERROR_DIR; return (t->visit_type); } + /* Top stack item needs a regular visit. */ + t->current = t->stack; t->findData = &t->_findData; pattern = NULL; } else if (!FindNextFileW(t->d, &t->_findData)) { @@ -2046,12 +2086,24 @@ tree_current_file_information(struct tree *t, BY_HANDLE_FILE_INFORMATION *st, HANDLE h; int r; DWORD flag = FILE_FLAG_BACKUP_SEMANTICS; - +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif + if (sim_lstat && tree_current_is_physical_link(t)) flag |= FILE_FLAG_OPEN_REPARSE_POINT; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = flag; + h = CreateFile2(tree_current_access_path(t), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + OPEN_EXISTING, &createExParams); +#else h = CreateFileW(tree_current_access_path(t), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flag, NULL); +#endif if (h == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); t->tree_errno = errno; @@ -2257,7 +2309,10 @@ archive_read_disk_entry_from_file(struct archive *_a, } else { WIN32_FIND_DATAW findData; DWORD flag, desiredAccess; - +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif + h = FindFirstFileW(path, &findData); if (h == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); @@ -2279,9 +2334,18 @@ archive_read_disk_entry_from_file(struct archive *_a, } else desiredAccess = GENERIC_READ; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = flag; + h = CreateFile2(path, desiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + OPEN_EXISTING, &createExParams); +#else h = CreateFileW(path, desiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flag, NULL); +#endif if (h == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); archive_set_error(&a->archive, errno, @@ -2342,9 +2406,19 @@ archive_read_disk_entry_from_file(struct archive *_a, if (fd >= 0) { h = (HANDLE)_get_osfhandle(fd); } else { +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS; + h = CreateFile2(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + OPEN_EXISTING, &createExParams); +#else h = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); +#endif if (h == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); archive_set_error(&a->archive, errno, @@ -2371,6 +2445,7 @@ archive_read_disk_entry_from_file(struct archive *_a, return (ARCHIVE_OK); } + r = ARCHIVE_OK; if ((a->flags & ARCHIVE_READDISK_NO_SPARSE) == 0) { r = setup_sparse_from_disk(a, entry, h); if (fd < 0) @@ -2403,7 +2478,7 @@ setup_sparse_from_disk(struct archive_read_disk *a, range.FileOffset.QuadPart = 0; range.Length.QuadPart = entry_size; outranges_size = 2048; - outranges = (FILE_ALLOCATED_RANGE_BUFFER *)malloc(outranges_size); + outranges = malloc(outranges_size); if (outranges == NULL) { archive_set_error(&a->archive, ENOMEM, "Couldn't allocate memory"); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_extract.3 b/Utilities/cmlibarchive/libarchive/archive_read_extract.3 index 858f3974255..f3feb5ad551 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_extract.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_extract.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ_EXTRACT 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_extract.c b/Utilities/cmlibarchive/libarchive/archive_read_extract.c index b7973fa8e00..d2159c64cd8 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_extract.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_extract.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_extract.c,v 1.61 2008/05/26 17:00:22 kientzle Exp $"); #ifdef HAVE_ERRNO_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_read_extract2.c b/Utilities/cmlibarchive/libarchive/archive_read_extract2.c index 4febd8ce056..7cf38c30117 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_extract2.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_extract2.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_extract.c,v 1.61 2008/05/26 17:00:22 kientzle Exp $"); #ifdef HAVE_SYS_TYPES_H #include @@ -52,7 +51,7 @@ struct archive_read_extract * __archive_read_get_extract(struct archive_read *a) { if (a->extract == NULL) { - a->extract = (struct archive_read_extract *)calloc(1, sizeof(*a->extract)); + a->extract = calloc(1, sizeof(*a->extract)); if (a->extract == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't extract"); return (NULL); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_filter.3 b/Utilities/cmlibarchive/libarchive/archive_read_filter.3 index 4f5c3518a6c..72ff240fd39 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_filter.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_filter.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd June 9, 2020 .Dt ARCHIVE_READ_FILTER 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_format.3 b/Utilities/cmlibarchive/libarchive/archive_read_format.3 index f3804ce3796..990293c8313 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_format.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_format.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ_FORMAT 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_free.3 b/Utilities/cmlibarchive/libarchive/archive_read_free.3 index 8371c3a0c60..7dc121fcaea 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_free.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_free.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ_FREE 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_header.3 b/Utilities/cmlibarchive/libarchive/archive_read_header.3 index 1e97f3a2750..024dc41da83 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_header.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_header.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ_HEADER 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_new.3 b/Utilities/cmlibarchive/libarchive/archive_read_new.3 index 8bb6b848b06..c2b5cddef09 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_new.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_new.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ_NEW 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_open.3 b/Utilities/cmlibarchive/libarchive/archive_read_open.3 index f67677823bd..081b7114bea 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_open.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_open.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_READ_OPEN 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_read_open_fd.c b/Utilities/cmlibarchive/libarchive/archive_read_open_fd.c index f59cd07fe6c..debfde20868 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_open_fd.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_open_fd.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_open_fd.c 201103 2009-12-28 03:13:49Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -75,7 +74,7 @@ archive_read_open_fd(struct archive *a, int fd, size_t block_size) return (ARCHIVE_FATAL); } - mine = (struct read_fd_data *)calloc(1, sizeof(*mine)); + mine = calloc(1, sizeof(*mine)); b = malloc(block_size); if (mine == NULL || b == NULL) { archive_set_error(a, ENOMEM, "No memory"); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_open_file.c b/Utilities/cmlibarchive/libarchive/archive_read_open_file.c index 101dae6cd9e..276b2742381 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_open_file.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_open_file.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_open_file.c 201093 2009-12-28 02:28:44Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -57,9 +56,10 @@ struct read_FILE_data { char can_skip; }; -static int file_close(struct archive *, void *); -static ssize_t file_read(struct archive *, void *, const void **buff); -static int64_t file_skip(struct archive *, void *, int64_t request); +static int FILE_close(struct archive *, void *); +static ssize_t FILE_read(struct archive *, void *, const void **buff); +static int64_t FILE_seek(struct archive *, void *, int64_t, int); +static int64_t FILE_skip(struct archive *, void *, int64_t); int archive_read_open_FILE(struct archive *a, FILE *f) @@ -70,7 +70,7 @@ archive_read_open_FILE(struct archive *a, FILE *f) void *b; archive_clear_error(a); - mine = (struct read_FILE_data *)malloc(sizeof(*mine)); + mine = calloc(1, sizeof(*mine)); b = malloc(block_size); if (mine == NULL || b == NULL) { archive_set_error(a, ENOMEM, "No memory"); @@ -91,22 +91,22 @@ archive_read_open_FILE(struct archive *a, FILE *f) archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); /* Enable the seek optimization only for regular files. */ mine->can_skip = 1; - } else - mine->can_skip = 0; + } #if defined(__CYGWIN__) || defined(_WIN32) setmode(fileno(mine->f), O_BINARY); #endif - archive_read_set_read_callback(a, file_read); - archive_read_set_skip_callback(a, file_skip); - archive_read_set_close_callback(a, file_close); + archive_read_set_read_callback(a, FILE_read); + archive_read_set_skip_callback(a, FILE_skip); + archive_read_set_seek_callback(a, FILE_seek); + archive_read_set_close_callback(a, FILE_close); archive_read_set_callback_data(a, mine); return (archive_read_open1(a)); } static ssize_t -file_read(struct archive *a, void *client_data, const void **buff) +FILE_read(struct archive *a, void *client_data, const void **buff) { struct read_FILE_data *mine = (struct read_FILE_data *)client_data; size_t bytes_read; @@ -120,13 +120,13 @@ file_read(struct archive *a, void *client_data, const void **buff) } static int64_t -file_skip(struct archive *a, void *client_data, int64_t request) +FILE_skip(struct archive *a, void *client_data, int64_t request) { struct read_FILE_data *mine = (struct read_FILE_data *)client_data; -#if HAVE_FSEEKO - off_t skip = (off_t)request; -#elif HAVE__FSEEKI64 +#if HAVE__FSEEKI64 int64_t skip = request; +#elif HAVE_FSEEKO + off_t skip = (off_t)request; #else long skip = (long)request; #endif @@ -154,10 +154,10 @@ file_skip(struct archive *a, void *client_data, int64_t request) #ifdef __ANDROID__ /* fileno() isn't safe on all platforms ... see above. */ if (lseek(fileno(mine->f), skip, SEEK_CUR) < 0) -#elif HAVE_FSEEKO - if (fseeko(mine->f, skip, SEEK_CUR) != 0) #elif HAVE__FSEEKI64 if (_fseeki64(mine->f, skip, SEEK_CUR) != 0) +#elif HAVE_FSEEKO + if (fseeko(mine->f, skip, SEEK_CUR) != 0) #else if (fseek(mine->f, skip, SEEK_CUR) != 0) #endif @@ -168,8 +168,57 @@ file_skip(struct archive *a, void *client_data, int64_t request) return (request); } +/* + * TODO: Store the offset and use it in the read callback. + */ +static int64_t +FILE_seek(struct archive *a, void *client_data, int64_t request, int whence) +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; +#if HAVE__FSEEKI64 + int64_t skip = request; +#elif HAVE_FSEEKO + off_t skip = (off_t)request; +#else + long skip = (long)request; +#endif + int skip_bits = sizeof(skip) * 8 - 1; + (void)a; /* UNUSED */ + + /* If request is too big for a long or an off_t, reduce it. */ + if (sizeof(request) > sizeof(skip)) { + int64_t max_skip = + (((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1; + if (request > max_skip) + skip = max_skip; + } + +#ifdef __ANDROID__ + /* Newer Android versions have fseeko...to meditate. */ + int64_t ret = lseek(fileno(mine->f), skip, whence); + if (ret >= 0) { + return ret; + } +#elif HAVE__FSEEKI64 + if (_fseeki64(mine->f, skip, whence) == 0) { + return _ftelli64(mine->f); + } +#elif HAVE_FSEEKO + if (fseeko(mine->f, skip, whence) == 0) { + return ftello(mine->f); + } +#else + if (fseek(mine->f, skip, whence) == 0) { + return ftell(mine->f); + } +#endif + /* If we arrive here, the input is corrupted or truncated so fail. */ + archive_set_error(a, errno, "Error seeking in FILE* pointer"); + return (ARCHIVE_FATAL); +} + static int -file_close(struct archive *a, void *client_data) +FILE_close(struct archive *a, void *client_data) { struct read_FILE_data *mine = (struct read_FILE_data *)client_data; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_open_filename.c b/Utilities/cmlibarchive/libarchive/archive_read_open_filename.c index 561289b694b..05f0ffbd941 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_open_filename.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_open_filename.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_open_filename.c 201093 2009-12-28 02:28:44Z kientzle $"); #ifdef HAVE_SYS_IOCTL_H #include @@ -123,7 +122,7 @@ archive_read_open_filenames(struct archive *a, const char **filenames, { if (filename == NULL) filename = ""; - mine = (struct read_file_data *)calloc(1, + mine = calloc(1, sizeof(*mine) + strlen(filename)); if (mine == NULL) goto no_memory; @@ -155,55 +154,73 @@ archive_read_open_filenames(struct archive *a, const char **filenames, return (ARCHIVE_FATAL); } +/* + * This function is an implementation detail of archive_read_open_filename_w, + * which is exposed as a separate API on Windows. + */ +#if !defined(_WIN32) || defined(__CYGWIN__) +static +#endif int -archive_read_open_filename_w(struct archive *a, const wchar_t *wfilename, +archive_read_open_filenames_w(struct archive *a, const wchar_t **wfilenames, size_t block_size) { - struct read_file_data *mine = (struct read_file_data *)calloc(1, - sizeof(*mine) + wcslen(wfilename) * sizeof(wchar_t)); - if (!mine) + struct read_file_data *mine; + const wchar_t *wfilename = NULL; + if (wfilenames) + wfilename = *(wfilenames++); + + archive_clear_error(a); + do { - archive_set_error(a, ENOMEM, "No memory"); - return (ARCHIVE_FATAL); - } - mine->fd = -1; - mine->block_size = block_size; + if (wfilename == NULL) + wfilename = L""; + mine = calloc(1, + sizeof(*mine) + wcslen(wfilename) * sizeof(wchar_t)); + if (mine == NULL) + goto no_memory; + mine->block_size = block_size; + mine->fd = -1; - if (wfilename == NULL || wfilename[0] == L'\0') { - mine->filename_type = FNT_STDIN; - } else { + if (wfilename == NULL || wfilename[0] == L'\0') { + mine->filename_type = FNT_STDIN; + } else { #if defined(_WIN32) && !defined(__CYGWIN__) - mine->filename_type = FNT_WCS; - wcscpy(mine->filename.w, wfilename); + mine->filename_type = FNT_WCS; + wcscpy(mine->filename.w, wfilename); #else - /* - * POSIX system does not support a wchar_t interface for - * open() system call, so we have to translate a wchar_t - * filename to multi-byte one and use it. - */ - struct archive_string fn; - - archive_string_init(&fn); - if (archive_string_append_from_wcs(&fn, wfilename, - wcslen(wfilename)) != 0) { - if (errno == ENOMEM) - archive_set_error(a, errno, - "Can't allocate memory"); - else - archive_set_error(a, EINVAL, - "Failed to convert a wide-character" - " filename to a multi-byte filename"); + /* + * POSIX system does not support a wchar_t interface for + * open() system call, so we have to translate a wchar_t + * filename to multi-byte one and use it. + */ + struct archive_string fn; + + archive_string_init(&fn); + if (archive_string_append_from_wcs(&fn, wfilename, + wcslen(wfilename)) != 0) { + if (errno == ENOMEM) + archive_set_error(a, errno, + "Can't allocate memory"); + else + archive_set_error(a, EINVAL, + "Failed to convert a wide-character" + " filename to a multi-byte filename"); + archive_string_free(&fn); + free(mine); + return (ARCHIVE_FATAL); + } + mine->filename_type = FNT_MBS; + strcpy(mine->filename.m, fn.s); archive_string_free(&fn); - free(mine); - return (ARCHIVE_FATAL); - } - mine->filename_type = FNT_MBS; - strcpy(mine->filename.m, fn.s); - archive_string_free(&fn); #endif - } - if (archive_read_append_callback_data(a, mine) != (ARCHIVE_OK)) - return (ARCHIVE_FATAL); + } + if (archive_read_append_callback_data(a, mine) != (ARCHIVE_OK)) + return (ARCHIVE_FATAL); + if (wfilenames == NULL) + break; + wfilename = *(wfilenames++); + } while (wfilename != NULL && wfilename[0] != '\0'); archive_read_set_open_callback(a, file_open); archive_read_set_read_callback(a, file_read); archive_read_set_skip_callback(a, file_skip); @@ -212,6 +229,19 @@ archive_read_open_filename_w(struct archive *a, const wchar_t *wfilename, archive_read_set_seek_callback(a, file_seek); return (archive_read_open1(a)); +no_memory: + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); +} + +int +archive_read_open_filename_w(struct archive *a, const wchar_t *wfilename, + size_t block_size) +{ + const wchar_t *wfilenames[2]; + wfilenames[0] = wfilename; + wfilenames[1] = NULL; + return archive_read_open_filenames_w(a, wfilenames, block_size); } static int @@ -273,7 +303,7 @@ file_open(struct archive *a, void *client_data) } if (fd < 0) { archive_set_error(a, errno, - "Failed to open '%S'", wfilename); + "Failed to open '%ls'", wfilename); return (ARCHIVE_FATAL); } #else @@ -285,7 +315,7 @@ file_open(struct archive *a, void *client_data) if (fstat(fd, &st) != 0) { #if defined(_WIN32) && !defined(__CYGWIN__) if (mine->filename_type == FNT_WCS) - archive_set_error(a, errno, "Can't stat '%S'", + archive_set_error(a, errno, "Can't stat '%ls'", wfilename); else #endif @@ -417,7 +447,7 @@ file_read(struct archive *a, void *client_data, const void **buff) "Error reading '%s'", mine->filename.m); else archive_set_error(a, errno, - "Error reading '%S'", mine->filename.w); + "Error reading '%ls'", mine->filename.w); } return (bytes_read); } @@ -479,7 +509,7 @@ file_skip_lseek(struct archive *a, void *client_data, int64_t request) archive_set_error(a, errno, "Error seeking in '%s'", mine->filename.m); else - archive_set_error(a, errno, "Error seeking in '%S'", + archive_set_error(a, errno, "Error seeking in '%ls'", mine->filename.w); return (-1); } @@ -525,7 +555,7 @@ file_seek(struct archive *a, void *client_data, int64_t request, int whence) archive_set_error(a, errno, "Error seeking in '%s'", mine->filename.m); else - archive_set_error(a, errno, "Error seeking in '%S'", + archive_set_error(a, errno, "Error seeking in '%ls'", mine->filename.w); return (ARCHIVE_FATAL); } diff --git a/Utilities/cmlibarchive/libarchive/archive_read_open_memory.c b/Utilities/cmlibarchive/libarchive/archive_read_open_memory.c index 311be47046a..460bb5ae6a4 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_open_memory.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_open_memory.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_memory.c,v 1.6 2007/07/06 15:51:59 kientzle Exp $"); #include #include @@ -70,7 +69,7 @@ archive_read_open_memory2(struct archive *a, const void *buff, { struct read_memory_data *mine; - mine = (struct read_memory_data *)calloc(1, sizeof(*mine)); + mine = calloc(1, sizeof(*mine)); if (mine == NULL) { archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_private.h b/Utilities/cmlibarchive/libarchive/archive_read_private.h index 383405d5290..0c374f487eb 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_read_private.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_read_private.h 201088 2009-12-28 02:18:55Z kientzle $ */ #ifndef ARCHIVE_READ_PRIVATE_H_INCLUDED diff --git a/Utilities/cmlibarchive/libarchive/archive_read_set_format.c b/Utilities/cmlibarchive/libarchive/archive_read_set_format.c index 796dcdcced1..c74361b20c1 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_set_format.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_set_format.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_read_set_options.3 b/Utilities/cmlibarchive/libarchive/archive_read_set_options.3 index b2db4cbcb89..ef18dfaa271 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_set_options.3 +++ b/Utilities/cmlibarchive/libarchive/archive_read_set_options.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd January 31, 2020 .Dt ARCHIVE_READ_OPTIONS 3 .Os @@ -255,6 +253,27 @@ have been concatenated together. Without this option, only the contents of the first concatenated archive would be read. .El +.It Format zip +.Bl -tag -compact -width indent +.It Cm compat-2x +Libarchive 2.x incorrectly encoded Unicode filenames on +some platforms. +This option mimics the libarchive 2.x filename handling +so that such archives can be read correctly. +.It Cm hdrcharset +The value is used as a character set name that will be +used when translating file names. +.It Cm ignorecrc32 +Skip the CRC32 check. +Mostly used for testing. +.It Cm mac-ext +Support Mac OS metadata extension that records data in special +files beginning with a period and underscore. +Defaults to enabled on Mac OS, disabled on other platforms. +Use +.Cm !mac-ext +to disable. +.El .El .\" .Sh ERRORS diff --git a/Utilities/cmlibarchive/libarchive/archive_read_set_options.c b/Utilities/cmlibarchive/libarchive/archive_read_set_options.c index 2bd9b811eaf..c0a4b420734 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_set_options.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_set_options.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #include "archive_read_private.h" #include "archive_options_private.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_all.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_all.c index edb508c1dfd..cb46d120d10 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_all.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_all.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #include "archive.h" #include "archive_private.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_by_code.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_by_code.c index 94c4af695f4..4c8b6cb59c2 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_by_code.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_by_code.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #include "archive.h" #include "archive_private.h" @@ -38,46 +37,32 @@ archive_read_support_filter_by_code(struct archive *a, int filter_code) switch (filter_code) { case ARCHIVE_FILTER_NONE: return archive_read_support_filter_none(a); - break; case ARCHIVE_FILTER_GZIP: return archive_read_support_filter_gzip(a); - break; case ARCHIVE_FILTER_BZIP2: return archive_read_support_filter_bzip2(a); - break; case ARCHIVE_FILTER_COMPRESS: return archive_read_support_filter_compress(a); - break; case ARCHIVE_FILTER_LZMA: return archive_read_support_filter_lzma(a); - break; case ARCHIVE_FILTER_XZ: return archive_read_support_filter_xz(a); - break; case ARCHIVE_FILTER_UU: return archive_read_support_filter_uu(a); - break; case ARCHIVE_FILTER_RPM: return archive_read_support_filter_rpm(a); - break; case ARCHIVE_FILTER_LZIP: return archive_read_support_filter_lzip(a); - break; case ARCHIVE_FILTER_LRZIP: return archive_read_support_filter_lrzip(a); - break; case ARCHIVE_FILTER_LZOP: return archive_read_support_filter_lzop(a); - break; case ARCHIVE_FILTER_GRZIP: return archive_read_support_filter_grzip(a); - break; case ARCHIVE_FILTER_LZ4: return archive_read_support_filter_lz4(a); - break; case ARCHIVE_FILTER_ZSTD: return archive_read_support_filter_zstd(a); - break; } return (ARCHIVE_FATAL); } diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_bzip2.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_bzip2.c index a5243aff715..a7fb44f5b80 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_bzip2.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_bzip2.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif @@ -192,8 +190,8 @@ bzip2_reader_init(struct archive_read_filter *self) self->code = ARCHIVE_FILTER_BZIP2; self->name = "bzip2"; - state = (struct private_data *)calloc(sizeof(*state), 1); - out_block = (unsigned char *)malloc(out_block_size); + state = calloc(1, sizeof(*state)); + out_block = malloc(out_block_size); if (state == NULL || out_block == NULL) { archive_set_error(&self->archive->archive, ENOMEM, "Can't allocate data for bzip2 decompression"); @@ -230,7 +228,7 @@ bzip2_filter_read(struct archive_read_filter *self, const void **p) /* Empty our output buffer. */ state->stream.next_out = state->out_block; - state->stream.avail_out = state->out_block_size; + state->stream.avail_out = (uint32_t)state->out_block_size; /* Try to fill the output buffer. */ for (;;) { @@ -288,7 +286,7 @@ bzip2_filter_read(struct archive_read_filter *self, const void **p) return (ARCHIVE_FATAL); } state->stream.next_in = (char *)(uintptr_t)read_buf; - state->stream.avail_in = ret; + state->stream.avail_in = (uint32_t)ret; /* There is no more data, return whatever we have. */ if (ret == 0) { state->eof = 1; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_compress.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_compress.c index 05b80a576ac..b6e9816dfee 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_compress.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_compress.c @@ -64,7 +64,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -218,7 +217,7 @@ compress_bidder_init(struct archive_read_filter *self) self->code = ARCHIVE_FILTER_COMPRESS; self->name = "compress (.Z)"; - state = (struct private_data *)calloc(sizeof(*state), 1); + state = calloc(1, sizeof(*state)); out_block = malloc(out_block_size); if (state == NULL || out_block == NULL) { free(out_block); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_grzip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_grzip.c index d4d1737cd97..15b6757cb90 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_grzip.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_grzip.c @@ -25,9 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - - #ifdef HAVE_ERRNO_H #include #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_gzip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_gzip.c index 976a392e358..728d846d100 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_gzip.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_gzip.c @@ -25,9 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - - #ifdef HAVE_ERRNO_H #include #endif @@ -126,6 +123,8 @@ archive_read_support_filter_gzip(struct archive *_a) * number of bytes in header. If pbits is non-NULL, it receives a * count of bits verified, suitable for use by bidder. */ +#define MAX_FILENAME_LENGTH (1024 * 1024L) +#define MAX_COMMENT_LENGTH (1024 * 1024L) static ssize_t peek_at_header(struct archive_read_filter *filter, int *pbits, #ifdef HAVE_ZLIB_H @@ -183,9 +182,13 @@ peek_at_header(struct archive_read_filter *filter, int *pbits, #endif do { ++len; - if (avail < len) + if (avail < len) { + if (avail > MAX_FILENAME_LENGTH) { + return (0); + } p = __archive_read_filter_ahead(filter, len, &avail); + } if (p == NULL) return (0); } while (p[len - 1] != 0); @@ -203,9 +206,13 @@ peek_at_header(struct archive_read_filter *filter, int *pbits, if (header_flags & 16) { do { ++len; - if (avail < len) + if (avail < len) { + if (avail > MAX_COMMENT_LENGTH) { + return (0); + } p = __archive_read_filter_ahead(filter, len, &avail); + } if (p == NULL) return (0); } while (p[len - 1] != 0); @@ -310,8 +317,8 @@ gzip_bidder_init(struct archive_read_filter *self) self->code = ARCHIVE_FILTER_GZIP; self->name = "gzip"; - state = (struct private_data *)calloc(sizeof(*state), 1); - out_block = (unsigned char *)malloc(out_block_size); + state = calloc(1, sizeof(*state)); + out_block = malloc(out_block_size); if (state == NULL || out_block == NULL) { free(out_block); free(state); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lrzip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lrzip.c index a2389894f1d..a562d538ed8 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lrzip.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lrzip.c @@ -25,9 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - - #ifdef HAVE_ERRNO_H #include #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c index 1e99542d7b7..760e6d938d2 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif @@ -225,7 +223,7 @@ lz4_reader_init(struct archive_read_filter *self) self->code = ARCHIVE_FILTER_LZ4; self->name = "lz4"; - state = (struct private_data *)calloc(sizeof(*state), 1); + state = calloc(1, sizeof(*state)); if (state == NULL) { archive_set_error(&self->archive->archive, ENOMEM, "Can't allocate data for lz4 decompression"); @@ -250,7 +248,7 @@ lz4_allocate_out_block(struct archive_read_filter *self) out_block_size += 64 * 1024; if (state->out_block_size < out_block_size) { free(state->out_block); - out_block = (unsigned char *)malloc(out_block_size); + out_block = malloc(out_block_size); state->out_block_size = out_block_size; if (out_block == NULL) { archive_set_error(&self->archive->archive, ENOMEM, @@ -273,7 +271,7 @@ lz4_allocate_out_block_for_legacy(struct archive_read_filter *self) if (state->out_block_size < out_block_size) { free(state->out_block); - out_block = (unsigned char *)malloc(out_block_size); + out_block = malloc(out_block_size); state->out_block_size = out_block_size; if (out_block == NULL) { archive_set_error(&self->archive->archive, ENOMEM, @@ -325,7 +323,6 @@ lz4_filter_read(struct archive_read_filter *self, const void **p) archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, "Program error."); return (ARCHIVE_FATAL); - break; } while (state->stage == SELECT_STREAM) { @@ -449,8 +446,8 @@ lz4_filter_read_descriptor(struct archive_read_filter *self) chsum = __archive_xxhash.XXH32(read_buf, (int)descriptor_bytes -1, 0); chsum = (chsum >> 8) & 0xff; chsum_verifier = read_buf[descriptor_bytes-1] & 0xff; - if (chsum != chsum_verifier) #ifndef DONT_FAIL_ON_CRC_ERROR + if (chsum != chsum_verifier) goto malformed_error; #endif @@ -522,8 +519,8 @@ lz4_filter_read_data_block(struct archive_read_filter *self, const void **p) read_buf + 4, (int)compressed_size, 0); unsigned int chsum_block = archive_le32dec(read_buf + 4 + compressed_size); - if (chsum != chsum_block) #ifndef DONT_FAIL_ON_CRC_ERROR + if (chsum != chsum_block) goto malformed_error; #endif } @@ -584,7 +581,7 @@ lz4_filter_read_data_block(struct archive_read_filter *self, const void **p) state->out_block + prefix64k, (int)compressed_size, state->flags.block_maximum_size, state->out_block, - prefix64k); + (int)prefix64k); #else uncompressed_size = LZ4_decompress_safe_withPrefix64k( read_buf + 4, diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c index cc1572f9d0c..2fe8bd8d705 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c @@ -26,8 +26,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_UNISTD_H #include #endif @@ -187,7 +185,7 @@ lzop_bidder_init(struct archive_read_filter *self) self->code = ARCHIVE_FILTER_LZOP; self->name = "lzop"; - state = (struct read_lzop *)calloc(sizeof(*state), 1); + state = calloc(1, sizeof(*state)); if (state == NULL) { archive_set_error(&self->archive->archive, ENOMEM, "Can't allocate data for lzop decompression"); @@ -282,8 +280,8 @@ consume_header(struct archive_read_filter *self) checksum = crc32(crc32(0, NULL, 0), p, len); else checksum = adler32(adler32(0, NULL, 0), p, len); - if (archive_be32dec(p + len) != checksum) #ifndef DONT_FAIL_ON_CRC_ERROR + if (archive_be32dec(p + len) != checksum) goto corrupted; #endif __archive_read_filter_consume(self->upstream, len + 4); @@ -293,7 +291,8 @@ consume_header(struct archive_read_filter *self) if (p == NULL) goto truncated; len = archive_be32dec(p); - __archive_read_filter_consume(self->upstream, len + 4 + 4); + __archive_read_filter_consume(self->upstream, + (int64_t)len + 4 + 4); } state->flags = flags; state->in_stream = 1; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_none.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_none.c index 95e5cfdb15d..9eb8e54ae96 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_none.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_none.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #include "archive.h" #include "archive_private.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_program.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_program.c index 885b2c2056e..9e825223b26 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_program.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_program.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_WAIT_H # include @@ -140,7 +139,7 @@ archive_read_support_filter_program_signature(struct archive *_a, /* * Allocate our private state. */ - state = (struct program_bidder *)calloc(1, sizeof (*state)); + state = calloc(1, sizeof (*state)); if (state == NULL) goto memerr; state->cmd = strdup(cmd); @@ -399,8 +398,8 @@ __archive_read_program(struct archive_read_filter *self, const char *cmd) size_t l; l = strlen(prefix) + strlen(cmd) + 1; - state = (struct program_filter *)calloc(1, sizeof(*state)); - out_buf = (char *)malloc(out_buf_len); + state = calloc(1, sizeof(*state)); + out_buf = malloc(out_buf_len); if (state == NULL || out_buf == NULL || archive_string_ensure(&state->description, l) == NULL) { archive_set_error(&self->archive->archive, ENOMEM, diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_rpm.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_rpm.c index 67a979cd78f..25ace4a25bc 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_rpm.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_rpm.c @@ -39,8 +39,8 @@ struct rpm { int64_t total_in; - size_t hpos; - size_t hlen; + uint64_t hpos; + uint64_t hlen; unsigned char header[16]; enum { ST_LEAD, /* Skipping 'Lead' section. */ @@ -53,7 +53,8 @@ struct rpm { } state; int first_header; }; -#define RPM_LEAD_SIZE 96 /* Size of 'Lead' section. */ +#define RPM_LEAD_SIZE 96 /* Size of 'Lead' section. */ +#define RPM_MIN_HEAD_SIZE 16 /* Minimum size of 'Head'. */ static int rpm_bidder_bid(struct archive_read_filter_bidder *, struct archive_read_filter *); @@ -63,6 +64,8 @@ static ssize_t rpm_filter_read(struct archive_read_filter *, const void **); static int rpm_filter_close(struct archive_read_filter *); +static inline size_t rpm_limit_bytes(uint64_t, size_t); + #if ARCHIVE_VERSION_NUMBER < 4000000 /* Deprecated; remove in libarchive 4.0 */ int @@ -141,7 +144,7 @@ rpm_bidder_init(struct archive_read_filter *self) self->code = ARCHIVE_FILTER_RPM; self->name = "rpm"; - rpm = (struct rpm *)calloc(sizeof(*rpm), 1); + rpm = calloc(1, sizeof(*rpm)); if (rpm == NULL) { archive_set_error(&self->archive->archive, ENOMEM, "Can't allocate data for rpm"); @@ -155,15 +158,21 @@ rpm_bidder_init(struct archive_read_filter *self) return (ARCHIVE_OK); } +static inline size_t +rpm_limit_bytes(uint64_t bytes, size_t max) +{ + return (bytes > max ? max : (size_t)bytes); +} + static ssize_t rpm_filter_read(struct archive_read_filter *self, const void **buff) { struct rpm *rpm; const unsigned char *b; - ssize_t avail_in, total; - size_t used, n; - uint32_t section; - uint32_t bytes; + ssize_t avail_in, total, used; + size_t n; + uint64_t section; + uint64_t bytes; rpm = (struct rpm *)self->data; *buff = NULL; @@ -197,15 +206,14 @@ rpm_filter_read(struct archive_read_filter *self, const void **buff) } break; case ST_HEADER: - n = 16 - rpm->hpos; - if (n > avail_in - used) - n = avail_in - used; + n = rpm_limit_bytes(RPM_MIN_HEAD_SIZE - rpm->hpos, + avail_in - used); memcpy(rpm->header+rpm->hpos, b, n); b += n; used += n; rpm->hpos += n; - if (rpm->hpos == 16) { + if (rpm->hpos == RPM_MIN_HEAD_SIZE) { if (rpm->header[0] != 0x8e || rpm->header[1] != 0xad || rpm->header[2] != 0xe8 || @@ -219,21 +227,20 @@ rpm_filter_read(struct archive_read_filter *self, const void **buff) } rpm->state = ST_ARCHIVE; *buff = rpm->header; - total = rpm->hpos; + total = RPM_MIN_HEAD_SIZE; break; } /* Calculate 'Header' length. */ section = archive_be32dec(rpm->header+8); bytes = archive_be32dec(rpm->header+12); - rpm->hlen = 16 + section * 16 + bytes; + rpm->hlen = rpm->hpos + section * 16 + bytes; rpm->state = ST_HEADER_DATA; rpm->first_header = 0; } break; case ST_HEADER_DATA: - n = rpm->hlen - rpm->hpos; - if (n > avail_in - used) - n = avail_in - used; + n = rpm_limit_bytes(rpm->hlen - rpm->hpos, + avail_in - used); b += n; used += n; rpm->hpos += n; @@ -241,7 +248,7 @@ rpm_filter_read(struct archive_read_filter *self, const void **buff) rpm->state = ST_PADDING; break; case ST_PADDING: - while (used < (size_t)avail_in) { + while (used < avail_in) { if (*b != 0) { /* Read next header. */ rpm->state = ST_HEADER; @@ -259,7 +266,7 @@ rpm_filter_read(struct archive_read_filter *self, const void **buff) used = avail_in; break; } - if (used == (size_t)avail_in) { + if (used == avail_in) { rpm->total_in += used; __archive_read_filter_consume(self->upstream, used); b = NULL; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c index c66c2478418..242d14b5e87 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -41,17 +40,20 @@ __FBSDID("$FreeBSD$"); #endif #include "archive.h" +#include "archive_entry.h" #include "archive_private.h" #include "archive_read_private.h" /* Maximum lookahead during bid phase */ #define UUENCODE_BID_MAX_READ 128*1024 /* in bytes */ +#define UUENCODE_MAX_LINE_LENGTH 34*1024 /* in bytes */ + struct uudecode { int64_t total; unsigned char *in_buff; #define IN_BUFF_SIZE (1024) - int in_cnt; + ssize_t in_cnt; size_t in_allocated; unsigned char *out_buff; #define OUT_BUFF_SIZE (64 * 1024) @@ -61,12 +63,17 @@ struct uudecode { #define ST_UUEND 2 #define ST_READ_BASE64 3 #define ST_IGNORE 4 + mode_t mode; + int mode_set; + char *name; }; static int uudecode_bidder_bid(struct archive_read_filter_bidder *, struct archive_read_filter *filter); static int uudecode_bidder_init(struct archive_read_filter *); +static int uudecode_read_header(struct archive_read_filter *, + struct archive_entry *entry); static ssize_t uudecode_filter_read(struct archive_read_filter *, const void **); static int uudecode_filter_close(struct archive_read_filter *); @@ -359,6 +366,7 @@ static const struct archive_read_filter_vtable uudecode_reader_vtable = { .read = uudecode_filter_read, .close = uudecode_filter_close, + .read_header = uudecode_read_header }; static int @@ -371,7 +379,7 @@ uudecode_bidder_init(struct archive_read_filter *self) self->code = ARCHIVE_FILTER_UU; self->name = "uu"; - uudecode = (struct uudecode *)calloc(sizeof(*uudecode), 1); + uudecode = calloc(1, sizeof(*uudecode)); out_buff = malloc(OUT_BUFF_SIZE); in_buff = malloc(IN_BUFF_SIZE); if (uudecode == NULL || out_buff == NULL || in_buff == NULL) { @@ -389,6 +397,8 @@ uudecode_bidder_init(struct archive_read_filter *self) uudecode->in_allocated = IN_BUFF_SIZE; uudecode->out_buff = out_buff; uudecode->state = ST_FIND_HEAD; + uudecode->mode_set = 0; + uudecode->name = NULL; self->vtable = &uudecode_reader_vtable; return (ARCHIVE_OK); @@ -434,6 +444,22 @@ ensure_in_buff_size(struct archive_read_filter *self, return (ARCHIVE_OK); } +static int +uudecode_read_header(struct archive_read_filter *self, struct archive_entry *entry) +{ + + struct uudecode *uudecode; + uudecode = (struct uudecode *)self->data; + + if (uudecode->mode_set != 0) + archive_entry_set_mode(entry, S_IFREG | uudecode->mode); + + if (uudecode->name != NULL) + archive_entry_set_pathname(entry, uudecode->name); + + return (ARCHIVE_OK); +} + static ssize_t uudecode_filter_read(struct archive_read_filter *self, const void **buff) { @@ -443,7 +469,7 @@ uudecode_filter_read(struct archive_read_filter *self, const void **buff) ssize_t avail_in, ravail; ssize_t used; ssize_t total; - ssize_t len, llen, nl; + ssize_t len, llen, nl, namelen; uudecode = (struct uudecode *)self->data; @@ -464,6 +490,12 @@ uudecode_filter_read(struct archive_read_filter *self, const void **buff) goto finish; } if (uudecode->in_cnt) { + if (uudecode->in_cnt > UUENCODE_MAX_LINE_LENGTH) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid format data"); + return (ARCHIVE_FATAL); + } /* * If there is remaining data which is saved by * previous calling, use it first. @@ -481,7 +513,7 @@ uudecode_filter_read(struct archive_read_filter *self, const void **buff) uudecode->in_cnt = 0; } for (;used < avail_in; d += llen, used += llen) { - int64_t l, body; + ssize_t l, body; b = d; len = get_line(b, avail_in - used, &nl); @@ -516,7 +548,7 @@ uudecode_filter_read(struct archive_read_filter *self, const void **buff) return (ARCHIVE_FATAL); if (uudecode->in_buff != b) memmove(uudecode->in_buff, b, len); - uudecode->in_cnt = (int)len; + uudecode->in_cnt = len; if (total == 0) { /* Do not return 0; it means end-of-file. * We should try to read bytes more. */ @@ -551,6 +583,28 @@ uudecode_filter_read(struct archive_read_filter *self, const void **buff) uudecode->state = ST_READ_UU; else uudecode->state = ST_READ_BASE64; + uudecode->mode = (mode_t)( + ((int)(b[l] - '0') * 64) + + ((int)(b[l+1] - '0') * 8) + + (int)(b[l+2] - '0')); + uudecode->mode_set = 1; + namelen = len - nl - 4 - l; + if (namelen > 1) { + if (uudecode->name != NULL) + free(uudecode->name); + uudecode->name = malloc(namelen + 1); + if (uudecode->name == NULL) { + archive_set_error( + &self->archive->archive, + ENOMEM, + "Can't allocate data for uudecode"); + return (ARCHIVE_FATAL); + } + strncpy(uudecode->name, + (const char *)(b + l + 4), + namelen); + uudecode->name[namelen] = '\0'; + } } break; case ST_READ_UU: @@ -683,6 +737,7 @@ uudecode_filter_close(struct archive_read_filter *self) uudecode = (struct uudecode *)self->data; free(uudecode->in_buff); free(uudecode->out_buff); + free(uudecode->name); free(uudecode); return (ARCHIVE_OK); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c index 90b0da2338c..cb13291b95d 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c @@ -26,8 +26,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif @@ -478,8 +476,8 @@ xz_lzma_bidder_init(struct archive_read_filter *self) struct private_data *state; int ret; - state = (struct private_data *)calloc(sizeof(*state), 1); - out_block = (unsigned char *)malloc(out_block_size); + state = calloc(1, sizeof(*state)); + out_block = malloc(out_block_size); if (state == NULL || out_block == NULL) { archive_set_error(&self->archive->archive, ENOMEM, "Can't allocate data for xz decompression"); @@ -656,13 +654,16 @@ xz_filter_read(struct archive_read_filter *self, const void **p) struct private_data *state; size_t decompressed; ssize_t avail_in; + int64_t member_in; int ret; state = (struct private_data *)self->data; + redo: /* Empty our output buffer. */ state->stream.next_out = state->out_block; state->stream.avail_out = state->out_block_size; + member_in = state->member_in; /* Try to fill the output buffer. */ while (state->stream.avail_out > 0 && !state->eof) { @@ -707,9 +708,18 @@ xz_filter_read(struct archive_read_filter *self, const void **p) decompressed = state->stream.next_out - state->out_block; state->total_out += decompressed; state->member_out += decompressed; - if (decompressed == 0) + if (decompressed == 0) { + if (member_in != state->member_in && + self->code == ARCHIVE_FILTER_LZIP && + state->eof) { + ret = lzip_tail(self); + if (ret != ARCHIVE_OK) + return (ret); + if (!state->eof) + goto redo; + } *p = NULL; - else { + } else { *p = state->out_block; if (self->code == ARCHIVE_FILTER_LZIP) { state->crc32 = lzma_crc32(state->out_block, diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_zstd.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_zstd.c index 29d4d62772d..4a88b4634e6 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_zstd.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_zstd.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif @@ -115,9 +113,9 @@ zstd_bidder_bid(struct archive_read_filter_bidder *self, unsigned prefix; /* Zstd frame magic values */ - const unsigned zstd_magic = 0xFD2FB528U; - const unsigned zstd_magic_skippable_start = 0x184D2A50U; - const unsigned zstd_magic_skippable_mask = 0xFFFFFFF0; + unsigned zstd_magic = 0xFD2FB528U; + unsigned zstd_magic_skippable_start = 0x184D2A50U; + unsigned zstd_magic_skippable_mask = 0xFFFFFFF0; (void) self; /* UNUSED */ @@ -170,15 +168,15 @@ static int zstd_bidder_init(struct archive_read_filter *self) { struct private_data *state; - const size_t out_block_size = ZSTD_DStreamOutSize(); + size_t out_block_size = ZSTD_DStreamOutSize(); void *out_block; ZSTD_DStream *dstream; self->code = ARCHIVE_FILTER_ZSTD; self->name = "zstd"; - state = (struct private_data *)calloc(sizeof(*state), 1); - out_block = (unsigned char *)malloc(out_block_size); + state = calloc(1, sizeof(*state)); + out_block = malloc(out_block_size); dstream = ZSTD_createDStream(); if (state == NULL || out_block == NULL || dstream == NULL) { @@ -211,6 +209,7 @@ zstd_filter_read(struct archive_read_filter *self, const void **p) ssize_t avail_in; ZSTD_outBuffer out; ZSTD_inBuffer in; + size_t ret; state = (struct private_data *)self->data; @@ -219,7 +218,7 @@ zstd_filter_read(struct archive_read_filter *self, const void **p) /* Try to fill the output buffer. */ while (out.pos < out.size && !state->eof) { if (!state->in_frame) { - const size_t ret = ZSTD_initDStream(state->dstream); + ret = ZSTD_initDStream(state->dstream); if (ZSTD_isError(ret)) { archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, @@ -249,8 +248,7 @@ zstd_filter_read(struct archive_read_filter *self, const void **p) in.pos = 0; { - const size_t ret = - ZSTD_decompressStream(state->dstream, &out, &in); + ret = ZSTD_decompressStream(state->dstream, &out, &in); if (ZSTD_isError(ret)) { archive_set_error(&self->archive->archive, diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c index a4d9dcf291e..6a13b0b5388 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c @@ -24,11 +24,13 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include #endif +#if HAVE_STDINT_H +#include +#endif #ifdef HAVE_STDLIB_H #include #endif @@ -41,6 +43,9 @@ __FBSDID("$FreeBSD$"); #ifdef HAVE_ZLIB_H #include #endif +#ifdef HAVE_ZSTD_H +#include +#endif #ifdef __clang_analyzer__ #include @@ -84,8 +89,11 @@ __FBSDID("$FreeBSD$"); #define _7Z_IA64 0x03030401 #define _7Z_ARM 0x03030501 #define _7Z_ARMTHUMB 0x03030701 +#define _7Z_ARM64 0xa #define _7Z_SPARC 0x03030805 +#define _7Z_ZSTD 0x4F71101 /* Copied from https://github.com/mcmilk/7-Zip-zstd.git */ + /* * 7-Zip header property IDs. */ @@ -114,6 +122,30 @@ __FBSDID("$FreeBSD$"); #define kEncodedHeader 0x17 #define kDummy 0x19 +// Check that some windows file attribute constants are defined. +// Reference: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants +#ifndef FILE_ATTRIBUTE_READONLY +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#endif + +#ifndef FILE_ATTRIBUTE_HIDDEN +#define FILE_ATTRIBUTE_HIDDEN 0x00000002 +#endif + +#ifndef FILE_ATTRIBUTE_SYSTEM +#define FILE_ATTRIBUTE_SYSTEM 0x00000004 +#endif + +#ifndef FILE_ATTRIBUTE_DIRECTORY +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#endif + +// This value is defined in 7zip with the comment "trick for Unix". +// +// 7z archives created on unix have this bit set in the high 16 bits of +// the attr field along with the unix permissions. +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 + struct _7z_digests { unsigned char *defineds; uint32_t *digests; @@ -281,6 +313,11 @@ struct _7zip { #ifdef HAVE_ZLIB_H z_stream stream; int stream_valid; +#endif + /* Decoding Zstandard data. */ +#if HAVE_ZSTD_H + ZSTD_DStream *zstd_dstream; + int zstdstream_valid; #endif /* Decoding PPMd data. */ int ppmd7_stat; @@ -401,7 +438,12 @@ static int setup_decode_folder(struct archive_read *, struct _7z_folder *, int); static void x86_Init(struct _7zip *); static size_t x86_Convert(struct _7zip *, uint8_t *, size_t); +static void arm_Init(struct _7zip *); +static size_t arm_Convert(struct _7zip *, uint8_t *, size_t); +static size_t arm64_Convert(struct _7zip *, uint8_t *, size_t); static ssize_t Bcj2_Decode(struct _7zip *, uint8_t *, size_t); +static size_t sparc_Convert(struct _7zip *, uint8_t *, size_t); +static size_t powerpc_Convert(struct _7zip *, uint8_t *, size_t); int @@ -729,6 +771,35 @@ archive_read_format_7zip_read_header(struct archive_read *a, archive_entry_set_size(entry, 0); } + // These attributes are supported by the windows implementation of archive_write_disk. + const int supported_attrs = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM; + + if (zip_entry->attr & supported_attrs) { + char *fflags_text, *ptr; + /* allocate for ",rdonly,hidden,system" */ + fflags_text = malloc(22 * sizeof(*fflags_text)); + if (fflags_text != NULL) { + ptr = fflags_text; + if (zip_entry->attr & FILE_ATTRIBUTE_READONLY) { + strcpy(ptr, ",rdonly"); + ptr = ptr + 7; + } + if (zip_entry->attr & FILE_ATTRIBUTE_HIDDEN) { + strcpy(ptr, ",hidden"); + ptr = ptr + 7; + } + if (zip_entry->attr & FILE_ATTRIBUTE_SYSTEM) { + strcpy(ptr, ",system"); + ptr = ptr + 7; + } + if (ptr > fflags_text) { + archive_entry_copy_fflags_text(entry, + fflags_text + 1); + } + free(fflags_text); + } + } + /* If there's no body, force read_data() to return EOF immediately. */ if (zip->entry_bytes_remaining < 1) zip->end_of_entry = 1; @@ -774,9 +845,20 @@ archive_read_format_7zip_read_header(struct archive_read *a, zip_entry->mode |= AE_IFREG; archive_entry_set_mode(entry, zip_entry->mode); } else { + struct archive_string_conv* utf8_conv; + symname[symsize] = '\0'; - archive_entry_copy_symlink(entry, - (const char *)symname); + + /* Symbolic links are embedded as UTF-8 strings */ + utf8_conv = archive_string_conversion_from_charset(&a->archive, + "UTF-8", 1); + if (utf8_conv == NULL) { + free(symname); + return ARCHIVE_FATAL; + } + + archive_entry_copy_symlink_l(entry, (const char*)symname, symsize, + utf8_conv); } free(symname); archive_entry_set_size(entry, 0); @@ -816,10 +898,9 @@ archive_read_format_7zip_read_data(struct archive_read *a, if (zip->end_of_entry) return (ARCHIVE_EOF); - const uint64_t max_read_size = 16 * 1024 * 1024; // Don't try to read more than 16 MB at a time - size_t bytes_to_read = max_read_size; + size_t bytes_to_read = 16 * 1024 * 1024; // Don't try to read more than 16 MB at a time if ((uint64_t)bytes_to_read > zip->entry_bytes_remaining) { - bytes_to_read = zip->entry_bytes_remaining; + bytes_to_read = (size_t)zip->entry_bytes_remaining; } bytes = read_stream(a, buff, bytes_to_read, 0); if (bytes < 0) @@ -1002,8 +1083,8 @@ ppmd_read(void *p) */ ssize_t bytes_avail = 0; const uint8_t* data = __archive_read_ahead(a, - zip->ppstream.stream_in+1, &bytes_avail); - if(bytes_avail < zip->ppstream.stream_in+1) { + (size_t)zip->ppstream.stream_in+1, &bytes_avail); + if(data == NULL || bytes_avail < zip->ppstream.stream_in+1) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Truncated 7z file data"); @@ -1034,10 +1115,15 @@ init_decompression(struct archive_read *a, struct _7zip *zip, case _7Z_COPY: case _7Z_BZ2: case _7Z_DEFLATE: + case _7Z_ZSTD: case _7Z_PPMD: if (coder2 != NULL) { if (coder2->codec != _7Z_X86 && - coder2->codec != _7Z_X86_BCJ2) { + coder2->codec != _7Z_X86_BCJ2 && + coder2->codec != _7Z_ARM && + coder2->codec != _7Z_ARM64 && + coder2->codec != _7Z_POWERPC && + coder2->codec != _7Z_SPARC) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Unsupported filter %lx for %lx", @@ -1048,6 +1134,8 @@ init_decompression(struct archive_read *a, struct _7zip *zip, zip->bcj_state = 0; if (coder2->codec == _7Z_X86) x86_Init(zip); + else if (coder2->codec == _7Z_ARM) + arm_Init(zip); } break; default: @@ -1144,6 +1232,12 @@ init_decompression(struct archive_read *a, struct _7zip *zip, filters[fi].id = LZMA_FILTER_ARMTHUMB; fi++; break; +#ifdef LZMA_FILTER_ARM64 + case _7Z_ARM64: + filters[fi].id = LZMA_FILTER_ARM64; + fi++; + break; +#endif case _7Z_SPARC: filters[fi].id = LZMA_FILTER_SPARC; fi++; @@ -1229,6 +1323,22 @@ init_decompression(struct archive_read *a, struct _7zip *zip, "BZ2 codec is unsupported"); return (ARCHIVE_FAILED); #endif + case _7Z_ZSTD: + { +#if defined(HAVE_ZSTD_H) + if (zip->zstdstream_valid) { + ZSTD_freeDStream(zip->zstd_dstream); + zip->zstdstream_valid = 0; + } + zip->zstd_dstream = ZSTD_createDStream(); + zip->zstdstream_valid = 1; + break; +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZSTD codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + } case _7Z_DEFLATE: #ifdef HAVE_ZLIB_H if (zip->stream_valid) @@ -1299,6 +1409,7 @@ init_decompression(struct archive_read *a, struct _7zip *zip, case _7Z_IA64: case _7Z_ARM: case _7Z_ARMTHUMB: + case _7Z_ARM64: case _7Z_SPARC: case _7Z_DELTA: archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, @@ -1443,9 +1554,9 @@ decompress(struct archive_read *a, struct _7zip *zip, #if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) case _7Z_BZ2: zip->bzstream.next_in = (char *)(uintptr_t)t_next_in; - zip->bzstream.avail_in = t_avail_in; + zip->bzstream.avail_in = (uint32_t)t_avail_in; zip->bzstream.next_out = (char *)(uintptr_t)t_next_out; - zip->bzstream.avail_out = t_avail_out; + zip->bzstream.avail_out = (uint32_t)t_avail_out; r = BZ2_bzDecompress(&(zip->bzstream)); switch (r) { case BZ_STREAM_END: /* Found end of stream. */ @@ -1494,6 +1605,22 @@ decompress(struct archive_read *a, struct _7zip *zip, t_avail_in = zip->stream.avail_in; t_avail_out = zip->stream.avail_out; break; +#endif +#ifdef HAVE_ZSTD_H + case _7Z_ZSTD: + { + ZSTD_inBuffer input = { t_next_in, t_avail_in, 0 }; // src, size, pos + ZSTD_outBuffer output = { t_next_out, t_avail_out, 0 }; // dst, size, pos + + size_t const zret = ZSTD_decompressStream(zip->zstd_dstream, &output, &input); + if (ZSTD_isError(zret)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Zstd decompression failed: %s", ZSTD_getErrorName(zret)); + return ARCHIVE_FAILED; + } + t_avail_in -= input.pos; + t_avail_out -= output.pos; + break; + } #endif case _7Z_PPMD: { @@ -1579,16 +1706,27 @@ decompress(struct archive_read *a, struct _7zip *zip, /* * Decord BCJ. */ - if (zip->codec != _7Z_LZMA2 && zip->codec2 == _7Z_X86) { - size_t l = x86_Convert(zip, buff, *outbytes); - zip->odd_bcj_size = *outbytes - l; - if (zip->odd_bcj_size > 0 && zip->odd_bcj_size <= 4 && - o_avail_in && ret != ARCHIVE_EOF) { - memcpy(zip->odd_bcj, ((unsigned char *)buff) + l, - zip->odd_bcj_size); - *outbytes = l; - } else - zip->odd_bcj_size = 0; + if (zip->codec != _7Z_LZMA2) { + if (zip->codec2 == _7Z_X86) { + size_t l = x86_Convert(zip, buff, *outbytes); + + zip->odd_bcj_size = *outbytes - l; + if (zip->odd_bcj_size > 0 && zip->odd_bcj_size <= 4 && + o_avail_in && ret != ARCHIVE_EOF) { + memcpy(zip->odd_bcj, ((unsigned char *)buff) + l, + zip->odd_bcj_size); + *outbytes = l; + } else + zip->odd_bcj_size = 0; + } else if (zip->codec2 == _7Z_ARM) { + *outbytes = arm_Convert(zip, buff, *outbytes); + } else if (zip->codec2 == _7Z_ARM64) { + *outbytes = arm64_Convert(zip, buff, *outbytes); + } else if (zip->codec2 == _7Z_SPARC) { + *outbytes = sparc_Convert(zip, buff, *outbytes); + } else if (zip->codec2 == _7Z_POWERPC) { + *outbytes = powerpc_Convert(zip, buff, *outbytes); + } } /* @@ -1654,6 +1792,10 @@ free_decompression(struct archive_read *a, struct _7zip *zip) } zip->stream_valid = 0; } +#endif +#ifdef HAVE_ZSTD_H + if (zip->zstdstream_valid) + ZSTD_freeDStream(zip->zstd_dstream); #endif if (zip->ppmd7_valid) { __archive_ppmd7_functions.Ppmd7_Free( @@ -1925,6 +2067,8 @@ read_Folder(struct archive_read *a, struct _7z_folder *f) if (parse_7zip_uint64( a, &(f->coders[i].propertiesSize)) < 0) return (-1); + if (UMAX_ENTRY < f->coders[i].propertiesSize) + return (-1); if ((p = header_bytes( a, (size_t)f->coders[i].propertiesSize)) == NULL) return (-1); @@ -2194,7 +2338,7 @@ read_SubStreamsInfo(struct archive_read *a, struct _7z_substream_info *ss, usizes = ss->unpackSizes; for (i = 0; i < numFolders; i++) { unsigned pack; - uint64_t sum; + uint64_t size, sum; if (f[i].numUnpackStreams == 0) continue; @@ -2204,10 +2348,15 @@ read_SubStreamsInfo(struct archive_read *a, struct _7z_substream_info *ss, for (pack = 1; pack < f[i].numUnpackStreams; pack++) { if (parse_7zip_uint64(a, usizes) < 0) return (-1); + if (*usizes > UINT64_MAX - sum) + return (-1); sum += *usizes++; } } - *usizes++ = folder_uncompressed_size(&f[i]) - sum; + size = folder_uncompressed_size(&f[i]); + if (size < sum) + return (-1); + *usizes++ = size - sum; } if (type == kSize) { @@ -2301,6 +2450,8 @@ read_StreamsInfo(struct archive_read *a, struct _7z_stream_info *si) packPos = si->pi.pos; for (i = 0; i < si->pi.numPackStreams; i++) { si->pi.positions[i] = packPos; + if (packPos > UINT64_MAX - si->pi.sizes[i]) + return (-1); packPos += si->pi.sizes[i]; if (packPos > zip->header_offset) return (-1); @@ -2322,6 +2473,10 @@ read_StreamsInfo(struct archive_read *a, struct _7z_stream_info *si) f = si->ci.folders; for (i = 0; i < si->ci.numFolders; i++) { f[i].packIndex = packIndex; + if (f[i].numPackedStreams > UINT32_MAX) + return (-1); + if (packIndex > UINT32_MAX - (uint32_t)f[i].numPackedStreams) + return (-1); packIndex += (uint32_t)f[i].numPackedStreams; if (packIndex > si->pi.numPackStreams) return (-1); @@ -2612,6 +2767,28 @@ read_Header(struct archive_read *a, struct _7z_header_info *h, entries[i].flg |= HAS_STREAM; /* The high 16 bits of attributes is a posix file mode. */ entries[i].mode = entries[i].attr >> 16; + + if (!(entries[i].attr & FILE_ATTRIBUTE_UNIX_EXTENSION)) { + // Only windows permissions specified for this entry. Translate to + // reasonable corresponding unix permissions. + + if (entries[i].attr & FILE_ATTRIBUTE_DIRECTORY) { + if (entries[i].attr & FILE_ATTRIBUTE_READONLY) { + // Read-only directory. + entries[i].mode = AE_IFDIR | 0555; + } else { + // Read-write directory. + entries[i].mode = AE_IFDIR | 0755; + } + } else if (entries[i].attr & FILE_ATTRIBUTE_READONLY) { + // Readonly file. + entries[i].mode = AE_IFREG | 0444; + } else { + // Assume read-write file. + entries[i].mode = AE_IFREG | 0644; + } + } + if (entries[i].flg & HAS_STREAM) { if ((size_t)sindex >= si->ss.unpack_streams) return (-1); @@ -2652,7 +2829,7 @@ read_Header(struct archive_read *a, struct _7z_header_info *h, } entries[i].ssIndex = -1; } - if (entries[i].attr & 0x01) + if (entries[i].attr & FILE_ATTRIBUTE_READONLY) entries[i].mode &= ~0222;/* Read only. */ if ((entries[i].flg & HAS_STREAM) == 0 && indexInFolder == 0) { @@ -2867,7 +3044,7 @@ slurp_central_directory(struct archive_read *a, struct _7zip *zip, /* CRC check. */ if (crc32(0, (const unsigned char *)p + 12, 20) != archive_le32dec(p + 8)) { -#ifdef DONT_FAIL_ON_CRC_ERROR +#ifndef DONT_FAIL_ON_CRC_ERROR archive_set_error(&a->archive, -1, "Header CRC error"); return (ARCHIVE_FATAL); #endif @@ -2919,8 +3096,8 @@ slurp_central_directory(struct archive_read *a, struct _7zip *zip, /* Check the EncodedHeader CRC.*/ if (r == 0 && zip->header_crc32 != next_header_crc) { - archive_set_error(&a->archive, -1, #ifndef DONT_FAIL_ON_CRC_ERROR + archive_set_error(&a->archive, -1, "Damaged 7-Zip archive"); r = -1; #endif @@ -3009,7 +3186,7 @@ get_uncompressed_data(struct archive_read *a, const void **buff, size_t size, /* Copy mode. */ *buff = __archive_read_ahead(a, minimum, &bytes_avail); - if (bytes_avail <= 0) { + if (*buff == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Truncated 7-Zip file data"); @@ -3315,7 +3492,7 @@ read_stream(struct archive_read *a, const void **buff, size_t size, /* * Skip the bytes we already has skipped in skip_stream(). */ - while (skip_bytes) { + while (1) { ssize_t skipped; if (zip->uncompressed_buffer_bytes_remaining == 0) { @@ -3335,6 +3512,10 @@ read_stream(struct archive_read *a, const void **buff, size_t size, return (ARCHIVE_FATAL); } } + + if (!skip_bytes) + break; + skipped = get_uncompressed_data( a, buff, (size_t)skip_bytes, 0); if (skipped < 0) @@ -3737,6 +3918,231 @@ x86_Convert(struct _7zip *zip, uint8_t *data, size_t size) return (bufferPos); } +static void +arm_Init(struct _7zip *zip) +{ + zip->bcj_ip = 8; +} + +static size_t +arm_Convert(struct _7zip *zip, uint8_t *buf, size_t size) +{ + // This function was adapted from + // static size_t bcj_arm(struct xz_dec_bcj *s, uint8_t *buf, size_t size) + // in https://git.tukaani.org/xz-embedded.git + + /* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin + * Igor Pavlov + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 4) { + if (buf[i + 3] == 0xEB) { + // Calculate the transformed addr. + addr = (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) + | ((uint32_t)buf[i + 2] << 16); + addr <<= 2; + addr -= zip->bcj_ip + (uint32_t)i; + addr >>= 2; + + // Store the transformed addr in buf. + buf[i] = (uint8_t)addr; + buf[i + 1] = (uint8_t)(addr >> 8); + buf[i + 2] = (uint8_t)(addr >> 16); + } + } + + zip->bcj_ip += (uint32_t)i; + + return i; +} + +static size_t +arm64_Convert(struct _7zip *zip, uint8_t *buf, size_t size) +{ + // This function was adapted from + // static size_t bcj_arm64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) + // in https://git.tukaani.org/xz-embedded.git + + /* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin + * Igor Pavlov + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + + size_t i; + uint32_t instr; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 4) { + instr = (uint32_t)buf[i] + | ((uint32_t)buf[i+1] << 8) + | ((uint32_t)buf[i+2] << 16) + | ((uint32_t)buf[i+3] << 24); + + if ((instr >> 26) == 0x25) { + /* BL instruction */ + addr = instr - ((zip->bcj_ip + (uint32_t)i) >> 2); + instr = 0x94000000 | (addr & 0x03FFFFFF); + + buf[i] = (uint8_t)instr; + buf[i+1] = (uint8_t)(instr >> 8); + buf[i+2] = (uint8_t)(instr >> 16); + buf[i+3] = (uint8_t)(instr >> 24); + } else if ((instr & 0x9F000000) == 0x90000000) { + /* ADRP instruction */ + addr = ((instr >> 29) & 3) | ((instr >> 3) & 0x1FFFFC); + + /* Only convert values in the range +/-512 MiB. */ + if ((addr + 0x020000) & 0x1C0000) + continue; + + addr -= (zip->bcj_ip + (uint32_t)i) >> 12; + + instr &= 0x9000001F; + instr |= (addr & 3) << 29; + instr |= (addr & 0x03FFFC) << 3; + instr |= (0U - (addr & 0x020000)) & 0xE00000; + + buf[i] = (uint8_t)instr; + buf[i+1] = (uint8_t)(instr >> 8); + buf[i+2] = (uint8_t)(instr >> 16); + buf[i+3] = (uint8_t)(instr >> 24); + } + } + + zip->bcj_ip += (uint32_t)i; + + return i; +} + +static size_t +sparc_Convert(struct _7zip *zip, uint8_t *buf, size_t size) +{ + // This function was adapted from + // static size_t bcj_sparc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) + // in https://git.tukaani.org/xz-embedded.git + + /* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin + * Igor Pavlov + * + * Copyright (C) The XZ Embedded authors and contributors + * + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + size_t i; + uint32_t instr; + + size &= ~(size_t)3; + + for (i = 0; i < size; i += 4) { + instr = (uint32_t)(buf[i] << 24) + | ((uint32_t)buf[i+1] << 16) + | ((uint32_t)buf[i+2] << 8) + | (uint32_t)buf[i+3]; + + if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) { + instr <<= 2; + instr -= zip->bcj_ip + (uint32_t)i; + instr >>= 2; + instr = ((uint32_t)0x40000000 - (instr & 0x400000)) + | 0x40000000 | (instr & 0x3FFFFF); + + buf[i] = (uint8_t)(instr >> 24); + buf[i+1] = (uint8_t)(instr >> 16); + buf[i+2] = (uint8_t)(instr >> 8); + buf[i+3] = (uint8_t)instr; + } + } + + zip->bcj_ip += (uint32_t)i; + + return i; +} + +static size_t +powerpc_Convert(struct _7zip *zip, uint8_t *buf, size_t size) +{ + // This function was adapted from + // static size_t powerpc_code(void *simple, uint32_t now_pos, bool is_encoder, uint8_t *buffer, size_t size) + // in https://git.tukaani.org/xz.git + + /* + * Filter for PowerPC (big endian) binaries + * + * Authors: Igor Pavlov + * Lasse Collin + * + * Copyright (C) The XZ Utils authors and contributors + * + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + size &= ~(size_t)3; + + size_t i; + for (i = 0; i < size; i += 4) { + // PowerPC branch 6(48) 24(Offset) 1(Abs) 1(Link) + if ((buf[i] >> 2) == 0x12 + && ((buf[i + 3] & 3) == 1)) { + + const uint32_t src + = (((uint32_t)(buf[i + 0]) & 3) << 24) + | ((uint32_t)(buf[i + 1]) << 16) + | ((uint32_t)(buf[i + 2]) << 8) + | ((uint32_t)(buf[i + 3]) & ~UINT32_C(3)); + + uint32_t dest = src - (zip->bcj_ip + (uint32_t)(i)); + + buf[i + 0] = 0x48 | ((dest >> 24) & 0x03); + buf[i + 1] = (dest >> 16); + buf[i + 2] = (dest >> 8); + buf[i + 3] &= 0x03; + buf[i + 3] |= dest; + } + } + + zip->bcj_ip += (uint32_t)i; + + return i; +} + /* * Brought from LZMA SDK. * @@ -3759,8 +4165,17 @@ x86_Convert(struct _7zip *zip, uint8_t *data, size_t size) #define RC_READ_BYTE (*buffer++) #define RC_TEST { if (buffer == bufferLim) return SZ_ERROR_DATA; } -#define RC_INIT2 zip->bcj2_code = 0; zip->bcj2_range = 0xFFFFFFFF; \ - { int ii; for (ii = 0; ii < 5; ii++) { RC_TEST; zip->bcj2_code = (zip->bcj2_code << 8) | RC_READ_BYTE; }} +#define RC_INIT2 do { \ + zip->bcj2_code = 0; \ + zip->bcj2_range = 0xFFFFFFFF; \ + { \ + int ii; \ + for (ii = 0; ii < 5; ii++) { \ + RC_TEST; \ + zip->bcj2_code = (zip->bcj2_code << 8) | RC_READ_BYTE; \ + } \ + } \ +} while (0) #define NORMALIZE if (zip->bcj2_range < kTopValue) { RC_TEST; zip->bcj2_range <<= 8; zip->bcj2_code = (zip->bcj2_code << 8) | RC_READ_BYTE; } diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_all.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_all.c index dea558bbfcc..3b53c9ad5f5 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_all.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_all.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_all.c 174991 2007-12-30 04:58:22Z kientzle $"); #include "archive.h" #include "archive_private.h" @@ -68,7 +67,7 @@ archive_read_support_format_all(struct archive *a) * increase the chance that a high bid from someone else will * make it unnecessary for these to do anything at all. */ - /* These three have potentially large look-ahead. */ + /* These have potentially large look-ahead. */ archive_read_support_format_7zip(a); archive_read_support_format_cab(a); archive_read_support_format_rar(a); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_ar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_ar.c index 296b7db0411..6dfe2939d09 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_ar.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_ar.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_ar.c 201101 2009-12-28 03:06:27Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -104,7 +103,7 @@ archive_read_support_format_ar(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_ar"); - ar = (struct ar *)calloc(1, sizeof(*ar)); + ar = calloc(1, sizeof(*ar)); if (ar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate ar data"); @@ -271,7 +270,7 @@ _ar_read_header(struct archive_read *a, struct archive_entry *entry, } if (ar->strtab != NULL) { archive_set_error(&a->archive, EINVAL, - "More than one string tables exist"); + "More than one string table exists"); return (ARCHIVE_FATAL); } @@ -369,7 +368,7 @@ _ar_read_header(struct archive_read *a, struct archive_entry *entry, return (ARCHIVE_FATAL); } /* Store it in the entry. */ - p = (char *)malloc(bsd_name_length + 1); + p = malloc(bsd_name_length + 1); if (p == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate fname buffer"); @@ -440,9 +439,9 @@ archive_read_format_ar_read_header(struct archive_read *a, if ((header_data = __archive_read_ahead(a, 60, NULL)) == NULL) /* Broken header. */ return (ARCHIVE_EOF); - + unconsumed = 60; - + ret = _ar_read_header(a, entry, ar, (const char *)header_data, &unconsumed); if (unconsumed) @@ -459,7 +458,6 @@ ar_parse_common_header(struct ar *ar, struct archive_entry *entry, uint64_t n; /* Copy remaining header */ - archive_entry_set_filetype(entry, AE_IFREG); archive_entry_set_mtime(entry, (time_t)ar_atol10(h + AR_date_offset, AR_date_size), 0L); archive_entry_set_uid(entry, @@ -468,6 +466,7 @@ ar_parse_common_header(struct ar *ar, struct archive_entry *entry, (gid_t)ar_atol10(h + AR_gid_offset, AR_gid_size)); archive_entry_set_mode(entry, (mode_t)ar_atol8(h + AR_mode_offset, AR_mode_size)); + archive_entry_set_filetype(entry, AE_IFREG); n = ar_atol10(h + AR_size_offset, AR_size_size); ar->entry_offset = 0; @@ -516,7 +515,7 @@ archive_read_format_ar_read_data(struct archive_read *a, if (ar->entry_padding) { if (skipped >= 0) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Truncated ar archive- failed consuming padding"); + "Truncated ar archive - failed consuming padding"); } return (ARCHIVE_FATAL); } diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_by_code.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_by_code.c index 89e96f1f591..982e2db70f9 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_by_code.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_by_code.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -42,49 +41,34 @@ archive_read_support_format_by_code(struct archive *a, int format_code) switch (format_code & ARCHIVE_FORMAT_BASE_MASK) { case ARCHIVE_FORMAT_7ZIP: return archive_read_support_format_7zip(a); - break; case ARCHIVE_FORMAT_AR: return archive_read_support_format_ar(a); - break; case ARCHIVE_FORMAT_CAB: return archive_read_support_format_cab(a); - break; case ARCHIVE_FORMAT_CPIO: return archive_read_support_format_cpio(a); - break; case ARCHIVE_FORMAT_EMPTY: return archive_read_support_format_empty(a); - break; case ARCHIVE_FORMAT_ISO9660: return archive_read_support_format_iso9660(a); - break; case ARCHIVE_FORMAT_LHA: return archive_read_support_format_lha(a); - break; case ARCHIVE_FORMAT_MTREE: return archive_read_support_format_mtree(a); - break; case ARCHIVE_FORMAT_RAR: return archive_read_support_format_rar(a); - break; case ARCHIVE_FORMAT_RAR_V5: return archive_read_support_format_rar5(a); - break; case ARCHIVE_FORMAT_RAW: return archive_read_support_format_raw(a); - break; case ARCHIVE_FORMAT_TAR: return archive_read_support_format_tar(a); - break; case ARCHIVE_FORMAT_WARC: return archive_read_support_format_warc(a); - break; case ARCHIVE_FORMAT_XAR: return archive_read_support_format_xar(a); - break; case ARCHIVE_FORMAT_ZIP: return archive_read_support_format_zip(a); - break; } archive_set_error(a, ARCHIVE_ERRNO_PROGRAMMER, "Invalid format code specified"); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c index 6fcfbfcd308..6c0254644e5 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c @@ -356,7 +356,7 @@ archive_read_support_format_cab(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_cab"); - cab = (struct cab *)calloc(1, sizeof(*cab)); + cab = calloc(1, sizeof(*cab)); if (cab == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate CAB data"); @@ -717,7 +717,7 @@ cab_read_header(struct archive_read *a) /* * Read CFFOLDER. */ - hd->folder_array = (struct cffolder *)calloc( + hd->folder_array = calloc( hd->folder_count, sizeof(struct cffolder)); if (hd->folder_array == NULL) goto nomem; @@ -780,7 +780,7 @@ cab_read_header(struct archive_read *a) cab->cab_offset += skip; } /* Allocate memory for CFDATA */ - hd->file_array = (struct cffile *)calloc( + hd->file_array = calloc( hd->file_count, sizeof(struct cffile)); if (hd->file_array == NULL) goto nomem; @@ -1412,7 +1412,7 @@ cab_read_ahead_cfdata_deflate(struct archive_read *a, ssize_t *avail) if (cab->uncompressed_buffer == NULL) { cab->uncompressed_buffer_size = 0x8000; cab->uncompressed_buffer - = (unsigned char *)malloc(cab->uncompressed_buffer_size); + = malloc(cab->uncompressed_buffer_size); if (cab->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory for CAB reader"); @@ -1641,7 +1641,7 @@ cab_read_ahead_cfdata_lzx(struct archive_read *a, ssize_t *avail) if (cab->uncompressed_buffer == NULL) { cab->uncompressed_buffer_size = 0x8000; cab->uncompressed_buffer - = (unsigned char *)malloc(cab->uncompressed_buffer_size); + = malloc(cab->uncompressed_buffer_size); if (cab->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory for CAB reader"); @@ -1682,7 +1682,7 @@ cab_read_ahead_cfdata_lzx(struct archive_read *a, ssize_t *avail) cfdata->uncompressed_size - cab->xstrm.total_out; d = __archive_read_ahead(a, 1, &bytes_avail); - if (bytes_avail <= 0) { + if (d == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Truncated CAB file data"); @@ -2294,10 +2294,10 @@ lzx_br_fillup(struct lzx_stream *strm, struct lzx_br *br) (br->cache_buffer << 48) | ((uint64_t)strm->next_in[1]) << 40 | ((uint64_t)strm->next_in[0]) << 32 | - ((uint32_t)strm->next_in[3]) << 24 | - ((uint32_t)strm->next_in[2]) << 16 | - ((uint32_t)strm->next_in[5]) << 8 | - (uint32_t)strm->next_in[4]; + ((uint64_t)strm->next_in[3]) << 24 | + ((uint64_t)strm->next_in[2]) << 16 | + ((uint64_t)strm->next_in[5]) << 8 | + (uint64_t)strm->next_in[4]; strm->next_in += 6; strm->avail_in -= 6; br->cache_avail += 6 * 8; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_cpio.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_cpio.c index 6b8ae33a480..42a89c1b69c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_cpio.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_cpio.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_cpio.c 201163 2009-12-29 05:50:34Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -229,7 +228,7 @@ archive_read_support_format_cpio(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_cpio"); - cpio = (struct cpio *)calloc(1, sizeof(*cpio)); + cpio = calloc(1, sizeof(*cpio)); if (cpio == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); return (ARCHIVE_FATAL); @@ -441,7 +440,7 @@ archive_read_format_cpio_read_header(struct archive_read *a, /* Compare name to "TRAILER!!!" to test for end-of-archive. */ if (namelength == 11 && strncmp((const char *)h, "TRAILER!!!", - 11) == 0) { + 10) == 0) { /* TODO: Store file location of start of block. */ archive_clear_error(&a->archive); return (ARCHIVE_EOF); @@ -835,6 +834,7 @@ static int header_afiol(struct archive_read *a, struct cpio *cpio, struct archive_entry *entry, size_t *namelength, size_t *name_pad) { + int64_t t; const void *h; const char *header; @@ -851,7 +851,12 @@ header_afiol(struct archive_read *a, struct cpio *cpio, archive_entry_set_dev(entry, (dev_t)atol16(header + afiol_dev_offset, afiol_dev_size)); - archive_entry_set_ino(entry, atol16(header + afiol_ino_offset, afiol_ino_size)); + t = atol16(header + afiol_ino_offset, afiol_ino_size); + if (t < 0) { + archive_set_error(&a->archive, 0, "Nonsensical ino value"); + return (ARCHIVE_FATAL); + } + archive_entry_set_ino(entry, t); archive_entry_set_mode(entry, (mode_t)atol8(header + afiol_mode_offset, afiol_mode_size)); archive_entry_set_uid(entry, atol16(header + afiol_uid_offset, afiol_uid_size)); @@ -864,8 +869,12 @@ header_afiol(struct archive_read *a, struct cpio *cpio, *namelength = (size_t)atol16(header + afiol_namesize_offset, afiol_namesize_size); *name_pad = 0; /* No padding of filename. */ - cpio->entry_bytes_remaining = - atol16(header + afiol_filesize_offset, afiol_filesize_size); + t = atol16(header + afiol_filesize_offset, afiol_filesize_size); + if (t < 0) { + archive_set_error(&a->archive, 0, "Nonsensical file size"); + return (ARCHIVE_FATAL); + } + cpio->entry_bytes_remaining = t; archive_entry_set_size(entry, cpio->entry_bytes_remaining); cpio->entry_padding = 0; __archive_read_consume(a, afiol_header_size); @@ -985,14 +994,14 @@ archive_read_format_cpio_cleanup(struct archive_read *a) static int64_t le4(const unsigned char *p) { - return ((p[0] << 16) + (((int64_t)p[1]) << 24) + (p[2] << 0) + (p[3] << 8)); + return ((p[0] << 16) | (((int64_t)p[1]) << 24) | (p[2] << 0) | (p[3] << 8)); } static int64_t be4(const unsigned char *p) { - return ((((int64_t)p[0]) << 24) + (p[1] << 16) + (p[2] << 8) + (p[3])); + return ((((int64_t)p[0]) << 24) | (p[1] << 16) | (p[2] << 8) | (p[3])); } /* @@ -1003,7 +1012,7 @@ be4(const unsigned char *p) static int64_t atol8(const char *p, unsigned char_cnt) { - int64_t l; + uint64_t l; int digit; l = 0; @@ -1011,18 +1020,18 @@ atol8(const char *p, unsigned char_cnt) if (*p >= '0' && *p <= '7') digit = *p - '0'; else - return (l); + return ((int64_t)l); p++; l <<= 3; l |= digit; } - return (l); + return ((int64_t)l); } static int64_t atol16(const char *p, unsigned char_cnt) { - int64_t l; + uint64_t l; int digit; l = 0; @@ -1034,12 +1043,12 @@ atol16(const char *p, unsigned char_cnt) else if (*p >= '0' && *p <= '9') digit = *p - '0'; else - return (l); + return ((int64_t)l); p++; l <<= 4; l |= digit; } - return (l); + return ((int64_t)l); } static int @@ -1079,7 +1088,7 @@ record_hardlink(struct archive_read *a, } } - le = (struct links_entry *)malloc(sizeof(struct links_entry)); + le = malloc(sizeof(struct links_entry)); if (le == NULL) { archive_set_error(&a->archive, ENOMEM, "Out of memory adding file to list"); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_empty.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_empty.c index 53fb6cc4743..0dccd9d9bab 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_empty.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_empty.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_empty.c 191524 2009-04-26 18:24:14Z kientzle $"); #include "archive.h" #include "archive_entry.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c index 91b918734df..cca5a4bfd61 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_iso9660.c 201246 2009-12-30 05:30:35Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -274,7 +273,7 @@ struct file_info { char re; /* Having RRIP "RE" extension. */ char re_descendant; uint64_t cl_offset; /* Having RRIP "CL" extension. */ - int birthtime_is_set; + int time_is_set; /* Bitmask indicating which times are known */ time_t birthtime; /* File created time. */ time_t mtime; /* File last modified time. */ time_t atime; /* File last accessed time. */ @@ -307,6 +306,11 @@ struct file_info { } rede_files; }; +#define BIRTHTIME_IS_SET 1 +#define MTIME_IS_SET 2 +#define ATIME_IS_SET 4 +#define CTIME_IS_SET 8 + struct heap_queue { struct file_info **files; int allocated; @@ -395,7 +399,9 @@ static void dump_isodirrec(FILE *, const unsigned char *isodirrec); #endif static time_t time_from_tm(struct tm *); static time_t isodate17(const unsigned char *); +static int isodate17_valid(const unsigned char *); static time_t isodate7(const unsigned char *); +static int isodate7_valid(const unsigned char *); static int isBootRecord(struct iso9660 *, const unsigned char *); static int isVolumePartition(struct iso9660 *, const unsigned char *); static int isVDSetTerminator(struct iso9660 *, const unsigned char *); @@ -403,6 +409,9 @@ static int isJolietSVD(struct iso9660 *, const unsigned char *); static int isSVD(struct iso9660 *, const unsigned char *); static int isEVD(struct iso9660 *, const unsigned char *); static int isPVD(struct iso9660 *, const unsigned char *); +static int isRootDirectoryRecord(const unsigned char *); +static int isValid723Integer(const unsigned char *); +static int isValid733Integer(const unsigned char *); static int next_cache_entry(struct archive_read *, struct iso9660 *, struct file_info **); static int next_entry_seek(struct archive_read *, struct iso9660 *, @@ -454,7 +463,7 @@ archive_read_support_format_iso9660(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_iso9660"); - iso9660 = (struct iso9660 *)calloc(1, sizeof(*iso9660)); + iso9660 = calloc(1, sizeof(*iso9660)); if (iso9660 == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate iso9660 data"); @@ -774,8 +783,9 @@ isSVD(struct iso9660 *iso9660, const unsigned char *h) /* Read Root Directory Record in Volume Descriptor. */ p = h + SVD_root_directory_record_offset; - if (p[DR_length_offset] != 34) + if (!isRootDirectoryRecord(p)) { return (0); + } return (48); } @@ -852,8 +862,9 @@ isEVD(struct iso9660 *iso9660, const unsigned char *h) /* Read Root Directory Record in Volume Descriptor. */ p = h + PVD_root_directory_record_offset; - if (p[DR_length_offset] != 34) + if (!isRootDirectoryRecord(p)) { return (0); + } return (48); } @@ -883,21 +894,43 @@ isPVD(struct iso9660 *iso9660, const unsigned char *h) if (!isNull(iso9660, h, PVD_reserved2_offset, PVD_reserved2_size)) return (0); + /* Volume space size must be encoded according to 7.3.3 */ + if (!isValid733Integer(h + PVD_volume_space_size_offset)) { + return (0); + } + volume_block = archive_le32dec(h + PVD_volume_space_size_offset); + if (volume_block <= SYSTEM_AREA_BLOCK+4) + return (0); + /* Reserved field must be 0. */ if (!isNull(iso9660, h, PVD_reserved3_offset, PVD_reserved3_size)) return (0); + /* Volume set size must be encoded according to 7.2.3 */ + if (!isValid723Integer(h + PVD_volume_set_size_offset)) { + return (0); + } + + /* Volume sequence number must be encoded according to 7.2.3 */ + if (!isValid723Integer(h + PVD_volume_sequence_number_offset)) { + return (0); + } + /* Logical block size must be > 0. */ /* I've looked at Ecma 119 and can't find any stronger * restriction on this field. */ + if (!isValid723Integer(h + PVD_logical_block_size_offset)) { + return (0); + } logical_block_size = archive_le16dec(h + PVD_logical_block_size_offset); if (logical_block_size <= 0) return (0); - volume_block = archive_le32dec(h + PVD_volume_space_size_offset); - if (volume_block <= SYSTEM_AREA_BLOCK+4) + /* Path Table size must be encoded according to 7.3.3 */ + if (!isValid733Integer(h + PVD_path_table_size_offset)) { return (0); + } /* File structure version must be 1 for ISO9660/ECMA119. */ if (h[PVD_file_structure_version_offset] != 1) @@ -936,8 +969,9 @@ isPVD(struct iso9660 *iso9660, const unsigned char *h) /* Read Root Directory Record in Volume Descriptor. */ p = h + PVD_root_directory_record_offset; - if (p[DR_length_offset] != 34) + if (!isRootDirectoryRecord(p)) { return (0); + } if (!iso9660->primary.location) { iso9660->logical_block_size = logical_block_size; @@ -952,6 +986,51 @@ isPVD(struct iso9660 *iso9660, const unsigned char *h) return (48); } +static int +isRootDirectoryRecord(const unsigned char *p) { + int flags; + + /* ECMA119/ISO9660 requires that the root directory record be _exactly_ 34 bytes. + * However, we've seen images that have root directory records up to 68 bytes. */ + if (p[DR_length_offset] < 34 || p[DR_length_offset] > 68) { + return (0); + } + + /* The root directory location must be a 7.3.3 32-bit integer. */ + if (!isValid733Integer(p + DR_extent_offset)) { + return (0); + } + + /* The root directory size must be a 7.3.3 integer. */ + if (!isValid733Integer(p + DR_size_offset)) { + return (0); + } + + /* According to the standard, certain bits must be one or zero: + * Bit 1: must be 1 (this is a directory) + * Bit 2: must be 0 (not an associated file) + * Bit 3: must be 0 (doesn't use extended attribute record) + * Bit 7: must be 0 (final directory record for this file) + */ + flags = p[DR_flags_offset]; + if ((flags & 0x8E) != 0x02) { + return (0); + } + + /* Volume sequence number must be a 7.2.3 integer. */ + if (!isValid723Integer(p + DR_volume_sequence_number_offset)) { + return (0); + } + + /* Root directory name is a single zero byte... */ + if (p[DR_name_len_offset] != 1 || p[DR_name_offset] != 0) { + return (0); + } + + /* Nothing looked wrong, so let's accept it. */ + return (1); +} + static int read_children(struct archive_read *a, struct file_info *parent) { @@ -1213,7 +1292,7 @@ archive_read_format_iso9660_read_header(struct archive_read *a, } } if (iso9660->utf16be_previous_path == NULL) { - iso9660->utf16be_previous_path = malloc(UTF16_NAME_MAX); + iso9660->utf16be_previous_path = calloc(1, UTF16_NAME_MAX); if (iso9660->utf16be_previous_path == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory"); @@ -1279,13 +1358,22 @@ archive_read_format_iso9660_read_header(struct archive_read *a, archive_entry_set_uid(entry, file->uid); archive_entry_set_gid(entry, file->gid); archive_entry_set_nlink(entry, file->nlinks); - if (file->birthtime_is_set) + if ((file->time_is_set & BIRTHTIME_IS_SET)) archive_entry_set_birthtime(entry, file->birthtime, 0); else archive_entry_unset_birthtime(entry); - archive_entry_set_mtime(entry, file->mtime, 0); - archive_entry_set_ctime(entry, file->ctime, 0); - archive_entry_set_atime(entry, file->atime, 0); + if ((file->time_is_set & MTIME_IS_SET)) + archive_entry_set_mtime(entry, file->mtime, 0); + else + archive_entry_unset_mtime(entry); + if ((file->time_is_set & CTIME_IS_SET)) + archive_entry_set_ctime(entry, file->ctime, 0); + else + archive_entry_unset_ctime(entry); + if ((file->time_is_set & ATIME_IS_SET)) + archive_entry_set_atime(entry, file->atime, 0); + else + archive_entry_unset_atime(entry); /* N.B.: Rock Ridge supports 64-bit device numbers. */ archive_entry_set_rdev(entry, (dev_t)file->rdev); archive_entry_set_size(entry, iso9660->entry_bytes_remaining); @@ -1341,7 +1429,7 @@ archive_read_format_iso9660_read_header(struct archive_read *a, * information first, then store all file bodies. */ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Ignoring out-of-order file @%jx (%s) %jd < %jd", - (intmax_t)file->number, + (uintmax_t)file->number, iso9660->pathname.s, (intmax_t)file->offset, (intmax_t)iso9660->current_position); @@ -1817,7 +1905,7 @@ parse_file_info(struct archive_read *a, struct file_info *parent, } /* Create a new file entry and copy data from the ISO dir record. */ - file = (struct file_info *)calloc(1, sizeof(*file)); + file = calloc(1, sizeof(*file)); if (file == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory for file entry"); @@ -1826,8 +1914,11 @@ parse_file_info(struct archive_read *a, struct file_info *parent, file->parent = parent; file->offset = offset; file->size = fsize; - file->mtime = isodate7(isodirrec + DR_date_offset); - file->ctime = file->atime = file->mtime; + if (isodate7_valid(isodirrec + DR_date_offset)) { + file->time_is_set |= MTIME_IS_SET | ATIME_IS_SET | CTIME_IS_SET; + file->mtime = isodate7(isodirrec + DR_date_offset); + file->ctime = file->atime = file->mtime; + } file->rede_files.first = NULL; file->rede_files.last = &(file->rede_files.first); @@ -1901,7 +1992,7 @@ parse_file_info(struct archive_read *a, struct file_info *parent, * NUMBER of RRIP "PX" extension. * Note: Old mkisofs did not record that FILE SERIAL NUMBER * in ISO images. - * Note2: xorriso set 0 to the location of a symlink file. + * Note2: xorriso set 0 to the location of a symlink file. */ if (file->size == 0 && location >= 0) { /* If file->size is zero, its location points wrong place, @@ -1955,7 +2046,7 @@ parse_file_info(struct archive_read *a, struct file_info *parent, * made by makefs is not zero and its location is * the same as those of next regular file. That is * the same as hard like file and it causes unexpected - * error. + * error. */ if (file->size > 0 && (file->mode & AE_IFMT) == AE_IFLNK) { @@ -2501,51 +2592,73 @@ parse_rockridge_TF1(struct file_info *file, const unsigned char *data, /* Use 17-byte time format. */ if ((flag & 1) && data_length >= 17) { /* Create time. */ - file->birthtime_is_set = 1; - file->birthtime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= BIRTHTIME_IS_SET; + file->birthtime = isodate17(data); + } data += 17; data_length -= 17; } if ((flag & 2) && data_length >= 17) { /* Modify time. */ - file->mtime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= MTIME_IS_SET; + file->mtime = isodate17(data); + } data += 17; data_length -= 17; } if ((flag & 4) && data_length >= 17) { /* Access time. */ - file->atime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= ATIME_IS_SET; + file->atime = isodate17(data); + } data += 17; data_length -= 17; } if ((flag & 8) && data_length >= 17) { /* Attribute change time. */ - file->ctime = isodate17(data); + if (isodate17_valid(data)) { + file->time_is_set |= CTIME_IS_SET; + file->ctime = isodate17(data); + } } } else { /* Use 7-byte time format. */ if ((flag & 1) && data_length >= 7) { /* Create time. */ - file->birthtime_is_set = 1; - file->birthtime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= BIRTHTIME_IS_SET; + file->birthtime = isodate7(data); + } data += 7; data_length -= 7; } if ((flag & 2) && data_length >= 7) { /* Modify time. */ - file->mtime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= MTIME_IS_SET; + file->mtime = isodate7(data); + } data += 7; data_length -= 7; } if ((flag & 4) && data_length >= 7) { /* Access time. */ - file->atime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= ATIME_IS_SET; + file->atime = isodate7(data); + } data += 7; data_length -= 7; } if ((flag & 8) && data_length >= 7) { /* Attribute change time. */ - file->ctime = isodate7(data); + if (isodate7_valid(data)) { + file->time_is_set |= CTIME_IS_SET; + file->ctime = isodate7(data); + } } } } @@ -2747,7 +2860,7 @@ next_cache_entry(struct archive_read *a, struct iso9660 *iso9660, * If directory entries all which are descendant of * rr_moved are still remaining, expose their. */ - if (iso9660->re_files.first != NULL && + if (iso9660->re_files.first != NULL && iso9660->rr_moved != NULL && iso9660->rr_moved->rr_moved_has_re_only) /* Expose "rr_moved" entry. */ @@ -3015,6 +3128,11 @@ heap_add_entry(struct archive_read *a, struct heap_queue *heap, uint64_t file_key, parent_key; int hole, parent; + /* Reserve 16 bits for possible key collisions (needed for linked items) */ + /* For ISO files with more than 65535 entries, reordering will still occur */ + key <<= 16; + key += heap->used & 0xFFFF; + #ifndef __clang_analyzer__ /* It cannot see heap->files remains populated. */ /* Expand our pending files list as necessary. */ if (heap->used >= heap->allocated) { @@ -3030,7 +3148,7 @@ heap_add_entry(struct archive_read *a, struct heap_queue *heap, return (ARCHIVE_FATAL); } new_pending_files = (struct file_info **) - malloc(new_size * sizeof(new_pending_files[0])); + calloc(new_size, sizeof(new_pending_files[0])); if (new_pending_files == NULL) { archive_set_error(&a->archive, ENOMEM, "Out of memory"); @@ -3125,6 +3243,82 @@ toi(const void *p, int n) return (0); } +/* + * ECMA119/ISO9660 stores multi-byte integers in one of + * three different formats: + * * Little-endian (specified in section 7.2.1 and 7.3.1) + * * Big-endian (specified in section 7.2.2 and 7.3.2) + * * Both (specified in section 7.2.3 and 7.3.3) + * + * For values that follow section 7.2.3 (16-bit) or 7.3.3 (32-bit), we + * can check that the little-endian and big-endian forms agree with + * each other. This helps us avoid trying to decode files that are + * not really ISO images. + */ +static int +isValid723Integer(const unsigned char *p) { + return (p[0] == p[3] && p[1] == p[2]); +} + +static int +isValid733Integer(const unsigned char *p) +{ + return (p[0] == p[7] + && p[1] == p[6] + && p[2] == p[5] + && p[3] == p[4]); +} + +static int +isodate7_valid(const unsigned char *v) +{ + int year = v[0]; + int month = v[1]; + int day = v[2]; + int hour = v[3]; + int minute = v[4]; + int second = v[5]; + int gmt_off = (signed char)v[6]; + + /* ECMA-119 9.1.5 "If all seven values are zero, it shall mean + * that the date is unspecified" */ + if (year == 0 + && month == 0 + && day == 0 + && hour == 0 + && minute == 0 + && second == 0 + && gmt_off == 0) + return 0; + /* + * Sanity-test each individual field + */ + /* Year can have any value */ + /* Month must be 1-12 */ + if (month < 1 || month > 12) + return 0; + /* Day must be 1-31 */ + if (day < 1 || day > 31) + return 0; + /* Hour must be 0-23 */ + if (hour > 23) + return 0; + /* Minute must be 0-59 */ + if (minute > 59) + return 0; + /* second must be 0-59 according to ECMA-119 9.1.5 */ + /* BUT: we should probably allow for the time being in UTC, which + allows up to 61 seconds in a minute in certain cases */ + if (second > 61) + return 0; + /* Offset from GMT must be -48 to +52 */ + if (gmt_off < -48 || gmt_off > +52) + return 0; + + /* All tests pass, this is OK */ + return 1; +} + static time_t isodate7(const unsigned char *v) { @@ -3151,6 +3345,67 @@ isodate7(const unsigned char *v) return (t); } +static int +isodate17_valid(const unsigned char *v) +{ + /* First 16 bytes are all ASCII digits */ + for (int i = 0; i < 16; i++) { + if (v[i] < '0' || v[i] > '9') + return 0; + } + + int year = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + + (v[2] - '0') * 10 + (v[3] - '0'); + int month = (v[4] - '0') * 10 + (v[5] - '0'); + int day = (v[6] - '0') * 10 + (v[7] - '0'); + int hour = (v[8] - '0') * 10 + (v[9] - '0'); + int minute = (v[10] - '0') * 10 + (v[11] - '0'); + int second = (v[12] - '0') * 10 + (v[13] - '0'); + int hundredths = (v[14] - '0') * 10 + (v[15] - '0'); + int gmt_off = (signed char)v[16]; + + if (year == 0 && month == 0 && day == 0 + && hour == 0 && minute == 0 && second == 0 + && hundredths == 0 && gmt_off == 0) + return 0; + /* + * Sanity-test each individual field + */ + + /* Year must be 1900-2300 */ + /* (Not specified in ECMA-119, but these seem + like reasonable limits. */ + if (year < 1900 || year > 2300) + return 0; + /* Month must be 1-12 */ + if (month < 1 || month > 12) + return 0; + /* Day must be 1-31 */ + if (day < 1 || day > 31) + return 0; + /* Hour must be 0-23 */ + if (hour > 23) + return 0; + /* Minute must be 0-59 */ + if (minute > 59) + return 0; + /* second must be 0-59 according to ECMA-119 9.1.5 */ + /* BUT: we should probably allow for the time being in UTC, which + allows up to 61 seconds in a minute in certain cases */ + if (second > 61) + return 0; + /* Hundredths must be 0-99 */ + if (hundredths > 99) + return 0; + /* Offset from GMT must be -48 to +52 */ + if (gmt_off < -48 || gmt_off > +52) + return 0; + + /* All tests pass, this is OK */ + return 1; + +} + static time_t isodate17(const unsigned char *v) { @@ -3162,7 +3417,7 @@ isodate17(const unsigned char *v) tm.tm_year = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + (v[2] - '0') * 10 + (v[3] - '0') - 1900; - tm.tm_mon = (v[4] - '0') * 10 + (v[5] - '0'); + tm.tm_mon = (v[4] - '0') * 10 + (v[5] - '0') - 1; tm.tm_mday = (v[6] - '0') * 10 + (v[7] - '0'); tm.tm_hour = (v[8] - '0') * 10 + (v[9] - '0'); tm.tm_min = (v[10] - '0') * 10 + (v[11] - '0'); @@ -3182,11 +3437,11 @@ isodate17(const unsigned char *v) static time_t time_from_tm(struct tm *t) { -#if HAVE_TIMEGM +#if HAVE__MKGMTIME + return _mkgmtime(t); +#elif HAVE_TIMEGM /* Use platform timegm() if available. */ return (timegm(t)); -#elif HAVE__MKGMTIME64 - return (_mkgmtime64(t)); #else /* Else use direct calculation using POSIX assumptions. */ /* First, fix up tm_yday based on the year/month/day. */ diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c index 8b7bf663bac..c7cbad272ea 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c @@ -227,7 +227,7 @@ static int lha_read_file_header_1(struct archive_read *, struct lha *); static int lha_read_file_header_2(struct archive_read *, struct lha *); static int lha_read_file_header_3(struct archive_read *, struct lha *); static int lha_read_file_extended_header(struct archive_read *, - struct lha *, uint16_t *, int, size_t, size_t *); + struct lha *, uint16_t *, int, uint64_t, size_t *); static size_t lha_check_header_format(const void *); static int lha_skip_sfx(struct archive_read *); static time_t lha_dos_time(const unsigned char *); @@ -265,7 +265,7 @@ archive_read_support_format_lha(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_lha"); - lha = (struct lha *)calloc(1, sizeof(*lha)); + lha = calloc(1, sizeof(*lha)); if (lha == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate lha data"); @@ -945,7 +945,7 @@ lha_read_file_header_1(struct archive_read *a, struct lha *lha) /* Read extended headers */ err2 = lha_read_file_extended_header(a, lha, NULL, 2, - (size_t)(lha->compsize + 2), &extdsize); + (uint64_t)(lha->compsize + 2), &extdsize); if (err2 < ARCHIVE_WARN) return (err2); if (err2 < err) @@ -1138,7 +1138,7 @@ lha_read_file_header_3(struct archive_read *a, struct lha *lha) */ static int lha_read_file_extended_header(struct archive_read *a, struct lha *lha, - uint16_t *crc, int sizefield_length, size_t limitsize, size_t *total_size) + uint16_t *crc, int sizefield_length, uint64_t limitsize, size_t *total_size) { const void *h; const unsigned char *extdheader; @@ -1187,8 +1187,7 @@ lha_read_file_extended_header(struct archive_read *a, struct lha *lha, } /* Sanity check to the extended header size. */ - if (((uint64_t)*total_size + extdsize) > - (uint64_t)limitsize || + if (((uint64_t)*total_size + extdsize) > limitsize || extdsize <= (size_t)sizefield_length) goto invalid; @@ -1347,6 +1346,8 @@ lha_read_file_extended_header(struct archive_read *a, struct lha *lha, lha->compsize = archive_le64dec(extdheader); extdheader += sizeof(uint64_t); lha->origsize = archive_le64dec(extdheader); + if (lha->compsize < 0 || lha->origsize < 0) + goto invalid; } break; case EXT_CODEPAGE: @@ -1693,7 +1694,7 @@ archive_read_format_lha_cleanup(struct archive_read *a) * example. * 1. a symbolic-name is 'aaa/bb/cc' * 2. a filename is 'xxx/bbb' - * then a archived pathname is 'xxx/bbb|aaa/bb/cc' + * then an archived pathname is 'xxx/bbb|aaa/bb/cc' */ static int lha_parse_linkname(struct archive_wstring *linkname, @@ -1819,7 +1820,7 @@ lha_crc16(uint16_t crc, const void *pp, size_t len) * remove the statement which will not be executed. */ #undef bswap16 #ifndef __has_builtin -# define __has_builtin(x) 0 +#define __has_builtin(x) 0 #endif #if defined(_MSC_VER) && _MSC_VER >= 1400 /* Visual Studio */ # define bswap16(x) _byteswap_ushort(x) @@ -1827,7 +1828,7 @@ lha_crc16(uint16_t crc, const void *pp, size_t len) /* GCC 4.8 and later has __builtin_bswap16() */ # define bswap16(x) __builtin_bswap16(x) #elif defined(__clang__) && __has_builtin(__builtin_bswap16) -/* All clang versions have __builtin_bswap16() */ +/* Newer clang versions have __builtin_bswap16() */ # define bswap16(x) __builtin_bswap16(x) #else # define bswap16(x) ((((x) >> 8) & 0xff) | ((x) << 8)) @@ -2012,10 +2013,10 @@ lzh_br_fillup(struct lzh_stream *strm, struct lzh_br *br) ((uint64_t)strm->next_in[0]) << 48 | ((uint64_t)strm->next_in[1]) << 40 | ((uint64_t)strm->next_in[2]) << 32 | - ((uint32_t)strm->next_in[3]) << 24 | - ((uint32_t)strm->next_in[4]) << 16 | - ((uint32_t)strm->next_in[5]) << 8 | - (uint32_t)strm->next_in[6]; + ((uint64_t)strm->next_in[3]) << 24 | + ((uint64_t)strm->next_in[4]) << 16 | + ((uint64_t)strm->next_in[5]) << 8 | + (uint64_t)strm->next_in[6]; strm->next_in += 7; strm->avail_in -= 7; br->cache_avail += 7 * 8; @@ -2025,10 +2026,10 @@ lzh_br_fillup(struct lzh_stream *strm, struct lzh_br *br) (br->cache_buffer << 48) | ((uint64_t)strm->next_in[0]) << 40 | ((uint64_t)strm->next_in[1]) << 32 | - ((uint32_t)strm->next_in[2]) << 24 | - ((uint32_t)strm->next_in[3]) << 16 | - ((uint32_t)strm->next_in[4]) << 8 | - (uint32_t)strm->next_in[5]; + ((uint64_t)strm->next_in[2]) << 24 | + ((uint64_t)strm->next_in[3]) << 16 | + ((uint64_t)strm->next_in[4]) << 8 | + (uint64_t)strm->next_in[5]; strm->next_in += 6; strm->avail_in -= 6; br->cache_avail += 6 * 8; @@ -2385,7 +2386,7 @@ lzh_decode_blocks(struct lzh_stream *strm, int last) return (100); } - /* lzh_br_read_ahead() always try to fill the + /* lzh_br_read_ahead() always tries to fill the * cache buffer up. In specific situation we * are close to the end of the data, the cache * buffer will not be full and thus we have to diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c index 2bc3ba066c3..ba0e49de240 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_mtree.c 201165 2009-12-29 05:52:13Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -274,7 +273,7 @@ archive_read_support_format_mtree(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_mtree"); - mtree = (struct mtree *)calloc(1, sizeof(*mtree)); + mtree = calloc(1, sizeof(*mtree)); if (mtree == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate mtree data"); @@ -417,8 +416,8 @@ next_line(struct archive_read *a, } /* - * Compare characters with a mtree keyword. - * Returns the length of a mtree keyword if matched. + * Compare characters with an mtree keyword. + * Returns the length of an mtree keyword if matched. * Returns 0 if not matched. */ static int @@ -516,7 +515,7 @@ bid_keyword(const char *p, ssize_t len) /* * Test whether there is a set of mtree keywords. - * Returns the number of keyword. + * Returns the number of keywords. * Returns -1 if we got incorrect sequence. * This function expects a set of "keyword=value". * When "unset" is specified, expects a set of "keyword". @@ -761,7 +760,7 @@ detect_form(struct archive_read *a, int *is_form_d) multiline = 1; else { /* We've got plenty of correct lines - * to assume that this file is a mtree + * to assume that this file is an mtree * format. */ if (++entry_cnt >= MAX_BID_ENTRY) break; @@ -1280,7 +1279,13 @@ parse_file(struct archive_read *a, struct archive_entry *entry, mtree->fd = -1; st = NULL; } - } else if (lstat(path, st) == -1) { + } +#ifdef HAVE_LSTAT + else if (lstat(path, st) == -1) +#else + else if (la_stat(path, st) == -1) +#endif + { st = NULL; } diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c index 41d6cb2d339..004685d8cd1 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c @@ -434,7 +434,7 @@ static int make_table_recurse(struct archive_read *, struct huffman_code *, int, struct huffman_table_entry *, int, int); static int expand(struct archive_read *, int64_t *); static int copy_from_lzss_window_to_unp(struct archive_read *, const void **, - int64_t, int); + int64_t, size_t); static const void *rar_read_ahead(struct archive_read *, size_t, ssize_t *); static int parse_filter(struct archive_read *, const uint8_t *, uint16_t, uint8_t); @@ -477,7 +477,7 @@ static inline uint32_t vm_read_32(struct rar_virtual_machine*, size_t); ((rar_br_has(br, (n)) || rar_br_fillup(a, br)) || rar_br_has(br, (n))) /* Notify how many bits we consumed. */ #define rar_br_consume(br, n) ((br)->cache_avail -= (n)) -#define rar_br_consume_unalined_bits(br) ((br)->cache_avail &= ~7) +#define rar_br_consume_unaligned_bits(br) ((br)->cache_avail &= ~7) static const uint32_t cache_masks[] = { 0x00000000, 0x00000001, 0x00000003, 0x00000007, @@ -736,7 +736,7 @@ archive_read_support_format_rar(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_rar"); - rar = (struct rar *)calloc(sizeof(*rar), 1); + rar = calloc(1, sizeof(*rar)); if (rar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate rar data"); @@ -1064,7 +1064,7 @@ archive_read_format_rar_read_header(struct archive_read *a, return (ARCHIVE_FATAL); } p = h; - crc32_val = crc32(crc32_val, (const unsigned char *)p, to_read); + crc32_val = crc32(crc32_val, (const unsigned char *)p, (unsigned int)to_read); __archive_read_consume(a, to_read); skip -= to_read; } @@ -1373,8 +1373,10 @@ read_header(struct archive_read *a, struct archive_entry *entry, char unp_size[8]; int ttime; struct archive_string_conv *sconv, *fn_sconv; - unsigned long crc32_val; + uint32_t crc32_computed, crc32_read; int ret = (ARCHIVE_OK), ret2; + char *newptr; + size_t newsize; rar = (struct rar *)(a->format->data); @@ -1402,7 +1404,7 @@ read_header(struct archive_read *a, struct archive_entry *entry, "Invalid header size"); return (ARCHIVE_FATAL); } - crc32_val = crc32(0, (const unsigned char *)p + 2, 7 - 2); + crc32_computed = crc32(0, (const unsigned char *)p + 2, 7 - 2); __archive_read_consume(a, 7); if (!(rar->file_flags & FHD_SOLID)) @@ -1436,8 +1438,9 @@ read_header(struct archive_read *a, struct archive_entry *entry, return (ARCHIVE_FATAL); /* File Header CRC check. */ - crc32_val = crc32(crc32_val, h, (unsigned)(header_size - 7)); - if ((crc32_val & 0xffff) != archive_le16dec(rar_header.crc)) { + crc32_computed = crc32(crc32_computed, h, (unsigned)(header_size - 7)); + crc32_read = archive_le16dec(rar_header.crc); + if ((crc32_computed & 0xffff) != crc32_read) { #ifndef DONT_FAIL_ON_CRC_ERROR archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Header CRC error"); @@ -1471,6 +1474,11 @@ read_header(struct archive_read *a, struct archive_entry *entry, if (rar->file_flags & FHD_LARGE) { + if (p + 8 > endp) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } memcpy(packed_size, file_header.pack_size, 4); memcpy(packed_size + 4, p, 4); /* High pack size */ p += 4; @@ -1516,8 +1524,7 @@ read_header(struct archive_read *a, struct archive_entry *entry, return (ARCHIVE_FATAL); } if (rar->filename_allocated < filename_size * 2 + 2) { - char *newptr; - size_t newsize = filename_size * 2 + 2; + newsize = filename_size * 2 + 2; newptr = realloc(rar->filename, newsize); if (newptr == NULL) { archive_set_error(&a->archive, ENOMEM, @@ -1541,7 +1548,7 @@ read_header(struct archive_read *a, struct archive_entry *entry, fn_end = filename_size * 2; filename_size = 0; offset = (unsigned)strlen(filename) + 1; - highbyte = *(p + offset++); + highbyte = offset >= end ? 0 : *(p + offset++); flagbits = 0; flagbyte = 0; while (offset < end && filename_size < fn_end) @@ -1556,14 +1563,22 @@ read_header(struct archive_read *a, struct archive_entry *entry, switch((flagbyte >> flagbits) & 3) { case 0: + if (offset >= end) + continue; filename[filename_size++] = '\0'; filename[filename_size++] = *(p + offset++); break; case 1: + if (offset >= end) + continue; filename[filename_size++] = highbyte; filename[filename_size++] = *(p + offset++); break; case 2: + if (offset >= end - 1) { + offset = end; + continue; + } filename[filename_size++] = *(p + offset + 1); filename[filename_size++] = *(p + offset); offset += 2; @@ -1571,9 +1586,15 @@ read_header(struct archive_read *a, struct archive_entry *entry, case 3: { char extra, high; - uint8_t length = *(p + offset++); + uint8_t length; + + if (offset >= end) + continue; + length = *(p + offset++); if (length & 0x80) { + if (offset >= end) + continue; extra = *(p + offset++); high = (char)highbyte; } else @@ -1654,13 +1675,16 @@ read_header(struct archive_read *a, struct archive_entry *entry, rar->cursor++; if (rar->cursor >= rar->nodes) { - rar->nodes++; - if ((rar->dbo = - realloc(rar->dbo, sizeof(*rar->dbo) * rar->nodes)) == NULL) + struct data_block_offsets *newdbo; + + newsize = sizeof(*rar->dbo) * (rar->nodes + 1); + if ((newdbo = realloc(rar->dbo, newsize)) == NULL) { archive_set_error(&a->archive, ENOMEM, "Couldn't allocate memory."); return (ARCHIVE_FATAL); } + rar->dbo = newdbo; + rar->nodes++; rar->dbo[rar->cursor].header_size = header_size; rar->dbo[rar->cursor].start_offset = -1; rar->dbo[rar->cursor].end_offset = -1; @@ -1680,9 +1704,14 @@ read_header(struct archive_read *a, struct archive_entry *entry, return (ARCHIVE_FATAL); } - rar->filename_save = (char*)realloc(rar->filename_save, - filename_size + 1); - memcpy(rar->filename_save, rar->filename, filename_size + 1); + newsize = filename_size + 1; + if ((newptr = realloc(rar->filename_save, newsize)) == NULL) + { + archive_set_error(&a->archive, ENOMEM, "Couldn't allocate memory."); + return (ARCHIVE_FATAL); + } + rar->filename_save = newptr; + memcpy(rar->filename_save, rar->filename, newsize); rar->filename_save_size = filename_size; /* Set info for seeking */ @@ -1832,13 +1861,9 @@ read_exttime(const char *p, struct rar *rar, const char *endp) struct tm *tm; time_t t; long nsec; -#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S) +#if defined(HAVE_LOCALTIME_R) || defined(HAVE_LOCALTIME_S) struct tm tmbuf; #endif -#if defined(HAVE__LOCALTIME64_S) - errno_t terr; - __time64_t tmptime; -#endif if (p + 2 > endp) return (-1); @@ -1870,15 +1895,10 @@ read_exttime(const char *p, struct rar *rar, const char *endp) rem = (((unsigned)(unsigned char)*p) << 16) | (rem >> 8); p++; } -#if defined(HAVE_LOCALTIME_R) +#if defined(HAVE_LOCALTIME_S) + tm = localtime_s(&tmbuf, &t) ? NULL : &tmbuf; +#elif defined(HAVE_LOCALTIME_R) tm = localtime_r(&t, &tmbuf); -#elif defined(HAVE__LOCALTIME64_S) - tmptime = t; - terr = _localtime64_s(&tmbuf, &tmptime); - if (terr) - tm = NULL; - else - tm = &tmbuf; #else tm = localtime(&t); #endif @@ -2071,7 +2091,7 @@ read_data_compressed(struct archive_read *a, const void **buff, size_t *size, bs = rar->unp_buffer_size - rar->unp_offset; else bs = (size_t)rar->bytes_uncopied; - ret = copy_from_lzss_window_to_unp(a, buff, rar->offset, (int)bs); + ret = copy_from_lzss_window_to_unp(a, buff, rar->offset, bs); if (ret != ARCHIVE_OK) return (ret); rar->offset += bs; @@ -2187,6 +2207,19 @@ read_data_compressed(struct archive_read *a, const void **buff, size_t *size, { start = rar->offset; end = start + rar->dictionary_size; + + /* We don't want to overflow the window and overwrite data that we write + * at 'start'. Therefore, reduce the end length by the maximum match size, + * which is 260 bytes. You can compute this maximum by looking at the + * definition of 'expand', in particular when 'symbol >= 271'. */ + /* NOTE: It's possible for 'dictionary_size' to be less than this 260 + * value, however that will only be the case when 'unp_size' is small, + * which should only happen when the entry size is small and there's no + * risk of overflowing the buffer */ + if (rar->dictionary_size > 260) { + end -= 260; + } + if (rar->filters.filterstart < end) { end = rar->filters.filterstart; } @@ -2211,7 +2244,7 @@ read_data_compressed(struct archive_read *a, const void **buff, size_t *size, bs = rar->unp_buffer_size - rar->unp_offset; else bs = (size_t)rar->bytes_uncopied; - ret = copy_from_lzss_window_to_unp(a, buff, rar->offset, (int)bs); + ret = copy_from_lzss_window_to_unp(a, buff, rar->offset, bs); if (ret != ARCHIVE_OK) return (ret); rar->offset += bs; @@ -2245,7 +2278,7 @@ parse_codes(struct archive_read *a) free_codes(a); /* Skip to the next byte */ - rar_br_consume_unalined_bits(br); + rar_br_consume_unaligned_bits(br); /* PPMd block flag */ if (!rar_br_read_ahead(a, br, 1)) @@ -2577,8 +2610,7 @@ read_next_symbol(struct archive_read *a, struct huffman_code *code) rar_br_consume(br, code->tablesize); node = value; - while (!(code->tree[node].branches[0] == - code->tree[node].branches[1])) + while (code->tree[node].branches[0] != code->tree[node].branches[1]) { if (!rar_br_read_ahead(a, br, 1)) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, @@ -2773,9 +2805,7 @@ make_table(struct archive_read *a, struct huffman_code *code) else code->tablesize = code->maxlength; - code->table = - (struct huffman_table_entry *)calloc(1, sizeof(*code->table) - * ((size_t)1 << code->tablesize)); + code->table = calloc(((size_t)1U) << code->tablesize, sizeof(*code->table)); return make_table_recurse(a, code, 0, code->table, 0, code->tablesize); } @@ -2953,7 +2983,7 @@ expand(struct archive_read *a, int64_t *end) if ((lensymbol = read_next_symbol(a, &rar->lengthcode)) < 0) goto bad_data; - if (lensymbol > lengthb_min) + if (lensymbol >= lengthb_min) goto bad_data; len = lengthbases[lensymbol] + 2; if (lengthbits[lensymbol] > 0) { @@ -2985,7 +3015,7 @@ expand(struct archive_read *a, int64_t *end) } else { - if (symbol-271 > lengthb_min) + if (symbol-271 >= lengthb_min) goto bad_data; len = lengthbases[symbol-271]+3; if(lengthbits[symbol-271] > 0) { @@ -2997,7 +3027,7 @@ expand(struct archive_read *a, int64_t *end) if ((offssymbol = read_next_symbol(a, &rar->offsetcode)) < 0) goto bad_data; - if (offssymbol > offsetb_min) + if (offssymbol >= offsetb_min) goto bad_data; offs = offsetbases[offssymbol]+1; if(offsetbits[offssymbol] > 0) @@ -3092,11 +3122,16 @@ copy_from_lzss_window(struct archive_read *a, void *buffer, static int copy_from_lzss_window_to_unp(struct archive_read *a, const void **buffer, - int64_t startpos, int length) + int64_t startpos, size_t length) { int windowoffs, firstpart; struct rar *rar = (struct rar *)(a->format->data); + if (length > rar->unp_buffer_size) + { + goto fatal; + } + if (!rar->unp_buffer) { if ((rar->unp_buffer = malloc(rar->unp_buffer_size)) == NULL) @@ -3108,17 +3143,17 @@ copy_from_lzss_window_to_unp(struct archive_read *a, const void **buffer, } windowoffs = lzss_offset_for_position(&rar->lzss, startpos); - if(windowoffs + length <= lzss_size(&rar->lzss)) { + if(windowoffs + length <= (size_t)lzss_size(&rar->lzss)) { memcpy(&rar->unp_buffer[rar->unp_offset], &rar->lzss.window[windowoffs], length); - } else if (length <= lzss_size(&rar->lzss)) { + } else if (length <= (size_t)lzss_size(&rar->lzss)) { firstpart = lzss_size(&rar->lzss) - windowoffs; if (firstpart < 0) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Bad RAR file data"); return (ARCHIVE_FATAL); } - if (firstpart < length) { + if ((size_t)firstpart < length) { memcpy(&rar->unp_buffer[rar->unp_offset], &rar->lzss.window[windowoffs], firstpart); memcpy(&rar->unp_buffer[rar->unp_offset + firstpart], @@ -3128,16 +3163,19 @@ copy_from_lzss_window_to_unp(struct archive_read *a, const void **buffer, &rar->lzss.window[windowoffs], length); } } else { - archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Bad RAR file data"); - return (ARCHIVE_FATAL); + goto fatal; } - rar->unp_offset += length; + rar->unp_offset += (unsigned int) length; if (rar->unp_offset >= rar->unp_buffer_size) *buffer = rar->unp_buffer; else *buffer = NULL; return (ARCHIVE_OK); + +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad RAR file data"); + return (ARCHIVE_FATAL); } static const void * @@ -3323,7 +3361,8 @@ create_filter(struct rar_program_code *prog, const uint8_t *globaldata, uint32_t filter->prog = prog; filter->globaldatalen = globaldatalen > PROGRAM_SYSTEM_GLOBAL_SIZE ? globaldatalen : PROGRAM_SYSTEM_GLOBAL_SIZE; filter->globaldata = calloc(1, filter->globaldatalen); - if (!filter->globaldata) { + if (!filter->globaldata) + { free(filter); return NULL; } @@ -3353,7 +3392,7 @@ run_filters(struct archive_read *a) if (filters == NULL || filter == NULL) return (0); - start = filters->filterstart; + start = (size_t)filters->filterstart; end = start + filter->blocklength; filters->filterstart = INT64_MAX; @@ -3390,10 +3429,16 @@ run_filters(struct archive_read *a) return 0; } + if (filter->blocklength > VM_MEMORY_SIZE) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Bad RAR file data"); + return 0; + } + ret = copy_from_lzss_window(a, filters->vm->memory, start, filter->blocklength); if (ret != ARCHIVE_OK) return 0; - if (!execute_filter(a, filter, filters->vm, rar->offset)) + if (!execute_filter(a, filter, filters->vm, (size_t)rar->offset)) return 0; lastfilteraddress = filter->filteredblockaddress; @@ -3405,7 +3450,7 @@ run_filters(struct archive_read *a) while ((filter = filters->stack) != NULL && (int64_t)filter->blockstartpos == filters->filterstart && filter->blocklength == lastfilterlength) { memmove(&filters->vm->memory[0], &filters->vm->memory[lastfilteraddress], lastfilterlength); - if (!execute_filter(a, filter, filters->vm, rar->offset)) + if (!execute_filter(a, filter, filters->vm, (size_t)rar->offset)) return 0; lastfilteraddress = filter->filteredblockaddress; @@ -3451,7 +3496,7 @@ compile_program(const uint8_t *bytes, size_t length) prog = calloc(1, sizeof(*prog)); if (!prog) return NULL; - prog->fingerprint = crc32(0, bytes, length) | ((uint64_t)length << 32); + prog->fingerprint = crc32(0, bytes, (unsigned int)length) | ((uint64_t)length << 32); if (membr_bits(&br, 1)) { @@ -3613,7 +3658,15 @@ execute_filter_delta(struct rar_filter *filter, struct rar_virtual_machine *vm) { uint8_t lastbyte = 0; for (idx = i; idx < length; idx += numchannels) + { + /* + * The src block should not overlap with the dst block. + * If so it would be better to consider this archive is broken. + */ + if (src >= dst) + return 0; lastbyte = dst[idx] = lastbyte - *src++; + } } filter->filteredblockaddress = length; @@ -3629,7 +3682,7 @@ execute_filter_e8(struct rar_filter *filter, struct rar_virtual_machine *vm, siz uint32_t filesize = 0x1000000; uint32_t i; - if (length > PROGRAM_WORK_SIZE || length < 4) + if (length > PROGRAM_WORK_SIZE || length <= 4) return 0; for (i = 0; i <= length - 5; i++) @@ -3638,7 +3691,7 @@ execute_filter_e8(struct rar_filter *filter, struct rar_virtual_machine *vm, siz { uint32_t currpos = (uint32_t)pos + i + 1; int32_t address = (int32_t)vm_read_32(vm, i + 1); - if (address < 0 && currpos >= (uint32_t)-address) + if (address < 0 && currpos >= (~(uint32_t)address + 1)) vm_write_32(vm, i + 1, address + filesize); else if (address >= 0 && (uint32_t)address < filesize) vm_write_32(vm, i + 1, address - currpos); @@ -3661,7 +3714,7 @@ execute_filter_rgb(struct rar_filter *filter, struct rar_virtual_machine *vm) uint8_t *src, *dst; uint32_t i, j; - if (blocklength > PROGRAM_WORK_SIZE / 2 || stride > blocklength) + if (blocklength > PROGRAM_WORK_SIZE / 2 || stride > blocklength || blocklength < 3 || byteoffset > 2) return 0; src = &vm->memory[0]; @@ -3671,6 +3724,13 @@ execute_filter_rgb(struct rar_filter *filter, struct rar_virtual_machine *vm) uint8_t *prev = dst + i - stride; for (j = i; j < blocklength; j += 3) { + /* + * The src block should not overlap with the dst block. + * If so it would be better to consider this archive is broken. + */ + if (src >= dst) + return 0; + if (prev >= dst) { uint32_t delta1 = abs(prev[3] - prev[0]); @@ -3715,6 +3775,13 @@ execute_filter_audio(struct rar_filter *filter, struct rar_virtual_machine *vm) memset(&state, 0, sizeof(state)); for (j = i; j < length; j += numchannels) { + /* + * The src block should not overlap with the dst block. + * If so it would be better to consider this archive is broken. + */ + if (src >= dst) + return 0; + int8_t delta = (int8_t)*src++; uint8_t predbyte, byte; int prederror; diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c index aa7b8619e83..b8b9e630b1d 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c @@ -210,7 +210,11 @@ struct comp_state { or just a part of it. */ uint8_t block_parsing_finished : 1; - signed int notused : 4; + /* Flag used to indicate that a previous file using this buffer was + encrypted, meaning no data in the buffer can be trusted */ + uint8_t data_encrypted : 1; + + signed int notused : 3; int flags; /* Uncompression flags. */ int method; /* Uncompression algorithm method. */ @@ -220,7 +224,7 @@ struct comp_state { decompression. */ uint8_t* filtered_buf; /* Buffer used when applying filters. */ const uint8_t* block_buf; /* Buffer used when merging blocks. */ - size_t window_mask; /* Convenience field; window_size - 1. */ + ssize_t window_mask; /* Convenience field; window_size - 1. */ int64_t write_ptr; /* This amount of data has been unpacked in the window buffer. */ int64_t last_write_ptr; /* This amount of data has been stored in @@ -352,6 +356,12 @@ struct rar5 { /* The header of currently processed RARv5 block. Used in main * decompression logic loop. */ struct compressed_block_header last_block_hdr; + + /* + * Custom field to denote that this archive contains encrypted entries + */ + int has_encrypted_entries; + int headers_are_encrypted; }; /* Forward function declarations. */ @@ -361,6 +371,7 @@ static int verify_global_checksums(struct archive_read* a); static int rar5_read_data_skip(struct archive_read *a); static int push_data_ready(struct archive_read* a, struct rar5* rar, const uint8_t* buf, size_t size, int64_t offset); +static void clear_data_ready_stack(struct rar5* rar); /* CDE_xxx = Circular Double Ended (Queue) return values. */ enum CDE_RETURN_VALUES { @@ -495,12 +506,17 @@ uint8_t bf_is_table_present(const struct compressed_block_header* hdr) { return (hdr->block_flags_u8 >> 7) & 1; } +static inline +uint8_t bf_is_last_block(const struct compressed_block_header* hdr) { + return (hdr->block_flags_u8 >> 6) & 1; +} + static inline struct rar5* get_context(struct archive_read* a) { return (struct rar5*) a->format->data; } /* Convenience functions used by filter implementations. */ -static void circular_memcpy(uint8_t* dst, uint8_t* window, const uint64_t mask, +static void circular_memcpy(uint8_t* dst, uint8_t* window, const ssize_t mask, int64_t start, int64_t end) { if((start & mask) > (end & mask)) { @@ -529,8 +545,7 @@ static void write_filter_data(struct rar5* rar, uint32_t offset, /* Allocates a new filter descriptor and adds it to the filter array. */ static struct filter_info* add_new_filter(struct rar5* rar) { - struct filter_info* f = - (struct filter_info*) calloc(1, sizeof(struct filter_info)); + struct filter_info* f = calloc(1, sizeof(*f)); if(!f) { return NULL; @@ -647,6 +662,7 @@ static int run_filter(struct archive_read* a, struct filter_info* flt) { int ret; struct rar5* rar = get_context(a); + clear_data_ready_stack(rar); free(rar->cstate.filtered_buf); rar->cstate.filtered_buf = malloc(flt->block_length); @@ -675,7 +691,8 @@ static int run_filter(struct archive_read* a, struct filter_info* flt) { default: archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Unsupported filter type: 0x%x", flt->type); + "Unsupported filter type: 0x%x", + (unsigned int)flt->type); return ARCHIVE_FATAL; } @@ -704,7 +721,7 @@ static int run_filter(struct archive_read* a, struct filter_info* flt) { static void push_data(struct archive_read* a, struct rar5* rar, const uint8_t* buf, int64_t idx_begin, int64_t idx_end) { - const uint64_t wmask = rar->cstate.window_mask; + const ssize_t wmask = rar->cstate.window_mask; const ssize_t solid_write_ptr = (rar->cstate.solid_offset + rar->cstate.last_write_ptr) & wmask; @@ -1241,7 +1258,7 @@ static int process_main_locator_extra_block(struct archive_read* a, } static int parse_file_extra_hash(struct archive_read* a, struct rar5* rar, - ssize_t* extra_data_size) + int64_t* extra_data_size) { size_t hash_type = 0; size_t value_len; @@ -1277,7 +1294,7 @@ static int parse_file_extra_hash(struct archive_read* a, struct rar5* rar, *extra_data_size -= hash_size; } else { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Unsupported hash type (0x%x)", (int) hash_type); + "Unsupported hash type (0x%jx)", (uintmax_t)hash_type); return ARCHIVE_FATAL; } @@ -1291,7 +1308,7 @@ static uint64_t time_win_to_unix(uint64_t win_time) { } static int parse_htime_item(struct archive_read* a, char unix_time, - uint64_t* where, ssize_t* extra_data_size) + uint64_t* where, int64_t* extra_data_size) { if(unix_time) { uint32_t time_val; @@ -1313,7 +1330,7 @@ static int parse_htime_item(struct archive_read* a, char unix_time, } static int parse_file_extra_version(struct archive_read* a, - struct archive_entry* e, ssize_t* extra_data_size) + struct archive_entry* e, int64_t* extra_data_size) { size_t flags = 0; size_t version = 0; @@ -1367,7 +1384,7 @@ static int parse_file_extra_version(struct archive_read* a, } static int parse_file_extra_htime(struct archive_read* a, - struct archive_entry* e, struct rar5* rar, ssize_t* extra_data_size) + struct archive_entry* e, struct rar5* rar, int64_t* extra_data_size) { char unix_time = 0; size_t flags = 0; @@ -1420,7 +1437,7 @@ static int parse_file_extra_htime(struct archive_read* a, } static int parse_file_extra_redir(struct archive_read* a, - struct archive_entry* e, struct rar5* rar, ssize_t* extra_data_size) + struct archive_entry* e, struct rar5* rar, int64_t* extra_data_size) { uint64_t value_size = 0; size_t target_size = 0; @@ -1443,9 +1460,6 @@ static int parse_file_extra_redir(struct archive_read* a, return ARCHIVE_EOF; *extra_data_size -= target_size + 1; - if(!read_ahead(a, target_size, &p)) - return ARCHIVE_EOF; - if(target_size > (MAX_NAME_IN_CHARS - 1)) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Link target is too long"); @@ -1458,6 +1472,9 @@ static int parse_file_extra_redir(struct archive_read* a, return ARCHIVE_FATAL; } + if(!read_ahead(a, target_size, &p)) + return ARCHIVE_EOF; + memcpy(target_utf8_buf, p, target_size); target_utf8_buf[target_size] = 0; @@ -1491,7 +1508,7 @@ static int parse_file_extra_redir(struct archive_read* a, } static int parse_file_extra_owner(struct archive_read* a, - struct archive_entry* e, ssize_t* extra_data_size) + struct archive_entry* e, int64_t* extra_data_size) { uint64_t flags = 0; uint64_t value_size = 0; @@ -1571,15 +1588,15 @@ static int parse_file_extra_owner(struct archive_read* a, } static int process_head_file_extra(struct archive_read* a, - struct archive_entry* e, struct rar5* rar, ssize_t extra_data_size) + struct archive_entry* e, struct rar5* rar, int64_t extra_data_size) { - size_t extra_field_size; - size_t extra_field_id = 0; + uint64_t extra_field_size; + uint64_t extra_field_id = 0; int ret = ARCHIVE_FATAL; - size_t var_size; + uint64_t var_size; while(extra_data_size > 0) { - if(!read_var_sized(a, &extra_field_size, &var_size)) + if(!read_var(a, &extra_field_size, &var_size)) return ARCHIVE_EOF; extra_data_size -= var_size; @@ -1587,9 +1604,10 @@ static int process_head_file_extra(struct archive_read* a, return ARCHIVE_EOF; } - if(!read_var_sized(a, &extra_field_id, &var_size)) + if(!read_var(a, &extra_field_id, &var_size)) return ARCHIVE_EOF; + extra_field_size -= var_size; extra_data_size -= var_size; if(ARCHIVE_OK != consume(a, var_size)) { return ARCHIVE_EOF; @@ -1617,12 +1635,19 @@ static int process_head_file_extra(struct archive_read* a, &extra_data_size); break; case EX_CRYPT: + /* Mark the entry as encrypted */ + archive_entry_set_is_data_encrypted(e, 1); + rar->has_encrypted_entries = 1; + rar->cstate.data_encrypted = 1; /* fallthrough */ case EX_SUBDATA: /* fallthrough */ default: /* Skip unsupported entry. */ - return consume(a, extra_data_size); + extra_data_size -= extra_field_size; + if (ARCHIVE_OK != consume(a, extra_field_size)) { + return ARCHIVE_EOF; + } } } @@ -1637,7 +1662,7 @@ static int process_head_file_extra(struct archive_read* a, static int process_head_file(struct archive_read* a, struct rar5* rar, struct archive_entry* entry, size_t block_flags) { - ssize_t extra_data_size = 0; + int64_t extra_data_size = 0; size_t data_size = 0; size_t file_flags = 0; size_t file_attr = 0; @@ -1677,12 +1702,12 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, } if(block_flags & HFL_EXTRA_DATA) { - size_t edata_size = 0; - if(!read_var_sized(a, &edata_size, NULL)) + uint64_t edata_size = 0; + if(!read_var(a, &edata_size, NULL)) return ARCHIVE_EOF; /* Intentional type cast from unsigned to signed. */ - extra_data_size = (ssize_t) edata_size; + extra_data_size = (int64_t) edata_size; } if(block_flags & HFL_DATA) { @@ -1740,9 +1765,11 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, rar->file.solid = (compression_info & SOLID) > 0; /* Archives which declare solid files without initializing the window - * buffer first are invalid. */ + * buffer first are invalid, unless previous data was encrypted, in + * which case we may never have had the chance */ - if(rar->file.solid > 0 && rar->cstate.window_buf == NULL) { + if(rar->file.solid > 0 && rar->cstate.data_encrypted == 0 && + rar->cstate.window_buf == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Declared solid file, but no window buffer " "initialized yet."); @@ -1771,15 +1798,24 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, return ARCHIVE_FATAL; } } + else + rar->cstate.data_encrypted = 0; /* Reset for new buffer */ if(rar->cstate.window_size < (ssize_t) window_size && rar->cstate.window_buf) { + /* The `data_ready` stack contains pointers to the `window_buf` or + * `filtered_buf` buffers. Since we're about to reallocate the first + * buffer, some of those pointers could become invalid. Therefore, we + * need to dispose of all entries from the stack before attempting the + * realloc. */ + clear_data_ready_stack(rar); + /* If window_buf has been allocated before, reallocate it, so * that its size will match new window_size. */ uint8_t* new_window_buf = - realloc(rar->cstate.window_buf, window_size); + realloc(rar->cstate.window_buf, (size_t) window_size); if(!new_window_buf) { archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, @@ -1832,27 +1868,25 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, if (file_attr & (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM)) { char *fflags_text, *ptr; - /* allocate for "rdonly,hidden,system," */ - fflags_text = malloc(22 * sizeof(char)); + /* allocate for ",rdonly,hidden,system" */ + fflags_text = malloc(22 * sizeof(*fflags_text)); if (fflags_text != NULL) { ptr = fflags_text; if (file_attr & ATTR_READONLY) { - strcpy(ptr, "rdonly,"); + strcpy(ptr, ",rdonly"); ptr = ptr + 7; } if (file_attr & ATTR_HIDDEN) { - strcpy(ptr, "hidden,"); + strcpy(ptr, ",hidden"); ptr = ptr + 7; } if (file_attr & ATTR_SYSTEM) { - strcpy(ptr, "system,"); + strcpy(ptr, ",system"); ptr = ptr + 7; } if (ptr > fflags_text) { - /* Delete trailing comma */ - *(ptr - 1) = '\0'; archive_entry_copy_fflags_text(entry, - fflags_text); + fflags_text + 1); } free(fflags_text); } @@ -1863,7 +1897,8 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, } else { /* Unknown host OS */ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Unsupported Host OS: 0x%x", (int) host_os); + "Unsupported Host OS: 0x%jx", + (uintmax_t)host_os); return ARCHIVE_FATAL; } @@ -1871,9 +1906,6 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, if(!read_var_sized(a, &name_size, NULL)) return ARCHIVE_EOF; - if(!read_ahead(a, name_size, &p)) - return ARCHIVE_EOF; - if(name_size > (MAX_NAME_IN_CHARS - 1)) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Filename is too long"); @@ -1888,6 +1920,9 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, return ARCHIVE_FATAL; } + if(!read_ahead(a, name_size, &p)) + return ARCHIVE_EOF; + memcpy(name_utf8_buf, p, name_size); name_utf8_buf[name_size] = 0; if(ARCHIVE_OK != consume(a, name_size)) { @@ -1975,7 +2010,7 @@ static int process_head_main(struct archive_read* a, struct rar5* rar, struct archive_entry* entry, size_t block_flags) { int ret; - size_t extra_data_size = 0; + uint64_t extra_data_size = 0; size_t extra_field_size = 0; size_t extra_field_id = 0; size_t archive_flags = 0; @@ -1997,7 +2032,7 @@ static int process_head_main(struct archive_read* a, struct rar5* rar, (void) entry; if(block_flags & HFL_EXTRA_DATA) { - if(!read_var_sized(a, &extra_data_size, NULL)) + if(!read_var(a, &extra_data_size, NULL)) return ARCHIVE_EOF; } else { extra_data_size = 0; @@ -2071,8 +2106,8 @@ static int process_head_main(struct archive_read* a, struct rar5* rar, default: archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Unsupported extra type (0x%x)", - (int) extra_field_id); + "Unsupported extra type (0x%jx)", + (uintmax_t)extra_field_id); return ARCHIVE_FATAL; } @@ -2224,10 +2259,12 @@ static int process_base_block(struct archive_read* a, /* Verify the CRC32 of the header data. */ computed_crc = (uint32_t) crc32(0, p, (int) hdr_size); if(computed_crc != hdr_crc) { +#ifndef DONT_FAIL_ON_CRC_ERROR archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Header CRC error"); return ARCHIVE_FATAL; +#endif } /* If the checksum is OK, we proceed with parsing. */ @@ -2266,6 +2303,10 @@ static int process_base_block(struct archive_read* a, ret = process_head_file(a, rar, entry, header_flags); return ret; case HEAD_CRYPT: + archive_entry_set_is_metadata_encrypted(entry, 1); + archive_entry_set_is_data_encrypted(entry, 1); + rar->has_encrypted_entries = 1; + rar->headers_are_encrypted = 1; archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Encryption is not supported"); @@ -2410,6 +2451,14 @@ static int rar5_read_header(struct archive_read *a, struct rar5* rar = get_context(a); int ret; + /* + * It should be sufficient to call archive_read_next_header() for + * a reader to determine if an entry is encrypted or not. + */ + if (rar->has_encrypted_entries == ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW) { + rar->has_encrypted_entries = 0; + } + if(rar->header_initialized == 0) { init_header(a); if ((ret = try_skip_sfx(a)) < ARCHIVE_WARN) @@ -2448,6 +2497,8 @@ static void init_unpack(struct rar5* rar) { rar->cstate.filtered_buf = NULL; } + clear_data_ready_stack(rar); + rar->cstate.write_ptr = 0; rar->cstate.last_write_ptr = 0; @@ -2475,7 +2526,7 @@ static void update_crc(struct rar5* rar, const uint8_t* p, size_t to_read) { * `stored_crc32` info filled in. */ if(rar->file.stored_crc32 > 0) { rar->file.calculated_crc32 = - crc32(rar->file.calculated_crc32, p, to_read); + crc32(rar->file.calculated_crc32, p, (unsigned int)to_read); } /* Check if the file uses an optional BLAKE2sp checksum @@ -2989,7 +3040,7 @@ static int decode_code_length(struct archive_read* a, struct rar5* rar, static int copy_string(struct archive_read* a, int len, int dist) { struct rar5* rar = get_context(a); - const uint64_t cmask = rar->cstate.window_mask; + const ssize_t cmask = rar->cstate.window_mask; const uint64_t write_ptr = rar->cstate.write_ptr + rar->cstate.solid_offset; int i; @@ -3633,6 +3684,10 @@ static int use_data(struct rar5* rar, const void** buf, size_t* size, return ARCHIVE_RETRY; } +static void clear_data_ready_stack(struct rar5* rar) { + memset(&rar->cstate.dready, 0, sizeof(rar->cstate.dready)); +} + /* Pushes the `buf`, `size` and `offset` arguments to the rar->cstate.dready * FIFO stack. Those values will be popped from this stack by the `use_data` * function. */ @@ -3768,7 +3823,12 @@ static int do_uncompress_file(struct archive_read* a) { if(rar->cstate.last_write_ptr == rar->cstate.write_ptr) { /* The block didn't generate any new data, - * so just process a new block. */ + * so just process a new block if this one + * wasn't the last block in the file. */ + if (bf_is_last_block(&rar->last_block_hdr)) { + return ARCHIVE_EOF; + } + continue; } @@ -3936,7 +3996,7 @@ static int do_unpack(struct archive_read* a, struct rar5* rar, archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Compression method not supported: 0x%x", - rar->cstate.method); + (unsigned int)rar->cstate.method); return ARCHIVE_FATAL; } @@ -4070,6 +4130,16 @@ static int rar5_read_data(struct archive_read *a, const void **buff, if (size) *size = 0; + if (rar->has_encrypted_entries == ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW) { + rar->has_encrypted_entries = 0; + } + + if (rar->headers_are_encrypted || rar->cstate.data_encrypted) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Reading encrypted data is not currently supported"); + return ARCHIVE_FATAL; + } + if(rar->file.dir > 0) { /* Don't process any data if this file entry was declared * as a directory. This is needed, because entries marked as @@ -4121,11 +4191,14 @@ static int rar5_read_data(struct archive_read *a, const void **buff, static int rar5_read_data_skip(struct archive_read *a) { struct rar5* rar = get_context(a); - if(rar->main.solid) { + if(rar->main.solid && (rar->cstate.data_encrypted == 0)) { /* In solid archives, instead of skipping the data, we need to * extract it, and dispose the result. The side effect of this * operation will be setting up the initial window buffer state - * needed to be able to extract the selected file. */ + * needed to be able to extract the selected file. Note that + * this is only possible when data withing this solid block is + * not encrypted, in which case we'll skip and fail if the user + * tries to read data. */ int ret; @@ -4186,6 +4259,7 @@ static int rar5_cleanup(struct archive_read *a) { free(rar->cstate.window_buf); free(rar->cstate.filtered_buf); + clear_data_ready_stack(rar); free(rar->vol.push_buf); @@ -4200,14 +4274,19 @@ static int rar5_cleanup(struct archive_read *a) { static int rar5_capabilities(struct archive_read * a) { (void) a; - return 0; + return (ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_DATA + | ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_METADATA); } static int rar5_has_encrypted_entries(struct archive_read *_a) { - (void) _a; + if (_a && _a->format) { + struct rar5 *rar = (struct rar5 *)_a->format->data; + if (rar) { + return rar->has_encrypted_entries; + } + } - /* Unsupported for now. */ - return ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED; + return ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW; } static int rar5_init(struct rar5* rar) { @@ -4216,6 +4295,12 @@ static int rar5_init(struct rar5* rar) { if(CDE_OK != cdeque_init(&rar->cstate.filters, 8192)) return ARCHIVE_FATAL; + /* + * Until enough data has been read, we cannot tell about + * any encrypted entries yet. + */ + rar->has_encrypted_entries = ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW; + return ARCHIVE_OK; } diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_raw.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_raw.c index ec0520b60a6..e935396dda6 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_raw.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_raw.c @@ -23,7 +23,6 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_raw.c 201107 2009-12-28 03:25:33Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -62,7 +61,7 @@ archive_read_support_format_raw(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_raw"); - info = (struct raw_info *)calloc(1, sizeof(*info)); + info = calloc(1, sizeof(*info)); if (info == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate raw_info data"); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c index 93c3fd58573..1036d128689 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2003-2023 Tim Kientzle * Copyright (c) 2011-2012 Michihiro NAKAJIMA * Copyright (c) 2016 Martin Matuska * All rights reserved. @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_tar.c 201161 2009-12-29 05:44:39Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -118,33 +117,32 @@ struct sparse_block { }; struct tar { - struct archive_string acl_text; struct archive_string entry_pathname; /* For "GNU.sparse.name" and other similar path extensions. */ struct archive_string entry_pathname_override; - struct archive_string entry_linkpath; struct archive_string entry_uname; struct archive_string entry_gname; - struct archive_string longlink; - struct archive_string longname; - struct archive_string pax_header; - struct archive_string pax_global; + struct archive_string entry_linkpath; struct archive_string line; - int pax_hdrcharset_binary; - int header_recursion_depth; + int pax_hdrcharset_utf8; int64_t entry_bytes_remaining; int64_t entry_offset; int64_t entry_padding; int64_t entry_bytes_unconsumed; - int64_t realsize; - int sparse_allowed; + int64_t disk_size; + int64_t GNU_sparse_realsize; + int64_t GNU_sparse_size; + int64_t SCHILY_sparse_realsize; + int64_t pax_size; struct sparse_block *sparse_list; struct sparse_block *sparse_last; int64_t sparse_offset; int64_t sparse_numbytes; int sparse_gnu_major; int sparse_gnu_minor; - char sparse_gnu_pending; + char sparse_gnu_attributes_seen; + char filetype; + char size_fields; /* Bits defined below */ struct archive_string localname; struct archive_string_conv *opt_sconv; @@ -155,9 +153,15 @@ struct tar { int compat_2x; int process_mac_extensions; int read_concatenated_archives; - int realsize_override; }; +/* Track which size fields were present in the headers */ +#define TAR_SIZE_PAX_SIZE 1 +#define TAR_SIZE_GNU_SPARSE_REALSIZE 2 +#define TAR_SIZE_GNU_SPARSE_SIZE 4 +#define TAR_SIZE_SCHILY_SPARSE_REALSIZE 8 + + static int archive_block_is_null(const char *p); static char *base64_decode(const char *, size_t, size_t *); static int gnu_add_sparse_entry(struct archive_read *, struct tar *, @@ -169,25 +173,26 @@ static int gnu_sparse_old_read(struct archive_read *, struct tar *, static int gnu_sparse_old_parse(struct archive_read *, struct tar *, const struct gnu_sparse *sparse, int length); static int gnu_sparse_01_parse(struct archive_read *, struct tar *, - const char *); + const char *, size_t); static ssize_t gnu_sparse_10_read(struct archive_read *, struct tar *, - size_t *); + size_t *); static int header_Solaris_ACL(struct archive_read *, struct tar *, struct archive_entry *, const void *, size_t *); static int header_common(struct archive_read *, struct tar *, struct archive_entry *, const void *); static int header_old_tar(struct archive_read *, struct tar *, struct archive_entry *, const void *); -static int header_pax_extensions(struct archive_read *, struct tar *, +static int header_pax_extension(struct archive_read *, struct tar *, struct archive_entry *, const void *, size_t *); static int header_pax_global(struct archive_read *, struct tar *, struct archive_entry *, const void *h, size_t *); -static int header_longlink(struct archive_read *, struct tar *, - struct archive_entry *, const void *h, size_t *); -static int header_longname(struct archive_read *, struct tar *, +static int header_gnu_longlink(struct archive_read *, struct tar *, struct archive_entry *, const void *h, size_t *); -static int read_mac_metadata_blob(struct archive_read *, struct tar *, +static int header_gnu_longname(struct archive_read *, struct tar *, struct archive_entry *, const void *h, size_t *); +static int is_mac_metadata_entry(struct archive_entry *entry); +static int read_mac_metadata_blob(struct archive_read *, + struct archive_entry *, size_t *); static int header_volume(struct archive_read *, struct tar *, struct archive_entry *, const void *h, size_t *); static int header_ustar(struct archive_read *, struct tar *, @@ -205,21 +210,21 @@ static int archive_read_format_tar_read_header(struct archive_read *, struct archive_entry *); static int checksum(struct archive_read *, const void *); static int pax_attribute(struct archive_read *, struct tar *, - struct archive_entry *, const char *key, const char *value, - size_t value_length); -static int pax_attribute_acl(struct archive_read *, struct tar *, - struct archive_entry *, const char *, int); -static int pax_attribute_xattr(struct archive_entry *, const char *, - const char *); -static int pax_header(struct archive_read *, struct tar *, - struct archive_entry *, struct archive_string *); -static void pax_time(const char *, int64_t *sec, long *nanos); + struct archive_entry *, const char *key, size_t key_length, + size_t value_length, size_t *unconsumed); +static int pax_attribute_LIBARCHIVE_xattr(struct archive_entry *, + const char *, size_t, const char *, size_t); +static int pax_attribute_SCHILY_acl(struct archive_read *, struct tar *, + struct archive_entry *, size_t, int); +static int pax_attribute_SUN_holesdata(struct archive_read *, struct tar *, + struct archive_entry *, const char *, size_t); +static void pax_time(const char *, size_t, int64_t *sec, long *nanos); static ssize_t readline(struct archive_read *, struct tar *, const char **, ssize_t limit, size_t *); static int read_body_to_string(struct archive_read *, struct tar *, struct archive_string *, const void *h, size_t *); -static int solaris_sparse_parse(struct archive_read *, struct tar *, - struct archive_entry *, const char *); +static int read_bytes_to_string(struct archive_read *, + struct archive_string *, size_t, size_t *); static int64_t tar_atol(const char *, size_t); static int64_t tar_atol10(const char *, size_t); static int64_t tar_atol256(const char *, size_t); @@ -227,9 +232,21 @@ static int64_t tar_atol8(const char *, size_t); static int tar_read_header(struct archive_read *, struct tar *, struct archive_entry *, size_t *); static int tohex(int c); -static char *url_decode(const char *); +static char *url_decode(const char *, size_t); static void tar_flush_unconsumed(struct archive_read *, size_t *); +/* Sanity limits: These numbers should be low enough to + * prevent a maliciously-crafted archive from forcing us to + * allocate extreme amounts of memory. But of course, they + * need to be high enough for any correct value. These + * will likely need some adjustment as we get more experience. */ +static const size_t guname_limit = 65536; /* Longest uname or gname: 64kiB */ +static const size_t pathname_limit = 1048576; /* Longest path name: 1MiB */ +static const size_t sparse_map_limit = 8 * 1048576; /* Longest sparse map: 8MiB */ +static const size_t xattr_limit = 16 * 1048576; /* Longest xattr: 16MiB */ +static const size_t fflags_limit = 512; /* Longest fflags */ +static const size_t acl_limit = 131072; /* Longest textual ACL: 128kiB */ +static const int64_t entry_limit = 0xfffffffffffffffLL; /* 2^60 bytes = 1 ExbiByte */ int archive_read_support_format_gnutar(struct archive *a) @@ -250,7 +267,7 @@ archive_read_support_format_tar(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_tar"); - tar = (struct tar *)calloc(1, sizeof(*tar)); + tar = calloc(1, sizeof(*tar)); if (tar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate tar data"); @@ -284,17 +301,12 @@ archive_read_format_tar_cleanup(struct archive_read *a) tar = (struct tar *)(a->format->data); gnu_clear_sparse_list(tar); - archive_string_free(&tar->acl_text); archive_string_free(&tar->entry_pathname); archive_string_free(&tar->entry_pathname_override); - archive_string_free(&tar->entry_linkpath); archive_string_free(&tar->entry_uname); archive_string_free(&tar->entry_gname); + archive_string_free(&tar->entry_linkpath); archive_string_free(&tar->line); - archive_string_free(&tar->pax_global); - archive_string_free(&tar->pax_header); - archive_string_free(&tar->longname); - archive_string_free(&tar->longlink); archive_string_free(&tar->localname); free(tar); (a->format->data) = NULL; @@ -506,6 +518,8 @@ archive_read_format_tar_read_header(struct archive_read *a, * probably not worthwhile just to support the relatively * obscure tar->cpio conversion case. */ + /* TODO: Move this into `struct tar` to avoid conflicts + * when reading multiple archives */ static int default_inode; static int default_dev; struct tar *tar; @@ -526,8 +540,7 @@ archive_read_format_tar_read_header(struct archive_read *a, tar = (struct tar *)(a->format->data); tar->entry_offset = 0; gnu_clear_sparse_list(tar); - tar->realsize = -1; /* Mark this as "unset" */ - tar->realsize_override = 0; + tar->size_fields = 0; /* We don't have any size info yet */ /* Setup default string conversion. */ tar->sconv = tar->opt_sconv; @@ -619,16 +632,15 @@ archive_read_format_tar_read_data(struct archive_read *a, tar->entry_padding = 0; *buff = NULL; *size = 0; - *offset = tar->realsize; + *offset = tar->disk_size; return (ARCHIVE_EOF); } *buff = __archive_read_ahead(a, 1, &bytes_read); - if (bytes_read < 0) - return (ARCHIVE_FATAL); if (*buff == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Truncated tar archive"); + "Truncated tar archive" + " detected while reading data"); return (ARCHIVE_FATAL); } if (bytes_read > tar->entry_bytes_remaining) @@ -689,7 +701,7 @@ archive_read_format_tar_skip(struct archive_read *a) } /* - * This function recursively interprets all of the headers associated + * This function reads and interprets all of the headers associated * with a single entry. */ static int @@ -697,190 +709,259 @@ tar_read_header(struct archive_read *a, struct tar *tar, struct archive_entry *entry, size_t *unconsumed) { ssize_t bytes; - int err, eof_vol_header; + int err = ARCHIVE_OK, err2; + int eof_fatal = 0; /* EOF is okay at some points... */ const char *h; const struct archive_entry_header_ustar *header; const struct archive_entry_header_gnutar *gnuheader; - eof_vol_header = 0; - - /* Loop until we find a workable header record. */ - for (;;) { - tar_flush_unconsumed(a, unconsumed); + /* Bitmask of what header types we've seen. */ + int32_t seen_headers = 0; + static const int32_t seen_A_header = 1; + static const int32_t seen_g_header = 2; + static const int32_t seen_K_header = 4; + static const int32_t seen_L_header = 8; + static const int32_t seen_V_header = 16; + static const int32_t seen_x_header = 32; /* Also X */ + static const int32_t seen_mac_metadata = 512; + + tar->pax_hdrcharset_utf8 = 1; + tar->sparse_gnu_attributes_seen = 0; + archive_string_empty(&(tar->entry_gname)); + archive_string_empty(&(tar->entry_pathname)); + archive_string_empty(&(tar->entry_pathname_override)); + archive_string_empty(&(tar->entry_uname)); + archive_string_empty(&tar->entry_linkpath); - /* Read 512-byte header record */ - h = __archive_read_ahead(a, 512, &bytes); - if (bytes < 0) - return ((int)bytes); - if (bytes == 0) { /* EOF at a block boundary. */ - /* Some writers do omit the block of nulls. */ - return (ARCHIVE_EOF); - } - if (bytes < 512) { /* Short block at EOF; this is bad. */ - archive_set_error(&a->archive, - ARCHIVE_ERRNO_FILE_FORMAT, - "Truncated tar archive"); - return (ARCHIVE_FATAL); - } - *unconsumed = 512; + /* Ensure format is set. */ + if (a->archive.archive_format_name == NULL) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR; + a->archive.archive_format_name = "tar"; + } - /* Header is workable if it's not an end-of-archive mark. */ - if (h[0] != 0 || !archive_block_is_null(h)) - break; + /* + * TODO: Write global/default pax options into + * 'entry' struct here before overwriting with + * file-specific options. + */ - /* Ensure format is set for archives with only null blocks. */ - if (a->archive.archive_format_name == NULL) { - a->archive.archive_format = ARCHIVE_FORMAT_TAR; - a->archive.archive_format_name = "tar"; - } + /* Loop over all the headers needed for the next entry */ + for (;;) { - if (!tar->read_concatenated_archives) { - /* Try to consume a second all-null record, as well. */ + /* Find the next valid header record. */ + while (1) { tar_flush_unconsumed(a, unconsumed); - h = __archive_read_ahead(a, 512, NULL); - if (h != NULL && h[0] == 0 && archive_block_is_null(h)) - __archive_read_consume(a, 512); - archive_clear_error(&a->archive); - return (ARCHIVE_EOF); - } - /* - * We're reading concatenated archives, ignore this block and - * loop to get the next. - */ - } + /* Read 512-byte header record */ + h = __archive_read_ahead(a, 512, &bytes); + if (bytes == 0) { /* EOF at a block boundary. */ + if (eof_fatal) { + /* We've read a special header already; + * if there's no regular header, then this is + * a premature EOF. */ + archive_set_error(&a->archive, EINVAL, + "Damaged tar archive (end-of-archive within a sequence of headers)"); + return (ARCHIVE_FATAL); + } else { + return (ARCHIVE_EOF); + } + } + if (h == NULL) { /* Short block at EOF; this is bad. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive" + " detected while reading next header"); + return (ARCHIVE_FATAL); + } + *unconsumed += 512; - /* - * Note: If the checksum fails and we return ARCHIVE_RETRY, - * then the client is likely to just retry. This is a very - * crude way to search for the next valid header! - * - * TODO: Improve this by implementing a real header scan. - */ - if (!checksum(a, h)) { - tar_flush_unconsumed(a, unconsumed); - archive_set_error(&a->archive, EINVAL, "Damaged tar archive"); - return (ARCHIVE_RETRY); /* Retryable: Invalid header */ - } + if (h[0] == 0 && archive_block_is_null(h)) { + /* We found a NULL block which indicates end-of-archive */ - if (++tar->header_recursion_depth > 32) { - tar_flush_unconsumed(a, unconsumed); - archive_set_error(&a->archive, EINVAL, "Too many special headers"); - return (ARCHIVE_WARN); - } + if (tar->read_concatenated_archives) { + /* We're ignoring NULL blocks, so keep going. */ + continue; + } - /* Determine the format variant. */ - header = (const struct archive_entry_header_ustar *)h; + /* Try to consume a second all-null record, as well. */ + /* If we can't, that's okay. */ + tar_flush_unconsumed(a, unconsumed); + h = __archive_read_ahead(a, 512, NULL); + if (h != NULL && h[0] == 0 && archive_block_is_null(h)) + __archive_read_consume(a, 512); - switch(header->typeflag[0]) { - case 'A': /* Solaris tar ACL */ - a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; - a->archive.archive_format_name = "Solaris tar"; - err = header_Solaris_ACL(a, tar, entry, h, unconsumed); - break; - case 'g': /* POSIX-standard 'g' header. */ - a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; - a->archive.archive_format_name = "POSIX pax interchange format"; - err = header_pax_global(a, tar, entry, h, unconsumed); - if (err == ARCHIVE_EOF) - return (err); - break; - case 'K': /* Long link name (GNU tar, others) */ - err = header_longlink(a, tar, entry, h, unconsumed); - break; - case 'L': /* Long filename (GNU tar, others) */ - err = header_longname(a, tar, entry, h, unconsumed); - break; - case 'V': /* GNU volume header */ - err = header_volume(a, tar, entry, h, unconsumed); - if (err == ARCHIVE_EOF) - eof_vol_header = 1; - break; - case 'X': /* Used by SUN tar; same as 'x'. */ - a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; - a->archive.archive_format_name = - "POSIX pax interchange format (Sun variant)"; - err = header_pax_extensions(a, tar, entry, h, unconsumed); - break; - case 'x': /* POSIX-standard 'x' header. */ - a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; - a->archive.archive_format_name = "POSIX pax interchange format"; - err = header_pax_extensions(a, tar, entry, h, unconsumed); - break; - default: - gnuheader = (const struct archive_entry_header_gnutar *)h; - if (memcmp(gnuheader->magic, "ustar \0", 8) == 0) { - a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR; - a->archive.archive_format_name = "GNU tar format"; - err = header_gnutar(a, tar, entry, h, unconsumed); - } else if (memcmp(header->magic, "ustar", 5) == 0) { - if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) { - a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR; - a->archive.archive_format_name = "POSIX ustar format"; + archive_clear_error(&a->archive); + return (ARCHIVE_EOF); } - err = header_ustar(a, tar, entry, h); - } else { - a->archive.archive_format = ARCHIVE_FORMAT_TAR; - a->archive.archive_format_name = "tar (non-POSIX)"; - err = header_old_tar(a, tar, entry, h); - } - } - if (err == ARCHIVE_FATAL) - return (err); - tar_flush_unconsumed(a, unconsumed); - - h = NULL; - header = NULL; + /* This is NOT a null block, so it must be a valid header. */ + if (!checksum(a, h)) { + tar_flush_unconsumed(a, unconsumed); + archive_set_error(&a->archive, EINVAL, + "Damaged tar archive (bad header checksum)"); + /* If we've read some critical information (pax headers, etc) + * and _then_ see a bad header, we can't really recover. */ + if (eof_fatal) { + return (ARCHIVE_FATAL); + } else { + return (ARCHIVE_RETRY); + } + } + break; + } - --tar->header_recursion_depth; - /* Yuck. Apple's design here ends up storing long pathname - * extensions for both the AppleDouble extension entry and the - * regular entry. - */ - if ((err == ARCHIVE_WARN || err == ARCHIVE_OK) && - tar->header_recursion_depth == 0 && - tar->process_mac_extensions) { - int err2 = read_mac_metadata_blob(a, tar, entry, h, unconsumed); - if (err2 < err) - err = err2; - } - - /* We return warnings or success as-is. Anything else is fatal. */ - if (err == ARCHIVE_WARN || err == ARCHIVE_OK) { - if (tar->sparse_gnu_pending) { - if (tar->sparse_gnu_major == 1 && - tar->sparse_gnu_minor == 0) { - ssize_t bytes_read; - - tar->sparse_gnu_pending = 0; - /* Read initial sparse map. */ - bytes_read = gnu_sparse_10_read(a, tar, unconsumed); - if (bytes_read < 0) - return ((int)bytes_read); - tar->entry_bytes_remaining -= bytes_read; + /* Determine the format variant. */ + header = (const struct archive_entry_header_ustar *)h; + switch(header->typeflag[0]) { + case 'A': /* Solaris tar ACL */ + if (seen_headers & seen_A_header) { + return (ARCHIVE_FATAL); + } + seen_headers |= seen_A_header; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "Solaris tar"; + err2 = header_Solaris_ACL(a, tar, entry, h, unconsumed); + break; + case 'g': /* POSIX-standard 'g' header. */ + if (seen_headers & seen_g_header) { + return (ARCHIVE_FATAL); + } + seen_headers |= seen_g_header; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange format"; + err2 = header_pax_global(a, tar, entry, h, unconsumed); + break; + case 'K': /* Long link name (GNU tar, others) */ + if (seen_headers & seen_K_header) { + return (ARCHIVE_FATAL); + } + seen_headers |= seen_K_header; + err2 = header_gnu_longlink(a, tar, entry, h, unconsumed); + break; + case 'L': /* Long filename (GNU tar, others) */ + if (seen_headers & seen_L_header) { + return (ARCHIVE_FATAL); + } + seen_headers |= seen_L_header; + err2 = header_gnu_longname(a, tar, entry, h, unconsumed); + break; + case 'V': /* GNU volume header */ + if (seen_headers & seen_V_header) { + return (ARCHIVE_FATAL); + } + seen_headers |= seen_V_header; + err2 = header_volume(a, tar, entry, h, unconsumed); + break; + case 'X': /* Used by SUN tar; same as 'x'. */ + if (seen_headers & seen_x_header) { + return (ARCHIVE_FATAL); + } + seen_headers |= seen_x_header; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = + "POSIX pax interchange format (Sun variant)"; + err2 = header_pax_extension(a, tar, entry, h, unconsumed); + break; + case 'x': /* POSIX-standard 'x' header. */ + if (seen_headers & seen_x_header) { + return (ARCHIVE_FATAL); + } + seen_headers |= seen_x_header; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange format"; + err2 = header_pax_extension(a, tar, entry, h, unconsumed); + break; + default: /* Regular header: Legacy tar, GNU tar, or ustar */ + gnuheader = (const struct archive_entry_header_gnutar *)h; + if (memcmp(gnuheader->magic, "ustar \0", 8) == 0) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR; + a->archive.archive_format_name = "GNU tar format"; + err2 = header_gnutar(a, tar, entry, h, unconsumed); + } else if (memcmp(header->magic, "ustar", 5) == 0) { + if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR; + a->archive.archive_format_name = "POSIX ustar format"; + } + err2 = header_ustar(a, tar, entry, h); } else { - archive_set_error(&a->archive, - ARCHIVE_ERRNO_MISC, - "Unrecognized GNU sparse file format"); - return (ARCHIVE_WARN); + a->archive.archive_format = ARCHIVE_FORMAT_TAR; + a->archive.archive_format_name = "tar (non-POSIX)"; + err2 = header_old_tar(a, tar, entry, h); + } + err = err_combine(err, err2); + /* We return warnings or success as-is. Anything else is fatal. */ + if (err < ARCHIVE_WARN) { + return (ARCHIVE_FATAL); + } + /* Filename of the form `._filename` is an AppleDouble + * extension entry. The body is the macOS metadata blob; + * this is followed by another entry with the actual + * regular file data. + * This design has two drawbacks: + * = it's brittle; you might just have a file with such a name + * = it duplicates any long pathname extensions + * + * TODO: This probably shouldn't be here at all. Consider + * just returning the contents as a regular entry here and + * then dealing with it when we write data to disk. + */ + if (tar->process_mac_extensions + && ((seen_headers & seen_mac_metadata) == 0) + && is_mac_metadata_entry(entry)) { + err2 = read_mac_metadata_blob(a, entry, unconsumed); + if (err2 < ARCHIVE_WARN) { + return (ARCHIVE_FATAL); + } + err = err_combine(err, err2); + /* Note: Other headers can appear again. */ + seen_headers = seen_mac_metadata; + break; + } + + /* Reconcile GNU sparse attributes */ + if (tar->sparse_gnu_attributes_seen) { + /* Only 'S' (GNU sparse) and ustar '0' regular files can be sparse */ + if (tar->filetype != 'S' && tar->filetype != '0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Non-regular file cannot be sparse"); + return (ARCHIVE_WARN); + } else if (tar->sparse_gnu_major == 0 && + tar->sparse_gnu_minor == 0) { + /* Sparse map already parsed from 'x' header */ + } else if (tar->sparse_gnu_major == 0 && + tar->sparse_gnu_minor == 1) { + /* Sparse map already parsed from 'x' header */ + } else if (tar->sparse_gnu_major == 1 && + tar->sparse_gnu_minor == 0) { + /* Sparse map is prepended to file contents */ + ssize_t bytes_read; + bytes_read = gnu_sparse_10_read(a, tar, unconsumed); + if (bytes_read < 0) + return ((int)bytes_read); + tar->entry_bytes_remaining -= bytes_read; + } else { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Unrecognized GNU sparse file format"); + return (ARCHIVE_WARN); + } } - tar->sparse_gnu_pending = 0; + return (err); } - return (err); - } - if (err == ARCHIVE_EOF) { - if (!eof_vol_header) { - /* EOF when recursively reading a header is bad. */ - archive_set_error(&a->archive, EINVAL, - "Damaged tar archive"); - } else { - /* If we encounter just a GNU volume header treat - * this situation as an empty archive */ - return (ARCHIVE_EOF); + + /* We're between headers ... */ + err = err_combine(err, err2); + if (err == ARCHIVE_FATAL) + return (err); + + /* The GNU volume header and the pax `g` global header + * are both allowed to be the only header in an + * archive. If we've seen any other header, a + * following EOF is fatal. */ + if ((seen_headers & ~seen_V_header & ~seen_g_header) != 0) { + eof_fatal = 1; } } - return (ARCHIVE_FATAL); } /* @@ -935,7 +1016,12 @@ checksum(struct archive_read *a, const void *h) if (sum == check) return (1); +#if DONT_FAIL_ON_CRC_ERROR + /* Speed up fuzzing by pretending the checksum is always right. */ + return (1); +#else return (0); +#endif } /* @@ -960,25 +1046,20 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, struct archive_entry *entry, const void *h, size_t *unconsumed) { const struct archive_entry_header_ustar *header; + struct archive_string acl_text; size_t size; int err, acl_type; - int64_t type; + uint64_t type; char *acl, *p; - /* - * read_body_to_string adds a NUL terminator, but we need a little - * more to make sure that we don't overrun acl_text later. - */ header = (const struct archive_entry_header_ustar *)h; size = (size_t)tar_atol(header->size, sizeof(header->size)); - err = read_body_to_string(a, tar, &(tar->acl_text), h, unconsumed); - if (err != ARCHIVE_OK) - return (err); - - /* Recursively read next header */ - err = tar_read_header(a, tar, entry, unconsumed); - if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + archive_string_init(&acl_text); + err = read_body_to_string(a, tar, &acl_text, h, unconsumed); + if (err != ARCHIVE_OK) { + archive_string_free(&acl_text); return (err); + } /* TODO: Examine the first characters to see if this * is an AIX ACL descriptor. We'll likely never support @@ -986,12 +1067,13 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, * we do see them. */ /* Leading octal number indicates ACL type and number of entries. */ - p = acl = tar->acl_text.s; + p = acl = acl_text.s; type = 0; while (*p != '\0' && p < acl + size) { if (*p < '0' || *p > '7') { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Malformed Solaris ACL attribute (invalid digit)"); + archive_string_free(&acl_text); return(ARCHIVE_WARN); } type <<= 3; @@ -999,11 +1081,12 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, if (type > 077777777) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Malformed Solaris ACL attribute (count too large)"); + archive_string_free(&acl_text); return (ARCHIVE_WARN); } p++; } - switch ((int)type & ~0777777) { + switch (type & ~0777777) { case 01000000: /* POSIX.1e ACL */ acl_type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; @@ -1014,8 +1097,9 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, break; default: archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Malformed Solaris ACL attribute (unsupported type %o)", - (int)type); + "Malformed Solaris ACL attribute (unsupported type %" + PRIo64 ")", type); + archive_string_free(&acl_text); return (ARCHIVE_WARN); } p++; @@ -1023,6 +1107,7 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, if (p >= acl + size) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Malformed Solaris ACL attribute (body overflow)"); + archive_string_free(&acl_text); return(ARCHIVE_WARN); } @@ -1036,12 +1121,17 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, if (tar->sconv_acl == NULL) { tar->sconv_acl = archive_string_conversion_from_charset( &(a->archive), "UTF-8", 1); - if (tar->sconv_acl == NULL) + if (tar->sconv_acl == NULL) { + archive_string_free(&acl_text); return (ARCHIVE_FATAL); + } } archive_strncpy(&(tar->localname), acl, p - acl); err = archive_acl_from_text_l(archive_entry_acl(entry), tar->localname.s, acl_type, tar->sconv_acl); + /* Workaround: Force perm_is_set() to be correct */ + /* If this bit were stored in the ACL, this wouldn't be needed */ + archive_entry_set_perm(entry, archive_entry_perm(entry)); if (err != ARCHIVE_OK) { if (errno == ENOMEM) { archive_set_error(&a->archive, ENOMEM, @@ -1050,6 +1140,7 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Malformed Solaris ACL attribute (unparsable)"); } + archive_string_free(&acl_text); return (err); } @@ -1057,20 +1148,19 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar, * Interpret 'K' long linkname header. */ static int -header_longlink(struct archive_read *a, struct tar *tar, +header_gnu_longlink(struct archive_read *a, struct tar *tar, struct archive_entry *entry, const void *h, size_t *unconsumed) { int err; - err = read_body_to_string(a, tar, &(tar->longlink), h, unconsumed); - if (err != ARCHIVE_OK) - return (err); - err = tar_read_header(a, tar, entry, unconsumed); - if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) - return (err); - /* Set symlink if symlink already set, else hardlink. */ - archive_entry_copy_link(entry, tar->longlink.s); - return (ARCHIVE_OK); + struct archive_string linkpath; + archive_string_init(&linkpath); + err = read_body_to_string(a, tar, &linkpath, h, unconsumed); + if (err == ARCHIVE_OK) { + archive_entry_set_link(entry, linkpath.s); + } + archive_string_free(&linkpath); + return (err); } static int @@ -1092,25 +1182,23 @@ set_conversion_failed_error(struct archive_read *a, * Interpret 'L' long filename header. */ static int -header_longname(struct archive_read *a, struct tar *tar, +header_gnu_longname(struct archive_read *a, struct tar *tar, struct archive_entry *entry, const void *h, size_t *unconsumed) { int err; + struct archive_string longname; - err = read_body_to_string(a, tar, &(tar->longname), h, unconsumed); - if (err != ARCHIVE_OK) - return (err); - /* Read and parse "real" header, then override name. */ - err = tar_read_header(a, tar, entry, unconsumed); - if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) - return (err); - if (archive_entry_copy_pathname_l(entry, tar->longname.s, - archive_strlen(&(tar->longname)), tar->sconv) != 0) - err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + archive_string_init(&longname); + err = read_body_to_string(a, tar, &longname, h, unconsumed); + if (err == ARCHIVE_OK) { + if (archive_entry_copy_pathname_l(entry, longname.s, + archive_strlen(&longname), tar->sconv) != 0) + err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + } + archive_string_free(&longname); return (err); } - /* * Interpret 'V' GNU tar volume header. */ @@ -1118,32 +1206,33 @@ static int header_volume(struct archive_read *a, struct tar *tar, struct archive_entry *entry, const void *h, size_t *unconsumed) { - (void)h; + const struct archive_entry_header_ustar *header; + int64_t size, to_consume; - /* Just skip this and read the next header. */ - return (tar_read_header(a, tar, entry, unconsumed)); + (void)a; /* UNUSED */ + (void)tar; /* UNUSED */ + (void)entry; /* UNUSED */ + + header = (const struct archive_entry_header_ustar *)h; + size = tar_atol(header->size, sizeof(header->size)); + if (size > (int64_t)pathname_limit) { + return (ARCHIVE_FATAL); + } + to_consume = ((size + 511) & ~511); + *unconsumed += to_consume; + return (ARCHIVE_OK); } /* - * Read body of an archive entry into an archive_string object. + * Read the next `size` bytes into the provided string. + * Null-terminate the string. */ static int -read_body_to_string(struct archive_read *a, struct tar *tar, - struct archive_string *as, const void *h, size_t *unconsumed) -{ - int64_t size; - const struct archive_entry_header_ustar *header; +read_bytes_to_string(struct archive_read *a, + struct archive_string *as, size_t size, + size_t *unconsumed) { const void *src; - (void)tar; /* UNUSED */ - header = (const struct archive_entry_header_ustar *)h; - size = tar_atol(header->size, sizeof(header->size)); - if ((size > 1048576) || (size < 0)) { - archive_set_error(&a->archive, EINVAL, - "Special header too large"); - return (ARCHIVE_FATAL); - } - /* Fail if we can't make our buffer big enough. */ if (archive_string_ensure(as, (size_t)size+1) == NULL) { archive_set_error(&a->archive, ENOMEM, @@ -1154,18 +1243,54 @@ read_body_to_string(struct archive_read *a, struct tar *tar, tar_flush_unconsumed(a, unconsumed); /* Read the body into the string. */ - *unconsumed = (size_t)((size + 511) & ~ 511); - src = __archive_read_ahead(a, *unconsumed, NULL); + src = __archive_read_ahead(a, size, NULL); if (src == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading metadata"); *unconsumed = 0; return (ARCHIVE_FATAL); } memcpy(as->s, src, (size_t)size); as->s[size] = '\0'; as->length = (size_t)size; + *unconsumed += size; return (ARCHIVE_OK); } +/* + * Read body of an archive entry into an archive_string object. + */ +static int +read_body_to_string(struct archive_read *a, struct tar *tar, + struct archive_string *as, const void *h, size_t *unconsumed) +{ + int64_t size; + const struct archive_entry_header_ustar *header; + int r; + + (void)tar; /* UNUSED */ + header = (const struct archive_entry_header_ustar *)h; + size = tar_atol(header->size, sizeof(header->size)); + if (size > entry_limit) { + return (ARCHIVE_FATAL); + } + if ((size > (int64_t)pathname_limit) || (size < 0)) { + archive_string_empty(as); + int64_t to_consume = ((size + 511) & ~511); + if (to_consume != __archive_read_consume(a, to_consume)) { + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, EINVAL, + "Special header too large: %d > 1MiB", + (int)size); + return (ARCHIVE_WARN); + } + r = read_bytes_to_string(a, as, size, unconsumed); + *unconsumed += 0x1ff & (-size); + return(r); +} + /* * Parse out common header elements. * @@ -1175,56 +1300,128 @@ read_body_to_string(struct archive_read *a, struct tar *tar, * allows header_old_tar and header_ustar * to handle filenames differently, while still putting most of the * common parsing into one place. + * + * This is called _after_ ustar, GNU tar, Schily, etc, special + * fields have already been parsed into the `tar` structure. + * So we can make final decisions here about how to reconcile + * size, mode, etc, information. */ static int header_common(struct archive_read *a, struct tar *tar, struct archive_entry *entry, const void *h) { const struct archive_entry_header_ustar *header; - char tartype; + const char *existing_linkpath; + const wchar_t *existing_wcs_linkpath; int err = ARCHIVE_OK; header = (const struct archive_entry_header_ustar *)h; - if (header->linkname[0]) - archive_strncpy(&(tar->entry_linkpath), - header->linkname, sizeof(header->linkname)); - else - archive_string_empty(&(tar->entry_linkpath)); /* Parse out the numeric fields (all are octal) */ - archive_entry_set_mode(entry, - (mode_t)tar_atol(header->mode, sizeof(header->mode))); - archive_entry_set_uid(entry, tar_atol(header->uid, sizeof(header->uid))); - archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid))); - tar->entry_bytes_remaining = tar_atol(header->size, sizeof(header->size)); + + /* Split mode handling: Set filetype always, perm only if not already set */ + archive_entry_set_filetype(entry, + (mode_t)tar_atol(header->mode, sizeof(header->mode))); + if (!archive_entry_perm_is_set(entry)) { + archive_entry_set_perm(entry, + (mode_t)tar_atol(header->mode, sizeof(header->mode))); + } + + /* Set uid, gid, mtime if not already set */ + if (!archive_entry_uid_is_set(entry)) { + archive_entry_set_uid(entry, tar_atol(header->uid, sizeof(header->uid))); + } + if (!archive_entry_gid_is_set(entry)) { + archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid))); + } + if (!archive_entry_mtime_is_set(entry)) { + archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0); + } + + /* Reconcile the size info. */ + /* First, how big is the file on disk? */ + if ((tar->size_fields & TAR_SIZE_GNU_SPARSE_REALSIZE) != 0) { + /* GNU sparse format 1.0 uses `GNU.sparse.realsize` + * to hold the size of the file on disk. */ + tar->disk_size = tar->GNU_sparse_realsize; + } else if ((tar->size_fields & TAR_SIZE_GNU_SPARSE_SIZE) != 0 + && (tar->sparse_gnu_major == 0)) { + /* GNU sparse format 0.0 and 0.1 use `GNU.sparse.size` + * to hold the size of the file on disk. */ + tar->disk_size = tar->GNU_sparse_size; + } else if ((tar->size_fields & TAR_SIZE_SCHILY_SPARSE_REALSIZE) != 0) { + tar->disk_size = tar->SCHILY_sparse_realsize; + } else if ((tar->size_fields & TAR_SIZE_PAX_SIZE) != 0) { + tar->disk_size = tar->pax_size; + } else { + /* There wasn't a suitable pax header, so use the ustar info */ + tar->disk_size = tar_atol(header->size, sizeof(header->size)); + } + + if (tar->disk_size < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Tar entry has negative file size"); + return (ARCHIVE_FATAL); + } else if (tar->disk_size > entry_limit) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Tar entry size overflow"); + return (ARCHIVE_FATAL); + } else { + archive_entry_set_size(entry, tar->disk_size); + } + + /* Second, how big is the data in the archive? */ + if ((tar->size_fields & TAR_SIZE_GNU_SPARSE_SIZE) != 0 + && (tar->sparse_gnu_major == 1)) { + /* GNU sparse format 1.0 uses `GNU.sparse.size` + * to hold the size of the data in the archive. */ + tar->entry_bytes_remaining = tar->GNU_sparse_size; + } else if ((tar->size_fields & TAR_SIZE_PAX_SIZE) != 0) { + tar->entry_bytes_remaining = tar->pax_size; + } else { + tar->entry_bytes_remaining + = tar_atol(header->size, sizeof(header->size)); + } if (tar->entry_bytes_remaining < 0) { tar->entry_bytes_remaining = 0; archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Tar entry has negative size"); + "Tar entry has negative size"); return (ARCHIVE_FATAL); - } - if (tar->entry_bytes_remaining == INT64_MAX) { - /* Note: tar_atol returns INT64_MAX on overflow */ + } else if (tar->entry_bytes_remaining > entry_limit) { tar->entry_bytes_remaining = 0; archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Tar entry size overflow"); + "Tar entry size overflow"); return (ARCHIVE_FATAL); } - tar->realsize = tar->entry_bytes_remaining; - archive_entry_set_size(entry, tar->entry_bytes_remaining); - archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0); /* Handle the tar type flag appropriately. */ - tartype = header->typeflag[0]; + tar->filetype = header->typeflag[0]; - switch (tartype) { + /* + * TODO: If the linkpath came from Pax extension header, then + * we should obey the hdrcharset_utf8 flag when converting these. + */ + switch (tar->filetype) { case '1': /* Hard link */ - if (archive_entry_copy_hardlink_l(entry, tar->entry_linkpath.s, - archive_strlen(&(tar->entry_linkpath)), tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, - "Linkname"); - if (err == ARCHIVE_FATAL) - return (err); + archive_entry_set_link_to_hardlink(entry); + existing_wcs_linkpath = archive_entry_hardlink_w(entry); + existing_linkpath = archive_entry_hardlink(entry); + if ((existing_linkpath == NULL || existing_linkpath[0] == '\0') + && (existing_wcs_linkpath == NULL || existing_wcs_linkpath[0] == '\0')) { + struct archive_string linkpath; + archive_string_init(&linkpath); + archive_strncpy(&linkpath, + header->linkname, sizeof(header->linkname)); + if (archive_entry_copy_hardlink_l(entry, linkpath.s, + archive_strlen(&linkpath), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, + "Linkname"); + if (err == ARCHIVE_FATAL) { + archive_string_free(&linkpath); + return (err); + } + } + archive_string_free(&linkpath); } /* * The following may seem odd, but: Technically, tar @@ -1284,16 +1481,29 @@ header_common(struct archive_read *a, struct tar *tar, */ break; case '2': /* Symlink */ + archive_entry_set_link_to_symlink(entry); + existing_wcs_linkpath = archive_entry_symlink_w(entry); + existing_linkpath = archive_entry_symlink(entry); + if ((existing_linkpath == NULL || existing_linkpath[0] == '\0') + && (existing_wcs_linkpath == NULL || existing_wcs_linkpath[0] == '\0')) { + struct archive_string linkpath; + archive_string_init(&linkpath); + archive_strncpy(&linkpath, + header->linkname, sizeof(header->linkname)); + if (archive_entry_copy_symlink_l(entry, linkpath.s, + archive_strlen(&linkpath), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, + "Linkname"); + if (err == ARCHIVE_FATAL) { + archive_string_free(&linkpath); + return (err); + } + } + archive_string_free(&linkpath); + } archive_entry_set_filetype(entry, AE_IFLNK); archive_entry_set_size(entry, 0); tar->entry_bytes_remaining = 0; - if (archive_entry_copy_symlink_l(entry, tar->entry_linkpath.s, - archive_strlen(&(tar->entry_linkpath)), tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, - "Linkname"); - if (err == ARCHIVE_FATAL) - return (err); - } break; case '3': /* Character device */ archive_entry_set_filetype(entry, AE_IFCHR); @@ -1343,15 +1553,9 @@ header_common(struct archive_read *a, struct tar *tar, * sparse information in the extended area. */ /* FALLTHROUGH */ - case '0': - /* - * Enable sparse file "read" support only for regular - * files and explicit GNU sparse files. However, we - * don't allow non-standard file types to be sparse. - */ - tar->sparse_allowed = 1; + case '0': /* ustar "regular" file */ /* FALLTHROUGH */ - default: /* Regular file and non-standard types */ + default: /* Non-standard file types */ /* * Per POSIX: non-recognized types should always be * treated as regular files. @@ -1372,9 +1576,17 @@ header_old_tar(struct archive_read *a, struct tar *tar, const struct archive_entry_header_ustar *header; int err = ARCHIVE_OK, err2; - /* Copy filename over (to ensure null termination). */ + /* + * Copy filename over (to ensure null termination). + * Skip if pathname was already set e.g. by header_gnu_longname() + */ header = (const struct archive_entry_header_ustar *)h; - if (archive_entry_copy_pathname_l(entry, + + const char *existing_pathname = archive_entry_pathname(entry); + const wchar_t *existing_wcs_pathname = archive_entry_pathname_w(entry); + if ((existing_pathname == NULL || existing_pathname[0] == '\0') + && (existing_wcs_pathname == NULL || existing_wcs_pathname[0] == '\0') && + archive_entry_copy_pathname_l(entry, header->name, sizeof(header->name), tar->sconv) != 0) { err = set_conversion_failed_error(a, tar->sconv, "Pathname"); if (err == ARCHIVE_FATAL) @@ -1391,21 +1603,13 @@ header_old_tar(struct archive_read *a, struct tar *tar, } /* - * Read a Mac AppleDouble-encoded blob of file metadata, - * if there is one. + * Is this likely an AppleDouble extension? */ static int -read_mac_metadata_blob(struct archive_read *a, struct tar *tar, - struct archive_entry *entry, const void *h, size_t *unconsumed) -{ - int64_t size; - size_t msize; - const void *data; +is_mac_metadata_entry(struct archive_entry *entry) { const char *p, *name; const wchar_t *wp, *wname; - (void)h; /* UNUSED */ - wname = wp = archive_entry_pathname_w(entry); if (wp != NULL) { /* Find the last path element. */ @@ -1417,8 +1621,8 @@ read_mac_metadata_blob(struct archive_read *a, struct tar *tar, * If last path element starts with "._", then * this is a Mac extension. */ - if (wname[0] != L'.' || wname[1] != L'_' || wname[2] == L'\0') - return ARCHIVE_OK; + if (wname[0] == L'.' && wname[1] == L'_' && wname[2] != L'\0') + return 1; } else { /* Find the last path element. */ name = p = archive_entry_pathname(entry); @@ -1432,9 +1636,29 @@ read_mac_metadata_blob(struct archive_read *a, struct tar *tar, * If last path element starts with "._", then * this is a Mac extension. */ - if (name[0] != '.' || name[1] != '_' || name[2] == '\0') - return ARCHIVE_OK; + if (name[0] == '.' && name[1] == '_' && name[2] != '\0') + return 1; } + /* Not a mac extension */ + return 0; +} + +/* + * Read a Mac AppleDouble-encoded blob of file metadata, + * if there is one. + * + * TODO: In Libarchive 4, we should consider ripping this + * out -- instead, return a file starting with `._` as + * a regular file and let the client (or archive_write logic) + * handle it. + */ +static int +read_mac_metadata_blob(struct archive_read *a, + struct archive_entry *entry, size_t *unconsumed) +{ + int64_t size; + size_t msize; + const void *data; /* Read the body as a Mac OS metadata blob. */ size = archive_entry_size(entry); @@ -1444,6 +1668,17 @@ read_mac_metadata_blob(struct archive_read *a, struct tar *tar, return (ARCHIVE_FATAL); } + /* TODO: Should this merely skip the overlarge entry and + * WARN? Or is xattr_limit sufficiently large that we can + * safely assume anything larger is malicious? */ + if (size > (int64_t)xattr_limit) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Oversized AppleDouble extension has size %llu > %llu", + (unsigned long long)size, + (unsigned long long)xattr_limit); + return (ARCHIVE_FATAL); + } + /* * TODO: Look beyond the body here to peek at the next header. * If it's a regular header (not an extension header) @@ -1456,15 +1691,19 @@ read_mac_metadata_blob(struct archive_read *a, struct tar *tar, * Q: Is the above idea really possible? Even * when there are GNU or pax extension entries? */ + tar_flush_unconsumed(a, unconsumed); data = __archive_read_ahead(a, msize, NULL); if (data == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading macOS metadata"); *unconsumed = 0; return (ARCHIVE_FATAL); } + archive_entry_clear(entry); archive_entry_copy_mac_metadata(entry, data, msize); *unconsumed = (msize + 511) & ~ 511; - tar_flush_unconsumed(a, unconsumed); - return (tar_read_header(a, tar, entry, unconsumed)); + return (ARCHIVE_OK); } /* @@ -1474,76 +1713,62 @@ static int header_pax_global(struct archive_read *a, struct tar *tar, struct archive_entry *entry, const void *h, size_t *unconsumed) { - int err; + const struct archive_entry_header_ustar *header; + int64_t size, to_consume; - err = read_body_to_string(a, tar, &(tar->pax_global), h, unconsumed); - if (err != ARCHIVE_OK) - return (err); - err = tar_read_header(a, tar, entry, unconsumed); - return (err); + (void)a; /* UNUSED */ + (void)tar; /* UNUSED */ + (void)entry; /* UNUSED */ + + header = (const struct archive_entry_header_ustar *)h; + size = tar_atol(header->size, sizeof(header->size)); + if (size > entry_limit) { + return (ARCHIVE_FATAL); + } + to_consume = ((size + 511) & ~511); + *unconsumed += to_consume; + return (ARCHIVE_OK); } -static int -header_pax_extensions(struct archive_read *a, struct tar *tar, - struct archive_entry *entry, const void *h, size_t *unconsumed) -{ - int err, err2; - - err = read_body_to_string(a, tar, &(tar->pax_header), h, unconsumed); - if (err != ARCHIVE_OK) - return (err); - - /* Parse the next header. */ - err = tar_read_header(a, tar, entry, unconsumed); - if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) - return (err); - - /* - * TODO: Parse global/default options into 'entry' struct here - * before handling file-specific options. - * - * This design (parse standard header, then overwrite with pax - * extended attribute data) usually works well, but isn't ideal; - * it would be better to parse the pax extended attributes first - * and then skip any fields in the standard header that were - * defined in the pax header. - */ - err2 = pax_header(a, tar, entry, &tar->pax_header); - err = err_combine(err, err2); - tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); - return (err); -} - - /* * Parse a file header for a Posix "ustar" archive entry. This also * handles "pax" or "extended ustar" entries. + * + * In order to correctly handle pax attributes (which precede this), + * we have to skip parsing any field for which the entry already has + * contents. */ static int header_ustar(struct archive_read *a, struct tar *tar, struct archive_entry *entry, const void *h) { const struct archive_entry_header_ustar *header; - struct archive_string *as; + struct archive_string as; int err = ARCHIVE_OK, r; header = (const struct archive_entry_header_ustar *)h; /* Copy name into an internal buffer to ensure null-termination. */ - as = &(tar->entry_pathname); - if (header->prefix[0]) { - archive_strncpy(as, header->prefix, sizeof(header->prefix)); - if (as->s[archive_strlen(as) - 1] != '/') - archive_strappend_char(as, '/'); - archive_strncat(as, header->name, sizeof(header->name)); - } else { - archive_strncpy(as, header->name, sizeof(header->name)); - } - if (archive_entry_copy_pathname_l(entry, as->s, archive_strlen(as), - tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, "Pathname"); - if (err == ARCHIVE_FATAL) - return (err); + const char *existing_pathname = archive_entry_pathname(entry); + const wchar_t *existing_wcs_pathname = archive_entry_pathname_w(entry); + if ((existing_pathname == NULL || existing_pathname[0] == '\0') + && (existing_wcs_pathname == NULL || existing_wcs_pathname[0] == '\0')) { + archive_string_init(&as); + if (header->prefix[0]) { + archive_strncpy(&as, header->prefix, sizeof(header->prefix)); + if (as.s[archive_strlen(&as) - 1] != '/') + archive_strappend_char(&as, '/'); + archive_strncat(&as, header->name, sizeof(header->name)); + } else { + archive_strncpy(&as, header->name, sizeof(header->name)); + } + if (archive_entry_copy_pathname_l(entry, as.s, archive_strlen(&as), + tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + if (err == ARCHIVE_FATAL) + return (err); + } + archive_string_free(&as); } /* Handle rest of common fields. */ @@ -1554,26 +1779,36 @@ header_ustar(struct archive_read *a, struct tar *tar, err = r; /* Handle POSIX ustar fields. */ - if (archive_entry_copy_uname_l(entry, - header->uname, sizeof(header->uname), tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, "Uname"); - if (err == ARCHIVE_FATAL) - return (err); + const char *existing_uname = archive_entry_uname(entry); + if (existing_uname == NULL || existing_uname[0] == '\0') { + if (archive_entry_copy_uname_l(entry, + header->uname, sizeof(header->uname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Uname"); + if (err == ARCHIVE_FATAL) + return (err); + } } - if (archive_entry_copy_gname_l(entry, - header->gname, sizeof(header->gname), tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, "Gname"); - if (err == ARCHIVE_FATAL) - return (err); + const char *existing_gname = archive_entry_gname(entry); + if (existing_gname == NULL || existing_gname[0] == '\0') { + if (archive_entry_copy_gname_l(entry, + header->gname, sizeof(header->gname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Gname"); + if (err == ARCHIVE_FATAL) + return (err); + } } /* Parse out device numbers only for char and block specials. */ if (header->typeflag[0] == '3' || header->typeflag[0] == '4') { - archive_entry_set_rdevmajor(entry, (dev_t) - tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); - archive_entry_set_rdevminor(entry, (dev_t) - tar_atol(header->rdevminor, sizeof(header->rdevminor))); + if (!archive_entry_rdev_is_set(entry)) { + archive_entry_set_rdevmajor(entry, (dev_t) + tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); + archive_entry_set_rdevminor(entry, (dev_t) + tar_atol(header->rdevminor, sizeof(header->rdevminor))); + } + } else { + archive_entry_set_rdev(entry, 0); } tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); @@ -1581,117 +1816,204 @@ header_ustar(struct archive_read *a, struct tar *tar, return (err); } - -/* - * Parse the pax extended attributes record. - * - * Returns non-zero if there's an error in the data. - */ static int -pax_header(struct archive_read *a, struct tar *tar, - struct archive_entry *entry, struct archive_string *in_as) +header_pax_extension(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) { - size_t attr_length, l, line_length, value_length; - char *p; - char *key, *value; - struct archive_string *as; + /* Sanity checks: The largest `x` body I've ever heard of was + * a little over 4MB. So I doubt there has ever been a + * well-formed archive with an `x` body over 1GiB. Similarly, + * it seems plausible that no single attribute has ever been + * larger than 100MB. So if we see a larger value here, it's + * almost certainly a sign of a corrupted/malicious archive. */ + + /* Maximum sane size for extension body: 1 GiB */ + /* This cannot be raised to larger than 8GiB without + * exceeding the maximum size for a standard ustar + * entry. */ + const int64_t ext_size_limit = 1024 * 1024 * (int64_t)1024; + /* Maximum size for a single line/attr: 100 million characters */ + /* This cannot be raised to more than 2GiB without exceeding + * a `size_t` on 32-bit platforms. */ + const size_t max_parsed_line_length = 99999999ULL; + /* Largest attribute prolog: size + name. */ + const size_t max_size_name = 512; + + /* Size and padding of the full extension body */ + int64_t ext_size, ext_padding; + size_t line_length, value_length, name_length; + ssize_t to_read, did_read; + const struct archive_entry_header_ustar *header; + const char *p, *attr_start, *name_start; struct archive_string_conv *sconv; - int err, err2; - char *attr = in_as->s; + struct archive_string *pas = NULL; + struct archive_string attr_name; + int err = ARCHIVE_OK, r; - attr_length = in_as->length; - tar->pax_hdrcharset_binary = 0; - archive_string_empty(&(tar->entry_gname)); - archive_string_empty(&(tar->entry_linkpath)); - archive_string_empty(&(tar->entry_pathname)); - archive_string_empty(&(tar->entry_pathname_override)); - archive_string_empty(&(tar->entry_uname)); - err = ARCHIVE_OK; - while (attr_length > 0) { - /* Parse decimal length field at start of line. */ + header = (const struct archive_entry_header_ustar *)h; + ext_size = tar_atol(header->size, sizeof(header->size)); + if (ext_size > entry_limit) { + return (ARCHIVE_FATAL); + } + if (ext_size < 0) { + archive_set_error(&a->archive, EINVAL, + "pax extension header has invalid size: %lld", + (long long)ext_size); + return (ARCHIVE_FATAL); + } + + ext_padding = 0x1ff & (-ext_size); + if (ext_size > ext_size_limit) { + /* Consume the pax extension body and return an error */ + if (ext_size + ext_padding != __archive_read_consume(a, ext_size + ext_padding)) { + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, EINVAL, + "Ignoring oversized pax extensions: %d > %d", + (int)ext_size, (int)ext_size_limit); + return (ARCHIVE_WARN); + } + tar_flush_unconsumed(a, unconsumed); + + /* Parse the size/name of each pax attribute in the body */ + archive_string_init(&attr_name); + while (ext_size > 0) { + /* Read enough bytes to parse the size/name of the next attribute */ + to_read = max_size_name; + if (to_read > ext_size) { + to_read = ext_size; + } + p = __archive_read_ahead(a, to_read, &did_read); + if (p == NULL) { /* EOF */ + archive_set_error(&a->archive, EINVAL, + "Truncated tar archive" + " detected while reading pax attribute name"); + return (ARCHIVE_FATAL); + } + if (did_read > ext_size) { + did_read = ext_size; + } + + /* Parse size of attribute */ line_length = 0; - l = attr_length; - p = attr; /* Record start of line. */ - while (l>0) { + attr_start = p; + while (1) { + if (p >= attr_start + did_read) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring malformed pax attributes: overlarge attribute size field"); + *unconsumed += ext_size + ext_padding; + return (ARCHIVE_WARN); + } if (*p == ' ') { p++; - l--; break; } if (*p < '0' || *p > '9') { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Ignoring malformed pax extended attributes"); + "Ignoring malformed pax attributes: malformed attribute size field"); + *unconsumed += ext_size + ext_padding; return (ARCHIVE_WARN); } line_length *= 10; line_length += *p - '0'; - if (line_length > 999999) { + if (line_length > max_parsed_line_length) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Rejecting pax extended attribute > 1MB"); + "Ignoring malformed pax attribute: size > %lld", + (long long)max_parsed_line_length); + *unconsumed += ext_size + ext_padding; return (ARCHIVE_WARN); } p++; - l--; } - /* - * Parsed length must be no bigger than available data, - * at least 1, and the last character of the line must - * be '\n'. - */ - if (line_length > attr_length - || line_length < 1 - || attr[line_length - 1] != '\n') - { - archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Ignoring malformed pax extended attribute"); - return (ARCHIVE_WARN); + if ((int64_t)line_length > ext_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring malformed pax attribute: %lld > %lld", + (long long)line_length, (long long)ext_size); + *unconsumed += ext_size + ext_padding; + return (ARCHIVE_WARN); } - /* Null-terminate the line. */ - attr[line_length - 1] = '\0'; - - /* Find end of key and null terminate it. */ - key = p; - if (key[0] == '=') - return (-1); - while (*p && *p != '=') - ++p; - if (*p == '\0') { + /* Parse name of attribute */ + if (p >= attr_start + did_read + || p >= attr_start + line_length + || *p == '=') { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Invalid pax extended attributes"); + "Ignoring malformed pax attributes: empty name found"); + *unconsumed += ext_size + ext_padding; return (ARCHIVE_WARN); } - *p = '\0'; + name_start = p; + while (1) { + if (p >= attr_start + did_read || p >= attr_start + line_length) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring malformed pax attributes: overlarge attribute name"); + *unconsumed += ext_size + ext_padding; + return (ARCHIVE_WARN); + } + if (*p == '=') { + break; + } + p++; + } + name_length = p - name_start; + p++; // Skip '=' - value = p + 1; + // Save the name before we consume it + archive_strncpy(&attr_name, name_start, name_length); - /* Some values may be binary data */ - value_length = attr + line_length - 1 - value; + ext_size -= p - attr_start; + value_length = line_length - (p - attr_start); - /* Identify this attribute and set it in the entry. */ - err2 = pax_attribute(a, tar, entry, key, value, value_length); - if (err2 == ARCHIVE_FATAL) - return (err2); - err = err_combine(err, err2); + /* Consume size, name, and `=` */ + *unconsumed += p - attr_start; + tar_flush_unconsumed(a, unconsumed); + + /* pax_attribute will consume value_length - 1 */ + r = pax_attribute(a, tar, entry, attr_name.s, archive_strlen(&attr_name), value_length - 1, unconsumed); + ext_size -= value_length - 1; - /* Skip to next line */ - attr += line_length; - attr_length -= line_length; + // Release the allocated attr_name (either here or before every return in this function) + archive_string_free(&attr_name); + + if (r < ARCHIVE_WARN) { + *unconsumed += ext_size + ext_padding; + return (r); + } + err = err_combine(err, r); + + /* Consume the `\n` that follows the pax attribute value. */ + tar_flush_unconsumed(a, unconsumed); + p = __archive_read_ahead(a, 1, &did_read); + if (p == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated tar archive" + " detected while completing pax attribute"); + return (ARCHIVE_FATAL); + } + if (p[0] != '\n') { + archive_set_error(&a->archive, EINVAL, + "Malformed pax attributes"); + *unconsumed += ext_size + ext_padding; + return (ARCHIVE_WARN); + } + ext_size -= 1; + *unconsumed += 1; + tar_flush_unconsumed(a, unconsumed); } + archive_string_free(&attr_name); + *unconsumed += ext_size + ext_padding; /* - * PAX format uses UTF-8 as default charset for its metadata - * unless hdrcharset=BINARY is present in its header. - * We apply the charset specified by the hdrcharset option only - * when the hdrcharset attribute(in PAX header) is BINARY because - * we respect the charset described in PAX header and BINARY also - * means that metadata(filename,uname and gname) character-set - * is unknown. + * Some PAX values -- pathname, linkpath, uname, gname -- + * can't be copied into the entry until we know the character + * set to use: */ - if (tar->pax_hdrcharset_binary) + if (!tar->pax_hdrcharset_utf8) + /* PAX specified "BINARY", so use the default charset */ sconv = tar->opt_sconv; else { + /* PAX default UTF-8 */ sconv = archive_string_conversion_from_charset( &(a->archive), "UTF-8", 1); if (sconv == NULL) @@ -1701,83 +2023,85 @@ pax_header(struct archive_read *a, struct tar *tar, SCONV_SET_OPT_UTF8_LIBARCHIVE2X); } + /* Pathname */ + pas = NULL; + if (archive_strlen(&(tar->entry_pathname_override)) > 0) { + /* Prefer GNU.sparse.name attribute if present */ + /* GNU sparse files store a fake name under the standard + * "pathname" key. */ + pas = &(tar->entry_pathname_override); + } else if (archive_strlen(&(tar->entry_pathname)) > 0) { + /* Use standard "pathname" PAX extension */ + pas = &(tar->entry_pathname); + } + if (pas != NULL) { + if (archive_entry_copy_pathname_l(entry, pas->s, + archive_strlen(pas), sconv) != 0) { + err = set_conversion_failed_error(a, sconv, "Pathname"); + if (err == ARCHIVE_FATAL) + return (err); + /* Use raw name without conversion */ + archive_entry_copy_pathname(entry, pas->s); + } + } + /* Uname */ + if (archive_strlen(&(tar->entry_uname)) > 0) { + if (archive_entry_copy_uname_l(entry, tar->entry_uname.s, + archive_strlen(&(tar->entry_uname)), sconv) != 0) { + err = set_conversion_failed_error(a, sconv, "Uname"); + if (err == ARCHIVE_FATAL) + return (err); + /* Use raw name without conversion */ + archive_entry_copy_uname(entry, tar->entry_uname.s); + } + } + /* Gname */ if (archive_strlen(&(tar->entry_gname)) > 0) { if (archive_entry_copy_gname_l(entry, tar->entry_gname.s, archive_strlen(&(tar->entry_gname)), sconv) != 0) { err = set_conversion_failed_error(a, sconv, "Gname"); if (err == ARCHIVE_FATAL) return (err); - /* Use a converted an original name. */ + /* Use raw name without conversion */ archive_entry_copy_gname(entry, tar->entry_gname.s); } } + /* Linkpath */ if (archive_strlen(&(tar->entry_linkpath)) > 0) { if (archive_entry_copy_link_l(entry, tar->entry_linkpath.s, archive_strlen(&(tar->entry_linkpath)), sconv) != 0) { - err = set_conversion_failed_error(a, sconv, "Linkname"); + err = set_conversion_failed_error(a, sconv, "Linkpath"); if (err == ARCHIVE_FATAL) return (err); - /* Use a converted an original name. */ + /* Use raw name without conversion */ archive_entry_copy_link(entry, tar->entry_linkpath.s); } } - /* - * Some extensions (such as the GNU sparse file extensions) - * deliberately store a synthetic name under the regular 'path' - * attribute and the real file name under a different attribute. - * Since we're supposed to not care about the order, we - * have no choice but to store all of the various filenames - * we find and figure it all out afterwards. This is the - * figuring out part. - */ - as = NULL; - if (archive_strlen(&(tar->entry_pathname_override)) > 0) - as = &(tar->entry_pathname_override); - else if (archive_strlen(&(tar->entry_pathname)) > 0) - as = &(tar->entry_pathname); - if (as != NULL) { - if (archive_entry_copy_pathname_l(entry, as->s, - archive_strlen(as), sconv) != 0) { - err = set_conversion_failed_error(a, sconv, "Pathname"); - if (err == ARCHIVE_FATAL) - return (err); - /* Use a converted an original name. */ - archive_entry_copy_pathname(entry, as->s); - } - } - if (archive_strlen(&(tar->entry_uname)) > 0) { - if (archive_entry_copy_uname_l(entry, tar->entry_uname.s, - archive_strlen(&(tar->entry_uname)), sconv) != 0) { - err = set_conversion_failed_error(a, sconv, "Uname"); - if (err == ARCHIVE_FATAL) - return (err); - /* Use a converted an original name. */ - archive_entry_copy_uname(entry, tar->entry_uname.s); - } - } + + /* Extension may have given us a corrected `entry_bytes_remaining` for + * the main entry; update the padding appropriately. */ + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); return (err); } static int -pax_attribute_xattr(struct archive_entry *entry, - const char *name, const char *value) +pax_attribute_LIBARCHIVE_xattr(struct archive_entry *entry, + const char *name, size_t name_length, const char *value, size_t value_length) { char *name_decoded; void *value_decoded; size_t value_len; - if (strlen(name) < 18 || (memcmp(name, "LIBARCHIVE.xattr.", 17)) != 0) + if (name_length < 1) return 3; - name += 17; - /* URL-decode name */ - name_decoded = url_decode(name); + name_decoded = url_decode(name, name_length); if (name_decoded == NULL) return 2; /* Base-64 decode value */ - value_decoded = base64_decode(value, strlen(value), &value_len); + value_decoded = base64_decode(value, value_length, &value_len); if (value_decoded == NULL) { free(name_decoded); return 1; @@ -1792,21 +2116,26 @@ pax_attribute_xattr(struct archive_entry *entry, } static int -pax_attribute_schily_xattr(struct archive_entry *entry, - const char *name, const char *value, size_t value_length) +pax_attribute_SCHILY_xattr(struct archive_entry *entry, + const char *name, size_t name_length, const char *value, size_t value_length) { - if (strlen(name) < 14 || (memcmp(name, "SCHILY.xattr.", 13)) != 0) + if (name_length < 1 || name_length > 128) { return 1; + } - name += 13; - - archive_entry_xattr_add_entry(entry, name, value, value_length); + char * null_terminated_name = malloc(name_length + 1); + if (null_terminated_name != NULL) { + memcpy(null_terminated_name, name, name_length); + null_terminated_name[name_length] = '\0'; + archive_entry_xattr_add_entry(entry, null_terminated_name, value, value_length); + free(null_terminated_name); + } return 0; } static int -pax_attribute_rht_security_selinux(struct archive_entry *entry, +pax_attribute_RHT_security_selinux(struct archive_entry *entry, const char *value, size_t value_length) { archive_entry_xattr_add_entry(entry, "security.selinux", @@ -1816,10 +2145,11 @@ pax_attribute_rht_security_selinux(struct archive_entry *entry, } static int -pax_attribute_acl(struct archive_read *a, struct tar *tar, - struct archive_entry *entry, const char *value, int type) +pax_attribute_SCHILY_acl(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, size_t value_length, int type) { int r; + const char *p; const char* errstr; switch (type) { @@ -1846,8 +2176,28 @@ pax_attribute_acl(struct archive_read *a, struct tar *tar, return (ARCHIVE_FATAL); } - r = archive_acl_from_text_l(archive_entry_acl(entry), value, type, - tar->sconv_acl); + if (value_length > acl_limit) { + __archive_read_consume(a, value_length); + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unreasonably large ACL: %d > %d", + (int)value_length, (int)acl_limit); + return (ARCHIVE_WARN); + } + + p = __archive_read_ahead(a, value_length, NULL); + if (p == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive " + "detected while reading ACL data"); + return (ARCHIVE_FATAL); + } + + r = archive_acl_from_text_nl(archive_entry_acl(entry), p, value_length, + type, tar->sconv_acl); + __archive_read_consume(a, value_length); + /* Workaround: Force perm_is_set() to be correct */ + /* If this bit were stored in the ACL, this wouldn't be needed */ + archive_entry_set_perm(entry, archive_entry_perm(entry)); if (r != ARCHIVE_OK) { if (r == ARCHIVE_FATAL) { archive_set_error(&a->archive, ENOMEM, @@ -1861,240 +2211,561 @@ pax_attribute_acl(struct archive_read *a, struct tar *tar, return (r); } +static int +pax_attribute_read_time(struct archive_read *a, size_t value_length, int64_t *ps, long *pn, size_t *unconsumed) { + struct archive_string as; + int r; + + if (value_length > 128) { + __archive_read_consume(a, value_length); + *ps = 0; + *pn = 0; + return (ARCHIVE_FATAL); + } + + archive_string_init(&as); + r = read_bytes_to_string(a, &as, value_length, unconsumed); + if (r < ARCHIVE_OK) { + archive_string_free(&as); + return (r); + } + + pax_time(as.s, archive_strlen(&as), ps, pn); + archive_string_free(&as); + if (*ps < 0 || *ps == INT64_MAX) { + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +pax_attribute_read_number(struct archive_read *a, size_t value_length, int64_t *result) { + struct archive_string as; + size_t unconsumed = 0; + int r; + + if (value_length > 64) { + __archive_read_consume(a, value_length); + *result = 0; + return (ARCHIVE_FATAL); + } + + archive_string_init(&as); + r = read_bytes_to_string(a, &as, value_length, &unconsumed); + tar_flush_unconsumed(a, &unconsumed); + if (r < ARCHIVE_OK) { + archive_string_free(&as); + return (r); + } + + *result = tar_atol10(as.s, archive_strlen(&as)); + archive_string_free(&as); + if (*result < 0 || *result == INT64_MAX) { + *result = INT64_MAX; + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + /* - * Parse a single key=value attribute. key/value pointers are - * assumed to point into reasonably long-lived storage. + * Parse a single key=value attribute. * - * Note that POSIX reserves all-lowercase keywords. Vendor-specific - * extensions should always have keywords of the form "VENDOR.attribute" - * In particular, it's quite feasible to support many different - * vendor extensions here. I'm using "LIBARCHIVE" for extensions - * unique to this library. + * POSIX reserves all-lowercase keywords. Vendor-specific extensions + * should always have keywords of the form "VENDOR.attribute" In + * particular, it's quite feasible to support many different vendor + * extensions here. I'm using "LIBARCHIVE" for extensions unique to + * this library. * - * Investigate other vendor-specific extensions and see if + * TODO: Investigate other vendor-specific extensions and see if * any of them look useful. */ static int -pax_attribute(struct archive_read *a, struct tar *tar, - struct archive_entry *entry, const char *key, const char *value, size_t value_length) +pax_attribute(struct archive_read *a, struct tar *tar, struct archive_entry *entry, + const char *key, size_t key_length, size_t value_length, size_t *unconsumed) { - int64_t s; + int64_t t; long n; - int err = ARCHIVE_OK, r; + const char *p; + ssize_t bytes_read; + int err = ARCHIVE_OK; - if (value == NULL) - value = ""; /* Disable compiler warning; do not pass - * NULL pointer to strlen(). */ switch (key[0]) { case 'G': - /* Reject GNU.sparse.* headers on non-regular files. */ - if (strncmp(key, "GNU.sparse", 10) == 0 && - !tar->sparse_allowed) { - archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "Non-regular file cannot be sparse"); - return (ARCHIVE_FATAL); - } - - /* GNU "0.0" sparse pax format. */ - if (strcmp(key, "GNU.sparse.numblocks") == 0) { - tar->sparse_offset = -1; - tar->sparse_numbytes = -1; - tar->sparse_gnu_major = 0; - tar->sparse_gnu_minor = 0; - } - if (strcmp(key, "GNU.sparse.offset") == 0) { - tar->sparse_offset = tar_atol10(value, strlen(value)); - if (tar->sparse_numbytes != -1) { - if (gnu_add_sparse_entry(a, tar, - tar->sparse_offset, tar->sparse_numbytes) - != ARCHIVE_OK) - return (ARCHIVE_FATAL); - tar->sparse_offset = -1; - tar->sparse_numbytes = -1; + /* GNU.* extensions */ + if (key_length > 4 && memcmp(key, "GNU.", 4) == 0) { + key += 4; + key_length -= 4; + + /* GNU.sparse marks the existence of GNU sparse information */ + if (key_length == 6 && memcmp(key, "sparse", 6) == 0) { + tar->sparse_gnu_attributes_seen = 1; } - } - if (strcmp(key, "GNU.sparse.numbytes") == 0) { - tar->sparse_numbytes = tar_atol10(value, strlen(value)); - if (tar->sparse_offset != -1) { - if (gnu_add_sparse_entry(a, tar, - tar->sparse_offset, tar->sparse_numbytes) - != ARCHIVE_OK) - return (ARCHIVE_FATAL); - tar->sparse_offset = -1; - tar->sparse_numbytes = -1; - } - } - if (strcmp(key, "GNU.sparse.size") == 0) { - tar->realsize = tar_atol10(value, strlen(value)); - archive_entry_set_size(entry, tar->realsize); - tar->realsize_override = 1; - } - /* GNU "0.1" sparse pax format. */ - if (strcmp(key, "GNU.sparse.map") == 0) { - tar->sparse_gnu_major = 0; - tar->sparse_gnu_minor = 1; - if (gnu_sparse_01_parse(a, tar, value) != ARCHIVE_OK) - return (ARCHIVE_WARN); - } - - /* GNU "1.0" sparse pax format */ - if (strcmp(key, "GNU.sparse.major") == 0) { - tar->sparse_gnu_major = (int)tar_atol10(value, strlen(value)); - tar->sparse_gnu_pending = 1; - } - if (strcmp(key, "GNU.sparse.minor") == 0) { - tar->sparse_gnu_minor = (int)tar_atol10(value, strlen(value)); - tar->sparse_gnu_pending = 1; - } - if (strcmp(key, "GNU.sparse.name") == 0) { - /* - * The real filename; when storing sparse - * files, GNU tar puts a synthesized name into - * the regular 'path' attribute in an attempt - * to limit confusion. ;-) - */ - archive_strcpy(&(tar->entry_pathname_override), value); - } - if (strcmp(key, "GNU.sparse.realsize") == 0) { - tar->realsize = tar_atol10(value, strlen(value)); - archive_entry_set_size(entry, tar->realsize); - tar->realsize_override = 1; + /* GNU.sparse.* extensions */ + else if (key_length > 7 && memcmp(key, "sparse.", 7) == 0) { + tar->sparse_gnu_attributes_seen = 1; + key += 7; + key_length -= 7; + + /* GNU "0.0" sparse pax format. */ + if (key_length == 9 && memcmp(key, "numblocks", 9) == 0) { + /* GNU.sparse.numblocks */ + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + tar->sparse_gnu_major = 0; + tar->sparse_gnu_minor = 0; + } + else if (key_length == 6 && memcmp(key, "offset", 6) == 0) { + /* GNU.sparse.offset */ + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + tar->sparse_offset = t; + if (tar->sparse_numbytes != -1) { + if (gnu_add_sparse_entry(a, tar, + tar->sparse_offset, tar->sparse_numbytes) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + } + } + return (err); + } + else if (key_length == 8 && memcmp(key, "numbytes", 8) == 0) { + /* GNU.sparse.numbytes */ + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + tar->sparse_numbytes = t; + if (tar->sparse_offset != -1) { + if (gnu_add_sparse_entry(a, tar, + tar->sparse_offset, tar->sparse_numbytes) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + } + } + return (err); + } + else if (key_length == 4 && memcmp(key, "size", 4) == 0) { + /* GNU.sparse.size */ + /* This is either the size of stored entry OR the size of data on disk, + * depending on which GNU sparse format version is in use. + * Since pax attributes can be in any order, we may not actually + * know at this point how to interpret this. */ + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + tar->GNU_sparse_size = t; + tar->size_fields |= TAR_SIZE_GNU_SPARSE_SIZE; + } + return (err); + } + + /* GNU "0.1" sparse pax format. */ + else if (key_length == 3 && memcmp(key, "map", 3) == 0) { + /* GNU.sparse.map */ + tar->sparse_gnu_major = 0; + tar->sparse_gnu_minor = 1; + if (value_length > sparse_map_limit) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unreasonably large sparse map: %d > %d", + (int)value_length, (int)sparse_map_limit); + err = ARCHIVE_FAILED; + } else { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading GNU sparse data"); + return (ARCHIVE_FATAL); + } + if (gnu_sparse_01_parse(a, tar, p, value_length) != ARCHIVE_OK) { + err = ARCHIVE_WARN; + } + } + __archive_read_consume(a, value_length); + return (err); + } + + /* GNU "1.0" sparse pax format */ + else if (key_length == 5 && memcmp(key, "major", 5) == 0) { + /* GNU.sparse.major */ + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK + && t >= 0 + && t <= 10) { + tar->sparse_gnu_major = (int)t; + } + return (err); + } + else if (key_length == 5 && memcmp(key, "minor", 5) == 0) { + /* GNU.sparse.minor */ + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK + && t >= 0 + && t <= 10) { + tar->sparse_gnu_minor = (int)t; + } + return (err); + } + else if (key_length == 4 && memcmp(key, "name", 4) == 0) { + /* GNU.sparse.name */ + /* + * The real filename; when storing sparse + * files, GNU tar puts a synthesized name into + * the regular 'path' attribute in an attempt + * to limit confusion. ;-) + */ + if (value_length > pathname_limit) { + *unconsumed += value_length; + err = ARCHIVE_WARN; + } else { + err = read_bytes_to_string(a, &(tar->entry_pathname_override), + value_length, unconsumed); + } + return (err); + } + else if (key_length == 8 && memcmp(key, "realsize", 8) == 0) { + /* GNU.sparse.realsize = size of file on disk */ + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + tar->GNU_sparse_realsize = t; + tar->size_fields |= TAR_SIZE_GNU_SPARSE_REALSIZE; + } + return (err); + } + } } break; case 'L': - /* Our extensions */ -/* TODO: Handle arbitrary extended attributes... */ -/* - if (strcmp(key, "LIBARCHIVE.xxxxxxx") == 0) - archive_entry_set_xxxxxx(entry, value); -*/ - if (strcmp(key, "LIBARCHIVE.creationtime") == 0) { - pax_time(value, &s, &n); - archive_entry_set_birthtime(entry, s, n); - } - if (strcmp(key, "LIBARCHIVE.symlinktype") == 0) { - if (strcmp(value, "file") == 0) { - archive_entry_set_symlink_type(entry, - AE_SYMLINK_TYPE_FILE); - } else if (strcmp(value, "dir") == 0) { - archive_entry_set_symlink_type(entry, - AE_SYMLINK_TYPE_DIRECTORY); + /* LIBARCHIVE extensions */ + if (key_length > 11 && memcmp(key, "LIBARCHIVE.", 11) == 0) { + key_length -= 11; + key += 11; + + /* TODO: Handle arbitrary extended attributes... */ + /* + if (strcmp(key, "LIBARCHIVE.xxxxxxx") == 0) + archive_entry_set_xxxxxx(entry, value); + */ + if (key_length == 12 && memcmp(key, "creationtime", 12) == 0) { + /* LIBARCHIVE.creationtime */ + if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) { + archive_entry_set_birthtime(entry, t, n); + } + return (err); + } + else if (key_length == 11 && memcmp(key, "symlinktype", 11) == 0) { + /* LIBARCHIVE.symlinktype */ + if (value_length < 16) { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive " + "detected while reading `symlinktype` attribute"); + return (ARCHIVE_FATAL); + } + if (value_length == 4 && memcmp(p, "file", 4) == 0) { + archive_entry_set_symlink_type(entry, + AE_SYMLINK_TYPE_FILE); + } else if (value_length == 3 && memcmp(p, "dir", 3) == 0) { + archive_entry_set_symlink_type(entry, + AE_SYMLINK_TYPE_DIRECTORY); + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unrecognized symlink type"); + err = ARCHIVE_WARN; + } + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "symlink type is very long" + "(longest recognized value is 4 bytes, this is %d)", + (int)value_length); + err = ARCHIVE_WARN; + } + __archive_read_consume(a, value_length); + return (err); + } + else if (key_length > 6 && memcmp(key, "xattr.", 6) == 0) { + key_length -= 6; + key += 6; + if (value_length > xattr_limit) { + err = ARCHIVE_WARN; + } else { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading xattr information"); + return (ARCHIVE_FATAL); + } + if (pax_attribute_LIBARCHIVE_xattr(entry, key, key_length, p, value_length)) { + /* TODO: Unable to parse xattr */ + err = ARCHIVE_WARN; + } + } + __archive_read_consume(a, value_length); + return (err); } } - if (memcmp(key, "LIBARCHIVE.xattr.", 17) == 0) - pax_attribute_xattr(entry, key, value); break; case 'R': /* GNU tar uses RHT.security header to store SELinux xattrs * SCHILY.xattr.security.selinux == RHT.security.selinux */ - if (strcmp(key, "RHT.security.selinux") == 0) { - pax_attribute_rht_security_selinux(entry, value, - value_length); + if (key_length == 20 && memcmp(key, "RHT.security.selinux", 20) == 0) { + if (value_length > xattr_limit) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring unreasonably large security.selinux attribute:" + " %d > %d", + (int)value_length, (int)xattr_limit); + /* TODO: Should this be FAILED instead? */ + err = ARCHIVE_WARN; + } else { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading selinux data"); + return (ARCHIVE_FATAL); + } + if (pax_attribute_RHT_security_selinux(entry, p, value_length)) { + /* TODO: Unable to parse xattr */ + err = ARCHIVE_WARN; + } } + __archive_read_consume(a, value_length); + return (err); + } break; case 'S': - /* We support some keys used by the "star" archiver */ - if (strcmp(key, "SCHILY.acl.access") == 0) { - r = pax_attribute_acl(a, tar, entry, value, - ARCHIVE_ENTRY_ACL_TYPE_ACCESS); - if (r == ARCHIVE_FATAL) - return (r); - } else if (strcmp(key, "SCHILY.acl.default") == 0) { - r = pax_attribute_acl(a, tar, entry, value, - ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); - if (r == ARCHIVE_FATAL) - return (r); - } else if (strcmp(key, "SCHILY.acl.ace") == 0) { - r = pax_attribute_acl(a, tar, entry, value, - ARCHIVE_ENTRY_ACL_TYPE_NFS4); - if (r == ARCHIVE_FATAL) - return (r); - } else if (strcmp(key, "SCHILY.devmajor") == 0) { - archive_entry_set_rdevmajor(entry, - (dev_t)tar_atol10(value, strlen(value))); - } else if (strcmp(key, "SCHILY.devminor") == 0) { - archive_entry_set_rdevminor(entry, - (dev_t)tar_atol10(value, strlen(value))); - } else if (strcmp(key, "SCHILY.fflags") == 0) { - archive_entry_copy_fflags_text(entry, value); - } else if (strcmp(key, "SCHILY.dev") == 0) { - archive_entry_set_dev(entry, - (dev_t)tar_atol10(value, strlen(value))); - } else if (strcmp(key, "SCHILY.ino") == 0) { - archive_entry_set_ino(entry, - tar_atol10(value, strlen(value))); - } else if (strcmp(key, "SCHILY.nlink") == 0) { - archive_entry_set_nlink(entry, (unsigned) - tar_atol10(value, strlen(value))); - } else if (strcmp(key, "SCHILY.realsize") == 0) { - tar->realsize = tar_atol10(value, strlen(value)); - tar->realsize_override = 1; - archive_entry_set_size(entry, tar->realsize); - } else if (strncmp(key, "SCHILY.xattr.", 13) == 0) { - pax_attribute_schily_xattr(entry, key, value, - value_length); - } else if (strcmp(key, "SUN.holesdata") == 0) { - /* A Solaris extension for sparse. */ - r = solaris_sparse_parse(a, tar, entry, value); - if (r < err) { - if (r == ARCHIVE_FATAL) - return (r); - err = r; - archive_set_error(&a->archive, - ARCHIVE_ERRNO_MISC, - "Parse error: SUN.holesdata"); + /* SCHILY.* extensions used by "star" archiver */ + if (key_length > 7 && memcmp(key, "SCHILY.", 7) == 0) { + key_length -= 7; + key += 7; + + if (key_length == 10 && memcmp(key, "acl.access", 10) == 0) { + err = pax_attribute_SCHILY_acl(a, tar, entry, value_length, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + // TODO: Mark mode as set + return (err); + } + else if (key_length == 11 && memcmp(key, "acl.default", 11) == 0) { + err = pax_attribute_SCHILY_acl(a, tar, entry, value_length, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); + return (err); + } + else if (key_length == 7 && memcmp(key, "acl.ace", 7) == 0) { + err = pax_attribute_SCHILY_acl(a, tar, entry, value_length, + ARCHIVE_ENTRY_ACL_TYPE_NFS4); + // TODO: Mark mode as set + return (err); + } + else if (key_length == 8 && memcmp(key, "devmajor", 8) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + archive_entry_set_rdevmajor(entry, (dev_t)t); + } + return (err); + } + else if (key_length == 8 && memcmp(key, "devminor", 8) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + archive_entry_set_rdevminor(entry, (dev_t)t); + } + return (err); + } + else if (key_length == 6 && memcmp(key, "fflags", 6) == 0) { + if (value_length < fflags_limit) { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + /* Truncated archive */ + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading SCHILY.fflags"); + return (ARCHIVE_FATAL); + } + archive_entry_copy_fflags_text_len(entry, p, value_length); + err = ARCHIVE_OK; + } else { + /* Overlong fflags field */ + err = ARCHIVE_WARN; + } + __archive_read_consume(a, value_length); + return (err); + } + else if (key_length == 3 && memcmp(key, "dev", 3) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + archive_entry_set_dev(entry, (dev_t)t); + } + return (err); + } + else if (key_length == 3 && memcmp(key, "ino", 3) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + archive_entry_set_ino(entry, t); + } + return (err); + } + else if (key_length == 5 && memcmp(key, "nlink", 5) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + archive_entry_set_nlink(entry, (unsigned int)t); + } + return (err); + } + else if (key_length == 8 && memcmp(key, "realsize", 8) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + tar->SCHILY_sparse_realsize = t; + tar->size_fields |= TAR_SIZE_SCHILY_SPARSE_REALSIZE; + } + return (err); + } + /* TODO: Is there a SCHILY.sparse.size similar to GNU.sparse.size ? */ + else if (key_length > 6 && memcmp(key, "xattr.", 6) == 0) { + key_length -= 6; + key += 6; + if (value_length < xattr_limit) { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading SCHILY.xattr"); + return (ARCHIVE_FATAL); + } + if (pax_attribute_SCHILY_xattr(entry, key, key_length, p, value_length)) { + /* TODO: Unable to parse xattr */ + err = ARCHIVE_WARN; + } + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unreasonably large xattr: %d > %d", + (int)value_length, (int)xattr_limit); + err = ARCHIVE_WARN; + } + __archive_read_consume(a, value_length); + return (err); + } + } + /* SUN.* extensions from Solaris tar */ + if (key_length > 4 && memcmp(key, "SUN.", 4) == 0) { + key_length -= 4; + key += 4; + + if (key_length == 9 && memcmp(key, "holesdata", 9) == 0) { + /* SUN.holesdata */ + if (value_length < sparse_map_limit) { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + archive_set_error(&a->archive, EINVAL, + "Truncated archive" + " detected while reading SUN.holesdata"); + return (ARCHIVE_FATAL); + } + err = pax_attribute_SUN_holesdata(a, tar, entry, p, value_length); + if (err < ARCHIVE_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Parse error: SUN.holesdata"); + } + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unreasonably large sparse map: %d > %d", + (int)value_length, (int)sparse_map_limit); + err = ARCHIVE_FAILED; + } + __archive_read_consume(a, value_length); + return (err); } } break; case 'a': - if (strcmp(key, "atime") == 0) { - pax_time(value, &s, &n); - archive_entry_set_atime(entry, s, n); + if (key_length == 5 && memcmp(key, "atime", 5) == 0) { + if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) { + archive_entry_set_atime(entry, t, n); + } + return (err); } break; case 'c': - if (strcmp(key, "ctime") == 0) { - pax_time(value, &s, &n); - archive_entry_set_ctime(entry, s, n); - } else if (strcmp(key, "charset") == 0) { + if (key_length == 5 && memcmp(key, "ctime", 5) == 0) { + if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) { + archive_entry_set_ctime(entry, t, n); + } + return (err); + } else if (key_length == 7 && memcmp(key, "charset", 7) == 0) { /* TODO: Publish charset information in entry. */ - } else if (strcmp(key, "comment") == 0) { + } else if (key_length == 7 && memcmp(key, "comment", 7) == 0) { /* TODO: Publish comment in entry. */ } break; case 'g': - if (strcmp(key, "gid") == 0) { - archive_entry_set_gid(entry, - tar_atol10(value, strlen(value))); - } else if (strcmp(key, "gname") == 0) { - archive_strcpy(&(tar->entry_gname), value); + if (key_length == 3 && memcmp(key, "gid", 3) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + archive_entry_set_gid(entry, t); + } + return (err); + } else if (key_length == 5 && memcmp(key, "gname", 5) == 0) { + if (value_length > guname_limit) { + *unconsumed += value_length; + err = ARCHIVE_WARN; + } else { + err = read_bytes_to_string(a, &(tar->entry_gname), value_length, unconsumed); + } + return (err); } break; case 'h': - if (strcmp(key, "hdrcharset") == 0) { - if (strcmp(value, "BINARY") == 0) - /* Binary mode. */ - tar->pax_hdrcharset_binary = 1; - else if (strcmp(value, "ISO-IR 10646 2000 UTF-8") == 0) - tar->pax_hdrcharset_binary = 0; + if (key_length == 10 && memcmp(key, "hdrcharset", 10) == 0) { + if (value_length < 64) { + p = __archive_read_ahead(a, value_length, &bytes_read); + if (p == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive " + "detected while reading hdrcharset attribute"); + return (ARCHIVE_FATAL); + } + if (value_length == 6 + && memcmp(p, "BINARY", 6) == 0) { + /* Binary mode. */ + tar->pax_hdrcharset_utf8 = 0; + err = ARCHIVE_OK; + } else if (value_length == 23 + && memcmp(p, "ISO-IR 10646 2000 UTF-8", 23) == 0) { + tar->pax_hdrcharset_utf8 = 1; + err = ARCHIVE_OK; + } else { + /* TODO: Unrecognized character set */ + err = ARCHIVE_WARN; + } + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "hdrcharset attribute is unreasonably large (%d bytes)", + (int)value_length); + err = ARCHIVE_WARN; + } + __archive_read_consume(a, value_length); + return (err); } break; case 'l': /* pax interchange doesn't distinguish hardlink vs. symlink. */ - if (strcmp(key, "linkpath") == 0) { - archive_strcpy(&(tar->entry_linkpath), value); + if (key_length == 8 && memcmp(key, "linkpath", 8) == 0) { + if (value_length > pathname_limit) { + *unconsumed += value_length; + err = ARCHIVE_WARN; + } else { + err = read_bytes_to_string(a, &tar->entry_linkpath, value_length, unconsumed); + } + return (err); } break; case 'm': - if (strcmp(key, "mtime") == 0) { - pax_time(value, &s, &n); - archive_entry_set_mtime(entry, s, n); + if (key_length == 5 && memcmp(key, "mtime", 5) == 0) { + if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) { + archive_entry_set_mtime(entry, t, n); + } + return (err); } break; case 'p': - if (strcmp(key, "path") == 0) { - archive_strcpy(&(tar->entry_pathname), value); + if (key_length == 4 && memcmp(key, "path", 4) == 0) { + if (value_length > pathname_limit) { + *unconsumed += value_length; + err = ARCHIVE_WARN; + } else { + err = read_bytes_to_string(a, &(tar->entry_pathname), value_length, unconsumed); + } + return (err); } break; case 'r': @@ -2103,48 +2774,43 @@ pax_attribute(struct archive_read *a, struct tar *tar, case 's': /* POSIX has reserved 'security.*' */ /* Someday: if (strcmp(key, "security.acl") == 0) { ... } */ - if (strcmp(key, "size") == 0) { + if (key_length == 4 && memcmp(key, "size", 4) == 0) { /* "size" is the size of the data in the entry. */ - tar->entry_bytes_remaining - = tar_atol10(value, strlen(value)); - if (tar->entry_bytes_remaining < 0) { - tar->entry_bytes_remaining = 0; - archive_set_error(&a->archive, - ARCHIVE_ERRNO_MISC, - "Tar size attribute is negative"); - return (ARCHIVE_FATAL); + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + tar->pax_size = t; + tar->size_fields |= TAR_SIZE_PAX_SIZE; } - if (tar->entry_bytes_remaining == INT64_MAX) { - /* Note: tar_atol returns INT64_MAX on overflow */ + else if (t == INT64_MAX) { + /* Note: pax_attr_read_number returns INT64_MAX on overflow or < 0 */ tar->entry_bytes_remaining = 0; archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Tar size attribute overflow"); return (ARCHIVE_FATAL); } - /* - * The "size" pax header keyword always overrides the - * "size" field in the tar header. - * GNU.sparse.realsize, GNU.sparse.size and - * SCHILY.realsize override this value. - */ - if (!tar->realsize_override) { - archive_entry_set_size(entry, - tar->entry_bytes_remaining); - tar->realsize - = tar->entry_bytes_remaining; - } + return (err); } break; case 'u': - if (strcmp(key, "uid") == 0) { - archive_entry_set_uid(entry, - tar_atol10(value, strlen(value))); - } else if (strcmp(key, "uname") == 0) { - archive_strcpy(&(tar->entry_uname), value); + if (key_length == 3 && memcmp(key, "uid", 3) == 0) { + if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) { + archive_entry_set_uid(entry, t); + } + return (err); + } else if (key_length == 5 && memcmp(key, "uname", 5) == 0) { + if (value_length > guname_limit) { + *unconsumed += value_length; + err = ARCHIVE_WARN; + } else { + err = read_bytes_to_string(a, &(tar->entry_uname), value_length, unconsumed); + } + return (err); } break; } + + /* Unrecognized key, just skip the entire value. */ + __archive_read_consume(a, value_length); return (err); } @@ -2154,7 +2820,7 @@ pax_attribute(struct archive_read *a, struct tar *tar, * parse a decimal time value, which may include a fractional portion */ static void -pax_time(const char *p, int64_t *ps, long *pn) +pax_time(const char *p, size_t length, int64_t *ps, long *pn) { char digit; int64_t s; @@ -2165,13 +2831,18 @@ pax_time(const char *p, int64_t *ps, long *pn) limit = INT64_MAX / 10; last_digit_limit = INT64_MAX % 10; + if (length <= 0) { + *ps = 0; + return; + } s = 0; sign = 1; if (*p == '-') { sign = -1; p++; + length--; } - while (*p >= '0' && *p <= '9') { + while (length > 0 && *p >= '0' && *p <= '9') { digit = *p - '0'; if (s > limit || (s == limit && digit > last_digit_limit)) { @@ -2180,6 +2851,7 @@ pax_time(const char *p, int64_t *ps, long *pn) } s = (s * 10) + digit; ++p; + --length; } *ps = s * sign; @@ -2187,13 +2859,14 @@ pax_time(const char *p, int64_t *ps, long *pn) /* Calculate nanoseconds. */ *pn = 0; - if (*p != '.') + if (length <= 0 || *p != '.') return; l = 100000000UL; do { ++p; - if (*p >= '0' && *p <= '9') + --length; + if (length > 0 && *p >= '0' && *p <= '9') *pn += (*p - '0') * l; else break; @@ -2217,62 +2890,71 @@ header_gnutar(struct archive_read *a, struct tar *tar, * filename is stored as in old-style archives. */ - /* Grab fields common to all tar variants. */ - err = header_common(a, tar, entry, h); - if (err == ARCHIVE_FATAL) - return (err); - /* Copy filename over (to ensure null termination). */ header = (const struct archive_entry_header_gnutar *)h; - if (archive_entry_copy_pathname_l(entry, - header->name, sizeof(header->name), tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, "Pathname"); - if (err == ARCHIVE_FATAL) - return (err); + const char *existing_pathname = archive_entry_pathname(entry); + if (existing_pathname == NULL || existing_pathname[0] == '\0') { + if (archive_entry_copy_pathname_l(entry, + header->name, sizeof(header->name), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + if (err == ARCHIVE_FATAL) + return (err); + } } /* Fields common to ustar and GNU */ /* XXX Can the following be factored out since it's common * to ustar and gnu tar? Is it okay to move it down into * header_common, perhaps? */ - if (archive_entry_copy_uname_l(entry, - header->uname, sizeof(header->uname), tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, "Uname"); - if (err == ARCHIVE_FATAL) - return (err); + const char *existing_uname = archive_entry_uname(entry); + if (existing_uname == NULL || existing_uname[0] == '\0') { + if (archive_entry_copy_uname_l(entry, + header->uname, sizeof(header->uname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Uname"); + if (err == ARCHIVE_FATAL) + return (err); + } } - if (archive_entry_copy_gname_l(entry, - header->gname, sizeof(header->gname), tar->sconv) != 0) { - err = set_conversion_failed_error(a, tar->sconv, "Gname"); - if (err == ARCHIVE_FATAL) - return (err); + const char *existing_gname = archive_entry_gname(entry); + if (existing_gname == NULL || existing_gname[0] == '\0') { + if (archive_entry_copy_gname_l(entry, + header->gname, sizeof(header->gname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Gname"); + if (err == ARCHIVE_FATAL) + return (err); + } } /* Parse out device numbers only for char and block specials */ if (header->typeflag[0] == '3' || header->typeflag[0] == '4') { - archive_entry_set_rdevmajor(entry, (dev_t) - tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); - archive_entry_set_rdevminor(entry, (dev_t) - tar_atol(header->rdevminor, sizeof(header->rdevminor))); - } else + if (!archive_entry_rdev_is_set(entry)) { + archive_entry_set_rdevmajor(entry, (dev_t) + tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); + archive_entry_set_rdevminor(entry, (dev_t) + tar_atol(header->rdevminor, sizeof(header->rdevminor))); + } + } else { archive_entry_set_rdev(entry, 0); - - tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + } /* Grab GNU-specific fields. */ - t = tar_atol(header->atime, sizeof(header->atime)); - if (t > 0) - archive_entry_set_atime(entry, t, 0); - t = tar_atol(header->ctime, sizeof(header->ctime)); - if (t > 0) - archive_entry_set_ctime(entry, t, 0); + if (!archive_entry_atime_is_set(entry)) { + t = tar_atol(header->atime, sizeof(header->atime)); + if (t > 0) + archive_entry_set_atime(entry, t, 0); + } + if (!archive_entry_ctime_is_set(entry)) { + t = tar_atol(header->ctime, sizeof(header->ctime)); + if (t > 0) + archive_entry_set_ctime(entry, t, 0); + } if (header->realsize[0] != 0) { - tar->realsize + /* Treat as a synonym for the pax GNU.sparse.realsize attr */ + tar->GNU_sparse_realsize = tar_atol(header->realsize, sizeof(header->realsize)); - archive_entry_set_size(entry, tar->realsize); - tar->realsize_override = 1; + tar->size_fields |= TAR_SIZE_GNU_SPARSE_REALSIZE; } if (header->sparse[0].offset[0] != 0) { @@ -2285,6 +2967,13 @@ header_gnutar(struct archive_read *a, struct tar *tar, } } + /* Grab fields common to all tar variants. */ + err = header_common(a, tar, entry, h); + if (err == ARCHIVE_FATAL) + return (err); + + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + return (err); } @@ -2294,7 +2983,7 @@ gnu_add_sparse_entry(struct archive_read *a, struct tar *tar, { struct sparse_block *p; - p = (struct sparse_block *)calloc(1, sizeof(*p)); + p = calloc(1, sizeof(*p)); if (p == NULL) { archive_set_error(&a->archive, ENOMEM, "Out of memory"); return (ARCHIVE_FATAL); @@ -2359,9 +3048,7 @@ gnu_sparse_old_read(struct archive_read *a, struct tar *tar, do { tar_flush_unconsumed(a, unconsumed); data = __archive_read_ahead(a, 512, &bytes_read); - if (bytes_read < 0) - return (ARCHIVE_FATAL); - if (bytes_read < 512) { + if (data == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Truncated tar archive " "detected while reading sparse file data"); @@ -2418,19 +3105,19 @@ gnu_sparse_old_parse(struct archive_read *a, struct tar *tar, * importantly, the sparse data was lost when extracted by archivers * that didn't recognize this extension. */ - static int -gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p) +gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p, size_t length) { const char *e; int64_t offset = -1, size = -1; for (;;) { e = p; - while (*e != '\0' && *e != ',') { + while (length > 0 && *e != ',') { if (*e < '0' || *e > '9') return (ARCHIVE_WARN); e++; + length--; } if (offset < 0) { offset = tar_atol10(p, e - p); @@ -2445,9 +3132,10 @@ gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p) return (ARCHIVE_FATAL); offset = -1; } - if (*e == '\0') + if (length == 0) return (ARCHIVE_OK); p = e + 1; + length--; } } @@ -2465,8 +3153,7 @@ gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p) * it's not possible to support both variants. This code supports * the later variant at the expense of not supporting the former. * - * This variant also replaced GNU.sparse.size with GNU.sparse.realsize - * and introduced the GNU.sparse.major/GNU.sparse.minor attributes. + * This variant also introduced the GNU.sparse.major/GNU.sparse.minor attributes. */ /* @@ -2569,8 +3256,8 @@ gnu_sparse_10_read(struct archive_read *a, struct tar *tar, size_t *unconsumed) * consist of both data and hole. */ static int -solaris_sparse_parse(struct archive_read *a, struct tar *tar, - struct archive_entry *entry, const char *p) +pax_attribute_SUN_holesdata(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const char *p, size_t length) { const char *e; int64_t start, end; @@ -2579,16 +3266,21 @@ solaris_sparse_parse(struct archive_read *a, struct tar *tar, (void)entry; /* UNUSED */ end = 0; - if (*p == ' ') + if (length <= 0) + return (ARCHIVE_WARN); + if (*p == ' ') { p++; - else + length--; + } else { return (ARCHIVE_WARN); + } for (;;) { e = p; - while (*e != '\0' && *e != ' ') { + while (length > 0 && *e != ' ') { if (*e < '0' || *e > '9') return (ARCHIVE_WARN); e++; + length--; } start = end; end = tar_atol10(p, e - p); @@ -2600,9 +3292,15 @@ solaris_sparse_parse(struct archive_read *a, struct tar *tar, return (ARCHIVE_FATAL); tar->sparse_last->hole = hole; } - if (*e == '\0') - return (ARCHIVE_OK); + if (length == 0 || *e == '\n') { + if (length == 0 && *e == '\n') { + return (ARCHIVE_OK); + } else { + return (ARCHIVE_WARN); + } + } p = e + 1; + length--; hole = hole == 0; } } @@ -2769,7 +3467,7 @@ readline(struct archive_read *a, struct tar *tar, const char **start, tar_flush_unconsumed(a, unconsumed); t = __archive_read_ahead(a, 1, &bytes_read); - if (bytes_read <= 0) + if (bytes_read <= 0 || t == NULL) return (ARCHIVE_FATAL); s = t; /* Start of line? */ p = memchr(t, '\n', bytes_read); @@ -2810,7 +3508,7 @@ readline(struct archive_read *a, struct tar *tar, const char **start, } /* Read some more. */ t = __archive_read_ahead(a, 1, &bytes_read); - if (bytes_read <= 0) + if (bytes_read <= 0 || t == NULL) return (ARCHIVE_FATAL); s = t; /* Start of line? */ p = memchr(t, '\n', bytes_read); @@ -2854,7 +3552,7 @@ base64_decode(const char *s, size_t len, size_t *out_len) /* Allocate enough space to hold the entire output. */ /* Note that we may not use all of this... */ - out = (char *)malloc(len - len / 4 + 1); + out = malloc(len - len / 4 + 1); if (out == NULL) { *out_len = 0; return (NULL); @@ -2904,22 +3602,23 @@ base64_decode(const char *s, size_t len, size_t *out_len) } static char * -url_decode(const char *in) +url_decode(const char *in, size_t length) { char *out, *d; const char *s; - out = (char *)malloc(strlen(in) + 1); + out = malloc(length + 1); if (out == NULL) return (NULL); - for (s = in, d = out; *s != '\0'; ) { - if (s[0] == '%' && s[1] != '\0' && s[2] != '\0') { + for (s = in, d = out; length > 0 && *s != '\0'; ) { + if (s[0] == '%' && length > 2) { /* Try to convert % escape */ int digit1 = tohex(s[1]); int digit2 = tohex(s[2]); if (digit1 >= 0 && digit2 >= 0) { /* Looks good, consume three chars */ s += 3; + length -= 3; /* Convert output */ *d++ = ((digit1 << 4) | digit2); continue; @@ -2927,6 +3626,7 @@ url_decode(const char *in) /* Else fall through and treat '%' as normal char */ } *d++ = *s++; + --length; } *d = '\0'; return (out); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_warc.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_warc.c index 27329962d6d..fcec5bc4cbb 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_warc.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_warc.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); /** * WARC is standardised by ISO TC46/SC4/WG12 and currently available as @@ -216,6 +215,7 @@ _warc_rdhdr(struct archive_read *a, struct archive_entry *entry) const char *buf; ssize_t nrd; const char *eoh; + char *tmp; /* for the file name, saves some strndup()'ing */ warc_string_t fnam; /* warc record type, not that we really use it a lot */ @@ -322,7 +322,14 @@ _warc_rdhdr(struct archive_read *a, struct archive_entry *entry) * malloc()+free() roundtrip */ if (fnam.len + 1U > w->pool.len) { w->pool.len = ((fnam.len + 64U) / 64U) * 64U; - w->pool.str = realloc(w->pool.str, w->pool.len); + tmp = realloc(w->pool.str, w->pool.len); + if (tmp == NULL) { + archive_set_error( + &a->archive, ENOMEM, + "Out of memory"); + return (ARCHIVE_FATAL); + } + w->pool.str = tmp; } memcpy(w->pool.str, fnam.str, fnam.len); w->pool.str[fnam.len] = '\0'; @@ -530,11 +537,11 @@ strtoi_lim(const char *str, const char **ep, int llim, int ulim) static time_t time_from_tm(struct tm *t) { -#if HAVE_TIMEGM +#if HAVE__MKGMTIME + return _mkgmtime(t); +#elif HAVE_TIMEGM /* Use platform timegm() if available. */ return (timegm(t)); -#elif HAVE__MKGMTIME64 - return (_mkgmtime64(t)); #else /* Else use direct calculation using POSIX assumptions. */ /* First, fix up tm_yday based on the year/month/day. */ diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c index 330df5879a4..b41333e4ca3 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c @@ -23,7 +23,6 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -417,7 +416,7 @@ static void unknowntag_end(struct xar *, const char *); static int xml_start(struct archive_read *, const char *, struct xmlattr_list *); static void xml_end(void *, const char *); -static void xml_data(void *, const char *, int); +static void xml_data(void *, const char *, size_t); static int xml_parse_file_flags(struct xar *, const char *); static int xml_parse_file_ext2(struct xar *, const char *); #if defined(HAVE_LIBXML_XMLREADER_H) @@ -451,7 +450,7 @@ archive_read_support_format_xar(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_xar"); - xar = (struct xar *)calloc(1, sizeof(*xar)); + xar = calloc(1, sizeof(*xar)); if (xar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate xar data"); @@ -623,8 +622,8 @@ read_toc(struct archive_read *a) (size_t)xar->toc_chksum_size, NULL, 0); __archive_read_consume(a, xar->toc_chksum_size); xar->offset += xar->toc_chksum_size; - if (r != ARCHIVE_OK) #ifndef DONT_FAIL_ON_CRC_ERROR + if (r != ARCHIVE_OK) return (ARCHIVE_FATAL); #endif } @@ -1127,7 +1126,7 @@ atohex(unsigned char *b, size_t bsize, const char *p, size_t psize) x |= p[1] - '0'; else return (-1); - + *b++ = x; bsize--; p += 2; @@ -1139,11 +1138,11 @@ atohex(unsigned char *b, size_t bsize, const char *p, size_t psize) static time_t time_from_tm(struct tm *t) { -#if HAVE_TIMEGM +#if HAVE__MKGMTIME + return _mkgmtime(t); +#elif HAVE_TIMEGM /* Use platform timegm() if available. */ return (timegm(t)); -#elif HAVE__MKGMTIME64 - return (_mkgmtime64(t)); #else /* Else use direct calculation using POSIX assumptions. */ /* First, fix up tm_yday based on the year/month/day. */ @@ -1243,7 +1242,7 @@ heap_add_entry(struct archive_read *a, return (ARCHIVE_FATAL); } new_pending_files = (struct xar_file **) - malloc(new_size * sizeof(new_pending_files[0])); + calloc(new_size, sizeof(new_pending_files[0])); if (new_pending_files == NULL) { archive_set_error(&a->archive, ENOMEM, "Out of memory"); @@ -1617,9 +1616,9 @@ decompress(struct archive_read *a, const void **buff, size_t *outbytes, switch (xar->rd_encoding) { case GZIP: xar->stream.next_in = (Bytef *)(uintptr_t)b; - xar->stream.avail_in = avail_in; + xar->stream.avail_in = (uInt)avail_in; xar->stream.next_out = (unsigned char *)outbuff; - xar->stream.avail_out = avail_out; + xar->stream.avail_out = (uInt)avail_out; r = inflate(&(xar->stream), 0); switch (r) { case Z_OK: /* Decompressor made some progress.*/ @@ -1636,9 +1635,9 @@ decompress(struct archive_read *a, const void **buff, size_t *outbytes, #if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) case BZIP2: xar->bzstream.next_in = (char *)(uintptr_t)b; - xar->bzstream.avail_in = avail_in; + xar->bzstream.avail_in = (unsigned int)avail_in; xar->bzstream.next_out = (char *)outbuff; - xar->bzstream.avail_out = avail_out; + xar->bzstream.avail_out = (unsigned int)avail_out; r = BZ2_bzDecompress(&(xar->bzstream)); switch (r) { case BZ_STREAM_END: /* Found end of stream. */ @@ -1746,15 +1745,6 @@ decompression_cleanup(struct archive_read *a) #if defined(HAVE_LZMA_H) && defined(HAVE_LIBLZMA) if (xar->lzstream_valid) lzma_end(&(xar->lzstream)); -#elif defined(HAVE_LZMA_H) && defined(HAVE_LIBLZMA) - if (xar->lzstream_valid) { - if (lzmadec_end(&(xar->lzstream)) != LZMADEC_OK) { - archive_set_error(&a->archive, - ARCHIVE_ERRNO_MISC, - "Failed to clean up lzmadec decompressor"); - r = ARCHIVE_FATAL; - } - } #endif return (r); } @@ -2056,6 +2046,12 @@ xml_start(struct archive_read *a, const char *name, struct xmlattr_list *list) attr = attr->next) { if (strcmp(attr->name, "link") != 0) continue; + if (xar->file->hdnext != NULL || xar->file->link != 0 || + xar->file == xar->hdlink_orgs) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "File with multiple link attributes"); + return (ARCHIVE_FATAL); + } if (strcmp(attr->value, "original") == 0) { xar->file->hdnext = xar->hdlink_orgs; xar->hdlink_orgs = xar->file; @@ -2065,7 +2061,7 @@ xml_start(struct archive_read *a, const char *name, struct xmlattr_list *list) if (xar->file->link > 0) if (add_link(a, xar, xar->file) != ARCHIVE_OK) { return (ARCHIVE_FATAL); - }; + } } } } @@ -2669,7 +2665,7 @@ is_string(const char *known, const char *data, size_t len) } static void -xml_data(void *userData, const char *s, int len) +xml_data(void *userData, const char *s, size_t len) { struct archive_read *a; struct xar *xar; @@ -2702,6 +2698,9 @@ xml_data(void *userData, const char *s, int len) switch (xar->xmlsts) { case FILE_NAME: + if (xar->file->has & HAS_PATHNAME) + break; + if (xar->file->parent != NULL) { archive_string_concat(&(xar->file->pathname), &(xar->file->parent->pathname)); @@ -2843,7 +2842,6 @@ xml_data(void *userData, const char *s, int len) xar->file->has |= HAS_XATTR; archive_strncpy(&(xar->xattr->fstype), s, len); break; - break; case FILE_ACL_DEFAULT: case FILE_ACL_ACCESS: case FILE_ACL_APPLEEXTENDED: @@ -3185,8 +3183,11 @@ xml2_read_toc(struct archive_read *a) if (r == ARCHIVE_OK) r = xml_start(a, name, &list); xmlattr_cleanup(&list); - if (r != ARCHIVE_OK) + if (r != ARCHIVE_OK) { + xmlFreeTextReader(reader); + xmlCleanupParser(); return (r); + } if (empty) xml_end(a, name); break; @@ -3252,6 +3253,9 @@ expat_start_cb(void *userData, const XML_Char *name, const XML_Char **atts) struct xmlattr_list list; int r; + if (ud->state != ARCHIVE_OK) + return; + r = expat_xmlattr_setup(a, &list, atts); if (r == ARCHIVE_OK) r = xml_start(a, (const char *)name, &list); @@ -3272,7 +3276,7 @@ expat_data_cb(void *userData, const XML_Char *s, int len) { struct expat_userData *ud = (struct expat_userData *)userData; - xml_data(ud->archive, s, len); + xml_data(ud->archive, s, (size_t)len); } static int @@ -3308,14 +3312,16 @@ expat_read_toc(struct archive_read *a) d = NULL; r = rd_contents(a, &d, &outbytes, &used, xar->toc_remaining); - if (r != ARCHIVE_OK) + if (r != ARCHIVE_OK) { + XML_ParserFree(parser); return (r); + } xar->toc_remaining -= used; xar->offset += used; xar->toc_total += outbytes; PRINT_TOC(d, outbytes); - xr = XML_Parse(parser, d, outbytes, xar->toc_remaining == 0); + xr = XML_Parse(parser, d, (int)outbytes, xar->toc_remaining == 0); __archive_read_consume(a, used); if (xr == XML_STATUS_ERROR) { XML_ParserFree(parser); diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c index e126ae38fa7..6a8582f83eb 100644 --- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c +++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_zip.c 201102 2009-12-28 03:11:36Z kientzle $"); /* * The definitive documentation of the Zip file format is: @@ -119,7 +118,7 @@ struct trad_enc_ctx { /* Bits used in zip_flags. */ #define ZIP_ENCRYPTED (1 << 0) -#define ZIP_LENGTH_AT_END (1 << 3) +#define ZIP_LENGTH_AT_END (1 << 3) /* Also called "Streaming bit" */ #define ZIP_STRONG_ENCRYPTED (1 << 6) #define ZIP_UTF8_NAME (1 << 11) /* See "7.2 Single Password Symmetric Encryption Method" @@ -166,8 +165,8 @@ struct zip { int64_t entry_compressed_bytes_read; int64_t entry_uncompressed_bytes_read; - /* Running CRC32 of the decompressed data */ - unsigned long entry_crc32; + /* Running CRC32 of the decompressed and decrypted data */ + unsigned long computed_crc32; unsigned long (*crc32func)(unsigned long, const void *, size_t); char ignore_crc32; @@ -945,7 +944,7 @@ zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, zip->end_of_entry = 0; zip->entry_uncompressed_bytes_read = 0; zip->entry_compressed_bytes_read = 0; - zip->entry_crc32 = zip->crc32func(0, NULL, 0); + zip->computed_crc32 = zip->crc32func(0, NULL, 0); /* Setup default conversion. */ if (zip->sconv == NULL && !zip->init_default_conversion) { @@ -1140,7 +1139,8 @@ zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, "Inconsistent CRC32 values"); ret = ARCHIVE_WARN; } - if (zip_entry->compressed_size == 0) { + if (zip_entry->compressed_size == 0 + || zip_entry->compressed_size == 0xffffffff) { zip_entry->compressed_size = zip_entry_central_dir.compressed_size; } else if (zip_entry->compressed_size @@ -1284,7 +1284,8 @@ zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, return ARCHIVE_FATAL; } } else if (0 == (zip_entry->zip_flags & ZIP_LENGTH_AT_END) - || zip_entry->uncompressed_size > 0) { + || (zip_entry->uncompressed_size > 0 + && zip_entry->uncompressed_size != 0xffffffff)) { /* Set the size only if it's meaningful. */ archive_entry_set_size(entry, zip_entry->uncompressed_size); } @@ -1343,25 +1344,267 @@ check_authentication_code(struct archive_read *a, const void *_p) } /* - * Read "uncompressed" data. There are three cases: - * 1) We know the size of the data. This is always true for the - * seeking reader (we've examined the Central Directory already). - * 2) ZIP_LENGTH_AT_END was set, but only the CRC was deferred. - * Info-ZIP seems to do this; we know the size but have to grab - * the CRC from the data descriptor afterwards. - * 3) We're streaming and ZIP_LENGTH_AT_END was specified and - * we have no size information. In this case, we can do pretty - * well by watching for the data descriptor record. The data - * descriptor is 16 bytes and includes a computed CRC that should - * provide a strong check. + * The Zip end-of-file marker is inherently ambiguous. The specification + * in APPNOTE.TXT allows any of four possible formats, and there is no + * guaranteed-correct way for a reader to know a priori which one the writer + * will have used. The four formats are: + * 1. 32-bit format with an initial PK78 marker + * 2. 32-bit format without that marker + * 3. 64-bit format with the marker + * 4. 64-bit format without the marker * - * TODO: Technically, the PK\007\010 signature is optional. - * In the original spec, the data descriptor contained CRC - * and size fields but had no leading signature. In practice, - * newer writers seem to provide the signature pretty consistently. + * Mark Adler's `sunzip` streaming unzip program solved this ambiguity + * by just looking at every possible combination and accepting the + * longest one that matches the expected values. His approach always + * consumes the longest possible matching EOF marker, based on an + * analysis of all the possible failures and how the values could + * overlap. * - * For uncompressed data, the PK\007\010 marker seems essential - * to be sure we've actually seen the end of the entry. + * For example, suppose both of the first two formats listed + * above match. In that case, we know the next four + * 32-bit words match this pattern: + * ``` + * [PK\07\08] [CRC32] [compressed size] [uncompressed size] + * ``` + * but we know they must also match this pattern: + * ``` + * [CRC32] [compressed size] [uncompressed size] [other PK marker] + * ``` + * + * Since the first word here matches both the PK78 signature in the + * first form and the CRC32 in the second, we know those two values + * are equal, the CRC32 must be exactly 0x08074b50. Similarly, the + * compressed and uncompressed size must also be exactly this value. + * So we know these four words are all 0x08074b50. If we were to + * accept the shorter pattern, it would be immediately followed by + * another PK78 marker, which is not possible in a well-formed ZIP + * archive unless there is garbage between entries. This implies we + * should not accept the shorter form in such a case; we should accept + * the longer form. + * + * If the second and third possibilities above both match, we + * have a slightly different situation. The following words + * must match both the 32-bit format + * ``` + * [CRC32] [compressed size] [uncompressed size] [other PK marker] + * ``` + * and the 64-bit format + * ``` + * [CRC32] [compressed low] [compressed high] [uncompressed low] [uncompressed high] [other PK marker] + * ``` + * Since the 32-bit and 64-bit compressed sizes both match, the + * actual size must fit in 32 bits, which implies the high-order + * word of the compressed size is zero. So we know the uncompressed + * low word is zero, which again implies that if we accept the shorter + * format, there will not be a valid PK marker following it. + * + * Similar considerations rule out the shorter form in every other + * possibly-ambiguous pair. So if two of the four possible formats + * match, we should accept the longer option. + * + * If none of the four formats matches, we know the archive must be + * corrupted in some fashion. In particular, it's possible that the + * length-at-end bit was incorrect and we should not really be looking + * for an EOF marker at all. To allow for this possibility, we + * evaluate the following words to collect data for a later error + * report but do not consume any bytes. We instead rely on the later + * search for a new PK marker to re-sync to the next well-formed + * entry. + */ +static void +consume_end_of_file_marker(struct archive_read *a, struct zip *zip) +{ + const char *marker; + const char *p; + uint64_t compressed32, uncompressed32; + uint64_t compressed64, uncompressed64; + uint64_t compressed_actual, uncompressed_actual; + uint32_t crc32_actual; + const uint32_t PK78 = 0x08074B50ULL; + uint8_t crc32_ignored, crc32_may_be_zero; + + /* If there shouldn't be a marker, don't consume it. */ + if ((zip->entry->zip_flags & ZIP_LENGTH_AT_END) == 0) { + return; + } + + /* The longest Zip end-of-file record is 24 bytes. Since an + * end-of-file record can never appear at the end of the + * archive, we know 24 bytes will be available unless + * the archive is severely truncated. */ + if (NULL == (marker = __archive_read_ahead(a, 24, NULL))) { + return; + } + p = marker; + + /* The end-of-file record comprises: + * = Optional PK\007\010 marker + * = 4-byte CRC32 + * = Compressed size + * = Uncompressed size + * + * The last two fields are either both 32 bits or both 64 + * bits. We check all possible layouts and accept any one + * that gives us a complete match, else we make a best-effort + * attempt to parse out the pieces. + */ + + /* CRC32 checking can be tricky: + * * Test suites sometimes ignore the CRC32 + * * AES AE-2 always writes zero for the CRC32 + * * AES AE-1 sometimes writes zero for the CRC32 + */ + crc32_ignored = zip->ignore_crc32; + crc32_may_be_zero = 0; + crc32_actual = zip->computed_crc32; + if (zip->hctx_valid) { + switch (zip->entry->aes_extra.vendor) { + case AES_VENDOR_AE_2: + crc32_actual = 0; + break; + case AES_VENDOR_AE_1: + default: + crc32_may_be_zero = 1; + break; + } + } + + /* Values computed from the actual data in the archive. */ + compressed_actual = (uint64_t)zip->entry_compressed_bytes_read; + uncompressed_actual = (uint64_t)zip->entry_uncompressed_bytes_read; + + + /* Longest: PK78 marker, all 64-bit fields (24 bytes total) */ + if (archive_le32dec(p) == PK78 + && ((archive_le32dec(p + 4) == crc32_actual) + || (crc32_may_be_zero && (archive_le32dec(p + 4) == 0)) + || crc32_ignored) + && (archive_le64dec(p + 8) == compressed_actual) + && (archive_le64dec(p + 16) == uncompressed_actual)) { + if (!crc32_ignored) { + zip->entry->crc32 = crc32_actual; + } + zip->entry->compressed_size = compressed_actual; + zip->entry->uncompressed_size = uncompressed_actual; + zip->unconsumed += 24; + return; + } + + /* No PK78 marker, 64-bit fields (20 bytes total) */ + if (((archive_le32dec(p) == crc32_actual) + || (crc32_may_be_zero && (archive_le32dec(p + 4) == 0)) + || crc32_ignored) + && (archive_le64dec(p + 4) == compressed_actual) + && (archive_le64dec(p + 12) == uncompressed_actual)) { + if (!crc32_ignored) { + zip->entry->crc32 = crc32_actual; + } + zip->entry->compressed_size = compressed_actual; + zip->entry->uncompressed_size = uncompressed_actual; + zip->unconsumed += 20; + return; + } + + /* PK78 marker and 32-bit fields (16 bytes total) */ + if (archive_le32dec(p) == PK78 + && ((archive_le32dec(p + 4) == crc32_actual) + || (crc32_may_be_zero && (archive_le32dec(p + 4) == 0)) + || crc32_ignored) + && (archive_le32dec(p + 8) == compressed_actual) + && (archive_le32dec(p + 12) == uncompressed_actual)) { + if (!crc32_ignored) { + zip->entry->crc32 = crc32_actual; + } + zip->entry->compressed_size = compressed_actual; + zip->entry->uncompressed_size = uncompressed_actual; + zip->unconsumed += 16; + return; + } + + /* Shortest: No PK78 marker, all 32-bit fields (12 bytes total) */ + if (((archive_le32dec(p) == crc32_actual) + || (crc32_may_be_zero && (archive_le32dec(p + 4) == 0)) + || crc32_ignored) + && (archive_le32dec(p + 4) == compressed_actual) + && (archive_le32dec(p + 8) == uncompressed_actual)) { + if (!crc32_ignored) { + zip->entry->crc32 = crc32_actual; + } + zip->entry->compressed_size = compressed_actual; + zip->entry->uncompressed_size = uncompressed_actual; + zip->unconsumed += 12; + return; + } + + /* If none of the above patterns gives us a full exact match, + * then there's something definitely amiss. The fallback code + * below will parse out some plausible values for error + * reporting purposes. Note that this won't actually + * consume anything: + * + * = If there really is a marker here, the logic to resync to + * the next entry will suffice to skip it. + * + * = There might not really be a marker: Corruption or bugs + * may have set the length-at-end bit without a marker ever + * having actually been written. In this case, we + * explicitly should not consume any bytes, since that would + * prevent us from correctly reading the next entry. + */ + if (archive_le32dec(p) == PK78) { + p += 4; /* Ignore PK78 if it appears to be present */ + } + zip->entry->crc32 = archive_le32dec(p); /* Parse CRC32 */ + p += 4; + + /* Consider both 32- and 64-bit interpretations */ + compressed32 = archive_le32dec(p); + uncompressed32 = archive_le32dec(p + 4); + compressed64 = archive_le64dec(p); + uncompressed64 = archive_le64dec(p + 8); + + /* The earlier patterns may have failed because of CRC32 + * mismatch, so it's still possible that both sizes match. + * Try to match as many as we can... + */ + if (compressed32 == compressed_actual + && uncompressed32 == uncompressed_actual) { + /* Both 32-bit fields match */ + zip->entry->compressed_size = compressed32; + zip->entry->uncompressed_size = uncompressed32; + } else if (compressed64 == compressed_actual + || uncompressed64 == uncompressed_actual) { + /* One or both 64-bit fields match */ + zip->entry->compressed_size = compressed64; + zip->entry->uncompressed_size = uncompressed64; + } else { + /* Zero or one 32-bit fields match */ + zip->entry->compressed_size = compressed32; + zip->entry->uncompressed_size = uncompressed32; + } +} + +/* + * Read "uncompressed" data. + * + * This is straightforward if we know the size of the data. This is + * always true for the seeking reader (we've examined the Central + * Directory already), and will often be true for the streaming reader + * (the writer was writing uncompressed so probably knows the size). + * + * If we don't know the size, then life is more interesting. Note + * that a careful reading of the Zip specification says that a writer + * must use ZIP_LENGTH_AT_END if it cannot write the CRC into the + * local header. And if it uses ZIP_LENGTH_AT_END, then it is + * prohibited from storing the sizes in the local header. This + * prevents fully-compliant streaming writers from providing any size + * clues to a streaming reader. In this case, we have to scan the + * data as we read to try to locate the end-of-file marker. + * + * We assume here that the end-of-file marker always has the + * PK\007\010 signature. Although it's technically optional, newer + * writers seem to provide it pretty consistently, and it's not clear + * how to efficiently recognize an end-of-file marker that lacks it. * * Returns ARCHIVE_OK if successful, ARCHIVE_FATAL otherwise, sets * zip->end_of_entry if it consumes all of the data. @@ -1373,18 +1616,18 @@ zip_read_data_none(struct archive_read *a, const void **_buff, struct zip *zip; const char *buff; ssize_t bytes_avail; + ssize_t trailing_extra; int r; (void)offset; /* UNUSED */ zip = (struct zip *)(a->format->data); + trailing_extra = zip->hctx_valid ? AUTH_CODE_SIZE : 0; if (zip->entry->zip_flags & ZIP_LENGTH_AT_END) { const char *p; - ssize_t grabbing_bytes = 24; + ssize_t grabbing_bytes = 24 + trailing_extra; - if (zip->hctx_valid) - grabbing_bytes += AUTH_CODE_SIZE; /* Grab at least 24 bytes. */ buff = __archive_read_ahead(a, grabbing_bytes, &bytes_avail); if (bytes_avail < grabbing_bytes) { @@ -1399,44 +1642,19 @@ zip_read_data_none(struct archive_read *a, const void **_buff, } /* Check for a complete PK\007\010 signature, followed * by the correct 4-byte CRC. */ - p = buff; - if (zip->hctx_valid) - p += AUTH_CODE_SIZE; + p = buff + trailing_extra; if (p[0] == 'P' && p[1] == 'K' && p[2] == '\007' && p[3] == '\010' - && (archive_le32dec(p + 4) == zip->entry_crc32 + && (archive_le32dec(p + 4) == zip->computed_crc32 || zip->ignore_crc32 || (zip->hctx_valid && zip->entry->aes_extra.vendor == AES_VENDOR_AE_2))) { - if (zip->entry->flags & LA_USED_ZIP64) { - uint64_t compressed, uncompressed; - zip->entry->crc32 = archive_le32dec(p + 4); - compressed = archive_le64dec(p + 8); - uncompressed = archive_le64dec(p + 16); - if (compressed > INT64_MAX || uncompressed > - INT64_MAX) { - archive_set_error(&a->archive, - ARCHIVE_ERRNO_FILE_FORMAT, - "Overflow of 64-bit file sizes"); - return ARCHIVE_FAILED; - } - zip->entry->compressed_size = compressed; - zip->entry->uncompressed_size = uncompressed; - zip->unconsumed = 24; - } else { - zip->entry->crc32 = archive_le32dec(p + 4); - zip->entry->compressed_size = - archive_le32dec(p + 8); - zip->entry->uncompressed_size = - archive_le32dec(p + 12); - zip->unconsumed = 16; - } + zip->end_of_entry = 1; if (zip->hctx_valid) { r = check_authentication_code(a, buff); if (r != ARCHIVE_OK) return (r); } - zip->end_of_entry = 1; return (ARCHIVE_OK); } /* If not at EOF, ensure we consume at least one byte. */ @@ -1452,11 +1670,10 @@ zip_read_data_none(struct archive_read *a, const void **_buff, else if (p[3] == '\007') { p += 1; } else if (p[3] == '\010' && p[2] == '\007' && p[1] == 'K' && p[0] == 'P') { - if (zip->hctx_valid) - p -= AUTH_CODE_SIZE; break; } else { p += 4; } } + p -= trailing_extra; bytes_avail = p - buff; } else { if (zip->entry_bytes_remaining == 0) { @@ -1499,59 +1716,15 @@ zip_read_data_none(struct archive_read *a, const void **_buff, bytes_avail = dec_size; buff = (const char *)zip->decrypted_buffer; } - *size = bytes_avail; zip->entry_bytes_remaining -= bytes_avail; zip->entry_uncompressed_bytes_read += bytes_avail; zip->entry_compressed_bytes_read += bytes_avail; zip->unconsumed += bytes_avail; + *size = bytes_avail; *_buff = buff; return (ARCHIVE_OK); } -static int -consume_optional_marker(struct archive_read *a, struct zip *zip) -{ - if (zip->end_of_entry && (zip->entry->zip_flags & ZIP_LENGTH_AT_END)) { - const char *p; - - if (NULL == (p = __archive_read_ahead(a, 24, NULL))) { - archive_set_error(&a->archive, - ARCHIVE_ERRNO_FILE_FORMAT, - "Truncated ZIP end-of-file record"); - return (ARCHIVE_FATAL); - } - /* Consume the optional PK\007\010 marker. */ - if (p[0] == 'P' && p[1] == 'K' && - p[2] == '\007' && p[3] == '\010') { - p += 4; - zip->unconsumed = 4; - } - if (zip->entry->flags & LA_USED_ZIP64) { - uint64_t compressed, uncompressed; - zip->entry->crc32 = archive_le32dec(p); - compressed = archive_le64dec(p + 4); - uncompressed = archive_le64dec(p + 12); - if (compressed > INT64_MAX || - uncompressed > INT64_MAX) { - archive_set_error(&a->archive, - ARCHIVE_ERRNO_FILE_FORMAT, - "Overflow of 64-bit file sizes"); - return ARCHIVE_FAILED; - } - zip->entry->compressed_size = compressed; - zip->entry->uncompressed_size = uncompressed; - zip->unconsumed += 20; - } else { - zip->entry->crc32 = archive_le32dec(p); - zip->entry->compressed_size = archive_le32dec(p + 4); - zip->entry->uncompressed_size = archive_le32dec(p + 8); - zip->unconsumed += 12; - } - } - - return (ARCHIVE_OK); -} - #if HAVE_LZMA_H && HAVE_LIBLZMA static int zipx_xz_init(struct archive_read *a, struct zip *zip) @@ -1578,8 +1751,7 @@ zipx_xz_init(struct archive_read *a, struct zip *zip) free(zip->uncompressed_buffer); zip->uncompressed_buffer_size = 256 * 1024; - zip->uncompressed_buffer = - (uint8_t*) malloc(zip->uncompressed_buffer_size); + zip->uncompressed_buffer = malloc(zip->uncompressed_buffer_size); if (zip->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory for xz decompression"); @@ -1689,8 +1861,7 @@ zipx_lzma_alone_init(struct archive_read *a, struct zip *zip) if(!zip->uncompressed_buffer) { zip->uncompressed_buffer_size = 256 * 1024; - zip->uncompressed_buffer = - (uint8_t*) malloc(zip->uncompressed_buffer_size); + zip->uncompressed_buffer = malloc(zip->uncompressed_buffer_size); if (zip->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, @@ -1751,7 +1922,7 @@ zip_read_data_zipx_xz(struct archive_read *a, const void **buff, return (ARCHIVE_FATAL); } - in_bytes = zipmin(zip->entry_bytes_remaining, bytes_avail); + in_bytes = (ssize_t)zipmin(zip->entry_bytes_remaining, bytes_avail); zip->zipx_lzma_stream.next_in = compressed_buf; zip->zipx_lzma_stream.avail_in = in_bytes; zip->zipx_lzma_stream.total_in = 0; @@ -1793,20 +1964,16 @@ zip_read_data_zipx_xz(struct archive_read *a, const void **buff, break; } - to_consume = zip->zipx_lzma_stream.total_in; + to_consume = (ssize_t)zip->zipx_lzma_stream.total_in; __archive_read_consume(a, to_consume); zip->entry_bytes_remaining -= to_consume; zip->entry_compressed_bytes_read += to_consume; zip->entry_uncompressed_bytes_read += zip->zipx_lzma_stream.total_out; - *size = zip->zipx_lzma_stream.total_out; + *size = (size_t)zip->zipx_lzma_stream.total_out; *buff = zip->uncompressed_buffer; - ret = consume_optional_marker(a, zip); - if (ret != ARCHIVE_OK) - return (ret); - return (ARCHIVE_OK); } @@ -1845,7 +2012,7 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff, } /* Set decompressor parameters. */ - in_bytes = zipmin(zip->entry_bytes_remaining, bytes_avail); + in_bytes = (ssize_t)zipmin(zip->entry_bytes_remaining, bytes_avail); zip->zipx_lzma_stream.next_in = compressed_buf; zip->zipx_lzma_stream.avail_in = in_bytes; @@ -1855,7 +2022,7 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff, /* These lzma_alone streams lack end of stream marker, so let's * make sure the unpacker won't try to unpack more than it's * supposed to. */ - zipmin((int64_t) zip->uncompressed_buffer_size, + (size_t)zipmin((int64_t) zip->uncompressed_buffer_size, zip->entry->uncompressed_size - zip->entry_uncompressed_bytes_read); zip->zipx_lzma_stream.total_out = 0; @@ -1871,8 +2038,6 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff, /* This case is optional in lzma alone format. It can happen, * but most of the files don't have it. (GitHub #1257) */ case LZMA_STREAM_END: - lzma_end(&zip->zipx_lzma_stream); - zip->zipx_lzma_valid = 0; if((int64_t) zip->zipx_lzma_stream.total_in != zip->entry_bytes_remaining) { @@ -1894,7 +2059,7 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff, return (ARCHIVE_FATAL); } - to_consume = zip->zipx_lzma_stream.total_in; + to_consume = (ssize_t)zip->zipx_lzma_stream.total_in; /* Update pointers. */ __archive_read_consume(a, to_consume); @@ -1906,21 +2071,18 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff, zip->end_of_entry = 1; } - /* Return values. */ - *size = zip->zipx_lzma_stream.total_out; - *buff = zip->uncompressed_buffer; - - /* Behave the same way as during deflate decompression. */ - ret = consume_optional_marker(a, zip); - if (ret != ARCHIVE_OK) - return (ret); - /* Free lzma decoder handle because we'll no longer need it. */ + /* This cannot be folded into LZMA_STREAM_END handling above + * because the stream end marker is not required in this format. */ if(zip->end_of_entry) { lzma_end(&zip->zipx_lzma_stream); zip->zipx_lzma_valid = 0; } + /* Return values. */ + *size = (size_t)zip->zipx_lzma_stream.total_out; + *buff = zip->uncompressed_buffer; + /* If we're here, then we're good! */ return (ARCHIVE_OK); } @@ -1972,15 +2134,15 @@ zipx_ppmd8_init(struct archive_read *a, struct zip *zip) if(order < 2 || restore_method > 2) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Invalid parameter set in PPMd8 stream (order=%" PRId32 ", " - "restore=%" PRId32 ")", order, restore_method); + "Invalid parameter set in PPMd8 stream (order=%" PRIu32 ", " + "restore=%" PRIu32 ")", order, restore_method); return (ARCHIVE_FAILED); } /* Allocate the memory needed to properly decompress the file. */ if(!__archive_ppmd8_functions.Ppmd8_Alloc(&zip->ppmd8, mem << 20)) { archive_set_error(&a->archive, ENOMEM, - "Unable to allocate memory for PPMd8 stream: %" PRId32 " bytes", + "Unable to allocate memory for PPMd8 stream: %" PRIu32 " bytes", mem << 20); return (ARCHIVE_FATAL); } @@ -2003,8 +2165,7 @@ zipx_ppmd8_init(struct archive_read *a, struct zip *zip) free(zip->uncompressed_buffer); zip->uncompressed_buffer_size = 256 * 1024; - zip->uncompressed_buffer = - (uint8_t*) malloc(zip->uncompressed_buffer_size); + zip->uncompressed_buffer = malloc(zip->uncompressed_buffer_size); if(zip->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, @@ -2078,10 +2239,6 @@ zip_read_data_zipx_ppmd(struct archive_read *a, const void **buff, ++consumed_bytes; } while(consumed_bytes < zip->uncompressed_buffer_size); - /* Update pointers for libarchive. */ - *buff = zip->uncompressed_buffer; - *size = consumed_bytes; - /* Update pointers so we can continue decompression in another call. */ zip->entry_bytes_remaining -= zip->zipx_ppmd_read_compressed; zip->entry_compressed_bytes_read += zip->zipx_ppmd_read_compressed; @@ -2093,10 +2250,9 @@ zip_read_data_zipx_ppmd(struct archive_read *a, const void **buff, zip->ppmd8_valid = 0; } - /* Seek for optional marker, same way as in each zip entry. */ - ret = consume_optional_marker(a, zip); - if (ret != ARCHIVE_OK) - return ret; + /* Update pointers for libarchive. */ + *buff = zip->uncompressed_buffer; + *size = consumed_bytes; return ARCHIVE_OK; } @@ -2132,8 +2288,7 @@ zipx_bzip2_init(struct archive_read *a, struct zip *zip) free(zip->uncompressed_buffer); zip->uncompressed_buffer_size = 256 * 1024; - zip->uncompressed_buffer = - (uint8_t*) malloc(zip->uncompressed_buffer_size); + zip->uncompressed_buffer = malloc(zip->uncompressed_buffer_size); if (zip->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory for bzip2 decompression"); @@ -2172,7 +2327,7 @@ zip_read_data_zipx_bzip2(struct archive_read *a, const void **buff, return (ARCHIVE_FATAL); } - in_bytes = zipmin(zip->entry_bytes_remaining, bytes_avail); + in_bytes = (ssize_t)zipmin(zip->entry_bytes_remaining, bytes_avail); if(in_bytes < 1) { /* libbz2 doesn't complain when caller feeds avail_in == 0. * It will actually return success in this case, which is @@ -2186,11 +2341,11 @@ zip_read_data_zipx_bzip2(struct archive_read *a, const void **buff, /* Setup buffer boundaries. */ zip->bzstream.next_in = (char*)(uintptr_t) compressed_buff; - zip->bzstream.avail_in = in_bytes; + zip->bzstream.avail_in = (uint32_t)in_bytes; zip->bzstream.total_in_hi32 = 0; zip->bzstream.total_in_lo32 = 0; zip->bzstream.next_out = (char*) zip->uncompressed_buffer; - zip->bzstream.avail_out = zip->uncompressed_buffer_size; + zip->bzstream.avail_out = (uint32_t)zip->uncompressed_buffer_size; zip->bzstream.total_out_hi32 = 0; zip->bzstream.total_out_lo32 = 0; @@ -2227,7 +2382,7 @@ zip_read_data_zipx_bzip2(struct archive_read *a, const void **buff, to_consume = zip->bzstream.total_in_lo32; __archive_read_consume(a, to_consume); - total_out = ((uint64_t) zip->bzstream.total_out_hi32 << 32) + + total_out = ((uint64_t) zip->bzstream.total_out_hi32 << 32) | zip->bzstream.total_out_lo32; zip->entry_bytes_remaining -= to_consume; @@ -2235,14 +2390,9 @@ zip_read_data_zipx_bzip2(struct archive_read *a, const void **buff, zip->entry_uncompressed_bytes_read += total_out; /* Give libarchive its due. */ - *size = total_out; + *size = (size_t)total_out; *buff = zip->uncompressed_buffer; - /* Seek for optional marker, like in other entries. */ - r = consume_optional_marker(a, zip); - if(r != ARCHIVE_OK) - return r; - return ARCHIVE_OK; } @@ -2280,8 +2430,7 @@ zipx_zstd_init(struct archive_read *a, struct zip *zip) free(zip->uncompressed_buffer); zip->uncompressed_buffer_size = ZSTD_DStreamOutSize(); - zip->uncompressed_buffer = - (uint8_t*) malloc(zip->uncompressed_buffer_size); + zip->uncompressed_buffer = malloc(zip->uncompressed_buffer_size); if (zip->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory for Zstd decompression"); @@ -2324,7 +2473,7 @@ zip_read_data_zipx_zstd(struct archive_read *a, const void **buff, return (ARCHIVE_FATAL); } - in_bytes = zipmin(zip->entry_bytes_remaining, bytes_avail); + in_bytes = (ssize_t)zipmin(zip->entry_bytes_remaining, bytes_avail); if(in_bytes < 1) { /* zstd doesn't complain when caller feeds avail_in == 0. * It will actually return success in this case, which is @@ -2370,14 +2519,9 @@ zip_read_data_zipx_zstd(struct archive_read *a, const void **buff, zip->entry_uncompressed_bytes_read += total_out; /* Give libarchive its due. */ - *size = total_out; + *size = (size_t)total_out; *buff = zip->uncompressed_buffer; - /* Seek for optional marker, like in other entries. */ - r = consume_optional_marker(a, zip); - if(r != ARCHIVE_OK) - return r; - return ARCHIVE_OK; } #endif @@ -2413,7 +2557,7 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, size_t *size, int64_t *offset) { struct zip *zip; - ssize_t bytes_avail; + ssize_t bytes_avail, to_consume = 0; const void *compressed_buff, *sp; int r; @@ -2425,7 +2569,7 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, if (zip->uncompressed_buffer == NULL) { zip->uncompressed_buffer_size = 256 * 1024; zip->uncompressed_buffer - = (unsigned char *)malloc(zip->uncompressed_buffer_size); + = malloc(zip->uncompressed_buffer_size); if (zip->uncompressed_buffer == NULL) { archive_set_error(&a->archive, ENOMEM, "No memory for ZIP decompression"); @@ -2534,34 +2678,33 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, } /* Consume as much as the compressor actually used. */ - bytes_avail = zip->stream.total_in; + to_consume = zip->stream.total_in; + __archive_read_consume(a, to_consume); + zip->entry_bytes_remaining -= to_consume; + zip->entry_compressed_bytes_read += to_consume; + zip->entry_uncompressed_bytes_read += zip->stream.total_out; + if (zip->tctx_valid || zip->cctx_valid) { - zip->decrypted_bytes_remaining -= bytes_avail; + zip->decrypted_bytes_remaining -= to_consume; if (zip->decrypted_bytes_remaining == 0) zip->decrypted_ptr = zip->decrypted_buffer; else - zip->decrypted_ptr += bytes_avail; + zip->decrypted_ptr += to_consume; } - /* Calculate compressed data as much as we used.*/ if (zip->hctx_valid) - archive_hmac_sha1_update(&zip->hctx, sp, bytes_avail); - __archive_read_consume(a, bytes_avail); - zip->entry_bytes_remaining -= bytes_avail; - zip->entry_compressed_bytes_read += bytes_avail; + archive_hmac_sha1_update(&zip->hctx, sp, to_consume); - *size = zip->stream.total_out; - zip->entry_uncompressed_bytes_read += zip->stream.total_out; - *buff = zip->uncompressed_buffer; - - if (zip->end_of_entry && zip->hctx_valid) { - r = check_authentication_code(a, NULL); - if (r != ARCHIVE_OK) - return (r); + if (zip->end_of_entry) { + if (zip->hctx_valid) { + r = check_authentication_code(a, NULL); + if (r != ARCHIVE_OK) { + return (r); + } + } } - r = consume_optional_marker(a, zip); - if (r != ARCHIVE_OK) - return (r); + *size = zip->stream.total_out; + *buff = zip->uncompressed_buffer; return (ARCHIVE_OK); } @@ -3025,17 +3168,30 @@ archive_read_format_zip_read_data(struct archive_read *a, /* We can't decompress this entry, but we will * be able to skip() it and try the next entry. */ return (ARCHIVE_FAILED); - break; } if (r != ARCHIVE_OK) return (r); - /* Update checksum */ - if (*size) - zip->entry_crc32 = zip->crc32func(zip->entry_crc32, *buff, - (unsigned)*size); - /* If we hit the end, swallow any end-of-data marker. */ + if (*size > 0) { + zip->computed_crc32 = zip->crc32func(zip->computed_crc32, *buff, + (unsigned)*size); + } + /* If we hit the end, swallow any end-of-data marker and + * verify the final check values. */ if (zip->end_of_entry) { - /* Check file size, CRC against these values. */ + consume_end_of_file_marker(a, zip); + + /* Check computed CRC against header */ + if ((!zip->hctx_valid || + zip->entry->aes_extra.vendor != AES_VENDOR_AE_2) && + zip->entry->crc32 != zip->computed_crc32 + && !zip->ignore_crc32) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP bad CRC: 0x%lx should be 0x%lx", + (unsigned long)zip->computed_crc32, + (unsigned long)zip->entry->crc32); + return (ARCHIVE_FAILED); + } + /* Check file size against header. */ if (zip->entry->compressed_size != zip->entry_compressed_bytes_read) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, @@ -3043,7 +3199,7 @@ archive_read_format_zip_read_data(struct archive_read *a, "(read %jd, expected %jd)", (intmax_t)zip->entry_compressed_bytes_read, (intmax_t)zip->entry->compressed_size); - return (ARCHIVE_WARN); + return (ARCHIVE_FAILED); } /* Size field only stores the lower 32 bits of the actual * size. */ @@ -3054,18 +3210,7 @@ archive_read_format_zip_read_data(struct archive_read *a, "(read %jd, expected %jd)\n", (intmax_t)zip->entry_uncompressed_bytes_read, (intmax_t)zip->entry->uncompressed_size); - return (ARCHIVE_WARN); - } - /* Check computed CRC against header */ - if ((!zip->hctx_valid || - zip->entry->aes_extra.vendor != AES_VENDOR_AE_2) && - zip->entry->crc32 != zip->entry_crc32 - && !zip->ignore_crc32) { - archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, - "ZIP bad CRC: 0x%lx should be 0x%lx", - (unsigned long)zip->entry_crc32, - (unsigned long)zip->entry->crc32); - return (ARCHIVE_WARN); + return (ARCHIVE_FAILED); } } @@ -3449,7 +3594,7 @@ archive_read_support_format_zip_streamable(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_zip"); - zip = (struct zip *)calloc(1, sizeof(*zip)); + zip = calloc(1, sizeof(*zip)); if (zip == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate zip data"); @@ -3529,7 +3674,7 @@ read_eocd(struct zip *zip, const char *p, int64_t current_offset) if (archive_le16dec(p + 10) != archive_le16dec(p + 8)) return 0; /* Central directory can't extend beyond start of EOCD record. */ - if (cd_offset + cd_size > current_offset) + if ((int64_t)cd_offset + cd_size > current_offset) return 0; /* Save the central directory location for later use. */ @@ -3932,6 +4077,17 @@ slurp_central_directory(struct archive_read *a, struct archive_entry* entry, } else { /* Generate resource fork name to find its * resource file at zip->tree_rsrc. */ + + /* If this is an entry ending with slash, + * make the resource for name slash-less + * as the actual resource fork doesn't end with '/'. + */ + size_t tmp_length = filename_length; + if (tmp_length > 0 && name[tmp_length - 1] == '/') { + tmp_length--; + r = rsrc_basename(name, tmp_length); + } + archive_strcpy(&(zip_entry->rsrcname), "__MACOSX/"); archive_strncat(&(zip_entry->rsrcname), @@ -3939,7 +4095,7 @@ slurp_central_directory(struct archive_read *a, struct archive_entry* entry, archive_strcat(&(zip_entry->rsrcname), "._"); archive_strncat(&(zip_entry->rsrcname), name + (r - name), - filename_length - (r - name)); + tmp_length - (r - name)); /* Register an entry to RB tree to sort it by * file offset. */ __archive_rb_tree_insert_node(&zip->tree, @@ -4230,7 +4386,7 @@ archive_read_support_format_zip_seekable(struct archive *_a) archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_support_format_zip_seekable"); - zip = (struct zip *)calloc(1, sizeof(*zip)); + zip = calloc(1, sizeof(*zip)); if (zip == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate zip data"); diff --git a/Utilities/cmlibarchive/libarchive/archive_string.c b/Utilities/cmlibarchive/libarchive/archive_string.c index 69458e1a12b..8ab3b994b4b 100644 --- a/Utilities/cmlibarchive/libarchive/archive_string.c +++ b/Utilities/cmlibarchive/libarchive/archive_string.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_string.c 201095 2009-12-28 02:33:22Z kientzle $"); /* * Basic resizable string support, to simplify manipulating arbitrary-sized @@ -155,7 +154,6 @@ static int archive_wstring_append_from_mbs_in_codepage( struct archive_string_conv *); static int archive_string_append_from_wcs_in_codepage(struct archive_string *, const wchar_t *, size_t, struct archive_string_conv *); -static int is_big_endian(void); static int strncat_in_codepage(struct archive_string *, const void *, size_t, struct archive_string_conv *); static int win_strncat_from_utf16be(struct archive_string *, const void *, @@ -200,6 +198,29 @@ static int archive_string_normalize_D(struct archive_string *, const void *, static int archive_string_append_unicode(struct archive_string *, const void *, size_t, struct archive_string_conv *); +#if defined __LITTLE_ENDIAN__ + #define IS_BIG_ENDIAN 0 +#elif defined __BIG_ENDIAN__ + #define IS_BIG_ENDIAN 1 +#elif defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define IS_BIG_ENDIAN 0 +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + #define IS_BIG_ENDIAN 1 +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_X64) || defined(_M_ARM64)) + #define IS_BIG_ENDIAN 0 +#else +// Detect endianness at runtime. +static int +is_big_endian(void) +{ + uint16_t d = 1; + + return (archive_be16dec(&d) == 1); +} + +#define IS_BIG_ENDIAN is_big_endian() +#endif + static struct archive_string * archive_string_append(struct archive_string *as, const char *p, size_t s) { @@ -314,7 +335,7 @@ archive_string_ensure(struct archive_string *as, size_t s) if (new_length < s) new_length = s; /* Now we can reallocate the buffer. */ - p = (char *)realloc(as->s, new_length); + p = realloc(as->s, new_length); if (p == NULL) { /* On failure, wipe the string and return NULL. */ archive_string_free(as); @@ -486,7 +507,7 @@ archive_wstring_append_from_mbs_in_codepage(struct archive_wstring *dest, struct archive_string u16; int saved_flag = sc->flag;/* save current flag. */ - if (is_big_endian()) + if (IS_BIG_ENDIAN) sc->flag |= SCONV_TO_UTF16BE; else sc->flag |= SCONV_TO_UTF16LE; @@ -505,7 +526,7 @@ archive_wstring_append_from_mbs_in_codepage(struct archive_wstring *dest, count = (int)mbsnbytes(s, length); } u16.s = (char *)dest->s; - u16.length = dest->length << 1;; + u16.length = dest->length << 1; u16.buffer_length = dest->buffer_length; if (sc->flag & SCONV_NORMALIZATION_C) ret = archive_string_normalize_C(&u16, s, count, sc); @@ -524,14 +545,14 @@ archive_wstring_append_from_mbs_in_codepage(struct archive_wstring *dest, dest->length + count + 1)) return (-1); wmemcpy(dest->s + dest->length, (const wchar_t *)s, count); - if ((sc->flag & SCONV_FROM_UTF16BE) && !is_big_endian()) { + if ((sc->flag & SCONV_FROM_UTF16BE) && !IS_BIG_ENDIAN) { uint16_t *u16 = (uint16_t *)(dest->s + dest->length); int b; for (b = 0; b < count; b++) { uint16_t val = archive_le16dec(u16+b); archive_be16enc(u16+b, val); } - } else if ((sc->flag & SCONV_FROM_UTF16LE) && is_big_endian()) { + } else if ((sc->flag & SCONV_FROM_UTF16LE) && IS_BIG_ENDIAN) { uint16_t *u16 = (uint16_t *)(dest->s + dest->length); int b; for (b = 0; b < count; b++) { @@ -553,6 +574,8 @@ archive_wstring_append_from_mbs_in_codepage(struct archive_wstring *dest, } else mbflag = MB_PRECOMPOSED; + mbflag |= MB_ERR_INVALID_CHARS; + buffsize = dest->length + length + 1; do { /* Allocate memory for WCS. */ @@ -1324,6 +1347,10 @@ free_sconv_object(struct archive_string_conv *sc) } #if defined(_WIN32) && !defined(__CYGWIN__) +# if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define GetOEMCP() CP_OEMCP +# endif + static unsigned my_atoi(const char *p) { @@ -1523,7 +1550,7 @@ get_current_codepage(void) p = strrchr(locale, '.'); if (p == NULL) return (GetACP()); - if (strcmp(p+1, "utf8") == 0) + if ((strcmp(p+1, "utf8") == 0) || (strcmp(p+1, "UTF-8") == 0)) return CP_UTF8; cp = my_atoi(p+1); if ((int)cp <= 0) @@ -2635,81 +2662,69 @@ unicode_to_utf16le(char *p, size_t remaining, uint32_t uc) } /* - * Copy UTF-8 string in checking surrogate pair. - * If any surrogate pair are found, it would be canonicalized. + * Append new UTF-8 string to existing UTF-8 string. + * Existing string is assumed to already be in proper form; + * the new string will have invalid sequences replaced and + * surrogate pairs canonicalized. */ static int -strncat_from_utf8_to_utf8(struct archive_string *as, const void *_p, +strncat_from_utf8_to_utf8(struct archive_string *as, const void *_src, size_t len, struct archive_string_conv *sc) { - const char *s; - char *p, *endp; - int n, ret = 0; - + int ret = 0; + const char *src = _src; (void)sc; /* UNUSED */ + /* Pre-extend the destination */ if (archive_string_ensure(as, as->length + len + 1) == NULL) return (-1); - s = (const char *)_p; - p = as->s + as->length; - endp = as->s + as->buffer_length -1; - do { + /* Invariant: src points to the first UTF8 byte that hasn't + * been copied to the destination `as`. */ + for (;;) { + int n; uint32_t uc; - const char *ss = s; - size_t w; + const char *e = src; - /* - * Forward byte sequence until a conversion of that is needed. - */ - while ((n = utf8_to_unicode(&uc, s, len)) > 0) { - s += n; + /* Skip UTF-8 sequences until we reach end-of-string or + * a code point that needs conversion. */ + while ((n = utf8_to_unicode(&uc, e, len)) > 0) { + e += n; len -= n; } - if (ss < s) { - if (p + (s - ss) > endp) { - as->length = p - as->s; - if (archive_string_ensure(as, - as->buffer_length + len + 1) == NULL) - return (-1); - p = as->s + as->length; - endp = as->s + as->buffer_length -1; - } - - memcpy(p, ss, s - ss); - p += s - ss; + /* Copy the part that doesn't need conversion */ + if (e > src) { + if (archive_string_append(as, src, e - src) == NULL) + return (-1); + src = e; } - /* - * If n is negative, current byte sequence needs a replacement. - */ - if (n < 0) { + if (n == 0) { + /* We reached end-of-string */ + return (ret); + } else { + /* Next code point needs conversion */ + char t[4]; + size_t w; + + /* Try decoding a surrogate pair */ if (n == -3 && IS_SURROGATE_PAIR_LA(uc)) { - /* Current byte sequence may be CESU-8. */ - n = cesu8_to_unicode(&uc, s, len); + n = cesu8_to_unicode(&uc, src, len); } + /* Not a (valid) surrogate, so use a replacement char */ if (n < 0) { - ret = -1; - n *= -1;/* Use a replaced unicode character. */ + ret = -1; /* Return -1 if we used any replacement */ + n *= -1; } - - /* Rebuild UTF-8 byte sequence. */ - while ((w = unicode_to_utf8(p, endp - p, uc)) == 0) { - as->length = p - as->s; - if (archive_string_ensure(as, - as->buffer_length + len + 1) == NULL) - return (-1); - p = as->s + as->length; - endp = as->s + as->buffer_length -1; - } - p += w; - s += n; + /* Consume converted code point */ + src += n; len -= n; + /* Convert and append new UTF-8 sequence. */ + w = unicode_to_utf8(t, sizeof(t), uc); + if (archive_string_append(as, t, w) == NULL) + return (-1); } - } while (n > 0); - as->length = p - as->s; - as->s[as->length] = '\0'; - return (ret); + } } static int @@ -3545,7 +3560,7 @@ win_strncat_from_utf16(struct archive_string *as, const void *_p, size_t bytes, archive_string_init(&tmp); if (be) { - if (is_big_endian()) { + if (IS_BIG_ENDIAN) { u16 = _p; } else { if (archive_string_ensure(&tmp, bytes+2) == NULL) @@ -3558,7 +3573,7 @@ win_strncat_from_utf16(struct archive_string *as, const void *_p, size_t bytes, u16 = tmp.s; } } else { - if (!is_big_endian()) { + if (!IS_BIG_ENDIAN) { u16 = _p; } else { if (archive_string_ensure(&tmp, bytes+2) == NULL) @@ -3612,14 +3627,6 @@ win_strncat_from_utf16le(struct archive_string *as, const void *_p, return (win_strncat_from_utf16(as, _p, bytes, sc, 0)); } -static int -is_big_endian(void) -{ - uint16_t d = 1; - - return (archive_be16dec(&d) == 1); -} - /* * Convert a current locale string to UTF-16BE/LE and copy the result. * Return -1 if conversion fails. @@ -3680,7 +3687,7 @@ win_strncat_to_utf16(struct archive_string *as16, const void *_p, if (count == 0) return (-1); - if (is_big_endian()) { + if (IS_BIG_ENDIAN) { if (!bigendian) { while (count > 0) { uint16_t v = archive_be16dec(u16); @@ -3881,6 +3888,30 @@ archive_mstring_get_utf8(struct archive *a, struct archive_mstring *aes, } *p = NULL; +#if defined(_WIN32) && !defined(__CYGWIN__) + /* + * On Windows, first try converting from WCS because (1) there's no + * guarantee that the conversion to MBS will succeed, e.g. when using + * CP_ACP, and (2) that's more efficient than converting to MBS, just to + * convert back to WCS again before finally converting to UTF-8 + */ + if ((aes->aes_set & AES_SET_WCS) != 0) { + sc = archive_string_conversion_to_charset(a, "UTF-8", 1); + if (sc == NULL) + return (-1);/* Couldn't allocate memory for sc. */ + archive_string_empty(&(aes->aes_utf8)); + r = archive_string_append_from_wcs_in_codepage(&(aes->aes_utf8), + aes->aes_wcs.s, aes->aes_wcs.length, sc); + if (a == NULL) + free_sconv_object(sc); + if (r == 0) { + aes->aes_set |= AES_SET_UTF8; + *p = aes->aes_utf8.s; + return (0);/* success. */ + } else + return (-1);/* failure. */ + } +#endif /* Try converting WCS to MBS first if MBS does not exist yet. */ if ((aes->aes_set & AES_SET_MBS) == 0) { const char *pm; /* unused */ @@ -3965,6 +3996,32 @@ archive_mstring_get_wcs(struct archive *a, struct archive_mstring *aes, } *wp = NULL; +#if defined(_WIN32) && !defined(__CYGWIN__) + /* + * On Windows, prefer converting from UTF-8 directly to WCS because: + * (1) there's no guarantee that the string can be represented in MBS (e.g. + * with CP_ACP), and (2) in order to convert from UTF-8 to MBS, we're going + * to need to convert from UTF-8 to WCS anyway and its wasteful to throw + * away that intermediate result + */ + if (aes->aes_set & AES_SET_UTF8) { + struct archive_string_conv *sc; + + sc = archive_string_conversion_from_charset(a, "UTF-8", 1); + if (sc != NULL) { + archive_wstring_empty((&aes->aes_wcs)); + r = archive_wstring_append_from_mbs_in_codepage(&(aes->aes_wcs), + aes->aes_utf8.s, aes->aes_utf8.length, sc); + if (a == NULL) + free_sconv_object(sc); + if (r == 0) { + aes->aes_set |= AES_SET_WCS; + *wp = aes->aes_wcs.s; + return (0); + } + } + } +#endif /* Try converting UTF8 to MBS first if MBS does not exist yet. */ if ((aes->aes_set & AES_SET_MBS) == 0) { const char *p; /* unused */ @@ -4218,11 +4275,32 @@ archive_mstring_update_utf8(struct archive *a, struct archive_mstring *aes, aes->aes_set = AES_SET_UTF8; /* Only UTF8 is set now. */ - /* Try converting UTF-8 to MBS, return false on failure. */ sc = archive_string_conversion_from_charset(a, "UTF-8", 1); if (sc == NULL) return (-1);/* Couldn't allocate memory for sc. */ + +#if defined(_WIN32) && !defined(__CYGWIN__) + /* On Windows, there's no good way to convert from UTF8 -> MBS directly, so + * prefer to first convert to WCS as (1) it's wasteful to throw away the + * intermediate result, and (2) WCS will still be set even if we fail to + * convert to MBS (e.g. with ACP that can't represent the characters) */ + r = archive_wstring_append_from_mbs_in_codepage(&(aes->aes_wcs), + aes->aes_utf8.s, aes->aes_utf8.length, sc); + + if (a == NULL) + free_sconv_object(sc); + if (r != 0) + return (-1); /* This will guarantee we can't convert to MBS */ + aes->aes_set = AES_SET_UTF8 | AES_SET_WCS; /* Both UTF8 and WCS set. */ + + /* Try converting WCS to MBS, return false on failure. */ + if (archive_string_append_from_wcs(&(aes->aes_mbs), aes->aes_wcs.s, + aes->aes_wcs.length)) + return (-1); +#else + /* Try converting UTF-8 to MBS, return false on failure. */ r = archive_strcpy_l(&(aes->aes_mbs), utf8, sc); + if (a == NULL) free_sconv_object(sc); if (r != 0) @@ -4233,8 +4311,10 @@ archive_mstring_update_utf8(struct archive *a, struct archive_mstring *aes, if (archive_wstring_append_from_mbs(&(aes->aes_wcs), aes->aes_mbs.s, aes->aes_mbs.length)) return (-1); - aes->aes_set = AES_SET_UTF8 | AES_SET_WCS | AES_SET_MBS; +#endif /* All conversions succeeded. */ + aes->aes_set = AES_SET_UTF8 | AES_SET_WCS | AES_SET_MBS; + return (0); } diff --git a/Utilities/cmlibarchive/libarchive/archive_string.h b/Utilities/cmlibarchive/libarchive/archive_string.h index 49d7d3064a3..e8987867d3c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_string.h +++ b/Utilities/cmlibarchive/libarchive/archive_string.h @@ -21,9 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_string.h 201092 2009-12-28 02:26:06Z kientzle $ - * */ #ifndef ARCHIVE_STRING_H_INCLUDED diff --git a/Utilities/cmlibarchive/libarchive/archive_string_composition.h b/Utilities/cmlibarchive/libarchive/archive_string_composition.h index d0ac340961a..e917036f116 100644 --- a/Utilities/cmlibarchive/libarchive/archive_string_composition.h +++ b/Utilities/cmlibarchive/libarchive/archive_string_composition.h @@ -22,8 +22,6 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD$ - * */ /* diff --git a/Utilities/cmlibarchive/libarchive/archive_string_sprintf.c b/Utilities/cmlibarchive/libarchive/archive_string_sprintf.c index 969a5603a49..c785e12bdf6 100644 --- a/Utilities/cmlibarchive/libarchive/archive_string_sprintf.c +++ b/Utilities/cmlibarchive/libarchive/archive_string_sprintf.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_string_sprintf.c 189435 2009-03-06 05:14:55Z kientzle $"); /* * The use of printf()-family functions can be troublesome diff --git a/Utilities/cmlibarchive/libarchive/archive_util.3 b/Utilities/cmlibarchive/libarchive/archive_util.3 index d5d4e7dfd7d..3aa508f25aa 100644 --- a/Utilities/cmlibarchive/libarchive/archive_util.3 +++ b/Utilities/cmlibarchive/libarchive/archive_util.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_UTIL 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_util.c b/Utilities/cmlibarchive/libarchive/archive_util.c index 83586b5e9b1..3a84b202198 100644 --- a/Utilities/cmlibarchive/libarchive/archive_util.c +++ b/Utilities/cmlibarchive/libarchive/archive_util.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_util.c 201098 2009-12-28 02:58:14Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include @@ -42,9 +41,20 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_util.c 201098 2009-12-28 02:58:1 #ifdef HAVE_STRING_H #include #endif -#if defined(HAVE_WINCRYPT_H) && !defined(__CYGWIN__) +#if defined(_WIN32) && !defined(__CYGWIN__) +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA +/* don't use bcrypt when XP needs to be supported */ +#include + +/* Common in other bcrypt implementations, but missing from VS2008. */ +#ifndef BCRYPT_SUCCESS +#define BCRYPT_SUCCESS(r) ((NTSTATUS)(r) == STATUS_SUCCESS) +#endif + +#elif defined(HAVE_WINCRYPT_H) #include #endif +#endif #ifdef HAVE_ZLIB_H #include #endif @@ -233,20 +243,21 @@ __archive_mktempx(const char *tmpdir, wchar_t *template) L'm', L'n', L'o', L'p', L'q', L'r', L's', L't', L'u', L'v', L'w', L'x', L'y', L'z' }; - HCRYPTPROV hProv; struct archive_wstring temp_name; wchar_t *ws; DWORD attr; wchar_t *xp, *ep; int fd; - - hProv = (HCRYPTPROV)NULL; +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + BCRYPT_ALG_HANDLE hAlg = NULL; +#else + HCRYPTPROV hProv = (HCRYPTPROV)NULL; +#endif fd = -1; ws = NULL; + archive_string_init(&temp_name); if (template == NULL) { - archive_string_init(&temp_name); - /* Get a temporary directory. */ if (tmpdir == NULL) { size_t l; @@ -269,7 +280,8 @@ __archive_mktempx(const char *tmpdir, wchar_t *template) if (archive_wstring_append_from_mbs(&temp_name, tmpdir, strlen(tmpdir)) < 0) goto exit_tmpfile; - if (temp_name.s[temp_name.length-1] != L'/') + if (temp_name.length == 0 || + temp_name.s[temp_name.length-1] != L'/') archive_wstrappend_wchar(&temp_name, L'/'); } @@ -314,23 +326,42 @@ __archive_mktempx(const char *tmpdir, wchar_t *template) abort(); } +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RNG_ALGORITHM, + NULL, 0))) { + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } +#else if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { la_dosmaperr(GetLastError()); goto exit_tmpfile; } +#endif for (;;) { wchar_t *p; HANDLE h; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif /* Generate a random file name through CryptGenRandom(). */ p = xp; +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + if (!BCRYPT_SUCCESS(BCryptGenRandom(hAlg, (PUCHAR)p, + (DWORD)(ep - p)*sizeof(wchar_t), 0))) { + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } +#else if (!CryptGenRandom(hProv, (DWORD)(ep - p)*sizeof(wchar_t), (BYTE*)p)) { la_dosmaperr(GetLastError()); goto exit_tmpfile; } +#endif for (; p < ep; p++) *p = num[((DWORD)*p) % (sizeof(num)/sizeof(num[0]))]; @@ -347,6 +378,17 @@ __archive_mktempx(const char *tmpdir, wchar_t *template) /* mkstemp */ attr = FILE_ATTRIBUTE_NORMAL; } +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileAttributes = attr & 0xFFFF; + createExParams.dwFileFlags = attr & 0xFFF00000; + h = CreateFile2(ws, + GENERIC_READ | GENERIC_WRITE | DELETE, + 0,/* Not share */ + CREATE_NEW, + &createExParams); +#else h = CreateFileW(ws, GENERIC_READ | GENERIC_WRITE | DELETE, 0,/* Not share */ @@ -354,6 +396,7 @@ __archive_mktempx(const char *tmpdir, wchar_t *template) CREATE_NEW,/* Create a new file only */ attr, NULL); +#endif if (h == INVALID_HANDLE_VALUE) { /* The same file already exists. retry with * a new filename. */ @@ -372,8 +415,13 @@ __archive_mktempx(const char *tmpdir, wchar_t *template) break;/* success! */ } exit_tmpfile: +#if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + if (hAlg != NULL) + BCryptCloseAlgorithmProvider(hAlg, 0); +#else if (hProv != (HCRYPTPROV)NULL) CryptReleaseContext(hProv, 0); +#endif free(ws); if (template == temp_name.s) archive_wstring_free(&temp_name); @@ -407,7 +455,7 @@ get_tempdir(struct archive_string *temppath) tmp = "/tmp"; #endif archive_strcpy(temppath, tmp); - if (temppath->s[temppath->length-1] != '/') + if (temppath->length == 0 || temppath->s[temppath->length-1] != '/') archive_strappend_char(temppath, '/'); return (ARCHIVE_OK); } @@ -430,7 +478,8 @@ __archive_mktemp(const char *tmpdir) goto exit_tmpfile; } else { archive_strcpy(&temp_name, tmpdir); - if (temp_name.s[temp_name.length-1] != '/') + if (temp_name.length == 0 || + temp_name.s[temp_name.length-1] != '/') archive_strappend_char(&temp_name, '/'); } #ifdef O_TMPFILE @@ -491,7 +540,7 @@ __archive_mktempx(const char *tmpdir, char *template) goto exit_tmpfile; } else archive_strcpy(&temp_name, tmpdir); - if (temp_name.s[temp_name.length-1] == '/') { + if (temp_name.length > 0 && temp_name.s[temp_name.length-1] == '/') { temp_name.s[temp_name.length-1] = '\0'; temp_name.length --; } @@ -602,8 +651,7 @@ archive_utility_string_sort_helper(char **strings, unsigned int n) if (strcmp(strings[i], pivot) < 0) { lesser_count++; - tmp = (char **)realloc(lesser, - lesser_count * sizeof(char *)); + tmp = realloc(lesser, lesser_count * sizeof(*tmp)); if (!tmp) { free(greater); free(lesser); @@ -615,8 +663,7 @@ archive_utility_string_sort_helper(char **strings, unsigned int n) else { greater_count++; - tmp = (char **)realloc(greater, - greater_count * sizeof(char *)); + tmp = realloc(greater, greater_count * sizeof(*tmp)); if (!tmp) { free(greater); free(lesser); diff --git a/Utilities/cmlibarchive/libarchive/archive_version_details.c b/Utilities/cmlibarchive/libarchive/archive_version_details.c index 5f5a5b743c7..2d2b6ba510e 100644 --- a/Utilities/cmlibarchive/libarchive/archive_version_details.c +++ b/Utilities/cmlibarchive/libarchive/archive_version_details.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_util.c 201098 2009-12-28 02:58:14Z kientzle $"); #ifdef HAVE_STDLIB_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_virtual.c b/Utilities/cmlibarchive/libarchive/archive_virtual.c index f509ee5c672..97e0b8a0d7b 100644 --- a/Utilities/cmlibarchive/libarchive/archive_virtual.c +++ b/Utilities/cmlibarchive/libarchive/archive_virtual.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_virtual.c 201098 2009-12-28 02:58:14Z kientzle $"); #include "archive.h" #include "archive_entry.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_windows.c b/Utilities/cmlibarchive/libarchive/archive_windows.c index 624e270095d..bb540da011f 100644 --- a/Utilities/cmlibarchive/libarchive/archive_windows.c +++ b/Utilities/cmlibarchive/libarchive/archive_windows.c @@ -22,8 +22,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ /* @@ -234,7 +232,11 @@ la_CreateFile(const char *path, DWORD dwDesiredAccess, DWORD dwShareMode, { wchar_t *wpath; HANDLE handle; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif +#if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION (WINAPI_PARTITION_DESKTOP) handle = CreateFileA(path, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); @@ -242,12 +244,25 @@ la_CreateFile(const char *path, DWORD dwDesiredAccess, DWORD dwShareMode, return (handle); if (GetLastError() != ERROR_PATH_NOT_FOUND) return (handle); +#endif wpath = __la_win_permissive_name(path); if (wpath == NULL) - return (handle); + return INVALID_HANDLE_VALUE; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileAttributes = dwFlagsAndAttributes & 0xFFFF; + createExParams.dwFileFlags = dwFlagsAndAttributes & 0xFFF00000; + createExParams.dwSecurityQosFlags = dwFlagsAndAttributes & 0x000F00000; + createExParams.lpSecurityAttributes = lpSecurityAttributes; + createExParams.hTemplateFile = hTemplateFile; + handle = CreateFile2(wpath, dwDesiredAccess, dwShareMode, + dwCreationDisposition, &createExParams); +#else /* !WINAPI_PARTITION_DESKTOP */ handle = CreateFileW(wpath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); +#endif /* !WINAPI_PARTITION_DESKTOP */ free(wpath); return (handle); } @@ -305,7 +320,10 @@ __la_open(const char *path, int flags, ...) * "Permission denied" error. */ attr = GetFileAttributesA(path); - if (attr == (DWORD)-1 && GetLastError() == ERROR_PATH_NOT_FOUND) { +#if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION (WINAPI_PARTITION_DESKTOP) + if (attr == (DWORD)-1 && GetLastError() == ERROR_PATH_NOT_FOUND) +#endif + { ws = __la_win_permissive_name(path); if (ws == NULL) { errno = EINVAL; @@ -320,7 +338,7 @@ __la_open(const char *path, int flags, ...) } if (attr & FILE_ATTRIBUTE_DIRECTORY) { HANDLE handle; - +#if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION (WINAPI_PARTITION_DESKTOP) if (ws != NULL) handle = CreateFileW(ws, 0, 0, NULL, OPEN_EXISTING, @@ -333,6 +351,15 @@ __la_open(const char *path, int flags, ...) FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_READONLY, NULL); +#else /* !WINAPI_PARTITION_DESKTOP */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileAttributes = FILE_ATTRIBUTE_READONLY; + createExParams.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS; + handle = CreateFile2(ws, 0, 0, + OPEN_EXISTING, &createExParams); +#endif /* !WINAPI_PARTITION_DESKTOP */ free(ws); if (handle == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); diff --git a/Utilities/cmlibarchive/libarchive/archive_windows.h b/Utilities/cmlibarchive/libarchive/archive_windows.h index dda63b874da..b8b2ce96878 100644 --- a/Utilities/cmlibarchive/libarchive/archive_windows.h +++ b/Utilities/cmlibarchive/libarchive/archive_windows.h @@ -23,8 +23,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ /* @@ -300,12 +298,17 @@ typedef int mbstate_t; size_t wcrtomb(char *, wchar_t, mbstate_t *); #endif -#if defined(_MSC_VER) && _MSC_VER < 1300 +#if !WINAPI_FAMILY_PARTITION (WINAPI_PARTITION_DESKTOP) && NTDDI_VERSION < NTDDI_WIN10_VB +// not supported in UWP SDK before 20H1 +#define GetVolumePathNameW(f, v, c) (0) +#elif defined(_MSC_VER) && _MSC_VER < 1300 WINBASEAPI BOOL WINAPI GetVolumePathNameW( LPCWSTR lpszFileName, LPWSTR lpszVolumePathName, DWORD cchBufferLength ); +#endif +#if defined(_MSC_VER) && _MSC_VER < 1300 # if _WIN32_WINNT < 0x0500 /* windows.h not providing 0x500 API */ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { LARGE_INTEGER FileOffset; diff --git a/Utilities/cmlibarchive/libarchive/archive_write.3 b/Utilities/cmlibarchive/libarchive/archive_write.3 index e7f7f1384ee..227e4e02844 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_WRITE 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write.c b/Utilities/cmlibarchive/libarchive/archive_write.c index 27626b54147..303c686881a 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write.c +++ b/Utilities/cmlibarchive/libarchive/archive_write.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write.c 201099 2009-12-28 03:03:00Z kientzle $"); /* * This file contains the "essential" portions of the write API, that @@ -99,7 +98,7 @@ archive_write_new(void) struct archive_write *a; unsigned char *nulls; - a = (struct archive_write *)calloc(1, sizeof(*a)); + a = calloc(1, sizeof(*a)); if (a == NULL) return (NULL); a->archive.magic = ARCHIVE_WRITE_MAGIC; @@ -115,7 +114,7 @@ archive_write_new(void) /* Initialize a block of nulls for padding purposes. */ a->null_length = 1024; - nulls = (unsigned char *)calloc(1, a->null_length); + nulls = calloc(a->null_length, sizeof(unsigned char)); if (nulls == NULL) { free(a); return (NULL); @@ -133,12 +132,17 @@ archive_write_set_bytes_per_block(struct archive *_a, int bytes_per_block) struct archive_write *a = (struct archive_write *)_a; archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, "archive_write_set_bytes_per_block"); + + if (bytes_per_block < 0) { + // Do nothing if the bytes_per_block is negative + return 0; + } a->bytes_per_block = bytes_per_block; return (ARCHIVE_OK); } /* - * Get the current block size. -1 if it has never been set. + * Get the current block size. */ int archive_write_get_bytes_per_block(struct archive *_a) @@ -146,6 +150,10 @@ archive_write_get_bytes_per_block(struct archive *_a) struct archive_write *a = (struct archive_write *)_a; archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_ANY, "archive_write_get_bytes_per_block"); + if (a->bytes_per_block < 0) { + // Don't return a negative value + return 1; + } return (a->bytes_per_block); } @@ -310,6 +318,25 @@ __archive_write_output(struct archive_write *a, const void *buff, size_t length) return (__archive_write_filter(a->filter_first, buff, length)); } +static int +__archive_write_filters_flush(struct archive_write *a) +{ + struct archive_write_filter *f; + int ret, ret1; + + ret = ARCHIVE_OK; + for (f = a->filter_first; f != NULL; f = f->next_filter) { + if (f->flush != NULL && f->bytes_written > 0) { + ret1 = (f->flush)(f); + if (ret1 < ret) + ret = ret1; + if (ret1 < ARCHIVE_WARN) + f->state = ARCHIVE_WRITE_FILTER_STATE_FATAL; + } + } + return (ret); +} + int __archive_write_nulls(struct archive_write *a, size_t length) { @@ -340,8 +367,8 @@ archive_write_client_open(struct archive_write_filter *f) archive_write_get_bytes_in_last_block(f->archive); buffer_size = f->bytes_per_block; - state = (struct archive_none *)calloc(1, sizeof(*state)); - buffer = (char *)malloc(buffer_size); + state = calloc(1, sizeof(*state)); + buffer = malloc(buffer_size); if (state == NULL || buffer == NULL) { free(state); free(buffer); @@ -740,6 +767,18 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry) return (ARCHIVE_FAILED); } + /* Flush filters at boundary. */ + r2 = __archive_write_filters_flush(a); + if (r2 == ARCHIVE_FAILED) { + return (ARCHIVE_FAILED); + } + if (r2 == ARCHIVE_FATAL) { + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + if (r2 < ret) + ret = r2; + /* Format and write header. */ r2 = ((a->format_write_header)(a, entry)); if (r2 == ARCHIVE_FAILED) { diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter.c index 203f4142b5c..aa962515a04 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_TYPES_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_b64encode.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_b64encode.c index 87fdb73ecb0..dbedf9d305c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_b64encode.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_b64encode.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif @@ -85,9 +83,9 @@ archive_write_add_filter_b64encode(struct archive *_a) struct private_b64encode *state; archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, - ARCHIVE_STATE_NEW, "archive_write_add_filter_uu"); + ARCHIVE_STATE_NEW, "archive_write_add_filter_b64encode"); - state = (struct private_b64encode *)calloc(1, sizeof(*state)); + state = calloc(1, sizeof(*state)); if (state == NULL) { archive_set_error(f->archive, ENOMEM, "Can't allocate data for b64encode filter"); @@ -151,7 +149,7 @@ archive_filter_b64encode_open(struct archive_write_filter *f) size_t bs = 65536, bpb; if (f->archive->magic == ARCHIVE_WRITE_MAGIC) { - /* Buffer size should be a multiple number of the of bytes + /* Buffer size should be a multiple number of the bytes * per block for performance. */ bpb = archive_write_get_bytes_per_block(f->archive); if (bpb > bs) @@ -168,7 +166,7 @@ archive_filter_b64encode_open(struct archive_write_filter *f) } archive_string_sprintf(&state->encoded_buff, "begin-base64 %o %s\n", - state->mode, state->name.s); + (unsigned int)state->mode, state->name.s); f->data = state; return (0); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_by_name.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_by_name.c index ffa633c9637..fc62458c56e 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_by_name.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_by_name.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_TYPES_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_bzip2.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_bzip2.c index 0637e961437..ea9e9b09244 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_bzip2.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_bzip2.c @@ -26,8 +26,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_bzip2.c 201091 2009-12-28 02:22:41Z kientzle $"); - #ifdef HAVE_ERRNO_H #include #endif @@ -170,7 +168,7 @@ archive_compressor_bzip2_open(struct archive_write_filter *f) if (data->compressed == NULL) { size_t bs = 65536, bpb; if (f->archive->magic == ARCHIVE_WRITE_MAGIC) { - /* Buffer size should be a multiple number of the of bytes + /* Buffer size should be a multiple number of the bytes * per block for performance. */ bpb = archive_write_get_bytes_per_block(f->archive); if (bpb > bs) @@ -179,8 +177,7 @@ archive_compressor_bzip2_open(struct archive_write_filter *f) bs -= bs % bpb; } data->compressed_buffer_size = bs; - data->compressed - = (char *)malloc(data->compressed_buffer_size); + data->compressed = malloc(data->compressed_buffer_size); if (data->compressed == NULL) { archive_set_error(f->archive, ENOMEM, "Can't allocate data for compression buffer"); @@ -190,7 +187,7 @@ archive_compressor_bzip2_open(struct archive_write_filter *f) memset(&data->stream, 0, sizeof(data->stream)); data->stream.next_out = data->compressed; - data->stream.avail_out = data->compressed_buffer_size; + data->stream.avail_out = (uint32_t)data->compressed_buffer_size; f->write = archive_compressor_bzip2_write; /* Initialize compression library */ @@ -244,7 +241,7 @@ archive_compressor_bzip2_write(struct archive_write_filter *f, /* Compress input data to output buffer */ SET_NEXT_IN(data, buff); - data->stream.avail_in = length; + data->stream.avail_in = (uint32_t)length; if (drive_compressor(f, data, 0)) return (ARCHIVE_FATAL); return (ARCHIVE_OK); @@ -313,7 +310,7 @@ drive_compressor(struct archive_write_filter *f, return (ARCHIVE_FATAL); } data->stream.next_out = data->compressed; - data->stream.avail_out = data->compressed_buffer_size; + data->stream.avail_out = (uint32_t)data->compressed_buffer_size; } /* If there's nothing to do, we're done. */ diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_compress.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_compress.c index d404fae7dba..a54a8575429 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_compress.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_compress.c @@ -58,8 +58,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_compress.c 201111 2009-12-28 03:33:05Z kientzle $"); - #ifdef HAVE_ERRNO_H #include #endif @@ -152,7 +150,7 @@ archive_compressor_compress_open(struct archive_write_filter *f) f->code = ARCHIVE_FILTER_COMPRESS; f->name = "compress"; - state = (struct private_data *)calloc(1, sizeof(*state)); + state = calloc(1, sizeof(*state)); if (state == NULL) { archive_set_error(f->archive, ENOMEM, "Can't allocate data for compression"); @@ -160,7 +158,7 @@ archive_compressor_compress_open(struct archive_write_filter *f) } if (f->archive->magic == ARCHIVE_WRITE_MAGIC) { - /* Buffer size should be a multiple number of the of bytes + /* Buffer size should be a multiple number of the bytes * per block for performance. */ bpb = archive_write_get_bytes_per_block(f->archive); if (bpb > bs) @@ -352,7 +350,7 @@ archive_compressor_compress_write(struct archive_write_filter *f, while (length--) { c = *bp++; state->in_count++; - state->cur_fcode = (c << 16) + state->cur_code; + state->cur_fcode = (c << 16) | state->cur_code; i = ((c << HSHIFT) ^ state->cur_code); /* Xor hashing. */ if (state->hashtab[i] == state->cur_fcode) { diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_grzip.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_grzip.c index 371102d74c0..f8bb886061e 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_grzip.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_grzip.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_gzip.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_gzip.c index 3e26605ec1b..31d7e3053bf 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_gzip.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_gzip.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_gzip.c 201081 2009-12-28 02:04:42Z kientzle $"); - #ifdef HAVE_ERRNO_H #include #endif @@ -196,8 +194,7 @@ archive_compressor_gzip_open(struct archive_write_filter *f) bs -= bs % bpb; } data->compressed_buffer_size = bs; - data->compressed - = (unsigned char *)malloc(data->compressed_buffer_size); + data->compressed = malloc(data->compressed_buffer_size); if (data->compressed == NULL) { archive_set_error(f->archive, ENOMEM, "Can't allocate data for compression buffer"); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lrzip.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lrzip.c index e215f890325..fe974c93d5d 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lrzip.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lrzip.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lz4.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lz4.c index cf19fadd563..24061a16952 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lz4.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lz4.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif @@ -518,10 +516,10 @@ drive_compressor_independence(struct archive_write_filter *f, const char *p, } else { /* The buffer is not compressed. The compressed size was * bigger than its uncompressed size. */ - archive_le32enc(data->out, length | 0x80000000); + archive_le32enc(data->out, (uint32_t)(length | 0x80000000)); data->out += 4; memcpy(data->out, p, length); - outsize = length; + outsize = (uint32_t)length; } data->out += outsize; if (data->block_checksum) { @@ -603,10 +601,10 @@ drive_compressor_dependence(struct archive_write_filter *f, const char *p, } else { /* The buffer is not compressed. The compressed size was * bigger than its uncompressed size. */ - archive_le32enc(data->out, length | 0x80000000); + archive_le32enc(data->out, (uint32_t)(length | 0x80000000)); data->out += 4; memcpy(data->out, p, length); - outsize = length; + outsize = (uint32_t)length; } data->out += outsize; if (data->block_checksum) { diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lzop.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lzop.c index 3bd9062e4d3..8580e58844a 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lzop.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_lzop.c @@ -25,7 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); //#undef HAVE_LZO_LZOCONF_H //#undef HAVE_LZO_LZO1X_H diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_none.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_none.c index 3c06c642e73..b7aa6d4bcd3 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_none.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_none.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_none.c 201080 2009-12-28 02:03:54Z kientzle $"); #include "archive.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_program.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_program.c index c096e7227ba..c661cc7f412 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_program.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_program.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_program.c 201104 2009-12-28 03:14:30Z kientzle $"); #ifdef HAVE_SYS_WAIT_H # include diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_uuencode.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_uuencode.c index 1ad45892192..99c7a2cb7a7 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_uuencode.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_uuencode.c @@ -25,8 +25,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include #endif @@ -76,7 +74,7 @@ archive_write_add_filter_uuencode(struct archive *_a) archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, "archive_write_add_filter_uu"); - state = (struct private_uuencode *)calloc(1, sizeof(*state)); + state = calloc(1, sizeof(*state)); if (state == NULL) { archive_set_error(f->archive, ENOMEM, "Can't allocate data for uuencode filter"); @@ -157,7 +155,7 @@ archive_filter_uuencode_open(struct archive_write_filter *f) } archive_string_sprintf(&state->encoded_buff, "begin %o %s\n", - state->mode, state->name.s); + (unsigned int)state->mode, state->name.s); f->data = state; return (0); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_xz.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_xz.c index 0cc03b1d08a..dd8f31957cd 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_xz.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_xz.c @@ -26,8 +26,6 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_xz.c 201108 2009-12-28 03:28:21Z kientzle $"); - #ifdef HAVE_ERRNO_H #include #endif @@ -312,7 +310,7 @@ archive_compressor_xz_open(struct archive_write_filter *f) if (data->compressed == NULL) { size_t bs = 65536, bpb; if (f->archive->magic == ARCHIVE_WRITE_MAGIC) { - /* Buffer size should be a multiple number of the of bytes + /* Buffer size should be a multiple number of the bytes * per block for performance. */ bpb = archive_write_get_bytes_per_block(f->archive); if (bpb > bs) @@ -321,8 +319,7 @@ archive_compressor_xz_open(struct archive_write_filter *f) bs -= bs % bpb; } data->compressed_buffer_size = bs; - data->compressed - = (unsigned char *)malloc(data->compressed_buffer_size); + data->compressed = malloc(data->compressed_buffer_size); if (data->compressed == NULL) { archive_set_error(f->archive, ENOMEM, "Can't allocate data for compression buffer"); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_zstd.c b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_zstd.c index 7d36d587e55..ca421c98194 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_add_filter_zstd.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_add_filter_zstd.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 2017 Sean Purcell + * Copyright (c) 2023-2024 Klara, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,18 +26,24 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - - #ifdef HAVE_ERRNO_H #include #endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_STDINT_H +#include +#endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_ZSTD_H #include #endif @@ -50,10 +57,24 @@ __FBSDID("$FreeBSD$"); struct private_data { int compression_level; - int threads; -#if HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR + int threads; + int long_distance; +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream + enum { + running, + finishing, + resetting, + } state; + int frame_per_file; + size_t min_frame_in; + size_t max_frame_in; + size_t min_frame_out; + size_t max_frame_out; + size_t cur_frame; + size_t cur_frame_in; + size_t cur_frame_out; + size_t total_in; ZSTD_CStream *cstream; - int64_t total_in; ZSTD_outBuffer out; #else struct archive_write_program_data *pdata; @@ -67,17 +88,21 @@ struct private_data { #define CLEVEL_STD_MAX 19 /* without using --ultra */ #define CLEVEL_MAX 22 +#define LONG_STD 27 + #define MINVER_NEGCLEVEL 10304 #define MINVER_MINCLEVEL 10306 +#define MINVER_LONG 10302 static int archive_compressor_zstd_options(struct archive_write_filter *, const char *, const char *); static int archive_compressor_zstd_open(struct archive_write_filter *); static int archive_compressor_zstd_write(struct archive_write_filter *, const void *, size_t); +static int archive_compressor_zstd_flush(struct archive_write_filter *); static int archive_compressor_zstd_close(struct archive_write_filter *); static int archive_compressor_zstd_free(struct archive_write_filter *); -#if HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream static int drive_compressor(struct archive_write_filter *, struct private_data *, int, const void *, size_t); #endif @@ -103,13 +128,22 @@ archive_write_add_filter_zstd(struct archive *_a) f->data = data; f->open = &archive_compressor_zstd_open; f->options = &archive_compressor_zstd_options; + f->flush = &archive_compressor_zstd_flush; f->close = &archive_compressor_zstd_close; f->free = &archive_compressor_zstd_free; f->code = ARCHIVE_FILTER_ZSTD; f->name = "zstd"; data->compression_level = CLEVEL_DEFAULT; data->threads = 0; -#if HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR + data->long_distance = 0; +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream + data->frame_per_file = 0; + data->min_frame_in = 0; + data->max_frame_in = SIZE_MAX; + data->min_frame_out = 0; + data->max_frame_out = SIZE_MAX; + data->cur_frame_in = 0; + data->cur_frame_out = 0; data->cstream = ZSTD_createCStream(); if (data->cstream == NULL) { free(data); @@ -136,7 +170,7 @@ static int archive_compressor_zstd_free(struct archive_write_filter *f) { struct private_data *data = (struct private_data *)f->data; -#if HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream ZSTD_freeCStream(data->cstream); free(data->out.dst); #else @@ -147,30 +181,57 @@ archive_compressor_zstd_free(struct archive_write_filter *f) return (ARCHIVE_OK); } -static int string_is_numeric (const char* value) +static int +string_to_number(const char *string, intmax_t *numberp) +{ + char *end; + + if (string == NULL || *string == '\0') + return (ARCHIVE_WARN); + *numberp = strtoimax(string, &end, 10); + if (end == string || *end != '\0' || errno == EOVERFLOW) { + *numberp = 0; + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream +static int +string_to_size(const char *string, size_t *numberp) { - size_t len = strlen(value); - size_t i; - - if (len == 0) { - return (ARCHIVE_WARN); - } - else if (len == 1 && !(value[0] >= '0' && value[0] <= '9')) { - return (ARCHIVE_WARN); - } - else if (!(value[0] >= '0' && value[0] <= '9') && - value[0] != '-' && value[0] != '+') { - return (ARCHIVE_WARN); - } - - for (i = 1; i < len; i++) { - if (!(value[i] >= '0' && value[i] <= '9')) { - return (ARCHIVE_WARN); - } - } - - return (ARCHIVE_OK); + uintmax_t number; + char *end; + unsigned int shift = 0; + + if (string == NULL || *string == '\0' || *string == '-') + return (ARCHIVE_WARN); + number = strtoumax(string, &end, 10); + if (end > string) { + if (*end == 'K' || *end == 'k') { + shift = 10; + end++; + } else if (*end == 'M' || *end == 'm') { + shift = 20; + end++; + } else if (*end == 'G' || *end == 'g') { + shift = 30; + end++; + } + if (*end == 'B' || *end == 'b') { + end++; + } + } + if (end == string || *end != '\0' || errno == EOVERFLOW) { + return (ARCHIVE_WARN); + } + if (number > (uintmax_t)SIZE_MAX >> shift) { + return (ARCHIVE_WARN); + } + *numberp = (size_t)(number << shift); + return (ARCHIVE_OK); } +#endif /* * Set write options. @@ -182,14 +243,14 @@ archive_compressor_zstd_options(struct archive_write_filter *f, const char *key, struct private_data *data = (struct private_data *)f->data; if (strcmp(key, "compression-level") == 0) { - int level = atoi(value); + intmax_t level; + if (string_to_number(value, &level) != ARCHIVE_OK) { + return (ARCHIVE_WARN); + } /* If we don't have the library, hard-code the max level */ int minimum = CLEVEL_MIN; int maximum = CLEVEL_MAX; - if (string_is_numeric(value) != ARCHIVE_OK) { - return (ARCHIVE_WARN); - } -#if HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream maximum = ZSTD_maxCLevel(); #if ZSTD_VERSION_NUMBER >= MINVER_MINCLEVEL if (ZSTD_versionNumber() >= MINVER_MINCLEVEL) { @@ -204,21 +265,82 @@ archive_compressor_zstd_options(struct archive_write_filter *f, const char *key, if (level < minimum || level > maximum) { return (ARCHIVE_WARN); } - data->compression_level = level; + data->compression_level = (int)level; return (ARCHIVE_OK); } else if (strcmp(key, "threads") == 0) { - int threads = atoi(value); - if (string_is_numeric(value) != ARCHIVE_OK) { + intmax_t threads; + if (string_to_number(value, &threads) != ARCHIVE_OK) { return (ARCHIVE_WARN); } - int minimum = 0; - - if (threads < minimum) { +#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_ONLN) + if (threads == 0) { + threads = sysconf(_SC_NPROCESSORS_ONLN); + } +#elif !defined(__CYGWIN__) && defined(_WIN32_WINNT) && \ + _WIN32_WINNT >= 0x0601 /* _WIN32_WINNT_WIN7 */ + if (threads == 0) { + DWORD winCores = GetActiveProcessorCount( + ALL_PROCESSOR_GROUPS); + threads = (intmax_t)winCores; + } +#endif + if (threads < 0 || threads > INT_MAX) { return (ARCHIVE_WARN); } - - data->threads = threads; + data->threads = (int)threads; + return (ARCHIVE_OK); +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream + } else if (strcmp(key, "frame-per-file") == 0) { + data->frame_per_file = 1; + return (ARCHIVE_OK); + } else if (strcmp(key, "min-frame-in") == 0) { + if (string_to_size(value, &data->min_frame_in) != ARCHIVE_OK) { + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); + } else if (strcmp(key, "min-frame-out") == 0 || + strcmp(key, "min-frame-size") == 0) { + if (string_to_size(value, &data->min_frame_out) != ARCHIVE_OK) { + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); + } else if (strcmp(key, "max-frame-in") == 0 || + strcmp(key, "max-frame-size") == 0) { + if (string_to_size(value, &data->max_frame_in) != ARCHIVE_OK || + data->max_frame_in < 1024) { + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); + } else if (strcmp(key, "max-frame-out") == 0) { + if (string_to_size(value, &data->max_frame_out) != ARCHIVE_OK || + data->max_frame_out < 1024) { + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +#endif + } + else if (strcmp(key, "long") == 0) { + intmax_t long_distance; + if (string_to_number(value, &long_distance) != ARCHIVE_OK) { + return (ARCHIVE_WARN); + } +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream && ZSTD_VERSION_NUMBER >= MINVER_LONG + ZSTD_bounds bounds = ZSTD_cParam_getBounds(ZSTD_c_windowLog); + if (ZSTD_isError(bounds.error)) { + int max_distance = ((int)(sizeof(size_t) == 4 ? 30 : 31)); + if (((int)long_distance) < 10 || (int)long_distance > max_distance) + return (ARCHIVE_WARN); + } else { + if ((int)long_distance < bounds.lowerBound || (int)long_distance > bounds.upperBound) + return (ARCHIVE_WARN); + } +#else + int max_distance = ((int)(sizeof(size_t) == 4 ? 30 : 31)); + if (((int)long_distance) < 10 || (int)long_distance > max_distance) + return (ARCHIVE_WARN); +#endif + data->long_distance = (int)long_distance; return (ARCHIVE_OK); } @@ -228,7 +350,7 @@ archive_compressor_zstd_options(struct archive_write_filter *f, const char *key, return (ARCHIVE_WARN); } -#if HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR +#if HAVE_ZSTD_H && HAVE_ZSTD_compressStream /* * Setup callback. */ @@ -250,8 +372,7 @@ archive_compressor_zstd_open(struct archive_write_filter *f) } data->out.size = bs; data->out.pos = 0; - data->out.dst - = (unsigned char *)malloc(data->out.size); + data->out.dst = malloc(data->out.size); if (data->out.dst == NULL) { archive_set_error(f->archive, ENOMEM, "Can't allocate data for compression buffer"); @@ -270,6 +391,10 @@ archive_compressor_zstd_open(struct archive_write_filter *f) ZSTD_CCtx_setParameter(data->cstream, ZSTD_c_nbWorkers, data->threads); +#if ZSTD_VERSION_NUMBER >= MINVER_LONG + ZSTD_CCtx_setParameter(data->cstream, ZSTD_c_windowLog, data->long_distance); +#endif + return (ARCHIVE_OK); } @@ -281,15 +406,25 @@ archive_compressor_zstd_write(struct archive_write_filter *f, const void *buff, size_t length) { struct private_data *data = (struct private_data *)f->data; - int ret; - /* Update statistics */ - data->total_in += length; + return (drive_compressor(f, data, 0, buff, length)); +} - if ((ret = drive_compressor(f, data, 0, buff, length)) != ARCHIVE_OK) - return (ret); +/* + * Flush the compressed stream. + */ +static int +archive_compressor_zstd_flush(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; - return (ARCHIVE_OK); + if (data->frame_per_file && data->state == running) { + if (data->cur_frame_in > data->min_frame_in && + data->cur_frame_out > data->min_frame_out) { + data->state = finishing; + } + } + return (drive_compressor(f, data, 1, NULL, 0)); } /* @@ -300,60 +435,77 @@ archive_compressor_zstd_close(struct archive_write_filter *f) { struct private_data *data = (struct private_data *)f->data; - /* Finish zstd frame */ - return drive_compressor(f, data, 1, NULL, 0); + if (data->state == running) + data->state = finishing; + return (drive_compressor(f, data, 1, NULL, 0)); } /* * Utility function to push input data through compressor, * writing full output blocks as necessary. - * - * Note that this handles both the regular write case (finishing == - * false) and the end-of-archive case (finishing == true). */ static int drive_compressor(struct archive_write_filter *f, - struct private_data *data, int finishing, const void *src, size_t length) + struct private_data *data, int flush, const void *src, size_t length) { - ZSTD_inBuffer in = (ZSTD_inBuffer) { src, length, 0 }; + ZSTD_inBuffer in = { .src = src, .size = length, .pos = 0 }; + size_t ipos, opos, zstdret = 0; + int ret; for (;;) { - if (data->out.pos == data->out.size) { - const int ret = __archive_write_filter(f->next_filter, - data->out.dst, data->out.size); - if (ret != ARCHIVE_OK) - return (ARCHIVE_FATAL); - data->out.pos = 0; + ipos = in.pos; + opos = data->out.pos; + switch (data->state) { + case running: + if (in.pos == in.size) + return (ARCHIVE_OK); + zstdret = ZSTD_compressStream(data->cstream, + &data->out, &in); + if (ZSTD_isError(zstdret)) + goto zstd_fatal; + break; + case finishing: + zstdret = ZSTD_endStream(data->cstream, &data->out); + if (ZSTD_isError(zstdret)) + goto zstd_fatal; + if (zstdret == 0) + data->state = resetting; + break; + case resetting: + ZSTD_CCtx_reset(data->cstream, ZSTD_reset_session_only); + data->cur_frame++; + data->cur_frame_in = 0; + data->cur_frame_out = 0; + data->state = running; + break; } - - /* If there's nothing to do, we're done. */ - if (!finishing && in.pos == in.size) - return (ARCHIVE_OK); - - { - const size_t zstdret = !finishing ? - ZSTD_compressStream(data->cstream, &data->out, &in) - : ZSTD_endStream(data->cstream, &data->out); - - if (ZSTD_isError(zstdret)) { - archive_set_error(f->archive, - ARCHIVE_ERRNO_MISC, - "Zstd compression failed: %s", - ZSTD_getErrorName(zstdret)); - return (ARCHIVE_FATAL); - } - - /* If we're finishing, 0 means nothing left to flush */ - if (finishing && zstdret == 0) { - const int ret = __archive_write_filter(f->next_filter, - data->out.dst, data->out.pos); - return (ret); + data->total_in += in.pos - ipos; + data->cur_frame_in += in.pos - ipos; + data->cur_frame_out += data->out.pos - opos; + if (data->state == running) { + if (data->cur_frame_in >= data->max_frame_in || + data->cur_frame_out >= data->max_frame_out) { + data->state = finishing; } } + if (data->out.pos == data->out.size || + (flush && data->out.pos > 0)) { + ret = __archive_write_filter(f->next_filter, + data->out.dst, data->out.pos); + if (ret != ARCHIVE_OK) + goto fatal; + data->out.pos = 0; + } } +zstd_fatal: + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Zstd compression failed: %s", + ZSTD_getErrorName(zstdret)); +fatal: + return (ARCHIVE_FATAL); } -#else /* HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR */ +#else /* HAVE_ZSTD_H && HAVE_ZSTD_compressStream */ static int archive_compressor_zstd_open(struct archive_write_filter *f) @@ -367,17 +519,9 @@ archive_compressor_zstd_open(struct archive_write_filter *f) archive_strcpy(&as, "zstd --no-check"); if (data->compression_level < CLEVEL_STD_MIN) { - struct archive_string as2; - archive_string_init(&as2); - archive_string_sprintf(&as2, " --fast=%d", -data->compression_level); - archive_string_concat(&as, &as2); - archive_string_free(&as2); + archive_string_sprintf(&as, " --fast=%d", -data->compression_level); } else { - struct archive_string as2; - archive_string_init(&as2); - archive_string_sprintf(&as2, " -%d", data->compression_level); - archive_string_concat(&as, &as2); - archive_string_free(&as2); + archive_string_sprintf(&as, " -%d", data->compression_level); } if (data->compression_level > CLEVEL_STD_MAX) { @@ -385,11 +529,11 @@ archive_compressor_zstd_open(struct archive_write_filter *f) } if (data->threads != 0) { - struct archive_string as2; - archive_string_init(&as2); - archive_string_sprintf(&as2, " --threads=%d", data->threads); - archive_string_concat(&as, &as2); - archive_string_free(&as2); + archive_string_sprintf(&as, " --threads=%d", data->threads); + } + + if (data->long_distance != 0) { + archive_string_sprintf(&as, " --long=%d", data->long_distance); } f->write = archive_compressor_zstd_write; @@ -407,6 +551,14 @@ archive_compressor_zstd_write(struct archive_write_filter *f, const void *buff, return __archive_write_program_write(f, data->pdata, buff, length); } +static int +archive_compressor_zstd_flush(struct archive_write_filter *f) +{ + (void)f; /* UNUSED */ + + return (ARCHIVE_OK); +} + static int archive_compressor_zstd_close(struct archive_write_filter *f) { @@ -415,4 +567,4 @@ archive_compressor_zstd_close(struct archive_write_filter *f) return __archive_write_program_close(f, data->pdata); } -#endif /* HAVE_ZSTD_H && HAVE_LIBZSTD_COMPRESSOR */ +#endif /* HAVE_ZSTD_H && HAVE_ZSTD_compressStream */ diff --git a/Utilities/cmlibarchive/libarchive/archive_write_blocksize.3 b/Utilities/cmlibarchive/libarchive/archive_write_blocksize.3 index 4973f999056..3508851cefa 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_blocksize.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_blocksize.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_WRITE_BLOCKSIZE 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_data.3 b/Utilities/cmlibarchive/libarchive/archive_write_data.3 index bc208b45d53..9f239f7c9ec 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_data.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_data.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 28, 2017 .Dt ARCHIVE_WRITE_DATA 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk.3 b/Utilities/cmlibarchive/libarchive/archive_write_disk.3 index 97f3fcde9f8..b046168c338 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_disk.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_disk.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd January 19, 2020 .Dt ARCHIVE_WRITE_DISK 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c b/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c index bd5180e7637..3f4553cd022 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #if !defined(_WIN32) || defined(__CYGWIN__) @@ -397,6 +396,7 @@ static int set_times_from_entry(struct archive_write_disk *); static struct fixup_entry *sort_dir_list(struct fixup_entry *p); static ssize_t write_data_block(struct archive_write_disk *, const char *, size_t); +static void close_file_descriptor(struct archive_write_disk *); static int _archive_write_disk_close(struct archive *); static int _archive_write_disk_free(struct archive *); @@ -478,9 +478,11 @@ la_verify_filetype(mode_t mode, __LA_MODE_T filetype) { case AE_IFLNK: ret = (S_ISLNK(mode)); break; +#ifdef S_ISSOCK case AE_IFSOCK: ret = (S_ISSOCK(mode)); break; +#endif case AE_IFCHR: ret = (S_ISCHR(mode)); break; @@ -514,7 +516,12 @@ lazy_stat(struct archive_write_disk *a) * XXX At this point, symlinks should not be hit, otherwise * XXX a race occurred. Do we want to check explicitly for that? */ - if (lstat(a->name, &a->st) == 0) { +#ifdef HAVE_LSTAT + if (lstat(a->name, &a->st) == 0) +#else + if (la_stat(a->name, &a->st) == 0) +#endif + { a->pst = &a->st; return (ARCHIVE_OK); } @@ -1605,12 +1612,12 @@ hfs_write_data_block(struct archive_write_disk *a, const char *buff, "Seek failed"); return (ARCHIVE_FATAL); } else if (a->offset > a->fd_offset) { - int64_t skip = a->offset - a->fd_offset; + uint64_t skip = a->offset - a->fd_offset; char nullblock[1024]; memset(nullblock, 0, sizeof(nullblock)); while (skip > 0) { - if (skip > (int64_t)sizeof(nullblock)) + if (skip > sizeof(nullblock)) bytes_written = hfs_write_decmpfs_block( a, nullblock, sizeof(nullblock)); else @@ -1725,8 +1732,10 @@ _archive_write_disk_finish_entry(struct archive *_a) else r = hfs_write_data_block( a, null_d, a->file_remaining_bytes); - if (r < 0) + if (r < 0) { + close_file_descriptor(a); return ((int)r); + } } #endif } else { @@ -1735,6 +1744,7 @@ _archive_write_disk_finish_entry(struct archive *_a) a->filesize == 0) { archive_set_error(&a->archive, errno, "File size could not be restored"); + close_file_descriptor(a); return (ARCHIVE_FAILED); } #endif @@ -1744,8 +1754,10 @@ _archive_write_disk_finish_entry(struct archive *_a) * to see what happened. */ a->pst = NULL; - if ((ret = lazy_stat(a)) != ARCHIVE_OK) - return (ret); + if ((ret = lazy_stat(a)) != ARCHIVE_OK) { + close_file_descriptor(a); + return (ret); + } /* We can use lseek()/write() to extend the file if * ftruncate didn't work or isn't available. */ if (a->st.st_size < a->filesize) { @@ -1753,11 +1765,13 @@ _archive_write_disk_finish_entry(struct archive *_a) if (lseek(a->fd, a->filesize - 1, SEEK_SET) < 0) { archive_set_error(&a->archive, errno, "Seek failed"); + close_file_descriptor(a); return (ARCHIVE_FATAL); } if (write(a->fd, &nul, 1) < 0) { archive_set_error(&a->archive, errno, "Write to restore size failed"); + close_file_descriptor(a); return (ARCHIVE_FATAL); } a->pst = NULL; @@ -1979,7 +1993,7 @@ archive_write_disk_new(void) { struct archive_write_disk *a; - a = (struct archive_write_disk *)calloc(1, sizeof(*a)); + a = calloc(1, sizeof(*a)); if (a == NULL) return (NULL); a->archive.magic = ARCHIVE_WRITE_DISK_MAGIC; @@ -2154,7 +2168,11 @@ restore_entry(struct archive_write_disk *a) * then don't follow it. */ if (r != 0 || !S_ISDIR(a->mode)) +#ifdef HAVE_LSTAT r = lstat(a->name, &a->st); +#else + r = la_stat(a->name, &a->st); +#endif if (r != 0) { archive_set_error(&a->archive, errno, "Can't stat existing object"); @@ -2550,7 +2568,12 @@ _archive_write_disk_close(struct archive *_a) goto skip_fixup_entry; } else #endif - if (lstat(p->name, &st) != 0 || + if ( +#ifdef HAVE_LSTAT + lstat(p->name, &st) != 0 || +#else + la_stat(p->name, &st) != 0 || +#endif la_verify_filetype(st.st_mode, p->filetype) == 0) { goto skip_fixup_entry; @@ -2565,7 +2588,12 @@ _archive_write_disk_close(struct archive *_a) goto skip_fixup_entry; } else #endif - if (lstat(p->name, &st) != 0 || + if ( +#ifdef HAVE_LSTAT + lstat(p->name, &st) != 0 || +#else + la_stat(p->name, &st) != 0 || +#endif la_verify_filetype(st.st_mode, p->filetype) == 0) { goto skip_fixup_entry; @@ -2732,7 +2760,7 @@ new_fixup(struct archive_write_disk *a, const char *pathname) { struct fixup_entry *fe; - fe = (struct fixup_entry *)calloc(1, sizeof(struct fixup_entry)); + fe = calloc(1, sizeof(struct fixup_entry)); if (fe == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate memory for a fixup"); @@ -2785,8 +2813,8 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, !(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)) /* Platform doesn't have lstat, so we can't look for symlinks. */ (void)path; /* UNUSED */ - (void)error_number; /* UNUSED */ - (void)error_string; /* UNUSED */ + (void)a_eno; /* UNUSED */ + (void)a_estr; /* UNUSED */ (void)flags; /* UNUSED */ (void)checking_linkname; /* UNUSED */ return (ARCHIVE_OK); @@ -2859,8 +2887,10 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, /* Check that we haven't hit a symlink. */ #if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) r = fstatat(chdir_fd, head, &st, AT_SYMLINK_NOFOLLOW); -#else +#elif defined(HAVE_LSTAT) r = lstat(head, &st); +#else + r = la_stat(head, &st); #endif if (r != 0) { tail[0] = c; @@ -3558,7 +3588,9 @@ set_time(int fd, int mode, const char *name, (void)fd; /* UNUSED */ (void)mode; /* UNUSED */ (void)name; /* UNUSED */ + (void)atime; /* UNUSED */ (void)atime_nsec; /* UNUSED */ + (void)mtime; /* UNUSED */ (void)mtime_nsec; /* UNUSED */ return (ARCHIVE_WARN); #endif @@ -3575,7 +3607,7 @@ set_time_tru64(int fd, int mode, const char *name, tstamp.atime.tv_sec = atime; tstamp.mtime.tv_sec = mtime; tstamp.ctime.tv_sec = ctime; -#if defined (__hpux) && defined (__ia64) +#if defined (__hpux) && ( defined (__ia64) || defined (__hppa) ) tstamp.atime.tv_nsec = atime_nsec; tstamp.mtime.tv_nsec = mtime_nsec; tstamp.ctime.tv_nsec = ctime_nsec; @@ -3758,7 +3790,7 @@ set_mode(struct archive_write_disk *a, int mode) * permissions on symlinks, so a failure here has no * impact. */ - if (lchmod(a->name, mode) != 0) { + if (lchmod(a->name, (mode_t)mode) != 0) { switch (errno) { case ENOTSUP: case ENOSYS: @@ -3773,7 +3805,8 @@ set_mode(struct archive_write_disk *a, int mode) break; default: archive_set_error(&a->archive, errno, - "Can't set permissions to 0%o", (int)mode); + "Can't set permissions to 0%o", + (unsigned int)mode); r = ARCHIVE_WARN; } } @@ -3787,16 +3820,16 @@ set_mode(struct archive_write_disk *a, int mode) */ #ifdef HAVE_FCHMOD if (a->fd >= 0) - r2 = fchmod(a->fd, mode); + r2 = fchmod(a->fd, (mode_t)mode); else #endif /* If this platform lacks fchmod(), then * we'll just use chmod(). */ - r2 = chmod(a->name, mode); + r2 = chmod(a->name, (mode_t)mode); if (r2 != 0) { archive_set_error(&a->archive, errno, - "Can't set permissions to 0%o", (int)mode); + "Can't set permissions to 0%o", (unsigned int)mode); r = ARCHIVE_WARN; } } @@ -4166,7 +4199,7 @@ copy_xattrs(struct archive_write_disk *a, int tmpfd, int dffd) } for (xattr_i = 0; xattr_i < xattr_size; xattr_i += strlen(xattr_names + xattr_i) + 1) { - char *xattr_val_saved; + char *p; ssize_t s; int f; @@ -4177,15 +4210,14 @@ copy_xattrs(struct archive_write_disk *a, int tmpfd, int dffd) ret = ARCHIVE_WARN; goto exit_xattr; } - xattr_val_saved = xattr_val; - xattr_val = realloc(xattr_val, s); - if (xattr_val == NULL) { + p = realloc(xattr_val, s); + if (p == NULL) { archive_set_error(&a->archive, ENOMEM, "Failed to get metadata(xattr)"); ret = ARCHIVE_WARN; - free(xattr_val_saved); goto exit_xattr; } + xattr_val = p; s = fgetxattr(tmpfd, xattr_names + xattr_i, xattr_val, s, 0, 0); if (s == -1) { archive_set_error(&a->archive, errno, @@ -4331,8 +4363,7 @@ set_mac_metadata(struct archive_write_disk *a, const char *pathname, * silly dance of writing the data to disk just so that * copyfile() can read it back in again. */ archive_string_init(&tmp); - archive_strcpy(&tmp, pathname); - archive_strcat(&tmp, ".XXXXXX"); + archive_strcpy(&tmp, "tar.mmd.XXXXXX"); fd = mkstemp(tmp.s); if (fd < 0) { @@ -4391,8 +4422,14 @@ fixup_appledouble(struct archive_write_disk *a, const char *pathname) */ archive_strncpy(&datafork, pathname, p - pathname); archive_strcat(&datafork, p + 2); - if (lstat(datafork.s, &st) == -1 || - (st.st_mode & AE_IFMT) != AE_IFREG) + if ( +#ifdef HAVE_LSTAT + lstat(datafork.s, &st) == -1 || +#else + la_stat(datafork.s, &st) == -1 || +#endif + (((st.st_mode & AE_IFMT) != AE_IFREG) && + ((st.st_mode & AE_IFMT) != AE_IFDIR))) goto skip_appledouble; /* @@ -4707,5 +4744,17 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name, } #endif +/* + * Close the file descriptor if one is open. + */ +static void close_file_descriptor(struct archive_write_disk* a) +{ + if (a->fd >= 0) { + close(a->fd); + a->fd = -1; + } +} + + #endif /* !_WIN32 || __CYGWIN__ */ diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk_private.h b/Utilities/cmlibarchive/libarchive/archive_write_disk_private.h index 557d7e2bf34..3efe2bad336 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_disk_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_write_disk_private.h @@ -22,8 +22,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_write_disk_private.h 201086 2009-12-28 02:17:53Z kientzle $ */ #ifndef ARCHIVE_WRITE_DISK_PRIVATE_H_INCLUDED diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk_set_standard_lookup.c b/Utilities/cmlibarchive/libarchive/archive_write_disk_set_standard_lookup.c index 5fccdb9dc65..964169898e4 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_disk_set_standard_lookup.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_disk_set_standard_lookup.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_disk_set_standard_lookup.c 201083 2009-12-28 02:09:57Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c b/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c index 88df3ce020f..b42fc7d357b 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #if defined(_WIN32) && !defined(__CYGWIN__) @@ -254,9 +253,9 @@ static ssize_t _archive_write_disk_data_block(struct archive *, const void *, * which is high-16-bits of nFileIndexHigh. */ #define bhfi_ino(bhfi) \ ((((int64_t)((bhfi)->nFileIndexHigh & 0x0000FFFFUL)) << 32) \ - + (bhfi)->nFileIndexLow) + | (bhfi)->nFileIndexLow) #define bhfi_size(bhfi) \ - ((((int64_t)(bhfi)->nFileSizeHigh) << 32) + (bhfi)->nFileSizeLow) + ((((int64_t)(bhfi)->nFileSizeHigh) << 32) | (bhfi)->nFileSizeLow) static int file_information(struct archive_write_disk *a, wchar_t *path, @@ -266,6 +265,9 @@ file_information(struct archive_write_disk *a, wchar_t *path, int r; DWORD flag = FILE_FLAG_BACKUP_SEMANTICS; WIN32_FIND_DATAW findData; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif if (sim_lstat || mode != NULL) { h = FindFirstFileW(path, &findData); @@ -290,14 +292,27 @@ file_information(struct archive_write_disk *a, wchar_t *path, (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK))) flag |= FILE_FLAG_OPEN_REPARSE_POINT; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = flag; + h = CreateFile2(a->name, 0, 0, + OPEN_EXISTING, &createExParams); +#else h = CreateFileW(a->name, 0, 0, NULL, OPEN_EXISTING, flag, NULL); +#endif if (h == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_NAME) { wchar_t *full; full = __la_win_permissive_name_w(path); +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + h = CreateFile2(full, 0, 0, + OPEN_EXISTING, &createExParams); +#else h = CreateFileW(full, 0, 0, NULL, OPEN_EXISTING, flag, NULL); +#endif free(full); } if (h == INVALID_HANDLE_VALUE) { @@ -559,6 +574,7 @@ la_mktemp(struct archive_write_disk *a) return (fd); } +#if _WIN32_WINNT < _WIN32_WINNT_VISTA static void * la_GetFunctionKernel32(const char *name) { @@ -574,18 +590,24 @@ la_GetFunctionKernel32(const char *name) } return (void *)GetProcAddress(lib, name); } +#endif static int la_CreateHardLinkW(wchar_t *linkname, wchar_t *target) { - static BOOLEAN (WINAPI *f)(LPWSTR, LPWSTR, LPSECURITY_ATTRIBUTES); - static int set; + static BOOL (WINAPI *f)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); BOOL ret; +#if _WIN32_WINNT < _WIN32_WINNT_XP + static int set; +/* CreateHardLinkW is available since XP and always loaded */ if (!set) { set = 1; f = la_GetFunctionKernel32("CreateHardLinkW"); } +#else + f = CreateHardLinkW; +#endif if (!f) { errno = ENOTSUP; return (0); @@ -624,7 +646,6 @@ static int la_CreateSymbolicLinkW(const wchar_t *linkname, const wchar_t *target, int linktype) { static BOOLEAN (WINAPI *f)(LPCWSTR, LPCWSTR, DWORD); - static int set; wchar_t *ttarget, *p; size_t len; DWORD attrs = 0; @@ -632,10 +653,20 @@ la_CreateSymbolicLinkW(const wchar_t *linkname, const wchar_t *target, DWORD newflags = 0; BOOL ret = 0; +#if _WIN32_WINNT < _WIN32_WINNT_VISTA +/* CreateSymbolicLinkW is available since Vista and always loaded */ + static int set; if (!set) { set = 1; f = la_GetFunctionKernel32("CreateSymbolicLinkW"); } +#else +# if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + f = CreateSymbolicLinkW; +# else + f = NULL; +# endif +#endif if (!f) return (0); @@ -1185,6 +1216,8 @@ _archive_write_disk_finish_entry(struct archive *_a) if (la_ftruncate(a->fh, a->filesize) == -1) { archive_set_error(&a->archive, errno, "File size could not be restored"); + CloseHandle(a->fh); + a->fh = INVALID_HANDLE_VALUE; return (ARCHIVE_FAILED); } } @@ -1329,23 +1362,23 @@ archive_write_disk_set_user_lookup(struct archive *_a, int64_t archive_write_disk_gid(struct archive *_a, const char *name, la_int64_t id) { - struct archive_write_disk *a = (struct archive_write_disk *)_a; - archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, - ARCHIVE_STATE_ANY, "archive_write_disk_gid"); - if (a->lookup_gid) - return (a->lookup_gid)(a->lookup_gid_data, name, id); - return (id); + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_gid"); + if (a->lookup_gid) + return (a->lookup_gid)(a->lookup_gid_data, name, id); + return (id); } int64_t archive_write_disk_uid(struct archive *_a, const char *name, la_int64_t id) { - struct archive_write_disk *a = (struct archive_write_disk *)_a; - archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, - ARCHIVE_STATE_ANY, "archive_write_disk_uid"); - if (a->lookup_uid) - return (a->lookup_uid)(a->lookup_uid_data, name, id); - return (id); + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_uid"); + if (a->lookup_uid) + return (a->lookup_uid)(a->lookup_uid_data, name, id); + return (id); } /* @@ -1356,7 +1389,7 @@ archive_write_disk_new(void) { struct archive_write_disk *a; - a = (struct archive_write_disk *)calloc(1, sizeof(*a)); + a = calloc(1, sizeof(*a)); if (a == NULL) return (NULL); a->archive.magic = ARCHIVE_WRITE_DISK_MAGIC; @@ -1656,6 +1689,9 @@ create_filesystem_object(struct archive_write_disk *a) mode_t final_mode, mode; int r; DWORD attrs = 0; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif /* We identify hard/symlinks according to the link names. */ /* Since link(2) and symlink(2) don't handle modes, we're done here. */ @@ -1719,8 +1755,16 @@ create_filesystem_object(struct archive_write_disk *a) a->todo = 0; a->deferred = 0; } else if (r == 0 && a->filesize > 0) { +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + a->fh = CreateFile2(namefull, GENERIC_WRITE, 0, + TRUNCATE_EXISTING, &createExParams); +#else a->fh = CreateFileW(namefull, GENERIC_WRITE, 0, NULL, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif if (a->fh == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); r = errno; @@ -1783,14 +1827,27 @@ create_filesystem_object(struct archive_write_disk *a) a->tmpname = NULL; fullname = a->name; /* O_WRONLY | O_CREAT | O_EXCL */ +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + a->fh = CreateFile2(fullname, GENERIC_WRITE, 0, + CREATE_NEW, &createExParams); +#else a->fh = CreateFileW(fullname, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); +#endif if (a->fh == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_NAME && fullname == a->name) { fullname = __la_win_permissive_name_w(a->name); +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + a->fh = CreateFile2(fullname, GENERIC_WRITE, 0, + CREATE_NEW, &createExParams); +#else a->fh = CreateFileW(fullname, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); +#endif } if (a->fh == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_ACCESS_DENIED) { @@ -2022,7 +2079,7 @@ new_fixup(struct archive_write_disk *a, const wchar_t *pathname) { struct fixup_entry *fe; - fe = (struct fixup_entry *)calloc(1, sizeof(struct fixup_entry)); + fe = calloc(1, sizeof(struct fixup_entry)); if (fe == NULL) return (NULL); fe->next = a->fixup_list; @@ -2186,13 +2243,15 @@ guidword(wchar_t *p, int n) * Canonicalize the pathname. In particular, this strips duplicate * '\' characters, '.' elements, and trailing '\'. It also raises an * error for an empty path, a trailing '..' or (if _SECURE_NODOTDOT is - * set) any '..' in the path. + * set) any '..' in the path or (if ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS) + * if the path is absolute. */ static int cleanup_pathname(struct archive_write_disk *a, wchar_t *name) { wchar_t *dest, *src, *p, *top; wchar_t separator = L'\0'; + BOOL absolute_path = 0; p = name; if (*p == L'\0') { @@ -2214,6 +2273,8 @@ cleanup_pathname(struct archive_write_disk *a, wchar_t *name) if (p[0] == L'\\' && p[1] == L'\\' && (p[2] == L'.' || p[2] == L'?') && p[3] == L'\\') { + absolute_path = 1; + /* A path begin with "\\?\UNC\" */ if (p[2] == L'?' && (p[4] == L'U' || p[4] == L'u') && @@ -2261,9 +2322,10 @@ cleanup_pathname(struct archive_write_disk *a, wchar_t *name) return (ARCHIVE_FAILED); } else p += 4; - /* Network drive path like "\\\\file" */ - } else if (p[0] == L'\\' && p[1] == L'\\') { - p += 2; + /* Network drive path like "\\\\file" */ + } else if (p[0] == L'\\' && p[1] == L'\\') { + absolute_path = 1; + p += 2; } /* Skip leading drive letter from archives created @@ -2276,10 +2338,18 @@ cleanup_pathname(struct archive_write_disk *a, wchar_t *name) "Path is a drive name"); return (ARCHIVE_FAILED); } + + absolute_path = 1; + if (p[2] == L'\\') p += 2; } + if (absolute_path && (a->flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Path is absolute"); + return (ARCHIVE_FAILED); + } + top = dest = src = p; /* Rewrite the path name if its character is a unusable. */ for (; *p != L'\0'; p++) { @@ -2536,8 +2606,8 @@ set_times(struct archive_write_disk *a, time_t ctime_sec, long ctime_nanos) { #define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) -#define WINTIME(sec, nsec) ((Int32x32To64(sec, 10000000) + EPOC_TIME)\ - + (((nsec)/1000)*10)) +#define WINTIME(sec, nsec) (((sec * 10000000LL) + EPOC_TIME)\ + + ((nsec)/100)) HANDLE hw = 0; ULARGE_INTEGER wintm; @@ -2551,14 +2621,25 @@ set_times(struct archive_write_disk *a, hw = NULL; } else { wchar_t *ws; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + CREATEFILE2_EXTENDED_PARAMETERS createExParams; +#endif if (S_ISLNK(mode)) return (ARCHIVE_OK); ws = __la_win_permissive_name_w(name); if (ws == NULL) goto settimes_failed; +# if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ + ZeroMemory(&createExParams, sizeof(createExParams)); + createExParams.dwSize = sizeof(createExParams); + createExParams.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS; + hw = CreateFile2(ws, FILE_WRITE_ATTRIBUTES, 0, + OPEN_EXISTING, &createExParams); +#else hw = CreateFileW(ws, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); +#endif free(ws); if (hw == INVALID_HANDLE_VALUE) goto settimes_failed; @@ -2761,7 +2842,7 @@ set_fflags(struct archive_write_disk *a) return (ARCHIVE_OK); return (set_fflags_platform(a->name, set, clear)); - } + } return (ARCHIVE_OK); } diff --git a/Utilities/cmlibarchive/libarchive/archive_write_filter.3 b/Utilities/cmlibarchive/libarchive/archive_write_filter.3 index c83eb77b6a5..b39cabe0478 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_filter.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_filter.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd August 14, 2014 .Dt ARCHIVE_WRITE_FILTER 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_finish_entry.3 b/Utilities/cmlibarchive/libarchive/archive_write_finish_entry.3 index 5797e16a6db..574d6008581 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_finish_entry.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_finish_entry.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 28, 2017 .Dt ARCHIVE_WRITE_FINISH_ENTRY 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_format.3 b/Utilities/cmlibarchive/libarchive/archive_write_format.3 index 653089f7795..9e331368aee 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_format.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_format.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 14, 2013 .Dt ARCHIVE_WRITE_FORMAT 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_free.3 b/Utilities/cmlibarchive/libarchive/archive_write_free.3 index 5210e2a633d..f6b84eae918 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_free.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_free.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_WRITE_FREE 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_header.3 b/Utilities/cmlibarchive/libarchive/archive_write_header.3 index 2217b1871bb..9c6ecec4e09 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_header.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_header.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_WRITE_HEADER 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_new.3 b/Utilities/cmlibarchive/libarchive/archive_write_new.3 index 788cbb85598..15a7c40703b 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_new.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_new.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd February 2, 2012 .Dt ARCHIVE_WRITE_NEW 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_open.3 b/Utilities/cmlibarchive/libarchive/archive_write_open.3 index 6bceb964f58..b12d097028c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_open.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_open.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd November 12, 2020 .Dt ARCHIVE_WRITE_OPEN 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_open_fd.c b/Utilities/cmlibarchive/libarchive/archive_write_open_fd.c index b8d491faa27..8a3f68d0699 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_open_fd.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_open_fd.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_open_fd.c 201093 2009-12-28 02:28:44Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -63,7 +62,7 @@ archive_write_open_fd(struct archive *a, int fd) { struct write_fd_data *mine; - mine = (struct write_fd_data *)malloc(sizeof(*mine)); + mine = malloc(sizeof(*mine)); if (mine == NULL) { archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_open_file.c b/Utilities/cmlibarchive/libarchive/archive_write_open_file.c index bf5b55a672e..4c6ebfb2269 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_open_file.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_open_file.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_file.c,v 1.19 2007/01/09 08:05:56 kientzle Exp $"); #ifdef HAVE_SYS_STAT_H #include @@ -60,7 +59,7 @@ archive_write_open_FILE(struct archive *a, FILE *f) { struct write_FILE_data *mine; - mine = (struct write_FILE_data *)malloc(sizeof(*mine)); + mine = malloc(sizeof(*mine)); if (mine == NULL) { archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_open_filename.c b/Utilities/cmlibarchive/libarchive/archive_write_open_filename.c index 9ceefb19bc9..34209426558 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_open_filename.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_open_filename.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_open_filename.c 191165 2009-04-17 00:39:35Z kientzle $"); #ifdef HAVE_SYS_STAT_H #include @@ -99,7 +98,7 @@ open_filename(struct archive *a, int mbs_fn, const void *filename) struct write_file_data *mine; int r; - mine = (struct write_file_data *)calloc(1, sizeof(*mine)); + mine = calloc(1, sizeof(*mine)); if (mine == NULL) { archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); @@ -119,7 +118,7 @@ open_filename(struct archive *a, int mbs_fn, const void *filename) (const char *)filename); else archive_set_error(a, ARCHIVE_ERRNO_MISC, - "Can't convert '%S' to MBS", + "Can't convert '%ls' to MBS", (const wchar_t *)filename); return (ARCHIVE_FAILED); } @@ -171,7 +170,7 @@ file_open(struct archive *a, void *client_data) else { archive_mstring_get_wcs(a, &mine->filename, &wcs); archive_set_error(a, errno, - "Can't convert '%S' to MBS", wcs); + "Can't convert '%ls' to MBS", wcs); } return (ARCHIVE_FATAL); } @@ -182,7 +181,7 @@ file_open(struct archive *a, void *client_data) if (mbs != NULL) archive_set_error(a, errno, "Failed to open '%s'", mbs); else - archive_set_error(a, errno, "Failed to open '%S'", wcs); + archive_set_error(a, errno, "Failed to open '%ls'", wcs); return (ARCHIVE_FATAL); } @@ -190,7 +189,7 @@ file_open(struct archive *a, void *client_data) if (mbs != NULL) archive_set_error(a, errno, "Couldn't stat '%s'", mbs); else - archive_set_error(a, errno, "Couldn't stat '%S'", wcs); + archive_set_error(a, errno, "Couldn't stat '%ls'", wcs); return (ARCHIVE_FATAL); } diff --git a/Utilities/cmlibarchive/libarchive/archive_write_open_memory.c b/Utilities/cmlibarchive/libarchive/archive_write_open_memory.c index a8a0b817fc2..e3165044727 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_open_memory.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_open_memory.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_memory.c,v 1.3 2007/01/09 08:05:56 kientzle Exp $"); #include #include @@ -53,7 +52,7 @@ archive_write_open_memory(struct archive *a, void *buff, size_t buffSize, size_t { struct write_memory_data *mine; - mine = (struct write_memory_data *)calloc(1, sizeof(*mine)); + mine = calloc(1, sizeof(*mine)); if (mine == NULL) { archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_private.h b/Utilities/cmlibarchive/libarchive/archive_write_private.h index 155fdd73488..f259ccb1654 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_write_private.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/archive_write_private.h 201155 2009-12-29 05:20:12Z kientzle $ */ #ifndef ARCHIVE_WRITE_PRIVATE_H_INCLUDED @@ -53,6 +51,7 @@ struct archive_write_filter { const char *key, const char *value); int (*open)(struct archive_write_filter *); int (*write)(struct archive_write_filter *, const void *, size_t); + int (*flush)(struct archive_write_filter *); int (*close)(struct archive_write_filter *); int (*free)(struct archive_write_filter *); void *data; @@ -159,7 +158,7 @@ int __archive_write_program_write(struct archive_write_filter *, struct archive_write_program_data *, const void *, size_t); /* - * Get a encryption passphrase. + * Get an encryption passphrase. */ const char * __archive_write_get_passphrase(struct archive_write *a); #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format.c index 1f65fa4a77e..f636cff7471 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format.c 201168 2009-12-29 06:15:32Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_7zip.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_7zip.c index 87b35864ec1..a725ade78e3 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_7zip.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_7zip.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -91,6 +90,26 @@ __FBSDID("$FreeBSD$"); #define kAttributes 0x15 #define kEncodedHeader 0x17 +// Check that some windows file attribute constants are defined. +// Reference: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants +#ifndef FILE_ATTRIBUTE_READONLY +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#endif + +#ifndef FILE_ATTRIBUTE_DIRECTORY +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#endif + +#ifndef FILE_ATTRIBUTE_ARCHIVE +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020 +#endif + +// This value is defined in 7zip with the comment "trick for Unix". +// +// 7z archives created on unix have this bit set in the high 16 bits of +// the attr field along with the unix permissions. +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 + enum la_zaction { ARCHIVE_Z_FINISH, ARCHIVE_Z_RUN @@ -165,7 +184,7 @@ struct file { mode_t mode; uint32_t crc32; - signed int dir:1; + unsigned dir:1; }; struct _7zip { @@ -502,7 +521,7 @@ _7z_write_header(struct archive_write *a, struct archive_entry *entry) */ if (archive_entry_filetype(entry) == AE_IFLNK) { ssize_t bytes; - const void *p = (const void *)archive_entry_symlink(entry); + const void *p = (const void *)archive_entry_symlink_utf8(entry); bytes = compress_out(a, p, (size_t)file->size, ARCHIVE_Z_RUN); if (bytes < 0) return ((int)bytes); @@ -1424,14 +1443,19 @@ make_header(struct archive_write *a, uint64_t offset, uint64_t pack_size, * High 16bits is unix mode. * Low 16bits is Windows attributes. */ - uint32_t encattr, attr; + uint32_t encattr, attr = 0; + if (file->dir) - attr = 0x8010; + attr |= FILE_ATTRIBUTE_DIRECTORY; else - attr = 0x8020; + attr |= FILE_ATTRIBUTE_ARCHIVE; + if ((file->mode & 0222) == 0) - attr |= 1;/* Read Only. */ + attr |= FILE_ATTRIBUTE_READONLY; + + attr |= FILE_ATTRIBUTE_UNIX_EXTENSION; attr |= ((uint32_t)file->mode) << 16; + archive_le32enc(&encattr, attr); r = (int)compress_out(a, &encattr, 4, ARCHIVE_Z_RUN); if (r < 0) @@ -1539,8 +1563,18 @@ file_new(struct archive_write *a, struct archive_entry *entry, archive_entry_set_size(entry, 0); if (archive_entry_filetype(entry) == AE_IFDIR) file->dir = 1; - else if (archive_entry_filetype(entry) == AE_IFLNK) - file->size = strlen(archive_entry_symlink(entry)); + else if (archive_entry_filetype(entry) == AE_IFLNK) { + const char* linkpath; + linkpath = archive_entry_symlink_utf8(entry); + if (linkpath == NULL) { + free(file); + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "symlink path could not be converted to UTF-8"); + return (ARCHIVE_FAILED); + } + else + file->size = strlen(linkpath); + } if (archive_entry_mtime_is_set(entry)) { file->flg |= MTIME_IS_SET; file->times[MTIME].time = archive_entry_mtime(entry); @@ -1809,11 +1843,11 @@ compression_init_encoder_bzip2(struct archive *a, * of ugly hackery to convert a const * pointer to * a non-const pointer. */ strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; - strm->avail_in = lastrm->avail_in; + strm->avail_in = (uint32_t)lastrm->avail_in; strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); strm->next_out = (char *)lastrm->next_out; - strm->avail_out = lastrm->avail_out; + strm->avail_out = (uint32_t)lastrm->avail_out; strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); if (BZ2_bzCompressInit(strm, level, 0, 30) != BZ_OK) { @@ -1842,11 +1876,11 @@ compression_code_bzip2(struct archive *a, * of ugly hackery to convert a const * pointer to * a non-const pointer. */ strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; - strm->avail_in = lastrm->avail_in; + strm->avail_in = (uint32_t)lastrm->avail_in; strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); strm->next_out = (char *)lastrm->next_out; - strm->avail_out = lastrm->avail_out; + strm->avail_out = (uint32_t)lastrm->avail_out; strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); r = BZ2_bzCompress(strm, diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_ar.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_ar.c index fc0de1e9f6f..e5e9c806b63 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_ar.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_ar.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_ar.c 201108 2009-12-28 03:28:21Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -127,7 +126,7 @@ archive_write_set_format_ar(struct archive_write *a) if (a->format_free != NULL) (a->format_free)(a); - ar = (struct ar_w *)calloc(1, sizeof(*ar)); + ar = calloc(1, sizeof(*ar)); if (ar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate ar data"); return (ARCHIVE_FATAL); @@ -247,7 +246,7 @@ archive_write_ar_header(struct archive_write *a, struct archive_entry *entry) return (ARCHIVE_WARN); } - se = (char *)malloc(strlen(filename) + 3); + se = malloc(strlen(filename) + 3); if (se == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate filename buffer"); @@ -380,7 +379,7 @@ archive_write_ar_data(struct archive_write *a, const void *buff, size_t s) return (ARCHIVE_WARN); } - ar->strtab = (char *)malloc(s + 1); + ar->strtab = malloc(s + 1); if (ar->strtab == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate strtab buffer"); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_by_name.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_by_name.c index bfb4b3545f2..09519b12389 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_by_name.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_by_name.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_by_name.c 201168 2009-12-29 06:15:32Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_binary.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_binary.c index d6ce35a7bc1..aefb2ca6f51 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_binary.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_binary.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_cpio.c 201170 2009-12-29 06:34:23Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -186,7 +185,7 @@ archive_write_set_format_cpio_binary(struct archive *_a, int format) if (a->format_free != NULL) (a->format_free)(a); - cpio = (struct cpio *)calloc(1, sizeof(*cpio)); + cpio = calloc(1, sizeof(*cpio)); if (cpio == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); return (ARCHIVE_FATAL); @@ -578,6 +577,9 @@ archive_write_binary_close(struct archive_write *a) struct archive_entry *trailer; trailer = archive_entry_new2(NULL); + if (trailer == NULL) { + return ARCHIVE_FATAL; + } /* nlink = 1 here for GNU cpio compat. */ archive_entry_set_nlink(trailer, 1); archive_entry_set_size(trailer, 0); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_newc.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_newc.c index f0f39809dad..254d5a9901c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_newc.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_newc.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_cpio_newc.c 201160 2009-12-29 05:41:57Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -117,7 +116,7 @@ archive_write_set_format_cpio_newc(struct archive *_a) if (a->format_free != NULL) (a->format_free)(a); - cpio = (struct cpio *)calloc(1, sizeof(*cpio)); + cpio = calloc(1, sizeof(*cpio)); if (cpio == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); return (ARCHIVE_FATAL); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_odc.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_odc.c index 091925a2f9f..c72c6b2796b 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_odc.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_cpio_odc.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_cpio.c 201170 2009-12-29 06:34:23Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -111,7 +110,7 @@ archive_write_set_format_cpio_odc(struct archive *_a) if (a->format_free != NULL) (a->format_free)(a); - cpio = (struct cpio *)calloc(1, sizeof(*cpio)); + cpio = calloc(1, sizeof(*cpio)); if (cpio == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); return (ARCHIVE_FATAL); @@ -468,6 +467,9 @@ archive_write_odc_close(struct archive_write *a) struct archive_entry *trailer; trailer = archive_entry_new2(NULL); + if (trailer == NULL) { + return ARCHIVE_FATAL; + } /* nlink = 1 here for GNU cpio compat. */ archive_entry_set_nlink(trailer, 1); archive_entry_set_size(trailer, 0); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_filter_by_ext.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_filter_by_ext.c index 9fe21e4542a..1bb33b04bf9 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_filter_by_ext.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_filter_by_ext.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_by_name.c 201168 2009-12-29 06:15:32Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_gnutar.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_gnutar.c index ec29c5c418e..04b190de440 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_gnutar.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_gnutar.c @@ -27,8 +27,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_gnu_tar.c 191579 2009-04-27 18:35:03Z gastal $"); - #ifdef HAVE_ERRNO_H #include @@ -176,7 +174,7 @@ archive_write_set_format_gnutar(struct archive *_a) struct archive_write *a = (struct archive_write *)_a; struct gnutar *gnutar; - gnutar = (struct gnutar *)calloc(1, sizeof(*gnutar)); + gnutar = calloc(1, sizeof(*gnutar)); if (gnutar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate gnutar data"); @@ -298,7 +296,7 @@ archive_write_gnutar_header(struct archive_write *a, /* Only regular files (not hardlinks) have data. */ if (archive_entry_hardlink(entry) != NULL || archive_entry_symlink(entry) != NULL || - !(archive_entry_filetype(entry) == AE_IFREG)) + archive_entry_filetype(entry) != AE_IFREG) archive_entry_set_size(entry, 0); if (AE_IFDIR == archive_entry_filetype(entry)) { @@ -389,7 +387,7 @@ archive_write_gnutar_header(struct archive_write *a, if (r != 0) { if (errno == ENOMEM) { archive_set_error(&a->archive, ENOMEM, - "Can't allocate memory for Pathame"); + "Can't allocate memory for pathname"); ret = ARCHIVE_FATAL; goto exit_write_header; } @@ -525,7 +523,7 @@ archive_write_gnutar_header(struct archive_write *a, goto exit_write_header; } - if (archive_entry_hardlink(entry) != NULL) { + if (archive_entry_hardlink_is_set(entry)) { tartype = '1'; } else switch (archive_entry_filetype(entry)) { diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c index ebd33c5160c..89c4a695586 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c @@ -293,12 +293,12 @@ struct isoent { struct extr_rec *current; } extr_rec_list; - signed int virtual:1; + unsigned int virtual:1; /* If set to one, this file type is a directory. * A convenience flag to be used as * "archive_entry_filetype(isoent->file->entry) == AE_IFDIR". */ - signed int dir:1; + unsigned int dir:1; }; struct hardlink { @@ -656,7 +656,7 @@ struct iso_option { #define VOLUME_IDENTIFIER_SIZE 32 /* - * Usage : !zisofs [DEFAULT] + * Usage : !zisofs [DEFAULT] * : Disable to generate RRIP 'ZF' extension. * : zisofs * : Make files zisofs file and generate RRIP 'ZF' @@ -693,7 +693,7 @@ struct iso9660 { uint64_t bytes_remaining; int need_multi_extent; - /* Temporary string buffer for Joliet extension. */ + /* Temporary string buffer for Joliet extension. */ struct archive_string utf16be; struct archive_string mbs; @@ -759,9 +759,9 @@ struct iso9660 { /* Used for making zisofs. */ struct { - signed int detect_magic:1; - signed int making:1; - signed int allzero:1; + unsigned int detect_magic:1; + unsigned int making:1; + unsigned int allzero:1; unsigned char magic_buffer[64]; int magic_cnt; @@ -915,7 +915,7 @@ static int iso9660_finish_entry(struct archive_write *); static int iso9660_close(struct archive_write *); static int iso9660_free(struct archive_write *); -static void get_system_identitier(char *, size_t); +static void get_system_identifier(char *, size_t); static void set_str(unsigned char *, const char *, size_t, char, const char *); static inline int joliet_allowed_char(unsigned char, unsigned char); @@ -2170,7 +2170,7 @@ iso9660_free(struct archive_write *a) * Get the System Identifier */ static void -get_system_identitier(char *system_id, size_t size) +get_system_identifier(char *system_id, size_t size) { #if defined(HAVE_SYS_UTSNAME_H) struct utsname u; @@ -2241,7 +2241,7 @@ set_str_utf16be(struct archive_write *a, unsigned char *p, const char *s, int onepad; if (s == NULL) - s = ""; + s = "\0\0"; if (l & 0x01) { onepad = 1; l &= ~1; @@ -2525,12 +2525,11 @@ get_gmoffset(struct tm *tm) static void get_tmfromtime(struct tm *tm, time_t *t) { -#if HAVE_LOCALTIME_R +#if HAVE_LOCALTIME_S + localtime_s(tm, t); +#elif HAVE_LOCALTIME_R tzset(); localtime_r(t, tm); -#elif HAVE__LOCALTIME64_S - __time64_t tmp_t = (__time64_t) *t; //time_t may be shorter than 64 bits - _localtime64_s(tm, &tmp_t); #else memcpy(tm, localtime(t), sizeof(*tm)); #endif @@ -3877,7 +3876,7 @@ write_VD(struct archive_write *a, struct vdd *vdd) /* Unused Field */ set_unused_field_bp(bp, 8, 8); /* System Identifier */ - get_system_identitier(identifier, sizeof(identifier)); + get_system_identifier(identifier, sizeof(identifier)); r = set_str_a_characters_bp(a, bp, 9, 40, identifier, vdc); if (r != ARCHIVE_OK) return (r); @@ -4042,7 +4041,7 @@ set_option_info(struct archive_string *info, int *opt, const char *key, case KEY_HEX: d = va_arg(ap, int); archive_string_sprintf(info, "%c%s=%x", - prefix, key, d); + prefix, key, (unsigned int)d); break; } va_end(ap); @@ -4078,11 +4077,8 @@ write_information_block(struct archive_write *a) } memset(info.s, 0, info_size); opt = 0; -#if defined(HAVE__CTIME64_S) - { - __time64_t iso9660_birth_time_tmp = (__time64_t) iso9660->birth_time; //time_t may be shorter than 64 bits - _ctime64_s(buf, sizeof(buf), &(iso9660_birth_time_tmp)); - } +#if defined(HAVE_CTIME_S) + ctime_s(buf, sizeof(buf), &(iso9660->birth_time)); #elif defined(HAVE_CTIME_R) ctime_r(&(iso9660->birth_time), buf); #else @@ -7811,8 +7807,8 @@ struct zisofs_extract { uint64_t pz_uncompressed_size; size_t uncompressed_buffer_size; - signed int initialized:1; - signed int header_passed:1; + unsigned int initialized:1; + unsigned int header_passed:1; uint32_t pz_offset; unsigned char *block_pointers; diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_mtree.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_mtree.c index 619b7714eeb..14d68516fba 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_mtree.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_mtree.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_mtree.c 201171 2009-12-29 06:39:07Z kientzle $"); #ifdef HAVE_SYS_TYPES_H #include @@ -55,7 +54,7 @@ struct attr_counter { int count; }; -struct att_counter_set { +struct attr_counter_set { struct attr_counter *uid_list; struct attr_counter *gid_list; struct attr_counter *mode_list; @@ -142,7 +141,7 @@ struct mtree_writer { unsigned long fflags_set; unsigned long fflags_clear; } set; - struct att_counter_set acs; + struct attr_counter_set acs; int classic; int depth; @@ -438,7 +437,7 @@ write_global(struct mtree_writer *mtree) { struct archive_string setstr; struct archive_string unsetstr; - struct att_counter_set *acs; + struct attr_counter_set *acs; int keys, oldkeys, effkeys; archive_string_init(&setstr); @@ -639,7 +638,7 @@ static int attr_counter_set_collect(struct mtree_writer *mtree, struct mtree_entry *me) { struct attr_counter *ac, *last; - struct att_counter_set *acs = &mtree->acs; + struct attr_counter_set *acs = &mtree->acs; int keys = mtree->keys; if (keys & (F_UNAME | F_UID)) { @@ -715,7 +714,7 @@ attr_counter_set_collect(struct mtree_writer *mtree, struct mtree_entry *me) static void attr_counter_set_free(struct mtree_writer *mtree) { - struct att_counter_set *acs = &mtree->acs; + struct attr_counter_set *acs = &mtree->acs; attr_counter_free(&acs->uid_list); attr_counter_free(&acs->gid_list); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c index cf1f4770ebe..6e35f705e13 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_pax.c 201162 2009-12-29 05:47:46Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -100,6 +99,7 @@ static int has_non_ASCII(const char *); static void sparse_list_clear(struct pax *); static int sparse_list_add(struct pax *, int64_t, int64_t); static char *url_encode(const char *in); +static time_t get_ustar_max_mtime(void); /* * Set output format to 'restricted pax' format. @@ -138,7 +138,7 @@ archive_write_set_format_pax(struct archive *_a) if (a->format_free != NULL) (a->format_free)(a); - pax = (struct pax *)calloc(1, sizeof(*pax)); + pax = calloc(1, sizeof(*pax)); if (pax == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate pax data"); @@ -367,10 +367,12 @@ archive_write_pax_header_xattr(struct pax *pax, const char *encoded_name, struct archive_string s; char *encoded_value; + if (encoded_name == NULL) + return; + if (pax->flags & WRITE_LIBARCHIVE_XATTR) { encoded_value = base64_encode((const char *)value, value_len); - - if (encoded_name != NULL && encoded_value != NULL) { + if (encoded_value != NULL) { archive_string_init(&s); archive_strcpy(&s, "LIBARCHIVE.xattr."); archive_strcat(&s, encoded_name); @@ -403,17 +405,22 @@ archive_write_pax_header_xattrs(struct archive_write *a, archive_entry_xattr_next(entry, &name, &value, &size); url_encoded_name = url_encode(name); - if (url_encoded_name != NULL) { + if (url_encoded_name == NULL) + goto malloc_error; + else { /* Convert narrow-character to UTF-8. */ r = archive_strcpy_l(&(pax->l_url_encoded_name), url_encoded_name, pax->sconv_utf8); free(url_encoded_name); /* Done with this. */ if (r == 0) encoded_name = pax->l_url_encoded_name.s; - else if (errno == ENOMEM) { - archive_set_error(&a->archive, ENOMEM, - "Can't allocate memory for Linkname"); - return (ARCHIVE_FATAL); + else if (r == -1) + goto malloc_error; + else { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Error encoding pax extended attribute"); + return (ARCHIVE_FAILED); } } @@ -422,6 +429,9 @@ archive_write_pax_header_xattrs(struct archive_write *a, } return (ARCHIVE_OK); +malloc_error: + archive_set_error(&a->archive, ENOMEM, "Can't allocate memory"); + return (ARCHIVE_FATAL); } static int @@ -595,8 +605,18 @@ archive_write_pax_header(struct archive_write *a, need_extension = 0; pax = (struct pax *)a->format_data; + const time_t ustar_max_mtime = get_ustar_max_mtime(); + /* Sanity check. */ +#if defined(_WIN32) && !defined(__CYGWIN__) + /* NOTE: If the caller supplied a pathname that fails WCS conversion (e.g. + * if it is invalid UTF-8), we are expected to return ARCHIVE_WARN later on + * in execution, hence the check for both pointers */ + if ((archive_entry_pathname_w(entry_original) == NULL) && + (archive_entry_pathname(entry_original) == NULL)) { +#else if (archive_entry_pathname(entry_original) == NULL) { +#endif archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Can't record entry in tar file without pathname"); return (ARCHIVE_FAILED); @@ -1020,6 +1040,14 @@ archive_write_pax_header(struct archive_write *a, archive_entry_set_symlink(entry_main, "././@LongSymLink"); } + else { + /* Otherwise, has non-ASCII characters; update the paths to + * however they got decoded above */ + if (hardlink != NULL) + archive_entry_set_hardlink(entry_main, linkpath); + else + archive_entry_set_symlink(entry_main, linkpath); + } need_extension = 1; } } @@ -1116,16 +1144,13 @@ archive_write_pax_header(struct archive_write *a, } /* - * Technically, the mtime field in the ustar header can - * support 33 bits, but many platforms use signed 32-bit time - * values. The cutoff of 0x7fffffff here is a compromise. * Yes, this check is duplicated just below; this helps to * avoid writing an mtime attribute just to handle a * high-resolution timestamp in "restricted pax" mode. */ if (!need_extension && ((archive_entry_mtime(entry_main) < 0) - || (archive_entry_mtime(entry_main) >= 0x7fffffff))) + || (archive_entry_mtime(entry_main) >= ustar_max_mtime))) need_extension = 1; /* I use a star-compatible file flag attribute. */ @@ -1190,7 +1215,7 @@ archive_write_pax_header(struct archive_write *a, if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_RESTRICTED || need_extension) { if (archive_entry_mtime(entry_main) < 0 || - archive_entry_mtime(entry_main) >= 0x7fffffff || + archive_entry_mtime(entry_main) >= ustar_max_mtime || archive_entry_mtime_nsec(entry_main) != 0) add_pax_attr_time(&(pax->pax_header), "mtime", archive_entry_mtime(entry_main), @@ -1428,7 +1453,7 @@ archive_write_pax_header(struct archive_write *a, /* Copy mtime, but clip to ustar limits. */ s = archive_entry_mtime(entry_main); if (s < 0) { s = 0; } - if (s >= 0x7fffffff) { s = 0x7fffffff; } + if (s > ustar_max_mtime) { s = ustar_max_mtime; } archive_entry_set_mtime(pax_attr_entry, s, 0); /* Standard ustar doesn't support atime. */ @@ -1904,17 +1929,22 @@ url_encode(const char *in) { const char *s; char *d; - int out_len = 0; + size_t out_len = 0; char *out; for (s = in; *s != '\0'; s++) { - if (*s < 33 || *s > 126 || *s == '%' || *s == '=') + if (*s < 33 || *s > 126 || *s == '%' || *s == '=') { + if (SIZE_MAX - out_len < 4) + return (NULL); out_len += 3; - else + } else { + if (SIZE_MAX - out_len < 2) + return (NULL); out_len++; + } } - out = (char *)malloc(out_len + 1); + out = malloc(out_len + 1); if (out == NULL) return (NULL); @@ -1952,7 +1982,7 @@ base64_encode(const char *s, size_t len) char *d, *out; /* 3 bytes becomes 4 chars, but round up and allow for trailing NUL */ - out = (char *)malloc((len * 4 + 2) / 3 + 1); + out = malloc((len * 4 + 2) / 3 + 1); if (out == NULL) return (NULL); d = out; @@ -2007,7 +2037,7 @@ _sparse_list_add_block(struct pax *pax, int64_t offset, int64_t length, { struct sparse_block *sb; - sb = (struct sparse_block *)malloc(sizeof(*sb)); + sb = malloc(sizeof(*sb)); if (sb == NULL) return (ARCHIVE_FATAL); sb->next = NULL; @@ -2046,3 +2076,18 @@ sparse_list_add(struct pax *pax, int64_t offset, int64_t length) return (_sparse_list_add_block(pax, offset, length, 0)); } +static time_t +get_ustar_max_mtime(void) +{ + /* + * Technically, the mtime field in the ustar header can + * support 33 bits. We are using all of them to keep + * tar/test/test_option_C_mtree.c simple and passing after 2038. + * For platforms that use signed 32-bit time values we + * use the 32-bit maximum. + */ + if (sizeof(time_t) > sizeof(int32_t)) + return (time_t)0x1ffffffff; + else + return (time_t)0x7fffffff; +} diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_private.h b/Utilities/cmlibarchive/libarchive/archive_write_set_format_private.h index e20022755f8..ef9dee9d808 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_private.h +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_private.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ #ifndef ARCHIVE_WRITE_SET_FORMAT_PRIVATE_H_INCLUDED diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_raw.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_raw.c index feff9369773..ff3e9ae0ebd 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_raw.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_raw.c @@ -58,7 +58,7 @@ archive_write_set_format_raw(struct archive *_a) if (a->format_free != NULL) (a->format_free)(a); - raw = (struct raw *)calloc(1, sizeof(*raw)); + raw = calloc(1, sizeof(*raw)); if (raw == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate raw data"); return (ARCHIVE_FATAL); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_shar.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_shar.c index 9e4931c95c1..be9f78ce96c 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_shar.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_shar.c @@ -25,7 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_shar.c 189438 2009-03-06 05:58:56Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -114,7 +113,7 @@ archive_write_set_format_shar(struct archive *_a) if (a->format_free != NULL) (a->format_free)(a); - shar = (struct shar *)calloc(1, sizeof(*shar)); + shar = calloc(1, sizeof(*shar)); if (shar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate shar data"); return (ARCHIVE_FATAL); @@ -210,6 +209,10 @@ archive_write_shar_header(struct archive_write *a, struct archive_entry *entry) if (archive_entry_filetype(entry) != AE_IFDIR) { /* Try to create the dir. */ p = strdup(name); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } pp = strrchr(p, '/'); /* If there is a / character, try to create the dir. */ if (pp != NULL) { @@ -292,6 +295,10 @@ archive_write_shar_header(struct archive_write *a, struct archive_entry *entry) free(shar->last_dir); shar->last_dir = strdup(name); + if (shar->last_dir == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } /* Trim a trailing '/'. */ pp = strrchr(shar->last_dir, '/'); if (pp != NULL && pp[1] == '\0') diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_ustar.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_ustar.c index d1a06bc4f7e..09b71fe6672 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_ustar.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_ustar.c @@ -25,8 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_ustar.c 191579 2009-04-27 18:35:03Z kientzle $"); - #ifdef HAVE_ERRNO_H #include @@ -185,7 +183,7 @@ archive_write_set_format_ustar(struct archive *_a) return (ARCHIVE_FATAL); } - ustar = (struct ustar *)calloc(1, sizeof(*ustar)); + ustar = calloc(1, sizeof(*ustar)); if (ustar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate ustar data"); @@ -256,7 +254,11 @@ archive_write_ustar_header(struct archive_write *a, struct archive_entry *entry) sconv = ustar->opt_sconv; /* Sanity check. */ +#if defined(_WIN32) && !defined(__CYGWIN__) + if (archive_entry_pathname_w(entry) == NULL) { +#else if (archive_entry_pathname(entry) == NULL) { +#endif archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Can't record entry in tar file without pathname"); return (ARCHIVE_FAILED); @@ -265,7 +267,7 @@ archive_write_ustar_header(struct archive_write *a, struct archive_entry *entry) /* Only regular files (not hardlinks) have data. */ if (archive_entry_hardlink(entry) != NULL || archive_entry_symlink(entry) != NULL || - !(archive_entry_filetype(entry) == AE_IFREG)) + archive_entry_filetype(entry) != AE_IFREG) archive_entry_set_size(entry, 0); if (AE_IFDIR == archive_entry_filetype(entry)) { diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_v7tar.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_v7tar.c index 59940714412..2598fc076c0 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_v7tar.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_v7tar.c @@ -25,8 +25,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); - #ifdef HAVE_ERRNO_H #include @@ -162,7 +160,7 @@ archive_write_set_format_v7tar(struct archive *_a) return (ARCHIVE_FATAL); } - v7tar = (struct v7tar *)calloc(1, sizeof(*v7tar)); + v7tar = calloc(1, sizeof(*v7tar)); if (v7tar == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate v7tar data"); @@ -243,7 +241,7 @@ archive_write_v7tar_header(struct archive_write *a, struct archive_entry *entry) /* Only regular files (not hardlinks) have data. */ if (archive_entry_hardlink(entry) != NULL || archive_entry_symlink(entry) != NULL || - !(archive_entry_filetype(entry) == AE_IFREG)) + archive_entry_filetype(entry) != AE_IFREG) archive_entry_set_size(entry, 0); if (AE_IFDIR == archive_entry_filetype(entry)) { diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_warc.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_warc.c index 46b05734121..3d22e1f4ba5 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_warc.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_warc.c @@ -26,7 +26,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -329,30 +328,21 @@ xstrftime(struct archive_string *as, const char *fmt, time_t t) { /** like strftime(3) but for time_t objects */ struct tm *rt; -#if defined(HAVE_GMTIME_R) || defined(HAVE__GMTIME64_S) +#if defined(HAVE_GMTIME_R) || defined(HAVE_GMTIME_S) struct tm timeHere; -#endif -#if defined(HAVE__GMTIME64_S) - errno_t terr; - __time64_t tmptime; #endif char strtime[100]; size_t len; -#ifdef HAVE_GMTIME_R - if ((rt = gmtime_r(&t, &timeHere)) == NULL) - return; -#elif defined(HAVE__GMTIME64_S) - tmptime = t; - terr = _gmtime64_s(&timeHere, &tmptime); - if (terr) - rt = NULL; - else - rt = &timeHere; +#if defined(HAVE_GMTIME_S) + rt = gmtime_s(&timeHere, &t) ? NULL : &timeHere; +#elif defined(HAVE_GMTIME_R) + rt = gmtime_r(&t, &timeHere); #else - if ((rt = gmtime(&t)) == NULL) - return; + rt = gmtime(&t); #endif + if (!rt) + return; /* leave the hard yacker to our role model strftime() */ len = strftime(strtime, sizeof(strtime)-1, fmt, rt); archive_strncat(as, strtime, len); diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_xar.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_xar.c index 1e35375340a..0fcd31ff038 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_xar.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_xar.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include @@ -212,8 +211,8 @@ struct file { struct heap_data data; struct archive_string script; - signed int virtual:1; - signed int dir:1; + unsigned int virtual:1; + unsigned int dir:1; }; struct hardlink { @@ -797,7 +796,7 @@ xar_finish_entry(struct archive_write *a) if (w > 0) xar->bytes_remaining -= w; else - return (w); + return ((int)w); } file = xar->cur_file; checksum_final(&(xar->e_sumwrk), &(file->data.e_sum)); @@ -906,15 +905,11 @@ xmlwrite_time(struct archive_write *a, xmlTextWriterPtr writer, { char timestr[100]; struct tm tm; -#if defined(HAVE__GMTIME64_S) - __time64_t tmptime; -#endif -#if defined(HAVE_GMTIME_R) +#if defined(HAVE_GMTIME_S) + gmtime_s(&tm, &t); +#elif defined(HAVE_GMTIME_R) gmtime_r(&t, &tm); -#elif defined(HAVE__GMTIME64_S) - tmptime = t; - _gmtime64_s(&tm, &tmptime); #else memcpy(&tm, gmtime(&t), sizeof(tm)); #endif @@ -1168,7 +1163,7 @@ make_file_entry(struct archive_write *a, xmlTextWriterPtr writer, /* * Make a file name entry, "". */ - l = ll = archive_strlen(&(file->basename)); + l = ll = (int)archive_strlen(&(file->basename)); tmp = malloc(l); if (tmp == NULL) { archive_set_error(&a->archive, ENOMEM, @@ -1194,7 +1189,7 @@ make_file_entry(struct archive_write *a, xmlTextWriterPtr writer, return (ARCHIVE_FATAL); } r = xmlTextWriterWriteBase64(writer, file->basename.s, - 0, archive_strlen(&(file->basename))); + 0, (int)archive_strlen(&(file->basename))); if (r < 0) { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, @@ -2236,10 +2231,10 @@ get_path_component(char *name, int n, const char *fn) p = strchr(fn, '/'); if (p == NULL) { - if ((l = strlen(fn)) == 0) + if ((l = (int)strlen(fn)) == 0) return (0); } else - l = p - fn; + l = (int)(p - fn); if (l > n -1) return (-1); memcpy(name, fn, l); @@ -2656,10 +2651,10 @@ compression_init_encoder_gzip(struct archive *a, * of ugly hackery to convert a const * pointer to * a non-const pointer. */ strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in; - strm->avail_in = lastrm->avail_in; + strm->avail_in = (uInt)lastrm->avail_in; strm->total_in = (uLong)lastrm->total_in; strm->next_out = lastrm->next_out; - strm->avail_out = lastrm->avail_out; + strm->avail_out = (uInt)lastrm->avail_out; strm->total_out = (uLong)lastrm->total_out; if (deflateInit2(strm, level, Z_DEFLATED, (withheader)?15:-15, @@ -2689,10 +2684,10 @@ compression_code_gzip(struct archive *a, * of ugly hackery to convert a const * pointer to * a non-const pointer. */ strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in; - strm->avail_in = lastrm->avail_in; + strm->avail_in = (uInt)lastrm->avail_in; strm->total_in = (uLong)lastrm->total_in; strm->next_out = lastrm->next_out; - strm->avail_out = lastrm->avail_out; + strm->avail_out = (uInt)lastrm->avail_out; strm->total_out = (uLong)lastrm->total_out; r = deflate(strm, (action == ARCHIVE_Z_FINISH)? Z_FINISH: Z_NO_FLUSH); @@ -2753,11 +2748,11 @@ compression_init_encoder_bzip2(struct archive *a, * of ugly hackery to convert a const * pointer to * a non-const pointer. */ strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; - strm->avail_in = lastrm->avail_in; + strm->avail_in = (unsigned int)lastrm->avail_in; strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); strm->next_out = (char *)lastrm->next_out; - strm->avail_out = lastrm->avail_out; + strm->avail_out = (unsigned int)lastrm->avail_out; strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); if (BZ2_bzCompressInit(strm, level, 0, 30) != BZ_OK) { @@ -2786,11 +2781,11 @@ compression_code_bzip2(struct archive *a, * of ugly hackery to convert a const * pointer to * a non-const pointer. */ strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; - strm->avail_in = lastrm->avail_in; + strm->avail_in = (unsigned int)lastrm->avail_in; strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); strm->next_out = (char *)lastrm->next_out; - strm->avail_out = lastrm->avail_out; + strm->avail_out = (unsigned int)lastrm->avail_out; strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); r = BZ2_bzCompress(strm, diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_zip.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_zip.c index 530e1e8d198..1ffd94f5ffb 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_zip.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_zip.c @@ -30,7 +30,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_zip.c 201168 2009-12-29 06:15:32Z kientzle $"); #ifdef HAVE_ERRNO_H #include @@ -132,7 +131,6 @@ struct zip { enum compression entry_compression; enum encryption entry_encryption; int entry_flags; - int entry_uses_zip64; int experiments; struct trad_enc_ctx tctx; char tctx_valid; @@ -458,7 +456,7 @@ archive_write_set_format_zip(struct archive *_a) if (a->format_free != NULL) (a->format_free)(a); - zip = (struct zip *) calloc(1, sizeof(*zip)); + zip = calloc(1, sizeof(*zip)); if (zip == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate zip data"); @@ -523,6 +521,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) int ret, ret2 = ARCHIVE_OK; mode_t type; int version_needed = 10; +#define MIN_VERSION_NEEDED(x) do { if (version_needed < x) { version_needed = x; } } while (0) /* Ignore types of entries that we don't support. */ type = archive_entry_filetype(entry); @@ -530,7 +529,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) __archive_write_entry_filetype_unsupported( &a->archive, entry, "zip"); return ARCHIVE_FAILED; - }; + } /* If we're not using Zip64, reject large files. */ if (zip->flags & ZIP_FLAG_AVOID_ZIP64) { @@ -557,12 +556,12 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) /* Reset information from last entry. */ zip->entry_offset = zip->written_bytes; zip->entry_uncompressed_limit = INT64_MAX; + /* Zero size values implies that we're using a trailing data descriptor */ zip->entry_compressed_size = 0; zip->entry_uncompressed_size = 0; zip->entry_compressed_written = 0; zip->entry_uncompressed_written = 0; zip->entry_flags = 0; - zip->entry_uses_zip64 = 0; zip->entry_crc32 = zip->crc32func(0, NULL, 0); zip->entry_encryption = 0; archive_entry_free(zip->entry); @@ -610,7 +609,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) const char *p; size_t len; - if (archive_entry_pathname_l(entry, &p, &len, sconv) != 0) { + if (archive_entry_pathname_l(zip->entry, &p, &len, sconv) != 0) { if (errno == ENOMEM) { archive_set_error(&a->archive, ENOMEM, "Can't allocate memory for Pathname"); @@ -619,7 +618,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Can't translate Pathname '%s' to %s", - archive_entry_pathname(entry), + archive_entry_pathname(zip->entry), archive_string_conversion_charset_name(sconv)); ret2 = ARCHIVE_WARN; } @@ -632,7 +631,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) * for filename. */ if (type == AE_IFLNK) { - if (archive_entry_symlink_l(entry, &p, &len, sconv)) { + if (archive_entry_symlink_l(zip->entry, &p, &len, sconv)) { if (errno == ENOMEM) { archive_set_error(&a->archive, ENOMEM, "Can't allocate memory " @@ -672,11 +671,11 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) zip->entry_crc32 = zip->crc32func(zip->entry_crc32, (const unsigned char *)slink, slink_size); zip->entry_compression = COMPRESSION_STORE; - version_needed = 20; + MIN_VERSION_NEEDED(20); } else if (type != AE_IFREG) { zip->entry_compression = COMPRESSION_STORE; zip->entry_uncompressed_limit = 0; - version_needed = 20; + MIN_VERSION_NEEDED(20); } else if (archive_entry_size_is_set(zip->entry)) { int64_t size = archive_entry_size(zip->entry); int64_t additional_size = 0; @@ -689,27 +688,27 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) if (zip->entry_compression == COMPRESSION_STORE) { zip->entry_compressed_size = size; zip->entry_uncompressed_size = size; - version_needed = 10; + MIN_VERSION_NEEDED(10); } else { zip->entry_uncompressed_size = size; - version_needed = 20; + MIN_VERSION_NEEDED(20); } if (zip->entry_flags & ZIP_ENTRY_FLAG_ENCRYPTED) { switch (zip->entry_encryption) { case ENCRYPTION_TRADITIONAL: additional_size = TRAD_HEADER_SIZE; - version_needed = 20; + MIN_VERSION_NEEDED(20); break; case ENCRYPTION_WINZIP_AES128: additional_size = WINZIP_AES128_HEADER_SIZE + AUTH_CODE_SIZE; - version_needed = 20; + MIN_VERSION_NEEDED(20); break; case ENCRYPTION_WINZIP_AES256: additional_size = WINZIP_AES256_HEADER_SIZE + AUTH_CODE_SIZE; - version_needed = 20; + MIN_VERSION_NEEDED(20); break; case ENCRYPTION_NONE: default: @@ -733,8 +732,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) || (zip->entry_uncompressed_size + additional_size > ZIP_4GB_MAX) || (zip->entry_uncompressed_size > ZIP_4GB_MAX_UNCOMPRESSED && zip->entry_compression != COMPRESSION_STORE)) { - zip->entry_uses_zip64 = 1; - version_needed = 45; + MIN_VERSION_NEEDED(45); } /* We may know the size, but never the CRC. */ @@ -742,7 +740,6 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) } else { /* We don't know the size. Use the default * compression unless specified otherwise. - * We enable Zip64 extensions unless we're told not to. */ zip->entry_compression = zip->requested_compression; @@ -752,12 +749,12 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) zip->entry_flags |= ZIP_ENTRY_FLAG_LENGTH_AT_END; if ((zip->flags & ZIP_FLAG_AVOID_ZIP64) == 0) { - zip->entry_uses_zip64 = 1; - version_needed = 45; + /* We might use zip64 extensions, so require 4.5 */ + MIN_VERSION_NEEDED(45); } else if (zip->entry_compression == COMPRESSION_STORE) { - version_needed = 10; + MIN_VERSION_NEEDED(10); } else { - version_needed = 20; + MIN_VERSION_NEEDED(20); } if (zip->entry_flags & ZIP_ENTRY_FLAG_ENCRYPTED) { @@ -765,8 +762,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) case ENCRYPTION_TRADITIONAL: case ENCRYPTION_WINZIP_AES128: case ENCRYPTION_WINZIP_AES256: - if (version_needed < 20) - version_needed = 20; + MIN_VERSION_NEEDED(20); break; case ENCRYPTION_NONE: default: @@ -787,16 +783,8 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) archive_le16enc(local_header + 8, zip->entry_compression); archive_le32enc(local_header + 10, dos_time(archive_entry_mtime(zip->entry))); - archive_le32enc(local_header + 14, zip->entry_crc32); - if (zip->entry_uses_zip64) { - /* Zip64 data in the local header "must" include both - * compressed and uncompressed sizes AND those fields - * are included only if these are 0xffffffff; - * THEREFORE these must be set this way, even if we - * know one of them is smaller. */ - archive_le32enc(local_header + 18, ZIP_4GB_MAX); - archive_le32enc(local_header + 22, ZIP_4GB_MAX); - } else { + if ((zip->entry_flags & ZIP_ENTRY_FLAG_LENGTH_AT_END) == 0) { + archive_le32enc(local_header + 14, zip->entry_crc32); archive_le32enc(local_header + 18, (uint32_t)zip->entry_compressed_size); archive_le32enc(local_header + 22, (uint32_t)zip->entry_uncompressed_size); } @@ -842,41 +830,18 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) * the local file header and the central directory. * We format them once and then duplicate them. */ - /* UT timestamp, length depends on what timestamps are set. */ - memcpy(e, "UT", 2); - archive_le16enc(e + 2, - 1 - + (archive_entry_mtime_is_set(entry) ? 4 : 0) - + (archive_entry_atime_is_set(entry) ? 4 : 0) - + (archive_entry_ctime_is_set(entry) ? 4 : 0)); - e += 4; - *e++ = - (archive_entry_mtime_is_set(entry) ? 1 : 0) - | (archive_entry_atime_is_set(entry) ? 2 : 0) - | (archive_entry_ctime_is_set(entry) ? 4 : 0); - if (archive_entry_mtime_is_set(entry)) { - archive_le32enc(e, (uint32_t)archive_entry_mtime(entry)); + /* ux Unix extra data, length 11, version 1 */ + if (archive_entry_uid_is_set(entry) || archive_entry_gid_is_set(entry)) { + /* TODO: If uid < 64k, use 2 bytes, ditto for gid. */ + memcpy(e, "ux\013\000\001", 5); + e += 5; + *e++ = 4; /* Length of following UID */ + archive_le32enc(e, (uint32_t)archive_entry_uid(entry)); e += 4; - } - if (archive_entry_atime_is_set(entry)) { - archive_le32enc(e, (uint32_t)archive_entry_atime(entry)); + *e++ = 4; /* Length of following GID */ + archive_le32enc(e, (uint32_t)archive_entry_gid(entry)); e += 4; } - if (archive_entry_ctime_is_set(entry)) { - archive_le32enc(e, (uint32_t)archive_entry_ctime(entry)); - e += 4; - } - - /* ux Unix extra data, length 11, version 1 */ - /* TODO: If uid < 64k, use 2 bytes, ditto for gid. */ - memcpy(e, "ux\013\000\001", 5); - e += 5; - *e++ = 4; /* Length of following UID */ - archive_le32enc(e, (uint32_t)archive_entry_uid(entry)); - e += 4; - *e++ = 4; /* Length of following GID */ - archive_le32enc(e, (uint32_t)archive_entry_gid(entry)); - e += 4; /* AES extra data field: WinZIP AES information, ID=0x9901 */ if ((zip->entry_flags & ZIP_ENTRY_FLAG_ENCRYPTED) @@ -904,7 +869,7 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) e += 2; } - /* Copy UT ,ux, and AES-extra into central directory as well. */ + /* Copy ux, AES-extra into central directory as well. */ zip->file_header_extra_offset = zip->central_directory_bytes; cd_extra = cd_alloc(zip, e - local_extra); memcpy(cd_extra, local_extra, e - local_extra); @@ -916,17 +881,50 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) * archive_write_zip_finish_entry() below. */ - /* "[Zip64 entry] in the local header MUST include BOTH - * original [uncompressed] and compressed size fields." */ - if (zip->entry_uses_zip64) { - unsigned char *zip64_start = e; - memcpy(e, "\001\000\020\000", 4); + /* UT timestamp: length depends on what timestamps are set. + * This header appears in the Central Directory also, but + * according to Info-Zip specification, the CD form + * only holds mtime, so we format it separately. */ + if (archive_entry_mtime_is_set(entry) + || archive_entry_atime_is_set(entry) + || archive_entry_ctime_is_set(entry)) { + unsigned char *ut = e; + memcpy(e, "UT\000\000", 4); + e += 4; + *e++ = (archive_entry_mtime_is_set(entry) ? 1 : 0) + | (archive_entry_atime_is_set(entry) ? 2 : 0) + | (archive_entry_ctime_is_set(entry) ? 4 : 0); + if (archive_entry_mtime_is_set(entry)) { + archive_le32enc(e, (uint32_t)archive_entry_mtime(entry)); + e += 4; + } + if (archive_entry_atime_is_set(entry)) { + archive_le32enc(e, (uint32_t)archive_entry_atime(entry)); + e += 4; + } + if (archive_entry_ctime_is_set(entry)) { + archive_le32enc(e, (uint32_t)archive_entry_ctime(entry)); + e += 4; + } + archive_le16enc(ut + 2, (uint16_t)(e - ut - 4)); + } + + /* + * Note about Zip64 Extended Information Extra Field: + * Because libarchive always writes in a streaming + * fashion, we never know the CRC when we're writing + * the local header. So we have to use length-at-end, which + * prevents us from putting size information into a Zip64 + * extra field. However, apparently some readers find it + * a helpful clue to have an empty such field so they + * can expect a 64-bit length-at-end marker. + */ + if (archive_entry_size_is_set(zip->entry) + && (zip->entry_uncompressed_size > ZIP_4GB_MAX + || zip->entry_compressed_size > ZIP_4GB_MAX)) { + /* Header ID 0x0001, size 0 */ + memcpy(e, "\001\000\000\000", 4); e += 4; - archive_le64enc(e, zip->entry_uncompressed_size); - e += 8; - archive_le64enc(e, zip->entry_compressed_size); - e += 8; - archive_le16enc(zip64_start + 2, (uint16_t)(e - (zip64_start + 4))); } if (zip->flags & ZIP_FLAG_EXPERIMENT_xl) { @@ -1205,7 +1203,9 @@ archive_write_zip_finish_entry(struct archive_write *a) archive_le32enc(d + 4, 0);/* no CRC.*/ else archive_le32enc(d + 4, zip->entry_crc32); - if (zip->entry_uses_zip64) { + if (zip->entry_compressed_written > ZIP_4GB_MAX + || zip->entry_uncompressed_written > ZIP_4GB_MAX + || zip->flags & ZIP_FLAG_FORCE_ZIP64) { archive_le64enc(d + 8, (uint64_t)zip->entry_compressed_written); archive_le64enc(d + 16, @@ -1224,23 +1224,60 @@ archive_write_zip_finish_entry(struct archive_write *a) return (ARCHIVE_FATAL); } - /* Append Zip64 extra data to central directory information. */ - if (zip->entry_compressed_written > ZIP_4GB_MAX - || zip->entry_uncompressed_written > ZIP_4GB_MAX + /* UT timestamp: Info-Zip specifies that _only_ the mtime should + * be recorded here; ctime and atime are also included in the + * local file descriptor. */ + if (archive_entry_mtime_is_set(zip->entry)) { + unsigned char ut[9]; + unsigned char *u = ut, *ud; + memcpy(u, "UT\005\000\001", 5); + u += 5; + archive_le32enc(u, (uint32_t)archive_entry_mtime(zip->entry)); + u += 4; + ud = cd_alloc(zip, u - ut); + if (ud == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate zip data"); + return (ARCHIVE_FATAL); + } + memcpy(ud, ut, u - ut); + } + + /* Fill in size information in the central directory entry. */ + /* Fix up central directory file header. */ + if (zip->cctx_valid && zip->aes_vendor == AES_VENDOR_AE_2) + archive_le32enc(zip->file_header + 16, 0);/* no CRC.*/ + else + archive_le32enc(zip->file_header + 16, zip->entry_crc32); + /* Truncate to 32 bits; we'll fix up below. */ + archive_le32enc(zip->file_header + 20, (uint32_t)zip->entry_compressed_written); + archive_le32enc(zip->file_header + 24, (uint32_t)zip->entry_uncompressed_written); + archive_le16enc(zip->file_header + 30, + (uint16_t)(zip->central_directory_bytes - zip->file_header_extra_offset)); + archive_le32enc(zip->file_header + 42, (uint32_t)zip->entry_offset); + + /* If any of the values immediately above are too large, we'll + * need to put the corresponding value in a Zip64 extra field + * and set the central directory value to 0xffffffff as a flag. */ + if (zip->entry_compressed_written >= ZIP_4GB_MAX + || zip->entry_uncompressed_written >= ZIP_4GB_MAX || zip->entry_offset > ZIP_4GB_MAX) { unsigned char zip64[32]; unsigned char *z = zip64, *zd; memcpy(z, "\001\000\000\000", 4); z += 4; if (zip->entry_uncompressed_written >= ZIP_4GB_MAX) { + archive_le32enc(zip->file_header + 24, ZIP_4GB_MAX); archive_le64enc(z, zip->entry_uncompressed_written); z += 8; } if (zip->entry_compressed_written >= ZIP_4GB_MAX) { + archive_le32enc(zip->file_header + 20, ZIP_4GB_MAX); archive_le64enc(z, zip->entry_compressed_written); z += 8; } if (zip->entry_offset >= ZIP_4GB_MAX) { + archive_le32enc(zip->file_header + 42, ZIP_4GB_MAX); archive_le64enc(z, zip->entry_offset); z += 8; } @@ -1382,25 +1419,14 @@ dos_time(const time_t unix_time) { struct tm *t; unsigned int dt; -#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S) +#if defined(HAVE_LOCALTIME_R) || defined(HAVE_LOCALTIME_S) struct tm tmbuf; #endif -#if defined(HAVE__LOCALTIME64_S) - errno_t terr; - __time64_t tmptime; -#endif - /* This will not preserve time when creating/extracting the archive - * on two systems with different time zones. */ -#if defined(HAVE_LOCALTIME_R) +#if defined(HAVE_LOCALTIME_S) + t = localtime_s(&tmbuf, &unix_time) ? NULL : &tmbuf; +#elif defined(HAVE_LOCALTIME_R) t = localtime_r(&unix_time, &tmbuf); -#elif defined(HAVE__LOCALTIME64_S) - tmptime = unix_time; - terr = _localtime64_s(&tmbuf, &tmptime); - if (terr) - t = NULL; - else - t = &tmbuf; #else t = localtime(&unix_time); #endif diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_options.3 b/Utilities/cmlibarchive/libarchive/archive_write_set_options.3 index dd573588d57..454c79671b9 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_options.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_options.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd January 31, 2020 .Dt ARCHIVE_WRITE_OPTIONS 3 .Os @@ -257,12 +255,22 @@ If supported, the default value is read from The value is interpreted as a decimal integer specifying the compression level. Supported values depend on the library version, common values are from 1 to 22. +.It Cm long +Enables long distance matching. The value is interpreted as a +decimal integer specifying log2 window size in bytes. Values from +10 to 30 for 32 bit, or 31 for 64 bit, are supported. +.It Cm threads +The value is interpreted as a decimal integer specifying the +number of threads for multi-threaded zstd compression. +If set to 0, zstd will attempt to detect and use the number +of physical CPU cores. .El .It Format 7zip .Bl -tag -compact -width indent .It Cm compression The value is one of .Dq store , +.Dq copy , .Dq deflate , .Dq bzip2 , .Dq lzma1 , @@ -270,12 +278,18 @@ The value is one of or .Dq ppmd to indicate how the following entries should be compressed. +The values +.Dq store +and +.Dq copy +are synonyms. Note that this setting is ignored for directories, symbolic links, and other special entries. .It Cm compression-level The value is interpreted as a decimal integer specifying the compression level. -Values between 0 and 9 are supported. +Values between 0 and 9 are supported, with the exception of bzip2 +which only supports values between 1 and 9. The interpretation of the compression level depends on the chosen compression method. .El diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_options.c b/Utilities/cmlibarchive/libarchive/archive_write_set_options.c index 962309ada5c..be2a6063188 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_options.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_options.c @@ -24,7 +24,6 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #include "archive_write_private.h" #include "archive_options_private.h" diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.3 b/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.3 index 2db77034c76..629e059b237 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.3 +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd September 21, 2014 .Dt ARCHIVE_WRITE_SET_PASSPHRASE 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.c b/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.c index 710ecba52c3..f871c8e2f81 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_set_passphrase.c @@ -24,21 +24,15 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD$"); #ifdef HAVE_ERRNO_H #include #endif #include "archive_write_private.h" -int -archive_write_set_passphrase(struct archive *_a, const char *p) +static int +set_passphrase(struct archive_write *a, const char *p) { - struct archive_write *a = (struct archive_write *)_a; - - archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, - "archive_write_set_passphrase"); - if (p == NULL || p[0] == '\0') { archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Empty passphrase is unacceptable"); @@ -55,6 +49,18 @@ archive_write_set_passphrase(struct archive *_a, const char *p) } +int +archive_write_set_passphrase(struct archive *_a, const char *p) +{ + struct archive_write *a = (struct archive_write *)_a; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, + "archive_write_set_passphrase"); + + return (set_passphrase(a, p)); +} + + int archive_write_set_passphrase_callback(struct archive *_a, void *client_data, archive_passphrase_callback *cb) @@ -81,15 +87,9 @@ __archive_write_get_passphrase(struct archive_write *a) const char *p; p = a->passphrase_callback(&a->archive, a->passphrase_client_data); - if (p != NULL) { - a->passphrase = strdup(p); - if (a->passphrase == NULL) { - archive_set_error(&a->archive, ENOMEM, - "Can't allocate data for passphrase"); - return (NULL); - } - return (a->passphrase); - } + set_passphrase(a, p); + a->passphrase_callback = NULL; + a->passphrase_client_data = NULL; } - return (NULL); + return (a->passphrase); } diff --git a/Utilities/cmlibarchive/libarchive/config_freebsd.h b/Utilities/cmlibarchive/libarchive/config_freebsd.h index 758621c4b68..5b36e1695de 100644 --- a/Utilities/cmlibarchive/libarchive/config_freebsd.h +++ b/Utilities/cmlibarchive/libarchive/config_freebsd.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD$ */ #define __LIBARCHIVE_CONFIG_H_INCLUDED 1 @@ -111,6 +109,8 @@ #define HAVE_FCNTL 1 #define HAVE_FCNTL_H 1 #define HAVE_FDOPENDIR 1 +#define HAVE_FNMATCH 1 +#define HAVE_FNMATCH_H 1 #define HAVE_FORK 1 #define HAVE_FSEEKO 1 #define HAVE_FSTAT 1 @@ -123,6 +123,8 @@ #define HAVE_GETEUID 1 #define HAVE_GETGRGID_R 1 #define HAVE_GETGRNAM_R 1 +#define HAVE_GETLINE 1 +#define HAVE_GETOPT_OPTRESET 1 #define HAVE_GETPID 1 #define HAVE_GETPWNAM_R 1 #define HAVE_GETPWUID_R 1 @@ -208,6 +210,8 @@ #define HAVE_SYS_TYPES_H 1 #define HAVE_SYS_UTSNAME_H 1 #define HAVE_SYS_WAIT_H 1 +#define HAVE_TCGETATTR 1 +#define HAVE_TCSETATTR 1 #define HAVE_TIMEGM 1 #define HAVE_TIME_H 1 #define HAVE_TZSET 1 @@ -234,7 +238,7 @@ #define HAVE_WMEMCPY 1 #define HAVE_WMEMMOVE 1 #define HAVE_ZLIB_H 1 -#define TIME_WITH_SYS_TIME 1 +#define HAVE_SYS_TIME_H 1 #if __FreeBSD_version >= 800505 #define HAVE_LIBLZMA 1 diff --git a/Utilities/cmlibarchive/libarchive/cpio.5 b/Utilities/cmlibarchive/libarchive/cpio.5 index c71018b1996..21c30d78d3c 100644 --- a/Utilities/cmlibarchive/libarchive/cpio.5 +++ b/Utilities/cmlibarchive/libarchive/cpio.5 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd December 23, 2011 .Dt CPIO 5 .Os diff --git a/Utilities/cmlibarchive/libarchive/filter_fork.h b/Utilities/cmlibarchive/libarchive/filter_fork.h index 2bf290c4d9e..aeab70ae634 100644 --- a/Utilities/cmlibarchive/libarchive/filter_fork.h +++ b/Utilities/cmlibarchive/libarchive/filter_fork.h @@ -21,8 +21,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: head/lib/libarchive/filter_fork.h 201087 2009-12-28 02:18:26Z kientzle $ */ #ifndef FILTER_FORK_H diff --git a/Utilities/cmlibarchive/libarchive/filter_fork_posix.c b/Utilities/cmlibarchive/libarchive/filter_fork_posix.c index 62085a7099b..c895c08e59b 100644 --- a/Utilities/cmlibarchive/libarchive/filter_fork_posix.c +++ b/Utilities/cmlibarchive/libarchive/filter_fork_posix.c @@ -30,8 +30,6 @@ #if defined(HAVE_PIPE) && defined(HAVE_FCNTL) && \ (defined(HAVE_FORK) || defined(HAVE_VFORK) || defined(HAVE_POSIX_SPAWNP)) -__FBSDID("$FreeBSD: head/lib/libarchive/filter_fork.c 182958 2008-09-12 05:33:00Z kientzle $"); - #if defined(HAVE_SYS_TYPES_H) # include #endif diff --git a/Utilities/cmlibarchive/libarchive/filter_fork_windows.c b/Utilities/cmlibarchive/libarchive/filter_fork_windows.c index 0b963975b90..9e49c5655f1 100644 --- a/Utilities/cmlibarchive/libarchive/filter_fork_windows.c +++ b/Utilities/cmlibarchive/libarchive/filter_fork_windows.c @@ -31,6 +31,7 @@ #include "filter_fork.h" +#if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) /* There are some editions of Windows ("nano server," for example) that * do not host user32.dll. If we want to keep running on those editions, * we need to delay-load WaitForInputIdle. */ @@ -224,6 +225,14 @@ __archive_create_child(const char *cmd, int *child_stdin, int *child_stdout, __archive_cmdline_free(acmd); return ARCHIVE_FAILED; } +#else /* !WINAPI_PARTITION_DESKTOP */ +int +__archive_create_child(const char *cmd, int *child_stdin, int *child_stdout, HANDLE *out_child) +{ + (void)cmd; (void)child_stdin; (void) child_stdout; (void) out_child; + return ARCHIVE_FAILED; +} +#endif /* !WINAPI_PARTITION_DESKTOP */ void __archive_check_child(int in, int out) diff --git a/Utilities/cmlibarchive/libarchive/libarchive-formats.5 b/Utilities/cmlibarchive/libarchive/libarchive-formats.5 index 5a118ff5d24..fab2f866027 100644 --- a/Utilities/cmlibarchive/libarchive/libarchive-formats.5 +++ b/Utilities/cmlibarchive/libarchive/libarchive-formats.5 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd December 27, 2016 .Dt LIBARCHIVE-FORMATS 5 .Os diff --git a/Utilities/cmlibarchive/libarchive/libarchive.3 b/Utilities/cmlibarchive/libarchive/libarchive.3 index 64905624228..c67172bf654 100644 --- a/Utilities/cmlibarchive/libarchive/libarchive.3 +++ b/Utilities/cmlibarchive/libarchive/libarchive.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd March 18, 2012 .Dt LIBARCHIVE 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/libarchive_changes.3 b/Utilities/cmlibarchive/libarchive/libarchive_changes.3 index 6bf8db038c7..fd0e721053c 100644 --- a/Utilities/cmlibarchive/libarchive/libarchive_changes.3 +++ b/Utilities/cmlibarchive/libarchive/libarchive_changes.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd December 23, 2011 .Dt LIBARCHIVE_CHANGES 3 .Os diff --git a/Utilities/cmlibarchive/libarchive/libarchive_internals.3 b/Utilities/cmlibarchive/libarchive/libarchive_internals.3 index d672f3e8a64..2978b48c3e9 100644 --- a/Utilities/cmlibarchive/libarchive/libarchive_internals.3 +++ b/Utilities/cmlibarchive/libarchive/libarchive_internals.3 @@ -22,8 +22,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd January 26, 2011 .Dt LIBARCHIVE_INTERNALS 3 .Os @@ -126,7 +124,7 @@ to read the entire file into memory at once and return the entire file to libarchive as a single block; other clients may begin asynchronous I/O operations for the next block on each request. -.Ss Decompresssion Layer +.Ss Decompression Layer The decompression layer not only handles decompression, it also buffers data so that the format handlers see a much nicer I/O model. diff --git a/Utilities/cmlibarchive/libarchive/mtree.5 b/Utilities/cmlibarchive/libarchive/mtree.5 index 8147796f310..5ea53613166 100644 --- a/Utilities/cmlibarchive/libarchive/mtree.5 +++ b/Utilities/cmlibarchive/libarchive/mtree.5 @@ -26,7 +26,6 @@ .\" SUCH DAMAGE. .\" .\" From: @(#)mtree.8 8.2 (Berkeley) 12/11/93 -.\" $FreeBSD$ .\" .Dd September 4, 2013 .Dt MTREE 5 diff --git a/Utilities/cmlibarchive/libarchive/tar.5 b/Utilities/cmlibarchive/libarchive/tar.5 index 34ad4f79315..725a7d68374 100644 --- a/Utilities/cmlibarchive/libarchive/tar.5 +++ b/Utilities/cmlibarchive/libarchive/tar.5 @@ -23,8 +23,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" .Dd December 27, 2016 .Dt TAR 5 .Os diff --git a/Utilities/cmlibarchive/libarchive/xxhash.c b/Utilities/cmlibarchive/libarchive/xxhash.c index f96e9d93493..beacd239122 100644 --- a/Utilities/cmlibarchive/libarchive/xxhash.c +++ b/Utilities/cmlibarchive/libarchive/xxhash.c @@ -149,6 +149,10 @@ typedef struct _U32_S { U32 v; } _PACKED U32_S; #if GCC_VERSION >= 409 __attribute__((__no_sanitize_undefined__)) +#else +# if defined(__clang__) +__attribute__((no_sanitize("undefined"))) +# endif #endif #if defined(_MSC_VER) static __inline U32 A32(const void * x) diff --git a/Utilities/cmliblzma/CMakeLists.txt b/Utilities/cmliblzma/CMakeLists.txt index 3121fbe4c21..6ac02f0e0e3 100644 --- a/Utilities/cmliblzma/CMakeLists.txt +++ b/Utilities/cmliblzma/CMakeLists.txt @@ -176,6 +176,11 @@ endif() ADD_LIBRARY(cmliblzma STATIC ${LZMA_SRCS}) +# Disable inline assembly in a case where it does not compile. +if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_FLAGS MATCHES "-ftrapv") + set_property(SOURCE liblzma/lzma/lzma_decoder.c PROPERTY COMPILE_DEFINITIONS LZMA_RANGE_DECODER_CONFIG=0) +endif() + IF(CMAKE_C_COMPILER_ID STREQUAL "XL") # Disable the XL compiler optimizer because it causes crashes # and other bad behavior in liblzma code. @@ -186,4 +191,8 @@ ELSEIF((CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "LCC" SET_PROPERTY(TARGET cmliblzma PROPERTY COMPILE_FLAGS "-O0") ENDIF() +if(WIN32 AND CMake_BUILD_PCH) + target_precompile_headers(cmliblzma PRIVATE "common/mythread.h") +endif() + INSTALL(FILES COPYING DESTINATION ${CMAKE_DOC_DIR}/cmliblzma) diff --git a/Utilities/cmliblzma/COPYING b/Utilities/cmliblzma/COPYING index 20e60d5b242..aed21531497 100644 --- a/Utilities/cmliblzma/COPYING +++ b/Utilities/cmliblzma/COPYING @@ -3,63 +3,81 @@ XZ Utils Licensing ================== Different licenses apply to different files in this package. Here - is a rough summary of which licenses apply to which parts of this - package (but check the individual files to be sure!): + is a summary of which licenses apply to which parts of this package: - - liblzma is in the public domain. + - liblzma is under the BSD Zero Clause License (0BSD). - - xz, xzdec, and lzmadec command line tools are in the public - domain unless GNU getopt_long had to be compiled and linked - in from the lib directory. The getopt_long code is under - GNU LGPLv2.1+. + - The command line tools xz, xzdec, lzmadec, and lzmainfo are + under 0BSD except that, on systems that don't have a usable + getopt_long, GNU getopt_long is compiled and linked in from the + 'lib' directory. The getopt_long code is under GNU LGPLv2.1+. - The scripts to grep, diff, and view compressed files have been - adapted from gzip. These scripts and their documentation are - under GNU GPLv2+. + adapted from GNU gzip. These scripts (xzgrep, xzdiff, xzless, + and xzmore) are under GNU GPLv2+. The man pages of the scripts + are under 0BSD; they aren't based on the man pages of GNU gzip. - - All the documentation in the doc directory and most of the - XZ Utils specific documentation files in other directories - are in the public domain. + - Most of the XZ Utils specific documentation that is in + plain text files (like README, INSTALL, PACKAGERS, NEWS, + and ChangeLog) are under 0BSD unless stated otherwise in + the file itself. The files xz-file-format.txt and + lzma-file-format.xt are in the public domain but may + be distributed under the terms of 0BSD too. - - Translated messages are in the public domain. + - Translated messages and man pages are under 0BSD except that + some old translations are in the public domain. - - The build system contains public domain files, and files that - are under GNU GPLv2+ or GNU GPLv3+. None of these files end up - in the binaries being built. + - Test files and test code in the 'tests' directory, and + debugging utilities in the 'debug' directory are under + the BSD Zero Clause License (0BSD). - - Test files and test code in the tests directory, and debugging - utilities in the debug directory are in the public domain. + - The GNU Autotools based build system contains files that are + under GNU GPLv2+, GNU GPLv3+, and a few permissive licenses. + These files don't affect the licensing of the binaries being + built. - - The extra directory may contain public domain files, and files - that are under various free software licenses. + - The 'extra' directory contains files that are under various + free software licenses. These aren't built or installed as + part of XZ Utils. - You can do whatever you want with the files that have been put into - the public domain. If you find public domain legally problematic, - take the previous sentence as a license grant. If you still find - the lack of copyright legally problematic, you have too many - lawyers. + For the files under the BSD Zero Clause License (0BSD), if + a copyright notice is needed, the following is sufficient: - As usual, this software is provided "as is", without any warranty. + Copyright (C) The XZ Utils authors and contributors - If you copy significant amounts of public domain code from XZ Utils + If you copy significant amounts of 0BSD-licensed code from XZ Utils into your project, acknowledging this somewhere in your software is polite (especially if it is proprietary, non-free software), but - naturally it is not legally required. Here is an example of a good - notice to put into "about box" or into documentation: + it is not legally required by the license terms. Here is an example + of a good notice to put into "about box" or into documentation: This software includes code from XZ Utils . The following license texts are included in the following files: + - COPYING.0BSD: BSD Zero Clause License - COPYING.LGPLv2.1: GNU Lesser General Public License version 2.1 - COPYING.GPLv2: GNU General Public License version 2 - COPYING.GPLv3: GNU General Public License version 3 - Note that the toolchain (compiler, linker etc.) may add some code - pieces that are copyrighted. Thus, it is possible that e.g. liblzma - binary wouldn't actually be in the public domain in its entirety - even though it contains no copyrighted code from the XZ Utils source - package. - - If you have questions, don't hesitate to ask the author(s) for more - information. + A note about old XZ Utils releases: + + XZ Utils releases 5.4.6 and older and 5.5.1alpha have a + significant amount of code put into the public domain and + that obviously remains so. The switch from public domain to + 0BSD for newer releases was made in Febrary 2024 because + public domain has (real or perceived) legal ambiguities in + some jurisdictions. + + There is very little *practical* difference between public + domain and 0BSD. The main difference likely is that one + shouldn't claim that 0BSD-licensed code is in the public + domain; 0BSD-licensed code is copyrighted but available under + an extremely permissive license. Neither 0BSD nor public domain + require retaining or reproducing author, copyright holder, or + license notices when distributing the software. (Compare to, + for example, BSD 2-Clause "Simplified" License which does have + such requirements.) + + If you have questions, don't hesitate to ask for more information. + The contact information is in the README file. diff --git a/Utilities/cmliblzma/common/common_w32res.rc b/Utilities/cmliblzma/common/common_w32res.rc index a70de343205..8114ee311e6 100644 --- a/Utilities/cmliblzma/common/common_w32res.rc +++ b/Utilities/cmliblzma/common/common_w32res.rc @@ -1,12 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. */ #include -#include "config.h" +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif #define LZMA_H_INTERNAL #define LZMA_H_INTERNAL_RC #include "lzma/version.h" @@ -21,14 +22,15 @@ #define MY_PRODUCT PACKAGE_NAME " <" PACKAGE_URL ">" LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + VS_VERSION_INFO VERSIONINFO - FILEVERSION MY_VERSION - PRODUCTVERSION MY_VERSION - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK - FILEFLAGS 0 - FILEOS VOS_NT_WINDOWS32 - FILETYPE MY_TYPE - FILESUBTYPE 0x0L +FILEVERSION MY_VERSION +PRODUCTVERSION MY_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS 0 +FILEOS VOS_NT_WINDOWS32 +FILETYPE MY_TYPE +FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN @@ -48,3 +50,8 @@ BEGIN VALUE "Translation", 0x409, 1200 END END + +/* Omit the manifest on Cygwin and MSYS2 (both define __CYGWIN__). */ +#if MY_TYPE == VFT_APP && !defined(__CYGWIN__) +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "w32_application.manifest" +#endif diff --git a/Utilities/cmliblzma/common/mythread.h b/Utilities/cmliblzma/common/mythread.h index be22654240a..10ea2d42c24 100644 --- a/Utilities/cmliblzma/common/mythread.h +++ b/Utilities/cmliblzma/common/mythread.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file mythread.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef MYTHREAD_H @@ -79,7 +78,7 @@ do { \ } while (0) -#if !(defined(_WIN32) && !defined(__CYGWIN__)) +#if !(defined(_WIN32) && !defined(__CYGWIN__)) && !defined(__wasm__) // Use sigprocmask() to set the signal mask in single-threaded programs. #include @@ -100,12 +99,37 @@ mythread_sigmask(int how, const sigset_t *restrict set, // Using pthreads // //////////////////// -#include #include #include #include #include +// If clock_gettime() isn't available, use gettimeofday() from +// as a fallback. gettimeofday() is in SUSv2 and thus is supported on all +// relevant POSIX systems. +#ifndef HAVE_CLOCK_GETTIME +# include +#endif + +// MinGW-w64 with winpthreads: +// +// NOTE: Typical builds with MinGW-w64 don't use this code (MYTHREAD_POSIX). +// Instead, native Windows threading APIs are used (MYTHREAD_VISTA or +// MYTHREAD_WIN95). +// +// MinGW-w64 has _sigset_t (an integer type) in . +// If _POSIX was #defined, the header would add the alias sigset_t too. +// Let's keep this working even without _POSIX. +// +// There are no functions that actually do something with sigset_t +// because signals barely exist on Windows. The sigfillset macro below +// is just to silence warnings. There is no sigfillset() in MinGW-w64. +#ifdef __MINGW32__ +# include +# define sigset_t _sigset_t +# define sigfillset(set_ptr) do { *(set_ptr) = 0; } while (0) +#endif + #define MYTHREAD_RET_TYPE void * #define MYTHREAD_RET_VALUE NULL @@ -134,11 +158,13 @@ typedef struct timespec mythread_condtime; // Use pthread_sigmask() to set the signal mask in multi-threaded programs. // Do nothing on OpenVMS since it lacks pthread_sigmask(). +// Do nothing on MinGW-w64 too to silence warnings (its pthread_sigmask() +// is #defined to 0 so it's a no-op). static inline void mythread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset) { -#ifdef __VMS +#if defined(__VMS) || defined(__MINGW32__) (void)how; (void)set; (void)oset; @@ -174,7 +200,7 @@ mythread_join(mythread thread) } -// Initiatlizes a mutex. Returns zero on success and non-zero on error. +// Initializes a mutex. Returns zero on success and non-zero on error. static inline int mythread_mutex_init(mythread_mutex *mutex) { @@ -219,8 +245,8 @@ static inline int mythread_cond_init(mythread_cond *mycond) { #ifdef HAVE_CLOCK_GETTIME - // NOTE: HAVE_DECL_CLOCK_MONOTONIC is always defined to 0 or 1. -# if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && HAVE_DECL_CLOCK_MONOTONIC +# if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && \ + defined(HAVE_CLOCK_MONOTONIC) struct timespec ts; pthread_condattr_t condattr; @@ -294,8 +320,8 @@ static inline void mythread_condtime_set(mythread_condtime *condtime, const mythread_cond *cond, uint32_t timeout_ms) { - condtime->tv_sec = timeout_ms / 1000; - condtime->tv_nsec = (timeout_ms % 1000) * 1000000; + condtime->tv_sec = (time_t)(timeout_ms / 1000); + condtime->tv_nsec = (long)((timeout_ms % 1000) * 1000000); #ifdef HAVE_CLOCK_GETTIME struct timespec now; @@ -370,10 +396,11 @@ typedef struct { BOOL pending_; \ if (!InitOnceBeginInitialize(&once_, 0, &pending_, NULL)) \ abort(); \ - if (pending_) \ + if (pending_) { \ func(); \ - if (!InitOnceComplete(&once, 0, NULL)) \ - abort(); \ + if (!InitOnceComplete(&once_, 0, NULL)) \ + abort(); \ + } \ } while (0) #endif diff --git a/Utilities/cmliblzma/common/sysdefs.h b/Utilities/cmliblzma/common/sysdefs.h index 86c5da0d693..3cbdf1ed3e5 100644 --- a/Utilities/cmliblzma/common/sysdefs.h +++ b/Utilities/cmliblzma/common/sysdefs.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file sysdefs.h @@ -8,9 +10,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_SYSDEFS_H @@ -29,7 +28,15 @@ #include "config.h" -// Get standard-compliant stdio functions under MinGW and MinGW-w64. +// This #define ensures that C99 and POSIX compliant stdio functions are +// available with MinGW-w64 (both 32-bit and 64-bit). Modern MinGW-w64 adds +// this automatically, for example, when the compiler is in C99 (or later) +// mode when building against msvcrt.dll. It still doesn't hurt to be explicit +// that we always want this and #define this unconditionally. +// +// With Universal CRT (UCRT) this is less important because UCRT contains +// C99-compatible stdio functions. It's still nice to #define this as UCRT +// doesn't support the POSIX thousand separator flag in printf (like "%'u"). #ifdef __MINGW32__ # define __USE_MINGW_ANSI_STDIO 1 #endif @@ -138,7 +145,7 @@ #include #include -// Pre-C99 systems lack stdbool.h. All the code in LZMA Utils must be written +// Pre-C99 systems lack stdbool.h. All the code in XZ Utils must be written // so that it works with fake bool type, for example: // // bool foo = (flags & 0x100) != 0; @@ -160,21 +167,21 @@ typedef unsigned char _Bool; # define __bool_true_false_are_defined 1 #endif -// string.h should be enough but let's include strings.h and memory.h too if -// they exists, since that shouldn't do any harm, but may improve portability. #include -#ifdef HAVE_STRINGS_H -# include -#endif - -#ifdef HAVE_MEMORY_H -# include -#endif - -// As of MSVC 2013, inline and restrict are supported with -// non-standard keywords. -#if defined(_WIN32) && defined(_MSC_VER) +// Visual Studio 2013 update 2 supports only __inline, not inline. +// MSVC v19.0 / VS 2015 and newer support both. +// +// MSVC v19.27 (VS 2019 version 16.7) added support for restrict. +// Older ones support only __restrict. +#ifdef _MSC_VER +# if _MSC_VER < 1900 && !defined(inline) +# define inline __inline +# endif +# if _MSC_VER < 1927 && !defined(restrict) +# define restrict __restrict +# endif +#elif defined(__EDG__) && defined(__LCC__) # ifndef inline # define inline __inline # endif diff --git a/Utilities/cmliblzma/common/tuklib_common.h b/Utilities/cmliblzma/common/tuklib_common.h index 31fbab58b00..7554dfc86fb 100644 --- a/Utilities/cmliblzma/common/tuklib_common.h +++ b/Utilities/cmliblzma/common/tuklib_common.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file tuklib_common.h @@ -5,16 +7,13 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef TUKLIB_COMMON_H #define TUKLIB_COMMON_H // The config file may be replaced by a package-specific file. -// It should include at least stddef.h, inttypes.h, and limits.h. +// It should include at least stddef.h, stdbool.h, inttypes.h, and limits.h. #include "tuklib_config.h" // TUKLIB_SYMBOL_PREFIX is prefixed to all symbols exported by @@ -57,8 +56,28 @@ # define TUKLIB_GNUC_REQ(major, minor) 0 #endif -#if TUKLIB_GNUC_REQ(2, 5) +// tuklib_attr_noreturn attribute is used to mark functions as non-returning. +// We cannot use "noreturn" as the macro name because then C23 code that +// uses [[noreturn]] would break as it would expand to [[ [[noreturn]] ]]. +// +// tuklib_attr_noreturn must be used at the beginning of function declaration +// to work in all cases. The [[noreturn]] syntax is the most limiting, it +// must be even before any GNU C's __attribute__ keywords: +// +// tuklib_attr_noreturn +// __attribute__((nonnull(1))) +// extern void foo(const char *s); +// +// FIXME: Update __STDC_VERSION__ for the final C23 version. 202000 is used +// by GCC 13 and Clang 15 with -std=c2x. +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000 +# define tuklib_attr_noreturn [[noreturn]] +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112 +# define tuklib_attr_noreturn _Noreturn +#elif TUKLIB_GNUC_REQ(2, 5) # define tuklib_attr_noreturn __attribute__((__noreturn__)) +#elif defined(_MSC_VER) +# define tuklib_attr_noreturn __declspec(noreturn) #else # define tuklib_attr_noreturn #endif diff --git a/Utilities/cmliblzma/common/tuklib_config.h b/Utilities/cmliblzma/common/tuklib_config.h index 549cb24d773..b27251dc276 100644 --- a/Utilities/cmliblzma/common/tuklib_config.h +++ b/Utilities/cmliblzma/common/tuklib_config.h @@ -1,7 +1,12 @@ +// SPDX-License-Identifier: 0BSD + +// If config.h isn't available, assume that the headers required by +// tuklib_common.h are available. This is required by crc32_tablegen.c. #ifdef HAVE_CONFIG_H # include "sysdefs.h" #else # include +# include # include # include #endif diff --git a/Utilities/cmliblzma/common/tuklib_cpucores.c b/Utilities/cmliblzma/common/tuklib_cpucores.c index cc968dd25ef..c4a781ac387 100644 --- a/Utilities/cmliblzma/common/tuklib_cpucores.c +++ b/Utilities/cmliblzma/common/tuklib_cpucores.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file tuklib_cpucores.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "tuklib_cpucores.h" @@ -72,7 +71,16 @@ tuklib_cpucores(void) } #elif defined(TUKLIB_CPUCORES_SYSCTL) + // On OpenBSD HW_NCPUONLINE tells the number of processor cores that + // are online so it is preferred over HW_NCPU which also counts cores + // that aren't currently available. The number of cores online is + // often less than HW_NCPU because OpenBSD disables simultaneous + // multi-threading (SMT) by default. +# ifdef HW_NCPUONLINE + int name[2] = { CTL_HW, HW_NCPUONLINE }; +# else int name[2] = { CTL_HW, HW_NCPU }; +# endif int cpus; size_t cpus_size = sizeof(cpus); if (sysctl(name, 2, &cpus, &cpus_size, NULL, 0) != -1 diff --git a/Utilities/cmliblzma/common/tuklib_cpucores.h b/Utilities/cmliblzma/common/tuklib_cpucores.h index be1ce1c175a..edff9395e41 100644 --- a/Utilities/cmliblzma/common/tuklib_cpucores.h +++ b/Utilities/cmliblzma/common/tuklib_cpucores.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file tuklib_cpucores.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef TUKLIB_CPUCORES_H diff --git a/Utilities/cmliblzma/common/tuklib_integer.h b/Utilities/cmliblzma/common/tuklib_integer.h index 4e613575e59..ea6af84c73f 100644 --- a/Utilities/cmliblzma/common/tuklib_integer.h +++ b/Utilities/cmliblzma/common/tuklib_integer.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file tuklib_integer.h @@ -14,11 +16,11 @@ /// /// Endianness-converting integer operations (these can be macros!) /// (XX = 16, 32, or 64; Y = b or l): -/// - Byte swapping: bswapXX(num) +/// - Byte swapping: byteswapXX(num) /// - Byte order conversions to/from native (byteswaps if Y isn't /// the native endianness): convXXYe(num) -/// - Unaligned reads (16/32-bit only): readXXYe(ptr) -/// - Unaligned writes (16/32-bit only): writeXXYe(ptr, num) +/// - Unaligned reads: readXXYe(ptr) +/// - Unaligned writes: writeXXYe(ptr, num) /// - Aligned reads: aligned_readXXYe(ptr) /// - Aligned writes: aligned_writeXXYe(ptr, num) /// @@ -37,9 +39,6 @@ // Authors: Lasse Collin // Joachim Henke // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef TUKLIB_INTEGER_H @@ -52,6 +51,12 @@ // and such functions. #if defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1500) # include +// Only include when it is needed. GCC and Clang can both +// use __builtin's, so we only need Windows instrincs when using MSVC. +// GCC and Clang can set _MSC_VER on Windows, so we need to exclude these +// cases explicitly. +#elif defined(_MSC_VER) && !TUKLIB_GNUC_REQ(3, 4) && !defined(__clang__) +# include #endif @@ -61,38 +66,47 @@ #if defined(HAVE___BUILTIN_BSWAPXX) // GCC >= 4.8 and Clang -# define bswap16(n) __builtin_bswap16(n) -# define bswap32(n) __builtin_bswap32(n) -# define bswap64(n) __builtin_bswap64(n) +# define byteswap16(num) __builtin_bswap16(num) +# define byteswap32(num) __builtin_bswap32(num) +# define byteswap64(num) __builtin_bswap64(num) #elif defined(HAVE_BYTESWAP_H) // glibc, uClibc, dietlibc # include # ifdef HAVE_BSWAP_16 -# define bswap16(num) bswap_16(num) +# define byteswap16(num) bswap_16(num) # endif # ifdef HAVE_BSWAP_32 -# define bswap32(num) bswap_32(num) +# define byteswap32(num) bswap_32(num) # endif # ifdef HAVE_BSWAP_64 -# define bswap64(num) bswap_64(num) +# define byteswap64(num) bswap_64(num) # endif #elif defined(HAVE_SYS_ENDIAN_H) // *BSDs and Darwin # include +# ifdef __OpenBSD__ +# define byteswap16(num) swap16(num) +# define byteswap32(num) swap32(num) +# define byteswap64(num) swap64(num) +# else +# define byteswap16(num) bswap16(num) +# define byteswap32(num) bswap32(num) +# define byteswap64(num) bswap64(num) +# endif #elif defined(HAVE_SYS_BYTEORDER_H) // Solaris # include # ifdef BSWAP_16 -# define bswap16(num) BSWAP_16(num) +# define byteswap16(num) BSWAP_16(num) # endif # ifdef BSWAP_32 -# define bswap32(num) BSWAP_32(num) +# define byteswap32(num) BSWAP_32(num) # endif # ifdef BSWAP_64 -# define bswap64(num) BSWAP_64(num) +# define byteswap64(num) BSWAP_64(num) # endif # ifdef BE_16 # define conv16be(num) BE_16(num) @@ -114,15 +128,15 @@ # endif #endif -#ifndef bswap16 -# define bswap16(n) (uint16_t)( \ +#ifndef byteswap16 +# define byteswap16(n) (uint16_t)( \ (((n) & 0x00FFU) << 8) \ | (((n) & 0xFF00U) >> 8) \ ) #endif -#ifndef bswap32 -# define bswap32(n) (uint32_t)( \ +#ifndef byteswap32 +# define byteswap32(n) (uint32_t)( \ (((n) & UINT32_C(0x000000FF)) << 24) \ | (((n) & UINT32_C(0x0000FF00)) << 8) \ | (((n) & UINT32_C(0x00FF0000)) >> 8) \ @@ -130,8 +144,8 @@ ) #endif -#ifndef bswap64 -# define bswap64(n) (uint64_t)( \ +#ifndef byteswap64 +# define byteswap64(n) (uint64_t)( \ (((n) & UINT64_C(0x00000000000000FF)) << 56) \ | (((n) & UINT64_C(0x000000000000FF00)) << 40) \ | (((n) & UINT64_C(0x0000000000FF0000)) << 24) \ @@ -155,23 +169,23 @@ # define conv64be(num) ((uint64_t)(num)) # endif # ifndef conv16le -# define conv16le(num) bswap16(num) +# define conv16le(num) byteswap16(num) # endif # ifndef conv32le -# define conv32le(num) bswap32(num) +# define conv32le(num) byteswap32(num) # endif # ifndef conv64le -# define conv64le(num) bswap64(num) +# define conv64le(num) byteswap64(num) # endif #else # ifndef conv16be -# define conv16be(num) bswap16(num) +# define conv16be(num) byteswap16(num) # endif # ifndef conv32be -# define conv32be(num) bswap32(num) +# define conv32be(num) byteswap32(num) # endif # ifndef conv64be -# define conv64be(num) bswap64(num) +# define conv64be(num) byteswap64(num) # endif # ifndef conv16le # define conv16le(num) ((uint16_t)(num)) @@ -189,6 +203,9 @@ // Unaligned reads and writes // //////////////////////////////// +// No-strict-align archs like x86-64 +// --------------------------------- +// // The traditional way of casting e.g. *(const uint16_t *)uint8_pointer // is bad even if the uint8_pointer is properly aligned because this kind // of casts break strict aliasing rules and result in undefined behavior. @@ -203,12 +220,115 @@ // build time. A third method, casting to a packed struct, would also be // an option but isn't provided to keep things simpler (it's already a mess). // Hopefully this is flexible enough in practice. +// +// Some compilers on x86-64 like Clang >= 10 and GCC >= 5.1 detect that +// +// buf[0] | (buf[1] << 8) +// +// reads a 16-bit value and can emit a single 16-bit load and produce +// identical code than with the memcpy() method. In other cases Clang and GCC +// produce either the same or better code with memcpy(). For example, Clang 9 +// on x86-64 can detect 32-bit load but not 16-bit load. +// +// MSVC uses unaligned access with the memcpy() method but emits byte-by-byte +// code for "buf[0] | (buf[1] << 8)". +// +// Conclusion: The memcpy() method is the best choice when unaligned access +// is supported. +// +// Strict-align archs like SPARC +// ----------------------------- +// +// GCC versions from around 4.x to to at least 13.2.0 produce worse code +// from the memcpy() method than from simple byte-by-byte shift-or code +// when reading a 32-bit integer: +// +// (1) It may be constructed on stack using four 8-bit loads, +// four 8-bit stores to stack, and finally one 32-bit load from stack. +// +// (2) Especially with -Os, an actual memcpy() call may be emitted. +// +// This is true on at least on ARM, ARM64, SPARC, SPARC64, MIPS64EL, and +// RISC-V. Of these, ARM, ARM64, and RISC-V support unaligned access in +// some processors but not all so this is relevant only in the case when +// GCC assumes that unaligned is not supported or -mstrict-align or +// -mno-unaligned-access is used. +// +// For Clang it makes little difference. ARM64 with -O2 -mstrict-align +// was one the very few with a minor difference: the memcpy() version +// was one instruction longer. +// +// Conclusion: At least in case of GCC and Clang, byte-by-byte code is +// the best choice for strict-align archs to do unaligned access. +// +// See also: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111502 +// +// Thanks to it was easy to test different compilers. +// The following is for little endian targets: +/* +#include +#include + +uint32_t bytes16(const uint8_t *b) +{ + return (uint32_t)b[0] + | ((uint32_t)b[1] << 8); +} + +uint32_t copy16(const uint8_t *b) +{ + uint16_t v; + memcpy(&v, b, sizeof(v)); + return v; +} + +uint32_t bytes32(const uint8_t *b) +{ + return (uint32_t)b[0] + | ((uint32_t)b[1] << 8) + | ((uint32_t)b[2] << 16) + | ((uint32_t)b[3] << 24); +} + +uint32_t copy32(const uint8_t *b) +{ + uint32_t v; + memcpy(&v, b, sizeof(v)); + return v; +} + +void wbytes16(uint8_t *b, uint16_t v) +{ + b[0] = (uint8_t)v; + b[1] = (uint8_t)(v >> 8); +} + +void wcopy16(uint8_t *b, uint16_t v) +{ + memcpy(b, &v, sizeof(v)); +} + +void wbytes32(uint8_t *b, uint32_t v) +{ + b[0] = (uint8_t)v; + b[1] = (uint8_t)(v >> 8); + b[2] = (uint8_t)(v >> 16); + b[3] = (uint8_t)(v >> 24); +} + +void wcopy32(uint8_t *b, uint32_t v) +{ + memcpy(b, &v, sizeof(v)); +} +*/ + + +#ifdef TUKLIB_FAST_UNALIGNED_ACCESS static inline uint16_t read16ne(const uint8_t *buf) { -#if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ - && defined(TUKLIB_USE_UNSAFE_TYPE_PUNNING) +#ifdef TUKLIB_USE_UNSAFE_TYPE_PUNNING return *(const uint16_t *)buf; #else uint16_t num; @@ -221,8 +341,7 @@ read16ne(const uint8_t *buf) static inline uint32_t read32ne(const uint8_t *buf) { -#if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ - && defined(TUKLIB_USE_UNSAFE_TYPE_PUNNING) +#ifdef TUKLIB_USE_UNSAFE_TYPE_PUNNING return *(const uint32_t *)buf; #else uint32_t num; @@ -235,8 +354,7 @@ read32ne(const uint8_t *buf) static inline uint64_t read64ne(const uint8_t *buf) { -#if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ - && defined(TUKLIB_USE_UNSAFE_TYPE_PUNNING) +#ifdef TUKLIB_USE_UNSAFE_TYPE_PUNNING return *(const uint64_t *)buf; #else uint64_t num; @@ -249,8 +367,7 @@ read64ne(const uint8_t *buf) static inline void write16ne(uint8_t *buf, uint16_t num) { -#if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ - && defined(TUKLIB_USE_UNSAFE_TYPE_PUNNING) +#ifdef TUKLIB_USE_UNSAFE_TYPE_PUNNING *(uint16_t *)buf = num; #else memcpy(buf, &num, sizeof(num)); @@ -262,8 +379,7 @@ write16ne(uint8_t *buf, uint16_t num) static inline void write32ne(uint8_t *buf, uint32_t num) { -#if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ - && defined(TUKLIB_USE_UNSAFE_TYPE_PUNNING) +#ifdef TUKLIB_USE_UNSAFE_TYPE_PUNNING *(uint32_t *)buf = num; #else memcpy(buf, &num, sizeof(num)); @@ -275,8 +391,7 @@ write32ne(uint8_t *buf, uint32_t num) static inline void write64ne(uint8_t *buf, uint64_t num) { -#if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ - && defined(TUKLIB_USE_UNSAFE_TYPE_PUNNING) +#ifdef TUKLIB_USE_UNSAFE_TYPE_PUNNING *(uint64_t *)buf = num; #else memcpy(buf, &num, sizeof(num)); @@ -288,77 +403,149 @@ write64ne(uint8_t *buf, uint64_t num) static inline uint16_t read16be(const uint8_t *buf) { -#if defined(WORDS_BIGENDIAN) || defined(TUKLIB_FAST_UNALIGNED_ACCESS) uint16_t num = read16ne(buf); return conv16be(num); -#else - uint16_t num = ((uint16_t)buf[0] << 8) | (uint16_t)buf[1]; - return num; -#endif } static inline uint16_t read16le(const uint8_t *buf) { -#if !defined(WORDS_BIGENDIAN) || defined(TUKLIB_FAST_UNALIGNED_ACCESS) uint16_t num = read16ne(buf); return conv16le(num); -#else - uint16_t num = ((uint16_t)buf[0]) | ((uint16_t)buf[1] << 8); - return num; -#endif } static inline uint32_t read32be(const uint8_t *buf) { -#if defined(WORDS_BIGENDIAN) || defined(TUKLIB_FAST_UNALIGNED_ACCESS) uint32_t num = read32ne(buf); return conv32be(num); +} + + +static inline uint32_t +read32le(const uint8_t *buf) +{ + uint32_t num = read32ne(buf); + return conv32le(num); +} + + +static inline uint64_t +read64be(const uint8_t *buf) +{ + uint64_t num = read64ne(buf); + return conv64be(num); +} + + +static inline uint64_t +read64le(const uint8_t *buf) +{ + uint64_t num = read64ne(buf); + return conv64le(num); +} + + +// NOTE: Possible byte swapping must be done in a macro to allow the compiler +// to optimize byte swapping of constants when using glibc's or *BSD's +// byte swapping macros. The actual write is done in an inline function +// to make type checking of the buf pointer possible. +#define write16be(buf, num) write16ne(buf, conv16be(num)) +#define write32be(buf, num) write32ne(buf, conv32be(num)) +#define write64be(buf, num) write64ne(buf, conv64be(num)) +#define write16le(buf, num) write16ne(buf, conv16le(num)) +#define write32le(buf, num) write32ne(buf, conv32le(num)) +#define write64le(buf, num) write64ne(buf, conv64le(num)) + #else + +#ifdef WORDS_BIGENDIAN +# define read16ne read16be +# define read32ne read32be +# define read64ne read64be +# define write16ne write16be +# define write32ne write32be +# define write64ne write64be +#else +# define read16ne read16le +# define read32ne read32le +# define read64ne read64le +# define write16ne write16le +# define write32ne write32le +# define write64ne write64le +#endif + + +static inline uint16_t +read16be(const uint8_t *buf) +{ + uint16_t num = ((uint16_t)buf[0] << 8) | (uint16_t)buf[1]; + return num; +} + + +static inline uint16_t +read16le(const uint8_t *buf) +{ + uint16_t num = ((uint16_t)buf[0]) | ((uint16_t)buf[1] << 8); + return num; +} + + +static inline uint32_t +read32be(const uint8_t *buf) +{ uint32_t num = (uint32_t)buf[0] << 24; num |= (uint32_t)buf[1] << 16; num |= (uint32_t)buf[2] << 8; num |= (uint32_t)buf[3]; return num; -#endif } static inline uint32_t read32le(const uint8_t *buf) { -#if !defined(WORDS_BIGENDIAN) || defined(TUKLIB_FAST_UNALIGNED_ACCESS) - uint32_t num = read32ne(buf); - return conv32le(num); -#else uint32_t num = (uint32_t)buf[0]; num |= (uint32_t)buf[1] << 8; num |= (uint32_t)buf[2] << 16; num |= (uint32_t)buf[3] << 24; return num; -#endif } -// NOTE: Possible byte swapping must be done in a macro to allow the compiler -// to optimize byte swapping of constants when using glibc's or *BSD's -// byte swapping macros. The actual write is done in an inline function -// to make type checking of the buf pointer possible. -#if defined(WORDS_BIGENDIAN) || defined(TUKLIB_FAST_UNALIGNED_ACCESS) -# define write16be(buf, num) write16ne(buf, conv16be(num)) -# define write32be(buf, num) write32ne(buf, conv32be(num)) -#endif +static inline uint64_t +read64be(const uint8_t *buf) +{ + uint64_t num = (uint64_t)buf[0] << 56; + num |= (uint64_t)buf[1] << 48; + num |= (uint64_t)buf[2] << 40; + num |= (uint64_t)buf[3] << 32; + num |= (uint64_t)buf[4] << 24; + num |= (uint64_t)buf[5] << 16; + num |= (uint64_t)buf[6] << 8; + num |= (uint64_t)buf[7]; + return num; +} -#if !defined(WORDS_BIGENDIAN) || defined(TUKLIB_FAST_UNALIGNED_ACCESS) -# define write16le(buf, num) write16ne(buf, conv16le(num)) -# define write32le(buf, num) write32ne(buf, conv32le(num)) -#endif + +static inline uint64_t +read64le(const uint8_t *buf) +{ + uint64_t num = (uint64_t)buf[0]; + num |= (uint64_t)buf[1] << 8; + num |= (uint64_t)buf[2] << 16; + num |= (uint64_t)buf[3] << 24; + num |= (uint64_t)buf[4] << 32; + num |= (uint64_t)buf[5] << 40; + num |= (uint64_t)buf[6] << 48; + num |= (uint64_t)buf[7] << 56; + return num; +} -#ifndef write16be static inline void write16be(uint8_t *buf, uint16_t num) { @@ -366,10 +553,8 @@ write16be(uint8_t *buf, uint16_t num) buf[1] = (uint8_t)num; return; } -#endif -#ifndef write16le static inline void write16le(uint8_t *buf, uint16_t num) { @@ -377,10 +562,8 @@ write16le(uint8_t *buf, uint16_t num) buf[1] = (uint8_t)(num >> 8); return; } -#endif -#ifndef write32be static inline void write32be(uint8_t *buf, uint32_t num) { @@ -390,10 +573,8 @@ write32be(uint8_t *buf, uint32_t num) buf[3] = (uint8_t)num; return; } -#endif -#ifndef write32le static inline void write32le(uint8_t *buf, uint32_t num) { @@ -403,6 +584,37 @@ write32le(uint8_t *buf, uint32_t num) buf[3] = (uint8_t)(num >> 24); return; } + + +static inline void +write64be(uint8_t *buf, uint64_t num) +{ + buf[0] = (uint8_t)(num >> 56); + buf[1] = (uint8_t)(num >> 48); + buf[2] = (uint8_t)(num >> 40); + buf[3] = (uint8_t)(num >> 32); + buf[4] = (uint8_t)(num >> 24); + buf[5] = (uint8_t)(num >> 16); + buf[6] = (uint8_t)(num >> 8); + buf[7] = (uint8_t)num; + return; +} + + +static inline void +write64le(uint8_t *buf, uint64_t num) +{ + buf[0] = (uint8_t)num; + buf[1] = (uint8_t)(num >> 8); + buf[2] = (uint8_t)(num >> 16); + buf[3] = (uint8_t)(num >> 24); + buf[4] = (uint8_t)(num >> 32); + buf[5] = (uint8_t)(num >> 40); + buf[6] = (uint8_t)(num >> 48); + buf[7] = (uint8_t)(num >> 56); + return; +} + #endif @@ -421,7 +633,7 @@ write32le(uint8_t *buf, uint32_t num) // aligned but some compilers have language extensions to do that. With // such language extensions the memcpy() method gives excellent results. // -// What to do on a strict-align system when no known language extentensions +// What to do on a strict-align system when no known language extensions // are available? Falling back to byte-by-byte access would be safe but ruin // optimizations that have been made specifically with aligned access in mind. // As a compromise, aligned reads will fall back to non-compliant type punning @@ -588,7 +800,7 @@ bsr32(uint32_t n) #if defined(__INTEL_COMPILER) return _bit_scan_reverse(n); -#elif TUKLIB_GNUC_REQ(3, 4) && UINT_MAX == UINT32_MAX +#elif (TUKLIB_GNUC_REQ(3, 4) || defined(__clang__)) && UINT_MAX == UINT32_MAX // GCC >= 3.4 has __builtin_clz(), which gives good results on // multiple architectures. On x86, __builtin_clz() ^ 31U becomes // either plain BSR (so the XOR gets optimized away) or LZCNT and @@ -637,7 +849,7 @@ clz32(uint32_t n) #if defined(__INTEL_COMPILER) return _bit_scan_reverse(n) ^ 31U; -#elif TUKLIB_GNUC_REQ(3, 4) && UINT_MAX == UINT32_MAX +#elif (TUKLIB_GNUC_REQ(3, 4) || defined(__clang__)) && UINT_MAX == UINT32_MAX return (uint32_t)__builtin_clz(n); #elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) @@ -684,7 +896,7 @@ ctz32(uint32_t n) #if defined(__INTEL_COMPILER) return _bit_scan_forward(n); -#elif TUKLIB_GNUC_REQ(3, 4) && UINT_MAX >= UINT32_MAX +#elif (TUKLIB_GNUC_REQ(3, 4) || defined(__clang__)) && UINT_MAX >= UINT32_MAX return (uint32_t)__builtin_ctz(n); #elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) diff --git a/Utilities/cmliblzma/liblzma/api/lzma.h b/Utilities/cmliblzma/liblzma/api/lzma.h index 122dab80d35..6ca6e503d8a 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma.h +++ b/Utilities/cmliblzma/liblzma/api/lzma.h @@ -1,30 +1,30 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file api/lzma.h * \brief The public API of liblzma data compression library + * \mainpage + * + * liblzma is a general-purpose data compression library with a zlib-like API. + * The native file format is .xz, but also the old .lzma format and raw (no + * headers) streams are supported. Multiple compression algorithms (filters) + * are supported. Currently LZMA2 is the primary filter. * - * liblzma is a public domain general-purpose data compression library with - * a zlib-like API. The native file format is .xz, but also the old .lzma - * format and raw (no headers) streams are supported. Multiple compression - * algorithms (filters) are supported. Currently LZMA2 is the primary filter. + * liblzma is part of XZ Utils . XZ Utils + * includes a gzip-like command line tool named xz and some other tools. + * XZ Utils is developed and maintained by Lasse Collin. * - * liblzma is part of XZ Utils . XZ Utils includes - * a gzip-like command line tool named xz and some other tools. XZ Utils - * is developed and maintained by Lasse Collin. + * Major parts of liblzma are based on code written by Igor Pavlov, + * specifically the LZMA SDK . * - * Major parts of liblzma are based on Igor Pavlov's public domain LZMA SDK - * . + * The SHA-256 implementation in liblzma is based on code written by + * Wei Dai in Crypto++ Library . * - * The SHA-256 implementation is based on the public domain code found from - * 7-Zip , which has a modified version of the public - * domain SHA-256 code found from Crypto++ . - * The SHA-256 code in Crypto++ was written by Kevin Springle and Wei Dai. + * liblzma is distributed under the BSD Zero Clause License (0BSD). */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. */ #ifndef LZMA_H @@ -181,11 +181,11 @@ * against static liblzma on them, don't worry about LZMA_API_STATIC. That * is, most developers will never need to use LZMA_API_STATIC. * - * The GCC variants are a special case on Windows (Cygwin and MinGW). + * The GCC variants are a special case on Windows (Cygwin and MinGW-w64). * We rely on GCC doing the right thing with its auto-import feature, * and thus don't use __declspec(dllimport). This way developers don't * need to worry about LZMA_API_STATIC. Also the calling convention is - * omitted on Cygwin but not on MinGW. + * omitted on Cygwin but not on MinGW-w64. */ #ifndef LZMA_API_IMPORT # if !defined(LZMA_API_STATIC) && defined(_WIN32) && !defined(__GNUC__) @@ -219,7 +219,8 @@ */ #ifndef lzma_nothrow # if defined(__cplusplus) -# if __cplusplus >= 201103L +# if __cplusplus >= 201103L || (defined(_MSVC_LANG) \ + && _MSVC_LANG >= 201103L) # define lzma_nothrow noexcept # else # define lzma_nothrow throw() diff --git a/Utilities/cmliblzma/liblzma/api/lzma/base.h b/Utilities/cmliblzma/liblzma/api/lzma/base.h index a6005accc93..590e1d22bb0 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/base.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/base.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/base.h * \brief Data types and functions used in many places in liblzma API + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -22,8 +20,8 @@ * * This is here because C89 doesn't have stdbool.h. To set a value for * variables having type lzma_bool, you can use - * - C99's `true' and `false' from stdbool.h; - * - C++'s internal `true' and `false'; or + * - C99's 'true' and 'false' from stdbool.h; + * - C++'s internal 'true' and 'false'; or * - integers one (true) and zero (false). */ typedef unsigned char lzma_bool; @@ -138,13 +136,19 @@ typedef enum { */ LZMA_MEMLIMIT_ERROR = 6, - /** + /**< * \brief Memory usage limit was reached * * Decoder would need more memory than allowed by the * specified memory usage limit. To continue decoding, * the memory usage limit has to be increased with * lzma_memlimit_set(). + * + * liblzma 5.2.6 and earlier had a bug in single-threaded .xz + * decoder (lzma_stream_decoder()) which made it impossible + * to continue decoding after LZMA_MEMLIMIT_ERROR even if + * the limit was increased using lzma_memlimit_set(). + * Other decoders worked correctly. */ LZMA_FORMAT_ERROR = 7, @@ -234,17 +238,47 @@ typedef enum { * can be a sign of a bug in liblzma. See the documentation * how to report bugs. */ + + LZMA_SEEK_NEEDED = 12, + /**< + * \brief Request to change the input file position + * + * Some coders can do random access in the input file. The + * initialization functions of these coders take the file size + * as an argument. No other coders can return LZMA_SEEK_NEEDED. + * + * When this value is returned, the application must seek to + * the file position given in lzma_stream.seek_pos. This value + * is guaranteed to never exceed the file size that was + * specified at the coder initialization. + * + * After seeking the application should read new input and + * pass it normally via lzma_stream.next_in and .avail_in. + */ + + /* + * These enumerations may be used internally by liblzma + * but they will never be returned to applications. + */ + LZMA_RET_INTERNAL1 = 101, + LZMA_RET_INTERNAL2 = 102, + LZMA_RET_INTERNAL3 = 103, + LZMA_RET_INTERNAL4 = 104, + LZMA_RET_INTERNAL5 = 105, + LZMA_RET_INTERNAL6 = 106, + LZMA_RET_INTERNAL7 = 107, + LZMA_RET_INTERNAL8 = 108 } lzma_ret; /** - * \brief The `action' argument for lzma_code() + * \brief The 'action' argument for lzma_code() * * After the first use of LZMA_SYNC_FLUSH, LZMA_FULL_FLUSH, LZMA_FULL_BARRIER, - * or LZMA_FINISH, the same `action' must is used until lzma_code() returns + * or LZMA_FINISH, the same 'action' must be used until lzma_code() returns * LZMA_STREAM_END. Also, the amount of input (that is, strm->avail_in) must * not be modified by the application until lzma_code() returns - * LZMA_STREAM_END. Changing the `action' or modifying the amount of input + * LZMA_STREAM_END. Changing the 'action' or modifying the amount of input * will make lzma_code() return LZMA_PROG_ERROR. */ typedef enum { @@ -358,8 +392,8 @@ typedef enum { * Single-threaded mode only: liblzma doesn't make an internal copy of * lzma_allocator. Thus, it is OK to change these function pointers in * the middle of the coding process, but obviously it must be done - * carefully to make sure that the replacement `free' can deallocate - * memory allocated by the earlier `alloc' function(s). + * carefully to make sure that the replacement 'free' can deallocate + * memory allocated by the earlier 'alloc' function(s). * * Multithreaded mode: liblzma might internally store pointers to the * lzma_allocator given via the lzma_stream structure. The application @@ -387,7 +421,7 @@ typedef struct { * liblzma never sets this to zero. * * \return Pointer to the beginning of a memory block of - * `size' bytes, or NULL if allocation fails + * 'size' bytes, or NULL if allocation fails * for some reason. When allocation fails, functions * of liblzma return LZMA_MEM_ERROR. * @@ -447,7 +481,7 @@ typedef struct lzma_internal_s lzma_internal; * * The lzma_stream structure is used for * - passing pointers to input and output buffers to liblzma; - * - defining custom memory hander functions; and + * - defining custom memory handler functions; and * - holding a pointer to coder-specific internal data structures. * * Typical usage: @@ -510,15 +544,44 @@ typedef struct { * you should not touch these, because the names of these variables * may change. */ + + /** \private Reserved member. */ void *reserved_ptr1; + + /** \private Reserved member. */ void *reserved_ptr2; + + /** \private Reserved member. */ void *reserved_ptr3; + + /** \private Reserved member. */ void *reserved_ptr4; - uint64_t reserved_int1; + + /** + * \brief New seek input position for LZMA_SEEK_NEEDED + * + * When lzma_code() returns LZMA_SEEK_NEEDED, the new input position + * needed by liblzma will be available seek_pos. The value is + * guaranteed to not exceed the file size that was specified when + * this lzma_stream was initialized. + * + * In all other situations the value of this variable is undefined. + */ + uint64_t seek_pos; + + /** \private Reserved member. */ uint64_t reserved_int2; + + /** \private Reserved member. */ size_t reserved_int3; + + /** \private Reserved member. */ size_t reserved_int4; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum1; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum2; } lzma_stream; @@ -558,7 +621,15 @@ typedef struct { * to and get output from liblzma. * * See the description of the coder-specific initialization function to find - * out what `action' values are supported by the coder. + * out what 'action' values are supported by the coder. + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param action Action for this function to take. Must be a valid + * lzma_action enum value. + * + * \return Any valid lzma_ret. See the lzma_ret enum description for more + * information. */ extern LZMA_API(lzma_ret) lzma_code(lzma_stream *strm, lzma_action action) lzma_nothrow lzma_attr_warn_unused_result; @@ -567,15 +638,15 @@ extern LZMA_API(lzma_ret) lzma_code(lzma_stream *strm, lzma_action action) /** * \brief Free memory allocated for the coder data structures * - * \param strm Pointer to lzma_stream that is at least initialized - * with LZMA_STREAM_INIT. - * * After lzma_end(strm), strm->internal is guaranteed to be NULL. No other * members of the lzma_stream structure are touched. * * \note zlib indicates an error if application end()s unfinished * stream structure. liblzma doesn't do this, and assumes that * application knows what it is doing. + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. */ extern LZMA_API(void) lzma_end(lzma_stream *strm) lzma_nothrow; @@ -594,6 +665,11 @@ extern LZMA_API(void) lzma_end(lzma_stream *strm) lzma_nothrow; * mode by taking into account the progress made by each thread. In * single-threaded mode *progress_in and *progress_out are set to * strm->total_in and strm->total_out, respectively. + * + * \param strm Pointer to lzma_stream that is at least + * initialized with LZMA_STREAM_INIT. + * \param[out] progress_in Pointer to the number of input bytes processed. + * \param[out] progress_out Pointer to the number of output bytes processed. */ extern LZMA_API(void) lzma_get_progress(lzma_stream *strm, uint64_t *progress_in, uint64_t *progress_out) lzma_nothrow; @@ -612,6 +688,9 @@ extern LZMA_API(void) lzma_get_progress(lzma_stream *strm, * this may give misleading information if decoding .xz Streams that have * multiple Blocks, because each Block can have different memory requirements. * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * * \return How much memory is currently allocated for the filter * decoders. If no filter chain is currently allocated, * some non-zero value is still returned, which is less than @@ -631,6 +710,9 @@ extern LZMA_API(uint64_t) lzma_memusage(const lzma_stream *strm) * This function is supported only when *strm has been initialized with * a function that takes a memlimit argument. * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * * \return On success, the current memory usage limit is returned * (always non-zero). On error, zero is returned. */ @@ -649,7 +731,13 @@ extern LZMA_API(uint64_t) lzma_memlimit_get(const lzma_stream *strm) * return LZMA_OK. Later versions treat 0 as if 1 had been specified (so * lzma_memlimit_get() will return 1 even if you specify 0 here). * - * \return - LZMA_OK: New memory usage limit successfully set. + * liblzma 5.2.6 and earlier had a bug in single-threaded .xz decoder + * (lzma_stream_decoder()) which made it impossible to continue decoding + * after LZMA_MEMLIMIT_ERROR even if the limit was increased using + * lzma_memlimit_set(). Other decoders worked correctly. + * + * \return Possible lzma_ret values: + * - LZMA_OK: New memory usage limit successfully set. * - LZMA_MEMLIMIT_ERROR: The new limit is too small. * The limit was not changed. * - LZMA_PROG_ERROR: Invalid arguments, e.g. *strm doesn't diff --git a/Utilities/cmliblzma/liblzma/api/lzma/bcj.h b/Utilities/cmliblzma/liblzma/api/lzma/bcj.h index 8e37538ad4a..7f6611feb32 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/bcj.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/bcj.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/bcj.h * \brief Branch/Call/Jump conversion filters + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -19,35 +17,45 @@ /* Filter IDs for lzma_filter.id */ +/** + * \brief Filter for x86 binaries + */ #define LZMA_FILTER_X86 LZMA_VLI_C(0x04) - /**< - * Filter for x86 binaries - */ +/** + * \brief Filter for Big endian PowerPC binaries + */ #define LZMA_FILTER_POWERPC LZMA_VLI_C(0x05) - /**< - * Filter for Big endian PowerPC binaries - */ +/** + * \brief Filter for IA-64 (Itanium) binaries + */ #define LZMA_FILTER_IA64 LZMA_VLI_C(0x06) - /**< - * Filter for IA-64 (Itanium) binaries. - */ +/** + * \brief Filter for ARM binaries + */ #define LZMA_FILTER_ARM LZMA_VLI_C(0x07) - /**< - * Filter for ARM binaries. - */ +/** + * \brief Filter for ARM-Thumb binaries + */ #define LZMA_FILTER_ARMTHUMB LZMA_VLI_C(0x08) - /**< - * Filter for ARM-Thumb binaries. - */ +/** + * \brief Filter for SPARC binaries + */ #define LZMA_FILTER_SPARC LZMA_VLI_C(0x09) - /**< - * Filter for SPARC binaries. - */ + +/** + * \brief Filter for ARM64 binaries + */ +#define LZMA_FILTER_ARM64 LZMA_VLI_C(0x0A) + +/** + * \brief Filter for RISC-V binaries + */ +#define LZMA_FILTER_RISCV LZMA_VLI_C(0x0B) /** diff --git a/Utilities/cmliblzma/liblzma/api/lzma/block.h b/Utilities/cmliblzma/liblzma/api/lzma/block.h index 962f38779cc..05b77e59aab 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/block.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/block.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/block.h * \brief .xz Block handling + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -32,19 +30,28 @@ typedef struct { * \brief Block format version * * To prevent API and ABI breakages when new features are needed, - * a version number is used to indicate which fields in this + * a version number is used to indicate which members in this * structure are in use: * - liblzma >= 5.0.0: version = 0 is supported. * - liblzma >= 5.1.4beta: Support for version = 1 was added, - * which adds the ignore_check field. + * which adds the ignore_check member. * * If version is greater than one, most Block related functions * will return LZMA_OPTIONS_ERROR (lzma_block_header_decode() works * with any version value). * * Read by: - * - All functions that take pointer to lzma_block as argument, - * including lzma_block_header_decode(). + * - lzma_block_header_size() + * - lzma_block_header_encode() + * - lzma_block_header_decode() + * - lzma_block_compressed_size() + * - lzma_block_unpadded_size() + * - lzma_block_total_size() + * - lzma_block_encoder() + * - lzma_block_decoder() + * - lzma_block_buffer_encode() + * - lzma_block_uncomp_encode() + * - lzma_block_buffer_decode() * * Written by: * - lzma_block_header_decode() @@ -52,7 +59,7 @@ typedef struct { uint32_t version; /** - * \brief Size of the Block Header field + * \brief Size of the Block Header field in bytes * * This is always a multiple of four. * @@ -68,6 +75,7 @@ typedef struct { * Written by: * - lzma_block_header_size() * - lzma_block_buffer_encode() + * - lzma_block_uncomp_encode() */ uint32_t header_size; # define LZMA_BLOCK_HEADER_SIZE_MIN 8 @@ -143,6 +151,7 @@ typedef struct { * - lzma_block_encoder() * - lzma_block_decoder() * - lzma_block_buffer_encode() + * - lzma_block_uncomp_encode() * - lzma_block_buffer_decode() */ lzma_vli compressed_size; @@ -167,6 +176,7 @@ typedef struct { * - lzma_block_encoder() * - lzma_block_decoder() * - lzma_block_buffer_encode() + * - lzma_block_uncomp_encode() * - lzma_block_buffer_decode() */ lzma_vli uncompressed_size; @@ -212,6 +222,7 @@ typedef struct { * - lzma_block_encoder() * - lzma_block_decoder() * - lzma_block_buffer_encode() + * - lzma_block_uncomp_encode() * - lzma_block_buffer_decode() */ uint8_t raw_check[LZMA_CHECK_SIZE_MAX]; @@ -223,26 +234,56 @@ typedef struct { * with the currently supported options, so it is safe to leave these * uninitialized. */ + + /** \private Reserved member. */ void *reserved_ptr1; + + /** \private Reserved member. */ void *reserved_ptr2; + + /** \private Reserved member. */ void *reserved_ptr3; + + /** \private Reserved member. */ uint32_t reserved_int1; + + /** \private Reserved member. */ uint32_t reserved_int2; + + /** \private Reserved member. */ lzma_vli reserved_int3; + + /** \private Reserved member. */ lzma_vli reserved_int4; + + /** \private Reserved member. */ lzma_vli reserved_int5; + + /** \private Reserved member. */ lzma_vli reserved_int6; + + /** \private Reserved member. */ lzma_vli reserved_int7; + + /** \private Reserved member. */ lzma_vli reserved_int8; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum1; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum2; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum3; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum4; /** * \brief A flag to Block decoder to not verify the Check field * - * This field is supported by liblzma >= 5.1.4beta if .version >= 1. + * This member is supported by liblzma >= 5.1.4beta if .version >= 1. * * If this is set to true, the integrity check won't be calculated * and verified. Unless you know what you are doing, you should @@ -260,12 +301,25 @@ typedef struct { */ lzma_bool ignore_check; + /** \private Reserved member. */ lzma_bool reserved_bool2; + + /** \private Reserved member. */ lzma_bool reserved_bool3; + + /** \private Reserved member. */ lzma_bool reserved_bool4; + + /** \private Reserved member. */ lzma_bool reserved_bool5; + + /** \private Reserved member. */ lzma_bool reserved_bool6; + + /** \private Reserved member. */ lzma_bool reserved_bool7; + + /** \private Reserved member. */ lzma_bool reserved_bool8; } lzma_block; @@ -280,7 +334,8 @@ typedef struct { * Note that if the first byte is 0x00, it indicates beginning of Index; use * this macro only when the byte is not 0x00. * - * There is no encoding macro, because Block Header encoder is enough for that. + * There is no encoding macro because lzma_block_header_size() and + * lzma_block_header_encode() should be used. */ #define lzma_block_header_size_decode(b) (((uint32_t)(b) + 1) * 4) @@ -294,17 +349,20 @@ typedef struct { * four and doesn't exceed LZMA_BLOCK_HEADER_SIZE_MAX. Increasing header_size * just means that lzma_block_header_encode() will add Header Padding. * - * \return - LZMA_OK: Size calculated successfully and stored to - * block->header_size. - * - LZMA_OPTIONS_ERROR: Unsupported version, filters or - * filter options. - * - LZMA_PROG_ERROR: Invalid values like compressed_size == 0. - * * \note This doesn't check that all the options are valid i.e. this * may return LZMA_OK even if lzma_block_header_encode() or * lzma_block_encoder() would fail. If you want to validate the * filter chain, consider using lzma_memlimit_encoder() which as * a side-effect validates the filter chain. + * + * \param block Block options + * + * \return Possible lzma_ret values: + * - LZMA_OK: Size calculated successfully and stored to + * block->header_size. + * - LZMA_OPTIONS_ERROR: Unsupported version, filters or + * filter options. + * - LZMA_PROG_ERROR: Invalid values like compressed_size == 0. */ extern LZMA_API(lzma_ret) lzma_block_header_size(lzma_block *block) lzma_nothrow lzma_attr_warn_unused_result; @@ -318,11 +376,12 @@ extern LZMA_API(lzma_ret) lzma_block_header_size(lzma_block *block) * lzma_block_header_size() is used, the Block Header will be padded to the * specified size. * - * \param out Beginning of the output buffer. This must be - * at least block->header_size bytes. * \param block Block options to be encoded. + * \param[out] out Beginning of the output buffer. This must be + * at least block->header_size bytes. * - * \return - LZMA_OK: Encoding was successful. block->header_size + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. block->header_size * bytes were written to output buffer. * - LZMA_OPTIONS_ERROR: Invalid or unsupported options. * - LZMA_PROG_ERROR: Invalid arguments, for example @@ -354,14 +413,15 @@ extern LZMA_API(lzma_ret) lzma_block_header_encode( * block->filters must have been allocated, but they don't need to be * initialized (possible existing filter options are not freed). * - * \param block Destination for Block options. + * \param[out] block Destination for Block options * \param allocator lzma_allocator for custom allocator functions. * Set to NULL to use malloc() (and also free() * if an error occurs). * \param in Beginning of the input buffer. This must be * at least block->header_size bytes. * - * \return - LZMA_OK: Decoding was successful. block->header_size + * \return Possible lzma_ret values: + * - LZMA_OK: Decoding was successful. block->header_size * bytes were read from the input buffer. * - LZMA_OPTIONS_ERROR: The Block Header specifies some * unsupported options such as unsupported filters. This can @@ -398,7 +458,12 @@ extern LZMA_API(lzma_ret) lzma_block_header_decode(lzma_block *block, * field so that it can properly validate Compressed Size if it * was present in Block Header. * - * \return - LZMA_OK: block->compressed_size was set successfully. + * \param block Block options: block->header_size must + * already be set with lzma_block_header_size(). + * \param unpadded_size Unpadded Size from the Index field in bytes + * + * \return Possible lzma_ret values: + * - LZMA_OK: block->compressed_size was set successfully. * - LZMA_DATA_ERROR: unpadded_size is too small compared to * block->header_size and lzma_check_size(block->check). * - LZMA_PROG_ERROR: Some values are invalid. For example, @@ -419,6 +484,9 @@ extern LZMA_API(lzma_ret) lzma_block_compressed_size( * Compressed Size, and size of the Check field. This is where this function * is needed. * + * \param block Block options: block->header_size must already be + * set with lzma_block_header_size(). + * * \return Unpadded Size on success, or zero on error. */ extern LZMA_API(lzma_vli) lzma_block_unpadded_size(const lzma_block *block) @@ -431,6 +499,9 @@ extern LZMA_API(lzma_vli) lzma_block_unpadded_size(const lzma_block *block) * This is equivalent to lzma_block_unpadded_size() except that the returned * value includes the size of the Block Padding field. * + * \param block Block options: block->header_size must already be + * set with lzma_block_header_size(). + * * \return On success, total encoded size of the Block. On error, * zero is returned. */ @@ -444,7 +515,17 @@ extern LZMA_API(lzma_vli) lzma_block_total_size(const lzma_block *block) * Valid actions for lzma_code() are LZMA_RUN, LZMA_SYNC_FLUSH (only if the * filter chain supports it), and LZMA_FINISH. * - * \return - LZMA_OK: All good, continue with lzma_code(). + * The Block encoder encodes the Block Data, Block Padding, and Check value. + * It does NOT encode the Block Header which can be encoded with + * lzma_block_header_encode(). + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param block Block options: block->version, block->check, + * and block->filters must have been initialized. + * + * \return Possible lzma_ret values: + * - LZMA_OK: All good, continue with lzma_code(). * - LZMA_MEM_ERROR * - LZMA_OPTIONS_ERROR * - LZMA_UNSUPPORTED_CHECK: block->check specifies a Check ID @@ -463,10 +544,16 @@ extern LZMA_API(lzma_ret) lzma_block_encoder( * Valid actions for lzma_code() are LZMA_RUN and LZMA_FINISH. Using * LZMA_FINISH is not required. It is supported only for convenience. * - * \return - LZMA_OK: All good, continue with lzma_code(). - * - LZMA_UNSUPPORTED_CHECK: Initialization was successful, but - * the given Check ID is not supported, thus Check will be - * ignored. + * The Block decoder decodes the Block Data, Block Padding, and Check value. + * It does NOT decode the Block Header which can be decoded with + * lzma_block_header_decode(). + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param block Block options + * + * \return Possible lzma_ret values: + * - LZMA_OK: All good, continue with lzma_code(). * - LZMA_PROG_ERROR * - LZMA_MEM_ERROR */ @@ -480,6 +567,11 @@ extern LZMA_API(lzma_ret) lzma_block_decoder( * * This is equivalent to lzma_stream_buffer_bound() but for .xz Blocks. * See the documentation of lzma_stream_buffer_bound(). + * + * \param uncompressed_size Size of the data to be encoded with the + * single-call Block encoder. + * + * \return Maximum output size in bytes for single-call Block encoding. */ extern LZMA_API(size_t) lzma_block_buffer_bound(size_t uncompressed_size) lzma_nothrow; @@ -508,13 +600,14 @@ extern LZMA_API(size_t) lzma_block_buffer_bound(size_t uncompressed_size) * Set to NULL to use malloc() and free(). * \param in Beginning of the input buffer * \param in_size Size of the input buffer - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if encoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_BUF_ERROR: Not enough output buffer space. * - LZMA_UNSUPPORTED_CHECK * - LZMA_OPTIONS_ERROR @@ -540,6 +633,25 @@ extern LZMA_API(lzma_ret) lzma_block_buffer_encode( * Since the data won't be compressed, this function ignores block->filters. * This function doesn't take lzma_allocator because this function doesn't * allocate any memory from the heap. + * + * \param block Block options: block->version, block->check, + * and block->filters must have been initialized. + * \param in Beginning of the input buffer + * \param in_size Size of the input buffer + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. + * *out_pos is updated only if encoding succeeds. + * \param out_size Size of the out buffer; the first byte into + * which no data is written to is out[out_size]. + * + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. + * - LZMA_BUF_ERROR: Not enough output buffer space. + * - LZMA_UNSUPPORTED_CHECK + * - LZMA_OPTIONS_ERROR + * - LZMA_MEM_ERROR + * - LZMA_DATA_ERROR + * - LZMA_PROG_ERROR */ extern LZMA_API(lzma_ret) lzma_block_uncomp_encode(lzma_block *block, const uint8_t *in, size_t in_size, @@ -553,7 +665,7 @@ extern LZMA_API(lzma_ret) lzma_block_uncomp_encode(lzma_block *block, * This is single-call equivalent of lzma_block_decoder(), and requires that * the caller has already decoded Block Header and checked its memory usage. * - * \param block Block options just like with lzma_block_decoder(). + * \param block Block options * \param allocator lzma_allocator for custom allocator functions. * Set to NULL to use malloc() and free(). * \param in Beginning of the input buffer @@ -561,13 +673,14 @@ extern LZMA_API(lzma_ret) lzma_block_uncomp_encode(lzma_block *block, * *in_pos is updated only if decoding succeeds. * \param in_size Size of the input buffer; the first byte that * won't be read is in[in_size]. - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if encoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * - * \return - LZMA_OK: Decoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Decoding was successful. * - LZMA_OPTIONS_ERROR * - LZMA_DATA_ERROR * - LZMA_MEM_ERROR diff --git a/Utilities/cmliblzma/liblzma/api/lzma/check.h b/Utilities/cmliblzma/liblzma/api/lzma/check.h index 6a243db0d79..e7a50ed3a3c 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/check.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/check.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/check.h * \brief Integrity checks + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -71,12 +69,17 @@ typedef enum { /** * \brief Test if the given Check ID is supported * - * Return true if the given Check ID is supported by this liblzma build. - * Otherwise false is returned. It is safe to call this with a value that - * is not in the range [0, 15]; in that case the return value is always false. + * LZMA_CHECK_NONE and LZMA_CHECK_CRC32 are always supported (even if + * liblzma is built with limited features). * - * You can assume that LZMA_CHECK_NONE and LZMA_CHECK_CRC32 are always - * supported (even if liblzma is built with limited features). + * \note It is safe to call this with a value that is not in the + * range [0, 15]; in that case the return value is always false. + * + * \param check Check ID + * + * \return lzma_bool: + * - true if Check ID is supported by this liblzma build. + * - false otherwise. */ extern LZMA_API(lzma_bool) lzma_check_is_supported(lzma_check check) lzma_nothrow lzma_attr_const; @@ -90,7 +93,10 @@ extern LZMA_API(lzma_bool) lzma_check_is_supported(lzma_check check) * the Check field with the specified Check ID. The values are: * { 0, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 32, 64, 64, 64 } * - * If the argument is not in the range [0, 15], UINT32_MAX is returned. + * \param check Check ID + * + * \return Size of the Check field in bytes. If the argument is not in + * the range [0, 15], UINT32_MAX is returned. */ extern LZMA_API(uint32_t) lzma_check_size(lzma_check check) lzma_nothrow lzma_attr_const; @@ -126,25 +132,32 @@ extern LZMA_API(uint32_t) lzma_crc32( * * Calculate CRC64 using the polynomial from the ECMA-182 standard. * - * This function is used similarly to lzma_crc32(). See its documentation. + * This function is used similarly to lzma_crc32(). + * + * \param buf Pointer to the input buffer + * \param size Size of the input buffer + * \param crc Previously returned CRC value. This is used to + * calculate the CRC of a big buffer in smaller chunks. + * Set to zero when starting a new calculation. + * + * \return Updated CRC value, which can be passed to this function + * again to continue CRC calculation. */ extern LZMA_API(uint64_t) lzma_crc64( const uint8_t *buf, size_t size, uint64_t crc) lzma_nothrow lzma_attr_pure; -/* - * SHA-256 functions are currently not exported to public API. - * Contact Lasse Collin if you think it should be. - */ - - /** * \brief Get the type of the integrity check * * This function can be called only immediately after lzma_code() has * returned LZMA_NO_CHECK, LZMA_UNSUPPORTED_CHECK, or LZMA_GET_CHECK. * Calling this function in any other situation has undefined behavior. + * + * \param strm Pointer to lzma_stream meeting the above conditions. + * + * \return Check ID in the lzma_stream, or undefined if called improperly. */ extern LZMA_API(lzma_check) lzma_get_check(const lzma_stream *strm) lzma_nothrow; diff --git a/Utilities/cmliblzma/liblzma/api/lzma/container.h b/Utilities/cmliblzma/liblzma/api/lzma/container.h index 9fbf4df0617..ee5d77e4f1a 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/container.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/container.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/container.h * \brief File formats + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -51,7 +49,7 @@ * * This flag modifies the preset to make the encoding significantly slower * while improving the compression ratio only marginally. This is useful - * when you don't mind wasting time to get as small result as possible. + * when you don't mind spending time to get as small result as possible. * * This flag doesn't affect the memory usage requirements of the decoder (at * least not significantly). The memory usage of the encoder may be increased @@ -69,7 +67,15 @@ typedef struct { * * Set this to zero if no flags are wanted. * - * No flags are currently supported. + * Encoder: No flags are currently supported. + * + * Decoder: Bitwise-or of zero or more of the decoder flags: + * - LZMA_TELL_NO_CHECK + * - LZMA_TELL_UNSUPPORTED_CHECK + * - LZMA_TELL_ANY_CHECK + * - LZMA_IGNORE_CHECK + * - LZMA_CONCATENATED + * - LZMA_FAIL_FAST */ uint32_t flags; @@ -79,7 +85,7 @@ typedef struct { uint32_t threads; /** - * \brief Maximum uncompressed size of a Block + * \brief Encoder only: Maximum uncompressed size of a Block * * The encoder will start a new .xz Block every block_size bytes. * Using LZMA_FULL_FLUSH or LZMA_FULL_BARRIER with lzma_code() @@ -106,7 +112,7 @@ typedef struct { /** * \brief Timeout to allow lzma_code() to return early * - * Multithreading can make liblzma to consume input and produce + * Multithreading can make liblzma consume input and produce * output in a very bursty way: it may first read a lot of input * to fill internal buffers, then no input or output occurs for * a while. @@ -123,19 +129,18 @@ typedef struct { * LZMA_OK. Reasonable values are 100 ms or more. The xz command * line tool uses 300 ms. * - * If long blocking times are fine for you, set timeout to a special - * value of 0, which will disable the timeout mechanism and will make + * If long blocking times are acceptable, set timeout to a special + * value of 0. This will disable the timeout mechanism and will make * lzma_code() block until all the input is consumed or the output * buffer has been filled. * * \note Even with a timeout, lzma_code() might sometimes take - * somewhat long time to return. No timing guarantees - * are made. + * a long time to return. No timing guarantees are made. */ uint32_t timeout; /** - * \brief Compression preset (level and possible flags) + * \brief Encoder only: Compression preset * * The preset is set just like with lzma_easy_encoder(). * The preset is ignored if filters below is non-NULL. @@ -143,7 +148,7 @@ typedef struct { uint32_t preset; /** - * \brief Filter chain (alternative to a preset) + * \brief Encoder only: Filter chain (alternative to a preset) * * If this is NULL, the preset above is used. Otherwise the preset * is ignored and the filter chain specified here is used. @@ -151,7 +156,7 @@ typedef struct { const lzma_filter *filters; /** - * \brief Integrity check type + * \brief Encoder only: Integrity check type * * See check.h for available checks. The xz command line tool * defaults to LZMA_CHECK_CRC64, which is a good choice if you @@ -166,20 +171,86 @@ typedef struct { * with the currently supported options, so it is safe to leave these * uninitialized. */ + /** \private Reserved member. */ lzma_reserved_enum reserved_enum1; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum2; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum3; + + /** \private Reserved member. */ uint32_t reserved_int1; + + /** \private Reserved member. */ uint32_t reserved_int2; + + /** \private Reserved member. */ uint32_t reserved_int3; + + /** \private Reserved member. */ uint32_t reserved_int4; - uint64_t reserved_int5; - uint64_t reserved_int6; + + /** + * \brief Memory usage limit to reduce the number of threads + * + * Encoder: Ignored. + * + * Decoder: + * + * If the number of threads has been set so high that more than + * memlimit_threading bytes of memory would be needed, the number + * of threads will be reduced so that the memory usage will not exceed + * memlimit_threading bytes. However, if memlimit_threading cannot + * be met even in single-threaded mode, then decoding will continue + * in single-threaded mode and memlimit_threading may be exceeded + * even by a large amount. That is, memlimit_threading will never make + * lzma_code() return LZMA_MEMLIMIT_ERROR. To truly cap the memory + * usage, see memlimit_stop below. + * + * Setting memlimit_threading to UINT64_MAX or a similar huge value + * means that liblzma is allowed to keep the whole compressed file + * and the whole uncompressed file in memory in addition to the memory + * needed by the decompressor data structures used by each thread! + * In other words, a reasonable value limit must be set here or it + * will cause problems sooner or later. If you have no idea what + * a reasonable value could be, try lzma_physmem() / 4 as a starting + * point. Setting this limit will never prevent decompression of + * a file; this will only reduce the number of threads. + * + * If memlimit_threading is greater than memlimit_stop, then the value + * of memlimit_stop will be used for both. + */ + uint64_t memlimit_threading; + + /** + * \brief Memory usage limit that should never be exceeded + * + * Encoder: Ignored. + * + * Decoder: If decompressing will need more than this amount of + * memory even in the single-threaded mode, then lzma_code() will + * return LZMA_MEMLIMIT_ERROR. + */ + uint64_t memlimit_stop; + + /** \private Reserved member. */ uint64_t reserved_int7; + + /** \private Reserved member. */ uint64_t reserved_int8; + + /** \private Reserved member. */ void *reserved_ptr1; + + /** \private Reserved member. */ void *reserved_ptr2; + + /** \private Reserved member. */ void *reserved_ptr3; + + /** \private Reserved member. */ void *reserved_ptr4; } lzma_mt; @@ -193,8 +264,7 @@ typedef struct { * \param preset Compression preset (level and possible flags) * * \return Number of bytes of memory required for the given - * preset when encoding. If an error occurs, for example - * due to unsupported preset, UINT64_MAX is returned. + * preset when encoding or UINT64_MAX on error. */ extern LZMA_API(uint64_t) lzma_easy_encoder_memusage(uint32_t preset) lzma_nothrow lzma_attr_pure; @@ -208,9 +278,8 @@ extern LZMA_API(uint64_t) lzma_easy_encoder_memusage(uint32_t preset) * \param preset Compression preset (level and possible flags) * * \return Number of bytes of memory required to decompress a file - * that was compressed using the given preset. If an error - * occurs, for example due to unsupported preset, UINT64_MAX - * is returned. + * that was compressed using the given preset or UINT64_MAX + * on error. */ extern LZMA_API(uint64_t) lzma_easy_decoder_memusage(uint32_t preset) lzma_nothrow lzma_attr_pure; @@ -220,7 +289,16 @@ extern LZMA_API(uint64_t) lzma_easy_decoder_memusage(uint32_t preset) * \brief Initialize .xz Stream encoder using a preset number * * This function is intended for those who just want to use the basic features - * if liblzma (that is, most developers out there). + * of liblzma (that is, most developers out there). + * + * If initialization fails (return value is not LZMA_OK), all the memory + * allocated for *strm by liblzma is always freed. Thus, there is no need + * to call lzma_end() after failed initialization. + * + * If initialization succeeds, use lzma_code() to do the actual encoding. + * Valid values for 'action' (the second argument of lzma_code()) are + * LZMA_RUN, LZMA_SYNC_FLUSH, LZMA_FULL_FLUSH, and LZMA_FINISH. In future, + * there may be compression levels or flags that don't support LZMA_SYNC_FLUSH. * * \param strm Pointer to lzma_stream that is at least initialized * with LZMA_STREAM_INIT. @@ -228,7 +306,7 @@ extern LZMA_API(uint64_t) lzma_easy_decoder_memusage(uint32_t preset) * number and zero or more flags. Usually flags aren't * used, so preset is simply a number [0, 9] which match * the options -0 ... -9 of the xz command line tool. - * Additional flags can be be set using bitwise-or with + * Additional flags can be set using bitwise-or with * the preset level number, e.g. 6 | LZMA_PRESET_EXTREME. * \param check Integrity check type to use. See check.h for available * checks. The xz command line tool defaults to @@ -236,7 +314,8 @@ extern LZMA_API(uint64_t) lzma_easy_decoder_memusage(uint32_t preset) * unsure. LZMA_CHECK_CRC32 is good too as long as the * uncompressed file is not many gigabytes. * - * \return - LZMA_OK: Initialization succeeded. Use lzma_code() to + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization succeeded. Use lzma_code() to * encode your data. * - LZMA_MEM_ERROR: Memory allocation failed. * - LZMA_OPTIONS_ERROR: The given compression preset is not @@ -245,15 +324,6 @@ extern LZMA_API(uint64_t) lzma_easy_decoder_memusage(uint32_t preset) * supported by this liblzma build. * - LZMA_PROG_ERROR: One or more of the parameters have values * that will never be valid. For example, strm == NULL. - * - * If initialization fails (return value is not LZMA_OK), all the memory - * allocated for *strm by liblzma is always freed. Thus, there is no need - * to call lzma_end() after failed initialization. - * - * If initialization succeeds, use lzma_code() to do the actual encoding. - * Valid values for `action' (the second argument of lzma_code()) are - * LZMA_RUN, LZMA_SYNC_FLUSH, LZMA_FULL_FLUSH, and LZMA_FINISH. In future, - * there may be compression levels or flags that don't support LZMA_SYNC_FLUSH. */ extern LZMA_API(lzma_ret) lzma_easy_encoder( lzma_stream *strm, uint32_t preset, lzma_check check) @@ -274,13 +344,14 @@ extern LZMA_API(lzma_ret) lzma_easy_encoder( * Set to NULL to use malloc() and free(). * \param in Beginning of the input buffer * \param in_size Size of the input buffer - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if encoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_BUF_ERROR: Not enough output buffer space. * - LZMA_UNSUPPORTED_CHECK * - LZMA_OPTIONS_ERROR @@ -298,14 +369,16 @@ extern LZMA_API(lzma_ret) lzma_easy_buffer_encode( /** * \brief Initialize .xz Stream encoder using a custom filter chain * - * \param strm Pointer to properly prepared lzma_stream - * \param filters Array of filters. This must be terminated with - * filters[n].id = LZMA_VLI_UNKNOWN. See filter.h for - * more information. + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. See filters.h for more + * information. * \param check Type of the integrity check to calculate from * uncompressed data. * - * \return - LZMA_OK: Initialization was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization was successful. * - LZMA_MEM_ERROR * - LZMA_UNSUPPORTED_CHECK * - LZMA_OPTIONS_ERROR @@ -345,10 +418,12 @@ extern LZMA_API(uint64_t) lzma_stream_encoder_mt_memusage( * LZMA_FULL_BARRIER, and LZMA_FINISH. Support for LZMA_SYNC_FLUSH might be * added in the future. * - * \param strm Pointer to properly prepared lzma_stream + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. * \param options Pointer to multithreaded compression options * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_UNSUPPORTED_CHECK * - LZMA_OPTIONS_ERROR @@ -359,6 +434,34 @@ extern LZMA_API(lzma_ret) lzma_stream_encoder_mt( lzma_nothrow lzma_attr_warn_unused_result; +/** + * \brief Calculate recommended Block size for multithreaded .xz encoder + * + * This calculates a recommended Block size for multithreaded encoding given + * a filter chain. This is used internally by lzma_stream_encoder_mt() to + * determine the Block size if the block_size member is not set to the + * special value of 0 in the lzma_mt options struct. + * + * If one wishes to change the filters between Blocks, this function is + * helpful to set the block_size member of the lzma_mt struct before calling + * lzma_stream_encoder_mt(). Since the block_size member represents the + * maximum possible Block size for the multithreaded .xz encoder, one can + * use this function to find the maximum recommended Block size based on + * all planned filter chains. Otherwise, the multithreaded encoder will + * base its maximum Block size on the first filter chain used (if the + * block_size member is not set), which may unnecessarily limit the Block + * size for a later filter chain. + * + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. + * + * \return Recommended Block size in bytes, or UINT64_MAX if + * an error occurred. + */ +extern LZMA_API(uint64_t) lzma_mt_block_size(const lzma_filter *filters) + lzma_nothrow; + + /** * \brief Initialize .lzma encoder (legacy file format) * @@ -374,7 +477,12 @@ extern LZMA_API(lzma_ret) lzma_stream_encoder_mt( * No kind of flushing is supported, because the file format doesn't make * it possible. * - * \return - LZMA_OK + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param options Pointer to encoder options + * + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_OPTIONS_ERROR * - LZMA_PROG_ERROR @@ -387,7 +495,7 @@ extern LZMA_API(lzma_ret) lzma_alone_encoder( /** * \brief Calculate output buffer size for single-call Stream encoder * - * When trying to compress uncompressible data, the encoded size will be + * When trying to compress incompressible data, the encoded size will be * slightly bigger than the input data. This function calculates how much * output buffer space is required to be sure that lzma_stream_buffer_encode() * doesn't return LZMA_BUF_ERROR. @@ -403,8 +511,13 @@ extern LZMA_API(lzma_ret) lzma_alone_encoder( * \note The limit calculated by this function applies only to * single-call encoding. Multi-call encoding may (and probably * will) have larger maximum expansion when encoding - * uncompressible data. Currently there is no function to + * incompressible data. Currently there is no function to * calculate the maximum expansion of multi-call encoding. + * + * \param uncompressed_size Size in bytes of the uncompressed + * input data + * + * \return Maximum number of bytes needed to store the compressed data. */ extern LZMA_API(size_t) lzma_stream_buffer_bound(size_t uncompressed_size) lzma_nothrow; @@ -413,22 +526,23 @@ extern LZMA_API(size_t) lzma_stream_buffer_bound(size_t uncompressed_size) /** * \brief Single-call .xz Stream encoder * - * \param filters Array of filters. This must be terminated with - * filters[n].id = LZMA_VLI_UNKNOWN. See filter.h - * for more information. + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. See filters.h for more + * information. * \param check Type of the integrity check to calculate from * uncompressed data. * \param allocator lzma_allocator for custom allocator functions. * Set to NULL to use malloc() and free(). * \param in Beginning of the input buffer * \param in_size Size of the input buffer - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if encoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_BUF_ERROR: Not enough output buffer space. * - LZMA_UNSUPPORTED_CHECK * - LZMA_OPTIONS_ERROR @@ -444,6 +558,66 @@ extern LZMA_API(lzma_ret) lzma_stream_buffer_encode( lzma_nothrow lzma_attr_warn_unused_result; +/** + * \brief MicroLZMA encoder + * + * The MicroLZMA format is a raw LZMA stream whose first byte (always 0x00) + * has been replaced with bitwise-negation of the LZMA properties (lc/lp/pb). + * This encoding ensures that the first byte of MicroLZMA stream is never + * 0x00. There is no end of payload marker and thus the uncompressed size + * must be stored separately. For the best error detection the dictionary + * size should be stored separately as well but alternatively one may use + * the uncompressed size as the dictionary size when decoding. + * + * With the MicroLZMA encoder, lzma_code() behaves slightly unusually. + * The action argument must be LZMA_FINISH and the return value will never be + * LZMA_OK. Thus the encoding is always done with a single lzma_code() after + * the initialization. The benefit of the combination of initialization + * function and lzma_code() is that memory allocations can be re-used for + * better performance. + * + * lzma_code() will try to encode as much input as is possible to fit into + * the given output buffer. If not all input can be encoded, the stream will + * be finished without encoding all the input. The caller must check both + * input and output buffer usage after lzma_code() (total_in and total_out + * in lzma_stream can be convenient). Often lzma_code() can fill the output + * buffer completely if there is a lot of input, but sometimes a few bytes + * may remain unused because the next LZMA symbol would require more space. + * + * lzma_stream.avail_out must be at least 6. Otherwise LZMA_PROG_ERROR + * will be returned. + * + * The LZMA dictionary should be reasonably low to speed up the encoder + * re-initialization. A good value is bigger than the resulting + * uncompressed size of most of the output chunks. For example, if output + * size is 4 KiB, dictionary size of 32 KiB or 64 KiB is good. If the + * data compresses extremely well, even 128 KiB may be useful. + * + * The MicroLZMA format and this encoder variant were made with the EROFS + * file system in mind. This format may be convenient in other embedded + * uses too where many small streams are needed. XZ Embedded includes a + * decoder for this format. + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param options Pointer to encoder options + * + * \return Possible lzma_ret values: + * - LZMA_STREAM_END: All good. Check the amounts of input used + * and output produced. Store the amount of input used + * (uncompressed size) as it needs to be known to decompress + * the data. + * - LZMA_OPTIONS_ERROR + * - LZMA_MEM_ERROR + * - LZMA_PROG_ERROR: In addition to the generic reasons for this + * error code, this may also be returned if there isn't enough + * output space (6 bytes) to create a valid MicroLZMA stream. + */ +extern LZMA_API(lzma_ret) lzma_microlzma_encoder( + lzma_stream *strm, const lzma_options_lzma *options) + lzma_nothrow; + + /************ * Decoding * ************/ @@ -501,24 +675,54 @@ extern LZMA_API(lzma_ret) lzma_stream_buffer_encode( /** * This flag enables decoding of concatenated files with file formats that * allow concatenating compressed files as is. From the formats currently - * supported by liblzma, only the .xz format allows concatenated files. - * Concatenated files are not allowed with the legacy .lzma format. + * supported by liblzma, only the .xz and .lz formats allow concatenated + * files. Concatenated files are not allowed with the legacy .lzma format. * - * This flag also affects the usage of the `action' argument for lzma_code(). + * This flag also affects the usage of the 'action' argument for lzma_code(). * When LZMA_CONCATENATED is used, lzma_code() won't return LZMA_STREAM_END - * unless LZMA_FINISH is used as `action'. Thus, the application has to set + * unless LZMA_FINISH is used as 'action'. Thus, the application has to set * LZMA_FINISH in the same way as it does when encoding. * * If LZMA_CONCATENATED is not used, the decoders still accept LZMA_FINISH - * as `action' for lzma_code(), but the usage of LZMA_FINISH isn't required. + * as 'action' for lzma_code(), but the usage of LZMA_FINISH isn't required. */ #define LZMA_CONCATENATED UINT32_C(0x08) +/** + * This flag makes the threaded decoder report errors (like LZMA_DATA_ERROR) + * as soon as they are detected. This saves time when the application has no + * interest in a partially decompressed truncated or corrupt file. Note that + * due to timing randomness, if the same truncated or corrupt input is + * decompressed multiple times with this flag, a different amount of output + * may be produced by different runs, and even the error code might vary. + * + * When using LZMA_FAIL_FAST, it is recommended to use LZMA_FINISH to tell + * the decoder when no more input will be coming because it can help fast + * detection and reporting of truncated files. Note that in this situation + * truncated files might be diagnosed with LZMA_DATA_ERROR instead of + * LZMA_OK or LZMA_BUF_ERROR! + * + * Without this flag the threaded decoder will provide as much output as + * possible at first and then report the pending error. This default behavior + * matches the single-threaded decoder and provides repeatable behavior + * with truncated or corrupt input. There are a few special cases where the + * behavior can still differ like memory allocation failures (LZMA_MEM_ERROR). + * + * Single-threaded decoders currently ignore this flag. + * + * Support for this flag was added in liblzma 5.3.3alpha. Note that in older + * versions this flag isn't supported (LZMA_OPTIONS_ERROR) even by functions + * that ignore this flag in newer liblzma versions. + */ +#define LZMA_FAIL_FAST UINT32_C(0x20) + + /** * \brief Initialize .xz Stream decoder * - * \param strm Pointer to properly prepared lzma_stream + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. * \param memlimit Memory usage limit as bytes. Use UINT64_MAX * to effectively disable the limiter. liblzma * 5.2.3 and earlier don't allow 0 here and return @@ -526,9 +730,11 @@ extern LZMA_API(lzma_ret) lzma_stream_buffer_encode( * had been specified. * \param flags Bitwise-or of zero or more of the decoder flags: * LZMA_TELL_NO_CHECK, LZMA_TELL_UNSUPPORTED_CHECK, - * LZMA_TELL_ANY_CHECK, LZMA_CONCATENATED + * LZMA_TELL_ANY_CHECK, LZMA_IGNORE_CHECK, + * LZMA_CONCATENATED, LZMA_FAIL_FAST * - * \return - LZMA_OK: Initialization was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization was successful. * - LZMA_MEM_ERROR: Cannot allocate memory. * - LZMA_OPTIONS_ERROR: Unsupported flags * - LZMA_PROG_ERROR @@ -539,21 +745,67 @@ extern LZMA_API(lzma_ret) lzma_stream_decoder( /** - * \brief Decode .xz Streams and .lzma files with autodetection + * \brief Initialize multithreaded .xz Stream decoder + * + * The decoder can decode multiple Blocks in parallel. This requires that each + * Block Header contains the Compressed Size and Uncompressed size fields + * which are added by the multi-threaded encoder, see lzma_stream_encoder_mt(). + * + * A Stream with one Block will only utilize one thread. A Stream with multiple + * Blocks but without size information in Block Headers will be processed in + * single-threaded mode in the same way as done by lzma_stream_decoder(). + * Concatenated Streams are processed one Stream at a time; no inter-Stream + * parallelization is done. + * + * This function behaves like lzma_stream_decoder() when options->threads == 1 + * and options->memlimit_threading <= 1. + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param options Pointer to multithreaded compression options + * + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization was successful. + * - LZMA_MEM_ERROR: Cannot allocate memory. + * - LZMA_MEMLIMIT_ERROR: Memory usage limit was reached. + * - LZMA_OPTIONS_ERROR: Unsupported flags. + * - LZMA_PROG_ERROR + */ +extern LZMA_API(lzma_ret) lzma_stream_decoder_mt( + lzma_stream *strm, const lzma_mt *options) + lzma_nothrow lzma_attr_warn_unused_result; + + +/** + * \brief Decode .xz, .lzma, and .lz (lzip) files with autodetection + * + * This decoder autodetects between the .xz, .lzma, and .lz file formats, + * and calls lzma_stream_decoder(), lzma_alone_decoder(), or + * lzma_lzip_decoder() once the type of the input file has been detected. * - * This decoder autodetects between the .xz and .lzma file formats, and - * calls lzma_stream_decoder() or lzma_alone_decoder() once the type - * of the input file has been detected. + * Support for .lz was added in 5.4.0. * - * \param strm Pointer to properly prepared lzma_stream + * If the flag LZMA_CONCATENATED is used and the input is a .lzma file: + * For historical reasons concatenated .lzma files aren't supported. + * If there is trailing data after one .lzma stream, lzma_code() will + * return LZMA_DATA_ERROR. (lzma_alone_decoder() doesn't have such a check + * as it doesn't support any decoder flags. It will return LZMA_STREAM_END + * after one .lzma stream.) + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. * \param memlimit Memory usage limit as bytes. Use UINT64_MAX * to effectively disable the limiter. liblzma * 5.2.3 and earlier don't allow 0 here and return * LZMA_PROG_ERROR; later versions treat 0 as if 1 * had been specified. - * \param flags Bitwise-or of flags, or zero for no flags. + * \param flags Bitwise-or of zero or more of the decoder flags: + * LZMA_TELL_NO_CHECK, LZMA_TELL_UNSUPPORTED_CHECK, + * LZMA_TELL_ANY_CHECK, LZMA_IGNORE_CHECK, + * LZMA_CONCATENATED, LZMA_FAIL_FAST * - * \return - LZMA_OK: Initialization was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization was successful. * - LZMA_MEM_ERROR: Cannot allocate memory. * - LZMA_OPTIONS_ERROR: Unsupported flags * - LZMA_PROG_ERROR @@ -566,18 +818,20 @@ extern LZMA_API(lzma_ret) lzma_auto_decoder( /** * \brief Initialize .lzma decoder (legacy file format) * - * \param strm Pointer to properly prepared lzma_stream + * Valid 'action' arguments to lzma_code() are LZMA_RUN and LZMA_FINISH. + * There is no need to use LZMA_FINISH, but it's allowed because it may + * simplify certain types of applications. + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. * \param memlimit Memory usage limit as bytes. Use UINT64_MAX * to effectively disable the limiter. liblzma * 5.2.3 and earlier don't allow 0 here and return * LZMA_PROG_ERROR; later versions treat 0 as if 1 * had been specified. * - * Valid `action' arguments to lzma_code() are LZMA_RUN and LZMA_FINISH. - * There is no need to use LZMA_FINISH, but it's allowed because it may - * simplify certain types of applications. - * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_PROG_ERROR */ @@ -586,6 +840,66 @@ extern LZMA_API(lzma_ret) lzma_alone_decoder( lzma_nothrow lzma_attr_warn_unused_result; +/** + * \brief Initialize .lz (lzip) decoder (a foreign file format) + * + * This decoder supports the .lz format version 0 and the unextended .lz + * format version 1: + * + * - Files in the format version 0 were produced by lzip 1.3 and older. + * Such files aren't common but may be found from file archives + * as a few source packages were released in this format. People + * might have old personal files in this format too. Decompression + * support for the format version 0 was removed in lzip 1.18. + * + * - lzip 1.3 added decompression support for .lz format version 1 files. + * Compression support was added in lzip 1.4. In lzip 1.6 the .lz format + * version 1 was extended to support the Sync Flush marker. This extension + * is not supported by liblzma. lzma_code() will return LZMA_DATA_ERROR + * at the location of the Sync Flush marker. In practice files with + * the Sync Flush marker are very rare and thus liblzma can decompress + * almost all .lz files. + * + * Just like with lzma_stream_decoder() for .xz files, LZMA_CONCATENATED + * should be used when decompressing normal standalone .lz files. + * + * The .lz format allows putting non-.lz data at the end of a file after at + * least one valid .lz member. That is, one can append custom data at the end + * of a .lz file and the decoder is required to ignore it. In liblzma this + * is relevant only when LZMA_CONCATENATED is used. In that case lzma_code() + * will return LZMA_STREAM_END and leave lzma_stream.next_in pointing to + * the first byte of the non-.lz data. An exception to this is if the first + * 1-3 bytes of the non-.lz data are identical to the .lz magic bytes + * (0x4C, 0x5A, 0x49, 0x50; "LZIP" in US-ASCII). In such a case the 1-3 bytes + * will have been ignored by lzma_code(). If one wishes to locate the non-.lz + * data reliably, one must ensure that the first byte isn't 0x4C. Actually + * one should ensure that none of the first four bytes of trailing data are + * equal to the magic bytes because lzip >= 1.20 requires it by default. + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param memlimit Memory usage limit as bytes. Use UINT64_MAX + * to effectively disable the limiter. + * \param flags Bitwise-or of flags, or zero for no flags. + * All decoder flags listed above are supported + * although only LZMA_CONCATENATED and (in very rare + * cases) LZMA_IGNORE_CHECK are actually useful. + * LZMA_TELL_NO_CHECK, LZMA_TELL_UNSUPPORTED_CHECK, + * and LZMA_FAIL_FAST do nothing. LZMA_TELL_ANY_CHECK + * is supported for consistency only as CRC32 is + * always used in the .lz format. + * + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization was successful. + * - LZMA_MEM_ERROR: Cannot allocate memory. + * - LZMA_OPTIONS_ERROR: Unsupported flags + * - LZMA_PROG_ERROR + */ +extern LZMA_API(lzma_ret) lzma_lzip_decoder( + lzma_stream *strm, uint64_t memlimit, uint32_t flags) + lzma_nothrow lzma_attr_warn_unused_result; + + /** * \brief Single-call .xz Stream decoder * @@ -595,7 +909,8 @@ extern LZMA_API(lzma_ret) lzma_alone_decoder( * returned. * \param flags Bitwise-or of zero or more of the decoder flags: * LZMA_TELL_NO_CHECK, LZMA_TELL_UNSUPPORTED_CHECK, - * LZMA_CONCATENATED. Note that LZMA_TELL_ANY_CHECK + * LZMA_IGNORE_CHECK, LZMA_CONCATENATED, + * LZMA_FAIL_FAST. Note that LZMA_TELL_ANY_CHECK * is not allowed and will return LZMA_PROG_ERROR. * \param allocator lzma_allocator for custom allocator functions. * Set to NULL to use malloc() and free(). @@ -604,13 +919,14 @@ extern LZMA_API(lzma_ret) lzma_alone_decoder( * *in_pos is updated only if decoding succeeds. * \param in_size Size of the input buffer; the first byte that * won't be read is in[in_size]. - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if decoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * - * \return - LZMA_OK: Decoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Decoding was successful. * - LZMA_FORMAT_ERROR * - LZMA_OPTIONS_ERROR * - LZMA_DATA_ERROR @@ -630,3 +946,50 @@ extern LZMA_API(lzma_ret) lzma_stream_buffer_decode( const uint8_t *in, size_t *in_pos, size_t in_size, uint8_t *out, size_t *out_pos, size_t out_size) lzma_nothrow lzma_attr_warn_unused_result; + + +/** + * \brief MicroLZMA decoder + * + * See lzma_microlzma_encoder() for more information. + * + * The lzma_code() usage with this decoder is completely normal. The + * special behavior of lzma_code() applies to lzma_microlzma_encoder() only. + * + * \param strm Pointer to lzma_stream that is at least initialized + * with LZMA_STREAM_INIT. + * \param comp_size Compressed size of the MicroLZMA stream. + * The caller must somehow know this exactly. + * \param uncomp_size Uncompressed size of the MicroLZMA stream. + * If the exact uncompressed size isn't known, this + * can be set to a value that is at most as big as + * the exact uncompressed size would be, but then the + * next argument uncomp_size_is_exact must be false. + * \param uncomp_size_is_exact + * If true, uncomp_size must be exactly correct. + * This will improve error detection at the end of + * the stream. If the exact uncompressed size isn't + * known, this must be false. uncomp_size must still + * be at most as big as the exact uncompressed size + * is. Setting this to false when the exact size is + * known will work but error detection at the end of + * the stream will be weaker. + * \param dict_size LZMA dictionary size that was used when + * compressing the data. It is OK to use a bigger + * value too but liblzma will then allocate more + * memory than would actually be required and error + * detection will be slightly worse. (Note that with + * the implementation in XZ Embedded it doesn't + * affect the memory usage if one specifies bigger + * dictionary than actually required.) + * + * \return Possible lzma_ret values: + * - LZMA_OK + * - LZMA_MEM_ERROR + * - LZMA_OPTIONS_ERROR + * - LZMA_PROG_ERROR + */ +extern LZMA_API(lzma_ret) lzma_microlzma_decoder( + lzma_stream *strm, uint64_t comp_size, + uint64_t uncomp_size, lzma_bool uncomp_size_is_exact, + uint32_t dict_size) lzma_nothrow; diff --git a/Utilities/cmliblzma/liblzma/api/lzma/delta.h b/Utilities/cmliblzma/liblzma/api/lzma/delta.h index 592fc4f8496..5ebacef8158 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/delta.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/delta.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/delta.h * \brief Delta filter + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -57,7 +55,15 @@ typedef struct { * - 24-bit RGB image data: distance = 3 bytes */ uint32_t dist; + + /** + * \brief Minimum value for lzma_options_delta.dist. + */ # define LZMA_DELTA_DIST_MIN 1 + + /** + * \brief Maximum value for lzma_options_delta.dist. + */ # define LZMA_DELTA_DIST_MAX 256 /* @@ -67,11 +73,23 @@ typedef struct { * when type is LZMA_DELTA_TYPE_BYTE, so it is safe to leave these * uninitialized. */ + + /** \private Reserved member. */ uint32_t reserved_int1; + + /** \private Reserved member. */ uint32_t reserved_int2; + + /** \private Reserved member. */ uint32_t reserved_int3; + + /** \private Reserved member. */ uint32_t reserved_int4; + + /** \private Reserved member. */ void *reserved_ptr1; + + /** \private Reserved member. */ void *reserved_ptr2; } lzma_options_delta; diff --git a/Utilities/cmliblzma/liblzma/api/lzma/filter.h b/Utilities/cmliblzma/liblzma/api/lzma/filter.h index 8c859314765..e86809c4e39 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/filter.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/filter.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/filter.h * \brief Common filter related types and functions + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -29,7 +27,7 @@ /** * \brief Filter options * - * This structure is used to pass Filter ID and a pointer filter's + * This structure is used to pass a Filter ID and a pointer to the filter's * options to liblzma. A few functions work with a single lzma_filter * structure, while most functions expect a filter chain. * @@ -37,14 +35,14 @@ * The array is terminated with .id = LZMA_VLI_UNKNOWN. Thus, the filter * array must have LZMA_FILTERS_MAX + 1 elements (that is, five) to * be able to hold any arbitrary filter chain. This is important when - * using lzma_block_header_decode() from block.h, because too small - * array would make liblzma write past the end of the filters array. + * using lzma_block_header_decode() from block.h, because a filter array + * that is too small would make liblzma write past the end of the array. */ typedef struct { /** * \brief Filter ID * - * Use constants whose name begin with `LZMA_FILTER_' to specify + * Use constants whose name begin with 'LZMA_FILTER_' to specify * different filters. In an array of lzma_filter structures, use * LZMA_VLI_UNKNOWN to indicate end of filters. * @@ -68,12 +66,12 @@ typedef struct { /** * \brief Test if the given Filter ID is supported for encoding * - * Return true if the give Filter ID is supported for encoding by this - * liblzma build. Otherwise false is returned. + * \param id Filter ID * - * There is no way to list which filters are available in this particular - * liblzma version and build. It would be useless, because the application - * couldn't know what kind of options the filter would need. + * \return lzma_bool: + * - true if the Filter ID is supported for encoding by this + * liblzma build. + * - false otherwise. */ extern LZMA_API(lzma_bool) lzma_filter_encoder_is_supported(lzma_vli id) lzma_nothrow lzma_attr_const; @@ -82,8 +80,12 @@ extern LZMA_API(lzma_bool) lzma_filter_encoder_is_supported(lzma_vli id) /** * \brief Test if the given Filter ID is supported for decoding * - * Return true if the give Filter ID is supported for decoding by this - * liblzma build. Otherwise false is returned. + * \param id Filter ID + * + * \return lzma_bool: + * - true if the Filter ID is supported for decoding by this + * liblzma build. + * - false otherwise. */ extern LZMA_API(lzma_bool) lzma_filter_decoder_is_supported(lzma_vli id) lzma_nothrow lzma_attr_const; @@ -108,9 +110,18 @@ extern LZMA_API(lzma_bool) lzma_filter_decoder_is_supported(lzma_vli id) * need to be initialized by the caller in any way. * * If an error occurs, memory possibly already allocated by this function - * is always freed. + * is always freed. liblzma versions older than 5.2.7 may modify the dest + * array and leave its contents in an undefined state if an error occurs. + * liblzma 5.2.7 and newer only modify the dest array when returning LZMA_OK. + * + * \param src Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. + * \param[out] dest Destination filter array + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_OPTIONS_ERROR: Unsupported Filter ID and its options * is not NULL. @@ -118,7 +129,34 @@ extern LZMA_API(lzma_bool) lzma_filter_decoder_is_supported(lzma_vli id) */ extern LZMA_API(lzma_ret) lzma_filters_copy( const lzma_filter *src, lzma_filter *dest, - const lzma_allocator *allocator) lzma_nothrow; + const lzma_allocator *allocator) + lzma_nothrow lzma_attr_warn_unused_result; + + +/** + * \brief Free the options in the array of lzma_filter structures + * + * This frees the filter chain options. The filters array itself is not freed. + * + * The filters array must have at most LZMA_FILTERS_MAX + 1 elements + * including the terminating element which must have .id = LZMA_VLI_UNKNOWN. + * For all elements before the terminating element: + * - options will be freed using the given lzma_allocator or, + * if allocator is NULL, using free(). + * - options will be set to NULL. + * - id will be set to LZMA_VLI_UNKNOWN. + * + * If filters is NULL, this does nothing. Again, this never frees the + * filters array itself. + * + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). + */ +extern LZMA_API(void) lzma_filters_free( + lzma_filter *filters, const lzma_allocator *allocator) + lzma_nothrow; /** @@ -132,9 +170,7 @@ extern LZMA_API(lzma_ret) lzma_filters_copy( * .id == LZMA_VLI_UNKNOWN. * * \return Number of bytes of memory required for the given - * filter chain when encoding. If an error occurs, - * for example due to unsupported filter chain, - * UINT64_MAX is returned. + * filter chain when encoding or UINT64_MAX on error. */ extern LZMA_API(uint64_t) lzma_raw_encoder_memusage(const lzma_filter *filters) lzma_nothrow lzma_attr_pure; @@ -151,9 +187,7 @@ extern LZMA_API(uint64_t) lzma_raw_encoder_memusage(const lzma_filter *filters) * .id == LZMA_VLI_UNKNOWN. * * \return Number of bytes of memory required for the given - * filter chain when decoding. If an error occurs, - * for example due to unsupported filter chain, - * UINT64_MAX is returned. + * filter chain when decoding or UINT64_MAX on error. */ extern LZMA_API(uint64_t) lzma_raw_decoder_memusage(const lzma_filter *filters) lzma_nothrow lzma_attr_pure; @@ -164,14 +198,16 @@ extern LZMA_API(uint64_t) lzma_raw_decoder_memusage(const lzma_filter *filters) * * This function may be useful when implementing custom file formats. * - * \param strm Pointer to properly prepared lzma_stream - * \param filters Array of lzma_filter structures. The end of the - * array must be marked with .id = LZMA_VLI_UNKNOWN. - * - * The `action' with lzma_code() can be LZMA_RUN, LZMA_SYNC_FLUSH (if the + * The 'action' with lzma_code() can be LZMA_RUN, LZMA_SYNC_FLUSH (if the * filter chain supports it), or LZMA_FINISH. * - * \return - LZMA_OK + * \param strm Pointer to lzma_stream that is at least + * initialized with LZMA_STREAM_INIT. + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. + * + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_OPTIONS_ERROR * - LZMA_PROG_ERROR @@ -186,10 +222,16 @@ extern LZMA_API(lzma_ret) lzma_raw_encoder( * * The initialization of raw decoder goes similarly to raw encoder. * - * The `action' with lzma_code() can be LZMA_RUN or LZMA_FINISH. Using + * The 'action' with lzma_code() can be LZMA_RUN or LZMA_FINISH. Using * LZMA_FINISH is not required, it is supported just for convenience. * - * \return - LZMA_OK + * \param strm Pointer to lzma_stream that is at least + * initialized with LZMA_STREAM_INIT. + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. + * + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_OPTIONS_ERROR * - LZMA_PROG_ERROR @@ -202,24 +244,36 @@ extern LZMA_API(lzma_ret) lzma_raw_decoder( /** * \brief Update the filter chain in the encoder * - * This function is for advanced users only. This function has two slightly - * different purposes: + * This function may be called after lzma_code() has returned LZMA_STREAM_END + * when LZMA_FULL_BARRIER, LZMA_FULL_FLUSH, or LZMA_SYNC_FLUSH was used: + * + * - After LZMA_FULL_BARRIER or LZMA_FULL_FLUSH: Single-threaded .xz Stream + * encoder (lzma_stream_encoder()) and (since liblzma 5.4.0) multi-threaded + * Stream encoder (lzma_stream_encoder_mt()) allow setting a new filter + * chain to be used for the next Block(s). * - * - After LZMA_FULL_FLUSH when using Stream encoder: Set a new filter - * chain, which will be used starting from the next Block. + * - After LZMA_SYNC_FLUSH: Raw encoder (lzma_raw_encoder()), + * Block encoder (lzma_block_encoder()), and single-threaded .xz Stream + * encoder (lzma_stream_encoder()) allow changing certain filter-specific + * options in the middle of encoding. The actual filters in the chain + * (Filter IDs) must not be changed! Currently only the lc, lp, and pb + * options of LZMA2 (not LZMA1) can be changed this way. * - * - After LZMA_SYNC_FLUSH using Raw, Block, or Stream encoder: Change - * the filter-specific options in the middle of encoding. The actual - * filters in the chain (Filter IDs) cannot be changed. In the future, - * it might become possible to change the filter options without - * using LZMA_SYNC_FLUSH. + * - In the future some filters might allow changing some of their options + * without any barrier or flushing but currently such filters don't exist. * - * While rarely useful, this function may be called also when no data has - * been compressed yet. In that case, this function will behave as if - * LZMA_FULL_FLUSH (Stream encoder) or LZMA_SYNC_FLUSH (Raw or Block + * This function may also be called when no data has been compressed yet + * although this is rarely useful. In that case, this function will behave + * as if LZMA_FULL_FLUSH (Stream encoders) or LZMA_SYNC_FLUSH (Raw or Block * encoder) had been used right before calling this function. * - * \return - LZMA_OK + * \param strm Pointer to lzma_stream that is at least + * initialized with LZMA_STREAM_INIT. + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. + * + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_MEMLIMIT_ERROR * - LZMA_OPTIONS_ERROR @@ -232,29 +286,30 @@ extern LZMA_API(lzma_ret) lzma_filters_update( /** * \brief Single-call raw encoder * - * \param filters Array of lzma_filter structures. The end of the - * array must be marked with .id = LZMA_VLI_UNKNOWN. + * \note There is no function to calculate how big output buffer + * would surely be big enough. (lzma_stream_buffer_bound() + * works only for lzma_stream_buffer_encode(); raw encoder + * won't necessarily meet that bound.) + * + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. * \param allocator lzma_allocator for custom allocator functions. * Set to NULL to use malloc() and free(). * \param in Beginning of the input buffer * \param in_size Size of the input buffer - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if encoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_BUF_ERROR: Not enough output buffer space. * - LZMA_OPTIONS_ERROR * - LZMA_MEM_ERROR * - LZMA_DATA_ERROR * - LZMA_PROG_ERROR - * - * \note There is no function to calculate how big output buffer - * would surely be big enough. (lzma_stream_buffer_bound() - * works only for lzma_stream_buffer_encode(); raw encoder - * won't necessarily meet that bound.) */ extern LZMA_API(lzma_ret) lzma_raw_buffer_encode( const lzma_filter *filters, const lzma_allocator *allocator, @@ -265,8 +320,8 @@ extern LZMA_API(lzma_ret) lzma_raw_buffer_encode( /** * \brief Single-call raw decoder * - * \param filters Array of lzma_filter structures. The end of the - * array must be marked with .id = LZMA_VLI_UNKNOWN. + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. * \param allocator lzma_allocator for custom allocator functions. * Set to NULL to use malloc() and free(). * \param in Beginning of the input buffer @@ -274,11 +329,19 @@ extern LZMA_API(lzma_ret) lzma_raw_buffer_encode( * *in_pos is updated only if decoding succeeds. * \param in_size Size of the input buffer; the first byte that * won't be read is in[in_size]. - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if encoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. + * + * \return Possible lzma_ret values: + * - LZMA_OK: Decoding was successful. + * - LZMA_BUF_ERROR: Not enough output buffer space. + * - LZMA_OPTIONS_ERROR + * - LZMA_MEM_ERROR + * - LZMA_DATA_ERROR + * - LZMA_PROG_ERROR */ extern LZMA_API(lzma_ret) lzma_raw_buffer_decode( const lzma_filter *filters, const lzma_allocator *allocator, @@ -292,18 +355,19 @@ extern LZMA_API(lzma_ret) lzma_raw_buffer_decode( * This function may be useful when implementing custom file formats * using the raw encoder and decoder. * - * \param size Pointer to uint32_t to hold the size of the properties + * \note This function validates the Filter ID, but does not + * necessarily validate the options. Thus, it is possible + * that this returns LZMA_OK while the following call to + * lzma_properties_encode() returns LZMA_OPTIONS_ERROR. + * + * \param[out] size Pointer to uint32_t to hold the size of the properties * \param filter Filter ID and options (the size of the properties may * vary depending on the options) * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_OPTIONS_ERROR * - LZMA_PROG_ERROR - * - * \note This function validates the Filter ID, but does not - * necessarily validate the options. Thus, it is possible - * that this returns LZMA_OK while the following call to - * lzma_properties_encode() returns LZMA_OPTIONS_ERROR. */ extern LZMA_API(lzma_ret) lzma_properties_size( uint32_t *size, const lzma_filter *filter) lzma_nothrow; @@ -312,15 +376,6 @@ extern LZMA_API(lzma_ret) lzma_properties_size( /** * \brief Encode the Filter Properties field * - * \param filter Filter ID and options - * \param props Buffer to hold the encoded options. The size of - * buffer must have been already determined with - * lzma_properties_size(). - * - * \return - LZMA_OK - * - LZMA_OPTIONS_ERROR - * - LZMA_PROG_ERROR - * * \note Even this function won't validate more options than actually * necessary. Thus, it is possible that encoding the properties * succeeds but using the same options to initialize the encoder @@ -330,6 +385,15 @@ extern LZMA_API(lzma_ret) lzma_properties_size( * of the Filter Properties field is zero, calling * lzma_properties_encode() is not required, but it * won't do any harm either. + * + * \param filter Filter ID and options + * \param[out] props Buffer to hold the encoded options. The size of + * the buffer must have been already determined with + * lzma_properties_size(). + * + * \return Possible lzma_ret values: + * - LZMA_OK + * - LZMA_PROG_ERROR */ extern LZMA_API(lzma_ret) lzma_properties_encode( const lzma_filter *filter, uint8_t *props) lzma_nothrow; @@ -345,15 +409,16 @@ extern LZMA_API(lzma_ret) lzma_properties_encode( * it's application's responsibility to free it when * appropriate. filter->options is set to NULL if * there are no properties or if an error occurs. - * \param allocator Custom memory allocator used to allocate the - * options. Set to NULL to use the default malloc(), + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). * and in case of an error, also free(). * \param props Input buffer containing the properties. * \param props_size Size of the properties. This must be the exact * size; giving too much or too little input will * return LZMA_OPTIONS_ERROR. * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_OPTIONS_ERROR * - LZMA_MEM_ERROR */ @@ -368,18 +433,19 @@ extern LZMA_API(lzma_ret) lzma_properties_decode( * Knowing the size of Filter Flags is useful to know when allocating * memory to hold the encoded Filter Flags. * - * \param size Pointer to integer to hold the calculated size + * \note If you need to calculate size of List of Filter Flags, + * you need to loop over every lzma_filter entry. + * + * \param[out] size Pointer to integer to hold the calculated size * \param filter Filter ID and associated options whose encoded * size is to be calculated * - * \return - LZMA_OK: *size set successfully. Note that this doesn't + * \return Possible lzma_ret values: + * - LZMA_OK: *size set successfully. Note that this doesn't * guarantee that filter->options is valid, thus * lzma_filter_flags_encode() may still fail. * - LZMA_OPTIONS_ERROR: Unknown Filter ID or unsupported options. * - LZMA_PROG_ERROR: Invalid options - * - * \note If you need to calculate size of List of Filter Flags, - * you need to loop over every lzma_filter entry. */ extern LZMA_API(lzma_ret) lzma_filter_flags_size( uint32_t *size, const lzma_filter *filter) @@ -393,12 +459,13 @@ extern LZMA_API(lzma_ret) lzma_filter_flags_size( * This is due to how this function is used internally by liblzma. * * \param filter Filter ID and options to be encoded - * \param out Beginning of the output buffer - * \param out_pos out[*out_pos] is the next write position. This + * \param[out] out Beginning of the output buffer + * \param[out] out_pos out[*out_pos] is the next write position. This * is updated by the encoder. * \param out_size out[out_size] is the first byte to not write. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_OPTIONS_ERROR: Invalid or unsupported options. * - LZMA_PROG_ERROR: Invalid options or not enough output * buffer space (you should have checked it with @@ -413,14 +480,290 @@ extern LZMA_API(lzma_ret) lzma_filter_flags_encode(const lzma_filter *filter, * \brief Decode Filter Flags from given buffer * * The decoded result is stored into *filter. The old value of - * filter->options is not free()d. + * filter->options is not free()d. If anything other than LZMA_OK + * is returned, filter->options is set to NULL. * - * \return - LZMA_OK + * \param[out] filter Destination filter. The decoded Filter ID will + * be stored in filter->id. If options are needed + * they will be allocated and the pointer will be + * stored in filter->options. + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). + * \param in Beginning of the input buffer + * \param[out] in_pos The next byte will be read from in[*in_pos]. + * *in_pos is updated only if decoding succeeds. + * \param in_size Size of the input buffer; the first byte that + * won't be read is in[in_size]. + * + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_OPTIONS_ERROR * - LZMA_MEM_ERROR + * - LZMA_DATA_ERROR * - LZMA_PROG_ERROR */ extern LZMA_API(lzma_ret) lzma_filter_flags_decode( lzma_filter *filter, const lzma_allocator *allocator, const uint8_t *in, size_t *in_pos, size_t in_size) lzma_nothrow lzma_attr_warn_unused_result; + + +/*********** + * Strings * + ***********/ + +/** + * \brief Allow or show all filters + * + * By default only the filters supported in the .xz format are accept by + * lzma_str_to_filters() or shown by lzma_str_list_filters(). + */ +#define LZMA_STR_ALL_FILTERS UINT32_C(0x01) + + +/** + * \brief Do not validate the filter chain in lzma_str_to_filters() + * + * By default lzma_str_to_filters() can return an error if the filter chain + * as a whole isn't usable in the .xz format or in the raw encoder or decoder. + * With this flag, this validation is skipped. This flag doesn't affect the + * handling of the individual filter options. To allow non-.xz filters also + * LZMA_STR_ALL_FILTERS is needed. + */ +#define LZMA_STR_NO_VALIDATION UINT32_C(0x02) + + +/** + * \brief Stringify encoder options + * + * Show the filter-specific options that the encoder will use. + * This may be useful for verbose diagnostic messages. + * + * Note that if options were decoded from .xz headers then the encoder options + * may be undefined. This flag shouldn't be used in such a situation. + */ +#define LZMA_STR_ENCODER UINT32_C(0x10) + + +/** + * \brief Stringify decoder options + * + * Show the filter-specific options that the decoder will use. + * This may be useful for showing what filter options were decoded + * from file headers. + */ +#define LZMA_STR_DECODER UINT32_C(0x20) + + +/** + * \brief Produce xz-compatible getopt_long() syntax + * + * That is, "delta:dist=2 lzma2:dict=4MiB,pb=1,lp=1" becomes + * "--delta=dist=2 --lzma2=dict=4MiB,pb=1,lp=1". + * + * This syntax is compatible with xz 5.0.0 as long as the filters and + * their options are supported too. + */ +#define LZMA_STR_GETOPT_LONG UINT32_C(0x40) + + +/** + * \brief Use two dashes "--" instead of a space to separate filters + * + * That is, "delta:dist=2 lzma2:pb=1,lp=1" becomes + * "delta:dist=2--lzma2:pb=1,lp=1". This looks slightly odd but this + * kind of strings should be usable on the command line without quoting. + * However, it is possible that future versions with new filter options + * might produce strings that require shell quoting anyway as the exact + * set of possible characters isn't frozen for now. + * + * It is guaranteed that the single quote (') will never be used in + * filter chain strings (even if LZMA_STR_NO_SPACES isn't used). + */ +#define LZMA_STR_NO_SPACES UINT32_C(0x80) + + +/** + * \brief Convert a string to a filter chain + * + * This tries to make it easier to write applications that allow users + * to set custom compression options. This only handles the filter + * configuration (including presets) but not the number of threads, + * block size, check type, or memory limits. + * + * The input string can be either a preset or a filter chain. Presets + * begin with a digit 0-9 and may be followed by zero or more flags + * which are lower-case letters. Currently only "e" is supported, matching + * LZMA_PRESET_EXTREME. For partial xz command line syntax compatibility, + * a preset string may start with a single dash "-". + * + * A filter chain consists of one or more "filtername:opt1=value1,opt2=value2" + * strings separated by one or more spaces. Leading and trailing spaces are + * ignored. All names and values must be lower-case. Extra commas in the + * option list are ignored. The order of filters is significant: when + * encoding, the uncompressed input data goes to the leftmost filter first. + * Normally "lzma2" is the last filter in the chain. + * + * If one wishes to avoid spaces, for example, to avoid shell quoting, + * it is possible to use two dashes "--" instead of spaces to separate + * the filters. + * + * For xz command line compatibility, each filter may be prefixed with + * two dashes "--" and the colon ":" separating the filter name from + * the options may be replaced with an equals sign "=". + * + * By default, only filters that can be used in the .xz format are accepted. + * To allow all filters (LZMA1) use the flag LZMA_STR_ALL_FILTERS. + * + * By default, very basic validation is done for the filter chain as a whole, + * for example, that LZMA2 is only used as the last filter in the chain. + * The validation isn't perfect though and it's possible that this function + * succeeds but using the filter chain for encoding or decoding will still + * result in LZMA_OPTIONS_ERROR. To disable this validation, use the flag + * LZMA_STR_NO_VALIDATION. + * + * The available filter names and their options are available via + * lzma_str_list_filters(). See the xz man page for the description + * of filter names and options. + * + * For command line applications, below is an example how an error message + * can be displayed. Note the use of an empty string for the field width. + * If "^" was used there it would create an off-by-one error except at + * the very beginning of the line. + * + * \code{.c} + * const char *str = ...; // From user + * lzma_filter filters[LZMA_FILTERS_MAX + 1]; + * int pos; + * const char *msg = lzma_str_to_filters(str, &pos, filters, 0, NULL); + * if (msg != NULL) { + * printf("%s: Error in XZ compression options:\n", argv[0]); + * printf("%s: %s\n", argv[0], str); + * printf("%s: %*s^\n", argv[0], errpos, ""); + * printf("%s: %s\n", argv[0], msg); + * } + * \endcode + * + * \param str User-supplied string describing a preset or + * a filter chain. If a default value is needed and + * you don't know what would be good, use "6" since + * that is the default preset in xz too. + * \param[out] error_pos If this isn't NULL, this value will be set on + * both success and on all errors. This tells the + * location of the error in the string. This is + * an int to make it straightforward to use this + * as printf() field width. The value is guaranteed + * to be in the range [0, INT_MAX] even if strlen(str) + * somehow was greater than INT_MAX. + * \param[out] filters An array of lzma_filter structures. There must + * be LZMA_FILTERS_MAX + 1 (that is, five) elements + * in the array. The old contents are ignored so it + * doesn't need to be initialized. This array is + * modified only if this function returns NULL. + * Once the allocated filter options are no longer + * needed, lzma_filters_free() can be used to free the + * options (it doesn't free the filters array itself). + * \param flags Bitwise-or of zero or more of the flags + * LZMA_STR_ALL_FILTERS and LZMA_STR_NO_VALIDATION. + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). + * + * \return On success, NULL is returned. On error, a statically-allocated + * error message is returned which together with the error_pos + * should give some idea what is wrong. + */ +extern LZMA_API(const char *) lzma_str_to_filters( + const char *str, int *error_pos, lzma_filter *filters, + uint32_t flags, const lzma_allocator *allocator) + lzma_nothrow lzma_attr_warn_unused_result; + + +/** + * \brief Convert a filter chain to a string + * + * Use cases: + * + * - Verbose output showing the full encoder options to the user + * (use LZMA_STR_ENCODER in flags) + * + * - Showing the filters and options that are required to decode a file + * (use LZMA_STR_DECODER in flags) + * + * - Showing the filter names without any options in informational messages + * where the technical details aren't important (no flags). In this case + * the .options in the filters array are ignored and may be NULL even if + * a filter has a mandatory options structure. + * + * Note that even if the filter chain was specified using a preset, + * the resulting filter chain isn't reversed to a preset. So if you + * specify "6" to lzma_str_to_filters() then lzma_str_from_filters() + * will produce a string containing "lzma2". + * + * \param[out] str On success *str will be set to point to an + * allocated string describing the given filter + * chain. Old value is ignored. On error *str is + * always set to NULL. + * \param filters Array of filters terminated with + * .id == LZMA_VLI_UNKNOWN. + * \param flags Bitwise-or of zero or more of the flags + * LZMA_STR_ENCODER, LZMA_STR_DECODER, + * LZMA_STR_GETOPT_LONG, and LZMA_STR_NO_SPACES. + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). + * + * \return Possible lzma_ret values: + * - LZMA_OK + * - LZMA_OPTIONS_ERROR: Empty filter chain + * (filters[0].id == LZMA_VLI_UNKNOWN) or the filter chain + * includes a Filter ID that is not supported by this function. + * - LZMA_MEM_ERROR + * - LZMA_PROG_ERROR + */ +extern LZMA_API(lzma_ret) lzma_str_from_filters( + char **str, const lzma_filter *filters, uint32_t flags, + const lzma_allocator *allocator) + lzma_nothrow lzma_attr_warn_unused_result; + + +/** + * \brief List available filters and/or their options (for help message) + * + * If a filter_id is given then only one line is created which contains the + * filter name. If LZMA_STR_ENCODER or LZMA_STR_DECODER is used then the + * options read by the encoder or decoder are printed on the same line. + * + * If filter_id is LZMA_VLI_UNKNOWN then all supported .xz-compatible filters + * are listed: + * + * - If neither LZMA_STR_ENCODER nor LZMA_STR_DECODER is used then + * the supported filter names are listed on a single line separated + * by spaces. + * + * - If LZMA_STR_ENCODER or LZMA_STR_DECODER is used then filters and + * the supported options are listed one filter per line. There won't + * be a newline after the last filter. + * + * - If LZMA_STR_ALL_FILTERS is used then the list will include also + * those filters that cannot be used in the .xz format (LZMA1). + * + * \param str On success *str will be set to point to an + * allocated string listing the filters and options. + * Old value is ignored. On error *str is always set + * to NULL. + * \param filter_id Filter ID or LZMA_VLI_UNKNOWN. + * \param flags Bitwise-or of zero or more of the flags + * LZMA_STR_ALL_FILTERS, LZMA_STR_ENCODER, + * LZMA_STR_DECODER, and LZMA_STR_GETOPT_LONG. + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). + * + * \return Possible lzma_ret values: + * - LZMA_OK + * - LZMA_OPTIONS_ERROR: Unsupported filter_id or flags + * - LZMA_MEM_ERROR + * - LZMA_PROG_ERROR + */ +extern LZMA_API(lzma_ret) lzma_str_list_filters( + char **str, lzma_vli filter_id, uint32_t flags, + const lzma_allocator *allocator) + lzma_nothrow lzma_attr_warn_unused_result; diff --git a/Utilities/cmliblzma/liblzma/api/lzma/hardware.h b/Utilities/cmliblzma/liblzma/api/lzma/hardware.h index 47481f2581f..7a1a84fcccf 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/hardware.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/hardware.h @@ -1,6 +1,9 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/hardware.h * \brief Hardware information + * \note Never include this file directly. Use instead. * * Since liblzma can consume a lot of system resources, it also provides * ways to limit the resource usage. Applications linking against liblzma @@ -22,11 +25,6 @@ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -57,7 +55,7 @@ extern LZMA_API(uint64_t) lzma_physmem(void) lzma_nothrow; * If the hardware supports more than one thread per CPU core, the number * of hardware threads is returned if that information is available. * - * \brief On success, the number of available CPU threads or cores is + * \return On success, the number of available CPU threads or cores is * returned. If this information isn't available or an error * occurs, zero is returned. */ diff --git a/Utilities/cmliblzma/liblzma/api/lzma/index.h b/Utilities/cmliblzma/liblzma/api/lzma/index.h index 3dac6fb85cc..b17025e3d90 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/index.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/index.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/index.h * \brief Handling of .xz Index and related information + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -50,8 +48,13 @@ typedef struct { */ const lzma_stream_flags *flags; + /** \private Reserved member. */ const void *reserved_ptr1; + + /** \private Reserved member. */ const void *reserved_ptr2; + + /** \private Reserved member. */ const void *reserved_ptr3; /** @@ -107,9 +110,17 @@ typedef struct { */ lzma_vli padding; + + /** \private Reserved member. */ lzma_vli reserved_vli1; + + /** \private Reserved member. */ lzma_vli reserved_vli2; + + /** \private Reserved member. */ lzma_vli reserved_vli3; + + /** \private Reserved member. */ lzma_vli reserved_vli4; } stream; @@ -196,25 +207,46 @@ typedef struct { */ lzma_vli total_size; + /** \private Reserved member. */ lzma_vli reserved_vli1; + + /** \private Reserved member. */ lzma_vli reserved_vli2; + + /** \private Reserved member. */ lzma_vli reserved_vli3; + + /** \private Reserved member. */ lzma_vli reserved_vli4; + /** \private Reserved member. */ const void *reserved_ptr1; + + /** \private Reserved member. */ const void *reserved_ptr2; + + /** \private Reserved member. */ const void *reserved_ptr3; + + /** \private Reserved member. */ const void *reserved_ptr4; } block; - /* + /** + * \private Internal data + * * Internal data which is used to store the state of the iterator. * The exact format may vary between liblzma versions, so don't * touch these in any way. */ union { + /** \private Internal member. */ const void *p; + + /** \private Internal member. */ size_t s; + + /** \private Internal member. */ lzma_vli v; } internal[6]; } lzma_index_iter; @@ -268,20 +300,47 @@ typedef enum { } lzma_index_iter_mode; +/** + * \brief Mask for return value from lzma_index_checks() for check none + * + * \note This and the other CHECK_MASK macros were added in 5.5.1alpha. + */ +#define LZMA_INDEX_CHECK_MASK_NONE (UINT32_C(1) << LZMA_CHECK_NONE) + +/** + * \brief Mask for return value from lzma_index_checks() for check CRC32 + */ +#define LZMA_INDEX_CHECK_MASK_CRC32 (UINT32_C(1) << LZMA_CHECK_CRC32) + +/** + * \brief Mask for return value from lzma_index_checks() for check CRC64 + */ +#define LZMA_INDEX_CHECK_MASK_CRC64 (UINT32_C(1) << LZMA_CHECK_CRC64) + +/** + * \brief Mask for return value from lzma_index_checks() for check SHA256 + */ +#define LZMA_INDEX_CHECK_MASK_SHA256 (UINT32_C(1) << LZMA_CHECK_SHA256) + /** * \brief Calculate memory usage of lzma_index * * On disk, the size of the Index field depends on both the number of Records - * stored and how big values the Records store (due to variable-length integer + * stored and the size of the Records (due to variable-length integer * encoding). When the Index is kept in lzma_index structure, the memory usage * depends only on the number of Records/Blocks stored in the Index(es), and * in case of concatenated lzma_indexes, the number of Streams. The size in * RAM is almost always significantly bigger than in the encoded form on disk. * - * This function calculates an approximate amount of memory needed hold + * This function calculates an approximate amount of memory needed to hold * the given number of Streams and Blocks in lzma_index structure. This * value may vary between CPU architectures and also between liblzma versions * if the internal implementation is modified. + * + * \param streams Number of Streams + * \param blocks Number of Blocks + * + * \return Approximate memory in bytes needed in a lzma_index structure. */ extern LZMA_API(uint64_t) lzma_index_memusage( lzma_vli streams, lzma_vli blocks) lzma_nothrow; @@ -292,6 +351,10 @@ extern LZMA_API(uint64_t) lzma_index_memusage( * * This is a shorthand for lzma_index_memusage(lzma_index_stream_count(i), * lzma_index_block_count(i)). + * + * \param i Pointer to lzma_index structure + * + * \return Approximate memory in bytes used by the lzma_index structure. */ extern LZMA_API(uint64_t) lzma_index_memused(const lzma_index *i) lzma_nothrow; @@ -300,6 +363,9 @@ extern LZMA_API(uint64_t) lzma_index_memused(const lzma_index *i) /** * \brief Allocate and initialize a new lzma_index structure * + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). + * * \return On success, a pointer to an empty initialized lzma_index is * returned. If allocation fails, NULL is returned. */ @@ -311,6 +377,10 @@ extern LZMA_API(lzma_index *) lzma_index_init(const lzma_allocator *allocator) * \brief Deallocate lzma_index * * If i is NULL, this does nothing. + * + * \param i Pointer to lzma_index structure to deallocate + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). */ extern LZMA_API(void) lzma_index_end( lzma_index *i, const lzma_allocator *allocator) lzma_nothrow; @@ -320,8 +390,9 @@ extern LZMA_API(void) lzma_index_end( * \brief Add a new Block to lzma_index * * \param i Pointer to a lzma_index structure - * \param allocator Pointer to lzma_allocator, or NULL to - * use malloc() + * \param allocator lzma_allocator for custom allocator + * functions. Set to NULL to use malloc() + * and free(). * \param unpadded_size Unpadded Size of a Block. This can be * calculated with lzma_block_unpadded_size() * after encoding or decoding the Block. @@ -334,7 +405,8 @@ extern LZMA_API(void) lzma_index_end( * lzma_index_append() it is possible to read the next Block with * an existing iterator. * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_MEM_ERROR * - LZMA_DATA_ERROR: Compressed or uncompressed size of the * Stream or size of the Index field would grow too big. @@ -354,11 +426,15 @@ extern LZMA_API(lzma_ret) lzma_index_append( * lzma_index, because to decode Blocks, knowing the integrity check type * is needed. * - * The given Stream Flags are copied into internal preallocated structure - * in the lzma_index, thus the caller doesn't need to keep the *stream_flags - * available after calling this function. + * \param i Pointer to lzma_index structure + * \param stream_flags Pointer to lzma_stream_flags structure. This + * is copied into the internal preallocated + * structure, so the caller doesn't need to keep + * the flags' data available after calling this + * function. * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_OPTIONS_ERROR: Unsupported stream_flags->version. * - LZMA_PROG_ERROR */ @@ -376,6 +452,11 @@ extern LZMA_API(lzma_ret) lzma_index_stream_flags( * showing the Check types to the user. * * The bitmask is 1 << check_id, e.g. CRC32 is 1 << 1 and SHA-256 is 1 << 10. + * These masks are defined for convenience as LZMA_INDEX_CHECK_MASK_XXX + * + * \param i Pointer to lzma_index structure + * + * \return Bitmask indicating which Check types are used in the lzma_index */ extern LZMA_API(uint32_t) lzma_index_checks(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -390,7 +471,8 @@ extern LZMA_API(uint32_t) lzma_index_checks(const lzma_index *i) * * By default, the amount of Stream Padding is assumed to be zero bytes. * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_DATA_ERROR: The file size would grow too big. * - LZMA_PROG_ERROR */ @@ -401,6 +483,10 @@ extern LZMA_API(lzma_ret) lzma_index_stream_padding( /** * \brief Get the number of Streams + * + * \param i Pointer to lzma_index structure + * + * \return Number of Streams in the lzma_index */ extern LZMA_API(lzma_vli) lzma_index_stream_count(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -411,6 +497,10 @@ extern LZMA_API(lzma_vli) lzma_index_stream_count(const lzma_index *i) * * This returns the total number of Blocks in lzma_index. To get number * of Blocks in individual Streams, use lzma_index_iter. + * + * \param i Pointer to lzma_index structure + * + * \return Number of blocks in the lzma_index */ extern LZMA_API(lzma_vli) lzma_index_block_count(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -420,6 +510,10 @@ extern LZMA_API(lzma_vli) lzma_index_block_count(const lzma_index *i) * \brief Get the size of the Index field as bytes * * This is needed to verify the Backward Size field in the Stream Footer. + * + * \param i Pointer to lzma_index structure + * + * \return Size in bytes of the Index */ extern LZMA_API(lzma_vli) lzma_index_size(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -431,6 +525,11 @@ extern LZMA_API(lzma_vli) lzma_index_size(const lzma_index *i) * If multiple lzma_indexes have been combined, this works as if the Blocks * were in a single Stream. This is useful if you are going to combine * Blocks from multiple Streams into a single new Stream. + * + * \param i Pointer to lzma_index structure + * + * \return Size in bytes of the Stream (if all Blocks are combined + * into one Stream). */ extern LZMA_API(lzma_vli) lzma_index_stream_size(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -441,6 +540,10 @@ extern LZMA_API(lzma_vli) lzma_index_stream_size(const lzma_index *i) * * This doesn't include the Stream Header, Stream Footer, Stream Padding, * or Index fields. + * + * \param i Pointer to lzma_index structure + * + * \return Size in bytes of all Blocks in the Stream(s) */ extern LZMA_API(lzma_vli) lzma_index_total_size(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -453,6 +556,10 @@ extern LZMA_API(lzma_vli) lzma_index_total_size(const lzma_index *i) * no Stream Padding, this function is identical to lzma_index_stream_size(). * If multiple lzma_indexes have been combined, this includes also the headers * of each separate Stream and the possible Stream Padding fields. + * + * \param i Pointer to lzma_index structure + * + * \return Total size of the .xz file in bytes */ extern LZMA_API(lzma_vli) lzma_index_file_size(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -460,6 +567,10 @@ extern LZMA_API(lzma_vli) lzma_index_file_size(const lzma_index *i) /** * \brief Get the uncompressed size of the file + * + * \param i Pointer to lzma_index structure + * + * \return Size in bytes of the uncompressed data in the file */ extern LZMA_API(lzma_vli) lzma_index_uncompressed_size(const lzma_index *i) lzma_nothrow lzma_attr_pure; @@ -468,9 +579,6 @@ extern LZMA_API(lzma_vli) lzma_index_uncompressed_size(const lzma_index *i) /** * \brief Initialize an iterator * - * \param iter Pointer to a lzma_index_iter structure - * \param i lzma_index to which the iterator will be associated - * * This function associates the iterator with the given lzma_index, and calls * lzma_index_iter_rewind() on the iterator. * @@ -483,6 +591,9 @@ extern LZMA_API(lzma_vli) lzma_index_uncompressed_size(const lzma_index *i) * * It is safe to make copies of an initialized lzma_index_iter, for example, * to easily restart reading at some particular position. + * + * \param iter Pointer to a lzma_index_iter structure + * \param i lzma_index to which the iterator will be associated */ extern LZMA_API(void) lzma_index_iter_init( lzma_index_iter *iter, const lzma_index *i) lzma_nothrow; @@ -493,6 +604,8 @@ extern LZMA_API(void) lzma_index_iter_init( * * Rewind the iterator so that next call to lzma_index_iter_next() will * return the first Block or Stream. + * + * \param iter Pointer to a lzma_index_iter structure */ extern LZMA_API(void) lzma_index_iter_rewind(lzma_index_iter *iter) lzma_nothrow; @@ -505,11 +618,11 @@ extern LZMA_API(void) lzma_index_iter_rewind(lzma_index_iter *iter) * \param mode Specify what kind of information the caller wants * to get. See lzma_index_iter_mode for details. * - * \return If next Block or Stream matching the mode was found, *iter - * is updated and this function returns false. If no Block or - * Stream matching the mode is found, *iter is not modified - * and this function returns true. If mode is set to an unknown - * value, *iter is not modified and this function returns true. + * \return lzma_bool: + * - true if no Block or Stream matching the mode is found. + * *iter is not updated (failure). + * - false if the next Block or Stream matching the mode was + * found. *iter is updated (success). */ extern LZMA_API(lzma_bool) lzma_index_iter_next( lzma_index_iter *iter, lzma_index_iter_mode mode) @@ -523,21 +636,26 @@ extern LZMA_API(lzma_bool) lzma_index_iter_next( * the Index field(s) and use lzma_index_iter_locate() to do random-access * reading with granularity of Block size. * - * \param iter Iterator that was earlier initialized with - * lzma_index_iter_init(). - * \param target Uncompressed target offset which the caller would - * like to locate from the Stream - * * If the target is smaller than the uncompressed size of the Stream (can be * checked with lzma_index_uncompressed_size()): * - Information about the Stream and Block containing the requested * uncompressed offset is stored into *iter. * - Internal state of the iterator is adjusted so that * lzma_index_iter_next() can be used to read subsequent Blocks or Streams. - * - This function returns false. * - * If target is greater than the uncompressed size of the Stream, *iter - * is not modified, and this function returns true. + * If the target is greater than the uncompressed size of the Stream, *iter + * is not modified. + * + * \param iter Iterator that was earlier initialized with + * lzma_index_iter_init(). + * \param target Uncompressed target offset which the caller would + * like to locate from the Stream + * + * \return lzma_bool: + * - true if the target is greater than or equal to the + * uncompressed size of the Stream (failure) + * - false if the target is smaller than the uncompressed size + * of the Stream (success) */ extern LZMA_API(lzma_bool) lzma_index_iter_locate( lzma_index_iter *iter, lzma_vli target) lzma_nothrow; @@ -550,15 +668,16 @@ extern LZMA_API(lzma_bool) lzma_index_iter_locate( * multi-Stream .xz file, or when combining multiple Streams into single * Stream. * - * \param dest lzma_index after which src is appended + * \param[out] dest lzma_index after which src is appended * \param src lzma_index to be appended after dest. If this * function succeeds, the memory allocated for src * is freed or moved to be part of dest, and all * iterators pointing to src will become invalid. - * \param allocator Custom memory allocator; can be NULL to use - * malloc() and free(). + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). * - * \return - LZMA_OK: lzma_indexes were concatenated successfully. + * \return Possible lzma_ret values: + * - LZMA_OK: lzma_indexes were concatenated successfully. * src is now a dangling pointer. * - LZMA_DATA_ERROR: *dest would grow too big. * - LZMA_MEM_ERROR @@ -572,6 +691,10 @@ extern LZMA_API(lzma_ret) lzma_index_cat(lzma_index *dest, lzma_index *src, /** * \brief Duplicate lzma_index * + * \param i Pointer to lzma_index structure to be duplicated + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). + * * \return A copy of the lzma_index, or NULL if memory allocation failed. */ extern LZMA_API(lzma_index *) lzma_index_dup( @@ -585,10 +708,11 @@ extern LZMA_API(lzma_index *) lzma_index_dup( * \param strm Pointer to properly prepared lzma_stream * \param i Pointer to lzma_index which should be encoded. * - * The valid `action' values for lzma_code() are LZMA_RUN and LZMA_FINISH. + * The valid 'action' values for lzma_code() are LZMA_RUN and LZMA_FINISH. * It is enough to use only one of them (you can choose freely). * - * \return - LZMA_OK: Initialization succeeded, continue with lzma_code(). + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization succeeded, continue with lzma_code(). * - LZMA_MEM_ERROR * - LZMA_PROG_ERROR */ @@ -601,7 +725,7 @@ extern LZMA_API(lzma_ret) lzma_index_encoder( * \brief Initialize .xz Index decoder * * \param strm Pointer to properly prepared lzma_stream - * \param i The decoded Index will be made available via + * \param[out] i The decoded Index will be made available via * this pointer. Initially this function will * set *i to NULL (the old value is ignored). If * decoding succeeds (lzma_code() returns @@ -613,15 +737,16 @@ extern LZMA_API(lzma_ret) lzma_index_encoder( * don't allow 0 here and return LZMA_PROG_ERROR; * later versions treat 0 as if 1 had been specified. * - * Valid `action' arguments to lzma_code() are LZMA_RUN and LZMA_FINISH. + * Valid 'action' arguments to lzma_code() are LZMA_RUN and LZMA_FINISH. * There is no need to use LZMA_FINISH, but it's allowed because it may * simplify certain types of applications. * - * \return - LZMA_OK: Initialization succeeded, continue with lzma_code(). + * \return Possible lzma_ret values: + * - LZMA_OK: Initialization succeeded, continue with lzma_code(). * - LZMA_MEM_ERROR * - LZMA_PROG_ERROR * - * liblzma 5.2.3 and older list also LZMA_MEMLIMIT_ERROR here + * \note liblzma 5.2.3 and older list also LZMA_MEMLIMIT_ERROR here * but that error code has never been possible from this * initialization function. */ @@ -633,21 +758,23 @@ extern LZMA_API(lzma_ret) lzma_index_decoder( /** * \brief Single-call .xz Index encoder * + * \note This function doesn't take allocator argument since all + * the internal data is allocated on stack. + * * \param i lzma_index to be encoded - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * *out_pos is updated only if encoding succeeds. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_BUF_ERROR: Output buffer is too small. Use * lzma_index_size() to find out how much output * space is needed. * - LZMA_PROG_ERROR * - * \note This function doesn't take allocator argument since all - * the internal data is allocated on stack. */ extern LZMA_API(lzma_ret) lzma_index_buffer_encode(const lzma_index *i, uint8_t *out, size_t *out_pos, size_t out_size) lzma_nothrow; @@ -656,24 +783,26 @@ extern LZMA_API(lzma_ret) lzma_index_buffer_encode(const lzma_index *i, /** * \brief Single-call .xz Index decoder * - * \param i If decoding succeeds, *i will point to a new + * \param[out] i If decoding succeeds, *i will point to a new * lzma_index, which the application has to * later free with lzma_index_end(). If an error * occurs, *i will be NULL. The old value of *i * is always ignored and thus doesn't need to be * initialized by the caller. - * \param memlimit Pointer to how much memory the resulting + * \param[out] memlimit Pointer to how much memory the resulting * lzma_index is allowed to require. The value * pointed by this pointer is modified if and only * if LZMA_MEMLIMIT_ERROR is returned. - * \param allocator Pointer to lzma_allocator, or NULL to use malloc() + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). * \param in Beginning of the input buffer * \param in_pos The next byte will be read from in[*in_pos]. * *in_pos is updated only if decoding succeeds. * \param in_size Size of the input buffer; the first byte that * won't be read is in[in_size]. * - * \return - LZMA_OK: Decoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Decoding was successful. * - LZMA_MEM_ERROR * - LZMA_MEMLIMIT_ERROR: Memory usage limit was reached. * The minimum required memlimit value was stored to *memlimit. @@ -684,3 +813,70 @@ extern LZMA_API(lzma_ret) lzma_index_buffer_decode(lzma_index **i, uint64_t *memlimit, const lzma_allocator *allocator, const uint8_t *in, size_t *in_pos, size_t in_size) lzma_nothrow; + + +/** + * \brief Initialize a .xz file information decoder + * + * This decoder decodes the Stream Header, Stream Footer, Index, and + * Stream Padding field(s) from the input .xz file and stores the resulting + * combined index in *dest_index. This information can be used to get the + * uncompressed file size with lzma_index_uncompressed_size(*dest_index) or, + * for example, to implement random access reading by locating the Blocks + * in the Streams. + * + * To get the required information from the .xz file, lzma_code() may ask + * the application to seek in the input file by returning LZMA_SEEK_NEEDED + * and having the target file position specified in lzma_stream.seek_pos. + * The number of seeks required depends on the input file and how big buffers + * the application provides. When possible, the decoder will seek backward + * and forward in the given buffer to avoid useless seek requests. Thus, if + * the application provides the whole file at once, no external seeking will + * be required (that is, lzma_code() won't return LZMA_SEEK_NEEDED). + * + * The value in lzma_stream.total_in can be used to estimate how much data + * liblzma had to read to get the file information. However, due to seeking + * and the way total_in is updated, the value of total_in will be somewhat + * inaccurate (a little too big). Thus, total_in is a good estimate but don't + * expect to see the same exact value for the same file if you change the + * input buffer size or switch to a different liblzma version. + * + * Valid 'action' arguments to lzma_code() are LZMA_RUN and LZMA_FINISH. + * You only need to use LZMA_RUN; LZMA_FINISH is only supported because it + * might be convenient for some applications. If you use LZMA_FINISH and if + * lzma_code() asks the application to seek, remember to reset 'action' back + * to LZMA_RUN unless you hit the end of the file again. + * + * Possible return values from lzma_code(): + * - LZMA_OK: All OK so far, more input needed + * - LZMA_SEEK_NEEDED: Provide more input starting from the absolute + * file position strm->seek_pos + * - LZMA_STREAM_END: Decoding was successful, *dest_index has been set + * - LZMA_FORMAT_ERROR: The input file is not in the .xz format (the + * expected magic bytes were not found from the beginning of the file) + * - LZMA_OPTIONS_ERROR: File looks valid but contains headers that aren't + * supported by this version of liblzma + * - LZMA_DATA_ERROR: File is corrupt + * - LZMA_BUF_ERROR + * - LZMA_MEM_ERROR + * - LZMA_MEMLIMIT_ERROR + * - LZMA_PROG_ERROR + * + * \param strm Pointer to a properly prepared lzma_stream + * \param[out] dest_index Pointer to a pointer where the decoder will put + * the decoded lzma_index. The old value + * of *dest_index is ignored (not freed). + * \param memlimit How much memory the resulting lzma_index is + * allowed to require. Use UINT64_MAX to + * effectively disable the limiter. + * \param file_size Size of the input .xz file + * + * \return Possible lzma_ret values: + * - LZMA_OK + * - LZMA_MEM_ERROR + * - LZMA_PROG_ERROR + */ +extern LZMA_API(lzma_ret) lzma_file_info_decoder( + lzma_stream *strm, lzma_index **dest_index, + uint64_t memlimit, uint64_t file_size) + lzma_nothrow; diff --git a/Utilities/cmliblzma/liblzma/api/lzma/index_hash.h b/Utilities/cmliblzma/liblzma/api/lzma/index_hash.h index 9287f1dfdb5..68f9024eb3b 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/index_hash.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/index_hash.h @@ -1,6 +1,9 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/index_hash.h * \brief Validate Index by using a hash function + * \note Never include this file directly. Use instead. * * Hashing makes it possible to use constant amount of memory to validate * Index of arbitrary size. @@ -8,11 +11,6 @@ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -28,13 +26,21 @@ typedef struct lzma_index_hash_s lzma_index_hash; /** * \brief Allocate and initialize a new lzma_index_hash structure * - * If index_hash is NULL, a new lzma_index_hash structure is allocated, - * initialized, and a pointer to it returned. If allocation fails, NULL - * is returned. + * If index_hash is NULL, this function allocates and initializes a new + * lzma_index_hash structure and returns a pointer to it. If allocation + * fails, NULL is returned. + * + * If index_hash is non-NULL, this function reinitializes the lzma_index_hash + * structure and returns the same pointer. In this case, return value cannot + * be NULL or a different pointer than the index_hash that was given as + * an argument. + * + * \param index_hash Pointer to a lzma_index_hash structure or NULL. + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). * - * If index_hash is non-NULL, it is reinitialized and the same pointer - * returned. In this case, return value cannot be NULL or a different - * pointer than the index_hash that was given as an argument. + * \return Initialized lzma_index_hash structure on success or + * NULL on failure. */ extern LZMA_API(lzma_index_hash *) lzma_index_hash_init( lzma_index_hash *index_hash, const lzma_allocator *allocator) @@ -43,6 +49,10 @@ extern LZMA_API(lzma_index_hash *) lzma_index_hash_init( /** * \brief Deallocate lzma_index_hash structure + * + * \param index_hash Pointer to a lzma_index_hash structure to free. + * \param allocator lzma_allocator for custom allocator functions. + * Set to NULL to use malloc() and free(). */ extern LZMA_API(void) lzma_index_hash_end( lzma_index_hash *index_hash, const lzma_allocator *allocator) @@ -52,11 +62,12 @@ extern LZMA_API(void) lzma_index_hash_end( /** * \brief Add a new Record to an Index hash * - * \param index Pointer to a lzma_index_hash structure + * \param index_hash Pointer to a lzma_index_hash structure * \param unpadded_size Unpadded Size of a Block * \param uncompressed_size Uncompressed Size of a Block * - * \return - LZMA_OK + * \return Possible lzma_ret values: + * - LZMA_OK * - LZMA_DATA_ERROR: Compressed or uncompressed size of the * Stream or size of the Index field would grow too big. * - LZMA_PROG_ERROR: Invalid arguments or this function is being @@ -81,10 +92,11 @@ extern LZMA_API(lzma_ret) lzma_index_hash_append(lzma_index_hash *index_hash, * * \param index_hash Pointer to a lzma_index_hash structure * \param in Pointer to the beginning of the input buffer - * \param in_pos in[*in_pos] is the next byte to process + * \param[out] in_pos in[*in_pos] is the next byte to process * \param in_size in[in_size] is the first byte not to process * - * \return - LZMA_OK: So far good, but more input is needed. + * \return Possible lzma_ret values: + * - LZMA_OK: So far good, but more input is needed. * - LZMA_STREAM_END: Index decoded successfully and it matches * the Records given with lzma_index_hash_append(). * - LZMA_DATA_ERROR: Index is corrupt or doesn't match the @@ -101,6 +113,10 @@ extern LZMA_API(lzma_ret) lzma_index_hash_decode(lzma_index_hash *index_hash, * \brief Get the size of the Index field as bytes * * This is needed to verify the Backward Size field in the Stream Footer. + * + * \param index_hash Pointer to a lzma_index_hash structure + * + * \return Size of the Index field in bytes. */ extern LZMA_API(lzma_vli) lzma_index_hash_size( const lzma_index_hash *index_hash) diff --git a/Utilities/cmliblzma/liblzma/api/lzma/lzma12.h b/Utilities/cmliblzma/liblzma/api/lzma/lzma12.h index df5f23b61a4..05f5b66eb56 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/lzma12.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/lzma12.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/lzma12.h * \brief LZMA1 and LZMA2 filters + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -18,23 +16,46 @@ /** - * \brief LZMA1 Filter ID + * \brief LZMA1 Filter ID (for raw encoder/decoder only, not in .xz) * * LZMA1 is the very same thing as what was called just LZMA in LZMA Utils, * 7-Zip, and LZMA SDK. It's called LZMA1 here to prevent developers from * accidentally using LZMA when they actually want LZMA2. - * - * LZMA1 shouldn't be used for new applications unless you _really_ know - * what you are doing. LZMA2 is almost always a better choice. */ #define LZMA_FILTER_LZMA1 LZMA_VLI_C(0x4000000000000001) +/** + * \brief LZMA1 Filter ID with extended options (for raw encoder/decoder) + * + * This is like LZMA_FILTER_LZMA1 but with this ID a few extra options + * are supported in the lzma_options_lzma structure: + * + * - A flag to tell the encoder if the end of payload marker (EOPM) alias + * end of stream (EOS) marker must be written at the end of the stream. + * In contrast, LZMA_FILTER_LZMA1 always writes the end marker. + * + * - Decoder needs to be told the uncompressed size of the stream + * or that it is unknown (using the special value UINT64_MAX). + * If the size is known, a flag can be set to allow the presence of + * the end marker anyway. In contrast, LZMA_FILTER_LZMA1 always + * behaves as if the uncompressed size was unknown. + * + * This allows handling file formats where LZMA1 streams are used but where + * the end marker isn't allowed or where it might not (always) be present. + * This extended LZMA1 functionality is provided as a Filter ID for raw + * encoder and decoder instead of adding new encoder and decoder initialization + * functions because this way it is possible to also use extra filters, + * for example, LZMA_FILTER_X86 in a filter chain with LZMA_FILTER_LZMA1EXT, + * which might be needed to handle some file formats. + */ +#define LZMA_FILTER_LZMA1EXT LZMA_VLI_C(0x4000000000000002) + /** * \brief LZMA2 Filter ID * * Usually you want this instead of LZMA1. Compared to LZMA1, LZMA2 adds * support for LZMA_SYNC_FLUSH, uncompressed chunks (smaller expansion - * when trying to compress uncompressible data), possibility to change + * when trying to compress incompressible data), possibility to change * lc/lp/pb in the middle of encoding, and some other internal improvements. */ #define LZMA_FILTER_LZMA2 LZMA_VLI_C(0x21) @@ -114,16 +135,20 @@ typedef enum { /** * \brief Test if given match finder is supported * - * Return true if the given match finder is supported by this liblzma build. - * Otherwise false is returned. It is safe to call this with a value that - * isn't listed in lzma_match_finder enumeration; the return value will be - * false. + * It is safe to call this with a value that isn't listed in + * lzma_match_finder enumeration; the return value will be false. * * There is no way to list which match finders are available in this * particular liblzma version and build. It would be useless, because * a new match finder, which the application developer wasn't aware, * could require giving additional options to the encoder that the older * match finders don't need. + * + * \param match_finder Match finder ID + * + * \return lzma_bool: + * - true if the match finder is supported by this liblzma build. + * - false otherwise. */ extern LZMA_API(lzma_bool) lzma_mf_is_supported(lzma_match_finder match_finder) lzma_nothrow lzma_attr_const; @@ -158,14 +183,20 @@ typedef enum { /** * \brief Test if given compression mode is supported * - * Return true if the given compression mode is supported by this liblzma - * build. Otherwise false is returned. It is safe to call this with a value - * that isn't listed in lzma_mode enumeration; the return value will be false. + * It is safe to call this with a value that isn't listed in lzma_mode + * enumeration; the return value will be false. * * There is no way to list which modes are available in this particular * liblzma version and build. It would be useless, because a new compression * mode, which the application developer wasn't aware, could require giving * additional options to the encoder that the older modes don't need. + * + * \param mode Mode ID. + * + * \return lzma_bool: + * - true if the compression mode is supported by this liblzma + * build. + * - false otherwise. */ extern LZMA_API(lzma_bool) lzma_mode_is_supported(lzma_mode mode) lzma_nothrow lzma_attr_const; @@ -257,7 +288,7 @@ typedef struct { * \brief Number of literal context bits * * How many of the highest bits of the previous uncompressed - * eight-bit byte (also known as `literal') are taken into + * eight-bit byte (also known as 'literal') are taken into * account when predicting the bits of the next literal. * * E.g. in typical English text, an upper-case letter is @@ -374,6 +405,82 @@ typedef struct { */ uint32_t depth; + /** + * \brief For LZMA_FILTER_LZMA1EXT: Extended flags + * + * This is used only with LZMA_FILTER_LZMA1EXT. + * + * Currently only one flag is supported, LZMA_LZMA1EXT_ALLOW_EOPM: + * + * - Encoder: If the flag is set, then end marker is written just + * like it is with LZMA_FILTER_LZMA1. Without this flag the + * end marker isn't written and the application has to store + * the uncompressed size somewhere outside the compressed stream. + * To decompress streams without the end marker, the application + * has to set the correct uncompressed size in ext_size_low and + * ext_size_high. + * + * - Decoder: If the uncompressed size in ext_size_low and + * ext_size_high is set to the special value UINT64_MAX + * (indicating unknown uncompressed size) then this flag is + * ignored and the end marker must always be present, that is, + * the behavior is identical to LZMA_FILTER_LZMA1. + * + * Otherwise, if this flag isn't set, then the input stream + * must not have the end marker; if the end marker is detected + * then it will result in LZMA_DATA_ERROR. This is useful when + * it is known that the stream must not have the end marker and + * strict validation is wanted. + * + * If this flag is set, then it is autodetected if the end marker + * is present after the specified number of uncompressed bytes + * has been decompressed (ext_size_low and ext_size_high). The + * end marker isn't allowed in any other position. This behavior + * is useful when uncompressed size is known but the end marker + * may or may not be present. This is the case, for example, + * in .7z files (valid .7z files that have the end marker in + * LZMA1 streams are rare but they do exist). + */ + uint32_t ext_flags; +# define LZMA_LZMA1EXT_ALLOW_EOPM UINT32_C(0x01) + + /** + * \brief For LZMA_FILTER_LZMA1EXT: Uncompressed size (low bits) + * + * The 64-bit uncompressed size is needed for decompression with + * LZMA_FILTER_LZMA1EXT. The size is ignored by the encoder. + * + * The special value UINT64_MAX indicates that the uncompressed size + * is unknown and that the end of payload marker (also known as + * end of stream marker) must be present to indicate the end of + * the LZMA1 stream. Any other value indicates the expected + * uncompressed size of the LZMA1 stream. (If LZMA1 was used together + * with filters that change the size of the data then the uncompressed + * size of the LZMA1 stream could be different than the final + * uncompressed size of the filtered stream.) + * + * ext_size_low holds the least significant 32 bits of the + * uncompressed size. The most significant 32 bits must be set + * in ext_size_high. The macro lzma_ext_size_set(opt_lzma, u64size) + * can be used to set these members. + * + * The 64-bit uncompressed size is split into two uint32_t variables + * because there were no reserved uint64_t members and using the + * same options structure for LZMA_FILTER_LZMA1, LZMA_FILTER_LZMA1EXT, + * and LZMA_FILTER_LZMA2 was otherwise more convenient than having + * a new options structure for LZMA_FILTER_LZMA1EXT. (Replacing two + * uint32_t members with one uint64_t changes the ABI on some systems + * as the alignment of this struct can increase from 4 bytes to 8.) + */ + uint32_t ext_size_low; + + /** + * \brief For LZMA_FILTER_LZMA1EXT: Uncompressed size (high bits) + * + * This holds the most significant 32 bits of the uncompressed size. + */ + uint32_t ext_size_high; + /* * Reserved space to allow possible future extensions without * breaking the ABI. You should not touch these, because the names @@ -381,24 +488,56 @@ typedef struct { * with the currently supported options, so it is safe to leave these * uninitialized. */ - uint32_t reserved_int1; - uint32_t reserved_int2; - uint32_t reserved_int3; + + /** \private Reserved member. */ uint32_t reserved_int4; + + /** \private Reserved member. */ uint32_t reserved_int5; + + /** \private Reserved member. */ uint32_t reserved_int6; + + /** \private Reserved member. */ uint32_t reserved_int7; + + /** \private Reserved member. */ uint32_t reserved_int8; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum1; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum2; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum3; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum4; + + /** \private Reserved member. */ void *reserved_ptr1; + + /** \private Reserved member. */ void *reserved_ptr2; } lzma_options_lzma; +/** + * \brief Macro to set the 64-bit uncompressed size in ext_size_* + * + * This might be convenient when decoding using LZMA_FILTER_LZMA1EXT. + * This isn't used with LZMA_FILTER_LZMA1 or LZMA_FILTER_LZMA2. + */ +#define lzma_set_ext_size(opt_lzma2, u64size) \ +do { \ + (opt_lzma2).ext_size_low = (uint32_t)(u64size); \ + (opt_lzma2).ext_size_high = (uint32_t)((uint64_t)(u64size) >> 32); \ +} while (0) + + /** * \brief Set a compression preset to lzma_options_lzma structure * @@ -408,13 +547,22 @@ typedef struct { * The flags are defined in container.h, because the flags are used also * with lzma_easy_encoder(). * - * The preset values are subject to changes between liblzma versions. + * The preset levels are subject to changes between liblzma versions. * * This function is available only if LZMA1 or LZMA2 encoder has been enabled * when building liblzma. * - * \return On success, false is returned. If the preset is not - * supported, true is returned. + * If features (like certain match finders) have been disabled at build time, + * then the function may return success (false) even though the resulting + * LZMA1/LZMA2 options may not be usable for encoder initialization + * (LZMA_OPTIONS_ERROR). + * + * \param[out] options Pointer to LZMA1 or LZMA2 options to be filled + * \param preset Preset level bitwse-ORed with preset flags + * + * \return lzma_bool: + * - true if the preset is not supported (failure). + * - false otherwise (success). */ extern LZMA_API(lzma_bool) lzma_lzma_preset( lzma_options_lzma *options, uint32_t preset) lzma_nothrow; diff --git a/Utilities/cmliblzma/liblzma/api/lzma/stream_flags.h b/Utilities/cmliblzma/liblzma/api/lzma/stream_flags.h index bbdd408263e..a33fe468376 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/stream_flags.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/stream_flags.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/stream_flags.h * \brief .xz Stream Header and Stream Footer encoder and decoder + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -36,7 +34,7 @@ typedef struct { * * To prevent API and ABI breakages if new features are needed in * Stream Header or Stream Footer, a version number is used to - * indicate which fields in this structure are in use. For now, + * indicate which members in this structure are in use. For now, * version must always be zero. With non-zero version, the * lzma_stream_header_encode() and lzma_stream_footer_encode() * will return LZMA_OPTIONS_ERROR. @@ -67,7 +65,15 @@ typedef struct { * Footer have been decoded. */ lzma_vli backward_size; + + /** + * \brief Minimum value for lzma_stream_flags.backward_size + */ # define LZMA_BACKWARD_SIZE_MIN 4 + + /** + * \brief Maximum value for lzma_stream_flags.backward_size + */ # define LZMA_BACKWARD_SIZE_MAX (LZMA_VLI_C(1) << 34) /** @@ -87,19 +93,47 @@ typedef struct { * is just two bytes plus Backward Size of four bytes. But it's * nice to have the proper types when they are needed.) */ + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum1; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum2; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum3; + + /** \private Reserved member. */ lzma_reserved_enum reserved_enum4; + + /** \private Reserved member. */ lzma_bool reserved_bool1; + + /** \private Reserved member. */ lzma_bool reserved_bool2; + + /** \private Reserved member. */ lzma_bool reserved_bool3; + + /** \private Reserved member. */ lzma_bool reserved_bool4; + + /** \private Reserved member. */ lzma_bool reserved_bool5; + + /** \private Reserved member. */ lzma_bool reserved_bool6; + + /** \private Reserved member. */ lzma_bool reserved_bool7; + + /** \private Reserved member. */ lzma_bool reserved_bool8; + + /** \private Reserved member. */ uint32_t reserved_int1; + + /** \private Reserved member. */ uint32_t reserved_int2; } lzma_stream_flags; @@ -111,10 +145,11 @@ typedef struct { * \param options Stream Header options to be encoded. * options->backward_size is ignored and doesn't * need to be initialized. - * \param out Beginning of the output buffer of + * \param[out] out Beginning of the output buffer of * LZMA_STREAM_HEADER_SIZE bytes. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_OPTIONS_ERROR: options->version is not supported by * this liblzma version. * - LZMA_PROG_ERROR: Invalid options. @@ -128,10 +163,11 @@ extern LZMA_API(lzma_ret) lzma_stream_header_encode( * \brief Encode Stream Footer * * \param options Stream Footer options to be encoded. - * \param out Beginning of the output buffer of + * \param[out] out Beginning of the output buffer of * LZMA_STREAM_HEADER_SIZE bytes. * - * \return - LZMA_OK: Encoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Encoding was successful. * - LZMA_OPTIONS_ERROR: options->version is not supported by * this liblzma version. * - LZMA_PROG_ERROR: Invalid options. @@ -144,32 +180,33 @@ extern LZMA_API(lzma_ret) lzma_stream_footer_encode( /** * \brief Decode Stream Header * - * \param options Target for the decoded Stream Header options. - * \param in Beginning of the input buffer of - * LZMA_STREAM_HEADER_SIZE bytes. - * * options->backward_size is always set to LZMA_VLI_UNKNOWN. This is to * help comparing Stream Flags from Stream Header and Stream Footer with * lzma_stream_flags_compare(). * - * \return - LZMA_OK: Decoding was successful. - * - LZMA_FORMAT_ERROR: Magic bytes don't match, thus the given - * buffer cannot be Stream Header. - * - LZMA_DATA_ERROR: CRC32 doesn't match, thus the header - * is corrupt. - * - LZMA_OPTIONS_ERROR: Unsupported options are present - * in the header. - * * \note When decoding .xz files that contain multiple Streams, it may * make sense to print "file format not recognized" only if - * decoding of the Stream Header of the _first_ Stream gives + * decoding of the Stream Header of the \a first Stream gives * LZMA_FORMAT_ERROR. If non-first Stream Header gives * LZMA_FORMAT_ERROR, the message used for LZMA_DATA_ERROR is * probably more appropriate. + * For example, the Stream decoder in liblzma uses + * LZMA_DATA_ERROR if LZMA_FORMAT_ERROR is returned by + * lzma_stream_header_decode() when decoding non-first Stream. + * + * \param[out] options Target for the decoded Stream Header options. + * \param in Beginning of the input buffer of + * LZMA_STREAM_HEADER_SIZE bytes. * - * For example, Stream decoder in liblzma uses LZMA_DATA_ERROR if - * LZMA_FORMAT_ERROR is returned by lzma_stream_header_decode() - * when decoding non-first Stream. + * + * \return Possible lzma_ret values: + * - LZMA_OK: Decoding was successful. + * - LZMA_FORMAT_ERROR: Magic bytes don't match, thus the given + * buffer cannot be Stream Header. + * - LZMA_DATA_ERROR: CRC32 doesn't match, thus the header + * is corrupt. + * - LZMA_OPTIONS_ERROR: Unsupported options are present + * in the header. */ extern LZMA_API(lzma_ret) lzma_stream_header_decode( lzma_stream_flags *options, const uint8_t *in) @@ -179,24 +216,25 @@ extern LZMA_API(lzma_ret) lzma_stream_header_decode( /** * \brief Decode Stream Footer * - * \param options Target for the decoded Stream Header options. + * \note If Stream Header was already decoded successfully, but + * decoding Stream Footer returns LZMA_FORMAT_ERROR, the + * application should probably report some other error message + * than "file format not recognized". The file likely + * is corrupt (possibly truncated). The Stream decoder in liblzma + * uses LZMA_DATA_ERROR in this situation. + * + * \param[out] options Target for the decoded Stream Footer options. * \param in Beginning of the input buffer of * LZMA_STREAM_HEADER_SIZE bytes. * - * \return - LZMA_OK: Decoding was successful. + * \return Possible lzma_ret values: + * - LZMA_OK: Decoding was successful. * - LZMA_FORMAT_ERROR: Magic bytes don't match, thus the given * buffer cannot be Stream Footer. * - LZMA_DATA_ERROR: CRC32 doesn't match, thus the Stream Footer * is corrupt. * - LZMA_OPTIONS_ERROR: Unsupported options are present * in Stream Footer. - * - * \note If Stream Header was already decoded successfully, but - * decoding Stream Footer returns LZMA_FORMAT_ERROR, the - * application should probably report some other error message - * than "file format not recognized", since the file more likely - * is corrupt (possibly truncated). Stream decoder in liblzma - * uses LZMA_DATA_ERROR in this situation. */ extern LZMA_API(lzma_ret) lzma_stream_footer_decode( lzma_stream_flags *options, const uint8_t *in) @@ -209,7 +247,11 @@ extern LZMA_API(lzma_ret) lzma_stream_footer_decode( * backward_size values are compared only if both are not * LZMA_VLI_UNKNOWN. * - * \return - LZMA_OK: Both are equal. If either had backward_size set + * \param a Pointer to lzma_stream_flags structure + * \param b Pointer to lzma_stream_flags structure + * + * \return Possible lzma_ret values: + * - LZMA_OK: Both are equal. If either had backward_size set * to LZMA_VLI_UNKNOWN, backward_size values were not * compared or validated. * - LZMA_DATA_ERROR: The structures differ. diff --git a/Utilities/cmliblzma/liblzma/api/lzma/version.h b/Utilities/cmliblzma/liblzma/api/lzma/version.h index 2bf3eaed24f..e86c0ea4c3d 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/version.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/version.h @@ -1,15 +1,13 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/version.h * \brief Version number + * \note Never include this file directly. Use instead. */ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -17,14 +15,26 @@ #endif -/* - * Version number split into components - */ +/** \brief Major version number of the liblzma release. */ #define LZMA_VERSION_MAJOR 5 -#define LZMA_VERSION_MINOR 2 -#define LZMA_VERSION_PATCH 5 + +/** \brief Minor version number of the liblzma release. */ +#define LZMA_VERSION_MINOR 6 + +/** \brief Patch version number of the liblzma release. */ +#define LZMA_VERSION_PATCH 3 + +/** + * \brief Version stability marker + * + * This will always be one of three values: + * - LZMA_VERSION_STABILITY_ALPHA + * - LZMA_VERSION_STABILITY_BETA + * - LZMA_VERSION_STABILITY_STABLE + */ #define LZMA_VERSION_STABILITY LZMA_VERSION_STABILITY_STABLE +/** \brief Commit version number of the liblzma release */ #ifndef LZMA_VERSION_COMMIT # define LZMA_VERSION_COMMIT "" #endif @@ -95,15 +105,16 @@ LZMA_VERSION_COMMIT) -/* #ifndef is needed for use with windres (MinGW or Cygwin). */ +/* #ifndef is needed for use with windres (MinGW-w64 or Cygwin). */ #ifndef LZMA_H_INTERNAL_RC /** * \brief Run-time version number as an integer * - * Return the value of LZMA_VERSION macro at the compile time of liblzma. - * This allows the application to compare if it was built against the same, + * This allows an application to compare if it was built against the same, * older, or newer version of liblzma that is currently running. + * + * \return The value of LZMA_VERSION macro at the compile time of liblzma */ extern LZMA_API(uint32_t) lzma_version_number(void) lzma_nothrow lzma_attr_const; @@ -112,8 +123,10 @@ extern LZMA_API(uint32_t) lzma_version_number(void) /** * \brief Run-time version as a string * - * This function may be useful if you want to display which version of - * liblzma your application is currently using. + * This function may be useful to display which version of liblzma an + * application is currently using. + * + * \return Run-time version of liblzma */ extern LZMA_API(const char *) lzma_version_string(void) lzma_nothrow lzma_attr_const; diff --git a/Utilities/cmliblzma/liblzma/api/lzma/vli.h b/Utilities/cmliblzma/liblzma/api/lzma/vli.h index 1b7a952a406..6b049021b90 100644 --- a/Utilities/cmliblzma/liblzma/api/lzma/vli.h +++ b/Utilities/cmliblzma/liblzma/api/lzma/vli.h @@ -1,6 +1,9 @@ +/* SPDX-License-Identifier: 0BSD */ + /** * \file lzma/vli.h * \brief Variable-length integer handling + * \note Never include this file directly. Use instead. * * In the .xz format, most integers are encoded in a variable-length * representation, which is sometimes called little endian base-128 encoding. @@ -16,11 +19,6 @@ /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * - * See ../lzma.h for information about liblzma as a whole. */ #ifndef LZMA_H_INTERNAL @@ -69,8 +67,8 @@ typedef uint64_t lzma_vli; * This is useful to test that application has given acceptable values * for example in the uncompressed_size and compressed_size variables. * - * \return True if the integer is representable as VLI or if it - * indicates unknown value. + * \return True if the integer is representable as a VLI or if it + * indicates an unknown value. False otherwise. */ #define lzma_vli_is_valid(vli) \ ((vli) <= LZMA_VLI_MAX || (vli) == LZMA_VLI_UNKNOWN) @@ -86,12 +84,12 @@ typedef uint64_t lzma_vli; * integer has been encoded. * * \param vli Integer to be encoded - * \param vli_pos How many VLI-encoded bytes have already been written + * \param[out] vli_pos How many VLI-encoded bytes have already been written * out. When starting to encode a new integer in * multi-call mode, *vli_pos must be set to zero. * To use single-call encoding, set vli_pos to NULL. - * \param out Beginning of the output buffer - * \param out_pos The next byte will be written to out[*out_pos]. + * \param[out] out Beginning of the output buffer + * \param[out] out_pos The next byte will be written to out[*out_pos]. * \param out_size Size of the out buffer; the first byte into * which no data is written to is out[out_size]. * @@ -121,15 +119,15 @@ extern LZMA_API(lzma_ret) lzma_vli_encode(lzma_vli vli, size_t *vli_pos, * * Like lzma_vli_encode(), this function has single-call and multi-call modes. * - * \param vli Pointer to decoded integer. The decoder will + * \param[out] vli Pointer to decoded integer. The decoder will * initialize it to zero when *vli_pos == 0, so * application isn't required to initialize *vli. - * \param vli_pos How many bytes have already been decoded. When + * \param[out] vli_pos How many bytes have already been decoded. When * starting to decode a new integer in multi-call * mode, *vli_pos must be initialized to zero. To * use single-call decoding, set vli_pos to NULL. * \param in Beginning of the input buffer - * \param in_pos The next byte will be read from in[*in_pos]. + * \param[out] in_pos The next byte will be read from in[*in_pos]. * \param in_size Size of the input buffer; the first byte that * won't be read is in[in_size]. * @@ -159,6 +157,8 @@ extern LZMA_API(lzma_ret) lzma_vli_decode(lzma_vli *vli, size_t *vli_pos, /** * \brief Get the number of bytes required to encode a VLI * + * \param vli Integer whose encoded size is to be determined + * * \return Number of bytes on success (1-9). If vli isn't valid, * zero is returned. */ diff --git a/Utilities/cmliblzma/liblzma/check/check.c b/Utilities/cmliblzma/liblzma/check/check.c index 428ddaeb779..7734ace1856 100644 --- a/Utilities/cmliblzma/liblzma/check/check.c +++ b/Utilities/cmliblzma/liblzma/check/check.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file check.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "check.h" diff --git a/Utilities/cmliblzma/liblzma/check/check.h b/Utilities/cmliblzma/liblzma/check/check.h index 3007d889b0f..f0eb1172d90 100644 --- a/Utilities/cmliblzma/liblzma/check/check.h +++ b/Utilities/cmliblzma/liblzma/check/check.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file check.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_CHECK_H @@ -99,19 +98,22 @@ typedef struct { /// lzma_crc32_table[0] is needed by LZ encoder so we need to keep /// the array two-dimensional. #ifdef HAVE_SMALL +lzma_attr_visibility_hidden extern uint32_t lzma_crc32_table[1][256]; + extern void lzma_crc32_init(void); + #else + +lzma_attr_visibility_hidden extern const uint32_t lzma_crc32_table[8][256]; + +lzma_attr_visibility_hidden extern const uint64_t lzma_crc64_table[4][256]; #endif /// \brief Initialize *check depending on type -/// -/// \return LZMA_OK on success. LZMA_UNSUPPORTED_CHECK if the type is not -/// supported by the current version or build of liblzma. -/// LZMA_PROG_ERROR if type > LZMA_CHECK_ID_MAX. extern void lzma_check_init(lzma_check_state *check, lzma_check type); /// Update the check state diff --git a/Utilities/cmliblzma/liblzma/check/crc32_arm64.h b/Utilities/cmliblzma/liblzma/check/crc32_arm64.h new file mode 100644 index 00000000000..39c1c63ec0e --- /dev/null +++ b/Utilities/cmliblzma/liblzma/check/crc32_arm64.h @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file crc32_arm64.h +/// \brief CRC32 calculation with ARM64 optimization +// +// Authors: Chenxi Mao +// Jia Tan +// Hans Jansen +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef LZMA_CRC32_ARM64_H +#define LZMA_CRC32_ARM64_H + +// MSVC always has the CRC intrinsics available when building for ARM64 +// there is no need to include any header files. +#ifndef _MSC_VER +# include +#endif + +// If both versions are going to be built, we need runtime detection +// to check if the instructions are supported. +#if defined(CRC32_GENERIC) && defined(CRC32_ARCH_OPTIMIZED) +# if defined(HAVE_GETAUXVAL) || defined(HAVE_ELF_AUX_INFO) +# include +# elif defined(_WIN32) +# include +# elif defined(__APPLE__) && defined(HAVE_SYSCTLBYNAME) +# include +# endif +#endif + +// Some EDG-based compilers support ARM64 and define __GNUC__ +// (such as Nvidia's nvcc), but do not support function attributes. +// +// NOTE: Build systems check for this too, keep them in sync with this. +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__) +# define crc_attr_target __attribute__((__target__("+crc"))) +#else +# define crc_attr_target +#endif + + +crc_attr_target +static uint32_t +crc32_arch_optimized(const uint8_t *buf, size_t size, uint32_t crc) +{ + crc = ~crc; + + // Align the input buffer because this was shown to be + // significantly faster than unaligned accesses. + const size_t align_amount = my_min(size, (0U - (uintptr_t)buf) & 7); + + for (const uint8_t *limit = buf + align_amount; buf < limit; ++buf) + crc = __crc32b(crc, *buf); + + size -= align_amount; + + // Process 8 bytes at a time. The end point is determined by + // ignoring the least significant three bits of size to ensure + // we do not process past the bounds of the buffer. This guarantees + // that limit is a multiple of 8 and is strictly less than size. + for (const uint8_t *limit = buf + (size & ~(size_t)7); + buf < limit; buf += 8) + crc = __crc32d(crc, aligned_read64le(buf)); + + // Process the remaining bytes that are not 8 byte aligned. + for (const uint8_t *limit = buf + (size & 7); buf < limit; ++buf) + crc = __crc32b(crc, *buf); + + return ~crc; +} + + +#if defined(CRC32_GENERIC) && defined(CRC32_ARCH_OPTIMIZED) +static inline bool +is_arch_extension_supported(void) +{ +#if defined(HAVE_GETAUXVAL) + return (getauxval(AT_HWCAP) & HWCAP_CRC32) != 0; + +#elif defined(HAVE_ELF_AUX_INFO) + unsigned long feature_flags; + + if (elf_aux_info(AT_HWCAP, &feature_flags, sizeof(feature_flags)) != 0) + return false; + + return (feature_flags & HWCAP_CRC32) != 0; + +#elif defined(_WIN32) + return IsProcessorFeaturePresent( + PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); + +#elif defined(__APPLE__) && defined(HAVE_SYSCTLBYNAME) + int has_crc32 = 0; + size_t size = sizeof(has_crc32); + + // The sysctlbyname() function requires a string identifier for the + // CPU feature it tests. The Apple documentation lists the string + // "hw.optional.armv8_crc32", which can be found here: + // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics#3915619 + if (sysctlbyname("hw.optional.armv8_crc32", &has_crc32, + &size, NULL, 0) != 0) + return false; + + return has_crc32; + +#else + // If a runtime detection method cannot be found, then this must + // be a compile time error. The checks in crc_common.h should ensure + // a runtime detection method is always found if this function is + // built. It would be possible to just return false here, but this + // is inefficient for binary size and runtime since only the generic + // method could ever be used. +# error Runtime detection method unavailable. +#endif +} +#endif + +#endif // LZMA_CRC32_ARM64_H diff --git a/Utilities/cmliblzma/liblzma/check/crc32_fast.c b/Utilities/cmliblzma/liblzma/check/crc32_fast.c index eed7350582e..16dbb746751 100644 --- a/Utilities/cmliblzma/liblzma/check/crc32_fast.c +++ b/Utilities/cmliblzma/liblzma/check/crc32_fast.c @@ -1,35 +1,40 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc32.c /// \brief CRC32 calculation -/// -/// Calculate the CRC32 using the slice-by-eight algorithm. -/// It is explained in this document: -/// http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf -/// The code in this file is not the same as in Intel's paper, but -/// the basic principle is identical. -// -// Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. +// Authors: Lasse Collin +// Ilya Kurdyukov +// Hans Jansen // /////////////////////////////////////////////////////////////////////////////// #include "check.h" -#include "crc_macros.h" +#include "crc_common.h" + +#if defined(CRC_X86_CLMUL) +# define BUILDING_CRC32_CLMUL +# include "crc_x86_clmul.h" +#elif defined(CRC32_ARM64) +# include "crc32_arm64.h" +#endif -// If you make any changes, do some benchmarking! Seemingly unrelated -// changes can very easily ruin the performance (and very probably is -// very compiler dependent). -extern LZMA_API(uint32_t) -lzma_crc32(const uint8_t *buf, size_t size, uint32_t crc) +#ifdef CRC32_GENERIC + +/////////////////// +// Generic CRC32 // +/////////////////// + +static uint32_t +crc32_generic(const uint8_t *buf, size_t size, uint32_t crc) { crc = ~crc; #ifdef WORDS_BIGENDIAN - crc = bswap32(crc); + crc = byteswap32(crc); #endif if (size > 8) { @@ -75,8 +80,125 @@ lzma_crc32(const uint8_t *buf, size_t size, uint32_t crc) crc = lzma_crc32_table[0][*buf++ ^ A(crc)] ^ S8(crc); #ifdef WORDS_BIGENDIAN - crc = bswap32(crc); + crc = byteswap32(crc); #endif return ~crc; } +#endif + + +#if defined(CRC32_GENERIC) && defined(CRC32_ARCH_OPTIMIZED) + +////////////////////////// +// Function dispatching // +////////////////////////// + +// If both the generic and arch-optimized implementations are built, then +// the function to use is selected at runtime because the system running +// the binary might not have the arch-specific instruction set extension(s) +// available. The dispatch methods in order of priority: +// +// 1. Constructor. This method uses __attribute__((__constructor__)) to +// set crc32_func at load time. This avoids extra computation (and any +// unlikely threading bugs) on the first call to lzma_crc32() to decide +// which implementation should be used. +// +// 2. First Call Resolution. On the very first call to lzma_crc32(), the +// call will be directed to crc32_dispatch() instead. This will set the +// appropriate implementation function and will not be called again. +// This method does not use any kind of locking but is safe because if +// multiple threads run the dispatcher simultaneously then they will all +// set crc32_func to the same value. + +typedef uint32_t (*crc32_func_type)( + const uint8_t *buf, size_t size, uint32_t crc); + +// This resolver is shared between all dispatch methods. +static crc32_func_type +crc32_resolve(void) +{ + return is_arch_extension_supported() + ? &crc32_arch_optimized : &crc32_generic; +} + + +#ifdef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR +// Constructor method. +# define CRC32_SET_FUNC_ATTR __attribute__((__constructor__)) +static crc32_func_type crc32_func; +#else +// First Call Resolution method. +# define CRC32_SET_FUNC_ATTR +static uint32_t crc32_dispatch(const uint8_t *buf, size_t size, uint32_t crc); +static crc32_func_type crc32_func = &crc32_dispatch; +#endif + +CRC32_SET_FUNC_ATTR +static void +crc32_set_func(void) +{ + crc32_func = crc32_resolve(); + return; +} + +#ifndef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR +static uint32_t +crc32_dispatch(const uint8_t *buf, size_t size, uint32_t crc) +{ + // When __attribute__((__constructor__)) isn't supported, set the + // function pointer without any locking. If multiple threads run + // the detection code in parallel, they will all end up setting + // the pointer to the same value. This avoids the use of + // mythread_once() on every call to lzma_crc32() but this likely + // isn't strictly standards compliant. Let's change it if it breaks. + crc32_set_func(); + return crc32_func(buf, size, crc); +} + +#endif +#endif + + +extern LZMA_API(uint32_t) +lzma_crc32(const uint8_t *buf, size_t size, uint32_t crc) +{ +#if defined(CRC32_GENERIC) && defined(CRC32_ARCH_OPTIMIZED) + // On x86-64, if CLMUL is available, it is the best for non-tiny + // inputs, being over twice as fast as the generic slice-by-four + // version. However, for size <= 16 it's different. In the extreme + // case of size == 1 the generic version can be five times faster. + // At size >= 8 the CLMUL starts to become reasonable. It + // varies depending on the alignment of buf too. + // + // The above doesn't include the overhead of mythread_once(). + // At least on x86-64 GNU/Linux, pthread_once() is very fast but + // it still makes lzma_crc32(buf, 1, crc) 50-100 % slower. When + // size reaches 12-16 bytes the overhead becomes negligible. + // + // So using the generic version for size <= 16 may give better + // performance with tiny inputs but if such inputs happen rarely + // it's not so obvious because then the lookup table of the + // generic version may not be in the processor cache. +#ifdef CRC_USE_GENERIC_FOR_SMALL_INPUTS + if (size <= 16) + return crc32_generic(buf, size, crc); +#endif + +/* +#ifndef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR + // See crc32_dispatch(). This would be the alternative which uses + // locking and doesn't use crc32_dispatch(). Note that on Windows + // this method needs Vista threads. + mythread_once(crc64_set_func); +#endif +*/ + return crc32_func(buf, size, crc); + +#elif defined(CRC32_ARCH_OPTIMIZED) + return crc32_arch_optimized(buf, size, crc); + +#else + return crc32_generic(buf, size, crc); +#endif +} diff --git a/Utilities/cmliblzma/liblzma/check/crc32_small.c b/Utilities/cmliblzma/liblzma/check/crc32_small.c index 5f8a3286878..6a1bd66185e 100644 --- a/Utilities/cmliblzma/liblzma/check/crc32_small.c +++ b/Utilities/cmliblzma/liblzma/check/crc32_small.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc32_small.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "check.h" @@ -16,6 +15,9 @@ uint32_t lzma_crc32_table[1][256]; +#ifdef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR +__attribute__((__constructor__)) +#endif static void crc32_init(void) { @@ -37,18 +39,22 @@ crc32_init(void) } +#ifndef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR extern void lzma_crc32_init(void) { mythread_once(crc32_init); return; } +#endif extern LZMA_API(uint32_t) lzma_crc32(const uint8_t *buf, size_t size, uint32_t crc) { +#ifndef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR lzma_crc32_init(); +#endif crc = ~crc; diff --git a/Utilities/cmliblzma/liblzma/check/crc32_table.c b/Utilities/cmliblzma/liblzma/check/crc32_table.c index b11762ae0ac..56413eec336 100644 --- a/Utilities/cmliblzma/liblzma/check/crc32_table.c +++ b/Utilities/cmliblzma/liblzma/check/crc32_table.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc32_table.c @@ -5,18 +7,36 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" + +// FIXME: Compared to crc_common.h this has to check for __x86_64__ too +// so that in 32-bit builds crc32_x86.S won't break due to a missing table. +#if defined(HAVE_USABLE_CLMUL) && ((defined(__x86_64__) && defined(__SSSE3__) \ + && defined(__SSE4_1__) && defined(__PCLMUL__)) \ + || (defined(__e2k__) && __iset__ >= 6)) +# define NO_CRC32_TABLE + +#elif defined(HAVE_ARM64_CRC32) \ + && !defined(WORDS_BIGENDIAN) \ + && defined(__ARM_FEATURE_CRC32) +# define NO_CRC32_TABLE +#endif + + +#if !defined(HAVE_ENCODERS) && defined(NO_CRC32_TABLE) +// No table needed. Use a typedef to avoid an empty translation unit. +typedef void lzma_crc32_dummy; + +#else // Having the declaration here silences clang -Wmissing-variable-declarations. extern const uint32_t lzma_crc32_table[8][256]; -#ifdef WORDS_BIGENDIAN -# include "crc32_table_be.h" -#else -# include "crc32_table_le.h" +# ifdef WORDS_BIGENDIAN +# include "crc32_table_be.h" +# else +# include "crc32_table_le.h" +# endif #endif diff --git a/Utilities/cmliblzma/liblzma/check/crc32_table_be.h b/Utilities/cmliblzma/liblzma/check/crc32_table_be.h index c483cb670dc..505c23074c1 100644 --- a/Utilities/cmliblzma/liblzma/check/crc32_table_be.h +++ b/Utilities/cmliblzma/liblzma/check/crc32_table_be.h @@ -1,4 +1,6 @@ -/* This file has been automatically generated by crc32_tablegen.c. */ +// SPDX-License-Identifier: 0BSD + +// This file has been generated by crc32_tablegen.c. const uint32_t lzma_crc32_table[8][256] = { { diff --git a/Utilities/cmliblzma/liblzma/check/crc32_table_le.h b/Utilities/cmliblzma/liblzma/check/crc32_table_le.h index 25f4fc44353..e89c21a7b23 100644 --- a/Utilities/cmliblzma/liblzma/check/crc32_table_le.h +++ b/Utilities/cmliblzma/liblzma/check/crc32_table_le.h @@ -1,4 +1,6 @@ -/* This file has been automatically generated by crc32_tablegen.c. */ +// SPDX-License-Identifier: 0BSD + +// This file has been generated by crc32_tablegen.c. const uint32_t lzma_crc32_table[8][256] = { { diff --git a/Utilities/cmliblzma/liblzma/check/crc32_tablegen.c b/Utilities/cmliblzma/liblzma/check/crc32_tablegen.c index 31a4d2751db..b8cf459f8e7 100644 --- a/Utilities/cmliblzma/liblzma/check/crc32_tablegen.c +++ b/Utilities/cmliblzma/liblzma/check/crc32_tablegen.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc32_tablegen.c @@ -9,9 +11,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include @@ -44,7 +43,7 @@ init_crc32_table(void) #ifdef WORDS_BIGENDIAN for (size_t s = 0; s < 8; ++s) for (size_t b = 0; b < 256; ++b) - crc32_table[s][b] = bswap32(crc32_table[s][b]); + crc32_table[s][b] = byteswap32(crc32_table[s][b]); #endif return; @@ -54,9 +53,11 @@ init_crc32_table(void) static void print_crc32_table(void) { - printf("/* This file has been automatically generated by " - "crc32_tablegen.c. */\n\n" - "const uint32_t lzma_crc32_table[8][256] = {\n\t{"); + // Split the SPDX string so that it won't accidentally match + // when tools search for the string. + printf("// SPDX" "-License-Identifier" ": 0BSD\n\n" + "// This file has been generated by crc32_tablegen.c.\n\n" + "const uint32_t lzma_crc32_table[8][256] = {\n\t{"); for (size_t s = 0; s < 8; ++s) { for (size_t b = 0; b < 256; ++b) { @@ -82,9 +83,11 @@ print_crc32_table(void) static void print_lz_table(void) { - printf("/* This file has been automatically generated by " - "crc32_tablegen.c. */\n\n" - "const uint32_t lzma_lz_hash_table[256] = {"); + // Split the SPDX string so that it won't accidentally match + // when tools search for the string. + printf("// SPDX" "-License-Identifier" ": 0BSD\n\n" + "// This file has been generated by crc32_tablegen.c.\n\n" + "const uint32_t lzma_lz_hash_table[256] = {"); for (size_t b = 0; b < 256; ++b) { if ((b % 4) == 0) diff --git a/Utilities/cmliblzma/liblzma/check/crc32_x86.S b/Utilities/cmliblzma/liblzma/check/crc32_x86.S index 67f68a4145f..ddc3cee6ea5 100644 --- a/Utilities/cmliblzma/liblzma/check/crc32_x86.S +++ b/Utilities/cmliblzma/liblzma/check/crc32_x86.S @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: 0BSD */ + /* * Speed-optimized CRC32 using slicing-by-eight algorithm * @@ -11,9 +13,6 @@ * Authors: Igor Pavlov (original version) * Lasse Collin (AT&T syntax, PIC support, better portability) * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * * This code needs lzma_crc32_table, which can be created using the * following C code: @@ -51,6 +50,14 @@ init_table(void) * extern uint32_t lzma_crc32(const uint8_t *buf, size_t size, uint32_t crc); */ +/* When Intel CET is enabled, include in assembly code to mark + Intel CET support. */ +#ifdef __CET__ +# include +#else +# define _CET_ENDBR +#endif + /* * On some systems, the functions need to be prefixed. The prefix is * usually an underscore. @@ -83,6 +90,7 @@ init_table(void) ALIGN(4, 16) LZMA_CRC32: + _CET_ENDBR /* * Register usage: * %eax crc @@ -195,7 +203,7 @@ LZMA_CRC32: /* * Read the next four bytes, for which the CRC is calculated - * on the next interation of the loop. + * on the next iteration of the loop. */ movl 12(%esi), %ecx @@ -296,9 +304,9 @@ LZMA_CRC32: /* * This is needed to support non-executable stack. It's ugly to - * use __linux__ here, but I don't know a way to detect when + * use __FreeBSD__ and __linux__ here, but I don't know a way to detect when * we are using GNU assembler. */ -#if defined(__ELF__) && defined(__linux__) +#if defined(__ELF__) && (defined(__FreeBSD__) || defined(__linux__)) .section .note.GNU-stack,"",@progbits #endif diff --git a/Utilities/cmliblzma/liblzma/check/crc64_fast.c b/Utilities/cmliblzma/liblzma/check/crc64_fast.c index 8af54cda7b5..0ce83fe4ad3 100644 --- a/Utilities/cmliblzma/liblzma/check/crc64_fast.c +++ b/Utilities/cmliblzma/liblzma/check/crc64_fast.c @@ -1,22 +1,29 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc64.c /// \brief CRC64 calculation -/// -/// Calculate the CRC64 using the slice-by-four algorithm. This is the same -/// idea that is used in crc32_fast.c, but for CRC64 we use only four tables -/// instead of eight to avoid increasing CPU cache usage. -// -// Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. +// Authors: Lasse Collin +// Ilya Kurdyukov // /////////////////////////////////////////////////////////////////////////////// #include "check.h" -#include "crc_macros.h" +#include "crc_common.h" + +#if defined(CRC_X86_CLMUL) +# define BUILDING_CRC64_CLMUL +# include "crc_x86_clmul.h" +#endif + + +#ifdef CRC64_GENERIC +///////////////////////////////// +// Generic slice-by-four CRC64 // +///////////////////////////////// #ifdef WORDS_BIGENDIAN # define A1(x) ((x) >> 56) @@ -26,13 +33,13 @@ // See the comments in crc32_fast.c. They aren't duplicated here. -extern LZMA_API(uint64_t) -lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc) +static uint64_t +crc64_generic(const uint8_t *buf, size_t size, uint64_t crc) { crc = ~crc; #ifdef WORDS_BIGENDIAN - crc = bswap64(crc); + crc = byteswap64(crc); #endif if (size > 4) { @@ -46,10 +53,11 @@ lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc) while (buf < limit) { #ifdef WORDS_BIGENDIAN - const uint32_t tmp = (crc >> 32) + const uint32_t tmp = (uint32_t)(crc >> 32) ^ aligned_read32ne(buf); #else - const uint32_t tmp = crc ^ aligned_read32ne(buf); + const uint32_t tmp = (uint32_t)crc + ^ aligned_read32ne(buf); #endif buf += 4; @@ -65,8 +73,84 @@ lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc) crc = lzma_crc64_table[0][*buf++ ^ A1(crc)] ^ S8(crc); #ifdef WORDS_BIGENDIAN - crc = bswap64(crc); + crc = byteswap64(crc); #endif return ~crc; } +#endif + + +#if defined(CRC64_GENERIC) && defined(CRC64_ARCH_OPTIMIZED) + +////////////////////////// +// Function dispatching // +////////////////////////// + +// If both the generic and arch-optimized implementations are usable, then +// the function that is used is selected at runtime. See crc32_fast.c. + +typedef uint64_t (*crc64_func_type)( + const uint8_t *buf, size_t size, uint64_t crc); + +static crc64_func_type +crc64_resolve(void) +{ + return is_arch_extension_supported() + ? &crc64_arch_optimized : &crc64_generic; +} + +#ifdef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR +# define CRC64_SET_FUNC_ATTR __attribute__((__constructor__)) +static crc64_func_type crc64_func; +#else +# define CRC64_SET_FUNC_ATTR +static uint64_t crc64_dispatch(const uint8_t *buf, size_t size, uint64_t crc); +static crc64_func_type crc64_func = &crc64_dispatch; +#endif + + +CRC64_SET_FUNC_ATTR +static void +crc64_set_func(void) +{ + crc64_func = crc64_resolve(); + return; +} + + +#ifndef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR +static uint64_t +crc64_dispatch(const uint8_t *buf, size_t size, uint64_t crc) +{ + crc64_set_func(); + return crc64_func(buf, size, crc); +} +#endif +#endif + + +extern LZMA_API(uint64_t) +lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc) +{ +#if defined(CRC64_GENERIC) && defined(CRC64_ARCH_OPTIMIZED) + +#ifdef CRC_USE_GENERIC_FOR_SMALL_INPUTS + if (size <= 16) + return crc64_generic(buf, size, crc); +#endif + return crc64_func(buf, size, crc); + +#elif defined(CRC64_ARCH_OPTIMIZED) + // If arch-optimized version is used unconditionally without runtime + // CPU detection then omitting the generic version and its 8 KiB + // lookup table makes the library smaller. + // + // FIXME: Lookup table isn't currently omitted on 32-bit x86, + // see crc64_table.c. + return crc64_arch_optimized(buf, size, crc); + +#else + return crc64_generic(buf, size, crc); +#endif +} diff --git a/Utilities/cmliblzma/liblzma/check/crc64_small.c b/Utilities/cmliblzma/liblzma/check/crc64_small.c index 55d72316bce..ee4ea26f67d 100644 --- a/Utilities/cmliblzma/liblzma/check/crc64_small.c +++ b/Utilities/cmliblzma/liblzma/check/crc64_small.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc64_small.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "check.h" @@ -16,6 +15,9 @@ static uint64_t crc64_table[256]; +#ifdef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR +__attribute__((__constructor__)) +#endif static void crc64_init(void) { @@ -40,7 +42,9 @@ crc64_init(void) extern LZMA_API(uint64_t) lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc) { +#ifndef HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR mythread_once(crc64_init); +#endif crc = ~crc; diff --git a/Utilities/cmliblzma/liblzma/check/crc64_table.c b/Utilities/cmliblzma/liblzma/check/crc64_table.c index 7560eb0a3b1..78e427597ce 100644 --- a/Utilities/cmliblzma/liblzma/check/crc64_table.c +++ b/Utilities/cmliblzma/liblzma/check/crc64_table.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc64_table.c @@ -5,18 +7,31 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" + +// FIXME: Compared to crc_common.h this has to check for __x86_64__ too +// so that in 32-bit builds crc64_x86.S won't break due to a missing table. +#if defined(HAVE_USABLE_CLMUL) && ((defined(__x86_64__) && defined(__SSSE3__) \ + && defined(__SSE4_1__) && defined(__PCLMUL__)) \ + || (defined(__e2k__) && __iset__ >= 6)) +# define NO_CRC64_TABLE +#endif + + +#ifdef NO_CRC64_TABLE +// No table needed. Use a typedef to avoid an empty translation unit. +typedef void lzma_crc64_dummy; + +#else // Having the declaration here silences clang -Wmissing-variable-declarations. extern const uint64_t lzma_crc64_table[4][256]; -#ifdef WORDS_BIGENDIAN -# include "crc64_table_be.h" -#else -# include "crc64_table_le.h" +# if defined(WORDS_BIGENDIAN) +# include "crc64_table_be.h" +# else +# include "crc64_table_le.h" +# endif #endif diff --git a/Utilities/cmliblzma/liblzma/check/crc64_table_be.h b/Utilities/cmliblzma/liblzma/check/crc64_table_be.h index ea074f397a7..db76cc70e07 100644 --- a/Utilities/cmliblzma/liblzma/check/crc64_table_be.h +++ b/Utilities/cmliblzma/liblzma/check/crc64_table_be.h @@ -1,4 +1,6 @@ -/* This file has been automatically generated by crc64_tablegen.c. */ +// SPDX-License-Identifier: 0BSD + +// This file has been generated by crc64_tablegen.c. const uint64_t lzma_crc64_table[4][256] = { { diff --git a/Utilities/cmliblzma/liblzma/check/crc64_table_le.h b/Utilities/cmliblzma/liblzma/check/crc64_table_le.h index 1196b31e132..e40a8c82105 100644 --- a/Utilities/cmliblzma/liblzma/check/crc64_table_le.h +++ b/Utilities/cmliblzma/liblzma/check/crc64_table_le.h @@ -1,4 +1,6 @@ -/* This file has been automatically generated by crc64_tablegen.c. */ +// SPDX-License-Identifier: 0BSD + +// This file has been generated by crc64_tablegen.c. const uint64_t lzma_crc64_table[4][256] = { { diff --git a/Utilities/cmliblzma/liblzma/check/crc64_tablegen.c b/Utilities/cmliblzma/liblzma/check/crc64_tablegen.c index fddaa7ed140..2035127a112 100644 --- a/Utilities/cmliblzma/liblzma/check/crc64_tablegen.c +++ b/Utilities/cmliblzma/liblzma/check/crc64_tablegen.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file crc64_tablegen.c @@ -8,9 +10,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include @@ -43,7 +42,7 @@ init_crc64_table(void) #ifdef WORDS_BIGENDIAN for (size_t s = 0; s < 4; ++s) for (size_t b = 0; b < 256; ++b) - crc64_table[s][b] = bswap64(crc64_table[s][b]); + crc64_table[s][b] = byteswap64(crc64_table[s][b]); #endif return; @@ -53,9 +52,11 @@ init_crc64_table(void) static void print_crc64_table(void) { - printf("/* This file has been automatically generated by " - "crc64_tablegen.c. */\n\n" - "const uint64_t lzma_crc64_table[4][256] = {\n\t{"); + // Split the SPDX string so that it won't accidentally match + // when tools search for the string. + printf("// SPDX" "-License-Identifier" ": 0BSD\n\n" + "// This file has been generated by crc64_tablegen.c.\n\n" + "const uint64_t lzma_crc64_table[4][256] = {\n\t{"); for (size_t s = 0; s < 4; ++s) { for (size_t b = 0; b < 256; ++b) { diff --git a/Utilities/cmliblzma/liblzma/check/crc64_x86.S b/Utilities/cmliblzma/liblzma/check/crc64_x86.S index f5bb84b97e0..47f608181ea 100644 --- a/Utilities/cmliblzma/liblzma/check/crc64_x86.S +++ b/Utilities/cmliblzma/liblzma/check/crc64_x86.S @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: 0BSD */ + /* * Speed-optimized CRC64 using slicing-by-four algorithm * @@ -7,9 +9,6 @@ * Authors: Igor Pavlov (original CRC32 assembly code) * Lasse Collin (CRC64 adaptation of the modified CRC32 code) * - * This file has been put into the public domain. - * You can do whatever you want with this file. - * * This code needs lzma_crc64_table, which can be created using the * following C code: @@ -41,6 +40,14 @@ init_table(void) * extern uint64_t lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc); */ +/* When Intel CET is enabled, include in assembly code to mark + Intel CET support. */ +#ifdef __CET__ +# include +#else +# define _CET_ENDBR +#endif + /* * On some systems, the functions need to be prefixed. The prefix is * usually an underscore. @@ -73,6 +80,7 @@ init_table(void) ALIGN(4, 16) LZMA_CRC64: + _CET_ENDBR /* * Register usage: * %eax crc LSB @@ -279,9 +287,9 @@ LZMA_CRC64: /* * This is needed to support non-executable stack. It's ugly to - * use __linux__ here, but I don't know a way to detect when + * use __FreeBSD__ and __linux__ here, but I don't know a way to detect when * we are using GNU assembler. */ -#if defined(__ELF__) && defined(__linux__) +#if defined(__ELF__) && (defined(__FreeBSD__) || defined(__linux__)) .section .note.GNU-stack,"",@progbits #endif diff --git a/Utilities/cmliblzma/liblzma/check/crc_common.h b/Utilities/cmliblzma/liblzma/check/crc_common.h new file mode 100644 index 00000000000..c15d4c675c8 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/check/crc_common.h @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file crc_common.h +/// \brief Some functions and macros for CRC32 and CRC64 +// +// Authors: Lasse Collin +// Ilya Kurdyukov +// Hans Jansen +// Jia Tan +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef LZMA_CRC_COMMON_H +#define LZMA_CRC_COMMON_H + +#include "common.h" + + +#ifdef WORDS_BIGENDIAN +# define A(x) ((x) >> 24) +# define B(x) (((x) >> 16) & 0xFF) +# define C(x) (((x) >> 8) & 0xFF) +# define D(x) ((x) & 0xFF) + +# define S8(x) ((x) << 8) +# define S32(x) ((x) << 32) + +#else +# define A(x) ((x) & 0xFF) +# define B(x) (((x) >> 8) & 0xFF) +# define C(x) (((x) >> 16) & 0xFF) +# define D(x) ((x) >> 24) + +# define S8(x) ((x) >> 8) +# define S32(x) ((x) >> 32) +#endif + + +// CRC CLMUL code needs this because accessing input buffers that aren't +// aligned to the vector size will inherently trip the address sanitizer. +#if lzma_has_attribute(__no_sanitize_address__) +# define crc_attr_no_sanitize_address \ + __attribute__((__no_sanitize_address__)) +#else +# define crc_attr_no_sanitize_address +#endif + +// Keep this in sync with changes to crc32_arm64.h +#if defined(_WIN32) || defined(HAVE_GETAUXVAL) \ + || defined(HAVE_ELF_AUX_INFO) \ + || (defined(__APPLE__) && defined(HAVE_SYSCTLBYNAME)) +# define ARM64_RUNTIME_DETECTION 1 +#endif + + +#undef CRC32_GENERIC +#undef CRC64_GENERIC + +#undef CRC32_ARCH_OPTIMIZED +#undef CRC64_ARCH_OPTIMIZED + +// The x86 CLMUL is used for both CRC32 and CRC64. +#undef CRC_X86_CLMUL + +#undef CRC32_ARM64 +#undef CRC64_ARM64_CLMUL + +#undef CRC_USE_GENERIC_FOR_SMALL_INPUTS + +// ARM64 CRC32 instruction is only useful for CRC32. Currently, only +// little endian is supported since we were unable to test on a big +// endian machine. +// +// NOTE: Keep this and the next check in sync with the macro +// NO_CRC32_TABLE in crc32_table.c +#if defined(HAVE_ARM64_CRC32) && !defined(WORDS_BIGENDIAN) + // Allow ARM64 CRC32 instruction without a runtime check if + // __ARM_FEATURE_CRC32 is defined. GCC and Clang only define + // this if the proper compiler options are used. +# if defined(__ARM_FEATURE_CRC32) +# define CRC32_ARCH_OPTIMIZED 1 +# define CRC32_ARM64 1 +# elif defined(ARM64_RUNTIME_DETECTION) +# define CRC32_ARCH_OPTIMIZED 1 +# define CRC32_ARM64 1 +# define CRC32_GENERIC 1 +# endif +#endif + +#if defined(HAVE_USABLE_CLMUL) +// If CLMUL is allowed unconditionally in the compiler options then the +// generic version can be omitted. Note that this doesn't work with MSVC +// as I don't know how to detect the features here. +// +// NOTE: Keep this in sync with the NO_CRC32_TABLE macro in crc32_table.c +// and NO_CRC64_TABLE in crc64_table.c. +# if (defined(__SSSE3__) && defined(__SSE4_1__) && defined(__PCLMUL__)) \ + || (defined(__e2k__) && __iset__ >= 6) +# define CRC32_ARCH_OPTIMIZED 1 +# define CRC64_ARCH_OPTIMIZED 1 +# define CRC_X86_CLMUL 1 +# else +# define CRC32_GENERIC 1 +# define CRC64_GENERIC 1 +# define CRC32_ARCH_OPTIMIZED 1 +# define CRC64_ARCH_OPTIMIZED 1 +# define CRC_X86_CLMUL 1 + +/* + // The generic code is much faster with 1-8-byte inputs and + // has similar performance up to 16 bytes at least in + // microbenchmarks (it depends on input buffer alignment + // too). If both versions are built, this #define will use + // the generic version for inputs up to 16 bytes and CLMUL + // for bigger inputs. It saves a little in code size since + // the special cases for 0-16-byte inputs will be omitted + // from the CLMUL code. +# define CRC_USE_GENERIC_FOR_SMALL_INPUTS 1 +*/ +# endif +#endif + +// For CRC32 use the generic slice-by-eight implementation if no optimized +// version is available. +#if !defined(CRC32_ARCH_OPTIMIZED) && !defined(CRC32_GENERIC) +# define CRC32_GENERIC 1 +#endif + +// For CRC64 use the generic slice-by-four implementation if no optimized +// version is available. +#if !defined(CRC64_ARCH_OPTIMIZED) && !defined(CRC64_GENERIC) +# define CRC64_GENERIC 1 +#endif + +#endif diff --git a/Utilities/cmliblzma/liblzma/check/crc_macros.h b/Utilities/cmliblzma/liblzma/check/crc_macros.h deleted file mode 100644 index a7c21b765dc..00000000000 --- a/Utilities/cmliblzma/liblzma/check/crc_macros.h +++ /dev/null @@ -1,30 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file crc_macros.h -/// \brief Some endian-dependent macros for CRC32 and CRC64 -// -// Author: Lasse Collin -// -// This file has been put into the public domain. -// You can do whatever you want with this file. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifdef WORDS_BIGENDIAN -# define A(x) ((x) >> 24) -# define B(x) (((x) >> 16) & 0xFF) -# define C(x) (((x) >> 8) & 0xFF) -# define D(x) ((x) & 0xFF) - -# define S8(x) ((x) << 8) -# define S32(x) ((x) << 32) - -#else -# define A(x) ((x) & 0xFF) -# define B(x) (((x) >> 8) & 0xFF) -# define C(x) (((x) >> 16) & 0xFF) -# define D(x) ((x) >> 24) - -# define S8(x) ((x) >> 8) -# define S32(x) ((x) >> 32) -#endif diff --git a/Utilities/cmliblzma/liblzma/check/crc_x86_clmul.h b/Utilities/cmliblzma/liblzma/check/crc_x86_clmul.h new file mode 100644 index 00000000000..50306e49a72 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/check/crc_x86_clmul.h @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file crc_x86_clmul.h +/// \brief CRC32 and CRC64 implementations using CLMUL instructions. +/// +/// The CRC32 and CRC64 implementations use 32/64-bit x86 SSSE3, SSE4.1, and +/// CLMUL instructions. This is compatible with Elbrus 2000 (E2K) too. +/// +/// They were derived from +/// https://www.researchgate.net/publication/263424619_Fast_CRC_computation +/// and the public domain code from https://github.com/rawrunprotected/crc +/// (URLs were checked on 2023-10-14). +/// +/// While this file has both CRC32 and CRC64 implementations, only one +/// should be built at a time to ensure that crc_simd_body() is inlined +/// even with compilers with which lzma_always_inline expands to plain inline. +/// The version to build is selected by defining BUILDING_CRC32_CLMUL or +/// BUILDING_CRC64_CLMUL before including this file. +/// +/// FIXME: Builds for 32-bit x86 use the assembly .S files by default +/// unless configured with --disable-assembler. Even then the lookup table +/// isn't omitted in crc64_table.c since it doesn't know that assembly +/// code has been disabled. +// +// Authors: Ilya Kurdyukov +// Hans Jansen +// Lasse Collin +// Jia Tan +// +/////////////////////////////////////////////////////////////////////////////// + +// This file must not be included more than once. +#ifdef LZMA_CRC_X86_CLMUL_H +# error crc_x86_clmul.h was included twice. +#endif +#define LZMA_CRC_X86_CLMUL_H + +#include + +#if defined(_MSC_VER) +# include +#elif defined(HAVE_CPUID_H) +# include +#endif + + +// EDG-based compilers (Intel's classic compiler and compiler for E2K) can +// define __GNUC__ but the attribute must not be used with them. +// The new Clang-based ICX needs the attribute. +// +// NOTE: Build systems check for this too, keep them in sync with this. +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__) +# define crc_attr_target \ + __attribute__((__target__("ssse3,sse4.1,pclmul"))) +#else +# define crc_attr_target +#endif + + +#define MASK_L(in, mask, r) r = _mm_shuffle_epi8(in, mask) + +#define MASK_H(in, mask, r) \ + r = _mm_shuffle_epi8(in, _mm_xor_si128(mask, vsign)) + +#define MASK_LH(in, mask, low, high) \ + MASK_L(in, mask, low); \ + MASK_H(in, mask, high) + + +crc_attr_target +crc_attr_no_sanitize_address +static lzma_always_inline void +crc_simd_body(const uint8_t *buf, const size_t size, __m128i *v0, __m128i *v1, + const __m128i vfold16, const __m128i initial_crc) +{ + // Create a vector with 8-bit values 0 to 15. This is used to + // construct control masks for _mm_blendv_epi8 and _mm_shuffle_epi8. + const __m128i vramp = _mm_setr_epi32( + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c); + + // This is used to inverse the control mask of _mm_shuffle_epi8 + // so that bytes that wouldn't be picked with the original mask + // will be picked and vice versa. + const __m128i vsign = _mm_set1_epi8(-0x80); + + // Memory addresses A to D and the distances between them: + // + // A B C D + // [skip_start][size][skip_end] + // [ size2 ] + // + // A and D are 16-byte aligned. B and C are 1-byte aligned. + // skip_start and skip_end are 0-15 bytes. size is at least 1 byte. + // + // A = aligned_buf will initially point to this address. + // B = The address pointed by the caller-supplied buf. + // C = buf + size == aligned_buf + size2 + // D = buf + size + skip_end == aligned_buf + size2 + skip_end + const size_t skip_start = (size_t)((uintptr_t)buf & 15); + const size_t skip_end = (size_t)((0U - (uintptr_t)(buf + size)) & 15); + const __m128i *aligned_buf = (const __m128i *)( + (uintptr_t)buf & ~(uintptr_t)15); + + // If size2 <= 16 then the whole input fits into a single 16-byte + // vector. If size2 > 16 then at least two 16-byte vectors must + // be processed. If size2 > 16 && size <= 16 then there is only + // one 16-byte vector's worth of input but it is unaligned in memory. + // + // NOTE: There is no integer overflow here if the arguments + // are valid. If this overflowed, buf + size would too. + const size_t size2 = skip_start + size; + + // Masks to be used with _mm_blendv_epi8 and _mm_shuffle_epi8: + // The first skip_start or skip_end bytes in the vectors will have + // the high bit (0x80) set. _mm_blendv_epi8 and _mm_shuffle_epi8 + // will produce zeros for these positions. (Bitwise-xor of these + // masks with vsign will produce the opposite behavior.) + const __m128i mask_start + = _mm_sub_epi8(vramp, _mm_set1_epi8((char)skip_start)); + const __m128i mask_end + = _mm_sub_epi8(vramp, _mm_set1_epi8((char)skip_end)); + + // Get the first 1-16 bytes into data0. If loading less than 16 + // bytes, the bytes are loaded to the high bits of the vector and + // the least significant positions are filled with zeros. + const __m128i data0 = _mm_blendv_epi8(_mm_load_si128(aligned_buf), + _mm_setzero_si128(), mask_start); + aligned_buf++; + + __m128i v2, v3; + +#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS + if (size <= 16) { + // Right-shift initial_crc by 1-16 bytes based on "size" + // and store the result in v1 (high bytes) and v0 (low bytes). + // + // NOTE: The highest 8 bytes of initial_crc are zeros so + // v1 will be filled with zeros if size >= 8. The highest + // 8 bytes of v1 will always become zeros. + // + // [ v1 ][ v0 ] + // [ initial_crc ] size == 1 + // [ initial_crc ] size == 2 + // [ initial_crc ] size == 15 + // [ initial_crc ] size == 16 (all in v0) + const __m128i mask_low = _mm_add_epi8( + vramp, _mm_set1_epi8((char)(size - 16))); + MASK_LH(initial_crc, mask_low, *v0, *v1); + + if (size2 <= 16) { + // There are 1-16 bytes of input and it is all + // in data0. Copy the input bytes to v3. If there + // are fewer than 16 bytes, the low bytes in v3 + // will be filled with zeros. That is, the input + // bytes are stored to the same position as + // (part of) initial_crc is in v0. + MASK_L(data0, mask_end, v3); + } else { + // There are 2-16 bytes of input but not all bytes + // are in data0. + const __m128i data1 = _mm_load_si128(aligned_buf); + + // Collect the 2-16 input bytes from data0 and data1 + // to v2 and v3, and bitwise-xor them with the + // low bits of initial_crc in v0. Note that the + // the second xor is below this else-block as it + // is shared with the other branch. + MASK_H(data0, mask_end, v2); + MASK_L(data1, mask_end, v3); + *v0 = _mm_xor_si128(*v0, v2); + } + + *v0 = _mm_xor_si128(*v0, v3); + *v1 = _mm_alignr_epi8(*v1, *v0, 8); + } else +#endif + { + // There is more than 16 bytes of input. + const __m128i data1 = _mm_load_si128(aligned_buf); + const __m128i *end = (const __m128i*)( + (const char *)aligned_buf - 16 + size2); + aligned_buf++; + + MASK_LH(initial_crc, mask_start, *v0, *v1); + *v0 = _mm_xor_si128(*v0, data0); + *v1 = _mm_xor_si128(*v1, data1); + + while (aligned_buf < end) { + *v1 = _mm_xor_si128(*v1, _mm_clmulepi64_si128( + *v0, vfold16, 0x00)); + *v0 = _mm_xor_si128(*v1, _mm_clmulepi64_si128( + *v0, vfold16, 0x11)); + *v1 = _mm_load_si128(aligned_buf++); + } + + if (aligned_buf != end) { + MASK_H(*v0, mask_end, v2); + MASK_L(*v0, mask_end, *v0); + MASK_L(*v1, mask_end, v3); + *v1 = _mm_or_si128(v2, v3); + } + + *v1 = _mm_xor_si128(*v1, _mm_clmulepi64_si128( + *v0, vfold16, 0x00)); + *v0 = _mm_xor_si128(*v1, _mm_clmulepi64_si128( + *v0, vfold16, 0x11)); + *v1 = _mm_srli_si128(*v0, 8); + } +} + + +///////////////////// +// x86 CLMUL CRC32 // +///////////////////// + +/* +// These functions were used to generate the constants +// at the top of crc32_arch_optimized(). +static uint64_t +calc_lo(uint64_t p, uint64_t a, int n) +{ + uint64_t b = 0; int i; + for (i = 0; i < n; i++) { + b = b >> 1 | (a & 1) << (n - 1); + a = (a >> 1) ^ ((0 - (a & 1)) & p); + } + return b; +} + +// same as ~crc(&a, sizeof(a), ~0) +static uint64_t +calc_hi(uint64_t p, uint64_t a, int n) +{ + int i; + for (i = 0; i < n; i++) + a = (a >> 1) ^ ((0 - (a & 1)) & p); + return a; +} +*/ + +#ifdef BUILDING_CRC32_CLMUL + +crc_attr_target +crc_attr_no_sanitize_address +static uint32_t +crc32_arch_optimized(const uint8_t *buf, size_t size, uint32_t crc) +{ +#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS + // The code assumes that there is at least one byte of input. + if (size == 0) + return crc; +#endif + + // uint32_t poly = 0xedb88320; + const int64_t p = 0x1db710640; // p << 1 + const int64_t mu = 0x1f7011641; // calc_lo(p, p, 32) << 1 | 1 + const int64_t k5 = 0x163cd6124; // calc_hi(p, p, 32) << 1 + const int64_t k4 = 0x0ccaa009e; // calc_hi(p, p, 64) << 1 + const int64_t k3 = 0x1751997d0; // calc_hi(p, p, 128) << 1 + + const __m128i vfold4 = _mm_set_epi64x(mu, p); + const __m128i vfold8 = _mm_set_epi64x(0, k5); + const __m128i vfold16 = _mm_set_epi64x(k4, k3); + + __m128i v0, v1, v2; + + crc_simd_body(buf, size, &v0, &v1, vfold16, + _mm_cvtsi32_si128((int32_t)~crc)); + + v1 = _mm_xor_si128( + _mm_clmulepi64_si128(v0, vfold16, 0x10), v1); // xxx0 + v2 = _mm_shuffle_epi32(v1, 0xe7); // 0xx0 + v0 = _mm_slli_epi64(v1, 32); // [0] + v0 = _mm_clmulepi64_si128(v0, vfold8, 0x00); + v0 = _mm_xor_si128(v0, v2); // [1] [2] + v2 = _mm_clmulepi64_si128(v0, vfold4, 0x10); + v2 = _mm_clmulepi64_si128(v2, vfold4, 0x00); + v0 = _mm_xor_si128(v0, v2); // [2] + return ~(uint32_t)_mm_extract_epi32(v0, 2); +} +#endif // BUILDING_CRC32_CLMUL + + +///////////////////// +// x86 CLMUL CRC64 // +///////////////////// + +/* +// These functions were used to generate the constants +// at the top of crc64_arch_optimized(). +static uint64_t +calc_lo(uint64_t poly) +{ + uint64_t a = poly; + uint64_t b = 0; + + for (unsigned i = 0; i < 64; ++i) { + b = (b >> 1) | (a << 63); + a = (a >> 1) ^ (a & 1 ? poly : 0); + } + + return b; +} + +static uint64_t +calc_hi(uint64_t poly, uint64_t a) +{ + for (unsigned i = 0; i < 64; ++i) + a = (a >> 1) ^ (a & 1 ? poly : 0); + + return a; +} +*/ + +#ifdef BUILDING_CRC64_CLMUL + +// MSVC (VS2015 - VS2022) produces bad 32-bit x86 code from the CLMUL CRC +// code when optimizations are enabled (release build). According to the bug +// report, the ebx register is corrupted and the calculated result is wrong. +// Trying to workaround the problem with "__asm mov ebx, ebx" didn't help. +// The following pragma works and performance is still good. x86-64 builds +// and CRC32 CLMUL aren't affected by this problem. The problem does not +// happen in crc_simd_body() either (which is shared with CRC32 CLMUL anyway). +// +// NOTE: Another pragma after crc64_arch_optimized() restores +// the optimizations. If the #if condition here is updated, +// the other one must be updated too. +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \ + && defined(_M_IX86) +# pragma optimize("g", off) +#endif + +crc_attr_target +crc_attr_no_sanitize_address +static uint64_t +crc64_arch_optimized(const uint8_t *buf, size_t size, uint64_t crc) +{ +#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS + // The code assumes that there is at least one byte of input. + if (size == 0) + return crc; +#endif + + // const uint64_t poly = 0xc96c5795d7870f42; // CRC polynomial + const uint64_t p = 0x92d8af2baf0e1e85; // (poly << 1) | 1 + const uint64_t mu = 0x9c3e466c172963d5; // (calc_lo(poly) << 1) | 1 + const uint64_t k2 = 0xdabe95afc7875f40; // calc_hi(poly, 1) + const uint64_t k1 = 0xe05dd497ca393ae4; // calc_hi(poly, k2) + + const __m128i vfold8 = _mm_set_epi64x((int64_t)p, (int64_t)mu); + const __m128i vfold16 = _mm_set_epi64x((int64_t)k2, (int64_t)k1); + + __m128i v0, v1, v2; + +#if defined(__i386__) || defined(_M_IX86) + crc_simd_body(buf, size, &v0, &v1, vfold16, + _mm_set_epi64x(0, (int64_t)~crc)); +#else + // GCC and Clang would produce good code with _mm_set_epi64x + // but MSVC needs _mm_cvtsi64_si128 on x86-64. + crc_simd_body(buf, size, &v0, &v1, vfold16, + _mm_cvtsi64_si128((int64_t)~crc)); +#endif + + v1 = _mm_xor_si128(_mm_clmulepi64_si128(v0, vfold16, 0x10), v1); + v0 = _mm_clmulepi64_si128(v1, vfold8, 0x00); + v2 = _mm_clmulepi64_si128(v0, vfold8, 0x10); + v0 = _mm_xor_si128(_mm_xor_si128(v1, _mm_slli_si128(v0, 8)), v2); + +#if defined(__i386__) || defined(_M_IX86) + return ~(((uint64_t)(uint32_t)_mm_extract_epi32(v0, 3) << 32) | + (uint64_t)(uint32_t)_mm_extract_epi32(v0, 2)); +#else + return ~(uint64_t)_mm_extract_epi64(v0, 1); +#endif +} + +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \ + && defined(_M_IX86) +# pragma optimize("", on) +#endif + +#endif // BUILDING_CRC64_CLMUL + + +// Even though this is an inline function, compile it only when needed. +// This way it won't appear in E2K builds at all. +#if defined(CRC32_GENERIC) || defined(CRC64_GENERIC) +// Inlining this function duplicates the function body in crc32_resolve() and +// crc64_resolve(), but this is acceptable because this is a tiny function. +static inline bool +is_arch_extension_supported(void) +{ + int success = 1; + uint32_t r[4]; // eax, ebx, ecx, edx + +#if defined(_MSC_VER) + // This needs with MSVC. ICC has it as a built-in + // on all platforms. + __cpuid(r, 1); +#elif defined(HAVE_CPUID_H) + // Compared to just using __asm__ to run CPUID, this also checks + // that CPUID is supported and saves and restores ebx as that is + // needed with GCC < 5 with position-independent code (PIC). + success = __get_cpuid(1, &r[0], &r[1], &r[2], &r[3]); +#else + // Just a fallback that shouldn't be needed. + __asm__("cpuid\n\t" + : "=a"(r[0]), "=b"(r[1]), "=c"(r[2]), "=d"(r[3]) + : "a"(1), "c"(0)); +#endif + + // Returns true if these are supported: + // CLMUL (bit 1 in ecx) + // SSSE3 (bit 9 in ecx) + // SSE4.1 (bit 19 in ecx) + const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19); + return success && (r[2] & ecx_mask) == ecx_mask; + + // Alternative methods that weren't used: + // - ICC's _may_i_use_cpu_feature: the other methods should work too. + // - GCC >= 6 / Clang / ICX __builtin_cpu_supports("pclmul") + // + // CPUID decoding is needed with MSVC anyway and older GCC. This keeps + // the feature checks in the build system simpler too. The nice thing + // about __builtin_cpu_supports would be that it generates very short + // code as is it only reads a variable set at startup but a few bytes + // doesn't matter here. +} +#endif diff --git a/Utilities/cmliblzma/liblzma/check/sha256.c b/Utilities/cmliblzma/liblzma/check/sha256.c index 5eede5ce05b..c067a3a693f 100644 --- a/Utilities/cmliblzma/liblzma/check/sha256.c +++ b/Utilities/cmliblzma/liblzma/check/sha256.c @@ -1,24 +1,17 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file sha256.c /// \brief SHA-256 -/// -/// \todo Crypto++ has x86 ASM optimizations. They use SSE so if they -/// are imported to liblzma, SSE instructions need to be used -/// conditionally to keep the code working on older boxes. // -// This code is based on the code found from 7-Zip, which has a modified -// version of the SHA-256 found from Crypto++ . -// The code was modified a little to fit into liblzma. +// The C code is based on the public domain SHA-256 code found from +// Crypto++ Library 5.5.1 released in 2007: https://www.cryptopp.com/ +// A few minor tweaks have been made in liblzma. // -// Authors: Kevin Springle -// Wei Dai -// Igor Pavlov +// Authors: Wei Dai // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "check.h" @@ -28,7 +21,7 @@ static inline uint32_t rotr_32(uint32_t num, unsigned amount) { - return (num >> amount) | (num << (32 - amount)); + return (num >> amount) | (num << (32 - amount)); } #define blk0(i) (W[i] = conv32be(data[i])) diff --git a/Utilities/cmliblzma/liblzma/common/alone_decoder.c b/Utilities/cmliblzma/liblzma/common/alone_decoder.c index 239b230ef19..78af651578f 100644 --- a/Utilities/cmliblzma/liblzma/common/alone_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/alone_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file alone_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "alone_decoder.h" @@ -110,12 +109,24 @@ alone_decode(void *coder_ptr, const lzma_allocator *allocator, // Another hack to ditch false positives: Assume that // if the uncompressed size is known, it must be less // than 256 GiB. + // + // FIXME? Without picky we allow > LZMA_VLI_MAX which doesn't + // really matter in this specific situation (> LZMA_VLI_MAX is + // safe in the LZMA decoder) but it's somewhat weird still. if (coder->picky && coder->uncompressed_size != LZMA_VLI_UNKNOWN && coder->uncompressed_size >= (LZMA_VLI_C(1) << 38)) return LZMA_FORMAT_ERROR; + // Use LZMA_FILTER_LZMA1EXT features to specify the + // uncompressed size and that the end marker is allowed + // even when the uncompressed size is known. Both .lzma + // header and LZMA1EXT use UINT64_MAX indicate that size + // is unknown. + coder->options.ext_flags = LZMA_LZMA1EXT_ALLOW_EOPM; + lzma_set_ext_size(coder->options, coder->uncompressed_size); + // Calculate the memory usage so that it is ready // for SEQ_CODER_INIT. coder->memusage = lzma_lzma_decoder_memusage(&coder->options) @@ -132,6 +143,7 @@ alone_decode(void *coder_ptr, const lzma_allocator *allocator, lzma_filter_info filters[2] = { { + .id = LZMA_FILTER_LZMA1EXT, .init = &lzma_lzma_decoder_init, .options = &coder->options, }, { @@ -139,14 +151,8 @@ alone_decode(void *coder_ptr, const lzma_allocator *allocator, } }; - const lzma_ret ret = lzma_next_filter_init(&coder->next, - allocator, filters); - if (ret != LZMA_OK) - return ret; - - // Use a hack to set the uncompressed size. - lzma_lz_decoder_uncompressed(coder->next.coder, - coder->uncompressed_size); + return_if_error(lzma_next_filter_init(&coder->next, + allocator, filters)); coder->sequence = SEQ_CODE; break; diff --git a/Utilities/cmliblzma/liblzma/common/alone_decoder.h b/Utilities/cmliblzma/liblzma/common/alone_decoder.h index dfa031aa77d..61ee24d97fe 100644 --- a/Utilities/cmliblzma/liblzma/common/alone_decoder.h +++ b/Utilities/cmliblzma/liblzma/common/alone_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file alone_decoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_ALONE_DECODER_H diff --git a/Utilities/cmliblzma/liblzma/common/alone_encoder.c b/Utilities/cmliblzma/liblzma/common/alone_encoder.c index 96c1db70cc6..21b039509ae 100644 --- a/Utilities/cmliblzma/liblzma/common/alone_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/alone_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file alone_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" @@ -75,7 +74,6 @@ alone_encoder_end(void *coder_ptr, const lzma_allocator *allocator) } -// At least for now, this is not used by any internal function. static lzma_ret alone_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const lzma_options_lzma *options) @@ -129,6 +127,7 @@ alone_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, // Initialize the LZMA encoder. const lzma_filter_info filters[2] = { { + .id = LZMA_FILTER_LZMA1, .init = &lzma_lzma_encoder_init, .options = (void *)(options), }, { @@ -140,16 +139,6 @@ alone_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, } -/* -extern lzma_ret -lzma_alone_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, - const lzma_options_alone *options) -{ - lzma_next_coder_init(&alone_encoder_init, next, allocator, options); -} -*/ - - extern LZMA_API(lzma_ret) lzma_alone_encoder(lzma_stream *strm, const lzma_options_lzma *options) { diff --git a/Utilities/cmliblzma/liblzma/common/auto_decoder.c b/Utilities/cmliblzma/liblzma/common/auto_decoder.c index 6895c7ccf7b..fdd520f905c 100644 --- a/Utilities/cmliblzma/liblzma/common/auto_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/auto_decoder.c @@ -1,21 +1,23 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file auto_decoder.c -/// \brief Autodetect between .xz Stream and .lzma (LZMA_Alone) formats +/// \brief Autodetect between .xz, .lzma (LZMA_Alone), and .lz (lzip) // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "stream_decoder.h" #include "alone_decoder.h" +#ifdef HAVE_LZIP_DECODER +# include "lzip_decoder.h" +#endif typedef struct { - /// Stream decoder or LZMA_Alone decoder + /// .xz Stream decoder, LZMA_Alone decoder, or lzip decoder lzma_next_coder next; uint64_t memlimit; @@ -46,14 +48,22 @@ auto_decode(void *coder_ptr, const lzma_allocator *allocator, // SEQ_CODE even if we return some LZMA_*_CHECK. coder->sequence = SEQ_CODE; - // Detect the file format. For now this is simple, since if - // it doesn't start with 0xFD (the first magic byte of the - // new format), it has to be LZMA_Alone, or something that - // we don't support at all. + // Detect the file format. .xz files start with 0xFD which + // cannot be the first byte of .lzma (LZMA_Alone) format. + // The .lz format starts with 0x4C which could be the + // first byte of a .lzma file but luckily it would mean + // lc/lp/pb being 4/3/1 which liblzma doesn't support because + // lc + lp > 4. So using just 0x4C to detect .lz is OK here. if (in[*in_pos] == 0xFD) { return_if_error(lzma_stream_decoder_init( &coder->next, allocator, coder->memlimit, coder->flags)); +#ifdef HAVE_LZIP_DECODER + } else if (in[*in_pos] == 0x4C) { + return_if_error(lzma_lzip_decoder_init( + &coder->next, allocator, + coder->memlimit, coder->flags)); +#endif } else { return_if_error(lzma_alone_decoder_init(&coder->next, allocator, coder->memlimit, true)); @@ -86,8 +96,8 @@ auto_decode(void *coder_ptr, const lzma_allocator *allocator, // Fall through case SEQ_FINISH: - // When LZMA_DECODE_CONCATENATED was used and we were decoding - // LZMA_Alone file, we need to check check that there is no + // When LZMA_CONCATENATED was used and we were decoding + // a LZMA_Alone file, we need to check that there is no // trailing garbage and wait for LZMA_FINISH. if (*in_pos < in_size) return LZMA_DATA_ERROR; diff --git a/Utilities/cmliblzma/liblzma/common/block_buffer_decoder.c b/Utilities/cmliblzma/liblzma/common/block_buffer_decoder.c index b0ded90ddc3..55566cd2f2b 100644 --- a/Utilities/cmliblzma/liblzma/common/block_buffer_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/block_buffer_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_buffer_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "block_decoder.h" diff --git a/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.c b/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.c index 39e263aa476..df3b90e8a18 100644 --- a/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_buffer_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "block_buffer_encoder.h" @@ -277,7 +276,7 @@ block_buffer_encode(lzma_block *block, const lzma_allocator *allocator, if (ret != LZMA_BUF_ERROR) return ret; - // The data was uncompressible (at least with the options + // The data was incompressible (at least with the options // given to us) or the output buffer was too small. Use the // uncompressed chunks of LZMA2 to wrap the data into a valid // Block. If we haven't been given enough output space, even @@ -325,6 +324,24 @@ lzma_block_buffer_encode(lzma_block *block, const lzma_allocator *allocator, } +#ifdef HAVE_SYMBOL_VERSIONS_LINUX +// This is for compatibility with binaries linked against liblzma that +// has been patched with xz-5.2.2-compat-libs.patch from RHEL/CentOS 7. +LZMA_SYMVER_API("lzma_block_uncomp_encode@XZ_5.2.2", + lzma_ret, lzma_block_uncomp_encode_522)(lzma_block *block, + const uint8_t *in, size_t in_size, + uint8_t *out, size_t *out_pos, size_t out_size) + lzma_nothrow lzma_attr_warn_unused_result + __attribute__((__alias__("lzma_block_uncomp_encode_52"))); + +LZMA_SYMVER_API("lzma_block_uncomp_encode@@XZ_5.2", + lzma_ret, lzma_block_uncomp_encode_52)(lzma_block *block, + const uint8_t *in, size_t in_size, + uint8_t *out, size_t *out_pos, size_t out_size) + lzma_nothrow lzma_attr_warn_unused_result; + +#define lzma_block_uncomp_encode lzma_block_uncomp_encode_52 +#endif extern LZMA_API(lzma_ret) lzma_block_uncomp_encode(lzma_block *block, const uint8_t *in, size_t in_size, diff --git a/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.h b/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.h index 653207f7349..5274ac40d3a 100644 --- a/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.h +++ b/Utilities/cmliblzma/liblzma/common/block_buffer_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_buffer_encoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_BLOCK_BUFFER_ENCODER_H diff --git a/Utilities/cmliblzma/liblzma/common/block_decoder.c b/Utilities/cmliblzma/liblzma/common/block_decoder.c index 075bd279ff6..2e369d316bd 100644 --- a/Utilities/cmliblzma/liblzma/common/block_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/block_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "block_decoder.h" @@ -40,6 +39,9 @@ typedef struct { /// is unknown. lzma_vli compressed_limit; + /// Maximum allowed Uncompressed Size. + lzma_vli uncompressed_limit; + /// Position when reading the Check field size_t check_pos; @@ -51,21 +53,6 @@ typedef struct { } lzma_block_coder; -static inline bool -update_size(lzma_vli *size, lzma_vli add, lzma_vli limit) -{ - if (limit > LZMA_VLI_MAX) - limit = LZMA_VLI_MAX; - - if (limit < *size || limit - *size < add) - return true; - - *size += add; - - return false; -} - - static inline bool is_size_valid(lzma_vli size, lzma_vli reference) { @@ -86,23 +73,59 @@ block_decode(void *coder_ptr, const lzma_allocator *allocator, const size_t in_start = *in_pos; const size_t out_start = *out_pos; + // Limit the amount of input and output space that we give + // to the raw decoder based on the information we have + // (or don't have) from Block Header. + const size_t in_stop = *in_pos + (size_t)my_min( + in_size - *in_pos, + coder->compressed_limit - coder->compressed_size); + const size_t out_stop = *out_pos + (size_t)my_min( + out_size - *out_pos, + coder->uncompressed_limit - coder->uncompressed_size); + const lzma_ret ret = coder->next.code(coder->next.coder, - allocator, in, in_pos, in_size, - out, out_pos, out_size, action); + allocator, in, in_pos, in_stop, + out, out_pos, out_stop, action); const size_t in_used = *in_pos - in_start; const size_t out_used = *out_pos - out_start; - // NOTE: We compare to compressed_limit here, which prevents - // the total size of the Block growing past LZMA_VLI_MAX. - if (update_size(&coder->compressed_size, in_used, - coder->compressed_limit) - || update_size(&coder->uncompressed_size, - out_used, - coder->block->uncompressed_size)) - return LZMA_DATA_ERROR; + // Because we have limited the input and output sizes, + // we know that these cannot grow too big or overflow. + coder->compressed_size += in_used; + coder->uncompressed_size += out_used; + + if (ret == LZMA_OK) { + const bool comp_done = coder->compressed_size + == coder->block->compressed_size; + const bool uncomp_done = coder->uncompressed_size + == coder->block->uncompressed_size; + + // If both input and output amounts match the sizes + // in Block Header but we still got LZMA_OK instead + // of LZMA_STREAM_END, the file is broken. + if (comp_done && uncomp_done) + return LZMA_DATA_ERROR; - if (!coder->ignore_check) + // If the decoder has consumed all the input that it + // needs but it still couldn't fill the output buffer + // or return LZMA_STREAM_END, the file is broken. + if (comp_done && *out_pos < out_size) + return LZMA_DATA_ERROR; + + // If the decoder has produced all the output but + // it still didn't return LZMA_STREAM_END or consume + // more input (for example, detecting an end of + // payload marker may need more input but produce + // no output) the file is broken. + if (uncomp_done && *in_pos < in_size) + return LZMA_DATA_ERROR; + } + + // Don't waste time updating the integrity check if it will be + // ignored. Also skip it if no new output was produced. This + // avoids null pointer + 0 (undefined behavior) when out == 0. + if (!coder->ignore_check && out_used > 0) lzma_check_update(&coder->check, coder->block->check, out + out_start, out_used); @@ -230,6 +253,14 @@ lzma_block_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, - lzma_check_size(block->check) : block->compressed_size; + // With Uncompressed Size this is simpler. If Block Header lacks + // the size info, then LZMA_VLI_MAX is the maximum possible + // Uncompressed Size. + coder->uncompressed_limit + = block->uncompressed_size == LZMA_VLI_UNKNOWN + ? LZMA_VLI_MAX + : block->uncompressed_size; + // Initialize the check. It's caller's problem if the Check ID is not // supported, and the Block decoder cannot verify the Check field. // Caller can test lzma_check_is_supported(block->check). diff --git a/Utilities/cmliblzma/liblzma/common/block_decoder.h b/Utilities/cmliblzma/liblzma/common/block_decoder.h index 718c5ced886..2cbf9ba6db8 100644 --- a/Utilities/cmliblzma/liblzma/common/block_decoder.h +++ b/Utilities/cmliblzma/liblzma/common/block_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_decoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_BLOCK_DECODER_H diff --git a/Utilities/cmliblzma/liblzma/common/block_encoder.c b/Utilities/cmliblzma/liblzma/common/block_encoder.c index 168846ad689..ce8c1de6944 100644 --- a/Utilities/cmliblzma/liblzma/common/block_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/block_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "block_encoder.h" @@ -77,8 +76,11 @@ block_encode(void *coder_ptr, const lzma_allocator *allocator, // checked it at the beginning of this function. coder->uncompressed_size += in_used; - lzma_check_update(&coder->check, coder->block->check, - in + in_start, in_used); + // Call lzma_check_update() only if input was consumed. This + // avoids null pointer + 0 (undefined behavior) when in == 0. + if (in_used > 0) + lzma_check_update(&coder->check, coder->block->check, + in + in_start, in_used); if (ret != LZMA_STREAM_END || action == LZMA_SYNC_FLUSH) return ret; @@ -217,6 +219,7 @@ lzma_block_encoder(lzma_stream *strm, lzma_block *block) lzma_next_strm_init(lzma_block_encoder_init, strm, block); strm->internal->supported_actions[LZMA_RUN] = true; + strm->internal->supported_actions[LZMA_SYNC_FLUSH] = true; strm->internal->supported_actions[LZMA_FINISH] = true; return LZMA_OK; diff --git a/Utilities/cmliblzma/liblzma/common/block_encoder.h b/Utilities/cmliblzma/liblzma/common/block_encoder.h index bd97c186e50..b7dfe9a0841 100644 --- a/Utilities/cmliblzma/liblzma/common/block_encoder.h +++ b/Utilities/cmliblzma/liblzma/common/block_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_encoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_BLOCK_ENCODER_H diff --git a/Utilities/cmliblzma/liblzma/common/block_header_decoder.c b/Utilities/cmliblzma/liblzma/common/block_header_decoder.c index 2e1135dd639..f0b2fbe54d8 100644 --- a/Utilities/cmliblzma/liblzma/common/block_header_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/block_header_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_header_decoder.c @@ -5,31 +7,12 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" #include "check.h" -static void -free_properties(lzma_block *block, const lzma_allocator *allocator) -{ - // Free allocated filter options. The last array member is not - // touched after the initialization in the beginning of - // lzma_block_header_decode(), so we don't need to touch that here. - for (size_t i = 0; i < LZMA_FILTERS_MAX; ++i) { - lzma_free(block->filters[i].options, allocator); - block->filters[i].id = LZMA_VLI_UNKNOWN; - block->filters[i].options = NULL; - } - - return; -} - - extern LZMA_API(lzma_ret) lzma_block_header_decode(lzma_block *block, const lzma_allocator *allocator, const uint8_t *in) @@ -39,6 +22,10 @@ lzma_block_header_decode(lzma_block *block, // are invalid or over 63 bits, or if the header is too small // to contain the claimed information. + // Catch unexpected NULL pointers. + if (block == NULL || block->filters == NULL || in == NULL) + return LZMA_PROG_ERROR; + // Initialize the filter options array. This way the caller can // safely free() the options even if an error occurs in this function. for (size_t i = 0; i <= LZMA_FILTERS_MAX; ++i) { @@ -67,8 +54,11 @@ lzma_block_header_decode(lzma_block *block, const size_t in_size = block->header_size - 4; // Verify CRC32 - if (lzma_crc32(in, in_size, 0) != read32le(in + in_size)) + if (lzma_crc32(in, in_size, 0) != read32le(in + in_size)) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return LZMA_DATA_ERROR; +#endif + } // Check for unsupported flags. if (in[1] & 0x3C) @@ -104,7 +94,7 @@ lzma_block_header_decode(lzma_block *block, &block->filters[i], allocator, in, &in_pos, in_size); if (ret != LZMA_OK) { - free_properties(block, allocator); + lzma_filters_free(block->filters, allocator); return ret; } } @@ -112,7 +102,7 @@ lzma_block_header_decode(lzma_block *block, // Padding while (in_pos < in_size) { if (in[in_pos++] != 0x00) { - free_properties(block, allocator); + lzma_filters_free(block->filters, allocator); // Possibly some new field present so use // LZMA_OPTIONS_ERROR instead of LZMA_DATA_ERROR. diff --git a/Utilities/cmliblzma/liblzma/common/block_header_encoder.c b/Utilities/cmliblzma/liblzma/common/block_header_encoder.c index 160425d27a3..45e57a26aba 100644 --- a/Utilities/cmliblzma/liblzma/common/block_header_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/block_header_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_header_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" diff --git a/Utilities/cmliblzma/liblzma/common/block_util.c b/Utilities/cmliblzma/liblzma/common/block_util.c index acb311142c2..191f6d444aa 100644 --- a/Utilities/cmliblzma/liblzma/common/block_util.c +++ b/Utilities/cmliblzma/liblzma/common/block_util.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file block_util.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" diff --git a/Utilities/cmliblzma/liblzma/common/common.c b/Utilities/cmliblzma/liblzma/common/common.c index cf714e5e43b..cc0e06a51be 100644 --- a/Utilities/cmliblzma/liblzma/common/common.c +++ b/Utilities/cmliblzma/liblzma/common/common.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file common.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" @@ -35,7 +34,8 @@ lzma_version_string(void) // Memory allocation // /////////////////////// -extern void * lzma_attribute((__malloc__)) lzma_attr_alloc_size(1) +lzma_attr_alloc_size(1) +extern void * lzma_alloc(size_t size, const lzma_allocator *allocator) { // Some malloc() variants return NULL if called with size == 0. @@ -53,7 +53,8 @@ lzma_alloc(size_t size, const lzma_allocator *allocator) } -extern void * lzma_attribute((__malloc__)) lzma_attr_alloc_size(1) +lzma_attr_alloc_size(1) +extern void * lzma_alloc_zero(size_t size, const lzma_allocator *allocator) { // Some calloc() variants return NULL if called with size == 0. @@ -211,7 +212,6 @@ lzma_code(lzma_stream *strm, lzma_action action) || strm->reserved_ptr2 != NULL || strm->reserved_ptr3 != NULL || strm->reserved_ptr4 != NULL - || strm->reserved_int1 != 0 || strm->reserved_int2 != 0 || strm->reserved_int3 != 0 || strm->reserved_int4 != 0 @@ -289,19 +289,25 @@ lzma_code(lzma_stream *strm, lzma_action action) strm->next_in, &in_pos, strm->avail_in, strm->next_out, &out_pos, strm->avail_out, action); - strm->next_in += in_pos; - strm->avail_in -= in_pos; - strm->total_in += in_pos; + // Updating next_in and next_out has to be skipped when they are NULL + // to avoid null pointer + 0 (undefined behavior). Do this by checking + // in_pos > 0 and out_pos > 0 because this way NULL + non-zero (a bug) + // will get caught one way or other. + if (in_pos > 0) { + strm->next_in += in_pos; + strm->avail_in -= in_pos; + strm->total_in += in_pos; + } - strm->next_out += out_pos; - strm->avail_out -= out_pos; - strm->total_out += out_pos; + if (out_pos > 0) { + strm->next_out += out_pos; + strm->avail_out -= out_pos; + strm->total_out += out_pos; + } strm->internal->avail_in = strm->avail_in; - // Cast is needed to silence a warning about LZMA_TIMED_OUT, which - // isn't part of lzma_ret enumeration. - switch ((unsigned int)(ret)) { + switch (ret) { case LZMA_OK: // Don't return LZMA_BUF_ERROR when it happens the first time. // This is to avoid returning LZMA_BUF_ERROR when avail_out @@ -322,6 +328,17 @@ lzma_code(lzma_stream *strm, lzma_action action) ret = LZMA_OK; break; + case LZMA_SEEK_NEEDED: + strm->internal->allow_buf_error = false; + + // If LZMA_FINISH was used, reset it back to the + // LZMA_RUN-based state so that new input can be supplied + // by the application. + if (strm->internal->sequence == ISEQ_FINISH) + strm->internal->sequence = ISEQ_RUN; + + break; + case LZMA_STREAM_END: if (strm->internal->sequence == ISEQ_SYNC_FLUSH || strm->internal->sequence == ISEQ_FULL_FLUSH @@ -366,6 +383,20 @@ lzma_end(lzma_stream *strm) } +#ifdef HAVE_SYMBOL_VERSIONS_LINUX +// This is for compatibility with binaries linked against liblzma that +// has been patched with xz-5.2.2-compat-libs.patch from RHEL/CentOS 7. +LZMA_SYMVER_API("lzma_get_progress@XZ_5.2.2", + void, lzma_get_progress_522)(lzma_stream *strm, + uint64_t *progress_in, uint64_t *progress_out) lzma_nothrow + __attribute__((__alias__("lzma_get_progress_52"))); + +LZMA_SYMVER_API("lzma_get_progress@@XZ_5.2", + void, lzma_get_progress_52)(lzma_stream *strm, + uint64_t *progress_in, uint64_t *progress_out) lzma_nothrow; + +#define lzma_get_progress lzma_get_progress_52 +#endif extern LZMA_API(void) lzma_get_progress(lzma_stream *strm, uint64_t *progress_in, uint64_t *progress_out) diff --git a/Utilities/cmliblzma/liblzma/common/common.h b/Utilities/cmliblzma/liblzma/common/common.h index b3d3b7a059b..20af32f6d6c 100644 --- a/Utilities/cmliblzma/liblzma/common/common.h +++ b/Utilities/cmliblzma/liblzma/common/common.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file common.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_COMMON_H @@ -17,23 +16,104 @@ #include "mythread.h" #include "tuklib_integer.h" +// LZMA_API_EXPORT is used to mark the exported API functions. +// It's used to define the LZMA_API macro. +// +// lzma_attr_visibility_hidden is used for marking *declarations* of extern +// variables that are internal to liblzma (-fvisibility=hidden alone is +// enough to hide the *definitions*). Such markings allow slightly more +// efficient code to accesses those variables in ELF shared libraries. #if defined(_WIN32) || defined(__CYGWIN__) # ifdef DLL_EXPORT # define LZMA_API_EXPORT __declspec(dllexport) # else # define LZMA_API_EXPORT # endif +# define lzma_attr_visibility_hidden // Don't use ifdef or defined() below. #elif HAVE_VISIBILITY # define LZMA_API_EXPORT __attribute__((__visibility__("default"))) +# define lzma_attr_visibility_hidden \ + __attribute__((__visibility__("hidden"))) #else # define LZMA_API_EXPORT +# define lzma_attr_visibility_hidden #endif #define LZMA_API(type) LZMA_API_EXPORT type LZMA_API_CALL #include "lzma.h" +// This is for detecting modern GCC and Clang attributes +// like __symver__ in GCC >= 10. +#ifdef __has_attribute +# define lzma_has_attribute(attr) __has_attribute(attr) +#else +# define lzma_has_attribute(attr) 0 +#endif + +// The extra symbol versioning in the C files may only be used when +// building a shared library. If HAVE_SYMBOL_VERSIONS_LINUX is defined +// to 2 then symbol versioning is done only if also PIC is defined. +// By default Libtool defines PIC when building a shared library and +// doesn't define it when building a static library but it can be +// overridden with --with-pic and --without-pic. configure let's rely +// on PIC if neither --with-pic or --without-pic was used. +#if defined(HAVE_SYMBOL_VERSIONS_LINUX) \ + && (HAVE_SYMBOL_VERSIONS_LINUX == 2 && !defined(PIC)) +# undef HAVE_SYMBOL_VERSIONS_LINUX +#endif + +#ifdef HAVE_SYMBOL_VERSIONS_LINUX +// To keep link-time optimization (LTO, -flto) working with GCC, +// the __symver__ attribute must be used instead of __asm__(".symver ..."). +// Otherwise the symbol versions may be lost, resulting in broken liblzma +// that has wrong default versions in the exported symbol list! +// The attribute was added in GCC 10; LTO with older GCC is not supported. +// +// To keep -Wmissing-prototypes happy, use LZMA_SYMVER_API only with function +// declarations (including those with __alias__ attribute) and LZMA_API with +// the function definitions. This means a little bit of silly copy-and-paste +// between declarations and definitions though. +// +// As of GCC 12.2, the __symver__ attribute supports only @ and @@ but the +// very convenient @@@ isn't supported (it's supported by GNU assembler +// since 2000). When using @@ instead of @@@, the internal name must not be +// the same as the external name to avoid problems in some situations. This +// is why "#define foo_52 foo" is needed for the default symbol versions. +// +// __has_attribute is supported before GCC 10 and it is supported in Clang 14 +// too (which doesn't support __symver__) so use it to detect if __symver__ +// is available. This should be far more reliable than looking at compiler +// version macros as nowadays especially __GNUC__ is defined by many compilers. +# if lzma_has_attribute(__symver__) +# define LZMA_SYMVER_API(extnamever, type, intname) \ + extern __attribute__((__symver__(extnamever))) \ + LZMA_API(type) intname +# else +# define LZMA_SYMVER_API(extnamever, type, intname) \ + __asm__(".symver " #intname "," extnamever); \ + extern LZMA_API(type) intname +# endif +#endif + +// MSVC has __forceinline which shouldn't be combined with the inline keyword +// (results in a warning). +// +// GCC 3.1 added always_inline attribute so we don't need to check +// for __GNUC__ version. Similarly, all relevant Clang versions +// support it (at least Clang 3.0.0 does already). +// Other compilers might support too which also support __has_attribute +// (Solaris Studio) so do that check too. +#if defined(_MSC_VER) +# define lzma_always_inline __forceinline +#elif defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) \ + || lzma_has_attribute(__always_inline__) +# define lzma_always_inline inline __attribute__((__always_inline__)) +#else +# define lzma_always_inline inline +#endif + // These allow helping the compiler in some often-executed branches, whose // result is almost always the same. #ifdef __GNUC__ @@ -67,14 +147,15 @@ #define LZMA_FILTER_RESERVED_START (LZMA_VLI_C(1) << 62) -/// Supported flags that can be passed to lzma_stream_decoder() -/// or lzma_auto_decoder(). +/// Supported flags that can be passed to lzma_stream_decoder(), +/// lzma_auto_decoder(), or lzma_stream_decoder_mt(). #define LZMA_SUPPORTED_FLAGS \ ( LZMA_TELL_NO_CHECK \ | LZMA_TELL_UNSUPPORTED_CHECK \ | LZMA_TELL_ANY_CHECK \ | LZMA_IGNORE_CHECK \ - | LZMA_CONCATENATED ) + | LZMA_CONCATENATED \ + | LZMA_FAIL_FAST ) /// Largest valid lzma_action value as unsigned integer. @@ -83,9 +164,12 @@ /// Special return value (lzma_ret) to indicate that a timeout was reached /// and lzma_code() must not return LZMA_BUF_ERROR. This is converted to -/// LZMA_OK in lzma_code(). This is not in the lzma_ret enumeration because -/// there's no need to have it in the public API. -#define LZMA_TIMED_OUT 32 +/// LZMA_OK in lzma_code(). +#define LZMA_TIMED_OUT LZMA_RET_INTERNAL1 + +/// Special return value (lzma_ret) for use in stream_decoder_mt.c to +/// indicate Index was detected instead of a Block Header. +#define LZMA_INDEX_DETECTED LZMA_RET_INTERNAL2 typedef struct lzma_next_coder_s lzma_next_coder; @@ -118,8 +202,11 @@ typedef void (*lzma_end_function)( /// an array of lzma_filter_info structures. This array is used with /// lzma_next_filter_init to initialize the filter chain. struct lzma_filter_info_s { - /// Filter ID. This is used only by the encoder - /// with lzma_filters_update(). + /// Filter ID. This can be used to share the same initiazation + /// function *and* data structures with different Filter IDs + /// (LZMA_FILTER_LZMA1EXT does it), and also by the encoder + /// with lzma_filters_update() if filter chain is updated + /// in the middle of a raw stream or Block (LZMA_SYNC_FLUSH). lzma_vli id; /// Pointer to function used to initialize the filter. @@ -173,6 +260,16 @@ struct lzma_next_coder_s { lzma_ret (*update)(void *coder, const lzma_allocator *allocator, const lzma_filter *filters, const lzma_filter *reversed_filters); + + /// Set how many bytes of output this coder may produce at maximum. + /// On success LZMA_OK must be returned. + /// If the filter chain as a whole cannot support this feature, + /// this must return LZMA_OPTIONS_ERROR. + /// If no input has been given to the coder and the requested limit + /// is too small, this must return LZMA_BUF_ERROR. If input has been + /// seen, LZMA_OK is allowed too. + lzma_ret (*set_out_limit)(void *coder, uint64_t *uncomp_size, + uint64_t out_limit); }; @@ -188,6 +285,7 @@ struct lzma_next_coder_s { .get_check = NULL, \ .memconfig = NULL, \ .update = NULL, \ + .set_out_limit = NULL, \ } @@ -226,14 +324,14 @@ struct lzma_internal_s { /// Allocates memory -extern void *lzma_alloc(size_t size, const lzma_allocator *allocator) - lzma_attribute((__malloc__)) lzma_attr_alloc_size(1); +lzma_attr_alloc_size(1) +extern void *lzma_alloc(size_t size, const lzma_allocator *allocator); /// Allocates memory and zeroes it (like calloc()). This can be faster /// than lzma_alloc() + memzero() while being backward compatible with /// custom allocators. -extern void * lzma_attribute((__malloc__)) lzma_attr_alloc_size(1) - lzma_alloc_zero(size_t size, const lzma_allocator *allocator); +lzma_attr_alloc_size(1) +extern void *lzma_alloc_zero(size_t size, const lzma_allocator *allocator); /// Frees memory extern void lzma_free(void *ptr, const lzma_allocator *allocator); diff --git a/Utilities/cmliblzma/liblzma/common/easy_buffer_encoder.c b/Utilities/cmliblzma/liblzma/common/easy_buffer_encoder.c index 48eb56f5cc9..da610cea6bf 100644 --- a/Utilities/cmliblzma/liblzma/common/easy_buffer_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/easy_buffer_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file easy_buffer_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "easy_preset.h" diff --git a/Utilities/cmliblzma/liblzma/common/easy_decoder_memusage.c b/Utilities/cmliblzma/liblzma/common/easy_decoder_memusage.c index 20bcd5b7175..0c76f10033b 100644 --- a/Utilities/cmliblzma/liblzma/common/easy_decoder_memusage.c +++ b/Utilities/cmliblzma/liblzma/common/easy_decoder_memusage.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file easy_decoder_memusage.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "easy_preset.h" diff --git a/Utilities/cmliblzma/liblzma/common/easy_encoder.c b/Utilities/cmliblzma/liblzma/common/easy_encoder.c index 5cb492dd068..8dfe29610f7 100644 --- a/Utilities/cmliblzma/liblzma/common/easy_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/easy_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file easy_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "easy_preset.h" diff --git a/Utilities/cmliblzma/liblzma/common/easy_encoder_memusage.c b/Utilities/cmliblzma/liblzma/common/easy_encoder_memusage.c index e9105758423..1184ac66542 100644 --- a/Utilities/cmliblzma/liblzma/common/easy_encoder_memusage.c +++ b/Utilities/cmliblzma/liblzma/common/easy_encoder_memusage.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file easy_encoder_memusage.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "easy_preset.h" diff --git a/Utilities/cmliblzma/liblzma/common/easy_preset.c b/Utilities/cmliblzma/liblzma/common/easy_preset.c index 2f9859860ad..7908a2bb73c 100644 --- a/Utilities/cmliblzma/liblzma/common/easy_preset.c +++ b/Utilities/cmliblzma/liblzma/common/easy_preset.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file easy_preset.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "easy_preset.h" diff --git a/Utilities/cmliblzma/liblzma/common/easy_preset.h b/Utilities/cmliblzma/liblzma/common/easy_preset.h index 382ade89406..4ef6d044ad5 100644 --- a/Utilities/cmliblzma/liblzma/common/easy_preset.h +++ b/Utilities/cmliblzma/liblzma/common/easy_preset.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file easy_preset.h @@ -5,11 +7,11 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// +#ifndef LZMA_EASY_PRESET_H +#define LZMA_EASY_PRESET_H + #include "common.h" @@ -30,3 +32,5 @@ typedef struct { /// Set *easy to the settings given by the preset. Returns true on error, /// false on success. extern bool lzma_easy_preset(lzma_options_easy *easy, uint32_t preset); + +#endif diff --git a/Utilities/cmliblzma/liblzma/common/file_info.c b/Utilities/cmliblzma/liblzma/common/file_info.c new file mode 100644 index 00000000000..7c85084a706 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/file_info.c @@ -0,0 +1,854 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file file_info.c +/// \brief Decode .xz file information into a lzma_index structure +// +// Author: Lasse Collin +// +/////////////////////////////////////////////////////////////////////////////// + +#include "index_decoder.h" + + +typedef struct { + enum { + SEQ_MAGIC_BYTES, + SEQ_PADDING_SEEK, + SEQ_PADDING_DECODE, + SEQ_FOOTER, + SEQ_INDEX_INIT, + SEQ_INDEX_DECODE, + SEQ_HEADER_DECODE, + SEQ_HEADER_COMPARE, + } sequence; + + /// Absolute position of in[*in_pos] in the file. All code that + /// modifies *in_pos also updates this. seek_to_pos() needs this + /// to determine if we need to request the application to seek for + /// us or if we can do the seeking internally by adjusting *in_pos. + uint64_t file_cur_pos; + + /// This refers to absolute positions of interesting parts of the + /// input file. Sometimes it points to the *beginning* of a specific + /// field and sometimes to the *end* of a field. The current target + /// position at each moment is explained in the comments. + uint64_t file_target_pos; + + /// Size of the .xz file (from the application). + uint64_t file_size; + + /// Index decoder + lzma_next_coder index_decoder; + + /// Number of bytes remaining in the Index field that is currently + /// being decoded. + lzma_vli index_remaining; + + /// The Index decoder will store the decoded Index in this pointer. + lzma_index *this_index; + + /// Amount of Stream Padding in the current Stream. + lzma_vli stream_padding; + + /// The final combined index is collected here. + lzma_index *combined_index; + + /// Pointer from the application where to store the index information + /// after successful decoding. + lzma_index **dest_index; + + /// Pointer to lzma_stream.seek_pos to be used when returning + /// LZMA_SEEK_NEEDED. This is set by seek_to_pos() when needed. + uint64_t *external_seek_pos; + + /// Memory usage limit + uint64_t memlimit; + + /// Stream Flags from the very beginning of the file. + lzma_stream_flags first_header_flags; + + /// Stream Flags from Stream Header of the current Stream. + lzma_stream_flags header_flags; + + /// Stream Flags from Stream Footer of the current Stream. + lzma_stream_flags footer_flags; + + size_t temp_pos; + size_t temp_size; + uint8_t temp[8192]; + +} lzma_file_info_coder; + + +/// Copies data from in[*in_pos] into coder->temp until +/// coder->temp_pos == coder->temp_size. This also keeps coder->file_cur_pos +/// in sync with *in_pos. Returns true if more input is needed. +static bool +fill_temp(lzma_file_info_coder *coder, const uint8_t *restrict in, + size_t *restrict in_pos, size_t in_size) +{ + coder->file_cur_pos += lzma_bufcpy(in, in_pos, in_size, + coder->temp, &coder->temp_pos, coder->temp_size); + return coder->temp_pos < coder->temp_size; +} + + +/// Seeks to the absolute file position specified by target_pos. +/// This tries to do the seeking by only modifying *in_pos, if possible. +/// The main benefit of this is that if one passes the whole file at once +/// to lzma_code(), the decoder will never need to return LZMA_SEEK_NEEDED +/// as all the seeking can be done by adjusting *in_pos in this function. +/// +/// Returns true if an external seek is needed and the caller must return +/// LZMA_SEEK_NEEDED. +static bool +seek_to_pos(lzma_file_info_coder *coder, uint64_t target_pos, + size_t in_start, size_t *in_pos, size_t in_size) +{ + // The input buffer doesn't extend beyond the end of the file. + // This has been checked by file_info_decode() already. + assert(coder->file_size - coder->file_cur_pos >= in_size - *in_pos); + + const uint64_t pos_min = coder->file_cur_pos - (*in_pos - in_start); + const uint64_t pos_max = coder->file_cur_pos + (in_size - *in_pos); + + bool external_seek_needed; + + if (target_pos >= pos_min && target_pos <= pos_max) { + // The requested position is available in the current input + // buffer or right after it. That is, in a corner case we + // end up setting *in_pos == in_size and thus will immediately + // need new input bytes from the application. + *in_pos += (size_t)(target_pos - coder->file_cur_pos); + external_seek_needed = false; + } else { + // Ask the application to seek the input file. + *coder->external_seek_pos = target_pos; + external_seek_needed = true; + + // Mark the whole input buffer as used. This way + // lzma_stream.total_in will have a better estimate + // of the amount of data read. It still won't be perfect + // as the value will depend on the input buffer size that + // the application uses, but it should be good enough for + // those few who want an estimate. + *in_pos = in_size; + } + + // After seeking (internal or external) the current position + // will match the requested target position. + coder->file_cur_pos = target_pos; + + return external_seek_needed; +} + + +/// The caller sets coder->file_target_pos so that it points to the *end* +/// of the desired file position. This function then determines how far +/// backwards from that position we can seek. After seeking fill_temp() +/// can be used to read data into coder->temp. When fill_temp() has finished, +/// coder->temp[coder->temp_size] will match coder->file_target_pos. +/// +/// This also validates that coder->target_file_pos is sane in sense that +/// we aren't trying to seek too far backwards (too close or beyond the +/// beginning of the file). +static lzma_ret +reverse_seek(lzma_file_info_coder *coder, + size_t in_start, size_t *in_pos, size_t in_size) +{ + // Check that there is enough data before the target position + // to contain at least Stream Header and Stream Footer. If there + // isn't, the file cannot be valid. + if (coder->file_target_pos < 2 * LZMA_STREAM_HEADER_SIZE) + return LZMA_DATA_ERROR; + + coder->temp_pos = 0; + + // The Stream Header at the very beginning of the file gets handled + // specially in SEQ_MAGIC_BYTES and thus we will never need to seek + // there. By not seeking to the first LZMA_STREAM_HEADER_SIZE bytes + // we avoid a useless external seek after SEQ_MAGIC_BYTES if the + // application uses an extremely small input buffer and the input + // file is very small. + if (coder->file_target_pos - LZMA_STREAM_HEADER_SIZE + < sizeof(coder->temp)) + coder->temp_size = (size_t)(coder->file_target_pos + - LZMA_STREAM_HEADER_SIZE); + else + coder->temp_size = sizeof(coder->temp); + + // The above if-statements guarantee this. This is important because + // the Stream Header/Footer decoders assume that there's at least + // LZMA_STREAM_HEADER_SIZE bytes in coder->temp. + assert(coder->temp_size >= LZMA_STREAM_HEADER_SIZE); + + if (seek_to_pos(coder, coder->file_target_pos - coder->temp_size, + in_start, in_pos, in_size)) + return LZMA_SEEK_NEEDED; + + return LZMA_OK; +} + + +/// Gets the number of zero-bytes at the end of the buffer. +static size_t +get_padding_size(const uint8_t *buf, size_t buf_size) +{ + size_t padding = 0; + while (buf_size > 0 && buf[--buf_size] == 0x00) + ++padding; + + return padding; +} + + +/// With the Stream Header at the very beginning of the file, LZMA_FORMAT_ERROR +/// is used to tell the application that Magic Bytes didn't match. In other +/// Stream Header/Footer fields (in the middle/end of the file) it could be +/// a bit confusing to return LZMA_FORMAT_ERROR as we already know that there +/// is a valid Stream Header at the beginning of the file. For those cases +/// this function is used to convert LZMA_FORMAT_ERROR to LZMA_DATA_ERROR. +static lzma_ret +hide_format_error(lzma_ret ret) +{ + if (ret == LZMA_FORMAT_ERROR) + ret = LZMA_DATA_ERROR; + + return ret; +} + + +/// Calls the Index decoder and updates coder->index_remaining. +/// This is a separate function because the input can be either directly +/// from the application or from coder->temp. +static lzma_ret +decode_index(lzma_file_info_coder *coder, const lzma_allocator *allocator, + const uint8_t *restrict in, size_t *restrict in_pos, + size_t in_size, bool update_file_cur_pos) +{ + const size_t in_start = *in_pos; + + const lzma_ret ret = coder->index_decoder.code( + coder->index_decoder.coder, + allocator, in, in_pos, in_size, + NULL, NULL, 0, LZMA_RUN); + + coder->index_remaining -= *in_pos - in_start; + + if (update_file_cur_pos) + coder->file_cur_pos += *in_pos - in_start; + + return ret; +} + + +static lzma_ret +file_info_decode(void *coder_ptr, const lzma_allocator *allocator, + const uint8_t *restrict in, size_t *restrict in_pos, + size_t in_size, + uint8_t *restrict out lzma_attribute((__unused__)), + size_t *restrict out_pos lzma_attribute((__unused__)), + size_t out_size lzma_attribute((__unused__)), + lzma_action action lzma_attribute((__unused__))) +{ + lzma_file_info_coder *coder = coder_ptr; + const size_t in_start = *in_pos; + + // If the caller provides input past the end of the file, trim + // the extra bytes from the buffer so that we won't read too far. + assert(coder->file_size >= coder->file_cur_pos); + if (coder->file_size - coder->file_cur_pos < in_size - in_start) + in_size = in_start + + (size_t)(coder->file_size - coder->file_cur_pos); + + while (true) + switch (coder->sequence) { + case SEQ_MAGIC_BYTES: + // Decode the Stream Header at the beginning of the file + // first to check if the Magic Bytes match. The flags + // are stored in coder->first_header_flags so that we + // don't need to seek to it again. + // + // Check that the file is big enough to contain at least + // Stream Header. + if (coder->file_size < LZMA_STREAM_HEADER_SIZE) + return LZMA_FORMAT_ERROR; + + // Read the Stream Header field into coder->temp. + if (fill_temp(coder, in, in_pos, in_size)) + return LZMA_OK; + + // This is the only Stream Header/Footer decoding where we + // want to return LZMA_FORMAT_ERROR if the Magic Bytes don't + // match. Elsewhere it will be converted to LZMA_DATA_ERROR. + return_if_error(lzma_stream_header_decode( + &coder->first_header_flags, coder->temp)); + + // Now that we know that the Magic Bytes match, check the + // file size. It's better to do this here after checking the + // Magic Bytes since this way we can give LZMA_FORMAT_ERROR + // instead of LZMA_DATA_ERROR when the Magic Bytes don't + // match in a file that is too big or isn't a multiple of + // four bytes. + if (coder->file_size > LZMA_VLI_MAX || (coder->file_size & 3)) + return LZMA_DATA_ERROR; + + // Start looking for Stream Padding and Stream Footer + // at the end of the file. + coder->file_target_pos = coder->file_size; + + // Fall through + + case SEQ_PADDING_SEEK: + coder->sequence = SEQ_PADDING_DECODE; + return_if_error(reverse_seek( + coder, in_start, in_pos, in_size)); + + // Fall through + + case SEQ_PADDING_DECODE: { + // Copy to coder->temp first. This keeps the code simpler if + // the application only provides input a few bytes at a time. + if (fill_temp(coder, in, in_pos, in_size)) + return LZMA_OK; + + // Scan the buffer backwards to get the size of the + // Stream Padding field (if any). + const size_t new_padding = get_padding_size( + coder->temp, coder->temp_size); + coder->stream_padding += new_padding; + + // Set the target position to the beginning of Stream Padding + // that has been observed so far. If all Stream Padding has + // been seen, then the target position will be at the end + // of the Stream Footer field. + coder->file_target_pos -= new_padding; + + if (new_padding == coder->temp_size) { + // The whole buffer was padding. Seek backwards in + // the file to get more input. + coder->sequence = SEQ_PADDING_SEEK; + break; + } + + // Size of Stream Padding must be a multiple of 4 bytes. + if (coder->stream_padding & 3) + return LZMA_DATA_ERROR; + + coder->sequence = SEQ_FOOTER; + + // Calculate the amount of non-padding data in coder->temp. + coder->temp_size -= new_padding; + coder->temp_pos = coder->temp_size; + + // We can avoid an external seek if the whole Stream Footer + // is already in coder->temp. In that case SEQ_FOOTER won't + // read more input and will find the Stream Footer from + // coder->temp[coder->temp_size - LZMA_STREAM_HEADER_SIZE]. + // + // Otherwise we will need to seek. The seeking is done so + // that Stream Footer will be at the end of coder->temp. + // This way it's likely that we also get a complete Index + // field into coder->temp without needing a separate seek + // for that (unless the Index field is big). + if (coder->temp_size < LZMA_STREAM_HEADER_SIZE) + return_if_error(reverse_seek( + coder, in_start, in_pos, in_size)); + } + + // Fall through + + case SEQ_FOOTER: + // Copy the Stream Footer field into coder->temp. + // If Stream Footer was already available in coder->temp + // in SEQ_PADDING_DECODE, then this does nothing. + if (fill_temp(coder, in, in_pos, in_size)) + return LZMA_OK; + + // Make coder->file_target_pos and coder->temp_size point + // to the beginning of Stream Footer and thus to the end + // of the Index field. coder->temp_pos will be updated + // a bit later. + coder->file_target_pos -= LZMA_STREAM_HEADER_SIZE; + coder->temp_size -= LZMA_STREAM_HEADER_SIZE; + + // Decode Stream Footer. + return_if_error(hide_format_error(lzma_stream_footer_decode( + &coder->footer_flags, + coder->temp + coder->temp_size))); + + // Check that we won't seek past the beginning of the file. + // + // LZMA_STREAM_HEADER_SIZE is added because there must be + // space for Stream Header too even though we won't seek + // there before decoding the Index field. + // + // There's no risk of integer overflow here because + // Backward Size cannot be greater than 2^34. + if (coder->file_target_pos < coder->footer_flags.backward_size + + LZMA_STREAM_HEADER_SIZE) + return LZMA_DATA_ERROR; + + // Set the target position to the beginning of the Index field. + coder->file_target_pos -= coder->footer_flags.backward_size; + coder->sequence = SEQ_INDEX_INIT; + + // We can avoid an external seek if the whole Index field is + // already available in coder->temp. + if (coder->temp_size >= coder->footer_flags.backward_size) { + // Set coder->temp_pos to point to the beginning + // of the Index. + coder->temp_pos = coder->temp_size + - coder->footer_flags.backward_size; + } else { + // These are set to zero to indicate that there's no + // useful data (Index or anything else) in coder->temp. + coder->temp_pos = 0; + coder->temp_size = 0; + + // Seek to the beginning of the Index field. + if (seek_to_pos(coder, coder->file_target_pos, + in_start, in_pos, in_size)) + return LZMA_SEEK_NEEDED; + } + + // Fall through + + case SEQ_INDEX_INIT: { + // Calculate the amount of memory already used by the earlier + // Indexes so that we know how big memory limit to pass to + // the Index decoder. + // + // NOTE: When there are multiple Streams, the separate + // lzma_index structures can use more RAM (as measured by + // lzma_index_memused()) than the final combined lzma_index. + // Thus memlimit may need to be slightly higher than the final + // calculated memory usage will be. This is perhaps a bit + // confusing to the application, but I think it shouldn't + // cause problems in practice. + uint64_t memused = 0; + if (coder->combined_index != NULL) { + memused = lzma_index_memused(coder->combined_index); + assert(memused <= coder->memlimit); + if (memused > coder->memlimit) // Extra sanity check + return LZMA_PROG_ERROR; + } + + // Initialize the Index decoder. + return_if_error(lzma_index_decoder_init( + &coder->index_decoder, allocator, + &coder->this_index, + coder->memlimit - memused)); + + coder->index_remaining = coder->footer_flags.backward_size; + coder->sequence = SEQ_INDEX_DECODE; + } + + // Fall through + + case SEQ_INDEX_DECODE: { + // Decode (a part of) the Index. If the whole Index is already + // in coder->temp, read it from there. Otherwise read from + // in[*in_pos] onwards. Note that index_decode() updates + // coder->index_remaining and optionally coder->file_cur_pos. + lzma_ret ret; + if (coder->temp_size != 0) { + assert(coder->temp_size - coder->temp_pos + == coder->index_remaining); + ret = decode_index(coder, allocator, coder->temp, + &coder->temp_pos, coder->temp_size, + false); + } else { + // Don't give the decoder more input than the known + // remaining size of the Index field. + size_t in_stop = in_size; + if (in_size - *in_pos > coder->index_remaining) + in_stop = *in_pos + + (size_t)(coder->index_remaining); + + ret = decode_index(coder, allocator, + in, in_pos, in_stop, true); + } + + switch (ret) { + case LZMA_OK: + // If the Index docoder asks for more input when we + // have already given it as much input as Backward Size + // indicated, the file is invalid. + if (coder->index_remaining == 0) + return LZMA_DATA_ERROR; + + // We cannot get here if we were reading Index from + // coder->temp because when reading from coder->temp + // we give the Index decoder exactly + // coder->index_remaining bytes of input. + assert(coder->temp_size == 0); + + return LZMA_OK; + + case LZMA_STREAM_END: + // If the decoding seems to be successful, check also + // that the Index decoder consumed as much input as + // indicated by the Backward Size field. + if (coder->index_remaining != 0) + return LZMA_DATA_ERROR; + + break; + + default: + return ret; + } + + // Calculate how much the Index tells us to seek backwards + // (relative to the beginning of the Index): Total size of + // all Blocks plus the size of the Stream Header field. + // No integer overflow here because lzma_index_total_size() + // cannot return a value greater than LZMA_VLI_MAX. + const uint64_t seek_amount + = lzma_index_total_size(coder->this_index) + + LZMA_STREAM_HEADER_SIZE; + + // Check that Index is sane in sense that seek_amount won't + // make us seek past the beginning of the file when locating + // the Stream Header. + // + // coder->file_target_pos still points to the beginning of + // the Index field. + if (coder->file_target_pos < seek_amount) + return LZMA_DATA_ERROR; + + // Set the target to the beginning of Stream Header. + coder->file_target_pos -= seek_amount; + + if (coder->file_target_pos == 0) { + // We would seek to the beginning of the file, but + // since we already decoded that Stream Header in + // SEQ_MAGIC_BYTES, we can use the cached value from + // coder->first_header_flags to avoid the seek. + coder->header_flags = coder->first_header_flags; + coder->sequence = SEQ_HEADER_COMPARE; + break; + } + + coder->sequence = SEQ_HEADER_DECODE; + + // Make coder->file_target_pos point to the end of + // the Stream Header field. + coder->file_target_pos += LZMA_STREAM_HEADER_SIZE; + + // If coder->temp_size is non-zero, it points to the end + // of the Index field. Then the beginning of the Index + // field is at coder->temp[coder->temp_size + // - coder->footer_flags.backward_size]. + assert(coder->temp_size == 0 || coder->temp_size + >= coder->footer_flags.backward_size); + + // If coder->temp contained the whole Index, see if it has + // enough data to contain also the Stream Header. If so, + // we avoid an external seek. + // + // NOTE: This can happen only with small .xz files and only + // for the non-first Stream as the Stream Flags of the first + // Stream are cached and already handled a few lines above. + // So this isn't as useful as the other seek-avoidance cases. + if (coder->temp_size != 0 && coder->temp_size + - coder->footer_flags.backward_size + >= seek_amount) { + // Make temp_pos and temp_size point to the *end* of + // Stream Header so that SEQ_HEADER_DECODE will find + // the start of Stream Header from coder->temp[ + // coder->temp_size - LZMA_STREAM_HEADER_SIZE]. + coder->temp_pos = coder->temp_size + - coder->footer_flags.backward_size + - seek_amount + + LZMA_STREAM_HEADER_SIZE; + coder->temp_size = coder->temp_pos; + } else { + // Seek so that Stream Header will be at the end of + // coder->temp. With typical multi-Stream files we + // will usually also get the Stream Footer and Index + // of the *previous* Stream in coder->temp and thus + // won't need a separate seek for them. + return_if_error(reverse_seek(coder, + in_start, in_pos, in_size)); + } + } + + // Fall through + + case SEQ_HEADER_DECODE: + // Copy the Stream Header field into coder->temp. + // If Stream Header was already available in coder->temp + // in SEQ_INDEX_DECODE, then this does nothing. + if (fill_temp(coder, in, in_pos, in_size)) + return LZMA_OK; + + // Make all these point to the beginning of Stream Header. + coder->file_target_pos -= LZMA_STREAM_HEADER_SIZE; + coder->temp_size -= LZMA_STREAM_HEADER_SIZE; + coder->temp_pos = coder->temp_size; + + // Decode the Stream Header. + return_if_error(hide_format_error(lzma_stream_header_decode( + &coder->header_flags, + coder->temp + coder->temp_size))); + + coder->sequence = SEQ_HEADER_COMPARE; + + // Fall through + + case SEQ_HEADER_COMPARE: + // Compare Stream Header against Stream Footer. They must + // match. + return_if_error(lzma_stream_flags_compare( + &coder->header_flags, &coder->footer_flags)); + + // Store the decoded Stream Flags into the Index. Use the + // Footer Flags because it contains Backward Size, although + // it shouldn't matter in practice. + if (lzma_index_stream_flags(coder->this_index, + &coder->footer_flags) != LZMA_OK) + return LZMA_PROG_ERROR; + + // Store also the size of the Stream Padding field. It is + // needed to calculate the offsets of the Streams correctly. + if (lzma_index_stream_padding(coder->this_index, + coder->stream_padding) != LZMA_OK) + return LZMA_PROG_ERROR; + + // Reset it so that it's ready for the next Stream. + coder->stream_padding = 0; + + // Append the earlier decoded Indexes after this_index. + if (coder->combined_index != NULL) + return_if_error(lzma_index_cat(coder->this_index, + coder->combined_index, allocator)); + + coder->combined_index = coder->this_index; + coder->this_index = NULL; + + // If the whole file was decoded, tell the caller that we + // are finished. + if (coder->file_target_pos == 0) { + // The combined index must indicate the same file + // size as was told to us at initialization. + assert(lzma_index_file_size(coder->combined_index) + == coder->file_size); + + // Make the combined index available to + // the application. + *coder->dest_index = coder->combined_index; + coder->combined_index = NULL; + + // Mark the input buffer as used since we may have + // done internal seeking and thus don't know how + // many input bytes were actually used. This way + // lzma_stream.total_in gets a slightly better + // estimate of the amount of input used. + *in_pos = in_size; + return LZMA_STREAM_END; + } + + // We didn't hit the beginning of the file yet, so continue + // reading backwards in the file. If we have unprocessed + // data in coder->temp, use it before requesting more data + // from the application. + // + // coder->file_target_pos, coder->temp_size, and + // coder->temp_pos all point to the beginning of Stream Header + // and thus the end of the previous Stream in the file. + coder->sequence = coder->temp_size > 0 + ? SEQ_PADDING_DECODE : SEQ_PADDING_SEEK; + break; + + default: + assert(0); + return LZMA_PROG_ERROR; + } +} + + +static lzma_ret +file_info_decoder_memconfig(void *coder_ptr, uint64_t *memusage, + uint64_t *old_memlimit, uint64_t new_memlimit) +{ + lzma_file_info_coder *coder = coder_ptr; + + // The memory usage calculation comes from three things: + // + // (1) The Indexes that have already been decoded and processed into + // coder->combined_index. + // + // (2) The latest Index in coder->this_index that has been decoded but + // not yet put into coder->combined_index. + // + // (3) The latest Index that we have started decoding but haven't + // finished and thus isn't available in coder->this_index yet. + // Memory usage and limit information needs to be communicated + // from/to coder->index_decoder. + // + // Care has to be taken to not do both (2) and (3) when calculating + // the memory usage. + uint64_t combined_index_memusage = 0; + uint64_t this_index_memusage = 0; + + // (1) If we have already successfully decoded one or more Indexes, + // get their memory usage. + if (coder->combined_index != NULL) + combined_index_memusage = lzma_index_memused( + coder->combined_index); + + // Choose between (2), (3), or neither. + if (coder->this_index != NULL) { + // (2) The latest Index is available. Use its memory usage. + this_index_memusage = lzma_index_memused(coder->this_index); + + } else if (coder->sequence == SEQ_INDEX_DECODE) { + // (3) The Index decoder is activate and hasn't yet stored + // the new index in coder->this_index. Get the memory usage + // information from the Index decoder. + // + // NOTE: If the Index decoder doesn't yet know how much memory + // it will eventually need, it will return a tiny value here. + uint64_t dummy; + if (coder->index_decoder.memconfig(coder->index_decoder.coder, + &this_index_memusage, &dummy, 0) + != LZMA_OK) { + assert(0); + return LZMA_PROG_ERROR; + } + } + + // Now we know the total memory usage/requirement. If we had neither + // old Indexes nor a new Index, this will be zero which isn't + // acceptable as lzma_memusage() has to return non-zero on success + // and even with an empty .xz file we will end up with a lzma_index + // that takes some memory. + *memusage = combined_index_memusage + this_index_memusage; + if (*memusage == 0) + *memusage = lzma_index_memusage(1, 0); + + *old_memlimit = coder->memlimit; + + // If requested, set a new memory usage limit. + if (new_memlimit != 0) { + if (new_memlimit < *memusage) + return LZMA_MEMLIMIT_ERROR; + + // In the condition (3) we need to tell the Index decoder + // its new memory usage limit. + if (coder->this_index == NULL + && coder->sequence == SEQ_INDEX_DECODE) { + const uint64_t idec_new_memlimit = new_memlimit + - combined_index_memusage; + + assert(this_index_memusage > 0); + assert(idec_new_memlimit > 0); + + uint64_t dummy1; + uint64_t dummy2; + + if (coder->index_decoder.memconfig( + coder->index_decoder.coder, + &dummy1, &dummy2, idec_new_memlimit) + != LZMA_OK) { + assert(0); + return LZMA_PROG_ERROR; + } + } + + coder->memlimit = new_memlimit; + } + + return LZMA_OK; +} + + +static void +file_info_decoder_end(void *coder_ptr, const lzma_allocator *allocator) +{ + lzma_file_info_coder *coder = coder_ptr; + + lzma_next_end(&coder->index_decoder, allocator); + lzma_index_end(coder->this_index, allocator); + lzma_index_end(coder->combined_index, allocator); + + lzma_free(coder, allocator); + return; +} + + +static lzma_ret +lzma_file_info_decoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, uint64_t *seek_pos, + lzma_index **dest_index, + uint64_t memlimit, uint64_t file_size) +{ + lzma_next_coder_init(&lzma_file_info_decoder_init, next, allocator); + + if (dest_index == NULL) + return LZMA_PROG_ERROR; + + lzma_file_info_coder *coder = next->coder; + if (coder == NULL) { + coder = lzma_alloc(sizeof(lzma_file_info_coder), allocator); + if (coder == NULL) + return LZMA_MEM_ERROR; + + next->coder = coder; + next->code = &file_info_decode; + next->end = &file_info_decoder_end; + next->memconfig = &file_info_decoder_memconfig; + + coder->index_decoder = LZMA_NEXT_CODER_INIT; + coder->this_index = NULL; + coder->combined_index = NULL; + } + + coder->sequence = SEQ_MAGIC_BYTES; + coder->file_cur_pos = 0; + coder->file_target_pos = 0; + coder->file_size = file_size; + + lzma_index_end(coder->this_index, allocator); + coder->this_index = NULL; + + lzma_index_end(coder->combined_index, allocator); + coder->combined_index = NULL; + + coder->stream_padding = 0; + + coder->dest_index = dest_index; + coder->external_seek_pos = seek_pos; + + // If memlimit is 0, make it 1 to ensure that lzma_memlimit_get() + // won't return 0 (which would indicate an error). + coder->memlimit = my_max(1, memlimit); + + // Prepare these for reading the first Stream Header into coder->temp. + coder->temp_pos = 0; + coder->temp_size = LZMA_STREAM_HEADER_SIZE; + + return LZMA_OK; +} + + +extern LZMA_API(lzma_ret) +lzma_file_info_decoder(lzma_stream *strm, lzma_index **dest_index, + uint64_t memlimit, uint64_t file_size) +{ + lzma_next_strm_init(lzma_file_info_decoder_init, strm, &strm->seek_pos, + dest_index, memlimit, file_size); + + // We allow LZMA_FINISH in addition to LZMA_RUN for convenience. + // lzma_code() is able to handle the LZMA_FINISH + LZMA_SEEK_NEEDED + // combination in a sane way. Applications still need to be careful + // if they use LZMA_FINISH so that they remember to reset it back + // to LZMA_RUN after seeking if needed. + strm->internal->supported_actions[LZMA_RUN] = true; + strm->internal->supported_actions[LZMA_FINISH] = true; + + return LZMA_OK; +} diff --git a/Utilities/cmliblzma/liblzma/common/filter_buffer_decoder.c b/Utilities/cmliblzma/liblzma/common/filter_buffer_decoder.c index 6620986eea8..cc0d88cc71c 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_buffer_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/filter_buffer_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_buffer_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_decoder.h" @@ -24,7 +23,7 @@ lzma_raw_buffer_decode( || out_pos == NULL || *out_pos > out_size) return LZMA_PROG_ERROR; - // Initialize the decoer. + // Initialize the decoder. lzma_next_coder next = LZMA_NEXT_CODER_INIT; return_if_error(lzma_raw_decoder_init(&next, allocator, filters)); diff --git a/Utilities/cmliblzma/liblzma/common/filter_buffer_encoder.c b/Utilities/cmliblzma/liblzma/common/filter_buffer_encoder.c index dda18e3d8e5..7fb8922ae90 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_buffer_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/filter_buffer_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_buffer_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_encoder.h" diff --git a/Utilities/cmliblzma/liblzma/common/filter_common.c b/Utilities/cmliblzma/liblzma/common/filter_common.c index 9ad5d5d8e2a..d15d9cc94f9 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_common.c +++ b/Utilities/cmliblzma/liblzma/common/filter_common.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_common.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_common.h" @@ -42,6 +41,13 @@ static const struct { .last_ok = true, .changes_size = true, }, + { + .id = LZMA_FILTER_LZMA1EXT, + .options_size = sizeof(lzma_options_lzma), + .non_last_ok = false, + .last_ok = true, + .changes_size = true, + }, #endif #if defined(HAVE_ENCODER_LZMA2) || defined(HAVE_DECODER_LZMA2) { @@ -97,6 +103,15 @@ static const struct { .changes_size = false, }, #endif +#if defined(HAVE_ENCODER_ARM64) || defined(HAVE_DECODER_ARM64) + { + .id = LZMA_FILTER_ARM64, + .options_size = sizeof(lzma_options_bcj), + .non_last_ok = true, + .last_ok = false, + .changes_size = false, + }, +#endif #if defined(HAVE_ENCODER_SPARC) || defined(HAVE_DECODER_SPARC) { .id = LZMA_FILTER_SPARC, @@ -106,6 +121,15 @@ static const struct { .changes_size = false, }, #endif +#if defined(HAVE_ENCODER_RISCV) || defined(HAVE_DECODER_RISCV) + { + .id = LZMA_FILTER_RISCV, + .options_size = sizeof(lzma_options_bcj), + .non_last_ok = true, + .last_ok = false, + .changes_size = false, + }, +#endif #if defined(HAVE_ENCODER_DELTA) || defined(HAVE_DECODER_DELTA) { .id = LZMA_FILTER_DELTA, @@ -122,12 +146,16 @@ static const struct { extern LZMA_API(lzma_ret) -lzma_filters_copy(const lzma_filter *src, lzma_filter *dest, +lzma_filters_copy(const lzma_filter *src, lzma_filter *real_dest, const lzma_allocator *allocator) { - if (src == NULL || dest == NULL) + if (src == NULL || real_dest == NULL) return LZMA_PROG_ERROR; + // Use a temporary destination so that the real destination + // will never be modified if an error occurs. + lzma_filter dest[LZMA_FILTERS_MAX + 1]; + lzma_ret ret; size_t i; for (i = 0; src[i].id != LZMA_VLI_UNKNOWN; ++i) { @@ -173,25 +201,53 @@ lzma_filters_copy(const lzma_filter *src, lzma_filter *dest, } // Terminate the filter array. - assert(i <= LZMA_FILTERS_MAX + 1); + assert(i < LZMA_FILTERS_MAX + 1); dest[i].id = LZMA_VLI_UNKNOWN; dest[i].options = NULL; + // Copy it to the caller-supplied array now that we know that + // no errors occurred. + memcpy(real_dest, dest, (i + 1) * sizeof(lzma_filter)); + return LZMA_OK; error: // Free the options which we have already allocated. - while (i-- > 0) { + while (i-- > 0) lzma_free(dest[i].options, allocator); - dest[i].options = NULL; - } return ret; } -static lzma_ret -validate_chain(const lzma_filter *filters, size_t *count) +extern LZMA_API(void) +lzma_filters_free(lzma_filter *filters, const lzma_allocator *allocator) +{ + if (filters == NULL) + return; + + for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) { + if (i == LZMA_FILTERS_MAX) { + // The API says that LZMA_FILTERS_MAX + 1 is the + // maximum allowed size including the terminating + // element. Thus, we should never get here but in + // case there is a bug and we do anyway, don't go + // past the (probable) end of the array. + assert(0); + break; + } + + lzma_free(filters[i].options, allocator); + filters[i].options = NULL; + filters[i].id = LZMA_VLI_UNKNOWN; + } + + return; +} + + +extern lzma_ret +lzma_validate_chain(const lzma_filter *filters, size_t *count) { // There must be at least one filter. if (filters == NULL || filters[0].id == LZMA_VLI_UNKNOWN) @@ -245,7 +301,7 @@ lzma_raw_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, { // Do some basic validation and get the number of filters. size_t count; - return_if_error(validate_chain(options, &count)); + return_if_error(lzma_validate_chain(options, &count)); // Set the filter functions and copy the options pointer. lzma_filter_info filters[LZMA_FILTERS_MAX + 1]; @@ -298,7 +354,7 @@ lzma_raw_coder_memusage(lzma_filter_find coder_find, // The chain has to have at least one filter. { size_t tmp; - if (validate_chain(filters, &tmp) != LZMA_OK) + if (lzma_validate_chain(filters, &tmp) != LZMA_OK) return UINT64_MAX; } diff --git a/Utilities/cmliblzma/liblzma/common/filter_common.h b/Utilities/cmliblzma/liblzma/common/filter_common.h index 9390305c26c..95f9fe27017 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_common.h +++ b/Utilities/cmliblzma/liblzma/common/filter_common.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_common.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_FILTER_COMMON_H @@ -35,6 +34,9 @@ typedef struct { typedef const lzma_filter_coder *(*lzma_filter_find)(lzma_vli id); +extern lzma_ret lzma_validate_chain(const lzma_filter *filters, size_t *count); + + extern lzma_ret lzma_raw_coder_init( lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter *filters, diff --git a/Utilities/cmliblzma/liblzma/common/filter_decoder.c b/Utilities/cmliblzma/liblzma/common/filter_decoder.c index c75b0a89c30..cbdeb5858f6 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/filter_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_decoder.h" @@ -50,6 +49,12 @@ static const lzma_filter_decoder decoders[] = { .memusage = &lzma_lzma_decoder_memusage, .props_decode = &lzma_lzma_props_decode, }, + { + .id = LZMA_FILTER_LZMA1EXT, + .init = &lzma_lzma_decoder_init, + .memusage = &lzma_lzma_decoder_memusage, + .props_decode = &lzma_lzma_props_decode, + }, #endif #ifdef HAVE_DECODER_LZMA2 { @@ -99,6 +104,14 @@ static const lzma_filter_decoder decoders[] = { .props_decode = &lzma_simple_props_decode, }, #endif +#ifdef HAVE_DECODER_ARM64 + { + .id = LZMA_FILTER_ARM64, + .init = &lzma_simple_arm64_decoder_init, + .memusage = NULL, + .props_decode = &lzma_simple_props_decode, + }, +#endif #ifdef HAVE_DECODER_SPARC { .id = LZMA_FILTER_SPARC, @@ -107,6 +120,14 @@ static const lzma_filter_decoder decoders[] = { .props_decode = &lzma_simple_props_decode, }, #endif +#ifdef HAVE_DECODER_RISCV + { + .id = LZMA_FILTER_RISCV, + .init = &lzma_simple_riscv_decoder_init, + .memusage = NULL, + .props_decode = &lzma_simple_props_decode, + }, +#endif #ifdef HAVE_DECODER_DELTA { .id = LZMA_FILTER_DELTA, @@ -129,6 +150,16 @@ decoder_find(lzma_vli id) } +// lzma_filter_coder begins with the same members as lzma_filter_decoder. +// This function is a wrapper with a type that is compatible with the +// typedef of lzma_filter_find in filter_common.h. +static const lzma_filter_coder * +coder_find(lzma_vli id) +{ + return (const lzma_filter_coder *)decoder_find(id); +} + + extern LZMA_API(lzma_bool) lzma_filter_decoder_is_supported(lzma_vli id) { @@ -141,7 +172,7 @@ lzma_raw_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter *options) { return lzma_raw_coder_init(next, allocator, - options, (lzma_filter_find)(&decoder_find), false); + options, &coder_find, false); } @@ -160,8 +191,7 @@ lzma_raw_decoder(lzma_stream *strm, const lzma_filter *options) extern LZMA_API(uint64_t) lzma_raw_decoder_memusage(const lzma_filter *filters) { - return lzma_raw_coder_memusage( - (lzma_filter_find)(&decoder_find), filters); + return lzma_raw_coder_memusage(&coder_find, filters); } diff --git a/Utilities/cmliblzma/liblzma/common/filter_decoder.h b/Utilities/cmliblzma/liblzma/common/filter_decoder.h index 2dac6028282..e610bc1f44e 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_decoder.h +++ b/Utilities/cmliblzma/liblzma/common/filter_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_decoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_FILTER_DECODER_H diff --git a/Utilities/cmliblzma/liblzma/common/filter_encoder.c b/Utilities/cmliblzma/liblzma/common/filter_encoder.c index c5d8f39721f..bc394448985 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/filter_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_encoder.h" @@ -33,13 +32,17 @@ typedef struct { /// Calculates the recommended Uncompressed Size for .xz Blocks to /// which the input data can be split to make multithreaded /// encoding possible. If this is NULL, it is assumed that - /// the encoder is fast enough with single thread. + /// the encoder is fast enough with single thread. If the options + /// are invalid, UINT64_MAX is returned. uint64_t (*block_size)(const void *options); /// Tells the size of the Filter Properties field. If options are - /// invalid, UINT32_MAX is returned. If this is NULL, props_size_fixed - /// is used. + /// invalid, LZMA_OPTIONS_ERROR is returned and size is set to + /// UINT32_MAX. lzma_ret (*props_size_get)(uint32_t *size, const void *options); + + /// Some filters will always have the same size Filter Properties + /// field. If props_size_get is NULL, this value is used. uint32_t props_size_fixed; /// Encodes Filter Properties. @@ -59,7 +62,16 @@ static const lzma_filter_encoder encoders[] = { .id = LZMA_FILTER_LZMA1, .init = &lzma_lzma_encoder_init, .memusage = &lzma_lzma_encoder_memusage, - .block_size = NULL, // FIXME + .block_size = NULL, // Not needed for LZMA1 + .props_size_get = NULL, + .props_size_fixed = 5, + .props_encode = &lzma_lzma_props_encode, + }, + { + .id = LZMA_FILTER_LZMA1EXT, + .init = &lzma_lzma_encoder_init, + .memusage = &lzma_lzma_encoder_memusage, + .block_size = NULL, // Not needed for LZMA1 .props_size_get = NULL, .props_size_fixed = 5, .props_encode = &lzma_lzma_props_encode, @@ -70,7 +82,7 @@ static const lzma_filter_encoder encoders[] = { .id = LZMA_FILTER_LZMA2, .init = &lzma_lzma2_encoder_init, .memusage = &lzma_lzma2_encoder_memusage, - .block_size = &lzma_lzma2_block_size, // FIXME + .block_size = &lzma_lzma2_block_size, .props_size_get = NULL, .props_size_fixed = 1, .props_encode = &lzma_lzma2_props_encode, @@ -126,6 +138,16 @@ static const lzma_filter_encoder encoders[] = { .props_encode = &lzma_simple_props_encode, }, #endif +#ifdef HAVE_ENCODER_ARM64 + { + .id = LZMA_FILTER_ARM64, + .init = &lzma_simple_arm64_encoder_init, + .memusage = NULL, + .block_size = NULL, + .props_size_get = &lzma_simple_props_size, + .props_encode = &lzma_simple_props_encode, + }, +#endif #ifdef HAVE_ENCODER_SPARC { .id = LZMA_FILTER_SPARC, @@ -136,6 +158,16 @@ static const lzma_filter_encoder encoders[] = { .props_encode = &lzma_simple_props_encode, }, #endif +#ifdef HAVE_ENCODER_RISCV + { + .id = LZMA_FILTER_RISCV, + .init = &lzma_simple_riscv_encoder_init, + .memusage = NULL, + .block_size = NULL, + .props_size_get = &lzma_simple_props_size, + .props_encode = &lzma_simple_props_encode, + }, +#endif #ifdef HAVE_ENCODER_DELTA { .id = LZMA_FILTER_DELTA, @@ -161,6 +193,16 @@ encoder_find(lzma_vli id) } +// lzma_filter_coder begins with the same members as lzma_filter_encoder. +// This function is a wrapper with a type that is compatible with the +// typedef of lzma_filter_find in filter_common.h. +static const lzma_filter_coder * +coder_find(lzma_vli id) +{ + return (const lzma_filter_coder *)encoder_find(id); +} + + extern LZMA_API(lzma_bool) lzma_filter_encoder_is_supported(lzma_vli id) { @@ -197,18 +239,18 @@ lzma_filters_update(lzma_stream *strm, const lzma_filter *filters) extern lzma_ret lzma_raw_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, - const lzma_filter *options) + const lzma_filter *filters) { return lzma_raw_coder_init(next, allocator, - options, (lzma_filter_find)(&encoder_find), true); + filters, &coder_find, true); } extern LZMA_API(lzma_ret) -lzma_raw_encoder(lzma_stream *strm, const lzma_filter *options) +lzma_raw_encoder(lzma_stream *strm, const lzma_filter *filters) { - lzma_next_strm_init(lzma_raw_coder_init, strm, options, - (lzma_filter_find)(&encoder_find), true); + lzma_next_strm_init(lzma_raw_coder_init, strm, filters, + &coder_find, true); strm->internal->supported_actions[LZMA_RUN] = true; strm->internal->supported_actions[LZMA_SYNC_FLUSH] = true; @@ -221,31 +263,33 @@ lzma_raw_encoder(lzma_stream *strm, const lzma_filter *options) extern LZMA_API(uint64_t) lzma_raw_encoder_memusage(const lzma_filter *filters) { - return lzma_raw_coder_memusage( - (lzma_filter_find)(&encoder_find), filters); + return lzma_raw_coder_memusage(&coder_find, filters); } -extern uint64_t +extern LZMA_API(uint64_t) lzma_mt_block_size(const lzma_filter *filters) { + if (filters == NULL) + return UINT64_MAX; + uint64_t max = 0; for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) { const lzma_filter_encoder *const fe = encoder_find(filters[i].id); + if (fe == NULL) + return UINT64_MAX; + if (fe->block_size != NULL) { const uint64_t size = fe->block_size(filters[i].options); - if (size == 0) - return 0; - if (size > max) max = size; } } - return max; + return max == 0 ? UINT64_MAX : max; } diff --git a/Utilities/cmliblzma/liblzma/common/filter_encoder.h b/Utilities/cmliblzma/liblzma/common/filter_encoder.h index f1d5683fe79..88f2dafa43b 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_encoder.h +++ b/Utilities/cmliblzma/liblzma/common/filter_encoder.h @@ -1,13 +1,12 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // -/// \file filter_encoder.c +/// \file filter_encoder.h /// \brief Filter ID mapping to filter-specific functions // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_FILTER_ENCODER_H @@ -16,10 +15,6 @@ #include "common.h" -// FIXME: Might become a part of the public API. -extern uint64_t lzma_mt_block_size(const lzma_filter *filters); - - extern lzma_ret lzma_raw_encoder_init( lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter *filters); diff --git a/Utilities/cmliblzma/liblzma/common/filter_flags_decoder.c b/Utilities/cmliblzma/liblzma/common/filter_flags_decoder.c index ddfb085943d..0f5d204d474 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_flags_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/filter_flags_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_flags_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_decoder.h" diff --git a/Utilities/cmliblzma/liblzma/common/filter_flags_encoder.c b/Utilities/cmliblzma/liblzma/common/filter_flags_encoder.c index b57b9fd80b0..e1d65884fb0 100644 --- a/Utilities/cmliblzma/liblzma/common/filter_flags_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/filter_flags_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file filter_flags_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_encoder.h" diff --git a/Utilities/cmliblzma/liblzma/common/hardware_cputhreads.c b/Utilities/cmliblzma/liblzma/common/hardware_cputhreads.c index f468366a604..4ce852b42c3 100644 --- a/Utilities/cmliblzma/liblzma/common/hardware_cputhreads.c +++ b/Utilities/cmliblzma/liblzma/common/hardware_cputhreads.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file hardware_cputhreads.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" @@ -15,6 +14,18 @@ #include "tuklib_cpucores.h" +#ifdef HAVE_SYMBOL_VERSIONS_LINUX +// This is for compatibility with binaries linked against liblzma that +// has been patched with xz-5.2.2-compat-libs.patch from RHEL/CentOS 7. +LZMA_SYMVER_API("lzma_cputhreads@XZ_5.2.2", + uint32_t, lzma_cputhreads_522)(void) lzma_nothrow + __attribute__((__alias__("lzma_cputhreads_52"))); + +LZMA_SYMVER_API("lzma_cputhreads@@XZ_5.2", + uint32_t, lzma_cputhreads_52)(void) lzma_nothrow; + +#define lzma_cputhreads lzma_cputhreads_52 +#endif extern LZMA_API(uint32_t) lzma_cputhreads(void) { diff --git a/Utilities/cmliblzma/liblzma/common/hardware_physmem.c b/Utilities/cmliblzma/liblzma/common/hardware_physmem.c index a2bbbe29d4b..1bc34864e84 100644 --- a/Utilities/cmliblzma/liblzma/common/hardware_physmem.c +++ b/Utilities/cmliblzma/liblzma/common/hardware_physmem.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file hardware_physmem.c @@ -5,9 +7,6 @@ // // Author: Jonathan Nieder // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" diff --git a/Utilities/cmliblzma/liblzma/common/index.c b/Utilities/cmliblzma/liblzma/common/index.c index 4c463ec001c..f823c035008 100644 --- a/Utilities/cmliblzma/liblzma/common/index.c +++ b/Utilities/cmliblzma/liblzma/common/index.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file index.c @@ -5,11 +7,9 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// +#include "common.h" #include "index.h" #include "stream_flags_common.h" @@ -659,6 +659,16 @@ lzma_index_append(lzma_index *i, const lzma_allocator *allocator, const uint32_t index_list_size_add = lzma_vli_size(unpadded_size) + lzma_vli_size(uncompressed_size); + // Check that uncompressed size will not overflow. + if (uncompressed_base + uncompressed_size > LZMA_VLI_MAX) + return LZMA_DATA_ERROR; + + // Check that the new unpadded sum will not overflow. This is + // checked again in index_file_size(), but the unpadded sum is + // passed to vli_ceil4() which expects a valid lzma_vli value. + if (compressed_base + unpadded_size > UNPADDED_SIZE_MAX) + return LZMA_DATA_ERROR; + // Check that the file size will stay within limits. if (index_file_size(s->node.compressed_base, compressed_base + unpadded_size, s->record_count + 1, @@ -770,6 +780,9 @@ extern LZMA_API(lzma_ret) lzma_index_cat(lzma_index *restrict dest, lzma_index *restrict src, const lzma_allocator *allocator) { + if (dest == NULL || src == NULL) + return LZMA_PROG_ERROR; + const lzma_vli dest_file_size = lzma_index_file_size(dest); // Check that we don't exceed the file size limits. @@ -838,6 +851,11 @@ lzma_index_cat(lzma_index *restrict dest, lzma_index *restrict src, } } + // dest->checks includes the check types of all except the last Stream + // in dest. Set the bit for the check type of the last Stream now so + // that it won't get lost when Stream(s) from src are appended to dest. + dest->checks = lzma_index_checks(dest); + // Add all the Streams from src to dest. Update the base offsets // of each Stream from src. const index_cat_info info = { @@ -854,7 +872,7 @@ lzma_index_cat(lzma_index *restrict dest, lzma_index *restrict src, dest->total_size += src->total_size; dest->record_count += src->record_count; dest->index_list_size += src->index_list_size; - dest->checks = lzma_index_checks(dest) | src->checks; + dest->checks |= src->checks; // There's nothing else left in src than the base structure. lzma_free(src, allocator); @@ -1229,7 +1247,7 @@ lzma_index_iter_locate(lzma_index_iter *iter, lzma_vli target) // Use binary search to locate the exact Record. It is the first // Record whose uncompressed_sum is greater than target. - // This is because we want the rightmost Record that fullfills the + // This is because we want the rightmost Record that fulfills the // search criterion. It is possible that there are empty Blocks; // we don't want to return them. size_t left = 0; diff --git a/Utilities/cmliblzma/liblzma/common/index.h b/Utilities/cmliblzma/liblzma/common/index.h index 64e97247dd3..007e1188f25 100644 --- a/Utilities/cmliblzma/liblzma/common/index.h +++ b/Utilities/cmliblzma/liblzma/common/index.h @@ -1,20 +1,24 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file index.h /// \brief Handling of Index +/// \note This header file does not include common.h or lzma.h because +/// this file is needed by both liblzma internally and by the +/// tests. Including common.h will include and define many things +/// the tests do not need and prevents issues with header file +/// include order. This way, if lzma.h or common.h are not +/// included before this file it will break on every OS instead +/// of causing more subtle errors. // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_INDEX_H #define LZMA_INDEX_H -#include "common.h" - /// Minimum Unpadded Size #define UNPADDED_SIZE_MIN LZMA_VLI_C(5) @@ -22,6 +26,9 @@ /// Maximum Unpadded Size #define UNPADDED_SIZE_MAX (LZMA_VLI_MAX & ~LZMA_VLI_C(3)) +/// Index Indicator based on xz specification +#define INDEX_INDICATOR 0 + /// Get the size of the Index Padding field. This is needed by Index encoder /// and decoder, but applications should have no use for this. @@ -38,7 +45,7 @@ extern void lzma_index_prealloc(lzma_index *i, lzma_vli records); static inline lzma_vli vli_ceil4(lzma_vli vli) { - assert(vli <= LZMA_VLI_MAX); + assert(vli <= UNPADDED_SIZE_MAX); return (vli + 3) & ~LZMA_VLI_C(3); } diff --git a/Utilities/cmliblzma/liblzma/common/index_decoder.c b/Utilities/cmliblzma/liblzma/common/index_decoder.c index cc07a1b8c53..4bcb3069211 100644 --- a/Utilities/cmliblzma/liblzma/common/index_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/index_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file index_decoder.c @@ -5,12 +7,9 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// -#include "index.h" +#include "index_decoder.h" #include "check.h" @@ -80,7 +79,7 @@ index_decode(void *coder_ptr, const lzma_allocator *allocator, // format". One could argue that the application should // verify the Index Indicator before trying to decode the // Index, but well, I suppose it is simpler this way. - if (in[(*in_pos)++] != 0x00) + if (in[(*in_pos)++] != INDEX_INDICATOR) return LZMA_DATA_ERROR; coder->sequence = SEQ_COUNT; @@ -180,8 +179,11 @@ index_decode(void *coder_ptr, const lzma_allocator *allocator, return LZMA_OK; if (((coder->crc32 >> (coder->pos * 8)) & 0xFF) - != in[(*in_pos)++]) + != in[(*in_pos)++]) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return LZMA_DATA_ERROR; +#endif + } } while (++coder->pos < 4); @@ -200,9 +202,16 @@ index_decode(void *coder_ptr, const lzma_allocator *allocator, } out: - // Update the CRC32, - coder->crc32 = lzma_crc32(in + in_start, - *in_pos - in_start, coder->crc32); + // Update the CRC32. + // + // Avoid null pointer + 0 (undefined behavior) in "in + in_start". + // In such a case we had no input and thus in_used == 0. + { + const size_t in_used = *in_pos - in_start; + if (in_used > 0) + coder->crc32 = lzma_crc32(in + in_start, + in_used, coder->crc32); + } return ret; } @@ -265,11 +274,11 @@ index_decoder_reset(lzma_index_coder *coder, const lzma_allocator *allocator, } -static lzma_ret -index_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, +extern lzma_ret +lzma_index_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, lzma_index **i, uint64_t memlimit) { - lzma_next_coder_init(&index_decoder_init, next, allocator); + lzma_next_coder_init(&lzma_index_decoder_init, next, allocator); if (i == NULL) return LZMA_PROG_ERROR; @@ -296,7 +305,13 @@ index_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, extern LZMA_API(lzma_ret) lzma_index_decoder(lzma_stream *strm, lzma_index **i, uint64_t memlimit) { - lzma_next_strm_init(index_decoder_init, strm, i, memlimit); + // If i isn't NULL, *i must always be initialized due to + // the wording in the API docs. This way it is initialized + // if we return LZMA_PROG_ERROR due to strm == NULL. + if (i != NULL) + *i = NULL; + + lzma_next_strm_init(lzma_index_decoder_init, strm, i, memlimit); strm->internal->supported_actions[LZMA_RUN] = true; strm->internal->supported_actions[LZMA_FINISH] = true; @@ -310,6 +325,11 @@ lzma_index_buffer_decode(lzma_index **i, uint64_t *memlimit, const lzma_allocator *allocator, const uint8_t *in, size_t *in_pos, size_t in_size) { + // If i isn't NULL, *i must always be initialized due to + // the wording in the API docs. + if (i != NULL) + *i = NULL; + // Sanity checks if (i == NULL || memlimit == NULL || in == NULL || in_pos == NULL || *in_pos > in_size) diff --git a/Utilities/cmliblzma/liblzma/common/index_decoder.h b/Utilities/cmliblzma/liblzma/common/index_decoder.h new file mode 100644 index 00000000000..5351d2f0dfa --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/index_decoder.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file index_decoder.h +/// \brief Decodes the Index field +// +// Author: Lasse Collin +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef LZMA_INDEX_DECODER_H +#define LZMA_INDEX_DECODER_H + +#include "common.h" +#include "index.h" + + +extern lzma_ret lzma_index_decoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + lzma_index **i, uint64_t memlimit); + + +#endif diff --git a/Utilities/cmliblzma/liblzma/common/index_encoder.c b/Utilities/cmliblzma/liblzma/common/index_encoder.c index 5e822cb465b..20dc6f4b30c 100644 --- a/Utilities/cmliblzma/liblzma/common/index_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/index_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file index_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "index_encoder.h" @@ -65,7 +64,7 @@ index_encode(void *coder_ptr, while (*out_pos < out_size) switch (coder->sequence) { case SEQ_INDICATOR: - out[*out_pos] = 0x00; + out[*out_pos] = INDEX_INDICATOR; ++*out_pos; coder->sequence = SEQ_COUNT; break; @@ -153,8 +152,15 @@ index_encode(void *coder_ptr, out: // Update the CRC32. - coder->crc32 = lzma_crc32(out + out_start, - *out_pos - out_start, coder->crc32); + // + // Avoid null pointer + 0 (undefined behavior) in "out + out_start". + // In such a case we had no input and thus out_used == 0. + { + const size_t out_used = *out_pos - out_start; + if (out_used > 0) + coder->crc32 = lzma_crc32(out + out_start, + out_used, coder->crc32); + } return ret; } diff --git a/Utilities/cmliblzma/liblzma/common/index_encoder.h b/Utilities/cmliblzma/liblzma/common/index_encoder.h index 4d55cd10478..29ba1106696 100644 --- a/Utilities/cmliblzma/liblzma/common/index_encoder.h +++ b/Utilities/cmliblzma/liblzma/common/index_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file index_encoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_INDEX_ENCODER_H diff --git a/Utilities/cmliblzma/liblzma/common/index_hash.c b/Utilities/cmliblzma/liblzma/common/index_hash.c index d7a0344b76c..caa5967ca49 100644 --- a/Utilities/cmliblzma/liblzma/common/index_hash.c +++ b/Utilities/cmliblzma/liblzma/common/index_hash.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file index_hash.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" @@ -122,7 +121,7 @@ lzma_index_hash_size(const lzma_index_hash *index_hash) /// Updates the sizes and the hash without any validation. -static lzma_ret +static void hash_append(lzma_index_hash_info *info, lzma_vli unpadded_size, lzma_vli uncompressed_size) { @@ -136,7 +135,7 @@ hash_append(lzma_index_hash_info *info, lzma_vli unpadded_size, lzma_check_update(&info->check, LZMA_CHECK_BEST, (const uint8_t *)(sizes), sizeof(sizes)); - return LZMA_OK; + return; } @@ -145,15 +144,14 @@ lzma_index_hash_append(lzma_index_hash *index_hash, lzma_vli unpadded_size, lzma_vli uncompressed_size) { // Validate the arguments. - if (index_hash->sequence != SEQ_BLOCK + if (index_hash == NULL || index_hash->sequence != SEQ_BLOCK || unpadded_size < UNPADDED_SIZE_MIN || unpadded_size > UNPADDED_SIZE_MAX || uncompressed_size > LZMA_VLI_MAX) return LZMA_PROG_ERROR; // Update the hash. - return_if_error(hash_append(&index_hash->blocks, - unpadded_size, uncompressed_size)); + hash_append(&index_hash->blocks, unpadded_size, uncompressed_size); // Validate the properties of *info are still in allowed limits. if (index_hash->blocks.blocks_size > LZMA_VLI_MAX @@ -191,7 +189,7 @@ lzma_index_hash_decode(lzma_index_hash *index_hash, const uint8_t *in, switch (index_hash->sequence) { case SEQ_BLOCK: // Check the Index Indicator is present. - if (in[(*in_pos)++] != 0x00) + if (in[(*in_pos)++] != INDEX_INDICATOR) return LZMA_DATA_ERROR; index_hash->sequence = SEQ_COUNT; @@ -239,9 +237,9 @@ lzma_index_hash_decode(lzma_index_hash *index_hash, const uint8_t *in, index_hash->sequence = SEQ_UNCOMPRESSED; } else { // Update the hash. - return_if_error(hash_append(&index_hash->records, + hash_append(&index_hash->records, index_hash->unpadded_size, - index_hash->uncompressed_size)); + index_hash->uncompressed_size); // Verify that we don't go over the known sizes. Note // that this validation is simpler than the one used @@ -313,8 +311,11 @@ lzma_index_hash_decode(lzma_index_hash *index_hash, const uint8_t *in, return LZMA_OK; if (((index_hash->crc32 >> (index_hash->pos * 8)) - & 0xFF) != in[(*in_pos)++]) + & 0xFF) != in[(*in_pos)++]) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return LZMA_DATA_ERROR; +#endif + } } while (++index_hash->pos < 4); @@ -326,9 +327,16 @@ lzma_index_hash_decode(lzma_index_hash *index_hash, const uint8_t *in, } out: - // Update the CRC32, - index_hash->crc32 = lzma_crc32(in + in_start, - *in_pos - in_start, index_hash->crc32); + // Update the CRC32. + // + // Avoid null pointer + 0 (undefined behavior) in "in + in_start". + // In such a case we had no input and thus in_used == 0. + { + const size_t in_used = *in_pos - in_start; + if (in_used > 0) + index_hash->crc32 = lzma_crc32(in + in_start, + in_used, index_hash->crc32); + } return ret; } diff --git a/Utilities/cmliblzma/liblzma/common/lzip_decoder.c b/Utilities/cmliblzma/liblzma/common/lzip_decoder.c new file mode 100644 index 00000000000..651a0ae712c --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/lzip_decoder.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file lzip_decoder.c +/// \brief Decodes .lz (lzip) files +// +// Author: Michał Górny +// Lasse Collin +// +/////////////////////////////////////////////////////////////////////////////// + +#include "lzip_decoder.h" +#include "lzma_decoder.h" +#include "check.h" + + +// .lz format version 0 lacks the 64-bit Member size field in the footer. +#define LZIP_V0_FOOTER_SIZE 12 +#define LZIP_V1_FOOTER_SIZE 20 +#define LZIP_FOOTER_SIZE_MAX LZIP_V1_FOOTER_SIZE + +// lc/lp/pb are hardcoded in the .lz format. +#define LZIP_LC 3 +#define LZIP_LP 0 +#define LZIP_PB 2 + + +typedef struct { + enum { + SEQ_ID_STRING, + SEQ_VERSION, + SEQ_DICT_SIZE, + SEQ_CODER_INIT, + SEQ_LZMA_STREAM, + SEQ_MEMBER_FOOTER, + } sequence; + + /// .lz member format version + uint32_t version; + + /// CRC32 of the uncompressed data in the .lz member + uint32_t crc32; + + /// Uncompressed size of the .lz member + uint64_t uncompressed_size; + + /// Compressed size of the .lz member + uint64_t member_size; + + /// Memory usage limit + uint64_t memlimit; + + /// Amount of memory actually needed + uint64_t memusage; + + /// If true, LZMA_GET_CHECK is returned after decoding the header + /// fields. As all files use CRC32 this is redundant but it's + /// implemented anyway since the initialization functions supports + /// all other flags in addition to LZMA_TELL_ANY_CHECK. + bool tell_any_check; + + /// If true, we won't calculate or verify the CRC32 of + /// the uncompressed data. + bool ignore_check; + + /// If true, we will decode concatenated .lz members and stop if + /// non-.lz data is seen after at least one member has been + /// successfully decoded. + bool concatenated; + + /// When decoding concatenated .lz members, this is true as long as + /// we are decoding the first .lz member. This is needed to avoid + /// incorrect LZMA_FORMAT_ERROR in case there is non-.lz data at + /// the end of the file. + bool first_member; + + /// Reading position in the header and footer fields + size_t pos; + + /// Buffer to hold the .lz footer fields + uint8_t buffer[LZIP_FOOTER_SIZE_MAX]; + + /// Options decoded from the .lz header that needed to initialize + /// the LZMA1 decoder. + lzma_options_lzma options; + + /// LZMA1 decoder + lzma_next_coder lzma_decoder; + +} lzma_lzip_coder; + + +static lzma_ret +lzip_decode(void *coder_ptr, const lzma_allocator *allocator, + const uint8_t *restrict in, size_t *restrict in_pos, + size_t in_size, uint8_t *restrict out, + size_t *restrict out_pos, size_t out_size, lzma_action action) +{ + lzma_lzip_coder *coder = coder_ptr; + + while (true) + switch (coder->sequence) { + case SEQ_ID_STRING: { + // The "ID string" or magic bytes are "LZIP" in US-ASCII. + const uint8_t lzip_id_string[4] = { 0x4C, 0x5A, 0x49, 0x50 }; + + while (coder->pos < sizeof(lzip_id_string)) { + if (*in_pos >= in_size) { + // If we are on the 2nd+ concatenated member + // and the input ends before we can read + // the magic bytes, we discard the bytes that + // were already read (up to 3) and finish. + // See the reasoning below. + return !coder->first_member + && action == LZMA_FINISH + ? LZMA_STREAM_END : LZMA_OK; + } + + if (in[*in_pos] != lzip_id_string[coder->pos]) { + // The .lz format allows putting non-.lz data + // at the end of the file. If we have seen + // at least one valid .lz member already, + // then we won't consume the byte at *in_pos + // and will return LZMA_STREAM_END. This way + // apps can easily locate and read the non-.lz + // data after the .lz member(s). + // + // NOTE: If the first 1-3 bytes of the non-.lz + // data match the .lz ID string then the first + // 1-3 bytes of the junk will get ignored by + // us. If apps want to properly locate the + // trailing data they must ensure that the + // first byte of their custom data isn't the + // same as the first byte of .lz ID string. + // With the liblzma API we cannot rewind the + // input position across calls to lzma_code(). + return !coder->first_member + ? LZMA_STREAM_END : LZMA_FORMAT_ERROR; + } + + ++*in_pos; + ++coder->pos; + } + + coder->pos = 0; + + coder->crc32 = 0; + coder->uncompressed_size = 0; + coder->member_size = sizeof(lzip_id_string); + + coder->sequence = SEQ_VERSION; + } + + // Fall through + + case SEQ_VERSION: + if (*in_pos >= in_size) + return LZMA_OK; + + coder->version = in[(*in_pos)++]; + + // We support version 0 and unextended version 1. + if (coder->version > 1) + return LZMA_OPTIONS_ERROR; + + ++coder->member_size; + coder->sequence = SEQ_DICT_SIZE; + + // .lz versions 0 and 1 use CRC32 as the integrity check + // so if the application wanted to know that + // (LZMA_TELL_ANY_CHECK) we can tell it now. + if (coder->tell_any_check) + return LZMA_GET_CHECK; + + // Fall through + + case SEQ_DICT_SIZE: { + if (*in_pos >= in_size) + return LZMA_OK; + + const uint32_t ds = in[(*in_pos)++]; + ++coder->member_size; + + // The five lowest bits are for the base-2 logarithm of + // the dictionary size and the highest three bits are + // the fractional part (0/16 to 7/16) that will be + // subtracted to get the final value. + // + // For example, with 0xB5: + // b2log = 21 + // fracnum = 5 + // dict_size = 2^21 - 2^21 * 5 / 16 = 1408 KiB + const uint32_t b2log = ds & 0x1F; + const uint32_t fracnum = ds >> 5; + + // The format versions 0 and 1 allow dictionary size in the + // range [4 KiB, 512 MiB]. + if (b2log < 12 || b2log > 29 || (b2log == 12 && fracnum > 0)) + return LZMA_DATA_ERROR; + + // 2^[b2log] - 2^[b2log] * [fracnum] / 16 + // = 2^[b2log] - [fracnum] * 2^([b2log] - 4) + coder->options.dict_size = (UINT32_C(1) << b2log) + - (fracnum << (b2log - 4)); + + assert(coder->options.dict_size >= 4096); + assert(coder->options.dict_size <= (UINT32_C(512) << 20)); + + coder->options.preset_dict = NULL; + coder->options.lc = LZIP_LC; + coder->options.lp = LZIP_LP; + coder->options.pb = LZIP_PB; + + // Calculate the memory usage. + coder->memusage = lzma_lzma_decoder_memusage(&coder->options) + + LZMA_MEMUSAGE_BASE; + + // Initialization is a separate step because if we return + // LZMA_MEMLIMIT_ERROR we need to be able to restart after + // the memlimit has been increased. + coder->sequence = SEQ_CODER_INIT; + } + + // Fall through + + case SEQ_CODER_INIT: { + if (coder->memusage > coder->memlimit) + return LZMA_MEMLIMIT_ERROR; + + const lzma_filter_info filters[2] = { + { + .id = LZMA_FILTER_LZMA1, + .init = &lzma_lzma_decoder_init, + .options = &coder->options, + }, { + .init = NULL, + } + }; + + return_if_error(lzma_next_filter_init(&coder->lzma_decoder, + allocator, filters)); + + coder->crc32 = 0; + coder->sequence = SEQ_LZMA_STREAM; + } + + // Fall through + + case SEQ_LZMA_STREAM: { + const size_t in_start = *in_pos; + const size_t out_start = *out_pos; + + const lzma_ret ret = coder->lzma_decoder.code( + coder->lzma_decoder.coder, allocator, + in, in_pos, in_size, out, out_pos, out_size, + action); + + const size_t out_used = *out_pos - out_start; + + coder->member_size += *in_pos - in_start; + coder->uncompressed_size += out_used; + + // Don't update the CRC32 if the integrity check will be + // ignored or if there was no new output. The latter is + // important in case out == NULL to avoid null pointer + 0 + // which is undefined behavior. + if (!coder->ignore_check && out_used > 0) + coder->crc32 = lzma_crc32(out + out_start, out_used, + coder->crc32); + + if (ret != LZMA_STREAM_END) + return ret; + + coder->sequence = SEQ_MEMBER_FOOTER; + } + + // Fall through + + case SEQ_MEMBER_FOOTER: { + // The footer of .lz version 0 lacks the Member size field. + // This is the only difference between version 0 and + // unextended version 1 formats. + const size_t footer_size = coder->version == 0 + ? LZIP_V0_FOOTER_SIZE + : LZIP_V1_FOOTER_SIZE; + + // Copy the CRC32, Data size, and Member size fields to + // the internal buffer. + lzma_bufcpy(in, in_pos, in_size, coder->buffer, &coder->pos, + footer_size); + + // Return if we didn't get the whole footer yet. + if (coder->pos < footer_size) + return LZMA_OK; + + coder->pos = 0; + coder->member_size += footer_size; + + // Check that the footer fields match the observed data. + if (!coder->ignore_check + && coder->crc32 != read32le(&coder->buffer[0])) + return LZMA_DATA_ERROR; + + if (coder->uncompressed_size != read64le(&coder->buffer[4])) + return LZMA_DATA_ERROR; + + if (coder->version > 0) { + // .lz version 0 has no Member size field. + if (coder->member_size != read64le(&coder->buffer[12])) + return LZMA_DATA_ERROR; + } + + // Decoding is finished if we weren't requested to decode + // more than one .lz member. + if (!coder->concatenated) + return LZMA_STREAM_END; + + coder->first_member = false; + coder->sequence = SEQ_ID_STRING; + break; + } + + default: + assert(0); + return LZMA_PROG_ERROR; + } + + // Never reached +} + + +static void +lzip_decoder_end(void *coder_ptr, const lzma_allocator *allocator) +{ + lzma_lzip_coder *coder = coder_ptr; + lzma_next_end(&coder->lzma_decoder, allocator); + lzma_free(coder, allocator); + return; +} + + +static lzma_check +lzip_decoder_get_check(const void *coder_ptr lzma_attribute((__unused__))) +{ + return LZMA_CHECK_CRC32; +} + + +static lzma_ret +lzip_decoder_memconfig(void *coder_ptr, uint64_t *memusage, + uint64_t *old_memlimit, uint64_t new_memlimit) +{ + lzma_lzip_coder *coder = coder_ptr; + + *memusage = coder->memusage; + *old_memlimit = coder->memlimit; + + if (new_memlimit != 0) { + if (new_memlimit < coder->memusage) + return LZMA_MEMLIMIT_ERROR; + + coder->memlimit = new_memlimit; + } + + return LZMA_OK; +} + + +extern lzma_ret +lzma_lzip_decoder_init( + lzma_next_coder *next, const lzma_allocator *allocator, + uint64_t memlimit, uint32_t flags) +{ + lzma_next_coder_init(&lzma_lzip_decoder_init, next, allocator); + + if (flags & ~LZMA_SUPPORTED_FLAGS) + return LZMA_OPTIONS_ERROR; + + lzma_lzip_coder *coder = next->coder; + if (coder == NULL) { + coder = lzma_alloc(sizeof(lzma_lzip_coder), allocator); + if (coder == NULL) + return LZMA_MEM_ERROR; + + next->coder = coder; + next->code = &lzip_decode; + next->end = &lzip_decoder_end; + next->get_check = &lzip_decoder_get_check; + next->memconfig = &lzip_decoder_memconfig; + + coder->lzma_decoder = LZMA_NEXT_CODER_INIT; + } + + coder->sequence = SEQ_ID_STRING; + coder->memlimit = my_max(1, memlimit); + coder->memusage = LZMA_MEMUSAGE_BASE; + coder->tell_any_check = (flags & LZMA_TELL_ANY_CHECK) != 0; + coder->ignore_check = (flags & LZMA_IGNORE_CHECK) != 0; + coder->concatenated = (flags & LZMA_CONCATENATED) != 0; + coder->first_member = true; + coder->pos = 0; + + return LZMA_OK; +} + + +extern LZMA_API(lzma_ret) +lzma_lzip_decoder(lzma_stream *strm, uint64_t memlimit, uint32_t flags) +{ + lzma_next_strm_init(lzma_lzip_decoder_init, strm, memlimit, flags); + + strm->internal->supported_actions[LZMA_RUN] = true; + strm->internal->supported_actions[LZMA_FINISH] = true; + + return LZMA_OK; +} diff --git a/Utilities/cmliblzma/liblzma/common/lzip_decoder.h b/Utilities/cmliblzma/liblzma/common/lzip_decoder.h new file mode 100644 index 00000000000..0e1f7bebd45 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/lzip_decoder.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file lzip_decoder.h +/// \brief Decodes .lz (lzip) files +// +// Author: Michał Górny +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef LZMA_LZIP_DECODER_H +#define LZMA_LZIP_DECODER_H + +#include "common.h" + +extern lzma_ret lzma_lzip_decoder_init( + lzma_next_coder *next, const lzma_allocator *allocator, + uint64_t memlimit, uint32_t flags); + +#endif diff --git a/Utilities/cmliblzma/liblzma/common/memcmplen.h b/Utilities/cmliblzma/liblzma/common/memcmplen.h index dcfd8d6f89d..394a4856dd6 100644 --- a/Utilities/cmliblzma/liblzma/common/memcmplen.h +++ b/Utilities/cmliblzma/liblzma/common/memcmplen.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file memcmplen.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_MEMCMPLEN_H @@ -19,6 +18,17 @@ # include #endif +// Only include if it is needed. The header is only needed +// on Windows when using an MSVC compatible compiler. The Intel compiler +// can use the intrinsics without the header file. +#if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ + && defined(_MSC_VER) \ + && (defined(_M_X64) \ + || defined(_M_ARM64) || defined(_M_ARM64EC)) \ + && !defined(__INTEL_COMPILER) +# include +#endif + /// Find out how many equal bytes the two buffers have. /// @@ -39,7 +49,7 @@ /// It's rounded up to 2^n. This extra amount needs to be /// allocated in the buffers being used. It needs to be /// initialized too to keep Valgrind quiet. -static inline uint32_t lzma_attribute((__always_inline__)) +static lzma_always_inline uint32_t lzma_memcmplen(const uint8_t *buf1, const uint8_t *buf2, uint32_t len, uint32_t limit) { @@ -47,27 +57,40 @@ lzma_memcmplen(const uint8_t *buf1, const uint8_t *buf2, assert(limit <= UINT32_MAX / 2); #if defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ - && ((TUKLIB_GNUC_REQ(3, 4) && defined(__x86_64__)) \ + && (((TUKLIB_GNUC_REQ(3, 4) || defined(__clang__)) \ + && (defined(__x86_64__) \ + || defined(__aarch64__))) \ || (defined(__INTEL_COMPILER) && defined(__x86_64__)) \ || (defined(__INTEL_COMPILER) && defined(_M_X64)) \ - || (defined(_MSC_VER) && defined(_M_X64))) - // NOTE: This will use 64-bit unaligned access which - // TUKLIB_FAST_UNALIGNED_ACCESS wasn't meant to permit, but - // it's convenient here at least as long as it's x86-64 only. + || (defined(_MSC_VER) && (defined(_M_X64) \ + || defined(_M_ARM64) || defined(_M_ARM64EC)))) + // This is only for x86-64 and ARM64 for now. This might be fine on + // other 64-bit processors too. On big endian one should use xor + // instead of subtraction and switch to __builtin_clzll(). + // + // Reasons to use subtraction instead of xor: + // + // - On some x86-64 processors (Intel Sandy Bridge to Tiger Lake), + // sub+jz and sub+jnz can be fused but xor+jz or xor+jnz cannot. + // Thus using subtraction has potential to be a tiny amount faster + // since the code checks if the quotient is non-zero. + // + // - Some processors (Intel Pentium 4) used to have more ALU + // resources for add/sub instructions than and/or/xor. // - // I keep this x86-64 only for now since that's where I know this - // to be a good method. This may be fine on other 64-bit CPUs too. - // On big endian one should use xor instead of subtraction and switch - // to __builtin_clzll(). + // The processor info is based on Agner Fog's microarchitecture.pdf + // version 2023-05-26. https://www.agner.org/optimize/ #define LZMA_MEMCMPLEN_EXTRA 8 while (len < limit) { const uint64_t x = read64ne(buf1 + len) - read64ne(buf2 + len); if (x != 0) { -# if defined(_M_X64) // MSVC or Intel C compiler on Windows + // MSVC or Intel C compiler on Windows +# if defined(_MSC_VER) || defined(__INTEL_COMPILER) unsigned long tmp; _BitScanForward64(&tmp, x); len += (uint32_t)tmp >> 3; -# else // GCC, clang, or Intel C compiler + // GCC, Clang, or Intel C compiler +# else len += (uint32_t)__builtin_ctzll(x) >> 3; # endif return my_min(len, limit); @@ -80,12 +103,12 @@ lzma_memcmplen(const uint8_t *buf1, const uint8_t *buf2, #elif defined(TUKLIB_FAST_UNALIGNED_ACCESS) \ && defined(HAVE__MM_MOVEMASK_EPI8) \ - && ((defined(__GNUC__) && defined(__SSE2_MATH__)) \ - || (defined(__INTEL_COMPILER) && defined(__SSE2__)) \ + && (defined(__SSE2__) \ || (defined(_MSC_VER) && defined(_M_IX86_FP) \ && _M_IX86_FP >= 2)) - // NOTE: Like above, this will use 128-bit unaligned access which - // TUKLIB_FAST_UNALIGNED_ACCESS wasn't meant to permit. + // NOTE: This will use 128-bit unaligned access which + // TUKLIB_FAST_UNALIGNED_ACCESS wasn't meant to permit, + // but it's convenient here since this is x86-only. // // SSE2 version for 32-bit and 64-bit x86. On x86-64 the above // version is sometimes significantly faster and sometimes @@ -93,7 +116,8 @@ lzma_memcmplen(const uint8_t *buf1, const uint8_t *buf2, // version isn't used on x86-64. # define LZMA_MEMCMPLEN_EXTRA 16 while (len < limit) { - const uint32_t x = 0xFFFF ^ _mm_movemask_epi8(_mm_cmpeq_epi8( + const uint32_t x = 0xFFFF ^ (uint32_t)_mm_movemask_epi8( + _mm_cmpeq_epi8( _mm_loadu_si128((const __m128i *)(buf1 + len)), _mm_loadu_si128((const __m128i *)(buf2 + len)))); diff --git a/Utilities/cmliblzma/liblzma/common/microlzma_decoder.c b/Utilities/cmliblzma/liblzma/common/microlzma_decoder.c new file mode 100644 index 00000000000..882cb2c808d --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/microlzma_decoder.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file microlzma_decoder.c +/// \brief Decode MicroLZMA format +// +// Author: Lasse Collin +// +/////////////////////////////////////////////////////////////////////////////// + +#include "lzma_decoder.h" +#include "lz_decoder.h" + + +typedef struct { + /// LZMA1 decoder + lzma_next_coder lzma; + + /// Compressed size of the stream as given by the application. + /// This must be exactly correct. + /// + /// This will be decremented when input is read. + uint64_t comp_size; + + /// Uncompressed size of the stream as given by the application. + /// This may be less than the actual uncompressed size if + /// uncomp_size_is_exact is false. + /// + /// This will be decremented when output is produced. + lzma_vli uncomp_size; + + /// LZMA dictionary size as given by the application + uint32_t dict_size; + + /// If true, the exact uncompressed size is known. If false, + /// uncomp_size may be smaller than the real uncompressed size; + /// uncomp_size may never be bigger than the real uncompressed size. + bool uncomp_size_is_exact; + + /// True once the first byte of the MicroLZMA stream + /// has been processed. + bool props_decoded; +} lzma_microlzma_coder; + + +static lzma_ret +microlzma_decode(void *coder_ptr, const lzma_allocator *allocator, + const uint8_t *restrict in, size_t *restrict in_pos, + size_t in_size, uint8_t *restrict out, + size_t *restrict out_pos, size_t out_size, lzma_action action) +{ + lzma_microlzma_coder *coder = coder_ptr; + + // Remember the in start position so that we can update comp_size. + const size_t in_start = *in_pos; + + // Remember the out start position so that we can update uncomp_size. + const size_t out_start = *out_pos; + + // Limit the amount of input so that the decoder won't read more than + // comp_size. This is required when uncomp_size isn't exact because + // in that case the LZMA decoder will try to decode more input even + // when it has no output space (it can be looking for EOPM). + if (in_size - *in_pos > coder->comp_size) + in_size = *in_pos + (size_t)(coder->comp_size); + + // When the exact uncompressed size isn't known, we must limit + // the available output space to prevent the LZMA decoder from + // trying to decode too much. + if (!coder->uncomp_size_is_exact + && out_size - *out_pos > coder->uncomp_size) + out_size = *out_pos + (size_t)(coder->uncomp_size); + + if (!coder->props_decoded) { + // There must be at least one byte of input to decode + // the properties byte. + if (*in_pos >= in_size) + return LZMA_OK; + + lzma_options_lzma options = { + .dict_size = coder->dict_size, + .preset_dict = NULL, + .preset_dict_size = 0, + .ext_flags = 0, // EOPM not allowed when size is known + .ext_size_low = UINT32_MAX, // Unknown size by default + .ext_size_high = UINT32_MAX, + }; + + if (coder->uncomp_size_is_exact) + lzma_set_ext_size(options, coder->uncomp_size); + + // The properties are stored as bitwise-negation + // of the typical encoding. + if (lzma_lzma_lclppb_decode(&options, ~in[*in_pos])) + return LZMA_OPTIONS_ERROR; + + ++*in_pos; + + // Initialize the decoder. + lzma_filter_info filters[2] = { + { + .id = LZMA_FILTER_LZMA1EXT, + .init = &lzma_lzma_decoder_init, + .options = &options, + }, { + .init = NULL, + } + }; + + return_if_error(lzma_next_filter_init(&coder->lzma, + allocator, filters)); + + // Pass one dummy 0x00 byte to the LZMA decoder since that + // is what it expects the first byte to be. + const uint8_t dummy_in = 0; + size_t dummy_in_pos = 0; + if (coder->lzma.code(coder->lzma.coder, allocator, + &dummy_in, &dummy_in_pos, 1, + out, out_pos, out_size, LZMA_RUN) != LZMA_OK) + return LZMA_PROG_ERROR; + + assert(dummy_in_pos == 1); + coder->props_decoded = true; + } + + // The rest is normal LZMA decoding. + lzma_ret ret = coder->lzma.code(coder->lzma.coder, allocator, + in, in_pos, in_size, + out, out_pos, out_size, action); + + // Update the remaining compressed size. + assert(coder->comp_size >= *in_pos - in_start); + coder->comp_size -= *in_pos - in_start; + + if (coder->uncomp_size_is_exact) { + // After successful decompression of the complete stream + // the compressed size must match. + if (ret == LZMA_STREAM_END && coder->comp_size != 0) + ret = LZMA_DATA_ERROR; + } else { + // Update the amount of output remaining. + assert(coder->uncomp_size >= *out_pos - out_start); + coder->uncomp_size -= *out_pos - out_start; + + // - We must not get LZMA_STREAM_END because the stream + // shouldn't have EOPM. + // - We must use uncomp_size to determine when to + // return LZMA_STREAM_END. + if (ret == LZMA_STREAM_END) + ret = LZMA_DATA_ERROR; + else if (coder->uncomp_size == 0) + ret = LZMA_STREAM_END; + } + + return ret; +} + + +static void +microlzma_decoder_end(void *coder_ptr, const lzma_allocator *allocator) +{ + lzma_microlzma_coder *coder = coder_ptr; + lzma_next_end(&coder->lzma, allocator); + lzma_free(coder, allocator); + return; +} + + +static lzma_ret +microlzma_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, + uint64_t comp_size, + uint64_t uncomp_size, bool uncomp_size_is_exact, + uint32_t dict_size) +{ + lzma_next_coder_init(µlzma_decoder_init, next, allocator); + + lzma_microlzma_coder *coder = next->coder; + + if (coder == NULL) { + coder = lzma_alloc(sizeof(lzma_microlzma_coder), allocator); + if (coder == NULL) + return LZMA_MEM_ERROR; + + next->coder = coder; + next->code = µlzma_decode; + next->end = µlzma_decoder_end; + + coder->lzma = LZMA_NEXT_CODER_INIT; + } + + // The public API is uint64_t but the internal LZ decoder API uses + // lzma_vli. + if (uncomp_size > LZMA_VLI_MAX) + return LZMA_OPTIONS_ERROR; + + coder->comp_size = comp_size; + coder->uncomp_size = uncomp_size; + coder->uncomp_size_is_exact = uncomp_size_is_exact; + coder->dict_size = dict_size; + + coder->props_decoded = false; + + return LZMA_OK; +} + + +extern LZMA_API(lzma_ret) +lzma_microlzma_decoder(lzma_stream *strm, uint64_t comp_size, + uint64_t uncomp_size, lzma_bool uncomp_size_is_exact, + uint32_t dict_size) +{ + lzma_next_strm_init(microlzma_decoder_init, strm, comp_size, + uncomp_size, uncomp_size_is_exact, dict_size); + + strm->internal->supported_actions[LZMA_RUN] = true; + strm->internal->supported_actions[LZMA_FINISH] = true; + + return LZMA_OK; +} diff --git a/Utilities/cmliblzma/liblzma/common/microlzma_encoder.c b/Utilities/cmliblzma/liblzma/common/microlzma_encoder.c new file mode 100644 index 00000000000..45ec0b12f45 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/microlzma_encoder.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file microlzma_encoder.c +/// \brief Encode into MicroLZMA format +// +// Author: Lasse Collin +// +/////////////////////////////////////////////////////////////////////////////// + +#include "lzma_encoder.h" + + +typedef struct { + /// LZMA1 encoder + lzma_next_coder lzma; + + /// LZMA properties byte (lc/lp/pb) + uint8_t props; +} lzma_microlzma_coder; + + +static lzma_ret +microlzma_encode(void *coder_ptr, const lzma_allocator *allocator, + const uint8_t *restrict in, size_t *restrict in_pos, + size_t in_size, uint8_t *restrict out, + size_t *restrict out_pos, size_t out_size, lzma_action action) +{ + lzma_microlzma_coder *coder = coder_ptr; + + // Remember *out_pos so that we can overwrite the first byte with + // the LZMA properties byte. + const size_t out_start = *out_pos; + + // Remember *in_pos so that we can set it based on how many + // uncompressed bytes were actually encoded. + const size_t in_start = *in_pos; + + // Set the output size limit based on the available output space. + // We know that the encoder supports set_out_limit() so + // LZMA_OPTIONS_ERROR isn't possible. LZMA_BUF_ERROR is possible + // but lzma_code() has an assertion to not allow it to be returned + // from here and I don't want to change that for now, so + // LZMA_BUF_ERROR becomes LZMA_PROG_ERROR. + uint64_t uncomp_size; + if (coder->lzma.set_out_limit(coder->lzma.coder, + &uncomp_size, out_size - *out_pos) != LZMA_OK) + return LZMA_PROG_ERROR; + + // set_out_limit fails if this isn't true. + assert(out_size - *out_pos >= 6); + + // Encode as much as possible. + const lzma_ret ret = coder->lzma.code(coder->lzma.coder, allocator, + in, in_pos, in_size, out, out_pos, out_size, action); + + if (ret != LZMA_STREAM_END) { + if (ret == LZMA_OK) { + assert(0); + return LZMA_PROG_ERROR; + } + + return ret; + } + + // The first output byte is bitwise-negation of the properties byte. + // We know that there is space for this byte because set_out_limit + // and the actual encoding succeeded. + out[out_start] = (uint8_t)(~coder->props); + + // The LZMA encoder likely read more input than it was able to encode. + // Set *in_pos based on uncomp_size. + assert(uncomp_size <= in_size - in_start); + *in_pos = in_start + (size_t)(uncomp_size); + + return ret; +} + + +static void +microlzma_encoder_end(void *coder_ptr, const lzma_allocator *allocator) +{ + lzma_microlzma_coder *coder = coder_ptr; + lzma_next_end(&coder->lzma, allocator); + lzma_free(coder, allocator); + return; +} + + +static lzma_ret +microlzma_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, + const lzma_options_lzma *options) +{ + lzma_next_coder_init(µlzma_encoder_init, next, allocator); + + lzma_microlzma_coder *coder = next->coder; + + if (coder == NULL) { + coder = lzma_alloc(sizeof(lzma_microlzma_coder), allocator); + if (coder == NULL) + return LZMA_MEM_ERROR; + + next->coder = coder; + next->code = µlzma_encode; + next->end = µlzma_encoder_end; + + coder->lzma = LZMA_NEXT_CODER_INIT; + } + + // Encode the properties byte. Bitwise-negation of it will be the + // first output byte. + if (lzma_lzma_lclppb_encode(options, &coder->props)) + return LZMA_OPTIONS_ERROR; + + // Initialize the LZMA encoder. + const lzma_filter_info filters[2] = { + { + .id = LZMA_FILTER_LZMA1, + .init = &lzma_lzma_encoder_init, + .options = (void *)(options), + }, { + .init = NULL, + } + }; + + return lzma_next_filter_init(&coder->lzma, allocator, filters); +} + + +extern LZMA_API(lzma_ret) +lzma_microlzma_encoder(lzma_stream *strm, const lzma_options_lzma *options) +{ + lzma_next_strm_init(microlzma_encoder_init, strm, options); + + strm->internal->supported_actions[LZMA_FINISH] = true; + + return LZMA_OK; + +} diff --git a/Utilities/cmliblzma/liblzma/common/outqueue.c b/Utilities/cmliblzma/liblzma/common/outqueue.c index 2dc8a38d1be..eb018eb42b2 100644 --- a/Utilities/cmliblzma/liblzma/common/outqueue.c +++ b/Utilities/cmliblzma/liblzma/common/outqueue.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file outqueue.c @@ -5,92 +7,126 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "outqueue.h" -/// This is to ease integer overflow checking: We may allocate up to -/// 2 * LZMA_THREADS_MAX buffers and we need some extra memory for other -/// data structures (that's the second /2). -#define BUF_SIZE_MAX (UINT64_MAX / LZMA_THREADS_MAX / 2 / 2) +/// Get the maximum number of buffers that may be allocated based +/// on the number of threads. For now this is twice the number of threads. +/// It's a compromise between RAM usage and keeping the worker threads busy +/// when buffers finish out of order. +#define GET_BUFS_LIMIT(threads) (2 * (threads)) -static lzma_ret -get_options(uint64_t *bufs_alloc_size, uint32_t *bufs_count, - uint64_t buf_size_max, uint32_t threads) +extern uint64_t +lzma_outq_memusage(uint64_t buf_size_max, uint32_t threads) { - if (threads > LZMA_THREADS_MAX || buf_size_max > BUF_SIZE_MAX) - return LZMA_OPTIONS_ERROR; - - // The number of buffers is twice the number of threads. - // This wastes RAM but keeps the threads busy when buffers - // finish out of order. + // This is to ease integer overflow checking: We may allocate up to + // GET_BUFS_LIMIT(LZMA_THREADS_MAX) buffers and we need some extra + // memory for other data structures too (that's the /2). // - // NOTE: If this is changed, update BUF_SIZE_MAX too. - *bufs_count = threads * 2; - *bufs_alloc_size = *bufs_count * buf_size_max; + // lzma_outq_prealloc_buf() will still accept bigger buffers than this. + const uint64_t limit + = UINT64_MAX / GET_BUFS_LIMIT(LZMA_THREADS_MAX) / 2; - return LZMA_OK; + if (threads > LZMA_THREADS_MAX || buf_size_max > limit) + return UINT64_MAX; + + return GET_BUFS_LIMIT(threads) + * lzma_outq_outbuf_memusage(buf_size_max); } -extern uint64_t -lzma_outq_memusage(uint64_t buf_size_max, uint32_t threads) +static void +move_head_to_cache(lzma_outq *outq, const lzma_allocator *allocator) { - uint64_t bufs_alloc_size; - uint32_t bufs_count; + assert(outq->head != NULL); + assert(outq->tail != NULL); + assert(outq->bufs_in_use > 0); - if (get_options(&bufs_alloc_size, &bufs_count, buf_size_max, threads) - != LZMA_OK) - return UINT64_MAX; + lzma_outbuf *buf = outq->head; + outq->head = buf->next; + if (outq->head == NULL) + outq->tail = NULL; + + if (outq->cache != NULL && outq->cache->allocated != buf->allocated) + lzma_outq_clear_cache(outq, allocator); + + buf->next = outq->cache; + outq->cache = buf; + + --outq->bufs_in_use; + outq->mem_in_use -= lzma_outq_outbuf_memusage(buf->allocated); + + return; +} + + +static void +free_one_cached_buffer(lzma_outq *outq, const lzma_allocator *allocator) +{ + assert(outq->cache != NULL); + + lzma_outbuf *buf = outq->cache; + outq->cache = buf->next; + + --outq->bufs_allocated; + outq->mem_allocated -= lzma_outq_outbuf_memusage(buf->allocated); + + lzma_free(buf, allocator); + return; +} + + +extern void +lzma_outq_clear_cache(lzma_outq *outq, const lzma_allocator *allocator) +{ + while (outq->cache != NULL) + free_one_cached_buffer(outq, allocator); - return sizeof(lzma_outq) + bufs_count * sizeof(lzma_outbuf) - + bufs_alloc_size; + return; +} + + +extern void +lzma_outq_clear_cache2(lzma_outq *outq, const lzma_allocator *allocator, + size_t keep_size) +{ + if (outq->cache == NULL) + return; + + // Free all but one. + while (outq->cache->next != NULL) + free_one_cached_buffer(outq, allocator); + + // Free the last one only if its size doesn't equal to keep_size. + if (outq->cache->allocated != keep_size) + free_one_cached_buffer(outq, allocator); + + return; } extern lzma_ret lzma_outq_init(lzma_outq *outq, const lzma_allocator *allocator, - uint64_t buf_size_max, uint32_t threads) + uint32_t threads) { - uint64_t bufs_alloc_size; - uint32_t bufs_count; - - // Set bufs_count and bufs_alloc_size. - return_if_error(get_options(&bufs_alloc_size, &bufs_count, - buf_size_max, threads)); - - // Allocate memory if needed. - if (outq->buf_size_max != buf_size_max - || outq->bufs_allocated != bufs_count) { - lzma_outq_end(outq, allocator); - -#if SIZE_MAX < UINT64_MAX - if (bufs_alloc_size > SIZE_MAX) - return LZMA_MEM_ERROR; -#endif - - outq->bufs = lzma_alloc(bufs_count * sizeof(lzma_outbuf), - allocator); - outq->bufs_mem = lzma_alloc((size_t)(bufs_alloc_size), - allocator); - - if (outq->bufs == NULL || outq->bufs_mem == NULL) { - lzma_outq_end(outq, allocator); - return LZMA_MEM_ERROR; - } - } + if (threads > LZMA_THREADS_MAX) + return LZMA_OPTIONS_ERROR; + + const uint32_t bufs_limit = GET_BUFS_LIMIT(threads); + + // Clear head/tail. + while (outq->head != NULL) + move_head_to_cache(outq, allocator); + + // If new buf_limit is lower than the old one, we may need to free + // a few cached buffers. + while (bufs_limit < outq->bufs_allocated) + free_one_cached_buffer(outq, allocator); - // Initialize the rest of the main structure. Initialization of - // outq->bufs[] is done when they are actually needed. - outq->buf_size_max = (size_t)(buf_size_max); - outq->bufs_allocated = bufs_count; - outq->bufs_pos = 0; - outq->bufs_used = 0; + outq->bufs_limit = bufs_limit; outq->read_pos = 0; return LZMA_OK; @@ -100,33 +136,81 @@ lzma_outq_init(lzma_outq *outq, const lzma_allocator *allocator, extern void lzma_outq_end(lzma_outq *outq, const lzma_allocator *allocator) { - lzma_free(outq->bufs, allocator); - outq->bufs = NULL; - - lzma_free(outq->bufs_mem, allocator); - outq->bufs_mem = NULL; + while (outq->head != NULL) + move_head_to_cache(outq, allocator); + lzma_outq_clear_cache(outq, allocator); return; } -extern lzma_outbuf * -lzma_outq_get_buf(lzma_outq *outq) +extern lzma_ret +lzma_outq_prealloc_buf(lzma_outq *outq, const lzma_allocator *allocator, + size_t size) { // Caller must have checked it with lzma_outq_has_buf(). - assert(outq->bufs_used < outq->bufs_allocated); + assert(outq->bufs_in_use < outq->bufs_limit); + + // If there already is appropriately-sized buffer in the cache, + // we need to do nothing. + if (outq->cache != NULL && outq->cache->allocated == size) + return LZMA_OK; + + if (size > SIZE_MAX - sizeof(lzma_outbuf)) + return LZMA_MEM_ERROR; + + const size_t alloc_size = lzma_outq_outbuf_memusage(size); + + // The cache may have buffers but their size is wrong. + lzma_outq_clear_cache(outq, allocator); + + outq->cache = lzma_alloc(alloc_size, allocator); + if (outq->cache == NULL) + return LZMA_MEM_ERROR; + + outq->cache->next = NULL; + outq->cache->allocated = size; + + ++outq->bufs_allocated; + outq->mem_allocated += alloc_size; + + return LZMA_OK; +} + - // Initialize the new buffer. - lzma_outbuf *buf = &outq->bufs[outq->bufs_pos]; - buf->buf = outq->bufs_mem + outq->bufs_pos * outq->buf_size_max; - buf->size = 0; +extern lzma_outbuf * +lzma_outq_get_buf(lzma_outq *outq, void *worker) +{ + // Caller must have used lzma_outq_prealloc_buf() to ensure these. + assert(outq->bufs_in_use < outq->bufs_limit); + assert(outq->bufs_in_use < outq->bufs_allocated); + assert(outq->cache != NULL); + + lzma_outbuf *buf = outq->cache; + outq->cache = buf->next; + buf->next = NULL; + + if (outq->tail != NULL) { + assert(outq->head != NULL); + outq->tail->next = buf; + } else { + assert(outq->head == NULL); + outq->head = buf; + } + + outq->tail = buf; + + buf->worker = worker; buf->finished = false; + buf->finish_ret = LZMA_STREAM_END; + buf->pos = 0; + buf->decoder_in_pos = 0; - // Update the queue state. - if (++outq->bufs_pos == outq->bufs_allocated) - outq->bufs_pos = 0; + buf->unpadded_size = 0; + buf->uncompressed_size = 0; - ++outq->bufs_used; + ++outq->bufs_in_use; + outq->mem_in_use += lzma_outq_outbuf_memusage(buf->allocated); return buf; } @@ -135,50 +219,68 @@ lzma_outq_get_buf(lzma_outq *outq) extern bool lzma_outq_is_readable(const lzma_outq *outq) { - uint32_t i = outq->bufs_pos - outq->bufs_used; - if (outq->bufs_pos < outq->bufs_used) - i += outq->bufs_allocated; + if (outq->head == NULL) + return false; - return outq->bufs[i].finished; + return outq->read_pos < outq->head->pos || outq->head->finished; } extern lzma_ret -lzma_outq_read(lzma_outq *restrict outq, uint8_t *restrict out, - size_t *restrict out_pos, size_t out_size, +lzma_outq_read(lzma_outq *restrict outq, + const lzma_allocator *restrict allocator, + uint8_t *restrict out, size_t *restrict out_pos, + size_t out_size, lzma_vli *restrict unpadded_size, lzma_vli *restrict uncompressed_size) { // There must be at least one buffer from which to read. - if (outq->bufs_used == 0) + if (outq->bufs_in_use == 0) return LZMA_OK; // Get the buffer. - uint32_t i = outq->bufs_pos - outq->bufs_used; - if (outq->bufs_pos < outq->bufs_used) - i += outq->bufs_allocated; - - lzma_outbuf *buf = &outq->bufs[i]; - - // If it isn't finished yet, we cannot read from it. - if (!buf->finished) - return LZMA_OK; + lzma_outbuf *buf = outq->head; // Copy from the buffer to output. - lzma_bufcpy(buf->buf, &outq->read_pos, buf->size, + // + // FIXME? In threaded decoder it may be bad to do this copy while + // the mutex is being held. + lzma_bufcpy(buf->buf, &outq->read_pos, buf->pos, out, out_pos, out_size); // Return if we didn't get all the data from the buffer. - if (outq->read_pos < buf->size) + if (!buf->finished || outq->read_pos < buf->pos) return LZMA_OK; // The buffer was finished. Tell the caller its size information. - *unpadded_size = buf->unpadded_size; - *uncompressed_size = buf->uncompressed_size; + if (unpadded_size != NULL) + *unpadded_size = buf->unpadded_size; + + if (uncompressed_size != NULL) + *uncompressed_size = buf->uncompressed_size; + + // Remember the return value. + const lzma_ret finish_ret = buf->finish_ret; // Free this buffer for further use. - --outq->bufs_used; + move_head_to_cache(outq, allocator); outq->read_pos = 0; - return LZMA_STREAM_END; + return finish_ret; +} + + +extern void +lzma_outq_enable_partial_output(lzma_outq *outq, + void (*enable_partial_output)(void *worker)) +{ + if (outq->head != NULL && !outq->head->finished + && outq->head->worker != NULL) { + enable_partial_output(outq->head->worker); + + // Set it to NULL since calling it twice is pointless. + outq->head->worker = NULL; + } + + return; } diff --git a/Utilities/cmliblzma/liblzma/common/outqueue.h b/Utilities/cmliblzma/liblzma/common/outqueue.h index 079634de458..25f071977a8 100644 --- a/Utilities/cmliblzma/liblzma/common/outqueue.h +++ b/Utilities/cmliblzma/liblzma/common/outqueue.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file outqueue.h @@ -5,25 +7,45 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// +#ifndef LZMA_OUTQUEUE_H +#define LZMA_OUTQUEUE_H + #include "common.h" /// Output buffer for a single thread -typedef struct { - /// Pointer to the output buffer of lzma_outq.buf_size_max bytes - uint8_t *buf; - - /// Amount of data written to buf - size_t size; +typedef struct lzma_outbuf_s lzma_outbuf; +struct lzma_outbuf_s { + /// Pointer to the next buffer. This is used for the cached buffers. + /// The worker thread must not modify this. + lzma_outbuf *next; + + /// This initialized by lzma_outq_get_buf() and + /// is used by lzma_outq_enable_partial_output(). + /// The worker thread must not modify this. + void *worker; + + /// Amount of memory allocated for buf[]. + /// The worker thread must not modify this. + size_t allocated; + + /// Writing position in the worker thread or, in other words, the + /// amount of finished data written to buf[] which can be copied out + /// + /// \note This is read by another thread and thus access + /// to this variable needs a mutex. + size_t pos; - /// Additional size information - lzma_vli unpadded_size; - lzma_vli uncompressed_size; + /// Decompression: Position in the input buffer in the worker thread + /// that matches the output "pos" above. This is used to detect if + /// more output might be possible from the worker thread: if it has + /// consumed all its input, then more output isn't possible. + /// + /// \note This is read by another thread and thus access + /// to this variable needs a mutex. + size_t decoder_in_pos; /// True when no more data will be written into this buffer. /// @@ -31,32 +53,55 @@ typedef struct { /// to this variable needs a mutex. bool finished; -} lzma_outbuf; + /// Return value for lzma_outq_read() when the last byte from + /// a finished buffer has been read. Defaults to LZMA_STREAM_END. + /// This must *not* be LZMA_OK. The idea is to allow a decoder to + /// pass an error code to the main thread, setting the code here + /// together with finished = true. + lzma_ret finish_ret; + + /// Additional size information. lzma_outq_read() may read these + /// when "finished" is true. + lzma_vli unpadded_size; + lzma_vli uncompressed_size; + + /// Buffer of "allocated" bytes + uint8_t buf[]; +}; typedef struct { - /// Array of buffers that are used cyclically. - lzma_outbuf *bufs; + /// Linked list of buffers in use. The next output byte will be + /// read from the head and buffers for the next thread will be + /// appended to the tail. tail->next is always NULL. + lzma_outbuf *head; + lzma_outbuf *tail; - /// Memory allocated for all the buffers - uint8_t *bufs_mem; + /// Number of bytes read from head->buf[] in lzma_outq_read() + size_t read_pos; - /// Amount of buffer space available in each buffer - size_t buf_size_max; + /// Linked list of allocated buffers that aren't currently used. + /// This way buffers of similar size can be reused and don't + /// need to be reallocated every time. For simplicity, all + /// cached buffers in the list have the same allocated size. + lzma_outbuf *cache; - /// Number of buffers allocated - uint32_t bufs_allocated; + /// Total amount of memory allocated for buffers + uint64_t mem_allocated; - /// Position in the bufs array. The next buffer to be taken - /// into use is bufs[bufs_pos]. - uint32_t bufs_pos; + /// Amount of memory used by the buffers that are in use in + /// the head...tail linked list. + uint64_t mem_in_use; - /// Number of buffers in use - uint32_t bufs_used; + /// Number of buffers in use in the head...tail list. If and only if + /// this is zero, the pointers head and tail above are NULL. + uint32_t bufs_in_use; - /// Position in the buffer in lzma_outq_read() - size_t read_pos; + /// Number of buffers allocated (in use + cached) + uint32_t bufs_allocated; + /// Maximum allowed number of allocated buffers + uint32_t bufs_limit; } lzma_outq; @@ -76,32 +121,60 @@ extern uint64_t lzma_outq_memusage(uint64_t buf_size_max, uint32_t threads); /// function knows that there are no previous /// allocations to free. /// \param allocator Pointer to allocator or NULL -/// \param buf_size_max Maximum amount of data that a single buffer -/// in the queue may need to store. /// \param threads Number of buffers that may be in use /// concurrently. Note that more than this number -/// of buffers will actually get allocated to +/// of buffers may actually get allocated to /// improve performance when buffers finish -/// out of order. +/// out of order. The actual maximum number of +/// allocated buffers is derived from the number +/// of threads. /// /// \return - LZMA_OK /// - LZMA_MEM_ERROR /// -extern lzma_ret lzma_outq_init( - lzma_outq *outq, const lzma_allocator *allocator, - uint64_t buf_size_max, uint32_t threads); +extern lzma_ret lzma_outq_init(lzma_outq *outq, + const lzma_allocator *allocator, uint32_t threads); /// \brief Free the memory associated with the output queue extern void lzma_outq_end(lzma_outq *outq, const lzma_allocator *allocator); +/// \brief Free all cached buffers that consume memory but aren't in use +extern void lzma_outq_clear_cache( + lzma_outq *outq, const lzma_allocator *allocator); + + +/// \brief Like lzma_outq_clear_cache() but might keep one buffer +/// +/// One buffer is not freed if its size is equal to keep_size. +/// This is useful if the caller knows that it will soon need a buffer of +/// keep_size bytes. This way it won't be freed and immediately reallocated. +extern void lzma_outq_clear_cache2( + lzma_outq *outq, const lzma_allocator *allocator, + size_t keep_size); + + +/// \brief Preallocate a new buffer into cache +/// +/// Splitting the buffer allocation into a separate function makes it +/// possible to ensure that way lzma_outq_get_buf() cannot fail. +/// If the preallocated buffer isn't actually used (for example, some +/// other error occurs), the caller has to do nothing as the buffer will +/// be used later or cleared from the cache when not needed. +/// +/// \return LZMA_OK on success, LZMA_MEM_ERROR if allocation fails +/// +extern lzma_ret lzma_outq_prealloc_buf( + lzma_outq *outq, const lzma_allocator *allocator, size_t size); + + /// \brief Get a new buffer /// -/// lzma_outq_has_buf() must be used to check that there is a buffer +/// lzma_outq_prealloc_buf() must be used to ensure that there is a buffer /// available before calling lzma_outq_get_buf(). /// -extern lzma_outbuf *lzma_outq_get_buf(lzma_outq *outq); +extern lzma_outbuf *lzma_outq_get_buf(lzma_outq *outq, void *worker); /// \brief Test if there is data ready to be read @@ -126,17 +199,32 @@ extern bool lzma_outq_is_readable(const lzma_outq *outq); /// \return - LZMA: All OK. Either no data was available or the buffer /// being read didn't become empty yet. /// - LZMA_STREAM_END: The buffer being read was finished. -/// *unpadded_size and *uncompressed_size were set. +/// *unpadded_size and *uncompressed_size were set if they +/// were not NULL. /// -/// \note This reads lzma_outbuf.finished variables and thus call -/// to this function needs to be protected with a mutex. +/// \note This reads lzma_outbuf.finished and .pos variables and thus +/// calls to this function need to be protected with a mutex. /// extern lzma_ret lzma_outq_read(lzma_outq *restrict outq, + const lzma_allocator *restrict allocator, uint8_t *restrict out, size_t *restrict out_pos, size_t out_size, lzma_vli *restrict unpadded_size, lzma_vli *restrict uncompressed_size); +/// \brief Enable partial output from a worker thread +/// +/// If the buffer at the head of the output queue isn't finished, +/// this will call enable_partial_output on the worker associated with +/// that output buffer. +/// +/// \note This reads a lzma_outbuf.finished variable and thus +/// calls to this function need to be protected with a mutex. +/// +extern void lzma_outq_enable_partial_output(lzma_outq *outq, + void (*enable_partial_output)(void *worker)); + + /// \brief Test if there is at least one buffer free /// /// This must be used before getting a new buffer with lzma_outq_get_buf(). @@ -144,7 +232,7 @@ extern lzma_ret lzma_outq_read(lzma_outq *restrict outq, static inline bool lzma_outq_has_buf(const lzma_outq *outq) { - return outq->bufs_used < outq->bufs_allocated; + return outq->bufs_in_use < outq->bufs_limit; } @@ -152,5 +240,19 @@ lzma_outq_has_buf(const lzma_outq *outq) static inline bool lzma_outq_is_empty(const lzma_outq *outq) { - return outq->bufs_used == 0; + return outq->bufs_in_use == 0; } + + +/// \brief Get the amount of memory needed for a single lzma_outbuf +/// +/// \note Caller must check that the argument is significantly less +/// than SIZE_MAX to avoid an integer overflow! +static inline uint64_t +lzma_outq_outbuf_memusage(size_t buf_size) +{ + assert(buf_size <= SIZE_MAX - sizeof(lzma_outbuf)); + return sizeof(lzma_outbuf) + buf_size; +} + +#endif diff --git a/Utilities/cmliblzma/liblzma/common/stream_buffer_decoder.c b/Utilities/cmliblzma/liblzma/common/stream_buffer_decoder.c index b9745b5dbe1..c4f91fb4983 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_buffer_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/stream_buffer_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_buffer_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "stream_decoder.h" diff --git a/Utilities/cmliblzma/liblzma/common/stream_buffer_encoder.c b/Utilities/cmliblzma/liblzma/common/stream_buffer_encoder.c index af49554a6b0..04d58695946 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_buffer_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/stream_buffer_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_buffer_encoder.c @@ -5,11 +7,9 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// +#include "common.h" #include "index.h" diff --git a/Utilities/cmliblzma/liblzma/common/stream_decoder.c b/Utilities/cmliblzma/liblzma/common/stream_decoder.c index fdd8ff2f9a3..7f426841366 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/stream_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_decoder.c @@ -5,28 +7,25 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "stream_decoder.h" #include "block_decoder.h" +#include "index.h" typedef struct { enum { SEQ_STREAM_HEADER, SEQ_BLOCK_HEADER, - SEQ_BLOCK, + SEQ_BLOCK_INIT, + SEQ_BLOCK_RUN, SEQ_INDEX, SEQ_STREAM_FOOTER, SEQ_STREAM_PADDING, } sequence; - /// Block or Metadata decoder. This takes little memory and the same - /// data structure can be used to decode every Block Header, so it's - /// a good idea to have a separate lzma_next_coder structure for it. + /// Block decoder lzma_next_coder block_decoder; /// Block options decoded by the Block Header decoder and used by @@ -63,9 +62,9 @@ typedef struct { /// If true, we will decode concatenated Streams that possibly have /// Stream Padding between or after them. LZMA_STREAM_END is returned - /// once the application isn't giving us any new input, and we aren't - /// in the middle of a Stream, and possible Stream Padding is a - /// multiple of four bytes. + /// once the application isn't giving us any new input (LZMA_FINISH), + /// and we aren't in the middle of a Stream, and possible + /// Stream Padding is a multiple of four bytes. bool concatenated; /// When decoding concatenated Streams, this is true as long as we @@ -165,7 +164,7 @@ stream_decode(void *coder_ptr, const lzma_allocator *allocator, if (coder->pos == 0) { // Detect if it's Index. - if (in[*in_pos] == 0x00) { + if (in[*in_pos] == INDEX_INDICATOR) { coder->sequence = SEQ_INDEX; break; } @@ -187,6 +186,15 @@ stream_decode(void *coder_ptr, const lzma_allocator *allocator, return LZMA_OK; coder->pos = 0; + coder->sequence = SEQ_BLOCK_INIT; + } + + // Fall through + + case SEQ_BLOCK_INIT: { + // Checking memusage and doing the initialization needs + // its own sequence point because we need to be able to + // retry if we return LZMA_MEMLIMIT_ERROR. // Version 1 is needed to support the .ignore_check option. coder->block_options.version = 1; @@ -235,22 +243,20 @@ stream_decode(void *coder_ptr, const lzma_allocator *allocator, // Free the allocated filter options since they are needed // only to initialize the Block decoder. - for (size_t i = 0; i < LZMA_FILTERS_MAX; ++i) - lzma_free(filters[i].options, allocator); - + lzma_filters_free(filters, allocator); coder->block_options.filters = NULL; - // Check if memory usage calculation and Block enocoder + // Check if memory usage calculation and Block decoder // initialization succeeded. if (ret != LZMA_OK) return ret; - coder->sequence = SEQ_BLOCK; + coder->sequence = SEQ_BLOCK_RUN; } // Fall through - case SEQ_BLOCK: { + case SEQ_BLOCK_RUN: { const lzma_ret ret = coder->block_decoder.code( coder->block_decoder.coder, allocator, in, in_pos, in_size, out, out_pos, out_size, diff --git a/Utilities/cmliblzma/liblzma/common/stream_decoder.h b/Utilities/cmliblzma/liblzma/common/stream_decoder.h index c13c6ba1270..5803715374d 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_decoder.h +++ b/Utilities/cmliblzma/liblzma/common/stream_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_decoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_STREAM_DECODER_H diff --git a/Utilities/cmliblzma/liblzma/common/stream_decoder_mt.c b/Utilities/cmliblzma/liblzma/common/stream_decoder_mt.c new file mode 100644 index 00000000000..244624a4790 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/stream_decoder_mt.c @@ -0,0 +1,2017 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file stream_decoder_mt.c +/// \brief Multithreaded .xz Stream decoder +// +// Authors: Sebastian Andrzej Siewior +// Lasse Collin +// +/////////////////////////////////////////////////////////////////////////////// + +#include "common.h" +#include "block_decoder.h" +#include "stream_decoder.h" +#include "index.h" +#include "outqueue.h" + + +typedef enum { + /// Waiting for work. + /// Main thread may change this to THR_RUN or THR_EXIT. + THR_IDLE, + + /// Decoding is in progress. + /// Main thread may change this to THR_STOP or THR_EXIT. + /// The worker thread may change this to THR_IDLE. + THR_RUN, + + /// The main thread wants the thread to stop whatever it was doing + /// but not exit. Main thread may change this to THR_EXIT. + /// The worker thread may change this to THR_IDLE. + THR_STOP, + + /// The main thread wants the thread to exit. + THR_EXIT, + +} worker_state; + + +typedef enum { + /// Partial updates (storing of worker thread progress + /// to lzma_outbuf) are disabled. + PARTIAL_DISABLED, + + /// Main thread requests partial updates to be enabled but + /// no partial update has been done by the worker thread yet. + /// + /// Changing from PARTIAL_DISABLED to PARTIAL_START requires + /// use of the worker-thread mutex. Other transitions don't + /// need a mutex. + PARTIAL_START, + + /// Partial updates are enabled and the worker thread has done + /// at least one partial update. + PARTIAL_ENABLED, + +} partial_update_mode; + + +struct worker_thread { + /// Worker state is protected with our mutex. + worker_state state; + + /// Input buffer that will contain the whole Block except Block Header. + uint8_t *in; + + /// Amount of memory allocated for "in" + size_t in_size; + + /// Number of bytes written to "in" by the main thread + size_t in_filled; + + /// Number of bytes consumed from "in" by the worker thread. + size_t in_pos; + + /// Amount of uncompressed data that has been decoded. This local + /// copy is needed because updating outbuf->pos requires locking + /// the main mutex (coder->mutex). + size_t out_pos; + + /// Pointer to the main structure is needed to (1) lock the main + /// mutex (coder->mutex) when updating outbuf->pos and (2) when + /// putting this thread back to the stack of free threads. + struct lzma_stream_coder *coder; + + /// The allocator is set by the main thread. Since a copy of the + /// pointer is kept here, the application must not change the + /// allocator before calling lzma_end(). + const lzma_allocator *allocator; + + /// Output queue buffer to which the uncompressed data is written. + lzma_outbuf *outbuf; + + /// Amount of compressed data that has already been decompressed. + /// This is updated from in_pos when our mutex is locked. + /// This is size_t, not uint64_t, because per-thread progress + /// is limited to sizes of allocated buffers. + size_t progress_in; + + /// Like progress_in but for uncompressed data. + size_t progress_out; + + /// Updating outbuf->pos requires locking the main mutex + /// (coder->mutex). Since the main thread will only read output + /// from the oldest outbuf in the queue, only the worker thread + /// that is associated with the oldest outbuf needs to update its + /// outbuf->pos. This avoids useless mutex contention that would + /// happen if all worker threads were frequently locking the main + /// mutex to update their outbuf->pos. + /// + /// Only when partial_update is something else than PARTIAL_DISABLED, + /// this worker thread will update outbuf->pos after each call to + /// the Block decoder. + partial_update_mode partial_update; + + /// Block decoder + lzma_next_coder block_decoder; + + /// Thread-specific Block options are needed because the Block + /// decoder modifies the struct given to it at initialization. + lzma_block block_options; + + /// Filter chain memory usage + uint64_t mem_filters; + + /// Next structure in the stack of free worker threads. + struct worker_thread *next; + + mythread_mutex mutex; + mythread_cond cond; + + /// The ID of this thread is used to join the thread + /// when it's not needed anymore. + mythread thread_id; +}; + + +struct lzma_stream_coder { + enum { + SEQ_STREAM_HEADER, + SEQ_BLOCK_HEADER, + SEQ_BLOCK_INIT, + SEQ_BLOCK_THR_INIT, + SEQ_BLOCK_THR_RUN, + SEQ_BLOCK_DIRECT_INIT, + SEQ_BLOCK_DIRECT_RUN, + SEQ_INDEX_WAIT_OUTPUT, + SEQ_INDEX_DECODE, + SEQ_STREAM_FOOTER, + SEQ_STREAM_PADDING, + SEQ_ERROR, + } sequence; + + /// Block decoder + lzma_next_coder block_decoder; + + /// Every Block Header will be decoded into this structure. + /// This is also used to initialize a Block decoder when in + /// direct mode. In threaded mode, a thread-specific copy will + /// be made for decoder initialization because the Block decoder + /// will modify the structure given to it. + lzma_block block_options; + + /// Buffer to hold a filter chain for Block Header decoding and + /// initialization. These are freed after successful Block decoder + /// initialization or at stream_decoder_mt_end(). The thread-specific + /// copy of block_options won't hold a pointer to filters[] after + /// initialization. + lzma_filter filters[LZMA_FILTERS_MAX + 1]; + + /// Stream Flags from Stream Header + lzma_stream_flags stream_flags; + + /// Index is hashed so that it can be compared to the sizes of Blocks + /// with O(1) memory usage. + lzma_index_hash *index_hash; + + + /// Maximum wait time if cannot use all the input and cannot + /// fill the output buffer. This is in milliseconds. + uint32_t timeout; + + + /// Error code from a worker thread. + /// + /// \note Use mutex. + lzma_ret thread_error; + + /// Error code to return after pending output has been copied out. If + /// set in read_output_and_wait(), this is a mirror of thread_error. + /// If set in stream_decode_mt() then it's, for example, error that + /// occurred when decoding Block Header. + lzma_ret pending_error; + + /// Number of threads that will be created at maximum. + uint32_t threads_max; + + /// Number of thread structures that have been initialized from + /// "threads", and thus the number of worker threads actually + /// created so far. + uint32_t threads_initialized; + + /// Array of allocated thread-specific structures. When no threads + /// are in use (direct mode) this is NULL. In threaded mode this + /// points to an array of threads_max number of worker_thread structs. + struct worker_thread *threads; + + /// Stack of free threads. When a thread finishes, it puts itself + /// back into this stack. This starts as empty because threads + /// are created only when actually needed. + /// + /// \note Use mutex. + struct worker_thread *threads_free; + + /// The most recent worker thread to which the main thread writes + /// the new input from the application. + struct worker_thread *thr; + + /// Output buffer queue for decompressed data from the worker threads + /// + /// \note Use mutex with operations that need it. + lzma_outq outq; + + mythread_mutex mutex; + mythread_cond cond; + + + /// Memory usage that will not be exceeded in multi-threaded mode. + /// Single-threaded mode can exceed this even by a large amount. + uint64_t memlimit_threading; + + /// Memory usage limit that should never be exceeded. + /// LZMA_MEMLIMIT_ERROR will be returned if decoding isn't possible + /// even in single-threaded mode without exceeding this limit. + uint64_t memlimit_stop; + + /// Amount of memory in use by the direct mode decoder + /// (coder->block_decoder). In threaded mode this is 0. + uint64_t mem_direct_mode; + + /// Amount of memory needed by the running worker threads. + /// This doesn't include the memory needed by the output buffer. + /// + /// \note Use mutex. + uint64_t mem_in_use; + + /// Amount of memory used by the idle (cached) threads. + /// + /// \note Use mutex. + uint64_t mem_cached; + + + /// Amount of memory needed for the filter chain of the next Block. + uint64_t mem_next_filters; + + /// Amount of memory needed for the thread-specific input buffer + /// for the next Block. + uint64_t mem_next_in; + + /// Amount of memory actually needed to decode the next Block + /// in threaded mode. This is + /// mem_next_filters + mem_next_in + memory needed for lzma_outbuf. + uint64_t mem_next_block; + + + /// Amount of compressed data in Stream Header + Blocks that have + /// already been finished. + /// + /// \note Use mutex. + uint64_t progress_in; + + /// Amount of uncompressed data in Blocks that have already + /// been finished. + /// + /// \note Use mutex. + uint64_t progress_out; + + + /// If true, LZMA_NO_CHECK is returned if the Stream has + /// no integrity check. + bool tell_no_check; + + /// If true, LZMA_UNSUPPORTED_CHECK is returned if the Stream has + /// an integrity check that isn't supported by this liblzma build. + bool tell_unsupported_check; + + /// If true, LZMA_GET_CHECK is returned after decoding Stream Header. + bool tell_any_check; + + /// If true, we will tell the Block decoder to skip calculating + /// and verifying the integrity check. + bool ignore_check; + + /// If true, we will decode concatenated Streams that possibly have + /// Stream Padding between or after them. LZMA_STREAM_END is returned + /// once the application isn't giving us any new input (LZMA_FINISH), + /// and we aren't in the middle of a Stream, and possible + /// Stream Padding is a multiple of four bytes. + bool concatenated; + + /// If true, we will return any errors immediately instead of first + /// producing all output before the location of the error. + bool fail_fast; + + + /// When decoding concatenated Streams, this is true as long as we + /// are decoding the first Stream. This is needed to avoid misleading + /// LZMA_FORMAT_ERROR in case the later Streams don't have valid magic + /// bytes. + bool first_stream; + + /// This is used to track if the previous call to stream_decode_mt() + /// had output space (*out_pos < out_size) and managed to fill the + /// output buffer (*out_pos == out_size). This may be set to true + /// in read_output_and_wait(). This is read and then reset to false + /// at the beginning of stream_decode_mt(). + /// + /// This is needed to support applications that call lzma_code() in + /// such a way that more input is provided only when lzma_code() + /// didn't fill the output buffer completely. Basically, this makes + /// it easier to convert such applications from single-threaded + /// decoder to multi-threaded decoder. + bool out_was_filled; + + /// Write position in buffer[] and position in Stream Padding + size_t pos; + + /// Buffer to hold Stream Header, Block Header, and Stream Footer. + /// Block Header has biggest maximum size. + uint8_t buffer[LZMA_BLOCK_HEADER_SIZE_MAX]; +}; + + +/// Enables updating of outbuf->pos. This is a callback function that is +/// used with lzma_outq_enable_partial_output(). +static void +worker_enable_partial_update(void *thr_ptr) +{ + struct worker_thread *thr = thr_ptr; + + mythread_sync(thr->mutex) { + thr->partial_update = PARTIAL_START; + mythread_cond_signal(&thr->cond); + } +} + + +/// Things do to at THR_STOP or when finishing a Block. +/// This is called with thr->mutex locked. +static void +worker_stop(struct worker_thread *thr) +{ + // Update memory usage counters. + thr->coder->mem_in_use -= thr->in_size; + thr->in_size = 0; // thr->in was freed above. + + thr->coder->mem_in_use -= thr->mem_filters; + thr->coder->mem_cached += thr->mem_filters; + + // Put this thread to the stack of free threads. + thr->next = thr->coder->threads_free; + thr->coder->threads_free = thr; + + mythread_cond_signal(&thr->coder->cond); + return; +} + + +static MYTHREAD_RET_TYPE +worker_decoder(void *thr_ptr) +{ + struct worker_thread *thr = thr_ptr; + size_t in_filled; + partial_update_mode partial_update; + lzma_ret ret; + +next_loop_lock: + + mythread_mutex_lock(&thr->mutex); +next_loop_unlocked: + + if (thr->state == THR_IDLE) { + mythread_cond_wait(&thr->cond, &thr->mutex); + goto next_loop_unlocked; + } + + if (thr->state == THR_EXIT) { + mythread_mutex_unlock(&thr->mutex); + + lzma_free(thr->in, thr->allocator); + lzma_next_end(&thr->block_decoder, thr->allocator); + + mythread_mutex_destroy(&thr->mutex); + mythread_cond_destroy(&thr->cond); + + return MYTHREAD_RET_VALUE; + } + + if (thr->state == THR_STOP) { + thr->state = THR_IDLE; + mythread_mutex_unlock(&thr->mutex); + + mythread_sync(thr->coder->mutex) { + worker_stop(thr); + } + + goto next_loop_lock; + } + + assert(thr->state == THR_RUN); + + // Update progress info for get_progress(). + thr->progress_in = thr->in_pos; + thr->progress_out = thr->out_pos; + + // If we don't have any new input, wait for a signal from the main + // thread except if partial output has just been enabled. In that + // case we will do one normal run so that the partial output info + // gets passed to the main thread. The call to block_decoder.code() + // is useless but harmless as it can occur only once per Block. + in_filled = thr->in_filled; + partial_update = thr->partial_update; + + if (in_filled == thr->in_pos && partial_update != PARTIAL_START) { + mythread_cond_wait(&thr->cond, &thr->mutex); + goto next_loop_unlocked; + } + + mythread_mutex_unlock(&thr->mutex); + + // Pass the input in small chunks to the Block decoder. + // This way we react reasonably fast if we are told to stop/exit, + // and (when partial update is enabled) we tell about our progress + // to the main thread frequently enough. + const size_t chunk_size = 16384; + if ((in_filled - thr->in_pos) > chunk_size) + in_filled = thr->in_pos + chunk_size; + + ret = thr->block_decoder.code( + thr->block_decoder.coder, thr->allocator, + thr->in, &thr->in_pos, in_filled, + thr->outbuf->buf, &thr->out_pos, + thr->outbuf->allocated, LZMA_RUN); + + if (ret == LZMA_OK) { + if (partial_update != PARTIAL_DISABLED) { + // The main thread uses thr->mutex to change from + // PARTIAL_DISABLED to PARTIAL_START. The main thread + // doesn't care about this variable after that so we + // can safely change it here to PARTIAL_ENABLED + // without a mutex. + thr->partial_update = PARTIAL_ENABLED; + + // The main thread is reading decompressed data + // from thr->outbuf. Tell the main thread about + // our progress. + // + // NOTE: It's possible that we consumed input without + // producing any new output so it's possible that + // only in_pos has changed. In case of PARTIAL_START + // it is possible that neither in_pos nor out_pos has + // changed. + mythread_sync(thr->coder->mutex) { + thr->outbuf->pos = thr->out_pos; + thr->outbuf->decoder_in_pos = thr->in_pos; + mythread_cond_signal(&thr->coder->cond); + } + } + + goto next_loop_lock; + } + + // Either we finished successfully (LZMA_STREAM_END) or an error + // occurred. Both cases are handled almost identically. The error + // case requires updating thr->coder->thread_error. + // + // The sizes are in the Block Header and the Block decoder + // checks that they match, thus we know these: + assert(ret != LZMA_STREAM_END || thr->in_pos == thr->in_size); + assert(ret != LZMA_STREAM_END + || thr->out_pos == thr->block_options.uncompressed_size); + + // Free the input buffer. Don't update in_size as we need + // it later to update thr->coder->mem_in_use. + lzma_free(thr->in, thr->allocator); + thr->in = NULL; + + mythread_sync(thr->mutex) { + if (thr->state != THR_EXIT) + thr->state = THR_IDLE; + } + + mythread_sync(thr->coder->mutex) { + // Move our progress info to the main thread. + thr->coder->progress_in += thr->in_pos; + thr->coder->progress_out += thr->out_pos; + thr->progress_in = 0; + thr->progress_out = 0; + + // Mark the outbuf as finished. + thr->outbuf->pos = thr->out_pos; + thr->outbuf->decoder_in_pos = thr->in_pos; + thr->outbuf->finished = true; + thr->outbuf->finish_ret = ret; + thr->outbuf = NULL; + + // If an error occurred, tell it to the main thread. + if (ret != LZMA_STREAM_END + && thr->coder->thread_error == LZMA_OK) + thr->coder->thread_error = ret; + + worker_stop(thr); + } + + goto next_loop_lock; +} + + +/// Tells the worker threads to exit and waits for them to terminate. +static void +threads_end(struct lzma_stream_coder *coder, const lzma_allocator *allocator) +{ + for (uint32_t i = 0; i < coder->threads_initialized; ++i) { + mythread_sync(coder->threads[i].mutex) { + coder->threads[i].state = THR_EXIT; + mythread_cond_signal(&coder->threads[i].cond); + } + } + + for (uint32_t i = 0; i < coder->threads_initialized; ++i) + mythread_join(coder->threads[i].thread_id); + + lzma_free(coder->threads, allocator); + coder->threads_initialized = 0; + coder->threads = NULL; + coder->threads_free = NULL; + + // The threads don't update these when they exit. Do it here. + coder->mem_in_use = 0; + coder->mem_cached = 0; + + return; +} + + +static void +threads_stop(struct lzma_stream_coder *coder) +{ + for (uint32_t i = 0; i < coder->threads_initialized; ++i) { + mythread_sync(coder->threads[i].mutex) { + // The state must be changed conditionally because + // THR_IDLE -> THR_STOP is not a valid state change. + if (coder->threads[i].state != THR_IDLE) { + coder->threads[i].state = THR_STOP; + mythread_cond_signal(&coder->threads[i].cond); + } + } + } + + return; +} + + +/// Initialize a new worker_thread structure and create a new thread. +static lzma_ret +initialize_new_thread(struct lzma_stream_coder *coder, + const lzma_allocator *allocator) +{ + // Allocate the coder->threads array if needed. It's done here instead + // of when initializing the decoder because we don't need this if we + // use the direct mode (we may even free coder->threads in the middle + // of the file if we switch from threaded to direct mode). + if (coder->threads == NULL) { + coder->threads = lzma_alloc( + coder->threads_max * sizeof(struct worker_thread), + allocator); + + if (coder->threads == NULL) + return LZMA_MEM_ERROR; + } + + // Pick a free structure. + assert(coder->threads_initialized < coder->threads_max); + struct worker_thread *thr + = &coder->threads[coder->threads_initialized]; + + if (mythread_mutex_init(&thr->mutex)) + goto error_mutex; + + if (mythread_cond_init(&thr->cond)) + goto error_cond; + + thr->state = THR_IDLE; + thr->in = NULL; + thr->in_size = 0; + thr->allocator = allocator; + thr->coder = coder; + thr->outbuf = NULL; + thr->block_decoder = LZMA_NEXT_CODER_INIT; + thr->mem_filters = 0; + + if (mythread_create(&thr->thread_id, worker_decoder, thr)) + goto error_thread; + + ++coder->threads_initialized; + coder->thr = thr; + + return LZMA_OK; + +error_thread: + mythread_cond_destroy(&thr->cond); + +error_cond: + mythread_mutex_destroy(&thr->mutex); + +error_mutex: + return LZMA_MEM_ERROR; +} + + +static lzma_ret +get_thread(struct lzma_stream_coder *coder, const lzma_allocator *allocator) +{ + // If there is a free structure on the stack, use it. + mythread_sync(coder->mutex) { + if (coder->threads_free != NULL) { + coder->thr = coder->threads_free; + coder->threads_free = coder->threads_free->next; + + // The thread is no longer in the cache so subtract + // it from the cached memory usage. Don't add it + // to mem_in_use though; the caller will handle it + // since it knows how much memory it will actually + // use (the filter chain might change). + coder->mem_cached -= coder->thr->mem_filters; + } + } + + if (coder->thr == NULL) { + assert(coder->threads_initialized < coder->threads_max); + + // Initialize a new thread. + return_if_error(initialize_new_thread(coder, allocator)); + } + + coder->thr->in_filled = 0; + coder->thr->in_pos = 0; + coder->thr->out_pos = 0; + + coder->thr->progress_in = 0; + coder->thr->progress_out = 0; + + coder->thr->partial_update = PARTIAL_DISABLED; + + return LZMA_OK; +} + + +static lzma_ret +read_output_and_wait(struct lzma_stream_coder *coder, + const lzma_allocator *allocator, + uint8_t *restrict out, size_t *restrict out_pos, + size_t out_size, + bool *input_is_possible, + bool waiting_allowed, + mythread_condtime *wait_abs, bool *has_blocked) +{ + lzma_ret ret = LZMA_OK; + + mythread_sync(coder->mutex) { + do { + // Get as much output from the queue as is possible + // without blocking. + const size_t out_start = *out_pos; + do { + ret = lzma_outq_read(&coder->outq, allocator, + out, out_pos, out_size, + NULL, NULL); + + // If a Block was finished, tell the worker + // thread of the next Block (if it is still + // running) to start telling the main thread + // when new output is available. + if (ret == LZMA_STREAM_END) + lzma_outq_enable_partial_output( + &coder->outq, + &worker_enable_partial_update); + + // Loop until a Block wasn't finished. + // It's important to loop around even if + // *out_pos == out_size because there could + // be an empty Block that will return + // LZMA_STREAM_END without needing any + // output space. + } while (ret == LZMA_STREAM_END); + + // Check if lzma_outq_read reported an error from + // the Block decoder. + if (ret != LZMA_OK) + break; + + // If the output buffer is now full but it wasn't full + // when this function was called, set out_was_filled. + // This way the next call to stream_decode_mt() knows + // that some output was produced and no output space + // remained in the previous call to stream_decode_mt(). + if (*out_pos == out_size && *out_pos != out_start) + coder->out_was_filled = true; + + // Check if any thread has indicated an error. + if (coder->thread_error != LZMA_OK) { + // If LZMA_FAIL_FAST was used, report errors + // from worker threads immediately. + if (coder->fail_fast) { + ret = coder->thread_error; + break; + } + + // Otherwise set pending_error. The value we + // set here will not actually get used other + // than working as a flag that an error has + // occurred. This is because in SEQ_ERROR + // all output before the error will be read + // first by calling this function, and once we + // reach the location of the (first) error the + // error code from the above lzma_outq_read() + // will be returned to the application. + // + // Use LZMA_PROG_ERROR since the value should + // never leak to the application. It's + // possible that pending_error has already + // been set but that doesn't matter: if we get + // here, pending_error only works as a flag. + coder->pending_error = LZMA_PROG_ERROR; + } + + // Check if decoding of the next Block can be started. + // The memusage of the active threads must be low + // enough, there must be a free buffer slot in the + // output queue, and there must be a free thread + // (that can be either created or an existing one + // reused). + // + // NOTE: This is checked after reading the output + // above because reading the output can free a slot in + // the output queue and also reduce active memusage. + // + // NOTE: If output queue is empty, then input will + // always be possible. + if (input_is_possible != NULL + && coder->memlimit_threading + - coder->mem_in_use + - coder->outq.mem_in_use + >= coder->mem_next_block + && lzma_outq_has_buf(&coder->outq) + && (coder->threads_initialized + < coder->threads_max + || coder->threads_free + != NULL)) { + *input_is_possible = true; + break; + } + + // If the caller doesn't want us to block, return now. + if (!waiting_allowed) + break; + + // This check is needed only when input_is_possible + // is NULL. We must return if we aren't waiting for + // input to become possible and there is no more + // output coming from the queue. + if (lzma_outq_is_empty(&coder->outq)) { + assert(input_is_possible == NULL); + break; + } + + // If there is more data available from the queue, + // our out buffer must be full and we need to return + // so that the application can provide more output + // space. + // + // NOTE: In general lzma_outq_is_readable() can return + // true also when there are no more bytes available. + // This can happen when a Block has finished without + // providing any new output. We know that this is not + // the case because in the beginning of this loop we + // tried to read as much as possible even when we had + // no output space left and the mutex has been locked + // all the time (so worker threads cannot have changed + // anything). Thus there must be actual pending output + // in the queue. + if (lzma_outq_is_readable(&coder->outq)) { + assert(*out_pos == out_size); + break; + } + + // If the application stops providing more input + // in the middle of a Block, there will eventually + // be one worker thread left that is stuck waiting for + // more input (that might never arrive) and a matching + // outbuf which the worker thread cannot finish due + // to lack of input. We must detect this situation, + // otherwise we would end up waiting indefinitely + // (if no timeout is in use) or keep returning + // LZMA_TIMED_OUT while making no progress. Thus, the + // application would never get LZMA_BUF_ERROR from + // lzma_code() which would tell the application that + // no more progress is possible. No LZMA_BUF_ERROR + // means that, for example, truncated .xz files could + // cause an infinite loop. + // + // A worker thread doing partial updates will + // store not only the output position in outbuf->pos + // but also the matching input position in + // outbuf->decoder_in_pos. Here we check if that + // input position matches the amount of input that + // the worker thread has been given (in_filled). + // If so, we must return and not wait as no more + // output will be coming without first getting more + // input to the worker thread. If the application + // keeps calling lzma_code() without providing more + // input, it will eventually get LZMA_BUF_ERROR. + // + // NOTE: We can read partial_update and in_filled + // without thr->mutex as only the main thread + // modifies these variables. decoder_in_pos requires + // coder->mutex which we are already holding. + if (coder->thr != NULL && coder->thr->partial_update + != PARTIAL_DISABLED) { + // There is exactly one outbuf in the queue. + assert(coder->thr->outbuf == coder->outq.head); + assert(coder->thr->outbuf == coder->outq.tail); + + if (coder->thr->outbuf->decoder_in_pos + == coder->thr->in_filled) + break; + } + + // Wait for input or output to become possible. + if (coder->timeout != 0) { + // See the comment in stream_encoder_mt.c + // about why mythread_condtime_set() is used + // like this. + // + // FIXME? + // In contrast to the encoder, this calls + // _condtime_set while the mutex is locked. + if (!*has_blocked) { + *has_blocked = true; + mythread_condtime_set(wait_abs, + &coder->cond, + coder->timeout); + } + + if (mythread_cond_timedwait(&coder->cond, + &coder->mutex, + wait_abs) != 0) { + ret = LZMA_TIMED_OUT; + break; + } + } else { + mythread_cond_wait(&coder->cond, + &coder->mutex); + } + } while (ret == LZMA_OK); + } + + // If we are returning an error, then the application cannot get + // more output from us and thus keeping the threads running is + // useless and waste of CPU time. + if (ret != LZMA_OK && ret != LZMA_TIMED_OUT) + threads_stop(coder); + + return ret; +} + + +static lzma_ret +decode_block_header(struct lzma_stream_coder *coder, + const lzma_allocator *allocator, const uint8_t *restrict in, + size_t *restrict in_pos, size_t in_size) +{ + if (*in_pos >= in_size) + return LZMA_OK; + + if (coder->pos == 0) { + // Detect if it's Index. + if (in[*in_pos] == INDEX_INDICATOR) + return LZMA_INDEX_DETECTED; + + // Calculate the size of the Block Header. Note that + // Block Header decoder wants to see this byte too + // so don't advance *in_pos. + coder->block_options.header_size + = lzma_block_header_size_decode( + in[*in_pos]); + } + + // Copy the Block Header to the internal buffer. + lzma_bufcpy(in, in_pos, in_size, coder->buffer, &coder->pos, + coder->block_options.header_size); + + // Return if we didn't get the whole Block Header yet. + if (coder->pos < coder->block_options.header_size) + return LZMA_OK; + + coder->pos = 0; + + // Version 1 is needed to support the .ignore_check option. + coder->block_options.version = 1; + + // Block Header decoder will initialize all members of this array + // so we don't need to do it here. + coder->block_options.filters = coder->filters; + + // Decode the Block Header. + return_if_error(lzma_block_header_decode(&coder->block_options, + allocator, coder->buffer)); + + // If LZMA_IGNORE_CHECK was used, this flag needs to be set. + // It has to be set after lzma_block_header_decode() because + // it always resets this to false. + coder->block_options.ignore_check = coder->ignore_check; + + // coder->block_options is ready now. + return LZMA_STREAM_END; +} + + +/// Get the size of the Compressed Data + Block Padding + Check. +static size_t +comp_blk_size(const struct lzma_stream_coder *coder) +{ + return vli_ceil4(coder->block_options.compressed_size) + + lzma_check_size(coder->stream_flags.check); +} + + +/// Returns true if the size (compressed or uncompressed) is such that +/// threaded decompression cannot be used. Sizes that are too big compared +/// to SIZE_MAX must be rejected to avoid integer overflows and truncations +/// when lzma_vli is assigned to a size_t. +static bool +is_direct_mode_needed(lzma_vli size) +{ + return size == LZMA_VLI_UNKNOWN || size > SIZE_MAX / 3; +} + + +static lzma_ret +stream_decoder_reset(struct lzma_stream_coder *coder, + const lzma_allocator *allocator) +{ + // Initialize the Index hash used to verify the Index. + coder->index_hash = lzma_index_hash_init(coder->index_hash, allocator); + if (coder->index_hash == NULL) + return LZMA_MEM_ERROR; + + // Reset the rest of the variables. + coder->sequence = SEQ_STREAM_HEADER; + coder->pos = 0; + + return LZMA_OK; +} + + +static lzma_ret +stream_decode_mt(void *coder_ptr, const lzma_allocator *allocator, + const uint8_t *restrict in, size_t *restrict in_pos, + size_t in_size, + uint8_t *restrict out, size_t *restrict out_pos, + size_t out_size, lzma_action action) +{ + struct lzma_stream_coder *coder = coder_ptr; + + mythread_condtime wait_abs; + bool has_blocked = false; + + // Determine if in SEQ_BLOCK_HEADER and SEQ_BLOCK_THR_RUN we should + // tell read_output_and_wait() to wait until it can fill the output + // buffer (or a timeout occurs). Two conditions must be met: + // + // (1) If the caller provided no new input. The reason for this + // can be, for example, the end of the file or that there is + // a pause in the input stream and more input is available + // a little later. In this situation we should wait for output + // because otherwise we would end up in a busy-waiting loop where + // we make no progress and the application just calls us again + // without providing any new input. This would then result in + // LZMA_BUF_ERROR even though more output would be available + // once the worker threads decode more data. + // + // (2) Even if (1) is true, we will not wait if the previous call to + // this function managed to produce some output and the output + // buffer became full. This is for compatibility with applications + // that call lzma_code() in such a way that new input is provided + // only when the output buffer didn't become full. Without this + // trick such applications would have bad performance (bad + // parallelization due to decoder not getting input fast enough). + // + // NOTE: Such loops might require that timeout is disabled (0) + // if they assume that output-not-full implies that all input has + // been consumed. If and only if timeout is enabled, we may return + // when output isn't full *and* not all input has been consumed. + // + // However, if LZMA_FINISH is used, the above is ignored and we always + // wait (timeout can still cause us to return) because we know that + // we won't get any more input. This matters if the input file is + // truncated and we are doing single-shot decoding, that is, + // timeout = 0 and LZMA_FINISH is used on the first call to + // lzma_code() and the output buffer is known to be big enough + // to hold all uncompressed data: + // + // - If LZMA_FINISH wasn't handled specially, we could return + // LZMA_OK before providing all output that is possible with the + // truncated input. The rest would be available if lzma_code() was + // called again but then it's not single-shot decoding anymore. + // + // - By handling LZMA_FINISH specially here, the first call will + // produce all the output, matching the behavior of the + // single-threaded decoder. + // + // So it's a very specific corner case but also easy to avoid. Note + // that this special handling of LZMA_FINISH has no effect for + // single-shot decoding when the input file is valid (not truncated); + // premature LZMA_OK wouldn't be possible as long as timeout = 0. + const bool waiting_allowed = action == LZMA_FINISH + || (*in_pos == in_size && !coder->out_was_filled); + coder->out_was_filled = false; + + while (true) + switch (coder->sequence) { + case SEQ_STREAM_HEADER: { + // Copy the Stream Header to the internal buffer. + const size_t in_old = *in_pos; + lzma_bufcpy(in, in_pos, in_size, coder->buffer, &coder->pos, + LZMA_STREAM_HEADER_SIZE); + coder->progress_in += *in_pos - in_old; + + // Return if we didn't get the whole Stream Header yet. + if (coder->pos < LZMA_STREAM_HEADER_SIZE) + return LZMA_OK; + + coder->pos = 0; + + // Decode the Stream Header. + const lzma_ret ret = lzma_stream_header_decode( + &coder->stream_flags, coder->buffer); + if (ret != LZMA_OK) + return ret == LZMA_FORMAT_ERROR && !coder->first_stream + ? LZMA_DATA_ERROR : ret; + + // If we are decoding concatenated Streams, and the later + // Streams have invalid Header Magic Bytes, we give + // LZMA_DATA_ERROR instead of LZMA_FORMAT_ERROR. + coder->first_stream = false; + + // Copy the type of the Check so that Block Header and Block + // decoders see it. + coder->block_options.check = coder->stream_flags.check; + + // Even if we return LZMA_*_CHECK below, we want + // to continue from Block Header decoding. + coder->sequence = SEQ_BLOCK_HEADER; + + // Detect if there's no integrity check or if it is + // unsupported if those were requested by the application. + if (coder->tell_no_check && coder->stream_flags.check + == LZMA_CHECK_NONE) + return LZMA_NO_CHECK; + + if (coder->tell_unsupported_check + && !lzma_check_is_supported( + coder->stream_flags.check)) + return LZMA_UNSUPPORTED_CHECK; + + if (coder->tell_any_check) + return LZMA_GET_CHECK; + } + + // Fall through + + case SEQ_BLOCK_HEADER: { + const size_t in_old = *in_pos; + const lzma_ret ret = decode_block_header(coder, allocator, + in, in_pos, in_size); + coder->progress_in += *in_pos - in_old; + + if (ret == LZMA_OK) { + // We didn't decode the whole Block Header yet. + // + // Read output from the queue before returning. This + // is important because it is possible that the + // application doesn't have any new input available + // immediately. If we didn't try to copy output from + // the output queue here, lzma_code() could end up + // returning LZMA_BUF_ERROR even though queued output + // is available. + // + // If the lzma_code() call provided at least one input + // byte, only copy as much data from the output queue + // as is available immediately. This way the + // application will be able to provide more input + // without a delay. + // + // On the other hand, if lzma_code() was called with + // an empty input buffer(*), treat it specially: try + // to fill the output buffer even if it requires + // waiting for the worker threads to provide output + // (timeout, if specified, can still cause us to + // return). + // + // - This way the application will be able to get all + // data that can be decoded from the input provided + // so far. + // + // - We avoid both premature LZMA_BUF_ERROR and + // busy-waiting where the application repeatedly + // calls lzma_code() which immediately returns + // LZMA_OK without providing new data. + // + // - If the queue becomes empty, we won't wait + // anything and will return LZMA_OK immediately + // (coder->timeout is completely ignored). + // + // (*) See the comment at the beginning of this + // function how waiting_allowed is determined + // and why there is an exception to the rule + // of "called with an empty input buffer". + assert(*in_pos == in_size); + + // If LZMA_FINISH was used we know that we won't get + // more input, so the file must be truncated if we + // get here. If worker threads don't detect any + // errors, eventually there will be no more output + // while we keep returning LZMA_OK which gets + // converted to LZMA_BUF_ERROR in lzma_code(). + // + // If fail-fast is enabled then we will return + // immediately using LZMA_DATA_ERROR instead of + // LZMA_OK or LZMA_BUF_ERROR. Rationale for the + // error code: + // + // - Worker threads may have a large amount of + // not-yet-decoded input data and we don't + // know for sure if all data is valid. Bad + // data there would result in LZMA_DATA_ERROR + // when fail-fast isn't used. + // + // - Immediate LZMA_BUF_ERROR would be a bit weird + // considering the older liblzma code. lzma_code() + // even has an assertion to prevent coders from + // returning LZMA_BUF_ERROR directly. + // + // The downside of this is that with fail-fast apps + // cannot always distinguish between corrupt and + // truncated files. + if (action == LZMA_FINISH && coder->fail_fast) { + // We won't produce any more output. Stop + // the unfinished worker threads so they + // won't waste CPU time. + threads_stop(coder); + return LZMA_DATA_ERROR; + } + + // read_output_and_wait() will call threads_stop() + // if needed so with that we can use return_if_error. + return_if_error(read_output_and_wait(coder, allocator, + out, out_pos, out_size, + NULL, waiting_allowed, + &wait_abs, &has_blocked)); + + if (coder->pending_error != LZMA_OK) { + coder->sequence = SEQ_ERROR; + break; + } + + return LZMA_OK; + } + + if (ret == LZMA_INDEX_DETECTED) { + coder->sequence = SEQ_INDEX_WAIT_OUTPUT; + break; + } + + // See if an error occurred. + if (ret != LZMA_STREAM_END) { + // NOTE: Here and in all other places where + // pending_error is set, it may overwrite the value + // (LZMA_PROG_ERROR) set by read_output_and_wait(). + // That function might overwrite value set here too. + // These are fine because when read_output_and_wait() + // sets pending_error, it actually works as a flag + // variable only ("some error has occurred") and the + // actual value of pending_error is not used in + // SEQ_ERROR. In such cases SEQ_ERROR will eventually + // get the correct error code from the return value of + // a later read_output_and_wait() call. + coder->pending_error = ret; + coder->sequence = SEQ_ERROR; + break; + } + + // Calculate the memory usage of the filters / Block decoder. + coder->mem_next_filters = lzma_raw_decoder_memusage( + coder->filters); + + if (coder->mem_next_filters == UINT64_MAX) { + // One or more unknown Filter IDs. + coder->pending_error = LZMA_OPTIONS_ERROR; + coder->sequence = SEQ_ERROR; + break; + } + + coder->sequence = SEQ_BLOCK_INIT; + } + + // Fall through + + case SEQ_BLOCK_INIT: { + // Check if decoding is possible at all with the current + // memlimit_stop which we must never exceed. + // + // This needs to be the first thing in SEQ_BLOCK_INIT + // to make it possible to restart decoding after increasing + // memlimit_stop with lzma_memlimit_set(). + if (coder->mem_next_filters > coder->memlimit_stop) { + // Flush pending output before returning + // LZMA_MEMLIMIT_ERROR. If the application doesn't + // want to increase the limit, at least it will get + // all the output possible so far. + return_if_error(read_output_and_wait(coder, allocator, + out, out_pos, out_size, + NULL, true, &wait_abs, &has_blocked)); + + if (!lzma_outq_is_empty(&coder->outq)) + return LZMA_OK; + + return LZMA_MEMLIMIT_ERROR; + } + + // Check if the size information is available in Block Header. + // If it is, check if the sizes are small enough that we don't + // need to worry *too* much about integer overflows later in + // the code. If these conditions are not met, we must use the + // single-threaded direct mode. + if (is_direct_mode_needed(coder->block_options.compressed_size) + || is_direct_mode_needed( + coder->block_options.uncompressed_size)) { + coder->sequence = SEQ_BLOCK_DIRECT_INIT; + break; + } + + // Calculate the amount of memory needed for the input and + // output buffers in threaded mode. + // + // These cannot overflow because we already checked that + // the sizes are small enough using is_direct_mode_needed(). + coder->mem_next_in = comp_blk_size(coder); + const uint64_t mem_buffers = coder->mem_next_in + + lzma_outq_outbuf_memusage( + coder->block_options.uncompressed_size); + + // Add the amount needed by the filters. + // Avoid integer overflows. + if (UINT64_MAX - mem_buffers < coder->mem_next_filters) { + // Use direct mode if the memusage would overflow. + // This is a theoretical case that shouldn't happen + // in practice unless the input file is weird (broken + // or malicious). + coder->sequence = SEQ_BLOCK_DIRECT_INIT; + break; + } + + // Amount of memory needed to decode this Block in + // threaded mode: + coder->mem_next_block = coder->mem_next_filters + mem_buffers; + + // If this alone would exceed memlimit_threading, then we must + // use the single-threaded direct mode. + if (coder->mem_next_block > coder->memlimit_threading) { + coder->sequence = SEQ_BLOCK_DIRECT_INIT; + break; + } + + // Use the threaded mode. Free the direct mode decoder in + // case it has been initialized. + lzma_next_end(&coder->block_decoder, allocator); + coder->mem_direct_mode = 0; + + // Since we already know what the sizes are supposed to be, + // we can already add them to the Index hash. The Block + // decoder will verify the values while decoding. + const lzma_ret ret = lzma_index_hash_append(coder->index_hash, + lzma_block_unpadded_size( + &coder->block_options), + coder->block_options.uncompressed_size); + if (ret != LZMA_OK) { + coder->pending_error = ret; + coder->sequence = SEQ_ERROR; + break; + } + + coder->sequence = SEQ_BLOCK_THR_INIT; + } + + // Fall through + + case SEQ_BLOCK_THR_INIT: { + // We need to wait for a multiple conditions to become true + // until we can initialize the Block decoder and let a worker + // thread decode it: + // + // - Wait for the memory usage of the active threads to drop + // so that starting the decoding of this Block won't make + // us go over memlimit_threading. + // + // - Wait for at least one free output queue slot. + // + // - Wait for a free worker thread. + // + // While we wait, we must copy decompressed data to the out + // buffer and catch possible decoder errors. + // + // read_output_and_wait() does all the above. + bool block_can_start = false; + + return_if_error(read_output_and_wait(coder, allocator, + out, out_pos, out_size, + &block_can_start, true, + &wait_abs, &has_blocked)); + + if (coder->pending_error != LZMA_OK) { + coder->sequence = SEQ_ERROR; + break; + } + + if (!block_can_start) { + // It's not a timeout because return_if_error handles + // it already. Output queue cannot be empty either + // because in that case block_can_start would have + // been true. Thus the output buffer must be full and + // the queue isn't empty. + assert(*out_pos == out_size); + assert(!lzma_outq_is_empty(&coder->outq)); + return LZMA_OK; + } + + // We know that we can start decoding this Block without + // exceeding memlimit_threading. However, to stay below + // memlimit_threading may require freeing some of the + // cached memory. + // + // Get a local copy of variables that require locking the + // mutex. It is fine if the worker threads modify the real + // values after we read these as those changes can only be + // towards more favorable conditions (less memory in use, + // more in cache). + // + // These are initialized to silence warnings. + uint64_t mem_in_use = 0; + uint64_t mem_cached = 0; + struct worker_thread *thr = NULL; + + mythread_sync(coder->mutex) { + mem_in_use = coder->mem_in_use; + mem_cached = coder->mem_cached; + thr = coder->threads_free; + } + + // The maximum amount of memory that can be held by other + // threads and cached buffers while allowing us to start + // decoding the next Block. + const uint64_t mem_max = coder->memlimit_threading + - coder->mem_next_block; + + // If the existing allocations are so large that starting + // to decode this Block might exceed memlimit_threads, + // try to free memory from the output queue cache first. + // + // NOTE: This math assumes the worst case. It's possible + // that the limit wouldn't be exceeded if the existing cached + // allocations are reused. + if (mem_in_use + mem_cached + coder->outq.mem_allocated + > mem_max) { + // Clear the outq cache except leave one buffer in + // the cache if its size is correct. That way we + // don't free and almost immediately reallocate + // an identical buffer. + lzma_outq_clear_cache2(&coder->outq, allocator, + coder->block_options.uncompressed_size); + } + + // If there is at least one worker_thread in the cache and + // the existing allocations are so large that starting to + // decode this Block might exceed memlimit_threads, free + // memory by freeing cached Block decoders. + // + // NOTE: The comparison is different here than above. + // Here we don't care about cached buffers in outq anymore + // and only look at memory actually in use. This is because + // if there is something in outq cache, it's a single buffer + // that can be used as is. We ensured this in the above + // if-block. + uint64_t mem_freed = 0; + if (thr != NULL && mem_in_use + mem_cached + + coder->outq.mem_in_use > mem_max) { + // Don't free the first Block decoder if its memory + // usage isn't greater than what this Block will need. + // Typically the same filter chain is used for all + // Blocks so this way the allocations can be reused + // when get_thread() picks the first worker_thread + // from the cache. + if (thr->mem_filters <= coder->mem_next_filters) + thr = thr->next; + + while (thr != NULL) { + lzma_next_end(&thr->block_decoder, allocator); + mem_freed += thr->mem_filters; + thr->mem_filters = 0; + thr = thr->next; + } + } + + // Update the memory usage counters. Note that coder->mem_* + // may have changed since we read them so we must subtract + // or add the changes. + mythread_sync(coder->mutex) { + coder->mem_cached -= mem_freed; + + // Memory needed for the filters and the input buffer. + // The output queue takes care of its own counter so + // we don't touch it here. + // + // NOTE: After this, coder->mem_in_use + + // coder->mem_cached might count the same thing twice. + // If so, this will get corrected in get_thread() when + // a worker_thread is picked from coder->free_threads + // and its memory usage is subtracted from mem_cached. + coder->mem_in_use += coder->mem_next_in + + coder->mem_next_filters; + } + + // Allocate memory for the output buffer in the output queue. + lzma_ret ret = lzma_outq_prealloc_buf( + &coder->outq, allocator, + coder->block_options.uncompressed_size); + if (ret != LZMA_OK) { + threads_stop(coder); + return ret; + } + + // Set up coder->thr. + ret = get_thread(coder, allocator); + if (ret != LZMA_OK) { + threads_stop(coder); + return ret; + } + + // The new Block decoder memory usage is already counted in + // coder->mem_in_use. Store it in the thread too. + coder->thr->mem_filters = coder->mem_next_filters; + + // Initialize the Block decoder. + coder->thr->block_options = coder->block_options; + ret = lzma_block_decoder_init( + &coder->thr->block_decoder, allocator, + &coder->thr->block_options); + + // Free the allocated filter options since they are needed + // only to initialize the Block decoder. + lzma_filters_free(coder->filters, allocator); + coder->thr->block_options.filters = NULL; + + // Check if memory usage calculation and Block encoder + // initialization succeeded. + if (ret != LZMA_OK) { + coder->pending_error = ret; + coder->sequence = SEQ_ERROR; + break; + } + + // Allocate the input buffer. + coder->thr->in_size = coder->mem_next_in; + coder->thr->in = lzma_alloc(coder->thr->in_size, allocator); + if (coder->thr->in == NULL) { + threads_stop(coder); + return LZMA_MEM_ERROR; + } + + // Get the preallocated output buffer. + coder->thr->outbuf = lzma_outq_get_buf( + &coder->outq, coder->thr); + + // Start the decoder. + mythread_sync(coder->thr->mutex) { + assert(coder->thr->state == THR_IDLE); + coder->thr->state = THR_RUN; + mythread_cond_signal(&coder->thr->cond); + } + + // Enable output from the thread that holds the oldest output + // buffer in the output queue (if such a thread exists). + mythread_sync(coder->mutex) { + lzma_outq_enable_partial_output(&coder->outq, + &worker_enable_partial_update); + } + + coder->sequence = SEQ_BLOCK_THR_RUN; + } + + // Fall through + + case SEQ_BLOCK_THR_RUN: { + if (action == LZMA_FINISH && coder->fail_fast) { + // We know that we won't get more input and that + // the caller wants fail-fast behavior. If we see + // that we don't have enough input to finish this + // Block, return LZMA_DATA_ERROR immediately. + // See SEQ_BLOCK_HEADER for the error code rationale. + const size_t in_avail = in_size - *in_pos; + const size_t in_needed = coder->thr->in_size + - coder->thr->in_filled; + if (in_avail < in_needed) { + threads_stop(coder); + return LZMA_DATA_ERROR; + } + } + + // Copy input to the worker thread. + size_t cur_in_filled = coder->thr->in_filled; + lzma_bufcpy(in, in_pos, in_size, coder->thr->in, + &cur_in_filled, coder->thr->in_size); + + // Tell the thread how much we copied. + mythread_sync(coder->thr->mutex) { + coder->thr->in_filled = cur_in_filled; + + // NOTE: Most of the time we are copying input faster + // than the thread can decode so most of the time + // calling mythread_cond_signal() is useless but + // we cannot make it conditional because thr->in_pos + // is updated without a mutex. And the overhead should + // be very much negligible anyway. + mythread_cond_signal(&coder->thr->cond); + } + + // Read output from the output queue. Just like in + // SEQ_BLOCK_HEADER, we wait to fill the output buffer + // only if waiting_allowed was set to true in the beginning + // of this function (see the comment there). + return_if_error(read_output_and_wait(coder, allocator, + out, out_pos, out_size, + NULL, waiting_allowed, + &wait_abs, &has_blocked)); + + if (coder->pending_error != LZMA_OK) { + coder->sequence = SEQ_ERROR; + break; + } + + // Return if the input didn't contain the whole Block. + if (coder->thr->in_filled < coder->thr->in_size) { + assert(*in_pos == in_size); + return LZMA_OK; + } + + // The whole Block has been copied to the thread-specific + // buffer. Continue from the next Block Header or Index. + coder->thr = NULL; + coder->sequence = SEQ_BLOCK_HEADER; + break; + } + + case SEQ_BLOCK_DIRECT_INIT: { + // Wait for the threads to finish and that all decoded data + // has been copied to the output. That is, wait until the + // output queue becomes empty. + // + // NOTE: No need to check for coder->pending_error as + // we aren't consuming any input until the queue is empty + // and if there is a pending error, read_output_and_wait() + // will eventually return it before the queue is empty. + return_if_error(read_output_and_wait(coder, allocator, + out, out_pos, out_size, + NULL, true, &wait_abs, &has_blocked)); + if (!lzma_outq_is_empty(&coder->outq)) + return LZMA_OK; + + // Free the cached output buffers. + lzma_outq_clear_cache(&coder->outq, allocator); + + // Get rid of the worker threads, including the coder->threads + // array. + threads_end(coder, allocator); + + // Initialize the Block decoder. + const lzma_ret ret = lzma_block_decoder_init( + &coder->block_decoder, allocator, + &coder->block_options); + + // Free the allocated filter options since they are needed + // only to initialize the Block decoder. + lzma_filters_free(coder->filters, allocator); + coder->block_options.filters = NULL; + + // Check if Block decoder initialization succeeded. + if (ret != LZMA_OK) + return ret; + + // Make the memory usage visible to _memconfig(). + coder->mem_direct_mode = coder->mem_next_filters; + + coder->sequence = SEQ_BLOCK_DIRECT_RUN; + } + + // Fall through + + case SEQ_BLOCK_DIRECT_RUN: { + const size_t in_old = *in_pos; + const size_t out_old = *out_pos; + const lzma_ret ret = coder->block_decoder.code( + coder->block_decoder.coder, allocator, + in, in_pos, in_size, out, out_pos, out_size, + action); + coder->progress_in += *in_pos - in_old; + coder->progress_out += *out_pos - out_old; + + if (ret != LZMA_STREAM_END) + return ret; + + // Block decoded successfully. Add the new size pair to + // the Index hash. + return_if_error(lzma_index_hash_append(coder->index_hash, + lzma_block_unpadded_size( + &coder->block_options), + coder->block_options.uncompressed_size)); + + coder->sequence = SEQ_BLOCK_HEADER; + break; + } + + case SEQ_INDEX_WAIT_OUTPUT: + // Flush the output from all worker threads so that we can + // decode the Index without thinking about threading. + return_if_error(read_output_and_wait(coder, allocator, + out, out_pos, out_size, + NULL, true, &wait_abs, &has_blocked)); + + if (!lzma_outq_is_empty(&coder->outq)) + return LZMA_OK; + + coder->sequence = SEQ_INDEX_DECODE; + + // Fall through + + case SEQ_INDEX_DECODE: { + // If we don't have any input, don't call + // lzma_index_hash_decode() since it would return + // LZMA_BUF_ERROR, which we must not do here. + if (*in_pos >= in_size) + return LZMA_OK; + + // Decode the Index and compare it to the hash calculated + // from the sizes of the Blocks (if any). + const size_t in_old = *in_pos; + const lzma_ret ret = lzma_index_hash_decode(coder->index_hash, + in, in_pos, in_size); + coder->progress_in += *in_pos - in_old; + if (ret != LZMA_STREAM_END) + return ret; + + coder->sequence = SEQ_STREAM_FOOTER; + } + + // Fall through + + case SEQ_STREAM_FOOTER: { + // Copy the Stream Footer to the internal buffer. + const size_t in_old = *in_pos; + lzma_bufcpy(in, in_pos, in_size, coder->buffer, &coder->pos, + LZMA_STREAM_HEADER_SIZE); + coder->progress_in += *in_pos - in_old; + + // Return if we didn't get the whole Stream Footer yet. + if (coder->pos < LZMA_STREAM_HEADER_SIZE) + return LZMA_OK; + + coder->pos = 0; + + // Decode the Stream Footer. The decoder gives + // LZMA_FORMAT_ERROR if the magic bytes don't match, + // so convert that return code to LZMA_DATA_ERROR. + lzma_stream_flags footer_flags; + const lzma_ret ret = lzma_stream_footer_decode( + &footer_flags, coder->buffer); + if (ret != LZMA_OK) + return ret == LZMA_FORMAT_ERROR + ? LZMA_DATA_ERROR : ret; + + // Check that Index Size stored in the Stream Footer matches + // the real size of the Index field. + if (lzma_index_hash_size(coder->index_hash) + != footer_flags.backward_size) + return LZMA_DATA_ERROR; + + // Compare that the Stream Flags fields are identical in + // both Stream Header and Stream Footer. + return_if_error(lzma_stream_flags_compare( + &coder->stream_flags, &footer_flags)); + + if (!coder->concatenated) + return LZMA_STREAM_END; + + coder->sequence = SEQ_STREAM_PADDING; + } + + // Fall through + + case SEQ_STREAM_PADDING: + assert(coder->concatenated); + + // Skip over possible Stream Padding. + while (true) { + if (*in_pos >= in_size) { + // Unless LZMA_FINISH was used, we cannot + // know if there's more input coming later. + if (action != LZMA_FINISH) + return LZMA_OK; + + // Stream Padding must be a multiple of + // four bytes. + return coder->pos == 0 + ? LZMA_STREAM_END + : LZMA_DATA_ERROR; + } + + // If the byte is not zero, it probably indicates + // beginning of a new Stream (or the file is corrupt). + if (in[*in_pos] != 0x00) + break; + + ++*in_pos; + ++coder->progress_in; + coder->pos = (coder->pos + 1) & 3; + } + + // Stream Padding must be a multiple of four bytes (empty + // Stream Padding is OK). + if (coder->pos != 0) { + ++*in_pos; + ++coder->progress_in; + return LZMA_DATA_ERROR; + } + + // Prepare to decode the next Stream. + return_if_error(stream_decoder_reset(coder, allocator)); + break; + + case SEQ_ERROR: + if (!coder->fail_fast) { + // Let the application get all data before the point + // where the error was detected. This matches the + // behavior of single-threaded use. + // + // FIXME? Some errors (LZMA_MEM_ERROR) don't get here, + // they are returned immediately. Thus in rare cases + // the output will be less than in the single-threaded + // mode. Maybe this doesn't matter much in practice. + return_if_error(read_output_and_wait(coder, allocator, + out, out_pos, out_size, + NULL, true, &wait_abs, &has_blocked)); + + // We get here only if the error happened in the main + // thread, for example, unsupported Block Header. + if (!lzma_outq_is_empty(&coder->outq)) + return LZMA_OK; + } + + // We only get here if no errors were detected by the worker + // threads. Errors from worker threads would have already been + // returned by the call to read_output_and_wait() above. + return coder->pending_error; + + default: + assert(0); + return LZMA_PROG_ERROR; + } + + // Never reached +} + + +static void +stream_decoder_mt_end(void *coder_ptr, const lzma_allocator *allocator) +{ + struct lzma_stream_coder *coder = coder_ptr; + + threads_end(coder, allocator); + lzma_outq_end(&coder->outq, allocator); + + lzma_next_end(&coder->block_decoder, allocator); + lzma_filters_free(coder->filters, allocator); + lzma_index_hash_end(coder->index_hash, allocator); + + lzma_free(coder, allocator); + return; +} + + +static lzma_check +stream_decoder_mt_get_check(const void *coder_ptr) +{ + const struct lzma_stream_coder *coder = coder_ptr; + return coder->stream_flags.check; +} + + +static lzma_ret +stream_decoder_mt_memconfig(void *coder_ptr, uint64_t *memusage, + uint64_t *old_memlimit, uint64_t new_memlimit) +{ + // NOTE: This function gets/sets memlimit_stop. For now, + // memlimit_threading cannot be modified after initialization. + // + // *memusage will include cached memory too. Excluding cached memory + // would be misleading and it wouldn't help the applications to + // know how much memory is actually needed to decompress the file + // because the higher the number of threads and the memlimits are + // the more memory the decoder may use. + // + // Setting a new limit includes the cached memory too and too low + // limits will be rejected. Alternative could be to free the cached + // memory immediately if that helps to bring the limit down but + // the current way is the simplest. It's unlikely that limit needs + // to be lowered in the middle of a file anyway; the typical reason + // to want a new limit is to increase after LZMA_MEMLIMIT_ERROR + // and even such use isn't common. + struct lzma_stream_coder *coder = coder_ptr; + + mythread_sync(coder->mutex) { + *memusage = coder->mem_direct_mode + + coder->mem_in_use + + coder->mem_cached + + coder->outq.mem_allocated; + } + + // If no filter chains are allocated, *memusage may be zero. + // Always return at least LZMA_MEMUSAGE_BASE. + if (*memusage < LZMA_MEMUSAGE_BASE) + *memusage = LZMA_MEMUSAGE_BASE; + + *old_memlimit = coder->memlimit_stop; + + if (new_memlimit != 0) { + if (new_memlimit < *memusage) + return LZMA_MEMLIMIT_ERROR; + + coder->memlimit_stop = new_memlimit; + } + + return LZMA_OK; +} + + +static void +stream_decoder_mt_get_progress(void *coder_ptr, + uint64_t *progress_in, uint64_t *progress_out) +{ + struct lzma_stream_coder *coder = coder_ptr; + + // Lock coder->mutex to prevent finishing threads from moving their + // progress info from the worker_thread structure to lzma_stream_coder. + mythread_sync(coder->mutex) { + *progress_in = coder->progress_in; + *progress_out = coder->progress_out; + + for (size_t i = 0; i < coder->threads_initialized; ++i) { + mythread_sync(coder->threads[i].mutex) { + *progress_in += coder->threads[i].progress_in; + *progress_out += coder->threads[i] + .progress_out; + } + } + } + + return; +} + + +static lzma_ret +stream_decoder_mt_init(lzma_next_coder *next, const lzma_allocator *allocator, + const lzma_mt *options) +{ + struct lzma_stream_coder *coder; + + if (options->threads == 0 || options->threads > LZMA_THREADS_MAX) + return LZMA_OPTIONS_ERROR; + + if (options->flags & ~LZMA_SUPPORTED_FLAGS) + return LZMA_OPTIONS_ERROR; + + lzma_next_coder_init(&stream_decoder_mt_init, next, allocator); + + coder = next->coder; + if (!coder) { + coder = lzma_alloc(sizeof(struct lzma_stream_coder), allocator); + if (coder == NULL) + return LZMA_MEM_ERROR; + + next->coder = coder; + + if (mythread_mutex_init(&coder->mutex)) { + lzma_free(coder, allocator); + return LZMA_MEM_ERROR; + } + + if (mythread_cond_init(&coder->cond)) { + mythread_mutex_destroy(&coder->mutex); + lzma_free(coder, allocator); + return LZMA_MEM_ERROR; + } + + next->code = &stream_decode_mt; + next->end = &stream_decoder_mt_end; + next->get_check = &stream_decoder_mt_get_check; + next->memconfig = &stream_decoder_mt_memconfig; + next->get_progress = &stream_decoder_mt_get_progress; + + coder->filters[0].id = LZMA_VLI_UNKNOWN; + memzero(&coder->outq, sizeof(coder->outq)); + + coder->block_decoder = LZMA_NEXT_CODER_INIT; + coder->mem_direct_mode = 0; + + coder->index_hash = NULL; + coder->threads = NULL; + coder->threads_free = NULL; + coder->threads_initialized = 0; + } + + // Cleanup old filter chain if one remains after unfinished decoding + // of a previous Stream. + lzma_filters_free(coder->filters, allocator); + + // By allocating threads from scratch we can start memory-usage + // accounting from scratch, too. Changes in filter and block sizes may + // affect number of threads. + // + // FIXME? Reusing should be easy but unlike the single-threaded + // decoder, with some types of input file combinations reusing + // could leave quite a lot of memory allocated but unused (first + // file could allocate a lot, the next files could use fewer + // threads and some of the allocations from the first file would not + // get freed unless memlimit_threading forces us to clear caches). + // + // NOTE: The direct mode decoder isn't freed here if one exists. + // It will be reused or freed as needed in the main loop. + threads_end(coder, allocator); + + // All memusage counters start at 0 (including mem_direct_mode). + // The little extra that is needed for the structs in this file + // get accounted well enough by the filter chain memory usage + // which adds LZMA_MEMUSAGE_BASE for each chain. However, + // stream_decoder_mt_memconfig() has to handle this specially so that + // it will never return less than LZMA_MEMUSAGE_BASE as memory usage. + coder->mem_in_use = 0; + coder->mem_cached = 0; + coder->mem_next_block = 0; + + coder->progress_in = 0; + coder->progress_out = 0; + + coder->sequence = SEQ_STREAM_HEADER; + coder->thread_error = LZMA_OK; + coder->pending_error = LZMA_OK; + coder->thr = NULL; + + coder->timeout = options->timeout; + + coder->memlimit_threading = my_max(1, options->memlimit_threading); + coder->memlimit_stop = my_max(1, options->memlimit_stop); + if (coder->memlimit_threading > coder->memlimit_stop) + coder->memlimit_threading = coder->memlimit_stop; + + coder->tell_no_check = (options->flags & LZMA_TELL_NO_CHECK) != 0; + coder->tell_unsupported_check + = (options->flags & LZMA_TELL_UNSUPPORTED_CHECK) != 0; + coder->tell_any_check = (options->flags & LZMA_TELL_ANY_CHECK) != 0; + coder->ignore_check = (options->flags & LZMA_IGNORE_CHECK) != 0; + coder->concatenated = (options->flags & LZMA_CONCATENATED) != 0; + coder->fail_fast = (options->flags & LZMA_FAIL_FAST) != 0; + + coder->first_stream = true; + coder->out_was_filled = false; + coder->pos = 0; + + coder->threads_max = options->threads; + + return_if_error(lzma_outq_init(&coder->outq, allocator, + coder->threads_max)); + + return stream_decoder_reset(coder, allocator); +} + + +extern LZMA_API(lzma_ret) +lzma_stream_decoder_mt(lzma_stream *strm, const lzma_mt *options) +{ + lzma_next_strm_init(stream_decoder_mt_init, strm, options); + + strm->internal->supported_actions[LZMA_RUN] = true; + strm->internal->supported_actions[LZMA_FINISH] = true; + + return LZMA_OK; +} diff --git a/Utilities/cmliblzma/liblzma/common/stream_encoder.c b/Utilities/cmliblzma/liblzma/common/stream_encoder.c index 858cba473ad..e7e5b3fce7e 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/stream_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "block_encoder.h" @@ -219,8 +218,7 @@ stream_encoder_end(void *coder_ptr, const lzma_allocator *allocator) lzma_next_end(&coder->index_encoder, allocator); lzma_index_end(coder->index, allocator); - for (size_t i = 0; coder->filters[i].id != LZMA_VLI_UNKNOWN; ++i) - lzma_free(coder->filters[i].options, allocator); + lzma_filters_free(coder->filters, allocator); lzma_free(coder, allocator); return; @@ -233,6 +231,13 @@ stream_encoder_update(void *coder_ptr, const lzma_allocator *allocator, const lzma_filter *reversed_filters) { lzma_stream_coder *coder = coder_ptr; + lzma_ret ret; + + // Make a copy to a temporary buffer first. This way it is easier + // to keep the encoder state unchanged if an error occurs with + // lzma_filters_copy(). + lzma_filter temp[LZMA_FILTERS_MAX + 1]; + return_if_error(lzma_filters_copy(filters, temp, allocator)); if (coder->sequence <= SEQ_BLOCK_INIT) { // There is no incomplete Block waiting to be finished, @@ -240,31 +245,40 @@ stream_encoder_update(void *coder_ptr, const lzma_allocator *allocator, // trying to initialize the Block encoder with the new // chain. This way we detect if the chain is valid. coder->block_encoder_is_initialized = false; - coder->block_options.filters = (lzma_filter *)(filters); - const lzma_ret ret = block_encoder_init(coder, allocator); + coder->block_options.filters = temp; + ret = block_encoder_init(coder, allocator); coder->block_options.filters = coder->filters; if (ret != LZMA_OK) - return ret; + goto error; coder->block_encoder_is_initialized = true; } else if (coder->sequence <= SEQ_BLOCK_ENCODE) { // We are in the middle of a Block. Try to update only // the filter-specific options. - return_if_error(coder->block_encoder.update( + ret = coder->block_encoder.update( coder->block_encoder.coder, allocator, - filters, reversed_filters)); + filters, reversed_filters); + if (ret != LZMA_OK) + goto error; } else { // Trying to update the filter chain when we are already // encoding Index or Stream Footer. - return LZMA_PROG_ERROR; + ret = LZMA_PROG_ERROR; + goto error; } - // Free the copy of the old chain and make a copy of the new chain. - for (size_t i = 0; coder->filters[i].id != LZMA_VLI_UNKNOWN; ++i) - lzma_free(coder->filters[i].options, allocator); + // Free the options of the old chain. + lzma_filters_free(coder->filters, allocator); + + // Copy the new filter chain in place. + memcpy(coder->filters, temp, sizeof(temp)); + + return LZMA_OK; - return lzma_filters_copy(filters, coder->filters, allocator); +error: + lzma_filters_free(temp, allocator); + return ret; } @@ -319,7 +333,7 @@ stream_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, // Initialize the Block encoder. This way we detect unsupported // filter chains when initializing the Stream encoder instead of - // giving an error after Stream Header has already written out. + // giving an error after Stream Header has already been written out. return stream_encoder_update(coder, allocator, filters, NULL); } diff --git a/Utilities/cmliblzma/liblzma/common/stream_encoder_mt.c b/Utilities/cmliblzma/liblzma/common/stream_encoder_mt.c index 01e40339750..f0fef152331 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_encoder_mt.c +++ b/Utilities/cmliblzma/liblzma/common/stream_encoder_mt.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_encoder_mt.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "filter_encoder.h" @@ -85,6 +84,11 @@ struct worker_thread_s { /// Compression options for this Block lzma_block block_options; + /// Filter chain for this thread. By copying the filters array + /// to each thread it is possible to change the filter chain + /// between Blocks using lzma_filters_update(). + lzma_filter filters[LZMA_FILTERS_MAX + 1]; + /// Next structure in the stack of free worker threads. worker_thread *next; @@ -109,9 +113,22 @@ struct lzma_stream_coder_s { /// LZMA_FULL_FLUSH or LZMA_FULL_BARRIER is used earlier. size_t block_size; - /// The filter chain currently in use + /// The filter chain to use for the next Block. + /// This can be updated using lzma_filters_update() + /// after LZMA_FULL_BARRIER or LZMA_FULL_FLUSH. lzma_filter filters[LZMA_FILTERS_MAX + 1]; + /// A copy of filters[] will be put here when attempting to get + /// a new worker thread. This will be copied to a worker thread + /// when a thread becomes free and then this cache is marked as + /// empty by setting [0].id = LZMA_VLI_UNKNOWN. Without this cache + /// the filter options from filters[] would get uselessly copied + /// multiple times (allocated and freed) when waiting for a new free + /// worker thread. + /// + /// This is freed if filters[] is updated via lzma_filters_update(). + lzma_filter filters_cache[LZMA_FILTERS_MAX + 1]; + /// Index to hold sizes of the Blocks lzma_index *index; @@ -133,6 +150,9 @@ struct lzma_stream_coder_s { /// Output buffer queue for compressed data lzma_outq outq; + /// How much memory to allocate for each lzma_outbuf.buf + size_t outbuf_alloc_size; + /// Maximum wait time if cannot use all the input and cannot /// fill the output buffer. This is in milliseconds. @@ -196,7 +216,7 @@ worker_error(worker_thread *thr, lzma_ret ret) static worker_state -worker_encode(worker_thread *thr, worker_state state) +worker_encode(worker_thread *thr, size_t *out_pos, worker_state state) { assert(thr->progress_in == 0); assert(thr->progress_out == 0); @@ -205,12 +225,9 @@ worker_encode(worker_thread *thr, worker_state state) thr->block_options = (lzma_block){ .version = 0, .check = thr->coder->stream_flags.check, - .compressed_size = thr->coder->outq.buf_size_max, + .compressed_size = thr->outbuf->allocated, .uncompressed_size = thr->coder->block_size, - - // TODO: To allow changing the filter chain, the filters - // array must be copied to each worker_thread. - .filters = thr->coder->filters, + .filters = thr->filters, }; // Calculate maximum size of the Block Header. This amount is @@ -234,12 +251,12 @@ worker_encode(worker_thread *thr, worker_state state) size_t in_pos = 0; size_t in_size = 0; - thr->outbuf->size = thr->block_options.header_size; - const size_t out_size = thr->coder->outq.buf_size_max; + *out_pos = thr->block_options.header_size; + const size_t out_size = thr->outbuf->allocated; do { mythread_sync(thr->mutex) { - // Store in_pos and out_pos into *thr so that + // Store in_pos and *out_pos into *thr so that // an application may read them via // lzma_get_progress() to get progress information. // @@ -247,7 +264,7 @@ worker_encode(worker_thread *thr, worker_state state) // finishes. Instead, the final values are taken // later from thr->outbuf. thr->progress_in = in_pos; - thr->progress_out = thr->outbuf->size; + thr->progress_out = *out_pos; while (in_size == thr->in_size && thr->state == THR_RUN) @@ -277,8 +294,8 @@ worker_encode(worker_thread *thr, worker_state state) ret = thr->block_encoder.code( thr->block_encoder.coder, thr->allocator, thr->in, &in_pos, in_limit, thr->outbuf->buf, - &thr->outbuf->size, out_size, action); - } while (ret == LZMA_OK && thr->outbuf->size < out_size); + out_pos, out_size, action); + } while (ret == LZMA_OK && *out_pos < out_size); switch (ret) { case LZMA_STREAM_END: @@ -313,10 +330,10 @@ worker_encode(worker_thread *thr, worker_state state) return state; // Do the encoding. This takes care of the Block Header too. - thr->outbuf->size = 0; + *out_pos = 0; ret = lzma_block_uncomp_encode(&thr->block_options, thr->in, in_size, thr->outbuf->buf, - &thr->outbuf->size, out_size); + out_pos, out_size); // It shouldn't fail. if (ret != LZMA_OK) { @@ -367,11 +384,13 @@ worker_start(void *thr_ptr) } } + size_t out_pos = 0; + assert(state != THR_IDLE); assert(state != THR_STOP); if (state <= THR_FINISH) - state = worker_encode(thr, state); + state = worker_encode(thr, &out_pos, state); if (state == THR_EXIT) break; @@ -387,14 +406,17 @@ worker_start(void *thr_ptr) } mythread_sync(thr->coder->mutex) { - // Mark the output buffer as finished if - // no errors occurred. - thr->outbuf->finished = state == THR_FINISH; + // If no errors occurred, make the encoded data + // available to be copied out. + if (state == THR_FINISH) { + thr->outbuf->pos = out_pos; + thr->outbuf->finished = true; + } // Update the main progress info. thr->coder->progress_in += thr->outbuf->uncompressed_size; - thr->coder->progress_out += thr->outbuf->size; + thr->coder->progress_out += out_pos; thr->progress_in = 0; thr->progress_out = 0; @@ -407,6 +429,8 @@ worker_start(void *thr_ptr) } // Exiting, free the resources. + lzma_filters_free(thr->filters, thr->allocator); + mythread_mutex_destroy(&thr->mutex); mythread_cond_destroy(&thr->cond); @@ -490,6 +514,7 @@ initialize_new_thread(lzma_stream_coder *coder, thr->progress_in = 0; thr->progress_out = 0; thr->block_encoder = LZMA_NEXT_CODER_INIT; + thr->filters[0].id = LZMA_VLI_UNKNOWN; if (mythread_create(&thr->thread_id, &worker_start, thr)) goto error_thread; @@ -519,6 +544,18 @@ get_thread(lzma_stream_coder *coder, const lzma_allocator *allocator) if (!lzma_outq_has_buf(&coder->outq)) return LZMA_OK; + // That's also true if we cannot allocate memory for the output + // buffer in the output queue. + return_if_error(lzma_outq_prealloc_buf(&coder->outq, allocator, + coder->outbuf_alloc_size)); + + // Make a thread-specific copy of the filter chain. Put it in + // the cache array first so that if we cannot get a new thread yet, + // the allocation is ready when we try again. + if (coder->filters_cache[0].id == LZMA_VLI_UNKNOWN) + return_if_error(lzma_filters_copy( + coder->filters, coder->filters_cache, allocator)); + // If there is a free structure on the stack, use it. mythread_sync(coder->mutex) { if (coder->threads_free != NULL) { @@ -541,7 +578,16 @@ get_thread(lzma_stream_coder *coder, const lzma_allocator *allocator) mythread_sync(coder->thr->mutex) { coder->thr->state = THR_RUN; coder->thr->in_size = 0; - coder->thr->outbuf = lzma_outq_get_buf(&coder->outq); + coder->thr->outbuf = lzma_outq_get_buf(&coder->outq, NULL); + + // Free the old thread-specific filter options and replace + // them with the already-allocated new options from + // coder->filters_cache[]. Then mark the cache as empty. + lzma_filters_free(coder->thr->filters, allocator); + memcpy(coder->thr->filters, coder->filters_cache, + sizeof(coder->filters_cache)); + coder->filters_cache[0].id = LZMA_VLI_UNKNOWN; + mythread_cond_signal(&coder->thr->cond); } @@ -598,7 +644,7 @@ stream_encode_in(lzma_stream_coder *coder, const lzma_allocator *allocator, } if (block_error) { - lzma_ret ret; + lzma_ret ret = LZMA_OK; // Init to silence a warning. mythread_sync(coder->mutex) { ret = coder->thread_error; @@ -627,9 +673,13 @@ wait_for_work(lzma_stream_coder *coder, mythread_condtime *wait_abs, // to true here and calculate the absolute time when // we must return if there's nothing to do. // - // The idea of *has_blocked is to avoid unneeded calls - // to mythread_condtime_set(), which may do a syscall - // depending on the operating system. + // This way if we block multiple times for short moments + // less than "timeout" milliseconds, we will return once + // "timeout" amount of time has passed since the *first* + // blocking occurred. If the absolute time was calculated + // again every time we block, "timeout" would effectively + // be meaningless if we never consecutively block longer + // than "timeout" ms. *has_blocked = true; mythread_condtime_set(wait_abs, &coder->cond, coder->timeout); } @@ -692,7 +742,7 @@ stream_encode_mt(void *coder_ptr, const lzma_allocator *allocator, // These are for wait_for_work(). bool has_blocked = false; - mythread_condtime wait_abs; + mythread_condtime wait_abs = { 0 }; while (true) { mythread_sync(coder->mutex) { @@ -704,7 +754,7 @@ stream_encode_mt(void *coder_ptr, const lzma_allocator *allocator, } // Try to read compressed data to out[]. - ret = lzma_outq_read(&coder->outq, + ret = lzma_outq_read(&coder->outq, allocator, out, out_pos, out_size, &unpadded_size, &uncompressed_size); @@ -715,6 +765,10 @@ stream_encode_mt(void *coder_ptr, const lzma_allocator *allocator, ret = lzma_index_append(coder->index, allocator, unpadded_size, uncompressed_size); + if (ret != LZMA_OK) { + threads_stop(coder, false); + return ret; + } // If we didn't fill the output buffer yet, // try to read more data. Maybe the next @@ -724,8 +778,7 @@ stream_encode_mt(void *coder_ptr, const lzma_allocator *allocator, } if (ret != LZMA_OK) { - // coder->thread_error was set or - // lzma_index_append() failed. + // coder->thread_error was set. threads_stop(coder, false); return ret; } @@ -846,8 +899,8 @@ stream_encoder_mt_end(void *coder_ptr, const lzma_allocator *allocator) threads_end(coder, allocator); lzma_outq_end(&coder->outq, allocator); - for (size_t i = 0; coder->filters[i].id != LZMA_VLI_UNKNOWN; ++i) - lzma_free(coder->filters[i].options, allocator); + lzma_filters_free(coder->filters, allocator); + lzma_filters_free(coder->filters_cache, allocator); lzma_next_end(&coder->index_encoder, allocator); lzma_index_end(coder->index, allocator); @@ -860,6 +913,45 @@ stream_encoder_mt_end(void *coder_ptr, const lzma_allocator *allocator) } +static lzma_ret +stream_encoder_mt_update(void *coder_ptr, const lzma_allocator *allocator, + const lzma_filter *filters, + const lzma_filter *reversed_filters + lzma_attribute((__unused__))) +{ + lzma_stream_coder *coder = coder_ptr; + + // Applications shouldn't attempt to change the options when + // we are already encoding the Index or Stream Footer. + if (coder->sequence > SEQ_BLOCK) + return LZMA_PROG_ERROR; + + // For now the threaded encoder doesn't support changing + // the options in the middle of a Block. + if (coder->thr != NULL) + return LZMA_PROG_ERROR; + + // Check if the filter chain seems mostly valid. See the comment + // in stream_encoder_mt_init(). + if (lzma_raw_encoder_memusage(filters) == UINT64_MAX) + return LZMA_OPTIONS_ERROR; + + // Make a copy to a temporary buffer first. This way the encoder + // state stays unchanged if an error occurs in lzma_filters_copy(). + lzma_filter temp[LZMA_FILTERS_MAX + 1]; + return_if_error(lzma_filters_copy(filters, temp, allocator)); + + // Free the options of the old chain as well as the cache. + lzma_filters_free(coder->filters, allocator); + lzma_filters_free(coder->filters_cache, allocator); + + // Copy the new filter chain in place. + memcpy(coder->filters, temp, sizeof(temp)); + + return LZMA_OK; +} + + /// Options handling for lzma_stream_encoder_mt_init() and /// lzma_stream_encoder_mt_memusage() static lzma_ret @@ -886,20 +978,18 @@ get_options(const lzma_mt *options, lzma_options_easy *opt_easy, *filters = opt_easy->filters; } - // Block size - if (options->block_size > 0) { - if (options->block_size > BLOCK_SIZE_MAX) - return LZMA_OPTIONS_ERROR; - + // If the Block size is not set, determine it from the filter chain. + if (options->block_size > 0) *block_size = options->block_size; - } else { - // Determine the Block size from the filter chain. + else *block_size = lzma_mt_block_size(*filters); - if (*block_size == 0) - return LZMA_OPTIONS_ERROR; - assert(*block_size <= BLOCK_SIZE_MAX); - } + // UINT64_MAX > BLOCK_SIZE_MAX, so the second condition + // should be optimized out by any reasonable compiler. + // The second condition should be there in the unlikely event that + // the macros change and UINT64_MAX < BLOCK_SIZE_MAX. + if (*block_size > BLOCK_SIZE_MAX || *block_size == UINT64_MAX) + return LZMA_OPTIONS_ERROR; // Calculate the maximum amount output that a single output buffer // may need to hold. This is the same as the maximum total size of @@ -951,14 +1041,16 @@ stream_encoder_mt_init(lzma_next_coder *next, const lzma_allocator *allocator, &block_size, &outbuf_size_max)); #if SIZE_MAX < UINT64_MAX - if (block_size > SIZE_MAX) + if (block_size > SIZE_MAX || outbuf_size_max > SIZE_MAX) return LZMA_MEM_ERROR; #endif // Validate the filter chain so that we can give an error in this // function instead of delaying it to the first call to lzma_code(). // The memory usage calculation verifies the filter chain as - // a side effect so we take advantage of that. + // a side effect so we take advantage of that. It's not a perfect + // check though as raw encoder allows LZMA1 too but such problems + // will be caught eventually with Block Header encoder. if (lzma_raw_encoder_memusage(filters) == UINT64_MAX) return LZMA_OPTIONS_ERROR; @@ -998,9 +1090,10 @@ stream_encoder_mt_init(lzma_next_coder *next, const lzma_allocator *allocator, next->code = &stream_encode_mt; next->end = &stream_encoder_mt_end; next->get_progress = &get_progress; -// next->update = &stream_encoder_mt_update; + next->update = &stream_encoder_mt_update; coder->filters[0].id = LZMA_VLI_UNKNOWN; + coder->filters_cache[0].id = LZMA_VLI_UNKNOWN; coder->index_encoder = LZMA_NEXT_CODER_INIT; coder->index = NULL; memzero(&coder->outq, sizeof(coder->outq)); @@ -1012,6 +1105,7 @@ stream_encoder_mt_init(lzma_next_coder *next, const lzma_allocator *allocator, // Basic initializations coder->sequence = SEQ_STREAM_HEADER; coder->block_size = (size_t)(block_size); + coder->outbuf_alloc_size = (size_t)(outbuf_size_max); coder->thread_error = LZMA_OK; coder->thr = NULL; @@ -1041,15 +1135,16 @@ stream_encoder_mt_init(lzma_next_coder *next, const lzma_allocator *allocator, // Output queue return_if_error(lzma_outq_init(&coder->outq, allocator, - outbuf_size_max, options->threads)); + options->threads)); // Timeout coder->timeout = options->timeout; - // Free the old filter chain and copy the new one. - for (size_t i = 0; coder->filters[i].id != LZMA_VLI_UNKNOWN; ++i) - lzma_free(coder->filters[i].options, allocator); + // Free the old filter chain and the cache. + lzma_filters_free(coder->filters, allocator); + lzma_filters_free(coder->filters_cache, allocator); + // Copy the new filter chain. return_if_error(lzma_filters_copy( filters, coder->filters, allocator)); @@ -1075,6 +1170,31 @@ stream_encoder_mt_init(lzma_next_coder *next, const lzma_allocator *allocator, } +#ifdef HAVE_SYMBOL_VERSIONS_LINUX +// These are for compatibility with binaries linked against liblzma that +// has been patched with xz-5.2.2-compat-libs.patch from RHEL/CentOS 7. +// Actually that patch didn't create lzma_stream_encoder_mt@XZ_5.2.2 +// but it has been added here anyway since someone might misread the +// RHEL patch and think both @XZ_5.1.2alpha and @XZ_5.2.2 exist. +LZMA_SYMVER_API("lzma_stream_encoder_mt@XZ_5.1.2alpha", + lzma_ret, lzma_stream_encoder_mt_512a)( + lzma_stream *strm, const lzma_mt *options) + lzma_nothrow lzma_attr_warn_unused_result + __attribute__((__alias__("lzma_stream_encoder_mt_52"))); + +LZMA_SYMVER_API("lzma_stream_encoder_mt@XZ_5.2.2", + lzma_ret, lzma_stream_encoder_mt_522)( + lzma_stream *strm, const lzma_mt *options) + lzma_nothrow lzma_attr_warn_unused_result + __attribute__((__alias__("lzma_stream_encoder_mt_52"))); + +LZMA_SYMVER_API("lzma_stream_encoder_mt@@XZ_5.2", + lzma_ret, lzma_stream_encoder_mt_52)( + lzma_stream *strm, const lzma_mt *options) + lzma_nothrow lzma_attr_warn_unused_result; + +#define lzma_stream_encoder_mt lzma_stream_encoder_mt_52 +#endif extern LZMA_API(lzma_ret) lzma_stream_encoder_mt(lzma_stream *strm, const lzma_mt *options) { @@ -1090,6 +1210,23 @@ lzma_stream_encoder_mt(lzma_stream *strm, const lzma_mt *options) } +#ifdef HAVE_SYMBOL_VERSIONS_LINUX +LZMA_SYMVER_API("lzma_stream_encoder_mt_memusage@XZ_5.1.2alpha", + uint64_t, lzma_stream_encoder_mt_memusage_512a)( + const lzma_mt *options) lzma_nothrow lzma_attr_pure + __attribute__((__alias__("lzma_stream_encoder_mt_memusage_52"))); + +LZMA_SYMVER_API("lzma_stream_encoder_mt_memusage@XZ_5.2.2", + uint64_t, lzma_stream_encoder_mt_memusage_522)( + const lzma_mt *options) lzma_nothrow lzma_attr_pure + __attribute__((__alias__("lzma_stream_encoder_mt_memusage_52"))); + +LZMA_SYMVER_API("lzma_stream_encoder_mt_memusage@@XZ_5.2", + uint64_t, lzma_stream_encoder_mt_memusage_52)( + const lzma_mt *options) lzma_nothrow lzma_attr_pure; + +#define lzma_stream_encoder_mt_memusage lzma_stream_encoder_mt_memusage_52 +#endif // This function name is a monster but it's consistent with the older // monster names. :-( 31 chars is the max that C99 requires so in that // sense it's not too long. ;-) diff --git a/Utilities/cmliblzma/liblzma/common/stream_flags_common.c b/Utilities/cmliblzma/liblzma/common/stream_flags_common.c index fbe8eb8abda..41b8dcb70d7 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_flags_common.c +++ b/Utilities/cmliblzma/liblzma/common/stream_flags_common.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_flags_common.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "stream_flags_common.h" diff --git a/Utilities/cmliblzma/liblzma/common/stream_flags_common.h b/Utilities/cmliblzma/liblzma/common/stream_flags_common.h index 9f3122a3b1e..28729dbcb6f 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_flags_common.h +++ b/Utilities/cmliblzma/liblzma/common/stream_flags_common.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_flags_common.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_STREAM_FLAGS_COMMON_H @@ -18,7 +17,10 @@ /// Size of the Stream Flags field #define LZMA_STREAM_FLAGS_SIZE 2 +lzma_attr_visibility_hidden extern const uint8_t lzma_header_magic[6]; + +lzma_attr_visibility_hidden extern const uint8_t lzma_footer_magic[2]; diff --git a/Utilities/cmliblzma/liblzma/common/stream_flags_decoder.c b/Utilities/cmliblzma/liblzma/common/stream_flags_decoder.c index 4e43e359e1e..522c98b6fd5 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_flags_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/stream_flags_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_flags_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "stream_flags_common.h" @@ -39,8 +38,11 @@ lzma_stream_header_decode(lzma_stream_flags *options, const uint8_t *in) const uint32_t crc = lzma_crc32(in + sizeof(lzma_header_magic), LZMA_STREAM_FLAGS_SIZE, 0); if (crc != read32le(in + sizeof(lzma_header_magic) - + LZMA_STREAM_FLAGS_SIZE)) + + LZMA_STREAM_FLAGS_SIZE)) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return LZMA_DATA_ERROR; +#endif + } // Stream Flags if (stream_flags_decode(options, in + sizeof(lzma_header_magic))) @@ -67,8 +69,11 @@ lzma_stream_footer_decode(lzma_stream_flags *options, const uint8_t *in) // CRC32 const uint32_t crc = lzma_crc32(in + sizeof(uint32_t), sizeof(uint32_t) + LZMA_STREAM_FLAGS_SIZE, 0); - if (crc != read32le(in)) + if (crc != read32le(in)) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return LZMA_DATA_ERROR; +#endif + } // Stream Flags if (stream_flags_decode(options, in + sizeof(uint32_t) * 2)) diff --git a/Utilities/cmliblzma/liblzma/common/stream_flags_encoder.c b/Utilities/cmliblzma/liblzma/common/stream_flags_encoder.c index b98ab17c456..f94b5cd0a23 100644 --- a/Utilities/cmliblzma/liblzma/common/stream_flags_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/stream_flags_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file stream_flags_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "stream_flags_common.h" diff --git a/Utilities/cmliblzma/liblzma/common/string_conversion.c b/Utilities/cmliblzma/liblzma/common/string_conversion.c new file mode 100644 index 00000000000..c899783c642 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/common/string_conversion.c @@ -0,0 +1,1338 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file string_conversion.c +/// \brief Conversion of strings to filter chain and vice versa +// +// Author: Lasse Collin +// +/////////////////////////////////////////////////////////////////////////////// + +#include "filter_common.h" + + +///////////////////// +// String building // +///////////////////// + +/// How much memory to allocate for strings. For now, no realloc is used +/// so this needs to be big enough even though there of course is +/// an overflow check still. +/// +/// FIXME? Using a fixed size is wasteful if the application doesn't free +/// the string fairly quickly but this can be improved later if needed. +#define STR_ALLOC_SIZE 800 + + +typedef struct { + char *buf; + size_t pos; +} lzma_str; + + +static lzma_ret +str_init(lzma_str *str, const lzma_allocator *allocator) +{ + str->buf = lzma_alloc(STR_ALLOC_SIZE, allocator); + if (str->buf == NULL) + return LZMA_MEM_ERROR; + + str->pos = 0; + return LZMA_OK; +} + + +static void +str_free(lzma_str *str, const lzma_allocator *allocator) +{ + lzma_free(str->buf, allocator); + return; +} + + +static bool +str_is_full(const lzma_str *str) +{ + return str->pos == STR_ALLOC_SIZE - 1; +} + + +static lzma_ret +str_finish(char **dest, lzma_str *str, const lzma_allocator *allocator) +{ + if (str_is_full(str)) { + // The preallocated buffer was too small. + // This shouldn't happen as STR_ALLOC_SIZE should + // be adjusted if new filters are added. + lzma_free(str->buf, allocator); + *dest = NULL; + assert(0); + return LZMA_PROG_ERROR; + } + + str->buf[str->pos] = '\0'; + *dest = str->buf; + return LZMA_OK; +} + + +static void +str_append_str(lzma_str *str, const char *s) +{ + const size_t len = strlen(s); + const size_t limit = STR_ALLOC_SIZE - 1 - str->pos; + const size_t copy_size = my_min(len, limit); + + memcpy(str->buf + str->pos, s, copy_size); + str->pos += copy_size; + return; +} + + +static void +str_append_u32(lzma_str *str, uint32_t v, bool use_byte_suffix) +{ + if (v == 0) { + str_append_str(str, "0"); + } else { + // NOTE: Don't use plain "B" because xz and the parser in this + // file don't support it and at glance it may look like 8 + // (there cannot be a space before the suffix). + static const char suffixes[4][4] = { "", "KiB", "MiB", "GiB" }; + + size_t suf = 0; + if (use_byte_suffix) { + while ((v & 1023) == 0 + && suf < ARRAY_SIZE(suffixes) - 1) { + v >>= 10; + ++suf; + } + } + + // UINT32_MAX in base 10 would need 10 + 1 bytes. Remember + // that initializing to "" initializes all elements to + // zero so '\0'-termination gets handled by this. + char buf[16] = ""; + size_t pos = sizeof(buf) - 1; + + do { + buf[--pos] = '0' + (v % 10); + v /= 10; + } while (v != 0); + + str_append_str(str, buf + pos); + str_append_str(str, suffixes[suf]); + } + + return; +} + + +////////////////////////////////////////////// +// Parsing and stringification declarations // +////////////////////////////////////////////// + +/// Maximum length for filter and option names. +/// 11 chars + terminating '\0' + sizeof(uint32_t) = 16 bytes +#define NAME_LEN_MAX 11 + + +/// For option_map.flags: Use .u.map to do convert the input value +/// to an integer. Without this flag, .u.range.{min,max} are used +/// as the allowed range for the integer. +#define OPTMAP_USE_NAME_VALUE_MAP 0x01 + +/// For option_map.flags: Allow KiB/MiB/GiB in input string and use them in +/// the stringified output if the value is an exact multiple of these. +/// This is used e.g. for LZMA1/2 dictionary size. +#define OPTMAP_USE_BYTE_SUFFIX 0x02 + +/// For option_map.flags: If the integer value is zero then this option +/// won't be included in the stringified output. It's used e.g. for +/// BCJ filter start offset which usually is zero. +#define OPTMAP_NO_STRFY_ZERO 0x04 + +/// Possible values for option_map.type. Since OPTMAP_TYPE_UINT32 is 0, +/// it doesn't need to be specified in the initializers as it is +/// the implicit value. +enum { + OPTMAP_TYPE_UINT32, + OPTMAP_TYPE_LZMA_MODE, + OPTMAP_TYPE_LZMA_MATCH_FINDER, + OPTMAP_TYPE_LZMA_PRESET, +}; + + +/// This is for mapping string values in options to integers. +/// The last element of an array must have "" as the name. +/// It's used e.g. for match finder names in LZMA1/2. +typedef struct { + const char name[NAME_LEN_MAX + 1]; + const uint32_t value; +} name_value_map; + + +/// Each filter that has options needs an array of option_map structures. +/// The array doesn't need to be terminated as the functions take the +/// length of the array as an argument. +/// +/// When converting a string to filter options structure, option values +/// will be handled in a few different ways: +/// +/// (1) If .type equals OPTMAP_TYPE_LZMA_PRESET then LZMA1/2 preset string +/// is handled specially. +/// +/// (2) If .flags has OPTMAP_USE_NAME_VALUE_MAP set then the string is +/// converted to an integer using the name_value_map pointed by .u.map. +/// The last element in .u.map must have .name = "" as the terminator. +/// +/// (3) Otherwise the string is treated as a non-negative unsigned decimal +/// integer which must be in the range set in .u.range. If .flags has +/// OPTMAP_USE_BYTE_SUFFIX then KiB, MiB, and GiB suffixes are allowed. +/// +/// The integer value from (2) or (3) is then stored to filter_options +/// at the offset specified in .offset using the type specified in .type +/// (default is uint32_t). +/// +/// Stringifying a filter is done by processing a given number of options +/// in order from the beginning of an option_map array. The integer is +/// read from filter_options at .offset using the type from .type. +/// +/// If the integer is zero and .flags has OPTMAP_NO_STRFY_ZERO then the +/// option is skipped. +/// +/// If .flags has OPTMAP_USE_NAME_VALUE_MAP set then .u.map will be used +/// to convert the option to a string. If the map doesn't contain a string +/// for the integer value then "UNKNOWN" is used. +/// +/// If .flags doesn't have OPTMAP_USE_NAME_VALUE_MAP set then the integer is +/// converted to a decimal value. If OPTMAP_USE_BYTE_SUFFIX is used then KiB, +/// MiB, or GiB suffix is used if the value is an exact multiple of these. +/// Plain "B" suffix is never used. +typedef struct { + char name[NAME_LEN_MAX + 1]; + uint8_t type; + uint8_t flags; + uint16_t offset; + + union { + // NVHPC has problems with unions that contain pointers that + // are not the first members, so keep "map" at the top. + const name_value_map *map; + + struct { + uint32_t min; + uint32_t max; + } range; + } u; +} option_map; + + +static const char *parse_options(const char **const str, const char *str_end, + void *filter_options, + const option_map *const optmap, const size_t optmap_size); + + +///////// +// BCJ // +///////// + +#if defined(HAVE_ENCODER_X86) \ + || defined(HAVE_DECODER_X86) \ + || defined(HAVE_ENCODER_ARM) \ + || defined(HAVE_DECODER_ARM) \ + || defined(HAVE_ENCODER_ARMTHUMB) \ + || defined(HAVE_DECODER_ARMTHUMB) \ + || defined(HAVE_ENCODER_ARM64) \ + || defined(HAVE_DECODER_ARM64) \ + || defined(HAVE_ENCODER_POWERPC) \ + || defined(HAVE_DECODER_POWERPC) \ + || defined(HAVE_ENCODER_IA64) \ + || defined(HAVE_DECODER_IA64) \ + || defined(HAVE_ENCODER_SPARC) \ + || defined(HAVE_DECODER_SPARC) \ + || defined(HAVE_ENCODER_RISCV) \ + || defined(HAVE_DECODER_RISCV) +static const option_map bcj_optmap[] = { + { + .name = "start", + .flags = OPTMAP_NO_STRFY_ZERO | OPTMAP_USE_BYTE_SUFFIX, + .offset = offsetof(lzma_options_bcj, start_offset), + .u.range.min = 0, + .u.range.max = UINT32_MAX, + } +}; + + +static const char * +parse_bcj(const char **const str, const char *str_end, void *filter_options) +{ + // filter_options was zeroed on allocation and that is enough + // for the default value. + return parse_options(str, str_end, filter_options, + bcj_optmap, ARRAY_SIZE(bcj_optmap)); +} +#endif + + +/////////// +// Delta // +/////////// + +#if defined(HAVE_ENCODER_DELTA) || defined(HAVE_DECODER_DELTA) +static const option_map delta_optmap[] = { + { + .name = "dist", + .offset = offsetof(lzma_options_delta, dist), + .u.range.min = LZMA_DELTA_DIST_MIN, + .u.range.max = LZMA_DELTA_DIST_MAX, + } +}; + + +static const char * +parse_delta(const char **const str, const char *str_end, void *filter_options) +{ + lzma_options_delta *opts = filter_options; + opts->type = LZMA_DELTA_TYPE_BYTE; + opts->dist = LZMA_DELTA_DIST_MIN; + + return parse_options(str, str_end, filter_options, + delta_optmap, ARRAY_SIZE(delta_optmap)); +} +#endif + + +/////////////////// +// LZMA1 & LZMA2 // +/////////////////// + +/// Help string for presets +#define LZMA12_PRESET_STR "0-9[e]" + + +static const char * +parse_lzma12_preset(const char **const str, const char *str_end, + uint32_t *preset) +{ + assert(*str < str_end); + *preset = (uint32_t)(**str - '0'); + + // NOTE: Remember to update LZMA12_PRESET_STR if this is modified! + while (++*str < str_end) { + switch (**str) { + case 'e': + *preset |= LZMA_PRESET_EXTREME; + break; + + default: + return "Unsupported preset flag"; + } + } + + return NULL; +} + + +static const char * +set_lzma12_preset(const char **const str, const char *str_end, + void *filter_options) +{ + uint32_t preset; + const char *errmsg = parse_lzma12_preset(str, str_end, &preset); + if (errmsg != NULL) + return errmsg; + + lzma_options_lzma *opts = filter_options; + if (lzma_lzma_preset(opts, preset)) + return "Unsupported preset"; + + return NULL; +} + + +static const name_value_map lzma12_mode_map[] = { + { "fast", LZMA_MODE_FAST }, + { "normal", LZMA_MODE_NORMAL }, + { "", 0 } +}; + + +static const name_value_map lzma12_mf_map[] = { + { "hc3", LZMA_MF_HC3 }, + { "hc4", LZMA_MF_HC4 }, + { "bt2", LZMA_MF_BT2 }, + { "bt3", LZMA_MF_BT3 }, + { "bt4", LZMA_MF_BT4 }, + { "", 0 } +}; + + +static const option_map lzma12_optmap[] = { + { + .name = "preset", + .type = OPTMAP_TYPE_LZMA_PRESET, + }, { + .name = "dict", + .flags = OPTMAP_USE_BYTE_SUFFIX, + .offset = offsetof(lzma_options_lzma, dict_size), + .u.range.min = LZMA_DICT_SIZE_MIN, + // FIXME? The max is really max for encoding but decoding + // would allow 4 GiB - 1 B. + .u.range.max = (UINT32_C(1) << 30) + (UINT32_C(1) << 29), + }, { + .name = "lc", + .offset = offsetof(lzma_options_lzma, lc), + .u.range.min = LZMA_LCLP_MIN, + .u.range.max = LZMA_LCLP_MAX, + }, { + .name = "lp", + .offset = offsetof(lzma_options_lzma, lp), + .u.range.min = LZMA_LCLP_MIN, + .u.range.max = LZMA_LCLP_MAX, + }, { + .name = "pb", + .offset = offsetof(lzma_options_lzma, pb), + .u.range.min = LZMA_PB_MIN, + .u.range.max = LZMA_PB_MAX, + }, { + .name = "mode", + .type = OPTMAP_TYPE_LZMA_MODE, + .flags = OPTMAP_USE_NAME_VALUE_MAP, + .offset = offsetof(lzma_options_lzma, mode), + .u.map = lzma12_mode_map, + }, { + .name = "nice", + .offset = offsetof(lzma_options_lzma, nice_len), + .u.range.min = 2, + .u.range.max = 273, + }, { + .name = "mf", + .type = OPTMAP_TYPE_LZMA_MATCH_FINDER, + .flags = OPTMAP_USE_NAME_VALUE_MAP, + .offset = offsetof(lzma_options_lzma, mf), + .u.map = lzma12_mf_map, + }, { + .name = "depth", + .offset = offsetof(lzma_options_lzma, depth), + .u.range.min = 0, + .u.range.max = UINT32_MAX, + } +}; + + +static const char * +parse_lzma12(const char **const str, const char *str_end, void *filter_options) +{ + lzma_options_lzma *opts = filter_options; + + // It cannot fail. + const bool preset_ret = lzma_lzma_preset(opts, LZMA_PRESET_DEFAULT); + assert(!preset_ret); + (void)preset_ret; + + const char *errmsg = parse_options(str, str_end, filter_options, + lzma12_optmap, ARRAY_SIZE(lzma12_optmap)); + if (errmsg != NULL) + return errmsg; + + if (opts->lc + opts->lp > LZMA_LCLP_MAX) + return "The sum of lc and lp must not exceed 4"; + + return NULL; +} + + +///////////////////////////////////////// +// Generic parsing and stringification // +///////////////////////////////////////// + +static const struct { + /// Name of the filter + char name[NAME_LEN_MAX + 1]; + + /// For lzma_str_to_filters: + /// Size of the filter-specific options structure. + uint32_t opts_size; + + /// Filter ID + lzma_vli id; + + /// For lzma_str_to_filters: + /// Function to parse the filter-specific options. The filter_options + /// will already have been allocated using lzma_alloc_zero(). + const char *(*parse)(const char **str, const char *str_end, + void *filter_options); + + /// For lzma_str_from_filters: + /// If the flag LZMA_STR_ENCODER is used then the first + /// strfy_encoder elements of optmap are stringified. + /// With LZMA_STR_DECODER strfy_decoder is used. + /// Currently encoders use all options that decoders do but if + /// that changes then this needs to be changed too, for example, + /// add a new OPTMAP flag to skip printing some decoder-only options. + const option_map *optmap; + uint8_t strfy_encoder; + uint8_t strfy_decoder; + + /// For lzma_str_from_filters: + /// If true, lzma_filter.options is allowed to be NULL. In that case, + /// only the filter name is printed without any options. + bool allow_null; + +} filter_name_map[] = { +#if defined (HAVE_ENCODER_LZMA1) || defined(HAVE_DECODER_LZMA1) + { "lzma1", sizeof(lzma_options_lzma), LZMA_FILTER_LZMA1, + &parse_lzma12, lzma12_optmap, 9, 5, false }, +#endif + +#if defined(HAVE_ENCODER_LZMA2) || defined(HAVE_DECODER_LZMA2) + { "lzma2", sizeof(lzma_options_lzma), LZMA_FILTER_LZMA2, + &parse_lzma12, lzma12_optmap, 9, 2, false }, +#endif + +#if defined(HAVE_ENCODER_X86) || defined(HAVE_DECODER_X86) + { "x86", sizeof(lzma_options_bcj), LZMA_FILTER_X86, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_ARM) || defined(HAVE_DECODER_ARM) + { "arm", sizeof(lzma_options_bcj), LZMA_FILTER_ARM, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_ARMTHUMB) || defined(HAVE_DECODER_ARMTHUMB) + { "armthumb", sizeof(lzma_options_bcj), LZMA_FILTER_ARMTHUMB, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_ARM64) || defined(HAVE_DECODER_ARM64) + { "arm64", sizeof(lzma_options_bcj), LZMA_FILTER_ARM64, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_RISCV) || defined(HAVE_DECODER_RISCV) + { "riscv", sizeof(lzma_options_bcj), LZMA_FILTER_RISCV, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_POWERPC) || defined(HAVE_DECODER_POWERPC) + { "powerpc", sizeof(lzma_options_bcj), LZMA_FILTER_POWERPC, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_IA64) || defined(HAVE_DECODER_IA64) + { "ia64", sizeof(lzma_options_bcj), LZMA_FILTER_IA64, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_SPARC) || defined(HAVE_DECODER_SPARC) + { "sparc", sizeof(lzma_options_bcj), LZMA_FILTER_SPARC, + &parse_bcj, bcj_optmap, 1, 1, true }, +#endif + +#if defined(HAVE_ENCODER_DELTA) || defined(HAVE_DECODER_DELTA) + { "delta", sizeof(lzma_options_delta), LZMA_FILTER_DELTA, + &parse_delta, delta_optmap, 1, 1, false }, +#endif +}; + + +/// Decodes options from a string for one filter (name1=value1,name2=value2). +/// Caller must have allocated memory for filter_options already and set +/// the initial default values. This is called from the filter-specific +/// parse_* functions. +/// +/// The input string starts at *str and the address in str_end is the first +/// char that is not part of the string anymore. So no '\0' terminator is +/// used. *str is advanced every time something has been decoded successfully. +static const char * +parse_options(const char **const str, const char *str_end, + void *filter_options, + const option_map *const optmap, const size_t optmap_size) +{ + while (*str < str_end && **str != '\0') { + // Each option is of the form name=value. + // Commas (',') separate options. Extra commas are ignored. + // Ignoring extra commas makes it simpler if an optional + // option stored in a shell variable which can be empty. + if (**str == ',') { + ++*str; + continue; + } + + // Find where the next name=value ends. + const size_t str_len = (size_t)(str_end - *str); + const char *name_eq_value_end = memchr(*str, ',', str_len); + if (name_eq_value_end == NULL) + name_eq_value_end = str_end; + + const char *equals_sign = memchr(*str, '=', + (size_t)(name_eq_value_end - *str)); + + // Fail if the '=' wasn't found or the option name is missing + // (the first char is '='). + if (equals_sign == NULL || **str == '=') + return "Options must be 'name=value' pairs separated " + "with commas"; + + // Reject a too long option name so that the memcmp() + // in the loop below won't read past the end of the + // string in optmap[i].name. + const size_t name_len = (size_t)(equals_sign - *str); + if (name_len > NAME_LEN_MAX) + return "Unknown option name"; + + // Find the option name from optmap[]. + size_t i = 0; + while (true) { + if (i == optmap_size) + return "Unknown option name"; + + if (memcmp(*str, optmap[i].name, name_len) == 0 + && optmap[i].name[name_len] == '\0') + break; + + ++i; + } + + // The input string is good at least until the start of + // the option value. + *str = equals_sign + 1; + + // The code assumes that the option value isn't an empty + // string so check it here. + const size_t value_len = (size_t)(name_eq_value_end - *str); + if (value_len == 0) + return "Option value cannot be empty"; + + // LZMA1/2 preset has its own parsing function. + if (optmap[i].type == OPTMAP_TYPE_LZMA_PRESET) { + const char *errmsg = set_lzma12_preset(str, + name_eq_value_end, filter_options); + if (errmsg != NULL) + return errmsg; + + continue; + } + + // It's an integer value. + uint32_t v; + if (optmap[i].flags & OPTMAP_USE_NAME_VALUE_MAP) { + // The integer is picked from a string-to-integer map. + // + // Reject a too long value string so that the memcmp() + // in the loop below won't read past the end of the + // string in optmap[i].u.map[j].name. + if (value_len > NAME_LEN_MAX) + return "Invalid option value"; + + const name_value_map *map = optmap[i].u.map; + size_t j = 0; + while (true) { + // The array is terminated with an empty name. + if (map[j].name[0] == '\0') + return "Invalid option value"; + + if (memcmp(*str, map[j].name, value_len) == 0 + && map[j].name[value_len] + == '\0') { + v = map[j].value; + break; + } + + ++j; + } + } else if (**str < '0' || **str > '9') { + // Note that "max" isn't supported while it is + // supported in xz. It's not useful here. + return "Value is not a non-negative decimal integer"; + } else { + // strtoul() has locale-specific behavior so it cannot + // be relied on to get reproducible results since we + // cannot change the locate in a thread-safe library. + // It also needs '\0'-termination. + // + // Use a temporary pointer so that *str will point + // to the beginning of the value string in case + // an error occurs. + const char *p = *str; + v = 0; + do { + if (v > UINT32_MAX / 10) + return "Value out of range"; + + v *= 10; + + const uint32_t add = (uint32_t)(*p - '0'); + if (UINT32_MAX - add < v) + return "Value out of range"; + + v += add; + ++p; + } while (p < name_eq_value_end + && *p >= '0' && *p <= '9'); + + if (p < name_eq_value_end) { + // Remember this position so that it can be + // used for error messages that are + // specifically about the suffix. (Out of + // range values are about the whole value + // and those error messages point to the + // beginning of the number part, + // not to the suffix.) + const char *multiplier_start = p; + + // If multiplier suffix shouldn't be used + // then don't allow them even if the value + // would stay within limits. This is a somewhat + // unnecessary check but it rejects silly + // things like lzma2:pb=0MiB which xz allows. + if ((optmap[i].flags & OPTMAP_USE_BYTE_SUFFIX) + == 0) { + *str = multiplier_start; + return "This option does not support " + "any integer suffixes"; + } + + uint32_t shift; + + switch (*p) { + case 'k': + case 'K': + shift = 10; + break; + + case 'm': + case 'M': + shift = 20; + break; + + case 'g': + case 'G': + shift = 30; + break; + + default: + *str = multiplier_start; + return "Invalid multiplier suffix " + "(KiB, MiB, or GiB)"; + } + + ++p; + + // Allow "M", "Mi", "MB", "MiB" and the same + // for the other five characters from the + // switch-statement above. All are handled + // as base-2 (perhaps a mistake, perhaps not). + // Note that 'i' and 'B' are case sensitive. + if (p < name_eq_value_end && *p == 'i') + ++p; + + if (p < name_eq_value_end && *p == 'B') + ++p; + + // Now we must have no chars remaining. + if (p < name_eq_value_end) { + *str = multiplier_start; + return "Invalid multiplier suffix " + "(KiB, MiB, or GiB)"; + } + + if (v > (UINT32_MAX >> shift)) + return "Value out of range"; + + v <<= shift; + } + + if (v < optmap[i].u.range.min + || v > optmap[i].u.range.max) + return "Value out of range"; + } + + // Set the value in filter_options. Enums are handled + // specially since the underlying type isn't the same + // as uint32_t on all systems. + void *ptr = (char *)filter_options + optmap[i].offset; + switch (optmap[i].type) { + case OPTMAP_TYPE_LZMA_MODE: + *(lzma_mode *)ptr = (lzma_mode)v; + break; + + case OPTMAP_TYPE_LZMA_MATCH_FINDER: + *(lzma_match_finder *)ptr = (lzma_match_finder)v; + break; + + default: + *(uint32_t *)ptr = v; + break; + } + + // This option has been successfully handled. + *str = name_eq_value_end; + } + + // No errors. + return NULL; +} + + +/// Finds the name of the filter at the beginning of the string and +/// calls filter_name_map[i].parse() to decode the filter-specific options. +/// The caller must have set str_end so that exactly one filter and its +/// options are present without any trailing characters. +static const char * +parse_filter(const char **const str, const char *str_end, lzma_filter *filter, + const lzma_allocator *allocator, bool only_xz) +{ + // Search for a colon or equals sign that would separate the filter + // name from filter options. If neither is found, then the input + // string only contains a filter name and there are no options. + // + // First assume that a colon or equals sign won't be found: + const char *name_end = str_end; + const char *opts_start = str_end; + + for (const char *p = *str; p < str_end; ++p) { + if (*p == ':' || *p == '=') { + name_end = p; + + // Filter options (name1=value1,name2=value2,...) + // begin after the colon or equals sign. + opts_start = p + 1; + break; + } + } + + // Reject a too long filter name so that the memcmp() + // in the loop below won't read past the end of the + // string in filter_name_map[i].name. + const size_t name_len = (size_t)(name_end - *str); + if (name_len > NAME_LEN_MAX) + return "Unknown filter name"; + + for (size_t i = 0; i < ARRAY_SIZE(filter_name_map); ++i) { + if (memcmp(*str, filter_name_map[i].name, name_len) == 0 + && filter_name_map[i].name[name_len] == '\0') { + if (only_xz && filter_name_map[i].id + >= LZMA_FILTER_RESERVED_START) + return "This filter cannot be used in " + "the .xz format"; + + // Allocate the filter-specific options and + // initialize the memory with zeros. + void *options = lzma_alloc_zero( + filter_name_map[i].opts_size, + allocator); + if (options == NULL) + return "Memory allocation failed"; + + // Filter name was found so the input string is good + // at least this far. + *str = opts_start; + + const char *errmsg = filter_name_map[i].parse( + str, str_end, options); + if (errmsg != NULL) { + lzma_free(options, allocator); + return errmsg; + } + + // *filter is modified only when parsing is successful. + filter->id = filter_name_map[i].id; + filter->options = options; + return NULL; + } + } + + return "Unknown filter name"; +} + + +/// Converts the string to a filter chain (array of lzma_filter structures). +/// +/// *str is advanced every time something has been decoded successfully. +/// This way the caller knows where in the string a possible error occurred. +static const char * +str_to_filters(const char **const str, lzma_filter *filters, uint32_t flags, + const lzma_allocator *allocator) +{ + const char *errmsg; + + // Skip leading spaces. + while (**str == ' ') + ++*str; + + if (**str == '\0') + return "Empty string is not allowed, " + "try \"6\" if a default value is needed"; + + // Detect the type of the string. + // + // A string beginning with a digit or a string beginning with + // one dash and a digit are treated as presets. Trailing spaces + // will be ignored too (leading spaces were already ignored above). + // + // For example, "6", "7 ", "-9e", or " -3 " are treated as presets. + // Strings like "-" or "- " aren't preset. +#define MY_IS_DIGIT(c) ((c) >= '0' && (c) <= '9') + if (MY_IS_DIGIT(**str) || (**str == '-' && MY_IS_DIGIT((*str)[1]))) { + if (**str == '-') + ++*str; + + // Ignore trailing spaces. + const size_t str_len = strlen(*str); + const char *str_end = memchr(*str, ' ', str_len); + if (str_end != NULL) { + // There is at least one trailing space. Check that + // there are no chars other than spaces. + for (size_t i = 1; str_end[i] != '\0'; ++i) + if (str_end[i] != ' ') + return "Unsupported preset"; + } else { + // There are no trailing spaces. Use the whole string. + str_end = *str + str_len; + } + + uint32_t preset; + errmsg = parse_lzma12_preset(str, str_end, &preset); + if (errmsg != NULL) + return errmsg; + + lzma_options_lzma *opts = lzma_alloc(sizeof(*opts), allocator); + if (opts == NULL) + return "Memory allocation failed"; + + if (lzma_lzma_preset(opts, preset)) { + lzma_free(opts, allocator); + return "Unsupported preset"; + } + + filters[0].id = LZMA_FILTER_LZMA2; + filters[0].options = opts; + filters[1].id = LZMA_VLI_UNKNOWN; + filters[1].options = NULL; + + return NULL; + } + + // Not a preset so it must be a filter chain. + // + // If LZMA_STR_ALL_FILTERS isn't used we allow only filters that + // can be used in .xz. + const bool only_xz = (flags & LZMA_STR_ALL_FILTERS) == 0; + + // Use a temporary array so that we don't modify the caller-supplied + // one until we know that no errors occurred. + lzma_filter temp_filters[LZMA_FILTERS_MAX + 1]; + + size_t i = 0; + do { + if (i == LZMA_FILTERS_MAX) { + errmsg = "The maximum number of filters is four"; + goto error; + } + + // Skip "--" if present. + if ((*str)[0] == '-' && (*str)[1] == '-') + *str += 2; + + // Locate the end of "filter:name1=value1,name2=value2", + // stopping at the first "--" or a single space. + const char *filter_end = *str; + while (filter_end[0] != '\0') { + if ((filter_end[0] == '-' && filter_end[1] == '-') + || filter_end[0] == ' ') + break; + + ++filter_end; + } + + // Inputs that have "--" at the end or "-- " in the middle + // will result in an empty filter name. + if (filter_end == *str) { + errmsg = "Filter name is missing"; + goto error; + } + + errmsg = parse_filter(str, filter_end, &temp_filters[i], + allocator, only_xz); + if (errmsg != NULL) + goto error; + + // Skip trailing spaces. + while (**str == ' ') + ++*str; + + ++i; + } while (**str != '\0'); + + // Seems to be good, terminate the array so that + // basic validation can be done. + temp_filters[i].id = LZMA_VLI_UNKNOWN; + temp_filters[i].options = NULL; + + // Do basic validation if the application didn't prohibit it. + if ((flags & LZMA_STR_NO_VALIDATION) == 0) { + size_t dummy; + const lzma_ret ret = lzma_validate_chain(temp_filters, &dummy); + assert(ret == LZMA_OK || ret == LZMA_OPTIONS_ERROR); + if (ret != LZMA_OK) { + errmsg = "Invalid filter chain " + "('lzma2' missing at the end?)"; + goto error; + } + } + + // All good. Copy the filters to the application supplied array. + memcpy(filters, temp_filters, (i + 1) * sizeof(lzma_filter)); + return NULL; + +error: + // Free the filter options that were successfully decoded. + while (i-- > 0) + lzma_free(temp_filters[i].options, allocator); + + return errmsg; +} + + +extern LZMA_API(const char *) +lzma_str_to_filters(const char *str, int *error_pos, lzma_filter *filters, + uint32_t flags, const lzma_allocator *allocator) +{ + // If error_pos isn't NULL, *error_pos must always be set. + // liblzma <= 5.4.6 and <= 5.6.1 have a bug and don't do this + // when str == NULL or filters == NULL or flags are unsupported. + if (error_pos != NULL) + *error_pos = 0; + + if (str == NULL || filters == NULL) + return "Unexpected NULL pointer argument(s) " + "to lzma_str_to_filters()"; + + // Validate the flags. + const uint32_t supported_flags + = LZMA_STR_ALL_FILTERS + | LZMA_STR_NO_VALIDATION; + + if (flags & ~supported_flags) + return "Unsupported flags to lzma_str_to_filters()"; + + const char *used = str; + const char *errmsg = str_to_filters(&used, filters, flags, allocator); + + if (error_pos != NULL) { + const size_t n = (size_t)(used - str); + *error_pos = n > INT_MAX ? INT_MAX : (int)n; + } + + return errmsg; +} + + +/// Converts options of one filter to a string. +/// +/// The caller must have already put the filter name in the destination +/// string. Since it is possible that no options will be needed, the caller +/// won't have put a delimiter character (':' or '=') in the string yet. +/// We will add it if at least one option will be added to the string. +static void +strfy_filter(lzma_str *dest, const char *delimiter, + const option_map *optmap, size_t optmap_count, + const void *filter_options) +{ + for (size_t i = 0; i < optmap_count; ++i) { + // No attempt is made to reverse LZMA1/2 preset. + if (optmap[i].type == OPTMAP_TYPE_LZMA_PRESET) + continue; + + // All options have integer values, some just are mapped + // to a string with a name_value_map. LZMA1/2 preset + // isn't reversed back to preset=PRESET form. + uint32_t v; + const void *ptr + = (const char *)filter_options + optmap[i].offset; + switch (optmap[i].type) { + case OPTMAP_TYPE_LZMA_MODE: + v = *(const lzma_mode *)ptr; + break; + + case OPTMAP_TYPE_LZMA_MATCH_FINDER: + v = *(const lzma_match_finder *)ptr; + break; + + default: + v = *(const uint32_t *)ptr; + break; + } + + // Skip this if this option should be omitted from + // the string when the value is zero. + if (v == 0 && (optmap[i].flags & OPTMAP_NO_STRFY_ZERO)) + continue; + + // Before the first option we add whatever delimiter + // the caller gave us. For later options a comma is used. + str_append_str(dest, delimiter); + delimiter = ","; + + // Add the option name and equals sign. + str_append_str(dest, optmap[i].name); + str_append_str(dest, "="); + + if (optmap[i].flags & OPTMAP_USE_NAME_VALUE_MAP) { + const name_value_map *map = optmap[i].u.map; + size_t j = 0; + while (true) { + if (map[j].name[0] == '\0') { + str_append_str(dest, "UNKNOWN"); + break; + } + + if (map[j].value == v) { + str_append_str(dest, map[j].name); + break; + } + + ++j; + } + } else { + str_append_u32(dest, v, + optmap[i].flags & OPTMAP_USE_BYTE_SUFFIX); + } + } + + return; +} + + +extern LZMA_API(lzma_ret) +lzma_str_from_filters(char **output_str, const lzma_filter *filters, + uint32_t flags, const lzma_allocator *allocator) +{ + // On error *output_str is always set to NULL. + // Do it as the very first step. + if (output_str == NULL) + return LZMA_PROG_ERROR; + + *output_str = NULL; + + if (filters == NULL) + return LZMA_PROG_ERROR; + + // Validate the flags. + const uint32_t supported_flags + = LZMA_STR_ENCODER + | LZMA_STR_DECODER + | LZMA_STR_GETOPT_LONG + | LZMA_STR_NO_SPACES; + + if (flags & ~supported_flags) + return LZMA_OPTIONS_ERROR; + + // There must be at least one filter. + if (filters[0].id == LZMA_VLI_UNKNOWN) + return LZMA_OPTIONS_ERROR; + + // Allocate memory for the output string. + lzma_str dest; + return_if_error(str_init(&dest, allocator)); + + const bool show_opts = (flags & (LZMA_STR_ENCODER | LZMA_STR_DECODER)); + + const char *opt_delim = (flags & LZMA_STR_GETOPT_LONG) ? "=" : ":"; + + for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) { + // If we reach LZMA_FILTERS_MAX, then the filters array + // is too large since the ID cannot be LZMA_VLI_UNKNOWN here. + if (i == LZMA_FILTERS_MAX) { + str_free(&dest, allocator); + return LZMA_OPTIONS_ERROR; + } + + // Don't add a space between filters if the caller + // doesn't want them. + if (i > 0 && !(flags & LZMA_STR_NO_SPACES)) + str_append_str(&dest, " "); + + // Use dashes for xz getopt_long() compatible syntax but also + // use dashes to separate filters when spaces weren't wanted. + if ((flags & LZMA_STR_GETOPT_LONG) + || (i > 0 && (flags & LZMA_STR_NO_SPACES))) + str_append_str(&dest, "--"); + + size_t j = 0; + while (true) { + if (j == ARRAY_SIZE(filter_name_map)) { + // Filter ID in filters[i].id isn't supported. + str_free(&dest, allocator); + return LZMA_OPTIONS_ERROR; + } + + if (filter_name_map[j].id == filters[i].id) { + // Add the filter name. + str_append_str(&dest, filter_name_map[j].name); + + // If only the filter names were wanted then + // skip to the next filter. In this case + // .options is ignored and may be NULL even + // when the filter doesn't allow NULL options. + if (!show_opts) + break; + + if (filters[i].options == NULL) { + if (!filter_name_map[j].allow_null) { + // Filter-specific options + // are missing but with + // this filter the options + // structure is mandatory. + str_free(&dest, allocator); + return LZMA_OPTIONS_ERROR; + } + + // .options is allowed to be NULL. + // There is no need to add any + // options to the string. + break; + } + + // Options structure is available. Add + // the filter options to the string. + const size_t optmap_count + = (flags & LZMA_STR_ENCODER) + ? filter_name_map[j].strfy_encoder + : filter_name_map[j].strfy_decoder; + strfy_filter(&dest, opt_delim, + filter_name_map[j].optmap, + optmap_count, + filters[i].options); + break; + } + + ++j; + } + } + + return str_finish(output_str, &dest, allocator); +} + + +extern LZMA_API(lzma_ret) +lzma_str_list_filters(char **output_str, lzma_vli filter_id, uint32_t flags, + const lzma_allocator *allocator) +{ + // On error *output_str is always set to NULL. + // Do it as the very first step. + if (output_str == NULL) + return LZMA_PROG_ERROR; + + *output_str = NULL; + + // Validate the flags. + const uint32_t supported_flags + = LZMA_STR_ALL_FILTERS + | LZMA_STR_ENCODER + | LZMA_STR_DECODER + | LZMA_STR_GETOPT_LONG; + + if (flags & ~supported_flags) + return LZMA_OPTIONS_ERROR; + + // Allocate memory for the output string. + lzma_str dest; + return_if_error(str_init(&dest, allocator)); + + // If only listing the filter names then separate them with spaces. + // Otherwise use newlines. + const bool show_opts = (flags & (LZMA_STR_ENCODER | LZMA_STR_DECODER)); + const char *filter_delim = show_opts ? "\n" : " "; + + const char *opt_delim = (flags & LZMA_STR_GETOPT_LONG) ? "=" : ":"; + bool first_filter_printed = false; + + for (size_t i = 0; i < ARRAY_SIZE(filter_name_map); ++i) { + // If we are printing only one filter then skip others. + if (filter_id != LZMA_VLI_UNKNOWN + && filter_id != filter_name_map[i].id) + continue; + + // If we are printing only .xz filters then skip the others. + if (filter_name_map[i].id >= LZMA_FILTER_RESERVED_START + && (flags & LZMA_STR_ALL_FILTERS) == 0 + && filter_id == LZMA_VLI_UNKNOWN) + continue; + + // Add a new line if this isn't the first filter being + // written to the string. + if (first_filter_printed) + str_append_str(&dest, filter_delim); + + first_filter_printed = true; + + if (flags & LZMA_STR_GETOPT_LONG) + str_append_str(&dest, "--"); + + str_append_str(&dest, filter_name_map[i].name); + + // If only the filter names were wanted then continue + // to the next filter. + if (!show_opts) + continue; + + const option_map *optmap = filter_name_map[i].optmap; + const char *d = opt_delim; + + const size_t end = (flags & LZMA_STR_ENCODER) + ? filter_name_map[i].strfy_encoder + : filter_name_map[i].strfy_decoder; + + for (size_t j = 0; j < end; ++j) { + // The first option is delimited from the filter + // name using "=" or ":" and the rest of the options + // are separated with ",". + str_append_str(&dest, d); + d = ","; + + // optname= + str_append_str(&dest, optmap[j].name); + str_append_str(&dest, "=<"); + + if (optmap[j].type == OPTMAP_TYPE_LZMA_PRESET) { + // LZMA1/2 preset has its custom help string. + str_append_str(&dest, LZMA12_PRESET_STR); + } else if (optmap[j].flags + & OPTMAP_USE_NAME_VALUE_MAP) { + // Separate the possible option values by "|". + const name_value_map *m = optmap[j].u.map; + for (size_t k = 0; m[k].name[0] != '\0'; ++k) { + if (k > 0) + str_append_str(&dest, "|"); + + str_append_str(&dest, m[k].name); + } + } else { + // Integer range is shown as min-max. + const bool use_byte_suffix = optmap[j].flags + & OPTMAP_USE_BYTE_SUFFIX; + str_append_u32(&dest, optmap[j].u.range.min, + use_byte_suffix); + str_append_str(&dest, "-"); + str_append_u32(&dest, optmap[j].u.range.max, + use_byte_suffix); + } + + str_append_str(&dest, ">"); + } + } + + // If no filters were added to the string then it must be because + // the caller provided an unsupported Filter ID. + if (!first_filter_printed) { + str_free(&dest, allocator); + return LZMA_OPTIONS_ERROR; + } + + return str_finish(output_str, &dest, allocator); +} diff --git a/Utilities/cmliblzma/liblzma/common/vli_decoder.c b/Utilities/cmliblzma/liblzma/common/vli_decoder.c index af2799d1fb9..3254ccc35bd 100644 --- a/Utilities/cmliblzma/liblzma/common/vli_decoder.c +++ b/Utilities/cmliblzma/liblzma/common/vli_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file vli_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" diff --git a/Utilities/cmliblzma/liblzma/common/vli_encoder.c b/Utilities/cmliblzma/liblzma/common/vli_encoder.c index f8642694e29..3859006a94f 100644 --- a/Utilities/cmliblzma/liblzma/common/vli_encoder.c +++ b/Utilities/cmliblzma/liblzma/common/vli_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file vli_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" diff --git a/Utilities/cmliblzma/liblzma/common/vli_size.c b/Utilities/cmliblzma/liblzma/common/vli_size.c index ec1b4fa488b..c8cb2ec10ad 100644 --- a/Utilities/cmliblzma/liblzma/common/vli_size.c +++ b/Utilities/cmliblzma/liblzma/common/vli_size.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file vli_size.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" diff --git a/Utilities/cmliblzma/liblzma/delta/delta_common.c b/Utilities/cmliblzma/liblzma/delta/delta_common.c index 4768201d1a9..5dbe253b4b3 100644 --- a/Utilities/cmliblzma/liblzma/delta/delta_common.c +++ b/Utilities/cmliblzma/liblzma/delta/delta_common.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file delta_common.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "delta_common.h" diff --git a/Utilities/cmliblzma/liblzma/delta/delta_common.h b/Utilities/cmliblzma/liblzma/delta/delta_common.h index 7e7e1baaf68..bd091276972 100644 --- a/Utilities/cmliblzma/liblzma/delta/delta_common.h +++ b/Utilities/cmliblzma/liblzma/delta/delta_common.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file delta_common.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_DELTA_COMMON_H diff --git a/Utilities/cmliblzma/liblzma/delta/delta_decoder.c b/Utilities/cmliblzma/liblzma/delta/delta_decoder.c index 13d8a28f0df..9f0d49ca415 100644 --- a/Utilities/cmliblzma/liblzma/delta/delta_decoder.c +++ b/Utilities/cmliblzma/liblzma/delta/delta_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file delta_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "delta_decoder.h" @@ -26,6 +25,11 @@ decode_buffer(lzma_delta_coder *coder, uint8_t *buffer, size_t size) } +// For an unknown reason NVIDIA HPC Compiler needs this pragma +// to produce working code. +#ifdef __NVCOMPILER +# pragma routine novector +#endif static lzma_ret delta_decode(void *coder_ptr, const lzma_allocator *allocator, const uint8_t *restrict in, size_t *restrict in_pos, @@ -42,7 +46,12 @@ delta_decode(void *coder_ptr, const lzma_allocator *allocator, in, in_pos, in_size, out, out_pos, out_size, action); - decode_buffer(coder, out + out_start, *out_pos - out_start); + // out might be NULL. In that case size == 0. Null pointer + 0 is + // undefined behavior so skip the call in that case as it would + // do nothing anyway. + const size_t size = *out_pos - out_start; + if (size > 0) + decode_buffer(coder, out + out_start, size); return ret; } diff --git a/Utilities/cmliblzma/liblzma/delta/delta_decoder.h b/Utilities/cmliblzma/liblzma/delta/delta_decoder.h index ad89cc65976..e2268ed44e7 100644 --- a/Utilities/cmliblzma/liblzma/delta/delta_decoder.h +++ b/Utilities/cmliblzma/liblzma/delta/delta_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file delta_decoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_DELTA_DECODER_H diff --git a/Utilities/cmliblzma/liblzma/delta/delta_encoder.c b/Utilities/cmliblzma/liblzma/delta/delta_encoder.c index 38416515162..ba4a50b1f42 100644 --- a/Utilities/cmliblzma/liblzma/delta/delta_encoder.c +++ b/Utilities/cmliblzma/liblzma/delta/delta_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file delta_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "delta_encoder.h" @@ -63,7 +62,12 @@ delta_encode(void *coder_ptr, const lzma_allocator *allocator, const size_t out_avail = out_size - *out_pos; const size_t size = my_min(in_avail, out_avail); - copy_and_encode(coder, in + *in_pos, out + *out_pos, size); + // in and out might be NULL. In such cases size == 0. + // Null pointer + 0 is undefined behavior so skip + // the call in that case as it would do nothing anyway. + if (size > 0) + copy_and_encode(coder, in + *in_pos, out + *out_pos, + size); *in_pos += size; *out_pos += size; @@ -78,7 +82,10 @@ delta_encode(void *coder_ptr, const lzma_allocator *allocator, in, in_pos, in_size, out, out_pos, out_size, action); - encode_in_place(coder, out + out_start, *out_pos - out_start); + // Like above, avoid null pointer + 0. + const size_t size = *out_pos - out_start; + if (size > 0) + encode_in_place(coder, out + out_start, size); } return ret; diff --git a/Utilities/cmliblzma/liblzma/delta/delta_encoder.h b/Utilities/cmliblzma/liblzma/delta/delta_encoder.h index 4ab98478517..735f0ed0091 100644 --- a/Utilities/cmliblzma/liblzma/delta/delta_encoder.h +++ b/Utilities/cmliblzma/liblzma/delta/delta_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file delta_encoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_DELTA_ENCODER_H diff --git a/Utilities/cmliblzma/liblzma/delta/delta_private.h b/Utilities/cmliblzma/liblzma/delta/delta_private.h index 0d6cb386611..e54721a8466 100644 --- a/Utilities/cmliblzma/liblzma/delta/delta_private.h +++ b/Utilities/cmliblzma/liblzma/delta/delta_private.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file delta_private.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_DELTA_PRIVATE_H diff --git a/Utilities/cmliblzma/liblzma/liblzma.pc.in b/Utilities/cmliblzma/liblzma/liblzma.pc.in index 9fa489115a0..a432992b707 100644 --- a/Utilities/cmliblzma/liblzma/liblzma.pc.in +++ b/Utilities/cmliblzma/liblzma/liblzma.pc.in @@ -1,9 +1,5 @@ -# +# SPDX-License-Identifier: 0BSD # Author: Lasse Collin -# -# This file has been put into the public domain. -# You can do whatever you want with this file. -# prefix=@prefix@ exec_prefix=@exec_prefix@ @@ -15,5 +11,6 @@ Description: General purpose data compression library URL: @PACKAGE_URL@ Version: @PACKAGE_VERSION@ Cflags: -I${includedir} +Cflags.private: -DLZMA_API_STATIC Libs: -L${libdir} -llzma Libs.private: @PTHREAD_CFLAGS@ @LIBS@ diff --git a/Utilities/cmliblzma/liblzma/liblzma_generic.map b/Utilities/cmliblzma/liblzma/liblzma_generic.map new file mode 100644 index 00000000000..f74c1548455 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/liblzma_generic.map @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: 0BSD */ + +XZ_5.0 { +global: + lzma_alone_decoder; + lzma_alone_encoder; + lzma_auto_decoder; + lzma_block_buffer_bound; + lzma_block_buffer_decode; + lzma_block_buffer_encode; + lzma_block_compressed_size; + lzma_block_decoder; + lzma_block_encoder; + lzma_block_header_decode; + lzma_block_header_encode; + lzma_block_header_size; + lzma_block_total_size; + lzma_block_unpadded_size; + lzma_check_is_supported; + lzma_check_size; + lzma_code; + lzma_crc32; + lzma_crc64; + lzma_easy_buffer_encode; + lzma_easy_decoder_memusage; + lzma_easy_encoder; + lzma_easy_encoder_memusage; + lzma_end; + lzma_filter_decoder_is_supported; + lzma_filter_encoder_is_supported; + lzma_filter_flags_decode; + lzma_filter_flags_encode; + lzma_filter_flags_size; + lzma_filters_copy; + lzma_filters_update; + lzma_get_check; + lzma_index_append; + lzma_index_block_count; + lzma_index_buffer_decode; + lzma_index_buffer_encode; + lzma_index_cat; + lzma_index_checks; + lzma_index_decoder; + lzma_index_dup; + lzma_index_encoder; + lzma_index_end; + lzma_index_file_size; + lzma_index_hash_append; + lzma_index_hash_decode; + lzma_index_hash_end; + lzma_index_hash_init; + lzma_index_hash_size; + lzma_index_init; + lzma_index_iter_init; + lzma_index_iter_locate; + lzma_index_iter_next; + lzma_index_iter_rewind; + lzma_index_memusage; + lzma_index_memused; + lzma_index_size; + lzma_index_stream_count; + lzma_index_stream_flags; + lzma_index_stream_padding; + lzma_index_stream_size; + lzma_index_total_size; + lzma_index_uncompressed_size; + lzma_lzma_preset; + lzma_memlimit_get; + lzma_memlimit_set; + lzma_memusage; + lzma_mf_is_supported; + lzma_mode_is_supported; + lzma_physmem; + lzma_properties_decode; + lzma_properties_encode; + lzma_properties_size; + lzma_raw_buffer_decode; + lzma_raw_buffer_encode; + lzma_raw_decoder; + lzma_raw_decoder_memusage; + lzma_raw_encoder; + lzma_raw_encoder_memusage; + lzma_stream_buffer_bound; + lzma_stream_buffer_decode; + lzma_stream_buffer_encode; + lzma_stream_decoder; + lzma_stream_encoder; + lzma_stream_flags_compare; + lzma_stream_footer_decode; + lzma_stream_footer_encode; + lzma_stream_header_decode; + lzma_stream_header_encode; + lzma_version_number; + lzma_version_string; + lzma_vli_decode; + lzma_vli_encode; + lzma_vli_size; + +local: + *; +}; + +XZ_5.2 { +global: + lzma_block_uncomp_encode; + lzma_cputhreads; + lzma_get_progress; + lzma_stream_encoder_mt; + lzma_stream_encoder_mt_memusage; +} XZ_5.0; + +XZ_5.4 { +global: + lzma_file_info_decoder; + lzma_filters_free; + lzma_lzip_decoder; + lzma_microlzma_decoder; + lzma_microlzma_encoder; + lzma_stream_decoder_mt; + lzma_str_from_filters; + lzma_str_list_filters; + lzma_str_to_filters; +} XZ_5.2; + +XZ_5.6.0 { +global: + lzma_mt_block_size; +} XZ_5.4; diff --git a/Utilities/cmliblzma/liblzma/liblzma_linux.map b/Utilities/cmliblzma/liblzma/liblzma_linux.map new file mode 100644 index 00000000000..7e4b25e1762 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/liblzma_linux.map @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: 0BSD */ + +XZ_5.0 { +global: + lzma_alone_decoder; + lzma_alone_encoder; + lzma_auto_decoder; + lzma_block_buffer_bound; + lzma_block_buffer_decode; + lzma_block_buffer_encode; + lzma_block_compressed_size; + lzma_block_decoder; + lzma_block_encoder; + lzma_block_header_decode; + lzma_block_header_encode; + lzma_block_header_size; + lzma_block_total_size; + lzma_block_unpadded_size; + lzma_check_is_supported; + lzma_check_size; + lzma_code; + lzma_crc32; + lzma_crc64; + lzma_easy_buffer_encode; + lzma_easy_decoder_memusage; + lzma_easy_encoder; + lzma_easy_encoder_memusage; + lzma_end; + lzma_filter_decoder_is_supported; + lzma_filter_encoder_is_supported; + lzma_filter_flags_decode; + lzma_filter_flags_encode; + lzma_filter_flags_size; + lzma_filters_copy; + lzma_filters_update; + lzma_get_check; + lzma_index_append; + lzma_index_block_count; + lzma_index_buffer_decode; + lzma_index_buffer_encode; + lzma_index_cat; + lzma_index_checks; + lzma_index_decoder; + lzma_index_dup; + lzma_index_encoder; + lzma_index_end; + lzma_index_file_size; + lzma_index_hash_append; + lzma_index_hash_decode; + lzma_index_hash_end; + lzma_index_hash_init; + lzma_index_hash_size; + lzma_index_init; + lzma_index_iter_init; + lzma_index_iter_locate; + lzma_index_iter_next; + lzma_index_iter_rewind; + lzma_index_memusage; + lzma_index_memused; + lzma_index_size; + lzma_index_stream_count; + lzma_index_stream_flags; + lzma_index_stream_padding; + lzma_index_stream_size; + lzma_index_total_size; + lzma_index_uncompressed_size; + lzma_lzma_preset; + lzma_memlimit_get; + lzma_memlimit_set; + lzma_memusage; + lzma_mf_is_supported; + lzma_mode_is_supported; + lzma_physmem; + lzma_properties_decode; + lzma_properties_encode; + lzma_properties_size; + lzma_raw_buffer_decode; + lzma_raw_buffer_encode; + lzma_raw_decoder; + lzma_raw_decoder_memusage; + lzma_raw_encoder; + lzma_raw_encoder_memusage; + lzma_stream_buffer_bound; + lzma_stream_buffer_decode; + lzma_stream_buffer_encode; + lzma_stream_decoder; + lzma_stream_encoder; + lzma_stream_flags_compare; + lzma_stream_footer_decode; + lzma_stream_footer_encode; + lzma_stream_header_decode; + lzma_stream_header_encode; + lzma_version_number; + lzma_version_string; + lzma_vli_decode; + lzma_vli_encode; + lzma_vli_size; + +local: + *; +}; + +XZ_5.2 { +global: + lzma_block_uncomp_encode; + lzma_cputhreads; + lzma_get_progress; + lzma_stream_encoder_mt; + lzma_stream_encoder_mt_memusage; +} XZ_5.0; + +XZ_5.1.2alpha { +global: + lzma_stream_encoder_mt; + lzma_stream_encoder_mt_memusage; +} XZ_5.0; + +XZ_5.2.2 { +global: + lzma_block_uncomp_encode; + lzma_cputhreads; + lzma_get_progress; + lzma_stream_encoder_mt; + lzma_stream_encoder_mt_memusage; +} XZ_5.1.2alpha; + +XZ_5.4 { +global: + lzma_file_info_decoder; + lzma_filters_free; + lzma_lzip_decoder; + lzma_microlzma_decoder; + lzma_microlzma_encoder; + lzma_stream_decoder_mt; + lzma_str_from_filters; + lzma_str_list_filters; + lzma_str_to_filters; +} XZ_5.2; + +XZ_5.6.0 { +global: + lzma_mt_block_size; +} XZ_5.4; diff --git a/Utilities/cmliblzma/liblzma/liblzma_w32res.rc b/Utilities/cmliblzma/liblzma/liblzma_w32res.rc index 773caf8b419..76c1cdfd657 100644 --- a/Utilities/cmliblzma/liblzma/liblzma_w32res.rc +++ b/Utilities/cmliblzma/liblzma/liblzma_w32res.rc @@ -1,12 +1,19 @@ +/* SPDX-License-Identifier: 0BSD */ + /* * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. */ #define MY_TYPE VFT_DLL -#define MY_NAME "liblzma" + +#if defined(__MSYS__) +# define MY_NAME "msys-lzma-5" +#elif defined(__CYGWIN__) +# define MY_NAME "cyglzma-5" +#else +# define MY_NAME "liblzma" +#endif + #define MY_SUFFIX ".dll" #define MY_DESC "liblzma data compression library" #define PACKAGE_NAME "XZ Utils" diff --git a/Utilities/cmliblzma/liblzma/lz/lz_decoder.c b/Utilities/cmliblzma/liblzma/lz/lz_decoder.c index 09b574388f5..92913f225a0 100644 --- a/Utilities/cmliblzma/liblzma/lz/lz_decoder.c +++ b/Utilities/cmliblzma/liblzma/lz/lz_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lz_decoder.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// // liblzma supports multiple LZ77-based filters. The LZ part is shared @@ -54,9 +53,10 @@ typedef struct { static void lz_decoder_reset(lzma_coder *coder) { - coder->dict.pos = 0; + coder->dict.pos = 2 * LZ_DICT_REPEAT_MAX; coder->dict.full = 0; - coder->dict.buf[coder->dict.size - 1] = '\0'; + coder->dict.buf[2 * LZ_DICT_REPEAT_MAX - 1] = '\0'; + coder->dict.has_wrapped = false; coder->dict.need_reset = false; return; } @@ -70,8 +70,15 @@ decode_buffer(lzma_coder *coder, { while (true) { // Wrap the dictionary if needed. - if (coder->dict.pos == coder->dict.size) - coder->dict.pos = 0; + if (coder->dict.pos == coder->dict.size) { + // See the comment of #define LZ_DICT_REPEAT_MAX. + coder->dict.pos = LZ_DICT_REPEAT_MAX; + coder->dict.has_wrapped = true; + memcpy(coder->dict.buf, coder->dict.buf + + coder->dict.size + - LZ_DICT_REPEAT_MAX, + LZ_DICT_REPEAT_MAX); + } // Store the current dictionary position. It is needed to know // where to start copying to the out[] buffer. @@ -212,7 +219,8 @@ extern lzma_ret lzma_lz_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter_info *filters, lzma_ret (*lz_init)(lzma_lz_decoder *lz, - const lzma_allocator *allocator, const void *options, + const lzma_allocator *allocator, + lzma_vli id, const void *options, lzma_lz_options *lz_options)) { // Allocate the base structure if it isn't already allocated. @@ -236,7 +244,7 @@ lzma_lz_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, // us the dictionary size. lzma_lz_options lz_options; return_if_error(lz_init(&coder->lz, allocator, - filters[0].options, &lz_options)); + filters[0].id, filters[0].options, &lz_options)); // If the dictionary size is very small, increase it to 4096 bytes. // This is to prevent constant wrapping of the dictionary, which @@ -252,21 +260,31 @@ lzma_lz_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, // dictionary to the output buffer, since applications are // recommended to give aligned buffers to liblzma. // + // Reserve 2 * LZ_DICT_REPEAT_MAX bytes of extra space which is + // needed for alloc_size. + // // Avoid integer overflow. - if (lz_options.dict_size > SIZE_MAX - 15) + if (lz_options.dict_size > SIZE_MAX - 15 - 2 * LZ_DICT_REPEAT_MAX) return LZMA_MEM_ERROR; lz_options.dict_size = (lz_options.dict_size + 15) & ~((size_t)(15)); + // Reserve extra space as explained in the comment + // of #define LZ_DICT_REPEAT_MAX. + const size_t alloc_size + = lz_options.dict_size + 2 * LZ_DICT_REPEAT_MAX; + // Allocate and initialize the dictionary. - if (coder->dict.size != lz_options.dict_size) { + if (coder->dict.size != alloc_size) { lzma_free(coder->dict.buf, allocator); - coder->dict.buf - = lzma_alloc(lz_options.dict_size, allocator); + coder->dict.buf = lzma_alloc(alloc_size, allocator); if (coder->dict.buf == NULL) return LZMA_MEM_ERROR; - coder->dict.size = lz_options.dict_size; + // NOTE: Yes, alloc_size, not lz_options.dict_size. The way + // coder->dict.full is updated will take care that we will + // still reject distances larger than lz_options.dict_size. + coder->dict.size = alloc_size; } lz_decoder_reset(next->coder); @@ -279,9 +297,12 @@ lzma_lz_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const size_t copy_size = my_min(lz_options.preset_dict_size, lz_options.dict_size); const size_t offset = lz_options.preset_dict_size - copy_size; - memcpy(coder->dict.buf, lz_options.preset_dict + offset, + memcpy(coder->dict.buf + coder->dict.pos, + lz_options.preset_dict + offset, copy_size); - coder->dict.pos = copy_size; + + // dict.pos isn't zero after lz_decoder_reset(). + coder->dict.pos += copy_size; coder->dict.full = copy_size; } @@ -301,11 +322,3 @@ lzma_lz_decoder_memusage(size_t dictionary_size) { return sizeof(lzma_coder) + (uint64_t)(dictionary_size); } - - -extern void -lzma_lz_decoder_uncompressed(void *coder_ptr, lzma_vli uncompressed_size) -{ - lzma_coder *coder = coder_ptr; - coder->lz.set_uncompressed(coder->lz.coder, uncompressed_size); -} diff --git a/Utilities/cmliblzma/liblzma/lz/lz_decoder.h b/Utilities/cmliblzma/liblzma/lz/lz_decoder.h index 754ccf37c6a..cb61b6e24c7 100644 --- a/Utilities/cmliblzma/liblzma/lz/lz_decoder.h +++ b/Utilities/cmliblzma/liblzma/lz/lz_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lz_decoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZ_DECODER_H @@ -17,10 +16,28 @@ #include "common.h" +/// Maximum length of a match rounded up to a nice power of 2 which is +/// a good size for aligned memcpy(). The allocated dictionary buffer will +/// be 2 * LZ_DICT_REPEAT_MAX bytes larger than the actual dictionary size: +/// +/// (1) Every time the decoder reaches the end of the dictionary buffer, +/// the last LZ_DICT_REPEAT_MAX bytes will be copied to the beginning. +/// This way dict_repeat() will only need to copy from one place, +/// never from both the end and beginning of the buffer. +/// +/// (2) The other LZ_DICT_REPEAT_MAX bytes is kept as a buffer between +/// the oldest byte still in the dictionary and the current write +/// position. This way dict_repeat(dict, dict->size - 1, &len) +/// won't need memmove() as the copying cannot overlap. +/// +/// Note that memcpy() still cannot be used if distance < len. +/// +/// LZMA's longest match length is 273 so pick a multiple of 16 above that. +#define LZ_DICT_REPEAT_MAX 288 + + typedef struct { - /// Pointer to the dictionary buffer. It can be an allocated buffer - /// internal to liblzma, or it can a be a buffer given by the - /// application when in single-call mode (not implemented yet). + /// Pointer to the dictionary buffer. uint8_t *buf; /// Write position in dictionary. The next byte will be written to @@ -35,9 +52,16 @@ typedef struct { /// Write limit size_t limit; - /// Size of the dictionary + /// Allocated size of buf. This is 2 * LZ_DICT_REPEAT_MAX bytes + /// larger than the actual dictionary size. This is enforced by + /// how the value for "full" is set; it can be at most + /// "size - 2 * LZ_DICT_REPEAT_MAX". size_t size; + /// True once the dictionary has become full and the writing position + /// has been wrapped in decode_buffer() in lz_decoder.c. + bool has_wrapped; + /// True when dictionary should be reset before decoding more data. bool need_reset; @@ -62,8 +86,10 @@ typedef struct { void (*reset)(void *coder, const void *options); - /// Set the uncompressed size - void (*set_uncompressed)(void *coder, lzma_vli uncompressed_size); + /// Set the uncompressed size. If uncompressed_size == LZMA_VLI_UNKNOWN + /// then allow_eopm will always be true. + void (*set_uncompressed)(void *coder, lzma_vli uncompressed_size, + bool allow_eopm); /// Free allocated resources void (*end)(void *coder, const lzma_allocator *allocator); @@ -85,14 +111,12 @@ extern lzma_ret lzma_lz_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter_info *filters, lzma_ret (*lz_init)(lzma_lz_decoder *lz, - const lzma_allocator *allocator, const void *options, + const lzma_allocator *allocator, + lzma_vli id, const void *options, lzma_lz_options *lz_options)); extern uint64_t lzma_lz_decoder_memusage(size_t dictionary_size); -extern void lzma_lz_decoder_uncompressed( - void *coder, lzma_vli uncompressed_size); - ////////////////////// // Inline functions // @@ -103,7 +127,16 @@ static inline uint8_t dict_get(const lzma_dict *const dict, const uint32_t distance) { return dict->buf[dict->pos - distance - 1 - + (distance < dict->pos ? 0 : dict->size)]; + + (distance < dict->pos + ? 0 : dict->size - LZ_DICT_REPEAT_MAX)]; +} + + +/// Optimized version of dict_get(dict, 0) +static inline uint8_t +dict_get0(const lzma_dict *const dict) +{ + return dict->buf[dict->pos - 1]; } @@ -132,68 +165,51 @@ dict_repeat(lzma_dict *dict, uint32_t distance, uint32_t *len) uint32_t left = my_min(dict_avail, *len); *len -= left; + size_t back = dict->pos - distance - 1; + if (distance >= dict->pos) + back += dict->size - LZ_DICT_REPEAT_MAX; + // Repeat a block of data from the history. Because memcpy() is faster // than copying byte by byte in a loop, the copying process gets split - // into three cases. + // into two cases. if (distance < left) { // Source and target areas overlap, thus we can't use // memcpy() nor even memmove() safely. do { - dict->buf[dict->pos] = dict_get(dict, distance); - ++dict->pos; + dict->buf[dict->pos++] = dict->buf[back++]; } while (--left > 0); - - } else if (distance < dict->pos) { - // The easiest and fastest case - memcpy(dict->buf + dict->pos, - dict->buf + dict->pos - distance - 1, - left); - dict->pos += left; - } else { - // The bigger the dictionary, the more rare this - // case occurs. We need to "wrap" the dict, thus - // we might need two memcpy() to copy all the data. - assert(dict->full == dict->size); - const uint32_t copy_pos - = dict->pos - distance - 1 + dict->size; - uint32_t copy_size = dict->size - copy_pos; - - if (copy_size < left) { - memmove(dict->buf + dict->pos, dict->buf + copy_pos, - copy_size); - dict->pos += copy_size; - copy_size = left - copy_size; - memcpy(dict->buf + dict->pos, dict->buf, copy_size); - dict->pos += copy_size; - } else { - memmove(dict->buf + dict->pos, dict->buf + copy_pos, - left); - dict->pos += left; - } + memcpy(dict->buf + dict->pos, dict->buf + back, left); + dict->pos += left; } // Update how full the dictionary is. - if (dict->full < dict->pos) - dict->full = dict->pos; + if (!dict->has_wrapped) + dict->full = dict->pos - 2 * LZ_DICT_REPEAT_MAX; + + return *len != 0; +} + + +static inline void +dict_put(lzma_dict *dict, uint8_t byte) +{ + dict->buf[dict->pos++] = byte; - return unlikely(*len != 0); + if (!dict->has_wrapped) + dict->full = dict->pos - 2 * LZ_DICT_REPEAT_MAX; } /// Puts one byte into the dictionary. Returns true if the dictionary was /// already full and the byte couldn't be added. static inline bool -dict_put(lzma_dict *dict, uint8_t byte) +dict_put_safe(lzma_dict *dict, uint8_t byte) { if (unlikely(dict->pos == dict->limit)) return true; - dict->buf[dict->pos++] = byte; - - if (dict->pos > dict->full) - dict->full = dict->pos; - + dict_put(dict, byte); return false; } @@ -217,8 +233,8 @@ dict_write(lzma_dict *restrict dict, const uint8_t *restrict in, *left -= lzma_bufcpy(in, in_pos, in_size, dict->buf, &dict->pos, dict->limit); - if (dict->pos > dict->full) - dict->full = dict->pos; + if (!dict->has_wrapped) + dict->full = dict->pos - 2 * LZ_DICT_REPEAT_MAX; return; } diff --git a/Utilities/cmliblzma/liblzma/lz/lz_encoder.c b/Utilities/cmliblzma/liblzma/lz/lz_encoder.c index 9a74b7c47ce..4af23e14c42 100644 --- a/Utilities/cmliblzma/liblzma/lz/lz_encoder.c +++ b/Utilities/cmliblzma/liblzma/lz/lz_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lz_encoder.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "lz_encoder.h" @@ -196,9 +195,7 @@ lz_encoder_prepare(lzma_mf *mf, const lzma_allocator *allocator, // For now, the dictionary size is limited to 1.5 GiB. This may grow // in the future if needed, but it needs a little more work than just // changing this check. - if (lz_options->dict_size < LZMA_DICT_SIZE_MIN - || lz_options->dict_size - > (UINT32_C(1) << 30) + (UINT32_C(1) << 29) + if (!IS_ENC_DICT_SIZE_VALID(lz_options->dict_size) || lz_options->nice_len > lz_options->match_len_max) return true; @@ -293,11 +290,15 @@ lz_encoder_prepare(lzma_mf *mf, const lzma_allocator *allocator, return true; } - // Calculate the sizes of mf->hash and mf->son and check that - // nice_len is big enough for the selected match finder. - const uint32_t hash_bytes = lz_options->match_finder & 0x0F; - if (hash_bytes > mf->nice_len) - return true; + // Calculate the sizes of mf->hash and mf->son. + // + // NOTE: Since 5.3.5beta the LZMA encoder ensures that nice_len + // is big enough for the selected match finder. This makes it + // easier for applications as nice_len = 2 will always be accepted + // even though the effective value can be slightly bigger. + const uint32_t hash_bytes + = mf_get_hash_bytes(lz_options->match_finder); + assert(hash_bytes <= mf->nice_len); const bool is_bt = (lz_options->match_finder & 0x10) != 0; uint32_t hs; @@ -521,15 +522,31 @@ lz_encoder_update(void *coder_ptr, const lzma_allocator *allocator, } +static lzma_ret +lz_encoder_set_out_limit(void *coder_ptr, uint64_t *uncomp_size, + uint64_t out_limit) +{ + lzma_coder *coder = coder_ptr; + + // This is supported only if there are no other filters chained. + if (coder->next.code == NULL && coder->lz.set_out_limit != NULL) + return coder->lz.set_out_limit( + coder->lz.coder, uncomp_size, out_limit); + + return LZMA_OPTIONS_ERROR; +} + + extern lzma_ret lzma_lz_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter_info *filters, lzma_ret (*lz_init)(lzma_lz_encoder *lz, - const lzma_allocator *allocator, const void *options, + const lzma_allocator *allocator, + lzma_vli id, const void *options, lzma_lz_options *lz_options)) { -#ifdef HAVE_SMALL - // We need that the CRC32 table has been initialized. +#if defined(HAVE_SMALL) && !defined(HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR) + // The CRC32 table must be initialized. lzma_crc32_init(); #endif @@ -544,10 +561,13 @@ lzma_lz_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, next->code = &lz_encode; next->end = &lz_encoder_end; next->update = &lz_encoder_update; + next->set_out_limit = &lz_encoder_set_out_limit; coder->lz.coder = NULL; coder->lz.code = NULL; coder->lz.end = NULL; + coder->lz.options_update = NULL; + coder->lz.set_out_limit = NULL; // mf.size is initialized to silence Valgrind // when used on optimized binaries (GCC may reorder @@ -565,7 +585,7 @@ lzma_lz_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, // Initialize the LZ-based encoder. lzma_lz_options lz_options; return_if_error(lz_init(&coder->lz, allocator, - filters[0].options, &lz_options)); + filters[0].id, filters[0].options, &lz_options)); // Setup the size information into coder->mf and deallocate // old buffers if they have wrong size. @@ -585,32 +605,28 @@ lzma_lz_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, extern LZMA_API(lzma_bool) lzma_mf_is_supported(lzma_match_finder mf) { - bool ret = false; - + switch (mf) { #ifdef HAVE_MF_HC3 - if (mf == LZMA_MF_HC3) - ret = true; + case LZMA_MF_HC3: + return true; #endif - #ifdef HAVE_MF_HC4 - if (mf == LZMA_MF_HC4) - ret = true; + case LZMA_MF_HC4: + return true; #endif - #ifdef HAVE_MF_BT2 - if (mf == LZMA_MF_BT2) - ret = true; + case LZMA_MF_BT2: + return true; #endif - #ifdef HAVE_MF_BT3 - if (mf == LZMA_MF_BT3) - ret = true; + case LZMA_MF_BT3: + return true; #endif - #ifdef HAVE_MF_BT4 - if (mf == LZMA_MF_BT4) - ret = true; + case LZMA_MF_BT4: + return true; #endif - - return ret; + default: + return false; + } } diff --git a/Utilities/cmliblzma/liblzma/lz/lz_encoder.h b/Utilities/cmliblzma/liblzma/lz/lz_encoder.h index 426dcd8a387..eb197c6b6cd 100644 --- a/Utilities/cmliblzma/liblzma/lz/lz_encoder.h +++ b/Utilities/cmliblzma/liblzma/lz/lz_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lz_encoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZ_ENCODER_H @@ -17,6 +16,14 @@ #include "common.h" +// For now, the dictionary size is limited to 1.5 GiB. This may grow +// in the future if needed, but it needs a little more work than just +// changing this check. +#define IS_ENC_DICT_SIZE_VALID(size) \ + ((size) >= LZMA_DICT_SIZE_MIN \ + && (size) <= (UINT32_C(1) << 30) + (UINT32_C(1) << 29)) + + /// A table of these is used by the LZ-based encoder to hold /// the length-distance pairs found by the match finder. typedef struct { @@ -153,9 +160,13 @@ typedef struct { /// Maximum search depth uint32_t depth; - /// TODO: Comment + /// Initial dictionary for the match finder to search. const uint8_t *preset_dict; + /// If the preset dictionary is NULL, this value is ignored. + /// Otherwise this member must indicate the preset dictionary's + /// buffer size. If this size is larger than dict_size, then only + /// the dict_size sized tail of the preset_dict will be used. uint32_t preset_dict_size; } lzma_lz_options; @@ -184,7 +195,7 @@ typedef struct { // // Algorithms such as LZMA2 first try to compress a chunk, and then check // if the encoded result is smaller than the uncompressed one. If the chunk -// was uncompressible, it is better to store it in uncompressed form in +// was incompressible, it is better to store it in uncompressed form in // the output stream. To do this, the whole uncompressed chunk has to be // still available in the history buffer. before_size achieves that. @@ -204,6 +215,10 @@ typedef struct { /// Update the options in the middle of the encoding. lzma_ret (*options_update)(void *coder, const lzma_filter *filter); + /// Set maximum allowed output size + lzma_ret (*set_out_limit)(void *coder, uint64_t *uncomp_size, + uint64_t out_limit); + } lzma_lz_encoder; @@ -213,7 +228,16 @@ typedef struct { // 3. The literals and matches are encoded using e.g. LZMA. // // The bytes that have been ran through the match finder, but not encoded yet, -// are called `read ahead'. +// are called 'read ahead'. + + +/// Get how many bytes the match finder hashes in its initial step. +/// This is also the minimum nice_len value with the match finder. +static inline uint32_t +mf_get_hash_bytes(lzma_match_finder match_finder) +{ + return (uint32_t)match_finder & 0x0F; +} /// Get pointer to the first byte not ran through the match finder @@ -298,7 +322,8 @@ extern lzma_ret lzma_lz_encoder_init( lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter_info *filters, lzma_ret (*lz_init)(lzma_lz_encoder *lz, - const lzma_allocator *allocator, const void *options, + const lzma_allocator *allocator, + lzma_vli id, const void *options, lzma_lz_options *lz_options)); diff --git a/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash.h b/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash.h index fb15c58146d..8ace82b04c5 100644 --- a/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash.h +++ b/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lz_encoder_hash.h @@ -5,9 +7,6 @@ // // Author: Igor Pavlov // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZ_ENCODER_HASH_H @@ -17,6 +16,7 @@ // This is to make liblzma produce the same output on big endian // systems that it does on little endian systems. lz_encoder.c // takes care of including the actual table. + lzma_attr_visibility_hidden extern const uint32_t lzma_lz_hash_table[256]; # define hash_table lzma_lz_hash_table #else diff --git a/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash_table.h b/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash_table.h index 8c51717d704..2b3a60e43e8 100644 --- a/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash_table.h +++ b/Utilities/cmliblzma/liblzma/lz/lz_encoder_hash_table.h @@ -1,4 +1,6 @@ -/* This file has been automatically generated by crc32_tablegen.c. */ +// SPDX-License-Identifier: 0BSD + +// This file has been generated by crc32_tablegen.c. const uint32_t lzma_lz_hash_table[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, diff --git a/Utilities/cmliblzma/liblzma/lz/lz_encoder_mf.c b/Utilities/cmliblzma/liblzma/lz/lz_encoder_mf.c index d03657a7c93..557c2612f2a 100644 --- a/Utilities/cmliblzma/liblzma/lz/lz_encoder_mf.c +++ b/Utilities/cmliblzma/liblzma/lz/lz_encoder_mf.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lz_encoder_mf.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "lz_encoder.h" @@ -220,10 +219,11 @@ move_pending(lzma_mf *mf) /// of matches found. #define call_find(func, len_best) \ do { \ - matches_count = func(len_limit, pos, cur, cur_match, mf->depth, \ - mf->son, mf->cyclic_pos, mf->cyclic_size, \ + matches_count = (uint32_t)(func(len_limit, pos, cur, cur_match, \ + mf->depth, mf->son, \ + mf->cyclic_pos, mf->cyclic_size, \ matches + matches_count, len_best) \ - - matches; \ + - matches); \ move_pos(mf); \ return matches_count; \ } while (0) @@ -242,8 +242,8 @@ do { \ /// \param cur_match Start position of the current match candidate /// \param depth Maximum length of the hash chain /// \param son lzma_mf.son (contains the hash chain) -/// \param cyclic_pos -/// \param cyclic_size +/// \param cyclic_pos lzma_mf.cyclic_pos +/// \param cyclic_size lzma_mf_cyclic_size /// \param matches Array to hold the matches. /// \param len_best The length of the longest match found so far. static lzma_match * diff --git a/Utilities/cmliblzma/liblzma/lzma/fastpos.h b/Utilities/cmliblzma/liblzma/lzma/fastpos.h index cba442c27e5..d3969a753fa 100644 --- a/Utilities/cmliblzma/liblzma/lzma/fastpos.h +++ b/Utilities/cmliblzma/liblzma/lzma/fastpos.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file fastpos.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_FASTPOS_H @@ -91,6 +90,7 @@ get_dist_slot_2(uint32_t dist) #define FASTPOS_BITS 13 +lzma_attr_visibility_hidden extern const uint8_t lzma_fastpos[1 << FASTPOS_BITS]; diff --git a/Utilities/cmliblzma/liblzma/lzma/fastpos_table.c b/Utilities/cmliblzma/liblzma/lzma/fastpos_table.c index 6a3ceac0e90..4e10e3795e2 100644 --- a/Utilities/cmliblzma/liblzma/lzma/fastpos_table.c +++ b/Utilities/cmliblzma/liblzma/lzma/fastpos_table.c @@ -1,4 +1,6 @@ -/* This file has been automatically generated by fastpos_tablegen.c. */ +// SPDX-License-Identifier: 0BSD + +// This file has been generated by fastpos_tablegen.c. #include "common.h" #include "fastpos.h" diff --git a/Utilities/cmliblzma/liblzma/lzma/fastpos_tablegen.c b/Utilities/cmliblzma/liblzma/lzma/fastpos_tablegen.c index d4484c82d0b..957ccb7a643 100644 --- a/Utilities/cmliblzma/liblzma/lzma/fastpos_tablegen.c +++ b/Utilities/cmliblzma/liblzma/lzma/fastpos_tablegen.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file fastpos_tablegen.c @@ -6,13 +8,12 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include #include + +#define lzma_attr_visibility_hidden #include "fastpos.h" @@ -33,11 +34,13 @@ main(void) fastpos[c] = slot_fast; } - printf("/* This file has been automatically generated " - "by fastpos_tablegen.c. */\n\n" - "#include \"common.h\"\n" - "#include \"fastpos.h\"\n\n" - "const uint8_t lzma_fastpos[1 << FASTPOS_BITS] = {"); + // Split the SPDX string so that it won't accidentally match + // when tools search for the string. + printf("// SPDX" "-License-Identifier" ": 0BSD\n\n" + "// This file has been generated by fastpos_tablegen.c.\n\n" + "#include \"common.h\"\n" + "#include \"fastpos.h\"\n\n" + "const uint8_t lzma_fastpos[1 << FASTPOS_BITS] = {"); for (size_t i = 0; i < (1 << FASTPOS_BITS); ++i) { if (i % 16 == 0) diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.c b/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.c index cf1b5110aca..37ab253f5b0 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.c +++ b/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma2_decoder.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "lzma2_decoder.h" @@ -139,7 +138,7 @@ lzma2_decode(void *coder_ptr, lzma_dict *restrict dict, coder->uncompressed_size += in[(*in_pos)++] + 1U; coder->sequence = SEQ_COMPRESSED_0; coder->lzma.set_uncompressed(coder->lzma.coder, - coder->uncompressed_size); + coder->uncompressed_size, false); break; case SEQ_COMPRESSED_0: @@ -226,7 +225,8 @@ lzma2_decoder_end(void *coder_ptr, const lzma_allocator *allocator) static lzma_ret lzma2_decoder_init(lzma_lz_decoder *lz, const lzma_allocator *allocator, - const void *opt, lzma_lz_options *lz_options) + lzma_vli id lzma_attribute((__unused__)), const void *opt, + lzma_lz_options *lz_options) { lzma_lzma2_coder *coder = lz->coder; if (coder == NULL) { diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.h b/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.h index ef2dcbfa76f..cdd8b463abf 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.h +++ b/Utilities/cmliblzma/liblzma/lzma/lzma2_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma2_decoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZMA2_DECODER_H diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.c b/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.c index 63588ee30c6..e20b75b3003 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.c +++ b/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma2_encoder.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "lz_encoder.h" @@ -310,7 +309,8 @@ lzma2_encoder_options_update(void *coder_ptr, const lzma_filter *filter) static lzma_ret lzma2_encoder_init(lzma_lz_encoder *lz, const lzma_allocator *allocator, - const void *options, lzma_lz_options *lz_options) + lzma_vli id lzma_attribute((__unused__)), const void *options, + lzma_lz_options *lz_options) { if (options == NULL) return LZMA_PROG_ERROR; @@ -340,7 +340,7 @@ lzma2_encoder_init(lzma_lz_encoder *lz, const lzma_allocator *allocator, // Initialize LZMA encoder return_if_error(lzma_lzma_encoder_create(&coder->lzma, allocator, - &coder->opt_cur, lz_options)); + LZMA_FILTER_LZMA2, &coder->opt_cur, lz_options)); // Make sure that we will always have enough history available in // case we need to use uncompressed chunks. They are used when the @@ -378,6 +378,9 @@ lzma_lzma2_encoder_memusage(const void *options) extern lzma_ret lzma_lzma2_props_encode(const void *options, uint8_t *out) { + if (options == NULL) + return LZMA_PROG_ERROR; + const lzma_options_lzma *const opt = options; uint32_t d = my_max(opt->dict_size, LZMA_DICT_SIZE_MIN); @@ -405,6 +408,9 @@ lzma_lzma2_block_size(const void *options) { const lzma_options_lzma *const opt = options; + if (!IS_ENC_DICT_SIZE_VALID(opt->dict_size)) + return UINT64_MAX; + // Use at least 1 MiB to keep compression ratio better. return my_max((uint64_t)(opt->dict_size) * 3, UINT64_C(1) << 20); } diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.h b/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.h index 515f1839347..29966a66d23 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.h +++ b/Utilities/cmliblzma/liblzma/lzma/lzma2_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma2_encoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZMA2_ENCODER_H diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_common.h b/Utilities/cmliblzma/liblzma/lzma/lzma_common.h index 9d040d95bb2..c3c587f090e 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_common.h +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_common.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_common.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZMA_COMMON_H @@ -84,6 +83,20 @@ typedef enum { ? (state) - 3 \ : (state) - 6)) +/// Like update_literal(state) but when it is already known that +/// is_literal_state(state) is true. +#define update_literal_normal(state) \ + state = ((state) <= STATE_SHORTREP_LIT_LIT \ + ? STATE_LIT_LIT \ + : (state) - 3); + +/// Like update_literal(state) but when it is already known that +/// is_literal_state(state) is false. +#define update_literal_matched(state) \ + state = ((state) <= STATE_LIT_SHORTREP \ + ? (state) - 3 \ + : (state) - 6); + /// Indicate that the latest state was a match. #define update_match(state) \ state = ((state) < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH) @@ -112,31 +125,33 @@ typedef enum { /// /// Match byte is used when the previous LZMA symbol was something else than /// a literal (that is, it was some kind of match). -#define LITERAL_CODER_SIZE 0x300 +#define LITERAL_CODER_SIZE UINT32_C(0x300) /// Maximum number of literal coders #define LITERAL_CODERS_MAX (1 << LZMA_LCLP_MAX) +/// Calculates the literal_mask that literal_subcoder() needs. +#define literal_mask_calc(lc, lp) \ + ((UINT32_C(0x100) << (lp)) - (UINT32_C(0x100) >> (lc))) + /// Locate the literal coder for the next literal byte. The choice depends on /// - the lowest literal_pos_bits bits of the position of the current /// byte; and /// - the highest literal_context_bits bits of the previous byte. -#define literal_subcoder(probs, lc, lp_mask, pos, prev_byte) \ - ((probs)[(((pos) & (lp_mask)) << (lc)) \ - + ((uint32_t)(prev_byte) >> (8U - (lc)))]) +#define literal_subcoder(probs, lc, literal_mask, pos, prev_byte) \ + ((probs) + UINT32_C(3) * \ + (((((pos) << 8) + (prev_byte)) & (literal_mask)) << (lc))) static inline void -literal_init(probability (*probs)[LITERAL_CODER_SIZE], - uint32_t lc, uint32_t lp) +literal_init(probability *probs, uint32_t lc, uint32_t lp) { assert(lc + lp <= LZMA_LCLP_MAX); - const uint32_t coders = 1U << (lc + lp); + const size_t coders = LITERAL_CODER_SIZE << (lc + lp); - for (uint32_t i = 0; i < coders; ++i) - for (uint32_t j = 0; j < LITERAL_CODER_SIZE; ++j) - bit_reset(probs[i][j]); + for (size_t i = 0; i < coders; ++i) + bit_reset(probs[i]); return; } diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.c b/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.c index e605a0a916b..0abed02b815 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.c +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_decoder.c @@ -5,9 +7,7 @@ /// // Authors: Igor Pavlov // Lasse Collin -// -// This file has been put into the public domain. -// You can do whatever you want with this file. +// Jia Tan // /////////////////////////////////////////////////////////////////////////////// @@ -22,25 +22,20 @@ # pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #endif +// Minimum number of input bytes to safely decode one LZMA symbol. +// The worst case is that we decode 22 bits using probabilities and 26 +// direct bits. This may decode at maximum 20 bytes of input. +#define LZMA_IN_REQUIRED 20 -#ifdef HAVE_SMALL // Macros for (somewhat) size-optimized code. -#define seq_4(seq) seq - -#define seq_6(seq) seq - -#define seq_8(seq) seq - -#define seq_len(seq) \ - seq ## _CHOICE, \ - seq ## _CHOICE2, \ - seq ## _BITTREE - +// This is used to decode the match length (how many bytes must be repeated +// from the dictionary). This version is used in the Resumable mode and +// does not unroll any loops. #define len_decode(target, ld, pos_state, seq) \ do { \ case seq ## _CHOICE: \ - rc_if_0(ld.choice, seq ## _CHOICE) { \ + rc_if_0_safe(ld.choice, seq ## _CHOICE) { \ rc_update_0(ld.choice); \ probs = ld.low[pos_state];\ limit = LEN_LOW_SYMBOLS; \ @@ -48,7 +43,7 @@ case seq ## _CHOICE: \ } else { \ rc_update_1(ld.choice); \ case seq ## _CHOICE2: \ - rc_if_0(ld.choice2, seq ## _CHOICE2) { \ + rc_if_0_safe(ld.choice2, seq ## _CHOICE2) { \ rc_update_0(ld.choice2); \ probs = ld.mid[pos_state]; \ limit = LEN_MID_SYMBOLS; \ @@ -64,98 +59,39 @@ case seq ## _CHOICE2: \ symbol = 1; \ case seq ## _BITTREE: \ do { \ - rc_bit(probs[symbol], , , seq ## _BITTREE); \ + rc_bit_safe(probs[symbol], , , seq ## _BITTREE); \ } while (symbol < limit); \ target += symbol - limit; \ } while (0) -#else // HAVE_SMALL - -// Unrolled versions -#define seq_4(seq) \ - seq ## 0, \ - seq ## 1, \ - seq ## 2, \ - seq ## 3 - -#define seq_6(seq) \ - seq ## 0, \ - seq ## 1, \ - seq ## 2, \ - seq ## 3, \ - seq ## 4, \ - seq ## 5 - -#define seq_8(seq) \ - seq ## 0, \ - seq ## 1, \ - seq ## 2, \ - seq ## 3, \ - seq ## 4, \ - seq ## 5, \ - seq ## 6, \ - seq ## 7 - -#define seq_len(seq) \ - seq ## _CHOICE, \ - seq ## _LOW0, \ - seq ## _LOW1, \ - seq ## _LOW2, \ - seq ## _CHOICE2, \ - seq ## _MID0, \ - seq ## _MID1, \ - seq ## _MID2, \ - seq ## _HIGH0, \ - seq ## _HIGH1, \ - seq ## _HIGH2, \ - seq ## _HIGH3, \ - seq ## _HIGH4, \ - seq ## _HIGH5, \ - seq ## _HIGH6, \ - seq ## _HIGH7 -#define len_decode(target, ld, pos_state, seq) \ +// This is the faster version of the match length decoder that does not +// worry about being resumable. It unrolls the bittree decoding loop. +#define len_decode_fast(target, ld, pos_state) \ do { \ symbol = 1; \ -case seq ## _CHOICE: \ - rc_if_0(ld.choice, seq ## _CHOICE) { \ + rc_if_0(ld.choice) { \ rc_update_0(ld.choice); \ - rc_bit_case(ld.low[pos_state][symbol], , , seq ## _LOW0); \ - rc_bit_case(ld.low[pos_state][symbol], , , seq ## _LOW1); \ - rc_bit_case(ld.low[pos_state][symbol], , , seq ## _LOW2); \ - target = symbol - LEN_LOW_SYMBOLS + MATCH_LEN_MIN; \ + rc_bittree3(ld.low[pos_state], \ + -LEN_LOW_SYMBOLS + MATCH_LEN_MIN); \ + target = symbol; \ } else { \ rc_update_1(ld.choice); \ -case seq ## _CHOICE2: \ - rc_if_0(ld.choice2, seq ## _CHOICE2) { \ + rc_if_0(ld.choice2) { \ rc_update_0(ld.choice2); \ - rc_bit_case(ld.mid[pos_state][symbol], , , \ - seq ## _MID0); \ - rc_bit_case(ld.mid[pos_state][symbol], , , \ - seq ## _MID1); \ - rc_bit_case(ld.mid[pos_state][symbol], , , \ - seq ## _MID2); \ - target = symbol - LEN_MID_SYMBOLS \ - + MATCH_LEN_MIN + LEN_LOW_SYMBOLS; \ + rc_bittree3(ld.mid[pos_state], -LEN_MID_SYMBOLS \ + + MATCH_LEN_MIN + LEN_LOW_SYMBOLS); \ + target = symbol; \ } else { \ rc_update_1(ld.choice2); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH0); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH1); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH2); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH3); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH4); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH5); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH6); \ - rc_bit_case(ld.high[symbol], , , seq ## _HIGH7); \ - target = symbol - LEN_HIGH_SYMBOLS \ + rc_bittree8(ld.high, -LEN_HIGH_SYMBOLS \ + MATCH_LEN_MIN \ - + LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS; \ + + LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS); \ + target = symbol; \ } \ } \ } while (0) -#endif // HAVE_SMALL - /// Length decoder probabilities; see comments in lzma_common.h. typedef struct { @@ -173,7 +109,7 @@ typedef struct { /////////////////// /// Literals; see comments in lzma_common.h. - probability literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; + probability literal[LITERAL_CODERS_MAX * LITERAL_CODER_SIZE]; /// If 1, it's a match. Otherwise it's a single 8-bit literal. probability is_match[STATES][POS_STATES_MAX]; @@ -232,12 +168,17 @@ typedef struct { uint32_t pos_mask; // (1U << pb) - 1 uint32_t literal_context_bits; - uint32_t literal_pos_mask; + uint32_t literal_mask; /// Uncompressed size as bytes, or LZMA_VLI_UNKNOWN if end of /// payload marker is expected. lzma_vli uncompressed_size; + /// True if end of payload marker (EOPM) is allowed even when + /// uncompressed_size is known; false if EOPM must not be present. + /// This is ignored if uncompressed_size == LZMA_VLI_UNKNOWN. + bool allow_eopm; + //////////////////////////////// // State of incomplete symbol // //////////////////////////////// @@ -246,22 +187,26 @@ typedef struct { enum { SEQ_NORMALIZE, SEQ_IS_MATCH, - seq_8(SEQ_LITERAL), - seq_8(SEQ_LITERAL_MATCHED), + SEQ_LITERAL, + SEQ_LITERAL_MATCHED, SEQ_LITERAL_WRITE, SEQ_IS_REP, - seq_len(SEQ_MATCH_LEN), - seq_6(SEQ_DIST_SLOT), + SEQ_MATCH_LEN_CHOICE, + SEQ_MATCH_LEN_CHOICE2, + SEQ_MATCH_LEN_BITTREE, + SEQ_DIST_SLOT, SEQ_DIST_MODEL, SEQ_DIRECT, - seq_4(SEQ_ALIGN), + SEQ_ALIGN, SEQ_EOPM, SEQ_IS_REP0, SEQ_SHORTREP, SEQ_IS_REP0_LONG, SEQ_IS_REP1, SEQ_IS_REP2, - seq_len(SEQ_REP_LEN), + SEQ_REP_LEN_CHOICE, + SEQ_REP_LEN_CHOICE2, + SEQ_REP_LEN_BITTREE, SEQ_COPY, } sequence; @@ -316,7 +261,7 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, const size_t dict_start = dict.pos; // Range decoder - rc_to_local(coder->rc, *in_pos); + rc_to_local(coder->rc, *in_pos, LZMA_IN_REQUIRED); // State uint32_t state = coder->state; @@ -335,7 +280,7 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, uint32_t offset = coder->offset; uint32_t len = coder->len; - const uint32_t literal_pos_mask = coder->literal_pos_mask; + const uint32_t literal_mask = coder->literal_mask; const uint32_t literal_context_bits = coder->literal_context_bits; // Temporary variables @@ -343,15 +288,43 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, lzma_ret ret = LZMA_OK; - // If uncompressed size is known, there must be no end of payload - // marker. - const bool no_eopm = coder->uncompressed_size - != LZMA_VLI_UNKNOWN; - if (no_eopm && coder->uncompressed_size < dict.limit - dict.pos) + // This is true when the next LZMA symbol is allowed to be EOPM. + // That is, if this is false, then EOPM is considered + // an invalid symbol and we will return LZMA_DATA_ERROR. + // + // EOPM is always required (not just allowed) when + // the uncompressed size isn't known. When uncompressed size + // is known, eopm_is_valid may be set to true later. + bool eopm_is_valid = coder->uncompressed_size == LZMA_VLI_UNKNOWN; + + // If uncompressed size is known and there is enough output space + // to decode all the data, limit the available buffer space so that + // the main loop won't try to decode past the end of the stream. + bool might_finish_without_eopm = false; + if (coder->uncompressed_size != LZMA_VLI_UNKNOWN + && coder->uncompressed_size <= dict.limit - dict.pos) { dict.limit = dict.pos + (size_t)(coder->uncompressed_size); + might_finish_without_eopm = true; + } + + // The main decoder loop. The "switch" is used to resume the decoder at + // correct location. Once resumed, the "switch" is no longer used. + // The decoder loops is split into two modes: + // + // 1 - Non-resumable mode (fast). This is used when it is guaranteed + // there is enough input to decode the next symbol. If the output + // limit is reached, then the decoder loop will save the place + // for the resumable mode to continue. This mode is not used if + // HAVE_SMALL is defined. This is faster than Resumable mode + // because it reduces the number of branches needed and allows + // for more compiler optimizations. + // + // 2 - Resumable mode (slow). This is used when a previous decoder + // loop did not have enough space in the input or output buffers + // to complete. It uses sequence enum values to set remind + // coder->sequence where to resume in the decoder loop. This + // is the only mode used when HAVE_SMALL is defined. - // The main decoder loop. The "switch" is used to restart the decoder at - // correct location. Once restarted, the "switch" is no longer used. switch (coder->sequence) while (true) { // Calculate new pos_state. This is skipped on the first loop @@ -359,54 +332,392 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, // variables. pos_state = dict.pos & pos_mask; +#ifndef HAVE_SMALL + + /////////////////////////////// + // Non-resumable Mode (fast) // + /////////////////////////////// + + // Go to Resumable mode (1) if there is not enough input to + // safely decode any possible LZMA symbol or (2) if the + // dictionary is full, which may need special checks that + // are only done in the Resumable mode. + if (unlikely(!rc_is_fast_allowed() + || dict.pos == dict.limit)) + goto slow; + + // Decode the first bit from the next LZMA symbol. + // If the bit is a 0, then we handle it as a literal. + // If the bit is a 1, then it is a match of previously + // decoded data. + rc_if_0(coder->is_match[state][pos_state]) { + ///////////////////// + // Decode literal. // + ///////////////////// + + // Update the RC that we have decoded a 0. + rc_update_0(coder->is_match[state][pos_state]); + + // Get the correct probability array from lp and + // lc params. + probs = literal_subcoder(coder->literal, + literal_context_bits, literal_mask, + dict.pos, dict_get0(&dict)); + + if (is_literal_state(state)) { + update_literal_normal(state); + + // Decode literal without match byte. + rc_bittree8(probs, 0); + } else { + update_literal_matched(state); + + // Decode literal with match byte. + rc_matched_literal(probs, + dict_get(&dict, rep0)); + } + + // Write decoded literal to dictionary + dict_put(&dict, symbol); + continue; + } + + /////////////////// + // Decode match. // + /////////////////// + + // Instead of a new byte we are going to decode a + // distance-length pair. The distance represents how far + // back in the dictionary to begin copying. The length + // represents how many bytes to copy. + + rc_update_1(coder->is_match[state][pos_state]); + + rc_if_0(coder->is_rep[state]) { + /////////////////// + // Simple match. // + /////////////////// + + // Not a repeated match. In this case, + // the length (how many bytes to copy) must be + // decoded first. Then, the distance (where to + // start copying) is decoded. + // + // This is also how we know when we are done + // decoding. If the distance decodes to UINT32_MAX, + // then we know to stop decoding (end of payload + // marker). + + rc_update_0(coder->is_rep[state]); + update_match(state); + + // The latest three match distances are kept in + // memory in case there are repeated matches. + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + + // Decode the length of the match. + len_decode_fast(len, coder->match_len_decoder, + pos_state); + + // Next, decode the distance into rep0. + + // The next 6 bits determine how to decode the + // rest of the distance. + probs = coder->dist_slot[get_dist_state(len)]; + + rc_bittree6(probs, -DIST_SLOTS); + assert(symbol <= 63); + + if (symbol < DIST_MODEL_START) { + // If the decoded symbol is < DIST_MODEL_START + // then we use its value directly as the + // match distance. No other bits are needed. + // The only possible distance values + // are [0, 3]. + rep0 = symbol; + } else { + // Use the first two bits of symbol as the + // highest bits of the match distance. + + // "limit" represents the number of low bits + // to decode. + limit = (symbol >> 1) - 1; + assert(limit >= 1 && limit <= 30); + rep0 = 2 + (symbol & 1); + + if (symbol < DIST_MODEL_END) { + // When symbol is > DIST_MODEL_START, + // but symbol < DIST_MODEL_END, then + // it can decode distances between + // [4, 127]. + assert(limit <= 5); + rep0 <<= limit; + assert(rep0 <= 96); + + // -1 is fine, because we start + // decoding at probs[1], not probs[0]. + // NOTE: This violates the C standard, + // since we are doing pointer + // arithmetic past the beginning of + // the array. + assert((int32_t)(rep0 - symbol - 1) + >= -1); + assert((int32_t)(rep0 - symbol - 1) + <= 82); + probs = coder->pos_special + rep0 + - symbol - 1; + symbol = 1; + offset = 1; + + // Variable number (1-5) of bits + // from a reverse bittree. This + // isn't worth manual unrolling. + // + // NOTE: Making one or many of the + // variables (probs, symbol, offset, + // or limit) local here (instead of + // using those declared outside the + // main loop) can affect code size + // and performance which isn't a + // surprise but it's not so clear + // what is the best. + do { + rc_bit_add_if_1(probs, + rep0, offset); + offset <<= 1; + } while (--limit > 0); + } else { + // The distance is >= 128. Decode the + // lower bits without probabilities + // except the lowest four bits. + assert(symbol >= 14); + assert(limit >= 6); + + limit -= ALIGN_BITS; + assert(limit >= 2); + + rc_direct(rep0, limit); + + // Decode the lowest four bits using + // probabilities. + rep0 <<= ALIGN_BITS; + rc_bittree_rev4(coder->pos_align); + rep0 += symbol; + + // If the end of payload marker (EOPM) + // is detected, jump to the safe code. + // The EOPM handling isn't speed + // critical at all. + // + // A final normalization is needed + // after the EOPM (there can be a + // dummy byte to read in some cases). + // If the normalization was done here + // in the fast code, it would need to + // be taken into account in the value + // of LZMA_IN_REQUIRED. Using the + // safe code allows keeping + // LZMA_IN_REQUIRED as 20 instead of + // 21. + if (rep0 == UINT32_MAX) + goto eopm; + } + } + + // Validate the distance we just decoded. + if (unlikely(!dict_is_distance_valid(&dict, rep0))) { + ret = LZMA_DATA_ERROR; + goto out; + } + + } else { + rc_update_1(coder->is_rep[state]); + + ///////////////////// + // Repeated match. // + ///////////////////// + + // The match distance is a value that we have decoded + // recently. The latest four match distances are + // available as rep0, rep1, rep2 and rep3. We will + // now decode which of them is the new distance. + // + // There cannot be a match if we haven't produced + // any output, so check that first. + if (unlikely(!dict_is_distance_valid(&dict, 0))) { + ret = LZMA_DATA_ERROR; + goto out; + } + + rc_if_0(coder->is_rep0[state]) { + rc_update_0(coder->is_rep0[state]); + // The distance is rep0. + + // Decode the next bit to determine if 1 byte + // should be copied from rep0 distance or + // if the number of bytes needs to be decoded. + + // If the next bit is 0, then it is a + // "Short Rep Match" and only 1 bit is copied. + // Otherwise, the length of the match is + // decoded after the "else" statement. + rc_if_0(coder->is_rep0_long[state][pos_state]) { + rc_update_0(coder->is_rep0_long[ + state][pos_state]); + + update_short_rep(state); + dict_put(&dict, dict_get(&dict, rep0)); + continue; + } + + // Repeating more than one byte at + // distance of rep0. + rc_update_1(coder->is_rep0_long[ + state][pos_state]); + + } else { + rc_update_1(coder->is_rep0[state]); + + // The distance is rep1, rep2 or rep3. Once + // we find out which one of these three, it + // is stored to rep0 and rep1, rep2 and rep3 + // are updated accordingly. There is no + // "Short Rep Match" option, so the length + // of the match must always be decoded next. + rc_if_0(coder->is_rep1[state]) { + // The distance is rep1. + rc_update_0(coder->is_rep1[state]); + + const uint32_t distance = rep1; + rep1 = rep0; + rep0 = distance; + + } else { + rc_update_1(coder->is_rep1[state]); + + rc_if_0(coder->is_rep2[state]) { + // The distance is rep2. + rc_update_0(coder->is_rep2[ + state]); + + const uint32_t distance = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance; + + } else { + // The distance is rep3. + rc_update_1(coder->is_rep2[ + state]); + + const uint32_t distance = rep3; + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance; + } + } + } + + update_long_rep(state); + + // Decode the length of the repeated match. + len_decode_fast(len, coder->rep_len_decoder, + pos_state); + } + + ///////////////////////////////// + // Repeat from history buffer. // + ///////////////////////////////// + + // The length is always between these limits. There is no way + // to trigger the algorithm to set len outside this range. + assert(len >= MATCH_LEN_MIN); + assert(len <= MATCH_LEN_MAX); + + // Repeat len bytes from distance of rep0. + if (unlikely(dict_repeat(&dict, rep0, &len))) { + coder->sequence = SEQ_COPY; + goto out; + } + + continue; + +slow: +#endif + /////////////////////////// + // Resumable Mode (slow) // + /////////////////////////// + + // This is very similar to Non-resumable Mode, so most of the + // comments are not repeated. The main differences are: + // - case labels are used to resume at the correct location. + // - Loops are not unrolled. + // - Range coder macros take an extra sequence argument + // so they can save to coder->sequence the location to + // resume in case there is not enough input. case SEQ_NORMALIZE: case SEQ_IS_MATCH: - if (unlikely(no_eopm && dict.pos == dict.limit)) - break; + if (unlikely(might_finish_without_eopm + && dict.pos == dict.limit)) { + // In rare cases there is a useless byte that needs + // to be read anyway. + rc_normalize_safe(SEQ_NORMALIZE); + + // If the range decoder state is such that we can + // be at the end of the LZMA stream, then the + // decoding is finished. + if (rc_is_finished(rc)) { + ret = LZMA_STREAM_END; + goto out; + } - rc_if_0(coder->is_match[state][pos_state], SEQ_IS_MATCH) { - rc_update_0(coder->is_match[state][pos_state]); + // If the caller hasn't allowed EOPM to be present + // together with known uncompressed size, then the + // LZMA stream is corrupt. + if (!coder->allow_eopm) { + ret = LZMA_DATA_ERROR; + goto out; + } - // It's a literal i.e. a single 8-bit byte. + // Otherwise continue decoding with the expectation + // that the next LZMA symbol is EOPM. + eopm_is_valid = true; + } + + rc_if_0_safe(coder->is_match[state][pos_state], SEQ_IS_MATCH) { + ///////////////////// + // Decode literal. // + ///////////////////// + + rc_update_0(coder->is_match[state][pos_state]); probs = literal_subcoder(coder->literal, - literal_context_bits, literal_pos_mask, - dict.pos, dict_get(&dict, 0)); + literal_context_bits, literal_mask, + dict.pos, dict_get0(&dict)); symbol = 1; if (is_literal_state(state)) { + update_literal_normal(state); + // Decode literal without match byte. -#ifdef HAVE_SMALL + // The "slow" version does not unroll + // the loop. case SEQ_LITERAL: do { - rc_bit(probs[symbol], , , SEQ_LITERAL); + rc_bit_safe(probs[symbol], , , + SEQ_LITERAL); } while (symbol < (1 << 8)); -#else - rc_bit_case(probs[symbol], , , SEQ_LITERAL0); - rc_bit_case(probs[symbol], , , SEQ_LITERAL1); - rc_bit_case(probs[symbol], , , SEQ_LITERAL2); - rc_bit_case(probs[symbol], , , SEQ_LITERAL3); - rc_bit_case(probs[symbol], , , SEQ_LITERAL4); - rc_bit_case(probs[symbol], , , SEQ_LITERAL5); - rc_bit_case(probs[symbol], , , SEQ_LITERAL6); - rc_bit_case(probs[symbol], , , SEQ_LITERAL7); -#endif } else { + update_literal_matched(state); + // Decode literal with match byte. - // - // We store the byte we compare against - // ("match byte") to "len" to minimize the - // number of variables we need to store - // between decoder calls. len = (uint32_t)(dict_get(&dict, rep0)) << 1; - // The usage of "offset" allows omitting some - // branches, which should give tiny speed - // improvement on some CPUs. "offset" gets - // set to zero if match_bit didn't match. offset = 0x100; -#ifdef HAVE_SMALL case SEQ_LITERAL_MATCHED: do { const uint32_t match_bit @@ -415,7 +726,7 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, = offset + match_bit + symbol; - rc_bit(probs[subcoder_index], + rc_bit_safe(probs[subcoder_index], offset &= ~match_bit, offset &= match_bit, SEQ_LITERAL_MATCHED); @@ -428,61 +739,10 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, len <<= 1; } while (symbol < (1 << 8)); -#else - // Unroll the loop. - uint32_t match_bit; - uint32_t subcoder_index; - -# define d(seq) \ - case seq: \ - match_bit = len & offset; \ - subcoder_index = offset + match_bit + symbol; \ - rc_bit(probs[subcoder_index], \ - offset &= ~match_bit, \ - offset &= match_bit, \ - seq) - - d(SEQ_LITERAL_MATCHED0); - len <<= 1; - d(SEQ_LITERAL_MATCHED1); - len <<= 1; - d(SEQ_LITERAL_MATCHED2); - len <<= 1; - d(SEQ_LITERAL_MATCHED3); - len <<= 1; - d(SEQ_LITERAL_MATCHED4); - len <<= 1; - d(SEQ_LITERAL_MATCHED5); - len <<= 1; - d(SEQ_LITERAL_MATCHED6); - len <<= 1; - d(SEQ_LITERAL_MATCHED7); -# undef d -#endif } - //update_literal(state); - // Use a lookup table to update to literal state, - // since compared to other state updates, this would - // need two branches. - static const lzma_lzma_state next_state[] = { - STATE_LIT_LIT, - STATE_LIT_LIT, - STATE_LIT_LIT, - STATE_LIT_LIT, - STATE_MATCH_LIT_LIT, - STATE_REP_LIT_LIT, - STATE_SHORTREP_LIT_LIT, - STATE_MATCH_LIT, - STATE_REP_LIT, - STATE_SHORTREP_LIT, - STATE_MATCH_LIT, - STATE_REP_LIT - }; - state = next_state[state]; - case SEQ_LITERAL_WRITE: - if (unlikely(dict_put(&dict, symbol))) { + if (dict_put_safe(&dict, symbol)) { coder->sequence = SEQ_LITERAL_WRITE; goto out; } @@ -490,64 +750,47 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, continue; } - // Instead of a new byte we are going to get a byte range - // (distance and length) which will be repeated from our - // output history. + /////////////////// + // Decode match. // + /////////////////// rc_update_1(coder->is_match[state][pos_state]); case SEQ_IS_REP: - rc_if_0(coder->is_rep[state], SEQ_IS_REP) { - // Not a repeated match + rc_if_0_safe(coder->is_rep[state], SEQ_IS_REP) { + /////////////////// + // Simple match. // + /////////////////// + rc_update_0(coder->is_rep[state]); update_match(state); - // The latest three match distances are kept in - // memory in case there are repeated matches. rep3 = rep2; rep2 = rep1; rep1 = rep0; - // Decode the length of the match. len_decode(len, coder->match_len_decoder, pos_state, SEQ_MATCH_LEN); - // Prepare to decode the highest two bits of the - // match distance. probs = coder->dist_slot[get_dist_state(len)]; symbol = 1; -#ifdef HAVE_SMALL case SEQ_DIST_SLOT: do { - rc_bit(probs[symbol], , , SEQ_DIST_SLOT); + rc_bit_safe(probs[symbol], , , SEQ_DIST_SLOT); } while (symbol < DIST_SLOTS); -#else - rc_bit_case(probs[symbol], , , SEQ_DIST_SLOT0); - rc_bit_case(probs[symbol], , , SEQ_DIST_SLOT1); - rc_bit_case(probs[symbol], , , SEQ_DIST_SLOT2); - rc_bit_case(probs[symbol], , , SEQ_DIST_SLOT3); - rc_bit_case(probs[symbol], , , SEQ_DIST_SLOT4); - rc_bit_case(probs[symbol], , , SEQ_DIST_SLOT5); -#endif - // Get rid of the highest bit that was needed for - // indexing of the probability array. + symbol -= DIST_SLOTS; assert(symbol <= 63); if (symbol < DIST_MODEL_START) { - // Match distances [0, 3] have only two bits. rep0 = symbol; } else { - // Decode the lowest [1, 29] bits of - // the match distance. limit = (symbol >> 1) - 1; assert(limit >= 1 && limit <= 30); rep0 = 2 + (symbol & 1); if (symbol < DIST_MODEL_END) { - // Prepare to decode the low bits for - // a distance of [4, 127]. assert(limit <= 5); rep0 <<= limit; assert(rep0 <= 96); @@ -566,103 +809,54 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, symbol = 1; offset = 0; case SEQ_DIST_MODEL: -#ifdef HAVE_SMALL do { - rc_bit(probs[symbol], , + rc_bit_safe(probs[symbol], , rep0 += 1U << offset, SEQ_DIST_MODEL); } while (++offset < limit); -#else - switch (limit) { - case 5: - assert(offset == 0); - rc_bit(probs[symbol], , - rep0 += 1U, - SEQ_DIST_MODEL); - ++offset; - --limit; - case 4: - rc_bit(probs[symbol], , - rep0 += 1U << offset, - SEQ_DIST_MODEL); - ++offset; - --limit; - case 3: - rc_bit(probs[symbol], , - rep0 += 1U << offset, - SEQ_DIST_MODEL); - ++offset; - --limit; - case 2: - rc_bit(probs[symbol], , - rep0 += 1U << offset, - SEQ_DIST_MODEL); - ++offset; - --limit; - case 1: - // We need "symbol" only for - // indexing the probability - // array, thus we can use - // rc_bit_last() here to omit - // the unneeded updating of - // "symbol". - rc_bit_last(probs[symbol], , - rep0 += 1U << offset, - SEQ_DIST_MODEL); - } -#endif } else { - // The distance is >= 128. Decode the - // lower bits without probabilities - // except the lowest four bits. assert(symbol >= 14); assert(limit >= 6); limit -= ALIGN_BITS; assert(limit >= 2); case SEQ_DIRECT: - // Not worth manual unrolling - do { - rc_direct(rep0, SEQ_DIRECT); - } while (--limit > 0); + rc_direct_safe(rep0, limit, + SEQ_DIRECT); - // Decode the lowest four bits using - // probabilities. rep0 <<= ALIGN_BITS; - symbol = 1; -#ifdef HAVE_SMALL - offset = 0; + symbol = 0; + offset = 1; case SEQ_ALIGN: do { - rc_bit(coder->pos_align[ - symbol], , - rep0 += 1U << offset, + rc_bit_last_safe( + coder->pos_align[ + offset + + symbol], + , + symbol += offset, SEQ_ALIGN); - } while (++offset < ALIGN_BITS); -#else - case SEQ_ALIGN0: - rc_bit(coder->pos_align[symbol], , - rep0 += 1, SEQ_ALIGN0); - case SEQ_ALIGN1: - rc_bit(coder->pos_align[symbol], , - rep0 += 2, SEQ_ALIGN1); - case SEQ_ALIGN2: - rc_bit(coder->pos_align[symbol], , - rep0 += 4, SEQ_ALIGN2); - case SEQ_ALIGN3: - // Like in SEQ_DIST_MODEL, we don't - // need "symbol" for anything else - // than indexing the probability array. - rc_bit_last(coder->pos_align[symbol], , - rep0 += 8, SEQ_ALIGN3); -#endif + offset <<= 1; + } while (offset < ALIGN_SIZE); + + rep0 += symbol; if (rep0 == UINT32_MAX) { // End of payload marker was - // found. It must not be - // present if uncompressed - // size is known. - if (coder->uncompressed_size - != LZMA_VLI_UNKNOWN) { + // found. It may only be + // present if + // - uncompressed size is + // unknown or + // - after known uncompressed + // size amount of bytes has + // been decompressed and + // caller has indicated + // that EOPM might be used + // (it's not allowed in + // LZMA2). +#ifndef HAVE_SMALL +eopm: +#endif + if (!eopm_is_valid) { ret = LZMA_DATA_ERROR; goto out; } @@ -670,43 +864,39 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, case SEQ_EOPM: // LZMA1 stream with // end-of-payload marker. - rc_normalize(SEQ_EOPM); - ret = LZMA_STREAM_END; + rc_normalize_safe(SEQ_EOPM); + ret = rc_is_finished(rc) + ? LZMA_STREAM_END + : LZMA_DATA_ERROR; goto out; } } } - // Validate the distance we just decoded. if (unlikely(!dict_is_distance_valid(&dict, rep0))) { ret = LZMA_DATA_ERROR; goto out; } } else { + ///////////////////// + // Repeated match. // + ///////////////////// + rc_update_1(coder->is_rep[state]); - // Repeated match - // - // The match distance is a value that we have had - // earlier. The latest four match distances are - // available as rep0, rep1, rep2 and rep3. We will - // now decode which of them is the new distance. - // - // There cannot be a match if we haven't produced - // any output, so check that first. if (unlikely(!dict_is_distance_valid(&dict, 0))) { ret = LZMA_DATA_ERROR; goto out; } case SEQ_IS_REP0: - rc_if_0(coder->is_rep0[state], SEQ_IS_REP0) { + rc_if_0_safe(coder->is_rep0[state], SEQ_IS_REP0) { rc_update_0(coder->is_rep0[state]); - // The distance is rep0. case SEQ_IS_REP0_LONG: - rc_if_0(coder->is_rep0_long[state][pos_state], + rc_if_0_safe(coder->is_rep0_long + [state][pos_state], SEQ_IS_REP0_LONG) { rc_update_0(coder->is_rep0_long[ state][pos_state]); @@ -714,8 +904,9 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, update_short_rep(state); case SEQ_SHORTREP: - if (unlikely(dict_put(&dict, dict_get( - &dict, rep0)))) { + if (dict_put_safe(&dict, + dict_get(&dict, + rep0))) { coder->sequence = SEQ_SHORTREP; goto out; } @@ -723,8 +914,6 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, continue; } - // Repeating more than one byte at - // distance of rep0. rc_update_1(coder->is_rep0_long[ state][pos_state]); @@ -732,11 +921,7 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, rc_update_1(coder->is_rep0[state]); case SEQ_IS_REP1: - // The distance is rep1, rep2 or rep3. Once - // we find out which one of these three, it - // is stored to rep0 and rep1, rep2 and rep3 - // are updated accordingly. - rc_if_0(coder->is_rep1[state], SEQ_IS_REP1) { + rc_if_0_safe(coder->is_rep1[state], SEQ_IS_REP1) { rc_update_0(coder->is_rep1[state]); const uint32_t distance = rep1; @@ -746,7 +931,7 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, } else { rc_update_1(coder->is_rep1[state]); case SEQ_IS_REP2: - rc_if_0(coder->is_rep2[state], + rc_if_0_safe(coder->is_rep2[state], SEQ_IS_REP2) { rc_update_0(coder->is_rep2[ state]); @@ -771,7 +956,6 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, update_long_rep(state); - // Decode the length of the repeated match. len_decode(len, coder->rep_len_decoder, pos_state, SEQ_REP_LEN); } @@ -780,22 +964,16 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, // Repeat from history buffer. // ///////////////////////////////// - // The length is always between these limits. There is no way - // to trigger the algorithm to set len outside this range. assert(len >= MATCH_LEN_MIN); assert(len <= MATCH_LEN_MAX); case SEQ_COPY: - // Repeat len bytes from distance of rep0. if (unlikely(dict_repeat(&dict, rep0, &len))) { coder->sequence = SEQ_COPY; goto out; } } - rc_normalize(SEQ_NORMALIZE); - coder->sequence = SEQ_IS_MATCH; - out: // Save state @@ -822,36 +1000,34 @@ lzma_decode(void *coder_ptr, lzma_dict *restrict dictptr, if (coder->uncompressed_size != LZMA_VLI_UNKNOWN) { coder->uncompressed_size -= dict.pos - dict_start; - // Since there cannot be end of payload marker if the - // uncompressed size was known, we check here if we - // finished decoding. + // If we have gotten all the output but the decoder wants + // to write more output, the file is corrupt. There are + // three SEQ values where output is produced. if (coder->uncompressed_size == 0 && ret == LZMA_OK - && coder->sequence != SEQ_NORMALIZE) - ret = coder->sequence == SEQ_IS_MATCH - ? LZMA_STREAM_END : LZMA_DATA_ERROR; + && (coder->sequence == SEQ_LITERAL_WRITE + || coder->sequence == SEQ_SHORTREP + || coder->sequence == SEQ_COPY)) + ret = LZMA_DATA_ERROR; } - // We can do an additional check in the range decoder to catch some - // corrupted files. if (ret == LZMA_STREAM_END) { - if (!rc_is_finished(coder->rc)) - ret = LZMA_DATA_ERROR; - // Reset the range decoder so that it is ready to reinitialize // for a new LZMA2 chunk. rc_reset(coder->rc); + coder->sequence = SEQ_IS_MATCH; } return ret; } - static void -lzma_decoder_uncompressed(void *coder_ptr, lzma_vli uncompressed_size) +lzma_decoder_uncompressed(void *coder_ptr, lzma_vli uncompressed_size, + bool allow_eopm) { lzma_lzma1_decoder *coder = coder_ptr; coder->uncompressed_size = uncompressed_size; + coder->allow_eopm = allow_eopm; } @@ -871,7 +1047,7 @@ lzma_decoder_reset(void *coder_ptr, const void *opt) literal_init(coder->literal, options->lc, options->lp); coder->literal_context_bits = options->lc; - coder->literal_pos_mask = (1U << options->lp) - 1; + coder->literal_mask = literal_mask_calc(options->lc, options->lp); // State coder->state = STATE_LIT_LIT; @@ -940,7 +1116,7 @@ lzma_decoder_reset(void *coder_ptr, const void *opt) extern lzma_ret lzma_lzma_decoder_create(lzma_lz_decoder *lz, const lzma_allocator *allocator, - const void *opt, lzma_lz_options *lz_options) + const lzma_options_lzma *options, lzma_lz_options *lz_options) { if (lz->coder == NULL) { lz->coder = lzma_alloc(sizeof(lzma_lzma1_decoder), allocator); @@ -954,7 +1130,6 @@ lzma_lzma_decoder_create(lzma_lz_decoder *lz, const lzma_allocator *allocator, // All dictionary sizes are OK here. LZ decoder will take care of // the special cases. - const lzma_options_lzma *options = opt; lz_options->dict_size = options->dict_size; lz_options->preset_dict = options->preset_dict; lz_options->preset_dict_size = options->preset_dict_size; @@ -968,16 +1143,40 @@ lzma_lzma_decoder_create(lzma_lz_decoder *lz, const lzma_allocator *allocator, /// the LZ initialization). static lzma_ret lzma_decoder_init(lzma_lz_decoder *lz, const lzma_allocator *allocator, - const void *options, lzma_lz_options *lz_options) + lzma_vli id, const void *options, lzma_lz_options *lz_options) { if (!is_lclppb_valid(options)) return LZMA_PROG_ERROR; + lzma_vli uncomp_size = LZMA_VLI_UNKNOWN; + bool allow_eopm = true; + + if (id == LZMA_FILTER_LZMA1EXT) { + const lzma_options_lzma *opt = options; + + // Only one flag is supported. + if (opt->ext_flags & ~LZMA_LZMA1EXT_ALLOW_EOPM) + return LZMA_OPTIONS_ERROR; + + // FIXME? Using lzma_vli instead of uint64_t is weird because + // this has nothing to do with .xz headers and variable-length + // integer encoding. On the other hand, using LZMA_VLI_UNKNOWN + // instead of UINT64_MAX is clearer when unknown size is + // meant. A problem with using lzma_vli is that now we + // allow > LZMA_VLI_MAX which is fine in this file but + // it's still confusing. Note that alone_decoder.c also + // allows > LZMA_VLI_MAX when setting uncompressed size. + uncomp_size = opt->ext_size_low + + ((uint64_t)(opt->ext_size_high) << 32); + allow_eopm = (opt->ext_flags & LZMA_LZMA1EXT_ALLOW_EOPM) != 0 + || uncomp_size == LZMA_VLI_UNKNOWN; + } + return_if_error(lzma_lzma_decoder_create( lz, allocator, options, lz_options)); lzma_decoder_reset(lz->coder, options); - lzma_decoder_uncompressed(lz->coder, LZMA_VLI_UNKNOWN); + lzma_decoder_uncompressed(lz->coder, uncomp_size, allow_eopm); return LZMA_OK; } diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.h b/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.h index fa8ecb23e43..9730f56fc26 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.h +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_decoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZMA_DECODER_H @@ -42,7 +41,7 @@ extern bool lzma_lzma_lclppb_decode( /// LZMA2 decoders. extern lzma_ret lzma_lzma_decoder_create( lzma_lz_decoder *lz, const lzma_allocator *allocator, - const void *opt, lzma_lz_options *lz_options); + const lzma_options_lzma *opt, lzma_lz_options *lz_options); /// Gets memory usage without validating lc/lp/pb. This is used by LZMA2 /// decoder, because raw LZMA2 decoding doesn't need lc/lp/pb. diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.c b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.c index 07d2b87bc65..543ca321c3c 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.c +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_encoder.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "lzma2_encoder.h" @@ -49,24 +48,24 @@ literal(lzma_lzma1_encoder *coder, lzma_mf *mf, uint32_t position) const uint8_t cur_byte = mf->buffer[ mf->read_pos - mf->read_ahead]; probability *subcoder = literal_subcoder(coder->literal, - coder->literal_context_bits, coder->literal_pos_mask, + coder->literal_context_bits, coder->literal_mask, position, mf->buffer[mf->read_pos - mf->read_ahead - 1]); if (is_literal_state(coder->state)) { // Previous LZMA-symbol was a literal. Encode a normal // literal without a match byte. + update_literal_normal(coder->state); rc_bittree(&coder->rc, subcoder, 8, cur_byte); } else { // Previous LZMA-symbol was a match. Use the last byte of // the match as a "match byte". That is, compare the bits // of the current literal and the match byte. + update_literal_matched(coder->state); const uint8_t match_byte = mf->buffer[ mf->read_pos - coder->reps[0] - 1 - mf->read_ahead]; literal_matched(&coder->rc, subcoder, match_byte, cur_byte); } - - update_literal(coder->state); } @@ -268,6 +267,7 @@ static bool encode_init(lzma_lzma1_encoder *coder, lzma_mf *mf) { assert(mf_position(mf) == 0); + assert(coder->uncomp_size == 0); if (mf->read_pos == mf->read_limit) { if (mf->action == LZMA_RUN) @@ -282,7 +282,8 @@ encode_init(lzma_lzma1_encoder *coder, lzma_mf *mf) mf_skip(mf, 1); mf->read_ahead = 0; rc_bit(&coder->rc, &coder->is_match[0][0], 0); - rc_bittree(&coder->rc, coder->literal[0], 8, mf->buffer[0]); + rc_bittree(&coder->rc, coder->literal + 0, 8, mf->buffer[0]); + ++coder->uncomp_size; } // Initialization is done (except if empty file). @@ -317,21 +318,28 @@ lzma_lzma_encode(lzma_lzma1_encoder *restrict coder, lzma_mf *restrict mf, if (!coder->is_initialized && !encode_init(coder, mf)) return LZMA_OK; - // Get the lowest bits of the uncompressed offset from the LZ layer. - uint32_t position = mf_position(mf); + // Encode pending output bytes from the range encoder. + // At the start of the stream, encode_init() encodes one literal. + // Later there can be pending output only with LZMA1 because LZMA2 + // ensures that there is always enough output space. Thus when using + // LZMA2, rc_encode() calls in this function will always return false. + if (rc_encode(&coder->rc, out, out_pos, out_size)) { + // We don't get here with LZMA2. + assert(limit == UINT32_MAX); + return LZMA_OK; + } - while (true) { - // Encode pending bits, if any. Calling this before encoding - // the next symbol is needed only with plain LZMA, since - // LZMA2 always provides big enough buffer to flush - // everything out from the range encoder. For the same reason, - // rc_encode() never returns true when this function is used - // as part of LZMA2 encoder. - if (rc_encode(&coder->rc, out, out_pos, out_size)) { - assert(limit == UINT32_MAX); - return LZMA_OK; - } + // If the range encoder was flushed in an earlier call to this + // function but there wasn't enough output buffer space, those + // bytes would have now been encoded by the above rc_encode() call + // and the stream has now been finished. This can only happen with + // LZMA1 as LZMA2 always provides enough output buffer space. + if (coder->is_flushed) { + assert(limit == UINT32_MAX); + return LZMA_STREAM_END; + } + while (true) { // With LZMA2 we need to take care that compressed size of // a chunk doesn't get too big. // FIXME? Check if this could be improved. @@ -365,37 +373,64 @@ lzma_lzma_encode(lzma_lzma1_encoder *restrict coder, lzma_mf *restrict mf, if (coder->fast_mode) lzma_lzma_optimum_fast(coder, mf, &back, &len); else - lzma_lzma_optimum_normal( - coder, mf, &back, &len, position); - - encode_symbol(coder, mf, back, len, position); - - position += len; - } - - if (!coder->is_flushed) { - coder->is_flushed = true; + lzma_lzma_optimum_normal(coder, mf, &back, &len, + (uint32_t)(coder->uncomp_size)); + + encode_symbol(coder, mf, back, len, + (uint32_t)(coder->uncomp_size)); + + // If output size limiting is active (out_limit != 0), check + // if encoding this LZMA symbol would make the output size + // exceed the specified limit. + if (coder->out_limit != 0 && rc_encode_dummy( + &coder->rc, coder->out_limit)) { + // The most recent LZMA symbol would make the output + // too big. Throw it away. + rc_forget(&coder->rc); + + // FIXME: Tell the LZ layer to not read more input as + // it would be waste of time. This doesn't matter if + // output-size-limited encoding is done with a single + // call though. - // We don't support encoding plain LZMA streams without EOPM, - // and LZMA2 doesn't use EOPM at LZMA level. - if (limit == UINT32_MAX) - encode_eopm(coder, position); + break; + } - // Flush the remaining bytes from the range encoder. - rc_flush(&coder->rc); + // This symbol will be encoded so update the uncompressed size. + coder->uncomp_size += len; - // Copy the remaining bytes to the output buffer. If there - // isn't enough output space, we will copy out the remaining - // bytes on the next call to this function by using - // the rc_encode() call in the encoding loop above. + // Encode the LZMA symbol. if (rc_encode(&coder->rc, out, out_pos, out_size)) { + // Once again, this can only happen with LZMA1. assert(limit == UINT32_MAX); return LZMA_OK; } } - // Make it ready for the next LZMA2 chunk. - coder->is_flushed = false; + // Make the uncompressed size available to the application. + if (coder->uncomp_size_ptr != NULL) + *coder->uncomp_size_ptr = coder->uncomp_size; + + // LZMA2 doesn't use EOPM at LZMA level. + // + // Plain LZMA streams without EOPM aren't supported except when + // output size limiting is enabled. + if (coder->use_eopm) + encode_eopm(coder, (uint32_t)(coder->uncomp_size)); + + // Flush the remaining bytes from the range encoder. + rc_flush(&coder->rc); + + // Copy the remaining bytes to the output buffer. If there + // isn't enough output space, we will copy out the remaining + // bytes on the next call to this function. + if (rc_encode(&coder->rc, out, out_pos, out_size)) { + // This cannot happen with LZMA2. + assert(limit == UINT32_MAX); + + coder->is_flushed = true; + return LZMA_OK; + } return LZMA_STREAM_END; } @@ -414,6 +449,23 @@ lzma_encode(void *coder, lzma_mf *restrict mf, } +static lzma_ret +lzma_lzma_set_out_limit( + void *coder_ptr, uint64_t *uncomp_size, uint64_t out_limit) +{ + // Minimum output size is 5 bytes but that cannot hold any output + // so we use 6 bytes. + if (out_limit < 6) + return LZMA_BUF_ERROR; + + lzma_lzma1_encoder *coder = coder_ptr; + coder->out_limit = out_limit; + coder->uncomp_size_ptr = uncomp_size; + coder->use_eopm = false; + return LZMA_OK; +} + + //////////////////// // Initialization // //////////////////// @@ -440,7 +492,8 @@ set_lz_options(lzma_lz_options *lz_options, const lzma_options_lzma *options) lz_options->dict_size = options->dict_size; lz_options->after_size = LOOP_INPUT_MAX; lz_options->match_len_max = MATCH_LEN_MAX; - lz_options->nice_len = options->nice_len; + lz_options->nice_len = my_max(mf_get_hash_bytes(options->mf), + options->nice_len); lz_options->match_finder = options->mf; lz_options->depth = options->depth; lz_options->preset_dict = options->preset_dict; @@ -481,7 +534,7 @@ lzma_lzma_encoder_reset(lzma_lzma1_encoder *coder, coder->pos_mask = (1U << options->pb) - 1; coder->literal_context_bits = options->lc; - coder->literal_pos_mask = (1U << options->lp) - 1; + coder->literal_mask = literal_mask_calc(options->lc, options->lp); // Range coder rc_reset(&coder->rc); @@ -546,10 +599,13 @@ lzma_lzma_encoder_reset(lzma_lzma1_encoder *coder, extern lzma_ret -lzma_lzma_encoder_create(void **coder_ptr, - const lzma_allocator *allocator, - const lzma_options_lzma *options, lzma_lz_options *lz_options) +lzma_lzma_encoder_create(void **coder_ptr, const lzma_allocator *allocator, + lzma_vli id, const lzma_options_lzma *options, + lzma_lz_options *lz_options) { + assert(id == LZMA_FILTER_LZMA1 || id == LZMA_FILTER_LZMA1EXT + || id == LZMA_FILTER_LZMA2); + // Allocate lzma_lzma1_encoder if it wasn't already allocated. if (*coder_ptr == NULL) { *coder_ptr = lzma_alloc(sizeof(lzma_lzma1_encoder), allocator); @@ -559,10 +615,9 @@ lzma_lzma_encoder_create(void **coder_ptr, lzma_lzma1_encoder *coder = *coder_ptr; - // Set compression mode. We haven't validates the options yet, - // but it's OK here, since nothing bad happens with invalid - // options in the code below, and they will get rejected by - // lzma_lzma_encoder_reset() call at the end of this function. + // Set compression mode. Note that we haven't validated the options + // yet. Invalid options will get rejected by lzma_lzma_encoder_reset() + // call at the end of this function. switch (options->mode) { case LZMA_MODE_FAST: coder->fast_mode = true; @@ -573,6 +628,18 @@ lzma_lzma_encoder_create(void **coder_ptr, // Set dist_table_size. // Round the dictionary size up to next 2^n. + // + // Currently the maximum encoder dictionary size + // is 1.5 GiB due to lz_encoder.c and here we need + // to be below 2 GiB to make the rounded up value + // fit in an uint32_t and avoid an infinite while-loop + // (and undefined behavior due to a too large shift). + // So do the same check as in LZ encoder, + // limiting to 1.5 GiB. + if (options->dict_size > (UINT32_C(1) << 30) + + (UINT32_C(1) << 29)) + return LZMA_OPTIONS_ERROR; + uint32_t log_size = 0; while ((UINT32_C(1) << log_size) < options->dict_size) ++log_size; @@ -580,10 +647,14 @@ lzma_lzma_encoder_create(void **coder_ptr, coder->dist_table_size = log_size * 2; // Length encoders' price table size + const uint32_t nice_len = my_max( + mf_get_hash_bytes(options->mf), + options->nice_len); + coder->match_len_encoder.table_size - = options->nice_len + 1 - MATCH_LEN_MIN; + = nice_len + 1 - MATCH_LEN_MIN; coder->rep_len_encoder.table_size - = options->nice_len + 1 - MATCH_LEN_MIN; + = nice_len + 1 - MATCH_LEN_MIN; break; } @@ -598,6 +669,37 @@ lzma_lzma_encoder_create(void **coder_ptr, coder->is_initialized = options->preset_dict != NULL && options->preset_dict_size > 0; coder->is_flushed = false; + coder->uncomp_size = 0; + coder->uncomp_size_ptr = NULL; + + // Output size limiting is disabled by default. + coder->out_limit = 0; + + // Determine if end marker is wanted: + // - It is never used with LZMA2. + // - It is always used with LZMA_FILTER_LZMA1 (unless + // lzma_lzma_set_out_limit() is called later). + // - LZMA_FILTER_LZMA1EXT has a flag for it in the options. + coder->use_eopm = (id == LZMA_FILTER_LZMA1); + if (id == LZMA_FILTER_LZMA1EXT) { + // Check if unsupported flags are present. + if (options->ext_flags & ~LZMA_LZMA1EXT_ALLOW_EOPM) + return LZMA_OPTIONS_ERROR; + + coder->use_eopm = (options->ext_flags + & LZMA_LZMA1EXT_ALLOW_EOPM) != 0; + + // TODO? As long as there are no filters that change the size + // of the data, it is enough to look at lzma_stream.total_in + // after encoding has been finished to know the uncompressed + // size of the LZMA1 stream. But in the future there could be + // filters that change the size of the data and then total_in + // doesn't work as the LZMA1 stream size might be different + // due to another filter in the chain. The problem is simple + // to solve: Add another flag to ext_flags and then set + // coder->uncomp_size_ptr to the address stored in + // lzma_options_lzma.reserved_ptr2 (or _ptr1). + } set_lz_options(lz_options, options); @@ -607,11 +709,15 @@ lzma_lzma_encoder_create(void **coder_ptr, static lzma_ret lzma_encoder_init(lzma_lz_encoder *lz, const lzma_allocator *allocator, - const void *options, lzma_lz_options *lz_options) + lzma_vli id, const void *options, lzma_lz_options *lz_options) { + if (options == NULL) + return LZMA_PROG_ERROR; + lz->code = &lzma_encode; + lz->set_out_limit = &lzma_lzma_set_out_limit; return lzma_lzma_encoder_create( - &lz->coder, allocator, options, lz_options); + &lz->coder, allocator, id, options, lz_options); } @@ -658,6 +764,9 @@ lzma_lzma_lclppb_encode(const lzma_options_lzma *options, uint8_t *byte) extern lzma_ret lzma_lzma_props_encode(const void *options, uint8_t *out) { + if (options == NULL) + return LZMA_PROG_ERROR; + const lzma_options_lzma *const opt = options; if (lzma_lzma_lclppb_encode(opt, out)) diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.h b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.h index 6cfdf228bf5..e8ae8079306 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.h +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_encoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZMA_ENCODER_H @@ -40,7 +39,8 @@ extern bool lzma_lzma_lclppb_encode( /// Initializes raw LZMA encoder; this is used by LZMA2. extern lzma_ret lzma_lzma_encoder_create( void **coder_ptr, const lzma_allocator *allocator, - const lzma_options_lzma *options, lzma_lz_options *lz_options); + lzma_vli id, const lzma_options_lzma *options, + lzma_lz_options *lz_options); /// Resets an already initialized LZMA encoder; this is used by LZMA2. diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_fast.c b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_fast.c index 6c53d2bd008..0f063d5be7a 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_fast.c +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_fast.c @@ -1,12 +1,11 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_encoder_optimum_fast.c // // Author: Igor Pavlov // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "lzma_encoder_private.h" diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_normal.c b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_normal.c index 101c8d47900..a6c0398f3af 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_normal.c +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_optimum_normal.c @@ -1,12 +1,11 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_encoder_optimum_normal.c // // Author: Igor Pavlov // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "lzma_encoder_private.h" @@ -24,7 +23,7 @@ get_literal_price(const lzma_lzma1_encoder *const coder, const uint32_t pos, uint32_t match_byte, uint32_t symbol) { const probability *const subcoder = literal_subcoder(coder->literal, - coder->literal_context_bits, coder->literal_pos_mask, + coder->literal_context_bits, coder->literal_mask, pos, prev_byte); uint32_t price = 0; diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_presets.c b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_presets.c index 711df025529..e53483f9958 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_presets.c +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_presets.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_encoder_presets.c @@ -6,9 +8,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "common.h" diff --git a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_private.h b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_private.h index 2e34aace16e..eeea5e9c128 100644 --- a/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_private.h +++ b/Utilities/cmliblzma/liblzma/lzma/lzma_encoder_private.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file lzma_encoder_private.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_LZMA_ENCODER_PRIVATE_H @@ -72,6 +71,18 @@ struct lzma_lzma1_encoder_s { /// Range encoder lzma_range_encoder rc; + /// Uncompressed size (doesn't include possible preset dictionary) + uint64_t uncomp_size; + + /// If non-zero, produce at most this much output. + /// Some input may then be missing from the output. + uint64_t out_limit; + + /// If the above out_limit is non-zero, *uncomp_size_ptr is set to + /// the amount of uncompressed data that we were able to fit + /// in the output buffer. + uint64_t *uncomp_size_ptr; + /// State lzma_lzma_state state; @@ -99,12 +110,15 @@ struct lzma_lzma1_encoder_s { /// have been written to the output buffer yet. bool is_flushed; + /// True if end of payload marker will be written. + bool use_eopm; + uint32_t pos_mask; ///< (1 << pos_bits) - 1 uint32_t literal_context_bits; - uint32_t literal_pos_mask; + uint32_t literal_mask; // These are the same as in lzma_decoder.c. See comments there. - probability literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; + probability literal[LITERAL_CODERS_MAX * LITERAL_CODER_SIZE]; probability is_match[STATES][POS_STATES_MAX]; probability is_rep[STATES]; probability is_rep0[STATES]; diff --git a/Utilities/cmliblzma/liblzma/rangecoder/price.h b/Utilities/cmliblzma/liblzma/rangecoder/price.h index 8ae02ca7474..cce6bdae5f9 100644 --- a/Utilities/cmliblzma/liblzma/rangecoder/price.h +++ b/Utilities/cmliblzma/liblzma/rangecoder/price.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file price.h @@ -5,9 +7,6 @@ // // Author: Igor Pavlov // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_PRICE_H @@ -22,6 +21,7 @@ /// Lookup table for the inline functions defined in this file. +lzma_attr_visibility_hidden extern const uint8_t lzma_rc_prices[RC_PRICE_TABLE_SIZE]; diff --git a/Utilities/cmliblzma/liblzma/rangecoder/price_table.c b/Utilities/cmliblzma/liblzma/rangecoder/price_table.c index ac64bf62c76..c33433f718c 100644 --- a/Utilities/cmliblzma/liblzma/rangecoder/price_table.c +++ b/Utilities/cmliblzma/liblzma/rangecoder/price_table.c @@ -1,4 +1,6 @@ -/* This file has been automatically generated by price_tablegen.c. */ +// SPDX-License-Identifier: 0BSD + +// This file has been generated by price_tablegen.c. #include "range_encoder.h" diff --git a/Utilities/cmliblzma/liblzma/rangecoder/price_tablegen.c b/Utilities/cmliblzma/liblzma/rangecoder/price_tablegen.c index bf08ce39d7e..4b6ca37efad 100644 --- a/Utilities/cmliblzma/liblzma/rangecoder/price_tablegen.c +++ b/Utilities/cmliblzma/liblzma/rangecoder/price_tablegen.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file price_tablegen.c @@ -8,13 +10,15 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include #include + +// Make it compile without common.h. +#define BUILDING_PRICE_TABLEGEN +#define lzma_attr_visibility_hidden + #include "range_common.h" #include "price.h" @@ -54,11 +58,13 @@ init_price_table(void) static void print_price_table(void) { - printf("/* This file has been automatically generated by " - "price_tablegen.c. */\n\n" - "#include \"range_encoder.h\"\n\n" - "const uint8_t lzma_rc_prices[" - "RC_PRICE_TABLE_SIZE] = {"); + // Split the SPDX string so that it won't accidentally match + // when tools search for the string. + printf("// SPDX" "-License-Identifier" ": 0BSD\n\n" + "// This file has been generated by price_tablegen.c.\n\n" + "#include \"range_encoder.h\"\n\n" + "const uint8_t lzma_rc_prices[" + "RC_PRICE_TABLE_SIZE] = {"); const size_t array_size = sizeof(lzma_rc_prices) / sizeof(lzma_rc_prices[0]); diff --git a/Utilities/cmliblzma/liblzma/rangecoder/range_common.h b/Utilities/cmliblzma/liblzma/rangecoder/range_common.h index 2c74dc1537c..ac4dbe196f5 100644 --- a/Utilities/cmliblzma/liblzma/rangecoder/range_common.h +++ b/Utilities/cmliblzma/liblzma/rangecoder/range_common.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file range_common.h @@ -6,15 +8,15 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_RANGE_COMMON_H #define LZMA_RANGE_COMMON_H -#include "common.h" +// Skip common.h if building price_tablegen.c. +#ifndef BUILDING_PRICE_TABLEGEN +# include "common.h" +#endif /////////////// @@ -66,6 +68,10 @@ /// /// I will be sticking to uint16_t unless some specific architectures /// are *much* faster (20-50 %) with uint32_t. +/// +/// Update in 2024: The branchless C and x86-64 assembly was written so that +/// probability is assumed to be uint16_t. (In contrast, LZMA SDK 23.01 +/// assembly supports both types.) typedef uint16_t probability; #endif diff --git a/Utilities/cmliblzma/liblzma/rangecoder/range_decoder.h b/Utilities/cmliblzma/liblzma/rangecoder/range_decoder.h index e0b051fac2d..a8aca9077c1 100644 --- a/Utilities/cmliblzma/liblzma/rangecoder/range_decoder.h +++ b/Utilities/cmliblzma/liblzma/rangecoder/range_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file range_decoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_RANGE_DECODER_H @@ -17,6 +16,55 @@ #include "range_common.h" +// Choose the range decoder variants to use using a bitmask. +// If no bits are set, only the basic version is used. +// If more than one version is selected for the same feature, +// the last one on the list below is used. +// +// Bitwise-or of the following enable branchless C versions: +// 0x01 normal bittrees +// 0x02 fixed-sized reverse bittrees +// 0x04 variable-sized reverse bittrees (not faster) +// 0x08 matched literal (not faster) +// +// GCC & Clang compatible x86-64 inline assembly: +// 0x010 normal bittrees +// 0x020 fixed-sized reverse bittrees +// 0x040 variable-sized reverse bittrees +// 0x080 matched literal +// 0x100 direct bits +// +// The default can be overridden at build time by defining +// LZMA_RANGE_DECODER_CONFIG to the desired mask. +// +// 2024-02-22: Feedback from benchmarks: +// - Brancless C (0x003) can be better than basic on x86-64 but often it's +// slightly worse on other archs. Since asm is much better on x86-64, +// branchless C is not used at all. +// - With x86-64 asm, there are slight differences between GCC and Clang +// and different processors. Overall 0x1F0 seems to be the best choice. +#ifndef LZMA_RANGE_DECODER_CONFIG +# if defined(__x86_64__) && !defined(__ILP32__) \ + && !defined(__NVCOMPILER) \ + && (defined(__GNUC__) || defined(__clang__)) +# define LZMA_RANGE_DECODER_CONFIG 0x1F0 +# else +# define LZMA_RANGE_DECODER_CONFIG 0 +# endif +#endif + + +// Negative RC_BIT_MODEL_TOTAL but the lowest RC_MOVE_BITS are flipped. +// This is useful for updating probability variables in branchless decoding: +// +// uint32_t decoded_bit = ...; +// probability tmp = RC_BIT_MODEL_OFFSET; +// tmp &= decoded_bit - 1; +// prob -= (prob + tmp) >> RC_MOVE_BITS; +#define RC_BIT_MODEL_OFFSET \ + ((UINT32_C(1) << RC_MOVE_BITS) - 1 - RC_BIT_MODEL_TOTAL) + + typedef struct { uint32_t range; uint32_t code; @@ -50,18 +98,28 @@ rc_read_init(lzma_range_decoder *rc, const uint8_t *restrict in, /// Makes local copies of range decoder and *in_pos variables. Doing this /// improves speed significantly. The range decoder macros expect also -/// variables `in' and `in_size' to be defined. -#define rc_to_local(range_decoder, in_pos) \ +/// variables 'in' and 'in_size' to be defined. +#define rc_to_local(range_decoder, in_pos, fast_mode_in_required) \ lzma_range_decoder rc = range_decoder; \ - size_t rc_in_pos = (in_pos); \ + const uint8_t *rc_in_ptr = in + (in_pos); \ + const uint8_t *rc_in_end = in + in_size; \ + const uint8_t *rc_in_fast_end \ + = (rc_in_end - rc_in_ptr) <= (fast_mode_in_required) \ + ? rc_in_ptr \ + : rc_in_end - (fast_mode_in_required); \ + (void)rc_in_fast_end; /* Silence a warning with HAVE_SMALL. */ \ uint32_t rc_bound +/// Evaluates to true if there is enough input remaining to use fast mode. +#define rc_is_fast_allowed() (rc_in_ptr < rc_in_fast_end) + + /// Stores the local copes back to the range decoder structure. #define rc_from_local(range_decoder, in_pos) \ do { \ range_decoder = rc; \ - in_pos = rc_in_pos; \ + in_pos = (size_t)(rc_in_ptr - in); \ } while (0) @@ -81,18 +139,30 @@ do { \ ((range_decoder).code == 0) -/// Read the next input byte if needed. If more input is needed but there is +// Read the next input byte if needed. +#define rc_normalize() \ +do { \ + if (rc.range < RC_TOP_VALUE) { \ + rc.range <<= RC_SHIFT_BITS; \ + rc.code = (rc.code << RC_SHIFT_BITS) | *rc_in_ptr++; \ + } \ +} while (0) + + +/// If more input is needed but there is /// no more input available, "goto out" is used to jump out of the main -/// decoder loop. -#define rc_normalize(seq) \ +/// decoder loop. The "_safe" macros are used in the Resumable decoder +/// mode in order to save the sequence to continue decoding from that +/// point later. +#define rc_normalize_safe(seq) \ do { \ if (rc.range < RC_TOP_VALUE) { \ - if (unlikely(rc_in_pos == in_size)) { \ + if (rc_in_ptr == rc_in_end) { \ coder->sequence = seq; \ goto out; \ } \ rc.range <<= RC_SHIFT_BITS; \ - rc.code = (rc.code << RC_SHIFT_BITS) | in[rc_in_pos++]; \ + rc.code = (rc.code << RC_SHIFT_BITS) | *rc_in_ptr++; \ } \ } while (0) @@ -100,7 +170,7 @@ do { \ /// Start decoding a bit. This must be used together with rc_update_0() /// and rc_update_1(): /// -/// rc_if_0(prob, seq) { +/// rc_if_0(prob) { /// rc_update_0(prob); /// // Do something /// } else { @@ -108,18 +178,28 @@ do { \ /// // Do something else /// } /// -#define rc_if_0(prob, seq) \ - rc_normalize(seq); \ +#define rc_if_0(prob) \ + rc_normalize(); \ + rc_bound = (rc.range >> RC_BIT_MODEL_TOTAL_BITS) * (prob); \ + if (rc.code < rc_bound) + + +#define rc_if_0_safe(prob, seq) \ + rc_normalize_safe(seq); \ rc_bound = (rc.range >> RC_BIT_MODEL_TOTAL_BITS) * (prob); \ if (rc.code < rc_bound) /// Update the range decoder state and the used probability variable to /// match a decoded bit of 0. +/// +/// The x86-64 assembly uses the commented method but it seems that, +/// at least on x86-64, the first version is slightly faster as C code. #define rc_update_0(prob) \ do { \ rc.range = rc_bound; \ prob += (RC_BIT_MODEL_TOTAL - (prob)) >> RC_MOVE_BITS; \ + /* prob -= ((prob) + RC_BIT_MODEL_OFFSET) >> RC_MOVE_BITS; */ \ } while (0) @@ -137,9 +217,21 @@ do { \ /// This macro is used as the last step in bittree reverse decoders since /// those don't use "symbol" for anything else than indexing the probability /// arrays. -#define rc_bit_last(prob, action0, action1, seq) \ +#define rc_bit_last(prob, action0, action1) \ +do { \ + rc_if_0(prob) { \ + rc_update_0(prob); \ + action0; \ + } else { \ + rc_update_1(prob); \ + action1; \ + } \ +} while (0) + + +#define rc_bit_last_safe(prob, action0, action1, seq) \ do { \ - rc_if_0(prob, seq) { \ + rc_if_0_safe(prob, seq) { \ rc_update_0(prob); \ action0; \ } else { \ @@ -151,35 +243,724 @@ do { \ /// Decodes one bit, updates "symbol", and runs action0 or action1 depending /// on the decoded bit. -#define rc_bit(prob, action0, action1, seq) \ +#define rc_bit(prob, action0, action1) \ rc_bit_last(prob, \ + symbol <<= 1; action0, \ + symbol = (symbol << 1) + 1; action1); + + +#define rc_bit_safe(prob, action0, action1, seq) \ + rc_bit_last_safe(prob, \ symbol <<= 1; action0, \ symbol = (symbol << 1) + 1; action1, \ seq); +// Unroll fixed-sized bittree decoding. +// +// A compile-time constant in final_add can be used to get rid of the high bit +// from symbol that is used for the array indexing (1U << bittree_bits). +// final_add may also be used to add offset to the result (LZMA length +// decoder does that). +// +// The reason to have final_add here is that in the asm code the addition +// can be done for free: in x86-64 there is SBB instruction with -1 as +// the immediate value, and final_add is combined with that value. +#define rc_bittree_bit(prob) \ + rc_bit(prob, , ) + +#define rc_bittree3(probs, final_add) \ +do { \ + symbol = 1; \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + symbol += (uint32_t)(final_add); \ +} while (0) -/// Like rc_bit() but add "case seq:" as a prefix. This makes the unrolled -/// loops more readable because the code isn't littered with "case" -/// statements. On the other hand this also makes it less readable, since -/// spotting the places where the decoder loop may be restarted is less -/// obvious. -#define rc_bit_case(prob, action0, action1, seq) \ - case seq: rc_bit(prob, action0, action1, seq) +#define rc_bittree6(probs, final_add) \ +do { \ + symbol = 1; \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + symbol += (uint32_t)(final_add); \ +} while (0) + +#define rc_bittree8(probs, final_add) \ +do { \ + symbol = 1; \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + rc_bittree_bit(probs[symbol]); \ + symbol += (uint32_t)(final_add); \ +} while (0) + + +// Fixed-sized reverse bittree +#define rc_bittree_rev4(probs) \ +do { \ + symbol = 0; \ + rc_bit_last(probs[symbol + 1], , symbol += 1); \ + rc_bit_last(probs[symbol + 2], , symbol += 2); \ + rc_bit_last(probs[symbol + 4], , symbol += 4); \ + rc_bit_last(probs[symbol + 8], , symbol += 8); \ +} while (0) + + +// Decode one bit from variable-sized reverse bittree. The loop is done +// in the code that uses this macro. This could be changed if the assembly +// version benefited from having the loop done in assembly but it didn't +// seem so in early 2024. +// +// Also, if the loop was done here, the loop counter would likely be local +// to the macro so that it wouldn't modify yet another input variable. +// If a _safe version of a macro with a loop was done then a modifiable +// input variable couldn't be avoided though. +#define rc_bit_add_if_1(probs, dest, value_to_add_if_1) \ + rc_bit(probs[symbol], \ + , \ + dest += value_to_add_if_1); + + +// Matched literal +#define decode_with_match_bit \ + t_match_byte <<= 1; \ + t_match_bit = t_match_byte & t_offset; \ + t_subcoder_index = t_offset + t_match_bit + symbol; \ + rc_bit(probs[t_subcoder_index], \ + t_offset &= ~t_match_bit, \ + t_offset &= t_match_bit) + +#define rc_matched_literal(probs_base_var, match_byte) \ +do { \ + uint32_t t_match_byte = (match_byte); \ + uint32_t t_match_bit; \ + uint32_t t_subcoder_index; \ + uint32_t t_offset = 0x100; \ + symbol = 1; \ + decode_with_match_bit; \ + decode_with_match_bit; \ + decode_with_match_bit; \ + decode_with_match_bit; \ + decode_with_match_bit; \ + decode_with_match_bit; \ + decode_with_match_bit; \ + decode_with_match_bit; \ +} while (0) /// Decode a bit without using a probability. -#define rc_direct(dest, seq) \ +// +// NOTE: GCC 13 and Clang/LLVM 16 can, at least on x86-64, optimize the bound +// calculation to use an arithmetic right shift so there's no need to provide +// the alternative code which, according to C99/C11/C23 6.3.1.3-p3 isn't +// perfectly portable: rc_bound = (uint32_t)((int32_t)rc.code >> 31); +#define rc_direct(dest, count_var) \ do { \ - rc_normalize(seq); \ + dest = (dest << 1) + 1; \ + rc_normalize(); \ + rc.range >>= 1; \ + rc.code -= rc.range; \ + rc_bound = UINT32_C(0) - (rc.code >> 31); \ + dest += rc_bound; \ + rc.code += rc.range & rc_bound; \ +} while (--count_var > 0) + + + +#define rc_direct_safe(dest, count_var, seq) \ +do { \ + rc_normalize_safe(seq); \ rc.range >>= 1; \ rc.code -= rc.range; \ rc_bound = UINT32_C(0) - (rc.code >> 31); \ rc.code += rc.range & rc_bound; \ dest = (dest << 1) + (rc_bound + 1); \ +} while (--count_var > 0) + + +////////////////// +// Branchless C // +////////////////// + +/// Decode a bit using a branchless method. This reduces the number of +/// mispredicted branches and thus can improve speed. +#define rc_c_bit(prob, action_bit, action_neg) \ +do { \ + probability *p = &(prob); \ + rc_normalize(); \ + rc_bound = (rc.range >> RC_BIT_MODEL_TOTAL_BITS) * *p; \ + uint32_t rc_mask = rc.code >= rc_bound; /* rc_mask = decoded bit */ \ + action_bit; /* action when rc_mask is 0 or 1 */ \ + /* rc_mask becomes 0 if bit is 0 and 0xFFFFFFFF if bit is 1: */ \ + rc_mask = 0U - rc_mask; \ + rc.range &= rc_mask; /* If bit 0: set rc.range = 0 */ \ + rc_bound ^= rc_mask; \ + rc_bound -= rc_mask; /* If bit 1: rc_bound = 0U - rc_bound */ \ + rc.range += rc_bound; \ + rc_bound &= rc_mask; \ + rc.code += rc_bound; \ + action_neg; /* action when rc_mask is 0 or 0xFFFFFFFF */ \ + rc_mask = ~rc_mask; /* If bit 0: all bits are set in rc_mask */ \ + rc_mask &= RC_BIT_MODEL_OFFSET; \ + *p -= (*p + rc_mask) >> RC_MOVE_BITS; \ } while (0) -// NOTE: No macros are provided for bittree decoding. It seems to be simpler -// to just write them open in the code. +// Testing on x86-64 give an impression that only the normal bittrees and +// the fixed-sized reverse bittrees are worth the branchless C code. +// It should be tested on other archs for which there isn't assembly code +// in this file. + +// Using addition in "(symbol << 1) + rc_mask" allows use of x86 LEA +// or RISC-V SH1ADD instructions. Compilers might infer it from +// "(symbol << 1) | rc_mask" too if they see that mask is 0 or 1 but +// the use of addition doesn't require such analysis from compilers. +#if LZMA_RANGE_DECODER_CONFIG & 0x01 +#undef rc_bittree_bit +#define rc_bittree_bit(prob) \ + rc_c_bit(prob, \ + symbol = (symbol << 1) + rc_mask, \ + ) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x01 + +#if LZMA_RANGE_DECODER_CONFIG & 0x02 +#undef rc_bittree_rev4 +#define rc_bittree_rev4(probs) \ +do { \ + symbol = 0; \ + rc_c_bit(probs[symbol + 1], symbol += rc_mask, ); \ + rc_c_bit(probs[symbol + 2], symbol += rc_mask << 1, ); \ + rc_c_bit(probs[symbol + 4], symbol += rc_mask << 2, ); \ + rc_c_bit(probs[symbol + 8], symbol += rc_mask << 3, ); \ +} while (0) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x02 + +#if LZMA_RANGE_DECODER_CONFIG & 0x04 +#undef rc_bit_add_if_1 +#define rc_bit_add_if_1(probs, dest, value_to_add_if_1) \ + rc_c_bit(probs[symbol], \ + symbol = (symbol << 1) + rc_mask, \ + dest += (value_to_add_if_1) & rc_mask) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x04 + + +#if LZMA_RANGE_DECODER_CONFIG & 0x08 +#undef decode_with_match_bit +#define decode_with_match_bit \ + t_match_byte <<= 1; \ + t_match_bit = t_match_byte & t_offset; \ + t_subcoder_index = t_offset + t_match_bit + symbol; \ + rc_c_bit(probs[t_subcoder_index], \ + symbol = (symbol << 1) + rc_mask, \ + t_offset &= ~t_match_bit ^ rc_mask) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x08 + + +//////////// +// x86-64 // +//////////// + +#if LZMA_RANGE_DECODER_CONFIG & 0x1F0 + +// rc_asm_y and rc_asm_n are used as arguments to macros to control which +// strings to include or omit. +#define rc_asm_y(str) str +#define rc_asm_n(str) + +// There are a few possible variations for normalization. +// This is the smallest variant which is also used by LZMA SDK. +// +// - This has partial register write (the MOV from (%[in_ptr])). +// +// - INC saves one byte in code size over ADD. False dependency on +// partial flags from INC shouldn't become a problem on any processor +// because the instructions after normalization don't read the flags +// until SUB which sets all flags. +// +#define rc_asm_normalize \ + "cmp %[top_value], %[range]\n\t" \ + "jae 1f\n\t" \ + "shl %[shift_bits], %[code]\n\t" \ + "mov (%[in_ptr]), %b[code]\n\t" \ + "shl %[shift_bits], %[range]\n\t" \ + "inc %[in_ptr]\n" \ + "1:\n" + +// rc_asm_calc(prob) is roughly equivalent to the C version of rc_if_0(prob)... +// +// rc_bound = (rc.range >> RC_BIT_MODEL_TOTAL_BITS) * (prob); +// if (rc.code < rc_bound) +// +// ...but the bound is stored in "range": +// +// t0 = range; +// range = (range >> RC_BIT_MODEL_TOTAL_BITS) * (prob); +// t0 -= range; +// t1 = code; +// code -= range; +// +// The carry flag (CF) from the last subtraction holds the negation of +// the decoded bit (if CF==0 then the decoded bit is 1). +// The values in t0 and t1 are needed for rc_update_0(prob) and +// rc_update_1(prob). If the bit is 0, rc_update_0(prob)... +// +// rc.range = rc_bound; +// +// ...has already been done but the "code -= range" has to be reverted using +// the old value stored in t1. (Also, prob needs to be updated.) +// +// If the bit is 1, rc_update_1(prob)... +// +// rc.range -= rc_bound; +// rc.code -= rc_bound; +// +// ...is already done for "code" but the value for "range" needs to be taken +// from t0. (Also, prob needs to be updated here as well.) +// +// The assignments from t0 and t1 can be done in a branchless manner with CMOV +// after the instructions from this macro. The CF from SUB tells which moves +// are needed. +#define rc_asm_calc(prob) \ + "mov %[range], %[t0]\n\t" \ + "shr %[bit_model_total_bits], %[range]\n\t" \ + "imul %[" prob "], %[range]\n\t" \ + "sub %[range], %[t0]\n\t" \ + "mov %[code], %[t1]\n\t" \ + "sub %[range], %[code]\n\t" + +// Also, prob needs to be updated: The update math depends on the decoded bit. +// It can be expressed in a few slightly different ways but this is fairly +// convenient here: +// +// prob -= (prob + (bit ? 0 : RC_BIT_MODEL_OFFSET)) >> RC_MOVE_BITS; +// +// To do it in branchless way when the negation of the decoded bit is in CF, +// both "prob" and "prob + RC_BIT_MODEL_OFFSET" are needed. Then the desired +// value can be picked with CMOV. The addition can be done using LEA without +// affecting CF. +// +// (This prob update method is a tiny bit different from LZMA SDK 23.01. +// In the LZMA SDK a single register is reserved solely for a constant to +// be used with CMOV when updating prob. That is fine since there are enough +// free registers to do so. The method used here uses one fewer register, +// which is valuable with inline assembly.) +// +// * * * +// +// In bittree decoding, each (unrolled) loop iteration decodes one bit +// and needs one prob variable. To make it faster, the prob variable of +// the iteration N+1 is loaded during iteration N. There are two possible +// prob variables to choose from for N+1. Both are loaded from memory and +// the correct one is chosen with CMOV using the same CF as is used for +// other things described above. +// +// This preloading/prefetching requires an extra register. To avoid +// useless moves from "preloaded prob register" to "current prob register", +// the macros swap between the two registers for odd and even iterations. +// +// * * * +// +// Finally, the decoded bit has to be stored in "symbol". Since the negation +// of the bit is in CF, this can be done with SBB: symbol -= CF - 1. That is, +// if the decoded bit is 0 (CF==1) the operation is a no-op "symbol -= 0" +// and when bit is 1 (CF==0) the operation is "symbol -= 0 - 1" which is +// the same as "symbol += 1". +// +// The instructions for all things are intertwined for a few reasons: +// - freeing temporary registers for new use +// - not modifying CF too early +// - instruction scheduling +// +// The first and last iterations can cheat a little. For example, +// on the first iteration "symbol" is known to start from 1 so it +// doesn't need to be read; it can even be immediately initialized +// to 2 to prepare for the second iteration of the loop. +// +// * * * +// +// a = number of the current prob variable (0 or 1) +// b = number of the next prob variable (1 or 0) +// *_only = rc_asm_y or _n to include or exclude code marked with them +#define rc_asm_bittree(a, b, first_only, middle_only, last_only) \ + first_only( \ + "movzwl 2(%[probs_base]), %[prob" #a "]\n\t" \ + "mov $2, %[symbol]\n\t" \ + "movzwl 4(%[probs_base]), %[prob" #b "]\n\t" \ + ) \ + middle_only( \ + /* Note the scaling of 4 instead of 2: */ \ + "movzwl (%[probs_base], %q[symbol], 4), %[prob" #b "]\n\t" \ + ) \ + last_only( \ + "add %[symbol], %[symbol]\n\t" \ + ) \ + \ + rc_asm_normalize \ + rc_asm_calc("prob" #a) \ + \ + "cmovae %[t0], %[range]\n\t" \ + \ + first_only( \ + "movzwl 6(%[probs_base]), %[t0]\n\t" \ + "cmovae %[t0], %[prob" #b "]\n\t" \ + ) \ + middle_only( \ + "movzwl 2(%[probs_base], %q[symbol], 4), %[t0]\n\t" \ + "lea (%q[symbol], %q[symbol]), %[symbol]\n\t" \ + "cmovae %[t0], %[prob" #b "]\n\t" \ + ) \ + \ + "lea %c[bit_model_offset](%q[prob" #a "]), %[t0]\n\t" \ + "cmovb %[t1], %[code]\n\t" \ + "mov %[symbol], %[t1]\n\t" \ + "cmovae %[prob" #a "], %[t0]\n\t" \ + \ + first_only( \ + "sbb $-1, %[symbol]\n\t" \ + ) \ + middle_only( \ + "sbb $-1, %[symbol]\n\t" \ + ) \ + last_only( \ + "sbb %[last_sbb], %[symbol]\n\t" \ + ) \ + \ + "shr %[move_bits], %[t0]\n\t" \ + "sub %[t0], %[prob" #a "]\n\t" \ + /* Scaling of 1 instead of 2 because symbol <<= 1. */ \ + "mov %w[prob" #a "], (%[probs_base], %q[t1], 1)\n\t" + +// NOTE: The order of variables in __asm__ can affect speed and code size. +#define rc_asm_bittree_n(probs_base_var, final_add, asm_str) \ +do { \ + uint32_t t0; \ + uint32_t t1; \ + uint32_t t_prob0; \ + uint32_t t_prob1; \ + \ + __asm__( \ + asm_str \ + : \ + [range] "+&r"(rc.range), \ + [code] "+&r"(rc.code), \ + [t0] "=&r"(t0), \ + [t1] "=&r"(t1), \ + [prob0] "=&r"(t_prob0), \ + [prob1] "=&r"(t_prob1), \ + [symbol] "=&r"(symbol), \ + [in_ptr] "+&r"(rc_in_ptr) \ + : \ + [probs_base] "r"(probs_base_var), \ + [last_sbb] "n"(-1 - (final_add)), \ + [top_value] "n"(RC_TOP_VALUE), \ + [shift_bits] "n"(RC_SHIFT_BITS), \ + [bit_model_total_bits] "n"(RC_BIT_MODEL_TOTAL_BITS), \ + [bit_model_offset] "n"(RC_BIT_MODEL_OFFSET), \ + [move_bits] "n"(RC_MOVE_BITS) \ + : \ + "cc", "memory"); \ +} while (0) + + +#if LZMA_RANGE_DECODER_CONFIG & 0x010 +#undef rc_bittree3 +#define rc_bittree3(probs_base_var, final_add) \ + rc_asm_bittree_n(probs_base_var, final_add, \ + rc_asm_bittree(0, 1, rc_asm_y, rc_asm_n, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(0, 1, rc_asm_n, rc_asm_n, rc_asm_y) \ + ) + +#undef rc_bittree6 +#define rc_bittree6(probs_base_var, final_add) \ + rc_asm_bittree_n(probs_base_var, final_add, \ + rc_asm_bittree(0, 1, rc_asm_y, rc_asm_n, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(0, 1, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(0, 1, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_n, rc_asm_y) \ + ) + +#undef rc_bittree8 +#define rc_bittree8(probs_base_var, final_add) \ + rc_asm_bittree_n(probs_base_var, final_add, \ + rc_asm_bittree(0, 1, rc_asm_y, rc_asm_n, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(0, 1, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(0, 1, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(0, 1, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree(1, 0, rc_asm_n, rc_asm_n, rc_asm_y) \ + ) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x010 + + +// Fixed-sized reverse bittree +// +// This uses the indexing that constructs the final value in symbol directly. +// add = 1, 2, 4, 8 +// dcur = -, 4, 8, 16 +// dnext0 = 4, 8, 16, - +// dnext0 = 6, 12, 24, - +#define rc_asm_bittree_rev(a, b, add, dcur, dnext0, dnext1, \ + first_only, middle_only, last_only) \ + first_only( \ + "movzwl 2(%[probs_base]), %[prob" #a "]\n\t" \ + "xor %[symbol], %[symbol]\n\t" \ + "movzwl 4(%[probs_base]), %[prob" #b "]\n\t" \ + ) \ + middle_only( \ + "movzwl " #dnext0 "(%[probs_base], %q[symbol], 2), " \ + "%[prob" #b "]\n\t" \ + ) \ + \ + rc_asm_normalize \ + rc_asm_calc("prob" #a) \ + \ + "cmovae %[t0], %[range]\n\t" \ + \ + first_only( \ + "movzwl 6(%[probs_base]), %[t0]\n\t" \ + "cmovae %[t0], %[prob" #b "]\n\t" \ + ) \ + middle_only( \ + "movzwl " #dnext1 "(%[probs_base], %q[symbol], 2), %[t0]\n\t" \ + "cmovae %[t0], %[prob" #b "]\n\t" \ + ) \ + \ + "lea " #add "(%q[symbol]), %[t0]\n\t" \ + "cmovb %[t1], %[code]\n\t" \ + middle_only( \ + "mov %[symbol], %[t1]\n\t" \ + ) \ + last_only( \ + "mov %[symbol], %[t1]\n\t" \ + ) \ + "cmovae %[t0], %[symbol]\n\t" \ + "lea %c[bit_model_offset](%q[prob" #a "]), %[t0]\n\t" \ + "cmovae %[prob" #a "], %[t0]\n\t" \ + \ + "shr %[move_bits], %[t0]\n\t" \ + "sub %[t0], %[prob" #a "]\n\t" \ + first_only( \ + "mov %w[prob" #a "], 2(%[probs_base])\n\t" \ + ) \ + middle_only( \ + "mov %w[prob" #a "], " \ + #dcur "(%[probs_base], %q[t1], 2)\n\t" \ + ) \ + last_only( \ + "mov %w[prob" #a "], " \ + #dcur "(%[probs_base], %q[t1], 2)\n\t" \ + ) + +#if LZMA_RANGE_DECODER_CONFIG & 0x020 +#undef rc_bittree_rev4 +#define rc_bittree_rev4(probs_base_var) \ +rc_asm_bittree_n(probs_base_var, 4, \ + rc_asm_bittree_rev(0, 1, 1, -, 4, 6, rc_asm_y, rc_asm_n, rc_asm_n) \ + rc_asm_bittree_rev(1, 0, 2, 4, 8, 12, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree_rev(0, 1, 4, 8, 16, 24, rc_asm_n, rc_asm_y, rc_asm_n) \ + rc_asm_bittree_rev(1, 0, 8, 16, -, -, rc_asm_n, rc_asm_n, rc_asm_y) \ +) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x020 + + +#if LZMA_RANGE_DECODER_CONFIG & 0x040 +#undef rc_bit_add_if_1 +#define rc_bit_add_if_1(probs_base_var, dest_var, value_to_add_if_1) \ +do { \ + uint32_t t0; \ + uint32_t t1; \ + uint32_t t2 = (value_to_add_if_1); \ + uint32_t t_prob; \ + uint32_t t_index; \ + \ + __asm__( \ + "movzwl (%[probs_base], %q[symbol], 2), %[prob]\n\t" \ + "mov %[symbol], %[index]\n\t" \ + \ + "add %[dest], %[t2]\n\t" \ + "add %[symbol], %[symbol]\n\t" \ + \ + rc_asm_normalize \ + rc_asm_calc("prob") \ + \ + "cmovae %[t0], %[range]\n\t" \ + "lea %c[bit_model_offset](%q[prob]), %[t0]\n\t" \ + "cmovb %[t1], %[code]\n\t" \ + "cmovae %[prob], %[t0]\n\t" \ + \ + "cmovae %[t2], %[dest]\n\t" \ + "sbb $-1, %[symbol]\n\t" \ + \ + "sar %[move_bits], %[t0]\n\t" \ + "sub %[t0], %[prob]\n\t" \ + "mov %w[prob], (%[probs_base], %q[index], 2)" \ + : \ + [range] "+&r"(rc.range), \ + [code] "+&r"(rc.code), \ + [t0] "=&r"(t0), \ + [t1] "=&r"(t1), \ + [prob] "=&r"(t_prob), \ + [index] "=&r"(t_index), \ + [symbol] "+&r"(symbol), \ + [t2] "+&r"(t2), \ + [dest] "+&r"(dest_var), \ + [in_ptr] "+&r"(rc_in_ptr) \ + : \ + [probs_base] "r"(probs_base_var), \ + [top_value] "n"(RC_TOP_VALUE), \ + [shift_bits] "n"(RC_SHIFT_BITS), \ + [bit_model_total_bits] "n"(RC_BIT_MODEL_TOTAL_BITS), \ + [bit_model_offset] "n"(RC_BIT_MODEL_OFFSET), \ + [move_bits] "n"(RC_MOVE_BITS) \ + : \ + "cc", "memory"); \ +} while (0) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x040 + + +// Literal decoding uses a normal 8-bit bittree but literal with match byte +// is more complex in picking the probability variable from the correct +// subtree. This doesn't use preloading/prefetching of the next prob because +// there are four choices instead of two. +// +// FIXME? The first iteration starts with symbol = 1 so it could be optimized +// by a tiny amount. +#define rc_asm_matched_literal(nonlast_only) \ + "add %[offset], %[symbol]\n\t" \ + "and %[offset], %[match_bit]\n\t" \ + "add %[match_bit], %[symbol]\n\t" \ + \ + "movzwl (%[probs_base], %q[symbol], 2), %[prob]\n\t" \ + \ + "add %[symbol], %[symbol]\n\t" \ + \ + nonlast_only( \ + "xor %[match_bit], %[offset]\n\t" \ + "add %[match_byte], %[match_byte]\n\t" \ + ) \ + \ + rc_asm_normalize \ + rc_asm_calc("prob") \ + \ + "cmovae %[t0], %[range]\n\t" \ + "lea %c[bit_model_offset](%q[prob]), %[t0]\n\t" \ + "cmovb %[t1], %[code]\n\t" \ + "mov %[symbol], %[t1]\n\t" \ + "cmovae %[prob], %[t0]\n\t" \ + \ + nonlast_only( \ + "cmovae %[match_bit], %[offset]\n\t" \ + "mov %[match_byte], %[match_bit]\n\t" \ + ) \ + \ + "sbb $-1, %[symbol]\n\t" \ + \ + "shr %[move_bits], %[t0]\n\t" \ + /* Undo symbol += match_bit + offset: */ \ + "and $0x1FF, %[symbol]\n\t" \ + "sub %[t0], %[prob]\n\t" \ + \ + /* Scaling of 1 instead of 2 because symbol <<= 1. */ \ + "mov %w[prob], (%[probs_base], %q[t1], 1)\n\t" + + +#if LZMA_RANGE_DECODER_CONFIG & 0x080 +#undef rc_matched_literal +#define rc_matched_literal(probs_base_var, match_byte_value) \ +do { \ + uint32_t t0; \ + uint32_t t1; \ + uint32_t t_prob; \ + uint32_t t_match_byte = (uint32_t)(match_byte_value) << 1; \ + uint32_t t_match_bit = t_match_byte; \ + uint32_t t_offset = 0x100; \ + symbol = 1; \ + \ + __asm__( \ + rc_asm_matched_literal(rc_asm_y) \ + rc_asm_matched_literal(rc_asm_y) \ + rc_asm_matched_literal(rc_asm_y) \ + rc_asm_matched_literal(rc_asm_y) \ + rc_asm_matched_literal(rc_asm_y) \ + rc_asm_matched_literal(rc_asm_y) \ + rc_asm_matched_literal(rc_asm_y) \ + rc_asm_matched_literal(rc_asm_n) \ + : \ + [range] "+&r"(rc.range), \ + [code] "+&r"(rc.code), \ + [t0] "=&r"(t0), \ + [t1] "=&r"(t1), \ + [prob] "=&r"(t_prob), \ + [match_bit] "+&r"(t_match_bit), \ + [symbol] "+&r"(symbol), \ + [match_byte] "+&r"(t_match_byte), \ + [offset] "+&r"(t_offset), \ + [in_ptr] "+&r"(rc_in_ptr) \ + : \ + [probs_base] "r"(probs_base_var), \ + [top_value] "n"(RC_TOP_VALUE), \ + [shift_bits] "n"(RC_SHIFT_BITS), \ + [bit_model_total_bits] "n"(RC_BIT_MODEL_TOTAL_BITS), \ + [bit_model_offset] "n"(RC_BIT_MODEL_OFFSET), \ + [move_bits] "n"(RC_MOVE_BITS) \ + : \ + "cc", "memory"); \ +} while (0) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x080 + + +// Doing the loop in asm instead of C seems to help a little. +#if LZMA_RANGE_DECODER_CONFIG & 0x100 +#undef rc_direct +#define rc_direct(dest_var, count_var) \ +do { \ + uint32_t t0; \ + uint32_t t1; \ + \ + __asm__( \ + "2:\n\t" \ + "add %[dest], %[dest]\n\t" \ + "lea 1(%q[dest]), %[t1]\n\t" \ + \ + rc_asm_normalize \ + \ + "shr $1, %[range]\n\t" \ + "mov %[code], %[t0]\n\t" \ + "sub %[range], %[code]\n\t" \ + "cmovns %[t1], %[dest]\n\t" \ + "cmovs %[t0], %[code]\n\t" \ + "dec %[count]\n\t" \ + "jnz 2b\n\t" \ + : \ + [range] "+&r"(rc.range), \ + [code] "+&r"(rc.code), \ + [t0] "=&r"(t0), \ + [t1] "=&r"(t1), \ + [dest] "+&r"(dest_var), \ + [count] "+&r"(count_var), \ + [in_ptr] "+&r"(rc_in_ptr) \ + : \ + [top_value] "n"(RC_TOP_VALUE), \ + [shift_bits] "n"(RC_SHIFT_BITS) \ + : \ + "cc", "memory"); \ +} while (0) +#endif // LZMA_RANGE_DECODER_CONFIG & 0x100 + +#endif // x86_64 #endif diff --git a/Utilities/cmliblzma/liblzma/rangecoder/range_encoder.h b/Utilities/cmliblzma/liblzma/rangecoder/range_encoder.h index 1e1c36995b6..8f62a47ac0a 100644 --- a/Utilities/cmliblzma/liblzma/rangecoder/range_encoder.h +++ b/Utilities/cmliblzma/liblzma/rangecoder/range_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file range_encoder.h @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_RANGE_ENCODER_H @@ -19,9 +18,9 @@ /// Maximum number of symbols that can be put pending into lzma_range_encoder -/// structure between calls to lzma_rc_encode(). For LZMA, 52+5 is enough +/// structure between calls to lzma_rc_encode(). For LZMA, 48+5 is enough /// (match with big distance and length followed by range encoder flush). -#define RC_SYMBOLS_MAX 58 +#define RC_SYMBOLS_MAX 53 typedef struct { @@ -30,6 +29,9 @@ typedef struct { uint32_t range; uint8_t cache; + /// Number of bytes written out by rc_encode() -> rc_shift_low() + uint64_t out_total; + /// Number of symbols in the tables size_t count; @@ -58,11 +60,21 @@ rc_reset(lzma_range_encoder *rc) rc->cache_size = 1; rc->range = UINT32_MAX; rc->cache = 0; + rc->out_total = 0; rc->count = 0; rc->pos = 0; } +static inline void +rc_forget(lzma_range_encoder *rc) +{ + // This must not be called when rc_encode() is partially done. + assert(rc->pos == 0); + rc->count = 0; +} + + static inline void rc_bit(lzma_range_encoder *rc, probability *prob, uint32_t bit) { @@ -132,6 +144,7 @@ rc_shift_low(lzma_range_encoder *rc, out[*out_pos] = rc->cache + (uint8_t)(rc->low >> 32); ++*out_pos; + ++rc->out_total; rc->cache = 0xFF; } while (--rc->cache_size != 0); @@ -146,6 +159,34 @@ rc_shift_low(lzma_range_encoder *rc, } +// NOTE: The last two arguments are uint64_t instead of size_t because in +// the dummy version these refer to the size of the whole range-encoded +// output stream, not just to the currently available output buffer space. +static inline bool +rc_shift_low_dummy(uint64_t *low, uint64_t *cache_size, uint8_t *cache, + uint64_t *out_pos, uint64_t out_size) +{ + if ((uint32_t)(*low) < (uint32_t)(0xFF000000) + || (uint32_t)(*low >> 32) != 0) { + do { + if (*out_pos == out_size) + return true; + + ++*out_pos; + *cache = 0xFF; + + } while (--*cache_size != 0); + + *cache = (*low >> 24) & 0xFF; + } + + ++*cache_size; + *low = (*low & 0x00FFFFFF) << RC_SHIFT_BITS; + + return false; +} + + static inline bool rc_encode(lzma_range_encoder *rc, uint8_t *out, size_t *out_pos, size_t out_size) @@ -222,6 +263,83 @@ rc_encode(lzma_range_encoder *rc, } +static inline bool +rc_encode_dummy(const lzma_range_encoder *rc, uint64_t out_limit) +{ + assert(rc->count <= RC_SYMBOLS_MAX); + + uint64_t low = rc->low; + uint64_t cache_size = rc->cache_size; + uint32_t range = rc->range; + uint8_t cache = rc->cache; + uint64_t out_pos = rc->out_total; + + size_t pos = rc->pos; + + while (true) { + // Normalize + if (range < RC_TOP_VALUE) { + if (rc_shift_low_dummy(&low, &cache_size, &cache, + &out_pos, out_limit)) + return true; + + range <<= RC_SHIFT_BITS; + } + + // This check is here because the normalization above must + // be done before flushing the last bytes. + if (pos == rc->count) + break; + + // Encode a bit + switch (rc->symbols[pos]) { + case RC_BIT_0: { + probability prob = *rc->probs[pos]; + range = (range >> RC_BIT_MODEL_TOTAL_BITS) + * prob; + break; + } + + case RC_BIT_1: { + probability prob = *rc->probs[pos]; + const uint32_t bound = prob * (range + >> RC_BIT_MODEL_TOTAL_BITS); + low += bound; + range -= bound; + break; + } + + case RC_DIRECT_0: + range >>= 1; + break; + + case RC_DIRECT_1: + range >>= 1; + low += range; + break; + + case RC_FLUSH: + default: + assert(0); + break; + } + + ++pos; + } + + // Flush the last bytes. This isn't in rc->symbols[] so we do + // it after the above loop to take into account the size of + // the flushing that will be done at the end of the stream. + for (pos = 0; pos < 5; ++pos) { + if (rc_shift_low_dummy(&low, &cache_size, + &cache, &out_pos, out_limit)) + return true; + } + + return false; +} + + static inline uint64_t rc_pending(const lzma_range_encoder *rc) { diff --git a/Utilities/cmliblzma/liblzma/simple/arm.c b/Utilities/cmliblzma/liblzma/simple/arm.c index ff5073ae58f..58acb2d11ad 100644 --- a/Utilities/cmliblzma/liblzma/simple/arm.c +++ b/Utilities/cmliblzma/liblzma/simple/arm.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file arm.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_private.h" @@ -53,6 +52,7 @@ arm_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, } +#ifdef HAVE_ENCODER_ARM extern lzma_ret lzma_simple_arm_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -60,8 +60,10 @@ lzma_simple_arm_encoder_init(lzma_next_coder *next, { return arm_coder_init(next, allocator, filters, true); } +#endif +#ifdef HAVE_DECODER_ARM extern lzma_ret lzma_simple_arm_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -69,3 +71,4 @@ lzma_simple_arm_decoder_init(lzma_next_coder *next, { return arm_coder_init(next, allocator, filters, false); } +#endif diff --git a/Utilities/cmliblzma/liblzma/simple/arm64.c b/Utilities/cmliblzma/liblzma/simple/arm64.c new file mode 100644 index 00000000000..16c2f565f73 --- /dev/null +++ b/Utilities/cmliblzma/liblzma/simple/arm64.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file arm64.c +/// \brief Filter for ARM64 binaries +/// +/// This converts ARM64 relative addresses in the BL and ADRP immediates +/// to absolute values to increase redundancy of ARM64 code. +/// +/// Converting B or ADR instructions was also tested but it's not useful. +/// A majority of the jumps for the B instruction are very small (+/- 0xFF). +/// These are typical for loops and if-statements. Encoding them to their +/// absolute address reduces redundancy since many of the small relative +/// jump values are repeated, but very few of the absolute addresses are. +// +// Authors: Lasse Collin +// Jia Tan +// Igor Pavlov +// +/////////////////////////////////////////////////////////////////////////////// + +#include "simple_private.h" + + +static size_t +arm64_code(void *simple lzma_attribute((__unused__)), + uint32_t now_pos, bool is_encoder, + uint8_t *buffer, size_t size) +{ + size_t i; + + // Clang 14.0.6 on x86-64 makes this four times bigger and 40 % slower + // with auto-vectorization that is enabled by default with -O2. + // Such vectorization bloat happens with -O2 when targeting ARM64 too + // but performance hasn't been tested. +#ifdef __clang__ +# pragma clang loop vectorize(disable) +#endif + for (i = 0; i + 4 <= size; i += 4) { + uint32_t pc = (uint32_t)(now_pos + i); + uint32_t instr = read32le(buffer + i); + + if ((instr >> 26) == 0x25) { + // BL instruction: + // The full 26-bit immediate is converted. + // The range is +/-128 MiB. + // + // Using the full range helps quite a lot with + // big executables. Smaller range would reduce false + // positives in non-code sections of the input though + // so this is a compromise that slightly favors big + // files. With the full range, only six bits of the 32 + // need to match to trigger a conversion. + const uint32_t src = instr; + instr = 0x94000000; + + pc >>= 2; + if (!is_encoder) + pc = 0U - pc; + + instr |= (src + pc) & 0x03FFFFFF; + write32le(buffer + i, instr); + + } else if ((instr & 0x9F000000) == 0x90000000) { + // ADRP instruction: + // Only values in the range +/-512 MiB are converted. + // + // Using less than the full +/-4 GiB range reduces + // false positives on non-code sections of the input + // while being excellent for executables up to 512 MiB. + // The positive effect of ADRP conversion is smaller + // than that of BL but it also doesn't hurt so much in + // non-code sections of input because, with +/-512 MiB + // range, nine bits of 32 need to match to trigger a + // conversion (two 10-bit match choices = 9 bits). + const uint32_t src = ((instr >> 29) & 3) + | ((instr >> 3) & 0x001FFFFC); + + // With the addition only one branch is needed to + // check the +/- range. This is usually false when + // processing ARM64 code so branch prediction will + // handle it well in terms of performance. + // + //if ((src & 0x001E0000) != 0 + // && (src & 0x001E0000) != 0x001E0000) + if ((src + 0x00020000) & 0x001C0000) + continue; + + instr &= 0x9000001F; + + pc >>= 12; + if (!is_encoder) + pc = 0U - pc; + + const uint32_t dest = src + pc; + instr |= (dest & 3) << 29; + instr |= (dest & 0x0003FFFC) << 3; + instr |= (0U - (dest & 0x00020000)) & 0x00E00000; + write32le(buffer + i, instr); + } + } + + return i; +} + + +static lzma_ret +arm64_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, + const lzma_filter_info *filters, bool is_encoder) +{ + return lzma_simple_coder_init(next, allocator, filters, + &arm64_code, 0, 4, 4, is_encoder); +} + + +#ifdef HAVE_ENCODER_ARM64 +extern lzma_ret +lzma_simple_arm64_encoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters) +{ + return arm64_coder_init(next, allocator, filters, true); +} +#endif + + +#ifdef HAVE_DECODER_ARM64 +extern lzma_ret +lzma_simple_arm64_decoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters) +{ + return arm64_coder_init(next, allocator, filters, false); +} +#endif diff --git a/Utilities/cmliblzma/liblzma/simple/armthumb.c b/Utilities/cmliblzma/liblzma/simple/armthumb.c index a8da334a04f..f1eeca9b80f 100644 --- a/Utilities/cmliblzma/liblzma/simple/armthumb.c +++ b/Utilities/cmliblzma/liblzma/simple/armthumb.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file armthumb.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_private.h" @@ -58,6 +57,7 @@ armthumb_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, } +#ifdef HAVE_ENCODER_ARMTHUMB extern lzma_ret lzma_simple_armthumb_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -65,8 +65,10 @@ lzma_simple_armthumb_encoder_init(lzma_next_coder *next, { return armthumb_coder_init(next, allocator, filters, true); } +#endif +#ifdef HAVE_DECODER_ARMTHUMB extern lzma_ret lzma_simple_armthumb_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -74,3 +76,4 @@ lzma_simple_armthumb_decoder_init(lzma_next_coder *next, { return armthumb_coder_init(next, allocator, filters, false); } +#endif diff --git a/Utilities/cmliblzma/liblzma/simple/ia64.c b/Utilities/cmliblzma/liblzma/simple/ia64.c index 6492d0a3846..50250140997 100644 --- a/Utilities/cmliblzma/liblzma/simple/ia64.c +++ b/Utilities/cmliblzma/liblzma/simple/ia64.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file ia64.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_private.h" @@ -94,6 +93,7 @@ ia64_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, } +#ifdef HAVE_ENCODER_IA64 extern lzma_ret lzma_simple_ia64_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -101,8 +101,10 @@ lzma_simple_ia64_encoder_init(lzma_next_coder *next, { return ia64_coder_init(next, allocator, filters, true); } +#endif +#ifdef HAVE_DECODER_IA64 extern lzma_ret lzma_simple_ia64_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -110,3 +112,4 @@ lzma_simple_ia64_decoder_init(lzma_next_coder *next, { return ia64_coder_init(next, allocator, filters, false); } +#endif diff --git a/Utilities/cmliblzma/liblzma/simple/powerpc.c b/Utilities/cmliblzma/liblzma/simple/powerpc.c index 0b60e9b3fe3..ba6cfbef3ab 100644 --- a/Utilities/cmliblzma/liblzma/simple/powerpc.c +++ b/Utilities/cmliblzma/liblzma/simple/powerpc.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file powerpc.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_private.h" @@ -58,6 +57,7 @@ powerpc_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, } +#ifdef HAVE_ENCODER_POWERPC extern lzma_ret lzma_simple_powerpc_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -65,8 +65,10 @@ lzma_simple_powerpc_encoder_init(lzma_next_coder *next, { return powerpc_coder_init(next, allocator, filters, true); } +#endif +#ifdef HAVE_DECODER_POWERPC extern lzma_ret lzma_simple_powerpc_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -74,3 +76,4 @@ lzma_simple_powerpc_decoder_init(lzma_next_coder *next, { return powerpc_coder_init(next, allocator, filters, false); } +#endif diff --git a/Utilities/cmliblzma/liblzma/simple/riscv.c b/Utilities/cmliblzma/liblzma/simple/riscv.c new file mode 100644 index 00000000000..b18df8b637d --- /dev/null +++ b/Utilities/cmliblzma/liblzma/simple/riscv.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: 0BSD + +/////////////////////////////////////////////////////////////////////////////// +// +/// \file riscv.c +/// \brief Filter for 32-bit/64-bit little/big endian RISC-V binaries +/// +/// This converts program counter relative addresses in function calls +/// (JAL, AUIPC+JALR), address calculation of functions and global +/// variables (AUIPC+ADDI), loads (AUIPC+load), and stores (AUIPC+store). +/// +/// For AUIPC+inst2 pairs, the paired instruction checking is fairly relaxed. +/// The paired instruction opcode must only have its lowest two bits set, +/// meaning it will convert any paired instruction that is not a 16-bit +/// compressed instruction. This was shown to be enough to keep the number +/// of false matches low while improving code size and speed. +// +// Authors: Lasse Collin +// Jia Tan +// +// Special thanks: +// +// - Chien Wong provided a few early versions of RISC-V +// filter variants along with test files and benchmark results. +// +// - Igor Pavlov helped a lot in the filter design, getting it both +// faster and smaller. The implementation here is still independently +// written, not based on LZMA SDK. +// +/////////////////////////////////////////////////////////////////////////////// + +/* + +RISC-V filtering +================ + + RV32I and RV64I, possibly combined with extensions C, Zfh, F, D, + and Q, are identical enough that the same filter works for both. + + The instruction encoding is always little endian, even on systems + with big endian data access. Thus the same filter works for both + endiannesses. + + The following instructions have program counter relative + (pc-relative) behavior: + +JAL +--- + + JAL is used for function calls (including tail calls) and + unconditional jumps within functions. Jumps within functions + aren't useful to filter because the absolute addresses often + appear only once or at most a few times. Tail calls and jumps + within functions look the same to a simple filter so neither + are filtered, that is, JAL x0 is ignored (the ABI name of the + register x0 is "zero"). + + Almost all calls store the return address to register x1 (ra) + or x5 (t0). To reduce false matches when the filter is applied + to non-code data, only the JAL instructions that use x1 or x5 + are converted. JAL has pc-relative range of +/-1 MiB so longer + calls and jumps need another method (AUIPC+JALR). + +C.J and C.JAL +------------- + + C.J and C.JAL have pc-relative range of +/-2 KiB. + + C.J is for tail calls and jumps within functions and isn't + filtered for the reasons mentioned for JAL x0. + + C.JAL is an RV32C-only instruction. Its encoding overlaps with + RV64C-only C.ADDIW which is a common instruction. So if filtering + C.JAL was useful (it wasn't tested) then a separate filter would + be needed for RV32 and RV64. Also, false positives would be a + significant problem when the filter is applied to non-code data + because C.JAL needs only five bits to match. Thus, this filter + doesn't modify C.JAL instructions. + +BEQ, BNE, BLT, BGE, BLTU, BGEU, C.BEQZ, and C.BNEZ +-------------------------------------------------- + + These are conditional branches with pc-relative range + of +/-4 KiB (+/-256 B for C.*). The absolute addresses often + appear only once and very short distances are the most common, + so filtering these instructions would make compression worse. + +AUIPC with rd != x0 +------------------- + + AUIPC is paired with a second instruction (inst2) to do + pc-relative jumps, calls, loads, stores, and for taking + an address of a symbol. AUIPC has a 20-bit immediate and + the possible inst2 choices have a 12-bit immediate. + + AUIPC stores pc + 20-bit signed immediate to a register. + The immediate encodes a multiple of 4 KiB so AUIPC itself + has a pc-relative range of +/-2 GiB. AUIPC does *NOT* set + the lowest 12 bits of the result to zero! This means that + the 12-bit immediate in inst2 cannot just include the lowest + 12 bits of the absolute address as is; the immediate has to + compensate for the lowest 12 bits that AUIPC copies from the + program counter. This means that a good filter has to convert + not only AUIPC but also the paired inst2. + + A strict filter would focus on filtering the following + AUIPC+inst2 pairs: + + - AUIPC+JALR: Function calls, including tail calls. + + - AUIPC+ADDI: Calculating the address of a function + or a global variable. + + - AUIPC+load/store from the base instruction sets + (RV32I, RV64I) or from the floating point extensions + Zfh, F, D, and Q: + * RV32I: LB, LH, LW, LBU, LHU, SB, SH, SW + * RV64I has also: LD, LWU, SD + * Zfh: FLH, FSH + * F: FLW, FSW + * D: FLD, FSD + * Q: FLQ, FSQ + + NOTE: AUIPC+inst2 can only be a pair if AUIPC's rd specifies + the same register as inst2's rs1. + + Instead of strictly accepting only the above instructions as inst2, + this filter uses a much simpler condition: the lowest two bits of + inst2 must be set, that is, inst2 must not be a 16-bit compressed + instruction. So this will accept all 32-bit and possible future + extended instructions as a pair to AUIPC if the bits in AUIPC's + rd [11:7] match the bits [19:15] in inst2 (the bits that I-type and + S-type instructions use for rs1). Testing showed that this relaxed + condition for inst2 did not consistently or significantly affect + compression ratio but it reduced code size and improved speed. + + Additionally, the paired instruction is always treated as an I-type + instruction. The S-type instructions used by stores (SB, SH, SW, + etc.) place the lowest 5 bits of the immediate in a different + location than I-type instructions. AUIPC+store pairs are less + common than other pairs, and testing showed that the extra + code required to handle S-type instructions was not worth the + compression ratio gained. + + AUIPC+inst2 don't necessarily appear sequentially next to each + other although very often they do. Especially AUIPC+JALR are + sequential as that may allow instruction fusion in processors + (and perhaps help branch prediction as a fused AUIPC+JALR is + a direct branch while JALR alone is an indirect branch). + + Clang 16 can generate code where AUIPC+inst2 is split: + + - AUIPC is outside a loop and inst2 (load/store) is inside + the loop. This way the AUIPC instruction needs to be + executed only once. + + - Load-modify-store may have AUIPC for the load and the same + AUIPC-result is used for the store too. This may get combined + with AUIPC being outside the loop. + + - AUIPC is before a conditional branch and inst2 is hundreds + of bytes away at the branch target. + + - Inner and outer pair: + + auipc a1,0x2f + auipc a2,0x3d + ld a2,-500(a2) + addi a1,a1,-233 + + - Many split pairs with an untaken conditional branch between: + + auipc s9,0x1613 # Pair 1 + auipc s4,0x1613 # Pair 2 + auipc s6,0x1613 # Pair 3 + auipc s10,0x1613 # Pair 4 + beqz a5,a3baae + ld a0,0(a6) + ld a6,246(s9) # Pair 1 + ld a1,250(s4) # Pair 2 + ld a3,254(s6) # Pair 3 + ld a4,258(s10) # Pair 4 + + It's not possible to find all split pairs in a filter like this. + At least in 2024, simple sequential pairs are 99 % of AUIPC uses + so filtering only such pairs gives good results and makes the + filter simpler. However, it's possible that future compilers will + produce different code where sequential pairs aren't as common. + + This filter doesn't convert AUIPC instructions alone because: + + (1) The conversion would be off-by-one (or off-by-4096) half the + time because the lowest 12 bits from inst2 (inst2_imm12) + aren't known. We only know that the absolute address is + pc + AUIPC_imm20 + [-2048, +2047] but there is no way to + know the exact 4096-byte multiple (or 4096 * n + 2048): + there are always two possibilities because AUIPC copies + the 12 lowest bits from pc instead of zeroing them. + + NOTE: The sign-extension of inst2_imm12 adds a tiny bit + of extra complexity to AUIPC math in general but it's not + the reason for this problem. The sign-extension only changes + the relative position of the pc-relative 4096-byte window. + + (2) Matching AUIPC instruction alone requires only seven bits. + When the filter is applied to non-code data, that leads + to many false positives which make compression worse. + As long as most AUIPC+inst2 pairs appear as two consecutive + instructions, converting only such pairs gives better results. + + In assembly, AUIPC+inst2 tend to look like this: + + # Call: + auipc ra, 0x12345 + jalr ra, -42(ra) + + # Tail call: + auipc t1, 0x12345 + jalr zero, -42(t1) + + # Getting the absolute address: + auipc a0, 0x12345 + addi a0, a0, -42 + + # rd of inst2 isn't necessarily the same as rs1 even + # in cases where there is no reason to preserve rs1. + auipc a0, 0x12345 + addi a1, a0, -42 + + As of 2024, 16-bit instructions from the C extension don't + appear as inst2. The RISC-V psABI doesn't list AUIPC+C.* as + a linker relaxation type explicitly but it's not disallowed + either. Usefulness is limited as most of the time the lowest + 12 bits won't fit in a C instruction. This filter doesn't + support AUIPC+C.* combinations because this makes the filter + simpler, there are no test files, and it hopefully will never + be needed anyway. + + (Compare AUIPC to ARM64 where ADRP does set the lowest 12 bits + to zero. The paired instruction has the lowest 12 bits of the + absolute address as is in a zero-extended immediate. Thus the + ARM64 filter doesn't need to care about the instructions that + are paired with ADRP. An off-by-4096 issue can still occur if + the code section isn't aligned with the filter's start offset. + It's not a problem with standalone ELF files but Windows PE + files need start_offset=3072 for best results. Also, a .tar + stores files with 512-byte alignment so most of the time it + won't be the best for ARM64.) + +AUIPC with rd == x0 +------------------- + + AUIPC instructions with rd=x0 are reserved for HINTs in the base + instruction set. Such AUIPC instructions are never filtered. + + As of January 2024, it seems likely that AUIPC with rd=x0 will + be used for landing pads (pseudoinstruction LPAD). LPAD is used + to mark valid targets for indirect jumps (for JALR), for example, + beginnings of functions. The 20-bit immediate in LPAD instruction + is a label, not a pc-relative address. Thus it would be + counterproductive to convert AUIPC instructions with rd=x0. + + Often the next instruction after LPAD won't have rs1=x0 and thus + the filtering would be skipped for that reason alone. However, + it's not good to rely on this. For example, consider a function + that begins like this: + + int foo(int i) + { + if (i <= 234) { + ... + } + + A compiler may generate something like this: + + lpad 0x54321 + li a5, 234 + bgt a0, a5, .L2 + + Converting the pseudoinstructions to raw instructions: + + auipc x0, 0x54321 + addi x15, x0, 234 + blt x15, x10, .L2 + + In this case the filter would undesirably convert the AUIPC+ADDI + pair if the filter didn't explicitly skip AUIPC instructions + that have rd=x0. + +*/ + + +#include "simple_private.h" + + +// This checks two conditions at once: +// - AUIPC rd == inst2 rs1. +// - inst2 opcode has the lowest two bits set. +// +// The 8 bit left shift aligns the rd of AUIPC with the rs1 of inst2. +// By XORing the registers, any non-zero value in those bits indicates the +// registers are not equal and thus not an AUIPC pair. Subtracting 3 from +// inst2 will zero out the first two opcode bits only when they are set. +// The mask tests if any of the register or opcode bits are set (and thus +// not an AUIPC pair). +// +// Alternative expression: (((((auipc) << 8) ^ (inst2)) & 0xF8003) != 3) +#define NOT_AUIPC_PAIR(auipc, inst2) \ + ((((auipc) << 8) ^ ((inst2) - 3)) & 0xF8003) + +// This macro checks multiple conditions: +// (1) AUIPC rd [11:7] == x2 (special rd value). +// (2) AUIPC bits 12 and 13 set (the lowest two opcode bits of packed inst2). +// (3) inst2_rs1 doesn't equal x0 or x2 because the opposite +// conversion is only done when +// auipc_rd != x0 && +// auipc_rd != x2 && +// auipc_rd == inst2_rs1. +// +// The left-hand side takes care of (1) and (2). +// (a) The lowest 7 bits are already known to be AUIPC so subtracting 0x17 +// makes those bits zeros. +// (b) If AUIPC rd equals x2, subtracting 0x100 makes bits [11:7] zeros. +// If rd doesn't equal x2, then there will be at least one non-zero bit +// and the next step (c) is irrelevant. +// (c) If the lowest two opcode bits of the packed inst2 are set in [13:12], +// then subtracting 0x3000 will make those bits zeros. Otherwise there +// will be at least one non-zero bit. +// +// The shift by 18 removes the high bits from the final '>=' comparison and +// ensures that any non-zero result will be larger than any possible result +// from the right-hand side of the comparison. The cast ensures that the +// left-hand side didn't get promoted to a larger type than uint32_t. +// +// On the right-hand side, inst2_rs1 & 0x1D will be non-zero as long as +// inst2_rs1 is not x0 or x2. +// +// The final '>=' comparison will make the expression true if: +// - The subtraction caused any bits to be set (special AUIPC rd value not +// used or inst2 opcode bits not set). (non-zero >= non-zero or 0) +// - The subtraction did not cause any bits to be set but inst2_rs1 was +// x0 or x2. (0 >= 0) +#define NOT_SPECIAL_AUIPC(auipc, inst2_rs1) \ + ((uint32_t)(((auipc) - 0x3117) << 18) >= ((inst2_rs1) & 0x1D)) + + +// The encode and decode functions are split for this filter because of the +// AUIPC+inst2 filtering. This filter design allows a decoder-only +// implementation to be smaller than alternative designs. + +#ifdef HAVE_ENCODER_RISCV +static size_t +riscv_encode(void *simple lzma_attribute((__unused__)), + uint32_t now_pos, + bool is_encoder lzma_attribute((__unused__)), + uint8_t *buffer, size_t size) +{ + // Avoid using i + 8 <= size in the loop condition. + // + // NOTE: If there is a JAL in the last six bytes of the stream, it + // won't be converted. This is intentional to keep the code simpler. + if (size < 8) + return 0; + + size -= 8; + + size_t i; + + // The loop is advanced by 2 bytes every iteration since the + // instruction stream may include 16-bit instructions (C extension). + for (i = 0; i <= size; i += 2) { + uint32_t inst = buffer[i]; + + if (inst == 0xEF) { + // JAL + const uint32_t b1 = buffer[i + 1]; + + // Only filter rd=x1(ra) and rd=x5(t0). + if ((b1 & 0x0D) != 0) + continue; + + // The 20-bit immediate is in four pieces. + // The encoder stores it in big endian form + // since it improves compression slightly. + const uint32_t b2 = buffer[i + 2]; + const uint32_t b3 = buffer[i + 3]; + const uint32_t pc = now_pos + (uint32_t)i; + +// The following chart shows the highest three bytes of JAL, focusing on +// the 20-bit immediate field [31:12]. The first row of numbers is the +// bit position in a 32-bit little endian instruction. The second row of +// numbers shows the order of the immediate field in a J-type instruction. +// The last row is the bit number in each byte. +// +// To determine the amount to shift each bit, subtract the value in +// the last row from the value in the second last row. If the number +// is positive, shift left. If negative, shift right. +// +// For example, at the rightmost side of the chart, the bit 4 in b1 is +// the bit 12 of the address. Thus that bit needs to be shifted left +// by 12 - 4 = 8 bits to put it in the right place in the addr variable. +// +// NOTE: The immediate of a J-type instruction holds bits [20:1] of +// the address. The bit [0] is always 0 and not part of the immediate. +// +// | b3 | b2 | b1 | +// | 31 30 29 28 27 26 25 24 | 23 22 21 20 19 18 17 16 | 15 14 13 12 x x x x | +// | 20 10 9 8 7 6 5 4 | 3 2 1 11 19 18 17 16 | 15 14 13 12 x x x x | +// | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 4 x x x x | + + uint32_t addr = ((b1 & 0xF0) << 8) + | ((b2 & 0x0F) << 16) + | ((b2 & 0x10) << 7) + | ((b2 & 0xE0) >> 4) + | ((b3 & 0x7F) << 4) + | ((b3 & 0x80) << 13); + + addr += pc; + + buffer[i + 1] = (uint8_t)((b1 & 0x0F) + | ((addr >> 13) & 0xF0)); + + buffer[i + 2] = (uint8_t)(addr >> 9); + buffer[i + 3] = (uint8_t)(addr >> 1); + + // The "-2" is included because the for-loop will + // always increment by 2. In this case, we want to + // skip an extra 2 bytes since we used 4 bytes + // of input. + i += 4 - 2; + + } else if ((inst & 0x7F) == 0x17) { + // AUIPC + inst |= (uint32_t)buffer[i + 1] << 8; + inst |= (uint32_t)buffer[i + 2] << 16; + inst |= (uint32_t)buffer[i + 3] << 24; + + // Branch based on AUIPC's rd. The bitmask test does + // the same thing as this: + // + // const uint32_t auipc_rd = (inst >> 7) & 0x1F; + // if (auipc_rd != 0 && auipc_rd != 2) { + if (inst & 0xE80) { + // AUIPC's rd doesn't equal x0 or x2. + + // Check if AUIPC+inst2 are a pair. + uint32_t inst2 = read32le(buffer + i + 4); + + if (NOT_AUIPC_PAIR(inst, inst2)) { + // The NOT_AUIPC_PAIR macro allows + // a false AUIPC+AUIPC pair if the + // bits [19:15] (where rs1 would be) + // in the second AUIPC match the rd + // of the first AUIPC. + // + // We must skip enough forward so + // that the first two bytes of the + // second AUIPC cannot get converted. + // Such a conversion could make the + // current pair become a valid pair + // which would desync the decoder. + // + // Skipping six bytes is enough even + // though the above condition looks + // at the lowest four bits of the + // buffer[i + 6] too. This is safe + // because this filter never changes + // those bits if a conversion at + // that position is done. + i += 6 - 2; + continue; + } + + // Convert AUIPC+inst2 to a special format: + // + // - The lowest 7 bits [6:0] retain the + // AUIPC opcode. + // + // - The rd [11:7] is set to x2(sp). x2 is + // used as the stack pointer so AUIPC with + // rd=x2 should be very rare in real-world + // executables. + // + // - The remaining 20 bits [31:12] (that + // normally hold the pc-relative immediate) + // are used to store the lowest 20 bits of + // inst2. That is, the 12-bit immediate of + // inst2 is not included. + // + // - The location of the original inst2 is + // used to store the 32-bit absolute + // address in big endian format. Compared + // to the 20+12-bit split encoding, this + // results in a longer uninterrupted + // sequence of identical common bytes + // when the same address is referred + // with different instruction pairs + // (like AUIPC+LD vs. AUIPC+ADDI) or + // when the occurrences of the same + // pair use different registers. When + // referring to adjacent memory locations + // (like function calls that go via the + // ELF PLT), in big endian order only the + // last 1-2 bytes differ; in little endian + // the differing 1-2 bytes would be in the + // middle of the 8-byte sequence. + // + // When reversing the transformation, the + // original rd of AUIPC can be restored + // from inst2's rs1 as they are required to + // be the same. + + // Arithmetic right shift makes sign extension + // trivial but (1) it's implementation-defined + // behavior (C99/C11/C23 6.5.7-p5) and so is + // (2) casting unsigned to signed (6.3.1.3-p3). + // + // One can check for (1) with + // + // if ((-1 >> 1) == -1) ... + // + // but (2) has to be checked from the + // compiler docs. GCC promises that (1) + // and (2) behave in the common expected + // way and thus + // + // addr += (uint32_t)( + // (int32_t)inst2 >> 20); + // + // does the same as the code below. But since + // the 100 % portable way is only a few bytes + // bigger code and there is no real speed + // difference, let's just use that, especially + // since the decoder doesn't need this at all. + uint32_t addr = inst & 0xFFFFF000; + addr += (inst2 >> 20) + - ((inst2 >> 19) & 0x1000); + + addr += now_pos + (uint32_t)i; + + // Construct the first 32 bits: + // [6:0] AUIPC opcode + // [11:7] Special AUIPC rd = x2 + // [31:12] The lowest 20 bits of inst2 + inst = 0x17 | (2 << 7) | (inst2 << 12); + + write32le(buffer + i, inst); + + // The second 32 bits store the absolute + // address in big endian order. + write32be(buffer + i + 4, addr); + } else { + // AUIPC's rd equals x0 or x2. + // + // x0 indicates a landing pad (LPAD). + // It's always skipped. + // + // AUIPC with rd == x2 is used for the special + // format as explained above. When the input + // contains a byte sequence that matches the + // special format, "fake" decoding must be + // done to keep the filter bijective (that + // is, safe to apply on arbitrary data). + // + // See the "x0 or x2" section in riscv_decode() + // for how the "real" decoding is done. The + // "fake" decoding is a simplified version + // of "real" decoding with the following + // differences (these reduce code size of + // the decoder): + // (1) The lowest 12 bits aren't sign-extended. + // (2) No address conversion is done. + // (3) Big endian format isn't used (the fake + // address is in little endian order). + + // Check if inst matches the special format. + const uint32_t fake_rs1 = inst >> 27; + + if (NOT_SPECIAL_AUIPC(inst, fake_rs1)) { + i += 4 - 2; + continue; + } + + const uint32_t fake_addr = + read32le(buffer + i + 4); + + // Construct the second 32 bits: + // [19:0] Upper 20 bits from AUIPC + // [31:20] The lowest 12 bits of fake_addr + const uint32_t fake_inst2 = (inst >> 12) + | (fake_addr << 20); + + // Construct new first 32 bits from: + // [6:0] AUIPC opcode + // [11:7] Fake AUIPC rd = fake_rs1 + // [31:12] The highest 20 bits of fake_addr + inst = 0x17 | (fake_rs1 << 7) + | (fake_addr & 0xFFFFF000); + + write32le(buffer + i, inst); + write32le(buffer + i + 4, fake_inst2); + } + + i += 8 - 2; + } + } + + return i; +} + + +extern lzma_ret +lzma_simple_riscv_encoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters) +{ + return lzma_simple_coder_init(next, allocator, filters, + &riscv_encode, 0, 8, 2, true); +} +#endif + + +#ifdef HAVE_DECODER_RISCV +static size_t +riscv_decode(void *simple lzma_attribute((__unused__)), + uint32_t now_pos, + bool is_encoder lzma_attribute((__unused__)), + uint8_t *buffer, size_t size) +{ + if (size < 8) + return 0; + + size -= 8; + + size_t i; + for (i = 0; i <= size; i += 2) { + uint32_t inst = buffer[i]; + + if (inst == 0xEF) { + // JAL + const uint32_t b1 = buffer[i + 1]; + + // Only filter rd=x1(ra) and rd=x5(t0). + if ((b1 & 0x0D) != 0) + continue; + + const uint32_t b2 = buffer[i + 2]; + const uint32_t b3 = buffer[i + 3]; + const uint32_t pc = now_pos + (uint32_t)i; + +// | b3 | b2 | b1 | +// | 31 30 29 28 27 26 25 24 | 23 22 21 20 19 18 17 16 | 15 14 13 12 x x x x | +// | 20 10 9 8 7 6 5 4 | 3 2 1 11 19 18 17 16 | 15 14 13 12 x x x x | +// | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 4 x x x x | + + uint32_t addr = ((b1 & 0xF0) << 13) + | (b2 << 9) | (b3 << 1); + + addr -= pc; + + buffer[i + 1] = (uint8_t)((b1 & 0x0F) + | ((addr >> 8) & 0xF0)); + + buffer[i + 2] = (uint8_t)(((addr >> 16) & 0x0F) + | ((addr >> 7) & 0x10) + | ((addr << 4) & 0xE0)); + + buffer[i + 3] = (uint8_t)(((addr >> 4) & 0x7F) + | ((addr >> 13) & 0x80)); + + i += 4 - 2; + + } else if ((inst & 0x7F) == 0x17) { + // AUIPC + uint32_t inst2; + + inst |= (uint32_t)buffer[i + 1] << 8; + inst |= (uint32_t)buffer[i + 2] << 16; + inst |= (uint32_t)buffer[i + 3] << 24; + + if (inst & 0xE80) { + // AUIPC's rd doesn't equal x0 or x2. + + // Check if it is a "fake" AUIPC+inst2 pair. + inst2 = read32le(buffer + i + 4); + + if (NOT_AUIPC_PAIR(inst, inst2)) { + i += 6 - 2; + continue; + } + + // Decode (or more like re-encode) the "fake" + // pair. The "fake" format doesn't do + // sign-extension, address conversion, or + // use big endian. (The use of little endian + // allows sharing the write32le() calls in + // the decoder to reduce code size when + // unaligned access isn't supported.) + uint32_t addr = inst & 0xFFFFF000; + addr += inst2 >> 20; + + inst = 0x17 | (2 << 7) | (inst2 << 12); + inst2 = addr; + } else { + // AUIPC's rd equals x0 or x2. + + // Check if inst matches the special format + // used by the encoder. + const uint32_t inst2_rs1 = inst >> 27; + + if (NOT_SPECIAL_AUIPC(inst, inst2_rs1)) { + i += 4 - 2; + continue; + } + + // Decode the "real" pair. + uint32_t addr = read32be(buffer + i + 4); + + addr -= now_pos + (uint32_t)i; + + // The second instruction: + // - Get the lowest 20 bits from inst. + // - Add the lowest 12 bits of the address + // as the immediate field. + inst2 = (inst >> 12) | (addr << 20); + + // AUIPC: + // - rd is the same as inst2_rs1. + // - The sign extension of the lowest 12 bits + // must be taken into account. + inst = 0x17 | (inst2_rs1 << 7) + | ((addr + 0x800) & 0xFFFFF000); + } + + // Both decoder branches write in little endian order. + write32le(buffer + i, inst); + write32le(buffer + i + 4, inst2); + + i += 8 - 2; + } + } + + return i; +} + + +extern lzma_ret +lzma_simple_riscv_decoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters) +{ + return lzma_simple_coder_init(next, allocator, filters, + &riscv_decode, 0, 8, 2, false); +} +#endif diff --git a/Utilities/cmliblzma/liblzma/simple/simple_coder.c b/Utilities/cmliblzma/liblzma/simple/simple_coder.c index 4f499befe95..5cbfa822704 100644 --- a/Utilities/cmliblzma/liblzma/simple/simple_coder.c +++ b/Utilities/cmliblzma/liblzma/simple/simple_coder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file simple_coder.c @@ -8,9 +10,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_private.h" @@ -139,9 +138,11 @@ simple_code(void *coder_ptr, const lzma_allocator *allocator, return ret; } - // Filter out[]. + // Filter out[] unless there is nothing to filter. + // This way we avoid null pointer + 0 (undefined behavior) + // when out == NULL. const size_t size = *out_pos - out_start; - const size_t filtered = call_filter( + const size_t filtered = size == 0 ? 0 : call_filter( coder, out + out_start, size); const size_t unfiltered = size - filtered; diff --git a/Utilities/cmliblzma/liblzma/simple/simple_coder.h b/Utilities/cmliblzma/liblzma/simple/simple_coder.h index 19c2ee03aff..2b762d50071 100644 --- a/Utilities/cmliblzma/liblzma/simple/simple_coder.h +++ b/Utilities/cmliblzma/liblzma/simple/simple_coder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file simple_coder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_SIMPLE_CODER_H @@ -61,6 +60,15 @@ extern lzma_ret lzma_simple_armthumb_decoder_init(lzma_next_coder *next, const lzma_filter_info *filters); +extern lzma_ret lzma_simple_arm64_encoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters); + +extern lzma_ret lzma_simple_arm64_decoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters); + + extern lzma_ret lzma_simple_sparc_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter_info *filters); @@ -69,4 +77,13 @@ extern lzma_ret lzma_simple_sparc_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, const lzma_filter_info *filters); + +extern lzma_ret lzma_simple_riscv_encoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters); + +extern lzma_ret lzma_simple_riscv_decoder_init(lzma_next_coder *next, + const lzma_allocator *allocator, + const lzma_filter_info *filters); + #endif diff --git a/Utilities/cmliblzma/liblzma/simple/simple_decoder.c b/Utilities/cmliblzma/liblzma/simple/simple_decoder.c index dc4d2415110..d9820ee8ed2 100644 --- a/Utilities/cmliblzma/liblzma/simple/simple_decoder.c +++ b/Utilities/cmliblzma/liblzma/simple/simple_decoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file simple_decoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_decoder.h" diff --git a/Utilities/cmliblzma/liblzma/simple/simple_decoder.h b/Utilities/cmliblzma/liblzma/simple/simple_decoder.h index bed8d37a965..2ae87bb8632 100644 --- a/Utilities/cmliblzma/liblzma/simple/simple_decoder.h +++ b/Utilities/cmliblzma/liblzma/simple/simple_decoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file simple_decoder.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_SIMPLE_DECODER_H diff --git a/Utilities/cmliblzma/liblzma/simple/simple_encoder.c b/Utilities/cmliblzma/liblzma/simple/simple_encoder.c index d2cc03e58b8..d1f35096e2a 100644 --- a/Utilities/cmliblzma/liblzma/simple/simple_encoder.c +++ b/Utilities/cmliblzma/liblzma/simple/simple_encoder.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file simple_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_encoder.h" diff --git a/Utilities/cmliblzma/liblzma/simple/simple_encoder.h b/Utilities/cmliblzma/liblzma/simple/simple_encoder.h index 1cee4823a4e..bf5edbb1c3f 100644 --- a/Utilities/cmliblzma/liblzma/simple/simple_encoder.h +++ b/Utilities/cmliblzma/liblzma/simple/simple_encoder.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file simple_encoder.c @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_SIMPLE_ENCODER_H diff --git a/Utilities/cmliblzma/liblzma/simple/simple_private.h b/Utilities/cmliblzma/liblzma/simple/simple_private.h index 9d2c0fdd761..7aa360ff49e 100644 --- a/Utilities/cmliblzma/liblzma/simple/simple_private.h +++ b/Utilities/cmliblzma/liblzma/simple/simple_private.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file simple_private.h @@ -5,9 +7,6 @@ // // Author: Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #ifndef LZMA_SIMPLE_PRIVATE_H diff --git a/Utilities/cmliblzma/liblzma/simple/sparc.c b/Utilities/cmliblzma/liblzma/simple/sparc.c index 74b2655f36e..e8ad285a192 100644 --- a/Utilities/cmliblzma/liblzma/simple/sparc.c +++ b/Utilities/cmliblzma/liblzma/simple/sparc.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file sparc.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_private.h" @@ -65,6 +64,7 @@ sparc_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, } +#ifdef HAVE_ENCODER_SPARC extern lzma_ret lzma_simple_sparc_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -72,8 +72,10 @@ lzma_simple_sparc_encoder_init(lzma_next_coder *next, { return sparc_coder_init(next, allocator, filters, true); } +#endif +#ifdef HAVE_DECODER_SPARC extern lzma_ret lzma_simple_sparc_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -81,3 +83,4 @@ lzma_simple_sparc_decoder_init(lzma_next_coder *next, { return sparc_coder_init(next, allocator, filters, false); } +#endif diff --git a/Utilities/cmliblzma/liblzma/simple/x86.c b/Utilities/cmliblzma/liblzma/simple/x86.c index b38cebfd5e1..b579ac3f1f8 100644 --- a/Utilities/cmliblzma/liblzma/simple/x86.c +++ b/Utilities/cmliblzma/liblzma/simple/x86.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: 0BSD + /////////////////////////////////////////////////////////////////////////////// // /// \file x86.c @@ -6,9 +8,6 @@ // Authors: Igor Pavlov // Lasse Collin // -// This file has been put into the public domain. -// You can do whatever you want with this file. -// /////////////////////////////////////////////////////////////////////////////// #include "simple_private.h" @@ -27,11 +26,7 @@ static size_t x86_code(void *simple_ptr, uint32_t now_pos, bool is_encoder, uint8_t *buffer, size_t size) { - static const bool MASK_TO_ALLOWED_STATUS[8] - = { true, true, true, false, true, false, false, false }; - - static const uint32_t MASK_TO_BIT_NUMBER[8] - = { 0, 1, 2, 2, 3, 3, 3, 3 }; + static const uint32_t MASK_TO_BIT_NUMBER[5] = { 0, 1, 2, 2, 3 }; lzma_simple_x86 *simple = simple_ptr; uint32_t prev_mask = simple->prev_mask; @@ -68,9 +63,8 @@ x86_code(void *simple_ptr, uint32_t now_pos, bool is_encoder, b = buffer[buffer_pos + 4]; - if (Test86MSByte(b) - && MASK_TO_ALLOWED_STATUS[(prev_mask >> 1) & 0x7] - && (prev_mask >> 1) < 0x10) { + if (Test86MSByte(b) && (prev_mask >> 1) <= 4 + && (prev_mask >> 1) != 3) { uint32_t src = ((uint32_t)(b) << 24) | ((uint32_t)(buffer[buffer_pos + 3]) << 16) @@ -141,6 +135,7 @@ x86_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, } +#ifdef HAVE_ENCODER_X86 extern lzma_ret lzma_simple_x86_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -148,8 +143,10 @@ lzma_simple_x86_encoder_init(lzma_next_coder *next, { return x86_coder_init(next, allocator, filters, true); } +#endif +#ifdef HAVE_DECODER_X86 extern lzma_ret lzma_simple_x86_decoder_init(lzma_next_coder *next, const lzma_allocator *allocator, @@ -157,3 +154,4 @@ lzma_simple_x86_decoder_init(lzma_next_coder *next, { return x86_coder_init(next, allocator, filters, false); } +#endif diff --git a/Utilities/cmlibrhash/CMakeLists.txt b/Utilities/cmlibrhash/CMakeLists.txt index 9f532ad48a7..317c5f83962 100644 --- a/Utilities/cmlibrhash/CMakeLists.txt +++ b/Utilities/cmlibrhash/CMakeLists.txt @@ -28,6 +28,7 @@ set(librhash_sources librhash/sha512.c librhash/sha512.h librhash/ustd.h + librhash/util.c librhash/util.h ) @@ -36,5 +37,6 @@ include_directories( ) add_library(cmlibrhash ${librhash_sources}) +target_compile_definitions(cmlibrhash PRIVATE NO_IMPORT_EXPORT) install(FILES COPYING DESTINATION ${CMAKE_DOC_DIR}/cmlibrhash) diff --git a/Utilities/cmlibrhash/librhash/algorithms.c b/Utilities/cmlibrhash/librhash/algorithms.c index cdd40533342..08e8e4ee4ad 100644 --- a/Utilities/cmlibrhash/librhash/algorithms.c +++ b/Utilities/cmlibrhash/librhash/algorithms.c @@ -14,16 +14,15 @@ * PERFORMANCE OF THIS SOFTWARE. */ -#include -#include - +#include "algorithms.h" #include "byte_order.h" #include "rhash.h" -#include "algorithms.h" -/* header files of all supported hash sums */ +/* header files of all supported hash functions */ #if 0 #include "aich.h" +#include "blake2b.h" +#include "blake2s.h" #include "crc32.h" #include "ed2k.h" #include "edonr.h" @@ -47,6 +46,11 @@ #include "whirlpool.h" #endif +#ifdef USE_OPENSSL +# include "plug_openssl.h" +#endif /* USE_OPENSSL */ +#include + #ifdef USE_OPENSSL /* note: BTIH and AICH depends on the used SHA1 algorithm */ # define NEED_OPENSSL_INIT (RHASH_MD4 | RHASH_MD5 | \ @@ -55,6 +59,7 @@ #else # define NEED_OPENSSL_INIT 0 #endif /* USE_OPENSSL */ + #ifdef GENERATE_GOST94_LOOKUP_TABLE # define NEED_GOST94_INIT (RHASH_GOST94 | RHASH_GOST94_CRYPTOPRO) #else @@ -85,10 +90,10 @@ rhash_info info_md5 = { RHASH_MD5, F_LE32, 16, "MD5", "md5" }; rhash_info info_sha1 = { RHASH_SHA1, F_BE32, 20, "SHA1", "sha1" }; #if 0 rhash_info info_tiger = { RHASH_TIGER, F_LE64, 24, "TIGER", "tiger" }; -rhash_info info_tth = { RHASH_TTH, F_BS32, 24, "TTH", "tree:tiger" }; -rhash_info info_btih = { RHASH_BTIH, 0, 20, "BTIH", "btih" }; +rhash_info info_tth = { RHASH_TTH, F_BS32 | F_SPCEXP, 24, "TTH", "tree:tiger" }; +rhash_info info_btih = { RHASH_BTIH, F_SPCEXP, 20, "BTIH", "btih" }; rhash_info info_ed2k = { RHASH_ED2K, F_LE32, 16, "ED2K", "ed2k" }; -rhash_info info_aich = { RHASH_AICH, F_BS32, 20, "AICH", "aich" }; +rhash_info info_aich = { RHASH_AICH, F_BS32 | F_SPCEXP, 20, "AICH", "aich" }; rhash_info info_whirlpool = { RHASH_WHIRLPOOL, F_BE64, 64, "WHIRLPOOL", "whirlpool" }; rhash_info info_rmd160 = { RHASH_RIPEMD160, F_LE32, 20, "RIPEMD-160", "ripemd160" }; rhash_info info_gost12_256 = { RHASH_GOST12_256, F_LE64, 32, "GOST12-256", "gost12-256" }; @@ -106,6 +111,8 @@ rhash_info info_sha512 = { RHASH_SHA512, F_BE64, 64, "SHA-512", "sha512" }; #if 0 rhash_info info_edr256 = { RHASH_EDONR256, F_LE32, 32, "EDON-R256", "edon-r256" }; rhash_info info_edr512 = { RHASH_EDONR512, F_LE64, 64, "EDON-R512", "edon-r512" }; +rhash_info info_blake2s = { RHASH_BLAKE2S, F_LE32, 32, "BLAKE2S", "blake2s" }; +rhash_info info_blake2b = { RHASH_BLAKE2B, F_LE64, 64, "BLAKE2B", "blake2b" }; #endif rhash_info info_sha3_224 = { RHASH_SHA3_224, F_LE64, 28, "SHA3-224", "sha3-224" }; rhash_info info_sha3_256 = { RHASH_SHA3_256, F_LE64, 32, "SHA3-256", "sha3-256" }; @@ -113,14 +120,13 @@ rhash_info info_sha3_384 = { RHASH_SHA3_384, F_LE64, 48, "SHA3-384", "sha3-384" rhash_info info_sha3_512 = { RHASH_SHA3_512, F_LE64, 64, "SHA3-512", "sha3-512" }; /* some helper macros */ -#define dgshft(name) (((char*)&((name##_ctx*)0)->hash) - (char*)0) -#define dgshft2(name, field) (((char*)&((name##_ctx*)0)->field) - (char*)0) +#define dgshft(name) ((uintptr_t)((char*)&((name##_ctx*)0)->hash)) +#define dgshft2(name, field) ((uintptr_t)((char*)&((name##_ctx*)0)->field)) #define ini(name) ((pinit_t)(name##_init)) #define upd(name) ((pupdate_t)(name##_update)) #define fin(name) ((pfinal_t)(name##_final)) #define iuf(name) ini(name), upd(name), fin(name) #define iuf2(name1, name2) ini(name1), upd(name2), fin(name2) -#define diuf(name) dgshft(name), ini(name), upd(name), fin(name) /* information about all supported hash functions */ rhash_hash_info rhash_hash_info_default[RHASH_HASH_COUNT] = @@ -160,6 +166,8 @@ rhash_hash_info rhash_hash_info_default[RHASH_HASH_COUNT] = { &info_crc32c, sizeof(uint32_t), 0, iuf(rhash_crc32c), 0 }, /* 32 bit */ { &info_snf128, sizeof(snefru_ctx), dgshft(snefru), iuf2(rhash_snefru128, rhash_snefru), 0 }, /* 128 bit */ { &info_snf256, sizeof(snefru_ctx), dgshft(snefru), iuf2(rhash_snefru256, rhash_snefru), 0 }, /* 256 bit */ + { &info_blake2s, sizeof(blake2s_ctx), dgshft(blake2s), iuf(rhash_blake2s), 0 }, /* 256 bit */ + { &info_blake2b, sizeof(blake2b_ctx), dgshft(blake2b), iuf(rhash_blake2b), 0 }, /* 512 bit */ #endif }; @@ -280,3 +288,76 @@ static void rhash_crc32c_final(uint32_t* crc32c, unsigned char* result) #endif } #endif + +#if !defined(NO_IMPORT_EXPORT) +/** + * Export a hash function context to a memory region, + * or calculate the size required for context export. + * + * @param hash_id identifier of the hash function + * @param ctx the algorithm context containing current hashing state + * @param out pointer to the memory region or NULL + * @param size size of memory region + * @return the size of the exported data on success, 0 on fail. + */ +size_t rhash_export_alg(unsigned hash_id, const void* ctx, void* out, size_t size) +{ + switch (hash_id) + { + case RHASH_TTH: + return rhash_tth_export((const tth_ctx*)ctx, out, size); + case RHASH_AICH: + return rhash_aich_export((const aich_ctx*)ctx, out, size); + } + return 0; +} + +/** + * Import a hash function context from a memory region. + * + * @param hash_id identifier of the hash function + * @param ctx pointer to the algorithm context + * @param in pointer to the data to import + * @param size size of data to import + * @return the size of the imported data on success, 0 on fail. + */ +size_t rhash_import_alg(unsigned hash_id, void* ctx, const void* in, size_t size) +{ + switch (hash_id) + { + case RHASH_TTH: + return rhash_tth_import((tth_ctx*)ctx, in, size); + case RHASH_AICH: + return rhash_aich_import((aich_ctx*)ctx, in, size); + } + return 0; +} +#endif /* !defined(NO_IMPORT_EXPORT) */ + +#ifdef USE_OPENSSL +void rhash_load_sha1_methods(rhash_hashing_methods* methods, int methods_type) +{ + int use_openssl; + switch (methods_type) { + case METHODS_OPENSSL: + use_openssl = 1; + break; + case METHODS_SELECTED: + assert(rhash_info_table[3].info->hash_id == RHASH_SHA1); + use_openssl = ARE_OPENSSL_METHODS(rhash_info_table[3]); + break; + default: + use_openssl = 0; + break; + } + if (use_openssl) { + methods->init = rhash_ossl_sha1_init(); + methods->update = rhash_ossl_sha1_update(); + methods->final = rhash_ossl_sha1_final(); + } else { + methods->init = (pinit_t)&rhash_sha1_init; + methods->update = (pupdate_t)&rhash_sha1_update; + methods->final = (pfinal_t)&rhash_sha1_final; + } +} +#endif diff --git a/Utilities/cmlibrhash/librhash/algorithms.h b/Utilities/cmlibrhash/librhash/algorithms.h index 01dda8868ed..510b2a64fd7 100644 --- a/Utilities/cmlibrhash/librhash/algorithms.h +++ b/Utilities/cmlibrhash/librhash/algorithms.h @@ -47,10 +47,10 @@ typedef struct rhash_info const char* magnet_name; } rhash_info; -typedef void (*pinit_t)(void*); +typedef void (*pinit_t)(void* ctx); typedef void (*pupdate_t)(void* ctx, const void* msg, size_t size); -typedef void (*pfinal_t)(void*, unsigned char*); -typedef void (*pcleanup_t)(void*); +typedef void (*pfinal_t)(void* ctx, unsigned char* result); +typedef void (*pcleanup_t)(void* ctx); /** * Information about a hash function @@ -83,11 +83,11 @@ typedef struct rhash_context_ext struct rhash_context rc; unsigned hash_vector_size; /* number of contained hash sums */ unsigned flags; - unsigned state; - void* callback; + volatile unsigned state; + rhash_callback_t callback; void* callback_data; void* bt_ctx; - rhash_vector_item vector[1]; /* contexts of contained hash sums */ + rhash_vector_item vector[]; /* contexts of contained hash sums */ } rhash_context_ext; extern rhash_hash_info rhash_hash_info_default[RHASH_HASH_COUNT]; @@ -125,8 +125,9 @@ extern rhash_info info_edr512; /* rhash_info flags */ #define F_BS32 1 /* default output in base32 */ -#define F_SWAP32 2 /* Big endian flag */ +#define F_SWAP32 2 /* big endian flag */ #define F_SWAP64 4 +#define F_SPCEXP 8 /* needs special import/export logic */ /* define endianness flags */ #if IS_LITTLE_ENDIAN @@ -144,10 +145,35 @@ extern rhash_info info_edr512; void rhash_init_algorithms(unsigned mask); const rhash_info* rhash_info_by_id(unsigned hash_id); /* get hash sum info by hash id */ +#if !defined(NO_IMPORT_EXPORT) +size_t rhash_export_alg(unsigned hash_id, const void* ctx, void* out, size_t size); +size_t rhash_import_alg(unsigned hash_id, void* ctx, const void* in, size_t size); +#endif /* !defined(NO_IMPORT_EXPORT) */ + #if defined(OPENSSL_RUNTIME) && !defined(USE_OPENSSL) # define USE_OPENSSL #endif +#ifdef USE_OPENSSL +typedef struct rhash_hashing_methods +{ + pinit_t init; + pupdate_t update; + pfinal_t final; +} rhash_hashing_methods; + +enum rhash_methods_type +{ + METHODS_RHASH, + METHODS_OPENSSL, + METHODS_SELECTED, +}; + +void rhash_load_sha1_methods(rhash_hashing_methods* methods, int methods_type); + +#define ARE_OPENSSL_METHODS(methods) ((methods).init != (void (*)(void*))&rhash_sha1_init) +#endif + #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ diff --git a/Utilities/cmlibrhash/librhash/byte_order.c b/Utilities/cmlibrhash/librhash/byte_order.c index de2c583b59d..7a05408beaa 100644 --- a/Utilities/cmlibrhash/librhash/byte_order.c +++ b/Utilities/cmlibrhash/librhash/byte_order.c @@ -74,7 +74,7 @@ unsigned rhash_ctz(unsigned x) void rhash_swap_copy_str_to_u32(void* to, int index, const void* from, size_t length) { /* if all pointers and length are 32-bits aligned */ - if ( 0 == (( (int)((char*)to - (char*)0) | ((char*)from - (char*)0) | index | length ) & 3) ) { + if ( 0 == (( (uintptr_t)to | (uintptr_t)from | (uintptr_t)index | length ) & 3) ) { /* copy memory as 32-bit words */ const uint32_t* src = (const uint32_t*)from; const uint32_t* end = (const uint32_t*)((const char*)src + length); @@ -101,7 +101,7 @@ void rhash_swap_copy_str_to_u32(void* to, int index, const void* from, size_t le void rhash_swap_copy_str_to_u64(void* to, int index, const void* from, size_t length) { /* if all pointers and length are 64-bits aligned */ - if ( 0 == (( (int)((char*)to - (char*)0) | ((char*)from - (char*)0) | index | length ) & 7) ) { + if ( 0 == (( (uintptr_t)to | (uintptr_t)from | (uintptr_t)index | length ) & 7) ) { /* copy aligned memory block as 64-bit integers */ const uint64_t* src = (const uint64_t*)from; const uint64_t* end = (const uint64_t*)((const char*)src + length); @@ -124,7 +124,7 @@ void rhash_swap_copy_str_to_u64(void* to, int index, const void* from, size_t le void rhash_swap_copy_u64_to_str(void* to, const void* from, size_t length) { /* if all pointers and length are 64-bits aligned */ - if ( 0 == (( (int)((char*)to - (char*)0) | ((char*)from - (char*)0) | length ) & 7) ) { + if ( 0 == (( (uintptr_t)to | (uintptr_t)from | length ) & 7) ) { /* copy aligned memory block as 64-bit integers */ const uint64_t* src = (const uint64_t*)from; const uint64_t* end = (const uint64_t*)((const char*)src + length); diff --git a/Utilities/cmlibrhash/librhash/byte_order.h b/Utilities/cmlibrhash/librhash/byte_order.h index cfb9e25e35f..73863e067f6 100644 --- a/Utilities/cmlibrhash/librhash/byte_order.h +++ b/Utilities/cmlibrhash/librhash/byte_order.h @@ -76,14 +76,15 @@ extern "C" { #ifdef RHASH_BYTE_ORDER #elif defined(CPU_IA32) || defined(CPU_X64) || defined(__ia64) || defined(__ia64__) || \ defined(__alpha__) || defined(_M_ALPHA) || defined(vax) || defined(MIPSEL) || \ - defined(_ARM_) || defined(__arm__) + defined(_ARM_) || defined(__arm__) || defined(_M_ARM64) || defined(_M_ARM64EC) || \ + defined(__loongarch64) # define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_LE #elif defined(__sparc) || defined(__sparc__) || defined(sparc) || \ defined(_ARCH_PPC) || defined(_ARCH_PPC64) || defined(_POWER) || \ defined(__POWERPC__) || defined(POWERPC) || defined(__powerpc) || \ defined(__powerpc__) || defined(__powerpc64__) || defined(__ppc__) || \ defined(__hpux) || defined(_MIPSEB) || defined(mc68000) || \ - defined(__s390__) || defined(__s390x__) || defined(sel) + defined(__s390__) || defined(__s390x__) || defined(sel) || defined(__hppa__) # define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_BE #else # error "Can't detect CPU architechture" @@ -97,8 +98,8 @@ extern "C" { # define __has_builtin(x) 0 #endif -#define IS_ALIGNED_32(p) (0 == (3 & ((const char*)(p) - (const char*)0))) -#define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) +#define IS_ALIGNED_32(p) (0 == (3 & (uintptr_t)(p))) +#define IS_ALIGNED_64(p) (0 == (7 & (uintptr_t)(p))) #if defined(_MSC_VER) #define ALIGN_ATTR(n) __declspec(align(n)) @@ -179,9 +180,9 @@ static RHASH_INLINE uint64_t bswap_64(uint64_t x) # define le2me_32(x) bswap_32(x) # define le2me_64(x) bswap_64(x) -# define be32_copy(to, index, from, length) memcpy((to) + (index), (from), (length)) +# define be32_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) # define le32_copy(to, index, from, length) rhash_swap_copy_str_to_u32((to), (index), (from), (length)) -# define be64_copy(to, index, from, length) memcpy((to) + (index), (from), (length)) +# define be64_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) # define le64_copy(to, index, from, length) rhash_swap_copy_str_to_u64((to), (index), (from), (length)) # define me64_to_be_str(to, from, length) memcpy((to), (from), (length)) # define me64_to_le_str(to, from, length) rhash_swap_copy_u64_to_str((to), (from), (length)) @@ -193,9 +194,9 @@ static RHASH_INLINE uint64_t bswap_64(uint64_t x) # define le2me_64(x) (x) # define be32_copy(to, index, from, length) rhash_swap_copy_str_to_u32((to), (index), (from), (length)) -# define le32_copy(to, index, from, length) memcpy((to) + (index), (from), (length)) +# define le32_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) # define be64_copy(to, index, from, length) rhash_swap_copy_str_to_u64((to), (index), (from), (length)) -# define le64_copy(to, index, from, length) memcpy((to) + (index), (from), (length)) +# define le64_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) # define me64_to_be_str(to, from, length) rhash_swap_copy_u64_to_str((to), (from), (length)) # define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) #endif /* IS_BIG_ENDIAN */ diff --git a/Utilities/cmlibrhash/librhash/hex.c b/Utilities/cmlibrhash/librhash/hex.c index cfd5892b963..40c2089a76c 100644 --- a/Utilities/cmlibrhash/librhash/hex.c +++ b/Utilities/cmlibrhash/librhash/hex.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ #include "hex.h" -#include +#include "util.h" #include #include @@ -113,8 +113,8 @@ size_t rhash_base64_url_encoded_helper(char* dst, const unsigned char* src, size #ifdef __clang_analyzer__ memset(buffer, 0, sizeof(buffer)); #endif - assert((BASE64_LENGTH(B64_CHUNK_SIZE) + 4) <= sizeof(buffer)); - assert((B64_CHUNK_SIZE % 6) == 0); + RHASH_ASSERT((BASE64_LENGTH(B64_CHUNK_SIZE) + 4) <= sizeof(buffer)); + RHASH_ASSERT((B64_CHUNK_SIZE % 6) == 0); if (url_encode) { size_t result_length = 0; for (; length > 0; src += B64_CHUNK_SIZE) { diff --git a/Utilities/cmlibrhash/librhash/md5.c b/Utilities/cmlibrhash/librhash/md5.c index 9b768220f8a..f989e620c17 100644 --- a/Utilities/cmlibrhash/librhash/md5.c +++ b/Utilities/cmlibrhash/librhash/md5.c @@ -19,13 +19,13 @@ #include "md5.h" /** - * Initialize context before calculaing hash. + * Initialize context before calculating hash. * * @param ctx context to initialize */ void rhash_md5_init(md5_ctx* ctx) { - ctx->length = 0; + memset(ctx, 0, sizeof(*ctx)); /* initialize state */ ctx->hash[0] = 0x67452301; @@ -170,7 +170,7 @@ void rhash_md5_update(md5_ctx* ctx, const unsigned char* msg, size_t size) /* fill partial block */ if (index) { unsigned left = md5_block_size - index; - le32_copy((char*)ctx->message, index, msg, (size < left ? size : left)); + le32_copy(ctx->message, index, msg, (size < left ? size : left)); if (size < left) return; /* process partial block */ diff --git a/Utilities/cmlibrhash/librhash/md5.h b/Utilities/cmlibrhash/librhash/md5.h index 12a6b527867..1f6c62573a1 100644 --- a/Utilities/cmlibrhash/librhash/md5.h +++ b/Utilities/cmlibrhash/librhash/md5.h @@ -22,7 +22,7 @@ typedef struct md5_ctx void rhash_md5_init(md5_ctx* ctx); void rhash_md5_update(md5_ctx* ctx, const unsigned char* msg, size_t size); -void rhash_md5_final(md5_ctx* ctx, unsigned char result[16]); +void rhash_md5_final(md5_ctx* ctx, unsigned char* result); #ifdef __cplusplus } /* extern "C" */ diff --git a/Utilities/cmlibrhash/librhash/rhash.c b/Utilities/cmlibrhash/librhash/rhash.c index 25301125a53..4e60c213bf9 100644 --- a/Utilities/cmlibrhash/librhash/rhash.c +++ b/Utilities/cmlibrhash/librhash/rhash.c @@ -38,15 +38,24 @@ #include #define STATE_ACTIVE 0xb01dbabe -#define STATE_STOPED 0xdeadbeef +#define STATE_STOPPED 0xdeadbeef #define STATE_DELETED 0xdecea5ed +#define IS_BAD_STATE(s) ((s) != STATE_ACTIVE && (s) != STATE_STOPPED) #define RCTX_AUTO_FINAL 0x1 #define RCTX_FINALIZED 0x2 #define RCTX_FINALIZED_MASK (RCTX_AUTO_FINAL | RCTX_FINALIZED) #define RHPR_FORMAT (RHPR_RAW | RHPR_HEX | RHPR_BASE32 | RHPR_BASE64) #define RHPR_MODIFIER (RHPR_UPPERCASE | RHPR_URLENCODE | RHPR_REVERSE) -void rhash_library_init(void) +#define HAS_ZERO_OR_ONE_BIT(id) (((id) & ((id) - 1)) == 0) +#define IS_VALID_HASH_MASK(bitmask) ((bitmask) != 0 && ((bitmask) & ~RHASH_ALL_HASHES) == 0) +#define IS_VALID_HASH_ID(id) (IS_VALID_HASH_MASK(id) && HAS_ZERO_OR_ONE_BIT(id)) + +/* each hash function context must be aligned to DEFAULT_ALIGNMENT bytes */ +#define GET_CTX_ALIGNED(size) ALIGN_SIZE_BY((size), DEFAULT_ALIGNMENT) +#define GET_EXPORT_ALIGNED(size) ALIGN_SIZE_BY((size), 8) + +RHASH_API void rhash_library_init(void) { rhash_init_algorithms(RHASH_ALL_HASHES); #ifdef USE_OPENSSL @@ -54,103 +63,120 @@ void rhash_library_init(void) #endif } -int RHASH_API rhash_count(void) +RHASH_API int rhash_count(void) { return rhash_info_size; } /* LOW-LEVEL LIBRHASH INTERFACE */ -RHASH_API rhash rhash_init(unsigned hash_id) +/** + * Allocate and initialize RHash context for calculating a single or multiple hash functions. + * The context after usage must be freed by calling rhash_free(). + * + * @param count the size of the hash_ids array, the count must be greater than zero + * @param hash_ids array of identifiers of hash functions. Each element must + * be an identifier of one hash function + * @param need_init initialize context for each hash function + * @return initialized rhash context, NULL on fail with error code stored in errno + */ +static rhash_context_ext* rhash_alloc_multi(size_t count, const unsigned hash_ids[], int need_init) { - unsigned tail_bit_index; /* index of hash_id trailing bit */ - unsigned num = 0; /* number of hashes to compute */ + struct rhash_hash_info* info; /* hash algorithm information */ rhash_context_ext* rctx = NULL; /* allocated rhash context */ - size_t hash_size_sum = 0; /* size of hash contexts to store in rctx */ - - unsigned i, bit_index, id; - struct rhash_hash_info* info; - size_t aligned_size; + const size_t header_size = GET_CTX_ALIGNED(sizeof(rhash_context_ext) + sizeof(rhash_vector_item) * count); + size_t ctx_size_sum = 0; /* size of hash contexts to store in rctx */ + size_t i; char* phash_ctx; + unsigned hash_bitmask = 0; - hash_id &= RHASH_ALL_HASHES; - if (hash_id == 0) { + if (count < 1) { errno = EINVAL; return NULL; } - - tail_bit_index = rhash_ctz(hash_id); /* get trailing bit index */ - assert(tail_bit_index < RHASH_HASH_COUNT); - - id = 1 << tail_bit_index; - - if (hash_id == id) { - /* handle the most common case of only one hash */ - num = 1; - info = &rhash_info_table[tail_bit_index]; - hash_size_sum = info->context_size; - } else { - /* another case: hash_id contains several hashes */ - for (bit_index = tail_bit_index; id <= hash_id; bit_index++, id = id << 1) { - assert(id != 0); - assert(bit_index < RHASH_HASH_COUNT); - info = &rhash_info_table[bit_index]; - if (hash_id & id) { - /* align sizes by 8 bytes */ - aligned_size = (info->context_size + 7) & ~7; - hash_size_sum += aligned_size; - num++; - } + for (i = 0; i < count; i++) { + unsigned hash_index; + if (!IS_VALID_HASH_ID(hash_ids[i])) { + errno = EINVAL; + return NULL; } - assert(num > 1); - } + hash_bitmask |= hash_ids[i]; + hash_index = rhash_ctz(hash_ids[i]); + assert(hash_index < RHASH_HASH_COUNT); /* correct until extended hash_ids are supported */ + info = &rhash_info_table[hash_index]; - /* align the size of the rhash context common part */ - aligned_size = ((offsetof(rhash_context_ext, vector) + sizeof(rhash_vector_item) * num) + 7) & ~7; - assert(aligned_size >= sizeof(rhash_context_ext)); + /* align context sizes and sum up */ + ctx_size_sum += GET_CTX_ALIGNED(info->context_size); + } - /* allocate rhash context with enough memory to store contexts of all used hashes */ - rctx = (rhash_context_ext*)malloc(aligned_size + hash_size_sum); - if (rctx == NULL) return NULL; + /* allocate rhash context with enough memory to store contexts of all selected hash functions */ + rctx = (rhash_context_ext*)rhash_aligned_alloc(DEFAULT_ALIGNMENT, header_size + ctx_size_sum); + if (rctx == NULL) + return NULL; /* initialize common fields of the rhash context */ - memset(rctx, 0, sizeof(rhash_context_ext)); - rctx->rc.hash_id = hash_id; + memset(rctx, 0, header_size); + rctx->rc.hash_id = hash_bitmask; rctx->flags = RCTX_AUTO_FINAL; /* turn on auto-final by default */ rctx->state = STATE_ACTIVE; - rctx->hash_vector_size = num; - - /* aligned hash contexts follows rctx->vector[num] in the same memory block */ - phash_ctx = (char*)rctx + aligned_size; - assert(phash_ctx >= (char*)&rctx->vector[num]); - - /* initialize context for every hash in a loop */ - for (bit_index = tail_bit_index, id = 1 << tail_bit_index, i = 0; - id <= hash_id; bit_index++, id = id << 1) - { - /* check if a hash function with given id shall be included into rctx */ - if ((hash_id & id) != 0) { - info = &rhash_info_table[bit_index]; - assert(info->context_size > 0); - assert(((phash_ctx - (char*)0) & 7) == 0); /* hash context is aligned */ - assert(info->init != NULL); - - rctx->vector[i].hash_info = info; - rctx->vector[i].context = phash_ctx; + rctx->hash_vector_size = count; + + /* calculate aligned pointer >= (&rctx->vector[count]) */ + phash_ctx = (char*)rctx + header_size; + assert(phash_ctx >= (char*)&rctx->vector[count]); + assert(phash_ctx < ((char*)&rctx->vector[count] + DEFAULT_ALIGNMENT)); + + for (i = 0; i < count; i++) { + unsigned hash_index = rhash_ctz(hash_ids[i]); + info = &rhash_info_table[hash_index]; + assert(info->context_size > 0); + assert(info->init != NULL); + assert(IS_PTR_ALIGNED_BY(phash_ctx, DEFAULT_ALIGNMENT)); /* hash context is aligned */ + + rctx->vector[i].hash_info = info; + rctx->vector[i].context = phash_ctx; #if 0 - /* BTIH initialization is complex, save pointer for later */ - if ((id & RHASH_BTIH) != 0) rctx->bt_ctx = phash_ctx; + /* BTIH initialization is a bit complicated, so store the context pointer for later usage */ + if ((hash_ids[i] & RHASH_BTIH) != 0) + rctx->bt_ctx = phash_ctx; #endif - phash_ctx += (info->context_size + 7) & ~7; + phash_ctx += GET_CTX_ALIGNED(info->context_size); - /* initialize the i-th hash context */ + /* initialize the i-th hash context */ + if (need_init) info->init(rctx->vector[i].context); - i++; - } } + return rctx; +} - return &rctx->rc; /* return allocated and initialized rhash context */ +RHASH_API rhash rhash_init_multi(size_t count, const unsigned hash_ids[]) +{ + rhash_context_ext* ectx = rhash_alloc_multi(count, hash_ids, 1); + return &ectx->rc; /* return initialized rhash context */ +} + +RHASH_API rhash rhash_init(unsigned hash_id) +{ + if (!IS_VALID_HASH_MASK(hash_id)) { + errno = EINVAL; + return NULL; + } + if (HAS_ZERO_OR_ONE_BIT(hash_id)) { + return rhash_init_multi(1, &hash_id); + } else { + /* handle the depricated case, when hash_id is a bitwise union of several hash function identifiers */ + size_t count; + unsigned hash_ids[32]; + unsigned id = hash_id & -hash_id; /* get the trailing bit */ + for (count = 0; id <= hash_id; id = id << 1) { + assert(id != 0); + if (hash_id & id) + hash_ids[count++] = id; + } + assert(count > 1); + return rhash_init_multi(count, hash_ids); + } } void rhash_free(rhash ctx) @@ -159,7 +185,6 @@ void rhash_free(rhash ctx) unsigned i; if (ctx == 0) return; - assert(ectx->hash_vector_size <= RHASH_HASH_COUNT); ectx->state = STATE_DELETED; /* mark memory block as being removed */ /* clean the hash functions, which require additional clean up */ @@ -169,8 +194,7 @@ void rhash_free(rhash ctx) info->cleanup(ectx->vector[i].context); } } - - free(ectx); + rhash_aligned_free(ectx); } RHASH_API void rhash_reset(rhash ctx) @@ -238,6 +262,161 @@ RHASH_API int rhash_final(rhash ctx, unsigned char* first_result) return 0; /* no error processing at the moment */ } +/** + * Header block for rhash context import/export. + */ +typedef struct export_header +{ + uint32_t state; + uint16_t hash_vector_size; + uint16_t flags; + uint64_t msg_size; +} export_header; + +/** + * Process export error. Returns 0 and set errno to EINVAL. + * + * @return NULL + */ +static size_t export_error_einval(void) +{ + errno = EINVAL; + return 0; +} + +/** + * Process import error. Returns NULL and set errno to EINVAL. + * + * @return NULL + */ +static rhash import_error_einval(void) +{ + errno = EINVAL; + return NULL; +} + +RHASH_API size_t rhash_export(rhash ctx, void* out, size_t size) +{ +#if !defined(NO_IMPORT_EXPORT) + size_t export_size; + size_t i; + rhash_context_ext* const ectx = (rhash_context_ext*)ctx; + export_header* header = (export_header*)out; + unsigned* hash_ids = NULL; + if (!ctx || (out && size < sizeof(export_header)) || IS_BAD_STATE(ectx->state)) + return export_error_einval(); + export_size = sizeof(export_header) + sizeof(unsigned) * ectx->hash_vector_size; + if (out != NULL) { + memset(out, 0, size); + header->state = ectx->state; + header->hash_vector_size = (uint16_t)(ectx->hash_vector_size); + header->flags = (uint16_t)(ectx->flags); + header->msg_size = ctx->msg_size; + hash_ids = (unsigned*)(void*)(header + 1); + } + for (i = 0; i < ectx->hash_vector_size; i++) { + void* src_context = ectx->vector[i].context; + struct rhash_hash_info* hash_info = ectx->vector[i].hash_info; + unsigned is_special = (hash_info->info->flags & F_SPCEXP); + size_t item_size; + if (out != NULL) { + if (size <= export_size) + return export_error_einval(); + hash_ids[i] = hash_info->info->hash_id; + if (is_special) { + char* dst_item; + size_t left_size; + export_size = GET_EXPORT_ALIGNED(export_size); + dst_item = (char*)out + export_size; + left_size = size - export_size; + item_size = rhash_export_alg(hash_info->info->hash_id, + src_context, dst_item, left_size); + if (!item_size) + return export_error_einval(); + } else { + char* dst_item = (char*)out + export_size; + item_size = hash_info->context_size; + if (size < (export_size + item_size)) + return export_error_einval(); + memcpy(dst_item, src_context, item_size); + } + } else { + if (is_special) { + export_size = GET_EXPORT_ALIGNED(export_size); + item_size = rhash_export_alg( + hash_info->info->hash_id, src_context, NULL, 0); + } else + item_size = hash_info->context_size; + } + export_size += item_size; + } + if (export_size < size) + return export_error_einval(); + return export_size; +#else + return export_error_einval(); +#endif /* !defined(NO_IMPORT_EXPORT) */ +} + +RHASH_API rhash rhash_import(const void* in, size_t size) +{ +#if !defined(NO_IMPORT_EXPORT) + const export_header* header = (const export_header*)in; + size_t i; + size_t imported_size; + const unsigned* hash_ids; + const char* src_item; + rhash_context_ext* ectx; + if (!header || IS_BAD_STATE(header->state) || size < sizeof(export_header)) + return import_error_einval(); + imported_size = sizeof(export_header) + sizeof(unsigned) * header->hash_vector_size; + if (!header->hash_vector_size || size < imported_size) + return import_error_einval(); + hash_ids = (const unsigned*)(const void*)(header + 1); + ectx = (rhash_context_ext*)rhash_alloc_multi(header->hash_vector_size, hash_ids, 0); + if (!ectx) + return NULL; /* errno must be set by the previous function */ + ectx->state = header->state; + ectx->hash_vector_size = header->hash_vector_size; + ectx->flags = header->flags; + ectx->rc.msg_size = header->msg_size; + for (i = 0; i < ectx->hash_vector_size; i++) { + void* dst_context = ectx->vector[i].context; + struct rhash_hash_info* hash_info = ectx->vector[i].hash_info; + unsigned is_special = (hash_info->info->flags & F_SPCEXP); + size_t item_size; + + if (is_special) { + size_t left_size; + imported_size = GET_EXPORT_ALIGNED(imported_size); + src_item = (const char*)in + imported_size; + left_size = size - imported_size; + assert(size >= imported_size); + item_size = rhash_import_alg(hash_ids[i], dst_context, src_item, left_size); + imported_size += item_size; + if (!item_size || size < imported_size) { + ectx->hash_vector_size = i + 1; /* clean only initialized contextes */ + rhash_free(&ectx->rc); + return import_error_einval(); + } + } else { + src_item = (const char*)in + imported_size; + item_size = hash_info->context_size; + imported_size += item_size; + if (size < imported_size) { + ectx->hash_vector_size = i + 1; + rhash_free(&ectx->rc); + return import_error_einval(); + } + memcpy(dst_context, src_item, item_size); + } + } + return &ectx->rc; +#else + return import_error_einval(); +#endif /* !defined(NO_IMPORT_EXPORT) */ +} + /** * Store digest for given hash_id. * If hash_id is zero, function stores digest for a hash with the lowest id found in the context. @@ -290,7 +469,7 @@ static void rhash_put_digest(rhash ctx, unsigned hash_id, unsigned char* result) RHASH_API void rhash_set_callback(rhash ctx, rhash_callback_t callback, void* callback_data) { - ((rhash_context_ext*)ctx)->callback = (void*)callback; + ((rhash_context_ext*)ctx)->callback = callback; ((rhash_context_ext*)ctx)->callback_data = callback_data; } @@ -313,26 +492,21 @@ RHASH_API int rhash_file_update(rhash ctx, FILE* fd) rhash_context_ext* const ectx = (rhash_context_ext*)ctx; const size_t block_size = 8192; unsigned char* buffer; - unsigned char* pmem; - size_t length = 0, align8; + size_t length = 0; int res = 0; - if (ectx->state != STATE_ACTIVE) return 0; /* do nothing if canceled */ - + if (ectx->state != STATE_ACTIVE) + return 0; /* do nothing if canceled */ if (ctx == NULL) { errno = EINVAL; return -1; } - - pmem = (unsigned char*)malloc(block_size + 8); - if (!pmem) return -1; /* errno is set to ENOMEM according to UNIX 98 */ - - align8 = ((unsigned char*)0 - pmem) & 7; - buffer = pmem + align8; + buffer = (unsigned char*)rhash_aligned_alloc(DEFAULT_ALIGNMENT, block_size); + if (!buffer) + return -1; /* errno is set to ENOMEM according to UNIX 98 */ while (!feof(fd)) { - /* stop if canceled */ - if (ectx->state != STATE_ACTIVE) break; - + if (ectx->state != STATE_ACTIVE) + break; /* stop if canceled */ length = fread(buffer, 1, block_size, fd); if (ferror(fd)) { @@ -346,11 +520,16 @@ RHASH_API int rhash_file_update(rhash ctx, FILE* fd) } } } - - free(buffer); + rhash_aligned_free(buffer); return res; } +#ifdef _WIN32 +# define FOPEN_MODE "rbS" +#else +# define FOPEN_MODE "rb" +#endif + RHASH_API int rhash_file(unsigned hash_id, const char* filepath, unsigned char* result) { FILE* fd; @@ -363,17 +542,19 @@ RHASH_API int rhash_file(unsigned hash_id, const char* filepath, unsigned char* return -1; } - if ((fd = fopen(filepath, "rb")) == NULL) return -1; + fd = fopen(filepath, FOPEN_MODE); + if (!fd) + return -1; - if ((ctx = rhash_init(hash_id)) == NULL) { + ctx = rhash_init(hash_id); + if (!ctx) { fclose(fd); return -1; } - res = rhash_file_update(ctx, fd); /* hash the file */ fclose(fd); - - rhash_final(ctx, result); + if (res >= 0) + rhash_final(ctx, result); rhash_free(ctx); return res; } @@ -393,17 +574,19 @@ RHASH_API int rhash_wfile(unsigned hash_id, const wchar_t* filepath, unsigned ch return -1; } - if ((fd = _wfsopen(filepath, L"rb", _SH_DENYWR)) == NULL) return -1; + fd = _wfsopen(filepath, L"rbS", _SH_DENYWR); + if (!fd) + return -1; - if ((ctx = rhash_init(hash_id)) == NULL) { + ctx = rhash_init(hash_id); + if (!ctx) { fclose(fd); return -1; } - res = rhash_file_update(ctx, fd); /* hash the file */ fclose(fd); - - rhash_final(ctx, result); + if (res >= 0) + rhash_final(ctx, result); rhash_free(ctx); return res; } @@ -576,7 +759,7 @@ size_t rhash_print_bytes(char* output, const unsigned char* bytes, size_t size, return result_length; } -size_t RHASH_API rhash_print(char* output, rhash context, unsigned hash_id, int flags) +RHASH_API size_t rhash_print(char* output, rhash context, unsigned hash_id, int flags) { const rhash_info* info; unsigned char digest[80]; @@ -654,6 +837,7 @@ RHASH_API rhash_uptr_t rhash_transmit(unsigned msg_id, void* dst, rhash_uptr_t l { /* for messages working with rhash context */ rhash_context_ext* const ctx = (rhash_context_ext*)dst; + (void)rdata; switch (msg_id) { case RMSG_GET_CONTEXT: @@ -669,11 +853,11 @@ RHASH_API rhash_uptr_t rhash_transmit(unsigned msg_id, void* dst, rhash_uptr_t l case RMSG_CANCEL: /* mark rhash context as canceled, in a multithreaded program */ - atomic_compare_and_swap(&ctx->state, STATE_ACTIVE, STATE_STOPED); + atomic_compare_and_swap(&ctx->state, STATE_ACTIVE, STATE_STOPPED); return 0; case RMSG_IS_CANCELED: - return (ctx->state == STATE_STOPED); + return (ctx->state == STATE_STOPPED); case RMSG_GET_FINALIZED: return ((ctx->flags & RCTX_FINALIZED) != 0); @@ -695,6 +879,9 @@ RHASH_API rhash_uptr_t rhash_transmit(unsigned msg_id, void* dst, rhash_uptr_t l case RMSG_GET_OPENSSL_AVAILABLE_MASK: return rhash_get_openssl_available_hash_mask(); + case RMSG_GET_LIBRHASH_VERSION: + return RHASH_XVERSION; + default: return RHASH_ERROR; /* unknown message */ } diff --git a/Utilities/cmlibrhash/librhash/rhash.h b/Utilities/cmlibrhash/librhash/rhash.h index c0117626eed..07b6d9f97c7 100644 --- a/Utilities/cmlibrhash/librhash/rhash.h +++ b/Utilities/cmlibrhash/librhash/rhash.h @@ -52,9 +52,11 @@ enum rhash_ids RHASH_CRC32C = 0x4000000, RHASH_SNEFRU128 = 0x8000000, RHASH_SNEFRU256 = 0x10000000, + RHASH_BLAKE2S = 0x20000000, + RHASH_BLAKE2B = 0x40000000, /** - * The bit-mask containing all supported hashe functions. + * The bit-mask containing all supported hash functions. */ RHASH_ALL_HASHES = RHASH_CRC32 | RHASH_CRC32C | RHASH_MD4 | RHASH_MD5 | RHASH_ED2K | RHASH_SHA1 |RHASH_TIGER | RHASH_TTH | @@ -63,14 +65,18 @@ enum rhash_ids RHASH_HAS160 | RHASH_SNEFRU128 | RHASH_SNEFRU256 | RHASH_SHA224 | RHASH_SHA256 | RHASH_SHA384 | RHASH_SHA512 | RHASH_SHA3_224 | RHASH_SHA3_256 | RHASH_SHA3_384 | RHASH_SHA3_512 | - RHASH_EDONR256 | RHASH_EDONR512, + RHASH_EDONR256 | RHASH_EDONR512 | RHASH_BLAKE2S | RHASH_BLAKE2B, RHASH_GOST = RHASH_GOST94, /* deprecated constant name */ RHASH_GOST_CRYPTOPRO = RHASH_GOST94_CRYPTOPRO, /* deprecated constant name */ + + /* bit-flag for extra hash identifiers */ + RHASH_EXTENDED_BIT = (int)0x80000000, + /** * The number of supported hash functions. */ - RHASH_HASH_COUNT = 29 + RHASH_HASH_COUNT = 31 #else RHASH_MD5 = 0x01, RHASH_SHA1 = 0x02, @@ -100,7 +106,7 @@ enum rhash_ids /** * The rhash context structure contains contexts for several hash functions. */ -typedef struct rhash_context +struct rhash_context { /** * The size of the hashed message. @@ -108,10 +114,10 @@ typedef struct rhash_context unsigned long long msg_size; /** - * The bit-mask containing identifiers of the hashes being calculated. + * The bit-mask containing identifiers of the hash functions being calculated. */ unsigned hash_id; -} rhash_context; +}; #ifndef LIBRHASH_RHASH_CTX_DEFINED #define LIBRHASH_RHASH_CTX_DEFINED @@ -135,34 +141,34 @@ RHASH_API void rhash_library_init(void); /* HIGH-LEVEL LIBRHASH INTERFACE */ /** - * Compute a hash of the given message. + * Compute a message digest of the given message. * - * @param hash_id id of hash sum to compute + * @param hash_id id of message digest to compute * @param message the message to process * @param length message length - * @param result buffer to receive binary hash string + * @param result buffer to receive the binary message digest value * @return 0 on success, -1 on error */ RHASH_API int rhash_msg(unsigned hash_id, const void* message, size_t length, unsigned char* result); /** - * Compute a single hash for given file. + * Compute a single message digest for the given file. * - * @param hash_id id of hash sum to compute - * @param filepath path to the file to hash - * @param result buffer to receive hash value with the lowest requested id - * @return 0 on success, -1 on error and errno is set + * @param hash_id id of hash function to compute + * @param filepath path to the file to process + * @param result buffer to receive message digest + * @return 0 on success, -1 on fail with error code stored in errno */ RHASH_API int rhash_file(unsigned hash_id, const char* filepath, unsigned char* result); #ifdef _WIN32 /** - * Compute a single hash for given file (Windows-specific function). + * Compute a single message digest for the given file (Windows-specific function). * - * @param hash_id id of hash sum to compute - * @param filepath path to the file to hash - * @param result buffer to receive hash value with the lowest requested id - * @return 0 on success, -1 on error, -1 on error and errno is set + * @param hash_id id of hash function to compute + * @param filepath path to the file to process + * @param result buffer to receive the binary message digest value + * @return 0 on success, -1 on fail with error code stored in errno */ RHASH_API int rhash_wfile(unsigned hash_id, const wchar_t* filepath, unsigned char* result); #endif @@ -171,45 +177,59 @@ RHASH_API int rhash_wfile(unsigned hash_id, const wchar_t* filepath, unsigned ch /* LOW-LEVEL LIBRHASH INTERFACE */ /** - * Allocate and initialize RHash context for calculating hash(es). - * After initializing rhash_update()/rhash_final() functions should be used. - * Then the context must be freed by calling rhash_free(). + * Allocate and initialize RHash context for calculating a single or multiple hash functions. + * The context after usage must be freed by calling rhash_free(). * - * @param hash_id union of bit flags, containing ids of hashes to calculate. - * @return initialized rhash context, NULL on error and errno is set + * @param count the size of the hash_ids array, the count must be greater than zero + * @param hash_ids array of identifiers of hash functions. Each element must + * be an identifier of one hash function + * @return initialized rhash context, NULL on fail with error code stored in errno + */ +RHASH_API rhash rhash_init_multi(size_t count, const unsigned hash_ids[]); + +/** + * Allocate and initialize RHash context for calculating a single hash function. + * + * This function also supports a depricated way to initialize rhash context + * for multiple hash functions, by passing a bitwise union of several hash + * identifiers. Only single-bit identifiers (not greater than RHASH_SNEFRU256) + * can be used in such bitwise union. + * + * @param hash_id identifier of a hash function + * @return initialized rhash context, NULL on fail with error code stored in errno */ RHASH_API rhash rhash_init(unsigned hash_id); /** - * Calculate hashes of message. + * Calculate message digests of message. * Can be called repeatedly with chunks of the message to be hashed. * * @param ctx the rhash context * @param message message chunk * @param length length of the message chunk - * @return 0 on success; On fail return -1 and set errno + * @return 0 on success, -1 on fail with error code stored in errno */ RHASH_API int rhash_update(rhash ctx, const void* message, size_t length); /** - * Hash a file or stream. Multiple hashes can be computed. + * Process a file or stream. Multiple message digests can be computed. * First, inintialize ctx parameter with rhash_init() before calling * rhash_file_update(). Then use rhash_final() and rhash_print() - * to retrive hash values. Finaly call rhash_free() on ctx + * to retrive message digests. Finaly call rhash_free() on ctx * to free allocated memory or call rhash_reset() to reuse ctx. * * @param ctx rhash context * @param fd descriptor of the file to hash - * @return 0 on success, -1 on error and errno is set + * @return 0 on success, -1 on fail with error code stored in errno */ RHASH_API int rhash_file_update(rhash ctx, FILE* fd); /** - * Finalize hash calculation and optionally store the first hash. + * Finalize message digest calculation and optionally store the first message digest. * * @param ctx the rhash context - * @param first_result optional buffer to store a calculated hash with the lowest available id - * @return 0 on success; On fail return -1 and set errno + * @param first_result optional buffer to store a calculated message digest with the lowest available id + * @return 0 on success, -1 on fail with error code stored in errno */ RHASH_API int rhash_final(rhash ctx, unsigned char* first_result); @@ -224,7 +244,7 @@ RHASH_API void rhash_reset(rhash ctx); /** * Free RHash context memory. * - * @param ctx the context to free. + * @param ctx the context to free */ RHASH_API void rhash_free(rhash ctx); @@ -238,8 +258,33 @@ RHASH_API void rhash_free(rhash ctx); * @param callback pointer to the callback function * @param callback_data pointer to data passed to the callback */ -RHASH_API void rhash_set_callback(rhash ctx, rhash_callback_t callback, void* callback_data); +RHASH_API void rhash_set_callback(rhash ctx, rhash_callback_t callback, void* callback_data); +/** + * Export RHash context data to a memory region. + * The size of the memory required for export + * is returned by rhash_export(ctx, NULL, 0). + * + * @param ctx the rhash context to export + * @param out pointer to a memory region, or NULL + * @param size the size of a memory region + * @return the size of exported data on success export. + * The size of memory required for export if out is NULL. + * 0 on fail with error code stored in errno + */ +RHASH_API size_t rhash_export(rhash ctx, void* out, size_t size); + +/** + * Import rhash context from a memory region. + * The returned rhash context must be released after usage + * by rhash_free(). + * + * @param in pointer to a memory region + * @param size the size of a memory region + * @return imported rhash context on success, + * NULL on fail with error code stored in errno + */ +RHASH_API rhash rhash_import(const void* in, size_t size); /* INFORMATION FUNCTIONS */ @@ -248,37 +293,37 @@ RHASH_API void rhash_set_callback(rhash ctx, rhash_callback_t callback, void* c * * @return the number of supported hash functions */ -RHASH_API int rhash_count(void); /* number of supported hashes */ +RHASH_API int rhash_count(void); /** - * Returns size of binary digest for given hash algorithm. + * Returns the size of binary message digest for given hash function. * - * @param hash_id the id of hash algorithm - * @return digest size in bytes + * @param hash_id the id of the hash function + * @return the size of the message digest in bytes */ -RHASH_API int rhash_get_digest_size(unsigned hash_id); /* size of binary message digest */ +RHASH_API int rhash_get_digest_size(unsigned hash_id); /** - * Returns length of digest hash string in default output format. + * Returns the length of message digest string in its default output format. * - * @param hash_id the id of hash algorithm - * @return the length of hash string + * @param hash_id the id of the hash function + * @return the length of the message digest */ -RHASH_API int rhash_get_hash_length(unsigned hash_id); /* length of formatted hash string */ +RHASH_API int rhash_get_hash_length(unsigned hash_id); /** - * Detect default digest output format for given hash algorithm. + * Detect default message digest output format for the given hash algorithm. * * @param hash_id the id of hash algorithm * @return 1 for base32 format, 0 for hexadecimal */ -RHASH_API int rhash_is_base32(unsigned hash_id); /* default digest output format */ +RHASH_API int rhash_is_base32(unsigned hash_id); /** - * Returns a name of given hash algorithm. + * Returns the name of the given hash function. * - * @param hash_id the id of hash algorithm - * @return algorithm name + * @param hash_id id of the hash function + * @return hash function name */ RHASH_API const char* rhash_get_name(unsigned hash_id); /* get hash function name */ @@ -287,7 +332,7 @@ RHASH_API const char* rhash_get_name(unsigned hash_id); /* get hash function nam * Such magnet_name is used to generate a magnet link of the form * urn:<magnet_name>=<hash_value>. * - * @param hash_id the id of hash algorithm + * @param hash_id id of the hash algorithm * @return name */ RHASH_API const char* rhash_get_magnet_name(unsigned hash_id); /* get name part of magnet urn */ @@ -296,7 +341,7 @@ RHASH_API const char* rhash_get_magnet_name(unsigned hash_id); /* get name part #if 0 /** - * Flags for printing a hash sum. + * Flags for printing a message digest. */ enum rhash_print_sum_flags { @@ -326,7 +371,7 @@ enum rhash_print_sum_flags */ RHPR_UPPERCASE = 0x8, /* - * Reverse hash bytes. Can be used for GOST hash. + * Reverse message digest bytes. Can be used for GOST hash functions. */ RHPR_REVERSE = 0x10, /* @@ -346,12 +391,12 @@ enum rhash_print_sum_flags /** - * Print a text presentation of a given hash sum to the specified buffer. + * Print to the specified buffer the text representation of the given message digest. * - * @param output a buffer to print the hash to - * @param bytes a hash sum to print - * @param size a size of hash sum in bytes - * @param flags a bit-mask controlling how to format the hash sum, + * @param output a buffer to print the message digest to + * @param bytes a binary message digest to print + * @param size a size of the message digest in bytes + * @param flags a bit-mask controlling how to format the message digest, * can be a mix of the flags: RHPR_RAW, RHPR_HEX, RHPR_BASE32, * RHPR_BASE64, RHPR_URLENCODE, RHPR_UPPERCASE, RHPR_REVERSE * @return the number of written characters @@ -360,33 +405,33 @@ RHASH_API size_t rhash_print_bytes(char* output, const unsigned char* bytes, size_t size, int flags); /** - * Print text presentation of a hash sum with given hash_id to the specified - * output buffer. If the hash_id is zero, then print the hash sum with - * the lowest id stored in the hash context. - * The function call fails if the context doesn't include a hash with the + * Print to the specified output buffer the text representation of the message digest + * with the given hash_id. If the hash_id is zero, then print the message digest with + * the lowest hash_id calculated by the hash context. + * The function call fails if the context doesn't include the message digest with the * given hash_id. * - * @param output a buffer to print the hash to - * @param ctx algorithms state - * @param hash_id id of the hash sum to print or 0 to print the first hash - * saved in the context. - * @param flags a bitmask controlling how to print the hash. Can contain flags - * RHPR_UPPERCASE, RHPR_HEX, RHPR_BASE32, RHPR_BASE64, etc. + * @param output a buffer to print the message digest to + * @param ctx algorithms state + * @param hash_id id of the message digest to print or 0 to print the first + * message digest saved in the context. + * @param flags a bitmask controlling how to print the message digest. Can contain + * flags RHPR_UPPERCASE, RHPR_HEX, RHPR_BASE32, RHPR_BASE64, etc. * @return the number of written characters on success or 0 on fail */ RHASH_API size_t rhash_print(char* output, rhash ctx, unsigned hash_id, int flags); /** - * Print magnet link with given filepath and calculated hash sums into the - * output buffer. The hash_mask can limit which hash values will be printed. + * Print magnet link with given filepath and calculated message digest into the + * output buffer. The hash_mask can limit which message digests will be printed. * The function returns the size of the required buffer. * If output is NULL the . * * @param output a string buffer to receive the magnet link or NULL * @param filepath the file path to be printed or NULL * @param context algorithms state - * @param hash_mask bit mask of the hash sums to add to the link + * @param hash_mask bit mask of the message digest to add to the link * @param flags can be combination of bits RHPR_UPPERCASE, RHPR_NO_MAGNET, * RHPR_FILESIZE * @return number of written characters, including terminating '\0' on success, 0 on fail @@ -445,19 +490,20 @@ RHASH_API rhash_uptr_t rhash_transmit( #define RMSG_GET_OPENSSL_MASK 11 #define RMSG_GET_OPENSSL_SUPPORTED_MASK 12 #define RMSG_GET_OPENSSL_AVAILABLE_MASK 13 +#define RMSG_GET_LIBRHASH_VERSION 20 /* HELPER MACROS */ /** - * Get a pointer to context of the specified hash function. + * Get a pointer to the context of the specified hash function. */ #define rhash_get_context_ptr(ctx, hash_id) RHASH_UPTR2PVOID(rhash_transmit(RMSG_GET_CONTEXT, ctx, hash_id, 0)) /** - * Cancel hash calculation of a file. + * Cancel file processing. */ #define rhash_cancel(ctx) rhash_transmit(RMSG_CANCEL, ctx, 0, 0) /** - * Return non-zero if hash calculation was canceled, zero otherwise. + * Return non-zero if a message digest calculation was canceled, zero otherwise. */ #define rhash_is_canceled(ctx) rhash_transmit(RMSG_IS_CANCELED, ctx, 0, 0) /** @@ -468,7 +514,7 @@ RHASH_API rhash_uptr_t rhash_transmit( /** * Turn on/off the auto-final flag for the given rhash_context. By default * auto-final is on, which means rhash_final is called automatically, if - * needed when a hash value is retrieved by rhash_print call. + * needed when a message digest is retrieved by rhash_print call. */ #define rhash_set_autofinal(ctx, on) rhash_transmit(RMSG_SET_AUTOFINAL, ctx, on, 0) @@ -500,9 +546,13 @@ RHASH_API rhash_uptr_t rhash_transmit( */ #define rhash_get_openssl_available_mask() rhash_transmit(RMSG_GET_OPENSSL_AVAILABLE_MASK, NULL, 0, 0) +/** + * Return librhash version. + */ +#define rhash_get_version() rhash_transmit(RMSG_GET_LIBRHASH_VERSION, NULL, 0, 0) /** - * Return non-zero if LibRHash hash been compiled with OpenSSL support, + * Return non-zero if LibRHash has been compiled with OpenSSL support, * and zero otherwise. */ #define rhash_is_openssl_supported() (rhash_get_openssl_mask() != RHASH_ERROR) diff --git a/Utilities/cmlibrhash/librhash/sha1.c b/Utilities/cmlibrhash/librhash/sha1.c index b226925f051..cbc2b7256d3 100644 --- a/Utilities/cmlibrhash/librhash/sha1.c +++ b/Utilities/cmlibrhash/librhash/sha1.c @@ -20,7 +20,7 @@ #include "sha1.h" /** - * Initialize context before calculaing hash. + * Initialize context before calculating hash. * * @param ctx context to initialize */ @@ -36,6 +36,23 @@ void rhash_sha1_init(sha1_ctx* ctx) ctx->hash[4] = 0xc3d2e1f0; } +/* constants for SHA1 rounds */ +static const uint32_t K0 = 0x5a827999; +static const uint32_t K1 = 0x6ed9eba1; +static const uint32_t K2 = 0x8f1bbcdc; +static const uint32_t K3 = 0xca62c1d6; + +/* round functions for SHA1 */ +#define CHO(X,Y,Z) (((X)&(Y))|((~(X))&(Z))) +#define PAR(X,Y,Z) ((X)^(Y)^(Z)) +#define MAJ(X,Y,Z) (((X)&(Y))|((X)&(Z))|((Y)&(Z))) + +#define ROUND_0(a,b,c,d,e, FF, k, w) e += FF(b, c, d )+ROTL32(a,5)+k+w +#define ROUND_1(a,b,c,d,e, FF, k, w) e += FF(b,ROTL32(c,30), d )+ROTL32(a,5)+k+w +#define ROUND_2(a,b,c,d,e, FF, k, w) e += FF(b,ROTL32(c,30),ROTL32(d,30))+ROTL32(a,5)+k+w +#define ROUND(a,b,c,d,e, FF, k, w) e = ROTL32(e,30)+FF(b,ROTL32(c,30),ROTL32(d,30))+ROTL32(a,5)+k+w + + /** * The core transformation. Process a 512-bit block. * The function has been taken from RFC 3174 with little changes. @@ -45,21 +62,9 @@ void rhash_sha1_init(sha1_ctx* ctx) */ static void rhash_sha1_process_block(unsigned* hash, const unsigned* block) { - int t; /* Loop counter */ - uint32_t temp; /* Temporary word value */ uint32_t W[80]; /* Word sequence */ uint32_t A, B, C, D, E; /* Word buffers */ - /* initialize the first 16 words in the array W */ - for (t = 0; t < 16; t++) { - /* note: it is much faster to apply be2me here, then using be32_copy */ - W[t] = be2me_32(block[t]); - } - - /* initialize the rest */ - for (t = 16; t < 80; t++) { - W[t] = ROTL32(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1); - } A = hash[0]; B = hash[1]; @@ -67,50 +72,189 @@ static void rhash_sha1_process_block(unsigned* hash, const unsigned* block) D = hash[3]; E = hash[4]; - for (t = 0; t < 20; t++) { - /* the following is faster than ((B & C) | ((~B) & D)) */ - temp = ROTL32(A, 5) + (((C ^ D) & B) ^ D) - + E + W[t] + 0x5A827999; - E = D; - D = C; - C = ROTL32(B, 30); - B = A; - A = temp; - } + /* 0..19 */ + W[ 0] = be2me_32(block[ 0]); + ROUND_0(A,B,C,D,E, CHO, K0, W[ 0]); + W[ 1] = be2me_32(block[ 1]); + ROUND_1(E,A,B,C,D, CHO, K0, W[ 1]); + W[ 2] = be2me_32(block[ 2]); + ROUND_2(D,E,A,B,C, CHO, K0, W[ 2]); + W[ 3] = be2me_32(block[ 3]); + ROUND(C,D,E,A,B, CHO, K0, W[ 3]); + W[ 4] = be2me_32(block[ 4]); + ROUND(B,C,D,E,A, CHO, K0, W[ 4]); - for (t = 20; t < 40; t++) { - temp = ROTL32(A, 5) + (B ^ C ^ D) + E + W[t] + 0x6ED9EBA1; - E = D; - D = C; - C = ROTL32(B, 30); - B = A; - A = temp; - } + W[ 5] = be2me_32(block[ 5]); + ROUND(A,B,C,D,E, CHO, K0, W[ 5]); + W[ 6] = be2me_32(block[ 6]); + ROUND(E,A,B,C,D, CHO, K0, W[ 6]); + W[ 7] = be2me_32(block[ 7]); + ROUND(D,E,A,B,C, CHO, K0, W[ 7]); + W[ 8] = be2me_32(block[ 8]); + ROUND(C,D,E,A,B, CHO, K0, W[ 8]); + W[ 9] = be2me_32(block[ 9]); + ROUND(B,C,D,E,A, CHO, K0, W[ 9]); - for (t = 40; t < 60; t++) { - temp = ROTL32(A, 5) + ((B & C) | (B & D) | (C & D)) - + E + W[t] + 0x8F1BBCDC; - E = D; - D = C; - C = ROTL32(B, 30); - B = A; - A = temp; - } + W[10] = be2me_32(block[10]); + ROUND(A,B,C,D,E, CHO, K0, W[10]); + W[11] = be2me_32(block[11]); + ROUND(E,A,B,C,D, CHO, K0, W[11]); + W[12] = be2me_32(block[12]); + ROUND(D,E,A,B,C, CHO, K0, W[12]); + W[13] = be2me_32(block[13]); + ROUND(C,D,E,A,B, CHO, K0, W[13]); + W[14] = be2me_32(block[14]); + ROUND(B,C,D,E,A, CHO, K0, W[14]); + + W[15] = be2me_32(block[15]); + ROUND(A,B,C,D,E, CHO, K0, W[15]); + W[16] = ROTL32(W[13] ^ W[ 8] ^ W[ 2] ^ W[ 0], 1); + ROUND(E,A,B,C,D, CHO, K0, W[16]); + W[17] = ROTL32(W[14] ^ W[ 9] ^ W[ 3] ^ W[ 1], 1); + ROUND(D,E,A,B,C, CHO, K0, W[17]); + W[18] = ROTL32(W[15] ^ W[10] ^ W[ 4] ^ W[ 2], 1); + ROUND(C,D,E,A,B, CHO, K0, W[18]); + W[19] = ROTL32(W[16] ^ W[11] ^ W[ 5] ^ W[ 3], 1); + ROUND(B,C,D,E,A, CHO, K0, W[19]); + /* 20..39 */ + W[20] = ROTL32(W[17] ^ W[12] ^ W[ 6] ^ W[ 4], 1); + ROUND(A,B,C,D,E, PAR, K1, W[20]); + W[21] = ROTL32(W[18] ^ W[13] ^ W[ 7] ^ W[ 5], 1); + ROUND(E,A,B,C,D, PAR, K1, W[21]); + W[22] = ROTL32(W[19] ^ W[14] ^ W[ 8] ^ W[ 6], 1); + ROUND(D,E,A,B,C, PAR, K1, W[22]); + W[23] = ROTL32(W[20] ^ W[15] ^ W[ 9] ^ W[ 7], 1); + ROUND(C,D,E,A,B, PAR, K1, W[23]); + W[24] = ROTL32(W[21] ^ W[16] ^ W[10] ^ W[ 8], 1); + ROUND(B,C,D,E,A, PAR, K1, W[24]); + + W[25] = ROTL32(W[22] ^ W[17] ^ W[11] ^ W[ 9], 1); + ROUND(A,B,C,D,E, PAR, K1, W[25]); + W[26] = ROTL32(W[23] ^ W[18] ^ W[12] ^ W[10], 1); + ROUND(E,A,B,C,D, PAR, K1, W[26]); + W[27] = ROTL32(W[24] ^ W[19] ^ W[13] ^ W[11], 1); + ROUND(D,E,A,B,C, PAR, K1, W[27]); + W[28] = ROTL32(W[25] ^ W[20] ^ W[14] ^ W[12], 1); + ROUND(C,D,E,A,B, PAR, K1, W[28]); + W[29] = ROTL32(W[26] ^ W[21] ^ W[15] ^ W[13], 1); + ROUND(B,C,D,E,A, PAR, K1, W[29]); + + W[30] = ROTL32(W[27] ^ W[22] ^ W[16] ^ W[14], 1); + ROUND(A,B,C,D,E, PAR, K1, W[30]); + W[31] = ROTL32(W[28] ^ W[23] ^ W[17] ^ W[15], 1); + ROUND(E,A,B,C,D, PAR, K1, W[31]); + W[32] = ROTL32(W[29] ^ W[24] ^ W[18] ^ W[16], 1); + ROUND(D,E,A,B,C, PAR, K1, W[32]); + W[33] = ROTL32(W[30] ^ W[25] ^ W[19] ^ W[17], 1); + ROUND(C,D,E,A,B, PAR, K1, W[33]); + W[34] = ROTL32(W[31] ^ W[26] ^ W[20] ^ W[18], 1); + ROUND(B,C,D,E,A, PAR, K1, W[34]); + + W[35] = ROTL32(W[32] ^ W[27] ^ W[21] ^ W[19], 1); + ROUND(A,B,C,D,E, PAR, K1, W[35]); + W[36] = ROTL32(W[33] ^ W[28] ^ W[22] ^ W[20], 1); + ROUND(E,A,B,C,D, PAR, K1, W[36]); + W[37] = ROTL32(W[34] ^ W[29] ^ W[23] ^ W[21], 1); + ROUND(D,E,A,B,C, PAR, K1, W[37]); + W[38] = ROTL32(W[35] ^ W[30] ^ W[24] ^ W[22], 1); + ROUND(C,D,E,A,B, PAR, K1, W[38]); + W[39] = ROTL32(W[36] ^ W[31] ^ W[25] ^ W[23], 1); + ROUND(B,C,D,E,A, PAR, K1, W[39]); + /* 40..59 */ + W[40] = ROTL32(W[37] ^ W[32] ^ W[26] ^ W[24], 1); + ROUND(A,B,C,D,E, MAJ, K2, W[40]); + W[41] = ROTL32(W[38] ^ W[33] ^ W[27] ^ W[25], 1); + ROUND(E,A,B,C,D, MAJ, K2, W[41]); + W[42] = ROTL32(W[39] ^ W[34] ^ W[28] ^ W[26], 1); + ROUND(D,E,A,B,C, MAJ, K2, W[42]); + W[43] = ROTL32(W[40] ^ W[35] ^ W[29] ^ W[27], 1); + ROUND(C,D,E,A,B, MAJ, K2, W[43]); + W[44] = ROTL32(W[41] ^ W[36] ^ W[30] ^ W[28], 1); + ROUND(B,C,D,E,A, MAJ, K2, W[44]); + + W[45] = ROTL32(W[42] ^ W[37] ^ W[31] ^ W[29], 1); + ROUND(A,B,C,D,E, MAJ, K2, W[45]); + W[46] = ROTL32(W[43] ^ W[38] ^ W[32] ^ W[30], 1); + ROUND(E,A,B,C,D, MAJ, K2, W[46]); + W[47] = ROTL32(W[44] ^ W[39] ^ W[33] ^ W[31], 1); + ROUND(D,E,A,B,C, MAJ, K2, W[47]); + W[48] = ROTL32(W[45] ^ W[40] ^ W[34] ^ W[32], 1); + ROUND(C,D,E,A,B, MAJ, K2, W[48]); + W[49] = ROTL32(W[46] ^ W[41] ^ W[35] ^ W[33], 1); + ROUND(B,C,D,E,A, MAJ, K2, W[49]); + + W[50] = ROTL32(W[47] ^ W[42] ^ W[36] ^ W[34], 1); + ROUND(A,B,C,D,E, MAJ, K2, W[50]); + W[51] = ROTL32(W[48] ^ W[43] ^ W[37] ^ W[35], 1); + ROUND(E,A,B,C,D, MAJ, K2, W[51]); + W[52] = ROTL32(W[49] ^ W[44] ^ W[38] ^ W[36], 1); + ROUND(D,E,A,B,C, MAJ, K2, W[52]); + W[53] = ROTL32(W[50] ^ W[45] ^ W[39] ^ W[37], 1); + ROUND(C,D,E,A,B, MAJ, K2, W[53]); + W[54] = ROTL32(W[51] ^ W[46] ^ W[40] ^ W[38], 1); + ROUND(B,C,D,E,A, MAJ, K2, W[54]); + + W[55] = ROTL32(W[52] ^ W[47] ^ W[41] ^ W[39], 1); + ROUND(A,B,C,D,E, MAJ, K2, W[55]); + W[56] = ROTL32(W[53] ^ W[48] ^ W[42] ^ W[40], 1); + ROUND(E,A,B,C,D, MAJ, K2, W[56]); + W[57] = ROTL32(W[54] ^ W[49] ^ W[43] ^ W[41], 1); + ROUND(D,E,A,B,C, MAJ, K2, W[57]); + W[58] = ROTL32(W[55] ^ W[50] ^ W[44] ^ W[42], 1); + ROUND(C,D,E,A,B, MAJ, K2, W[58]); + W[59] = ROTL32(W[56] ^ W[51] ^ W[45] ^ W[43], 1); + ROUND(B,C,D,E,A, MAJ, K2, W[59]); + /* 60..79 */ + W[60] = ROTL32(W[57] ^ W[52] ^ W[46] ^ W[44], 1); + ROUND(A,B,C,D,E, PAR, K3, W[60]); + W[61] = ROTL32(W[58] ^ W[53] ^ W[47] ^ W[45], 1); + ROUND(E,A,B,C,D, PAR, K3, W[61]); + W[62] = ROTL32(W[59] ^ W[54] ^ W[48] ^ W[46], 1); + ROUND(D,E,A,B,C, PAR, K3, W[62]); + W[63] = ROTL32(W[60] ^ W[55] ^ W[49] ^ W[47], 1); + ROUND(C,D,E,A,B, PAR, K3, W[63]); + W[64] = ROTL32(W[61] ^ W[56] ^ W[50] ^ W[48], 1); + ROUND(B,C,D,E,A, PAR, K3, W[64]); + + W[65] = ROTL32(W[62] ^ W[57] ^ W[51] ^ W[49], 1); + ROUND(A,B,C,D,E, PAR, K3, W[65]); + W[66] = ROTL32(W[63] ^ W[58] ^ W[52] ^ W[50], 1); + ROUND(E,A,B,C,D, PAR, K3, W[66]); + W[67] = ROTL32(W[64] ^ W[59] ^ W[53] ^ W[51], 1); + ROUND(D,E,A,B,C, PAR, K3, W[67]); + W[68] = ROTL32(W[65] ^ W[60] ^ W[54] ^ W[52], 1); + ROUND(C,D,E,A,B, PAR, K3, W[68]); + W[69] = ROTL32(W[66] ^ W[61] ^ W[55] ^ W[53], 1); + ROUND(B,C,D,E,A, PAR, K3, W[69]); + + W[70] = ROTL32(W[67] ^ W[62] ^ W[56] ^ W[54], 1); + ROUND(A,B,C,D,E, PAR, K3, W[70]); + W[71] = ROTL32(W[68] ^ W[63] ^ W[57] ^ W[55], 1); + ROUND(E,A,B,C,D, PAR, K3, W[71]); + W[72] = ROTL32(W[69] ^ W[64] ^ W[58] ^ W[56], 1); + ROUND(D,E,A,B,C, PAR, K3, W[72]); + W[73] = ROTL32(W[70] ^ W[65] ^ W[59] ^ W[57], 1); + ROUND(C,D,E,A,B, PAR, K3, W[73]); + W[74] = ROTL32(W[71] ^ W[66] ^ W[60] ^ W[58], 1); + ROUND(B,C,D,E,A, PAR, K3, W[74]); + + W[75] = ROTL32(W[72] ^ W[67] ^ W[61] ^ W[59], 1); + ROUND(A,B,C,D,E, PAR, K3, W[75]); + W[76] = ROTL32(W[73] ^ W[68] ^ W[62] ^ W[60], 1); + ROUND(E,A,B,C,D, PAR, K3, W[76]); + W[77] = ROTL32(W[74] ^ W[69] ^ W[63] ^ W[61], 1); + ROUND(D,E,A,B,C, PAR, K3, W[77]); + W[78] = ROTL32(W[75] ^ W[70] ^ W[64] ^ W[62], 1); + ROUND(C,D,E,A,B, PAR, K3, W[78]); + W[79] = ROTL32(W[76] ^ W[71] ^ W[65] ^ W[63], 1); + ROUND(B,C,D,E,A, PAR, K3, W[79]); - for (t = 60; t < 80; t++) { - temp = ROTL32(A, 5) + (B ^ C ^ D) + E + W[t] + 0xCA62C1D6; - E = D; - D = C; - C = ROTL32(B, 30); - B = A; - A = temp; - } hash[0] += A; hash[1] += B; - hash[2] += C; - hash[3] += D; - hash[4] += E; + hash[2] += ROTL32(C,30); + hash[3] += ROTL32(D,30); + hash[4] += ROTL32(E,30); } /** diff --git a/Utilities/cmlibrhash/librhash/sha256.c b/Utilities/cmlibrhash/librhash/sha256.c index 21a69aae238..69d28ce8f2a 100644 --- a/Utilities/cmlibrhash/librhash/sha256.c +++ b/Utilities/cmlibrhash/librhash/sha256.c @@ -61,7 +61,7 @@ static const unsigned rhash_k256[64] = { ROUND(a,b,c,d,e,f,g,h, k[n], RECALCULATE_W(W, n)) /** - * Initialize context before calculaing hash. + * Initialize context before calculating hash. * * @param ctx context to initialize */ @@ -74,7 +74,7 @@ void rhash_sha256_init(sha256_ctx* ctx) 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; - + memset(ctx->message, 0, sizeof(ctx->message)); ctx->length = 0; ctx->digest_length = sha256_hash_size; @@ -83,7 +83,7 @@ void rhash_sha256_init(sha256_ctx* ctx) } /** - * Initialize context before calculaing hash. + * Initialize context before calculating hash. * * @param ctx context to initialize */ @@ -96,7 +96,7 @@ void rhash_sha224_init(struct sha256_ctx* ctx) 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 }; - + memset(ctx->message, 0, sizeof(ctx->message)); ctx->length = 0; ctx->digest_length = sha224_hash_size; diff --git a/Utilities/cmlibrhash/librhash/sha256.h b/Utilities/cmlibrhash/librhash/sha256.h index 3625cfe2029..33ce9d91fdb 100644 --- a/Utilities/cmlibrhash/librhash/sha256.h +++ b/Utilities/cmlibrhash/librhash/sha256.h @@ -23,7 +23,7 @@ typedef struct sha256_ctx void rhash_sha224_init(sha256_ctx* ctx); void rhash_sha256_init(sha256_ctx* ctx); void rhash_sha256_update(sha256_ctx* ctx, const unsigned char* data, size_t length); -void rhash_sha256_final(sha256_ctx* ctx, unsigned char result[32]); +void rhash_sha256_final(sha256_ctx* ctx, unsigned char* result); #ifdef __cplusplus } /* extern "C" */ diff --git a/Utilities/cmlibrhash/librhash/sha512.c b/Utilities/cmlibrhash/librhash/sha512.c index 555e6ef596d..a9901ddf282 100644 --- a/Utilities/cmlibrhash/librhash/sha512.c +++ b/Utilities/cmlibrhash/librhash/sha512.c @@ -91,7 +91,7 @@ void rhash_sha512_init(sha512_ctx* ctx) I64(0xa54ff53a5f1d36f1), I64(0x510e527fade682d1), I64(0x9b05688c2b3e6c1f), I64(0x1f83d9abfb41bd6b), I64(0x5be0cd19137e2179) }; - + memset(ctx->message, 0, sizeof(ctx->message)); ctx->length = 0; ctx->digest_length = sha512_hash_size; @@ -100,7 +100,7 @@ void rhash_sha512_init(sha512_ctx* ctx) } /** - * Initialize context before calculaing hash. + * Initialize context before calculating hash. * * @param ctx context to initialize */ @@ -114,7 +114,7 @@ void rhash_sha384_init(struct sha512_ctx* ctx) I64(0x152fecd8f70e5939), I64(0x67332667ffc00b31), I64(0x8eb44a8768581511), I64(0xdb0c2e0d64f98fa7), I64(0x47b5481dbefa4fa4) }; - + memset(ctx->message, 0, sizeof(ctx->message)); ctx->length = 0; ctx->digest_length = sha384_hash_size; diff --git a/Utilities/cmlibrhash/librhash/util.c b/Utilities/cmlibrhash/librhash/util.c new file mode 100644 index 00000000000..82664608d83 --- /dev/null +++ b/Utilities/cmlibrhash/librhash/util.c @@ -0,0 +1,61 @@ +/* util.c - memory functions. + * + * Copyright (c) 2020, Aleksey Kravchenko + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ +#include "util.h" + +#if defined(HAS_POSIX_ALIGNED_ALLOC) + +#include + +void* rhash_px_aalloc(size_t alignment, size_t size) +{ + void* ptr; + if ((errno = posix_memalign(&ptr, alignment, size)) != 0) + return NULL; + return ptr; +} + +#elif defined(HAS_GENERIC_ALIGNED_ALLOC) + +#include +#include + +void* rhash_aligned_alloc(size_t alignment, size_t size) +{ + unsigned char* block = (unsigned char*)malloc(size + alignment); + assert((alignment & (alignment - 1)) == 0); + assert(alignment >= sizeof(void*)); + if (block) { + const size_t alignment_mask = (alignment - 1); + unsigned char* basement = block + sizeof(void*); + size_t offset = ((unsigned char*)0 - basement) & alignment_mask; + void** result = (void**)(basement + offset); + assert((((unsigned char*)result - (unsigned char*)0) % alignment) == 0); + result[-1] = block; /* store original pointer */ + return result; + } + return NULL; +} + +void rhash_aligned_free(void* ptr) +{ + void** pfree = (void**)ptr; + if (ptr) + free(pfree[-1]); +} + +#else +typedef int dummy_declaration_required_by_strict_iso_c; +#endif /* HAS_POSIX_ALIGNED_ALLOC / HAS_GENERIC_ALIGNED_ALLOC */ diff --git a/Utilities/cmlibrhash/librhash/util.h b/Utilities/cmlibrhash/librhash/util.h index 57cae9b5311..4da1be69fac 100644 --- a/Utilities/cmlibrhash/librhash/util.h +++ b/Utilities/cmlibrhash/librhash/util.h @@ -2,10 +2,15 @@ #ifndef UTIL_H #define UTIL_H +#include /* for aligned_alloc and __GLIBC__ version macros */ + #ifdef __cplusplus extern "C" { #endif +/* compile-time assert */ +#define RHASH_ASSERT(cond) (void)sizeof(char[1 - 2 * !(cond)]) + #if (defined(__GNUC__) && __GNUC__ >= 4 && (__GNUC__ > 4 || __GNUC_MINOR__ >= 1) \ && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4)) \ || (defined(__INTEL_COMPILER) && !defined(_WIN32)) @@ -24,6 +29,50 @@ extern "C" { # define NO_ATOMIC_BUILTINS #endif +/* alignment macros */ +#define DEFAULT_ALIGNMENT 64 +#define ALIGN_SIZE_BY(size, align) (((size) + ((align) - 1)) & ~((align) - 1)) +#define IS_SIZE_ALIGNED_BY(size, align) (((size) & ((align) - 1)) == 0) +#define IS_PTR_ALIGNED_BY(ptr, align) IS_SIZE_ALIGNED_BY((uintptr_t)(ptr), (align)) + +/* define rhash_aligned_alloc() and rhash_aligned_free() */ +#if !defined(NO_WIN32_ALIGNED_ALLOC) && defined(_WIN32) + +# define HAS_WIN32_ALIGNED_ALLOC +# include +# define rhash_aligned_alloc(alignment, size) _aligned_malloc((size), (alignment)) +# define rhash_aligned_free(ptr) _aligned_free(ptr) + +#elif !defined(NO_STDC_ALIGNED_ALLOC) && (__STDC_VERSION__ >= 201112L || defined(_ISOC11_SOURCE)) \ + && !(defined(__GLIBC__) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 16))) \ + && !(defined(__ibmxl__) && defined(__clang__) && defined(__linux__)) \ + && !defined(__APPLE__) && !defined(__HAIKU__) && !defined(__sun) \ + && (!defined(__ANDROID_API__) || __ANDROID_API__ >= 28) + +# define HAS_STDC_ALIGNED_ALLOC +# define rhash_aligned_alloc(alignment, size) aligned_alloc((alignment), ALIGN_SIZE_BY(size, alignment)) +# define rhash_aligned_free(ptr) free(ptr) + +#else /* defined(_WIN32) ... */ + +# include "ustd.h" /* for _POSIX_VERSION macro */ + +# if !defined(NO_POSIX_ALIGNED_ALLOC) && (_POSIX_VERSION >= 200112L || _XOPEN_SOURCE >= 600) + +# define HAS_POSIX_ALIGNED_ALLOC +# define rhash_aligned_alloc(alignment, size) rhash_px_aalloc((alignment), ALIGN_SIZE_BY(size, sizeof(void*))) +# define rhash_aligned_free(ptr) free(ptr) +void* rhash_px_aalloc(size_t size, size_t alignment); + +# else + +# define HAS_GENERIC_ALIGNED_ALLOC +void* rhash_aligned_alloc(size_t alignment, size_t size); +void rhash_aligned_free(void* ptr); + +# endif /* !defined(NO_POSIX_ALIGNED_ALLOC) ... */ +#endif /* defined(_WIN32) ... */ + #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ diff --git a/Utilities/cmlibuv/CMakeLists.txt b/Utilities/cmlibuv/CMakeLists.txt index ad3d433822a..495b5ed0b4f 100644 --- a/Utilities/cmlibuv/CMakeLists.txt +++ b/Utilities/cmlibuv/CMakeLists.txt @@ -165,6 +165,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS") include/uv/posix.h ) list(APPEND uv_defines + _GNU_SOURCE ) list(APPEND uv_sources src/unix/cygwin.c @@ -218,24 +219,6 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") list(APPEND uv_libraries - kvm - ) - list(APPEND uv_headers - include/uv/bsd.h - ) - list(APPEND uv_sources - src/unix/bsd-ifaddrs.c - src/unix/bsd-proctitle.c - src/unix/freebsd.c - src/unix/kqueue.c - src/unix/posix-hrtime.c - ) -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "kFreeBSD") - list(APPEND uv_libraries - freebsd-glue - kvm ) list(APPEND uv_headers include/uv/bsd.h @@ -267,7 +250,6 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") list(APPEND uv_libraries - kvm ) list(APPEND uv_headers include/uv/bsd.h @@ -294,27 +276,16 @@ if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") ) list(APPEND uv_defines __EXTENSIONS__ + _XOPEN_SOURCE=600 ) + if(NOT CMAKE_C_STANDARD OR CMAKE_C_STANDARD EQUAL 90) + set(CMAKE_C_STANDARD 11) + endif() if(CMAKE_SYSTEM_VERSION STREQUAL "5.10") - set(CMAKE_C_STANDARD 90) - if(CMAKE_VERSION VERSION_LESS 3.8.20170504 AND CMAKE_C_COMPILER_ID STREQUAL "SunPro" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.14) - # The running version of CMake does not know how to add this flag. - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c90") - endif() - list(APPEND uv_defines - _XOPEN_SOURCE=500 - ) - else() - if(NOT CMAKE_C_STANDARD OR CMAKE_C_STANDARD EQUAL 90) - set(CMAKE_C_STANDARD 11) - endif() - if(CMAKE_VERSION VERSION_LESS 3.8.20170505 AND CMAKE_C_COMPILER_ID STREQUAL "SunPro" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5.14) - # The running version of CMake does not know how to add this flag. - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -xc99") + list(APPEND uv_defines SUNOS_NO_IFADDRS) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i386|x86_64)$") + list(APPEND uv_defines CMAKE_NO_MKDTEMP) endif() - list(APPEND uv_defines - _XOPEN_SOURCE=600 - ) endif() list(APPEND uv_sources src/unix/no-proctitle.c @@ -331,6 +302,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "HP-UX") ) list(APPEND uv_defines _XOPEN_SOURCE_EXTENDED + CMAKE_NO_MKDTEMP ) list(APPEND uv_sources src/unix/hpux.c @@ -365,4 +337,8 @@ add_library(cmlibuv STATIC ${uv_sources}) target_link_libraries(cmlibuv ${uv_libraries}) set_property(TARGET cmlibuv PROPERTY COMPILE_DEFINITIONS ${uv_defines}) +if(WIN32 AND CMake_BUILD_PCH) + target_precompile_headers(cmlibuv PRIVATE "include/uv.h" "src/win/internal.h") +endif() + install(FILES LICENSE DESTINATION ${CMAKE_DOC_DIR}/cmlibuv) diff --git a/Utilities/cmlibuv/include/uv.h b/Utilities/cmlibuv/include/uv.h index ffe34ecb1ce..94113d11b79 100644 --- a/Utilities/cmlibuv/include/uv.h +++ b/Utilities/cmlibuv/include/uv.h @@ -1080,7 +1080,20 @@ enum uv_process_flags { * option is only meaningful on Windows systems. On Unix it is silently * ignored. */ - UV_PROCESS_WINDOWS_HIDE_GUI = (1 << 6) + UV_PROCESS_WINDOWS_HIDE_GUI = (1 << 6), + /* + * On Windows, if the path to the program to execute, specified in + * uv_process_options_t's file field, has a directory component, + * search for the exact file name before trying variants with + * extensions like '.exe' or '.cmd'. + */ + UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7), + /* + * Spawn the child process with the error mode of its parent. + * This option is only meaningful on Windows systems. On Unix + * it is silently ignored. + */ + UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE = (1 << 8) }; /* diff --git a/Utilities/cmlibuv/src/idna.c b/Utilities/cmlibuv/src/idna.c index 93d982ca018..858b19d00ee 100644 --- a/Utilities/cmlibuv/src/idna.c +++ b/Utilities/cmlibuv/src/idna.c @@ -274,6 +274,9 @@ long uv__idna_toascii(const char* s, const char* se, char* d, char* de) { char* ds; int rc; + if (s == se) + return UV_EINVAL; + ds = d; si = s; @@ -308,8 +311,9 @@ long uv__idna_toascii(const char* s, const char* se, char* d, char* de) { return rc; } - if (d < de) - *d++ = '\0'; + if (d >= de) + return UV_EINVAL; + *d++ = '\0'; return d - ds; /* Number of bytes written. */ } diff --git a/Utilities/cmlibuv/src/unix/core.c b/Utilities/cmlibuv/src/unix/core.c index d0b0e0069b0..83e8b611aab 100644 --- a/Utilities/cmlibuv/src/unix/core.c +++ b/Utilities/cmlibuv/src/unix/core.c @@ -366,6 +366,7 @@ static int uv__backend_timeout(const uv_loop_t* loop) { (uv__has_active_handles(loop) || uv__has_active_reqs(loop)) && QUEUE_EMPTY(&loop->pending_queue) && QUEUE_EMPTY(&loop->idle_handles) && + (loop->flags & UV_LOOP_REAP_CHILDREN) == 0 && loop->closing_handles == NULL) return uv__next_timeout(loop); return 0; diff --git a/Utilities/cmlibuv/src/unix/fs.c b/Utilities/cmlibuv/src/unix/fs.c index e2db3ad6683..ce7076b2c2a 100644 --- a/Utilities/cmlibuv/src/unix/fs.c +++ b/Utilities/cmlibuv/src/unix/fs.c @@ -282,13 +282,30 @@ static ssize_t uv__fs_futime(uv_fs_t* req) { #endif } -#if (defined(__sun) || defined(__hpux)) && (_XOPEN_SOURCE < 600 || defined(CMAKE_BOOTSTRAP)) -static char* uv__mkdtemp(char *template) -{ +#if defined(CMAKE_BOOTSTRAP) && defined(__sun) && defined(__i386) +# define CMAKE_NO_MKDTEMP +#endif + +#if defined(CMAKE_NO_MKDTEMP) +static char* uv__mkdtemp_fallback(char *template) { if (!mktemp(template) || mkdir(template, 0700)) return NULL; return template; } +static char* (*uv__mkdtemp_f)(char*); +static void uv__mkdtemp_initonce(void) { + uv__mkdtemp_f = (char* (*)(char*)) dlsym(RTLD_DEFAULT, "mkdtemp"); + dlerror(); /* Ignore/cleanup dlsym errors. */ + if (uv__mkdtemp_f == NULL) { + uv__mkdtemp_f = uv__mkdtemp_fallback; + } +} +static char* uv__mkdtemp(char *template) +{ + static uv_once_t once = UV_ONCE_INIT; + uv_once(&once, uv__mkdtemp_initonce); + return uv__mkdtemp_f(template); +} #else #define uv__mkdtemp mkdtemp #endif diff --git a/Utilities/cmlibuv/src/unix/internal.h b/Utilities/cmlibuv/src/unix/internal.h index f41ee3cd9a4..1b10516bfb8 100644 --- a/Utilities/cmlibuv/src/unix/internal.h +++ b/Utilities/cmlibuv/src/unix/internal.h @@ -272,6 +272,7 @@ int uv__epoll_init(uv_loop_t* loop); int uv__platform_loop_init(uv_loop_t* loop); void uv__platform_loop_delete(uv_loop_t* loop); void uv__platform_invalidate_fd(uv_loop_t* loop, int fd); +int uv__process_init(uv_loop_t* loop); /* various */ void uv__async_close(uv_async_t* handle); diff --git a/Utilities/cmlibuv/src/unix/kqueue.c b/Utilities/cmlibuv/src/unix/kqueue.c index 5dac76ae753..581c55280dc 100644 --- a/Utilities/cmlibuv/src/unix/kqueue.c +++ b/Utilities/cmlibuv/src/unix/kqueue.c @@ -235,6 +235,9 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { ARRAY_SIZE(events), timeout == -1 ? NULL : &spec); + if (nfds == -1) + assert(errno == EINTR); + if (pset != NULL) pthread_sigmask(SIG_UNBLOCK, pset, NULL); @@ -242,36 +245,26 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { * timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the * operating system didn't reschedule our process while in the syscall. */ - SAVE_ERRNO(uv__update_time(loop)); - - if (nfds == 0) { - if (reset_timeout != 0) { - timeout = user_timeout; - reset_timeout = 0; - if (timeout == -1) - continue; - if (timeout > 0) - goto update_timeout; + uv__update_time(loop); + + if (nfds == 0 || nfds == -1) { + /* If kqueue is empty or interrupted, we might still have children ready + * to reap immediately. */ + if (loop->flags & UV_LOOP_REAP_CHILDREN) { + loop->flags &= ~UV_LOOP_REAP_CHILDREN; + uv__wait_children(loop); + assert((reset_timeout == 0 ? timeout : user_timeout) == 0); + return; /* Equivalent to fall-through behavior. */ } - assert(timeout != -1); - return; - } - - if (nfds == -1) { - if (errno != EINTR) - abort(); - if (reset_timeout != 0) { timeout = user_timeout; reset_timeout = 0; - } - - if (timeout == 0) + } else if (nfds == 0) { + /* Reached the user timeout value. */ + assert(timeout != -1); return; - - if (timeout == -1) - continue; + } /* Interrupted by a signal. Update timeout and poll again. */ goto update_timeout; @@ -423,13 +416,13 @@ void uv__io_poll(uv_loop_t* loop, int timeout) { return; } +update_timeout: if (timeout == 0) return; if (timeout == -1) continue; -update_timeout: assert(timeout > 0); diff = loop->time - base; diff --git a/Utilities/cmlibuv/src/unix/loop.c b/Utilities/cmlibuv/src/unix/loop.c index a88e71c3393..89399269383 100644 --- a/Utilities/cmlibuv/src/unix/loop.c +++ b/Utilities/cmlibuv/src/unix/loop.c @@ -79,12 +79,9 @@ int uv_loop_init(uv_loop_t* loop) { goto fail_platform_init; uv__signal_global_once_init(); - err = uv_signal_init(loop, &loop->child_watcher); + err = uv__process_init(loop); if (err) goto fail_signal_init; - - uv__handle_unref(&loop->child_watcher); - loop->child_watcher.flags |= UV_HANDLE_INTERNAL; QUEUE_INIT(&loop->process_handles); err = uv_rwlock_init(&loop->cloexec_lock); diff --git a/Utilities/cmlibuv/src/unix/process.c b/Utilities/cmlibuv/src/unix/process.c index 0de5c4695bb..729a44b4722 100644 --- a/Utilities/cmlibuv/src/unix/process.c +++ b/Utilities/cmlibuv/src/unix/process.c @@ -37,7 +37,11 @@ #include #if defined(__APPLE__) -# include + /* macOS 10.8 and later have a working posix_spawn */ +# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 +# define UV_USE_APPLE_POSIX_SPAWN +# include +# endif # include # include # include @@ -75,11 +79,9 @@ extern char **environ; #endif #endif -#if defined(__APPLE__) || \ - defined(__DragonFly__) || \ - defined(__FreeBSD__) || \ - defined(__NetBSD__) || \ - defined(__OpenBSD__) +#ifdef CMAKE_BOOTSTRAP +#define UV_USE_SIGCHLD +#elif defined(UV_HAVE_KQUEUE) #include #else #define UV_USE_SIGCHLD @@ -90,8 +92,28 @@ static void uv__chld(uv_signal_t* handle, int signum) { assert(signum == SIGCHLD); uv__wait_children(handle->loop); } + + +int uv__process_init(uv_loop_t* loop) { + int err; + + err = uv_signal_init(loop, &loop->child_watcher); + if (err) + return err; + uv__handle_unref(&loop->child_watcher); + loop->child_watcher.flags |= UV_HANDLE_INTERNAL; + return 0; +} + + +#else +int uv__process_init(uv_loop_t* loop) { + memset(&loop->child_watcher, 0, sizeof(loop->child_watcher)); + return 0; +} #endif + void uv__wait_children(uv_loop_t* loop) { uv_process_t* process; int exit_status; @@ -116,6 +138,7 @@ void uv__wait_children(uv_loop_t* loop) { continue; options = 0; process->flags &= ~UV_HANDLE_REAP; + loop->nfds--; #else options = WNOHANG; #endif @@ -428,7 +451,7 @@ static void uv__process_child_init(const uv_process_options_t* options, #endif -#if defined(__APPLE__) +#if defined(UV_USE_APPLE_POSIX_SPAWN) typedef struct uv__posix_spawn_fncs_tag { struct { int (*addchdir_np)(const posix_spawn_file_actions_t *, const char *); @@ -880,7 +903,7 @@ static int uv__spawn_and_init_child( int exec_errorno; ssize_t r; -#if defined(__APPLE__) +#if defined(UV_USE_APPLE_POSIX_SPAWN) uv_once(&posix_spawn_init_once, uv__spawn_init_posix_spawn); /* Special child process spawn case for macOS Big Sur (11.0) onwards @@ -1006,10 +1029,12 @@ int uv_spawn(uv_loop_t* loop, assert(!(options->flags & ~(UV_PROCESS_DETACHED | UV_PROCESS_SETGID | UV_PROCESS_SETUID | + UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME | UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_HIDE_CONSOLE | UV_PROCESS_WINDOWS_HIDE_GUI | - UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); + UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | + UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE))); uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); QUEUE_INIT(&process->queue); @@ -1067,6 +1092,10 @@ int uv_spawn(uv_loop_t* loop, process->flags |= UV_HANDLE_REAP; loop->flags |= UV_LOOP_REAP_CHILDREN; } + /* This prevents uv__io_poll() from bailing out prematurely, being unaware + * that we added an event here for it to react to. We will decrement this + * again after the waitpid call succeeds. */ + loop->nfds++; #endif process->pid = pid; @@ -1135,6 +1164,8 @@ int uv_kill(int pid, int signum) { void uv__process_close(uv_process_t* handle) { QUEUE_REMOVE(&handle->queue); uv__handle_stop(handle); +#ifdef UV_USE_SIGCHLD if (QUEUE_EMPTY(&handle->loop->process_handles)) uv_signal_stop(&handle->loop->child_watcher); +#endif } diff --git a/Utilities/cmlibuv/src/unix/signal.c b/Utilities/cmlibuv/src/unix/signal.c index 1133c73a955..bb70523f561 100644 --- a/Utilities/cmlibuv/src/unix/signal.c +++ b/Utilities/cmlibuv/src/unix/signal.c @@ -279,6 +279,8 @@ static int uv__signal_loop_once_init(uv_loop_t* loop) { int uv__signal_loop_fork(uv_loop_t* loop) { + if (loop->signal_pipefd[0] == -1) + return 0; uv__io_stop(loop, &loop->signal_io_watcher, POLLIN); uv__close(loop->signal_pipefd[0]); uv__close(loop->signal_pipefd[1]); diff --git a/Utilities/cmlibuv/src/win/process.c b/Utilities/cmlibuv/src/win/process.c index 248b7ea34f0..59db8f81ac2 100644 --- a/Utilities/cmlibuv/src/win/process.c +++ b/Utilities/cmlibuv/src/win/process.c @@ -90,7 +90,6 @@ static void uv__init_global_job_handle(void) { info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK | - JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; uv_global_job_handle_ = CreateJobObjectW(&attr, NULL); @@ -102,6 +101,21 @@ static void uv__init_global_job_handle(void) { &info, sizeof info)) uv_fatal_error(GetLastError(), "SetInformationJobObject"); + + + if (!AssignProcessToJobObject(uv_global_job_handle_, GetCurrentProcess())) { + /* Make sure this handle is functional. The Windows kernel has a bug that + * if the first use of AssignProcessToJobObject is for a Windows Store + * program, subsequent attempts to use the handle with fail with + * INVALID_PARAMETER (87). This is possibly because all uses of the handle + * must be for the same Terminal Services session. We can ensure it is tied + * to our current session now by adding ourself to it. We could remove + * ourself afterwards, but there doesn't seem to be a reason to. + */ + DWORD err = GetLastError(); + if (err != ERROR_ACCESS_DENIED) + uv_fatal_error(err, "AssignProcessToJobObject"); + } } @@ -314,8 +328,9 @@ static WCHAR* path_search_walk_ext(const WCHAR *dir, * - If there's really only a filename, check the current directory for file, * then search all path directories. * - * - If filename specified has *any* extension, search for the file with the - * specified extension first. + * - If filename specified has *any* extension, or already contains a path + * and the UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME flag is specified, + * search for the file with the exact specified filename first. * * - If the literal filename is not found in a directory, try *appending* * (not replacing) .com first and then .exe. @@ -341,7 +356,8 @@ static WCHAR* path_search_walk_ext(const WCHAR *dir, */ static WCHAR* search_path(const WCHAR *file, WCHAR *cwd, - const WCHAR *path) { + const WCHAR *path, + unsigned int flags) { int file_has_dir; WCHAR* result = NULL; WCHAR *file_name_start; @@ -382,19 +398,21 @@ static WCHAR* search_path(const WCHAR *file, file, file_name_start - file, file_name_start, file_len - (file_name_start - file), cwd, cwd_len, - name_has_ext); + name_has_ext || (flags & UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME)); } else { dir_end = path; - /* The file is really only a name; look in cwd first, then scan path */ - result = path_search_walk_ext(L"", 0, - file, file_len, - cwd, cwd_len, - name_has_ext); + if (NeedCurrentDirectoryForExePathW(L"")) { + /* The file is really only a name; look in cwd first, then scan path */ + result = path_search_walk_ext(L"", 0, + file, file_len, + cwd, cwd_len, + name_has_ext); + } while (result == NULL) { - if (*dir_end == L'\0') { + if (dir_end == NULL || *dir_end == L'\0') { break; } @@ -970,10 +988,12 @@ int uv_spawn(uv_loop_t* loop, assert(!(options->flags & ~(UV_PROCESS_DETACHED | UV_PROCESS_SETGID | UV_PROCESS_SETUID | + UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME | UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_HIDE_CONSOLE | UV_PROCESS_WINDOWS_HIDE_GUI | - UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); + UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | + UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE))); err = uv__utf8_to_utf16_alloc(options->file, &application); if (err) @@ -1027,22 +1047,19 @@ int uv_spawn(uv_loop_t* loop, DWORD path_len, r; path_len = GetEnvironmentVariableW(L"PATH", NULL, 0); - if (path_len == 0) { - err = GetLastError(); - goto done; - } - - alloc_path = (WCHAR*) uv__malloc(path_len * sizeof(WCHAR)); - if (alloc_path == NULL) { - err = ERROR_OUTOFMEMORY; - goto done; - } - path = alloc_path; + if (path_len != 0) { + alloc_path = (WCHAR*) uv__malloc(path_len * sizeof(WCHAR)); + if (alloc_path == NULL) { + err = ERROR_OUTOFMEMORY; + goto done; + } + path = alloc_path; - r = GetEnvironmentVariableW(L"PATH", path, path_len); - if (r == 0 || r >= path_len) { - err = GetLastError(); - goto done; + r = GetEnvironmentVariableW(L"PATH", path, path_len); + if (r == 0 || r >= path_len) { + err = GetLastError(); + goto done; + } } } @@ -1052,7 +1069,8 @@ int uv_spawn(uv_loop_t* loop, application_path = search_path(application, cwd, - path); + path, + options->flags); if (application_path == NULL) { /* Not found. */ err = ERROR_FILE_NOT_FOUND; @@ -1065,14 +1083,24 @@ int uv_spawn(uv_loop_t* loop, startup.lpTitle = NULL; startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; +#if 1 + /* cmake does not need libuv's support for passing file descriptors >= 3 + to the MSVC C run-time in the child. Avoid using reserved members. */ + startup.cbReserved2 = 0; + startup.lpReserved2 = NULL; +#else startup.cbReserved2 = uv__stdio_size(process->child_stdio_buffer); startup.lpReserved2 = (BYTE*) process->child_stdio_buffer; +#endif startup.hStdInput = uv__stdio_handle(process->child_stdio_buffer, 0); startup.hStdOutput = uv__stdio_handle(process->child_stdio_buffer, 1); startup.hStdError = uv__stdio_handle(process->child_stdio_buffer, 2); - process_flags = CREATE_UNICODE_ENVIRONMENT; + process_flags = CREATE_UNICODE_ENVIRONMENT | CREATE_DEFAULT_ERROR_MODE; + if (options->flags & UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE) { + process_flags &= ~(CREATE_DEFAULT_ERROR_MODE); + } if ((options->flags & UV_PROCESS_WINDOWS_HIDE_CONSOLE) || (options->flags & UV_PROCESS_WINDOWS_HIDE)) { @@ -1104,6 +1132,7 @@ int uv_spawn(uv_loop_t* loop, * breakaway. */ process_flags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP; + process_flags |= CREATE_SUSPENDED; } if (options->cpumask != NULL) { @@ -1162,20 +1191,8 @@ int uv_spawn(uv_loop_t* loop, TerminateProcess(info.hProcess, 1); goto done; } - - /* The process affinity of the child is set. Let it run. */ - if (ResumeThread(info.hThread) == ((DWORD)-1)) { - err = GetLastError(); - TerminateProcess(info.hProcess, 1); - goto done; - } } - /* Spawn succeeded. Beyond this point, failure is reported asynchronously. */ - - process->process_handle = info.hProcess; - process->pid = info.dwProcessId; - /* If the process isn't spawned as detached, assign to the global job object * so windows will kill it when the parent process dies. */ if (!(options->flags & UV_PROCESS_DETACHED)) { @@ -1198,6 +1215,19 @@ int uv_spawn(uv_loop_t* loop, } } + if (process_flags & CREATE_SUSPENDED) { + if (ResumeThread(info.hThread) == ((DWORD)-1)) { + err = GetLastError(); + TerminateProcess(info.hProcess, 1); + goto done; + } + } + + /* Spawn succeeded. Beyond this point, failure is reported asynchronously. */ + + process->process_handle = info.hProcess; + process->pid = info.dwProcessId; + /* Set IPC pid to all IPC pipes. */ for (i = 0; i < options->stdio_count; i++) { const uv_stdio_container_t* fdopt = &options->stdio[i]; diff --git a/Utilities/cmllpkgc/.gitattributes b/Utilities/cmllpkgc/.gitattributes new file mode 100644 index 00000000000..562b12e16eb --- /dev/null +++ b/Utilities/cmllpkgc/.gitattributes @@ -0,0 +1 @@ +* -whitespace diff --git a/Utilities/cmllpkgc/CMakeLists.txt b/Utilities/cmllpkgc/CMakeLists.txt new file mode 100644 index 00000000000..88a382d99bd --- /dev/null +++ b/Utilities/cmllpkgc/CMakeLists.txt @@ -0,0 +1,9 @@ +# Disable warnings to avoid changing 3rd party code. +if(CMAKE_C_COMPILER_ID MATCHES + "^(GNU|LCC|Clang|AppleClang|IBMClang|XLClang|XL|VisualAge|SunPro|HP|Intel|IntelLLVM|NVHPC)$") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w") +elseif(CMAKE_C_COMPILER_ID STREQUAL "PathScale") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -woffall") +endif() + +add_library(cmllpkgc STATIC llpkgc.c llpkgc__internal.c) diff --git a/Utilities/cmllpkgc/README.rst b/Utilities/cmllpkgc/README.rst new file mode 100644 index 00000000000..233a5750fe4 --- /dev/null +++ b/Utilities/cmllpkgc/README.rst @@ -0,0 +1,14 @@ +llpkgc +****** + +This code is generated by the upstream llpkgc repository located at: +https://gitlab.kitware.com/utils/llpkgc + +Generally, updates to llpkgc should be made in the upstream utilities library +unless they are exceptionally specific to CMake itself. + +The upstream repository does not vendor a generated copy of the parser, so +the associated update script for this dependency runs the generator and +vendors it appropriately. This requires a reasonably up-to-date version of +npm be available in addition to the normal 3rd-party update tooling +requirements. diff --git a/Utilities/cmllpkgc/llpkgc.c b/Utilities/cmllpkgc/llpkgc.c new file mode 100644 index 00000000000..fdb22b5188f --- /dev/null +++ b/Utilities/cmllpkgc/llpkgc.c @@ -0,0 +1,205 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ + +#include +#include + +#include "llpkgc.h" + +#define CALLBACK_MAYBE(PARSER, NAME) \ + do { \ + const llpkgc_settings_t* settings; \ + settings = (const llpkgc_settings_t*) (PARSER)->settings; \ + if(settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER)); \ + } while(0) + +#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ + do { \ + const llpkgc_settings_t* settings; \ + settings = (const llpkgc_settings_t*) (PARSER)->settings; \ + if(settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER), (START), (LEN)); \ + if(err == -1) { \ + err = PCE_USER; \ + llpkgc_set_error_reason((PARSER), "Span callback error in " #NAME); \ + } \ + } while(0) + +void llpkgc_init(llpkgc_t* parser, const llpkgc_settings_t* settings) { + llpkgc__internal_init(parser); + + parser->settings = (void*) settings; +} + +void llpkgc_reset(llpkgc_t* parser) { + llpkgc_settings_t* settings = parser->settings; + void* data = parser->data; + + llpkgc__internal_init(parser); + + parser->settings = settings; + parser->data = data; +} + +void llpkgc_settings_init(llpkgc_settings_t* settings) { + memset(settings, 0, sizeof(*settings)); +} + +llpkgc_errno_t llpkgc_execute(llpkgc_t* parser, const char* data, size_t len) { + return llpkgc__internal_execute(parser, data, data + len); +} + +llpkgc_errno_t llpkgc_finish(llpkgc_t* parser) { + if(parser->error != 0) + return parser->error; + + int err; + // ToDo: Better handling of user callback errors here + if(parser->unfinished_ == 1) { + parser->reason = "Invalid EOF state"; + parser->error = PCE_UNFINISHED; + return PCE_UNFINISHED; + } else if(parser->unfinished_ == 2) { + CALLBACK_MAYBE(parser, on_value_literal_complete); + if(err != PCE_OK) { + parser->error = err; + return err; + } + CALLBACK_MAYBE(parser, on_value_complete); + if(err != PCE_OK) { + parser->error = err; + return err; + } + } else if(parser->unfinished_ == 3) { + CALLBACK_MAYBE(parser, on_value_complete); + if(err != PCE_OK) { + parser->error = err; + return err; + } + } + + CALLBACK_MAYBE(parser, on_pkgc_complete); + return err; +} + +void llpkgc_pause(llpkgc_t* parser) { + if(parser->error != PCE_OK) { + return; + } + + parser->error = PCE_PAUSED; + parser->reason = "Paused"; +} + +void llpkgc_resume(llpkgc_t* parser) { + if(parser->error != PCE_PAUSED) { + return; + } + + parser->error = 0; +} + +llpkgc_errno_t llpkgc_get_errno(const llpkgc_t* parser) { + return parser->error; +} + +const char* llpkgc_get_error_reason(const llpkgc_t* parser) { + return parser->reason; +} + +void llpkgc_set_error_reason(llpkgc_t* parser, const char* reason) { + parser->reason = reason; +} + +const char* llpkgc_get_error_pos(const llpkgc_t* parser) { + return parser->error_pos; +} + +const char* llpkgc_errno_name(llpkgc_errno_t err) { + switch(err) { + case PCE_OK: + return "PCE_OK"; + case PCE_INTERNAL: + return "PCE_INTERNAL"; + case PCE_PAUSED: + return "PCE_PAUSED"; + case PCE_USER: + return "PCE_USER"; + case PCE_UNFINISHED: + return "PCE_UNFINISHED"; + } + return "INVALID_ERRNO"; +} + +int llpkgc__line_begin(llpkgc_t* s, const char* p, const char* endp) { + int err; + s->unfinished_ = 1; + CALLBACK_MAYBE(s, on_line_begin); + return err; +} + +int llpkgc__key_span(llpkgc_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_key, p, endp - p); + return err; +} + +int llpkgc__keyword_complete(llpkgc_t* s, const char* p, const char* endp) { + int err; + s->unfinished_ = 3; + CALLBACK_MAYBE(s, on_keyword_complete); + return err; +} + +int llpkgc__variable_complete(llpkgc_t* s, const char* p, const char* endp) { + int err; + s->unfinished_ = 3; + CALLBACK_MAYBE(s, on_variable_complete); + return err; +} + +int llpkgc__vallit_span(llpkgc_t* s, const char* p, const char* endp) { + int err; + if(s->escaped_) { + --endp; + s->escaped_ = 0; + } + s->unfinished_ = 2; + SPAN_CALLBACK_MAYBE(s, on_value_literal, p, endp - p); + return err; +} + +int llpkgc__vallit_complete(llpkgc_t* s, const char* p, const char* endp) { + int err; + s->unfinished_ = 3; + CALLBACK_MAYBE(s, on_value_literal_complete); + return err; +} + +int llpkgc__valvar_span(llpkgc_t* s, const char* p, const char* endp) { + int err; + s->unfinished_ = 1; + SPAN_CALLBACK_MAYBE(s, on_value_variable, p, endp - p); + return err; +} + +int llpkgc__valvar_complete(llpkgc_t* s, const char* p, const char* endp) { + int err; + s->unfinished_ = 3; + CALLBACK_MAYBE(s, on_value_variable_complete); + return err; +} + +int llpkgc__value_complete(llpkgc_t* s, const char* p, const char* endp) { + int err; + s->unfinished_ = 0; + CALLBACK_MAYBE(s, on_value_complete); + return err; +} diff --git a/Utilities/cmllpkgc/llpkgc.h b/Utilities/cmllpkgc/llpkgc.h new file mode 100644 index 00000000000..7ab0fe9f8d5 --- /dev/null +++ b/Utilities/cmllpkgc/llpkgc.h @@ -0,0 +1,143 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ + +#ifndef INCLUDE_LLPKGC_API_H_ +#define INCLUDE_LLPKGC_API_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include + +#include "llpkgc__internal.h" + +#if defined(_MSC_VER) +#define LLPKGC_EXPORT __declspec(dllexport) +#else +#define LLPKGC_EXPORT __attribute__((visibility("default"))) +#endif + +typedef llpkgc__internal_t llpkgc_t; +typedef struct llpkgc_settings_s llpkgc_settings_t; + +typedef int (*llpkgc_data_cb)(llpkgc_t*, const char* at, size_t length); +typedef int (*llpkgc_cb)(llpkgc_t*); + +struct llpkgc_settings_s { + /* Possible return values 0, -1, PCE_USER */ + llpkgc_data_cb on_key; + llpkgc_data_cb on_value_literal; + llpkgc_data_cb on_value_variable; + + /* Possible return values 0, -1, `PCE_PAUSED` */ + llpkgc_cb on_line_begin; + llpkgc_cb on_keyword_complete; + llpkgc_cb on_variable_complete; + llpkgc_cb on_value_literal_complete; + llpkgc_cb on_value_variable_complete; + llpkgc_cb on_value_complete; + llpkgc_cb on_pkgc_complete; +}; + +enum llpkgc_errno { + PCE_OK = 0, + PCE_INTERNAL = 1, + PCE_PAUSED = 2, + PCE_USER = 3, + PCE_UNFINISHED = 4, +}; +typedef enum llpkgc_errno llpkgc_errno_t; + +/* Initialize the parser with user settings. + * + * NOTE: lifetime of `settings` has to be at least the same as the lifetime of + * the `parser` here. In practice, `settings` has to be either a static + * variable or be allocated with `malloc`, `new`, etc. + */ +LLPKGC_EXPORT +void llpkgc_init(llpkgc_t* parser, const llpkgc_settings_t* settings); + +/* Reset an already initialized parser back to the start state, preserving the + * existing callback settings and user data. + */ +LLPKGC_EXPORT +void llpkgc_reset(llpkgc_t* parser); + +/* Initialize the settings object */ +LLPKGC_EXPORT +void llpkgc_settings_init(llpkgc_settings_t* settings); + +/* Parse full or partial pc data, invoking user callbacks along the way. + * + * If any of `llpkgc_data_cb` returns errno not equal to `PCE_OK` - the parsing + * interrupts, and such errno is returned from `llpkgc_execute()`. If + * `PCE_PAUSED` was used as an errno, the execution can be resumed with + * `llpkgc_resume()` call. + * + * NOTE: if this function ever returns a non-pause type error, it will continue + * to return the same error upon each successive call up until `llpkgc_init()` + * or `llpkgc_reset()` are called. + */ +LLPKGC_EXPORT +llpkgc_errno_t llpkgc_execute(llpkgc_t* parser, const char* data, size_t len); + +/* This method should be called when the input has reached EOF + * + * This method will invoke `on_pkgc_complete()` callback if the file was + * terminated safely. Otherwise an error code will be returned. + */ +LLPKGC_EXPORT +llpkgc_errno_t llpkgc_finish(llpkgc_t* parser); + +/* Make further calls of `llpkgc_execute()` return `PCE_PAUSED` and set + * appropriate error reason. + * + * Important: do not call this from user callbacks! User callbacks must return + * `PCE_PAUSED` if pausing is required. + */ +LLPKGC_EXPORT +void llpkgc_pause(llpkgc_t* parser); + +/* Might be called to resume the execution after the pause in user's callback. + * See `llpkgc_execute()` above for details. + * + * Call this only if `llpkgc_execute()` returns `PCE_PAUSED`. + */ +LLPKGC_EXPORT +void llpkgc_resume(llpkgc_t* parser); + +/* Returns the latest return error */ +LLPKGC_EXPORT +llpkgc_errno_t llpkgc_get_errno(const llpkgc_t* parser); + +/* Returns the verbal explanation of the latest returned error. + * + * Note: User callback should set error reason when returning the error. See + * `llpkgc_set_error_reason()` for details. + */ +LLPKGC_EXPORT +const char* llpkgc_get_error_reason(const llpkgc_t* parser); + +/* Assign verbal description to the returned error. Must be called in user + * callbacks right before returning the errno. + * + * Note: `PCE_USER` error code might be useful in user callbacks. + */ +LLPKGC_EXPORT +void llpkgc_set_error_reason(llpkgc_t* parser, const char* reason); + +/* Returns the pointer to the last parsed byte before the returned error. The + * pointer is relative to the `data` argument of `llpkgc_execute()`. + * + * Note: this method might be useful for counting the number of parsed bytes. + */ +LLPKGC_EXPORT +const char* llpkgc_get_error_pos(const llpkgc_t* parser); + +/* Returns textual name of error code */ +LLPKGC_EXPORT +const char* llpkgc_errno_name(llpkgc_errno_t err); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLPKGC_API_H_ */ diff --git a/Utilities/cmllpkgc/llpkgc__internal.c b/Utilities/cmllpkgc/llpkgc__internal.c new file mode 100644 index 00000000000..9fa5856e15e --- /dev/null +++ b/Utilities/cmllpkgc/llpkgc__internal.c @@ -0,0 +1,1069 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ + +/* This code was generated by llpkgc, do not edit it by hand + See: https://gitlab.kitware.com/utils/llpkgc */ + + +#include +#include +#include + +#ifdef __SSE4_2__ + #ifdef _MSC_VER + #include + #else /* !_MSC_VER */ + #include + #endif /* _MSC_VER */ +#endif /* __SSE4_2__ */ + +#ifdef _MSC_VER + #define ALIGN(n) _declspec(align(n)) +#else /* !_MSC_VER */ + #define ALIGN(n) __attribute__((aligned(n))) +#endif /* _MSC_VER */ + +#include "llpkgc__internal.h" + +typedef int (*llpkgc__internal__span_cb)( + llpkgc__internal_t*, const char*, const char*); + +enum llparse_state_e { + s_error, + s_n_llpkgc__internal__n_comment, + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete, + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_1, + s_n_llpkgc__internal__n_literal_skip_dollar, + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_2, + s_n_llpkgc__internal__n_maybe_CR, + s_n_llpkgc__internal__n_skip_escaped_LF, + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_3, + s_n_llpkgc__internal__n_maybe_LF, + s_n_llpkgc__internal__n_skip_escaped_CR, + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_4, + s_n_llpkgc__internal__n_literal_skip_hash, + s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_1, + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_5, + s_n_llpkgc__internal__n_maybe_escaped, + s_n_llpkgc__internal__n_literal, + s_n_llpkgc__internal__n_variable_skip_dollar, + s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span, + s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_2, + s_n_llpkgc__internal__n_variable_close, + s_n_llpkgc__internal__n_variable_skip_curly, + s_n_llpkgc__internal__n_invoke_llpkgc__valvar_complete, + s_n_llpkgc__internal__n_variable, + s_n_llpkgc__internal__n_span_start_llpkgc__valvar_span, + s_n_llpkgc__internal__n_maybe_variable, + s_n_llpkgc__internal__n_expect_value, + s_n_llpkgc__internal__n_expect_sep, + s_n_llpkgc__internal__n_key, + s_n_llpkgc__internal__n_span_start_llpkgc__key_span, + s_n_llpkgc__internal__n_line_start, +}; +typedef enum llparse_state_e llparse_state_t; + +int llpkgc__key_span( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__vallit_span( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__valvar_span( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__line_begin( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__keyword_complete( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__value_complete( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__vallit_complete( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__internal__c_update_escaped_( + llpkgc__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->escaped_ = 1; + return 0; +} + +int llpkgc__valvar_complete( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__variable_complete( + llpkgc__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llpkgc__internal_init(llpkgc__internal_t* state) { + memset(state, 0, sizeof(*state)); + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_line_start; + return 0; +} + +static llparse_state_t llpkgc__internal__run( + llpkgc__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + int match; + switch ((llparse_state_t) (intptr_t) state->_current) { + case s_n_llpkgc__internal__n_comment: + s_n_llpkgc__internal__n_comment: { + if (p == endp) { + return s_n_llpkgc__internal__n_comment; + } + switch (*p) { + case 10: { + p++; + goto s_n_llpkgc__internal__n_line_start; + } + case 13: { + p++; + goto s_n_llpkgc__internal__n_line_start; + } + default: { + p++; + goto s_n_llpkgc__internal__n_comment; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete: + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete: { + switch (llpkgc__vallit_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete_1; + default: + goto s_n_llpkgc__internal__n_error_3; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_1: + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_1: { + switch (llpkgc__vallit_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete_2; + default: + goto s_n_llpkgc__internal__n_error_5; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_literal_skip_dollar: + s_n_llpkgc__internal__n_literal_skip_dollar: { + if (p == endp) { + return s_n_llpkgc__internal__n_literal_skip_dollar; + } + p++; + goto s_n_llpkgc__internal__n_maybe_variable; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_2: + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_2: { + switch (llpkgc__vallit_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_literal_skip_dollar; + default: + goto s_n_llpkgc__internal__n_error_7; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_maybe_CR: + s_n_llpkgc__internal__n_maybe_CR: { + if (p == endp) { + return s_n_llpkgc__internal__n_maybe_CR; + } + switch (*p) { + case 13: { + p++; + goto s_n_llpkgc__internal__n_expect_value; + } + default: { + goto s_n_llpkgc__internal__n_expect_value; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_skip_escaped_LF: + s_n_llpkgc__internal__n_skip_escaped_LF: { + if (p == endp) { + return s_n_llpkgc__internal__n_skip_escaped_LF; + } + p++; + goto s_n_llpkgc__internal__n_maybe_CR; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_3: + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_3: { + switch (llpkgc__vallit_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_skip_escaped_LF; + default: + goto s_n_llpkgc__internal__n_error_8; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_maybe_LF: + s_n_llpkgc__internal__n_maybe_LF: { + if (p == endp) { + return s_n_llpkgc__internal__n_maybe_LF; + } + switch (*p) { + case 10: { + p++; + goto s_n_llpkgc__internal__n_expect_value; + } + default: { + goto s_n_llpkgc__internal__n_expect_value; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_skip_escaped_CR: + s_n_llpkgc__internal__n_skip_escaped_CR: { + if (p == endp) { + return s_n_llpkgc__internal__n_skip_escaped_CR; + } + p++; + goto s_n_llpkgc__internal__n_maybe_LF; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_4: + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_4: { + switch (llpkgc__vallit_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_skip_escaped_CR; + default: + goto s_n_llpkgc__internal__n_error_9; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_literal_skip_hash: + s_n_llpkgc__internal__n_literal_skip_hash: { + if (p == endp) { + return s_n_llpkgc__internal__n_literal_skip_hash; + } + p++; + goto s_n_llpkgc__internal__n_literal; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_1: + s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_1: { + if (p == endp) { + return s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llpkgc__vallit_span; + goto s_n_llpkgc__internal__n_literal_skip_hash; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_5: + s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_5: { + switch (llpkgc__vallit_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_1; + default: + goto s_n_llpkgc__internal__n_error_10; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_maybe_escaped: + s_n_llpkgc__internal__n_maybe_escaped: { + if (p == endp) { + return s_n_llpkgc__internal__n_maybe_escaped; + } + switch (*p) { + case 10: { + goto s_n_llpkgc__internal__n_invoke_update_escaped_; + } + case 13: { + goto s_n_llpkgc__internal__n_invoke_update_escaped__1; + } + case '#': { + goto s_n_llpkgc__internal__n_invoke_update_escaped__2; + } + default: { + goto s_n_llpkgc__internal__n_literal; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_literal: + s_n_llpkgc__internal__n_literal: { + if (p == endp) { + return s_n_llpkgc__internal__n_literal; + } + switch (*p) { + case 10: { + goto s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span; + } + case 13: { + goto s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span; + } + case '#': { + goto s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_1; + } + case '$': { + goto s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_2; + } + case 92: { + p++; + goto s_n_llpkgc__internal__n_maybe_escaped; + } + default: { + p++; + goto s_n_llpkgc__internal__n_literal; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_variable_skip_dollar: + s_n_llpkgc__internal__n_variable_skip_dollar: { + if (p == endp) { + return s_n_llpkgc__internal__n_variable_skip_dollar; + } + p++; + goto s_n_llpkgc__internal__n_literal; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span: + s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span: { + if (p == endp) { + return s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llpkgc__vallit_span; + goto s_n_llpkgc__internal__n_variable_skip_dollar; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_2: + s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_2: { + if (p == endp) { + return s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_2; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llpkgc__vallit_span; + goto s_n_llpkgc__internal__n_literal; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_variable_close: + s_n_llpkgc__internal__n_variable_close: { + if (p == endp) { + return s_n_llpkgc__internal__n_variable_close; + } + switch (*p) { + case 10: { + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete; + } + case 13: { + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete; + } + case '#': { + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete; + } + case '$': { + p++; + goto s_n_llpkgc__internal__n_maybe_variable; + } + default: { + goto s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_2; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_variable_skip_curly: + s_n_llpkgc__internal__n_variable_skip_curly: { + if (p == endp) { + return s_n_llpkgc__internal__n_variable_skip_curly; + } + p++; + goto s_n_llpkgc__internal__n_variable_close; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_invoke_llpkgc__valvar_complete: + s_n_llpkgc__internal__n_invoke_llpkgc__valvar_complete: { + switch (llpkgc__valvar_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_variable_skip_curly; + default: + goto s_n_llpkgc__internal__n_error_11; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_variable: + s_n_llpkgc__internal__n_variable: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llpkgc__internal__n_variable; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llpkgc__internal__n_variable; + } + case 2: { + goto s_n_llpkgc__internal__n_span_end_llpkgc__valvar_span; + } + default: { + goto s_n_llpkgc__internal__n_error_12; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_span_start_llpkgc__valvar_span: + s_n_llpkgc__internal__n_span_start_llpkgc__valvar_span: { + if (p == endp) { + return s_n_llpkgc__internal__n_span_start_llpkgc__valvar_span; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llpkgc__valvar_span; + goto s_n_llpkgc__internal__n_variable; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_maybe_variable: + s_n_llpkgc__internal__n_maybe_variable: { + if (p == endp) { + return s_n_llpkgc__internal__n_maybe_variable; + } + switch (*p) { + case '$': { + goto s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span; + } + case '{': { + p++; + goto s_n_llpkgc__internal__n_span_start_llpkgc__valvar_span; + } + default: { + goto s_n_llpkgc__internal__n_error_13; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_expect_value: + s_n_llpkgc__internal__n_expect_value: { + if (p == endp) { + return s_n_llpkgc__internal__n_expect_value; + } + switch (*p) { + case 9: { + p++; + goto s_n_llpkgc__internal__n_expect_value; + } + case 10: { + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete; + } + case 13: { + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete; + } + case ' ': { + p++; + goto s_n_llpkgc__internal__n_expect_value; + } + case '#': { + goto s_n_llpkgc__internal__n_invoke_llpkgc__value_complete; + } + case '$': { + p++; + goto s_n_llpkgc__internal__n_maybe_variable; + } + default: { + goto s_n_llpkgc__internal__n_span_start_llpkgc__vallit_span_2; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_expect_sep: + s_n_llpkgc__internal__n_expect_sep: { + if (p == endp) { + return s_n_llpkgc__internal__n_expect_sep; + } + switch (*p) { + case 9: { + p++; + goto s_n_llpkgc__internal__n_expect_sep; + } + case ' ': { + p++; + goto s_n_llpkgc__internal__n_expect_sep; + } + case ':': { + p++; + goto s_n_llpkgc__internal__n_invoke_llpkgc__keyword_complete; + } + case '=': { + p++; + goto s_n_llpkgc__internal__n_invoke_llpkgc__variable_complete; + } + default: { + goto s_n_llpkgc__internal__n_error_15; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_key: + s_n_llpkgc__internal__n_key: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 1, 0, 0, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llpkgc__internal__n_key; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + goto s_n_llpkgc__internal__n_span_end_llpkgc__key_span; + } + case 2: { + p++; + goto s_n_llpkgc__internal__n_key; + } + default: { + goto s_n_llpkgc__internal__n_error_16; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_span_start_llpkgc__key_span: + s_n_llpkgc__internal__n_span_start_llpkgc__key_span: { + if (p == endp) { + return s_n_llpkgc__internal__n_span_start_llpkgc__key_span; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llpkgc__key_span; + goto s_n_llpkgc__internal__n_key; + /* UNREACHABLE */; + abort(); + } + case s_n_llpkgc__internal__n_line_start: + s_n_llpkgc__internal__n_line_start: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 3, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llpkgc__internal__n_line_start; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llpkgc__internal__n_line_start; + } + case 2: { + p++; + goto s_n_llpkgc__internal__n_comment; + } + case 3: { + goto s_n_llpkgc__internal__n_invoke_llpkgc__line_begin; + } + default: { + goto s_n_llpkgc__internal__n_error_17; + } + } + /* UNREACHABLE */; + abort(); + } + default: + /* UNREACHABLE */ + abort(); + } + s_n_llpkgc__internal__n_error_2: { + state->error = 0xb; + state->reason = "Value complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_llpkgc__value_complete: { + switch (llpkgc__value_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_line_start; + default: + goto s_n_llpkgc__internal__n_error_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_4: { + state->error = 0xb; + state->reason = "Value complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_llpkgc__value_complete_1: { + switch (llpkgc__value_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_line_start; + default: + goto s_n_llpkgc__internal__n_error_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_3: { + state->error = 0xa; + state->reason = "Literal complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__vallit_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete; + return s_error; + } + goto s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_6: { + state->error = 0xb; + state->reason = "Value complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_llpkgc__value_complete_2: { + switch (llpkgc__value_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_comment; + default: + goto s_n_llpkgc__internal__n_error_6; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_5: { + state->error = 0xa; + state->reason = "Literal complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__vallit_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_1; + return s_error; + } + goto s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_7: { + state->error = 0xa; + state->reason = "Literal complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__vallit_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_2; + return s_error; + } + goto s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_2; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_8: { + state->error = 0xa; + state->reason = "Literal complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__vallit_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_3; + return s_error; + } + goto s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_3; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_update_escaped_: { + switch (llpkgc__internal__c_update_escaped_(state, p, endp)) { + default: + goto s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_3; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_9: { + state->error = 0xa; + state->reason = "Literal complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__vallit_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_4; + return s_error; + } + goto s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_4; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_update_escaped__1: { + switch (llpkgc__internal__c_update_escaped_(state, p, endp)) { + default: + goto s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_10: { + state->error = 0xa; + state->reason = "Literal complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__vallit_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_5; + return s_error; + } + goto s_n_llpkgc__internal__n_invoke_llpkgc__vallit_complete_5; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_update_escaped__2: { + switch (llpkgc__internal__c_update_escaped_(state, p, endp)) { + default: + goto s_n_llpkgc__internal__n_span_end_llpkgc__vallit_span_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_11: { + state->error = 0xc; + state->reason = "Variable complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__valvar_span: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__valvar_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_invoke_llpkgc__valvar_complete; + return s_error; + } + goto s_n_llpkgc__internal__n_invoke_llpkgc__valvar_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_12: { + state->error = 0xd; + state->reason = "Invalid variable character"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_13: { + state->error = 0x9; + state->reason = "Unexpected `$`"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_1: { + state->error = 0x5; + state->reason = "Keyword complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_llpkgc__keyword_complete: { + switch (llpkgc__keyword_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_expect_value; + default: + goto s_n_llpkgc__internal__n_error_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_14: { + state->error = 0x6; + state->reason = "Variable complete error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_llpkgc__variable_complete: { + switch (llpkgc__variable_complete(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_expect_value; + default: + goto s_n_llpkgc__internal__n_error_14; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_15: { + state->error = 0x7; + state->reason = "Expected seperator"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_span_end_llpkgc__key_span: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llpkgc__key_span(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llpkgc__internal__n_expect_sep; + return s_error; + } + goto s_n_llpkgc__internal__n_expect_sep; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_16: { + state->error = 0x4; + state->reason = "Invalid key character"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error: { + state->error = 0x63; + state->reason = "Line start error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_invoke_llpkgc__line_begin: { + switch (llpkgc__line_begin(state, p, endp)) { + case 0: + goto s_n_llpkgc__internal__n_span_start_llpkgc__key_span; + default: + goto s_n_llpkgc__internal__n_error; + } + /* UNREACHABLE */; + abort(); + } + s_n_llpkgc__internal__n_error_17: { + state->error = 0x2; + state->reason = "Expected key"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } +} + +int llpkgc__internal_execute(llpkgc__internal_t* state, const char* p, const char* endp) { + llparse_state_t next; + + /* check lingering errors */ + if (state->error != 0) { + return state->error; + } + + /* restart spans */ + if (state->_span_pos0 != NULL) { + state->_span_pos0 = (void*) p; + } + + next = llpkgc__internal__run(state, (const unsigned char*) p, (const unsigned char*) endp); + if (next == s_error) { + return state->error; + } + state->_current = (void*) (intptr_t) next; + + /* execute spans */ + if (state->_span_pos0 != NULL) { + int error; + + error = ((llpkgc__internal__span_cb) state->_span_cb0)(state, state->_span_pos0, (const char*) endp); + if (error != 0) { + state->error = error; + state->error_pos = endp; + return error; + } + } + + return 0; +} diff --git a/Utilities/cmllpkgc/llpkgc__internal.h b/Utilities/cmllpkgc/llpkgc__internal.h new file mode 100644 index 00000000000..1488b95bdaa --- /dev/null +++ b/Utilities/cmllpkgc/llpkgc__internal.h @@ -0,0 +1,37 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ + +/* This code was generated by llpkgc, do not edit it by hand + See: https://gitlab.kitware.com/utils/llpkgc */ + + +#ifndef INCLUDE_LLPKGC__INTERNAL_H_ +#define INCLUDE_LLPKGC__INTERNAL_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct llpkgc__internal_s llpkgc__internal_t; +struct llpkgc__internal_s { + int32_t _index; + void* _span_pos0; + void* _span_cb0; + int32_t error; + const char* reason; + const char* error_pos; + void* data; + void* _current; + void* settings; + uint8_t unfinished_; + uint8_t escaped_; +}; + +int llpkgc__internal_init(llpkgc__internal_t* s); +int llpkgc__internal_execute(llpkgc__internal_t* s, const char* p, const char* endp); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLPKGC__INTERNAL_H_ */ diff --git a/Utilities/cmredbg/.gitignore b/Utilities/cmredbg/.gitignore new file mode 100644 index 00000000000..b0c4c5e5531 --- /dev/null +++ b/Utilities/cmredbg/.gitignore @@ -0,0 +1,2 @@ +re.txt +content.txt diff --git a/Utilities/cmredbg/README.rst b/Utilities/cmredbg/README.rst new file mode 100644 index 00000000000..2bc50706865 --- /dev/null +++ b/Utilities/cmredbg/README.rst @@ -0,0 +1,21 @@ +Regular expression debugging tool +================================= + +A tool to help diagnose issues with ``RunCMake`` regular expressions by +offering an editor with live results matching the haystack (``content.txt``) +against the needle (``re.txt``). + +This utility makes a few assumptions, but further improvement for other +workflows is welcome. One assumption is that it is run from this directory +(i.e., ``./run.sh``). + +Requirements +------------ + +The tool currently assumes it is running inside of a ``tmux`` session and +offers a split which prints the results of matching the regular expression +against the content. + +The ``EDITOR`` environment variable is used to detect the preferred editor, +defaulting to ``nano``. If the editor is detected as a Vi-alike (i.e., has +``vi`` in its name), both files are automatically opened in separate windows. diff --git a/Utilities/cmredbg/match.cmake b/Utilities/cmredbg/match.cmake new file mode 100644 index 00000000000..8621251cc25 --- /dev/null +++ b/Utilities/cmredbg/match.cmake @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) + +if (NOT EXISTS "re.txt") + message(FATAL_ERROR + "Place your regular expression in `re.txt`.") +endif () +if (NOT EXISTS "content.txt") + message(FATAL_ERROR + "Place your content in `content.txt`.") +endif () + +file(READ "re.txt" needle) +string(REGEX REPLACE "\n+$" "" needle "${needle}") +file(READ "content.txt" haystack) +string(REGEX REPLACE "\n+$" "" haystack "${haystack}") + +if (haystack MATCHES "${needle}") + message("Matches!") +else () + message("NO match!") +endif () diff --git a/Utilities/cmredbg/run.sh b/Utilities/cmredbg/run.sh new file mode 100755 index 00000000000..f184966e5e5 --- /dev/null +++ b/Utilities/cmredbg/run.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +die () { + echo >&2 "$@" + exit 1 +} + +test -x "$( which tmux 2>/dev/null )" || die "\`tmux\` is required" +test -n "$TMUX" || die "must be running within a \`tmux\` session" +test -x "$( which watch )" || die "\`watch\` is required" + +editor="${EDITOR:-nano}" +readonly editor + +test -x "$( which "$EDITOR" )" || die "\`$editor\` is required" + +tmux split-window -v -l 10 -c "$PWD" 'watch --interval 1 cmake -P match.cmake' +tmux select-pane -l +case "$editor" in + *vi*) + "$editor" re.txt content.txt -c 'vsp|bn' + ;; + *) + "$editor" re.txt content.txt + ;; +esac diff --git a/Utilities/cmzlib/Copyright.txt b/Utilities/cmzlib/Copyright.txt index 8f3d697df3d..7df3d0a9964 100644 --- a/Utilities/cmzlib/Copyright.txt +++ b/Utilities/cmzlib/Copyright.txt @@ -1,7 +1,7 @@ 'zlib' general purpose compression library -version 1.2.12, March 27th, 2022 +version 1.3.1, January 22nd, 2024 -Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (C) 1995-2024 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/Utilities/cmzlib/README b/Utilities/cmzlib/README index 024b79d3d8c..c5f917540b6 100644 --- a/Utilities/cmzlib/README +++ b/Utilities/cmzlib/README @@ -1,6 +1,6 @@ ZLIB DATA COMPRESSION LIBRARY -zlib 1.2.12 is a general purpose data compression library. All the code is +zlib 1.3.1 is a general purpose data compression library. All the code is thread safe. The data format used by the zlib library is described by RFCs (Request for Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and @@ -29,18 +29,17 @@ PLEASE read the zlib FAQ http://zlib.net/zlib_faq.html before asking for help. Mark Nelson wrote an article about zlib for the Jan. 1997 issue of Dr. Dobb's Journal; a copy of the article is available at -http://marknelson.us/1997/01/01/zlib-engine/ . +https://marknelson.us/posts/1997/01/01/zlib-engine.html . -The changes made in version 1.2.12 are documented in the file ChangeLog. +The changes made in version 1.3.1 are documented in the file ChangeLog. Unsupported third party contributions are provided in directory contrib/ . -zlib is available in Java using the java.util.zip package, documented at -http://java.sun.com/developer/technicalArticles/Programming/compression/ . +zlib is available in Java using the java.util.zip package. Follow the API +Documentation link at: https://docs.oracle.com/search/?q=java.util.zip . -A Perl interface to zlib written by Paul Marquess is available -at CPAN (Comprehensive Perl Archive Network) sites, including -http://search.cpan.org/~pmqs/IO-Compress-Zlib/ . +A Perl interface to zlib and bzip2 written by Paul Marquess +can be found at https://github.com/pmqs/IO-Compress . A Python interface to zlib written by A.M. Kuchling is available in Python 1.5 and later versions, see @@ -64,7 +63,7 @@ Notes for some targets: - zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works when compiled with cc. -- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is +- On Digital Unix 4.0D (formerly OSF/1) on AlphaServer, the cc option -std1 is necessary to get gzprintf working correctly. This is done by configure. - zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with @@ -84,7 +83,7 @@ Acknowledgments: Copyright notice: - (C) 1995-2022 Jean-loup Gailly and Mark Adler + (C) 1995-2024 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/Utilities/cmzlib/adler32.c b/Utilities/cmzlib/adler32.c index d0be4380a39..04b81d29bad 100644 --- a/Utilities/cmzlib/adler32.c +++ b/Utilities/cmzlib/adler32.c @@ -7,8 +7,6 @@ #include "zutil.h" -local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); - #define BASE 65521U /* largest prime smaller than 65536 */ #define NMAX 5552 /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ @@ -60,11 +58,7 @@ local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); #endif /* ========================================================================= */ -uLong ZEXPORT adler32_z(adler, buf, len) - uLong adler; - const Bytef *buf; - z_size_t len; -{ +uLong ZEXPORT adler32_z(uLong adler, const Bytef *buf, z_size_t len) { unsigned long sum2; unsigned n; @@ -131,20 +125,12 @@ uLong ZEXPORT adler32_z(adler, buf, len) } /* ========================================================================= */ -uLong ZEXPORT adler32(adler, buf, len) - uLong adler; - const Bytef *buf; - uInt len; -{ +uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len) { return adler32_z(adler, buf, len); } /* ========================================================================= */ -local uLong adler32_combine_(adler1, adler2, len2) - uLong adler1; - uLong adler2; - z_off64_t len2; -{ +local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2) { unsigned long sum1; unsigned long sum2; unsigned rem; @@ -169,18 +155,10 @@ local uLong adler32_combine_(adler1, adler2, len2) } /* ========================================================================= */ -uLong ZEXPORT adler32_combine(adler1, adler2, len2) - uLong adler1; - uLong adler2; - z_off_t len2; -{ +uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2) { return adler32_combine_(adler1, adler2, len2); } -uLong ZEXPORT adler32_combine64(adler1, adler2, len2) - uLong adler1; - uLong adler2; - z_off64_t len2; -{ +uLong ZEXPORT adler32_combine64(uLong adler1, uLong adler2, z_off64_t len2) { return adler32_combine_(adler1, adler2, len2); } diff --git a/Utilities/cmzlib/compress.c b/Utilities/cmzlib/compress.c index e2db404abf8..f43bacf7ab9 100644 --- a/Utilities/cmzlib/compress.c +++ b/Utilities/cmzlib/compress.c @@ -19,13 +19,8 @@ memory, Z_BUF_ERROR if there was not enough room in the output buffer, Z_STREAM_ERROR if the level parameter is invalid. */ -int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong sourceLen; - int level; -{ +int ZEXPORT compress2(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen, int level) { z_stream stream; int err; const uInt max = (uInt)-1; @@ -65,12 +60,8 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) /* =========================================================================== */ -int ZEXPORT compress (dest, destLen, source, sourceLen) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong sourceLen; -{ +int ZEXPORT compress(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen) { return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); } @@ -78,9 +69,7 @@ int ZEXPORT compress (dest, destLen, source, sourceLen) If the default memLevel or windowBits for deflateInit() is changed, then this function needs to be updated. */ -uLong ZEXPORT compressBound (sourceLen) - uLong sourceLen; -{ +uLong ZEXPORT compressBound(uLong sourceLen) { return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13; } diff --git a/Utilities/cmzlib/crc32.c b/Utilities/cmzlib/crc32.c index a1bdce5c23c..6c38f5c04c6 100644 --- a/Utilities/cmzlib/crc32.c +++ b/Utilities/cmzlib/crc32.c @@ -98,10 +98,6 @@ # endif #endif -/* Local functions. */ -local z_crc_t multmodp OF((z_crc_t a, z_crc_t b)); -local z_crc_t x2nmodp OF((z_off64_t n, unsigned k)); - /* If available, use the ARM processor CRC32 instruction. */ #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && W == 8 # define ARMCRC32 @@ -114,9 +110,7 @@ local z_crc_t x2nmodp OF((z_off64_t n, unsigned k)); instruction, if one is available. This assumes that word_t is either 32 bits or 64 bits. */ -local z_word_t byte_swap(word) - z_word_t word; -{ +local z_word_t byte_swap(z_word_t word) { # if W == 8 return (word & 0xff00000000000000) >> 56 | @@ -137,24 +131,77 @@ local z_word_t byte_swap(word) } #endif +#ifdef DYNAMIC_CRC_TABLE +/* ========================================================================= + * Table of powers of x for combining CRC-32s, filled in by make_crc_table() + * below. + */ + local z_crc_t FAR x2n_table[32]; +#else +/* ========================================================================= + * Tables for byte-wise and braided CRC-32 calculations, and a table of powers + * of x for combining CRC-32s, all made by make_crc_table(). + */ +# include "crc32.h" +#endif + /* CRC polynomial. */ #define POLY 0xedb88320 /* p(x) reflected, with x^32 implied */ -#ifdef DYNAMIC_CRC_TABLE +/* + Return a(x) multiplied by b(x) modulo p(x), where p(x) is the CRC polynomial, + reflected. For speed, this requires that a not be zero. + */ +local z_crc_t multmodp(z_crc_t a, z_crc_t b) { + z_crc_t m, p; + + m = (z_crc_t)1 << 31; + p = 0; + for (;;) { + if (a & m) { + p ^= b; + if ((a & (m - 1)) == 0) + break; + } + m >>= 1; + b = b & 1 ? (b >> 1) ^ POLY : b >> 1; + } + return p; +} + +/* + Return x^(n * 2^k) modulo p(x). Requires that x2n_table[] has been + initialized. + */ +local z_crc_t x2nmodp(z_off64_t n, unsigned k) { + z_crc_t p; + + p = (z_crc_t)1 << 31; /* x^0 == 1 */ + while (n) { + if (n & 1) + p = multmodp(x2n_table[k & 31], p); + n >>= 1; + k++; + } + return p; +} +#ifdef DYNAMIC_CRC_TABLE +/* ========================================================================= + * Build the tables for byte-wise and braided CRC-32 calculations, and a table + * of powers of x for combining CRC-32s. + */ local z_crc_t FAR crc_table[256]; -local z_crc_t FAR x2n_table[32]; -local void make_crc_table OF((void)); #ifdef W local z_word_t FAR crc_big_table[256]; local z_crc_t FAR crc_braid_table[W][256]; local z_word_t FAR crc_braid_big_table[W][256]; - local void braid OF((z_crc_t [][256], z_word_t [][256], int, int)); + local void braid(z_crc_t [][256], z_word_t [][256], int, int); #endif #ifdef MAKECRCH - local void write_table OF((FILE *, const z_crc_t FAR *, int)); - local void write_table32hi OF((FILE *, const z_word_t FAR *, int)); - local void write_table64 OF((FILE *, const z_word_t FAR *, int)); + local void write_table(FILE *, const z_crc_t FAR *, int); + local void write_table32hi(FILE *, const z_word_t FAR *, int); + local void write_table64(FILE *, const z_word_t FAR *, int); #endif /* MAKECRCH */ /* @@ -167,7 +214,6 @@ local void make_crc_table OF((void)); /* Definition of once functionality. */ typedef struct once_s once_t; -local void once OF((once_t *, void (*)(void))); /* Check for the availability of atomics. */ #if defined(__STDC__) && __STDC_VERSION__ >= 201112L && \ @@ -187,10 +233,7 @@ struct once_s { invoke once() at the same time. The state must be a once_t initialized with ONCE_INIT. */ -local void once(state, init) - once_t *state; - void (*init)(void); -{ +local void once(once_t *state, void (*init)(void)) { if (!atomic_load(&state->done)) { if (atomic_flag_test_and_set(&state->begun)) while (!atomic_load(&state->done)) @@ -213,10 +256,7 @@ struct once_s { /* Test and set. Alas, not atomic, but tries to minimize the period of vulnerability. */ -local int test_and_set OF((int volatile *)); -local int test_and_set(flag) - int volatile *flag; -{ +local int test_and_set(int volatile *flag) { int was; was = *flag; @@ -225,10 +265,7 @@ local int test_and_set(flag) } /* Run the provided init() function once. This is not thread-safe. */ -local void once(state, init) - once_t *state; - void (*init)(void); -{ +local void once(once_t *state, void (*init)(void)) { if (!state->done) { if (test_and_set(&state->begun)) while (!state->done) @@ -270,8 +307,7 @@ local once_t made = ONCE_INIT; combinations of CRC register values and incoming bytes. */ -local void make_crc_table() -{ +local void make_crc_table(void) { unsigned i, j, n; z_crc_t p; @@ -438,11 +474,7 @@ local void make_crc_table() Write the 32-bit values in table[0..k-1] to out, five per line in hexadecimal separated by commas. */ -local void write_table(out, table, k) - FILE *out; - const z_crc_t FAR *table; - int k; -{ +local void write_table(FILE *out, const z_crc_t FAR *table, int k) { int n; for (n = 0; n < k; n++) @@ -455,11 +487,7 @@ local void write_table(out, table, k) Write the high 32-bits of each value in table[0..k-1] to out, five per line in hexadecimal separated by commas. */ -local void write_table32hi(out, table, k) -FILE *out; -const z_word_t FAR *table; -int k; -{ +local void write_table32hi(FILE *out, const z_word_t FAR *table, int k) { int n; for (n = 0; n < k; n++) @@ -475,11 +503,7 @@ int k; bits. If not, then the type cast and format string can be adjusted accordingly. */ -local void write_table64(out, table, k) - FILE *out; - const z_word_t FAR *table; - int k; -{ +local void write_table64(FILE *out, const z_word_t FAR *table, int k) { int n; for (n = 0; n < k; n++) @@ -489,8 +513,7 @@ local void write_table64(out, table, k) } /* Actually do the deed. */ -int main() -{ +int main(void) { make_crc_table(); return 0; } @@ -502,12 +525,7 @@ int main() Generate the little and big-endian braid tables for the given n and z_word_t size w. Each array must have room for w blocks of 256 elements. */ -local void braid(ltl, big, n, w) - z_crc_t ltl[][256]; - z_word_t big[][256]; - int n; - int w; -{ +local void braid(z_crc_t ltl[][256], z_word_t big[][256], int n, int w) { int k; z_crc_t i, p, q; for (k = 0; k < w; k++) { @@ -522,69 +540,13 @@ local void braid(ltl, big, n, w) } #endif -#else /* !DYNAMIC_CRC_TABLE */ -/* ======================================================================== - * Tables for byte-wise and braided CRC-32 calculations, and a table of powers - * of x for combining CRC-32s, all made by make_crc_table(). - */ -#include "crc32.h" #endif /* DYNAMIC_CRC_TABLE */ -/* ======================================================================== - * Routines used for CRC calculation. Some are also required for the table - * generation above. - */ - -/* - Return a(x) multiplied by b(x) modulo p(x), where p(x) is the CRC polynomial, - reflected. For speed, this requires that a not be zero. - */ -local z_crc_t multmodp(a, b) - z_crc_t a; - z_crc_t b; -{ - z_crc_t m, p; - - m = (z_crc_t)1 << 31; - p = 0; - for (;;) { - if (a & m) { - p ^= b; - if ((a & (m - 1)) == 0) - break; - } - m >>= 1; - b = b & 1 ? (b >> 1) ^ POLY : b >> 1; - } - return p; -} - -/* - Return x^(n * 2^k) modulo p(x). Requires that x2n_table[] has been - initialized. - */ -local z_crc_t x2nmodp(n, k) - z_off64_t n; - unsigned k; -{ - z_crc_t p; - - p = (z_crc_t)1 << 31; /* x^0 == 1 */ - while (n) { - if (n & 1) - p = multmodp(x2n_table[k & 31], p); - n >>= 1; - k++; - } - return p; -} - /* ========================================================================= * This function can be used by asm versions of crc32(), and to force the * generation of the CRC tables in a threaded application. */ -const z_crc_t FAR * ZEXPORT get_crc_table() -{ +const z_crc_t FAR * ZEXPORT get_crc_table(void) { #ifdef DYNAMIC_CRC_TABLE once(&made, make_crc_table); #endif /* DYNAMIC_CRC_TABLE */ @@ -610,11 +572,8 @@ const z_crc_t FAR * ZEXPORT get_crc_table() #define Z_BATCH_ZEROS 0xa10d3d0c /* computed from Z_BATCH = 3990 */ #define Z_BATCH_MIN 800 /* fewest words in a final batch */ -unsigned long ZEXPORT crc32_z(crc, buf, len) - unsigned long crc; - const unsigned char FAR *buf; - z_size_t len; -{ +unsigned long ZEXPORT crc32_z(unsigned long crc, const unsigned char FAR *buf, + z_size_t len) { z_crc_t val; z_word_t crc1, crc2; const z_word_t *word; @@ -630,7 +589,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) #endif /* DYNAMIC_CRC_TABLE */ /* Pre-condition the CRC */ - crc ^= 0xffffffff; + crc = (~crc) & 0xffffffff; /* Compute the CRC up to a word boundary. */ while (len && ((z_size_t)buf & 7) != 0) { @@ -645,8 +604,8 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) len &= 7; /* Do three interleaved CRCs to realize the throughput of one crc32x - instruction per cycle. Each CRC is calcuated on Z_BATCH words. The three - CRCs are combined into a single CRC after each set of batches. */ + instruction per cycle. Each CRC is calculated on Z_BATCH words. The + three CRCs are combined into a single CRC after each set of batches. */ while (num >= 3 * Z_BATCH) { crc1 = 0; crc2 = 0; @@ -714,18 +673,14 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) least-significant byte of the word as the first byte of data, without any pre or post conditioning. This is used to combine the CRCs of each braid. */ -local z_crc_t crc_word(data) - z_word_t data; -{ +local z_crc_t crc_word(z_word_t data) { int k; for (k = 0; k < W; k++) data = (data >> 8) ^ crc_table[data & 0xff]; return (z_crc_t)data; } -local z_word_t crc_word_big(data) - z_word_t data; -{ +local z_word_t crc_word_big(z_word_t data) { int k; for (k = 0; k < W; k++) data = (data << 8) ^ @@ -736,11 +691,8 @@ local z_word_t crc_word_big(data) #endif /* ========================================================================= */ -unsigned long ZEXPORT crc32_z(crc, buf, len) - unsigned long crc; - const unsigned char FAR *buf; - z_size_t len; -{ +unsigned long ZEXPORT crc32_z(unsigned long crc, const unsigned char FAR *buf, + z_size_t len) { /* Return initial CRC, if requested. */ if (buf == Z_NULL) return 0; @@ -749,7 +701,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) #endif /* DYNAMIC_CRC_TABLE */ /* Pre-condition the CRC */ - crc ^= 0xffffffff; + crc = (~crc) & 0xffffffff; #ifdef W @@ -772,8 +724,8 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) words = (z_word_t const *)buf; /* Do endian check at execution time instead of compile time, since ARM - processors can change the endianess at execution time. If the - compiler knows what the endianess will be, it can optimize out the + processors can change the endianness at execution time. If the + compiler knows what the endianness will be, it can optimize out the check and the unused branch. */ endian = 1; if (*(unsigned char *)&endian) { @@ -1060,39 +1012,26 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) #endif /* ========================================================================= */ -unsigned long ZEXPORT crc32(crc, buf, len) - unsigned long crc; - const unsigned char FAR *buf; - uInt len; -{ +unsigned long ZEXPORT crc32(unsigned long crc, const unsigned char FAR *buf, + uInt len) { return crc32_z(crc, buf, len); } /* ========================================================================= */ -uLong ZEXPORT crc32_combine64(crc1, crc2, len2) - uLong crc1; - uLong crc2; - z_off64_t len2; -{ +uLong ZEXPORT crc32_combine64(uLong crc1, uLong crc2, z_off64_t len2) { #ifdef DYNAMIC_CRC_TABLE once(&made, make_crc_table); #endif /* DYNAMIC_CRC_TABLE */ - return multmodp(x2nmodp(len2, 3), crc1) ^ crc2; + return multmodp(x2nmodp(len2, 3), crc1) ^ (crc2 & 0xffffffff); } /* ========================================================================= */ -uLong ZEXPORT crc32_combine(crc1, crc2, len2) - uLong crc1; - uLong crc2; - z_off_t len2; -{ - return crc32_combine64(crc1, crc2, len2); +uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2) { + return crc32_combine64(crc1, crc2, (z_off64_t)len2); } /* ========================================================================= */ -uLong ZEXPORT crc32_combine_gen64(len2) - z_off64_t len2; -{ +uLong ZEXPORT crc32_combine_gen64(z_off64_t len2) { #ifdef DYNAMIC_CRC_TABLE once(&made, make_crc_table); #endif /* DYNAMIC_CRC_TABLE */ @@ -1100,17 +1039,11 @@ uLong ZEXPORT crc32_combine_gen64(len2) } /* ========================================================================= */ -uLong ZEXPORT crc32_combine_gen(len2) - z_off_t len2; -{ - return crc32_combine_gen64(len2); +uLong ZEXPORT crc32_combine_gen(z_off_t len2) { + return crc32_combine_gen64((z_off64_t)len2); } /* ========================================================================= */ -uLong crc32_combine_op(crc1, crc2, op) - uLong crc1; - uLong crc2; - uLong op; -{ - return multmodp(op, crc1) ^ crc2; +uLong ZEXPORT crc32_combine_op(uLong crc1, uLong crc2, uLong op) { + return multmodp(op, crc1) ^ (crc2 & 0xffffffff); } diff --git a/Utilities/cmzlib/deflate.c b/Utilities/cmzlib/deflate.c index 203220629e4..14aa1aff8e9 100644 --- a/Utilities/cmzlib/deflate.c +++ b/Utilities/cmzlib/deflate.c @@ -1,5 +1,5 @@ /* deflate.c -- compress data using the deflation algorithm - * Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler + * Copyright (C) 1995-2024 Jean-loup Gailly and Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -52,7 +52,7 @@ #include "deflate.h" const char deflate_copyright[] = - " deflate 1.2.12 Copyright 1995-2022 Jean-loup Gailly and Mark Adler "; + " deflate 1.3.1 Copyright 1995-2024 Jean-loup Gailly and Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -60,9 +60,6 @@ const char deflate_copyright[] = copyright string in the executable of your product. */ -/* =========================================================================== - * Function prototypes. - */ typedef enum { need_more, /* block not completed, need more input or more output */ block_done, /* block flush performed */ @@ -70,35 +67,16 @@ typedef enum { finish_done /* finish done, accept no more input or output */ } block_state; -typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +typedef block_state (*compress_func)(deflate_state *s, int flush); /* Compression function. Returns the block state after the call. */ -local int deflateStateCheck OF((z_streamp strm)); -local void slide_hash OF((deflate_state *s)); -local void fill_window OF((deflate_state *s)); -local block_state deflate_stored OF((deflate_state *s, int flush)); -local block_state deflate_fast OF((deflate_state *s, int flush)); +local block_state deflate_stored(deflate_state *s, int flush); +local block_state deflate_fast(deflate_state *s, int flush); #ifndef FASTEST -local block_state deflate_slow OF((deflate_state *s, int flush)); -#endif -local block_state deflate_rle OF((deflate_state *s, int flush)); -local block_state deflate_huff OF((deflate_state *s, int flush)); -local void lm_init OF((deflate_state *s)); -local void putShortMSB OF((deflate_state *s, uInt b)); -local void flush_pending OF((z_streamp strm)); -local unsigned read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); -#ifdef ASMV -# pragma message("Assembler code may have bugs -- use at your own risk") - void match_init OF((void)); /* asm code initialization */ - uInt longest_match OF((deflate_state *s, IPos cur_match)); -#else -local uInt longest_match OF((deflate_state *s, IPos cur_match)); -#endif - -#ifdef ZLIB_DEBUG -local void check_match OF((deflate_state *s, IPos start, IPos match, - int length)); +local block_state deflate_slow(deflate_state *s, int flush); #endif +local block_state deflate_rle(deflate_state *s, int flush); +local block_state deflate_huff(deflate_state *s, int flush); /* =========================================================================== * Local data @@ -160,7 +138,7 @@ local const config configuration_table[10] = { * characters, so that a running hash key can be computed from the previous * key instead of complete recalculation each time. */ -#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) +#define UPDATE_HASH(s,h,c) (h = (((h) << s->hash_shift) ^ (c)) & s->hash_mask) /* =========================================================================== @@ -191,9 +169,9 @@ local const config configuration_table[10] = { */ #define CLEAR_HASH(s) \ do { \ - s->head[s->hash_size-1] = NIL; \ + s->head[s->hash_size - 1] = NIL; \ zmemzero((Bytef *)s->head, \ - (unsigned)(s->hash_size-1)*sizeof(*s->head)); \ + (unsigned)(s->hash_size - 1)*sizeof(*s->head)); \ } while (0) /* =========================================================================== @@ -201,9 +179,12 @@ local const config configuration_table[10] = { * bit values at the expense of memory usage). We slide even when level == 0 to * keep the hash table consistent if we switch back to level > 0 later. */ -local void slide_hash(s) - deflate_state *s; -{ +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) + __attribute__((no_sanitize("memory"))) +# endif +#endif +local void slide_hash(deflate_state *s) { unsigned n, m; Posf *p; uInt wsize = s->w_size; @@ -227,30 +208,177 @@ local void slide_hash(s) #endif } +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local unsigned read_buf(z_streamp strm, Bytef *buf, unsigned size) { + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + zmemcpy(buf, strm->next_in, len); + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, buf, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, buf, len); + } +#endif + strm->next_in += len; + strm->total_in += len; + + return len; +} + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window(deflate_state *s) { + unsigned n; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ + if (sizeof(int) <= 2) { + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s->strstart >= wsize + MAX_DIST(s)) { + + zmemcpy(s->window, s->window + wsize, (unsigned)wsize - more); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + if (s->insert > s->strstart) + s->insert = s->strstart; + slide_hash(s); + more += wsize; + } + if (s->strm->avail_in == 0) break; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead + s->insert >= MIN_MATCH) { + uInt str = s->strstart - s->insert; + s->ins_h = s->window[str]; + UPDATE_HASH(s, s->ins_h, s->window[str + 1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + while (s->insert) { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + s->insert--; + if (s->lookahead + s->insert < MIN_MATCH) + break; + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ + if (s->high_water < s->window_size) { + ulg curr = s->strstart + (ulg)(s->lookahead); + ulg init; + + if (s->high_water < curr) { + /* Previous high water mark below current data -- zero WIN_INIT + * bytes or up to end of window, whichever is less. + */ + init = s->window_size - curr; + if (init > WIN_INIT) + init = WIN_INIT; + zmemzero(s->window + curr, (unsigned)init); + s->high_water = curr + init; + } + else if (s->high_water < (ulg)curr + WIN_INIT) { + /* High water mark at or above current data, but below current data + * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up + * to end of window, whichever is less. + */ + init = (ulg)curr + WIN_INIT - s->high_water; + if (init > s->window_size - s->high_water) + init = s->window_size - s->high_water; + zmemzero(s->window + s->high_water, (unsigned)init); + s->high_water += init; + } + } + + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "not enough room for search"); +} + /* ========================================================================= */ -int ZEXPORT deflateInit_(strm, level, version, stream_size) - z_streamp strm; - int level; - const char *version; - int stream_size; -{ +int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version, + int stream_size) { return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, version, stream_size); /* To do: ignore strm->next_in if we use it as window */ } /* ========================================================================= */ -int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, - version, stream_size) - z_streamp strm; - int level; - int method; - int windowBits; - int memLevel; - int strategy; - const char *version; - int stream_size; -{ +int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, + int windowBits, int memLevel, int strategy, + const char *version, int stream_size) { deflate_state *s; int wrap = 1; static const char my_version[] = ZLIB_VERSION; @@ -285,6 +413,8 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (windowBits < 0) { /* suppress zlib wrapper */ wrap = 0; + if (windowBits < -15) + return Z_STREAM_ERROR; windowBits = -windowBits; } #ifdef GZIP @@ -314,7 +444,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, s->hash_bits = (uInt)memLevel + 7; s->hash_size = 1 << s->hash_bits; s->hash_mask = s->hash_size - 1; - s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + s->hash_shift = ((s->hash_bits + MIN_MATCH-1) / MIN_MATCH); s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); @@ -347,11 +477,11 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, * sym_buf value to read moves forward three bytes. From that symbol, up to * 31 bits are written to pending_buf. The closest the written pending_buf * bits gets to the next sym_buf symbol to read is just before the last - * code is written. At that time, 31*(n-2) bits have been written, just - * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at - * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1 + * code is written. At that time, 31*(n - 2) bits have been written, just + * after 24*(n - 2) bits have been consumed from sym_buf. sym_buf starts at + * 8*n bits into pending_buf. (Note that the symbol buffer fills when n - 1 * symbols are written.) The closest the writing gets to what is unread is - * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and + * then n + 14 bits. Here n is lit_bufsize, which is 16384 by default, and * can range from 128 to 32768. * * Therefore, at a minimum, there are 142 bits of space between what is @@ -370,7 +500,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, * symbols from which it is being constructed. */ - s->pending_buf = (uchf *) ZALLOC(strm, s->lit_bufsize, 4); + s->pending_buf = (uchf *) ZALLOC(strm, s->lit_bufsize, LIT_BUFS); s->pending_buf_size = (ulg)s->lit_bufsize * 4; if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || @@ -380,8 +510,14 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, deflateEnd (strm); return Z_MEM_ERROR; } +#ifdef LIT_MEM + s->d_buf = (ushf *)(s->pending_buf + (s->lit_bufsize << 1)); + s->l_buf = s->pending_buf + (s->lit_bufsize << 2); + s->sym_end = s->lit_bufsize - 1; +#else s->sym_buf = s->pending_buf + s->lit_bufsize; s->sym_end = (s->lit_bufsize - 1) * 3; +#endif /* We avoid equality with lit_bufsize*3 because of wraparound at 64K * on 16 bit machines and because stored blocks are restricted to * 64K-1 bytes. @@ -397,9 +533,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, /* ========================================================================= * Check for a valid deflate stream state. Return 0 if ok, 1 if not. */ -local int deflateStateCheck (strm) - z_streamp strm; -{ +local int deflateStateCheck(z_streamp strm) { deflate_state *s; if (strm == Z_NULL || strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) @@ -420,11 +554,8 @@ local int deflateStateCheck (strm) } /* ========================================================================= */ -int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) - z_streamp strm; - const Bytef *dictionary; - uInt dictLength; -{ +int ZEXPORT deflateSetDictionary(z_streamp strm, const Bytef *dictionary, + uInt dictLength) { deflate_state *s; uInt str, n; int wrap; @@ -489,11 +620,8 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) } /* ========================================================================= */ -int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength) - z_streamp strm; - Bytef *dictionary; - uInt *dictLength; -{ +int ZEXPORT deflateGetDictionary(z_streamp strm, Bytef *dictionary, + uInt *dictLength) { deflate_state *s; uInt len; @@ -511,9 +639,7 @@ int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength) } /* ========================================================================= */ -int ZEXPORT deflateResetKeep (strm) - z_streamp strm; -{ +int ZEXPORT deflateResetKeep(z_streamp strm) { deflate_state *s; if (deflateStateCheck(strm)) { @@ -548,10 +674,32 @@ int ZEXPORT deflateResetKeep (strm) return Z_OK; } +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init(deflate_state *s) { + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->insert = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +} + /* ========================================================================= */ -int ZEXPORT deflateReset (strm) - z_streamp strm; -{ +int ZEXPORT deflateReset(z_streamp strm) { int ret; ret = deflateResetKeep(strm); @@ -561,10 +709,7 @@ int ZEXPORT deflateReset (strm) } /* ========================================================================= */ -int ZEXPORT deflateSetHeader (strm, head) - z_streamp strm; - gz_headerp head; -{ +int ZEXPORT deflateSetHeader(z_streamp strm, gz_headerp head) { if (deflateStateCheck(strm) || strm->state->wrap != 2) return Z_STREAM_ERROR; strm->state->gzhead = head; @@ -572,11 +717,7 @@ int ZEXPORT deflateSetHeader (strm, head) } /* ========================================================================= */ -int ZEXPORT deflatePending (strm, pending, bits) - unsigned *pending; - int *bits; - z_streamp strm; -{ +int ZEXPORT deflatePending(z_streamp strm, unsigned *pending, int *bits) { if (deflateStateCheck(strm)) return Z_STREAM_ERROR; if (pending != Z_NULL) *pending = strm->state->pending; @@ -586,19 +727,21 @@ int ZEXPORT deflatePending (strm, pending, bits) } /* ========================================================================= */ -int ZEXPORT deflatePrime (strm, bits, value) - z_streamp strm; - int bits; - int value; -{ +int ZEXPORT deflatePrime(z_streamp strm, int bits, int value) { deflate_state *s; int put; if (deflateStateCheck(strm)) return Z_STREAM_ERROR; s = strm->state; +#ifdef LIT_MEM + if (bits < 0 || bits > 16 || + (uchf *)s->d_buf < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; +#else if (bits < 0 || bits > 16 || s->sym_buf < s->pending_out + ((Buf_size + 7) >> 3)) return Z_BUF_ERROR; +#endif do { put = Buf_size - s->bi_valid; if (put > bits) @@ -613,11 +756,7 @@ int ZEXPORT deflatePrime (strm, bits, value) } /* ========================================================================= */ -int ZEXPORT deflateParams(strm, level, strategy) - z_streamp strm; - int level; - int strategy; -{ +int ZEXPORT deflateParams(z_streamp strm, int level, int strategy) { deflate_state *s; compress_func func; @@ -662,13 +801,8 @@ int ZEXPORT deflateParams(strm, level, strategy) } /* ========================================================================= */ -int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) - z_streamp strm; - int good_length; - int max_lazy; - int nice_length; - int max_chain; -{ +int ZEXPORT deflateTune(z_streamp strm, int good_length, int max_lazy, + int nice_length, int max_chain) { deflate_state *s; if (deflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -681,36 +815,47 @@ int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) } /* ========================================================================= - * For the default windowBits of 15 and memLevel of 8, this function returns - * a close to exact, as well as small, upper bound on the compressed size. - * They are coded as constants here for a reason--if the #define's are - * changed, then this function needs to be changed as well. The return - * value for 15 and 8 only works for those exact settings. + * For the default windowBits of 15 and memLevel of 8, this function returns a + * close to exact, as well as small, upper bound on the compressed size. This + * is an expansion of ~0.03%, plus a small constant. + * + * For any setting other than those defaults for windowBits and memLevel, one + * of two worst case bounds is returned. This is at most an expansion of ~4% or + * ~13%, plus a small constant. * - * For any setting other than those defaults for windowBits and memLevel, - * the value returned is a conservative worst case for the maximum expansion - * resulting from using fixed blocks instead of stored blocks, which deflate - * can emit on compressed data for some combinations of the parameters. + * Both the 0.03% and 4% derive from the overhead of stored blocks. The first + * one is for stored blocks of 16383 bytes (memLevel == 8), whereas the second + * is for stored blocks of 127 bytes (the worst case memLevel == 1). The + * expansion results from five bytes of header for each stored block. * - * This function could be more sophisticated to provide closer upper bounds for - * every combination of windowBits and memLevel. But even the conservative - * upper bound of about 14% expansion does not seem onerous for output buffer - * allocation. + * The larger expansion of 13% results from a window size less than or equal to + * the symbols buffer size (windowBits <= memLevel + 7). In that case some of + * the data being compressed may have slid out of the sliding window, impeding + * a stored block from being emitted. Then the only choice is a fixed or + * dynamic block, where a fixed block limits the maximum expansion to 9 bits + * per 8-bit byte, plus 10 bits for every block. The smallest block size for + * which this can occur is 255 (memLevel == 2). + * + * Shifts are used to approximate divisions, for speed. */ -uLong ZEXPORT deflateBound(strm, sourceLen) - z_streamp strm; - uLong sourceLen; -{ +uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen) { deflate_state *s; - uLong complen, wraplen; + uLong fixedlen, storelen, wraplen; + + /* upper bound for fixed blocks with 9-bit literals and length 255 + (memLevel == 2, which is the lowest that may not use stored blocks) -- + ~13% overhead plus a small constant */ + fixedlen = sourceLen + (sourceLen >> 3) + (sourceLen >> 8) + + (sourceLen >> 9) + 4; - /* conservative upper bound for compressed data */ - complen = sourceLen + - ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5; + /* upper bound for stored blocks with length 127 (memLevel == 1) -- + ~4% overhead plus a small constant */ + storelen = sourceLen + (sourceLen >> 5) + (sourceLen >> 7) + + (sourceLen >> 11) + 7; - /* if can't get parameters, return conservative bound plus zlib wrapper */ + /* if can't get parameters, return larger bound plus a zlib wrapper */ if (deflateStateCheck(strm)) - return complen + 6; + return (fixedlen > storelen ? fixedlen : storelen) + 6; /* compute wrapper length */ s = strm->state; @@ -747,11 +892,13 @@ uLong ZEXPORT deflateBound(strm, sourceLen) wraplen = 6; } - /* if not default parameters, return conservative bound */ + /* if not default parameters, return one of the conservative bounds */ if (s->w_bits != 15 || s->hash_bits != 8 + 7) - return complen + wraplen; + return (s->w_bits <= s->hash_bits && s->level ? fixedlen : storelen) + + wraplen; - /* default settings: return tight bound for that case */ + /* default settings: return tight bound for that case -- ~0.03% overhead + plus a small constant */ return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13 - 6 + wraplen; } @@ -761,10 +908,7 @@ uLong ZEXPORT deflateBound(strm, sourceLen) * IN assertion: the stream state is correct and there is enough room in * pending_buf. */ -local void putShortMSB (s, b) - deflate_state *s; - uInt b; -{ +local void putShortMSB(deflate_state *s, uInt b) { put_byte(s, (Byte)(b >> 8)); put_byte(s, (Byte)(b & 0xff)); } @@ -775,9 +919,7 @@ local void putShortMSB (s, b) * applications may wish to modify it to avoid allocating a large * strm->next_out buffer and copying into it. (See also read_buf()). */ -local void flush_pending(strm) - z_streamp strm; -{ +local void flush_pending(z_streamp strm) { unsigned len; deflate_state *s = strm->state; @@ -808,10 +950,7 @@ local void flush_pending(strm) } while (0) /* ========================================================================= */ -int ZEXPORT deflate (strm, flush) - z_streamp strm; - int flush; -{ +int ZEXPORT deflate(z_streamp strm, int flush) { int old_flush; /* value of flush param for previous deflate call */ deflate_state *s; @@ -863,7 +1002,7 @@ int ZEXPORT deflate (strm, flush) s->status = BUSY_STATE; if (s->status == INIT_STATE) { /* zlib header */ - uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt header = (Z_DEFLATED + ((s->w_bits - 8) << 4)) << 8; uInt level_flags; if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) @@ -1123,9 +1262,7 @@ int ZEXPORT deflate (strm, flush) } /* ========================================================================= */ -int ZEXPORT deflateEnd (strm) - z_streamp strm; -{ +int ZEXPORT deflateEnd(z_streamp strm) { int status; if (deflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1149,11 +1286,10 @@ int ZEXPORT deflateEnd (strm) * To simplify the source, this is not supported for 16-bit MSDOS (which * doesn't have enough memory anyway to duplicate compression states). */ -int ZEXPORT deflateCopy (dest, source) - z_streamp dest; - z_streamp source; -{ +int ZEXPORT deflateCopy(z_streamp dest, z_streamp source) { #ifdef MAXSEG_64K + (void)dest; + (void)source; return Z_STREAM_ERROR; #else deflate_state *ds; @@ -1177,7 +1313,7 @@ int ZEXPORT deflateCopy (dest, source) ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); - ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, 4); + ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, LIT_BUFS); if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || ds->pending_buf == Z_NULL) { @@ -1188,10 +1324,15 @@ int ZEXPORT deflateCopy (dest, source) zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); - zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + zmemcpy(ds->pending_buf, ss->pending_buf, ds->lit_bufsize * LIT_BUFS); ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); +#ifdef LIT_MEM + ds->d_buf = (ushf *)(ds->pending_buf + (ds->lit_bufsize << 1)); + ds->l_buf = ds->pending_buf + (ds->lit_bufsize << 2); +#else ds->sym_buf = ds->pending_buf + ds->lit_bufsize; +#endif ds->l_desc.dyn_tree = ds->dyn_ltree; ds->d_desc.dyn_tree = ds->dyn_dtree; @@ -1201,71 +1342,6 @@ int ZEXPORT deflateCopy (dest, source) #endif /* MAXSEG_64K */ } -/* =========================================================================== - * Read a new buffer from the current input stream, update the adler32 - * and total number of bytes read. All deflate() input goes through - * this function so some applications may wish to modify it to avoid - * allocating a large strm->next_in buffer and copying from it. - * (See also flush_pending()). - */ -local unsigned read_buf(strm, buf, size) - z_streamp strm; - Bytef *buf; - unsigned size; -{ - unsigned len = strm->avail_in; - - if (len > size) len = size; - if (len == 0) return 0; - - strm->avail_in -= len; - - zmemcpy(buf, strm->next_in, len); - if (strm->state->wrap == 1) { - strm->adler = adler32(strm->adler, buf, len); - } -#ifdef GZIP - else if (strm->state->wrap == 2) { - strm->adler = crc32(strm->adler, buf, len); - } -#endif - strm->next_in += len; - strm->total_in += len; - - return len; -} - -/* =========================================================================== - * Initialize the "longest match" routines for a new zlib stream - */ -local void lm_init (s) - deflate_state *s; -{ - s->window_size = (ulg)2L*s->w_size; - - CLEAR_HASH(s); - - /* Set the default configuration parameters: - */ - s->max_lazy_match = configuration_table[s->level].max_lazy; - s->good_match = configuration_table[s->level].good_length; - s->nice_match = configuration_table[s->level].nice_length; - s->max_chain_length = configuration_table[s->level].max_chain; - - s->strstart = 0; - s->block_start = 0L; - s->lookahead = 0; - s->insert = 0; - s->match_length = s->prev_length = MIN_MATCH-1; - s->match_available = 0; - s->ins_h = 0; -#ifndef FASTEST -#ifdef ASMV - match_init(); /* initialize the asm code */ -#endif -#endif -} - #ifndef FASTEST /* =========================================================================== * Set match_start to the longest match starting at the given string and @@ -1276,14 +1352,7 @@ local void lm_init (s) * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 * OUT assertion: the match length is not greater than s->lookahead. */ -#ifndef ASMV -/* For 80x86 and 680x0, an optimized version will be provided in match.asm or - * match.S. The code will be functionally equivalent. - */ -local uInt longest_match(s, cur_match) - deflate_state *s; - IPos cur_match; /* current match */ -{ +local uInt longest_match(deflate_state *s, IPos cur_match) { unsigned chain_length = s->max_chain_length;/* max hash chain length */ register Bytef *scan = s->window + s->strstart; /* current string */ register Bytef *match; /* matched string */ @@ -1304,10 +1373,10 @@ local uInt longest_match(s, cur_match) */ register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; register ush scan_start = *(ushf*)scan; - register ush scan_end = *(ushf*)(scan+best_len-1); + register ush scan_end = *(ushf*)(scan + best_len - 1); #else register Bytef *strend = s->window + s->strstart + MAX_MATCH; - register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end1 = scan[best_len - 1]; register Byte scan_end = scan[best_len]; #endif @@ -1325,7 +1394,8 @@ local uInt longest_match(s, cur_match) */ if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead; - Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "need lookahead"); do { Assert(cur_match < s->strstart, "no future"); @@ -1343,43 +1413,44 @@ local uInt longest_match(s, cur_match) /* This code assumes sizeof(unsigned short) == 2. Do not use * UNALIGNED_OK if your compiler uses a different size. */ - if (*(ushf*)(match+best_len-1) != scan_end || + if (*(ushf*)(match + best_len - 1) != scan_end || *(ushf*)match != scan_start) continue; /* It is not necessary to compare scan[2] and match[2] since they are * always equal when the other bytes match, given that the hash keys * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at - * strstart+3, +5, ... up to strstart+257. We check for insufficient + * strstart + 3, + 5, up to strstart + 257. We check for insufficient * lookahead only every 4th comparison; the 128th check will be made - * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * at strstart + 257. If MAX_MATCH-2 is not a multiple of 8, it is * necessary to put more guard bytes at the end of the window, or * to check more often for insufficient lookahead. */ Assert(scan[2] == match[2], "scan[2]?"); scan++, match++; do { - } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + } while (*(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && scan < strend); /* The funny "do {}" generates better code on most compilers */ - /* Here, scan <= window+strstart+257 */ - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + /* Here, scan <= window + strstart + 257 */ + Assert(scan <= s->window + (unsigned)(s->window_size - 1), + "wild scan"); if (*scan == *match) scan++; - len = (MAX_MATCH - 1) - (int)(strend-scan); + len = (MAX_MATCH - 1) - (int)(strend - scan); scan = strend - (MAX_MATCH-1); #else /* UNALIGNED_OK */ - if (match[best_len] != scan_end || - match[best_len-1] != scan_end1 || - *match != *scan || - *++match != scan[1]) continue; + if (match[best_len] != scan_end || + match[best_len - 1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; - /* The check at best_len-1 can be removed because it will be made + /* The check at best_len - 1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that @@ -1389,7 +1460,7 @@ local uInt longest_match(s, cur_match) Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. + * the 256th check will be made at strstart + 258. */ do { } while (*++scan == *++match && *++scan == *++match && @@ -1398,7 +1469,8 @@ local uInt longest_match(s, cur_match) *++scan == *++match && *++scan == *++match && scan < strend); - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (unsigned)(s->window_size - 1), + "wild scan"); len = MAX_MATCH - (int)(strend - scan); scan = strend - MAX_MATCH; @@ -1410,9 +1482,9 @@ local uInt longest_match(s, cur_match) best_len = len; if (len >= nice_match) break; #ifdef UNALIGNED_OK - scan_end = *(ushf*)(scan+best_len-1); + scan_end = *(ushf*)(scan + best_len - 1); #else - scan_end1 = scan[best_len-1]; + scan_end1 = scan[best_len - 1]; scan_end = scan[best_len]; #endif } @@ -1422,17 +1494,13 @@ local uInt longest_match(s, cur_match) if ((uInt)best_len <= s->lookahead) return (uInt)best_len; return s->lookahead; } -#endif /* ASMV */ #else /* FASTEST */ /* --------------------------------------------------------------------------- * Optimized version for FASTEST only */ -local uInt longest_match(s, cur_match) - deflate_state *s; - IPos cur_match; /* current match */ -{ +local uInt longest_match(deflate_state *s, IPos cur_match) { register Bytef *scan = s->window + s->strstart; /* current string */ register Bytef *match; /* matched string */ register int len; /* length of current match */ @@ -1443,7 +1511,8 @@ local uInt longest_match(s, cur_match) */ Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "need lookahead"); Assert(cur_match < s->strstart, "no future"); @@ -1453,7 +1522,7 @@ local uInt longest_match(s, cur_match) */ if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; - /* The check at best_len-1 can be removed because it will be made + /* The check at best_len - 1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that @@ -1463,7 +1532,7 @@ local uInt longest_match(s, cur_match) Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. + * the 256th check will be made at strstart + 258. */ do { } while (*++scan == *++match && *++scan == *++match && @@ -1472,7 +1541,7 @@ local uInt longest_match(s, cur_match) *++scan == *++match && *++scan == *++match && scan < strend); - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (unsigned)(s->window_size - 1), "wild scan"); len = MAX_MATCH - (int)(strend - scan); @@ -1492,23 +1561,27 @@ local uInt longest_match(s, cur_match) /* =========================================================================== * Check that the match at match_start is indeed a match. */ -local void check_match(s, start, match, length) - deflate_state *s; - IPos start, match; - int length; -{ +local void check_match(deflate_state *s, IPos start, IPos match, int length) { /* check that the match is indeed a match */ - if (zmemcmp(s->window + match, - s->window + start, length) != EQUAL) { - fprintf(stderr, " start %u, match %u, length %d\n", - start, match, length); + Bytef *back = s->window + (int)match, *here = s->window + start; + IPos len = length; + if (match == (IPos)-1) { + /* match starts one byte before the current window -- just compare the + subsequent length-1 bytes */ + back++; + here++; + len--; + } + if (zmemcmp(back, here, len) != EQUAL) { + fprintf(stderr, " start %u, match %d, length %d\n", + start, (int)match, length); do { - fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); - } while (--length != 0); + fprintf(stderr, "(%02x %02x)", *back++, *here++); + } while (--len != 0); z_error("invalid match"); } if (z_verbose > 1) { - fprintf(stderr,"\\[%d,%d]", start-match, length); + fprintf(stderr,"\\[%d,%d]", start - match, length); do { putc(s->window[start++], stderr); } while (--length != 0); } } @@ -1516,137 +1589,6 @@ local void check_match(s, start, match, length) # define check_match(s, start, match, length) #endif /* ZLIB_DEBUG */ -/* =========================================================================== - * Fill the window when the lookahead becomes insufficient. - * Updates strstart and lookahead. - * - * IN assertion: lookahead < MIN_LOOKAHEAD - * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD - * At least one byte has been read, or avail_in == 0; reads are - * performed for at least two bytes (required for the zip translate_eol - * option -- not supported here). - */ -local void fill_window(s) - deflate_state *s; -{ - unsigned n; - unsigned more; /* Amount of free space at the end of the window. */ - uInt wsize = s->w_size; - - Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); - - do { - more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); - - /* Deal with !@#$% 64K limit: */ - if (sizeof(int) <= 2) { - if (more == 0 && s->strstart == 0 && s->lookahead == 0) { - more = wsize; - - } else if (more == (unsigned)(-1)) { - /* Very unlikely, but possible on 16 bit machine if - * strstart == 0 && lookahead == 1 (input done a byte at time) - */ - more--; - } - } - - /* If the window is almost full and there is insufficient lookahead, - * move the upper half to the lower one to make room in the upper half. - */ - if (s->strstart >= wsize+MAX_DIST(s)) { - - zmemcpy(s->window, s->window+wsize, (unsigned)wsize - more); - s->match_start -= wsize; - s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ - s->block_start -= (long) wsize; - if (s->insert > s->strstart) - s->insert = s->strstart; - slide_hash(s); - more += wsize; - } - if (s->strm->avail_in == 0) break; - - /* If there was no sliding: - * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && - * more == window_size - lookahead - strstart - * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) - * => more >= window_size - 2*WSIZE + 2 - * In the BIG_MEM or MMAP case (not yet supported), - * window_size == input_size + MIN_LOOKAHEAD && - * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. - * Otherwise, window_size == 2*WSIZE so more >= 2. - * If there was sliding, more >= WSIZE. So in all cases, more >= 2. - */ - Assert(more >= 2, "more < 2"); - - n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); - s->lookahead += n; - - /* Initialize the hash value now that we have some input: */ - if (s->lookahead + s->insert >= MIN_MATCH) { - uInt str = s->strstart - s->insert; - s->ins_h = s->window[str]; - UPDATE_HASH(s, s->ins_h, s->window[str + 1]); -#if MIN_MATCH != 3 - Call UPDATE_HASH() MIN_MATCH-3 more times -#endif - while (s->insert) { - UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); -#ifndef FASTEST - s->prev[str & s->w_mask] = s->head[s->ins_h]; -#endif - s->head[s->ins_h] = (Pos)str; - str++; - s->insert--; - if (s->lookahead + s->insert < MIN_MATCH) - break; - } - } - /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, - * but this is not important since only literal bytes will be emitted. - */ - - } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); - - /* If the WIN_INIT bytes after the end of the current data have never been - * written, then zero those bytes in order to avoid memory check reports of - * the use of uninitialized (or uninitialised as Julian writes) bytes by - * the longest match routines. Update the high water mark for the next - * time through here. WIN_INIT is set to MAX_MATCH since the longest match - * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. - */ - if (s->high_water < s->window_size) { - ulg curr = s->strstart + (ulg)(s->lookahead); - ulg init; - - if (s->high_water < curr) { - /* Previous high water mark below current data -- zero WIN_INIT - * bytes or up to end of window, whichever is less. - */ - init = s->window_size - curr; - if (init > WIN_INIT) - init = WIN_INIT; - zmemzero(s->window + curr, (unsigned)init); - s->high_water = curr + init; - } - else if (s->high_water < (ulg)curr + WIN_INIT) { - /* High water mark at or above current data, but below current data - * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up - * to end of window, whichever is less. - */ - init = (ulg)curr + WIN_INIT - s->high_water; - if (init > s->window_size - s->high_water) - init = s->window_size - s->high_water; - zmemzero(s->window + s->high_water, (unsigned)init); - s->high_water += init; - } - } - - Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, - "not enough room for search"); -} - /* =========================================================================== * Flush the current block, with given end-of-file flag. * IN assertion: strstart is set to the end of the current match. @@ -1687,12 +1629,9 @@ local void fill_window(s) * * deflate_stored() is written to minimize the number of times an input byte is * copied. It is most efficient with large input and output buffers, which - * maximizes the opportunites to have a single copy from next_in to next_out. + * maximizes the opportunities to have a single copy from next_in to next_out. */ -local block_state deflate_stored(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_stored(deflate_state *s, int flush) { /* Smallest worthy block size when not flushing or finishing. By default * this is 32K. This can be as small as 507 bytes for memLevel == 1. For * large input and output buffers, the stored block size will be larger. @@ -1876,10 +1815,7 @@ local block_state deflate_stored(s, flush) * new strings in the dictionary only for unmatched strings or for short * matches. It is used only for the fast compression options. */ -local block_state deflate_fast(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_fast(deflate_state *s, int flush) { IPos hash_head; /* head of the hash chain */ int bflush; /* set if current block must be flushed */ @@ -1897,7 +1833,7 @@ local block_state deflate_fast(s, flush) if (s->lookahead == 0) break; /* flush the current block */ } - /* Insert the string window[strstart .. strstart+2] in the + /* Insert the string window[strstart .. strstart + 2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = NIL; @@ -1945,7 +1881,7 @@ local block_state deflate_fast(s, flush) s->strstart += s->match_length; s->match_length = 0; s->ins_h = s->window[s->strstart]; - UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); + UPDATE_HASH(s, s->ins_h, s->window[s->strstart + 1]); #if MIN_MATCH != 3 Call UPDATE_HASH() MIN_MATCH-3 more times #endif @@ -1956,7 +1892,7 @@ local block_state deflate_fast(s, flush) } else { /* No match, output a literal byte */ Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; } @@ -1978,10 +1914,7 @@ local block_state deflate_fast(s, flush) * evaluation for matches: a match is finally adopted only if there is * no better match at the next window position. */ -local block_state deflate_slow(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_slow(deflate_state *s, int flush) { IPos hash_head; /* head of hash chain */ int bflush; /* set if current block must be flushed */ @@ -2000,7 +1933,7 @@ local block_state deflate_slow(s, flush) if (s->lookahead == 0) break; /* flush the current block */ } - /* Insert the string window[strstart .. strstart+2] in the + /* Insert the string window[strstart .. strstart + 2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = NIL; @@ -2042,17 +1975,17 @@ local block_state deflate_slow(s, flush) uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; /* Do not insert strings in hash table beyond this. */ - check_match(s, s->strstart-1, s->prev_match, s->prev_length); + check_match(s, s->strstart - 1, s->prev_match, s->prev_length); - _tr_tally_dist(s, s->strstart -1 - s->prev_match, + _tr_tally_dist(s, s->strstart - 1 - s->prev_match, s->prev_length - MIN_MATCH, bflush); /* Insert in hash table all strings up to the end of the match. - * strstart-1 and strstart are already inserted. If there is not + * strstart - 1 and strstart are already inserted. If there is not * enough lookahead, the last two strings are not inserted in * the hash table. */ - s->lookahead -= s->prev_length-1; + s->lookahead -= s->prev_length - 1; s->prev_length -= 2; do { if (++s->strstart <= max_insert) { @@ -2070,8 +2003,8 @@ local block_state deflate_slow(s, flush) * single literal. If there was a match but the current match * is longer, truncate the previous match to a single literal. */ - Tracevv((stderr,"%c", s->window[s->strstart-1])); - _tr_tally_lit(s, s->window[s->strstart-1], bflush); + Tracevv((stderr,"%c", s->window[s->strstart - 1])); + _tr_tally_lit(s, s->window[s->strstart - 1], bflush); if (bflush) { FLUSH_BLOCK_ONLY(s, 0); } @@ -2089,8 +2022,8 @@ local block_state deflate_slow(s, flush) } Assert (flush != Z_NO_FLUSH, "no flush?"); if (s->match_available) { - Tracevv((stderr,"%c", s->window[s->strstart-1])); - _tr_tally_lit(s, s->window[s->strstart-1], bflush); + Tracevv((stderr,"%c", s->window[s->strstart - 1])); + _tr_tally_lit(s, s->window[s->strstart - 1], bflush); s->match_available = 0; } s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; @@ -2109,10 +2042,7 @@ local block_state deflate_slow(s, flush) * one. Do not maintain a hash table. (It will be regenerated if this run of * deflate switches away from Z_RLE.) */ -local block_state deflate_rle(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_rle(deflate_state *s, int flush) { int bflush; /* set if current block must be flushed */ uInt prev; /* byte at distance one to match */ Bytef *scan, *strend; /* scan goes up to strend for length of run */ @@ -2147,7 +2077,8 @@ local block_state deflate_rle(s, flush) if (s->match_length > s->lookahead) s->match_length = s->lookahead; } - Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (uInt)(s->window_size - 1), + "wild scan"); } /* Emit match if have run of MIN_MATCH or longer, else emit literal */ @@ -2162,7 +2093,7 @@ local block_state deflate_rle(s, flush) } else { /* No match, output a literal byte */ Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; } @@ -2182,10 +2113,7 @@ local block_state deflate_rle(s, flush) * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. * (It will be regenerated if this run of deflate switches away from Huffman.) */ -local block_state deflate_huff(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_huff(deflate_state *s, int flush) { int bflush; /* set if current block must be flushed */ for (;;) { @@ -2202,7 +2130,7 @@ local block_state deflate_huff(s, flush) /* Output a literal byte */ s->match_length = 0; Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; if (bflush) FLUSH_BLOCK(s, 0); diff --git a/Utilities/cmzlib/deflate.h b/Utilities/cmzlib/deflate.h index 17c226113b0..300c6ada62b 100644 --- a/Utilities/cmzlib/deflate.h +++ b/Utilities/cmzlib/deflate.h @@ -1,5 +1,5 @@ /* deflate.h -- internal compression state - * Copyright (C) 1995-2018 Jean-loup Gailly + * Copyright (C) 1995-2024 Jean-loup Gailly * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -23,6 +23,10 @@ # define GZIP #endif +/* define LIT_MEM to slightly increase the speed of deflate (order 1% to 2%) at + the cost of a larger memory footprint */ +/* #define LIT_MEM */ + /* =========================================================================== * Internal compression state. */ @@ -217,7 +221,14 @@ typedef struct internal_state { /* Depth of each subtree used as tie breaker for trees of equal frequency */ +#ifdef LIT_MEM +# define LIT_BUFS 5 + ushf *d_buf; /* buffer for distances */ + uchf *l_buf; /* buffer for literals/lengths */ +#else +# define LIT_BUFS 4 uchf *sym_buf; /* buffer for distances and literals/lengths */ +#endif uInt lit_bufsize; /* Size of match buffer for literals/lengths. There are 4 reasons for @@ -239,7 +250,7 @@ typedef struct internal_state { * - I can't count above 4 */ - uInt sym_next; /* running index in sym_buf */ + uInt sym_next; /* running index in symbol buffer */ uInt sym_end; /* symbol table full when sym_next reaches this */ ulg opt_len; /* bit length of current block with optimal trees */ @@ -291,14 +302,14 @@ typedef struct internal_state { memory checker errors from longest match routines */ /* in trees.c */ -void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); -int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); -void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, - ulg stored_len, int last)); -void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); -void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); -void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, - ulg stored_len, int last)); +void ZLIB_INTERNAL _tr_init(deflate_state *s); +int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc); +void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, + ulg stored_len, int last); +void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s); +void ZLIB_INTERNAL _tr_align(deflate_state *s); +void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, + ulg stored_len, int last); #define d_code(dist) \ ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) @@ -318,6 +329,25 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, extern const uch ZLIB_INTERNAL _dist_code[]; #endif +#ifdef LIT_MEM +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->sym_next] = 0; \ + s->l_buf[s->sym_next++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->sym_next == s->sym_end); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (uch)(length); \ + ush dist = (ush)(distance); \ + s->d_buf[s->sym_next] = dist; \ + s->l_buf[s->sym_next++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->sym_next == s->sym_end); \ + } +#else # define _tr_tally_lit(s, c, flush) \ { uch cc = (c); \ s->sym_buf[s->sym_next++] = 0; \ @@ -329,14 +359,15 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, # define _tr_tally_dist(s, distance, length, flush) \ { uch len = (uch)(length); \ ush dist = (ush)(distance); \ - s->sym_buf[s->sym_next++] = dist; \ - s->sym_buf[s->sym_next++] = dist >> 8; \ + s->sym_buf[s->sym_next++] = (uch)dist; \ + s->sym_buf[s->sym_next++] = (uch)(dist >> 8); \ s->sym_buf[s->sym_next++] = len; \ dist--; \ s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ s->dyn_dtree[d_code(dist)].Freq++; \ flush = (s->sym_next == s->sym_end); \ } +#endif #else # define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) # define _tr_tally_dist(s, distance, length, flush) \ diff --git a/Utilities/cmzlib/gzclose.c b/Utilities/cmzlib/gzclose.c index caeb99a3177..48d6a86f04b 100644 --- a/Utilities/cmzlib/gzclose.c +++ b/Utilities/cmzlib/gzclose.c @@ -8,9 +8,7 @@ /* gzclose() is in a separate file so that it is linked in only if it is used. That way the other gzclose functions can be used instead to avoid linking in unneeded compression or decompression routines. */ -int ZEXPORT gzclose(file) - gzFile file; -{ +int ZEXPORT gzclose(gzFile file) { #ifndef NO_GZCOMPRESS gz_statep state; diff --git a/Utilities/cmzlib/gzguts.h b/Utilities/cmzlib/gzguts.h index 57faf37165a..eba72085bb7 100644 --- a/Utilities/cmzlib/gzguts.h +++ b/Utilities/cmzlib/gzguts.h @@ -1,5 +1,5 @@ /* gzguts.h -- zlib internal header definitions for gz* operations - * Copyright (C) 2004-2019 Mark Adler + * Copyright (C) 2004-2024 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -7,9 +7,8 @@ # ifndef _LARGEFILE_SOURCE # define _LARGEFILE_SOURCE 1 # endif -# ifdef _FILE_OFFSET_BITS -# undef _FILE_OFFSET_BITS -# endif +# undef _FILE_OFFSET_BITS +# undef _TIME_BITS #endif #ifdef HAVE_HIDDEN @@ -119,8 +118,8 @@ /* gz* functions always use library allocation functions */ #ifndef STDC - extern voidp malloc OF((uInt size)); - extern void free OF((voidpf ptr)); + extern voidp malloc(uInt size); + extern void free(voidpf ptr); #endif /* get errno and strerror definition */ @@ -138,10 +137,10 @@ /* provide prototypes for these when building zlib without LFS */ #if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 - ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); - ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); - ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); - ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN gzFile ZEXPORT gzopen64(const char *, const char *); + ZEXTERN z_off64_t ZEXPORT gzseek64(gzFile, z_off64_t, int); + ZEXTERN z_off64_t ZEXPORT gztell64(gzFile); + ZEXTERN z_off64_t ZEXPORT gzoffset64(gzFile); #endif /* default memLevel */ @@ -203,17 +202,13 @@ typedef struct { typedef gz_state FAR *gz_statep; /* shared functions */ -void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *)); +void ZLIB_INTERNAL gz_error(gz_statep, int, const char *); #if defined UNDER_CE -char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error)); +char ZLIB_INTERNAL *gz_strwinerror(DWORD error); #endif /* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t value -- needed when comparing unsigned to z_off64_t, which is signed (possible z_off64_t types off_t, off64_t, and long are all signed) */ -#ifdef INT_MAX -# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX) -#else -unsigned ZLIB_INTERNAL gz_intmax OF((void)); -# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) -#endif +unsigned ZLIB_INTERNAL gz_intmax(void); +#define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) diff --git a/Utilities/cmzlib/gzlib.c b/Utilities/cmzlib/gzlib.c index dddaf268730..983153cc8e4 100644 --- a/Utilities/cmzlib/gzlib.c +++ b/Utilities/cmzlib/gzlib.c @@ -1,5 +1,5 @@ /* gzlib.c -- zlib functions common to reading and writing gzip files - * Copyright (C) 2004-2019 Mark Adler + * Copyright (C) 2004-2024 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -15,10 +15,6 @@ #endif #endif -/* Local functions */ -local void gz_reset OF((gz_statep)); -local gzFile gz_open OF((const void *, int, const char *)); - #if defined UNDER_CE /* Map the Windows error number in ERROR to a locale-dependent error message @@ -30,9 +26,7 @@ local gzFile gz_open OF((const void *, int, const char *)); The gz_strwinerror function does not change the current setting of GetLastError. */ -char ZLIB_INTERNAL *gz_strwinerror (error) - DWORD error; -{ +char ZLIB_INTERNAL *gz_strwinerror(DWORD error) { static char buf[1024]; wchar_t *msgbuf; @@ -72,9 +66,7 @@ char ZLIB_INTERNAL *gz_strwinerror (error) #endif /* UNDER_CE */ /* Reset gzip file state */ -local void gz_reset(state) - gz_statep state; -{ +local void gz_reset(gz_statep state) { state->x.have = 0; /* no output data available */ if (state->mode == GZ_READ) { /* for reading ... */ state->eof = 0; /* not at end of file */ @@ -90,11 +82,7 @@ local void gz_reset(state) } /* Open a gzip file either by name or file descriptor. */ -local gzFile gz_open(path, fd, mode) - const void *path; - int fd; - const char *mode; -{ +local gzFile gz_open(const void *path, int fd, const char *mode) { gz_statep state; z_size_t len; int oflag; @@ -269,26 +257,17 @@ local gzFile gz_open(path, fd, mode) } /* -- see zlib.h -- */ -gzFile ZEXPORT gzopen(path, mode) - const char *path; - const char *mode; -{ +gzFile ZEXPORT gzopen(const char *path, const char *mode) { return gz_open(path, -1, mode); } /* -- see zlib.h -- */ -gzFile ZEXPORT gzopen64(path, mode) - const char *path; - const char *mode; -{ +gzFile ZEXPORT gzopen64(const char *path, const char *mode) { return gz_open(path, -1, mode); } /* -- see zlib.h -- */ -gzFile ZEXPORT gzdopen(fd, mode) - int fd; - const char *mode; -{ +gzFile ZEXPORT gzdopen(int fd, const char *mode) { char *path; /* identifier for error messages */ gzFile gz; @@ -306,19 +285,13 @@ gzFile ZEXPORT gzdopen(fd, mode) /* -- see zlib.h -- */ #ifdef WIDECHAR -gzFile ZEXPORT gzopen_w(path, mode) - const wchar_t *path; - const char *mode; -{ +gzFile ZEXPORT gzopen_w(const wchar_t *path, const char *mode) { return gz_open(path, -2, mode); } #endif /* -- see zlib.h -- */ -int ZEXPORT gzbuffer(file, size) - gzFile file; - unsigned size; -{ +int ZEXPORT gzbuffer(gzFile file, unsigned size) { gz_statep state; /* get internal structure and check integrity */ @@ -335,16 +308,14 @@ int ZEXPORT gzbuffer(file, size) /* check and set requested size */ if ((size << 1) < size) return -1; /* need to be able to double it */ - if (size < 2) - size = 2; /* need two bytes to check magic header */ + if (size < 8) + size = 8; /* needed to behave well with flushing */ state->want = size; return 0; } /* -- see zlib.h -- */ -int ZEXPORT gzrewind(file) - gzFile file; -{ +int ZEXPORT gzrewind(gzFile file) { gz_statep state; /* get internal structure */ @@ -365,11 +336,7 @@ int ZEXPORT gzrewind(file) } /* -- see zlib.h -- */ -z_off64_t ZEXPORT gzseek64(file, offset, whence) - gzFile file; - z_off64_t offset; - int whence; -{ +z_off64_t ZEXPORT gzseek64(gzFile file, z_off64_t offset, int whence) { unsigned n; z_off64_t ret; gz_statep state; @@ -442,11 +409,7 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) } /* -- see zlib.h -- */ -z_off_t ZEXPORT gzseek(file, offset, whence) - gzFile file; - z_off_t offset; - int whence; -{ +z_off_t ZEXPORT gzseek(gzFile file, z_off_t offset, int whence) { z_off64_t ret; ret = gzseek64(file, (z_off64_t)offset, whence); @@ -454,9 +417,7 @@ z_off_t ZEXPORT gzseek(file, offset, whence) } /* -- see zlib.h -- */ -z_off64_t ZEXPORT gztell64(file) - gzFile file; -{ +z_off64_t ZEXPORT gztell64(gzFile file) { gz_statep state; /* get internal structure and check integrity */ @@ -471,9 +432,7 @@ z_off64_t ZEXPORT gztell64(file) } /* -- see zlib.h -- */ -z_off_t ZEXPORT gztell(file) - gzFile file; -{ +z_off_t ZEXPORT gztell(gzFile file) { z_off64_t ret; ret = gztell64(file); @@ -481,9 +440,7 @@ z_off_t ZEXPORT gztell(file) } /* -- see zlib.h -- */ -z_off64_t ZEXPORT gzoffset64(file) - gzFile file; -{ +z_off64_t ZEXPORT gzoffset64(gzFile file) { z_off64_t offset; gz_statep state; @@ -504,9 +461,7 @@ z_off64_t ZEXPORT gzoffset64(file) } /* -- see zlib.h -- */ -z_off_t ZEXPORT gzoffset(file) - gzFile file; -{ +z_off_t ZEXPORT gzoffset(gzFile file) { z_off64_t ret; ret = gzoffset64(file); @@ -514,9 +469,7 @@ z_off_t ZEXPORT gzoffset(file) } /* -- see zlib.h -- */ -int ZEXPORT gzeof(file) - gzFile file; -{ +int ZEXPORT gzeof(gzFile file) { gz_statep state; /* get internal structure and check integrity */ @@ -531,10 +484,7 @@ int ZEXPORT gzeof(file) } /* -- see zlib.h -- */ -const char * ZEXPORT gzerror(file, errnum) - gzFile file; - int *errnum; -{ +const char * ZEXPORT gzerror(gzFile file, int *errnum) { gz_statep state; /* get internal structure and check integrity */ @@ -552,9 +502,7 @@ const char * ZEXPORT gzerror(file, errnum) } /* -- see zlib.h -- */ -void ZEXPORT gzclearerr(file) - gzFile file; -{ +void ZEXPORT gzclearerr(gzFile file) { gz_statep state; /* get internal structure and check integrity */ @@ -578,11 +526,7 @@ void ZEXPORT gzclearerr(file) memory). Simply save the error message as a static string. If there is an allocation failure constructing the error message, then convert the error to out of memory. */ -void ZLIB_INTERNAL gz_error(state, err, msg) - gz_statep state; - int err; - const char *msg; -{ +void ZLIB_INTERNAL gz_error(gz_statep state, int err, const char *msg) { /* free previously allocated message and clear */ if (state->msg != NULL) { if (state->err != Z_MEM_ERROR) @@ -619,21 +563,20 @@ void ZLIB_INTERNAL gz_error(state, err, msg) #endif } -#ifndef INT_MAX /* portably return maximum value for an int (when limits.h presumed not available) -- we need to do this to cover cases where 2's complement not used, since C standard permits 1's complement and sign-bit representations, otherwise we could just use ((unsigned)-1) >> 1 */ -unsigned ZLIB_INTERNAL gz_intmax() -{ - unsigned p, q; - - p = 1; +unsigned ZLIB_INTERNAL gz_intmax(void) { +#ifdef INT_MAX + return INT_MAX; +#else + unsigned p = 1, q; do { q = p; p <<= 1; p++; } while (p > q); return q >> 1; -} #endif +} diff --git a/Utilities/cmzlib/gzread.c b/Utilities/cmzlib/gzread.c index e3519e6d058..e1a8a8dd0a8 100644 --- a/Utilities/cmzlib/gzread.c +++ b/Utilities/cmzlib/gzread.c @@ -5,25 +5,12 @@ #include "gzguts.h" -/* Local functions */ -local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *)); -local int gz_avail OF((gz_statep)); -local int gz_look OF((gz_statep)); -local int gz_decomp OF((gz_statep)); -local int gz_fetch OF((gz_statep)); -local int gz_skip OF((gz_statep, z_off64_t)); -local z_size_t gz_read OF((gz_statep, voidp, z_size_t)); - /* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from state->fd, and update state->eof, state->err, and state->msg as appropriate. This function needs to loop on read(), since read() is not guaranteed to read the number of bytes requested, depending on the type of descriptor. */ -local int gz_load(state, buf, len, have) - gz_statep state; - unsigned char *buf; - unsigned len; - unsigned *have; -{ +local int gz_load(gz_statep state, unsigned char *buf, unsigned len, + unsigned *have) { int ret; unsigned get, max = ((unsigned)-1 >> 2) + 1; @@ -53,9 +40,7 @@ local int gz_load(state, buf, len, have) If strm->avail_in != 0, then the current data is moved to the beginning of the input buffer, and then the remainder of the buffer is loaded with the available data from the input file. */ -local int gz_avail(state) - gz_statep state; -{ +local int gz_avail(gz_statep state) { unsigned got; z_streamp strm = &(state->strm); @@ -88,9 +73,7 @@ local int gz_avail(state) case, all further file reads will be directly to either the output buffer or a user buffer. If decompressing, the inflate state will be initialized. gz_look() will return 0 on success or -1 on failure. */ -local int gz_look(state) - gz_statep state; -{ +local int gz_look(gz_statep state) { z_streamp strm = &(state->strm); /* allocate read buffers and inflate memory */ @@ -157,11 +140,9 @@ local int gz_look(state) the output buffer is larger than the input buffer, which also assures space for gzungetc() */ state->x.next = state->out; - if (strm->avail_in) { - memcpy(state->x.next, strm->next_in, strm->avail_in); - state->x.have = strm->avail_in; - strm->avail_in = 0; - } + memcpy(state->x.next, strm->next_in, strm->avail_in); + state->x.have = strm->avail_in; + strm->avail_in = 0; state->how = COPY; state->direct = 1; return 0; @@ -172,9 +153,7 @@ local int gz_look(state) data. If the gzip stream completes, state->how is reset to LOOK to look for the next gzip stream or raw data, once state->x.have is depleted. Returns 0 on success, -1 on failure. */ -local int gz_decomp(state) - gz_statep state; -{ +local int gz_decomp(gz_statep state) { int ret = Z_OK; unsigned had; z_streamp strm = &(state->strm); @@ -226,9 +205,7 @@ local int gz_decomp(state) looked for to determine whether to copy or decompress. Returns -1 on error, otherwise 0. gz_fetch() will leave state->how as COPY or GZIP unless the end of the input file has been reached and all data has been processed. */ -local int gz_fetch(state) - gz_statep state; -{ +local int gz_fetch(gz_statep state) { z_streamp strm = &(state->strm); do { @@ -256,10 +233,7 @@ local int gz_fetch(state) } /* Skip len uncompressed bytes of output. Return -1 on error, 0 on success. */ -local int gz_skip(state, len) - gz_statep state; - z_off64_t len; -{ +local int gz_skip(gz_statep state, z_off64_t len) { unsigned n; /* skip over len bytes or reach end-of-file, whichever comes first */ @@ -291,11 +265,7 @@ local int gz_skip(state, len) input. Return the number of bytes read. If zero is returned, either the end of file was reached, or there was an error. state->err must be consulted in that case to determine which. */ -local z_size_t gz_read(state, buf, len) - gz_statep state; - voidp buf; - z_size_t len; -{ +local z_size_t gz_read(gz_statep state, voidp buf, z_size_t len) { z_size_t got; unsigned n; @@ -372,11 +342,7 @@ local z_size_t gz_read(state, buf, len) } /* -- see zlib.h -- */ -int ZEXPORT gzread(file, buf, len) - gzFile file; - voidp buf; - unsigned len; -{ +int ZEXPORT gzread(gzFile file, voidp buf, unsigned len) { gz_statep state; /* get internal structure */ @@ -408,12 +374,7 @@ int ZEXPORT gzread(file, buf, len) } /* -- see zlib.h -- */ -z_size_t ZEXPORT gzfread(buf, size, nitems, file) - voidp buf; - z_size_t size; - z_size_t nitems; - gzFile file; -{ +z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems, gzFile file) { z_size_t len; gz_statep state; @@ -452,9 +413,7 @@ z_size_t ZEXPORT gzfread(buf, size, nitems, file) #else # undef gzgetc #endif -int ZEXPORT gzgetc(file) - gzFile file; -{ +int ZEXPORT gzgetc(gzFile file) { unsigned char buf[1]; gz_statep state; @@ -479,17 +438,12 @@ int ZEXPORT gzgetc(file) return gz_read(state, buf, 1) < 1 ? -1 : buf[0]; } -int ZEXPORT gzgetc_(file) -gzFile file; -{ +int ZEXPORT gzgetc_(gzFile file) { return gzgetc(file); } /* -- see zlib.h -- */ -int ZEXPORT gzungetc(c, file) - int c; - gzFile file; -{ +int ZEXPORT gzungetc(int c, gzFile file) { gz_statep state; /* get internal structure */ @@ -497,6 +451,10 @@ int ZEXPORT gzungetc(c, file) return -1; state = (gz_statep)file; + /* in case this was just opened, set up the input buffer */ + if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0) + (void)gz_look(state); + /* check that we're reading and that there's no (serious) error */ if (state->mode != GZ_READ || (state->err != Z_OK && state->err != Z_BUF_ERROR)) @@ -546,11 +504,7 @@ int ZEXPORT gzungetc(c, file) } /* -- see zlib.h -- */ -char * ZEXPORT gzgets(file, buf, len) - gzFile file; - char *buf; - int len; -{ +char * ZEXPORT gzgets(gzFile file, char *buf, int len) { unsigned left, n; char *str; unsigned char *eol; @@ -610,9 +564,7 @@ char * ZEXPORT gzgets(file, buf, len) } /* -- see zlib.h -- */ -int ZEXPORT gzdirect(file) - gzFile file; -{ +int ZEXPORT gzdirect(gzFile file) { gz_statep state; /* get internal structure */ @@ -630,9 +582,7 @@ int ZEXPORT gzdirect(file) } /* -- see zlib.h -- */ -int ZEXPORT gzclose_r(file) - gzFile file; -{ +int ZEXPORT gzclose_r(gzFile file) { int ret, err; gz_statep state; diff --git a/Utilities/cmzlib/gzwrite.c b/Utilities/cmzlib/gzwrite.c index 33f49496926..6ac0de20e75 100644 --- a/Utilities/cmzlib/gzwrite.c +++ b/Utilities/cmzlib/gzwrite.c @@ -5,18 +5,10 @@ #include "gzguts.h" -/* Local functions */ -local int gz_init OF((gz_statep)); -local int gz_comp OF((gz_statep, int)); -local int gz_zero OF((gz_statep, z_off64_t)); -local z_size_t gz_write OF((gz_statep, voidpc, z_size_t)); - /* Initialize state for writing a gzip file. Mark initialization by setting state->size to non-zero. Return -1 on a memory allocation failure, or 0 on success. */ -local int gz_init(state) - gz_statep state; -{ +local int gz_init(gz_statep state) { int ret; z_streamp strm = &(state->strm); @@ -70,10 +62,7 @@ local int gz_init(state) deflate() flush value. If flush is Z_FINISH, then the deflate() state is reset to start a new gzip stream. If gz->direct is true, then simply write to the output file without compressing, and ignore flush. */ -local int gz_comp(state, flush) - gz_statep state; - int flush; -{ +local int gz_comp(gz_statep state, int flush) { int ret, writ; unsigned have, put, max = ((unsigned)-1 >> 2) + 1; z_streamp strm = &(state->strm); @@ -151,10 +140,7 @@ local int gz_comp(state, flush) /* Compress len zeros to output. Return -1 on a write error or memory allocation failure by gz_comp(), or 0 on success. */ -local int gz_zero(state, len) - gz_statep state; - z_off64_t len; -{ +local int gz_zero(gz_statep state, z_off64_t len) { int first; unsigned n; z_streamp strm = &(state->strm); @@ -184,11 +170,7 @@ local int gz_zero(state, len) /* Write len bytes from buf to file. Return the number of bytes written. If the returned value is less than len, then there was an error. */ -local z_size_t gz_write(state, buf, len) - gz_statep state; - voidpc buf; - z_size_t len; -{ +local z_size_t gz_write(gz_statep state, voidpc buf, z_size_t len) { z_size_t put = len; /* if len is zero, avoid unnecessary operations */ @@ -252,11 +234,7 @@ local z_size_t gz_write(state, buf, len) } /* -- see zlib.h -- */ -int ZEXPORT gzwrite(file, buf, len) - gzFile file; - voidpc buf; - unsigned len; -{ +int ZEXPORT gzwrite(gzFile file, voidpc buf, unsigned len) { gz_statep state; /* get internal structure */ @@ -280,12 +258,8 @@ int ZEXPORT gzwrite(file, buf, len) } /* -- see zlib.h -- */ -z_size_t ZEXPORT gzfwrite(buf, size, nitems, file) - voidpc buf; - z_size_t size; - z_size_t nitems; - gzFile file; -{ +z_size_t ZEXPORT gzfwrite(voidpc buf, z_size_t size, z_size_t nitems, + gzFile file) { z_size_t len; gz_statep state; @@ -316,10 +290,7 @@ z_size_t ZEXPORT gzfwrite(buf, size, nitems, file) } /* -- see zlib.h -- */ -int ZEXPORT gzputc(file, c) - gzFile file; - int c; -{ +int ZEXPORT gzputc(gzFile file, int c) { unsigned have; unsigned char buf[1]; gz_statep state; @@ -364,10 +335,7 @@ int ZEXPORT gzputc(file, c) } /* -- see zlib.h -- */ -int ZEXPORT gzputs(file, s) - gzFile file; - const char *s; -{ +int ZEXPORT gzputs(gzFile file, const char *s) { z_size_t len, put; gz_statep state; @@ -394,8 +362,7 @@ int ZEXPORT gzputs(file, s) #include /* -- see zlib.h -- */ -int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) -{ +int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) { int len; unsigned left; char *next; @@ -466,8 +433,7 @@ int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) return len; } -int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) -{ +int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) { va_list va; int ret; @@ -480,13 +446,10 @@ int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) #else /* !STDC && !Z_HAVE_STDARG_H */ /* -- see zlib.h -- */ -int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, - a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) - gzFile file; - const char *format; - int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, - a11, a12, a13, a14, a15, a16, a17, a18, a19, a20; -{ +int ZEXPORTVA gzprintf(gzFile file, const char *format, int a1, int a2, int a3, + int a4, int a5, int a6, int a7, int a8, int a9, int a10, + int a11, int a12, int a13, int a14, int a15, int a16, + int a17, int a18, int a19, int a20) { unsigned len, left; char *next; gz_statep state; @@ -568,10 +531,7 @@ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, #endif /* -- see zlib.h -- */ -int ZEXPORT gzflush(file, flush) - gzFile file; - int flush; -{ +int ZEXPORT gzflush(gzFile file, int flush) { gz_statep state; /* get internal structure */ @@ -600,11 +560,7 @@ int ZEXPORT gzflush(file, flush) } /* -- see zlib.h -- */ -int ZEXPORT gzsetparams(file, level, strategy) - gzFile file; - int level; - int strategy; -{ +int ZEXPORT gzsetparams(gzFile file, int level, int strategy) { gz_statep state; z_streamp strm; @@ -615,7 +571,7 @@ int ZEXPORT gzsetparams(file, level, strategy) strm = &(state->strm); /* check that we're writing and that there's no error */ - if (state->mode != GZ_WRITE || state->err != Z_OK) + if (state->mode != GZ_WRITE || state->err != Z_OK || state->direct) return Z_STREAM_ERROR; /* if no change is requested, then do nothing */ @@ -642,9 +598,7 @@ int ZEXPORT gzsetparams(file, level, strategy) } /* -- see zlib.h -- */ -int ZEXPORT gzclose_w(file) - gzFile file; -{ +int ZEXPORT gzclose_w(gzFile file) { int ret = Z_OK; gz_statep state; diff --git a/Utilities/cmzlib/inffast.c b/Utilities/cmzlib/inffast.c index 1fec7f363fa..9354676e786 100644 --- a/Utilities/cmzlib/inffast.c +++ b/Utilities/cmzlib/inffast.c @@ -47,10 +47,7 @@ requires strm->avail_out >= 258 for each loop to avoid checking for output space. */ -void ZLIB_INTERNAL inflate_fast(strm, start) -z_streamp strm; -unsigned start; /* inflate()'s starting value for strm->avail_out */ -{ +void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start) { struct inflate_state FAR *state; z_const unsigned char FAR *in; /* local strm->next_in */ z_const unsigned char FAR *last; /* have enough input while in < last */ diff --git a/Utilities/cmzlib/inffast.h b/Utilities/cmzlib/inffast.h index e5c1aa4ca8c..49c6d156c5c 100644 --- a/Utilities/cmzlib/inffast.h +++ b/Utilities/cmzlib/inffast.h @@ -8,4 +8,4 @@ subject to change. Applications should only use zlib.h. */ -void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start)); +void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start); diff --git a/Utilities/cmzlib/inflate.c b/Utilities/cmzlib/inflate.c index 7be8c63662a..94ecff015a9 100644 --- a/Utilities/cmzlib/inflate.c +++ b/Utilities/cmzlib/inflate.c @@ -91,20 +91,7 @@ # endif #endif -/* function prototypes */ -local int inflateStateCheck OF((z_streamp strm)); -local void fixedtables OF((struct inflate_state FAR *state)); -local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, - unsigned copy)); -#ifdef BUILDFIXED - void makefixed OF((void)); -#endif -local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, - unsigned len)); - -local int inflateStateCheck(strm) -z_streamp strm; -{ +local int inflateStateCheck(z_streamp strm) { struct inflate_state FAR *state; if (strm == Z_NULL || strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) @@ -116,9 +103,7 @@ z_streamp strm; return 0; } -int ZEXPORT inflateResetKeep(strm) -z_streamp strm; -{ +int ZEXPORT inflateResetKeep(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -142,9 +127,7 @@ z_streamp strm; return Z_OK; } -int ZEXPORT inflateReset(strm) -z_streamp strm; -{ +int ZEXPORT inflateReset(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -155,10 +138,7 @@ z_streamp strm; return inflateResetKeep(strm); } -int ZEXPORT inflateReset2(strm, windowBits) -z_streamp strm; -int windowBits; -{ +int ZEXPORT inflateReset2(z_streamp strm, int windowBits) { int wrap; struct inflate_state FAR *state; @@ -168,6 +148,8 @@ int windowBits; /* extract wrap request from windowBits parameter */ if (windowBits < 0) { + if (windowBits < -15) + return Z_STREAM_ERROR; wrap = 0; windowBits = -windowBits; } @@ -193,12 +175,8 @@ int windowBits; return inflateReset(strm); } -int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size) -z_streamp strm; -int windowBits; -const char *version; -int stream_size; -{ +int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, + const char *version, int stream_size) { int ret; struct inflate_state FAR *state; @@ -237,22 +215,17 @@ int stream_size; return ret; } -int ZEXPORT inflateInit_(strm, version, stream_size) -z_streamp strm; -const char *version; -int stream_size; -{ +int ZEXPORT inflateInit_(z_streamp strm, const char *version, + int stream_size) { return inflateInit2_(strm, DEF_WBITS, version, stream_size); } -int ZEXPORT inflatePrime(strm, bits, value) -z_streamp strm; -int bits; -int value; -{ +int ZEXPORT inflatePrime(z_streamp strm, int bits, int value) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + if (bits == 0) + return Z_OK; state = (struct inflate_state FAR *)strm->state; if (bits < 0) { state->hold = 0; @@ -276,9 +249,7 @@ int value; used for threaded applications, since the rewriting of the tables and virgin may not be thread-safe. */ -local void fixedtables(state) -struct inflate_state FAR *state; -{ +local void fixedtables(struct inflate_state FAR *state) { #ifdef BUILDFIXED static int virgin = 1; static code *lenfix, *distfix; @@ -340,7 +311,7 @@ struct inflate_state FAR *state; a.out > inffixed.h */ -void makefixed() +void makefixed(void) { unsigned low, size; struct inflate_state state; @@ -394,11 +365,7 @@ void makefixed() output will fall in the output data, making match copies simpler and faster. The advantage may be dependent on the size of the processor's data caches. */ -local int updatewindow(strm, end, copy) -z_streamp strm; -const Bytef *end; -unsigned copy; -{ +local int updatewindow(z_streamp strm, const Bytef *end, unsigned copy) { struct inflate_state FAR *state; unsigned dist; @@ -620,10 +587,7 @@ unsigned copy; will return Z_BUF_ERROR if it has not reached the end of the stream. */ -int ZEXPORT inflate(strm, flush) -z_streamp strm; -int flush; -{ +int ZEXPORT inflate(z_streamp strm, int flush) { struct inflate_state FAR *state; z_const unsigned char FAR *next; /* next input */ unsigned char FAR *put; /* next output */ @@ -764,8 +728,9 @@ int flush; if (copy > have) copy = have; if (copy) { if (state->head != Z_NULL && - state->head->extra != Z_NULL) { - len = state->head->extra_len - state->length; + state->head->extra != Z_NULL && + (len = state->head->extra_len - state->length) < + state->head->extra_max) { zmemcpy(state->head->extra + len, next, len + copy > state->head->extra_max ? state->head->extra_max - len : copy); @@ -1298,9 +1263,7 @@ int flush; return ret; } -int ZEXPORT inflateEnd(strm) -z_streamp strm; -{ +int ZEXPORT inflateEnd(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1312,11 +1275,8 @@ z_streamp strm; return Z_OK; } -int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength) -z_streamp strm; -Bytef *dictionary; -uInt *dictLength; -{ +int ZEXPORT inflateGetDictionary(z_streamp strm, Bytef *dictionary, + uInt *dictLength) { struct inflate_state FAR *state; /* check state */ @@ -1335,11 +1295,8 @@ uInt *dictLength; return Z_OK; } -int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) -z_streamp strm; -const Bytef *dictionary; -uInt dictLength; -{ +int ZEXPORT inflateSetDictionary(z_streamp strm, const Bytef *dictionary, + uInt dictLength) { struct inflate_state FAR *state; unsigned long dictid; int ret; @@ -1370,10 +1327,7 @@ uInt dictLength; return Z_OK; } -int ZEXPORT inflateGetHeader(strm, head) -z_streamp strm; -gz_headerp head; -{ +int ZEXPORT inflateGetHeader(z_streamp strm, gz_headerp head) { struct inflate_state FAR *state; /* check state */ @@ -1398,11 +1352,8 @@ gz_headerp head; called again with more data and the *have state. *have is initialized to zero for the first call. */ -local unsigned syncsearch(have, buf, len) -unsigned FAR *have; -const unsigned char FAR *buf; -unsigned len; -{ +local unsigned syncsearch(unsigned FAR *have, const unsigned char FAR *buf, + unsigned len) { unsigned got; unsigned next; @@ -1421,9 +1372,7 @@ unsigned len; return next; } -int ZEXPORT inflateSync(strm) -z_streamp strm; -{ +int ZEXPORT inflateSync(z_streamp strm) { unsigned len; /* number of bytes to look at or looked at */ int flags; /* temporary to save header status */ unsigned long in, out; /* temporary to save total_in and total_out */ @@ -1438,7 +1387,7 @@ z_streamp strm; /* if first time, start search in bit buffer */ if (state->mode != SYNC) { state->mode = SYNC; - state->hold <<= state->bits & 7; + state->hold >>= state->bits & 7; state->bits -= state->bits & 7; len = 0; while (state->bits >= 8) { @@ -1479,9 +1428,7 @@ z_streamp strm; block. When decompressing, PPP checks that at the end of input packet, inflate is waiting for these length bytes. */ -int ZEXPORT inflateSyncPoint(strm) -z_streamp strm; -{ +int ZEXPORT inflateSyncPoint(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1489,10 +1436,7 @@ z_streamp strm; return state->mode == STORED && state->bits == 0; } -int ZEXPORT inflateCopy(dest, source) -z_streamp dest; -z_streamp source; -{ +int ZEXPORT inflateCopy(z_streamp dest, z_streamp source) { struct inflate_state FAR *state; struct inflate_state FAR *copy; unsigned char FAR *window; @@ -1536,10 +1480,7 @@ z_streamp source; return Z_OK; } -int ZEXPORT inflateUndermine(strm, subvert) -z_streamp strm; -int subvert; -{ +int ZEXPORT inflateUndermine(z_streamp strm, int subvert) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1554,10 +1495,7 @@ int subvert; #endif } -int ZEXPORT inflateValidate(strm, check) -z_streamp strm; -int check; -{ +int ZEXPORT inflateValidate(z_streamp strm, int check) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1569,9 +1507,7 @@ int check; return Z_OK; } -long ZEXPORT inflateMark(strm) -z_streamp strm; -{ +long ZEXPORT inflateMark(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) @@ -1582,9 +1518,7 @@ z_streamp strm; (state->mode == MATCH ? state->was - state->length : 0)); } -unsigned long ZEXPORT inflateCodesUsed(strm) -z_streamp strm; -{ +unsigned long ZEXPORT inflateCodesUsed(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return (unsigned long)-1; state = (struct inflate_state FAR *)strm->state; diff --git a/Utilities/cmzlib/inftrees.c b/Utilities/cmzlib/inftrees.c index 09462a740b1..98cfe164458 100644 --- a/Utilities/cmzlib/inftrees.c +++ b/Utilities/cmzlib/inftrees.c @@ -1,5 +1,5 @@ /* inftrees.c -- generate Huffman trees for efficient decoding - * Copyright (C) 1995-2022 Mark Adler + * Copyright (C) 1995-2024 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -9,7 +9,7 @@ #define MAXBITS 15 const char inflate_copyright[] = - " inflate 1.2.12 Copyright 1995-2022 Mark Adler "; + " inflate 1.3.1 Copyright 1995-2024 Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -29,14 +29,9 @@ const char inflate_copyright[] = table index bits. It will differ if the request is greater than the longest code or if it is less than the shortest code. */ -int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work) -codetype type; -unsigned short FAR *lens; -unsigned codes; -code FAR * FAR *table; -unsigned FAR *bits; -unsigned short FAR *work; -{ +int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work) { unsigned len; /* a code's length in bits */ unsigned sym; /* index of code symbols */ unsigned min, max; /* minimum and maximum code lengths */ @@ -62,7 +57,7 @@ unsigned short FAR *work; 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; static const unsigned short lext[31] = { /* Length codes 257..285 extra */ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 199, 202}; + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 203, 77}; static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, diff --git a/Utilities/cmzlib/inftrees.h b/Utilities/cmzlib/inftrees.h index baa53a0b1a1..396f74b5da7 100644 --- a/Utilities/cmzlib/inftrees.h +++ b/Utilities/cmzlib/inftrees.h @@ -38,11 +38,11 @@ typedef struct { /* Maximum size of the dynamic table. The maximum number of code structures is 1444, which is the sum of 852 for literal/length codes and 592 for distance codes. These values were found by exhaustive searches using the program - examples/enough.c found in the zlib distribtution. The arguments to that + examples/enough.c found in the zlib distribution. The arguments to that program are the number of symbols, the initial root table size, and the maximum bit length of a code. "enough 286 9 15" for literal/length codes - returns returns 852, and "enough 30 6 15" for distance codes returns 592. - The initial root table size (9 or 6) is found in the fifth argument of the + returns 852, and "enough 30 6 15" for distance codes returns 592. The + initial root table size (9 or 6) is found in the fifth argument of the inflate_table() calls in inflate.c and infback.c. If the root table size is changed, then these maximum sizes would be need to be recalculated and updated. */ @@ -57,6 +57,6 @@ typedef enum { DISTS } codetype; -int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens, - unsigned codes, code FAR * FAR *table, - unsigned FAR *bits, unsigned short FAR *work)); +int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work); diff --git a/Utilities/cmzlib/trees.c b/Utilities/cmzlib/trees.c index f73fd99c37b..6a523ef34e3 100644 --- a/Utilities/cmzlib/trees.c +++ b/Utilities/cmzlib/trees.c @@ -1,5 +1,5 @@ /* trees.c -- output deflated data using Huffman coding - * Copyright (C) 1995-2021 Jean-loup Gailly + * Copyright (C) 1995-2024 Jean-loup Gailly * detect_data_type() function provided freely by Cosmin Truta, 2006 * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -122,39 +122,116 @@ struct static_tree_desc_s { int max_length; /* max bit length for the codes */ }; -local const static_tree_desc static_l_desc = +#ifdef NO_INIT_GLOBAL_POINTERS +# define TCONST +#else +# define TCONST const +#endif + +local TCONST static_tree_desc static_l_desc = {static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; -local const static_tree_desc static_d_desc = +local TCONST static_tree_desc static_d_desc = {static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; -local const static_tree_desc static_bl_desc = +local TCONST static_tree_desc static_bl_desc = {(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; /* =========================================================================== - * Local (static) routines in this file. + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse(unsigned code, int len) { + register unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush(deflate_state *s) { + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup(deflate_state *s) { + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef ZLIB_DEBUG + s->bits_sent = (s->bits_sent + 7) & ~7; +#endif +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. */ +local void gen_codes(ct_data *tree, int max_code, ushf *bl_count) { + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + unsigned code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ -local void tr_static_init OF((void)); -local void init_block OF((deflate_state *s)); -local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); -local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); -local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); -local void build_tree OF((deflate_state *s, tree_desc *desc)); -local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); -local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); -local int build_bl_tree OF((deflate_state *s)); -local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, - int blcodes)); -local void compress_block OF((deflate_state *s, const ct_data *ltree, - const ct_data *dtree)); -local int detect_data_type OF((deflate_state *s)); -local unsigned bi_reverse OF((unsigned code, int len)); -local void bi_windup OF((deflate_state *s)); -local void bi_flush OF((deflate_state *s)); + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + code = (code + bl_count[bits - 1]) << 1; + next_code[bits] = (ush)code; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS] - 1 == (1 << MAX_BITS) - 1, + "inconsistent bit counts"); + Tracev((stderr,"\ngen_codes: max_code %d ", max_code)); + + for (n = 0; n <= max_code; n++) { + int len = tree[n].Len; + if (len == 0) continue; + /* Now reverse the bits */ + tree[n].Code = (ush)bi_reverse(next_code[len]++, len); + + Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ", + n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len] - 1)); + } +} #ifdef GEN_TREES_H -local void gen_trees_header OF((void)); +local void gen_trees_header(void); #endif #ifndef ZLIB_DEBUG @@ -167,33 +244,18 @@ local void gen_trees_header OF((void)); send_bits(s, tree[c].Code, tree[c].Len); } #endif -/* =========================================================================== - * Output a short LSB first on the stream. - * IN assertion: there is enough room in pendingBuf. - */ -#define put_short(s, w) { \ - put_byte(s, (uch)((w) & 0xff)); \ - put_byte(s, (uch)((ush)(w) >> 8)); \ -} - /* =========================================================================== * Send a value on a given number of bits. * IN assertion: length <= 16 and value fits in length bits. */ #ifdef ZLIB_DEBUG -local void send_bits OF((deflate_state *s, int value, int length)); - -local void send_bits(s, value, length) - deflate_state *s; - int value; /* value to send */ - int length; /* number of bits */ -{ +local void send_bits(deflate_state *s, int value, int length) { Tracevv((stderr," l %2d v %4x ", length, value)); Assert(length > 0 && length <= 15, "invalid length"); s->bits_sent += (ulg)length; /* If not enough room in bi_buf, use (valid) bits from bi_buf and - * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * (16 - bi_valid) bits from value, leaving (width - (16 - bi_valid)) * unused bits in value. */ if (s->bi_valid > (int)Buf_size - length) { @@ -229,8 +291,7 @@ local void send_bits(s, value, length) /* =========================================================================== * Initialize the various 'constant' tables. */ -local void tr_static_init() -{ +local void tr_static_init(void) { #if defined(GEN_TREES_H) || !defined(STDC) static int static_init_done = 0; int n; /* iterates over tree elements */ @@ -256,7 +317,7 @@ local void tr_static_init() length = 0; for (code = 0; code < LENGTH_CODES-1; code++) { base_length[code] = length; - for (n = 0; n < (1< dist code (0..29) */ dist = 0; for (code = 0 ; code < 16; code++) { base_dist[code] = dist; - for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ for ( ; code < D_CODES; code++) { base_dist[code] = dist << 7; - for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { _dist_code[256 + dist++] = (uch)code; } } - Assert (dist == 256, "tr_static_init: 256+dist != 512"); + Assert (dist == 256, "tr_static_init: 256 + dist != 512"); /* Construct the codes of the static literal tree */ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; @@ -312,7 +373,7 @@ local void tr_static_init() } /* =========================================================================== - * Genererate the file trees.h describing the static trees. + * Generate the file trees.h describing the static trees. */ #ifdef GEN_TREES_H # ifndef ZLIB_DEBUG @@ -321,10 +382,9 @@ local void tr_static_init() # define SEPARATOR(i, last, width) \ ((i) == (last)? "\n};\n\n" : \ - ((i) % (width) == (width)-1 ? ",\n" : ", ")) + ((i) % (width) == (width) - 1 ? ",\n" : ", ")) -void gen_trees_header() -{ +void gen_trees_header(void) { FILE *header = fopen("trees.h", "w"); int i; @@ -373,12 +433,26 @@ void gen_trees_header() } #endif /* GEN_TREES_H */ +/* =========================================================================== + * Initialize a new block. + */ +local void init_block(deflate_state *s) { + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->sym_next = s->matches = 0; +} + /* =========================================================================== * Initialize the tree data structures for a new zlib stream. */ -void ZLIB_INTERNAL _tr_init(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_init(deflate_state *s) { tr_static_init(); s->l_desc.dyn_tree = s->dyn_ltree; @@ -401,24 +475,6 @@ void ZLIB_INTERNAL _tr_init(s) init_block(s); } -/* =========================================================================== - * Initialize a new block. - */ -local void init_block(s) - deflate_state *s; -{ - int n; /* iterates over tree elements */ - - /* Initialize the trees. */ - for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; - for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; - for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; - - s->dyn_ltree[END_BLOCK].Freq = 1; - s->opt_len = s->static_len = 0L; - s->sym_next = s->matches = 0; -} - #define SMALLEST 1 /* Index within the heap array of least frequent node in the Huffman tree */ @@ -448,17 +504,13 @@ local void init_block(s) * when the heap property is re-established (each father smaller than its * two sons). */ -local void pqdownheap(s, tree, k) - deflate_state *s; - ct_data *tree; /* the tree to restore */ - int k; /* node to move down */ -{ +local void pqdownheap(deflate_state *s, ct_data *tree, int k) { int v = s->heap[k]; int j = k << 1; /* left son of k */ while (j <= s->heap_len) { /* Set j to the smallest of the two sons: */ if (j < s->heap_len && - smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + smaller(tree, s->heap[j + 1], s->heap[j], s->depth)) { j++; } /* Exit if v is smaller than both sons */ @@ -483,10 +535,7 @@ local void pqdownheap(s, tree, k) * The length opt_len is updated; static_len is also updated if stree is * not null. */ -local void gen_bitlen(s, desc) - deflate_state *s; - tree_desc *desc; /* the tree descriptor */ -{ +local void gen_bitlen(deflate_state *s, tree_desc *desc) { ct_data *tree = desc->dyn_tree; int max_code = desc->max_code; const ct_data *stree = desc->stat_desc->static_tree; @@ -507,7 +556,7 @@ local void gen_bitlen(s, desc) */ tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ - for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + for (h = s->heap_max + 1; h < HEAP_SIZE; h++) { n = s->heap[h]; bits = tree[tree[n].Dad].Len + 1; if (bits > max_length) bits = max_length, overflow++; @@ -518,7 +567,7 @@ local void gen_bitlen(s, desc) s->bl_count[bits]++; xbits = 0; - if (n >= base) xbits = extra[n-base]; + if (n >= base) xbits = extra[n - base]; f = tree[n].Freq; s->opt_len += (ulg)f * (unsigned)(bits + xbits); if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits); @@ -530,10 +579,10 @@ local void gen_bitlen(s, desc) /* Find the first bit length which could increase: */ do { - bits = max_length-1; + bits = max_length - 1; while (s->bl_count[bits] == 0) bits--; - s->bl_count[bits]--; /* move one leaf down the tree */ - s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits + 1] += 2; /* move one overflow item as its brother */ s->bl_count[max_length]--; /* The brother of the overflow item also moves one step up, * but this does not affect bl_count[max_length] @@ -561,48 +610,9 @@ local void gen_bitlen(s, desc) } } -/* =========================================================================== - * Generate the codes for a given tree and bit counts (which need not be - * optimal). - * IN assertion: the array bl_count contains the bit length statistics for - * the given tree and the field len is set for all tree elements. - * OUT assertion: the field code is set for all tree elements of non - * zero code length. - */ -local void gen_codes (tree, max_code, bl_count) - ct_data *tree; /* the tree to decorate */ - int max_code; /* largest code with non zero frequency */ - ushf *bl_count; /* number of codes at each bit length */ -{ - ush next_code[MAX_BITS+1]; /* next code value for each bit length */ - unsigned code = 0; /* running code value */ - int bits; /* bit index */ - int n; /* code index */ - - /* The distribution counts are first used to generate the code values - * without bit reversal. - */ - for (bits = 1; bits <= MAX_BITS; bits++) { - code = (code + bl_count[bits-1]) << 1; - next_code[bits] = (ush)code; - } - /* Check that the bit counts in bl_count are consistent. The last code - * must be all ones. - */ - Assert (code + bl_count[MAX_BITS]-1 == (1< +#endif /* =========================================================================== * Construct one Huffman tree and assigns the code bit strings and lengths. @@ -612,10 +622,7 @@ local void gen_codes (tree, max_code, bl_count) * and corresponding code. The length opt_len is updated; static_len is * also updated if stree is not null. The field max_code is set. */ -local void build_tree(s, desc) - deflate_state *s; - tree_desc *desc; /* the tree descriptor */ -{ +local void build_tree(deflate_state *s, tree_desc *desc) { ct_data *tree = desc->dyn_tree; const ct_data *stree = desc->stat_desc->static_tree; int elems = desc->stat_desc->elems; @@ -624,7 +631,7 @@ local void build_tree(s, desc) int node; /* new node being created */ /* Construct the initial heap, with least frequent element in - * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n + 1]. * heap[0] is not used. */ s->heap_len = 0, s->heap_max = HEAP_SIZE; @@ -652,7 +659,7 @@ local void build_tree(s, desc) } desc->max_code = max_code; - /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + /* The elements heap[heap_len/2 + 1 .. heap_len] are leaves of the tree, * establish sub-heaps of increasing lengths: */ for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); @@ -700,11 +707,7 @@ local void build_tree(s, desc) * Scan a literal or distance tree to determine the frequencies of the codes * in the bit length tree. */ -local void scan_tree (s, tree, max_code) - deflate_state *s; - ct_data *tree; /* the tree to be scanned */ - int max_code; /* and its largest code of non zero frequency */ -{ +local void scan_tree(deflate_state *s, ct_data *tree, int max_code) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ @@ -714,10 +717,10 @@ local void scan_tree (s, tree, max_code) int min_count = 4; /* min repeat count */ if (nextlen == 0) max_count = 138, min_count = 3; - tree[max_code+1].Len = (ush)0xffff; /* guard */ + tree[max_code + 1].Len = (ush)0xffff; /* guard */ for (n = 0; n <= max_code; n++) { - curlen = nextlen; nextlen = tree[n+1].Len; + curlen = nextlen; nextlen = tree[n + 1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { @@ -745,11 +748,7 @@ local void scan_tree (s, tree, max_code) * Send a literal or distance tree in compressed form, using the codes in * bl_tree. */ -local void send_tree (s, tree, max_code) - deflate_state *s; - ct_data *tree; /* the tree to be scanned */ - int max_code; /* and its largest code of non zero frequency */ -{ +local void send_tree(deflate_state *s, ct_data *tree, int max_code) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ @@ -758,11 +757,11 @@ local void send_tree (s, tree, max_code) int max_count = 7; /* max repeat count */ int min_count = 4; /* min repeat count */ - /* tree[max_code+1].Len = -1; */ /* guard already set */ + /* tree[max_code + 1].Len = -1; */ /* guard already set */ if (nextlen == 0) max_count = 138, min_count = 3; for (n = 0; n <= max_code; n++) { - curlen = nextlen; nextlen = tree[n+1].Len; + curlen = nextlen; nextlen = tree[n + 1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { @@ -773,13 +772,13 @@ local void send_tree (s, tree, max_code) send_code(s, curlen, s->bl_tree); count--; } Assert(count >= 3 && count <= 6, " 3_6?"); - send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count - 3, 2); } else if (count <= 10) { - send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count - 3, 3); } else { - send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count - 11, 7); } count = 0; prevlen = curlen; if (nextlen == 0) { @@ -796,9 +795,7 @@ local void send_tree (s, tree, max_code) * Construct the Huffman tree for the bit lengths and return the index in * bl_order of the last bit length code to send. */ -local int build_bl_tree(s) - deflate_state *s; -{ +local int build_bl_tree(deflate_state *s) { int max_blindex; /* index of last bit length code of non zero freq */ /* Determine the bit length frequencies for literal and distance trees */ @@ -807,8 +804,8 @@ local int build_bl_tree(s) /* Build the bit length tree: */ build_tree(s, (tree_desc *)(&(s->bl_desc))); - /* opt_len now includes the length of the tree representations, except - * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + /* opt_len now includes the length of the tree representations, except the + * lengths of the bit lengths codes and the 5 + 5 + 4 bits for the counts. */ /* Determine the number of bit length codes to send. The pkzip format @@ -819,7 +816,7 @@ local int build_bl_tree(s) if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; } /* Update opt_len to include the bit length tree and counts */ - s->opt_len += 3*((ulg)max_blindex+1) + 5+5+4; + s->opt_len += 3*((ulg)max_blindex + 1) + 5 + 5 + 4; Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", s->opt_len, s->static_len)); @@ -831,42 +828,36 @@ local int build_bl_tree(s) * lengths of the bit length codes, the literal tree and the distance tree. * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. */ -local void send_all_trees(s, lcodes, dcodes, blcodes) - deflate_state *s; - int lcodes, dcodes, blcodes; /* number of codes for each tree */ -{ +local void send_all_trees(deflate_state *s, int lcodes, int dcodes, + int blcodes) { int rank; /* index in bl_order */ Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, "too many codes"); Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes-1, 5); - send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ for (rank = 0; rank < blcodes; rank++) { Tracev((stderr, "\nbl code %2d ", bl_order[rank])); send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); } Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); - send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + send_tree(s, (ct_data *)s->dyn_ltree, lcodes - 1); /* literal tree */ Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); - send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + send_tree(s, (ct_data *)s->dyn_dtree, dcodes - 1); /* distance tree */ Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); } /* =========================================================================== * Send a stored block */ -void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) - deflate_state *s; - charf *buf; /* input block */ - ulg stored_len; /* length of input block */ - int last; /* one if this is the last block for a file */ -{ - send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */ +void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, + ulg stored_len, int last) { + send_bits(s, (STORED_BLOCK<<1) + last, 3); /* send block type */ bi_windup(s); /* align on byte boundary */ put_short(s, (ush)stored_len); put_short(s, (ush)~stored_len); @@ -877,16 +868,14 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; s->compressed_len += (stored_len + 4) << 3; s->bits_sent += 2*16; - s->bits_sent += stored_len<<3; + s->bits_sent += stored_len << 3; #endif } /* =========================================================================== * Flush the bits in the bit buffer to pending output (leaves at most 7 bits) */ -void ZLIB_INTERNAL _tr_flush_bits(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s) { bi_flush(s); } @@ -894,9 +883,7 @@ void ZLIB_INTERNAL _tr_flush_bits(s) * Send one empty static block to give enough lookahead for inflate. * This takes 10 bits, of which 7 may remain in the bit buffer. */ -void ZLIB_INTERNAL _tr_align(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_align(deflate_state *s) { send_bits(s, STATIC_TREES<<1, 3); send_code(s, END_BLOCK, static_ltree); #ifdef ZLIB_DEBUG @@ -905,16 +892,108 @@ void ZLIB_INTERNAL _tr_align(s) bi_flush(s); } +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block(deflate_state *s, const ct_data *ltree, + const ct_data *dtree) { + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned sx = 0; /* running index in symbol buffers */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->sym_next != 0) do { +#ifdef LIT_MEM + dist = s->d_buf[sx]; + lc = s->l_buf[sx++]; +#else + dist = s->sym_buf[sx++] & 0xff; + dist += (unsigned)(s->sym_buf[sx++] & 0xff) << 8; + lc = s->sym_buf[sx++]; +#endif + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code + LITERALS + 1, ltree); /* send length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= (unsigned)base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check for no overlay of pending_buf on needed symbols */ +#ifdef LIT_MEM + Assert(s->pending < 2 * (s->lit_bufsize + sx), "pendingBuf overflow"); +#else + Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); +#endif + + } while (sx < s->sym_next); + + send_code(s, END_BLOCK, ltree); +} + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "block list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +local int detect_data_type(deflate_state *s) { + /* block_mask is the bit mask of block-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + unsigned long block_mask = 0xf3ffc07fUL; + int n; + + /* Check for non-textual ("block-listed") bytes. */ + for (n = 0; n <= 31; n++, block_mask >>= 1) + if ((block_mask & 1) && (s->dyn_ltree[n].Freq != 0)) + return Z_BINARY; + + /* Check for textual ("allow-listed") bytes. */ + if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0 + || s->dyn_ltree[13].Freq != 0) + return Z_TEXT; + for (n = 32; n < LITERALS; n++) + if (s->dyn_ltree[n].Freq != 0) + return Z_TEXT; + + /* There are no "block-listed" or "allow-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +} + /* =========================================================================== * Determine the best encoding for the current block: dynamic trees, static * trees or store, and write out the encoded block. */ -void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) - deflate_state *s; - charf *buf; /* input block, or NULL if too old */ - ulg stored_len; /* length of input block */ - int last; /* one if this is the last block for a file */ -{ +void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, + ulg stored_len, int last) { ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ int max_blindex = 0; /* index of last bit length code of non zero freq */ @@ -943,14 +1022,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) max_blindex = build_bl_tree(s); /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s->opt_len+3+7)>>3; - static_lenb = (s->static_len+3+7)>>3; + opt_lenb = (s->opt_len + 3 + 7) >> 3; + static_lenb = (s->static_len + 3 + 7) >> 3; Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, s->sym_next / 3)); - if (static_lenb <= opt_lenb) opt_lenb = static_lenb; +#ifndef FORCE_STATIC + if (static_lenb <= opt_lenb || s->strategy == Z_FIXED) +#endif + opt_lenb = static_lenb; } else { Assert(buf != (char*)0, "lost buf"); @@ -960,7 +1042,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) #ifdef FORCE_STORED if (buf != (char*)0) { /* force stored block */ #else - if (stored_len+4 <= opt_lenb && buf != (char*)0) { + if (stored_len + 4 <= opt_lenb && buf != (char*)0) { /* 4: two words for the lengths */ #endif /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. @@ -971,21 +1053,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) */ _tr_stored_block(s, buf, stored_len, last); -#ifdef FORCE_STATIC - } else if (static_lenb >= 0) { /* force static trees */ -#else - } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { -#endif - send_bits(s, (STATIC_TREES<<1)+last, 3); + } else if (static_lenb == opt_lenb) { + send_bits(s, (STATIC_TREES<<1) + last, 3); compress_block(s, (const ct_data *)static_ltree, (const ct_data *)static_dtree); #ifdef ZLIB_DEBUG s->compressed_len += 3 + s->static_len; #endif } else { - send_bits(s, (DYN_TREES<<1)+last, 3); - send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, - max_blindex+1); + send_bits(s, (DYN_TREES<<1) + last, 3); + send_all_trees(s, s->l_desc.max_code + 1, s->d_desc.max_code + 1, + max_blindex + 1); compress_block(s, (const ct_data *)s->dyn_ltree, (const ct_data *)s->dyn_dtree); #ifdef ZLIB_DEBUG @@ -1004,22 +1082,23 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) s->compressed_len += 7; /* align on byte boundary */ #endif } - Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, - s->compressed_len-7*last)); + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len >> 3, + s->compressed_len - 7*last)); } /* =========================================================================== * Save the match info and tally the frequency counts. Return true if * the current block must be flushed. */ -int ZLIB_INTERNAL _tr_tally (s, dist, lc) - deflate_state *s; - unsigned dist; /* distance of matched string */ - unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ -{ - s->sym_buf[s->sym_next++] = dist; - s->sym_buf[s->sym_next++] = dist >> 8; - s->sym_buf[s->sym_next++] = lc; +int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc) { +#ifdef LIT_MEM + s->d_buf[s->sym_next] = (ush)dist; + s->l_buf[s->sym_next++] = (uch)lc; +#else + s->sym_buf[s->sym_next++] = (uch)dist; + s->sym_buf[s->sym_next++] = (uch)(dist >> 8); + s->sym_buf[s->sym_next++] = (uch)lc; +#endif if (dist == 0) { /* lc is the unmatched char */ s->dyn_ltree[lc].Freq++; @@ -1031,152 +1110,8 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc) (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_ltree[_length_code[lc] + LITERALS + 1].Freq++; s->dyn_dtree[d_code(dist)].Freq++; } return (s->sym_next == s->sym_end); } - -/* =========================================================================== - * Send the block data compressed using the given Huffman trees - */ -local void compress_block(s, ltree, dtree) - deflate_state *s; - const ct_data *ltree; /* literal tree */ - const ct_data *dtree; /* distance tree */ -{ - unsigned dist; /* distance of matched string */ - int lc; /* match length or unmatched char (if dist == 0) */ - unsigned sx = 0; /* running index in sym_buf */ - unsigned code; /* the code to send */ - int extra; /* number of extra bits to send */ - - if (s->sym_next != 0) do { - dist = s->sym_buf[sx++] & 0xff; - dist += (unsigned)(s->sym_buf[sx++] & 0xff) << 8; - lc = s->sym_buf[sx++]; - if (dist == 0) { - send_code(s, lc, ltree); /* send a literal byte */ - Tracecv(isgraph(lc), (stderr," '%c' ", lc)); - } else { - /* Here, lc is the match length - MIN_MATCH */ - code = _length_code[lc]; - send_code(s, code+LITERALS+1, ltree); /* send the length code */ - extra = extra_lbits[code]; - if (extra != 0) { - lc -= base_length[code]; - send_bits(s, lc, extra); /* send the extra length bits */ - } - dist--; /* dist is now the match distance - 1 */ - code = d_code(dist); - Assert (code < D_CODES, "bad d_code"); - - send_code(s, code, dtree); /* send the distance code */ - extra = extra_dbits[code]; - if (extra != 0) { - dist -= (unsigned)base_dist[code]; - send_bits(s, dist, extra); /* send the extra distance bits */ - } - } /* literal or match pair ? */ - - /* Check that the overlay between pending_buf and sym_buf is ok: */ - Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); - - } while (sx < s->sym_next); - - send_code(s, END_BLOCK, ltree); -} - -/* =========================================================================== - * Check if the data type is TEXT or BINARY, using the following algorithm: - * - TEXT if the two conditions below are satisfied: - * a) There are no non-portable control characters belonging to the - * "block list" (0..6, 14..25, 28..31). - * b) There is at least one printable character belonging to the - * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). - * - BINARY otherwise. - * - The following partially-portable control characters form a - * "gray list" that is ignored in this detection algorithm: - * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). - * IN assertion: the fields Freq of dyn_ltree are set. - */ -local int detect_data_type(s) - deflate_state *s; -{ - /* block_mask is the bit mask of block-listed bytes - * set bits 0..6, 14..25, and 28..31 - * 0xf3ffc07f = binary 11110011111111111100000001111111 - */ - unsigned long block_mask = 0xf3ffc07fUL; - int n; - - /* Check for non-textual ("block-listed") bytes. */ - for (n = 0; n <= 31; n++, block_mask >>= 1) - if ((block_mask & 1) && (s->dyn_ltree[n].Freq != 0)) - return Z_BINARY; - - /* Check for textual ("allow-listed") bytes. */ - if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0 - || s->dyn_ltree[13].Freq != 0) - return Z_TEXT; - for (n = 32; n < LITERALS; n++) - if (s->dyn_ltree[n].Freq != 0) - return Z_TEXT; - - /* There are no "block-listed" or "allow-listed" bytes: - * this stream either is empty or has tolerated ("gray-listed") bytes only. - */ - return Z_BINARY; -} - -/* =========================================================================== - * Reverse the first len bits of a code, using straightforward code (a faster - * method would use a table) - * IN assertion: 1 <= len <= 15 - */ -local unsigned bi_reverse(code, len) - unsigned code; /* the value to invert */ - int len; /* its bit length */ -{ - register unsigned res = 0; - do { - res |= code & 1; - code >>= 1, res <<= 1; - } while (--len > 0); - return res >> 1; -} - -/* =========================================================================== - * Flush the bit buffer, keeping at most 7 bits in it. - */ -local void bi_flush(s) - deflate_state *s; -{ - if (s->bi_valid == 16) { - put_short(s, s->bi_buf); - s->bi_buf = 0; - s->bi_valid = 0; - } else if (s->bi_valid >= 8) { - put_byte(s, (Byte)s->bi_buf); - s->bi_buf >>= 8; - s->bi_valid -= 8; - } -} - -/* =========================================================================== - * Flush the bit buffer and align the output on a byte boundary - */ -local void bi_windup(s) - deflate_state *s; -{ - if (s->bi_valid > 8) { - put_short(s, s->bi_buf); - } else if (s->bi_valid > 0) { - put_byte(s, (Byte)s->bi_buf); - } - s->bi_buf = 0; - s->bi_valid = 0; -#ifdef ZLIB_DEBUG - s->bits_sent = (s->bits_sent+7) & ~7; -#endif -} diff --git a/Utilities/cmzlib/uncompr.c b/Utilities/cmzlib/uncompr.c index f03a1a865e3..5e256663b45 100644 --- a/Utilities/cmzlib/uncompr.c +++ b/Utilities/cmzlib/uncompr.c @@ -24,12 +24,8 @@ Z_DATA_ERROR if the input data was corrupted, including if the input data is an incomplete zlib stream. */ -int ZEXPORT uncompress2 (dest, destLen, source, sourceLen) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong *sourceLen; -{ +int ZEXPORT uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong *sourceLen) { z_stream stream; int err; const uInt max = (uInt)-1; @@ -83,11 +79,7 @@ int ZEXPORT uncompress2 (dest, destLen, source, sourceLen) err; } -int ZEXPORT uncompress (dest, destLen, source, sourceLen) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong sourceLen; -{ +int ZEXPORT uncompress(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen) { return uncompress2(dest, destLen, source, &sourceLen); } diff --git a/Utilities/cmzlib/zconf.h b/Utilities/cmzlib/zconf.h index 1790072cf77..3247b08c509 100644 --- a/Utilities/cmzlib/zconf.h +++ b/Utilities/cmzlib/zconf.h @@ -1,5 +1,5 @@ /* zconf.h -- configuration of the zlib compression library - * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * Copyright (C) 1995-2024 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -40,6 +40,9 @@ # define crc32 z_crc32 # define crc32_combine z_crc32_combine # define crc32_combine64 z_crc32_combine64 +# define crc32_combine_gen z_crc32_combine_gen +# define crc32_combine_gen64 z_crc32_combine_gen64 +# define crc32_combine_op z_crc32_combine_op # define crc32_z z_crc32_z # define deflate z_deflate # define deflateBound z_deflateBound @@ -240,7 +243,11 @@ #endif #ifdef Z_SOLO - typedef unsigned long z_size_t; +# ifdef _WIN64 + typedef unsigned long long z_size_t; +# else + typedef unsigned long z_size_t; +# endif #else # define z_longlong long long # if defined(NO_SIZE_T) @@ -295,14 +302,6 @@ # endif #endif -#ifndef Z_ARG /* function prototypes for stdarg */ -# if defined(STDC) || defined(Z_HAVE_STDARG_H) -# define Z_ARG(args) args -# else -# define Z_ARG(args) () -# endif -#endif - /* The following definitions for FAR are needed only for MSDOS mixed * model programming (small or medium model with some far allocations). * This was tested only with MSC; for other MSDOS compilers you may have @@ -351,6 +350,9 @@ # ifdef FAR # undef FAR # endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif # include /* No need for _export, use ZLIB.DEF instead. */ /* For complete Windows compatibility, use WINAPI, not __stdcall. */ @@ -469,11 +471,18 @@ typedef uLong FAR uLongf; # undef _LARGEFILE64_SOURCE #endif -#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) -# define Z_HAVE_UNISTD_H +#ifndef Z_HAVE_UNISTD_H +# ifdef __WATCOMC__ +# define Z_HAVE_UNISTD_H +# endif +#endif +#ifndef Z_HAVE_UNISTD_H +# if defined(_LARGEFILE64_SOURCE) && !defined(_WIN32) +# define Z_HAVE_UNISTD_H +# endif #endif #ifndef Z_SOLO -# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# if defined(Z_HAVE_UNISTD_H) # include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ # ifdef VMS # include /* for off_t */ @@ -509,7 +518,7 @@ typedef uLong FAR uLongf; #if !defined(_WIN32) && defined(Z_LARGE64) # define z_off64_t off64_t #else -# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# if defined(_WIN32) && !defined(__GNUC__) # define z_off64_t __int64 # else # define z_off64_t z_off_t diff --git a/Utilities/cmzlib/zlib.h b/Utilities/cmzlib/zlib.h index 3764e8577bb..43c5f2fb9ec 100644 --- a/Utilities/cmzlib/zlib.h +++ b/Utilities/cmzlib/zlib.h @@ -1,7 +1,7 @@ /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.12, March 11th, 2022 + version 1.3.1, January 22nd, 2024 - Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler + Copyright (C) 1995-2024 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -37,11 +37,11 @@ extern "C" { #endif -#define ZLIB_VERSION "1.2.12" -#define ZLIB_VERNUM 0x12c0 +#define ZLIB_VERSION "1.3.1" +#define ZLIB_VERNUM 0x1310 #define ZLIB_VER_MAJOR 1 -#define ZLIB_VER_MINOR 2 -#define ZLIB_VER_REVISION 12 +#define ZLIB_VER_MINOR 3 +#define ZLIB_VER_REVISION 1 #define ZLIB_VER_SUBREVISION 0 /* @@ -78,8 +78,8 @@ extern "C" { even in the case of corrupted input. */ -typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); -typedef void (*free_func) OF((voidpf opaque, voidpf address)); +typedef voidpf (*alloc_func)(voidpf opaque, uInt items, uInt size); +typedef void (*free_func)(voidpf opaque, voidpf address); struct internal_state; @@ -217,7 +217,7 @@ typedef gz_header FAR *gz_headerp; /* basic functions */ -ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +ZEXTERN const char * ZEXPORT zlibVersion(void); /* The application can compare zlibVersion and ZLIB_VERSION for consistency. If the first character differs, the library code actually used is not compatible with the zlib.h header file used by the application. This check @@ -225,12 +225,12 @@ ZEXTERN const char * ZEXPORT zlibVersion OF((void)); */ /* -ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); +ZEXTERN int ZEXPORT deflateInit(z_streamp strm, int level); Initializes the internal stream state for compression. The fields zalloc, zfree and opaque must be initialized before by the caller. If zalloc and zfree are set to Z_NULL, deflateInit updates them to use default - allocation functions. + allocation functions. total_in, total_out, adler, and msg are initialized. The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: 1 gives best speed, 9 gives best compression, 0 gives no compression at all @@ -247,7 +247,7 @@ ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); */ -ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +ZEXTERN int ZEXPORT deflate(z_streamp strm, int flush); /* deflate compresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full. It may introduce @@ -276,7 +276,7 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); == 0), or after each call of deflate(). If deflate returns Z_OK and with zero avail_out, it must be called again after making room in the output buffer because there might be more output pending. See deflatePending(), - which can be used if desired to determine whether or not there is more ouput + which can be used if desired to determine whether or not there is more output in that case. Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to @@ -320,8 +320,8 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); with the same value of the flush parameter and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that - avail_out is greater than six to avoid repeated flush markers due to - avail_out == 0 on return. + avail_out is greater than six when the flush marker begins, in order to avoid + repeated flush markers upon calling deflate() again when avail_out == 0. If the parameter flush is set to Z_FINISH, pending input is processed, pending output is flushed and deflate returns with Z_STREAM_END if there was @@ -360,7 +360,7 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); */ -ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +ZEXTERN int ZEXPORT deflateEnd(z_streamp strm); /* All dynamically allocated data structures for this stream are freed. This function discards any unprocessed input and does not flush any pending @@ -375,7 +375,7 @@ ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); /* -ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateInit(z_streamp strm); Initializes the internal stream state for decompression. The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by @@ -383,7 +383,8 @@ ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); read or consumed. The allocation of a sliding window will be deferred to the first call of inflate (if the decompression does not complete on the first call). If zalloc and zfree are set to Z_NULL, inflateInit updates - them to use default allocation functions. + them to use default allocation functions. total_in, total_out, adler, and + msg are initialized. inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_VERSION_ERROR if the zlib library version is incompatible with the @@ -397,7 +398,7 @@ ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); */ -ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +ZEXTERN int ZEXPORT inflate(z_streamp strm, int flush); /* inflate decompresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full. It may introduce @@ -517,7 +518,7 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); */ -ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateEnd(z_streamp strm); /* All dynamically allocated data structures for this stream are freed. This function discards any unprocessed input and does not flush any pending @@ -535,12 +536,12 @@ ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); */ /* -ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, - int level, - int method, - int windowBits, - int memLevel, - int strategy)); +ZEXTERN int ZEXPORT deflateInit2(z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy); This is another version of deflateInit with more compression options. The fields zalloc, zfree and opaque must be initialized before by the caller. @@ -607,9 +608,9 @@ ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, compression: this will be done by deflate(). */ -ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, - const Bytef *dictionary, - uInt dictLength)); +ZEXTERN int ZEXPORT deflateSetDictionary(z_streamp strm, + const Bytef *dictionary, + uInt dictLength); /* Initializes the compression dictionary from the given byte sequence without producing any compressed output. When using the zlib format, this @@ -651,16 +652,16 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, not perform any compression: this will be done by deflate(). */ -ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm, - Bytef *dictionary, - uInt *dictLength)); +ZEXTERN int ZEXPORT deflateGetDictionary(z_streamp strm, + Bytef *dictionary, + uInt *dictLength); /* Returns the sliding dictionary being maintained by deflate. dictLength is set to the number of bytes in the dictionary, and that many bytes are copied to dictionary. dictionary must have enough space, where 32768 bytes is always enough. If deflateGetDictionary() is called with dictionary equal to Z_NULL, then only the dictionary length is returned, and nothing is copied. - Similary, if dictLength is Z_NULL, then it is not set. + Similarly, if dictLength is Z_NULL, then it is not set. deflateGetDictionary() may return a length less than the window size, even when more than the window size in input has been provided. It may return up @@ -673,8 +674,8 @@ ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm, stream state is inconsistent. */ -ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, - z_streamp source)); +ZEXTERN int ZEXPORT deflateCopy(z_streamp dest, + z_streamp source); /* Sets the destination stream as a complete copy of the source stream. @@ -691,20 +692,20 @@ ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, destination. */ -ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +ZEXTERN int ZEXPORT deflateReset(z_streamp strm); /* This function is equivalent to deflateEnd followed by deflateInit, but does not free and reallocate the internal compression state. The stream will leave the compression level and any other attributes that may have been - set unchanged. + set unchanged. total_in, total_out, adler, and msg are initialized. deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source stream state was inconsistent (such as zalloc or state being Z_NULL). */ -ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, - int level, - int strategy)); +ZEXTERN int ZEXPORT deflateParams(z_streamp strm, + int level, + int strategy); /* Dynamically update the compression level and compression strategy. The interpretation of level and strategy is as in deflateInit2(). This can be @@ -729,7 +730,7 @@ ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, Then no more input data should be provided before the deflateParams() call. If this is done, the old level and strategy will be applied to the data compressed before deflateParams(), and the new level and strategy will be - applied to the the data compressed after deflateParams(). + applied to the data compressed after deflateParams(). deflateParams returns Z_OK on success, Z_STREAM_ERROR if the source stream state was inconsistent or if a parameter was invalid, or Z_BUF_ERROR if @@ -740,11 +741,11 @@ ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, retried with more output space. */ -ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, - int good_length, - int max_lazy, - int nice_length, - int max_chain)); +ZEXTERN int ZEXPORT deflateTune(z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain); /* Fine tune deflate's internal compression parameters. This should only be used by someone who understands the algorithm used by zlib's deflate for @@ -757,8 +758,8 @@ ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. */ -ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, - uLong sourceLen)); +ZEXTERN uLong ZEXPORT deflateBound(z_streamp strm, + uLong sourceLen); /* deflateBound() returns an upper bound on the compressed size after deflation of sourceLen bytes. It must be called after deflateInit() or @@ -772,9 +773,9 @@ ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, than Z_FINISH or Z_NO_FLUSH are used. */ -ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, - unsigned *pending, - int *bits)); +ZEXTERN int ZEXPORT deflatePending(z_streamp strm, + unsigned *pending, + int *bits); /* deflatePending() returns the number of bytes and bits of output that have been generated, but not yet provided in the available output. The bytes not @@ -787,9 +788,9 @@ ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, stream state was inconsistent. */ -ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, - int bits, - int value)); +ZEXTERN int ZEXPORT deflatePrime(z_streamp strm, + int bits, + int value); /* deflatePrime() inserts bits in the deflate output stream. The intent is that this function is used to start off the deflate output with the bits @@ -804,8 +805,8 @@ ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, source stream state was inconsistent. */ -ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, - gz_headerp head)); +ZEXTERN int ZEXPORT deflateSetHeader(z_streamp strm, + gz_headerp head); /* deflateSetHeader() provides gzip header information for when a gzip stream is requested by deflateInit2(). deflateSetHeader() may be called @@ -821,16 +822,17 @@ ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, gzip file" and give up. If deflateSetHeader is not used, the default gzip header has text false, - the time set to zero, and os set to 255, with no extra, name, or comment - fields. The gzip header is returned to the default state by deflateReset(). + the time set to zero, and os set to the current operating system, with no + extra, name, or comment fields. The gzip header is returned to the default + state by deflateReset(). deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source stream state was inconsistent. */ /* -ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, - int windowBits)); +ZEXTERN int ZEXPORT inflateInit2(z_streamp strm, + int windowBits); This is another version of inflateInit with an extra parameter. The fields next_in, avail_in, zalloc, zfree and opaque must be initialized @@ -883,9 +885,9 @@ ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, deferred until inflate() is called. */ -ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, - const Bytef *dictionary, - uInt dictLength)); +ZEXTERN int ZEXPORT inflateSetDictionary(z_streamp strm, + const Bytef *dictionary, + uInt dictLength); /* Initializes the decompression dictionary from the given uncompressed byte sequence. This function must be called immediately after a call of inflate, @@ -906,22 +908,22 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, inflate(). */ -ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, - Bytef *dictionary, - uInt *dictLength)); +ZEXTERN int ZEXPORT inflateGetDictionary(z_streamp strm, + Bytef *dictionary, + uInt *dictLength); /* Returns the sliding dictionary being maintained by inflate. dictLength is set to the number of bytes in the dictionary, and that many bytes are copied to dictionary. dictionary must have enough space, where 32768 bytes is always enough. If inflateGetDictionary() is called with dictionary equal to Z_NULL, then only the dictionary length is returned, and nothing is copied. - Similary, if dictLength is Z_NULL, then it is not set. + Similarly, if dictLength is Z_NULL, then it is not set. inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the stream state is inconsistent. */ -ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateSync(z_streamp strm); /* Skips invalid compressed data until a possible full flush point (see above for the description of deflate with Z_FULL_FLUSH) can be found, or until all @@ -934,14 +936,14 @@ ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); inflateSync returns Z_OK if a possible full flush point has been found, Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. - In the success case, the application may save the current current value of - total_in which indicates where valid compressed data was found. In the - error case, the application may repeatedly call inflateSync, providing more - input each time, until success or end of the input data. + In the success case, the application may save the current value of total_in + which indicates where valid compressed data was found. In the error case, + the application may repeatedly call inflateSync, providing more input each + time, until success or end of the input data. */ -ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, - z_streamp source)); +ZEXTERN int ZEXPORT inflateCopy(z_streamp dest, + z_streamp source); /* Sets the destination stream as a complete copy of the source stream. @@ -956,18 +958,19 @@ ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, destination. */ -ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateReset(z_streamp strm); /* This function is equivalent to inflateEnd followed by inflateInit, but does not free and reallocate the internal decompression state. The stream will keep attributes that may have been set by inflateInit2. + total_in, total_out, adler, and msg are initialized. inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source stream state was inconsistent (such as zalloc or state being Z_NULL). */ -ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, - int windowBits)); +ZEXTERN int ZEXPORT inflateReset2(z_streamp strm, + int windowBits); /* This function is the same as inflateReset, but it also permits changing the wrap and window size requests. The windowBits parameter is interpreted @@ -980,9 +983,9 @@ ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, the windowBits parameter is invalid. */ -ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, - int bits, - int value)); +ZEXTERN int ZEXPORT inflatePrime(z_streamp strm, + int bits, + int value); /* This function inserts bits in the inflate input stream. The intent is that this function is used to start inflating at a bit position in the @@ -1001,7 +1004,7 @@ ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, stream state was inconsistent. */ -ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); +ZEXTERN long ZEXPORT inflateMark(z_streamp strm); /* This function returns two values, one in the lower 16 bits of the return value, and the other in the remaining upper bits, obtained by shifting the @@ -1029,8 +1032,8 @@ ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); source stream state was inconsistent. */ -ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, - gz_headerp head)); +ZEXTERN int ZEXPORT inflateGetHeader(z_streamp strm, + gz_headerp head); /* inflateGetHeader() requests that gzip header information be stored in the provided gz_header structure. inflateGetHeader() may be called after @@ -1070,8 +1073,8 @@ ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, */ /* -ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, - unsigned char FAR *window)); +ZEXTERN int ZEXPORT inflateBackInit(z_streamp strm, int windowBits, + unsigned char FAR *window); Initialize the internal stream state for decompression using inflateBack() calls. The fields zalloc, zfree and opaque in strm must be initialized @@ -1091,13 +1094,13 @@ ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, the version of the header file. */ -typedef unsigned (*in_func) OF((void FAR *, - z_const unsigned char FAR * FAR *)); -typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); +typedef unsigned (*in_func)(void FAR *, + z_const unsigned char FAR * FAR *); +typedef int (*out_func)(void FAR *, unsigned char FAR *, unsigned); -ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, - in_func in, void FAR *in_desc, - out_func out, void FAR *out_desc)); +ZEXTERN int ZEXPORT inflateBack(z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc); /* inflateBack() does a raw inflate with a single call using a call-back interface for input and output. This is potentially more efficient than @@ -1165,7 +1168,7 @@ ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, cannot return Z_OK. */ -ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateBackEnd(z_streamp strm); /* All memory allocated by inflateBackInit() is freed. @@ -1173,7 +1176,7 @@ ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); state was inconsistent. */ -ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +ZEXTERN uLong ZEXPORT zlibCompileFlags(void); /* Return flags indicating compile-time options. Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: @@ -1226,8 +1229,8 @@ ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); you need special options. */ -ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen)); +ZEXTERN int ZEXPORT compress(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen); /* Compresses the source buffer into the destination buffer. sourceLen is the byte length of the source buffer. Upon entry, destLen is the total size @@ -1241,9 +1244,9 @@ ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, buffer. */ -ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen, - int level)); +ZEXTERN int ZEXPORT compress2(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level); /* Compresses the source buffer into the destination buffer. The level parameter has the same meaning as in deflateInit. sourceLen is the byte @@ -1257,15 +1260,15 @@ ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, Z_STREAM_ERROR if the level parameter is invalid. */ -ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +ZEXTERN uLong ZEXPORT compressBound(uLong sourceLen); /* compressBound() returns an upper bound on the compressed size after compress() or compress2() on sourceLen bytes. It would be used before a compress() or compress2() call to allocate the destination buffer. */ -ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen)); +ZEXTERN int ZEXPORT uncompress(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen); /* Decompresses the source buffer into the destination buffer. sourceLen is the byte length of the source buffer. Upon entry, destLen is the total size @@ -1282,8 +1285,8 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, buffer with the uncompressed data up to that point. */ -ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong *sourceLen)); +ZEXTERN int ZEXPORT uncompress2(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong *sourceLen); /* Same as uncompress, except that sourceLen is a pointer, where the length of the source is *sourceLen. On return, *sourceLen is the number of @@ -1302,7 +1305,7 @@ ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ /* -ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +ZEXTERN gzFile ZEXPORT gzopen(const char *path, const char *mode); Open the gzip (.gz) file at path for reading and decompressing, or compressing and writing. The mode parameter is as in fopen ("rb" or "wb") @@ -1339,7 +1342,7 @@ ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); file could not be opened. */ -ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +ZEXTERN gzFile ZEXPORT gzdopen(int fd, const char *mode); /* Associate a gzFile with the file descriptor fd. File descriptors are obtained from calls like open, dup, creat, pipe or fileno (if the file has @@ -1362,7 +1365,7 @@ ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); will not detect if fd is invalid (unless fd is -1). */ -ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); +ZEXTERN int ZEXPORT gzbuffer(gzFile file, unsigned size); /* Set the internal buffer size used by this library's functions for file to size. The default buffer size is 8192 bytes. This function must be called @@ -1378,7 +1381,7 @@ ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); too late. */ -ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +ZEXTERN int ZEXPORT gzsetparams(gzFile file, int level, int strategy); /* Dynamically update the compression level and strategy for file. See the description of deflateInit2 for the meaning of these parameters. Previously @@ -1389,7 +1392,7 @@ ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); or Z_MEM_ERROR if there is a memory allocation error. */ -ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +ZEXTERN int ZEXPORT gzread(gzFile file, voidp buf, unsigned len); /* Read and decompress up to len uncompressed bytes from file into buf. If the input file is not in gzip format, gzread copies the given number of @@ -1419,8 +1422,8 @@ ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); Z_STREAM_ERROR. */ -ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems, - gzFile file)); +ZEXTERN z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems, + gzFile file); /* Read and decompress up to nitems items of size size from file into buf, otherwise operating as gzread() does. This duplicates the interface of @@ -1437,22 +1440,22 @@ ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems, In the event that the end of file is reached and only a partial item is available at the end, i.e. the remaining uncompressed data length is not a - multiple of size, then the final partial item is nevetheless read into buf + multiple of size, then the final partial item is nevertheless read into buf and the end-of-file flag is set. The length of the partial item read is not provided, but could be inferred from the result of gztell(). This behavior is the same as the behavior of fread() implementations in common libraries, but it prevents the direct use of gzfread() to read a concurrently written - file, reseting and retrying on end-of-file, when size is not 1. + file, resetting and retrying on end-of-file, when size is not 1. */ -ZEXTERN int ZEXPORT gzwrite OF((gzFile file, voidpc buf, unsigned len)); +ZEXTERN int ZEXPORT gzwrite(gzFile file, voidpc buf, unsigned len); /* Compress and write the len uncompressed bytes at buf to file. gzwrite returns the number of uncompressed bytes written or 0 in case of error. */ -ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size, - z_size_t nitems, gzFile file)); +ZEXTERN z_size_t ZEXPORT gzfwrite(voidpc buf, z_size_t size, + z_size_t nitems, gzFile file); /* Compress and write nitems items of size size from buf to file, duplicating the interface of stdio's fwrite(), with size_t request and return types. If @@ -1465,7 +1468,7 @@ ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size, is returned, and the error state is set to Z_STREAM_ERROR. */ -ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); +ZEXTERN int ZEXPORTVA gzprintf(gzFile file, const char *format, ...); /* Convert, format, compress, and write the arguments (...) to file under control of the string format, as in fprintf. gzprintf returns the number of @@ -1480,7 +1483,7 @@ ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); This can be determined using zlibCompileFlags(). */ -ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +ZEXTERN int ZEXPORT gzputs(gzFile file, const char *s); /* Compress and write the given null-terminated string s to file, excluding the terminating null character. @@ -1488,7 +1491,7 @@ ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); gzputs returns the number of characters written, or -1 in case of error. */ -ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +ZEXTERN char * ZEXPORT gzgets(gzFile file, char *buf, int len); /* Read and decompress bytes from file into buf, until len-1 characters are read, or until a newline character is read and transferred to buf, or an @@ -1502,13 +1505,13 @@ ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); buf are indeterminate. */ -ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +ZEXTERN int ZEXPORT gzputc(gzFile file, int c); /* Compress and write c, converted to an unsigned char, into file. gzputc returns the value that was written, or -1 in case of error. */ -ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +ZEXTERN int ZEXPORT gzgetc(gzFile file); /* Read and decompress one byte from file. gzgetc returns this byte or -1 in case of end of file or error. This is implemented as a macro for speed. @@ -1517,7 +1520,7 @@ ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); points to has been clobbered or not. */ -ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +ZEXTERN int ZEXPORT gzungetc(int c, gzFile file); /* Push c back onto the stream for file to be read as the first character on the next read. At least one character of push-back is always allowed. @@ -1529,7 +1532,7 @@ ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); gzseek() or gzrewind(). */ -ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +ZEXTERN int ZEXPORT gzflush(gzFile file, int flush); /* Flush all pending output to file. The parameter flush is as in the deflate() function. The return value is the zlib error number (see function @@ -1545,8 +1548,8 @@ ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); */ /* -ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, - z_off_t offset, int whence)); +ZEXTERN z_off_t ZEXPORT gzseek(gzFile file, + z_off_t offset, int whence); Set the starting position to offset relative to whence for the next gzread or gzwrite on file. The offset represents a number of bytes in the @@ -1564,7 +1567,7 @@ ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, would be before the current position. */ -ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +ZEXTERN int ZEXPORT gzrewind(gzFile file); /* Rewind file. This function is supported only for reading. @@ -1572,7 +1575,7 @@ ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); */ /* -ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +ZEXTERN z_off_t ZEXPORT gztell(gzFile file); Return the starting position for the next gzread or gzwrite on file. This position represents a number of bytes in the uncompressed data stream, @@ -1583,7 +1586,7 @@ ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); */ /* -ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file)); +ZEXTERN z_off_t ZEXPORT gzoffset(gzFile file); Return the current compressed (actual) read or write offset of file. This offset includes the count of bytes that precede the gzip stream, for example @@ -1592,7 +1595,7 @@ ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file)); be used for a progress indicator. On error, gzoffset() returns -1. */ -ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +ZEXTERN int ZEXPORT gzeof(gzFile file); /* Return true (1) if the end-of-file indicator for file has been set while reading, false (0) otherwise. Note that the end-of-file indicator is set @@ -1607,7 +1610,7 @@ ZEXTERN int ZEXPORT gzeof OF((gzFile file)); has grown since the previous end of file was detected. */ -ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +ZEXTERN int ZEXPORT gzdirect(gzFile file); /* Return true (1) if file is being copied directly while reading, or false (0) if file is a gzip stream being decompressed. @@ -1628,7 +1631,7 @@ ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); gzip file reading and decompression, which may not be desired.) */ -ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +ZEXTERN int ZEXPORT gzclose(gzFile file); /* Flush all pending output for file, if necessary, close file and deallocate the (de)compression state. Note that once file is closed, you @@ -1641,8 +1644,8 @@ ZEXTERN int ZEXPORT gzclose OF((gzFile file)); last read ended in the middle of a gzip stream, or Z_OK on success. */ -ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); -ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); +ZEXTERN int ZEXPORT gzclose_r(gzFile file); +ZEXTERN int ZEXPORT gzclose_w(gzFile file); /* Same as gzclose(), but gzclose_r() is only for use when reading, and gzclose_w() is only for use when writing or appending. The advantage to @@ -1653,7 +1656,7 @@ ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); zlib library. */ -ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +ZEXTERN const char * ZEXPORT gzerror(gzFile file, int *errnum); /* Return the error message for the last error which occurred on file. errnum is set to zlib error number. If an error occurred in the file system @@ -1669,7 +1672,7 @@ ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); functions above that do not distinguish those cases in their return values. */ -ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +ZEXTERN void ZEXPORT gzclearerr(gzFile file); /* Clear the error and end-of-file flags for file. This is analogous to the clearerr() function in stdio. This is useful for continuing to read a gzip @@ -1686,7 +1689,7 @@ ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); library. */ -ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +ZEXTERN uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len); /* Update a running Adler-32 checksum with the bytes buf[0..len-1] and return the updated checksum. An Adler-32 value is in the range of a 32-bit @@ -1706,15 +1709,15 @@ ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); if (adler != original_adler) error(); */ -ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf, - z_size_t len)); +ZEXTERN uLong ZEXPORT adler32_z(uLong adler, const Bytef *buf, + z_size_t len); /* Same as adler32(), but with a size_t length. */ /* -ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, - z_off_t len2)); +ZEXTERN uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, + z_off_t len2); Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for @@ -1724,7 +1727,7 @@ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, negative, the result has no meaning or utility. */ -ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +ZEXTERN uLong ZEXPORT crc32(uLong crc, const Bytef *buf, uInt len); /* Update a running CRC-32 with the bytes buf[0..len-1] and return the updated CRC-32. A CRC-32 value is in the range of a 32-bit unsigned integer. @@ -1742,30 +1745,30 @@ ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); if (crc != original_crc) error(); */ -ZEXTERN uLong ZEXPORT crc32_z OF((uLong crc, const Bytef *buf, - z_size_t len)); +ZEXTERN uLong ZEXPORT crc32_z(uLong crc, const Bytef *buf, + z_size_t len); /* Same as crc32(), but with a size_t length. */ /* -ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); +ZEXTERN uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2); Combine two CRC-32 check values into one. For two sequences of bytes, seq1 and seq2 with lengths len1 and len2, CRC-32 check values were calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and - len2. + len2. len2 must be non-negative. */ /* -ZEXTERN uLong ZEXPORT crc32_combine_gen OF((z_off_t len2)); +ZEXTERN uLong ZEXPORT crc32_combine_gen(z_off_t len2); Return the operator corresponding to length len2, to be used with - crc32_combine_op(). + crc32_combine_op(). len2 must be non-negative. */ -ZEXTERN uLong ZEXPORT crc32_combine_op OF((uLong crc1, uLong crc2, uLong op)); +ZEXTERN uLong ZEXPORT crc32_combine_op(uLong crc1, uLong crc2, uLong op); /* Give the same result as crc32_combine(), using op in place of len2. op is is generated from len2 by crc32_combine_gen(). This will be faster than @@ -1778,20 +1781,20 @@ ZEXTERN uLong ZEXPORT crc32_combine_op OF((uLong crc1, uLong crc2, uLong op)); /* deflateInit and inflateInit are macros to allow checking the zlib version * and the compiler's view of z_stream: */ -ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, - const char *version, int stream_size)); -ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, - const char *version, int stream_size)); -ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, - int windowBits, int memLevel, - int strategy, const char *version, - int stream_size)); -ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, - const char *version, int stream_size)); -ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, - unsigned char FAR *window, - const char *version, - int stream_size)); +ZEXTERN int ZEXPORT deflateInit_(z_streamp strm, int level, + const char *version, int stream_size); +ZEXTERN int ZEXPORT inflateInit_(z_streamp strm, + const char *version, int stream_size); +ZEXTERN int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size); +ZEXTERN int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, + const char *version, int stream_size); +ZEXTERN int ZEXPORT inflateBackInit_(z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size); #ifdef Z_PREFIX_SET # define z_deflateInit(strm, level) \ deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) @@ -1850,7 +1853,7 @@ struct gzFile_s { unsigned char *next; z_off64_t pos; }; -ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ +ZEXTERN int ZEXPORT gzgetc_(gzFile file); /* backward compatibility */ #ifdef Z_PREFIX_SET # undef z_gzgetc # define z_gzgetc(g) \ @@ -1871,13 +1874,13 @@ ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ * without large file support, _LFS64_LARGEFILE must also be true */ #ifdef Z_LARGE64 - ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); - ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); - ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); - ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); - ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t)); - ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); - ZEXTERN uLong ZEXPORT crc32_combine_gen64 OF((z_off64_t)); + ZEXTERN gzFile ZEXPORT gzopen64(const char *, const char *); + ZEXTERN z_off64_t ZEXPORT gzseek64(gzFile, z_off64_t, int); + ZEXTERN z_off64_t ZEXPORT gztell64(gzFile); + ZEXTERN z_off64_t ZEXPORT gzoffset64(gzFile); + ZEXTERN uLong ZEXPORT adler32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen64(z_off64_t); #endif #if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) @@ -1899,50 +1902,50 @@ ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ # define crc32_combine_gen crc32_combine_gen64 # endif # ifndef Z_LARGE64 - ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); - ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); - ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); - ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile)); - ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine_gen64 OF((z_off_t)); + ZEXTERN gzFile ZEXPORT gzopen64(const char *, const char *); + ZEXTERN z_off_t ZEXPORT gzseek64(gzFile, z_off_t, int); + ZEXTERN z_off_t ZEXPORT gztell64(gzFile); + ZEXTERN z_off_t ZEXPORT gzoffset64(gzFile); + ZEXTERN uLong ZEXPORT adler32_combine64(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine64(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen64(z_off_t); # endif #else - ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *)); - ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int)); - ZEXTERN z_off_t ZEXPORT gztell OF((gzFile)); - ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile)); - ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine_gen OF((z_off_t)); + ZEXTERN gzFile ZEXPORT gzopen(const char *, const char *); + ZEXTERN z_off_t ZEXPORT gzseek(gzFile, z_off_t, int); + ZEXTERN z_off_t ZEXPORT gztell(gzFile); + ZEXTERN z_off_t ZEXPORT gzoffset(gzFile); + ZEXTERN uLong ZEXPORT adler32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen(z_off_t); #endif #else /* Z_SOLO */ - ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine_gen OF((z_off_t)); + ZEXTERN uLong ZEXPORT adler32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen(z_off_t); #endif /* !Z_SOLO */ /* undocumented functions */ -ZEXTERN const char * ZEXPORT zError OF((int)); -ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); -ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); -ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); -ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int)); -ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp)); -ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); -ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); +ZEXTERN const char * ZEXPORT zError(int); +ZEXTERN int ZEXPORT inflateSyncPoint(z_streamp); +ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table(void); +ZEXTERN int ZEXPORT inflateUndermine(z_streamp, int); +ZEXTERN int ZEXPORT inflateValidate(z_streamp, int); +ZEXTERN unsigned long ZEXPORT inflateCodesUsed(z_streamp); +ZEXTERN int ZEXPORT inflateResetKeep(z_streamp); +ZEXTERN int ZEXPORT deflateResetKeep(z_streamp); #if defined(_WIN32) && !defined(Z_SOLO) -ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, - const char *mode)); +ZEXTERN gzFile ZEXPORT gzopen_w(const wchar_t *path, + const char *mode); #endif #if defined(STDC) || defined(Z_HAVE_STDARG_H) # ifndef Z_SOLO -ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, - const char *format, - va_list va)); +ZEXTERN int ZEXPORTVA gzvprintf(gzFile file, + const char *format, + va_list va); # endif #endif diff --git a/Utilities/cmzlib/zutil.c b/Utilities/cmzlib/zutil.c index dcab28a0d51..b1c5d2d3c6d 100644 --- a/Utilities/cmzlib/zutil.c +++ b/Utilities/cmzlib/zutil.c @@ -24,13 +24,11 @@ z_const char * const z_errmsg[10] = { }; -const char * ZEXPORT zlibVersion() -{ +const char * ZEXPORT zlibVersion(void) { return ZLIB_VERSION; } -uLong ZEXPORT zlibCompileFlags() -{ +uLong ZEXPORT zlibCompileFlags(void) { uLong flags; flags = 0; @@ -61,9 +59,11 @@ uLong ZEXPORT zlibCompileFlags() #ifdef ZLIB_DEBUG flags += 1 << 8; #endif + /* #if defined(ASMV) || defined(ASMINF) flags += 1 << 9; #endif + */ #ifdef ZLIB_WINAPI flags += 1 << 10; #endif @@ -119,9 +119,7 @@ uLong ZEXPORT zlibCompileFlags() # endif int ZLIB_INTERNAL z_verbose = verbose; -void ZLIB_INTERNAL z_error (m) - char *m; -{ +void ZLIB_INTERNAL z_error(char *m) { fprintf(stderr, "%s\n", m); exit(1); } @@ -130,9 +128,7 @@ void ZLIB_INTERNAL z_error (m) /* exported to allow conversion of error code to string for compress() and * uncompress() */ -const char * ZEXPORT zError(err) - int err; -{ +const char * ZEXPORT zError(int err) { return ERR_MSG(err); } @@ -146,22 +142,14 @@ const char * ZEXPORT zError(err) #ifndef HAVE_MEMCPY -void ZLIB_INTERNAL zmemcpy(dest, source, len) - Bytef* dest; - const Bytef* source; - uInt len; -{ +void ZLIB_INTERNAL zmemcpy(Bytef* dest, const Bytef* source, uInt len) { if (len == 0) return; do { *dest++ = *source++; /* ??? to be unrolled */ } while (--len != 0); } -int ZLIB_INTERNAL zmemcmp(s1, s2, len) - const Bytef* s1; - const Bytef* s2; - uInt len; -{ +int ZLIB_INTERNAL zmemcmp(const Bytef* s1, const Bytef* s2, uInt len) { uInt j; for (j = 0; j < len; j++) { @@ -170,10 +158,7 @@ int ZLIB_INTERNAL zmemcmp(s1, s2, len) return 0; } -void ZLIB_INTERNAL zmemzero(dest, len) - Bytef* dest; - uInt len; -{ +void ZLIB_INTERNAL zmemzero(Bytef* dest, uInt len) { if (len == 0) return; do { *dest++ = 0; /* ??? to be unrolled */ @@ -214,8 +199,7 @@ local ptr_table table[MAX_PTR]; * a protected system like OS/2. Use Microsoft C instead. */ -voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) -{ +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size) { voidpf buf; ulg bsize = (ulg)items*size; @@ -240,8 +224,7 @@ voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) return buf; } -void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) -{ +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { int n; (void)opaque; @@ -277,14 +260,12 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) # define _hfree hfree #endif -voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size) -{ +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, uInt items, uInt size) { (void)opaque; return _halloc((long)items, size); } -void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) -{ +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { (void)opaque; _hfree(ptr); } @@ -297,25 +278,18 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) #ifndef MY_ZCALLOC /* Any system without a special alloc function */ #ifndef STDC -extern voidp malloc OF((uInt size)); -extern voidp calloc OF((uInt items, uInt size)); -extern void free OF((voidpf ptr)); +extern voidp malloc(uInt size); +extern voidp calloc(uInt items, uInt size); +extern void free(voidpf ptr); #endif -voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) - voidpf opaque; - unsigned items; - unsigned size; -{ +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size) { (void)opaque; return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : (voidpf)calloc(items, size); } -void ZLIB_INTERNAL zcfree (opaque, ptr) - voidpf opaque; - voidpf ptr; -{ +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { (void)opaque; free(ptr); } diff --git a/Utilities/cmzlib/zutil.h b/Utilities/cmzlib/zutil.h index 41092a81dff..6cf07e7353d 100644 --- a/Utilities/cmzlib/zutil.h +++ b/Utilities/cmzlib/zutil.h @@ -1,5 +1,5 @@ /* zutil.h -- internal interface and configuration of the compression library - * Copyright (C) 1995-2022 Jean-loup Gailly, Mark Adler + * Copyright (C) 1995-2024 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -56,7 +56,7 @@ typedef unsigned long ulg; extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* (size given to avoid silly warnings with Visual C++) */ -#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] +#define ERR_MSG(err) z_errmsg[(err) < -6 || (err) > 2 ? 9 : 2 - (err)] #define ERR_RETURN(strm,err) \ return (strm->msg = ERR_MSG(err), (err)) @@ -161,18 +161,6 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # define OS_CODE 19 #endif -#if defined(_BEOS_) || defined(RISCOS) -# define fdopen(fd,mode) NULL /* No fdopen() */ -#endif - -#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX -# if defined(_WIN32_WCE) -# define fdopen(fd,mode) NULL /* No fdopen() */ -# else -# define fdopen(fd,type) _fdopen(fd,type) -# endif -#endif - #if defined(_MSC_VER) #pragma warning ( disable : 4127 ) /* cond expr is constant */ #pragma warning ( disable : 4131 ) /* old style declaration */ @@ -188,8 +176,9 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* provide prototypes for these when building zlib without LFS */ #if !defined(_WIN32) && \ (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) - ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT adler32_combine64(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine64(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen64(z_off_t); #endif /* common defaults */ @@ -228,16 +217,16 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # define zmemzero(dest, len) memset(dest, 0, len) # endif #else - void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); - int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); - void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len)); + void ZLIB_INTERNAL zmemcpy(Bytef* dest, const Bytef* source, uInt len); + int ZLIB_INTERNAL zmemcmp(const Bytef* s1, const Bytef* s2, uInt len); + void ZLIB_INTERNAL zmemzero(Bytef* dest, uInt len); #endif /* Diagnostic functions */ #ifdef ZLIB_DEBUG # include extern int ZLIB_INTERNAL z_verbose; - extern void ZLIB_INTERNAL z_error OF((char *m)); + extern void ZLIB_INTERNAL z_error(char *m); # define Assert(cond,msg) {if(!(cond)) z_error(msg);} # define Trace(x) {if (z_verbose>=0) fprintf x ;} # define Tracev(x) {if (z_verbose>0) fprintf x ;} @@ -254,9 +243,9 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #endif #ifndef Z_SOLO - voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, - unsigned size)); - void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); + voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, + unsigned size); + void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr); #endif #define ZALLOC(strm, items, size) \ diff --git a/Utilities/cmzstd/CMakeLists.txt b/Utilities/cmzstd/CMakeLists.txt index 981e3d5019d..e8bee76ad21 100644 --- a/Utilities/cmzstd/CMakeLists.txt +++ b/Utilities/cmzstd/CMakeLists.txt @@ -44,7 +44,12 @@ add_library(cmzstd STATIC lib/dictBuilder/zdict.c ) -# BMI2 instructions are not supported in older environments. -set_property(TARGET cmzstd PROPERTY COMPILE_DEFINITIONS DYNAMIC_BMI2=0) +target_compile_definitions(cmzstd PRIVATE + # BMI2 instructions are not supported in older environments. + DYNAMIC_BMI2=0 + # Explicitly disable ASM build to work with more compilers. + # Our vendored zstd does not include the assembly language file. + ZSTD_DISABLE_ASM=1 + ) install(FILES LICENSE DESTINATION ${CMAKE_DOC_DIR}/cmzstd) diff --git a/Utilities/cmzstd/LICENSE b/Utilities/cmzstd/LICENSE index a793a802892..75800288cc2 100644 --- a/Utilities/cmzstd/LICENSE +++ b/Utilities/cmzstd/LICENSE @@ -2,7 +2,7 @@ BSD License For Zstandard software -Copyright (c) 2016-present, Facebook, Inc. All rights reserved. +Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -14,9 +14,9 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name Facebook nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. + * Neither the name Facebook, nor Meta, nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED diff --git a/Utilities/cmzstd/README.md b/Utilities/cmzstd/README.md index dcca7662d2f..f91e68fdb10 100644 --- a/Utilities/cmzstd/README.md +++ b/Utilities/cmzstd/README.md @@ -4,23 +4,21 @@ __Zstandard__, or `zstd` as short version, is a fast lossless compression algori targeting real-time compression scenarios at zlib-level and better compression ratios. It's backed by a very fast entropy stage, provided by [Huff0 and FSE library](https://github.com/Cyan4973/FiniteStateEntropy). -The project is provided as an open-source dual [BSD](LICENSE) and [GPLv2](COPYING) licensed **C** library, +Zstandard's format is stable and documented in [RFC8878](https://datatracker.ietf.org/doc/html/rfc8878). Multiple independent implementations are already available. +This repository represents the reference implementation, provided as an open-source dual [BSD](LICENSE) and [GPLv2](COPYING) licensed **C** library, and a command line utility producing and decoding `.zst`, `.gz`, `.xz` and `.lz4` files. Should your project require another programming language, -a list of known ports and bindings is provided on [Zstandard homepage](http://www.zstd.net/#other-languages). +a list of known ports and bindings is provided on [Zstandard homepage](https://facebook.github.io/zstd/#other-languages). **Development branch status:** [![Build Status][travisDevBadge]][travisLink] -[![Build status][AppveyorDevBadge]][AppveyorLink] [![Build status][CircleDevBadge]][CircleLink] [![Build status][CirrusDevBadge]][CirrusLink] [![Fuzzing Status][OSSFuzzBadge]][OSSFuzzLink] -[travisDevBadge]: https://travis-ci.org/facebook/zstd.svg?branch=dev "Continuous Integration test suite" -[travisLink]: https://travis-ci.org/facebook/zstd -[AppveyorDevBadge]: https://ci.appveyor.com/api/projects/status/xt38wbdxjk5mrbem/branch/dev?svg=true "Windows test suite" -[AppveyorLink]: https://ci.appveyor.com/project/YannCollet/zstd-p0yf0 +[travisDevBadge]: https://api.travis-ci.com/facebook/zstd.svg?branch=dev "Continuous Integration test suite" +[travisLink]: https://travis-ci.com/facebook/zstd [CircleDevBadge]: https://circleci.com/gh/facebook/zstd/tree/dev.svg?style=shield "Short test suite" [CircleLink]: https://circleci.com/gh/facebook/zstd [CirrusDevBadge]: https://api.cirrus-ci.com/github/facebook/zstd.svg?branch=dev @@ -31,37 +29,36 @@ a list of known ports and bindings is provided on [Zstandard homepage](http://ww ## Benchmarks For reference, several fast compression algorithms were tested and compared -on a server running Arch Linux (`Linux version 5.5.11-arch1-1`), -with a Core i9-9900K CPU @ 5.0GHz, +on a desktop running Ubuntu 20.04 (`Linux 5.11.0-41-generic`), +with a Core i7-9700K CPU @ 4.9GHz, using [lzbench], an open-source in-memory benchmark by @inikep compiled with [gcc] 9.3.0, on the [Silesia compression corpus]. [lzbench]: https://github.com/inikep/lzbench -[Silesia compression corpus]: http://sun.aei.polsl.pl/~sdeor/index.php?page=silesia +[Silesia compression corpus]: https://sun.aei.polsl.pl//~sdeor/index.php?page=silesia [gcc]: https://gcc.gnu.org/ | Compressor name | Ratio | Compression| Decompress.| | --------------- | ------| -----------| ---------- | -| **zstd 1.4.5 -1** | 2.884 | 500 MB/s | 1660 MB/s | -| zlib 1.2.11 -1 | 2.743 | 90 MB/s | 400 MB/s | -| brotli 1.0.7 -0 | 2.703 | 400 MB/s | 450 MB/s | -| **zstd 1.4.5 --fast=1** | 2.434 | 570 MB/s | 2200 MB/s | -| **zstd 1.4.5 --fast=3** | 2.312 | 640 MB/s | 2300 MB/s | -| quicklz 1.5.0 -1 | 2.238 | 560 MB/s | 710 MB/s | -| **zstd 1.4.5 --fast=5** | 2.178 | 700 MB/s | 2420 MB/s | -| lzo1x 2.10 -1 | 2.106 | 690 MB/s | 820 MB/s | -| lz4 1.9.2 | 2.101 | 740 MB/s | 4530 MB/s | -| **zstd 1.4.5 --fast=7** | 2.096 | 750 MB/s | 2480 MB/s | -| lzf 3.6 -1 | 2.077 | 410 MB/s | 860 MB/s | -| snappy 1.1.8 | 2.073 | 560 MB/s | 1790 MB/s | - -[zlib]: http://www.zlib.net/ -[LZ4]: http://www.lz4.org/ +| **zstd 1.5.1 -1** | 2.887 | 530 MB/s | 1700 MB/s | +| [zlib] 1.2.11 -1 | 2.743 | 95 MB/s | 400 MB/s | +| brotli 1.0.9 -0 | 2.702 | 395 MB/s | 450 MB/s | +| **zstd 1.5.1 --fast=1** | 2.437 | 600 MB/s | 2150 MB/s | +| **zstd 1.5.1 --fast=3** | 2.239 | 670 MB/s | 2250 MB/s | +| quicklz 1.5.0 -1 | 2.238 | 540 MB/s | 760 MB/s | +| **zstd 1.5.1 --fast=4** | 2.148 | 710 MB/s | 2300 MB/s | +| lzo1x 2.10 -1 | 2.106 | 660 MB/s | 845 MB/s | +| [lz4] 1.9.3 | 2.101 | 740 MB/s | 4500 MB/s | +| lzf 3.6 -1 | 2.077 | 410 MB/s | 830 MB/s | +| snappy 1.1.9 | 2.073 | 550 MB/s | 1750 MB/s | + +[zlib]: https://www.zlib.net/ +[lz4]: https://lz4.github.io/lz4/ The negative compression levels, specified with `--fast=#`, -offer faster compression and decompression speed in exchange for some loss in -compression ratio compared to level 1, as seen in the table above. +offer faster compression and decompression speed +at the cost of compression ratio (compared to level 1). Zstd can also offer stronger compression ratios at the cost of compression speed. Speed vs Compression trade-off is configurable by small increments. @@ -124,14 +121,27 @@ Dictionary gains are mostly effective in the first few KB. Then, the compression ## Build instructions +`make` is the officially maintained build system of this project. +All other build systems are "compatible" and 3rd-party maintained, +they may feature small differences in advanced options. +When your system allows it, prefer using `make` to build `zstd` and `libzstd`. + ### Makefile If your system is compatible with standard `make` (or `gmake`), invoking `make` in root directory will generate `zstd` cli in root directory. +It will also create `libzstd` into `lib/`. Other available options include: - `make install` : create and install zstd cli, library and man pages -- `make check` : create and run `zstd`, tests its behavior on local platform +- `make check` : create and run `zstd`, test its behavior on local platform + +The `Makefile` follows the [GNU Standard Makefile conventions](https://www.gnu.org/prep/standards/html_node/Makefile-Conventions.html), +allowing staged install, standard flags, directory variables and command variables. + +For advanced use cases, specialized compilation flags which control binary generation +are documented in [`lib/README.md`](lib/README.md#modular-build) for the `libzstd` library +and in [`programs/README.md`](programs/README.md#compilation-variables) for the `zstd` CLI. ### cmake @@ -141,6 +151,18 @@ to create `zstd` binary, and `libzstd` dynamic and static libraries. By default, `CMAKE_BUILD_TYPE` is set to `Release`. +#### Support for Fat (Universal2) Output + +`zstd` can be built and installed with support for both Apple Silicon (M1/M2) as well as Intel by using CMake's Universal2 support. +To perform a Fat/Universal2 build and install use the following commands: + +```bash +cmake -B build-cmake-debug -S build/cmake -G Ninja -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h;arm64" +cd build-cmake-debug +ninja +sudo ninja install +``` + ### Meson A Meson project is provided within [`build/meson`](build/meson). Follow @@ -178,13 +200,15 @@ The output binary will be in `buck-out/gen/programs/`. ## Testing -You can run quick local smoke tests by executing the `playTest.sh` script from the `src/tests` directory. -Two env variables `$ZSTD_BIN` and `$DATAGEN_BIN` are needed for the test script to locate the zstd and datagen binary. -For information on CI testing, please refer to TESTING.md +You can run quick local smoke tests by running `make check`. +If you can't use `make`, execute the `playTest.sh` script from the `src/tests` directory. +Two env variables `$ZSTD_BIN` and `$DATAGEN_BIN` are needed for the test script to locate the `zstd` and `datagen` binary. +For information on CI testing, please refer to `TESTING.md`. ## Status -Zstandard is currently deployed within Facebook. It is used continuously to compress large amounts of data in multiple formats and use cases. +Zstandard is currently deployed within Facebook and many other large cloud infrastructures. +It is run continuously to compress large amounts of data in multiple formats and use cases. Zstandard is considered safe for production environments. ## License diff --git a/Utilities/cmzstd/lib/common/allocations.h b/Utilities/cmzstd/lib/common/allocations.h new file mode 100644 index 00000000000..a3153c4bac2 --- /dev/null +++ b/Utilities/cmzstd/lib/common/allocations.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +/* This file provides custom allocation primitives + */ + +#define ZSTD_DEPS_NEED_MALLOC +#include "zstd_deps.h" /* ZSTD_malloc, ZSTD_calloc, ZSTD_free, ZSTD_memset */ + +#include "mem.h" /* MEM_STATIC */ +#define ZSTD_STATIC_LINKING_ONLY +#include "../zstd.h" /* ZSTD_customMem */ + +#ifndef ZSTD_ALLOCATIONS_H +#define ZSTD_ALLOCATIONS_H + +/* custom memory allocation functions */ + +MEM_STATIC void* ZSTD_customMalloc(size_t size, ZSTD_customMem customMem) +{ + if (customMem.customAlloc) + return customMem.customAlloc(customMem.opaque, size); + return ZSTD_malloc(size); +} + +MEM_STATIC void* ZSTD_customCalloc(size_t size, ZSTD_customMem customMem) +{ + if (customMem.customAlloc) { + /* calloc implemented as malloc+memset; + * not as efficient as calloc, but next best guess for custom malloc */ + void* const ptr = customMem.customAlloc(customMem.opaque, size); + ZSTD_memset(ptr, 0, size); + return ptr; + } + return ZSTD_calloc(1, size); +} + +MEM_STATIC void ZSTD_customFree(void* ptr, ZSTD_customMem customMem) +{ + if (ptr!=NULL) { + if (customMem.customFree) + customMem.customFree(customMem.opaque, ptr); + else + ZSTD_free(ptr); + } +} + +#endif /* ZSTD_ALLOCATIONS_H */ diff --git a/Utilities/cmzstd/lib/common/bits.h b/Utilities/cmzstd/lib/common/bits.h new file mode 100644 index 00000000000..def56c474c3 --- /dev/null +++ b/Utilities/cmzstd/lib/common/bits.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef ZSTD_BITS_H +#define ZSTD_BITS_H + +#include "mem.h" + +MEM_STATIC unsigned ZSTD_countTrailingZeros32_fallback(U32 val) +{ + assert(val != 0); + { + static const U32 DeBruijnBytePos[32] = {0, 1, 28, 2, 29, 14, 24, 3, + 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, + 26, 12, 18, 6, 11, 5, 10, 9}; + return DeBruijnBytePos[((U32) ((val & -(S32) val) * 0x077CB531U)) >> 27]; + } +} + +MEM_STATIC unsigned ZSTD_countTrailingZeros32(U32 val) +{ + assert(val != 0); +# if defined(_MSC_VER) +# if STATIC_BMI2 == 1 + return (unsigned)_tzcnt_u32(val); +# else + if (val != 0) { + unsigned long r; + _BitScanForward(&r, val); + return (unsigned)r; + } else { + /* Should not reach this code path */ + __assume(0); + } +# endif +# elif defined(__GNUC__) && (__GNUC__ >= 4) + return (unsigned)__builtin_ctz(val); +# else + return ZSTD_countTrailingZeros32_fallback(val); +# endif +} + +MEM_STATIC unsigned ZSTD_countLeadingZeros32_fallback(U32 val) { + assert(val != 0); + { + static const U32 DeBruijnClz[32] = {0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31}; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + return 31 - DeBruijnClz[(val * 0x07C4ACDDU) >> 27]; + } +} + +MEM_STATIC unsigned ZSTD_countLeadingZeros32(U32 val) +{ + assert(val != 0); +# if defined(_MSC_VER) +# if STATIC_BMI2 == 1 + return (unsigned)_lzcnt_u32(val); +# else + if (val != 0) { + unsigned long r; + _BitScanReverse(&r, val); + return (unsigned)(31 - r); + } else { + /* Should not reach this code path */ + __assume(0); + } +# endif +# elif defined(__GNUC__) && (__GNUC__ >= 4) + return (unsigned)__builtin_clz(val); +# else + return ZSTD_countLeadingZeros32_fallback(val); +# endif +} + +MEM_STATIC unsigned ZSTD_countTrailingZeros64(U64 val) +{ + assert(val != 0); +# if defined(_MSC_VER) && defined(_WIN64) +# if STATIC_BMI2 == 1 + return (unsigned)_tzcnt_u64(val); +# else + if (val != 0) { + unsigned long r; + _BitScanForward64(&r, val); + return (unsigned)r; + } else { + /* Should not reach this code path */ + __assume(0); + } +# endif +# elif defined(__GNUC__) && (__GNUC__ >= 4) && defined(__LP64__) + return (unsigned)__builtin_ctzll(val); +# else + { + U32 mostSignificantWord = (U32)(val >> 32); + U32 leastSignificantWord = (U32)val; + if (leastSignificantWord == 0) { + return 32 + ZSTD_countTrailingZeros32(mostSignificantWord); + } else { + return ZSTD_countTrailingZeros32(leastSignificantWord); + } + } +# endif +} + +MEM_STATIC unsigned ZSTD_countLeadingZeros64(U64 val) +{ + assert(val != 0); +# if defined(_MSC_VER) && defined(_WIN64) +# if STATIC_BMI2 == 1 + return (unsigned)_lzcnt_u64(val); +# else + if (val != 0) { + unsigned long r; + _BitScanReverse64(&r, val); + return (unsigned)(63 - r); + } else { + /* Should not reach this code path */ + __assume(0); + } +# endif +# elif defined(__GNUC__) && (__GNUC__ >= 4) + return (unsigned)(__builtin_clzll(val)); +# else + { + U32 mostSignificantWord = (U32)(val >> 32); + U32 leastSignificantWord = (U32)val; + if (mostSignificantWord == 0) { + return 32 + ZSTD_countLeadingZeros32(leastSignificantWord); + } else { + return ZSTD_countLeadingZeros32(mostSignificantWord); + } + } +# endif +} + +MEM_STATIC unsigned ZSTD_NbCommonBytes(size_t val) +{ + if (MEM_isLittleEndian()) { + if (MEM_64bits()) { + return ZSTD_countTrailingZeros64((U64)val) >> 3; + } else { + return ZSTD_countTrailingZeros32((U32)val) >> 3; + } + } else { /* Big Endian CPU */ + if (MEM_64bits()) { + return ZSTD_countLeadingZeros64((U64)val) >> 3; + } else { + return ZSTD_countLeadingZeros32((U32)val) >> 3; + } + } +} + +MEM_STATIC unsigned ZSTD_highbit32(U32 val) /* compress, dictBuilder, decodeCorpus */ +{ + assert(val != 0); + return 31 - ZSTD_countLeadingZeros32(val); +} + +/* ZSTD_rotateRight_*(): + * Rotates a bitfield to the right by "count" bits. + * https://en.wikipedia.org/w/index.php?title=Circular_shift&oldid=991635599#Implementing_circular_shifts + */ +MEM_STATIC +U64 ZSTD_rotateRight_U64(U64 const value, U32 count) { + assert(count < 64); + count &= 0x3F; /* for fickle pattern recognition */ + return (value >> count) | (U64)(value << ((0U - count) & 0x3F)); +} + +MEM_STATIC +U32 ZSTD_rotateRight_U32(U32 const value, U32 count) { + assert(count < 32); + count &= 0x1F; /* for fickle pattern recognition */ + return (value >> count) | (U32)(value << ((0U - count) & 0x1F)); +} + +MEM_STATIC +U16 ZSTD_rotateRight_U16(U16 const value, U32 count) { + assert(count < 16); + count &= 0x0F; /* for fickle pattern recognition */ + return (value >> count) | (U16)(value << ((0U - count) & 0x0F)); +} + +#endif /* ZSTD_BITS_H */ diff --git a/Utilities/cmzstd/lib/common/bitstream.h b/Utilities/cmzstd/lib/common/bitstream.h index 136a18881f1..31feb39618d 100644 --- a/Utilities/cmzstd/lib/common/bitstream.h +++ b/Utilities/cmzstd/lib/common/bitstream.h @@ -1,7 +1,7 @@ /* ****************************************************************** * bitstream * Part of FSE library - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -32,14 +32,15 @@ extern "C" { #include "compiler.h" /* UNLIKELY() */ #include "debug.h" /* assert(), DEBUGLOG(), RAWLOG() */ #include "error_private.h" /* error codes and messages */ +#include "bits.h" /* ZSTD_highbit32 */ /*========================================= * Target specific =========================================*/ #ifndef ZSTD_NO_INTRINSICS -# if defined(__BMI__) && defined(__GNUC__) -# include /* support for bextr (experimental) */ +# if (defined(__BMI__) || defined(__BMI2__)) && defined(__GNUC__) +# include /* support for bextr (experimental)/bzhi */ # elif defined(__ICCARM__) # include # endif @@ -134,42 +135,6 @@ MEM_STATIC void BIT_flushBitsFast(BIT_CStream_t* bitC); MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits); /* faster, but works only if nbBits >= 1 */ - - -/*-************************************************************** -* Internal functions -****************************************************************/ -MEM_STATIC unsigned BIT_highbit32 (U32 val) -{ - assert(val != 0); - { -# if defined(_MSC_VER) /* Visual */ -# if STATIC_BMI2 == 1 - return _lzcnt_u32(val) ^ 31; -# else - unsigned long r = 0; - return _BitScanReverse(&r, val) ? (unsigned)r : 0; -# endif -# elif defined(__GNUC__) && (__GNUC__ >= 3) /* Use GCC Intrinsic */ - return __builtin_clz (val) ^ 31; -# elif defined(__ICCARM__) /* IAR Intrinsic */ - return 31 - __CLZ(val); -# else /* Software version */ - static const unsigned DeBruijnClz[32] = { 0, 9, 1, 10, 13, 21, 2, 29, - 11, 14, 16, 18, 22, 25, 3, 30, - 8, 12, 20, 28, 15, 17, 24, 7, - 19, 27, 23, 6, 26, 5, 4, 31 }; - U32 v = val; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return DeBruijnClz[ (U32) (v * 0x07C4ACDDU) >> 27]; -# endif - } -} - /*===== Local Constants =====*/ static const unsigned BIT_mask[] = { 0, 1, 3, 7, 0xF, 0x1F, @@ -199,6 +164,16 @@ MEM_STATIC size_t BIT_initCStream(BIT_CStream_t* bitC, return 0; } +MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) +{ +#if defined(STATIC_BMI2) && STATIC_BMI2 == 1 && !defined(ZSTD_NO_INTRINSICS) + return _bzhi_u64(bitContainer, nbBits); +#else + assert(nbBits < BIT_MASK_SIZE); + return bitContainer & BIT_mask[nbBits]; +#endif +} + /*! BIT_addBits() : * can add up to 31 bits into `bitC`. * Note : does not check for register overflow ! */ @@ -208,7 +183,7 @@ MEM_STATIC void BIT_addBits(BIT_CStream_t* bitC, DEBUG_STATIC_ASSERT(BIT_MASK_SIZE == 32); assert(nbBits < BIT_MASK_SIZE); assert(nbBits + bitC->bitPos < sizeof(bitC->bitContainer) * 8); - bitC->bitContainer |= (value & BIT_mask[nbBits]) << bitC->bitPos; + bitC->bitContainer |= BIT_getLowerBits(value, nbBits) << bitC->bitPos; bitC->bitPos += nbBits; } @@ -287,7 +262,7 @@ MEM_STATIC size_t BIT_initDStream(BIT_DStream_t* bitD, const void* srcBuffer, si bitD->ptr = (const char*)srcBuffer + srcSize - sizeof(bitD->bitContainer); bitD->bitContainer = MEM_readLEST(bitD->ptr); { BYTE const lastByte = ((const BYTE*)srcBuffer)[srcSize-1]; - bitD->bitsConsumed = lastByte ? 8 - BIT_highbit32(lastByte) : 0; /* ensures bitsConsumed is always set */ + bitD->bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0; /* ensures bitsConsumed is always set */ if (lastByte == 0) return ERROR(GENERIC); /* endMark not present */ } } else { bitD->ptr = bitD->start; @@ -295,27 +270,27 @@ MEM_STATIC size_t BIT_initDStream(BIT_DStream_t* bitD, const void* srcBuffer, si switch(srcSize) { case 7: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[6]) << (sizeof(bitD->bitContainer)*8 - 16); - /* fall-through */ + ZSTD_FALLTHROUGH; case 6: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[5]) << (sizeof(bitD->bitContainer)*8 - 24); - /* fall-through */ + ZSTD_FALLTHROUGH; case 5: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[4]) << (sizeof(bitD->bitContainer)*8 - 32); - /* fall-through */ + ZSTD_FALLTHROUGH; case 4: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[3]) << 24; - /* fall-through */ + ZSTD_FALLTHROUGH; case 3: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[2]) << 16; - /* fall-through */ + ZSTD_FALLTHROUGH; case 2: bitD->bitContainer += (size_t)(((const BYTE*)(srcBuffer))[1]) << 8; - /* fall-through */ + ZSTD_FALLTHROUGH; default: break; } { BYTE const lastByte = ((const BYTE*)srcBuffer)[srcSize-1]; - bitD->bitsConsumed = lastByte ? 8 - BIT_highbit32(lastByte) : 0; + bitD->bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0; if (lastByte == 0) return ERROR(corruption_detected); /* endMark not present */ } bitD->bitsConsumed += (U32)(sizeof(bitD->bitContainer) - srcSize)*8; @@ -334,16 +309,15 @@ MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getMiddleBits(size_t bitContainer, U32 c U32 const regMask = sizeof(bitContainer)*8 - 1; /* if start > regMask, bitstream is corrupted, and result is undefined */ assert(nbBits < BIT_MASK_SIZE); - return (bitContainer >> (start & regMask)) & BIT_mask[nbBits]; -} - -MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) -{ -#if defined(STATIC_BMI2) && STATIC_BMI2 == 1 - return _bzhi_u64(bitContainer, nbBits); + /* x86 transform & ((1 << nbBits) - 1) to bzhi instruction, it is better + * than accessing memory. When bmi2 instruction is not present, we consider + * such cpus old (pre-Haswell, 2013) and their performance is not of that + * importance. + */ +#if defined(__x86_64__) || defined(_M_X86) + return (bitContainer >> (start & regMask)) & ((((U64)1) << nbBits) - 1); #else - assert(nbBits < BIT_MASK_SIZE); - return bitContainer & BIT_mask[nbBits]; + return (bitContainer >> (start & regMask)) & BIT_mask[nbBits]; #endif } @@ -393,7 +367,7 @@ MEM_STATIC FORCE_INLINE_ATTR size_t BIT_readBits(BIT_DStream_t* bitD, unsigned n } /*! BIT_readBitsFast() : - * unsafe version; only works only if nbBits >= 1 */ + * unsafe version; only works if nbBits >= 1 */ MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits) { size_t const value = BIT_lookBitsFast(bitD, nbBits); @@ -424,7 +398,7 @@ MEM_STATIC BIT_DStream_status BIT_reloadDStreamFast(BIT_DStream_t* bitD) * This function is safe, it guarantees it will not read beyond src buffer. * @return : status of `BIT_DStream_t` internal register. * when status == BIT_DStream_unfinished, internal register is filled with at least 25 or 57 bits */ -MEM_STATIC BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD) +MEM_STATIC FORCE_INLINE_ATTR BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD) { if (bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8)) /* overflow detected, like end of stream */ return BIT_DStream_overflow; diff --git a/Utilities/cmzstd/lib/common/compiler.h b/Utilities/cmzstd/lib/common/compiler.h index f7178556391..d07e189edf0 100644 --- a/Utilities/cmzstd/lib/common/compiler.h +++ b/Utilities/cmzstd/lib/common/compiler.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -11,6 +11,8 @@ #ifndef ZSTD_COMPILER_H #define ZSTD_COMPILER_H +#include "portability_macros.h" + /*-******************************************************* * Compiler specifics *********************************************************/ @@ -40,7 +42,7 @@ /** On MSVC qsort requires that functions passed into it use the __cdecl calling conversion(CC). - This explictly marks such functions as __cdecl so that the code will still compile + This explicitly marks such functions as __cdecl so that the code will still compile if a CC other than __cdecl has been made the default. */ #if defined(_MSC_VER) @@ -92,29 +94,17 @@ /* target attribute */ -#ifndef __has_attribute - #define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif #if defined(__GNUC__) || defined(__ICCARM__) # define TARGET_ATTRIBUTE(target) __attribute__((__target__(target))) #else # define TARGET_ATTRIBUTE(target) #endif -/* Enable runtime BMI2 dispatch based on the CPU. - * Enabled for clang & gcc >=4.8 on x86 when BMI2 isn't enabled by default. +/* Target attribute for BMI2 dynamic dispatch. + * Enable lzcnt, bmi, and bmi2. + * We test for bmi1 & bmi2. lzcnt is included in bmi1. */ -#ifndef DYNAMIC_BMI2 - #if ((defined(__clang__) && __has_attribute(__target__)) \ - || (defined(__GNUC__) \ - && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)))) \ - && (defined(__x86_64__) || defined(_M_X86)) \ - && !defined(__BMI2__) - # define DYNAMIC_BMI2 1 - #else - # define DYNAMIC_BMI2 0 - #endif -#endif +#define BMI2_TARGET_ATTRIBUTE TARGET_ATTRIBUTE("lzcnt,bmi,bmi2") /* prefetch * can be disabled, by declaring NO_PREFETCH build macro */ @@ -150,8 +140,9 @@ } /* vectorization - * older GCC (pre gcc-4.3 picked as the cutoff) uses a different syntax */ -#if !defined(__INTEL_COMPILER) && !defined(__clang__) && !defined(__LCC__) && defined(__GNUC__) + * older GCC (pre gcc-4.3 picked as the cutoff) uses a different syntax, + * and some compilers, like Intel ICC and MCST LCC, do not support it at all. */ +#if !defined(__INTEL_COMPILER) && !defined(__clang__) && defined(__GNUC__) && !defined(__LCC__) # if (__GNUC__ == 4 && __GNUC_MINOR__ > 3) || (__GNUC__ >= 5) # define DONT_VECTORIZE __attribute__((optimize("no-tree-vectorize"))) # else @@ -174,6 +165,12 @@ #define UNLIKELY(x) (x) #endif +#if __has_builtin(__builtin_unreachable) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))) +# define ZSTD_UNREACHABLE { assert(0), __builtin_unreachable(); } +#else +# define ZSTD_UNREACHABLE { assert(0); } +#endif + /* disable warnings */ #ifdef _MSC_VER /* Visual Studio */ # include /* For Visual 2005 */ @@ -190,6 +187,8 @@ # ifdef __AVX2__ //MSVC does not have a BMI2 specific flag, but every CPU that supports AVX2 also supports BMI2 # define STATIC_BMI2 1 # endif +# elif defined(__BMI2__) && defined(__x86_64__) && defined(__GNUC__) +# define STATIC_BMI2 1 # endif #endif @@ -197,26 +196,107 @@ #define STATIC_BMI2 0 #endif -/* compat. with non-clang compilers */ -#ifndef __has_builtin -# define __has_builtin(x) 0 +/* compile time determination of SIMD support */ +#if !defined(ZSTD_NO_INTRINSICS) +# if defined(__SSE2__) || defined(_M_AMD64) || (defined (_M_IX86) && defined(_M_IX86_FP) && (_M_IX86_FP >= 2)) +# define ZSTD_ARCH_X86_SSE2 +# endif +# if defined(__ARM_NEON) || defined(_M_ARM64) +# define ZSTD_ARCH_ARM_NEON +# endif +# +# if defined(ZSTD_ARCH_X86_SSE2) +# include +# elif defined(ZSTD_ARCH_ARM_NEON) +# include +# endif #endif -/* compat. with non-clang compilers */ -#ifndef __has_feature -# define __has_feature(x) 0 +/* C-language Attributes are added in C23. */ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ > 201710L) && defined(__has_c_attribute) +# define ZSTD_HAS_C_ATTRIBUTE(x) __has_c_attribute(x) +#else +# define ZSTD_HAS_C_ATTRIBUTE(x) 0 #endif -/* detects whether we are being compiled under msan */ -#ifndef ZSTD_MEMORY_SANITIZER -# if __has_feature(memory_sanitizer) -# define ZSTD_MEMORY_SANITIZER 1 -# else -# define ZSTD_MEMORY_SANITIZER 0 -# endif +/* Only use C++ attributes in C++. Some compilers report support for C++ + * attributes when compiling with C. + */ +#if defined(__cplusplus) && defined(__has_cpp_attribute) +# define ZSTD_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define ZSTD_HAS_CPP_ATTRIBUTE(x) 0 #endif -#if ZSTD_MEMORY_SANITIZER +/* Define ZSTD_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute. + * - C23: https://en.cppreference.com/w/c/language/attributes/fallthrough + * - CPP17: https://en.cppreference.com/w/cpp/language/attributes/fallthrough + * - Else: __attribute__((__fallthrough__)) + */ +#ifndef ZSTD_FALLTHROUGH +# if ZSTD_HAS_C_ATTRIBUTE(fallthrough) +# define ZSTD_FALLTHROUGH [[fallthrough]] +# elif ZSTD_HAS_CPP_ATTRIBUTE(fallthrough) +# define ZSTD_FALLTHROUGH [[fallthrough]] +# elif __has_attribute(__fallthrough__) +/* Leading semicolon is to satisfy gcc-11 with -pedantic. Without the semicolon + * gcc complains about: a label can only be part of a statement and a declaration is not a statement. + */ +# define ZSTD_FALLTHROUGH ; __attribute__((__fallthrough__)) +# else +# define ZSTD_FALLTHROUGH +# endif +#endif + +/*-************************************************************** +* Alignment check +*****************************************************************/ + +/* this test was initially positioned in mem.h, + * but this file is removed (or replaced) for linux kernel + * so it's now hosted in compiler.h, + * which remains valid for both user & kernel spaces. + */ + +#ifndef ZSTD_ALIGNOF +# if defined(__GNUC__) || defined(_MSC_VER) +/* covers gcc, clang & MSVC */ +/* note : this section must come first, before C11, + * due to a limitation in the kernel source generator */ +# define ZSTD_ALIGNOF(T) __alignof(T) + +# elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +/* Oracle Studio */ +# define ZSTD_ALIGNOF(T) _Alignof(T) + +# elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +/* C11 support */ +# include +# define ZSTD_ALIGNOF(T) alignof(T) + +# else +/* No known support for alignof() - imperfect backup */ +# define ZSTD_ALIGNOF(T) (sizeof(void*) < sizeof(T) ? sizeof(void*) : sizeof(T)) + +# endif +#endif /* ZSTD_ALIGNOF */ + +/*-************************************************************** +* Sanitizer +*****************************************************************/ + +/* Issue #3240 reports an ASAN failure on an llvm-mingw build. Out of an + * abundance of caution, disable our custom poisoning on mingw. */ +#ifdef __MINGW32__ +#ifndef ZSTD_ASAN_DONT_POISON_WORKSPACE +#define ZSTD_ASAN_DONT_POISON_WORKSPACE 1 +#endif +#ifndef ZSTD_MSAN_DONT_POISON_WORKSPACE +#define ZSTD_MSAN_DONT_POISON_WORKSPACE 1 +#endif +#endif + +#if ZSTD_MEMORY_SANITIZER && !defined(ZSTD_MSAN_DONT_POISON_WORKSPACE) /* Not all platforms that support msan provide sanitizers/msan_interface.h. * We therefore declare the functions we need ourselves, rather than trying to * include the header file... */ @@ -235,20 +315,13 @@ void __msan_poison(const volatile void *a, size_t size); /* Returns the offset of the first (at least partially) poisoned byte in the memory range, or -1 if the whole range is good. */ intptr_t __msan_test_shadow(const volatile void *x, size_t size); -#endif -/* detects whether we are being compiled under asan */ -#ifndef ZSTD_ADDRESS_SANITIZER -# if __has_feature(address_sanitizer) -# define ZSTD_ADDRESS_SANITIZER 1 -# elif defined(__SANITIZE_ADDRESS__) -# define ZSTD_ADDRESS_SANITIZER 1 -# else -# define ZSTD_ADDRESS_SANITIZER 0 -# endif +/* Print shadow and origin for the memory range to stderr in a human-readable + format. */ +void __msan_print_shadow(const volatile void *x, size_t size); #endif -#if ZSTD_ADDRESS_SANITIZER +#if ZSTD_ADDRESS_SANITIZER && !defined(ZSTD_ASAN_DONT_POISON_WORKSPACE) /* Not all platforms that support asan provide sanitizers/asan_interface.h. * We therefore declare the functions we need ourselves, rather than trying to * include the header file... */ diff --git a/Utilities/cmzstd/lib/common/cpu.h b/Utilities/cmzstd/lib/common/cpu.h index 8acd33be3cd..8bc34a36da2 100644 --- a/Utilities/cmzstd/lib/common/cpu.h +++ b/Utilities/cmzstd/lib/common/cpu.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/common/debug.c b/Utilities/cmzstd/lib/common/debug.c index bb863c9ea61..ebf7bfccfa6 100644 --- a/Utilities/cmzstd/lib/common/debug.c +++ b/Utilities/cmzstd/lib/common/debug.c @@ -1,7 +1,7 @@ /* ****************************************************************** * debug * Part of FSE library - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/Utilities/cmzstd/lib/common/debug.h b/Utilities/cmzstd/lib/common/debug.h index 3b2a320a188..0e9817ea6d6 100644 --- a/Utilities/cmzstd/lib/common/debug.h +++ b/Utilities/cmzstd/lib/common/debug.h @@ -1,7 +1,7 @@ /* ****************************************************************** * debug * Part of FSE library - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/Utilities/cmzstd/lib/common/entropy_common.c b/Utilities/cmzstd/lib/common/entropy_common.c index 41cd69566bc..e2173afb0a8 100644 --- a/Utilities/cmzstd/lib/common/entropy_common.c +++ b/Utilities/cmzstd/lib/common/entropy_common.c @@ -1,6 +1,6 @@ /* ****************************************************************** * Common functions of New Generation Entropy library - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - FSE+HUF source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -19,8 +19,8 @@ #include "error_private.h" /* ERR_*, ERROR */ #define FSE_STATIC_LINKING_ONLY /* FSE_MIN_TABLELOG */ #include "fse.h" -#define HUF_STATIC_LINKING_ONLY /* HUF_TABLELOG_ABSOLUTEMAX */ #include "huf.h" +#include "bits.h" /* ZSDT_highbit32, ZSTD_countTrailingZeros32 */ /*=== Version ===*/ @@ -38,28 +38,6 @@ const char* HUF_getErrorName(size_t code) { return ERR_getErrorName(code); } /*-************************************************************** * FSE NCount encoding-decoding ****************************************************************/ -static U32 FSE_ctz(U32 val) -{ - assert(val != 0); - { -# if defined(_MSC_VER) /* Visual */ - unsigned long r=0; - return _BitScanForward(&r, val) ? (unsigned)r : 0; -# elif defined(__GNUC__) && (__GNUC__ >= 3) /* GCC Intrinsic */ - return __builtin_ctz(val); -# elif defined(__ICCARM__) /* IAR Intrinsic */ - return __CTZ(val); -# else /* Software version */ - U32 count = 0; - while ((val & 1) == 0) { - val >>= 1; - ++count; - } - return count; -# endif - } -} - FORCE_INLINE_TEMPLATE size_t FSE_readNCount_body(short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr, const void* headerBuffer, size_t hbSize) @@ -107,7 +85,7 @@ size_t FSE_readNCount_body(short* normalizedCounter, unsigned* maxSVPtr, unsigne * repeat. * Avoid UB by setting the high bit to 1. */ - int repeats = FSE_ctz(~bitStream | 0x80000000) >> 1; + int repeats = ZSTD_countTrailingZeros32(~bitStream | 0x80000000) >> 1; while (repeats >= 12) { charnum += 3 * 12; if (LIKELY(ip <= iend-7)) { @@ -118,7 +96,7 @@ size_t FSE_readNCount_body(short* normalizedCounter, unsigned* maxSVPtr, unsigne ip = iend - 4; } bitStream = MEM_readLE32(ip) >> bitCount; - repeats = FSE_ctz(~bitStream | 0x80000000) >> 1; + repeats = ZSTD_countTrailingZeros32(~bitStream | 0x80000000) >> 1; } charnum += 3 * repeats; bitStream >>= 2 * repeats; @@ -183,7 +161,7 @@ size_t FSE_readNCount_body(short* normalizedCounter, unsigned* maxSVPtr, unsigne * know that threshold > 1. */ if (remaining <= 1) break; - nbBits = BIT_highbit32(remaining) + 1; + nbBits = ZSTD_highbit32(remaining) + 1; threshold = 1 << (nbBits - 1); } if (charnum >= maxSV1) break; @@ -217,7 +195,7 @@ static size_t FSE_readNCount_body_default( } #if DYNAMIC_BMI2 -TARGET_ATTRIBUTE("bmi2") static size_t FSE_readNCount_body_bmi2( +BMI2_TARGET_ATTRIBUTE static size_t FSE_readNCount_body_bmi2( short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr, const void* headerBuffer, size_t hbSize) { @@ -258,7 +236,7 @@ size_t HUF_readStats(BYTE* huffWeight, size_t hwSize, U32* rankStats, const void* src, size_t srcSize) { U32 wksp[HUF_READ_STATS_WORKSPACE_SIZE_U32]; - return HUF_readStats_wksp(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, wksp, sizeof(wksp), /* bmi2 */ 0); + return HUF_readStats_wksp(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, wksp, sizeof(wksp), /* flags */ 0); } FORCE_INLINE_TEMPLATE size_t @@ -299,21 +277,21 @@ HUF_readStats_body(BYTE* huffWeight, size_t hwSize, U32* rankStats, ZSTD_memset(rankStats, 0, (HUF_TABLELOG_MAX + 1) * sizeof(U32)); weightTotal = 0; { U32 n; for (n=0; n= HUF_TABLELOG_MAX) return ERROR(corruption_detected); + if (huffWeight[n] > HUF_TABLELOG_MAX) return ERROR(corruption_detected); rankStats[huffWeight[n]]++; weightTotal += (1 << huffWeight[n]) >> 1; } } if (weightTotal == 0) return ERROR(corruption_detected); /* get last non-null symbol weight (implied, total must be 2^n) */ - { U32 const tableLog = BIT_highbit32(weightTotal) + 1; + { U32 const tableLog = ZSTD_highbit32(weightTotal) + 1; if (tableLog > HUF_TABLELOG_MAX) return ERROR(corruption_detected); *tableLogPtr = tableLog; /* determine last weight */ { U32 const total = 1 << tableLog; U32 const rest = total - weightTotal; - U32 const verif = 1 << BIT_highbit32(rest); - U32 const lastWeight = BIT_highbit32(rest) + 1; + U32 const verif = 1 << ZSTD_highbit32(rest); + U32 const lastWeight = ZSTD_highbit32(rest) + 1; if (verif != rest) return ERROR(corruption_detected); /* last value must be a clean power of 2 */ huffWeight[oSize] = (BYTE)lastWeight; rankStats[lastWeight]++; @@ -337,7 +315,7 @@ static size_t HUF_readStats_body_default(BYTE* huffWeight, size_t hwSize, U32* r } #if DYNAMIC_BMI2 -static TARGET_ATTRIBUTE("bmi2") size_t HUF_readStats_body_bmi2(BYTE* huffWeight, size_t hwSize, U32* rankStats, +static BMI2_TARGET_ATTRIBUTE size_t HUF_readStats_body_bmi2(BYTE* huffWeight, size_t hwSize, U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr, const void* src, size_t srcSize, void* workSpace, size_t wkspSize) @@ -350,13 +328,13 @@ size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, - int bmi2) + int flags) { #if DYNAMIC_BMI2 - if (bmi2) { + if (flags & HUF_flags_bmi2) { return HUF_readStats_body_bmi2(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); } #endif - (void)bmi2; + (void)flags; return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); } diff --git a/Utilities/cmzstd/lib/common/error_private.c b/Utilities/cmzstd/lib/common/error_private.c index 6d1135f8c37..075fc5ef42f 100644 --- a/Utilities/cmzstd/lib/common/error_private.c +++ b/Utilities/cmzstd/lib/common/error_private.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -27,9 +27,11 @@ const char* ERR_getErrorString(ERR_enum code) case PREFIX(version_unsupported): return "Version not supported"; case PREFIX(frameParameter_unsupported): return "Unsupported frame parameter"; case PREFIX(frameParameter_windowTooLarge): return "Frame requires too much memory for decoding"; - case PREFIX(corruption_detected): return "Corrupted block detected"; + case PREFIX(corruption_detected): return "Data corruption detected"; case PREFIX(checksum_wrong): return "Restored data doesn't match checksum"; + case PREFIX(literals_headerWrong): return "Header of Literals' block doesn't respect format specification"; case PREFIX(parameter_unsupported): return "Unsupported parameter"; + case PREFIX(parameter_combination_unsupported): return "Unsupported combination of parameters"; case PREFIX(parameter_outOfBound): return "Parameter is out of bound"; case PREFIX(init_missing): return "Context should be init first"; case PREFIX(memory_allocation): return "Allocation error : not enough memory"; @@ -38,17 +40,22 @@ const char* ERR_getErrorString(ERR_enum code) case PREFIX(tableLog_tooLarge): return "tableLog requires too much memory : unsupported"; case PREFIX(maxSymbolValue_tooLarge): return "Unsupported max Symbol Value : too large"; case PREFIX(maxSymbolValue_tooSmall): return "Specified maxSymbolValue is too small"; + case PREFIX(stabilityCondition_notRespected): return "pledged buffer stability condition is not respected"; case PREFIX(dictionary_corrupted): return "Dictionary is corrupted"; case PREFIX(dictionary_wrong): return "Dictionary mismatch"; case PREFIX(dictionaryCreation_failed): return "Cannot create Dictionary from provided samples"; case PREFIX(dstSize_tooSmall): return "Destination buffer is too small"; case PREFIX(srcSize_wrong): return "Src size is incorrect"; case PREFIX(dstBuffer_null): return "Operation on NULL destination buffer"; + case PREFIX(noForwardProgress_destFull): return "Operation made no progress over multiple calls, due to output buffer being full"; + case PREFIX(noForwardProgress_inputEmpty): return "Operation made no progress over multiple calls, due to input being empty"; /* following error codes are not stable and may be removed or changed in a future version */ case PREFIX(frameIndex_tooLarge): return "Frame index is too large"; case PREFIX(seekableIO): return "An I/O error occurred when reading/seeking"; case PREFIX(dstBuffer_wrong): return "Destination buffer is wrong"; case PREFIX(srcBuffer_wrong): return "Source buffer is wrong"; + case PREFIX(sequenceProducer_failed): return "Block-level external sequence producer returned an error code"; + case PREFIX(externalSequences_invalid): return "External sequences are not valid"; case PREFIX(maxCode): default: return notErrorCode; } diff --git a/Utilities/cmzstd/lib/common/error_private.h b/Utilities/cmzstd/lib/common/error_private.h index 6d8b9f77633..325daad404b 100644 --- a/Utilities/cmzstd/lib/common/error_private.h +++ b/Utilities/cmzstd/lib/common/error_private.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -22,6 +22,8 @@ extern "C" { * Dependencies ******************************************/ #include "../zstd_errors.h" /* enum list */ +#include "compiler.h" +#include "debug.h" #include "zstd_deps.h" /* size_t */ @@ -73,6 +75,83 @@ ERR_STATIC const char* ERR_getErrorName(size_t code) return ERR_getErrorString(ERR_getErrorCode(code)); } +/** + * Ignore: this is an internal helper. + * + * This is a helper function to help force C99-correctness during compilation. + * Under strict compilation modes, variadic macro arguments can't be empty. + * However, variadic function arguments can be. Using a function therefore lets + * us statically check that at least one (string) argument was passed, + * independent of the compilation flags. + */ +static INLINE_KEYWORD UNUSED_ATTR +void _force_has_format_string(const char *format, ...) { + (void)format; +} + +/** + * Ignore: this is an internal helper. + * + * We want to force this function invocation to be syntactically correct, but + * we don't want to force runtime evaluation of its arguments. + */ +#define _FORCE_HAS_FORMAT_STRING(...) \ + if (0) { \ + _force_has_format_string(__VA_ARGS__); \ + } + +#define ERR_QUOTE(str) #str + +/** + * Return the specified error if the condition evaluates to true. + * + * In debug modes, prints additional information. + * In order to do that (particularly, printing the conditional that failed), + * this can't just wrap RETURN_ERROR(). + */ +#define RETURN_ERROR_IF(cond, err, ...) \ + if (cond) { \ + RAWLOG(3, "%s:%d: ERROR!: check %s failed, returning %s", \ + __FILE__, __LINE__, ERR_QUOTE(cond), ERR_QUOTE(ERROR(err))); \ + _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ + RAWLOG(3, ": " __VA_ARGS__); \ + RAWLOG(3, "\n"); \ + return ERROR(err); \ + } + +/** + * Unconditionally return the specified error. + * + * In debug modes, prints additional information. + */ +#define RETURN_ERROR(err, ...) \ + do { \ + RAWLOG(3, "%s:%d: ERROR!: unconditional check failed, returning %s", \ + __FILE__, __LINE__, ERR_QUOTE(ERROR(err))); \ + _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ + RAWLOG(3, ": " __VA_ARGS__); \ + RAWLOG(3, "\n"); \ + return ERROR(err); \ + } while(0); + +/** + * If the provided expression evaluates to an error code, returns that error code. + * + * In debug modes, prints additional information. + */ +#define FORWARD_IF_ERROR(err, ...) \ + do { \ + size_t const err_code = (err); \ + if (ERR_isError(err_code)) { \ + RAWLOG(3, "%s:%d: ERROR!: forwarding error in %s: %s", \ + __FILE__, __LINE__, ERR_QUOTE(err), ERR_getErrorName(err_code)); \ + _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ + RAWLOG(3, ": " __VA_ARGS__); \ + RAWLOG(3, "\n"); \ + return err_code; \ + } \ + } while(0); + #if defined (__cplusplus) } #endif diff --git a/Utilities/cmzstd/lib/common/fse.h b/Utilities/cmzstd/lib/common/fse.h index 19dd4febcd0..02a1f0bc530 100644 --- a/Utilities/cmzstd/lib/common/fse.h +++ b/Utilities/cmzstd/lib/common/fse.h @@ -1,7 +1,7 @@ /* ****************************************************************** * FSE : Finite State Entropy codec * Public Prototypes declaration - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -53,34 +53,6 @@ extern "C" { FSE_PUBLIC_API unsigned FSE_versionNumber(void); /**< library version number; to be used when checking dll version */ -/*-**************************************** -* FSE simple functions -******************************************/ -/*! FSE_compress() : - Compress content of buffer 'src', of size 'srcSize', into destination buffer 'dst'. - 'dst' buffer must be already allocated. Compression runs faster is dstCapacity >= FSE_compressBound(srcSize). - @return : size of compressed data (<= dstCapacity). - Special values : if return == 0, srcData is not compressible => Nothing is stored within dst !!! - if return == 1, srcData is a single byte symbol * srcSize times. Use RLE compression instead. - if FSE_isError(return), compression failed (more details using FSE_getErrorName()) -*/ -FSE_PUBLIC_API size_t FSE_compress(void* dst, size_t dstCapacity, - const void* src, size_t srcSize); - -/*! FSE_decompress(): - Decompress FSE data from buffer 'cSrc', of size 'cSrcSize', - into already allocated destination buffer 'dst', of size 'dstCapacity'. - @return : size of regenerated data (<= maxDstSize), - or an error code, which can be tested using FSE_isError() . - - ** Important ** : FSE_decompress() does not decompress non-compressible nor RLE data !!! - Why ? : making this distinction requires a header. - Header management is intentionally delegated to the user layer, which can better manage special cases. -*/ -FSE_PUBLIC_API size_t FSE_decompress(void* dst, size_t dstCapacity, - const void* cSrc, size_t cSrcSize); - - /*-***************************************** * Tool functions ******************************************/ @@ -91,20 +63,6 @@ FSE_PUBLIC_API unsigned FSE_isError(size_t code); /* tells if a return FSE_PUBLIC_API const char* FSE_getErrorName(size_t code); /* provides error code string (useful for debugging) */ -/*-***************************************** -* FSE advanced functions -******************************************/ -/*! FSE_compress2() : - Same as FSE_compress(), but allows the selection of 'maxSymbolValue' and 'tableLog' - Both parameters can be defined as '0' to mean : use default value - @return : size of compressed data - Special values : if return == 0, srcData is not compressible => Nothing is stored within cSrc !!! - if return == 1, srcData is a single byte symbol * srcSize times. Use RLE compression. - if FSE_isError(return), it's an error code. -*/ -FSE_PUBLIC_API size_t FSE_compress2 (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog); - - /*-***************************************** * FSE detailed API ******************************************/ @@ -164,8 +122,6 @@ FSE_PUBLIC_API size_t FSE_writeNCount (void* buffer, size_t bufferSize, /*! Constructor and Destructor of FSE_CTable. Note that FSE_CTable size depends on 'tableLog' and 'maxSymbolValue' */ typedef unsigned FSE_CTable; /* don't allocate that. It's only meant to be more restrictive than void* */ -FSE_PUBLIC_API FSE_CTable* FSE_createCTable (unsigned maxSymbolValue, unsigned tableLog); -FSE_PUBLIC_API void FSE_freeCTable (FSE_CTable* ct); /*! FSE_buildCTable(): Builds `ct`, which must be already allocated, using FSE_createCTable(). @@ -241,23 +197,7 @@ FSE_PUBLIC_API size_t FSE_readNCount_bmi2(short* normalizedCounter, unsigned* maxSymbolValuePtr, unsigned* tableLogPtr, const void* rBuffer, size_t rBuffSize, int bmi2); -/*! Constructor and Destructor of FSE_DTable. - Note that its size depends on 'tableLog' */ typedef unsigned FSE_DTable; /* don't allocate that. It's just a way to be more restrictive than void* */ -FSE_PUBLIC_API FSE_DTable* FSE_createDTable(unsigned tableLog); -FSE_PUBLIC_API void FSE_freeDTable(FSE_DTable* dt); - -/*! FSE_buildDTable(): - Builds 'dt', which must be already allocated, using FSE_createDTable(). - return : 0, or an errorCode, which can be tested using FSE_isError() */ -FSE_PUBLIC_API size_t FSE_buildDTable (FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog); - -/*! FSE_decompress_usingDTable(): - Decompress compressed source `cSrc` of size `cSrcSize` using `dt` - into `dst` which must be already allocated. - @return : size of regenerated data (necessarily <= `dstCapacity`), - or an errorCode, which can be tested using FSE_isError() */ -FSE_PUBLIC_API size_t FSE_decompress_usingDTable(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, const FSE_DTable* dt); /*! Tutorial : @@ -320,24 +260,15 @@ If there is an error, the function will return an error code, which can be teste unsigned FSE_optimalTableLog_internal(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, unsigned minus); /**< same as FSE_optimalTableLog(), which used `minus==2` */ -/* FSE_compress_wksp() : - * Same as FSE_compress2(), but using an externally allocated scratch buffer (`workSpace`). - * FSE_COMPRESS_WKSP_SIZE_U32() provides the minimum size required for `workSpace` as a table of FSE_CTable. - */ -#define FSE_COMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) ( FSE_CTABLE_SIZE_U32(maxTableLog, maxSymbolValue) + ((maxTableLog > 12) ? (1 << (maxTableLog - 2)) : 1024) ) -size_t FSE_compress_wksp (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize); - -size_t FSE_buildCTable_raw (FSE_CTable* ct, unsigned nbBits); -/**< build a fake FSE_CTable, designed for a flat distribution, where each symbol uses nbBits */ - size_t FSE_buildCTable_rle (FSE_CTable* ct, unsigned char symbolValue); /**< build a fake FSE_CTable, designed to compress always the same symbolValue */ /* FSE_buildCTable_wksp() : * Same as FSE_buildCTable(), but using an externally allocated scratch buffer (`workSpace`). * `wkspSize` must be >= `FSE_BUILD_CTABLE_WORKSPACE_SIZE_U32(maxSymbolValue, tableLog)` of `unsigned`. + * See FSE_buildCTable_wksp() for breakdown of workspace usage. */ -#define FSE_BUILD_CTABLE_WORKSPACE_SIZE_U32(maxSymbolValue, tableLog) (maxSymbolValue + 2 + (1ull << (tableLog - 2))) +#define FSE_BUILD_CTABLE_WORKSPACE_SIZE_U32(maxSymbolValue, tableLog) (((maxSymbolValue + 2) + (1ull << (tableLog)))/2 + sizeof(U64)/sizeof(U32) /* additional 8 bytes for potential table overwrite */) #define FSE_BUILD_CTABLE_WORKSPACE_SIZE(maxSymbolValue, tableLog) (sizeof(unsigned) * FSE_BUILD_CTABLE_WORKSPACE_SIZE_U32(maxSymbolValue, tableLog)) size_t FSE_buildCTable_wksp(FSE_CTable* ct, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize); @@ -346,19 +277,11 @@ size_t FSE_buildCTable_wksp(FSE_CTable* ct, const short* normalizedCounter, unsi FSE_PUBLIC_API size_t FSE_buildDTable_wksp(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize); /**< Same as FSE_buildDTable(), using an externally allocated `workspace` produced with `FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxSymbolValue)` */ -size_t FSE_buildDTable_raw (FSE_DTable* dt, unsigned nbBits); -/**< build a fake FSE_DTable, designed to read a flat distribution where each symbol uses nbBits */ - -size_t FSE_buildDTable_rle (FSE_DTable* dt, unsigned char symbolValue); -/**< build a fake FSE_DTable, designed to always generate the same symbolValue */ - -#define FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) (FSE_DTABLE_SIZE_U32(maxTableLog) + FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) + (FSE_MAX_SYMBOL_VALUE + 1) / 2 + 1) +#define FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) (FSE_DTABLE_SIZE_U32(maxTableLog) + 1 + FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) + (FSE_MAX_SYMBOL_VALUE + 1) / 2 + 1) #define FSE_DECOMPRESS_WKSP_SIZE(maxTableLog, maxSymbolValue) (FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) * sizeof(unsigned)) -size_t FSE_decompress_wksp(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize); -/**< same as FSE_decompress(), using an externally allocated `workSpace` produced with `FSE_DECOMPRESS_WKSP_SIZE_U32(maxLog, maxSymbolValue)` */ - size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2); -/**< Same as FSE_decompress_wksp() but with dynamic BMI2 support. Pass 1 if your CPU supports BMI2 or 0 if it doesn't. */ +/**< same as FSE_decompress(), using an externally allocated `workSpace` produced with `FSE_DECOMPRESS_WKSP_SIZE_U32(maxLog, maxSymbolValue)`. + * Set bmi2 to 1 if your CPU supports BMI2 or 0 if it doesn't */ typedef enum { FSE_repeat_none, /**< Cannot use the previous table */ @@ -554,7 +477,7 @@ MEM_STATIC void FSE_flushCState(BIT_CStream_t* bitC, const FSE_CState_t* statePt /* FSE_getMaxNbBits() : * Approximate maximum cost of a symbol, in bits. - * Fractional get rounded up (i.e : a symbol with a normalized frequency of 3 gives the same result as a frequency of 2) + * Fractional get rounded up (i.e. a symbol with a normalized frequency of 3 gives the same result as a frequency of 2) * note 1 : assume symbolValue is valid (<= maxSymbolValue) * note 2 : if freq[symbolValue]==0, @return a fake cost of tableLog+1 bits */ MEM_STATIC U32 FSE_getMaxNbBits(const void* symbolTTPtr, U32 symbolValue) diff --git a/Utilities/cmzstd/lib/common/fse_decompress.c b/Utilities/cmzstd/lib/common/fse_decompress.c index f4ff58fa0ab..1e1c9f92d6b 100644 --- a/Utilities/cmzstd/lib/common/fse_decompress.c +++ b/Utilities/cmzstd/lib/common/fse_decompress.c @@ -1,6 +1,6 @@ /* ****************************************************************** * FSE : Finite State Entropy decoder - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -24,6 +24,7 @@ #include "error_private.h" #define ZSTD_DEPS_NEED_MALLOC #include "zstd_deps.h" +#include "bits.h" /* ZSTD_highbit32 */ /* ************************************************************** @@ -55,19 +56,6 @@ #define FSE_FUNCTION_NAME(X,Y) FSE_CAT(X,Y) #define FSE_TYPE_NAME(X,Y) FSE_CAT(X,Y) - -/* Function templates */ -FSE_DTable* FSE_createDTable (unsigned tableLog) -{ - if (tableLog > FSE_TABLELOG_ABSOLUTE_MAX) tableLog = FSE_TABLELOG_ABSOLUTE_MAX; - return (FSE_DTable*)ZSTD_malloc( FSE_DTABLE_SIZE_U32(tableLog) * sizeof (U32) ); -} - -void FSE_freeDTable (FSE_DTable* dt) -{ - ZSTD_free(dt); -} - static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize) { void* const tdPtr = dt+1; /* because *dt is unsigned, 32-bits aligned on 32-bits */ @@ -127,10 +115,10 @@ static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCo } } /* Now we spread those positions across the table. - * The benefit of doing it in two stages is that we avoid the the + * The benefit of doing it in two stages is that we avoid the * variable size inner loop, which caused lots of branch misses. * Now we can run through all the positions without any branch misses. - * We unroll the loop twice, since that is what emperically worked best. + * We unroll the loop twice, since that is what empirically worked best. */ { size_t position = 0; @@ -166,7 +154,7 @@ static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCo for (u=0; utableLog = 0; - DTableH->fastMode = 0; - - cell->newState = 0; - cell->symbol = symbolValue; - cell->nbBits = 0; - - return 0; -} - - -size_t FSE_buildDTable_raw (FSE_DTable* dt, unsigned nbBits) -{ - void* ptr = dt; - FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr; - void* dPtr = dt + 1; - FSE_decode_t* const dinfo = (FSE_decode_t*)dPtr; - const unsigned tableSize = 1 << nbBits; - const unsigned tableMask = tableSize - 1; - const unsigned maxSV1 = tableMask+1; - unsigned s; - - /* Sanity checks */ - if (nbBits < 1) return ERROR(GENERIC); /* min size */ - - /* Build Decoding Table */ - DTableH->tableLog = (U16)nbBits; - DTableH->fastMode = 1; - for (s=0; sfastMode; - - /* select fast mode (static) */ - if (fastMode) return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 1); - return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 0); -} - - -size_t FSE_decompress_wksp(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize) -{ - return FSE_decompress_wksp_bmi2(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, /* bmi2 */ 0); -} - typedef struct { short ncount[FSE_MAX_SYMBOL_VALUE + 1]; FSE_DTable dtable[1]; /* Dynamically sized */ @@ -342,7 +267,8 @@ FORCE_INLINE_TEMPLATE size_t FSE_decompress_wksp_body( } if (FSE_DECOMPRESS_WKSP_SIZE(tableLog, maxSymbolValue) > wkspSize) return ERROR(tableLog_tooLarge); - workSpace = wksp->dtable + FSE_DTABLE_SIZE_U32(tableLog); + assert(sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog) <= wkspSize); + workSpace = (BYTE*)workSpace + sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog); wkspSize -= sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog); CHECK_F( FSE_buildDTable_internal(wksp->dtable, wksp->ncount, maxSymbolValue, tableLog, workSpace, wkspSize) ); @@ -365,7 +291,7 @@ static size_t FSE_decompress_wksp_body_default(void* dst, size_t dstCapacity, co } #if DYNAMIC_BMI2 -TARGET_ATTRIBUTE("bmi2") static size_t FSE_decompress_wksp_body_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize) +BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize) { return FSE_decompress_wksp_body(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, 1); } @@ -382,22 +308,4 @@ size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, return FSE_decompress_wksp_body_default(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); } - -typedef FSE_DTable DTable_max_t[FSE_DTABLE_SIZE_U32(FSE_MAX_TABLELOG)]; - -#ifndef ZSTD_NO_UNUSED_FUNCTIONS -size_t FSE_buildDTable(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog) { - U32 wksp[FSE_BUILD_DTABLE_WKSP_SIZE_U32(FSE_TABLELOG_ABSOLUTE_MAX, FSE_MAX_SYMBOL_VALUE)]; - return FSE_buildDTable_wksp(dt, normalizedCounter, maxSymbolValue, tableLog, wksp, sizeof(wksp)); -} - -size_t FSE_decompress(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize) -{ - /* Static analyzer seems unable to understand this table will be properly initialized later */ - U32 wksp[FSE_DECOMPRESS_WKSP_SIZE_U32(FSE_MAX_TABLELOG, FSE_MAX_SYMBOL_VALUE)]; - return FSE_decompress_wksp(dst, dstCapacity, cSrc, cSrcSize, FSE_MAX_TABLELOG, wksp, sizeof(wksp)); -} -#endif - - #endif /* FSE_COMMONDEFS_ONLY */ diff --git a/Utilities/cmzstd/lib/common/huf.h b/Utilities/cmzstd/lib/common/huf.h index 3d47ced030c..73d1ee56543 100644 --- a/Utilities/cmzstd/lib/common/huf.h +++ b/Utilities/cmzstd/lib/common/huf.h @@ -1,7 +1,7 @@ /* ****************************************************************** * huff0 huffman codec, * part of Finite State Entropy library - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -21,106 +21,29 @@ extern "C" { /* *** Dependencies *** */ #include "zstd_deps.h" /* size_t */ - - -/* *** library symbols visibility *** */ -/* Note : when linking with -fvisibility=hidden on gcc, or by default on Visual, - * HUF symbols remain "private" (internal symbols for library only). - * Set macro FSE_DLL_EXPORT to 1 if you want HUF symbols visible on DLL interface */ -#if defined(FSE_DLL_EXPORT) && (FSE_DLL_EXPORT==1) && defined(__GNUC__) && (__GNUC__ >= 4) -# define HUF_PUBLIC_API __attribute__ ((visibility ("default"))) -#elif defined(FSE_DLL_EXPORT) && (FSE_DLL_EXPORT==1) /* Visual expected */ -# define HUF_PUBLIC_API __declspec(dllexport) -#elif defined(FSE_DLL_IMPORT) && (FSE_DLL_IMPORT==1) -# define HUF_PUBLIC_API __declspec(dllimport) /* not required, just to generate faster code (saves a function pointer load from IAT and an indirect jump) */ -#else -# define HUF_PUBLIC_API -#endif - - -/* ========================== */ -/* *** simple functions *** */ -/* ========================== */ - -/** HUF_compress() : - * Compress content from buffer 'src', of size 'srcSize', into buffer 'dst'. - * 'dst' buffer must be already allocated. - * Compression runs faster if `dstCapacity` >= HUF_compressBound(srcSize). - * `srcSize` must be <= `HUF_BLOCKSIZE_MAX` == 128 KB. - * @return : size of compressed data (<= `dstCapacity`). - * Special values : if return == 0, srcData is not compressible => Nothing is stored within dst !!! - * if HUF_isError(return), compression failed (more details using HUF_getErrorName()) - */ -HUF_PUBLIC_API size_t HUF_compress(void* dst, size_t dstCapacity, - const void* src, size_t srcSize); - -/** HUF_decompress() : - * Decompress HUF data from buffer 'cSrc', of size 'cSrcSize', - * into already allocated buffer 'dst', of minimum size 'dstSize'. - * `originalSize` : **must** be the ***exact*** size of original (uncompressed) data. - * Note : in contrast with FSE, HUF_decompress can regenerate - * RLE (cSrcSize==1) and uncompressed (cSrcSize==dstSize) data, - * because it knows size to regenerate (originalSize). - * @return : size of regenerated data (== originalSize), - * or an error code, which can be tested using HUF_isError() - */ -HUF_PUBLIC_API size_t HUF_decompress(void* dst, size_t originalSize, - const void* cSrc, size_t cSrcSize); +#include "mem.h" /* U32 */ +#define FSE_STATIC_LINKING_ONLY +#include "fse.h" /* *** Tool functions *** */ -#define HUF_BLOCKSIZE_MAX (128 * 1024) /**< maximum input size for a single block compressed with HUF_compress */ -HUF_PUBLIC_API size_t HUF_compressBound(size_t size); /**< maximum compressed size (worst case) */ +#define HUF_BLOCKSIZE_MAX (128 * 1024) /**< maximum input size for a single block compressed with HUF_compress */ +size_t HUF_compressBound(size_t size); /**< maximum compressed size (worst case) */ /* Error Management */ -HUF_PUBLIC_API unsigned HUF_isError(size_t code); /**< tells if a return value is an error code */ -HUF_PUBLIC_API const char* HUF_getErrorName(size_t code); /**< provides error code string (useful for debugging) */ - - -/* *** Advanced function *** */ - -/** HUF_compress2() : - * Same as HUF_compress(), but offers control over `maxSymbolValue` and `tableLog`. - * `maxSymbolValue` must be <= HUF_SYMBOLVALUE_MAX . - * `tableLog` must be `<= HUF_TABLELOG_MAX` . */ -HUF_PUBLIC_API size_t HUF_compress2 (void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - unsigned maxSymbolValue, unsigned tableLog); - -/** HUF_compress4X_wksp() : - * Same as HUF_compress2(), but uses externally allocated `workSpace`. - * `workspace` must have minimum alignment of 4, and be at least as large as HUF_WORKSPACE_SIZE */ -#define HUF_WORKSPACE_SIZE ((6 << 10) + 256) -#define HUF_WORKSPACE_SIZE_U32 (HUF_WORKSPACE_SIZE / sizeof(U32)) -HUF_PUBLIC_API size_t HUF_compress4X_wksp (void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - unsigned maxSymbolValue, unsigned tableLog, - void* workSpace, size_t wkspSize); - -#endif /* HUF_H_298734234 */ +unsigned HUF_isError(size_t code); /**< tells if a return value is an error code */ +const char* HUF_getErrorName(size_t code); /**< provides error code string (useful for debugging) */ -/* ****************************************************************** - * WARNING !! - * The following section contains advanced and experimental definitions - * which shall never be used in the context of a dynamic library, - * because they are not guaranteed to remain stable in the future. - * Only consider them in association with static linking. - * *****************************************************************/ -#if defined(HUF_STATIC_LINKING_ONLY) && !defined(HUF_H_HUF_STATIC_LINKING_ONLY) -#define HUF_H_HUF_STATIC_LINKING_ONLY - -/* *** Dependencies *** */ -#include "mem.h" /* U32 */ -#define FSE_STATIC_LINKING_ONLY -#include "fse.h" +#define HUF_WORKSPACE_SIZE ((8 << 10) + 512 /* sorting scratch space */) +#define HUF_WORKSPACE_SIZE_U64 (HUF_WORKSPACE_SIZE / sizeof(U64)) /* *** Constants *** */ -#define HUF_TABLELOG_MAX 12 /* max runtime value of tableLog (due to static allocation); can be modified up to HUF_ABSOLUTEMAX_TABLELOG */ +#define HUF_TABLELOG_MAX 12 /* max runtime value of tableLog (due to static allocation); can be modified up to HUF_TABLELOG_ABSOLUTEMAX */ #define HUF_TABLELOG_DEFAULT 11 /* default tableLog value when none specified */ #define HUF_SYMBOLVALUE_MAX 255 -#define HUF_TABLELOG_ABSOLUTEMAX 15 /* absolute limit of HUF_MAX_TABLELOG. Beyond that value, code does not work */ +#define HUF_TABLELOG_ABSOLUTEMAX 12 /* absolute limit of HUF_MAX_TABLELOG. Beyond that value, code does not work */ #if (HUF_TABLELOG_MAX > HUF_TABLELOG_ABSOLUTEMAX) # error "HUF_TABLELOG_MAX is too large !" #endif @@ -136,15 +59,11 @@ HUF_PUBLIC_API size_t HUF_compress4X_wksp (void* dst, size_t dstCapacity, /* static allocation of HUF's Compression Table */ /* this is a private definition, just exposed for allocation and strict aliasing purpose. never EVER access its members directly */ -struct HUF_CElt_s { - U16 val; - BYTE nbBits; -}; /* typedef'd to HUF_CElt */ -typedef struct HUF_CElt_s HUF_CElt; /* consider it an incomplete type */ -#define HUF_CTABLE_SIZE_U32(maxSymbolValue) ((maxSymbolValue)+1) /* Use tables of U32, for proper alignment */ -#define HUF_CTABLE_SIZE(maxSymbolValue) (HUF_CTABLE_SIZE_U32(maxSymbolValue) * sizeof(U32)) +typedef size_t HUF_CElt; /* consider it an incomplete type */ +#define HUF_CTABLE_SIZE_ST(maxSymbolValue) ((maxSymbolValue)+2) /* Use tables of size_t, for proper alignment */ +#define HUF_CTABLE_SIZE(maxSymbolValue) (HUF_CTABLE_SIZE_ST(maxSymbolValue) * sizeof(size_t)) #define HUF_CREATE_STATIC_CTABLE(name, maxSymbolValue) \ - HUF_CElt name[HUF_CTABLE_SIZE_U32(maxSymbolValue)] /* no final ; */ + HUF_CElt name[HUF_CTABLE_SIZE_ST(maxSymbolValue)] /* no final ; */ /* static allocation of HUF's DTable */ typedef U32 HUF_DTable; @@ -158,25 +77,49 @@ typedef U32 HUF_DTable; /* **************************************** * Advanced decompression functions ******************************************/ -size_t HUF_decompress4X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_decompress4X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ -#endif -size_t HUF_decompress4X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< decodes RLE and uncompressed */ -size_t HUF_decompress4X_hufOnly(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< considers RLE and uncompressed as errors */ -size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< considers RLE and uncompressed as errors */ -size_t HUF_decompress4X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ -size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< single-symbol decoder */ -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_decompress4X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ -size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< double-symbols decoder */ -#endif +/** + * Huffman flags bitset. + * For all flags, 0 is the default value. + */ +typedef enum { + /** + * If compiled with DYNAMIC_BMI2: Set flag only if the CPU supports BMI2 at runtime. + * Otherwise: Ignored. + */ + HUF_flags_bmi2 = (1 << 0), + /** + * If set: Test possible table depths to find the one that produces the smallest header + encoded size. + * If unset: Use heuristic to find the table depth. + */ + HUF_flags_optimalDepth = (1 << 1), + /** + * If set: If the previous table can encode the input, always reuse the previous table. + * If unset: If the previous table can encode the input, reuse the previous table if it results in a smaller output. + */ + HUF_flags_preferRepeat = (1 << 2), + /** + * If set: Sample the input and check if the sample is uncompressible, if it is then don't attempt to compress. + * If unset: Always histogram the entire input. + */ + HUF_flags_suspectUncompressible = (1 << 3), + /** + * If set: Don't use assembly implementations + * If unset: Allow using assembly implementations + */ + HUF_flags_disableAsm = (1 << 4), + /** + * If set: Don't use the fast decoding loop, always use the fallback decoding loop. + * If unset: Use the fast decoding loop when possible. + */ + HUF_flags_disableFast = (1 << 5) +} HUF_flags_e; /* **************************************** * HUF detailed API * ****************************************/ +#define HUF_OPTIMAL_DEPTH_THRESHOLD ZSTD_btultra /*! HUF_compress() does the following: * 1. count symbol occurrence from source[] into table count[] using FSE_count() (exposed within "fse.h") @@ -189,11 +132,12 @@ size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, * For example, it's possible to compress several blocks using the same 'CTable', * or to save and regenerate 'CTable' using external methods. */ -unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue); -size_t HUF_buildCTable (HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue, unsigned maxNbBits); /* @return : maxNbBits; CTable and count can overlap. In which case, CTable will overwrite count content */ -size_t HUF_writeCTable (void* dst, size_t maxDstSize, const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog); +unsigned HUF_minTableLog(unsigned symbolCardinality); +unsigned HUF_cardinality(const unsigned* count, unsigned maxSymbolValue); +unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, void* workSpace, + size_t wkspSize, HUF_CElt* table, const unsigned* count, int flags); /* table is used as scratch space for building and testing tables, not a return value */ size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog, void* workspace, size_t workspaceSize); -size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable); +size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags); size_t HUF_estimateCompressedSize(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue); int HUF_validateCTable(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue); @@ -202,22 +146,24 @@ typedef enum { HUF_repeat_check, /**< Can use the previous table but it must be checked. Note : The previous table must have been constructed by HUF_compress{1, 4}X_repeat */ HUF_repeat_valid /**< Can use the previous table and it is assumed to be valid */ } HUF_repeat; + /** HUF_compress4X_repeat() : * Same as HUF_compress4X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none. * If it uses hufTable it does not modify hufTable or repeat. * If it doesn't, it sets *repeat = HUF_repeat_none, and it sets hufTable to the table used. - * If preferRepeat then the old table will always be used if valid. */ + * If preferRepeat then the old table will always be used if valid. + * If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */ size_t HUF_compress4X_repeat(void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize, /**< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */ - HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2); + HUF_CElt* hufTable, HUF_repeat* repeat, int flags); /** HUF_buildCTable_wksp() : * Same as HUF_buildCTable(), but using externally allocated scratch buffer. * `workSpace` must be aligned on 4-bytes boundaries, and its size must be >= HUF_CTABLE_WORKSPACE_SIZE. */ -#define HUF_CTABLE_WORKSPACE_SIZE_U32 (2*HUF_SYMBOLVALUE_MAX +1 +1) +#define HUF_CTABLE_WORKSPACE_SIZE_U32 ((4 * (HUF_SYMBOLVALUE_MAX + 1)) + 192) #define HUF_CTABLE_WORKSPACE_SIZE (HUF_CTABLE_WORKSPACE_SIZE_U32 * sizeof(unsigned)) size_t HUF_buildCTable_wksp (HUF_CElt* tree, const unsigned* count, U32 maxSymbolValue, U32 maxNbBits, @@ -243,17 +189,16 @@ size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr, const void* src, size_t srcSize, void* workspace, size_t wkspSize, - int bmi2); + int flags); /** HUF_readCTable() : * Loading a CTable saved with HUF_writeCTable() */ size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void* src, size_t srcSize, unsigned *hasZeroWeights); -/** HUF_getNbBits() : +/** HUF_getNbBitsFromCTable() : * Read nbBits from CTable symbolTable, for symbol `symbolValue` presumed <= HUF_SYMBOLVALUE_MAX - * Note 1 : is not inlined, as HUF_CElt definition is private - * Note 2 : const void* used, so that it can provide a statically allocated table as argument (which uses type U32) */ -U32 HUF_getNbBits(const void* symbolTable, U32 symbolValue); + * Note 1 : is not inlined, as HUF_CElt definition is private */ +U32 HUF_getNbBitsFromCTable(const HUF_CElt* symbolTable, U32 symbolValue); /* * HUF_decompress() does the following: @@ -282,80 +227,46 @@ U32 HUF_selectDecoder (size_t dstSize, size_t cSrcSize); #define HUF_DECOMPRESS_WORKSPACE_SIZE ((2 << 10) + (1 << 9)) #define HUF_DECOMPRESS_WORKSPACE_SIZE_U32 (HUF_DECOMPRESS_WORKSPACE_SIZE / sizeof(U32)) -#ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_readDTableX1 (HUF_DTable* DTable, const void* src, size_t srcSize); -size_t HUF_readDTableX1_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); -#endif -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_readDTableX2 (HUF_DTable* DTable, const void* src, size_t srcSize); -size_t HUF_readDTableX2_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); -#endif - -size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); -#ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_decompress4X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); -#endif -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_decompress4X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); -#endif - /* ====================== */ /* single stream variants */ /* ====================== */ -size_t HUF_compress1X (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog); -size_t HUF_compress1X_wksp (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize); /**< `workSpace` must be a table of at least HUF_WORKSPACE_SIZE_U32 unsigned */ -size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable); +size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags); /** HUF_compress1X_repeat() : * Same as HUF_compress1X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none. * If it uses hufTable it does not modify hufTable or repeat. * If it doesn't, it sets *repeat = HUF_repeat_none, and it sets hufTable to the table used. - * If preferRepeat then the old table will always be used if valid. */ + * If preferRepeat then the old table will always be used if valid. + * If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */ size_t HUF_compress1X_repeat(void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize, /**< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */ - HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2); - -size_t HUF_decompress1X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* single-symbol decoder */ -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_decompress1X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* double-symbol decoder */ -#endif - -size_t HUF_decompress1X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); -size_t HUF_decompress1X_DCtx_wksp (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); -#ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_decompress1X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ -size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< single-symbol decoder */ -#endif -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_decompress1X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ -size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< double-symbols decoder */ -#endif + HUF_CElt* hufTable, HUF_repeat* repeat, int flags); -size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); /**< automatic selection of sing or double symbol decoder, based on DTable */ -#ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_decompress1X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); -#endif +size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); #ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_decompress1X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); +size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); /**< double-symbols decoder */ #endif /* BMI2 variants. * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0. */ -size_t HUF_decompress1X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); +size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags); #ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_decompress1X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); +size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); #endif -size_t HUF_decompress4X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); -size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); +size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags); +size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); #ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int bmi2); +size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags); +#endif +#ifndef HUF_FORCE_DECOMPRESS_X1 +size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags); #endif -#endif /* HUF_STATIC_LINKING_ONLY */ +#endif /* HUF_H_298734234 */ #if defined (__cplusplus) } diff --git a/Utilities/cmzstd/lib/common/mem.h b/Utilities/cmzstd/lib/common/mem.h index 9f3b81ab9d3..98dd47a0476 100644 --- a/Utilities/cmzstd/lib/common/mem.h +++ b/Utilities/cmzstd/lib/common/mem.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -51,6 +51,8 @@ extern "C" { # include /* intptr_t */ # endif typedef uint8_t BYTE; + typedef uint8_t U8; + typedef int8_t S8; typedef uint16_t U16; typedef int16_t S16; typedef uint32_t U32; @@ -63,6 +65,8 @@ extern "C" { # error "this implementation requires char to be exactly 8-bit type" #endif typedef unsigned char BYTE; + typedef unsigned char U8; + typedef signed char S8; #if USHRT_MAX != 65535 # error "this implementation requires short to be exactly 16-bit type" #endif @@ -129,21 +133,15 @@ MEM_STATIC size_t MEM_swapST(size_t in); /*-************************************************************** * Memory I/O Implementation *****************************************************************/ -/* MEM_FORCE_MEMORY_ACCESS : - * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. - * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. - * The below switch allow to select different access method for improved performance. - * Method 0 (default) : use `memcpy()`. Safe and portable. - * Method 1 : `__packed` statement. It depends on compiler extension (i.e., not portable). - * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. +/* MEM_FORCE_MEMORY_ACCESS : For accessing unaligned memory: + * Method 0 : always use `memcpy()`. Safe and portable. + * Method 1 : Use compiler extension to set unaligned access. * Method 2 : direct access. This method is portable but violate C standard. * It can generate buggy code on targets depending on alignment. - * In some circumstances, it's the only known way to get the most performance (i.e. GCC + ARMv6) - * See http://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. - * Prefer these methods in priority order (0 > 1 > 2) + * Default : method 1 if supported, else method 0 */ #ifndef MEM_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ -# if defined(__INTEL_COMPILER) || defined(__GNUC__) || defined(__ICCARM__) +# ifdef __GNUC__ # define MEM_FORCE_MEMORY_ACCESS 1 # endif #endif @@ -153,8 +151,22 @@ MEM_STATIC unsigned MEM_64bits(void) { return sizeof(size_t)==8; } MEM_STATIC unsigned MEM_isLittleEndian(void) { +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + return 1; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return 0; +#elif defined(__clang__) && __LITTLE_ENDIAN__ + return 1; +#elif defined(__clang__) && __BIG_ENDIAN__ + return 0; +#elif defined(_MSC_VER) && (_M_AMD64 || _M_IX86) + return 1; +#elif defined(__DMC__) && defined(_M_IX86) + return 1; +#else const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ return one.c[0]; +#endif } #if defined(MEM_FORCE_MEMORY_ACCESS) && (MEM_FORCE_MEMORY_ACCESS==2) @@ -172,30 +184,19 @@ MEM_STATIC void MEM_write64(void* memPtr, U64 value) { *(U64*)memPtr = value; } #elif defined(MEM_FORCE_MEMORY_ACCESS) && (MEM_FORCE_MEMORY_ACCESS==1) -/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ -/* currently only defined for gcc and icc */ -#if defined(_MSC_VER) || (defined(__INTEL_COMPILER) && defined(WIN32)) - __pragma( pack(push, 1) ) - typedef struct { U16 v; } unalign16; - typedef struct { U32 v; } unalign32; - typedef struct { U64 v; } unalign64; - typedef struct { size_t v; } unalignArch; - __pragma( pack(pop) ) -#else - typedef struct { U16 v; } __attribute__((packed)) unalign16; - typedef struct { U32 v; } __attribute__((packed)) unalign32; - typedef struct { U64 v; } __attribute__((packed)) unalign64; - typedef struct { size_t v; } __attribute__((packed)) unalignArch; -#endif +typedef __attribute__((aligned(1))) U16 unalign16; +typedef __attribute__((aligned(1))) U32 unalign32; +typedef __attribute__((aligned(1))) U64 unalign64; +typedef __attribute__((aligned(1))) size_t unalignArch; -MEM_STATIC U16 MEM_read16(const void* ptr) { return ((const unalign16*)ptr)->v; } -MEM_STATIC U32 MEM_read32(const void* ptr) { return ((const unalign32*)ptr)->v; } -MEM_STATIC U64 MEM_read64(const void* ptr) { return ((const unalign64*)ptr)->v; } -MEM_STATIC size_t MEM_readST(const void* ptr) { return ((const unalignArch*)ptr)->v; } +MEM_STATIC U16 MEM_read16(const void* ptr) { return *(const unalign16*)ptr; } +MEM_STATIC U32 MEM_read32(const void* ptr) { return *(const unalign32*)ptr; } +MEM_STATIC U64 MEM_read64(const void* ptr) { return *(const unalign64*)ptr; } +MEM_STATIC size_t MEM_readST(const void* ptr) { return *(const unalignArch*)ptr; } -MEM_STATIC void MEM_write16(void* memPtr, U16 value) { ((unalign16*)memPtr)->v = value; } -MEM_STATIC void MEM_write32(void* memPtr, U32 value) { ((unalign32*)memPtr)->v = value; } -MEM_STATIC void MEM_write64(void* memPtr, U64 value) { ((unalign64*)memPtr)->v = value; } +MEM_STATIC void MEM_write16(void* memPtr, U16 value) { *(unalign16*)memPtr = value; } +MEM_STATIC void MEM_write32(void* memPtr, U32 value) { *(unalign32*)memPtr = value; } +MEM_STATIC void MEM_write64(void* memPtr, U64 value) { *(unalign64*)memPtr = value; } #else @@ -239,6 +240,14 @@ MEM_STATIC void MEM_write64(void* memPtr, U64 value) #endif /* MEM_FORCE_MEMORY_ACCESS */ +MEM_STATIC U32 MEM_swap32_fallback(U32 in) +{ + return ((in << 24) & 0xff000000 ) | + ((in << 8) & 0x00ff0000 ) | + ((in >> 8) & 0x0000ff00 ) | + ((in >> 24) & 0x000000ff ); +} + MEM_STATIC U32 MEM_swap32(U32 in) { #if defined(_MSC_VER) /* Visual Studio */ @@ -247,22 +256,13 @@ MEM_STATIC U32 MEM_swap32(U32 in) || (defined(__clang__) && __has_builtin(__builtin_bswap32)) return __builtin_bswap32(in); #else - return ((in << 24) & 0xff000000 ) | - ((in << 8) & 0x00ff0000 ) | - ((in >> 8) & 0x0000ff00 ) | - ((in >> 24) & 0x000000ff ); + return MEM_swap32_fallback(in); #endif } -MEM_STATIC U64 MEM_swap64(U64 in) +MEM_STATIC U64 MEM_swap64_fallback(U64 in) { -#if defined(_MSC_VER) /* Visual Studio */ - return _byteswap_uint64(in); -#elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \ - || (defined(__clang__) && __has_builtin(__builtin_bswap64)) - return __builtin_bswap64(in); -#else - return ((in << 56) & 0xff00000000000000ULL) | + return ((in << 56) & 0xff00000000000000ULL) | ((in << 40) & 0x00ff000000000000ULL) | ((in << 24) & 0x0000ff0000000000ULL) | ((in << 8) & 0x000000ff00000000ULL) | @@ -270,6 +270,17 @@ MEM_STATIC U64 MEM_swap64(U64 in) ((in >> 24) & 0x0000000000ff0000ULL) | ((in >> 40) & 0x000000000000ff00ULL) | ((in >> 56) & 0x00000000000000ffULL); +} + +MEM_STATIC U64 MEM_swap64(U64 in) +{ +#if defined(_MSC_VER) /* Visual Studio */ + return _byteswap_uint64(in); +#elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \ + || (defined(__clang__) && __has_builtin(__builtin_bswap64)) + return __builtin_bswap64(in); +#else + return MEM_swap64_fallback(in); #endif } diff --git a/Utilities/cmzstd/lib/common/pool.c b/Utilities/cmzstd/lib/common/pool.c index ea70b8b65ad..d5ca5a7808a 100644 --- a/Utilities/cmzstd/lib/common/pool.c +++ b/Utilities/cmzstd/lib/common/pool.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -10,9 +10,9 @@ /* ====== Dependencies ======= */ +#include "../common/allocations.h" /* ZSTD_customCalloc, ZSTD_customFree */ #include "zstd_deps.h" /* size_t */ #include "debug.h" /* assert */ -#include "zstd_internal.h" /* ZSTD_customMalloc, ZSTD_customFree */ #include "pool.h" /* ====== Compiler specifics ====== */ @@ -86,7 +86,7 @@ static void* POOL_thread(void* opaque) { { POOL_job const job = ctx->queue[ctx->queueHead]; ctx->queueHead = (ctx->queueHead + 1) % ctx->queueSize; ctx->numThreadsBusy++; - ctx->queueEmpty = ctx->queueHead == ctx->queueTail; + ctx->queueEmpty = (ctx->queueHead == ctx->queueTail); /* Unlock the mutex, signal a pusher, and run the job */ ZSTD_pthread_cond_signal(&ctx->queuePushCond); ZSTD_pthread_mutex_unlock(&ctx->queueMutex); @@ -96,15 +96,14 @@ static void* POOL_thread(void* opaque) { /* If the intended queue size was 0, signal after finishing job */ ZSTD_pthread_mutex_lock(&ctx->queueMutex); ctx->numThreadsBusy--; - if (ctx->queueSize == 1) { - ZSTD_pthread_cond_signal(&ctx->queuePushCond); - } + ZSTD_pthread_cond_signal(&ctx->queuePushCond); ZSTD_pthread_mutex_unlock(&ctx->queueMutex); } } /* for (;;) */ assert(0); /* Unreachable */ } +/* ZSTD_createThreadPool() : public access point */ POOL_ctx* ZSTD_createThreadPool(size_t numThreads) { return POOL_create (numThreads, 0); } @@ -114,7 +113,8 @@ POOL_ctx* POOL_create(size_t numThreads, size_t queueSize) { } POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize, - ZSTD_customMem customMem) { + ZSTD_customMem customMem) +{ POOL_ctx* ctx; /* Check parameters */ if (!numThreads) { return NULL; } @@ -126,7 +126,7 @@ POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize, * empty and full queues. */ ctx->queueSize = queueSize + 1; - ctx->queue = (POOL_job*)ZSTD_customMalloc(ctx->queueSize * sizeof(POOL_job), customMem); + ctx->queue = (POOL_job*)ZSTD_customCalloc(ctx->queueSize * sizeof(POOL_job), customMem); ctx->queueHead = 0; ctx->queueTail = 0; ctx->numThreadsBusy = 0; @@ -140,7 +140,7 @@ POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize, } ctx->shutdown = 0; /* Allocate space for the thread handles */ - ctx->threads = (ZSTD_pthread_t*)ZSTD_customMalloc(numThreads * sizeof(ZSTD_pthread_t), customMem); + ctx->threads = (ZSTD_pthread_t*)ZSTD_customCalloc(numThreads * sizeof(ZSTD_pthread_t), customMem); ctx->threadCapacity = 0; ctx->customMem = customMem; /* Check for errors */ @@ -173,7 +173,7 @@ static void POOL_join(POOL_ctx* ctx) { /* Join all of the threads */ { size_t i; for (i = 0; i < ctx->threadCapacity; ++i) { - ZSTD_pthread_join(ctx->threads[i], NULL); /* note : could fail */ + ZSTD_pthread_join(ctx->threads[i]); /* note : could fail */ } } } @@ -188,11 +188,22 @@ void POOL_free(POOL_ctx *ctx) { ZSTD_customFree(ctx, ctx->customMem); } +/*! POOL_joinJobs() : + * Waits for all queued jobs to finish executing. + */ +void POOL_joinJobs(POOL_ctx* ctx) { + ZSTD_pthread_mutex_lock(&ctx->queueMutex); + while(!ctx->queueEmpty || ctx->numThreadsBusy > 0) { + ZSTD_pthread_cond_wait(&ctx->queuePushCond, &ctx->queueMutex); + } + ZSTD_pthread_mutex_unlock(&ctx->queueMutex); +} + void ZSTD_freeThreadPool (ZSTD_threadPool* pool) { POOL_free (pool); } -size_t POOL_sizeof(POOL_ctx *ctx) { +size_t POOL_sizeof(const POOL_ctx* ctx) { if (ctx==NULL) return 0; /* supports sizeof NULL */ return sizeof(*ctx) + ctx->queueSize * sizeof(POOL_job) @@ -209,7 +220,7 @@ static int POOL_resize_internal(POOL_ctx* ctx, size_t numThreads) return 0; } /* numThreads > threadCapacity */ - { ZSTD_pthread_t* const threadPool = (ZSTD_pthread_t*)ZSTD_customMalloc(numThreads * sizeof(ZSTD_pthread_t), ctx->customMem); + { ZSTD_pthread_t* const threadPool = (ZSTD_pthread_t*)ZSTD_customCalloc(numThreads * sizeof(ZSTD_pthread_t), ctx->customMem); if (!threadPool) return 1; /* replace existing thread pool */ ZSTD_memcpy(threadPool, ctx->threads, ctx->threadCapacity * sizeof(*threadPool)); @@ -257,9 +268,12 @@ static int isQueueFull(POOL_ctx const* ctx) { } -static void POOL_add_internal(POOL_ctx* ctx, POOL_function function, void *opaque) +static void +POOL_add_internal(POOL_ctx* ctx, POOL_function function, void *opaque) { - POOL_job const job = {function, opaque}; + POOL_job job; + job.function = function; + job.opaque = opaque; assert(ctx != NULL); if (ctx->shutdown) return; @@ -313,7 +327,9 @@ POOL_ctx* POOL_create(size_t numThreads, size_t queueSize) { return POOL_create_advanced(numThreads, queueSize, ZSTD_defaultCMem); } -POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize, ZSTD_customMem customMem) { +POOL_ctx* +POOL_create_advanced(size_t numThreads, size_t queueSize, ZSTD_customMem customMem) +{ (void)numThreads; (void)queueSize; (void)customMem; @@ -325,6 +341,11 @@ void POOL_free(POOL_ctx* ctx) { (void)ctx; } +void POOL_joinJobs(POOL_ctx* ctx){ + assert(!ctx || ctx == &g_poolCtx); + (void)ctx; +} + int POOL_resize(POOL_ctx* ctx, size_t numThreads) { (void)ctx; (void)numThreads; return 0; @@ -341,7 +362,7 @@ int POOL_tryAdd(POOL_ctx* ctx, POOL_function function, void* opaque) { return 1; } -size_t POOL_sizeof(POOL_ctx* ctx) { +size_t POOL_sizeof(const POOL_ctx* ctx) { if (ctx==NULL) return 0; /* supports sizeof NULL */ assert(ctx == &g_poolCtx); return sizeof(*ctx); diff --git a/Utilities/cmzstd/lib/common/pool.h b/Utilities/cmzstd/lib/common/pool.h index e18aa0708f7..eb22ff509f5 100644 --- a/Utilities/cmzstd/lib/common/pool.h +++ b/Utilities/cmzstd/lib/common/pool.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -38,6 +38,12 @@ POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize, */ void POOL_free(POOL_ctx* ctx); + +/*! POOL_joinJobs() : + * Waits for all queued jobs to finish executing. + */ +void POOL_joinJobs(POOL_ctx* ctx); + /*! POOL_resize() : * Expands or shrinks pool's number of threads. * This is more efficient than releasing + creating a new context, @@ -53,7 +59,7 @@ int POOL_resize(POOL_ctx* ctx, size_t numThreads); * @return threadpool memory usage * note : compatible with NULL (returns 0 in this case) */ -size_t POOL_sizeof(POOL_ctx* ctx); +size_t POOL_sizeof(const POOL_ctx* ctx); /*! POOL_function : * The function type that can be added to a thread pool. @@ -70,7 +76,7 @@ void POOL_add(POOL_ctx* ctx, POOL_function function, void* opaque); /*! POOL_tryAdd() : - * Add the job `function(opaque)` to thread pool _if_ a worker is available. + * Add the job `function(opaque)` to thread pool _if_ a queue slot is available. * Returns immediately even if not (does not block). * @return : 1 if successful, 0 if not. */ diff --git a/Utilities/cmzstd/lib/common/portability_macros.h b/Utilities/cmzstd/lib/common/portability_macros.h new file mode 100644 index 00000000000..8fd6ea82d19 --- /dev/null +++ b/Utilities/cmzstd/lib/common/portability_macros.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef ZSTD_PORTABILITY_MACROS_H +#define ZSTD_PORTABILITY_MACROS_H + +/** + * This header file contains macro definitions to support portability. + * This header is shared between C and ASM code, so it MUST only + * contain macro definitions. It MUST not contain any C code. + * + * This header ONLY defines macros to detect platforms/feature support. + * + */ + + +/* compat. with non-clang compilers */ +#ifndef __has_attribute + #define __has_attribute(x) 0 +#endif + +/* compat. with non-clang compilers */ +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +/* compat. with non-clang compilers */ +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +/* detects whether we are being compiled under msan */ +#ifndef ZSTD_MEMORY_SANITIZER +# if __has_feature(memory_sanitizer) +# define ZSTD_MEMORY_SANITIZER 1 +# else +# define ZSTD_MEMORY_SANITIZER 0 +# endif +#endif + +/* detects whether we are being compiled under asan */ +#ifndef ZSTD_ADDRESS_SANITIZER +# if __has_feature(address_sanitizer) +# define ZSTD_ADDRESS_SANITIZER 1 +# elif defined(__SANITIZE_ADDRESS__) +# define ZSTD_ADDRESS_SANITIZER 1 +# else +# define ZSTD_ADDRESS_SANITIZER 0 +# endif +#endif + +/* detects whether we are being compiled under dfsan */ +#ifndef ZSTD_DATAFLOW_SANITIZER +# if __has_feature(dataflow_sanitizer) +# define ZSTD_DATAFLOW_SANITIZER 1 +# else +# define ZSTD_DATAFLOW_SANITIZER 0 +# endif +#endif + +/* Mark the internal assembly functions as hidden */ +#ifdef __ELF__ +# define ZSTD_HIDE_ASM_FUNCTION(func) .hidden func +#else +# define ZSTD_HIDE_ASM_FUNCTION(func) +#endif + +/* Enable runtime BMI2 dispatch based on the CPU. + * Enabled for clang & gcc >=4.8 on x86 when BMI2 isn't enabled by default. + */ +#ifndef DYNAMIC_BMI2 + #if ((defined(__clang__) && __has_attribute(__target__)) \ + || (defined(__GNUC__) \ + && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)))) \ + && (defined(__x86_64__) || defined(_M_X64)) \ + && !defined(__BMI2__) + # define DYNAMIC_BMI2 1 + #else + # define DYNAMIC_BMI2 0 + #endif +#endif + +/** + * Only enable assembly for GNUC compatible compilers, + * because other platforms may not support GAS assembly syntax. + * + * Only enable assembly for Linux / MacOS, other platforms may + * work, but they haven't been tested. This could likely be + * extended to BSD systems. + * + * Disable assembly when MSAN is enabled, because MSAN requires + * 100% of code to be instrumented to work. + */ +#if defined(__GNUC__) +# if defined(__linux__) || defined(__linux) || defined(__APPLE__) +# if ZSTD_MEMORY_SANITIZER +# define ZSTD_ASM_SUPPORTED 0 +# elif ZSTD_DATAFLOW_SANITIZER +# define ZSTD_ASM_SUPPORTED 0 +# else +# define ZSTD_ASM_SUPPORTED 1 +# endif +# else +# define ZSTD_ASM_SUPPORTED 0 +# endif +#else +# define ZSTD_ASM_SUPPORTED 0 +#endif + +/** + * Determines whether we should enable assembly for x86-64 + * with BMI2. + * + * Enable if all of the following conditions hold: + * - ASM hasn't been explicitly disabled by defining ZSTD_DISABLE_ASM + * - Assembly is supported + * - We are compiling for x86-64 and either: + * - DYNAMIC_BMI2 is enabled + * - BMI2 is supported at compile time + */ +#if !defined(ZSTD_DISABLE_ASM) && \ + ZSTD_ASM_SUPPORTED && \ + defined(__x86_64__) && \ + (DYNAMIC_BMI2 || defined(__BMI2__)) +# define ZSTD_ENABLE_ASM_X86_64_BMI2 1 +#else +# define ZSTD_ENABLE_ASM_X86_64_BMI2 0 +#endif + +/* + * For x86 ELF targets, add .note.gnu.property section for Intel CET in + * assembly sources when CET is enabled. + * + * Additionally, any function that may be called indirectly must begin + * with ZSTD_CET_ENDBRANCH. + */ +#if defined(__ELF__) && (defined(__x86_64__) || defined(__i386__)) \ + && defined(__has_include) +# if __has_include() +# include +# define ZSTD_CET_ENDBRANCH _CET_ENDBR +# endif +#endif + +#ifndef ZSTD_CET_ENDBRANCH +# define ZSTD_CET_ENDBRANCH +#endif + +#endif /* ZSTD_PORTABILITY_MACROS_H */ diff --git a/Utilities/cmzstd/lib/common/threading.c b/Utilities/cmzstd/lib/common/threading.c index 92cf57c195a..ca155b9b9db 100644 --- a/Utilities/cmzstd/lib/common/threading.c +++ b/Utilities/cmzstd/lib/common/threading.c @@ -23,8 +23,7 @@ int g_ZSTD_threading_useless_symbol; #if defined(ZSTD_MULTITHREAD) && defined(_WIN32) /** - * Windows minimalist Pthread Wrapper, based on : - * http://www.cse.wustl.edu/~schmidt/win32-cv-1.html + * Windows minimalist Pthread Wrapper */ @@ -35,37 +34,92 @@ int g_ZSTD_threading_useless_symbol; /* === Implementation === */ +typedef struct { + void* (*start_routine)(void*); + void* arg; + int initialized; + ZSTD_pthread_cond_t initialized_cond; + ZSTD_pthread_mutex_t initialized_mutex; +} ZSTD_thread_params_t; + static unsigned __stdcall worker(void *arg) { - ZSTD_pthread_t* const thread = (ZSTD_pthread_t*) arg; - thread->arg = thread->start_routine(thread->arg); + void* (*start_routine)(void*); + void* thread_arg; + + /* Initialized thread_arg and start_routine and signal main thread that we don't need it + * to wait any longer. + */ + { + ZSTD_thread_params_t* thread_param = (ZSTD_thread_params_t*)arg; + thread_arg = thread_param->arg; + start_routine = thread_param->start_routine; + + /* Signal main thread that we are running and do not depend on its memory anymore */ + ZSTD_pthread_mutex_lock(&thread_param->initialized_mutex); + thread_param->initialized = 1; + ZSTD_pthread_cond_signal(&thread_param->initialized_cond); + ZSTD_pthread_mutex_unlock(&thread_param->initialized_mutex); + } + + start_routine(thread_arg); + return 0; } int ZSTD_pthread_create(ZSTD_pthread_t* thread, const void* unused, void* (*start_routine) (void*), void* arg) { + ZSTD_thread_params_t thread_param; (void)unused; - thread->arg = arg; - thread->start_routine = start_routine; - thread->handle = (HANDLE) _beginthreadex(NULL, 0, worker, thread, 0, NULL); - if (!thread->handle) + thread_param.start_routine = start_routine; + thread_param.arg = arg; + thread_param.initialized = 0; + *thread = NULL; + + /* Setup thread initialization synchronization */ + if(ZSTD_pthread_cond_init(&thread_param.initialized_cond, NULL)) { + /* Should never happen on Windows */ + return -1; + } + if(ZSTD_pthread_mutex_init(&thread_param.initialized_mutex, NULL)) { + /* Should never happen on Windows */ + ZSTD_pthread_cond_destroy(&thread_param.initialized_cond); + return -1; + } + + /* Spawn thread */ + *thread = (HANDLE)_beginthreadex(NULL, 0, worker, &thread_param, 0, NULL); + if (!thread) { + ZSTD_pthread_mutex_destroy(&thread_param.initialized_mutex); + ZSTD_pthread_cond_destroy(&thread_param.initialized_cond); return errno; - else - return 0; + } + + /* Wait for thread to be initialized */ + ZSTD_pthread_mutex_lock(&thread_param.initialized_mutex); + while(!thread_param.initialized) { + ZSTD_pthread_cond_wait(&thread_param.initialized_cond, &thread_param.initialized_mutex); + } + ZSTD_pthread_mutex_unlock(&thread_param.initialized_mutex); + ZSTD_pthread_mutex_destroy(&thread_param.initialized_mutex); + ZSTD_pthread_cond_destroy(&thread_param.initialized_cond); + + return 0; } -int ZSTD_pthread_join(ZSTD_pthread_t thread, void **value_ptr) +int ZSTD_pthread_join(ZSTD_pthread_t thread) { DWORD result; - if (!thread.handle) return 0; + if (!thread) return 0; + + result = WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); - result = WaitForSingleObject(thread.handle, INFINITE); switch (result) { case WAIT_OBJECT_0: - if (value_ptr) *value_ptr = thread.arg; return 0; case WAIT_ABANDONED: return EINVAL; diff --git a/Utilities/cmzstd/lib/common/threading.h b/Utilities/cmzstd/lib/common/threading.h index fd0060d5aa2..fb5c1c87873 100644 --- a/Utilities/cmzstd/lib/common/threading.h +++ b/Utilities/cmzstd/lib/common/threading.h @@ -23,8 +23,7 @@ extern "C" { #if defined(ZSTD_MULTITHREAD) && defined(_WIN32) /** - * Windows minimalist Pthread Wrapper, based on : - * http://www.cse.wustl.edu/~schmidt/win32-cv-1.html + * Windows minimalist Pthread Wrapper */ #ifdef WINVER # undef WINVER @@ -62,16 +61,12 @@ extern "C" { #define ZSTD_pthread_cond_broadcast(a) WakeAllConditionVariable((a)) /* ZSTD_pthread_create() and ZSTD_pthread_join() */ -typedef struct { - HANDLE handle; - void* (*start_routine)(void*); - void* arg; -} ZSTD_pthread_t; +typedef HANDLE ZSTD_pthread_t; int ZSTD_pthread_create(ZSTD_pthread_t* thread, const void* unused, void* (*start_routine) (void*), void* arg); -int ZSTD_pthread_join(ZSTD_pthread_t thread, void** value_ptr); +int ZSTD_pthread_join(ZSTD_pthread_t thread); /** * add here more wrappers as required @@ -99,7 +94,7 @@ int ZSTD_pthread_join(ZSTD_pthread_t thread, void** value_ptr); #define ZSTD_pthread_t pthread_t #define ZSTD_pthread_create(a, b, c, d) pthread_create((a), (b), (c), (d)) -#define ZSTD_pthread_join(a, b) pthread_join((a),(b)) +#define ZSTD_pthread_join(a) pthread_join((a),NULL) #else /* DEBUGLEVEL >= 1 */ @@ -124,7 +119,7 @@ int ZSTD_pthread_cond_destroy(ZSTD_pthread_cond_t* cond); #define ZSTD_pthread_t pthread_t #define ZSTD_pthread_create(a, b, c, d) pthread_create((a), (b), (c), (d)) -#define ZSTD_pthread_join(a, b) pthread_join((a),(b)) +#define ZSTD_pthread_join(a) pthread_join((a),NULL) #endif diff --git a/Utilities/cmzstd/lib/common/xxhash.c b/Utilities/cmzstd/lib/common/xxhash.c index 926b33604ef..fd237c9062a 100644 --- a/Utilities/cmzstd/lib/common/xxhash.c +++ b/Utilities/cmzstd/lib/common/xxhash.c @@ -1,11 +1,11 @@ /* * xxHash - Fast Hash algorithm - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : - * - xxHash homepage: http://www.xxhash.com + * - xxHash homepage: https://cyan4973.github.io/xxHash/ * - xxHash source repository : https://github.com/Cyan4973/xxHash - * + * * This source code is licensed under both the BSD-style license (found in the * LICENSE file in the root directory of this source tree) and the GPLv2 (found * in the COPYING file in the root directory of this source tree). @@ -13,812 +13,12 @@ */ -/* ************************************* -* Tuning parameters -***************************************/ -/*!XXH_FORCE_MEMORY_ACCESS : - * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. - * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. - * The below switch allow to select different access method for improved performance. - * Method 0 (default) : use `memcpy()`. Safe and portable. - * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). - * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. - * Method 2 : direct access. This method doesn't depend on compiler but violate C standard. - * It can generate buggy code on targets which do not support unaligned memory accesses. - * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) - * See http://stackoverflow.com/a/32095106/646947 for details. - * Prefer these methods in priority order (0 > 1 > 2) - */ -#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ -# if (defined(__INTEL_COMPILER) && !defined(WIN32)) || \ - (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) )) || \ - defined(__ICCARM__) -# define XXH_FORCE_MEMORY_ACCESS 1 -# endif -#endif - -/*!XXH_ACCEPT_NULL_INPUT_POINTER : - * If the input pointer is a null pointer, xxHash default behavior is to trigger a memory access error, since it is a bad pointer. - * When this option is enabled, xxHash output for null input pointers will be the same as a null-length input. - * By default, this option is disabled. To enable it, uncomment below define : - */ -/* #define XXH_ACCEPT_NULL_INPUT_POINTER 1 */ - -/*!XXH_FORCE_NATIVE_FORMAT : - * By default, xxHash library provides endian-independent Hash values, based on little-endian convention. - * Results are therefore identical for little-endian and big-endian CPU. - * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. - * Should endian-independence be of no importance for your application, you may set the #define below to 1, - * to improve speed for Big-endian CPU. - * This option has no impact on Little_Endian CPU. - */ -#ifndef XXH_FORCE_NATIVE_FORMAT /* can be defined externally */ -# define XXH_FORCE_NATIVE_FORMAT 0 -#endif -/*!XXH_FORCE_ALIGN_CHECK : - * This is a minor performance trick, only useful with lots of very small keys. - * It means : check for aligned/unaligned input. - * The check costs one initial branch per hash; set to 0 when the input data - * is guaranteed to be aligned. +/* + * xxhash.c instantiates functions defined in xxhash.h */ -#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ -# if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) -# define XXH_FORCE_ALIGN_CHECK 0 -# else -# define XXH_FORCE_ALIGN_CHECK 1 -# endif -#endif - -/* ************************************* -* Includes & Memory related functions -***************************************/ -/* Modify the local functions below should you wish to use some other memory routines */ -/* for ZSTD_malloc(), ZSTD_free() */ -#define ZSTD_DEPS_NEED_MALLOC -#include "zstd_deps.h" /* size_t, ZSTD_malloc, ZSTD_free, ZSTD_memcpy */ -static void* XXH_malloc(size_t s) { return ZSTD_malloc(s); } -static void XXH_free (void* p) { ZSTD_free(p); } -static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_memcpy(dest,src,size); } +#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */ +#define XXH_IMPLEMENTATION /* access definitions */ -#ifndef XXH_STATIC_LINKING_ONLY -# define XXH_STATIC_LINKING_ONLY -#endif #include "xxhash.h" - - -/* ************************************* -* Compiler Specific Options -***************************************/ -#include "compiler.h" - - -/* ************************************* -* Basic Types -***************************************/ -#include "mem.h" /* BYTE, U32, U64, size_t */ - -#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) - -/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ -static U32 XXH_read32(const void* memPtr) { return *(const U32*) memPtr; } -static U64 XXH_read64(const void* memPtr) { return *(const U64*) memPtr; } - -#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) - -/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ -/* currently only defined for gcc and icc */ -typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign; - -static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } -static U64 XXH_read64(const void* ptr) { return ((const unalign*)ptr)->u64; } - -#else - -/* portable and safe solution. Generally efficient. - * see : http://stackoverflow.com/a/32095106/646947 - */ - -static U32 XXH_read32(const void* memPtr) -{ - U32 val; - ZSTD_memcpy(&val, memPtr, sizeof(val)); - return val; -} - -static U64 XXH_read64(const void* memPtr) -{ - U64 val; - ZSTD_memcpy(&val, memPtr, sizeof(val)); - return val; -} - -#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ - - -/* **************************************** -* Compiler-specific Functions and Macros -******************************************/ -#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) - -/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */ -#if defined(_MSC_VER) -# define XXH_rotl32(x,r) _rotl(x,r) -# define XXH_rotl64(x,r) _rotl64(x,r) -#else -#if defined(__ICCARM__) -# include -# define XXH_rotl32(x,r) __ROR(x,(32 - r)) -#else -# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r))) -#endif -# define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r))) -#endif - -#if defined(_MSC_VER) /* Visual Studio */ -# define XXH_swap32 _byteswap_ulong -# define XXH_swap64 _byteswap_uint64 -#elif GCC_VERSION >= 403 -# define XXH_swap32 __builtin_bswap32 -# define XXH_swap64 __builtin_bswap64 -#else -static U32 XXH_swap32 (U32 x) -{ - return ((x << 24) & 0xff000000 ) | - ((x << 8) & 0x00ff0000 ) | - ((x >> 8) & 0x0000ff00 ) | - ((x >> 24) & 0x000000ff ); -} -static U64 XXH_swap64 (U64 x) -{ - return ((x << 56) & 0xff00000000000000ULL) | - ((x << 40) & 0x00ff000000000000ULL) | - ((x << 24) & 0x0000ff0000000000ULL) | - ((x << 8) & 0x000000ff00000000ULL) | - ((x >> 8) & 0x00000000ff000000ULL) | - ((x >> 24) & 0x0000000000ff0000ULL) | - ((x >> 40) & 0x000000000000ff00ULL) | - ((x >> 56) & 0x00000000000000ffULL); -} -#endif - - -/* ************************************* -* Architecture Macros -***************************************/ -typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; - -/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ -#ifndef XXH_CPU_LITTLE_ENDIAN - static const int g_one = 1; -# define XXH_CPU_LITTLE_ENDIAN (*(const char*)(&g_one)) -#endif - - -/* *************************** -* Memory reads -*****************************/ -typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; - -FORCE_INLINE_TEMPLATE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) -{ - if (align==XXH_unaligned) - return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); - else - return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); -} - -FORCE_INLINE_TEMPLATE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) -{ - return XXH_readLE32_align(ptr, endian, XXH_unaligned); -} - -static U32 XXH_readBE32(const void* ptr) -{ - return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); -} - -FORCE_INLINE_TEMPLATE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) -{ - if (align==XXH_unaligned) - return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); - else - return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); -} - -FORCE_INLINE_TEMPLATE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) -{ - return XXH_readLE64_align(ptr, endian, XXH_unaligned); -} - -static U64 XXH_readBE64(const void* ptr) -{ - return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); -} - - -/* ************************************* -* Macros -***************************************/ -#define XXH_STATIC_ASSERT(c) { enum { XXH_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ - - -/* ************************************* -* Constants -***************************************/ -static const U32 PRIME32_1 = 2654435761U; -static const U32 PRIME32_2 = 2246822519U; -static const U32 PRIME32_3 = 3266489917U; -static const U32 PRIME32_4 = 668265263U; -static const U32 PRIME32_5 = 374761393U; - -static const U64 PRIME64_1 = 11400714785074694791ULL; -static const U64 PRIME64_2 = 14029467366897019727ULL; -static const U64 PRIME64_3 = 1609587929392839161ULL; -static const U64 PRIME64_4 = 9650029242287828579ULL; -static const U64 PRIME64_5 = 2870177450012600261ULL; - -XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } - - -/* ************************** -* Utils -****************************/ -XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* restrict dstState, const XXH32_state_t* restrict srcState) -{ - ZSTD_memcpy(dstState, srcState, sizeof(*dstState)); -} - -XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* restrict dstState, const XXH64_state_t* restrict srcState) -{ - ZSTD_memcpy(dstState, srcState, sizeof(*dstState)); -} - - -/* *************************** -* Simple Hash Functions -*****************************/ - -static U32 XXH32_round(U32 seed, U32 input) -{ - seed += input * PRIME32_2; - seed = XXH_rotl32(seed, 13); - seed *= PRIME32_1; - return seed; -} - -FORCE_INLINE_TEMPLATE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, XXH_endianess endian, XXH_alignment align) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* bEnd = p + len; - U32 h32; -#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) - -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER - if (p==NULL) { - len=0; - bEnd=p=(const BYTE*)(size_t)16; - } -#endif - - if (len>=16) { - const BYTE* const limit = bEnd - 16; - U32 v1 = seed + PRIME32_1 + PRIME32_2; - U32 v2 = seed + PRIME32_2; - U32 v3 = seed + 0; - U32 v4 = seed - PRIME32_1; - - do { - v1 = XXH32_round(v1, XXH_get32bits(p)); p+=4; - v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4; - v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4; - v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4; - } while (p<=limit); - - h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); - } else { - h32 = seed + PRIME32_5; - } - - h32 += (U32) len; - - while (p+4<=bEnd) { - h32 += XXH_get32bits(p) * PRIME32_3; - h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; - p+=4; - } - - while (p> 15; - h32 *= PRIME32_2; - h32 ^= h32 >> 13; - h32 *= PRIME32_3; - h32 ^= h32 >> 16; - - return h32; -} - - -XXH_PUBLIC_API unsigned int XXH32 (const void* input, size_t len, unsigned int seed) -{ -#if 0 - /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ - XXH32_CREATESTATE_STATIC(state); - XXH32_reset(state, seed); - XXH32_update(state, input, len); - return XXH32_digest(state); -#else - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if (XXH_FORCE_ALIGN_CHECK) { - if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); - else - return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); - } } - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); - else - return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); -#endif -} - - -static U64 XXH64_round(U64 acc, U64 input) -{ - acc += input * PRIME64_2; - acc = XXH_rotl64(acc, 31); - acc *= PRIME64_1; - return acc; -} - -static U64 XXH64_mergeRound(U64 acc, U64 val) -{ - val = XXH64_round(0, val); - acc ^= val; - acc = acc * PRIME64_1 + PRIME64_4; - return acc; -} - -FORCE_INLINE_TEMPLATE U64 XXH64_endian_align(const void* input, size_t len, U64 seed, XXH_endianess endian, XXH_alignment align) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; - U64 h64; -#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) - -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER - if (p==NULL) { - len=0; - bEnd=p=(const BYTE*)(size_t)32; - } -#endif - - if (len>=32) { - const BYTE* const limit = bEnd - 32; - U64 v1 = seed + PRIME64_1 + PRIME64_2; - U64 v2 = seed + PRIME64_2; - U64 v3 = seed + 0; - U64 v4 = seed - PRIME64_1; - - do { - v1 = XXH64_round(v1, XXH_get64bits(p)); p+=8; - v2 = XXH64_round(v2, XXH_get64bits(p)); p+=8; - v3 = XXH64_round(v3, XXH_get64bits(p)); p+=8; - v4 = XXH64_round(v4, XXH_get64bits(p)); p+=8; - } while (p<=limit); - - h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); - h64 = XXH64_mergeRound(h64, v1); - h64 = XXH64_mergeRound(h64, v2); - h64 = XXH64_mergeRound(h64, v3); - h64 = XXH64_mergeRound(h64, v4); - - } else { - h64 = seed + PRIME64_5; - } - - h64 += (U64) len; - - while (p+8<=bEnd) { - U64 const k1 = XXH64_round(0, XXH_get64bits(p)); - h64 ^= k1; - h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; - p+=8; - } - - if (p+4<=bEnd) { - h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; - h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; - p+=4; - } - - while (p> 33; - h64 *= PRIME64_2; - h64 ^= h64 >> 29; - h64 *= PRIME64_3; - h64 ^= h64 >> 32; - - return h64; -} - - -XXH_PUBLIC_API unsigned long long XXH64 (const void* input, size_t len, unsigned long long seed) -{ -#if 0 - /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ - XXH64_CREATESTATE_STATIC(state); - XXH64_reset(state, seed); - XXH64_update(state, input, len); - return XXH64_digest(state); -#else - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if (XXH_FORCE_ALIGN_CHECK) { - if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */ - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); - else - return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); - } } - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); - else - return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); -#endif -} - - -/* ************************************************** -* Advanced Hash Functions -****************************************************/ - -XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) -{ - return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); -} -XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) -{ - XXH_free(statePtr); - return XXH_OK; -} - -XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) -{ - return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); -} -XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) -{ - XXH_free(statePtr); - return XXH_OK; -} - - -/*** Hash feed ***/ - -XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) -{ - XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ - ZSTD_memset(&state, 0, sizeof(state)-4); /* do not write into reserved, for future removal */ - state.v1 = seed + PRIME32_1 + PRIME32_2; - state.v2 = seed + PRIME32_2; - state.v3 = seed + 0; - state.v4 = seed - PRIME32_1; - ZSTD_memcpy(statePtr, &state, sizeof(state)); - return XXH_OK; -} - - -XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) -{ - XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ - ZSTD_memset(&state, 0, sizeof(state)-8); /* do not write into reserved, for future removal */ - state.v1 = seed + PRIME64_1 + PRIME64_2; - state.v2 = seed + PRIME64_2; - state.v3 = seed + 0; - state.v4 = seed - PRIME64_1; - ZSTD_memcpy(statePtr, &state, sizeof(state)); - return XXH_OK; -} - - -FORCE_INLINE_TEMPLATE XXH_errorcode XXH32_update_endian (XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; - -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER - if (input==NULL) return XXH_ERROR; -#endif - - state->total_len_32 += (unsigned)len; - state->large_len |= (len>=16) | (state->total_len_32>=16); - - if (state->memsize + len < 16) { /* fill in tmp buffer */ - XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); - state->memsize += (unsigned)len; - return XXH_OK; - } - - if (state->memsize) { /* some data left from previous update */ - XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); - { const U32* p32 = state->mem32; - state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; - state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; - state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; - state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); p32++; - } - p += 16-state->memsize; - state->memsize = 0; - } - - if (p <= bEnd-16) { - const BYTE* const limit = bEnd - 16; - U32 v1 = state->v1; - U32 v2 = state->v2; - U32 v3 = state->v3; - U32 v4 = state->v4; - - do { - v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; - v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; - v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; - v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; - } while (p<=limit); - - state->v1 = v1; - state->v2 = v2; - state->v3 = v3; - state->v4 = v4; - } - - if (p < bEnd) { - XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); - state->memsize = (unsigned)(bEnd-p); - } - - return XXH_OK; -} - -XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_update_endian(state_in, input, len, XXH_littleEndian); - else - return XXH32_update_endian(state_in, input, len, XXH_bigEndian); -} - - - -FORCE_INLINE_TEMPLATE U32 XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) -{ - const BYTE * p = (const BYTE*)state->mem32; - const BYTE* const bEnd = (const BYTE*)(state->mem32) + state->memsize; - U32 h32; - - if (state->large_len) { - h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18); - } else { - h32 = state->v3 /* == seed */ + PRIME32_5; - } - - h32 += state->total_len_32; - - while (p+4<=bEnd) { - h32 += XXH_readLE32(p, endian) * PRIME32_3; - h32 = XXH_rotl32(h32, 17) * PRIME32_4; - p+=4; - } - - while (p> 15; - h32 *= PRIME32_2; - h32 ^= h32 >> 13; - h32 *= PRIME32_3; - h32 ^= h32 >> 16; - - return h32; -} - - -XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH32_digest_endian(state_in, XXH_littleEndian); - else - return XXH32_digest_endian(state_in, XXH_bigEndian); -} - - - -/* **** XXH64 **** */ - -FORCE_INLINE_TEMPLATE XXH_errorcode XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) -{ - const BYTE* p = (const BYTE*)input; - const BYTE* const bEnd = p + len; - -#ifdef XXH_ACCEPT_NULL_INPUT_POINTER - if (input==NULL) return XXH_ERROR; -#endif - - state->total_len += len; - - if (state->memsize + len < 32) { /* fill in tmp buffer */ - if (input != NULL) { - XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); - } - state->memsize += (U32)len; - return XXH_OK; - } - - if (state->memsize) { /* tmp buffer is full */ - XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); - state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); - state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); - state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); - state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); - p += 32-state->memsize; - state->memsize = 0; - } - - if (p+32 <= bEnd) { - const BYTE* const limit = bEnd - 32; - U64 v1 = state->v1; - U64 v2 = state->v2; - U64 v3 = state->v3; - U64 v4 = state->v4; - - do { - v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; - v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; - v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; - v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; - } while (p<=limit); - - state->v1 = v1; - state->v2 = v2; - state->v3 = v3; - state->v4 = v4; - } - - if (p < bEnd) { - XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); - state->memsize = (unsigned)(bEnd-p); - } - - return XXH_OK; -} - -XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* input, size_t len) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_update_endian(state_in, input, len, XXH_littleEndian); - else - return XXH64_update_endian(state_in, input, len, XXH_bigEndian); -} - - - -FORCE_INLINE_TEMPLATE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) -{ - const BYTE * p = (const BYTE*)state->mem64; - const BYTE* const bEnd = (const BYTE*)state->mem64 + state->memsize; - U64 h64; - - if (state->total_len >= 32) { - U64 const v1 = state->v1; - U64 const v2 = state->v2; - U64 const v3 = state->v3; - U64 const v4 = state->v4; - - h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); - h64 = XXH64_mergeRound(h64, v1); - h64 = XXH64_mergeRound(h64, v2); - h64 = XXH64_mergeRound(h64, v3); - h64 = XXH64_mergeRound(h64, v4); - } else { - h64 = state->v3 + PRIME64_5; - } - - h64 += (U64) state->total_len; - - while (p+8<=bEnd) { - U64 const k1 = XXH64_round(0, XXH_readLE64(p, endian)); - h64 ^= k1; - h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; - p+=8; - } - - if (p+4<=bEnd) { - h64 ^= (U64)(XXH_readLE32(p, endian)) * PRIME64_1; - h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; - p+=4; - } - - while (p> 33; - h64 *= PRIME64_2; - h64 ^= h64 >> 29; - h64 *= PRIME64_3; - h64 ^= h64 >> 32; - - return h64; -} - - -XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in) -{ - XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; - - if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) - return XXH64_digest_endian(state_in, XXH_littleEndian); - else - return XXH64_digest_endian(state_in, XXH_bigEndian); -} - - -/* ************************** -* Canonical representation -****************************/ - -/*! Default XXH result types are basic unsigned 32 and 64 bits. -* The canonical representation follows human-readable write convention, aka big-endian (large digits first). -* These functions allow transformation of hash result into and from its canonical format. -* This way, hash values can be written into a file or buffer, and remain comparable across different systems and programs. -*/ - -XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) -{ - XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); - if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); - ZSTD_memcpy(dst, &hash, sizeof(*dst)); -} - -XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) -{ - XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); - if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); - ZSTD_memcpy(dst, &hash, sizeof(*dst)); -} - -XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) -{ - return XXH_readBE32(src); -} - -XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) -{ - return XXH_readBE64(src); -} diff --git a/Utilities/cmzstd/lib/common/xxhash.h b/Utilities/cmzstd/lib/common/xxhash.h index 16c1f1617b9..b8b73290bbc 100644 --- a/Utilities/cmzstd/lib/common/xxhash.h +++ b/Utilities/cmzstd/lib/common/xxhash.h @@ -1,20 +1,36 @@ /* - * xxHash - Extremely Fast Hash algorithm - * Header File - * Copyright (c) Yann Collet, Facebook, Inc. + * xxHash - Fast Hash algorithm + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - xxHash homepage: https://cyan4973.github.io/xxHash/ + * - xxHash source repository : https://github.com/Cyan4973/xxHash * - * You can contact the author at : - * - xxHash source repository : https://github.com/Cyan4973/xxHash - * * This source code is licensed under both the BSD-style license (found in the * LICENSE file in the root directory of this source tree) and the GPLv2 (found * in the COPYING file in the root directory of this source tree). * You may select, at your option, one of the above-listed licenses. */ -/* Notice extracted from xxHash homepage : -xxHash is an extremely fast Hash algorithm, running at RAM speed limits. +#ifndef XXH_NO_XXH3 +# define XXH_NO_XXH3 +#endif + +#ifndef XXH_NAMESPACE +# define XXH_NAMESPACE ZSTD_ +#endif + +/*! + * @mainpage xxHash + * + * @file xxhash.h + * xxHash prototypes and implementation + */ +/* TODO: update */ +/* Notice extracted from xxHash homepage: + +xxHash is an extremely fast hash algorithm, running at RAM speed limits. It also successfully passes all tests from the SMHasher suite. Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) @@ -22,7 +38,7 @@ Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo Name Speed Q.Score Author xxHash 5.4 GB/s 10 CrapWow 3.2 GB/s 2 Andrew -MumurHash 3a 2.7 GB/s 10 Austin Appleby +MurmurHash 3a 2.7 GB/s 10 Austin Appleby SpookyHash 2.0 GB/s 10 Bob Jenkins SBox 1.4 GB/s 9 Bret Mulvey Lookup3 1.2 GB/s 9 Bob Jenkins @@ -37,8 +53,13 @@ Q.Score is a measure of quality of the hash function. It depends on successfully passing SMHasher test set. 10 is a perfect score. -A 64-bits version, named XXH64, is available since r35. -It offers much better speed, but for 64-bits applications only. +Note: SMHasher's CRC32 implementation is not the fastest one. +Other speed-oriented implementations can be faster, +especially in combination with PCLMUL instruction: +https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html?showComment=1552696407071#c3490092340461170735 + +A 64-bit version, named XXH64, is available since r35. +It offers much better speed, but for 64-bit applications only. Name Speed on 64 bits Speed on 32 bits XXH64 13.8 GB/s 1.9 GB/s XXH32 6.8 GB/s 6.0 GB/s @@ -48,33 +69,34 @@ XXH32 6.8 GB/s 6.0 GB/s extern "C" { #endif -#ifndef XXHASH_H_5627135585666179 -#define XXHASH_H_5627135585666179 1 - - -/* **************************** -* Definitions -******************************/ -#include "zstd_deps.h" -typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; - - /* **************************** -* API modifier -******************************/ -/** XXH_PRIVATE_API -* This is useful if you want to include xxhash functions in `static` mode -* in order to inline them, and remove their symbol from the public list. -* Methodology : -* #define XXH_PRIVATE_API -* #include "xxhash.h" -* `xxhash.c` is automatically included. -* It's not useful to compile and link it as a separate module anymore. -*/ -#ifdef XXH_PRIVATE_API -# ifndef XXH_STATIC_LINKING_ONLY -# define XXH_STATIC_LINKING_ONLY -# endif + * INLINE mode + ******************************/ +/*! + * XXH_INLINE_ALL (and XXH_PRIVATE_API) + * Use these build macros to inline xxhash into the target unit. + * Inlining improves performance on small inputs, especially when the length is + * expressed as a compile-time constant: + * + * https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html + * + * It also keeps xxHash symbols private to the unit, so they are not exported. + * + * Usage: + * #define XXH_INLINE_ALL + * #include "xxhash.h" + * + * Do not compile and link xxhash.o as a separate object, as it is not useful. + */ +#if (defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)) \ + && !defined(XXH_INLINE_ALL_31684351384) + /* this section should be traversed only once */ +# define XXH_INLINE_ALL_31684351384 + /* give access to the advanced API, required to compile implementations */ +# undef XXH_STATIC_LINKING_ONLY /* avoid macro redef */ +# define XXH_STATIC_LINKING_ONLY + /* make all functions private */ +# undef XXH_PUBLIC_API # if defined(__GNUC__) # define XXH_PUBLIC_API static __inline __attribute__((unused)) # elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) @@ -82,45 +104,205 @@ typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; # elif defined(_MSC_VER) # define XXH_PUBLIC_API static __inline # else -# define XXH_PUBLIC_API static /* this version may generate warnings for unused static functions; disable the relevant warning */ + /* note: this version may generate warnings for unused static functions */ +# define XXH_PUBLIC_API static # endif -#else -# define XXH_PUBLIC_API /* do nothing */ -#endif /* XXH_PRIVATE_API */ -/*!XXH_NAMESPACE, aka Namespace Emulation : + /* + * This part deals with the special case where a unit wants to inline xxHash, + * but "xxhash.h" has previously been included without XXH_INLINE_ALL, + * such as part of some previously included *.h header file. + * Without further action, the new include would just be ignored, + * and functions would effectively _not_ be inlined (silent failure). + * The following macros solve this situation by prefixing all inlined names, + * avoiding naming collision with previous inclusions. + */ + /* Before that, we unconditionally #undef all symbols, + * in case they were already defined with XXH_NAMESPACE. + * They will then be redefined for XXH_INLINE_ALL + */ +# undef XXH_versionNumber + /* XXH32 */ +# undef XXH32 +# undef XXH32_createState +# undef XXH32_freeState +# undef XXH32_reset +# undef XXH32_update +# undef XXH32_digest +# undef XXH32_copyState +# undef XXH32_canonicalFromHash +# undef XXH32_hashFromCanonical + /* XXH64 */ +# undef XXH64 +# undef XXH64_createState +# undef XXH64_freeState +# undef XXH64_reset +# undef XXH64_update +# undef XXH64_digest +# undef XXH64_copyState +# undef XXH64_canonicalFromHash +# undef XXH64_hashFromCanonical + /* XXH3_64bits */ +# undef XXH3_64bits +# undef XXH3_64bits_withSecret +# undef XXH3_64bits_withSeed +# undef XXH3_64bits_withSecretandSeed +# undef XXH3_createState +# undef XXH3_freeState +# undef XXH3_copyState +# undef XXH3_64bits_reset +# undef XXH3_64bits_reset_withSeed +# undef XXH3_64bits_reset_withSecret +# undef XXH3_64bits_update +# undef XXH3_64bits_digest +# undef XXH3_generateSecret + /* XXH3_128bits */ +# undef XXH128 +# undef XXH3_128bits +# undef XXH3_128bits_withSeed +# undef XXH3_128bits_withSecret +# undef XXH3_128bits_reset +# undef XXH3_128bits_reset_withSeed +# undef XXH3_128bits_reset_withSecret +# undef XXH3_128bits_reset_withSecretandSeed +# undef XXH3_128bits_update +# undef XXH3_128bits_digest +# undef XXH128_isEqual +# undef XXH128_cmp +# undef XXH128_canonicalFromHash +# undef XXH128_hashFromCanonical + /* Finally, free the namespace itself */ +# undef XXH_NAMESPACE -If you want to include _and expose_ xxHash functions from within your own library, -but also want to avoid symbol collisions with another library which also includes xxHash, + /* employ the namespace for XXH_INLINE_ALL */ +# define XXH_NAMESPACE XXH_INLINE_ + /* + * Some identifiers (enums, type names) are not symbols, + * but they must nonetheless be renamed to avoid redeclaration. + * Alternative solution: do not redeclare them. + * However, this requires some #ifdefs, and has a more dispersed impact. + * Meanwhile, renaming can be achieved in a single place. + */ +# define XXH_IPREF(Id) XXH_NAMESPACE ## Id +# define XXH_OK XXH_IPREF(XXH_OK) +# define XXH_ERROR XXH_IPREF(XXH_ERROR) +# define XXH_errorcode XXH_IPREF(XXH_errorcode) +# define XXH32_canonical_t XXH_IPREF(XXH32_canonical_t) +# define XXH64_canonical_t XXH_IPREF(XXH64_canonical_t) +# define XXH128_canonical_t XXH_IPREF(XXH128_canonical_t) +# define XXH32_state_s XXH_IPREF(XXH32_state_s) +# define XXH32_state_t XXH_IPREF(XXH32_state_t) +# define XXH64_state_s XXH_IPREF(XXH64_state_s) +# define XXH64_state_t XXH_IPREF(XXH64_state_t) +# define XXH3_state_s XXH_IPREF(XXH3_state_s) +# define XXH3_state_t XXH_IPREF(XXH3_state_t) +# define XXH128_hash_t XXH_IPREF(XXH128_hash_t) + /* Ensure the header is parsed again, even if it was previously included */ +# undef XXHASH_H_5627135585666179 +# undef XXHASH_H_STATIC_13879238742 +#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ -you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library -with the value of XXH_NAMESPACE (so avoid to keep it NULL and avoid numeric values). -Note that no change is required within the calling program as long as it includes `xxhash.h` : -regular symbol name will be automatically translated by this header. -*/ + +/* **************************************************************** + * Stable API + *****************************************************************/ +#ifndef XXHASH_H_5627135585666179 +#define XXHASH_H_5627135585666179 1 + + +/*! + * @defgroup public Public API + * Contains details on the public xxHash functions. + * @{ + */ +/* specific declaration modes for Windows */ +#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API) +# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT)) +# ifdef XXH_EXPORT +# define XXH_PUBLIC_API __declspec(dllexport) +# elif XXH_IMPORT +# define XXH_PUBLIC_API __declspec(dllimport) +# endif +# else +# define XXH_PUBLIC_API /* do nothing */ +# endif +#endif + +#ifdef XXH_DOXYGEN +/*! + * @brief Emulate a namespace by transparently prefixing all symbols. + * + * If you want to include _and expose_ xxHash functions from within your own + * library, but also want to avoid symbol collisions with other libraries which + * may also include xxHash, you can use XXH_NAMESPACE to automatically prefix + * any public symbol from xxhash library with the value of XXH_NAMESPACE + * (therefore, avoid empty or numeric values). + * + * Note that no change is required within the calling program as long as it + * includes `xxhash.h`: Regular symbol names will be automatically translated + * by this header. + */ +# define XXH_NAMESPACE /* YOUR NAME HERE */ +# undef XXH_NAMESPACE +#endif + #ifdef XXH_NAMESPACE # define XXH_CAT(A,B) A##B # define XXH_NAME2(A,B) XXH_CAT(A,B) -# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) -# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) # define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber) +/* XXH32 */ +# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) # define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState) -# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) # define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState) -# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) # define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset) -# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) # define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update) -# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) # define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest) -# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) # define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState) -# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) # define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash) -# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash) # define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical) +/* XXH64 */ +# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) +# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) +# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) +# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) +# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) +# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) +# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) +# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash) # define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical) +/* XXH3_64bits */ +# define XXH3_64bits XXH_NAME2(XXH_NAMESPACE, XXH3_64bits) +# define XXH3_64bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecret) +# define XXH3_64bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSeed) +# define XXH3_64bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecretandSeed) +# define XXH3_createState XXH_NAME2(XXH_NAMESPACE, XXH3_createState) +# define XXH3_freeState XXH_NAME2(XXH_NAMESPACE, XXH3_freeState) +# define XXH3_copyState XXH_NAME2(XXH_NAMESPACE, XXH3_copyState) +# define XXH3_64bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset) +# define XXH3_64bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSeed) +# define XXH3_64bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecret) +# define XXH3_64bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecretandSeed) +# define XXH3_64bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_update) +# define XXH3_64bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_digest) +# define XXH3_generateSecret XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret) +# define XXH3_generateSecret_fromSeed XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret_fromSeed) +/* XXH3_128bits */ +# define XXH128 XXH_NAME2(XXH_NAMESPACE, XXH128) +# define XXH3_128bits XXH_NAME2(XXH_NAMESPACE, XXH3_128bits) +# define XXH3_128bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSeed) +# define XXH3_128bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecret) +# define XXH3_128bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecretandSeed) +# define XXH3_128bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset) +# define XXH3_128bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSeed) +# define XXH3_128bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecret) +# define XXH3_128bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecretandSeed) +# define XXH3_128bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_update) +# define XXH3_128bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_digest) +# define XXH128_isEqual XXH_NAME2(XXH_NAMESPACE, XXH128_isEqual) +# define XXH128_cmp XXH_NAME2(XXH_NAMESPACE, XXH128_cmp) +# define XXH128_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH128_canonicalFromHash) +# define XXH128_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH128_hashFromCanonical) #endif @@ -128,156 +310,5375 @@ regular symbol name will be automatically translated by this header. * Version ***************************************/ #define XXH_VERSION_MAJOR 0 -#define XXH_VERSION_MINOR 6 -#define XXH_VERSION_RELEASE 2 +#define XXH_VERSION_MINOR 8 +#define XXH_VERSION_RELEASE 1 #define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) + +/*! + * @brief Obtains the xxHash version. + * + * This is mostly useful when xxHash is compiled as a shared library, + * since the returned value comes from the library, as opposed to header file. + * + * @return `XXH_VERSION_NUMBER` of the invoked library. + */ XXH_PUBLIC_API unsigned XXH_versionNumber (void); /* **************************** -* Simple Hash Functions +* Common basic types ******************************/ -typedef unsigned int XXH32_hash_t; -typedef unsigned long long XXH64_hash_t; - -XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, unsigned int seed); -XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed); - -/*! -XXH32() : - Calculate the 32-bits hash of sequence "length" bytes stored at memory address "input". - The memory between input & input+length must be valid (allocated and read-accessible). - "seed" can be used to alter the result predictably. - Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s -XXH64() : - Calculate the 64-bits hash of sequence of length "len" stored at memory address "input". - "seed" can be used to alter the result predictably. - This function runs 2x faster on 64-bits systems, but slower on 32-bits systems (see benchmark). -*/ +#include /* size_t */ +typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; -/* **************************** -* Streaming Hash Functions -******************************/ -typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */ -typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ +/*-********************************************************************** +* 32-bit hash +************************************************************************/ +#if defined(XXH_DOXYGEN) /* Don't show include */ +/*! + * @brief An unsigned 32-bit integer. + * + * Not necessarily defined to `uint32_t` but functionally equivalent. + */ +typedef uint32_t XXH32_hash_t; -/*! State allocation, compatible with dynamic libraries */ +#elif !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint32_t XXH32_hash_t; -XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); -XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); +#else +# include +# if UINT_MAX == 0xFFFFFFFFUL + typedef unsigned int XXH32_hash_t; +# else +# if ULONG_MAX == 0xFFFFFFFFUL + typedef unsigned long XXH32_hash_t; +# else +# error "unsupported platform: need a 32-bit type" +# endif +# endif +#endif -XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); -XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); +/*! + * @} + * + * @defgroup xxh32_family XXH32 family + * @ingroup public + * Contains functions used in the classic 32-bit xxHash algorithm. + * + * @note + * XXH32 is useful for older platforms, with no or poor 64-bit performance. + * Note that @ref xxh3_family provides competitive speed + * for both 32-bit and 64-bit systems, and offers true 64/128 bit hash results. + * + * @see @ref xxh64_family, @ref xxh3_family : Other xxHash families + * @see @ref xxh32_impl for implementation details + * @{ + */ + +/*! + * @brief Calculates the 32-bit hash of @p input using xxHash32. + * + * Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark): 5.4 GB/s + * + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * @param seed The 32-bit seed to alter the hash's output predictably. + * + * @pre + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return The calculated 32-bit hash value. + * + * @see + * XXH64(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128(): + * Direct equivalents for the other variants of xxHash. + * @see + * XXH32_createState(), XXH32_update(), XXH32_digest(): Streaming version. + */ +XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed); + +/*! + * Streaming functions generate the xxHash value from an incremental input. + * This method is slower than single-call functions, due to state management. + * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. + * + * An XXH state must first be allocated using `XXH*_createState()`. + * + * Start a new hash by initializing the state with a seed using `XXH*_reset()`. + * + * Then, feed the hash state by calling `XXH*_update()` as many times as necessary. + * + * The function returns an error code, with 0 meaning OK, and any other value + * meaning there is an error. + * + * Finally, a hash value can be produced anytime, by using `XXH*_digest()`. + * This function returns the nn-bits hash as an int or long long. + * + * It's still possible to continue inserting input into the hash state after a + * digest, and generate new hash values later on by invoking `XXH*_digest()`. + * + * When done, release the state using `XXH*_freeState()`. + * + * Example code for incrementally hashing a file: + * @code{.c} + * #include + * #include + * #define BUFFER_SIZE 256 + * + * // Note: XXH64 and XXH3 use the same interface. + * XXH32_hash_t + * hashFile(FILE* stream) + * { + * XXH32_state_t* state; + * unsigned char buf[BUFFER_SIZE]; + * size_t amt; + * XXH32_hash_t hash; + * + * state = XXH32_createState(); // Create a state + * assert(state != NULL); // Error check here + * XXH32_reset(state, 0xbaad5eed); // Reset state with our seed + * while ((amt = fread(buf, 1, sizeof(buf), stream)) != 0) { + * XXH32_update(state, buf, amt); // Hash the file in chunks + * } + * hash = XXH32_digest(state); // Finalize the hash + * XXH32_freeState(state); // Clean up + * return hash; + * } + * @endcode + */ + +/*! + * @typedef struct XXH32_state_s XXH32_state_t + * @brief The opaque state struct for the XXH32 streaming API. + * + * @see XXH32_state_s for details. + */ +typedef struct XXH32_state_s XXH32_state_t; +/*! + * @brief Allocates an @ref XXH32_state_t. + * + * Must be freed with XXH32_freeState(). + * @return An allocated XXH32_state_t on success, `NULL` on failure. + */ +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); +/*! + * @brief Frees an @ref XXH32_state_t. + * + * Must be allocated with XXH32_createState(). + * @param statePtr A pointer to an @ref XXH32_state_t allocated with @ref XXH32_createState(). + * @return XXH_OK. + */ +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); +/*! + * @brief Copies one @ref XXH32_state_t to another. + * + * @param dst_state The state to copy to. + * @param src_state The state to copy from. + * @pre + * @p dst_state and @p src_state must not be `NULL` and must not overlap. + */ +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state); -/* hash streaming */ +/*! + * @brief Resets an @ref XXH32_state_t to begin a new hash. + * + * This function resets and seeds a state. Call it before @ref XXH32_update(). + * + * @param statePtr The state struct to reset. + * @param seed The 32-bit seed to alter the hash result predictably. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return @ref XXH_OK on success, @ref XXH_ERROR on failure. + */ +XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t seed); -XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, unsigned int seed); +/*! + * @brief Consumes a block of @p input to an @ref XXH32_state_t. + * + * Call this to incrementally consume blocks of data. + * + * @param statePtr The state struct to update. + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * + * @pre + * @p statePtr must not be `NULL`. + * @pre + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return @ref XXH_OK on success, @ref XXH_ERROR on failure. + */ XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); + +/*! + * @brief Returns the calculated hash value from an @ref XXH32_state_t. + * + * @note + * Calling XXH32_digest() will not affect @p statePtr, so you can update, + * digest, and update again. + * + * @param statePtr The state struct to calculate the hash from. + * + * @pre + * @p statePtr must not be `NULL`. + * + * @return The calculated xxHash32 value from that state. + */ XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); -XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, unsigned long long seed); -XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); -XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); +/******* Canonical representation *******/ /* -These functions generate the xxHash of an input provided in multiple segments. -Note that, for small input, they are slower than single-call functions, due to state management. -For small input, prefer `XXH32()` and `XXH64()` . + * The default return values from XXH functions are unsigned 32 and 64 bit + * integers. + * This the simplest and fastest format for further post-processing. + * + * However, this leaves open the question of what is the order on the byte level, + * since little and big endian conventions will store the same number differently. + * + * The canonical representation settles this issue by mandating big-endian + * convention, the same convention as human-readable numbers (large digits first). + * + * When writing hash values to storage, sending them over a network, or printing + * them, it's highly recommended to use the canonical representation to ensure + * portability across a wider range of systems, present and future. + * + * The following functions allow transformation of hash values to and from + * canonical format. + */ + +/*! + * @brief Canonical (big endian) representation of @ref XXH32_hash_t. + */ +typedef struct { + unsigned char digest[4]; /*!< Hash bytes, big endian */ +} XXH32_canonical_t; -XXH state must first be allocated, using XXH*_createState() . +/*! + * @brief Converts an @ref XXH32_hash_t to a big endian @ref XXH32_canonical_t. + * + * @param dst The @ref XXH32_canonical_t pointer to be stored to. + * @param hash The @ref XXH32_hash_t to be converted. + * + * @pre + * @p dst must not be `NULL`. + */ +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); + +/*! + * @brief Converts an @ref XXH32_canonical_t to a native @ref XXH32_hash_t. + * + * @param src The @ref XXH32_canonical_t to convert. + * + * @pre + * @p src must not be `NULL`. + * + * @return The converted hash. + */ +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); -Start a new hash by initializing state with a seed, using XXH*_reset(). -Then, feed the hash state by calling XXH*_update() as many times as necessary. -Obviously, input must be allocated and read accessible. -The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. +#ifdef __has_attribute +# define XXH_HAS_ATTRIBUTE(x) __has_attribute(x) +#else +# define XXH_HAS_ATTRIBUTE(x) 0 +#endif -Finally, a hash value can be produced anytime, by using XXH*_digest(). -This function returns the nn-bits hash as an int or long long. +/* C-language Attributes are added in C23. */ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ > 201710L) && defined(__has_c_attribute) +# define XXH_HAS_C_ATTRIBUTE(x) __has_c_attribute(x) +#else +# define XXH_HAS_C_ATTRIBUTE(x) 0 +#endif -It's still possible to continue inserting input into the hash state after a digest, -and generate some new hashes later on, by calling again XXH*_digest(). +#if defined(__cplusplus) && defined(__has_cpp_attribute) +# define XXH_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define XXH_HAS_CPP_ATTRIBUTE(x) 0 +#endif -When done, free XXH state space if it was allocated dynamically. +/* +Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute +introduced in CPP17 and C23. +CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough +C23 : https://en.cppreference.com/w/c/language/attributes/fallthrough */ +#if XXH_HAS_C_ATTRIBUTE(x) +# define XXH_FALLTHROUGH [[fallthrough]] +#elif XXH_HAS_CPP_ATTRIBUTE(x) +# define XXH_FALLTHROUGH [[fallthrough]] +#elif XXH_HAS_ATTRIBUTE(__fallthrough__) +# define XXH_FALLTHROUGH __attribute__ ((fallthrough)) +#else +# define XXH_FALLTHROUGH +#endif +/*! + * @} + * @ingroup public + * @{ + */ -/* ************************** -* Utils -****************************/ -#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) /* ! C99 */ -# define restrict /* disable restrict */ +#ifndef XXH_NO_LONG_LONG +/*-********************************************************************** +* 64-bit hash +************************************************************************/ +#if defined(XXH_DOXYGEN) /* don't include */ +/*! + * @brief An unsigned 64-bit integer. + * + * Not necessarily defined to `uint64_t` but functionally equivalent. + */ +typedef uint64_t XXH64_hash_t; +#elif !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint64_t XXH64_hash_t; +#else +# include +# if defined(__LP64__) && ULONG_MAX == 0xFFFFFFFFFFFFFFFFULL + /* LP64 ABI says uint64_t is unsigned long */ + typedef unsigned long XXH64_hash_t; +# else + /* the following type must have a width of 64-bit */ + typedef unsigned long long XXH64_hash_t; +# endif #endif -XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* restrict dst_state, const XXH32_state_t* restrict src_state); -XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* restrict dst_state, const XXH64_state_t* restrict src_state); +/*! + * @} + * + * @defgroup xxh64_family XXH64 family + * @ingroup public + * @{ + * Contains functions used in the classic 64-bit xxHash algorithm. + * + * @note + * XXH3 provides competitive speed for both 32-bit and 64-bit systems, + * and offers true 64/128 bit hash results. + * It provides better speed for systems with vector processing capabilities. + */ -/* ************************** -* Canonical representation -****************************/ -/* Default result type for XXH functions are primitive unsigned 32 and 64 bits. -* The canonical representation uses human-readable write convention, aka big-endian (large digits first). -* These functions allow transformation of hash result into and from its canonical format. -* This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. -*/ -typedef struct { unsigned char digest[4]; } XXH32_canonical_t; -typedef struct { unsigned char digest[8]; } XXH64_canonical_t; +/*! + * @brief Calculates the 64-bit hash of @p input using xxHash64. + * + * This function usually runs faster on 64-bit systems, but slower on 32-bit + * systems (see benchmark). + * + * @param input The block of data to be hashed, at least @p length bytes in size. + * @param length The length of @p input, in bytes. + * @param seed The 64-bit seed to alter the hash's output predictably. + * + * @pre + * The memory between @p input and @p input + @p length must be valid, + * readable, contiguous memory. However, if @p length is `0`, @p input may be + * `NULL`. In C++, this also must be *TriviallyCopyable*. + * + * @return The calculated 64-bit hash. + * + * @see + * XXH32(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128(): + * Direct equivalents for the other variants of xxHash. + * @see + * XXH64_createState(), XXH64_update(), XXH64_digest(): Streaming version. + */ +XXH_PUBLIC_API XXH64_hash_t XXH64(const void* input, size_t length, XXH64_hash_t seed); -XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); -XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); +/******* Streaming *******/ +/*! + * @brief The opaque state struct for the XXH64 streaming API. + * + * @see XXH64_state_s for details. + */ +typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state); -XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); +XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, XXH64_hash_t seed); +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); + +/******* Canonical representation *******/ +typedef struct { unsigned char digest[sizeof(XXH64_hash_t)]; } XXH64_canonical_t; +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); +#ifndef XXH_NO_XXH3 +/*! + * @} + * ************************************************************************ + * @defgroup xxh3_family XXH3 family + * @ingroup public + * @{ + * + * XXH3 is a more recent hash algorithm featuring: + * - Improved speed for both small and large inputs + * - True 64-bit and 128-bit outputs + * - SIMD acceleration + * - Improved 32-bit viability + * + * Speed analysis methodology is explained here: + * + * https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html + * + * Compared to XXH64, expect XXH3 to run approximately + * ~2x faster on large inputs and >3x faster on small ones, + * exact differences vary depending on platform. + * + * XXH3's speed benefits greatly from SIMD and 64-bit arithmetic, + * but does not require it. + * Any 32-bit and 64-bit targets that can run XXH32 smoothly + * can run XXH3 at competitive speeds, even without vector support. + * Further details are explained in the implementation. + * + * Optimized implementations are provided for AVX512, AVX2, SSE2, NEON, POWER8, + * ZVector and scalar targets. This can be controlled via the XXH_VECTOR macro. + * + * XXH3 implementation is portable: + * it has a generic C90 formulation that can be compiled on any platform, + * all implementations generage exactly the same hash value on all platforms. + * Starting from v0.8.0, it's also labelled "stable", meaning that + * any future version will also generate the same hash value. + * + * XXH3 offers 2 variants, _64bits and _128bits. + * + * When only 64 bits are needed, prefer invoking the _64bits variant, as it + * reduces the amount of mixing, resulting in faster speed on small inputs. + * It's also generally simpler to manipulate a scalar return type than a struct. + * + * The API supports one-shot hashing, streaming mode, and custom secrets. + */ + +/*-********************************************************************** +* XXH3 64-bit variant +************************************************************************/ + +/* XXH3_64bits(): + * default 64-bit variant, using default secret and default seed of 0. + * It's the fastest variant. */ +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* data, size_t len); + +/* + * XXH3_64bits_withSeed(): + * This variant generates a custom secret on the fly + * based on default secret altered using the `seed` value. + * While this operation is decently fast, note that it's not completely free. + * Note: seed==0 produces the same results as XXH3_64bits(). + */ +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(const void* data, size_t len, XXH64_hash_t seed); + +/*! + * The bare minimum size for a custom secret. + * + * @see + * XXH3_64bits_withSecret(), XXH3_64bits_reset_withSecret(), + * XXH3_128bits_withSecret(), XXH3_128bits_reset_withSecret(). + */ +#define XXH3_SECRET_SIZE_MIN 136 + +/* + * XXH3_64bits_withSecret(): + * It's possible to provide any blob of bytes as a "secret" to generate the hash. + * This makes it more difficult for an external actor to prepare an intentional collision. + * The main condition is that secretSize *must* be large enough (>= XXH3_SECRET_SIZE_MIN). + * However, the quality of the secret impacts the dispersion of the hash algorithm. + * Therefore, the secret _must_ look like a bunch of random bytes. + * Avoid "trivial" or structured data such as repeated sequences or a text document. + * Whenever in doubt about the "randomness" of the blob of bytes, + * consider employing "XXH3_generateSecret()" instead (see below). + * It will generate a proper high entropy secret derived from the blob of bytes. + * Another advantage of using XXH3_generateSecret() is that + * it guarantees that all bits within the initial blob of bytes + * will impact every bit of the output. + * This is not necessarily the case when using the blob of bytes directly + * because, when hashing _small_ inputs, only a portion of the secret is employed. + */ +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize); + + +/******* Streaming *******/ +/* + * Streaming requires state maintenance. + * This operation costs memory and CPU. + * As a consequence, streaming is slower than one-shot hashing. + * For better performance, prefer one-shot functions whenever applicable. + */ + +/*! + * @brief The state struct for the XXH3 streaming API. + * + * @see XXH3_state_s for details. + */ +typedef struct XXH3_state_s XXH3_state_t; +XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr); +XXH_PUBLIC_API void XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state); + +/* + * XXH3_64bits_reset(): + * Initialize with default parameters. + * digest will be equivalent to `XXH3_64bits()`. + */ +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH3_state_t* statePtr); +/* + * XXH3_64bits_reset_withSeed(): + * Generate a custom secret from `seed`, and store it into `statePtr`. + * digest will be equivalent to `XXH3_64bits_withSeed()`. + */ +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed); +/* + * XXH3_64bits_reset_withSecret(): + * `secret` is referenced, it _must outlive_ the hash streaming session. + * Similar to one-shot API, `secretSize` must be >= `XXH3_SECRET_SIZE_MIN`, + * and the quality of produced hash values depends on secret's entropy + * (secret's content should look like a bunch of random bytes). + * When in doubt about the randomness of a candidate `secret`, + * consider employing `XXH3_generateSecret()` instead (see below). + */ +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize); + +XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH3_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* statePtr); + +/* note : canonical representation of XXH3 is the same as XXH64 + * since they both produce XXH64_hash_t values */ + + +/*-********************************************************************** +* XXH3 128-bit variant +************************************************************************/ + +/*! + * @brief The return value from 128-bit hashes. + * + * Stored in little endian order, although the fields themselves are in native + * endianness. + */ +typedef struct { + XXH64_hash_t low64; /*!< `value & 0xFFFFFFFFFFFFFFFF` */ + XXH64_hash_t high64; /*!< `value >> 64` */ +} XXH128_hash_t; + +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* data, size_t len); +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed(const void* data, size_t len, XXH64_hash_t seed); +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize); + +/******* Streaming *******/ +/* + * Streaming requires state maintenance. + * This operation costs memory and CPU. + * As a consequence, streaming is slower than one-shot hashing. + * For better performance, prefer one-shot functions whenever applicable. + * + * XXH3_128bits uses the same XXH3_state_t as XXH3_64bits(). + * Use already declared XXH3_createState() and XXH3_freeState(). + * + * All reset and streaming functions have same meaning as their 64-bit counterpart. + */ + +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH3_state_t* statePtr); +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed); +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize); + +XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH3_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* statePtr); + +/* Following helper functions make it possible to compare XXH128_hast_t values. + * Since XXH128_hash_t is a structure, this capability is not offered by the language. + * Note: For better performance, these functions can be inlined using XXH_INLINE_ALL */ + +/*! + * XXH128_isEqual(): + * Return: 1 if `h1` and `h2` are equal, 0 if they are not. + */ +XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2); + +/*! + * XXH128_cmp(): + * + * This comparator is compatible with stdlib's `qsort()`/`bsearch()`. + * + * return: >0 if *h128_1 > *h128_2 + * =0 if *h128_1 == *h128_2 + * <0 if *h128_1 < *h128_2 + */ +XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2); + + +/******* Canonical representation *******/ +typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t; +XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash); +XXH_PUBLIC_API XXH128_hash_t XXH128_hashFromCanonical(const XXH128_canonical_t* src); + + +#endif /* !XXH_NO_XXH3 */ +#endif /* XXH_NO_LONG_LONG */ + +/*! + * @} + */ #endif /* XXHASH_H_5627135585666179 */ -/* ================================================================================================ - This section contains definitions which are not guaranteed to remain stable. - They may change in future versions, becoming incompatible with a different version of the library. - They shall only be used with static linking. - Never use these definitions in association with dynamic linking ! -=================================================================================================== */ -#if defined(XXH_STATIC_LINKING_ONLY) && !defined(XXH_STATIC_H_3543687687345) -#define XXH_STATIC_H_3543687687345 - -/* These definitions are only meant to allow allocation of XXH state - statically, on stack, or in a struct for example. - Do not use members directly. */ - - struct XXH32_state_s { - unsigned total_len_32; - unsigned large_len; - unsigned v1; - unsigned v2; - unsigned v3; - unsigned v4; - unsigned mem32[4]; /* buffer defined as U32 for alignment */ - unsigned memsize; - unsigned reserved; /* never read nor write, will be removed in a future version */ - }; /* typedef'd to XXH32_state_t */ - - struct XXH64_state_s { - unsigned long long total_len; - unsigned long long v1; - unsigned long long v2; - unsigned long long v3; - unsigned long long v4; - unsigned long long mem64[4]; /* buffer defined as U64 for alignment */ - unsigned memsize; - unsigned reserved[2]; /* never read nor write, will be removed in a future version */ - }; /* typedef'd to XXH64_state_t */ - - -# ifdef XXH_PRIVATE_API -# include "xxhash.c" /* include xxhash functions as `static`, for inlining */ -# endif +#if defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742) +#define XXHASH_H_STATIC_13879238742 +/* **************************************************************************** + * This section contains declarations which are not guaranteed to remain stable. + * They may change in future versions, becoming incompatible with a different + * version of the library. + * These declarations should only be used with static linking. + * Never use them in association with dynamic linking! + ***************************************************************************** */ + +/* + * These definitions are only present to allow static allocation + * of XXH states, on stack or in a struct, for example. + * Never **ever** access their members directly. + */ + +/*! + * @internal + * @brief Structure for XXH32 streaming API. + * + * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY, + * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is + * an opaque type. This allows fields to safely be changed. + * + * Typedef'd to @ref XXH32_state_t. + * Do not access the members of this struct directly. + * @see XXH64_state_s, XXH3_state_s + */ +struct XXH32_state_s { + XXH32_hash_t total_len_32; /*!< Total length hashed, modulo 2^32 */ + XXH32_hash_t large_len; /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */ + XXH32_hash_t v[4]; /*!< Accumulator lanes */ + XXH32_hash_t mem32[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */ + XXH32_hash_t memsize; /*!< Amount of data in @ref mem32 */ + XXH32_hash_t reserved; /*!< Reserved field. Do not read nor write to it. */ +}; /* typedef'd to XXH32_state_t */ + -#endif /* XXH_STATIC_LINKING_ONLY && XXH_STATIC_H_3543687687345 */ +#ifndef XXH_NO_LONG_LONG /* defined when there is no 64-bit support */ + +/*! + * @internal + * @brief Structure for XXH64 streaming API. + * + * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY, + * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is + * an opaque type. This allows fields to safely be changed. + * + * Typedef'd to @ref XXH64_state_t. + * Do not access the members of this struct directly. + * @see XXH32_state_s, XXH3_state_s + */ +struct XXH64_state_s { + XXH64_hash_t total_len; /*!< Total length hashed. This is always 64-bit. */ + XXH64_hash_t v[4]; /*!< Accumulator lanes */ + XXH64_hash_t mem64[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */ + XXH32_hash_t memsize; /*!< Amount of data in @ref mem64 */ + XXH32_hash_t reserved32; /*!< Reserved field, needed for padding anyways*/ + XXH64_hash_t reserved64; /*!< Reserved field. Do not read or write to it. */ +}; /* typedef'd to XXH64_state_t */ + + +#ifndef XXH_NO_XXH3 + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* >= C11 */ +# include +# define XXH_ALIGN(n) alignas(n) +#elif defined(__cplusplus) && (__cplusplus >= 201103L) /* >= C++11 */ +/* In C++ alignas() is a keyword */ +# define XXH_ALIGN(n) alignas(n) +#elif defined(__GNUC__) +# define XXH_ALIGN(n) __attribute__ ((aligned(n))) +#elif defined(_MSC_VER) +# define XXH_ALIGN(n) __declspec(align(n)) +#else +# define XXH_ALIGN(n) /* disabled */ +#endif + +/* Old GCC versions only accept the attribute after the type in structures. */ +#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) /* C11+ */ \ + && ! (defined(__cplusplus) && (__cplusplus >= 201103L)) /* >= C++11 */ \ + && defined(__GNUC__) +# define XXH_ALIGN_MEMBER(align, type) type XXH_ALIGN(align) +#else +# define XXH_ALIGN_MEMBER(align, type) XXH_ALIGN(align) type +#endif + +/*! + * @brief The size of the internal XXH3 buffer. + * + * This is the optimal update size for incremental hashing. + * + * @see XXH3_64b_update(), XXH3_128b_update(). + */ +#define XXH3_INTERNALBUFFER_SIZE 256 + +/*! + * @brief Default size of the secret buffer (and @ref XXH3_kSecret). + * + * This is the size used in @ref XXH3_kSecret and the seeded functions. + * + * Not to be confused with @ref XXH3_SECRET_SIZE_MIN. + */ +#define XXH3_SECRET_DEFAULT_SIZE 192 + +/*! + * @internal + * @brief Structure for XXH3 streaming API. + * + * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY, + * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. + * Otherwise it is an opaque type. + * Never use this definition in combination with dynamic library. + * This allows fields to safely be changed in the future. + * + * @note ** This structure has a strict alignment requirement of 64 bytes!! ** + * Do not allocate this with `malloc()` or `new`, + * it will not be sufficiently aligned. + * Use @ref XXH3_createState() and @ref XXH3_freeState(), or stack allocation. + * + * Typedef'd to @ref XXH3_state_t. + * Do never access the members of this struct directly. + * + * @see XXH3_INITSTATE() for stack initialization. + * @see XXH3_createState(), XXH3_freeState(). + * @see XXH32_state_s, XXH64_state_s + */ +struct XXH3_state_s { + XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]); + /*!< The 8 accumulators. Similar to `vN` in @ref XXH32_state_s::v1 and @ref XXH64_state_s */ + XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]); + /*!< Used to store a custom secret generated from a seed. */ + XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]); + /*!< The internal buffer. @see XXH32_state_s::mem32 */ + XXH32_hash_t bufferedSize; + /*!< The amount of memory in @ref buffer, @see XXH32_state_s::memsize */ + XXH32_hash_t useSeed; + /*!< Reserved field. Needed for padding on 64-bit. */ + size_t nbStripesSoFar; + /*!< Number or stripes processed. */ + XXH64_hash_t totalLen; + /*!< Total length hashed. 64-bit even on 32-bit targets. */ + size_t nbStripesPerBlock; + /*!< Number of stripes per block. */ + size_t secretLimit; + /*!< Size of @ref customSecret or @ref extSecret */ + XXH64_hash_t seed; + /*!< Seed for _withSeed variants. Must be zero otherwise, @see XXH3_INITSTATE() */ + XXH64_hash_t reserved64; + /*!< Reserved field. */ + const unsigned char* extSecret; + /*!< Reference to an external secret for the _withSecret variants, NULL + * for other variants. */ + /* note: there may be some padding at the end due to alignment on 64 bytes */ +}; /* typedef'd to XXH3_state_t */ + +#undef XXH_ALIGN_MEMBER + +/*! + * @brief Initializes a stack-allocated `XXH3_state_s`. + * + * When the @ref XXH3_state_t structure is merely emplaced on stack, + * it should be initialized with XXH3_INITSTATE() or a memset() + * in case its first reset uses XXH3_NNbits_reset_withSeed(). + * This init can be omitted if the first reset uses default or _withSecret mode. + * This operation isn't necessary when the state is created with XXH3_createState(). + * Note that this doesn't prepare the state for a streaming operation, + * it's still necessary to use XXH3_NNbits_reset*() afterwards. + */ +#define XXH3_INITSTATE(XXH3_state_ptr) { (XXH3_state_ptr)->seed = 0; } + + +/* XXH128() : + * simple alias to pre-selected XXH3_128bits variant + */ +XXH_PUBLIC_API XXH128_hash_t XXH128(const void* data, size_t len, XXH64_hash_t seed); + + +/* === Experimental API === */ +/* Symbols defined below must be considered tied to a specific library version. */ + +/* + * XXH3_generateSecret(): + * + * Derive a high-entropy secret from any user-defined content, named customSeed. + * The generated secret can be used in combination with `*_withSecret()` functions. + * The `_withSecret()` variants are useful to provide a higher level of protection than 64-bit seed, + * as it becomes much more difficult for an external actor to guess how to impact the calculation logic. + * + * The function accepts as input a custom seed of any length and any content, + * and derives from it a high-entropy secret of length @secretSize + * into an already allocated buffer @secretBuffer. + * @secretSize must be >= XXH3_SECRET_SIZE_MIN + * + * The generated secret can then be used with any `*_withSecret()` variant. + * Functions `XXH3_128bits_withSecret()`, `XXH3_64bits_withSecret()`, + * `XXH3_128bits_reset_withSecret()` and `XXH3_64bits_reset_withSecret()` + * are part of this list. They all accept a `secret` parameter + * which must be large enough for implementation reasons (>= XXH3_SECRET_SIZE_MIN) + * _and_ feature very high entropy (consist of random-looking bytes). + * These conditions can be a high bar to meet, so + * XXH3_generateSecret() can be employed to ensure proper quality. + * + * customSeed can be anything. It can have any size, even small ones, + * and its content can be anything, even "poor entropy" sources such as a bunch of zeroes. + * The resulting `secret` will nonetheless provide all required qualities. + * + * When customSeedSize > 0, supplying NULL as customSeed is undefined behavior. + */ +XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize); + + +/* + * XXH3_generateSecret_fromSeed(): + * + * Generate the same secret as the _withSeed() variants. + * + * The resulting secret has a length of XXH3_SECRET_DEFAULT_SIZE (necessarily). + * @secretBuffer must be already allocated, of size at least XXH3_SECRET_DEFAULT_SIZE bytes. + * + * The generated secret can be used in combination with + *`*_withSecret()` and `_withSecretandSeed()` variants. + * This generator is notably useful in combination with `_withSecretandSeed()`, + * as a way to emulate a faster `_withSeed()` variant. + */ +XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed); + +/* + * *_withSecretandSeed() : + * These variants generate hash values using either + * @seed for "short" keys (< XXH3_MIDSIZE_MAX = 240 bytes) + * or @secret for "large" keys (>= XXH3_MIDSIZE_MAX). + * + * This generally benefits speed, compared to `_withSeed()` or `_withSecret()`. + * `_withSeed()` has to generate the secret on the fly for "large" keys. + * It's fast, but can be perceptible for "not so large" keys (< 1 KB). + * `_withSecret()` has to generate the masks on the fly for "small" keys, + * which requires more instructions than _withSeed() variants. + * Therefore, _withSecretandSeed variant combines the best of both worlds. + * + * When @secret has been generated by XXH3_generateSecret_fromSeed(), + * this variant produces *exactly* the same results as `_withSeed()` variant, + * hence offering only a pure speed benefit on "large" input, + * by skipping the need to regenerate the secret for every large input. + * + * Another usage scenario is to hash the secret to a 64-bit hash value, + * for example with XXH3_64bits(), which then becomes the seed, + * and then employ both the seed and the secret in _withSecretandSeed(). + * On top of speed, an added benefit is that each bit in the secret + * has a 50% chance to swap each bit in the output, + * via its impact to the seed. + * This is not guaranteed when using the secret directly in "small data" scenarios, + * because only portions of the secret are employed for small data. + */ +XXH_PUBLIC_API XXH64_hash_t +XXH3_64bits_withSecretandSeed(const void* data, size_t len, + const void* secret, size_t secretSize, + XXH64_hash_t seed); + +XXH_PUBLIC_API XXH128_hash_t +XXH3_128bits_withSecretandSeed(const void* data, size_t len, + const void* secret, size_t secretSize, + XXH64_hash_t seed64); + +XXH_PUBLIC_API XXH_errorcode +XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, + const void* secret, size_t secretSize, + XXH64_hash_t seed64); + +XXH_PUBLIC_API XXH_errorcode +XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, + const void* secret, size_t secretSize, + XXH64_hash_t seed64); + + +#endif /* XXH_NO_XXH3 */ +#endif /* XXH_NO_LONG_LONG */ +#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) +# define XXH_IMPLEMENTATION +#endif + +#endif /* defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742) */ + + +/* ======================================================================== */ +/* ======================================================================== */ +/* ======================================================================== */ + + +/*-********************************************************************** + * xxHash implementation + *-********************************************************************** + * xxHash's implementation used to be hosted inside xxhash.c. + * + * However, inlining requires implementation to be visible to the compiler, + * hence be included alongside the header. + * Previously, implementation was hosted inside xxhash.c, + * which was then #included when inlining was activated. + * This construction created issues with a few build and install systems, + * as it required xxhash.c to be stored in /include directory. + * + * xxHash implementation is now directly integrated within xxhash.h. + * As a consequence, xxhash.c is no longer needed in /include. + * + * xxhash.c is still available and is still useful. + * In a "normal" setup, when xxhash is not inlined, + * xxhash.h only exposes the prototypes and public symbols, + * while xxhash.c can be built into an object file xxhash.o + * which can then be linked into the final binary. + ************************************************************************/ + +#if ( defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) \ + || defined(XXH_IMPLEMENTATION) ) && !defined(XXH_IMPLEM_13a8737387) +# define XXH_IMPLEM_13a8737387 + +/* ************************************* +* Tuning parameters +***************************************/ + +/*! + * @defgroup tuning Tuning parameters + * @{ + * + * Various macros to control xxHash's behavior. + */ +#ifdef XXH_DOXYGEN +/*! + * @brief Define this to disable 64-bit code. + * + * Useful if only using the @ref xxh32_family and you have a strict C90 compiler. + */ +# define XXH_NO_LONG_LONG +# undef XXH_NO_LONG_LONG /* don't actually */ +/*! + * @brief Controls how unaligned memory is accessed. + * + * By default, access to unaligned memory is controlled by `memcpy()`, which is + * safe and portable. + * + * Unfortunately, on some target/compiler combinations, the generated assembly + * is sub-optimal. + * + * The below switch allow selection of a different access method + * in the search for improved performance. + * + * @par Possible options: + * + * - `XXH_FORCE_MEMORY_ACCESS=0` (default): `memcpy` + * @par + * Use `memcpy()`. Safe and portable. Note that most modern compilers will + * eliminate the function call and treat it as an unaligned access. + * + * - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((packed))` + * @par + * Depends on compiler extensions and is therefore not portable. + * This method is safe _if_ your compiler supports it, + * and *generally* as fast or faster than `memcpy`. + * + * - `XXH_FORCE_MEMORY_ACCESS=2`: Direct cast + * @par + * Casts directly and dereferences. This method doesn't depend on the + * compiler, but it violates the C standard as it directly dereferences an + * unaligned pointer. It can generate buggy code on targets which do not + * support unaligned memory accesses, but in some circumstances, it's the + * only known way to get the most performance. + * + * - `XXH_FORCE_MEMORY_ACCESS=3`: Byteshift + * @par + * Also portable. This can generate the best code on old compilers which don't + * inline small `memcpy()` calls, and it might also be faster on big-endian + * systems which lack a native byteswap instruction. However, some compilers + * will emit literal byteshifts even if the target supports unaligned access. + * . + * + * @warning + * Methods 1 and 2 rely on implementation-defined behavior. Use these with + * care, as what works on one compiler/platform/optimization level may cause + * another to read garbage data or even crash. + * + * See https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html for details. + * + * Prefer these methods in priority order (0 > 3 > 1 > 2) + */ +# define XXH_FORCE_MEMORY_ACCESS 0 + +/*! + * @def XXH_FORCE_ALIGN_CHECK + * @brief If defined to non-zero, adds a special path for aligned inputs (XXH32() + * and XXH64() only). + * + * This is an important performance trick for architectures without decent + * unaligned memory access performance. + * + * It checks for input alignment, and when conditions are met, uses a "fast + * path" employing direct 32-bit/64-bit reads, resulting in _dramatically + * faster_ read speed. + * + * The check costs one initial branch per hash, which is generally negligible, + * but not zero. + * + * Moreover, it's not useful to generate an additional code path if memory + * access uses the same instruction for both aligned and unaligned + * addresses (e.g. x86 and aarch64). + * + * In these cases, the alignment check can be removed by setting this macro to 0. + * Then the code will always use unaligned memory access. + * Align check is automatically disabled on x86, x64 & arm64, + * which are platforms known to offer good unaligned memory accesses performance. + * + * This option does not affect XXH3 (only XXH32 and XXH64). + */ +# define XXH_FORCE_ALIGN_CHECK 0 + +/*! + * @def XXH_NO_INLINE_HINTS + * @brief When non-zero, sets all functions to `static`. + * + * By default, xxHash tries to force the compiler to inline almost all internal + * functions. + * + * This can usually improve performance due to reduced jumping and improved + * constant folding, but significantly increases the size of the binary which + * might not be favorable. + * + * Additionally, sometimes the forced inlining can be detrimental to performance, + * depending on the architecture. + * + * XXH_NO_INLINE_HINTS marks all internal functions as static, giving the + * compiler full control on whether to inline or not. + * + * When not optimizing (-O0), optimizing for size (-Os, -Oz), or using + * -fno-inline with GCC or Clang, this will automatically be defined. + */ +# define XXH_NO_INLINE_HINTS 0 + +/*! + * @def XXH32_ENDJMP + * @brief Whether to use a jump for `XXH32_finalize`. + * + * For performance, `XXH32_finalize` uses multiple branches in the finalizer. + * This is generally preferable for performance, + * but depending on exact architecture, a jmp may be preferable. + * + * This setting is only possibly making a difference for very small inputs. + */ +# define XXH32_ENDJMP 0 + +/*! + * @internal + * @brief Redefines old internal names. + * + * For compatibility with code that uses xxHash's internals before the names + * were changed to improve namespacing. There is no other reason to use this. + */ +# define XXH_OLD_NAMES +# undef XXH_OLD_NAMES /* don't actually use, it is ugly. */ +#endif /* XXH_DOXYGEN */ +/*! + * @} + */ + +#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ + /* prefer __packed__ structures (method 1) for gcc on armv7+ and mips */ +# if !defined(__clang__) && \ +( \ + (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \ + ( \ + defined(__GNUC__) && ( \ + (defined(__ARM_ARCH) && __ARM_ARCH >= 7) || \ + ( \ + defined(__mips__) && \ + (__mips <= 5 || __mips_isa_rev < 6) && \ + (!defined(__mips16) || defined(__mips_mips16e2)) \ + ) \ + ) \ + ) \ +) +# define XXH_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ +# if defined(__i386) || defined(__x86_64__) || defined(__aarch64__) \ + || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) /* visual */ +# define XXH_FORCE_ALIGN_CHECK 0 +# else +# define XXH_FORCE_ALIGN_CHECK 1 +# endif +#endif + +#ifndef XXH_NO_INLINE_HINTS +# if defined(__OPTIMIZE_SIZE__) /* -Os, -Oz */ \ + || defined(__NO_INLINE__) /* -O0, -fno-inline */ +# define XXH_NO_INLINE_HINTS 1 +# else +# define XXH_NO_INLINE_HINTS 0 +# endif +#endif + +#ifndef XXH32_ENDJMP +/* generally preferable for performance */ +# define XXH32_ENDJMP 0 +#endif + +/*! + * @defgroup impl Implementation + * @{ + */ + + +/* ************************************* +* Includes & Memory related functions +***************************************/ +/* Modify the local functions below should you wish to use some other memory routines */ +/* for ZSTD_malloc(), ZSTD_free() */ +#define ZSTD_DEPS_NEED_MALLOC +#include "zstd_deps.h" /* size_t, ZSTD_malloc, ZSTD_free, ZSTD_memcpy */ +static void* XXH_malloc(size_t s) { return ZSTD_malloc(s); } +static void XXH_free (void* p) { ZSTD_free(p); } +static void* XXH_memcpy(void* dest, const void* src, size_t size) { return ZSTD_memcpy(dest,src,size); } + + +/* ************************************* +* Compiler Specific Options +***************************************/ +#ifdef _MSC_VER /* Visual Studio warning fix */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +#endif + +#if XXH_NO_INLINE_HINTS /* disable inlining hints */ +# if defined(__GNUC__) || defined(__clang__) +# define XXH_FORCE_INLINE static __attribute__((unused)) +# else +# define XXH_FORCE_INLINE static +# endif +# define XXH_NO_INLINE static +/* enable inlining hints */ +#elif defined(__GNUC__) || defined(__clang__) +# define XXH_FORCE_INLINE static __inline__ __attribute__((always_inline, unused)) +# define XXH_NO_INLINE static __attribute__((noinline)) +#elif defined(_MSC_VER) /* Visual Studio */ +# define XXH_FORCE_INLINE static __forceinline +# define XXH_NO_INLINE static __declspec(noinline) +#elif defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) /* C99 */ +# define XXH_FORCE_INLINE static inline +# define XXH_NO_INLINE static +#else +# define XXH_FORCE_INLINE static +# define XXH_NO_INLINE static +#endif + + + +/* ************************************* +* Debug +***************************************/ +/*! + * @ingroup tuning + * @def XXH_DEBUGLEVEL + * @brief Sets the debugging level. + * + * XXH_DEBUGLEVEL is expected to be defined externally, typically via the + * compiler's command line options. The value must be a number. + */ +#ifndef XXH_DEBUGLEVEL +# ifdef DEBUGLEVEL /* backwards compat */ +# define XXH_DEBUGLEVEL DEBUGLEVEL +# else +# define XXH_DEBUGLEVEL 0 +# endif +#endif + +#if (XXH_DEBUGLEVEL>=1) +# include /* note: can still be disabled with NDEBUG */ +# define XXH_ASSERT(c) assert(c) +#else +# define XXH_ASSERT(c) ((void)0) +#endif + +/* note: use after variable declarations */ +#ifndef XXH_STATIC_ASSERT +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ +# include +# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0) +# elif defined(__cplusplus) && (__cplusplus >= 201103L) /* C++11 */ +# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0) +# else +# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { struct xxh_sa { char x[(c) ? 1 : -1]; }; } while(0) +# endif +# define XXH_STATIC_ASSERT(c) XXH_STATIC_ASSERT_WITH_MESSAGE((c),#c) +#endif + +/*! + * @internal + * @def XXH_COMPILER_GUARD(var) + * @brief Used to prevent unwanted optimizations for @p var. + * + * It uses an empty GCC inline assembly statement with a register constraint + * which forces @p var into a general purpose register (e.g. eax, ebx, ecx + * on x86) and marks it as modified. + * + * This is used in a few places to avoid unwanted autovectorization (e.g. + * XXH32_round()). All vectorization we want is explicit via intrinsics, + * and _usually_ isn't wanted elsewhere. + * + * We also use it to prevent unwanted constant folding for AArch64 in + * XXH3_initCustomSecret_scalar(). + */ +#if defined(__GNUC__) || defined(__clang__) +# define XXH_COMPILER_GUARD(var) __asm__ __volatile__("" : "+r" (var)) +#else +# define XXH_COMPILER_GUARD(var) ((void)0) +#endif + +/* ************************************* +* Basic Types +***************************************/ +#if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t xxh_u8; +#else + typedef unsigned char xxh_u8; +#endif +typedef XXH32_hash_t xxh_u32; + +#ifdef XXH_OLD_NAMES +# define BYTE xxh_u8 +# define U8 xxh_u8 +# define U32 xxh_u32 +#endif + +/* *** Memory access *** */ + +/*! + * @internal + * @fn xxh_u32 XXH_read32(const void* ptr) + * @brief Reads an unaligned 32-bit integer from @p ptr in native endianness. + * + * Affected by @ref XXH_FORCE_MEMORY_ACCESS. + * + * @param ptr The pointer to read from. + * @return The 32-bit native endian integer from the bytes at @p ptr. + */ + +/*! + * @internal + * @fn xxh_u32 XXH_readLE32(const void* ptr) + * @brief Reads an unaligned 32-bit little endian integer from @p ptr. + * + * Affected by @ref XXH_FORCE_MEMORY_ACCESS. + * + * @param ptr The pointer to read from. + * @return The 32-bit little endian integer from the bytes at @p ptr. + */ + +/*! + * @internal + * @fn xxh_u32 XXH_readBE32(const void* ptr) + * @brief Reads an unaligned 32-bit big endian integer from @p ptr. + * + * Affected by @ref XXH_FORCE_MEMORY_ACCESS. + * + * @param ptr The pointer to read from. + * @return The 32-bit big endian integer from the bytes at @p ptr. + */ + +/*! + * @internal + * @fn xxh_u32 XXH_readLE32_align(const void* ptr, XXH_alignment align) + * @brief Like @ref XXH_readLE32(), but has an option for aligned reads. + * + * Affected by @ref XXH_FORCE_MEMORY_ACCESS. + * Note that when @ref XXH_FORCE_ALIGN_CHECK == 0, the @p align parameter is + * always @ref XXH_alignment::XXH_unaligned. + * + * @param ptr The pointer to read from. + * @param align Whether @p ptr is aligned. + * @pre + * If @p align == @ref XXH_alignment::XXH_aligned, @p ptr must be 4 byte + * aligned. + * @return The 32-bit little endian integer from the bytes at @p ptr. + */ + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) +/* + * Manual byteshift. Best for old compilers which don't inline memcpy. + * We actually directly use XXH_readLE32 and XXH_readBE32. + */ +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* + * Force direct memory access. Only works on CPU which support unaligned memory + * access in hardware. + */ +static xxh_u32 XXH_read32(const void* memPtr) { return *(const xxh_u32*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* + * __pack instructions are safer but compiler specific, hence potentially + * problematic for some compilers. + * + * Currently only defined for GCC and ICC. + */ +#ifdef XXH_OLD_NAMES +typedef union { xxh_u32 u32; } __attribute__((packed)) unalign; +#endif +static xxh_u32 XXH_read32(const void* ptr) +{ + typedef union { xxh_u32 u32; } __attribute__((packed)) xxh_unalign; + return ((const xxh_unalign*)ptr)->u32; +} + +#else + +/* + * Portable and safe solution. Generally efficient. + * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html + */ +static xxh_u32 XXH_read32(const void* memPtr) +{ + xxh_u32 val; + XXH_memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + + +/* *** Endianness *** */ + +/*! + * @ingroup tuning + * @def XXH_CPU_LITTLE_ENDIAN + * @brief Whether the target is little endian. + * + * Defined to 1 if the target is little endian, or 0 if it is big endian. + * It can be defined externally, for example on the compiler command line. + * + * If it is not defined, + * a runtime check (which is usually constant folded) is used instead. + * + * @note + * This is not necessarily defined to an integer constant. + * + * @see XXH_isLittleEndian() for the runtime check. + */ +#ifndef XXH_CPU_LITTLE_ENDIAN +/* + * Try to detect endianness automatically, to avoid the nonstandard behavior + * in `XXH_isLittleEndian()` + */ +# if defined(_WIN32) /* Windows is always little endian */ \ + || defined(__LITTLE_ENDIAN__) \ + || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define XXH_CPU_LITTLE_ENDIAN 1 +# elif defined(__BIG_ENDIAN__) \ + || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define XXH_CPU_LITTLE_ENDIAN 0 +# else +/*! + * @internal + * @brief Runtime check for @ref XXH_CPU_LITTLE_ENDIAN. + * + * Most compilers will constant fold this. + */ +static int XXH_isLittleEndian(void) +{ + /* + * Portable and well-defined behavior. + * Don't use static: it is detrimental to performance. + */ + const union { xxh_u32 u; xxh_u8 c[4]; } one = { 1 }; + return one.c[0]; +} +# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian() +# endif +#endif + + + + +/* **************************************** +* Compiler-specific Functions and Macros +******************************************/ +#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) + +#ifdef __has_builtin +# define XXH_HAS_BUILTIN(x) __has_builtin(x) +#else +# define XXH_HAS_BUILTIN(x) 0 +#endif + +/*! + * @internal + * @def XXH_rotl32(x,r) + * @brief 32-bit rotate left. + * + * @param x The 32-bit integer to be rotated. + * @param r The number of bits to rotate. + * @pre + * @p r > 0 && @p r < 32 + * @note + * @p x and @p r may be evaluated multiple times. + * @return The rotated result. + */ +#if !defined(NO_CLANG_BUILTIN) && XXH_HAS_BUILTIN(__builtin_rotateleft32) \ + && XXH_HAS_BUILTIN(__builtin_rotateleft64) +# define XXH_rotl32 __builtin_rotateleft32 +# define XXH_rotl64 __builtin_rotateleft64 +/* Note: although _rotl exists for minGW (GCC under windows), performance seems poor */ +#elif defined(_MSC_VER) +# define XXH_rotl32(x,r) _rotl(x,r) +# define XXH_rotl64(x,r) _rotl64(x,r) +#else +# define XXH_rotl32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +# define XXH_rotl64(x,r) (((x) << (r)) | ((x) >> (64 - (r)))) +#endif + +/*! + * @internal + * @fn xxh_u32 XXH_swap32(xxh_u32 x) + * @brief A 32-bit byteswap. + * + * @param x The 32-bit integer to byteswap. + * @return @p x, byteswapped. + */ +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap32 _byteswap_ulong +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap32 __builtin_bswap32 +#else +static xxh_u32 XXH_swap32 (xxh_u32 x) +{ + return ((x << 24) & 0xff000000 ) | + ((x << 8) & 0x00ff0000 ) | + ((x >> 8) & 0x0000ff00 ) | + ((x >> 24) & 0x000000ff ); +} +#endif + + +/* *************************** +* Memory reads +*****************************/ + +/*! + * @internal + * @brief Enum to indicate whether a pointer is aligned. + */ +typedef enum { + XXH_aligned, /*!< Aligned */ + XXH_unaligned /*!< Possibly unaligned */ +} XXH_alignment; + +/* + * XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load. + * + * This is ideal for older compilers which don't inline memcpy. + */ +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) + +XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* memPtr) +{ + const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; + return bytePtr[0] + | ((xxh_u32)bytePtr[1] << 8) + | ((xxh_u32)bytePtr[2] << 16) + | ((xxh_u32)bytePtr[3] << 24); +} + +XXH_FORCE_INLINE xxh_u32 XXH_readBE32(const void* memPtr) +{ + const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; + return bytePtr[3] + | ((xxh_u32)bytePtr[2] << 8) + | ((xxh_u32)bytePtr[1] << 16) + | ((xxh_u32)bytePtr[0] << 24); +} + +#else +XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); +} + +static xxh_u32 XXH_readBE32(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); +} +#endif + +XXH_FORCE_INLINE xxh_u32 +XXH_readLE32_align(const void* ptr, XXH_alignment align) +{ + if (align==XXH_unaligned) { + return XXH_readLE32(ptr); + } else { + return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u32*)ptr : XXH_swap32(*(const xxh_u32*)ptr); + } +} + + +/* ************************************* +* Misc +***************************************/ +/*! @ingroup public */ +XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } + + +/* ******************************************************************* +* 32-bit hash functions +*********************************************************************/ +/*! + * @} + * @defgroup xxh32_impl XXH32 implementation + * @ingroup impl + * @{ + */ + /* #define instead of static const, to be used as initializers */ +#define XXH_PRIME32_1 0x9E3779B1U /*!< 0b10011110001101110111100110110001 */ +#define XXH_PRIME32_2 0x85EBCA77U /*!< 0b10000101111010111100101001110111 */ +#define XXH_PRIME32_3 0xC2B2AE3DU /*!< 0b11000010101100101010111000111101 */ +#define XXH_PRIME32_4 0x27D4EB2FU /*!< 0b00100111110101001110101100101111 */ +#define XXH_PRIME32_5 0x165667B1U /*!< 0b00010110010101100110011110110001 */ + +#ifdef XXH_OLD_NAMES +# define PRIME32_1 XXH_PRIME32_1 +# define PRIME32_2 XXH_PRIME32_2 +# define PRIME32_3 XXH_PRIME32_3 +# define PRIME32_4 XXH_PRIME32_4 +# define PRIME32_5 XXH_PRIME32_5 +#endif + +/*! + * @internal + * @brief Normal stripe processing routine. + * + * This shuffles the bits so that any bit from @p input impacts several bits in + * @p acc. + * + * @param acc The accumulator lane. + * @param input The stripe of input to mix. + * @return The mixed accumulator lane. + */ +static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input) +{ + acc += input * XXH_PRIME32_2; + acc = XXH_rotl32(acc, 13); + acc *= XXH_PRIME32_1; +#if (defined(__SSE4_1__) || defined(__aarch64__)) && !defined(XXH_ENABLE_AUTOVECTORIZE) + /* + * UGLY HACK: + * A compiler fence is the only thing that prevents GCC and Clang from + * autovectorizing the XXH32 loop (pragmas and attributes don't work for some + * reason) without globally disabling SSE4.1. + * + * The reason we want to avoid vectorization is because despite working on + * 4 integers at a time, there are multiple factors slowing XXH32 down on + * SSE4: + * - There's a ridiculous amount of lag from pmulld (10 cycles of latency on + * newer chips!) making it slightly slower to multiply four integers at + * once compared to four integers independently. Even when pmulld was + * fastest, Sandy/Ivy Bridge, it is still not worth it to go into SSE + * just to multiply unless doing a long operation. + * + * - Four instructions are required to rotate, + * movqda tmp, v // not required with VEX encoding + * pslld tmp, 13 // tmp <<= 13 + * psrld v, 19 // x >>= 19 + * por v, tmp // x |= tmp + * compared to one for scalar: + * roll v, 13 // reliably fast across the board + * shldl v, v, 13 // Sandy Bridge and later prefer this for some reason + * + * - Instruction level parallelism is actually more beneficial here because + * the SIMD actually serializes this operation: While v1 is rotating, v2 + * can load data, while v3 can multiply. SSE forces them to operate + * together. + * + * This is also enabled on AArch64, as Clang autovectorizes it incorrectly + * and it is pointless writing a NEON implementation that is basically the + * same speed as scalar for XXH32. + */ + XXH_COMPILER_GUARD(acc); +#endif + return acc; +} + +/*! + * @internal + * @brief Mixes all bits to finalize the hash. + * + * The final mix ensures that all input bits have a chance to impact any bit in + * the output digest, resulting in an unbiased distribution. + * + * @param h32 The hash to avalanche. + * @return The avalanched hash. + */ +static xxh_u32 XXH32_avalanche(xxh_u32 h32) +{ + h32 ^= h32 >> 15; + h32 *= XXH_PRIME32_2; + h32 ^= h32 >> 13; + h32 *= XXH_PRIME32_3; + h32 ^= h32 >> 16; + return(h32); +} + +#define XXH_get32bits(p) XXH_readLE32_align(p, align) + +/*! + * @internal + * @brief Processes the last 0-15 bytes of @p ptr. + * + * There may be up to 15 bytes remaining to consume from the input. + * This final stage will digest them to ensure that all input bytes are present + * in the final mix. + * + * @param h32 The hash to finalize. + * @param ptr The pointer to the remaining input. + * @param len The remaining length, modulo 16. + * @param align Whether @p ptr is aligned. + * @return The finalized hash. + */ +static xxh_u32 +XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align) +{ +#define XXH_PROCESS1 do { \ + h32 += (*ptr++) * XXH_PRIME32_5; \ + h32 = XXH_rotl32(h32, 11) * XXH_PRIME32_1; \ +} while (0) + +#define XXH_PROCESS4 do { \ + h32 += XXH_get32bits(ptr) * XXH_PRIME32_3; \ + ptr += 4; \ + h32 = XXH_rotl32(h32, 17) * XXH_PRIME32_4; \ +} while (0) + + if (ptr==NULL) XXH_ASSERT(len == 0); + + /* Compact rerolled version; generally faster */ + if (!XXH32_ENDJMP) { + len &= 15; + while (len >= 4) { + XXH_PROCESS4; + len -= 4; + } + while (len > 0) { + XXH_PROCESS1; + --len; + } + return XXH32_avalanche(h32); + } else { + switch(len&15) /* or switch(bEnd - p) */ { + case 12: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 8: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 4: XXH_PROCESS4; + return XXH32_avalanche(h32); + + case 13: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 9: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 5: XXH_PROCESS4; + XXH_PROCESS1; + return XXH32_avalanche(h32); + + case 14: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 10: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 6: XXH_PROCESS4; + XXH_PROCESS1; + XXH_PROCESS1; + return XXH32_avalanche(h32); + + case 15: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 11: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 7: XXH_PROCESS4; + XXH_FALLTHROUGH; + case 3: XXH_PROCESS1; + XXH_FALLTHROUGH; + case 2: XXH_PROCESS1; + XXH_FALLTHROUGH; + case 1: XXH_PROCESS1; + XXH_FALLTHROUGH; + case 0: return XXH32_avalanche(h32); + } + XXH_ASSERT(0); + return h32; /* reaching this point is deemed impossible */ + } +} + +#ifdef XXH_OLD_NAMES +# define PROCESS1 XXH_PROCESS1 +# define PROCESS4 XXH_PROCESS4 +#else +# undef XXH_PROCESS1 +# undef XXH_PROCESS4 +#endif + +/*! + * @internal + * @brief The implementation for @ref XXH32(). + * + * @param input , len , seed Directly passed from @ref XXH32(). + * @param align Whether @p input is aligned. + * @return The calculated hash. + */ +XXH_FORCE_INLINE xxh_u32 +XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align) +{ + xxh_u32 h32; + + if (input==NULL) XXH_ASSERT(len == 0); + + if (len>=16) { + const xxh_u8* const bEnd = input + len; + const xxh_u8* const limit = bEnd - 15; + xxh_u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2; + xxh_u32 v2 = seed + XXH_PRIME32_2; + xxh_u32 v3 = seed + 0; + xxh_u32 v4 = seed - XXH_PRIME32_1; + + do { + v1 = XXH32_round(v1, XXH_get32bits(input)); input += 4; + v2 = XXH32_round(v2, XXH_get32bits(input)); input += 4; + v3 = XXH32_round(v3, XXH_get32bits(input)); input += 4; + v4 = XXH32_round(v4, XXH_get32bits(input)); input += 4; + } while (input < limit); + + h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); + } else { + h32 = seed + XXH_PRIME32_5; + } + + h32 += (xxh_u32)len; + + return XXH32_finalize(h32, input, len&15, align); +} + +/*! @ingroup xxh32_family */ +XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH32_state_t state; + XXH32_reset(&state, seed); + XXH32_update(&state, (const xxh_u8*)input, len); + return XXH32_digest(&state); +#else + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ + return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_aligned); + } } + + return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned); +#endif +} + + + +/******* Hash streaming *******/ +/*! + * @ingroup xxh32_family + */ +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) +{ + return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); +} +/*! @ingroup xxh32_family */ +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +/*! @ingroup xxh32_family */ +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) +{ + XXH_memcpy(dstState, srcState, sizeof(*dstState)); +} + +/*! @ingroup xxh32_family */ +XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed) +{ + XXH_ASSERT(statePtr != NULL); + memset(statePtr, 0, sizeof(*statePtr)); + statePtr->v[0] = seed + XXH_PRIME32_1 + XXH_PRIME32_2; + statePtr->v[1] = seed + XXH_PRIME32_2; + statePtr->v[2] = seed + 0; + statePtr->v[3] = seed - XXH_PRIME32_1; + return XXH_OK; +} + + +/*! @ingroup xxh32_family */ +XXH_PUBLIC_API XXH_errorcode +XXH32_update(XXH32_state_t* state, const void* input, size_t len) +{ + if (input==NULL) { + XXH_ASSERT(len == 0); + return XXH_OK; + } + + { const xxh_u8* p = (const xxh_u8*)input; + const xxh_u8* const bEnd = p + len; + + state->total_len_32 += (XXH32_hash_t)len; + state->large_len |= (XXH32_hash_t)((len>=16) | (state->total_len_32>=16)); + + if (state->memsize + len < 16) { /* fill in tmp buffer */ + XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, len); + state->memsize += (XXH32_hash_t)len; + return XXH_OK; + } + + if (state->memsize) { /* some data left from previous update */ + XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, 16-state->memsize); + { const xxh_u32* p32 = state->mem32; + state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p32)); p32++; + state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p32)); p32++; + state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p32)); p32++; + state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p32)); + } + p += 16-state->memsize; + state->memsize = 0; + } + + if (p <= bEnd-16) { + const xxh_u8* const limit = bEnd - 16; + + do { + state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p)); p+=4; + state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p)); p+=4; + state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p)); p+=4; + state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p)); p+=4; + } while (p<=limit); + + } + + if (p < bEnd) { + XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + } + + return XXH_OK; +} + + +/*! @ingroup xxh32_family */ +XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state) +{ + xxh_u32 h32; + + if (state->large_len) { + h32 = XXH_rotl32(state->v[0], 1) + + XXH_rotl32(state->v[1], 7) + + XXH_rotl32(state->v[2], 12) + + XXH_rotl32(state->v[3], 18); + } else { + h32 = state->v[2] /* == seed */ + XXH_PRIME32_5; + } + + h32 += state->total_len_32; + + return XXH32_finalize(h32, (const xxh_u8*)state->mem32, state->memsize, XXH_aligned); +} + + +/******* Canonical representation *******/ + +/*! + * @ingroup xxh32_family + * The default return values from XXH functions are unsigned 32 and 64 bit + * integers. + * + * The canonical representation uses big endian convention, the same convention + * as human-readable numbers (large digits first). + * + * This way, hash values can be written into a file or buffer, remaining + * comparable across different systems. + * + * The following functions allow transformation of hash values to and from their + * canonical format. + */ +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) +{ + /* XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); */ + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); + XXH_memcpy(dst, &hash, sizeof(*dst)); +} +/*! @ingroup xxh32_family */ +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) +{ + return XXH_readBE32(src); +} + + +#ifndef XXH_NO_LONG_LONG + +/* ******************************************************************* +* 64-bit hash functions +*********************************************************************/ +/*! + * @} + * @ingroup impl + * @{ + */ +/******* Memory access *******/ + +typedef XXH64_hash_t xxh_u64; + +#ifdef XXH_OLD_NAMES +# define U64 xxh_u64 +#endif + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) +/* + * Manual byteshift. Best for old compilers which don't inline memcpy. + * We actually directly use XXH_readLE64 and XXH_readBE64. + */ +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static xxh_u64 XXH_read64(const void* memPtr) +{ + return *(const xxh_u64*) memPtr; +} + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* + * __pack instructions are safer, but compiler specific, hence potentially + * problematic for some compilers. + * + * Currently only defined for GCC and ICC. + */ +#ifdef XXH_OLD_NAMES +typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) unalign64; +#endif +static xxh_u64 XXH_read64(const void* ptr) +{ + typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) xxh_unalign64; + return ((const xxh_unalign64*)ptr)->u64; +} + +#else + +/* + * Portable and safe solution. Generally efficient. + * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html + */ +static xxh_u64 XXH_read64(const void* memPtr) +{ + xxh_u64 val; + XXH_memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap64 _byteswap_uint64 +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap64 __builtin_bswap64 +#else +static xxh_u64 XXH_swap64(xxh_u64 x) +{ + return ((x << 56) & 0xff00000000000000ULL) | + ((x << 40) & 0x00ff000000000000ULL) | + ((x << 24) & 0x0000ff0000000000ULL) | + ((x << 8) & 0x000000ff00000000ULL) | + ((x >> 8) & 0x00000000ff000000ULL) | + ((x >> 24) & 0x0000000000ff0000ULL) | + ((x >> 40) & 0x000000000000ff00ULL) | + ((x >> 56) & 0x00000000000000ffULL); +} +#endif + + +/* XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load. */ +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) + +XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* memPtr) +{ + const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; + return bytePtr[0] + | ((xxh_u64)bytePtr[1] << 8) + | ((xxh_u64)bytePtr[2] << 16) + | ((xxh_u64)bytePtr[3] << 24) + | ((xxh_u64)bytePtr[4] << 32) + | ((xxh_u64)bytePtr[5] << 40) + | ((xxh_u64)bytePtr[6] << 48) + | ((xxh_u64)bytePtr[7] << 56); +} + +XXH_FORCE_INLINE xxh_u64 XXH_readBE64(const void* memPtr) +{ + const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; + return bytePtr[7] + | ((xxh_u64)bytePtr[6] << 8) + | ((xxh_u64)bytePtr[5] << 16) + | ((xxh_u64)bytePtr[4] << 24) + | ((xxh_u64)bytePtr[3] << 32) + | ((xxh_u64)bytePtr[2] << 40) + | ((xxh_u64)bytePtr[1] << 48) + | ((xxh_u64)bytePtr[0] << 56); +} + +#else +XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); +} + +static xxh_u64 XXH_readBE64(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); +} +#endif + +XXH_FORCE_INLINE xxh_u64 +XXH_readLE64_align(const void* ptr, XXH_alignment align) +{ + if (align==XXH_unaligned) + return XXH_readLE64(ptr); + else + return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u64*)ptr : XXH_swap64(*(const xxh_u64*)ptr); +} + + +/******* xxh64 *******/ +/*! + * @} + * @defgroup xxh64_impl XXH64 implementation + * @ingroup impl + * @{ + */ +/* #define rather that static const, to be used as initializers */ +#define XXH_PRIME64_1 0x9E3779B185EBCA87ULL /*!< 0b1001111000110111011110011011000110000101111010111100101010000111 */ +#define XXH_PRIME64_2 0xC2B2AE3D27D4EB4FULL /*!< 0b1100001010110010101011100011110100100111110101001110101101001111 */ +#define XXH_PRIME64_3 0x165667B19E3779F9ULL /*!< 0b0001011001010110011001111011000110011110001101110111100111111001 */ +#define XXH_PRIME64_4 0x85EBCA77C2B2AE63ULL /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */ +#define XXH_PRIME64_5 0x27D4EB2F165667C5ULL /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */ + +#ifdef XXH_OLD_NAMES +# define PRIME64_1 XXH_PRIME64_1 +# define PRIME64_2 XXH_PRIME64_2 +# define PRIME64_3 XXH_PRIME64_3 +# define PRIME64_4 XXH_PRIME64_4 +# define PRIME64_5 XXH_PRIME64_5 +#endif + +static xxh_u64 XXH64_round(xxh_u64 acc, xxh_u64 input) +{ + acc += input * XXH_PRIME64_2; + acc = XXH_rotl64(acc, 31); + acc *= XXH_PRIME64_1; + return acc; +} + +static xxh_u64 XXH64_mergeRound(xxh_u64 acc, xxh_u64 val) +{ + val = XXH64_round(0, val); + acc ^= val; + acc = acc * XXH_PRIME64_1 + XXH_PRIME64_4; + return acc; +} + +static xxh_u64 XXH64_avalanche(xxh_u64 h64) +{ + h64 ^= h64 >> 33; + h64 *= XXH_PRIME64_2; + h64 ^= h64 >> 29; + h64 *= XXH_PRIME64_3; + h64 ^= h64 >> 32; + return h64; +} + + +#define XXH_get64bits(p) XXH_readLE64_align(p, align) + +static xxh_u64 +XXH64_finalize(xxh_u64 h64, const xxh_u8* ptr, size_t len, XXH_alignment align) +{ + if (ptr==NULL) XXH_ASSERT(len == 0); + len &= 31; + while (len >= 8) { + xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr)); + ptr += 8; + h64 ^= k1; + h64 = XXH_rotl64(h64,27) * XXH_PRIME64_1 + XXH_PRIME64_4; + len -= 8; + } + if (len >= 4) { + h64 ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1; + ptr += 4; + h64 = XXH_rotl64(h64, 23) * XXH_PRIME64_2 + XXH_PRIME64_3; + len -= 4; + } + while (len > 0) { + h64 ^= (*ptr++) * XXH_PRIME64_5; + h64 = XXH_rotl64(h64, 11) * XXH_PRIME64_1; + --len; + } + return XXH64_avalanche(h64); +} + +#ifdef XXH_OLD_NAMES +# define PROCESS1_64 XXH_PROCESS1_64 +# define PROCESS4_64 XXH_PROCESS4_64 +# define PROCESS8_64 XXH_PROCESS8_64 +#else +# undef XXH_PROCESS1_64 +# undef XXH_PROCESS4_64 +# undef XXH_PROCESS8_64 +#endif + +XXH_FORCE_INLINE xxh_u64 +XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align) +{ + xxh_u64 h64; + if (input==NULL) XXH_ASSERT(len == 0); + + if (len>=32) { + const xxh_u8* const bEnd = input + len; + const xxh_u8* const limit = bEnd - 31; + xxh_u64 v1 = seed + XXH_PRIME64_1 + XXH_PRIME64_2; + xxh_u64 v2 = seed + XXH_PRIME64_2; + xxh_u64 v3 = seed + 0; + xxh_u64 v4 = seed - XXH_PRIME64_1; + + do { + v1 = XXH64_round(v1, XXH_get64bits(input)); input+=8; + v2 = XXH64_round(v2, XXH_get64bits(input)); input+=8; + v3 = XXH64_round(v3, XXH_get64bits(input)); input+=8; + v4 = XXH64_round(v4, XXH_get64bits(input)); input+=8; + } while (inputv[0] = seed + XXH_PRIME64_1 + XXH_PRIME64_2; + statePtr->v[1] = seed + XXH_PRIME64_2; + statePtr->v[2] = seed + 0; + statePtr->v[3] = seed - XXH_PRIME64_1; + return XXH_OK; +} + +/*! @ingroup xxh64_family */ +XXH_PUBLIC_API XXH_errorcode +XXH64_update (XXH64_state_t* state, const void* input, size_t len) +{ + if (input==NULL) { + XXH_ASSERT(len == 0); + return XXH_OK; + } + + { const xxh_u8* p = (const xxh_u8*)input; + const xxh_u8* const bEnd = p + len; + + state->total_len += len; + + if (state->memsize + len < 32) { /* fill in tmp buffer */ + XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, len); + state->memsize += (xxh_u32)len; + return XXH_OK; + } + + if (state->memsize) { /* tmp buffer is full */ + XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, 32-state->memsize); + state->v[0] = XXH64_round(state->v[0], XXH_readLE64(state->mem64+0)); + state->v[1] = XXH64_round(state->v[1], XXH_readLE64(state->mem64+1)); + state->v[2] = XXH64_round(state->v[2], XXH_readLE64(state->mem64+2)); + state->v[3] = XXH64_round(state->v[3], XXH_readLE64(state->mem64+3)); + p += 32 - state->memsize; + state->memsize = 0; + } + + if (p+32 <= bEnd) { + const xxh_u8* const limit = bEnd - 32; + + do { + state->v[0] = XXH64_round(state->v[0], XXH_readLE64(p)); p+=8; + state->v[1] = XXH64_round(state->v[1], XXH_readLE64(p)); p+=8; + state->v[2] = XXH64_round(state->v[2], XXH_readLE64(p)); p+=8; + state->v[3] = XXH64_round(state->v[3], XXH_readLE64(p)); p+=8; + } while (p<=limit); + + } + + if (p < bEnd) { + XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + } + + return XXH_OK; +} + + +/*! @ingroup xxh64_family */ +XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* state) +{ + xxh_u64 h64; + + if (state->total_len >= 32) { + h64 = XXH_rotl64(state->v[0], 1) + XXH_rotl64(state->v[1], 7) + XXH_rotl64(state->v[2], 12) + XXH_rotl64(state->v[3], 18); + h64 = XXH64_mergeRound(h64, state->v[0]); + h64 = XXH64_mergeRound(h64, state->v[1]); + h64 = XXH64_mergeRound(h64, state->v[2]); + h64 = XXH64_mergeRound(h64, state->v[3]); + } else { + h64 = state->v[2] /*seed*/ + XXH_PRIME64_5; + } + + h64 += (xxh_u64) state->total_len; + + return XXH64_finalize(h64, (const xxh_u8*)state->mem64, (size_t)state->total_len, XXH_aligned); +} + + +/******* Canonical representation *******/ + +/*! @ingroup xxh64_family */ +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) +{ + /* XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); */ + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); + XXH_memcpy(dst, &hash, sizeof(*dst)); +} + +/*! @ingroup xxh64_family */ +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) +{ + return XXH_readBE64(src); +} + +#ifndef XXH_NO_XXH3 + +/* ********************************************************************* +* XXH3 +* New generation hash designed for speed on small keys and vectorization +************************************************************************ */ +/*! + * @} + * @defgroup xxh3_impl XXH3 implementation + * @ingroup impl + * @{ + */ + +/* === Compiler specifics === */ + +#if ((defined(sun) || defined(__sun)) && __cplusplus) /* Solaris includes __STDC_VERSION__ with C++. Tested with GCC 5.5 */ +# define XXH_RESTRICT /* disable */ +#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */ +# define XXH_RESTRICT restrict +#else +/* Note: it might be useful to define __restrict or __restrict__ for some C++ compilers */ +# define XXH_RESTRICT /* disable */ +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) \ + || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) \ + || defined(__clang__) +# define XXH_likely(x) __builtin_expect(x, 1) +# define XXH_unlikely(x) __builtin_expect(x, 0) +#else +# define XXH_likely(x) (x) +# define XXH_unlikely(x) (x) +#endif + +#if defined(__GNUC__) || defined(__clang__) +# if defined(__ARM_NEON__) || defined(__ARM_NEON) \ + || defined(__aarch64__) || defined(_M_ARM) \ + || defined(_M_ARM64) || defined(_M_ARM64EC) +# define inline __inline__ /* circumvent a clang bug */ +# include +# undef inline +# elif defined(__AVX2__) +# include +# elif defined(__SSE2__) +# include +# endif +#endif + +#if defined(_MSC_VER) +# include +#endif + +/* + * One goal of XXH3 is to make it fast on both 32-bit and 64-bit, while + * remaining a true 64-bit/128-bit hash function. + * + * This is done by prioritizing a subset of 64-bit operations that can be + * emulated without too many steps on the average 32-bit machine. + * + * For example, these two lines seem similar, and run equally fast on 64-bit: + * + * xxh_u64 x; + * x ^= (x >> 47); // good + * x ^= (x >> 13); // bad + * + * However, to a 32-bit machine, there is a major difference. + * + * x ^= (x >> 47) looks like this: + * + * x.lo ^= (x.hi >> (47 - 32)); + * + * while x ^= (x >> 13) looks like this: + * + * // note: funnel shifts are not usually cheap. + * x.lo ^= (x.lo >> 13) | (x.hi << (32 - 13)); + * x.hi ^= (x.hi >> 13); + * + * The first one is significantly faster than the second, simply because the + * shift is larger than 32. This means: + * - All the bits we need are in the upper 32 bits, so we can ignore the lower + * 32 bits in the shift. + * - The shift result will always fit in the lower 32 bits, and therefore, + * we can ignore the upper 32 bits in the xor. + * + * Thanks to this optimization, XXH3 only requires these features to be efficient: + * + * - Usable unaligned access + * - A 32-bit or 64-bit ALU + * - If 32-bit, a decent ADC instruction + * - A 32 or 64-bit multiply with a 64-bit result + * - For the 128-bit variant, a decent byteswap helps short inputs. + * + * The first two are already required by XXH32, and almost all 32-bit and 64-bit + * platforms which can run XXH32 can run XXH3 efficiently. + * + * Thumb-1, the classic 16-bit only subset of ARM's instruction set, is one + * notable exception. + * + * First of all, Thumb-1 lacks support for the UMULL instruction which + * performs the important long multiply. This means numerous __aeabi_lmul + * calls. + * + * Second of all, the 8 functional registers are just not enough. + * Setup for __aeabi_lmul, byteshift loads, pointers, and all arithmetic need + * Lo registers, and this shuffling results in thousands more MOVs than A32. + * + * A32 and T32 don't have this limitation. They can access all 14 registers, + * do a 32->64 multiply with UMULL, and the flexible operand allowing free + * shifts is helpful, too. + * + * Therefore, we do a quick sanity check. + * + * If compiling Thumb-1 for a target which supports ARM instructions, we will + * emit a warning, as it is not a "sane" platform to compile for. + * + * Usually, if this happens, it is because of an accident and you probably need + * to specify -march, as you likely meant to compile for a newer architecture. + * + * Credit: large sections of the vectorial and asm source code paths + * have been contributed by @easyaspi314 + */ +#if defined(__thumb__) && !defined(__thumb2__) && defined(__ARM_ARCH_ISA_ARM) +# warning "XXH3 is highly inefficient without ARM or Thumb-2." +#endif + +/* ========================================== + * Vectorization detection + * ========================================== */ + +#ifdef XXH_DOXYGEN +/*! + * @ingroup tuning + * @brief Overrides the vectorization implementation chosen for XXH3. + * + * Can be defined to 0 to disable SIMD or any of the values mentioned in + * @ref XXH_VECTOR_TYPE. + * + * If this is not defined, it uses predefined macros to determine the best + * implementation. + */ +# define XXH_VECTOR XXH_SCALAR +/*! + * @ingroup tuning + * @brief Possible values for @ref XXH_VECTOR. + * + * Note that these are actually implemented as macros. + * + * If this is not defined, it is detected automatically. + * @ref XXH_X86DISPATCH overrides this. + */ +enum XXH_VECTOR_TYPE /* fake enum */ { + XXH_SCALAR = 0, /*!< Portable scalar version */ + XXH_SSE2 = 1, /*!< + * SSE2 for Pentium 4, Opteron, all x86_64. + * + * @note SSE2 is also guaranteed on Windows 10, macOS, and + * Android x86. + */ + XXH_AVX2 = 2, /*!< AVX2 for Haswell and Bulldozer */ + XXH_AVX512 = 3, /*!< AVX512 for Skylake and Icelake */ + XXH_NEON = 4, /*!< NEON for most ARMv7-A and all AArch64 */ + XXH_VSX = 5, /*!< VSX and ZVector for POWER8/z13 (64-bit) */ +}; +/*! + * @ingroup tuning + * @brief Selects the minimum alignment for XXH3's accumulators. + * + * When using SIMD, this should match the alignment required for said vector + * type, so, for example, 32 for AVX2. + * + * Default: Auto detected. + */ +# define XXH_ACC_ALIGN 8 +#endif + +/* Actual definition */ +#ifndef XXH_DOXYGEN +# define XXH_SCALAR 0 +# define XXH_SSE2 1 +# define XXH_AVX2 2 +# define XXH_AVX512 3 +# define XXH_NEON 4 +# define XXH_VSX 5 +#endif + +#ifndef XXH_VECTOR /* can be defined on command line */ +# if ( \ + defined(__ARM_NEON__) || defined(__ARM_NEON) /* gcc */ \ + || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) /* msvc */ \ + ) && ( \ + defined(_WIN32) || defined(__LITTLE_ENDIAN__) /* little endian only */ \ + || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \ + ) +# define XXH_VECTOR XXH_NEON +# elif defined(__AVX512F__) +# define XXH_VECTOR XXH_AVX512 +# elif defined(__AVX2__) +# define XXH_VECTOR XXH_AVX2 +# elif defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2)) +# define XXH_VECTOR XXH_SSE2 +# elif (defined(__PPC64__) && defined(__POWER8_VECTOR__)) \ + || (defined(__s390x__) && defined(__VEC__)) \ + && defined(__GNUC__) /* TODO: IBM XL */ +# define XXH_VECTOR XXH_VSX +# else +# define XXH_VECTOR XXH_SCALAR +# endif +#endif + +/* + * Controls the alignment of the accumulator, + * for compatibility with aligned vector loads, which are usually faster. + */ +#ifndef XXH_ACC_ALIGN +# if defined(XXH_X86DISPATCH) +# define XXH_ACC_ALIGN 64 /* for compatibility with avx512 */ +# elif XXH_VECTOR == XXH_SCALAR /* scalar */ +# define XXH_ACC_ALIGN 8 +# elif XXH_VECTOR == XXH_SSE2 /* sse2 */ +# define XXH_ACC_ALIGN 16 +# elif XXH_VECTOR == XXH_AVX2 /* avx2 */ +# define XXH_ACC_ALIGN 32 +# elif XXH_VECTOR == XXH_NEON /* neon */ +# define XXH_ACC_ALIGN 16 +# elif XXH_VECTOR == XXH_VSX /* vsx */ +# define XXH_ACC_ALIGN 16 +# elif XXH_VECTOR == XXH_AVX512 /* avx512 */ +# define XXH_ACC_ALIGN 64 +# endif +#endif + +#if defined(XXH_X86DISPATCH) || XXH_VECTOR == XXH_SSE2 \ + || XXH_VECTOR == XXH_AVX2 || XXH_VECTOR == XXH_AVX512 +# define XXH_SEC_ALIGN XXH_ACC_ALIGN +#else +# define XXH_SEC_ALIGN 8 +#endif + +/* + * UGLY HACK: + * GCC usually generates the best code with -O3 for xxHash. + * + * However, when targeting AVX2, it is overzealous in its unrolling resulting + * in code roughly 3/4 the speed of Clang. + * + * There are other issues, such as GCC splitting _mm256_loadu_si256 into + * _mm_loadu_si128 + _mm256_inserti128_si256. This is an optimization which + * only applies to Sandy and Ivy Bridge... which don't even support AVX2. + * + * That is why when compiling the AVX2 version, it is recommended to use either + * -O2 -mavx2 -march=haswell + * or + * -O2 -mavx2 -mno-avx256-split-unaligned-load + * for decent performance, or to use Clang instead. + * + * Fortunately, we can control the first one with a pragma that forces GCC into + * -O2, but the other one we can't control without "failed to inline always + * inline function due to target mismatch" warnings. + */ +#if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \ + && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \ + && defined(__OPTIMIZE__) && !defined(__OPTIMIZE_SIZE__) /* respect -O0 and -Os */ +# pragma GCC push_options +# pragma GCC optimize("-O2") +#endif + + +#if XXH_VECTOR == XXH_NEON +/* + * NEON's setup for vmlal_u32 is a little more complicated than it is on + * SSE2, AVX2, and VSX. + * + * While PMULUDQ and VMULEUW both perform a mask, VMLAL.U32 performs an upcast. + * + * To do the same operation, the 128-bit 'Q' register needs to be split into + * two 64-bit 'D' registers, performing this operation:: + * + * [ a | b ] + * | '---------. .--------' | + * | x | + * | .---------' '--------. | + * [ a & 0xFFFFFFFF | b & 0xFFFFFFFF ],[ a >> 32 | b >> 32 ] + * + * Due to significant changes in aarch64, the fastest method for aarch64 is + * completely different than the fastest method for ARMv7-A. + * + * ARMv7-A treats D registers as unions overlaying Q registers, so modifying + * D11 will modify the high half of Q5. This is similar to how modifying AH + * will only affect bits 8-15 of AX on x86. + * + * VZIP takes two registers, and puts even lanes in one register and odd lanes + * in the other. + * + * On ARMv7-A, this strangely modifies both parameters in place instead of + * taking the usual 3-operand form. + * + * Therefore, if we want to do this, we can simply use a D-form VZIP.32 on the + * lower and upper halves of the Q register to end up with the high and low + * halves where we want - all in one instruction. + * + * vzip.32 d10, d11 @ d10 = { d10[0], d11[0] }; d11 = { d10[1], d11[1] } + * + * Unfortunately we need inline assembly for this: Instructions modifying two + * registers at once is not possible in GCC or Clang's IR, and they have to + * create a copy. + * + * aarch64 requires a different approach. + * + * In order to make it easier to write a decent compiler for aarch64, many + * quirks were removed, such as conditional execution. + * + * NEON was also affected by this. + * + * aarch64 cannot access the high bits of a Q-form register, and writes to a + * D-form register zero the high bits, similar to how writes to W-form scalar + * registers (or DWORD registers on x86_64) work. + * + * The formerly free vget_high intrinsics now require a vext (with a few + * exceptions) + * + * Additionally, VZIP was replaced by ZIP1 and ZIP2, which are the equivalent + * of PUNPCKL* and PUNPCKH* in SSE, respectively, in order to only modify one + * operand. + * + * The equivalent of the VZIP.32 on the lower and upper halves would be this + * mess: + * + * ext v2.4s, v0.4s, v0.4s, #2 // v2 = { v0[2], v0[3], v0[0], v0[1] } + * zip1 v1.2s, v0.2s, v2.2s // v1 = { v0[0], v2[0] } + * zip2 v0.2s, v0.2s, v1.2s // v0 = { v0[1], v2[1] } + * + * Instead, we use a literal downcast, vmovn_u64 (XTN), and vshrn_n_u64 (SHRN): + * + * shrn v1.2s, v0.2d, #32 // v1 = (uint32x2_t)(v0 >> 32); + * xtn v0.2s, v0.2d // v0 = (uint32x2_t)(v0 & 0xFFFFFFFF); + * + * This is available on ARMv7-A, but is less efficient than a single VZIP.32. + */ + +/*! + * Function-like macro: + * void XXH_SPLIT_IN_PLACE(uint64x2_t &in, uint32x2_t &outLo, uint32x2_t &outHi) + * { + * outLo = (uint32x2_t)(in & 0xFFFFFFFF); + * outHi = (uint32x2_t)(in >> 32); + * in = UNDEFINED; + * } + */ +# if !defined(XXH_NO_VZIP_HACK) /* define to disable */ \ + && (defined(__GNUC__) || defined(__clang__)) \ + && (defined(__arm__) || defined(__thumb__) || defined(_M_ARM)) +# define XXH_SPLIT_IN_PLACE(in, outLo, outHi) \ + do { \ + /* Undocumented GCC/Clang operand modifier: %e0 = lower D half, %f0 = upper D half */ \ + /* https://github.com/gcc-mirror/gcc/blob/38cf91e5/gcc/config/arm/arm.c#L22486 */ \ + /* https://github.com/llvm-mirror/llvm/blob/2c4ca683/lib/Target/ARM/ARMAsmPrinter.cpp#L399 */ \ + __asm__("vzip.32 %e0, %f0" : "+w" (in)); \ + (outLo) = vget_low_u32 (vreinterpretq_u32_u64(in)); \ + (outHi) = vget_high_u32(vreinterpretq_u32_u64(in)); \ + } while (0) +# else +# define XXH_SPLIT_IN_PLACE(in, outLo, outHi) \ + do { \ + (outLo) = vmovn_u64 (in); \ + (outHi) = vshrn_n_u64 ((in), 32); \ + } while (0) +# endif + +/*! + * @ingroup tuning + * @brief Controls the NEON to scalar ratio for XXH3 + * + * On AArch64 when not optimizing for size, XXH3 will run 6 lanes using NEON and + * 2 lanes on scalar by default. + * + * This can be set to 2, 4, 6, or 8. ARMv7 will default to all 8 NEON lanes, as the + * emulated 64-bit arithmetic is too slow. + * + * Modern ARM CPUs are _very_ sensitive to how their pipelines are used. + * + * For example, the Cortex-A73 can dispatch 3 micro-ops per cycle, but it can't + * have more than 2 NEON (F0/F1) micro-ops. If you are only using NEON instructions, + * you are only using 2/3 of the CPU bandwidth. + * + * This is even more noticeable on the more advanced cores like the A76 which + * can dispatch 8 micro-ops per cycle, but still only 2 NEON micro-ops at once. + * + * Therefore, @ref XXH3_NEON_LANES lanes will be processed using NEON, and the + * remaining lanes will use scalar instructions. This improves the bandwidth + * and also gives the integer pipelines something to do besides twiddling loop + * counters and pointers. + * + * This change benefits CPUs with large micro-op buffers without negatively affecting + * other CPUs: + * + * | Chipset | Dispatch type | NEON only | 6:2 hybrid | Diff. | + * |:----------------------|:--------------------|----------:|-----------:|------:| + * | Snapdragon 730 (A76) | 2 NEON/8 micro-ops | 8.8 GB/s | 10.1 GB/s | ~16% | + * | Snapdragon 835 (A73) | 2 NEON/3 micro-ops | 5.1 GB/s | 5.3 GB/s | ~5% | + * | Marvell PXA1928 (A53) | In-order dual-issue | 1.9 GB/s | 1.9 GB/s | 0% | + * + * It also seems to fix some bad codegen on GCC, making it almost as fast as clang. + * + * @see XXH3_accumulate_512_neon() + */ +# ifndef XXH3_NEON_LANES +# if (defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) \ + && !defined(__OPTIMIZE_SIZE__) +# define XXH3_NEON_LANES 6 +# else +# define XXH3_NEON_LANES XXH_ACC_NB +# endif +# endif +#endif /* XXH_VECTOR == XXH_NEON */ + +/* + * VSX and Z Vector helpers. + * + * This is very messy, and any pull requests to clean this up are welcome. + * + * There are a lot of problems with supporting VSX and s390x, due to + * inconsistent intrinsics, spotty coverage, and multiple endiannesses. + */ +#if XXH_VECTOR == XXH_VSX +# if defined(__s390x__) +# include +# else +/* gcc's altivec.h can have the unwanted consequence to unconditionally + * #define bool, vector, and pixel keywords, + * with bad consequences for programs already using these keywords for other purposes. + * The paragraph defining these macros is skipped when __APPLE_ALTIVEC__ is defined. + * __APPLE_ALTIVEC__ is _generally_ defined automatically by the compiler, + * but it seems that, in some cases, it isn't. + * Force the build macro to be defined, so that keywords are not altered. + */ +# if defined(__GNUC__) && !defined(__APPLE_ALTIVEC__) +# define __APPLE_ALTIVEC__ +# endif +# include +# endif + +typedef __vector unsigned long long xxh_u64x2; +typedef __vector unsigned char xxh_u8x16; +typedef __vector unsigned xxh_u32x4; + +# ifndef XXH_VSX_BE +# if defined(__BIG_ENDIAN__) \ + || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define XXH_VSX_BE 1 +# elif defined(__VEC_ELEMENT_REG_ORDER__) && __VEC_ELEMENT_REG_ORDER__ == __ORDER_BIG_ENDIAN__ +# warning "-maltivec=be is not recommended. Please use native endianness." +# define XXH_VSX_BE 1 +# else +# define XXH_VSX_BE 0 +# endif +# endif /* !defined(XXH_VSX_BE) */ + +# if XXH_VSX_BE +# if defined(__POWER9_VECTOR__) || (defined(__clang__) && defined(__s390x__)) +# define XXH_vec_revb vec_revb +# else +/*! + * A polyfill for POWER9's vec_revb(). + */ +XXH_FORCE_INLINE xxh_u64x2 XXH_vec_revb(xxh_u64x2 val) +{ + xxh_u8x16 const vByteSwap = { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08 }; + return vec_perm(val, val, vByteSwap); +} +# endif +# endif /* XXH_VSX_BE */ + +/*! + * Performs an unaligned vector load and byte swaps it on big endian. + */ +XXH_FORCE_INLINE xxh_u64x2 XXH_vec_loadu(const void *ptr) +{ + xxh_u64x2 ret; + XXH_memcpy(&ret, ptr, sizeof(xxh_u64x2)); +# if XXH_VSX_BE + ret = XXH_vec_revb(ret); +# endif + return ret; +} + +/* + * vec_mulo and vec_mule are very problematic intrinsics on PowerPC + * + * These intrinsics weren't added until GCC 8, despite existing for a while, + * and they are endian dependent. Also, their meaning swap depending on version. + * */ +# if defined(__s390x__) + /* s390x is always big endian, no issue on this platform */ +# define XXH_vec_mulo vec_mulo +# define XXH_vec_mule vec_mule +# elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw) +/* Clang has a better way to control this, we can just use the builtin which doesn't swap. */ +# define XXH_vec_mulo __builtin_altivec_vmulouw +# define XXH_vec_mule __builtin_altivec_vmuleuw +# else +/* gcc needs inline assembly */ +/* Adapted from https://github.com/google/highwayhash/blob/master/highwayhash/hh_vsx.h. */ +XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mulo(xxh_u32x4 a, xxh_u32x4 b) +{ + xxh_u64x2 result; + __asm__("vmulouw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b)); + return result; +} +XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mule(xxh_u32x4 a, xxh_u32x4 b) +{ + xxh_u64x2 result; + __asm__("vmuleuw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b)); + return result; +} +# endif /* XXH_vec_mulo, XXH_vec_mule */ +#endif /* XXH_VECTOR == XXH_VSX */ + + +/* prefetch + * can be disabled, by declaring XXH_NO_PREFETCH build macro */ +#if defined(XXH_NO_PREFETCH) +# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */ +#else +# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) /* _mm_prefetch() not defined outside of x86/x64 */ +# include /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */ +# define XXH_PREFETCH(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0) +# elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) +# define XXH_PREFETCH(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */) +# else +# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */ +# endif +#endif /* XXH_NO_PREFETCH */ + + +/* ========================================== + * XXH3 default settings + * ========================================== */ + +#define XXH_SECRET_DEFAULT_SIZE 192 /* minimum XXH3_SECRET_SIZE_MIN */ + +#if (XXH_SECRET_DEFAULT_SIZE < XXH3_SECRET_SIZE_MIN) +# error "default keyset is not large enough" +#endif + +/*! Pseudorandom secret taken directly from FARSH. */ +XXH_ALIGN(64) static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = { + 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c, + 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, + 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21, + 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c, + 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3, + 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8, + 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d, + 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, + 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb, + 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e, + 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce, + 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e, +}; + + +#ifdef XXH_OLD_NAMES +# define kSecret XXH3_kSecret +#endif + +#ifdef XXH_DOXYGEN +/*! + * @brief Calculates a 32-bit to 64-bit long multiply. + * + * Implemented as a macro. + * + * Wraps `__emulu` on MSVC x86 because it tends to call `__allmul` when it doesn't + * need to (but it shouldn't need to anyways, it is about 7 instructions to do + * a 64x64 multiply...). Since we know that this will _always_ emit `MULL`, we + * use that instead of the normal method. + * + * If you are compiling for platforms like Thumb-1 and don't have a better option, + * you may also want to write your own long multiply routine here. + * + * @param x, y Numbers to be multiplied + * @return 64-bit product of the low 32 bits of @p x and @p y. + */ +XXH_FORCE_INLINE xxh_u64 +XXH_mult32to64(xxh_u64 x, xxh_u64 y) +{ + return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF); +} +#elif defined(_MSC_VER) && defined(_M_IX86) +# define XXH_mult32to64(x, y) __emulu((unsigned)(x), (unsigned)(y)) +#else +/* + * Downcast + upcast is usually better than masking on older compilers like + * GCC 4.2 (especially 32-bit ones), all without affecting newer compilers. + * + * The other method, (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF), will AND both operands + * and perform a full 64x64 multiply -- entirely redundant on 32-bit. + */ +# define XXH_mult32to64(x, y) ((xxh_u64)(xxh_u32)(x) * (xxh_u64)(xxh_u32)(y)) +#endif + +/*! + * @brief Calculates a 64->128-bit long multiply. + * + * Uses `__uint128_t` and `_umul128` if available, otherwise uses a scalar + * version. + * + * @param lhs , rhs The 64-bit integers to be multiplied + * @return The 128-bit result represented in an @ref XXH128_hash_t. + */ +static XXH128_hash_t +XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs) +{ + /* + * GCC/Clang __uint128_t method. + * + * On most 64-bit targets, GCC and Clang define a __uint128_t type. + * This is usually the best way as it usually uses a native long 64-bit + * multiply, such as MULQ on x86_64 or MUL + UMULH on aarch64. + * + * Usually. + * + * Despite being a 32-bit platform, Clang (and emscripten) define this type + * despite not having the arithmetic for it. This results in a laggy + * compiler builtin call which calculates a full 128-bit multiply. + * In that case it is best to use the portable one. + * https://github.com/Cyan4973/xxHash/issues/211#issuecomment-515575677 + */ +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__) \ + && defined(__SIZEOF_INT128__) \ + || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) + + __uint128_t const product = (__uint128_t)lhs * (__uint128_t)rhs; + XXH128_hash_t r128; + r128.low64 = (xxh_u64)(product); + r128.high64 = (xxh_u64)(product >> 64); + return r128; + + /* + * MSVC for x64's _umul128 method. + * + * xxh_u64 _umul128(xxh_u64 Multiplier, xxh_u64 Multiplicand, xxh_u64 *HighProduct); + * + * This compiles to single operand MUL on x64. + */ +#elif (defined(_M_X64) || defined(_M_IA64)) && !defined(_M_ARM64EC) + +#ifndef _MSC_VER +# pragma intrinsic(_umul128) +#endif + xxh_u64 product_high; + xxh_u64 const product_low = _umul128(lhs, rhs, &product_high); + XXH128_hash_t r128; + r128.low64 = product_low; + r128.high64 = product_high; + return r128; + + /* + * MSVC for ARM64's __umulh method. + * + * This compiles to the same MUL + UMULH as GCC/Clang's __uint128_t method. + */ +#elif defined(_M_ARM64) || defined(_M_ARM64EC) + +#ifndef _MSC_VER +# pragma intrinsic(__umulh) +#endif + XXH128_hash_t r128; + r128.low64 = lhs * rhs; + r128.high64 = __umulh(lhs, rhs); + return r128; + +#else + /* + * Portable scalar method. Optimized for 32-bit and 64-bit ALUs. + * + * This is a fast and simple grade school multiply, which is shown below + * with base 10 arithmetic instead of base 0x100000000. + * + * 9 3 // D2 lhs = 93 + * x 7 5 // D2 rhs = 75 + * ---------- + * 1 5 // D2 lo_lo = (93 % 10) * (75 % 10) = 15 + * 4 5 | // D2 hi_lo = (93 / 10) * (75 % 10) = 45 + * 2 1 | // D2 lo_hi = (93 % 10) * (75 / 10) = 21 + * + 6 3 | | // D2 hi_hi = (93 / 10) * (75 / 10) = 63 + * --------- + * 2 7 | // D2 cross = (15 / 10) + (45 % 10) + 21 = 27 + * + 6 7 | | // D2 upper = (27 / 10) + (45 / 10) + 63 = 67 + * --------- + * 6 9 7 5 // D4 res = (27 * 10) + (15 % 10) + (67 * 100) = 6975 + * + * The reasons for adding the products like this are: + * 1. It avoids manual carry tracking. Just like how + * (9 * 9) + 9 + 9 = 99, the same applies with this for UINT64_MAX. + * This avoids a lot of complexity. + * + * 2. It hints for, and on Clang, compiles to, the powerful UMAAL + * instruction available in ARM's Digital Signal Processing extension + * in 32-bit ARMv6 and later, which is shown below: + * + * void UMAAL(xxh_u32 *RdLo, xxh_u32 *RdHi, xxh_u32 Rn, xxh_u32 Rm) + * { + * xxh_u64 product = (xxh_u64)*RdLo * (xxh_u64)*RdHi + Rn + Rm; + * *RdLo = (xxh_u32)(product & 0xFFFFFFFF); + * *RdHi = (xxh_u32)(product >> 32); + * } + * + * This instruction was designed for efficient long multiplication, and + * allows this to be calculated in only 4 instructions at speeds + * comparable to some 64-bit ALUs. + * + * 3. It isn't terrible on other platforms. Usually this will be a couple + * of 32-bit ADD/ADCs. + */ + + /* First calculate all of the cross products. */ + xxh_u64 const lo_lo = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs & 0xFFFFFFFF); + xxh_u64 const hi_lo = XXH_mult32to64(lhs >> 32, rhs & 0xFFFFFFFF); + xxh_u64 const lo_hi = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs >> 32); + xxh_u64 const hi_hi = XXH_mult32to64(lhs >> 32, rhs >> 32); + + /* Now add the products together. These will never overflow. */ + xxh_u64 const cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi; + xxh_u64 const upper = (hi_lo >> 32) + (cross >> 32) + hi_hi; + xxh_u64 const lower = (cross << 32) | (lo_lo & 0xFFFFFFFF); + + XXH128_hash_t r128; + r128.low64 = lower; + r128.high64 = upper; + return r128; +#endif +} + +/*! + * @brief Calculates a 64-bit to 128-bit multiply, then XOR folds it. + * + * The reason for the separate function is to prevent passing too many structs + * around by value. This will hopefully inline the multiply, but we don't force it. + * + * @param lhs , rhs The 64-bit integers to multiply + * @return The low 64 bits of the product XOR'd by the high 64 bits. + * @see XXH_mult64to128() + */ +static xxh_u64 +XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs) +{ + XXH128_hash_t product = XXH_mult64to128(lhs, rhs); + return product.low64 ^ product.high64; +} + +/*! Seems to produce slightly better code on GCC for some reason. */ +XXH_FORCE_INLINE xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift) +{ + XXH_ASSERT(0 <= shift && shift < 64); + return v64 ^ (v64 >> shift); +} + +/* + * This is a fast avalanche stage, + * suitable when input bits are already partially mixed + */ +static XXH64_hash_t XXH3_avalanche(xxh_u64 h64) +{ + h64 = XXH_xorshift64(h64, 37); + h64 *= 0x165667919E3779F9ULL; + h64 = XXH_xorshift64(h64, 32); + return h64; +} + +/* + * This is a stronger avalanche, + * inspired by Pelle Evensen's rrmxmx + * preferable when input has not been previously mixed + */ +static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len) +{ + /* this mix is inspired by Pelle Evensen's rrmxmx */ + h64 ^= XXH_rotl64(h64, 49) ^ XXH_rotl64(h64, 24); + h64 *= 0x9FB21C651E98DF25ULL; + h64 ^= (h64 >> 35) + len ; + h64 *= 0x9FB21C651E98DF25ULL; + return XXH_xorshift64(h64, 28); +} + + +/* ========================================== + * Short keys + * ========================================== + * One of the shortcomings of XXH32 and XXH64 was that their performance was + * sub-optimal on short lengths. It used an iterative algorithm which strongly + * favored lengths that were a multiple of 4 or 8. + * + * Instead of iterating over individual inputs, we use a set of single shot + * functions which piece together a range of lengths and operate in constant time. + * + * Additionally, the number of multiplies has been significantly reduced. This + * reduces latency, especially when emulating 64-bit multiplies on 32-bit. + * + * Depending on the platform, this may or may not be faster than XXH32, but it + * is almost guaranteed to be faster than XXH64. + */ + +/* + * At very short lengths, there isn't enough input to fully hide secrets, or use + * the entire secret. + * + * There is also only a limited amount of mixing we can do before significantly + * impacting performance. + * + * Therefore, we use different sections of the secret and always mix two secret + * samples with an XOR. This should have no effect on performance on the + * seedless or withSeed variants because everything _should_ be constant folded + * by modern compilers. + * + * The XOR mixing hides individual parts of the secret and increases entropy. + * + * This adds an extra layer of strength for custom secrets. + */ +XXH_FORCE_INLINE XXH64_hash_t +XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + XXH_ASSERT(input != NULL); + XXH_ASSERT(1 <= len && len <= 3); + XXH_ASSERT(secret != NULL); + /* + * len = 1: combined = { input[0], 0x01, input[0], input[0] } + * len = 2: combined = { input[1], 0x02, input[0], input[1] } + * len = 3: combined = { input[2], 0x03, input[0], input[1] } + */ + { xxh_u8 const c1 = input[0]; + xxh_u8 const c2 = input[len >> 1]; + xxh_u8 const c3 = input[len - 1]; + xxh_u32 const combined = ((xxh_u32)c1 << 16) | ((xxh_u32)c2 << 24) + | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8); + xxh_u64 const bitflip = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed; + xxh_u64 const keyed = (xxh_u64)combined ^ bitflip; + return XXH64_avalanche(keyed); + } +} + +XXH_FORCE_INLINE XXH64_hash_t +XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + XXH_ASSERT(input != NULL); + XXH_ASSERT(secret != NULL); + XXH_ASSERT(4 <= len && len <= 8); + seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32; + { xxh_u32 const input1 = XXH_readLE32(input); + xxh_u32 const input2 = XXH_readLE32(input + len - 4); + xxh_u64 const bitflip = (XXH_readLE64(secret+8) ^ XXH_readLE64(secret+16)) - seed; + xxh_u64 const input64 = input2 + (((xxh_u64)input1) << 32); + xxh_u64 const keyed = input64 ^ bitflip; + return XXH3_rrmxmx(keyed, len); + } +} + +XXH_FORCE_INLINE XXH64_hash_t +XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + XXH_ASSERT(input != NULL); + XXH_ASSERT(secret != NULL); + XXH_ASSERT(9 <= len && len <= 16); + { xxh_u64 const bitflip1 = (XXH_readLE64(secret+24) ^ XXH_readLE64(secret+32)) + seed; + xxh_u64 const bitflip2 = (XXH_readLE64(secret+40) ^ XXH_readLE64(secret+48)) - seed; + xxh_u64 const input_lo = XXH_readLE64(input) ^ bitflip1; + xxh_u64 const input_hi = XXH_readLE64(input + len - 8) ^ bitflip2; + xxh_u64 const acc = len + + XXH_swap64(input_lo) + input_hi + + XXH3_mul128_fold64(input_lo, input_hi); + return XXH3_avalanche(acc); + } +} + +XXH_FORCE_INLINE XXH64_hash_t +XXH3_len_0to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + XXH_ASSERT(len <= 16); + { if (XXH_likely(len > 8)) return XXH3_len_9to16_64b(input, len, secret, seed); + if (XXH_likely(len >= 4)) return XXH3_len_4to8_64b(input, len, secret, seed); + if (len) return XXH3_len_1to3_64b(input, len, secret, seed); + return XXH64_avalanche(seed ^ (XXH_readLE64(secret+56) ^ XXH_readLE64(secret+64))); + } +} + +/* + * DISCLAIMER: There are known *seed-dependent* multicollisions here due to + * multiplication by zero, affecting hashes of lengths 17 to 240. + * + * However, they are very unlikely. + * + * Keep this in mind when using the unseeded XXH3_64bits() variant: As with all + * unseeded non-cryptographic hashes, it does not attempt to defend itself + * against specially crafted inputs, only random inputs. + * + * Compared to classic UMAC where a 1 in 2^31 chance of 4 consecutive bytes + * cancelling out the secret is taken an arbitrary number of times (addressed + * in XXH3_accumulate_512), this collision is very unlikely with random inputs + * and/or proper seeding: + * + * This only has a 1 in 2^63 chance of 8 consecutive bytes cancelling out, in a + * function that is only called up to 16 times per hash with up to 240 bytes of + * input. + * + * This is not too bad for a non-cryptographic hash function, especially with + * only 64 bit outputs. + * + * The 128-bit variant (which trades some speed for strength) is NOT affected + * by this, although it is always a good idea to use a proper seed if you care + * about strength. + */ +XXH_FORCE_INLINE xxh_u64 XXH3_mix16B(const xxh_u8* XXH_RESTRICT input, + const xxh_u8* XXH_RESTRICT secret, xxh_u64 seed64) +{ +#if defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \ + && defined(__i386__) && defined(__SSE2__) /* x86 + SSE2 */ \ + && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable like XXH32 hack */ + /* + * UGLY HACK: + * GCC for x86 tends to autovectorize the 128-bit multiply, resulting in + * slower code. + * + * By forcing seed64 into a register, we disrupt the cost model and + * cause it to scalarize. See `XXH32_round()` + * + * FIXME: Clang's output is still _much_ faster -- On an AMD Ryzen 3600, + * XXH3_64bits @ len=240 runs at 4.6 GB/s with Clang 9, but 3.3 GB/s on + * GCC 9.2, despite both emitting scalar code. + * + * GCC generates much better scalar code than Clang for the rest of XXH3, + * which is why finding a more optimal codepath is an interest. + */ + XXH_COMPILER_GUARD(seed64); +#endif + { xxh_u64 const input_lo = XXH_readLE64(input); + xxh_u64 const input_hi = XXH_readLE64(input+8); + return XXH3_mul128_fold64( + input_lo ^ (XXH_readLE64(secret) + seed64), + input_hi ^ (XXH_readLE64(secret+8) - seed64) + ); + } +} + +/* For mid range keys, XXH3 uses a Mum-hash variant. */ +XXH_FORCE_INLINE XXH64_hash_t +XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len, + const xxh_u8* XXH_RESTRICT secret, size_t secretSize, + XXH64_hash_t seed) +{ + XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; + XXH_ASSERT(16 < len && len <= 128); + + { xxh_u64 acc = len * XXH_PRIME64_1; + if (len > 32) { + if (len > 64) { + if (len > 96) { + acc += XXH3_mix16B(input+48, secret+96, seed); + acc += XXH3_mix16B(input+len-64, secret+112, seed); + } + acc += XXH3_mix16B(input+32, secret+64, seed); + acc += XXH3_mix16B(input+len-48, secret+80, seed); + } + acc += XXH3_mix16B(input+16, secret+32, seed); + acc += XXH3_mix16B(input+len-32, secret+48, seed); + } + acc += XXH3_mix16B(input+0, secret+0, seed); + acc += XXH3_mix16B(input+len-16, secret+16, seed); + + return XXH3_avalanche(acc); + } +} + +#define XXH3_MIDSIZE_MAX 240 + +XXH_NO_INLINE XXH64_hash_t +XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len, + const xxh_u8* XXH_RESTRICT secret, size_t secretSize, + XXH64_hash_t seed) +{ + XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; + XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX); + + #define XXH3_MIDSIZE_STARTOFFSET 3 + #define XXH3_MIDSIZE_LASTOFFSET 17 + + { xxh_u64 acc = len * XXH_PRIME64_1; + int const nbRounds = (int)len / 16; + int i; + for (i=0; i<8; i++) { + acc += XXH3_mix16B(input+(16*i), secret+(16*i), seed); + } + acc = XXH3_avalanche(acc); + XXH_ASSERT(nbRounds >= 8); +#if defined(__clang__) /* Clang */ \ + && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \ + && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */ + /* + * UGLY HACK: + * Clang for ARMv7-A tries to vectorize this loop, similar to GCC x86. + * In everywhere else, it uses scalar code. + * + * For 64->128-bit multiplies, even if the NEON was 100% optimal, it + * would still be slower than UMAAL (see XXH_mult64to128). + * + * Unfortunately, Clang doesn't handle the long multiplies properly and + * converts them to the nonexistent "vmulq_u64" intrinsic, which is then + * scalarized into an ugly mess of VMOV.32 instructions. + * + * This mess is difficult to avoid without turning autovectorization + * off completely, but they are usually relatively minor and/or not + * worth it to fix. + * + * This loop is the easiest to fix, as unlike XXH32, this pragma + * _actually works_ because it is a loop vectorization instead of an + * SLP vectorization. + */ + #pragma clang loop vectorize(disable) +#endif + for (i=8 ; i < nbRounds; i++) { + acc += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed); + } + /* last bytes */ + acc += XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed); + return XXH3_avalanche(acc); + } +} + + +/* ======= Long Keys ======= */ + +#define XXH_STRIPE_LEN 64 +#define XXH_SECRET_CONSUME_RATE 8 /* nb of secret bytes consumed at each accumulation */ +#define XXH_ACC_NB (XXH_STRIPE_LEN / sizeof(xxh_u64)) + +#ifdef XXH_OLD_NAMES +# define STRIPE_LEN XXH_STRIPE_LEN +# define ACC_NB XXH_ACC_NB +#endif + +XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64) +{ + if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64); + XXH_memcpy(dst, &v64, sizeof(v64)); +} + +/* Several intrinsic functions below are supposed to accept __int64 as argument, + * as documented in https://software.intel.com/sites/landingpage/IntrinsicsGuide/ . + * However, several environments do not define __int64 type, + * requiring a workaround. + */ +#if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) + typedef int64_t xxh_i64; +#else + /* the following type must have a width of 64-bit */ + typedef long long xxh_i64; +#endif + + +/* + * XXH3_accumulate_512 is the tightest loop for long inputs, and it is the most optimized. + * + * It is a hardened version of UMAC, based off of FARSH's implementation. + * + * This was chosen because it adapts quite well to 32-bit, 64-bit, and SIMD + * implementations, and it is ridiculously fast. + * + * We harden it by mixing the original input to the accumulators as well as the product. + * + * This means that in the (relatively likely) case of a multiply by zero, the + * original input is preserved. + * + * On 128-bit inputs, we swap 64-bit pairs when we add the input to improve + * cross-pollination, as otherwise the upper and lower halves would be + * essentially independent. + * + * This doesn't matter on 64-bit hashes since they all get merged together in + * the end, so we skip the extra step. + * + * Both XXH3_64bits and XXH3_128bits use this subroutine. + */ + +#if (XXH_VECTOR == XXH_AVX512) \ + || (defined(XXH_DISPATCH_AVX512) && XXH_DISPATCH_AVX512 != 0) + +#ifndef XXH_TARGET_AVX512 +# define XXH_TARGET_AVX512 /* disable attribute target */ +#endif + +XXH_FORCE_INLINE XXH_TARGET_AVX512 void +XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc, + const void* XXH_RESTRICT input, + const void* XXH_RESTRICT secret) +{ + __m512i* const xacc = (__m512i *) acc; + XXH_ASSERT((((size_t)acc) & 63) == 0); + XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i)); + + { + /* data_vec = input[0]; */ + __m512i const data_vec = _mm512_loadu_si512 (input); + /* key_vec = secret[0]; */ + __m512i const key_vec = _mm512_loadu_si512 (secret); + /* data_key = data_vec ^ key_vec; */ + __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec); + /* data_key_lo = data_key >> 32; */ + __m512i const data_key_lo = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1)); + /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ + __m512i const product = _mm512_mul_epu32 (data_key, data_key_lo); + /* xacc[0] += swap(data_vec); */ + __m512i const data_swap = _mm512_shuffle_epi32(data_vec, (_MM_PERM_ENUM)_MM_SHUFFLE(1, 0, 3, 2)); + __m512i const sum = _mm512_add_epi64(*xacc, data_swap); + /* xacc[0] += product; */ + *xacc = _mm512_add_epi64(product, sum); + } +} + +/* + * XXH3_scrambleAcc: Scrambles the accumulators to improve mixing. + * + * Multiplication isn't perfect, as explained by Google in HighwayHash: + * + * // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to + * // varying degrees. In descending order of goodness, bytes + * // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32. + * // As expected, the upper and lower bytes are much worse. + * + * Source: https://github.com/google/highwayhash/blob/0aaf66b/highwayhash/hh_avx2.h#L291 + * + * Since our algorithm uses a pseudorandom secret to add some variance into the + * mix, we don't need to (or want to) mix as often or as much as HighwayHash does. + * + * This isn't as tight as XXH3_accumulate, but still written in SIMD to avoid + * extraction. + * + * Both XXH3_64bits and XXH3_128bits use this subroutine. + */ + +XXH_FORCE_INLINE XXH_TARGET_AVX512 void +XXH3_scrambleAcc_avx512(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) +{ + XXH_ASSERT((((size_t)acc) & 63) == 0); + XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i)); + { __m512i* const xacc = (__m512i*) acc; + const __m512i prime32 = _mm512_set1_epi32((int)XXH_PRIME32_1); + + /* xacc[0] ^= (xacc[0] >> 47) */ + __m512i const acc_vec = *xacc; + __m512i const shifted = _mm512_srli_epi64 (acc_vec, 47); + __m512i const data_vec = _mm512_xor_si512 (acc_vec, shifted); + /* xacc[0] ^= secret; */ + __m512i const key_vec = _mm512_loadu_si512 (secret); + __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec); + + /* xacc[0] *= XXH_PRIME32_1; */ + __m512i const data_key_hi = _mm512_shuffle_epi32 (data_key, (_MM_PERM_ENUM)_MM_SHUFFLE(0, 3, 0, 1)); + __m512i const prod_lo = _mm512_mul_epu32 (data_key, prime32); + __m512i const prod_hi = _mm512_mul_epu32 (data_key_hi, prime32); + *xacc = _mm512_add_epi64(prod_lo, _mm512_slli_epi64(prod_hi, 32)); + } +} + +XXH_FORCE_INLINE XXH_TARGET_AVX512 void +XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64) +{ + XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 63) == 0); + XXH_STATIC_ASSERT(XXH_SEC_ALIGN == 64); + XXH_ASSERT(((size_t)customSecret & 63) == 0); + (void)(&XXH_writeLE64); + { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m512i); + __m512i const seed = _mm512_mask_set1_epi64(_mm512_set1_epi64((xxh_i64)seed64), 0xAA, (xxh_i64)(0U - seed64)); + + const __m512i* const src = (const __m512i*) ((const void*) XXH3_kSecret); + __m512i* const dest = ( __m512i*) customSecret; + int i; + XXH_ASSERT(((size_t)src & 63) == 0); /* control alignment */ + XXH_ASSERT(((size_t)dest & 63) == 0); + for (i=0; i < nbRounds; ++i) { + /* GCC has a bug, _mm512_stream_load_si512 accepts 'void*', not 'void const*', + * this will warn "discards 'const' qualifier". */ + union { + const __m512i* cp; + void* p; + } remote_const_void; + remote_const_void.cp = src + i; + dest[i] = _mm512_add_epi64(_mm512_stream_load_si512(remote_const_void.p), seed); + } } +} + +#endif + +#if (XXH_VECTOR == XXH_AVX2) \ + || (defined(XXH_DISPATCH_AVX2) && XXH_DISPATCH_AVX2 != 0) + +#ifndef XXH_TARGET_AVX2 +# define XXH_TARGET_AVX2 /* disable attribute target */ +#endif + +XXH_FORCE_INLINE XXH_TARGET_AVX2 void +XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc, + const void* XXH_RESTRICT input, + const void* XXH_RESTRICT secret) +{ + XXH_ASSERT((((size_t)acc) & 31) == 0); + { __m256i* const xacc = (__m256i *) acc; + /* Unaligned. This is mainly for pointer arithmetic, and because + * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */ + const __m256i* const xinput = (const __m256i *) input; + /* Unaligned. This is mainly for pointer arithmetic, and because + * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */ + const __m256i* const xsecret = (const __m256i *) secret; + + size_t i; + for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) { + /* data_vec = xinput[i]; */ + __m256i const data_vec = _mm256_loadu_si256 (xinput+i); + /* key_vec = xsecret[i]; */ + __m256i const key_vec = _mm256_loadu_si256 (xsecret+i); + /* data_key = data_vec ^ key_vec; */ + __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec); + /* data_key_lo = data_key >> 32; */ + __m256i const data_key_lo = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); + /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ + __m256i const product = _mm256_mul_epu32 (data_key, data_key_lo); + /* xacc[i] += swap(data_vec); */ + __m256i const data_swap = _mm256_shuffle_epi32(data_vec, _MM_SHUFFLE(1, 0, 3, 2)); + __m256i const sum = _mm256_add_epi64(xacc[i], data_swap); + /* xacc[i] += product; */ + xacc[i] = _mm256_add_epi64(product, sum); + } } +} + +XXH_FORCE_INLINE XXH_TARGET_AVX2 void +XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) +{ + XXH_ASSERT((((size_t)acc) & 31) == 0); + { __m256i* const xacc = (__m256i*) acc; + /* Unaligned. This is mainly for pointer arithmetic, and because + * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */ + const __m256i* const xsecret = (const __m256i *) secret; + const __m256i prime32 = _mm256_set1_epi32((int)XXH_PRIME32_1); + + size_t i; + for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) { + /* xacc[i] ^= (xacc[i] >> 47) */ + __m256i const acc_vec = xacc[i]; + __m256i const shifted = _mm256_srli_epi64 (acc_vec, 47); + __m256i const data_vec = _mm256_xor_si256 (acc_vec, shifted); + /* xacc[i] ^= xsecret; */ + __m256i const key_vec = _mm256_loadu_si256 (xsecret+i); + __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec); + + /* xacc[i] *= XXH_PRIME32_1; */ + __m256i const data_key_hi = _mm256_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); + __m256i const prod_lo = _mm256_mul_epu32 (data_key, prime32); + __m256i const prod_hi = _mm256_mul_epu32 (data_key_hi, prime32); + xacc[i] = _mm256_add_epi64(prod_lo, _mm256_slli_epi64(prod_hi, 32)); + } + } +} + +XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_initCustomSecret_avx2(void* XXH_RESTRICT customSecret, xxh_u64 seed64) +{ + XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 31) == 0); + XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE / sizeof(__m256i)) == 6); + XXH_STATIC_ASSERT(XXH_SEC_ALIGN <= 64); + (void)(&XXH_writeLE64); + XXH_PREFETCH(customSecret); + { __m256i const seed = _mm256_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64, (xxh_i64)(0U - seed64), (xxh_i64)seed64); + + const __m256i* const src = (const __m256i*) ((const void*) XXH3_kSecret); + __m256i* dest = ( __m256i*) customSecret; + +# if defined(__GNUC__) || defined(__clang__) + /* + * On GCC & Clang, marking 'dest' as modified will cause the compiler: + * - do not extract the secret from sse registers in the internal loop + * - use less common registers, and avoid pushing these reg into stack + */ + XXH_COMPILER_GUARD(dest); +# endif + XXH_ASSERT(((size_t)src & 31) == 0); /* control alignment */ + XXH_ASSERT(((size_t)dest & 31) == 0); + + /* GCC -O2 need unroll loop manually */ + dest[0] = _mm256_add_epi64(_mm256_stream_load_si256(src+0), seed); + dest[1] = _mm256_add_epi64(_mm256_stream_load_si256(src+1), seed); + dest[2] = _mm256_add_epi64(_mm256_stream_load_si256(src+2), seed); + dest[3] = _mm256_add_epi64(_mm256_stream_load_si256(src+3), seed); + dest[4] = _mm256_add_epi64(_mm256_stream_load_si256(src+4), seed); + dest[5] = _mm256_add_epi64(_mm256_stream_load_si256(src+5), seed); + } +} + +#endif + +/* x86dispatch always generates SSE2 */ +#if (XXH_VECTOR == XXH_SSE2) || defined(XXH_X86DISPATCH) + +#ifndef XXH_TARGET_SSE2 +# define XXH_TARGET_SSE2 /* disable attribute target */ +#endif + +XXH_FORCE_INLINE XXH_TARGET_SSE2 void +XXH3_accumulate_512_sse2( void* XXH_RESTRICT acc, + const void* XXH_RESTRICT input, + const void* XXH_RESTRICT secret) +{ + /* SSE2 is just a half-scale version of the AVX2 version. */ + XXH_ASSERT((((size_t)acc) & 15) == 0); + { __m128i* const xacc = (__m128i *) acc; + /* Unaligned. This is mainly for pointer arithmetic, and because + * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */ + const __m128i* const xinput = (const __m128i *) input; + /* Unaligned. This is mainly for pointer arithmetic, and because + * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */ + const __m128i* const xsecret = (const __m128i *) secret; + + size_t i; + for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) { + /* data_vec = xinput[i]; */ + __m128i const data_vec = _mm_loadu_si128 (xinput+i); + /* key_vec = xsecret[i]; */ + __m128i const key_vec = _mm_loadu_si128 (xsecret+i); + /* data_key = data_vec ^ key_vec; */ + __m128i const data_key = _mm_xor_si128 (data_vec, key_vec); + /* data_key_lo = data_key >> 32; */ + __m128i const data_key_lo = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); + /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ + __m128i const product = _mm_mul_epu32 (data_key, data_key_lo); + /* xacc[i] += swap(data_vec); */ + __m128i const data_swap = _mm_shuffle_epi32(data_vec, _MM_SHUFFLE(1,0,3,2)); + __m128i const sum = _mm_add_epi64(xacc[i], data_swap); + /* xacc[i] += product; */ + xacc[i] = _mm_add_epi64(product, sum); + } } +} + +XXH_FORCE_INLINE XXH_TARGET_SSE2 void +XXH3_scrambleAcc_sse2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) +{ + XXH_ASSERT((((size_t)acc) & 15) == 0); + { __m128i* const xacc = (__m128i*) acc; + /* Unaligned. This is mainly for pointer arithmetic, and because + * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */ + const __m128i* const xsecret = (const __m128i *) secret; + const __m128i prime32 = _mm_set1_epi32((int)XXH_PRIME32_1); + + size_t i; + for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) { + /* xacc[i] ^= (xacc[i] >> 47) */ + __m128i const acc_vec = xacc[i]; + __m128i const shifted = _mm_srli_epi64 (acc_vec, 47); + __m128i const data_vec = _mm_xor_si128 (acc_vec, shifted); + /* xacc[i] ^= xsecret[i]; */ + __m128i const key_vec = _mm_loadu_si128 (xsecret+i); + __m128i const data_key = _mm_xor_si128 (data_vec, key_vec); + + /* xacc[i] *= XXH_PRIME32_1; */ + __m128i const data_key_hi = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); + __m128i const prod_lo = _mm_mul_epu32 (data_key, prime32); + __m128i const prod_hi = _mm_mul_epu32 (data_key_hi, prime32); + xacc[i] = _mm_add_epi64(prod_lo, _mm_slli_epi64(prod_hi, 32)); + } + } +} + +XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_initCustomSecret_sse2(void* XXH_RESTRICT customSecret, xxh_u64 seed64) +{ + XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0); + (void)(&XXH_writeLE64); + { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m128i); + +# if defined(_MSC_VER) && defined(_M_IX86) && _MSC_VER < 1900 + /* MSVC 32bit mode does not support _mm_set_epi64x before 2015 */ + XXH_ALIGN(16) const xxh_i64 seed64x2[2] = { (xxh_i64)seed64, (xxh_i64)(0U - seed64) }; + __m128i const seed = _mm_load_si128((__m128i const*)seed64x2); +# else + __m128i const seed = _mm_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64); +# endif + int i; + + const void* const src16 = XXH3_kSecret; + __m128i* dst16 = (__m128i*) customSecret; +# if defined(__GNUC__) || defined(__clang__) + /* + * On GCC & Clang, marking 'dest' as modified will cause the compiler: + * - do not extract the secret from sse registers in the internal loop + * - use less common registers, and avoid pushing these reg into stack + */ + XXH_COMPILER_GUARD(dst16); +# endif + XXH_ASSERT(((size_t)src16 & 15) == 0); /* control alignment */ + XXH_ASSERT(((size_t)dst16 & 15) == 0); + + for (i=0; i < nbRounds; ++i) { + dst16[i] = _mm_add_epi64(_mm_load_si128((const __m128i *)src16+i), seed); + } } +} + +#endif + +#if (XXH_VECTOR == XXH_NEON) + +/* forward declarations for the scalar routines */ +XXH_FORCE_INLINE void +XXH3_scalarRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT input, + void const* XXH_RESTRICT secret, size_t lane); + +XXH_FORCE_INLINE void +XXH3_scalarScrambleRound(void* XXH_RESTRICT acc, + void const* XXH_RESTRICT secret, size_t lane); + +/*! + * @internal + * @brief The bulk processing loop for NEON. + * + * The NEON code path is actually partially scalar when running on AArch64. This + * is to optimize the pipelining and can have up to 15% speedup depending on the + * CPU, and it also mitigates some GCC codegen issues. + * + * @see XXH3_NEON_LANES for configuring this and details about this optimization. + */ +XXH_FORCE_INLINE void +XXH3_accumulate_512_neon( void* XXH_RESTRICT acc, + const void* XXH_RESTRICT input, + const void* XXH_RESTRICT secret) +{ + XXH_ASSERT((((size_t)acc) & 15) == 0); + XXH_STATIC_ASSERT(XXH3_NEON_LANES > 0 && XXH3_NEON_LANES <= XXH_ACC_NB && XXH3_NEON_LANES % 2 == 0); + { + uint64x2_t* const xacc = (uint64x2_t *) acc; + /* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */ + uint8_t const* const xinput = (const uint8_t *) input; + uint8_t const* const xsecret = (const uint8_t *) secret; + + size_t i; + /* NEON for the first few lanes (these loops are normally interleaved) */ + for (i=0; i < XXH3_NEON_LANES / 2; i++) { + /* data_vec = xinput[i]; */ + uint8x16_t data_vec = vld1q_u8(xinput + (i * 16)); + /* key_vec = xsecret[i]; */ + uint8x16_t key_vec = vld1q_u8(xsecret + (i * 16)); + uint64x2_t data_key; + uint32x2_t data_key_lo, data_key_hi; + /* xacc[i] += swap(data_vec); */ + uint64x2_t const data64 = vreinterpretq_u64_u8(data_vec); + uint64x2_t const swapped = vextq_u64(data64, data64, 1); + xacc[i] = vaddq_u64 (xacc[i], swapped); + /* data_key = data_vec ^ key_vec; */ + data_key = vreinterpretq_u64_u8(veorq_u8(data_vec, key_vec)); + /* data_key_lo = (uint32x2_t) (data_key & 0xFFFFFFFF); + * data_key_hi = (uint32x2_t) (data_key >> 32); + * data_key = UNDEFINED; */ + XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi); + /* xacc[i] += (uint64x2_t) data_key_lo * (uint64x2_t) data_key_hi; */ + xacc[i] = vmlal_u32 (xacc[i], data_key_lo, data_key_hi); + + } + /* Scalar for the remainder. This may be a zero iteration loop. */ + for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) { + XXH3_scalarRound(acc, input, secret, i); + } + } +} + +XXH_FORCE_INLINE void +XXH3_scrambleAcc_neon(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) +{ + XXH_ASSERT((((size_t)acc) & 15) == 0); + + { uint64x2_t* xacc = (uint64x2_t*) acc; + uint8_t const* xsecret = (uint8_t const*) secret; + uint32x2_t prime = vdup_n_u32 (XXH_PRIME32_1); + + size_t i; + /* NEON for the first few lanes (these loops are normally interleaved) */ + for (i=0; i < XXH3_NEON_LANES / 2; i++) { + /* xacc[i] ^= (xacc[i] >> 47); */ + uint64x2_t acc_vec = xacc[i]; + uint64x2_t shifted = vshrq_n_u64 (acc_vec, 47); + uint64x2_t data_vec = veorq_u64 (acc_vec, shifted); + + /* xacc[i] ^= xsecret[i]; */ + uint8x16_t key_vec = vld1q_u8 (xsecret + (i * 16)); + uint64x2_t data_key = veorq_u64 (data_vec, vreinterpretq_u64_u8(key_vec)); + + /* xacc[i] *= XXH_PRIME32_1 */ + uint32x2_t data_key_lo, data_key_hi; + /* data_key_lo = (uint32x2_t) (xacc[i] & 0xFFFFFFFF); + * data_key_hi = (uint32x2_t) (xacc[i] >> 32); + * xacc[i] = UNDEFINED; */ + XXH_SPLIT_IN_PLACE(data_key, data_key_lo, data_key_hi); + { /* + * prod_hi = (data_key >> 32) * XXH_PRIME32_1; + * + * Avoid vmul_u32 + vshll_n_u32 since Clang 6 and 7 will + * incorrectly "optimize" this: + * tmp = vmul_u32(vmovn_u64(a), vmovn_u64(b)); + * shifted = vshll_n_u32(tmp, 32); + * to this: + * tmp = "vmulq_u64"(a, b); // no such thing! + * shifted = vshlq_n_u64(tmp, 32); + * + * However, unlike SSE, Clang lacks a 64-bit multiply routine + * for NEON, and it scalarizes two 64-bit multiplies instead. + * + * vmull_u32 has the same timing as vmul_u32, and it avoids + * this bug completely. + * See https://bugs.llvm.org/show_bug.cgi?id=39967 + */ + uint64x2_t prod_hi = vmull_u32 (data_key_hi, prime); + /* xacc[i] = prod_hi << 32; */ + xacc[i] = vshlq_n_u64(prod_hi, 32); + /* xacc[i] += (prod_hi & 0xFFFFFFFF) * XXH_PRIME32_1; */ + xacc[i] = vmlal_u32(xacc[i], data_key_lo, prime); + } + } + /* Scalar for the remainder. This may be a zero iteration loop. */ + for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) { + XXH3_scalarScrambleRound(acc, secret, i); + } + } +} + +#endif + +#if (XXH_VECTOR == XXH_VSX) + +XXH_FORCE_INLINE void +XXH3_accumulate_512_vsx( void* XXH_RESTRICT acc, + const void* XXH_RESTRICT input, + const void* XXH_RESTRICT secret) +{ + /* presumed aligned */ + unsigned int* const xacc = (unsigned int*) acc; + xxh_u64x2 const* const xinput = (xxh_u64x2 const*) input; /* no alignment restriction */ + xxh_u64x2 const* const xsecret = (xxh_u64x2 const*) secret; /* no alignment restriction */ + xxh_u64x2 const v32 = { 32, 32 }; + size_t i; + for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) { + /* data_vec = xinput[i]; */ + xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + i); + /* key_vec = xsecret[i]; */ + xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + i); + xxh_u64x2 const data_key = data_vec ^ key_vec; + /* shuffled = (data_key << 32) | (data_key >> 32); */ + xxh_u32x4 const shuffled = (xxh_u32x4)vec_rl(data_key, v32); + /* product = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)shuffled & 0xFFFFFFFF); */ + xxh_u64x2 const product = XXH_vec_mulo((xxh_u32x4)data_key, shuffled); + /* acc_vec = xacc[i]; */ + xxh_u64x2 acc_vec = (xxh_u64x2)vec_xl(0, xacc + 4 * i); + acc_vec += product; + + /* swap high and low halves */ +#ifdef __s390x__ + acc_vec += vec_permi(data_vec, data_vec, 2); +#else + acc_vec += vec_xxpermdi(data_vec, data_vec, 2); +#endif + /* xacc[i] = acc_vec; */ + vec_xst((xxh_u32x4)acc_vec, 0, xacc + 4 * i); + } +} + +XXH_FORCE_INLINE void +XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) +{ + XXH_ASSERT((((size_t)acc) & 15) == 0); + + { xxh_u64x2* const xacc = (xxh_u64x2*) acc; + const xxh_u64x2* const xsecret = (const xxh_u64x2*) secret; + /* constants */ + xxh_u64x2 const v32 = { 32, 32 }; + xxh_u64x2 const v47 = { 47, 47 }; + xxh_u32x4 const prime = { XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1 }; + size_t i; + for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) { + /* xacc[i] ^= (xacc[i] >> 47); */ + xxh_u64x2 const acc_vec = xacc[i]; + xxh_u64x2 const data_vec = acc_vec ^ (acc_vec >> v47); + + /* xacc[i] ^= xsecret[i]; */ + xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + i); + xxh_u64x2 const data_key = data_vec ^ key_vec; + + /* xacc[i] *= XXH_PRIME32_1 */ + /* prod_lo = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)prime & 0xFFFFFFFF); */ + xxh_u64x2 const prod_even = XXH_vec_mule((xxh_u32x4)data_key, prime); + /* prod_hi = ((xxh_u64x2)data_key >> 32) * ((xxh_u64x2)prime >> 32); */ + xxh_u64x2 const prod_odd = XXH_vec_mulo((xxh_u32x4)data_key, prime); + xacc[i] = prod_odd + (prod_even << v32); + } } +} + +#endif + +/* scalar variants - universal */ + +/*! + * @internal + * @brief Scalar round for @ref XXH3_accumulate_512_scalar(). + * + * This is extracted to its own function because the NEON path uses a combination + * of NEON and scalar. + */ +XXH_FORCE_INLINE void +XXH3_scalarRound(void* XXH_RESTRICT acc, + void const* XXH_RESTRICT input, + void const* XXH_RESTRICT secret, + size_t lane) +{ + xxh_u64* xacc = (xxh_u64*) acc; + xxh_u8 const* xinput = (xxh_u8 const*) input; + xxh_u8 const* xsecret = (xxh_u8 const*) secret; + XXH_ASSERT(lane < XXH_ACC_NB); + XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN-1)) == 0); + { + xxh_u64 const data_val = XXH_readLE64(xinput + lane * 8); + xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + lane * 8); + xacc[lane ^ 1] += data_val; /* swap adjacent lanes */ + xacc[lane] += XXH_mult32to64(data_key & 0xFFFFFFFF, data_key >> 32); + } +} + +/*! + * @internal + * @brief Processes a 64 byte block of data using the scalar path. + */ +XXH_FORCE_INLINE void +XXH3_accumulate_512_scalar(void* XXH_RESTRICT acc, + const void* XXH_RESTRICT input, + const void* XXH_RESTRICT secret) +{ + size_t i; + for (i=0; i < XXH_ACC_NB; i++) { + XXH3_scalarRound(acc, input, secret, i); + } +} + +/*! + * @internal + * @brief Scalar scramble step for @ref XXH3_scrambleAcc_scalar(). + * + * This is extracted to its own function because the NEON path uses a combination + * of NEON and scalar. + */ +XXH_FORCE_INLINE void +XXH3_scalarScrambleRound(void* XXH_RESTRICT acc, + void const* XXH_RESTRICT secret, + size_t lane) +{ + xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */ + const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */ + XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN-1)) == 0); + XXH_ASSERT(lane < XXH_ACC_NB); + { + xxh_u64 const key64 = XXH_readLE64(xsecret + lane * 8); + xxh_u64 acc64 = xacc[lane]; + acc64 = XXH_xorshift64(acc64, 47); + acc64 ^= key64; + acc64 *= XXH_PRIME32_1; + xacc[lane] = acc64; + } +} + +/*! + * @internal + * @brief Scrambles the accumulators after a large chunk has been read + */ +XXH_FORCE_INLINE void +XXH3_scrambleAcc_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) +{ + size_t i; + for (i=0; i < XXH_ACC_NB; i++) { + XXH3_scalarScrambleRound(acc, secret, i); + } +} + +XXH_FORCE_INLINE void +XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64) +{ + /* + * We need a separate pointer for the hack below, + * which requires a non-const pointer. + * Any decent compiler will optimize this out otherwise. + */ + const xxh_u8* kSecretPtr = XXH3_kSecret; + XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0); + +#if defined(__clang__) && defined(__aarch64__) + /* + * UGLY HACK: + * Clang generates a bunch of MOV/MOVK pairs for aarch64, and they are + * placed sequentially, in order, at the top of the unrolled loop. + * + * While MOVK is great for generating constants (2 cycles for a 64-bit + * constant compared to 4 cycles for LDR), it fights for bandwidth with + * the arithmetic instructions. + * + * I L S + * MOVK + * MOVK + * MOVK + * MOVK + * ADD + * SUB STR + * STR + * By forcing loads from memory (as the asm line causes Clang to assume + * that XXH3_kSecretPtr has been changed), the pipelines are used more + * efficiently: + * I L S + * LDR + * ADD LDR + * SUB STR + * STR + * + * See XXH3_NEON_LANES for details on the pipsline. + * + * XXH3_64bits_withSeed, len == 256, Snapdragon 835 + * without hack: 2654.4 MB/s + * with hack: 3202.9 MB/s + */ + XXH_COMPILER_GUARD(kSecretPtr); +#endif + /* + * Note: in debug mode, this overrides the asm optimization + * and Clang will emit MOVK chains again. + */ + XXH_ASSERT(kSecretPtr == XXH3_kSecret); + + { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16; + int i; + for (i=0; i < nbRounds; i++) { + /* + * The asm hack causes Clang to assume that kSecretPtr aliases with + * customSecret, and on aarch64, this prevented LDP from merging two + * loads together for free. Putting the loads together before the stores + * properly generates LDP. + */ + xxh_u64 lo = XXH_readLE64(kSecretPtr + 16*i) + seed64; + xxh_u64 hi = XXH_readLE64(kSecretPtr + 16*i + 8) - seed64; + XXH_writeLE64((xxh_u8*)customSecret + 16*i, lo); + XXH_writeLE64((xxh_u8*)customSecret + 16*i + 8, hi); + } } +} + + +typedef void (*XXH3_f_accumulate_512)(void* XXH_RESTRICT, const void*, const void*); +typedef void (*XXH3_f_scrambleAcc)(void* XXH_RESTRICT, const void*); +typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64); + + +#if (XXH_VECTOR == XXH_AVX512) + +#define XXH3_accumulate_512 XXH3_accumulate_512_avx512 +#define XXH3_scrambleAcc XXH3_scrambleAcc_avx512 +#define XXH3_initCustomSecret XXH3_initCustomSecret_avx512 + +#elif (XXH_VECTOR == XXH_AVX2) + +#define XXH3_accumulate_512 XXH3_accumulate_512_avx2 +#define XXH3_scrambleAcc XXH3_scrambleAcc_avx2 +#define XXH3_initCustomSecret XXH3_initCustomSecret_avx2 + +#elif (XXH_VECTOR == XXH_SSE2) + +#define XXH3_accumulate_512 XXH3_accumulate_512_sse2 +#define XXH3_scrambleAcc XXH3_scrambleAcc_sse2 +#define XXH3_initCustomSecret XXH3_initCustomSecret_sse2 + +#elif (XXH_VECTOR == XXH_NEON) + +#define XXH3_accumulate_512 XXH3_accumulate_512_neon +#define XXH3_scrambleAcc XXH3_scrambleAcc_neon +#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar + +#elif (XXH_VECTOR == XXH_VSX) + +#define XXH3_accumulate_512 XXH3_accumulate_512_vsx +#define XXH3_scrambleAcc XXH3_scrambleAcc_vsx +#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar + +#else /* scalar */ + +#define XXH3_accumulate_512 XXH3_accumulate_512_scalar +#define XXH3_scrambleAcc XXH3_scrambleAcc_scalar +#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar + +#endif + + + +#ifndef XXH_PREFETCH_DIST +# ifdef __clang__ +# define XXH_PREFETCH_DIST 320 +# else +# if (XXH_VECTOR == XXH_AVX512) +# define XXH_PREFETCH_DIST 512 +# else +# define XXH_PREFETCH_DIST 384 +# endif +# endif /* __clang__ */ +#endif /* XXH_PREFETCH_DIST */ + +/* + * XXH3_accumulate() + * Loops over XXH3_accumulate_512(). + * Assumption: nbStripes will not overflow the secret size + */ +XXH_FORCE_INLINE void +XXH3_accumulate( xxh_u64* XXH_RESTRICT acc, + const xxh_u8* XXH_RESTRICT input, + const xxh_u8* XXH_RESTRICT secret, + size_t nbStripes, + XXH3_f_accumulate_512 f_acc512) +{ + size_t n; + for (n = 0; n < nbStripes; n++ ) { + const xxh_u8* const in = input + n*XXH_STRIPE_LEN; + XXH_PREFETCH(in + XXH_PREFETCH_DIST); + f_acc512(acc, + in, + secret + n*XXH_SECRET_CONSUME_RATE); + } +} + +XXH_FORCE_INLINE void +XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc, + const xxh_u8* XXH_RESTRICT input, size_t len, + const xxh_u8* XXH_RESTRICT secret, size_t secretSize, + XXH3_f_accumulate_512 f_acc512, + XXH3_f_scrambleAcc f_scramble) +{ + size_t const nbStripesPerBlock = (secretSize - XXH_STRIPE_LEN) / XXH_SECRET_CONSUME_RATE; + size_t const block_len = XXH_STRIPE_LEN * nbStripesPerBlock; + size_t const nb_blocks = (len - 1) / block_len; + + size_t n; + + XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); + + for (n = 0; n < nb_blocks; n++) { + XXH3_accumulate(acc, input + n*block_len, secret, nbStripesPerBlock, f_acc512); + f_scramble(acc, secret + secretSize - XXH_STRIPE_LEN); + } + + /* last partial block */ + XXH_ASSERT(len > XXH_STRIPE_LEN); + { size_t const nbStripes = ((len - 1) - (block_len * nb_blocks)) / XXH_STRIPE_LEN; + XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE)); + XXH3_accumulate(acc, input + nb_blocks*block_len, secret, nbStripes, f_acc512); + + /* last stripe */ + { const xxh_u8* const p = input + len - XXH_STRIPE_LEN; +#define XXH_SECRET_LASTACC_START 7 /* not aligned on 8, last secret is different from acc & scrambler */ + f_acc512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START); + } } +} + +XXH_FORCE_INLINE xxh_u64 +XXH3_mix2Accs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret) +{ + return XXH3_mul128_fold64( + acc[0] ^ XXH_readLE64(secret), + acc[1] ^ XXH_readLE64(secret+8) ); +} + +static XXH64_hash_t +XXH3_mergeAccs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret, xxh_u64 start) +{ + xxh_u64 result64 = start; + size_t i = 0; + + for (i = 0; i < 4; i++) { + result64 += XXH3_mix2Accs(acc+2*i, secret + 16*i); +#if defined(__clang__) /* Clang */ \ + && (defined(__arm__) || defined(__thumb__)) /* ARMv7 */ \ + && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \ + && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */ + /* + * UGLY HACK: + * Prevent autovectorization on Clang ARMv7-a. Exact same problem as + * the one in XXH3_len_129to240_64b. Speeds up shorter keys > 240b. + * XXH3_64bits, len == 256, Snapdragon 835: + * without hack: 2063.7 MB/s + * with hack: 2560.7 MB/s + */ + XXH_COMPILER_GUARD(result64); +#endif + } + + return XXH3_avalanche(result64); +} + +#define XXH3_INIT_ACC { XXH_PRIME32_3, XXH_PRIME64_1, XXH_PRIME64_2, XXH_PRIME64_3, \ + XXH_PRIME64_4, XXH_PRIME32_2, XXH_PRIME64_5, XXH_PRIME32_1 } + +XXH_FORCE_INLINE XXH64_hash_t +XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len, + const void* XXH_RESTRICT secret, size_t secretSize, + XXH3_f_accumulate_512 f_acc512, + XXH3_f_scrambleAcc f_scramble) +{ + XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC; + + XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc512, f_scramble); + + /* converge into final hash */ + XXH_STATIC_ASSERT(sizeof(acc) == 64); + /* do not align on 8, so that the secret is different from the accumulator */ +#define XXH_SECRET_MERGEACCS_START 11 + XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START); + return XXH3_mergeAccs(acc, (const xxh_u8*)secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)len * XXH_PRIME64_1); +} + +/* + * It's important for performance to transmit secret's size (when it's static) + * so that the compiler can properly optimize the vectorized loop. + * This makes a big performance difference for "medium" keys (<1 KB) when using AVX instruction set. + */ +XXH_FORCE_INLINE XXH64_hash_t +XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len, + XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) +{ + (void)seed64; + return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate_512, XXH3_scrambleAcc); +} + +/* + * It's preferable for performance that XXH3_hashLong is not inlined, + * as it results in a smaller function for small data, easier to the instruction cache. + * Note that inside this no_inline function, we do inline the internal loop, + * and provide a statically defined secret size to allow optimization of vector loop. + */ +XXH_NO_INLINE XXH64_hash_t +XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len, + XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) +{ + (void)seed64; (void)secret; (void)secretLen; + return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512, XXH3_scrambleAcc); +} + +/* + * XXH3_hashLong_64b_withSeed(): + * Generate a custom key based on alteration of default XXH3_kSecret with the seed, + * and then use this key for long mode hashing. + * + * This operation is decently fast but nonetheless costs a little bit of time. + * Try to avoid it whenever possible (typically when seed==0). + * + * It's important for performance that XXH3_hashLong is not inlined. Not sure + * why (uop cache maybe?), but the difference is large and easily measurable. + */ +XXH_FORCE_INLINE XXH64_hash_t +XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len, + XXH64_hash_t seed, + XXH3_f_accumulate_512 f_acc512, + XXH3_f_scrambleAcc f_scramble, + XXH3_f_initCustomSecret f_initSec) +{ + if (seed == 0) + return XXH3_hashLong_64b_internal(input, len, + XXH3_kSecret, sizeof(XXH3_kSecret), + f_acc512, f_scramble); + { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE]; + f_initSec(secret, seed); + return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret), + f_acc512, f_scramble); + } +} + +/* + * It's important for performance that XXH3_hashLong is not inlined. + */ +XXH_NO_INLINE XXH64_hash_t +XXH3_hashLong_64b_withSeed(const void* input, size_t len, + XXH64_hash_t seed, const xxh_u8* secret, size_t secretLen) +{ + (void)secret; (void)secretLen; + return XXH3_hashLong_64b_withSeed_internal(input, len, seed, + XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret); +} + + +typedef XXH64_hash_t (*XXH3_hashLong64_f)(const void* XXH_RESTRICT, size_t, + XXH64_hash_t, const xxh_u8* XXH_RESTRICT, size_t); + +XXH_FORCE_INLINE XXH64_hash_t +XXH3_64bits_internal(const void* XXH_RESTRICT input, size_t len, + XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen, + XXH3_hashLong64_f f_hashLong) +{ + XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN); + /* + * If an action is to be taken if `secretLen` condition is not respected, + * it should be done here. + * For now, it's a contract pre-condition. + * Adding a check and a branch here would cost performance at every hash. + * Also, note that function signature doesn't offer room to return an error. + */ + if (len <= 16) + return XXH3_len_0to16_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64); + if (len <= 128) + return XXH3_len_17to128_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); + if (len <= XXH3_MIDSIZE_MAX) + return XXH3_len_129to240_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); + return f_hashLong(input, len, seed64, (const xxh_u8*)secret, secretLen); +} + + +/* === Public entry point === */ + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* input, size_t len) +{ + return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH64_hash_t +XXH3_64bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize) +{ + return XXH3_64bits_internal(input, len, 0, secret, secretSize, XXH3_hashLong_64b_withSecret); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH64_hash_t +XXH3_64bits_withSeed(const void* input, size_t len, XXH64_hash_t seed) +{ + return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed); +} + +XXH_PUBLIC_API XXH64_hash_t +XXH3_64bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed) +{ + if (len <= XXH3_MIDSIZE_MAX) + return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL); + return XXH3_hashLong_64b_withSecret(input, len, seed, (const xxh_u8*)secret, secretSize); +} + + +/* === XXH3 streaming === */ + +/* + * Malloc's a pointer that is always aligned to align. + * + * This must be freed with `XXH_alignedFree()`. + * + * malloc typically guarantees 16 byte alignment on 64-bit systems and 8 byte + * alignment on 32-bit. This isn't enough for the 32 byte aligned loads in AVX2 + * or on 32-bit, the 16 byte aligned loads in SSE2 and NEON. + * + * This underalignment previously caused a rather obvious crash which went + * completely unnoticed due to XXH3_createState() not actually being tested. + * Credit to RedSpah for noticing this bug. + * + * The alignment is done manually: Functions like posix_memalign or _mm_malloc + * are avoided: To maintain portability, we would have to write a fallback + * like this anyways, and besides, testing for the existence of library + * functions without relying on external build tools is impossible. + * + * The method is simple: Overallocate, manually align, and store the offset + * to the original behind the returned pointer. + * + * Align must be a power of 2 and 8 <= align <= 128. + */ +static void* XXH_alignedMalloc(size_t s, size_t align) +{ + XXH_ASSERT(align <= 128 && align >= 8); /* range check */ + XXH_ASSERT((align & (align-1)) == 0); /* power of 2 */ + XXH_ASSERT(s != 0 && s < (s + align)); /* empty/overflow */ + { /* Overallocate to make room for manual realignment and an offset byte */ + xxh_u8* base = (xxh_u8*)XXH_malloc(s + align); + if (base != NULL) { + /* + * Get the offset needed to align this pointer. + * + * Even if the returned pointer is aligned, there will always be + * at least one byte to store the offset to the original pointer. + */ + size_t offset = align - ((size_t)base & (align - 1)); /* base % align */ + /* Add the offset for the now-aligned pointer */ + xxh_u8* ptr = base + offset; + + XXH_ASSERT((size_t)ptr % align == 0); + + /* Store the offset immediately before the returned pointer. */ + ptr[-1] = (xxh_u8)offset; + return ptr; + } + return NULL; + } +} +/* + * Frees an aligned pointer allocated by XXH_alignedMalloc(). Don't pass + * normal malloc'd pointers, XXH_alignedMalloc has a specific data layout. + */ +static void XXH_alignedFree(void* p) +{ + if (p != NULL) { + xxh_u8* ptr = (xxh_u8*)p; + /* Get the offset byte we added in XXH_malloc. */ + xxh_u8 offset = ptr[-1]; + /* Free the original malloc'd pointer */ + xxh_u8* base = ptr - offset; + XXH_free(base); + } +} +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void) +{ + XXH3_state_t* const state = (XXH3_state_t*)XXH_alignedMalloc(sizeof(XXH3_state_t), 64); + if (state==NULL) return NULL; + XXH3_INITSTATE(state); + return state; +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr) +{ + XXH_alignedFree(statePtr); + return XXH_OK; +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API void +XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state) +{ + XXH_memcpy(dst_state, src_state, sizeof(*dst_state)); +} + +static void +XXH3_reset_internal(XXH3_state_t* statePtr, + XXH64_hash_t seed, + const void* secret, size_t secretSize) +{ + size_t const initStart = offsetof(XXH3_state_t, bufferedSize); + size_t const initLength = offsetof(XXH3_state_t, nbStripesPerBlock) - initStart; + XXH_ASSERT(offsetof(XXH3_state_t, nbStripesPerBlock) > initStart); + XXH_ASSERT(statePtr != NULL); + /* set members from bufferedSize to nbStripesPerBlock (excluded) to 0 */ + memset((char*)statePtr + initStart, 0, initLength); + statePtr->acc[0] = XXH_PRIME32_3; + statePtr->acc[1] = XXH_PRIME64_1; + statePtr->acc[2] = XXH_PRIME64_2; + statePtr->acc[3] = XXH_PRIME64_3; + statePtr->acc[4] = XXH_PRIME64_4; + statePtr->acc[5] = XXH_PRIME32_2; + statePtr->acc[6] = XXH_PRIME64_5; + statePtr->acc[7] = XXH_PRIME32_1; + statePtr->seed = seed; + statePtr->useSeed = (seed != 0); + statePtr->extSecret = (const unsigned char*)secret; + XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); + statePtr->secretLimit = secretSize - XXH_STRIPE_LEN; + statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE; +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_64bits_reset(XXH3_state_t* statePtr) +{ + if (statePtr == NULL) return XXH_ERROR; + XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE); + return XXH_OK; +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize) +{ + if (statePtr == NULL) return XXH_ERROR; + XXH3_reset_internal(statePtr, 0, secret, secretSize); + if (secret == NULL) return XXH_ERROR; + if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR; + return XXH_OK; +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed) +{ + if (statePtr == NULL) return XXH_ERROR; + if (seed==0) return XXH3_64bits_reset(statePtr); + if ((seed != statePtr->seed) || (statePtr->extSecret != NULL)) + XXH3_initCustomSecret(statePtr->customSecret, seed); + XXH3_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE); + return XXH_OK; +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed64) +{ + if (statePtr == NULL) return XXH_ERROR; + if (secret == NULL) return XXH_ERROR; + if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR; + XXH3_reset_internal(statePtr, seed64, secret, secretSize); + statePtr->useSeed = 1; /* always, even if seed64==0 */ + return XXH_OK; +} + +/* Note : when XXH3_consumeStripes() is invoked, + * there must be a guarantee that at least one more byte must be consumed from input + * so that the function can blindly consume all stripes using the "normal" secret segment */ +XXH_FORCE_INLINE void +XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc, + size_t* XXH_RESTRICT nbStripesSoFarPtr, size_t nbStripesPerBlock, + const xxh_u8* XXH_RESTRICT input, size_t nbStripes, + const xxh_u8* XXH_RESTRICT secret, size_t secretLimit, + XXH3_f_accumulate_512 f_acc512, + XXH3_f_scrambleAcc f_scramble) +{ + XXH_ASSERT(nbStripes <= nbStripesPerBlock); /* can handle max 1 scramble per invocation */ + XXH_ASSERT(*nbStripesSoFarPtr < nbStripesPerBlock); + if (nbStripesPerBlock - *nbStripesSoFarPtr <= nbStripes) { + /* need a scrambling operation */ + size_t const nbStripesToEndofBlock = nbStripesPerBlock - *nbStripesSoFarPtr; + size_t const nbStripesAfterBlock = nbStripes - nbStripesToEndofBlock; + XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripesToEndofBlock, f_acc512); + f_scramble(acc, secret + secretLimit); + XXH3_accumulate(acc, input + nbStripesToEndofBlock * XXH_STRIPE_LEN, secret, nbStripesAfterBlock, f_acc512); + *nbStripesSoFarPtr = nbStripesAfterBlock; + } else { + XXH3_accumulate(acc, input, secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE, nbStripes, f_acc512); + *nbStripesSoFarPtr += nbStripes; + } +} + +#ifndef XXH3_STREAM_USE_STACK +# ifndef __clang__ /* clang doesn't need additional stack space */ +# define XXH3_STREAM_USE_STACK 1 +# endif +#endif +/* + * Both XXH3_64bits_update and XXH3_128bits_update use this routine. + */ +XXH_FORCE_INLINE XXH_errorcode +XXH3_update(XXH3_state_t* XXH_RESTRICT const state, + const xxh_u8* XXH_RESTRICT input, size_t len, + XXH3_f_accumulate_512 f_acc512, + XXH3_f_scrambleAcc f_scramble) +{ + if (input==NULL) { + XXH_ASSERT(len == 0); + return XXH_OK; + } + + XXH_ASSERT(state != NULL); + { const xxh_u8* const bEnd = input + len; + const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; +#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1 + /* For some reason, gcc and MSVC seem to suffer greatly + * when operating accumulators directly into state. + * Operating into stack space seems to enable proper optimization. + * clang, on the other hand, doesn't seem to need this trick */ + XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8]; memcpy(acc, state->acc, sizeof(acc)); +#else + xxh_u64* XXH_RESTRICT const acc = state->acc; +#endif + state->totalLen += len; + XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE); + + /* small input : just fill in tmp buffer */ + if (state->bufferedSize + len <= XXH3_INTERNALBUFFER_SIZE) { + XXH_memcpy(state->buffer + state->bufferedSize, input, len); + state->bufferedSize += (XXH32_hash_t)len; + return XXH_OK; + } + + /* total input is now > XXH3_INTERNALBUFFER_SIZE */ + #define XXH3_INTERNALBUFFER_STRIPES (XXH3_INTERNALBUFFER_SIZE / XXH_STRIPE_LEN) + XXH_STATIC_ASSERT(XXH3_INTERNALBUFFER_SIZE % XXH_STRIPE_LEN == 0); /* clean multiple */ + + /* + * Internal buffer is partially filled (always, except at beginning) + * Complete it, then consume it. + */ + if (state->bufferedSize) { + size_t const loadSize = XXH3_INTERNALBUFFER_SIZE - state->bufferedSize; + XXH_memcpy(state->buffer + state->bufferedSize, input, loadSize); + input += loadSize; + XXH3_consumeStripes(acc, + &state->nbStripesSoFar, state->nbStripesPerBlock, + state->buffer, XXH3_INTERNALBUFFER_STRIPES, + secret, state->secretLimit, + f_acc512, f_scramble); + state->bufferedSize = 0; + } + XXH_ASSERT(input < bEnd); + + /* large input to consume : ingest per full block */ + if ((size_t)(bEnd - input) > state->nbStripesPerBlock * XXH_STRIPE_LEN) { + size_t nbStripes = (size_t)(bEnd - 1 - input) / XXH_STRIPE_LEN; + XXH_ASSERT(state->nbStripesPerBlock >= state->nbStripesSoFar); + /* join to current block's end */ + { size_t const nbStripesToEnd = state->nbStripesPerBlock - state->nbStripesSoFar; + XXH_ASSERT(nbStripesToEnd <= nbStripes); + XXH3_accumulate(acc, input, secret + state->nbStripesSoFar * XXH_SECRET_CONSUME_RATE, nbStripesToEnd, f_acc512); + f_scramble(acc, secret + state->secretLimit); + state->nbStripesSoFar = 0; + input += nbStripesToEnd * XXH_STRIPE_LEN; + nbStripes -= nbStripesToEnd; + } + /* consume per entire blocks */ + while(nbStripes >= state->nbStripesPerBlock) { + XXH3_accumulate(acc, input, secret, state->nbStripesPerBlock, f_acc512); + f_scramble(acc, secret + state->secretLimit); + input += state->nbStripesPerBlock * XXH_STRIPE_LEN; + nbStripes -= state->nbStripesPerBlock; + } + /* consume last partial block */ + XXH3_accumulate(acc, input, secret, nbStripes, f_acc512); + input += nbStripes * XXH_STRIPE_LEN; + XXH_ASSERT(input < bEnd); /* at least some bytes left */ + state->nbStripesSoFar = nbStripes; + /* buffer predecessor of last partial stripe */ + XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN); + XXH_ASSERT(bEnd - input <= XXH_STRIPE_LEN); + } else { + /* content to consume <= block size */ + /* Consume input by a multiple of internal buffer size */ + if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) { + const xxh_u8* const limit = bEnd - XXH3_INTERNALBUFFER_SIZE; + do { + XXH3_consumeStripes(acc, + &state->nbStripesSoFar, state->nbStripesPerBlock, + input, XXH3_INTERNALBUFFER_STRIPES, + secret, state->secretLimit, + f_acc512, f_scramble); + input += XXH3_INTERNALBUFFER_SIZE; + } while (inputbuffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN); + } + } + + /* Some remaining input (always) : buffer it */ + XXH_ASSERT(input < bEnd); + XXH_ASSERT(bEnd - input <= XXH3_INTERNALBUFFER_SIZE); + XXH_ASSERT(state->bufferedSize == 0); + XXH_memcpy(state->buffer, input, (size_t)(bEnd-input)); + state->bufferedSize = (XXH32_hash_t)(bEnd-input); +#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1 + /* save stack accumulators into state */ + memcpy(state->acc, acc, sizeof(acc)); +#endif + } + + return XXH_OK; +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_64bits_update(XXH3_state_t* state, const void* input, size_t len) +{ + return XXH3_update(state, (const xxh_u8*)input, len, + XXH3_accumulate_512, XXH3_scrambleAcc); +} + + +XXH_FORCE_INLINE void +XXH3_digest_long (XXH64_hash_t* acc, + const XXH3_state_t* state, + const unsigned char* secret) +{ + /* + * Digest on a local copy. This way, the state remains unaltered, and it can + * continue ingesting more input afterwards. + */ + XXH_memcpy(acc, state->acc, sizeof(state->acc)); + if (state->bufferedSize >= XXH_STRIPE_LEN) { + size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN; + size_t nbStripesSoFar = state->nbStripesSoFar; + XXH3_consumeStripes(acc, + &nbStripesSoFar, state->nbStripesPerBlock, + state->buffer, nbStripes, + secret, state->secretLimit, + XXH3_accumulate_512, XXH3_scrambleAcc); + /* last stripe */ + XXH3_accumulate_512(acc, + state->buffer + state->bufferedSize - XXH_STRIPE_LEN, + secret + state->secretLimit - XXH_SECRET_LASTACC_START); + } else { /* bufferedSize < XXH_STRIPE_LEN */ + xxh_u8 lastStripe[XXH_STRIPE_LEN]; + size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize; + XXH_ASSERT(state->bufferedSize > 0); /* there is always some input buffered */ + XXH_memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize); + XXH_memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize); + XXH3_accumulate_512(acc, + lastStripe, + secret + state->secretLimit - XXH_SECRET_LASTACC_START); + } +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state) +{ + const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; + if (state->totalLen > XXH3_MIDSIZE_MAX) { + XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB]; + XXH3_digest_long(acc, state, secret); + return XXH3_mergeAccs(acc, + secret + XXH_SECRET_MERGEACCS_START, + (xxh_u64)state->totalLen * XXH_PRIME64_1); + } + /* totalLen <= XXH3_MIDSIZE_MAX: digesting a short input */ + if (state->useSeed) + return XXH3_64bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed); + return XXH3_64bits_withSecret(state->buffer, (size_t)(state->totalLen), + secret, state->secretLimit + XXH_STRIPE_LEN); +} + + + +/* ========================================== + * XXH3 128 bits (a.k.a XXH128) + * ========================================== + * XXH3's 128-bit variant has better mixing and strength than the 64-bit variant, + * even without counting the significantly larger output size. + * + * For example, extra steps are taken to avoid the seed-dependent collisions + * in 17-240 byte inputs (See XXH3_mix16B and XXH128_mix32B). + * + * This strength naturally comes at the cost of some speed, especially on short + * lengths. Note that longer hashes are about as fast as the 64-bit version + * due to it using only a slight modification of the 64-bit loop. + * + * XXH128 is also more oriented towards 64-bit machines. It is still extremely + * fast for a _128-bit_ hash on 32-bit (it usually clears XXH64). + */ + +XXH_FORCE_INLINE XXH128_hash_t +XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + /* A doubled version of 1to3_64b with different constants. */ + XXH_ASSERT(input != NULL); + XXH_ASSERT(1 <= len && len <= 3); + XXH_ASSERT(secret != NULL); + /* + * len = 1: combinedl = { input[0], 0x01, input[0], input[0] } + * len = 2: combinedl = { input[1], 0x02, input[0], input[1] } + * len = 3: combinedl = { input[2], 0x03, input[0], input[1] } + */ + { xxh_u8 const c1 = input[0]; + xxh_u8 const c2 = input[len >> 1]; + xxh_u8 const c3 = input[len - 1]; + xxh_u32 const combinedl = ((xxh_u32)c1 <<16) | ((xxh_u32)c2 << 24) + | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8); + xxh_u32 const combinedh = XXH_rotl32(XXH_swap32(combinedl), 13); + xxh_u64 const bitflipl = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed; + xxh_u64 const bitfliph = (XXH_readLE32(secret+8) ^ XXH_readLE32(secret+12)) - seed; + xxh_u64 const keyed_lo = (xxh_u64)combinedl ^ bitflipl; + xxh_u64 const keyed_hi = (xxh_u64)combinedh ^ bitfliph; + XXH128_hash_t h128; + h128.low64 = XXH64_avalanche(keyed_lo); + h128.high64 = XXH64_avalanche(keyed_hi); + return h128; + } +} + +XXH_FORCE_INLINE XXH128_hash_t +XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + XXH_ASSERT(input != NULL); + XXH_ASSERT(secret != NULL); + XXH_ASSERT(4 <= len && len <= 8); + seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32; + { xxh_u32 const input_lo = XXH_readLE32(input); + xxh_u32 const input_hi = XXH_readLE32(input + len - 4); + xxh_u64 const input_64 = input_lo + ((xxh_u64)input_hi << 32); + xxh_u64 const bitflip = (XXH_readLE64(secret+16) ^ XXH_readLE64(secret+24)) + seed; + xxh_u64 const keyed = input_64 ^ bitflip; + + /* Shift len to the left to ensure it is even, this avoids even multiplies. */ + XXH128_hash_t m128 = XXH_mult64to128(keyed, XXH_PRIME64_1 + (len << 2)); + + m128.high64 += (m128.low64 << 1); + m128.low64 ^= (m128.high64 >> 3); + + m128.low64 = XXH_xorshift64(m128.low64, 35); + m128.low64 *= 0x9FB21C651E98DF25ULL; + m128.low64 = XXH_xorshift64(m128.low64, 28); + m128.high64 = XXH3_avalanche(m128.high64); + return m128; + } +} + +XXH_FORCE_INLINE XXH128_hash_t +XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + XXH_ASSERT(input != NULL); + XXH_ASSERT(secret != NULL); + XXH_ASSERT(9 <= len && len <= 16); + { xxh_u64 const bitflipl = (XXH_readLE64(secret+32) ^ XXH_readLE64(secret+40)) - seed; + xxh_u64 const bitfliph = (XXH_readLE64(secret+48) ^ XXH_readLE64(secret+56)) + seed; + xxh_u64 const input_lo = XXH_readLE64(input); + xxh_u64 input_hi = XXH_readLE64(input + len - 8); + XXH128_hash_t m128 = XXH_mult64to128(input_lo ^ input_hi ^ bitflipl, XXH_PRIME64_1); + /* + * Put len in the middle of m128 to ensure that the length gets mixed to + * both the low and high bits in the 128x64 multiply below. + */ + m128.low64 += (xxh_u64)(len - 1) << 54; + input_hi ^= bitfliph; + /* + * Add the high 32 bits of input_hi to the high 32 bits of m128, then + * add the long product of the low 32 bits of input_hi and XXH_PRIME32_2 to + * the high 64 bits of m128. + * + * The best approach to this operation is different on 32-bit and 64-bit. + */ + if (sizeof(void *) < sizeof(xxh_u64)) { /* 32-bit */ + /* + * 32-bit optimized version, which is more readable. + * + * On 32-bit, it removes an ADC and delays a dependency between the two + * halves of m128.high64, but it generates an extra mask on 64-bit. + */ + m128.high64 += (input_hi & 0xFFFFFFFF00000000ULL) + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2); + } else { + /* + * 64-bit optimized (albeit more confusing) version. + * + * Uses some properties of addition and multiplication to remove the mask: + * + * Let: + * a = input_hi.lo = (input_hi & 0x00000000FFFFFFFF) + * b = input_hi.hi = (input_hi & 0xFFFFFFFF00000000) + * c = XXH_PRIME32_2 + * + * a + (b * c) + * Inverse Property: x + y - x == y + * a + (b * (1 + c - 1)) + * Distributive Property: x * (y + z) == (x * y) + (x * z) + * a + (b * 1) + (b * (c - 1)) + * Identity Property: x * 1 == x + * a + b + (b * (c - 1)) + * + * Substitute a, b, and c: + * input_hi.hi + input_hi.lo + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1)) + * + * Since input_hi.hi + input_hi.lo == input_hi, we get this: + * input_hi + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1)) + */ + m128.high64 += input_hi + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2 - 1); + } + /* m128 ^= XXH_swap64(m128 >> 64); */ + m128.low64 ^= XXH_swap64(m128.high64); + + { /* 128x64 multiply: h128 = m128 * XXH_PRIME64_2; */ + XXH128_hash_t h128 = XXH_mult64to128(m128.low64, XXH_PRIME64_2); + h128.high64 += m128.high64 * XXH_PRIME64_2; + + h128.low64 = XXH3_avalanche(h128.low64); + h128.high64 = XXH3_avalanche(h128.high64); + return h128; + } } +} + +/* + * Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN + */ +XXH_FORCE_INLINE XXH128_hash_t +XXH3_len_0to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) +{ + XXH_ASSERT(len <= 16); + { if (len > 8) return XXH3_len_9to16_128b(input, len, secret, seed); + if (len >= 4) return XXH3_len_4to8_128b(input, len, secret, seed); + if (len) return XXH3_len_1to3_128b(input, len, secret, seed); + { XXH128_hash_t h128; + xxh_u64 const bitflipl = XXH_readLE64(secret+64) ^ XXH_readLE64(secret+72); + xxh_u64 const bitfliph = XXH_readLE64(secret+80) ^ XXH_readLE64(secret+88); + h128.low64 = XXH64_avalanche(seed ^ bitflipl); + h128.high64 = XXH64_avalanche( seed ^ bitfliph); + return h128; + } } +} + +/* + * A bit slower than XXH3_mix16B, but handles multiply by zero better. + */ +XXH_FORCE_INLINE XXH128_hash_t +XXH128_mix32B(XXH128_hash_t acc, const xxh_u8* input_1, const xxh_u8* input_2, + const xxh_u8* secret, XXH64_hash_t seed) +{ + acc.low64 += XXH3_mix16B (input_1, secret+0, seed); + acc.low64 ^= XXH_readLE64(input_2) + XXH_readLE64(input_2 + 8); + acc.high64 += XXH3_mix16B (input_2, secret+16, seed); + acc.high64 ^= XXH_readLE64(input_1) + XXH_readLE64(input_1 + 8); + return acc; +} + + +XXH_FORCE_INLINE XXH128_hash_t +XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len, + const xxh_u8* XXH_RESTRICT secret, size_t secretSize, + XXH64_hash_t seed) +{ + XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; + XXH_ASSERT(16 < len && len <= 128); + + { XXH128_hash_t acc; + acc.low64 = len * XXH_PRIME64_1; + acc.high64 = 0; + if (len > 32) { + if (len > 64) { + if (len > 96) { + acc = XXH128_mix32B(acc, input+48, input+len-64, secret+96, seed); + } + acc = XXH128_mix32B(acc, input+32, input+len-48, secret+64, seed); + } + acc = XXH128_mix32B(acc, input+16, input+len-32, secret+32, seed); + } + acc = XXH128_mix32B(acc, input, input+len-16, secret, seed); + { XXH128_hash_t h128; + h128.low64 = acc.low64 + acc.high64; + h128.high64 = (acc.low64 * XXH_PRIME64_1) + + (acc.high64 * XXH_PRIME64_4) + + ((len - seed) * XXH_PRIME64_2); + h128.low64 = XXH3_avalanche(h128.low64); + h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64); + return h128; + } + } +} + +XXH_NO_INLINE XXH128_hash_t +XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len, + const xxh_u8* XXH_RESTRICT secret, size_t secretSize, + XXH64_hash_t seed) +{ + XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; + XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX); + + { XXH128_hash_t acc; + int const nbRounds = (int)len / 32; + int i; + acc.low64 = len * XXH_PRIME64_1; + acc.high64 = 0; + for (i=0; i<4; i++) { + acc = XXH128_mix32B(acc, + input + (32 * i), + input + (32 * i) + 16, + secret + (32 * i), + seed); + } + acc.low64 = XXH3_avalanche(acc.low64); + acc.high64 = XXH3_avalanche(acc.high64); + XXH_ASSERT(nbRounds >= 4); + for (i=4 ; i < nbRounds; i++) { + acc = XXH128_mix32B(acc, + input + (32 * i), + input + (32 * i) + 16, + secret + XXH3_MIDSIZE_STARTOFFSET + (32 * (i - 4)), + seed); + } + /* last bytes */ + acc = XXH128_mix32B(acc, + input + len - 16, + input + len - 32, + secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET - 16, + 0ULL - seed); + + { XXH128_hash_t h128; + h128.low64 = acc.low64 + acc.high64; + h128.high64 = (acc.low64 * XXH_PRIME64_1) + + (acc.high64 * XXH_PRIME64_4) + + ((len - seed) * XXH_PRIME64_2); + h128.low64 = XXH3_avalanche(h128.low64); + h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64); + return h128; + } + } +} + +XXH_FORCE_INLINE XXH128_hash_t +XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len, + const xxh_u8* XXH_RESTRICT secret, size_t secretSize, + XXH3_f_accumulate_512 f_acc512, + XXH3_f_scrambleAcc f_scramble) +{ + XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC; + + XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc512, f_scramble); + + /* converge into final hash */ + XXH_STATIC_ASSERT(sizeof(acc) == 64); + XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START); + { XXH128_hash_t h128; + h128.low64 = XXH3_mergeAccs(acc, + secret + XXH_SECRET_MERGEACCS_START, + (xxh_u64)len * XXH_PRIME64_1); + h128.high64 = XXH3_mergeAccs(acc, + secret + secretSize + - sizeof(acc) - XXH_SECRET_MERGEACCS_START, + ~((xxh_u64)len * XXH_PRIME64_2)); + return h128; + } +} + +/* + * It's important for performance that XXH3_hashLong is not inlined. + */ +XXH_NO_INLINE XXH128_hash_t +XXH3_hashLong_128b_default(const void* XXH_RESTRICT input, size_t len, + XXH64_hash_t seed64, + const void* XXH_RESTRICT secret, size_t secretLen) +{ + (void)seed64; (void)secret; (void)secretLen; + return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), + XXH3_accumulate_512, XXH3_scrambleAcc); +} + +/* + * It's important for performance to pass @secretLen (when it's static) + * to the compiler, so that it can properly optimize the vectorized loop. + */ +XXH_FORCE_INLINE XXH128_hash_t +XXH3_hashLong_128b_withSecret(const void* XXH_RESTRICT input, size_t len, + XXH64_hash_t seed64, + const void* XXH_RESTRICT secret, size_t secretLen) +{ + (void)seed64; + return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen, + XXH3_accumulate_512, XXH3_scrambleAcc); +} + +XXH_FORCE_INLINE XXH128_hash_t +XXH3_hashLong_128b_withSeed_internal(const void* XXH_RESTRICT input, size_t len, + XXH64_hash_t seed64, + XXH3_f_accumulate_512 f_acc512, + XXH3_f_scrambleAcc f_scramble, + XXH3_f_initCustomSecret f_initSec) +{ + if (seed64 == 0) + return XXH3_hashLong_128b_internal(input, len, + XXH3_kSecret, sizeof(XXH3_kSecret), + f_acc512, f_scramble); + { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE]; + f_initSec(secret, seed64); + return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, sizeof(secret), + f_acc512, f_scramble); + } +} + +/* + * It's important for performance that XXH3_hashLong is not inlined. + */ +XXH_NO_INLINE XXH128_hash_t +XXH3_hashLong_128b_withSeed(const void* input, size_t len, + XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen) +{ + (void)secret; (void)secretLen; + return XXH3_hashLong_128b_withSeed_internal(input, len, seed64, + XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret); +} + +typedef XXH128_hash_t (*XXH3_hashLong128_f)(const void* XXH_RESTRICT, size_t, + XXH64_hash_t, const void* XXH_RESTRICT, size_t); + +XXH_FORCE_INLINE XXH128_hash_t +XXH3_128bits_internal(const void* input, size_t len, + XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen, + XXH3_hashLong128_f f_hl128) +{ + XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN); + /* + * If an action is to be taken if `secret` conditions are not respected, + * it should be done here. + * For now, it's a contract pre-condition. + * Adding a check and a branch here would cost performance at every hash. + */ + if (len <= 16) + return XXH3_len_0to16_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64); + if (len <= 128) + return XXH3_len_17to128_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); + if (len <= XXH3_MIDSIZE_MAX) + return XXH3_len_129to240_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); + return f_hl128(input, len, seed64, secret, secretLen); +} + + +/* === Public XXH128 API === */ + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* input, size_t len) +{ + return XXH3_128bits_internal(input, len, 0, + XXH3_kSecret, sizeof(XXH3_kSecret), + XXH3_hashLong_128b_default); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH128_hash_t +XXH3_128bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize) +{ + return XXH3_128bits_internal(input, len, 0, + (const xxh_u8*)secret, secretSize, + XXH3_hashLong_128b_withSecret); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH128_hash_t +XXH3_128bits_withSeed(const void* input, size_t len, XXH64_hash_t seed) +{ + return XXH3_128bits_internal(input, len, seed, + XXH3_kSecret, sizeof(XXH3_kSecret), + XXH3_hashLong_128b_withSeed); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH128_hash_t +XXH3_128bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed) +{ + if (len <= XXH3_MIDSIZE_MAX) + return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL); + return XXH3_hashLong_128b_withSecret(input, len, seed, secret, secretSize); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH128_hash_t +XXH128(const void* input, size_t len, XXH64_hash_t seed) +{ + return XXH3_128bits_withSeed(input, len, seed); +} + + +/* === XXH3 128-bit streaming === */ + +/* + * All initialization and update functions are identical to 64-bit streaming variant. + * The only difference is the finalization routine. + */ + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_128bits_reset(XXH3_state_t* statePtr) +{ + return XXH3_64bits_reset(statePtr); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize) +{ + return XXH3_64bits_reset_withSecret(statePtr, secret, secretSize); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed) +{ + return XXH3_64bits_reset_withSeed(statePtr, seed); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed) +{ + return XXH3_64bits_reset_withSecretandSeed(statePtr, secret, secretSize, seed); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_128bits_update(XXH3_state_t* state, const void* input, size_t len) +{ + return XXH3_update(state, (const xxh_u8*)input, len, + XXH3_accumulate_512, XXH3_scrambleAcc); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* state) +{ + const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; + if (state->totalLen > XXH3_MIDSIZE_MAX) { + XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB]; + XXH3_digest_long(acc, state, secret); + XXH_ASSERT(state->secretLimit + XXH_STRIPE_LEN >= sizeof(acc) + XXH_SECRET_MERGEACCS_START); + { XXH128_hash_t h128; + h128.low64 = XXH3_mergeAccs(acc, + secret + XXH_SECRET_MERGEACCS_START, + (xxh_u64)state->totalLen * XXH_PRIME64_1); + h128.high64 = XXH3_mergeAccs(acc, + secret + state->secretLimit + XXH_STRIPE_LEN + - sizeof(acc) - XXH_SECRET_MERGEACCS_START, + ~((xxh_u64)state->totalLen * XXH_PRIME64_2)); + return h128; + } + } + /* len <= XXH3_MIDSIZE_MAX : short code */ + if (state->seed) + return XXH3_128bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed); + return XXH3_128bits_withSecret(state->buffer, (size_t)(state->totalLen), + secret, state->secretLimit + XXH_STRIPE_LEN); +} + +/* 128-bit utility functions */ + +#include /* memcmp, memcpy */ + +/* return : 1 is equal, 0 if different */ +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2) +{ + /* note : XXH128_hash_t is compact, it has no padding byte */ + return !(memcmp(&h1, &h2, sizeof(h1))); +} + +/* This prototype is compatible with stdlib's qsort(). + * return : >0 if *h128_1 > *h128_2 + * <0 if *h128_1 < *h128_2 + * =0 if *h128_1 == *h128_2 */ +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2) +{ + XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1; + XXH128_hash_t const h2 = *(const XXH128_hash_t*)h128_2; + int const hcmp = (h1.high64 > h2.high64) - (h2.high64 > h1.high64); + /* note : bets that, in most cases, hash values are different */ + if (hcmp) return hcmp; + return (h1.low64 > h2.low64) - (h2.low64 > h1.low64); +} + + +/*====== Canonical representation ======*/ +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API void +XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) { + hash.high64 = XXH_swap64(hash.high64); + hash.low64 = XXH_swap64(hash.low64); + } + XXH_memcpy(dst, &hash.high64, sizeof(hash.high64)); + XXH_memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64)); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH128_hash_t +XXH128_hashFromCanonical(const XXH128_canonical_t* src) +{ + XXH128_hash_t h; + h.high64 = XXH_readBE64(src); + h.low64 = XXH_readBE64(src->digest + 8); + return h; +} + + + +/* ========================================== + * Secret generators + * ========================================== + */ +#define XXH_MIN(x, y) (((x) > (y)) ? (y) : (x)) + +XXH_FORCE_INLINE void XXH3_combine16(void* dst, XXH128_hash_t h128) +{ + XXH_writeLE64( dst, XXH_readLE64(dst) ^ h128.low64 ); + XXH_writeLE64( (char*)dst+8, XXH_readLE64((char*)dst+8) ^ h128.high64 ); +} + +/*! @ingroup xxh3_family */ +XXH_PUBLIC_API XXH_errorcode +XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize) +{ +#if (XXH_DEBUGLEVEL >= 1) + XXH_ASSERT(secretBuffer != NULL); + XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); +#else + /* production mode, assert() are disabled */ + if (secretBuffer == NULL) return XXH_ERROR; + if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR; +#endif + + if (customSeedSize == 0) { + customSeed = XXH3_kSecret; + customSeedSize = XXH_SECRET_DEFAULT_SIZE; + } +#if (XXH_DEBUGLEVEL >= 1) + XXH_ASSERT(customSeed != NULL); +#else + if (customSeed == NULL) return XXH_ERROR; +#endif + + /* Fill secretBuffer with a copy of customSeed - repeat as needed */ + { size_t pos = 0; + while (pos < secretSize) { + size_t const toCopy = XXH_MIN((secretSize - pos), customSeedSize); + memcpy((char*)secretBuffer + pos, customSeed, toCopy); + pos += toCopy; + } } + + { size_t const nbSeg16 = secretSize / 16; + size_t n; + XXH128_canonical_t scrambler; + XXH128_canonicalFromHash(&scrambler, XXH128(customSeed, customSeedSize, 0)); + for (n=0; n -#endif #include "compiler.h" +#include "cpu.h" #include "mem.h" #include "debug.h" /* assert, DEBUGLOG, RAWLOG, g_debuglevel */ #include "error_private.h" @@ -30,7 +28,6 @@ #include "../zstd.h" #define FSE_STATIC_LINKING_ONLY #include "fse.h" -#define HUF_STATIC_LINKING_ONLY #include "huf.h" #ifndef XXH_STATIC_LINKING_ONLY # define XXH_STATIC_LINKING_ONLY /* XXH64_state_t */ @@ -60,81 +57,7 @@ extern "C" { #undef MAX #define MIN(a,b) ((a)<(b) ? (a) : (b)) #define MAX(a,b) ((a)>(b) ? (a) : (b)) - -/** - * Ignore: this is an internal helper. - * - * This is a helper function to help force C99-correctness during compilation. - * Under strict compilation modes, variadic macro arguments can't be empty. - * However, variadic function arguments can be. Using a function therefore lets - * us statically check that at least one (string) argument was passed, - * independent of the compilation flags. - */ -static INLINE_KEYWORD UNUSED_ATTR -void _force_has_format_string(const char *format, ...) { - (void)format; -} - -/** - * Ignore: this is an internal helper. - * - * We want to force this function invocation to be syntactically correct, but - * we don't want to force runtime evaluation of its arguments. - */ -#define _FORCE_HAS_FORMAT_STRING(...) \ - if (0) { \ - _force_has_format_string(__VA_ARGS__); \ - } - -/** - * Return the specified error if the condition evaluates to true. - * - * In debug modes, prints additional information. - * In order to do that (particularly, printing the conditional that failed), - * this can't just wrap RETURN_ERROR(). - */ -#define RETURN_ERROR_IF(cond, err, ...) \ - if (cond) { \ - RAWLOG(3, "%s:%d: ERROR!: check %s failed, returning %s", \ - __FILE__, __LINE__, ZSTD_QUOTE(cond), ZSTD_QUOTE(ERROR(err))); \ - _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ - RAWLOG(3, ": " __VA_ARGS__); \ - RAWLOG(3, "\n"); \ - return ERROR(err); \ - } - -/** - * Unconditionally return the specified error. - * - * In debug modes, prints additional information. - */ -#define RETURN_ERROR(err, ...) \ - do { \ - RAWLOG(3, "%s:%d: ERROR!: unconditional check failed, returning %s", \ - __FILE__, __LINE__, ZSTD_QUOTE(ERROR(err))); \ - _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ - RAWLOG(3, ": " __VA_ARGS__); \ - RAWLOG(3, "\n"); \ - return ERROR(err); \ - } while(0); - -/** - * If the provided expression evaluates to an error code, returns that error code. - * - * In debug modes, prints additional information. - */ -#define FORWARD_IF_ERROR(err, ...) \ - do { \ - size_t const err_code = (err); \ - if (ERR_isError(err_code)) { \ - RAWLOG(3, "%s:%d: ERROR!: forwarding error in %s: %s", \ - __FILE__, __LINE__, ZSTD_QUOTE(err), ERR_getErrorName(err_code)); \ - _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \ - RAWLOG(3, ": " __VA_ARGS__); \ - RAWLOG(3, "\n"); \ - return err_code; \ - } \ - } while(0); +#define BOUNDED(min,val,max) (MAX(min,MIN(val,max))) /*-************************************* @@ -143,7 +66,6 @@ void _force_has_format_string(const char *format, ...) { #define ZSTD_OPT_NUM (1<<12) #define ZSTD_REP_NUM 3 /* number of repcodes */ -#define ZSTD_REP_MOVE (ZSTD_REP_NUM-1) static UNUSED_ATTR const U32 repStartValue[ZSTD_REP_NUM] = { 1, 4, 8 }; #define KB *(1 <<10) @@ -170,9 +92,9 @@ typedef enum { bt_raw, bt_rle, bt_compressed, bt_reserved } blockType_e; #define ZSTD_FRAMECHECKSUMSIZE 4 #define MIN_SEQUENCES_SIZE 1 /* nbSeq==0 */ -#define MIN_CBLOCK_SIZE (1 /*litCSize*/ + 1 /* RLE or RAW */ + MIN_SEQUENCES_SIZE /* nbSeq==0 */) /* for a non-null block */ +#define MIN_CBLOCK_SIZE (1 /*litCSize*/ + 1 /* RLE or RAW */) /* for a non-null block */ +#define MIN_LITERALS_FOR_4_STREAMS 6 -#define HufLog 12 typedef enum { set_basic, set_rle, set_compressed, set_repeat } symbolEncodingType_e; #define LONGNBSEQ 0x7F00 @@ -180,6 +102,7 @@ typedef enum { set_basic, set_rle, set_compressed, set_repeat } symbolEncodingTy #define MINMATCH 3 #define Litbits 8 +#define LitHufLog 11 #define MaxLit ((1<= 8 || (ovtype == ZSTD_no_overlap && diff <= -WILDCOPY_VECLEN)); - if (ovtype == ZSTD_overlap_src_before_dst && diff < WILDCOPY_VECLEN) { /* Handle short offset copies. */ do { @@ -303,12 +237,6 @@ void ZSTD_wildcopy(void* dst, const void* src, ptrdiff_t length, ZSTD_overlap_e * one COPY16() in the first call. Then, do two calls per loop since * at that point it is more likely to have a high trip count. */ -#ifdef __aarch64__ - do { - COPY16(op, ip); - } - while (op < oend); -#else ZSTD_copy16(op, ip); if (16 >= length) return; op += 16; @@ -318,7 +246,6 @@ void ZSTD_wildcopy(void* dst, const void* src, ptrdiff_t length, ZSTD_overlap_e COPY16(op, ip); } while (op < oend); -#endif } } @@ -352,9 +279,9 @@ typedef enum { * Private declarations *********************************************/ typedef struct seqDef_s { - U32 offset; /* offset == rawOffset + ZSTD_REP_NUM, or equivalently, offCode + 1 */ + U32 offBase; /* offBase == Offset + ZSTD_REP_NUM, or repcode 1,2,3 */ U16 litLength; - U16 matchLength; + U16 mlBase; /* mlBase == matchLength - MINMATCH */ } seqDef; /* Controls whether seqStore has a single "long" litLength or matchLength. See seqStore_t. */ @@ -367,11 +294,11 @@ typedef enum { typedef struct { seqDef* sequencesStart; seqDef* sequences; /* ptr to end of sequences */ - BYTE* litStart; - BYTE* lit; /* ptr to end of literals */ - BYTE* llCode; - BYTE* mlCode; - BYTE* ofCode; + BYTE* litStart; + BYTE* lit; /* ptr to end of literals */ + BYTE* llCode; + BYTE* mlCode; + BYTE* ofCode; size_t maxNbSeq; size_t maxNbLit; @@ -379,8 +306,8 @@ typedef struct { * in the seqStore that has a value larger than U16 (if it exists). To do so, we increment * the existing value of the litLength or matchLength by 0x10000. */ - ZSTD_longLengthType_e longLengthType; - U32 longLengthPos; /* Index of the sequence to apply long length modification to */ + ZSTD_longLengthType_e longLengthType; + U32 longLengthPos; /* Index of the sequence to apply long length modification to */ } seqStore_t; typedef struct { @@ -396,13 +323,13 @@ MEM_STATIC ZSTD_sequenceLength ZSTD_getSequenceLength(seqStore_t const* seqStore { ZSTD_sequenceLength seqLen; seqLen.litLength = seq->litLength; - seqLen.matchLength = seq->matchLength + MINMATCH; + seqLen.matchLength = seq->mlBase + MINMATCH; if (seqStore->longLengthPos == (U32)(seq - seqStore->sequencesStart)) { if (seqStore->longLengthType == ZSTD_llt_literalLength) { - seqLen.litLength += 0xFFFF; + seqLen.litLength += 0x10000; } if (seqStore->longLengthType == ZSTD_llt_matchLength) { - seqLen.matchLength += 0xFFFF; + seqLen.matchLength += 0x10000; } } return seqLen; @@ -415,46 +342,13 @@ MEM_STATIC ZSTD_sequenceLength ZSTD_getSequenceLength(seqStore_t const* seqStore * `decompressedBound != ZSTD_CONTENTSIZE_ERROR` */ typedef struct { + size_t nbBlocks; size_t compressedSize; unsigned long long decompressedBound; } ZSTD_frameSizeInfo; /* decompress & legacy */ const seqStore_t* ZSTD_getSeqStore(const ZSTD_CCtx* ctx); /* compress & dictBuilder */ -void ZSTD_seqToCodes(const seqStore_t* seqStorePtr); /* compress, dictBuilder, decodeCorpus (shouldn't get its definition from here) */ - -/* custom memory allocation functions */ -void* ZSTD_customMalloc(size_t size, ZSTD_customMem customMem); -void* ZSTD_customCalloc(size_t size, ZSTD_customMem customMem); -void ZSTD_customFree(void* ptr, ZSTD_customMem customMem); - - -MEM_STATIC U32 ZSTD_highbit32(U32 val) /* compress, dictBuilder, decodeCorpus */ -{ - assert(val != 0); - { -# if defined(_MSC_VER) /* Visual */ -# if STATIC_BMI2 == 1 - return _lzcnt_u32(val)^31; -# else - unsigned long r=0; - return _BitScanReverse(&r, val) ? (unsigned)r : 0; -# endif -# elif defined(__GNUC__) && (__GNUC__ >= 3) /* GCC Intrinsic */ - return __builtin_clz (val) ^ 31; -# elif defined(__ICCARM__) /* IAR Intrinsic */ - return 31 - __CLZ(val); -# else /* Software version */ - static const U32 DeBruijnClz[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; - U32 v = val; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return DeBruijnClz[(v * 0x07C4ACDDU) >> 27]; -# endif - } -} +int ZSTD_seqToCodes(const seqStore_t* seqStorePtr); /* compress, dictBuilder, decodeCorpus (shouldn't get its definition from here) */ /* ZSTD_invalidateRepCodes() : @@ -482,6 +376,14 @@ size_t ZSTD_getcBlockSize(const void* src, size_t srcSize, size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, const void* src, size_t srcSize); +/** + * @returns true iff the CPU supports dynamic BMI2 dispatch. + */ +MEM_STATIC int ZSTD_cpuSupportsBmi2(void) +{ + ZSTD_cpuid_t cpuid = ZSTD_cpuid(); + return ZSTD_cpuid_bmi1(cpuid) && ZSTD_cpuid_bmi2(cpuid); +} #if defined (__cplusplus) } diff --git a/Utilities/cmzstd/lib/common/zstd_trace.h b/Utilities/cmzstd/lib/common/zstd_trace.h index 485cadf7c4c..da20534ebd8 100644 --- a/Utilities/cmzstd/lib/common/zstd_trace.h +++ b/Utilities/cmzstd/lib/common/zstd_trace.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -17,8 +17,17 @@ extern "C" { #include -/* weak symbol support */ -#if !defined(ZSTD_HAVE_WEAK_SYMBOLS) && defined(__GNUC__) && \ +/* weak symbol support + * For now, enable conservatively: + * - Only GNUC + * - Only ELF + * - Only x86-64, i386 and aarch64 + * Also, explicitly disable on platforms known not to work so they aren't + * forgotten in the future. + */ +#if !defined(ZSTD_HAVE_WEAK_SYMBOLS) && \ + defined(__GNUC__) && defined(__ELF__) && \ + (defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) || defined(__aarch64__)) && \ !defined(__APPLE__) && !defined(_WIN32) && !defined(__MINGW32__) && \ !defined(__CYGWIN__) && !defined(_AIX) # define ZSTD_HAVE_WEAK_SYMBOLS 1 diff --git a/Utilities/cmzstd/lib/compress/clevels.h b/Utilities/cmzstd/lib/compress/clevels.h new file mode 100644 index 00000000000..c18da465f32 --- /dev/null +++ b/Utilities/cmzstd/lib/compress/clevels.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef ZSTD_CLEVELS_H +#define ZSTD_CLEVELS_H + +#define ZSTD_STATIC_LINKING_ONLY /* ZSTD_compressionParameters */ +#include "../zstd.h" + +/*-===== Pre-defined compression levels =====-*/ + +#define ZSTD_MAX_CLEVEL 22 + +#ifdef __GNUC__ +__attribute__((__unused__)) +#endif + +static const ZSTD_compressionParameters ZSTD_defaultCParameters[4][ZSTD_MAX_CLEVEL+1] = { +{ /* "default" - for any srcSize > 256 KB */ + /* W, C, H, S, L, TL, strat */ + { 19, 12, 13, 1, 6, 1, ZSTD_fast }, /* base for negative levels */ + { 19, 13, 14, 1, 7, 0, ZSTD_fast }, /* level 1 */ + { 20, 15, 16, 1, 6, 0, ZSTD_fast }, /* level 2 */ + { 21, 16, 17, 1, 5, 0, ZSTD_dfast }, /* level 3 */ + { 21, 18, 18, 1, 5, 0, ZSTD_dfast }, /* level 4 */ + { 21, 18, 19, 3, 5, 2, ZSTD_greedy }, /* level 5 */ + { 21, 18, 19, 3, 5, 4, ZSTD_lazy }, /* level 6 */ + { 21, 19, 20, 4, 5, 8, ZSTD_lazy }, /* level 7 */ + { 21, 19, 20, 4, 5, 16, ZSTD_lazy2 }, /* level 8 */ + { 22, 20, 21, 4, 5, 16, ZSTD_lazy2 }, /* level 9 */ + { 22, 21, 22, 5, 5, 16, ZSTD_lazy2 }, /* level 10 */ + { 22, 21, 22, 6, 5, 16, ZSTD_lazy2 }, /* level 11 */ + { 22, 22, 23, 6, 5, 32, ZSTD_lazy2 }, /* level 12 */ + { 22, 22, 22, 4, 5, 32, ZSTD_btlazy2 }, /* level 13 */ + { 22, 22, 23, 5, 5, 32, ZSTD_btlazy2 }, /* level 14 */ + { 22, 23, 23, 6, 5, 32, ZSTD_btlazy2 }, /* level 15 */ + { 22, 22, 22, 5, 5, 48, ZSTD_btopt }, /* level 16 */ + { 23, 23, 22, 5, 4, 64, ZSTD_btopt }, /* level 17 */ + { 23, 23, 22, 6, 3, 64, ZSTD_btultra }, /* level 18 */ + { 23, 24, 22, 7, 3,256, ZSTD_btultra2}, /* level 19 */ + { 25, 25, 23, 7, 3,256, ZSTD_btultra2}, /* level 20 */ + { 26, 26, 24, 7, 3,512, ZSTD_btultra2}, /* level 21 */ + { 27, 27, 25, 9, 3,999, ZSTD_btultra2}, /* level 22 */ +}, +{ /* for srcSize <= 256 KB */ + /* W, C, H, S, L, T, strat */ + { 18, 12, 13, 1, 5, 1, ZSTD_fast }, /* base for negative levels */ + { 18, 13, 14, 1, 6, 0, ZSTD_fast }, /* level 1 */ + { 18, 14, 14, 1, 5, 0, ZSTD_dfast }, /* level 2 */ + { 18, 16, 16, 1, 4, 0, ZSTD_dfast }, /* level 3 */ + { 18, 16, 17, 3, 5, 2, ZSTD_greedy }, /* level 4.*/ + { 18, 17, 18, 5, 5, 2, ZSTD_greedy }, /* level 5.*/ + { 18, 18, 19, 3, 5, 4, ZSTD_lazy }, /* level 6.*/ + { 18, 18, 19, 4, 4, 4, ZSTD_lazy }, /* level 7 */ + { 18, 18, 19, 4, 4, 8, ZSTD_lazy2 }, /* level 8 */ + { 18, 18, 19, 5, 4, 8, ZSTD_lazy2 }, /* level 9 */ + { 18, 18, 19, 6, 4, 8, ZSTD_lazy2 }, /* level 10 */ + { 18, 18, 19, 5, 4, 12, ZSTD_btlazy2 }, /* level 11.*/ + { 18, 19, 19, 7, 4, 12, ZSTD_btlazy2 }, /* level 12.*/ + { 18, 18, 19, 4, 4, 16, ZSTD_btopt }, /* level 13 */ + { 18, 18, 19, 4, 3, 32, ZSTD_btopt }, /* level 14.*/ + { 18, 18, 19, 6, 3,128, ZSTD_btopt }, /* level 15.*/ + { 18, 19, 19, 6, 3,128, ZSTD_btultra }, /* level 16.*/ + { 18, 19, 19, 8, 3,256, ZSTD_btultra }, /* level 17.*/ + { 18, 19, 19, 6, 3,128, ZSTD_btultra2}, /* level 18.*/ + { 18, 19, 19, 8, 3,256, ZSTD_btultra2}, /* level 19.*/ + { 18, 19, 19, 10, 3,512, ZSTD_btultra2}, /* level 20.*/ + { 18, 19, 19, 12, 3,512, ZSTD_btultra2}, /* level 21.*/ + { 18, 19, 19, 13, 3,999, ZSTD_btultra2}, /* level 22.*/ +}, +{ /* for srcSize <= 128 KB */ + /* W, C, H, S, L, T, strat */ + { 17, 12, 12, 1, 5, 1, ZSTD_fast }, /* base for negative levels */ + { 17, 12, 13, 1, 6, 0, ZSTD_fast }, /* level 1 */ + { 17, 13, 15, 1, 5, 0, ZSTD_fast }, /* level 2 */ + { 17, 15, 16, 2, 5, 0, ZSTD_dfast }, /* level 3 */ + { 17, 17, 17, 2, 4, 0, ZSTD_dfast }, /* level 4 */ + { 17, 16, 17, 3, 4, 2, ZSTD_greedy }, /* level 5 */ + { 17, 16, 17, 3, 4, 4, ZSTD_lazy }, /* level 6 */ + { 17, 16, 17, 3, 4, 8, ZSTD_lazy2 }, /* level 7 */ + { 17, 16, 17, 4, 4, 8, ZSTD_lazy2 }, /* level 8 */ + { 17, 16, 17, 5, 4, 8, ZSTD_lazy2 }, /* level 9 */ + { 17, 16, 17, 6, 4, 8, ZSTD_lazy2 }, /* level 10 */ + { 17, 17, 17, 5, 4, 8, ZSTD_btlazy2 }, /* level 11 */ + { 17, 18, 17, 7, 4, 12, ZSTD_btlazy2 }, /* level 12 */ + { 17, 18, 17, 3, 4, 12, ZSTD_btopt }, /* level 13.*/ + { 17, 18, 17, 4, 3, 32, ZSTD_btopt }, /* level 14.*/ + { 17, 18, 17, 6, 3,256, ZSTD_btopt }, /* level 15.*/ + { 17, 18, 17, 6, 3,128, ZSTD_btultra }, /* level 16.*/ + { 17, 18, 17, 8, 3,256, ZSTD_btultra }, /* level 17.*/ + { 17, 18, 17, 10, 3,512, ZSTD_btultra }, /* level 18.*/ + { 17, 18, 17, 5, 3,256, ZSTD_btultra2}, /* level 19.*/ + { 17, 18, 17, 7, 3,512, ZSTD_btultra2}, /* level 20.*/ + { 17, 18, 17, 9, 3,512, ZSTD_btultra2}, /* level 21.*/ + { 17, 18, 17, 11, 3,999, ZSTD_btultra2}, /* level 22.*/ +}, +{ /* for srcSize <= 16 KB */ + /* W, C, H, S, L, T, strat */ + { 14, 12, 13, 1, 5, 1, ZSTD_fast }, /* base for negative levels */ + { 14, 14, 15, 1, 5, 0, ZSTD_fast }, /* level 1 */ + { 14, 14, 15, 1, 4, 0, ZSTD_fast }, /* level 2 */ + { 14, 14, 15, 2, 4, 0, ZSTD_dfast }, /* level 3 */ + { 14, 14, 14, 4, 4, 2, ZSTD_greedy }, /* level 4 */ + { 14, 14, 14, 3, 4, 4, ZSTD_lazy }, /* level 5.*/ + { 14, 14, 14, 4, 4, 8, ZSTD_lazy2 }, /* level 6 */ + { 14, 14, 14, 6, 4, 8, ZSTD_lazy2 }, /* level 7 */ + { 14, 14, 14, 8, 4, 8, ZSTD_lazy2 }, /* level 8.*/ + { 14, 15, 14, 5, 4, 8, ZSTD_btlazy2 }, /* level 9.*/ + { 14, 15, 14, 9, 4, 8, ZSTD_btlazy2 }, /* level 10.*/ + { 14, 15, 14, 3, 4, 12, ZSTD_btopt }, /* level 11.*/ + { 14, 15, 14, 4, 3, 24, ZSTD_btopt }, /* level 12.*/ + { 14, 15, 14, 5, 3, 32, ZSTD_btultra }, /* level 13.*/ + { 14, 15, 15, 6, 3, 64, ZSTD_btultra }, /* level 14.*/ + { 14, 15, 15, 7, 3,256, ZSTD_btultra }, /* level 15.*/ + { 14, 15, 15, 5, 3, 48, ZSTD_btultra2}, /* level 16.*/ + { 14, 15, 15, 6, 3,128, ZSTD_btultra2}, /* level 17.*/ + { 14, 15, 15, 7, 3,256, ZSTD_btultra2}, /* level 18.*/ + { 14, 15, 15, 8, 3,256, ZSTD_btultra2}, /* level 19.*/ + { 14, 15, 15, 8, 3,512, ZSTD_btultra2}, /* level 20.*/ + { 14, 15, 15, 9, 3,512, ZSTD_btultra2}, /* level 21.*/ + { 14, 15, 15, 10, 3,999, ZSTD_btultra2}, /* level 22.*/ +}, +}; + + + +#endif /* ZSTD_CLEVELS_H */ diff --git a/Utilities/cmzstd/lib/compress/fse_compress.c b/Utilities/cmzstd/lib/compress/fse_compress.c index 1b6a076901c..5d3770808dd 100644 --- a/Utilities/cmzstd/lib/compress/fse_compress.c +++ b/Utilities/cmzstd/lib/compress/fse_compress.c @@ -1,6 +1,6 @@ /* ****************************************************************** * FSE : Finite State Entropy encoder - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -26,6 +26,7 @@ #define ZSTD_DEPS_NEED_MALLOC #define ZSTD_DEPS_NEED_MATH64 #include "../common/zstd_deps.h" /* ZSTD_malloc, ZSTD_free, ZSTD_memcpy, ZSTD_memset */ +#include "../common/bits.h" /* ZSTD_highbit32 */ /* ************************************************************** @@ -75,13 +76,14 @@ size_t FSE_buildCTable_wksp(FSE_CTable* ct, void* const FSCT = ((U32*)ptr) + 1 /* header */ + (tableLog ? tableSize>>1 : 1) ; FSE_symbolCompressionTransform* const symbolTT = (FSE_symbolCompressionTransform*) (FSCT); U32 const step = FSE_TABLESTEP(tableSize); + U32 const maxSV1 = maxSymbolValue+1; - U32* cumul = (U32*)workSpace; - FSE_FUNCTION_TYPE* tableSymbol = (FSE_FUNCTION_TYPE*)(cumul + (maxSymbolValue + 2)); + U16* cumul = (U16*)workSpace; /* size = maxSV1 */ + FSE_FUNCTION_TYPE* const tableSymbol = (FSE_FUNCTION_TYPE*)(cumul + (maxSV1+1)); /* size = tableSize */ U32 highThreshold = tableSize-1; - if ((size_t)workSpace & 3) return ERROR(GENERIC); /* Must be 4 byte aligned */ + assert(((size_t)workSpace & 1) == 0); /* Must be 2 bytes-aligned */ if (FSE_BUILD_CTABLE_WORKSPACE_SIZE(maxSymbolValue, tableLog) > wkspSize) return ERROR(tableLog_tooLarge); /* CTable header */ tableU16[-2] = (U16) tableLog; @@ -89,7 +91,7 @@ size_t FSE_buildCTable_wksp(FSE_CTable* ct, assert(tableLog < 16); /* required for threshold strategy to work */ /* For explanations on how to distribute symbol values over the table : - * http://fastcompression.blogspot.fr/2014/02/fse-distributing-symbol-values.html */ + * https://fastcompression.blogspot.fr/2014/02/fse-distributing-symbol-values.html */ #ifdef __clang_analyzer__ ZSTD_memset(tableSymbol, 0, sizeof(*tableSymbol) * tableSize); /* useless initialization, just to keep scan-build happy */ @@ -98,20 +100,61 @@ size_t FSE_buildCTable_wksp(FSE_CTable* ct, /* symbol start positions */ { U32 u; cumul[0] = 0; - for (u=1; u <= maxSymbolValue+1; u++) { + for (u=1; u <= maxSV1; u++) { if (normalizedCounter[u-1]==-1) { /* Low proba symbol */ cumul[u] = cumul[u-1] + 1; tableSymbol[highThreshold--] = (FSE_FUNCTION_TYPE)(u-1); } else { - cumul[u] = cumul[u-1] + normalizedCounter[u-1]; + assert(normalizedCounter[u-1] >= 0); + cumul[u] = cumul[u-1] + (U16)normalizedCounter[u-1]; + assert(cumul[u] >= cumul[u-1]); /* no overflow */ } } - cumul[maxSymbolValue+1] = tableSize+1; + cumul[maxSV1] = (U16)(tableSize+1); } /* Spread symbols */ - { U32 position = 0; + if (highThreshold == tableSize - 1) { + /* Case for no low prob count symbols. Lay down 8 bytes at a time + * to reduce branch misses since we are operating on a small block + */ + BYTE* const spread = tableSymbol + tableSize; /* size = tableSize + 8 (may write beyond tableSize) */ + { U64 const add = 0x0101010101010101ull; + size_t pos = 0; + U64 sv = 0; + U32 s; + for (s=0; s=0); + pos += (size_t)n; + } + } + /* Spread symbols across the table. Lack of lowprob symbols means that + * we don't need variable sized inner loop, so we can unroll the loop and + * reduce branch misses. + */ + { size_t position = 0; + size_t s; + size_t const unroll = 2; /* Experimentally determined optimal unroll */ + assert(tableSize % unroll == 0); /* FSE_MIN_TABLELOG is 5 */ + for (s = 0; s < (size_t)tableSize; s += unroll) { + size_t u; + for (u = 0; u < unroll; ++u) { + size_t const uPosition = (position + (u * step)) & tableMask; + tableSymbol[uPosition] = spread[s + u]; + } + position = (position + (unroll * step)) & tableMask; + } + assert(position == 0); /* Must have initialized all positions */ + } + } else { + U32 position = 0; U32 symbol; - for (symbol=0; symbol<=maxSymbolValue; symbol++) { + for (symbol=0; symbol highThreshold) position = (position + step) & tableMask; /* Low proba area */ } } - assert(position==0); /* Must have initialized all positions */ } @@ -144,16 +186,17 @@ size_t FSE_buildCTable_wksp(FSE_CTable* ct, case -1: case 1: symbolTT[s].deltaNbBits = (tableLog << 16) - (1< 1); + { U32 const maxBitsOut = tableLog - ZSTD_highbit32 ((U32)normalizedCounter[s]-1); + U32 const minStatePlus = (U32)normalizedCounter[s] << maxBitsOut; symbolTT[s].deltaNbBits = (maxBitsOut << 16) - minStatePlus; - symbolTT[s].deltaFindState = total - normalizedCounter[s]; - total += normalizedCounter[s]; + symbolTT[s].deltaFindState = (int)(total - (unsigned)normalizedCounter[s]); + total += (unsigned)normalizedCounter[s]; } } } } #if 0 /* debug : symbol costs */ @@ -164,32 +207,26 @@ size_t FSE_buildCTable_wksp(FSE_CTable* ct, symbol, normalizedCounter[symbol], FSE_getMaxNbBits(symbolTT, symbol), (double)FSE_bitCost(symbolTT, tableLog, symbol, 8) / 256); - } - } + } } #endif return 0; } -#ifndef ZSTD_NO_UNUSED_FUNCTIONS -size_t FSE_buildCTable(FSE_CTable* ct, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog) -{ - FSE_FUNCTION_TYPE tableSymbol[FSE_MAX_TABLESIZE]; /* memset() is not necessary, even if static analyzer complain about it */ - return FSE_buildCTable_wksp(ct, normalizedCounter, maxSymbolValue, tableLog, tableSymbol, sizeof(tableSymbol)); -} -#endif - #ifndef FSE_COMMONDEFS_ONLY - /*-************************************************************** * FSE NCount encoding ****************************************************************/ size_t FSE_NCountWriteBound(unsigned maxSymbolValue, unsigned tableLog) { - size_t const maxHeaderSize = (((maxSymbolValue+1) * tableLog) >> 3) + 3; + size_t const maxHeaderSize = (((maxSymbolValue+1) * tableLog + + 4 /* bitCount initialized at 4 */ + + 2 /* first two symbols may use one additional bit each */) / 8) + + 1 /* round up to whole nb bytes */ + + 2 /* additional two bytes for bitstream flush */; return maxSymbolValue ? maxHeaderSize : FSE_NCOUNTBOUND; /* maxSymbolValue==0 ? use default */ } @@ -306,21 +343,11 @@ size_t FSE_writeNCount (void* buffer, size_t bufferSize, * FSE Compression Code ****************************************************************/ -FSE_CTable* FSE_createCTable (unsigned maxSymbolValue, unsigned tableLog) -{ - size_t size; - if (tableLog > FSE_TABLELOG_ABSOLUTE_MAX) tableLog = FSE_TABLELOG_ABSOLUTE_MAX; - size = FSE_CTABLE_SIZE_U32 (tableLog, maxSymbolValue) * sizeof(U32); - return (FSE_CTable*)ZSTD_malloc(size); -} - -void FSE_freeCTable (FSE_CTable* ct) { ZSTD_free(ct); } - /* provides the minimum logSize to safely represent a distribution */ static unsigned FSE_minTableLog(size_t srcSize, unsigned maxSymbolValue) { - U32 minBitsSrc = BIT_highbit32((U32)(srcSize)) + 1; - U32 minBitsSymbols = BIT_highbit32(maxSymbolValue) + 2; + U32 minBitsSrc = ZSTD_highbit32((U32)(srcSize)) + 1; + U32 minBitsSymbols = ZSTD_highbit32(maxSymbolValue) + 2; U32 minBits = minBitsSrc < minBitsSymbols ? minBitsSrc : minBitsSymbols; assert(srcSize > 1); /* Not supported, RLE should be used instead */ return minBits; @@ -328,7 +355,7 @@ static unsigned FSE_minTableLog(size_t srcSize, unsigned maxSymbolValue) unsigned FSE_optimalTableLog_internal(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, unsigned minus) { - U32 maxBitsSrc = BIT_highbit32((U32)(srcSize - 1)) - minus; + U32 maxBitsSrc = ZSTD_highbit32((U32)(srcSize - 1)) - minus; U32 tableLog = maxTableLog; U32 minBits = FSE_minTableLog(srcSize, maxSymbolValue); assert(srcSize > 1); /* Not supported, RLE should be used instead */ @@ -496,40 +523,6 @@ size_t FSE_normalizeCount (short* normalizedCounter, unsigned tableLog, return tableLog; } - -/* fake FSE_CTable, for raw (uncompressed) input */ -size_t FSE_buildCTable_raw (FSE_CTable* ct, unsigned nbBits) -{ - const unsigned tableSize = 1 << nbBits; - const unsigned tableMask = tableSize - 1; - const unsigned maxSymbolValue = tableMask; - void* const ptr = ct; - U16* const tableU16 = ( (U16*) ptr) + 2; - void* const FSCT = ((U32*)ptr) + 1 /* header */ + (tableSize>>1); /* assumption : tableLog >= 1 */ - FSE_symbolCompressionTransform* const symbolTT = (FSE_symbolCompressionTransform*) (FSCT); - unsigned s; - - /* Sanity checks */ - if (nbBits < 1) return ERROR(GENERIC); /* min size */ - - /* header */ - tableU16[-2] = (U16) nbBits; - tableU16[-1] = (U16) maxSymbolValue; - - /* Build table */ - for (s=0; s not compressible */ - if (maxCount < (srcSize >> 7)) return 0; /* Heuristic : not compressible enough */ - } - - tableLog = FSE_optimalTableLog(tableLog, srcSize, maxSymbolValue); - CHECK_F( FSE_normalizeCount(norm, tableLog, count, srcSize, maxSymbolValue, /* useLowProbCount */ srcSize >= 2048) ); - - /* Write table description header */ - { CHECK_V_F(nc_err, FSE_writeNCount(op, oend-op, norm, maxSymbolValue, tableLog) ); - op += nc_err; - } - - /* Compress */ - CHECK_F( FSE_buildCTable_wksp(CTable, norm, maxSymbolValue, tableLog, scratchBuffer, scratchBufferSize) ); - { CHECK_V_F(cSize, FSE_compress_usingCTable(op, oend - op, src, srcSize, CTable) ); - if (cSize == 0) return 0; /* not enough space for compressed data */ - op += cSize; - } - - /* check compressibility */ - if ( (size_t)(op-ostart) >= srcSize-1 ) return 0; - - return op-ostart; -} - -typedef struct { - FSE_CTable CTable_max[FSE_CTABLE_SIZE_U32(FSE_MAX_TABLELOG, FSE_MAX_SYMBOL_VALUE)]; - union { - U32 hist_wksp[HIST_WKSP_SIZE_U32]; - BYTE scratchBuffer[1 << FSE_MAX_TABLELOG]; - } workspace; -} fseWkspMax_t; - -size_t FSE_compress2 (void* dst, size_t dstCapacity, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog) -{ - fseWkspMax_t scratchBuffer; - DEBUG_STATIC_ASSERT(sizeof(scratchBuffer) >= FSE_COMPRESS_WKSP_SIZE_U32(FSE_MAX_TABLELOG, FSE_MAX_SYMBOL_VALUE)); /* compilation failures here means scratchBuffer is not large enough */ - if (tableLog > FSE_MAX_TABLELOG) return ERROR(tableLog_tooLarge); - return FSE_compress_wksp(dst, dstCapacity, src, srcSize, maxSymbolValue, tableLog, &scratchBuffer, sizeof(scratchBuffer)); -} - -size_t FSE_compress (void* dst, size_t dstCapacity, const void* src, size_t srcSize) -{ - return FSE_compress2(dst, dstCapacity, src, srcSize, FSE_MAX_SYMBOL_VALUE, FSE_DEFAULT_TABLELOG); -} -#endif - #endif /* FSE_COMMONDEFS_ONLY */ diff --git a/Utilities/cmzstd/lib/compress/hist.c b/Utilities/cmzstd/lib/compress/hist.c index 073c57e7527..e2fb431f03a 100644 --- a/Utilities/cmzstd/lib/compress/hist.c +++ b/Utilities/cmzstd/lib/compress/hist.c @@ -1,7 +1,7 @@ /* ****************************************************************** * hist : Histogram functions * part of Finite State Entropy project - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/Utilities/cmzstd/lib/compress/hist.h b/Utilities/cmzstd/lib/compress/hist.h index 228ed48a71d..887896b813b 100644 --- a/Utilities/cmzstd/lib/compress/hist.h +++ b/Utilities/cmzstd/lib/compress/hist.h @@ -1,7 +1,7 @@ /* ****************************************************************** * hist : Histogram functions * part of Finite State Entropy project - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/Utilities/cmzstd/lib/compress/huf_compress.c b/Utilities/cmzstd/lib/compress/huf_compress.c index 485906e678a..29871877a7f 100644 --- a/Utilities/cmzstd/lib/compress/huf_compress.c +++ b/Utilities/cmzstd/lib/compress/huf_compress.c @@ -1,6 +1,6 @@ /* ****************************************************************** * Huffman encoder, part of New Generation Entropy library - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - FSE+HUF source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -29,9 +29,9 @@ #include "hist.h" #define FSE_STATIC_LINKING_ONLY /* FSE_optimalTableLog_internal */ #include "../common/fse.h" /* header compression */ -#define HUF_STATIC_LINKING_ONLY #include "../common/huf.h" #include "../common/error_private.h" +#include "../common/bits.h" /* ZSTD_highbit32 */ /* ************************************************************** @@ -42,17 +42,93 @@ /* ************************************************************** -* Utils +* Required declarations ****************************************************************/ -unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue) +typedef struct nodeElt_s { + U32 count; + U16 parent; + BYTE byte; + BYTE nbBits; +} nodeElt; + + +/* ************************************************************** +* Debug Traces +****************************************************************/ + +#if DEBUGLEVEL >= 2 + +static size_t showU32(const U32* arr, size_t size) { - return FSE_optimalTableLog_internal(maxTableLog, srcSize, maxSymbolValue, 1); + size_t u; + for (u=0; u= add) { + assert(add < align); + assert(((size_t)aligned & mask) == 0); + *workspaceSizePtr -= add; + return aligned; + } else { + *workspaceSizePtr = 0; + return NULL; + } +} + + /* HUF_compressWeights() : * Same as FSE_compress(), but dedicated to huff0's weights compression. * The use case needs much less stack memory. @@ -67,7 +143,10 @@ typedef struct { S16 norm[HUF_TABLELOG_MAX+1]; } HUF_CompressWeightsWksp; -static size_t HUF_compressWeights(void* dst, size_t dstSize, const void* weightTable, size_t wtSize, void* workspace, size_t workspaceSize) +static size_t +HUF_compressWeights(void* dst, size_t dstSize, + const void* weightTable, size_t wtSize, + void* workspace, size_t workspaceSize) { BYTE* const ostart = (BYTE*) dst; BYTE* op = ostart; @@ -75,7 +154,7 @@ static size_t HUF_compressWeights(void* dst, size_t dstSize, const void* weightT unsigned maxSymbolValue = HUF_TABLELOG_MAX; U32 tableLog = MAX_FSE_TABLELOG_FOR_HUFF_HEADER; - HUF_CompressWeightsWksp* wksp = (HUF_CompressWeightsWksp*)workspace; + HUF_CompressWeightsWksp* wksp = (HUF_CompressWeightsWksp*)HUF_alignUpWorkspace(workspace, &workspaceSize, ZSTD_ALIGNOF(U32)); if (workspaceSize < sizeof(HUF_CompressWeightsWksp)) return ERROR(GENERIC); @@ -106,6 +185,40 @@ static size_t HUF_compressWeights(void* dst, size_t dstSize, const void* weightT return (size_t)(op-ostart); } +static size_t HUF_getNbBits(HUF_CElt elt) +{ + return elt & 0xFF; +} + +static size_t HUF_getNbBitsFast(HUF_CElt elt) +{ + return elt; +} + +static size_t HUF_getValue(HUF_CElt elt) +{ + return elt & ~(size_t)0xFF; +} + +static size_t HUF_getValueFast(HUF_CElt elt) +{ + return elt; +} + +static void HUF_setNbBits(HUF_CElt* elt, size_t nbBits) +{ + assert(nbBits <= HUF_TABLELOG_ABSOLUTEMAX); + *elt = nbBits; +} + +static void HUF_setValue(HUF_CElt* elt, size_t value) +{ + size_t const nbBits = HUF_getNbBits(*elt); + if (nbBits > 0) { + assert((value >> nbBits) == 0); + *elt |= value << (sizeof(HUF_CElt) * 8 - nbBits); + } +} typedef struct { HUF_CompressWeightsWksp wksp; @@ -117,9 +230,12 @@ size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog, void* workspace, size_t workspaceSize) { + HUF_CElt const* const ct = CTable + 1; BYTE* op = (BYTE*)dst; U32 n; - HUF_WriteCTableWksp* wksp = (HUF_WriteCTableWksp*)workspace; + HUF_WriteCTableWksp* wksp = (HUF_WriteCTableWksp*)HUF_alignUpWorkspace(workspace, &workspaceSize, ZSTD_ALIGNOF(U32)); + + HUF_STATIC_ASSERT(HUF_CTABLE_WORKSPACE_SIZE >= sizeof(HUF_WriteCTableWksp)); /* check conditions */ if (workspaceSize < sizeof(HUF_WriteCTableWksp)) return ERROR(GENERIC); @@ -130,9 +246,10 @@ size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, for (n=1; nbitsToWeight[n] = (BYTE)(huffLog + 1 - n); for (n=0; nhuffWeight[n] = wksp->bitsToWeight[CTable[n].nbBits]; + wksp->huffWeight[n] = wksp->bitsToWeight[HUF_getNbBits(ct[n])]; /* attempt weights compression by FSE */ + if (maxDstSize < 1) return ERROR(dstSize_tooSmall); { CHECK_V_F(hSize, HUF_compressWeights(op+1, maxDstSize-1, wksp->huffWeight, maxSymbolValue, &wksp->wksp, sizeof(wksp->wksp)) ); if ((hSize>1) & (hSize < maxSymbolValue/2)) { /* FSE compressed */ op[0] = (BYTE)hSize; @@ -149,16 +266,6 @@ size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, return ((maxSymbolValue+1)/2) + 1; } -/*! HUF_writeCTable() : - `CTable` : Huffman tree to save, using huf representation. - @return : size of saved CTable */ -size_t HUF_writeCTable (void* dst, size_t maxDstSize, - const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog) -{ - HUF_WriteCTableWksp wksp; - return HUF_writeCTable_wksp(dst, maxDstSize, CTable, maxSymbolValue, huffLog, &wksp, sizeof(wksp)); -} - size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void* src, size_t srcSize, unsigned* hasZeroWeights) { @@ -166,6 +273,7 @@ size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void U32 rankVal[HUF_TABLELOG_ABSOLUTEMAX + 1]; /* large enough for values from 0 to 16 */ U32 tableLog = 0; U32 nbSymbols = 0; + HUF_CElt* const ct = CTable + 1; /* get symbol weights */ CHECK_V_F(readSize, HUF_readStats(huffWeight, HUF_SYMBOLVALUE_MAX+1, rankVal, &nbSymbols, &tableLog, src, srcSize)); @@ -175,6 +283,8 @@ size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void if (tableLog > HUF_TABLELOG_MAX) return ERROR(tableLog_tooLarge); if (nbSymbols > *maxSymbolValuePtr+1) return ERROR(maxSymbolValue_tooSmall); + CTable[0] = tableLog; + /* Prepare base value per rank */ { U32 n, nextRankStart = 0; for (n=1; n<=tableLog; n++) { @@ -186,13 +296,13 @@ size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void /* fill nbBits */ { U32 n; for (n=0; nn=tableLog+1 */ U16 valPerRank[HUF_TABLELOG_MAX+2] = {0}; - { U32 n; for (n=0; n>= 1; } } /* assign value within rank, symbol order */ - { U32 n; for (n=0; n maxNbBits to be maxNbBits. Then it adjusts - * the tree to so that it is a valid canonical Huffman tree. + * It attempts to convert all nodes with nbBits > @targetNbBits + * to employ @targetNbBits instead. Then it adjusts the tree + * so that it remains a valid canonical Huffman tree. * * @pre The sum of the ranks of each symbol == 2^largestBits, * where largestBits == huffNode[lastNonNull].nbBits. * @post The sum of the ranks of each symbol == 2^largestBits, - * where largestBits is the return value <= maxNbBits. + * where largestBits is the return value (expected <= targetNbBits). * - * @param huffNode The Huffman tree modified in place to enforce maxNbBits. + * @param huffNode The Huffman tree modified in place to enforce targetNbBits. + * It's presumed sorted, from most frequent to rarest symbol. * @param lastNonNull The symbol with the lowest count in the Huffman tree. - * @param maxNbBits The maximum allowed number of bits, which the Huffman tree + * @param targetNbBits The allowed number of bits, which the Huffman tree * may not respect. After this function the Huffman tree will - * respect maxNbBits. - * @return The maximum number of bits of the Huffman tree after adjustment, - * necessarily no more than maxNbBits. + * respect targetNbBits. + * @return The maximum number of bits of the Huffman tree after adjustment. */ -static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 maxNbBits) +static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 targetNbBits) { const U32 largestBits = huffNode[lastNonNull].nbBits; - /* early exit : no elt > maxNbBits, so the tree is already valid. */ - if (largestBits <= maxNbBits) return largestBits; + /* early exit : no elt > targetNbBits, so the tree is already valid. */ + if (largestBits <= targetNbBits) return largestBits; + + DEBUGLOG(5, "HUF_setMaxHeight (targetNbBits = %u)", targetNbBits); /* there are several too large elements (at least >= 2) */ { int totalCost = 0; - const U32 baseCost = 1 << (largestBits - maxNbBits); + const U32 baseCost = 1 << (largestBits - targetNbBits); int n = (int)lastNonNull; - /* Adjust any ranks > maxNbBits to maxNbBits. + /* Adjust any ranks > targetNbBits to targetNbBits. * Compute totalCost, which is how far the sum of the ranks is * we are over 2^largestBits after adjust the offending ranks. */ - while (huffNode[n].nbBits > maxNbBits) { + while (huffNode[n].nbBits > targetNbBits) { totalCost += baseCost - (1 << (largestBits - huffNode[n].nbBits)); - huffNode[n].nbBits = (BYTE)maxNbBits; + huffNode[n].nbBits = (BYTE)targetNbBits; n--; } - /* n stops at huffNode[n].nbBits <= maxNbBits */ - assert(huffNode[n].nbBits <= maxNbBits); - /* n end at index of smallest symbol using < maxNbBits */ - while (huffNode[n].nbBits == maxNbBits) --n; + /* n stops at huffNode[n].nbBits <= targetNbBits */ + assert(huffNode[n].nbBits <= targetNbBits); + /* n end at index of smallest symbol using < targetNbBits */ + while (huffNode[n].nbBits == targetNbBits) --n; - /* renorm totalCost from 2^largestBits to 2^maxNbBits + /* renorm totalCost from 2^largestBits to 2^targetNbBits * note : totalCost is necessarily a multiple of baseCost */ - assert((totalCost & (baseCost - 1)) == 0); - totalCost >>= (largestBits - maxNbBits); + assert(((U32)totalCost & (baseCost - 1)) == 0); + totalCost >>= (largestBits - targetNbBits); assert(totalCost > 0); /* repay normalized cost */ @@ -281,19 +387,19 @@ static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 maxNbBits) /* Get pos of last (smallest = lowest cum. count) symbol per rank */ ZSTD_memset(rankLast, 0xF0, sizeof(rankLast)); - { U32 currentNbBits = maxNbBits; + { U32 currentNbBits = targetNbBits; int pos; for (pos=n ; pos >= 0; pos--) { if (huffNode[pos].nbBits >= currentNbBits) continue; - currentNbBits = huffNode[pos].nbBits; /* < maxNbBits */ - rankLast[maxNbBits-currentNbBits] = (U32)pos; + currentNbBits = huffNode[pos].nbBits; /* < targetNbBits */ + rankLast[targetNbBits-currentNbBits] = (U32)pos; } } while (totalCost > 0) { /* Try to reduce the next power of 2 above totalCost because we * gain back half the rank. */ - U32 nBitsToDecrease = BIT_highbit32((U32)totalCost) + 1; + U32 nBitsToDecrease = ZSTD_highbit32((U32)totalCost) + 1; for ( ; nBitsToDecrease > 1; nBitsToDecrease--) { U32 const highPos = rankLast[nBitsToDecrease]; U32 const lowPos = rankLast[nBitsToDecrease-1]; @@ -333,7 +439,7 @@ static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 maxNbBits) rankLast[nBitsToDecrease] = noSymbol; else { rankLast[nBitsToDecrease]--; - if (huffNode[rankLast[nBitsToDecrease]].nbBits != maxNbBits-nBitsToDecrease) + if (huffNode[rankLast[nBitsToDecrease]].nbBits != targetNbBits-nBitsToDecrease) rankLast[nBitsToDecrease] = noSymbol; /* this rank is now empty */ } } /* while (totalCost > 0) */ @@ -345,11 +451,11 @@ static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 maxNbBits) * TODO. */ while (totalCost < 0) { /* Sometimes, cost correction overshoot */ - /* special case : no rank 1 symbol (using maxNbBits-1); - * let's create one from largest rank 0 (using maxNbBits). + /* special case : no rank 1 symbol (using targetNbBits-1); + * let's create one from largest rank 0 (using targetNbBits). */ if (rankLast[1] == noSymbol) { - while (huffNode[n].nbBits == maxNbBits) n--; + while (huffNode[n].nbBits == targetNbBits) n--; huffNode[n+1].nbBits--; assert(n >= 0); rankLast[1] = (U32)(n+1); @@ -363,26 +469,122 @@ static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 maxNbBits) } /* repay normalized cost */ } /* there are several too large elements (at least >= 2) */ - return maxNbBits; + return targetNbBits; } typedef struct { - U32 base; - U32 curr; + U16 base; + U16 curr; } rankPos; -typedef nodeElt huffNodeTable[HUF_CTABLE_WORKSPACE_SIZE_U32]; +typedef nodeElt huffNodeTable[2 * (HUF_SYMBOLVALUE_MAX + 1)]; -#define RANK_POSITION_TABLE_SIZE 32 +/* Number of buckets available for HUF_sort() */ +#define RANK_POSITION_TABLE_SIZE 192 typedef struct { huffNodeTable huffNodeTbl; rankPos rankPosition[RANK_POSITION_TABLE_SIZE]; } HUF_buildCTable_wksp_tables; +/* RANK_POSITION_DISTINCT_COUNT_CUTOFF == Cutoff point in HUF_sort() buckets for which we use log2 bucketing. + * Strategy is to use as many buckets as possible for representing distinct + * counts while using the remainder to represent all "large" counts. + * + * To satisfy this requirement for 192 buckets, we can do the following: + * Let buckets 0-166 represent distinct counts of [0, 166] + * Let buckets 166 to 192 represent all remaining counts up to RANK_POSITION_MAX_COUNT_LOG using log2 bucketing. + */ +#define RANK_POSITION_MAX_COUNT_LOG 32 +#define RANK_POSITION_LOG_BUCKETS_BEGIN ((RANK_POSITION_TABLE_SIZE - 1) - RANK_POSITION_MAX_COUNT_LOG - 1 /* == 158 */) +#define RANK_POSITION_DISTINCT_COUNT_CUTOFF (RANK_POSITION_LOG_BUCKETS_BEGIN + ZSTD_highbit32(RANK_POSITION_LOG_BUCKETS_BEGIN) /* == 166 */) + +/* Return the appropriate bucket index for a given count. See definition of + * RANK_POSITION_DISTINCT_COUNT_CUTOFF for explanation of bucketing strategy. + */ +static U32 HUF_getIndex(U32 const count) { + return (count < RANK_POSITION_DISTINCT_COUNT_CUTOFF) + ? count + : ZSTD_highbit32(count) + RANK_POSITION_LOG_BUCKETS_BEGIN; +} + +/* Helper swap function for HUF_quickSortPartition() */ +static void HUF_swapNodes(nodeElt* a, nodeElt* b) { + nodeElt tmp = *a; + *a = *b; + *b = tmp; +} + +/* Returns 0 if the huffNode array is not sorted by descending count */ +MEM_STATIC int HUF_isSorted(nodeElt huffNode[], U32 const maxSymbolValue1) { + U32 i; + for (i = 1; i < maxSymbolValue1; ++i) { + if (huffNode[i].count > huffNode[i-1].count) { + return 0; + } + } + return 1; +} + +/* Insertion sort by descending order */ +HINT_INLINE void HUF_insertionSort(nodeElt huffNode[], int const low, int const high) { + int i; + int const size = high-low+1; + huffNode += low; + for (i = 1; i < size; ++i) { + nodeElt const key = huffNode[i]; + int j = i - 1; + while (j >= 0 && huffNode[j].count < key.count) { + huffNode[j + 1] = huffNode[j]; + j--; + } + huffNode[j + 1] = key; + } +} + +/* Pivot helper function for quicksort. */ +static int HUF_quickSortPartition(nodeElt arr[], int const low, int const high) { + /* Simply select rightmost element as pivot. "Better" selectors like + * median-of-three don't experimentally appear to have any benefit. + */ + U32 const pivot = arr[high].count; + int i = low - 1; + int j = low; + for ( ; j < high; j++) { + if (arr[j].count > pivot) { + i++; + HUF_swapNodes(&arr[i], &arr[j]); + } + } + HUF_swapNodes(&arr[i + 1], &arr[high]); + return i + 1; +} + +/* Classic quicksort by descending with partially iterative calls + * to reduce worst case callstack size. + */ +static void HUF_simpleQuickSort(nodeElt arr[], int low, int high) { + int const kInsertionSortThreshold = 8; + if (high - low < kInsertionSortThreshold) { + HUF_insertionSort(arr, low, high); + return; + } + while (low < high) { + int const idx = HUF_quickSortPartition(arr, low, high); + if (idx - low < high - idx) { + HUF_simpleQuickSort(arr, low, idx - 1); + low = idx + 1; + } else { + HUF_simpleQuickSort(arr, idx + 1, high); + high = idx - 1; + } + } +} + /** * HUF_sort(): * Sorts the symbols [0, maxSymbolValue] by count[symbol] in decreasing order. + * This is a typical bucket sorting strategy that uses either quicksort or insertion sort to sort each bucket. * * @param[out] huffNode Sorted symbols by decreasing count. Only members `.count` and `.byte` are filled. * Must have (maxSymbolValue + 1) entries. @@ -390,42 +592,51 @@ typedef struct { * @param[in] maxSymbolValue Maximum symbol value. * @param rankPosition This is a scratch workspace. Must have RANK_POSITION_TABLE_SIZE entries. */ -static void HUF_sort(nodeElt* huffNode, const unsigned* count, U32 maxSymbolValue, rankPos* rankPosition) -{ - int n; - int const maxSymbolValue1 = (int)maxSymbolValue + 1; +static void HUF_sort(nodeElt huffNode[], const unsigned count[], U32 const maxSymbolValue, rankPos rankPosition[]) { + U32 n; + U32 const maxSymbolValue1 = maxSymbolValue+1; /* Compute base and set curr to base. - * For symbol s let lowerRank = BIT_highbit32(count[n]+1) and rank = lowerRank + 1. - * Then 2^lowerRank <= count[n]+1 <= 2^rank. + * For symbol s let lowerRank = HUF_getIndex(count[n]) and rank = lowerRank + 1. + * See HUF_getIndex to see bucketing strategy. * We attribute each symbol to lowerRank's base value, because we want to know where * each rank begins in the output, so for rank R we want to count ranks R+1 and above. */ ZSTD_memset(rankPosition, 0, sizeof(*rankPosition) * RANK_POSITION_TABLE_SIZE); for (n = 0; n < maxSymbolValue1; ++n) { - U32 lowerRank = BIT_highbit32(count[n] + 1); + U32 lowerRank = HUF_getIndex(count[n]); + assert(lowerRank < RANK_POSITION_TABLE_SIZE - 1); rankPosition[lowerRank].base++; } + assert(rankPosition[RANK_POSITION_TABLE_SIZE - 1].base == 0); + /* Set up the rankPosition table */ for (n = RANK_POSITION_TABLE_SIZE - 1; n > 0; --n) { rankPosition[n-1].base += rankPosition[n].base; rankPosition[n-1].curr = rankPosition[n-1].base; } - /* Sort */ + + /* Insert each symbol into their appropriate bucket, setting up rankPosition table. */ for (n = 0; n < maxSymbolValue1; ++n) { U32 const c = count[n]; - U32 const r = BIT_highbit32(c+1) + 1; - U32 pos = rankPosition[r].curr++; - /* Insert into the correct position in the rank. - * We have at most 256 symbols, so this insertion should be fine. - */ - while ((pos > rankPosition[r].base) && (c > huffNode[pos-1].count)) { - huffNode[pos] = huffNode[pos-1]; - pos--; - } + U32 const r = HUF_getIndex(c) + 1; + U32 const pos = rankPosition[r].curr++; + assert(pos < maxSymbolValue1); huffNode[pos].count = c; huffNode[pos].byte = (BYTE)n; } + + /* Sort each bucket. */ + for (n = RANK_POSITION_DISTINCT_COUNT_CUTOFF; n < RANK_POSITION_TABLE_SIZE - 1; ++n) { + int const bucketSize = rankPosition[n].curr - rankPosition[n].base; + U32 const bucketStartIdx = rankPosition[n].base; + if (bucketSize > 1) { + assert(bucketStartIdx < maxSymbolValue1); + HUF_simpleQuickSort(huffNode + bucketStartIdx, 0, bucketSize-1); + } + } + + assert(HUF_isSorted(huffNode, maxSymbolValue1)); } @@ -449,6 +660,7 @@ static int HUF_buildTree(nodeElt* huffNode, U32 maxSymbolValue) int lowS, lowN; int nodeNb = STARTNODE; int n, nodeRoot; + DEBUGLOG(5, "HUF_buildTree (alphabet size = %u)", maxSymbolValue + 1); /* init for parents */ nonNullRank = (int)maxSymbolValue; while(huffNode[nonNullRank].count == 0) nonNullRank--; @@ -475,6 +687,8 @@ static int HUF_buildTree(nodeElt* huffNode, U32 maxSymbolValue) for (n=0; n<=nonNullRank; n++) huffNode[n].nbBits = huffNode[ huffNode[n].parent ].nbBits + 1; + DEBUGLOG(6, "Initial distribution of bits completed (%zu sorted symbols)", showHNodeBits(huffNode, maxSymbolValue+1)); + return nonNullRank; } @@ -490,6 +704,7 @@ static int HUF_buildTree(nodeElt* huffNode, U32 maxSymbolValue) */ static void HUF_buildCTableFromTree(HUF_CElt* CTable, nodeElt const* huffNode, int nonNullRank, U32 maxSymbolValue, U32 maxNbBits) { + HUF_CElt* const ct = CTable + 1; /* fill result into ctable (val, nbBits) */ int n; U16 nbPerRank[HUF_TABLELOG_MAX+1] = {0}; @@ -505,127 +720,373 @@ static void HUF_buildCTableFromTree(HUF_CElt* CTable, nodeElt const* huffNode, i min >>= 1; } } for (n=0; nhuffNodeTbl; nodeElt* const huffNode = huffNode0+1; int nonNullRank; + HUF_STATIC_ASSERT(HUF_CTABLE_WORKSPACE_SIZE == sizeof(HUF_buildCTable_wksp_tables)); + + DEBUGLOG(5, "HUF_buildCTable_wksp (alphabet size = %u)", maxSymbolValue+1); + /* safety checks */ - if (((size_t)workSpace & 3) != 0) return ERROR(GENERIC); /* must be aligned on 4-bytes boundaries */ if (wkspSize < sizeof(HUF_buildCTable_wksp_tables)) - return ERROR(workSpace_tooSmall); + return ERROR(workSpace_tooSmall); if (maxNbBits == 0) maxNbBits = HUF_TABLELOG_DEFAULT; if (maxSymbolValue > HUF_SYMBOLVALUE_MAX) - return ERROR(maxSymbolValue_tooLarge); + return ERROR(maxSymbolValue_tooLarge); ZSTD_memset(huffNode0, 0, sizeof(huffNodeTable)); /* sort, decreasing order */ HUF_sort(huffNode, count, maxSymbolValue, wksp_tables->rankPosition); + DEBUGLOG(6, "sorted symbols completed (%zu symbols)", showHNodeSymbols(huffNode, maxSymbolValue+1)); /* build tree */ nonNullRank = HUF_buildTree(huffNode, maxSymbolValue); - /* enforce maxTableLog */ + /* determine and enforce maxTableLog */ maxNbBits = HUF_setMaxHeight(huffNode, (U32)nonNullRank, maxNbBits); if (maxNbBits > HUF_TABLELOG_MAX) return ERROR(GENERIC); /* check fit into table */ - HUF_buildCTableFromTree(tree, huffNode, nonNullRank, maxSymbolValue, maxNbBits); + HUF_buildCTableFromTree(CTable, huffNode, nonNullRank, maxSymbolValue, maxNbBits); return maxNbBits; } size_t HUF_estimateCompressedSize(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue) { + HUF_CElt const* ct = CTable + 1; size_t nbBits = 0; int s; for (s = 0; s <= (int)maxSymbolValue; ++s) { - nbBits += CTable[s].nbBits * count[s]; + nbBits += HUF_getNbBits(ct[s]) * count[s]; } return nbBits >> 3; } int HUF_validateCTable(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue) { + HUF_CElt const* ct = CTable + 1; int bad = 0; int s; for (s = 0; s <= (int)maxSymbolValue; ++s) { - bad |= (count[s] != 0) & (CTable[s].nbBits == 0); + bad |= (count[s] != 0) & (HUF_getNbBits(ct[s]) == 0); } return !bad; } size_t HUF_compressBound(size_t size) { return HUF_COMPRESSBOUND(size); } +/** HUF_CStream_t: + * Huffman uses its own BIT_CStream_t implementation. + * There are three major differences from BIT_CStream_t: + * 1. HUF_addBits() takes a HUF_CElt (size_t) which is + * the pair (nbBits, value) in the format: + * format: + * - Bits [0, 4) = nbBits + * - Bits [4, 64 - nbBits) = 0 + * - Bits [64 - nbBits, 64) = value + * 2. The bitContainer is built from the upper bits and + * right shifted. E.g. to add a new value of N bits + * you right shift the bitContainer by N, then or in + * the new value into the N upper bits. + * 3. The bitstream has two bit containers. You can add + * bits to the second container and merge them into + * the first container. + */ + +#define HUF_BITS_IN_CONTAINER (sizeof(size_t) * 8) + +typedef struct { + size_t bitContainer[2]; + size_t bitPos[2]; + + BYTE* startPtr; + BYTE* ptr; + BYTE* endPtr; +} HUF_CStream_t; + +/**! HUF_initCStream(): + * Initializes the bitstream. + * @returns 0 or an error code. + */ +static size_t HUF_initCStream(HUF_CStream_t* bitC, + void* startPtr, size_t dstCapacity) +{ + ZSTD_memset(bitC, 0, sizeof(*bitC)); + bitC->startPtr = (BYTE*)startPtr; + bitC->ptr = bitC->startPtr; + bitC->endPtr = bitC->startPtr + dstCapacity - sizeof(bitC->bitContainer[0]); + if (dstCapacity <= sizeof(bitC->bitContainer[0])) return ERROR(dstSize_tooSmall); + return 0; +} + +/*! HUF_addBits(): + * Adds the symbol stored in HUF_CElt elt to the bitstream. + * + * @param elt The element we're adding. This is a (nbBits, value) pair. + * See the HUF_CStream_t docs for the format. + * @param idx Insert into the bitstream at this idx. + * @param kFast This is a template parameter. If the bitstream is guaranteed + * to have at least 4 unused bits after this call it may be 1, + * otherwise it must be 0. HUF_addBits() is faster when fast is set. + */ +FORCE_INLINE_TEMPLATE void HUF_addBits(HUF_CStream_t* bitC, HUF_CElt elt, int idx, int kFast) +{ + assert(idx <= 1); + assert(HUF_getNbBits(elt) <= HUF_TABLELOG_ABSOLUTEMAX); + /* This is efficient on x86-64 with BMI2 because shrx + * only reads the low 6 bits of the register. The compiler + * knows this and elides the mask. When fast is set, + * every operation can use the same value loaded from elt. + */ + bitC->bitContainer[idx] >>= HUF_getNbBits(elt); + bitC->bitContainer[idx] |= kFast ? HUF_getValueFast(elt) : HUF_getValue(elt); + /* We only read the low 8 bits of bitC->bitPos[idx] so it + * doesn't matter that the high bits have noise from the value. + */ + bitC->bitPos[idx] += HUF_getNbBitsFast(elt); + assert((bitC->bitPos[idx] & 0xFF) <= HUF_BITS_IN_CONTAINER); + /* The last 4-bits of elt are dirty if fast is set, + * so we must not be overwriting bits that have already been + * inserted into the bit container. + */ +#if DEBUGLEVEL >= 1 + { + size_t const nbBits = HUF_getNbBits(elt); + size_t const dirtyBits = nbBits == 0 ? 0 : ZSTD_highbit32((U32)nbBits) + 1; + (void)dirtyBits; + /* Middle bits are 0. */ + assert(((elt >> dirtyBits) << (dirtyBits + nbBits)) == 0); + /* We didn't overwrite any bits in the bit container. */ + assert(!kFast || (bitC->bitPos[idx] & 0xFF) <= HUF_BITS_IN_CONTAINER); + (void)dirtyBits; + } +#endif +} + +FORCE_INLINE_TEMPLATE void HUF_zeroIndex1(HUF_CStream_t* bitC) +{ + bitC->bitContainer[1] = 0; + bitC->bitPos[1] = 0; +} + +/*! HUF_mergeIndex1() : + * Merges the bit container @ index 1 into the bit container @ index 0 + * and zeros the bit container @ index 1. + */ +FORCE_INLINE_TEMPLATE void HUF_mergeIndex1(HUF_CStream_t* bitC) +{ + assert((bitC->bitPos[1] & 0xFF) < HUF_BITS_IN_CONTAINER); + bitC->bitContainer[0] >>= (bitC->bitPos[1] & 0xFF); + bitC->bitContainer[0] |= bitC->bitContainer[1]; + bitC->bitPos[0] += bitC->bitPos[1]; + assert((bitC->bitPos[0] & 0xFF) <= HUF_BITS_IN_CONTAINER); +} + +/*! HUF_flushBits() : +* Flushes the bits in the bit container @ index 0. +* +* @post bitPos will be < 8. +* @param kFast If kFast is set then we must know a-priori that +* the bit container will not overflow. +*/ +FORCE_INLINE_TEMPLATE void HUF_flushBits(HUF_CStream_t* bitC, int kFast) +{ + /* The upper bits of bitPos are noisy, so we must mask by 0xFF. */ + size_t const nbBits = bitC->bitPos[0] & 0xFF; + size_t const nbBytes = nbBits >> 3; + /* The top nbBits bits of bitContainer are the ones we need. */ + size_t const bitContainer = bitC->bitContainer[0] >> (HUF_BITS_IN_CONTAINER - nbBits); + /* Mask bitPos to account for the bytes we consumed. */ + bitC->bitPos[0] &= 7; + assert(nbBits > 0); + assert(nbBits <= sizeof(bitC->bitContainer[0]) * 8); + assert(bitC->ptr <= bitC->endPtr); + MEM_writeLEST(bitC->ptr, bitContainer); + bitC->ptr += nbBytes; + assert(!kFast || bitC->ptr <= bitC->endPtr); + if (!kFast && bitC->ptr > bitC->endPtr) bitC->ptr = bitC->endPtr; + /* bitContainer doesn't need to be modified because the leftover + * bits are already the top bitPos bits. And we don't care about + * noise in the lower values. + */ +} + +/*! HUF_endMark() + * @returns The Huffman stream end mark: A 1-bit value = 1. + */ +static HUF_CElt HUF_endMark(void) +{ + HUF_CElt endMark; + HUF_setNbBits(&endMark, 1); + HUF_setValue(&endMark, 1); + return endMark; +} + +/*! HUF_closeCStream() : + * @return Size of CStream, in bytes, + * or 0 if it could not fit into dstBuffer */ +static size_t HUF_closeCStream(HUF_CStream_t* bitC) +{ + HUF_addBits(bitC, HUF_endMark(), /* idx */ 0, /* kFast */ 0); + HUF_flushBits(bitC, /* kFast */ 0); + { + size_t const nbBits = bitC->bitPos[0] & 0xFF; + if (bitC->ptr >= bitC->endPtr) return 0; /* overflow detected */ + return (size_t)(bitC->ptr - bitC->startPtr) + (nbBits > 0); + } +} + FORCE_INLINE_TEMPLATE void -HUF_encodeSymbol(BIT_CStream_t* bitCPtr, U32 symbol, const HUF_CElt* CTable) +HUF_encodeSymbol(HUF_CStream_t* bitCPtr, U32 symbol, const HUF_CElt* CTable, int idx, int fast) { - BIT_addBitsFast(bitCPtr, CTable[symbol].val, CTable[symbol].nbBits); + HUF_addBits(bitCPtr, CTable[symbol], idx, fast); } -#define HUF_FLUSHBITS(s) BIT_flushBits(s) +FORCE_INLINE_TEMPLATE void +HUF_compress1X_usingCTable_internal_body_loop(HUF_CStream_t* bitC, + const BYTE* ip, size_t srcSize, + const HUF_CElt* ct, + int kUnroll, int kFastFlush, int kLastFast) +{ + /* Join to kUnroll */ + int n = (int)srcSize; + int rem = n % kUnroll; + if (rem > 0) { + for (; rem > 0; --rem) { + HUF_encodeSymbol(bitC, ip[--n], ct, 0, /* fast */ 0); + } + HUF_flushBits(bitC, kFastFlush); + } + assert(n % kUnroll == 0); + + /* Join to 2 * kUnroll */ + if (n % (2 * kUnroll)) { + int u; + for (u = 1; u < kUnroll; ++u) { + HUF_encodeSymbol(bitC, ip[n - u], ct, 0, 1); + } + HUF_encodeSymbol(bitC, ip[n - kUnroll], ct, 0, kLastFast); + HUF_flushBits(bitC, kFastFlush); + n -= kUnroll; + } + assert(n % (2 * kUnroll) == 0); -#define HUF_FLUSHBITS_1(stream) \ - if (sizeof((stream)->bitContainer)*8 < HUF_TABLELOG_MAX*2+7) HUF_FLUSHBITS(stream) + for (; n>0; n-= 2 * kUnroll) { + /* Encode kUnroll symbols into the bitstream @ index 0. */ + int u; + for (u = 1; u < kUnroll; ++u) { + HUF_encodeSymbol(bitC, ip[n - u], ct, /* idx */ 0, /* fast */ 1); + } + HUF_encodeSymbol(bitC, ip[n - kUnroll], ct, /* idx */ 0, /* fast */ kLastFast); + HUF_flushBits(bitC, kFastFlush); + /* Encode kUnroll symbols into the bitstream @ index 1. + * This allows us to start filling the bit container + * without any data dependencies. + */ + HUF_zeroIndex1(bitC); + for (u = 1; u < kUnroll; ++u) { + HUF_encodeSymbol(bitC, ip[n - kUnroll - u], ct, /* idx */ 1, /* fast */ 1); + } + HUF_encodeSymbol(bitC, ip[n - kUnroll - kUnroll], ct, /* idx */ 1, /* fast */ kLastFast); + /* Merge bitstream @ index 1 into the bitstream @ index 0 */ + HUF_mergeIndex1(bitC); + HUF_flushBits(bitC, kFastFlush); + } + assert(n == 0); + +} + +/** + * Returns a tight upper bound on the output space needed by Huffman + * with 8 bytes buffer to handle over-writes. If the output is at least + * this large we don't need to do bounds checks during Huffman encoding. + */ +static size_t HUF_tightCompressBound(size_t srcSize, size_t tableLog) +{ + return ((srcSize * tableLog) >> 3) + 8; +} -#define HUF_FLUSHBITS_2(stream) \ - if (sizeof((stream)->bitContainer)*8 < HUF_TABLELOG_MAX*4+7) HUF_FLUSHBITS(stream) FORCE_INLINE_TEMPLATE size_t HUF_compress1X_usingCTable_internal_body(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable) { + U32 const tableLog = (U32)CTable[0]; + HUF_CElt const* ct = CTable + 1; const BYTE* ip = (const BYTE*) src; BYTE* const ostart = (BYTE*)dst; BYTE* const oend = ostart + dstSize; BYTE* op = ostart; - size_t n; - BIT_CStream_t bitC; + HUF_CStream_t bitC; /* init */ if (dstSize < 8) return 0; /* not enough space to compress */ - { size_t const initErr = BIT_initCStream(&bitC, op, (size_t)(oend-op)); + { size_t const initErr = HUF_initCStream(&bitC, op, (size_t)(oend-op)); if (HUF_isError(initErr)) return 0; } - n = srcSize & ~3; /* join to mod 4 */ - switch (srcSize & 3) - { - case 3 : HUF_encodeSymbol(&bitC, ip[n+ 2], CTable); - HUF_FLUSHBITS_2(&bitC); - /* fall-through */ - case 2 : HUF_encodeSymbol(&bitC, ip[n+ 1], CTable); - HUF_FLUSHBITS_1(&bitC); - /* fall-through */ - case 1 : HUF_encodeSymbol(&bitC, ip[n+ 0], CTable); - HUF_FLUSHBITS(&bitC); - /* fall-through */ - case 0 : /* fall-through */ - default: break; - } - - for (; n>0; n-=4) { /* note : n&3==0 at this stage */ - HUF_encodeSymbol(&bitC, ip[n- 1], CTable); - HUF_FLUSHBITS_1(&bitC); - HUF_encodeSymbol(&bitC, ip[n- 2], CTable); - HUF_FLUSHBITS_2(&bitC); - HUF_encodeSymbol(&bitC, ip[n- 3], CTable); - HUF_FLUSHBITS_1(&bitC); - HUF_encodeSymbol(&bitC, ip[n- 4], CTable); - HUF_FLUSHBITS(&bitC); + if (dstSize < HUF_tightCompressBound(srcSize, (size_t)tableLog) || tableLog > 11) + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ MEM_32bits() ? 2 : 4, /* kFast */ 0, /* kLastFast */ 0); + else { + if (MEM_32bits()) { + switch (tableLog) { + case 11: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 2, /* kFastFlush */ 1, /* kLastFast */ 0); + break; + case 10: ZSTD_FALLTHROUGH; + case 9: ZSTD_FALLTHROUGH; + case 8: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 2, /* kFastFlush */ 1, /* kLastFast */ 1); + break; + case 7: ZSTD_FALLTHROUGH; + default: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 3, /* kFastFlush */ 1, /* kLastFast */ 1); + break; + } + } else { + switch (tableLog) { + case 11: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 5, /* kFastFlush */ 1, /* kLastFast */ 0); + break; + case 10: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 5, /* kFastFlush */ 1, /* kLastFast */ 1); + break; + case 9: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 6, /* kFastFlush */ 1, /* kLastFast */ 0); + break; + case 8: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 7, /* kFastFlush */ 1, /* kLastFast */ 0); + break; + case 7: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 8, /* kFastFlush */ 1, /* kLastFast */ 0); + break; + case 6: ZSTD_FALLTHROUGH; + default: + HUF_compress1X_usingCTable_internal_body_loop(&bitC, ip, srcSize, ct, /* kUnroll */ 9, /* kFastFlush */ 1, /* kLastFast */ 1); + break; + } + } } + assert(bitC.ptr <= bitC.endPtr); - return BIT_closeCStream(&bitC); + return HUF_closeCStream(&bitC); } #if DYNAMIC_BMI2 -static TARGET_ATTRIBUTE("bmi2") size_t +static BMI2_TARGET_ATTRIBUTE size_t HUF_compress1X_usingCTable_internal_bmi2(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable) @@ -644,9 +1105,9 @@ HUF_compress1X_usingCTable_internal_default(void* dst, size_t dstSize, static size_t HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, const void* src, size_t srcSize, - const HUF_CElt* CTable, const int bmi2) + const HUF_CElt* CTable, const int flags) { - if (bmi2) { + if (flags & HUF_flags_bmi2) { return HUF_compress1X_usingCTable_internal_bmi2(dst, dstSize, src, srcSize, CTable); } return HUF_compress1X_usingCTable_internal_default(dst, dstSize, src, srcSize, CTable); @@ -657,24 +1118,23 @@ HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, static size_t HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, const void* src, size_t srcSize, - const HUF_CElt* CTable, const int bmi2) + const HUF_CElt* CTable, const int flags) { - (void)bmi2; + (void)flags; return HUF_compress1X_usingCTable_internal_body(dst, dstSize, src, srcSize, CTable); } #endif -size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable) +size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags) { - return HUF_compress1X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, /* bmi2 */ 0); + return HUF_compress1X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, flags); } - static size_t HUF_compress4X_usingCTable_internal(void* dst, size_t dstSize, const void* src, size_t srcSize, - const HUF_CElt* CTable, int bmi2) + const HUF_CElt* CTable, int flags) { size_t const segmentSize = (srcSize+3)/4; /* first 3 segments */ const BYTE* ip = (const BYTE*) src; @@ -688,27 +1148,24 @@ HUF_compress4X_usingCTable_internal(void* dst, size_t dstSize, op += 6; /* jumpTable */ assert(op <= oend); - { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, bmi2) ); - if (cSize==0) return 0; - assert(cSize <= 65535); + { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; MEM_writeLE16(ostart, (U16)cSize); op += cSize; } ip += segmentSize; assert(op <= oend); - { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, bmi2) ); - if (cSize==0) return 0; - assert(cSize <= 65535); + { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; MEM_writeLE16(ostart+2, (U16)cSize); op += cSize; } ip += segmentSize; assert(op <= oend); - { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, bmi2) ); - if (cSize==0) return 0; - assert(cSize <= 65535); + { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; MEM_writeLE16(ostart+4, (U16)cSize); op += cSize; } @@ -716,17 +1173,17 @@ HUF_compress4X_usingCTable_internal(void* dst, size_t dstSize, ip += segmentSize; assert(op <= oend); assert(ip <= iend); - { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, (size_t)(iend-ip), CTable, bmi2) ); - if (cSize==0) return 0; + { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, (size_t)(iend-ip), CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; op += cSize; } return (size_t)(op-ostart); } -size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable) +size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags) { - return HUF_compress4X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, /* bmi2 */ 0); + return HUF_compress4X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, flags); } typedef enum { HUF_singleStream, HUF_fourStreams } HUF_nbStreams_e; @@ -734,11 +1191,11 @@ typedef enum { HUF_singleStream, HUF_fourStreams } HUF_nbStreams_e; static size_t HUF_compressCTable_internal( BYTE* const ostart, BYTE* op, BYTE* const oend, const void* src, size_t srcSize, - HUF_nbStreams_e nbStreams, const HUF_CElt* CTable, const int bmi2) + HUF_nbStreams_e nbStreams, const HUF_CElt* CTable, const int flags) { size_t const cSize = (nbStreams==HUF_singleStream) ? - HUF_compress1X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, bmi2) : - HUF_compress4X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, bmi2); + HUF_compress1X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, flags) : + HUF_compress4X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, flags); if (HUF_isError(cSize)) { return cSize; } if (cSize==0) { return 0; } /* uncompressible */ op += cSize; @@ -750,35 +1207,111 @@ static size_t HUF_compressCTable_internal( typedef struct { unsigned count[HUF_SYMBOLVALUE_MAX + 1]; - HUF_CElt CTable[HUF_SYMBOLVALUE_MAX + 1]; + HUF_CElt CTable[HUF_CTABLE_SIZE_ST(HUF_SYMBOLVALUE_MAX)]; union { HUF_buildCTable_wksp_tables buildCTable_wksp; HUF_WriteCTableWksp writeCTable_wksp; + U32 hist_wksp[HIST_WKSP_SIZE_U32]; } wksps; } HUF_compress_tables_t; +#define SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE 4096 +#define SUSPECT_INCOMPRESSIBLE_SAMPLE_RATIO 10 /* Must be >= 2 */ + +unsigned HUF_cardinality(const unsigned* count, unsigned maxSymbolValue) +{ + unsigned cardinality = 0; + unsigned i; + + for (i = 0; i < maxSymbolValue + 1; i++) { + if (count[i] != 0) cardinality += 1; + } + + return cardinality; +} + +unsigned HUF_minTableLog(unsigned symbolCardinality) +{ + U32 minBitsSymbols = ZSTD_highbit32(symbolCardinality) + 1; + return minBitsSymbols; +} + +unsigned HUF_optimalTableLog( + unsigned maxTableLog, + size_t srcSize, + unsigned maxSymbolValue, + void* workSpace, size_t wkspSize, + HUF_CElt* table, + const unsigned* count, + int flags) +{ + assert(srcSize > 1); /* Not supported, RLE should be used instead */ + assert(wkspSize >= sizeof(HUF_buildCTable_wksp_tables)); + + if (!(flags & HUF_flags_optimalDepth)) { + /* cheap evaluation, based on FSE */ + return FSE_optimalTableLog_internal(maxTableLog, srcSize, maxSymbolValue, 1); + } + + { BYTE* dst = (BYTE*)workSpace + sizeof(HUF_WriteCTableWksp); + size_t dstSize = wkspSize - sizeof(HUF_WriteCTableWksp); + size_t maxBits, hSize, newSize; + const unsigned symbolCardinality = HUF_cardinality(count, maxSymbolValue); + const unsigned minTableLog = HUF_minTableLog(symbolCardinality); + size_t optSize = ((size_t) ~0) - 1; + unsigned optLog = maxTableLog, optLogGuess; + + DEBUGLOG(6, "HUF_optimalTableLog: probing huf depth (srcSize=%zu)", srcSize); + + /* Search until size increases */ + for (optLogGuess = minTableLog; optLogGuess <= maxTableLog; optLogGuess++) { + DEBUGLOG(7, "checking for huffLog=%u", optLogGuess); + maxBits = HUF_buildCTable_wksp(table, count, maxSymbolValue, optLogGuess, workSpace, wkspSize); + if (ERR_isError(maxBits)) continue; + + if (maxBits < optLogGuess && optLogGuess > minTableLog) break; + + hSize = HUF_writeCTable_wksp(dst, dstSize, table, maxSymbolValue, (U32)maxBits, workSpace, wkspSize); + + if (ERR_isError(hSize)) continue; + + newSize = HUF_estimateCompressedSize(table, count, maxSymbolValue) + hSize; + + if (newSize > optSize + 1) { + break; + } + + if (newSize < optSize) { + optSize = newSize; + optLog = optLogGuess; + } + } + assert(optLog <= HUF_TABLELOG_MAX); + return optLog; + } +} + /* HUF_compress_internal() : * `workSpace_align4` must be aligned on 4-bytes boundaries, - * and occupies the same space as a table of HUF_WORKSPACE_SIZE_U32 unsigned */ + * and occupies the same space as a table of HUF_WORKSPACE_SIZE_U64 unsigned */ static size_t HUF_compress_internal (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned huffLog, HUF_nbStreams_e nbStreams, - void* workSpace_align4, size_t wkspSize, - HUF_CElt* oldHufTable, HUF_repeat* repeat, int preferRepeat, - const int bmi2) + void* workSpace, size_t wkspSize, + HUF_CElt* oldHufTable, HUF_repeat* repeat, int flags) { - HUF_compress_tables_t* const table = (HUF_compress_tables_t*)workSpace_align4; + HUF_compress_tables_t* const table = (HUF_compress_tables_t*)HUF_alignUpWorkspace(workSpace, &wkspSize, ZSTD_ALIGNOF(size_t)); BYTE* const ostart = (BYTE*)dst; BYTE* const oend = ostart + dstSize; BYTE* op = ostart; - HUF_STATIC_ASSERT(sizeof(*table) <= HUF_WORKSPACE_SIZE); - assert(((size_t)workSpace_align4 & 3) == 0); /* must be aligned on 4-bytes boundaries */ + DEBUGLOG(5, "HUF_compress_internal (srcSize=%zu)", srcSize); + HUF_STATIC_ASSERT(sizeof(*table) + HUF_WORKSPACE_MAX_ALIGNMENT <= HUF_WORKSPACE_SIZE); /* checks & inits */ - if (wkspSize < HUF_WORKSPACE_SIZE) return ERROR(workSpace_tooSmall); + if (wkspSize < sizeof(*table)) return ERROR(workSpace_tooSmall); if (!srcSize) return 0; /* Uncompressed */ if (!dstSize) return 0; /* cannot fit anything within dst budget */ if (srcSize > HUF_BLOCKSIZE_MAX) return ERROR(srcSize_wrong); /* current block size limit */ @@ -788,17 +1321,34 @@ HUF_compress_internal (void* dst, size_t dstSize, if (!huffLog) huffLog = HUF_TABLELOG_DEFAULT; /* Heuristic : If old table is valid, use it for small inputs */ - if (preferRepeat && repeat && *repeat == HUF_repeat_valid) { + if ((flags & HUF_flags_preferRepeat) && repeat && *repeat == HUF_repeat_valid) { return HUF_compressCTable_internal(ostart, op, oend, src, srcSize, - nbStreams, oldHufTable, bmi2); + nbStreams, oldHufTable, flags); + } + + /* If uncompressible data is suspected, do a smaller sampling first */ + DEBUG_STATIC_ASSERT(SUSPECT_INCOMPRESSIBLE_SAMPLE_RATIO >= 2); + if ((flags & HUF_flags_suspectUncompressible) && srcSize >= (SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE * SUSPECT_INCOMPRESSIBLE_SAMPLE_RATIO)) { + size_t largestTotal = 0; + DEBUGLOG(5, "input suspected incompressible : sampling to check"); + { unsigned maxSymbolValueBegin = maxSymbolValue; + CHECK_V_F(largestBegin, HIST_count_simple (table->count, &maxSymbolValueBegin, (const BYTE*)src, SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE) ); + largestTotal += largestBegin; + } + { unsigned maxSymbolValueEnd = maxSymbolValue; + CHECK_V_F(largestEnd, HIST_count_simple (table->count, &maxSymbolValueEnd, (const BYTE*)src + srcSize - SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE, SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE) ); + largestTotal += largestEnd; + } + if (largestTotal <= ((2 * SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE) >> 7)+4) return 0; /* heuristic : probably not compressible enough */ } /* Scan input and build symbol stats */ - { CHECK_V_F(largest, HIST_count_wksp (table->count, &maxSymbolValue, (const BYTE*)src, srcSize, workSpace_align4, wkspSize) ); + { CHECK_V_F(largest, HIST_count_wksp (table->count, &maxSymbolValue, (const BYTE*)src, srcSize, table->wksps.hist_wksp, sizeof(table->wksps.hist_wksp)) ); if (largest == srcSize) { *ostart = ((const BYTE*)src)[0]; return 1; } /* single symbol, rle */ if (largest <= (srcSize >> 7)+4) return 0; /* heuristic : probably not compressible enough */ } + DEBUGLOG(6, "histogram detail completed (%zu symbols)", showU32(table->count, maxSymbolValue+1)); /* Check validity of previous table */ if ( repeat @@ -807,22 +1357,26 @@ HUF_compress_internal (void* dst, size_t dstSize, *repeat = HUF_repeat_none; } /* Heuristic : use existing table for small inputs */ - if (preferRepeat && repeat && *repeat != HUF_repeat_none) { + if ((flags & HUF_flags_preferRepeat) && repeat && *repeat != HUF_repeat_none) { return HUF_compressCTable_internal(ostart, op, oend, src, srcSize, - nbStreams, oldHufTable, bmi2); + nbStreams, oldHufTable, flags); } /* Build Huffman Tree */ - huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue); + huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue, &table->wksps, sizeof(table->wksps), table->CTable, table->count, flags); { size_t const maxBits = HUF_buildCTable_wksp(table->CTable, table->count, maxSymbolValue, huffLog, &table->wksps.buildCTable_wksp, sizeof(table->wksps.buildCTable_wksp)); CHECK_F(maxBits); huffLog = (U32)maxBits; - /* Zero unused symbols in CTable, so we can check it for validity */ - ZSTD_memset(table->CTable + (maxSymbolValue + 1), 0, - sizeof(table->CTable) - ((maxSymbolValue + 1) * sizeof(HUF_CElt))); + DEBUGLOG(6, "bit distribution completed (%zu symbols)", showCTableBits(table->CTable + 1, maxSymbolValue+1)); + } + /* Zero unused symbols in CTable, so we can check it for validity */ + { + size_t const ctableSize = HUF_CTABLE_SIZE_ST(maxSymbolValue); + size_t const unusedSize = sizeof(table->CTable) - ctableSize * sizeof(HUF_CElt); + ZSTD_memset(table->CTable + ctableSize, 0, unusedSize); } /* Write table description header */ @@ -835,7 +1389,7 @@ HUF_compress_internal (void* dst, size_t dstSize, if (oldSize <= hSize + newSize || hSize + 12 >= srcSize) { return HUF_compressCTable_internal(ostart, op, oend, src, srcSize, - nbStreams, oldHufTable, bmi2); + nbStreams, oldHufTable, flags); } } /* Use the new huffman table */ @@ -847,91 +1401,35 @@ HUF_compress_internal (void* dst, size_t dstSize, } return HUF_compressCTable_internal(ostart, op, oend, src, srcSize, - nbStreams, table->CTable, bmi2); -} - - -size_t HUF_compress1X_wksp (void* dst, size_t dstSize, - const void* src, size_t srcSize, - unsigned maxSymbolValue, unsigned huffLog, - void* workSpace, size_t wkspSize) -{ - return HUF_compress_internal(dst, dstSize, src, srcSize, - maxSymbolValue, huffLog, HUF_singleStream, - workSpace, wkspSize, - NULL, NULL, 0, 0 /*bmi2*/); + nbStreams, table->CTable, flags); } size_t HUF_compress1X_repeat (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned huffLog, void* workSpace, size_t wkspSize, - HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2) + HUF_CElt* hufTable, HUF_repeat* repeat, int flags) { + DEBUGLOG(5, "HUF_compress1X_repeat (srcSize = %zu)", srcSize); return HUF_compress_internal(dst, dstSize, src, srcSize, maxSymbolValue, huffLog, HUF_singleStream, workSpace, wkspSize, hufTable, - repeat, preferRepeat, bmi2); -} - -/* HUF_compress4X_repeat(): - * compress input using 4 streams. - * provide workspace to generate compression tables */ -size_t HUF_compress4X_wksp (void* dst, size_t dstSize, - const void* src, size_t srcSize, - unsigned maxSymbolValue, unsigned huffLog, - void* workSpace, size_t wkspSize) -{ - return HUF_compress_internal(dst, dstSize, src, srcSize, - maxSymbolValue, huffLog, HUF_fourStreams, - workSpace, wkspSize, - NULL, NULL, 0, 0 /*bmi2*/); + repeat, flags); } /* HUF_compress4X_repeat(): * compress input using 4 streams. + * consider skipping quickly * re-use an existing huffman compression table */ size_t HUF_compress4X_repeat (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned huffLog, void* workSpace, size_t wkspSize, - HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2) + HUF_CElt* hufTable, HUF_repeat* repeat, int flags) { + DEBUGLOG(5, "HUF_compress4X_repeat (srcSize = %zu)", srcSize); return HUF_compress_internal(dst, dstSize, src, srcSize, maxSymbolValue, huffLog, HUF_fourStreams, workSpace, wkspSize, - hufTable, repeat, preferRepeat, bmi2); -} - -#ifndef ZSTD_NO_UNUSED_FUNCTIONS -/** HUF_buildCTable() : - * @return : maxNbBits - * Note : count is used before tree is written, so they can safely overlap - */ -size_t HUF_buildCTable (HUF_CElt* tree, const unsigned* count, unsigned maxSymbolValue, unsigned maxNbBits) -{ - HUF_buildCTable_wksp_tables workspace; - return HUF_buildCTable_wksp(tree, count, maxSymbolValue, maxNbBits, &workspace, sizeof(workspace)); -} - -size_t HUF_compress1X (void* dst, size_t dstSize, - const void* src, size_t srcSize, - unsigned maxSymbolValue, unsigned huffLog) -{ - unsigned workSpace[HUF_WORKSPACE_SIZE_U32]; - return HUF_compress1X_wksp(dst, dstSize, src, srcSize, maxSymbolValue, huffLog, workSpace, sizeof(workSpace)); + hufTable, repeat, flags); } - -size_t HUF_compress2 (void* dst, size_t dstSize, - const void* src, size_t srcSize, - unsigned maxSymbolValue, unsigned huffLog) -{ - unsigned workSpace[HUF_WORKSPACE_SIZE_U32]; - return HUF_compress4X_wksp(dst, dstSize, src, srcSize, maxSymbolValue, huffLog, workSpace, sizeof(workSpace)); -} - -size_t HUF_compress (void* dst, size_t maxDstSize, const void* src, size_t srcSize) -{ - return HUF_compress2(dst, maxDstSize, src, srcSize, 255, HUF_TABLELOG_DEFAULT); -} -#endif diff --git a/Utilities/cmzstd/lib/compress/zstd_compress.c b/Utilities/cmzstd/lib/compress/zstd_compress.c index b7ee2980a77..266186ca492 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress.c +++ b/Utilities/cmzstd/lib/compress/zstd_compress.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -11,13 +11,12 @@ /*-************************************* * Dependencies ***************************************/ +#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ #include "../common/zstd_deps.h" /* INT_MAX, ZSTD_memset, ZSTD_memcpy */ -#include "../common/cpu.h" #include "../common/mem.h" #include "hist.h" /* HIST_countFast_wksp */ #define FSE_STATIC_LINKING_ONLY /* FSE_encodeSymbol */ #include "../common/fse.h" -#define HUF_STATIC_LINKING_ONLY #include "../common/huf.h" #include "zstd_compress_internal.h" #include "zstd_compress_sequences.h" @@ -28,6 +27,7 @@ #include "zstd_opt.h" #include "zstd_ldm.h" #include "zstd_compress_superblock.h" +#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_rotateRight_U64 */ /* *************************************************************** * Tuning parameters @@ -42,19 +42,34 @@ # define ZSTD_COMPRESS_HEAPMODE 0 #endif +/*! + * ZSTD_HASHLOG3_MAX : + * Maximum size of the hash table dedicated to find 3-bytes matches, + * in log format, aka 17 => 1 << 17 == 128Ki positions. + * This structure is only used in zstd_opt. + * Since allocation is centralized for all strategies, it has to be known here. + * The actual (selected) size of the hash table is then stored in ZSTD_matchState_t.hashLog3, + * so that zstd_opt.c doesn't need to know about this constant. + */ +#ifndef ZSTD_HASHLOG3_MAX +# define ZSTD_HASHLOG3_MAX 17 +#endif /*-************************************* * Helper functions ***************************************/ /* ZSTD_compressBound() - * Note that the result from this function is only compatible with the "normal" - * full-block strategy. - * When there are a lot of small blocks due to frequent flush in streaming mode - * the overhead of headers can make the compressed data to be larger than the - * return value of ZSTD_compressBound(). + * Note that the result from this function is only valid for + * the one-pass compression functions. + * When employing the streaming mode, + * if flushes are frequently altering the size of blocks, + * the overhead from block headers can make the compressed data larger + * than the return value of ZSTD_compressBound(). */ size_t ZSTD_compressBound(size_t srcSize) { - return ZSTD_COMPRESSBOUND(srcSize); + size_t const r = ZSTD_COMPRESSBOUND(srcSize); + if (r==0) return ERROR(srcSize_wrong); + return r; } @@ -72,10 +87,10 @@ struct ZSTD_CDict_s { ZSTD_customMem customMem; U32 dictID; int compressionLevel; /* 0 indicates that advanced API was used to select CDict params */ - ZSTD_useRowMatchFinderMode_e useRowMatchFinder; /* Indicates whether the CDict was created with params that would use - * row-based matchfinder. Unless the cdict is reloaded, we will use - * the same greedy/lazy matchfinder at compression time. - */ + ZSTD_paramSwitch_e useRowMatchFinder; /* Indicates whether the CDict was created with params that would use + * row-based matchfinder. Unless the cdict is reloaded, we will use + * the same greedy/lazy matchfinder at compression time. + */ }; /* typedef'd to ZSTD_CDict within "zstd.h" */ ZSTD_CCtx* ZSTD_createCCtx(void) @@ -88,7 +103,7 @@ static void ZSTD_initCCtx(ZSTD_CCtx* cctx, ZSTD_customMem memManager) assert(cctx != NULL); ZSTD_memset(cctx, 0, sizeof(*cctx)); cctx->customMem = memManager; - cctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid()); + cctx->bmi2 = ZSTD_cpuSupportsBmi2(); { size_t const err = ZSTD_CCtx_reset(cctx, ZSTD_reset_parameters); assert(!ZSTD_isError(err)); (void)err; @@ -166,12 +181,9 @@ size_t ZSTD_freeCCtx(ZSTD_CCtx* cctx) if (cctx==NULL) return 0; /* support free on NULL */ RETURN_ERROR_IF(cctx->staticSize, memory_allocation, "not compatible with static CCtx"); - { - int cctxInWorkspace = ZSTD_cwksp_owns_buffer(&cctx->workspace, cctx); + { int cctxInWorkspace = ZSTD_cwksp_owns_buffer(&cctx->workspace, cctx); ZSTD_freeCCtxContent(cctx); - if (!cctxInWorkspace) { - ZSTD_customFree(cctx, cctx->customMem); - } + if (!cctxInWorkspace) ZSTD_customFree(cctx, cctx->customMem); } return 0; } @@ -214,55 +226,84 @@ static int ZSTD_rowMatchFinderSupported(const ZSTD_strategy strategy) { /* Returns true if the strategy and useRowMatchFinder mode indicate that we will use the row based matchfinder * for this compression. */ -static int ZSTD_rowMatchFinderUsed(const ZSTD_strategy strategy, const ZSTD_useRowMatchFinderMode_e mode) { - assert(mode != ZSTD_urm_auto); - return ZSTD_rowMatchFinderSupported(strategy) && (mode == ZSTD_urm_enableRowMatchFinder); +static int ZSTD_rowMatchFinderUsed(const ZSTD_strategy strategy, const ZSTD_paramSwitch_e mode) { + assert(mode != ZSTD_ps_auto); + return ZSTD_rowMatchFinderSupported(strategy) && (mode == ZSTD_ps_enable); } -/* Returns row matchfinder usage enum given an initial mode and cParams */ -static ZSTD_useRowMatchFinderMode_e ZSTD_resolveRowMatchFinderMode(ZSTD_useRowMatchFinderMode_e mode, - const ZSTD_compressionParameters* const cParams) { -#if !defined(ZSTD_NO_INTRINSICS) && (defined(__SSE2__) || defined(__ARM_NEON)) +/* Returns row matchfinder usage given an initial mode and cParams */ +static ZSTD_paramSwitch_e ZSTD_resolveRowMatchFinderMode(ZSTD_paramSwitch_e mode, + const ZSTD_compressionParameters* const cParams) { +#if defined(ZSTD_ARCH_X86_SSE2) || defined(ZSTD_ARCH_ARM_NEON) int const kHasSIMD128 = 1; #else int const kHasSIMD128 = 0; #endif - if (mode != ZSTD_urm_auto) return mode; /* if requested enabled, but no SIMD, we still will use row matchfinder */ - mode = ZSTD_urm_disableRowMatchFinder; + if (mode != ZSTD_ps_auto) return mode; /* if requested enabled, but no SIMD, we still will use row matchfinder */ + mode = ZSTD_ps_disable; if (!ZSTD_rowMatchFinderSupported(cParams->strategy)) return mode; if (kHasSIMD128) { - if (cParams->windowLog > 14) mode = ZSTD_urm_enableRowMatchFinder; + if (cParams->windowLog > 14) mode = ZSTD_ps_enable; } else { - if (cParams->windowLog > 17) mode = ZSTD_urm_enableRowMatchFinder; + if (cParams->windowLog > 17) mode = ZSTD_ps_enable; } return mode; } +/* Returns block splitter usage (generally speaking, when using slower/stronger compression modes) */ +static ZSTD_paramSwitch_e ZSTD_resolveBlockSplitterMode(ZSTD_paramSwitch_e mode, + const ZSTD_compressionParameters* const cParams) { + if (mode != ZSTD_ps_auto) return mode; + return (cParams->strategy >= ZSTD_btopt && cParams->windowLog >= 17) ? ZSTD_ps_enable : ZSTD_ps_disable; +} + /* Returns 1 if the arguments indicate that we should allocate a chainTable, 0 otherwise */ static int ZSTD_allocateChainTable(const ZSTD_strategy strategy, - const ZSTD_useRowMatchFinderMode_e useRowMatchFinder, + const ZSTD_paramSwitch_e useRowMatchFinder, const U32 forDDSDict) { - assert(useRowMatchFinder != ZSTD_urm_auto); + assert(useRowMatchFinder != ZSTD_ps_auto); /* We always should allocate a chaintable if we are allocating a matchstate for a DDS dictionary matchstate. * We do not allocate a chaintable if we are using ZSTD_fast, or are using the row-based matchfinder. */ return forDDSDict || ((strategy != ZSTD_fast) && !ZSTD_rowMatchFinderUsed(strategy, useRowMatchFinder)); } -/* Returns 1 if compression parameters are such that we should +/* Returns ZSTD_ps_enable if compression parameters are such that we should * enable long distance matching (wlog >= 27, strategy >= btopt). - * Returns 0 otherwise. + * Returns ZSTD_ps_disable otherwise. */ -static U32 ZSTD_CParams_shouldEnableLdm(const ZSTD_compressionParameters* const cParams) { - return cParams->strategy >= ZSTD_btopt && cParams->windowLog >= 27; +static ZSTD_paramSwitch_e ZSTD_resolveEnableLdm(ZSTD_paramSwitch_e mode, + const ZSTD_compressionParameters* const cParams) { + if (mode != ZSTD_ps_auto) return mode; + return (cParams->strategy >= ZSTD_btopt && cParams->windowLog >= 27) ? ZSTD_ps_enable : ZSTD_ps_disable; } -/* Returns 1 if compression parameters are such that we should - * enable blockSplitter (wlog >= 17, strategy >= btopt). - * Returns 0 otherwise. - */ -static U32 ZSTD_CParams_useBlockSplitter(const ZSTD_compressionParameters* const cParams) { - return cParams->strategy >= ZSTD_btopt && cParams->windowLog >= 17; +static int ZSTD_resolveExternalSequenceValidation(int mode) { + return mode; +} + +/* Resolves maxBlockSize to the default if no value is present. */ +static size_t ZSTD_resolveMaxBlockSize(size_t maxBlockSize) { + if (maxBlockSize == 0) { + return ZSTD_BLOCKSIZE_MAX; + } else { + return maxBlockSize; + } +} + +static ZSTD_paramSwitch_e ZSTD_resolveExternalRepcodeSearch(ZSTD_paramSwitch_e value, int cLevel) { + if (value != ZSTD_ps_auto) return value; + if (cLevel < 10) { + return ZSTD_ps_disable; + } else { + return ZSTD_ps_enable; + } +} + +/* Returns 1 if compression parameters are such that CDict hashtable and chaintable indices are tagged. + * If so, the tags need to be removed in ZSTD_resetCCtx_byCopyingCDict. */ +static int ZSTD_CDictIndicesAreTagged(const ZSTD_compressionParameters* const cParams) { + return cParams->strategy == ZSTD_fast || cParams->strategy == ZSTD_dfast; } static ZSTD_CCtx_params ZSTD_makeCCtxParamsFromCParams( @@ -274,21 +315,18 @@ static ZSTD_CCtx_params ZSTD_makeCCtxParamsFromCParams( cctxParams.cParams = cParams; /* Adjust advanced params according to cParams */ - if (ZSTD_CParams_shouldEnableLdm(&cParams)) { - DEBUGLOG(4, "ZSTD_makeCCtxParamsFromCParams(): Including LDM into cctx params"); - cctxParams.ldmParams.enableLdm = 1; - /* LDM is enabled by default for optimal parser and window size >= 128MB */ + cctxParams.ldmParams.enableLdm = ZSTD_resolveEnableLdm(cctxParams.ldmParams.enableLdm, &cParams); + if (cctxParams.ldmParams.enableLdm == ZSTD_ps_enable) { ZSTD_ldm_adjustParameters(&cctxParams.ldmParams, &cParams); assert(cctxParams.ldmParams.hashLog >= cctxParams.ldmParams.bucketSizeLog); assert(cctxParams.ldmParams.hashRateLog < 32); } - - if (ZSTD_CParams_useBlockSplitter(&cParams)) { - DEBUGLOG(4, "ZSTD_makeCCtxParamsFromCParams(): Including block splitting into cctx params"); - cctxParams.splitBlocks = 1; - } - + cctxParams.useBlockSplitter = ZSTD_resolveBlockSplitterMode(cctxParams.useBlockSplitter, &cParams); cctxParams.useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(cctxParams.useRowMatchFinder, &cParams); + cctxParams.validateSequences = ZSTD_resolveExternalSequenceValidation(cctxParams.validateSequences); + cctxParams.maxBlockSize = ZSTD_resolveMaxBlockSize(cctxParams.maxBlockSize); + cctxParams.searchForExternalRepcodes = ZSTD_resolveExternalRepcodeSearch(cctxParams.searchForExternalRepcodes, + cctxParams.compressionLevel); assert(!ZSTD_checkCParams(cParams)); return cctxParams; } @@ -334,10 +372,13 @@ size_t ZSTD_CCtxParams_init(ZSTD_CCtx_params* cctxParams, int compressionLevel) #define ZSTD_NO_CLEVEL 0 /** - * Initializes the cctxParams from params and compressionLevel. + * Initializes `cctxParams` from `params` and `compressionLevel`. * @param compressionLevel If params are derived from a compression level then that compression level, otherwise ZSTD_NO_CLEVEL. */ -static void ZSTD_CCtxParams_init_internal(ZSTD_CCtx_params* cctxParams, ZSTD_parameters const* params, int compressionLevel) +static void +ZSTD_CCtxParams_init_internal(ZSTD_CCtx_params* cctxParams, + const ZSTD_parameters* params, + int compressionLevel) { assert(!ZSTD_checkCParams(params->cParams)); ZSTD_memset(cctxParams, 0, sizeof(*cctxParams)); @@ -348,7 +389,13 @@ static void ZSTD_CCtxParams_init_internal(ZSTD_CCtx_params* cctxParams, ZSTD_par */ cctxParams->compressionLevel = compressionLevel; cctxParams->useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(cctxParams->useRowMatchFinder, ¶ms->cParams); - DEBUGLOG(4, "ZSTD_CCtxParams_init_internal: useRowMatchFinder=%d", cctxParams->useRowMatchFinder); + cctxParams->useBlockSplitter = ZSTD_resolveBlockSplitterMode(cctxParams->useBlockSplitter, ¶ms->cParams); + cctxParams->ldmParams.enableLdm = ZSTD_resolveEnableLdm(cctxParams->ldmParams.enableLdm, ¶ms->cParams); + cctxParams->validateSequences = ZSTD_resolveExternalSequenceValidation(cctxParams->validateSequences); + cctxParams->maxBlockSize = ZSTD_resolveMaxBlockSize(cctxParams->maxBlockSize); + cctxParams->searchForExternalRepcodes = ZSTD_resolveExternalRepcodeSearch(cctxParams->searchForExternalRepcodes, compressionLevel); + DEBUGLOG(4, "ZSTD_CCtxParams_init_internal: useRowMatchFinder=%d, useBlockSplitter=%d ldm=%d", + cctxParams->useRowMatchFinder, cctxParams->useBlockSplitter, cctxParams->ldmParams.enableLdm); } size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, ZSTD_parameters params) @@ -361,7 +408,7 @@ size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, ZSTD_paramete /** * Sets cctxParams' cParams and fParams from params, but otherwise leaves them alone. - * @param param Validated zstd parameters. + * @param params Validated zstd parameters. */ static void ZSTD_CCtxParams_setZstdParams( ZSTD_CCtx_params* cctxParams, const ZSTD_parameters* params) @@ -470,8 +517,8 @@ ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter param) return bounds; case ZSTD_c_enableLongDistanceMatching: - bounds.lowerBound = 0; - bounds.upperBound = 1; + bounds.lowerBound = (int)ZSTD_ps_auto; + bounds.upperBound = (int)ZSTD_ps_disable; return bounds; case ZSTD_c_ldmHashLog: @@ -518,9 +565,9 @@ ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter param) return bounds; case ZSTD_c_literalCompressionMode: - ZSTD_STATIC_ASSERT(ZSTD_lcm_auto < ZSTD_lcm_huffman && ZSTD_lcm_huffman < ZSTD_lcm_uncompressed); - bounds.lowerBound = ZSTD_lcm_auto; - bounds.upperBound = ZSTD_lcm_uncompressed; + ZSTD_STATIC_ASSERT(ZSTD_ps_auto < ZSTD_ps_enable && ZSTD_ps_enable < ZSTD_ps_disable); + bounds.lowerBound = (int)ZSTD_ps_auto; + bounds.upperBound = (int)ZSTD_ps_disable; return bounds; case ZSTD_c_targetCBlockSize: @@ -549,14 +596,14 @@ ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter param) bounds.upperBound = 1; return bounds; - case ZSTD_c_splitBlocks: - bounds.lowerBound = 0; - bounds.upperBound = 1; + case ZSTD_c_useBlockSplitter: + bounds.lowerBound = (int)ZSTD_ps_auto; + bounds.upperBound = (int)ZSTD_ps_disable; return bounds; case ZSTD_c_useRowMatchFinder: - bounds.lowerBound = (int)ZSTD_urm_auto; - bounds.upperBound = (int)ZSTD_urm_enableRowMatchFinder; + bounds.lowerBound = (int)ZSTD_ps_auto; + bounds.upperBound = (int)ZSTD_ps_disable; return bounds; case ZSTD_c_deterministicRefPrefix: @@ -564,6 +611,26 @@ ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter param) bounds.upperBound = 1; return bounds; + case ZSTD_c_prefetchCDictTables: + bounds.lowerBound = (int)ZSTD_ps_auto; + bounds.upperBound = (int)ZSTD_ps_disable; + return bounds; + + case ZSTD_c_enableSeqProducerFallback: + bounds.lowerBound = 0; + bounds.upperBound = 1; + return bounds; + + case ZSTD_c_maxBlockSize: + bounds.lowerBound = ZSTD_BLOCKSIZE_MAX_MIN; + bounds.upperBound = ZSTD_BLOCKSIZE_MAX; + return bounds; + + case ZSTD_c_searchForExternalRepcodes: + bounds.lowerBound = (int)ZSTD_ps_auto; + bounds.upperBound = (int)ZSTD_ps_disable; + return bounds; + default: bounds.error = ERROR(parameter_unsupported); return bounds; @@ -625,9 +692,13 @@ static int ZSTD_isUpdateAuthorized(ZSTD_cParameter param) case ZSTD_c_stableOutBuffer: case ZSTD_c_blockDelimiters: case ZSTD_c_validateSequences: - case ZSTD_c_splitBlocks: + case ZSTD_c_useBlockSplitter: case ZSTD_c_useRowMatchFinder: case ZSTD_c_deterministicRefPrefix: + case ZSTD_c_prefetchCDictTables: + case ZSTD_c_enableSeqProducerFallback: + case ZSTD_c_maxBlockSize: + case ZSTD_c_searchForExternalRepcodes: default: return 0; } @@ -640,7 +711,7 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, int value) if (ZSTD_isUpdateAuthorized(param)) { cctx->cParamsChanged = 1; } else { - RETURN_ERROR(stage_wrong, "can only set params in ctx init stage"); + RETURN_ERROR(stage_wrong, "can only set params in cctx init stage"); } } switch(param) @@ -680,9 +751,13 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, int value) case ZSTD_c_stableOutBuffer: case ZSTD_c_blockDelimiters: case ZSTD_c_validateSequences: - case ZSTD_c_splitBlocks: + case ZSTD_c_useBlockSplitter: case ZSTD_c_useRowMatchFinder: case ZSTD_c_deterministicRefPrefix: + case ZSTD_c_prefetchCDictTables: + case ZSTD_c_enableSeqProducerFallback: + case ZSTD_c_maxBlockSize: + case ZSTD_c_searchForExternalRepcodes: break; default: RETURN_ERROR(parameter_unsupported, "unknown parameter"); @@ -738,12 +813,12 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, case ZSTD_c_minMatch : if (value!=0) /* 0 => use default */ BOUNDCHECK(ZSTD_c_minMatch, value); - CCtxParams->cParams.minMatch = value; + CCtxParams->cParams.minMatch = (U32)value; return CCtxParams->cParams.minMatch; case ZSTD_c_targetLength : BOUNDCHECK(ZSTD_c_targetLength, value); - CCtxParams->cParams.targetLength = value; + CCtxParams->cParams.targetLength = (U32)value; return CCtxParams->cParams.targetLength; case ZSTD_c_strategy : @@ -756,12 +831,12 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, /* Content size written in frame header _when known_ (default:1) */ DEBUGLOG(4, "set content size flag = %u", (value!=0)); CCtxParams->fParams.contentSizeFlag = value != 0; - return CCtxParams->fParams.contentSizeFlag; + return (size_t)CCtxParams->fParams.contentSizeFlag; case ZSTD_c_checksumFlag : /* A 32-bits content checksum will be calculated and written at end of frame (default:0) */ CCtxParams->fParams.checksumFlag = value != 0; - return CCtxParams->fParams.checksumFlag; + return (size_t)CCtxParams->fParams.checksumFlag; case ZSTD_c_dictIDFlag : /* When applicable, dictionary's dictID is provided in frame header (default:1) */ DEBUGLOG(4, "set dictIDFlag = %u", (value!=0)); @@ -770,18 +845,18 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, case ZSTD_c_forceMaxWindow : CCtxParams->forceWindow = (value != 0); - return CCtxParams->forceWindow; + return (size_t)CCtxParams->forceWindow; case ZSTD_c_forceAttachDict : { const ZSTD_dictAttachPref_e pref = (ZSTD_dictAttachPref_e)value; - BOUNDCHECK(ZSTD_c_forceAttachDict, pref); + BOUNDCHECK(ZSTD_c_forceAttachDict, (int)pref); CCtxParams->attachDictPref = pref; return CCtxParams->attachDictPref; } case ZSTD_c_literalCompressionMode : { - const ZSTD_literalCompressionMode_e lcm = (ZSTD_literalCompressionMode_e)value; - BOUNDCHECK(ZSTD_c_literalCompressionMode, lcm); + const ZSTD_paramSwitch_e lcm = (ZSTD_paramSwitch_e)value; + BOUNDCHECK(ZSTD_c_literalCompressionMode, (int)lcm); CCtxParams->literalCompressionMode = lcm; return CCtxParams->literalCompressionMode; } @@ -832,47 +907,48 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, case ZSTD_c_enableDedicatedDictSearch : CCtxParams->enableDedicatedDictSearch = (value!=0); - return CCtxParams->enableDedicatedDictSearch; + return (size_t)CCtxParams->enableDedicatedDictSearch; case ZSTD_c_enableLongDistanceMatching : - CCtxParams->ldmParams.enableLdm = (value!=0); + BOUNDCHECK(ZSTD_c_enableLongDistanceMatching, value); + CCtxParams->ldmParams.enableLdm = (ZSTD_paramSwitch_e)value; return CCtxParams->ldmParams.enableLdm; case ZSTD_c_ldmHashLog : if (value!=0) /* 0 ==> auto */ BOUNDCHECK(ZSTD_c_ldmHashLog, value); - CCtxParams->ldmParams.hashLog = value; + CCtxParams->ldmParams.hashLog = (U32)value; return CCtxParams->ldmParams.hashLog; case ZSTD_c_ldmMinMatch : if (value!=0) /* 0 ==> default */ BOUNDCHECK(ZSTD_c_ldmMinMatch, value); - CCtxParams->ldmParams.minMatchLength = value; + CCtxParams->ldmParams.minMatchLength = (U32)value; return CCtxParams->ldmParams.minMatchLength; case ZSTD_c_ldmBucketSizeLog : if (value!=0) /* 0 ==> default */ BOUNDCHECK(ZSTD_c_ldmBucketSizeLog, value); - CCtxParams->ldmParams.bucketSizeLog = value; + CCtxParams->ldmParams.bucketSizeLog = (U32)value; return CCtxParams->ldmParams.bucketSizeLog; case ZSTD_c_ldmHashRateLog : - RETURN_ERROR_IF(value > ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN, - parameter_outOfBound, "Param out of bounds!"); - CCtxParams->ldmParams.hashRateLog = value; + if (value!=0) /* 0 ==> default */ + BOUNDCHECK(ZSTD_c_ldmHashRateLog, value); + CCtxParams->ldmParams.hashRateLog = (U32)value; return CCtxParams->ldmParams.hashRateLog; case ZSTD_c_targetCBlockSize : if (value!=0) /* 0 ==> default */ BOUNDCHECK(ZSTD_c_targetCBlockSize, value); - CCtxParams->targetCBlockSize = value; + CCtxParams->targetCBlockSize = (U32)value; return CCtxParams->targetCBlockSize; case ZSTD_c_srcSizeHint : if (value!=0) /* 0 ==> default */ BOUNDCHECK(ZSTD_c_srcSizeHint, value); CCtxParams->srcSizeHint = value; - return CCtxParams->srcSizeHint; + return (size_t)CCtxParams->srcSizeHint; case ZSTD_c_stableInBuffer: BOUNDCHECK(ZSTD_c_stableInBuffer, value); @@ -894,14 +970,14 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, CCtxParams->validateSequences = value; return CCtxParams->validateSequences; - case ZSTD_c_splitBlocks: - BOUNDCHECK(ZSTD_c_splitBlocks, value); - CCtxParams->splitBlocks = value; - return CCtxParams->splitBlocks; + case ZSTD_c_useBlockSplitter: + BOUNDCHECK(ZSTD_c_useBlockSplitter, value); + CCtxParams->useBlockSplitter = (ZSTD_paramSwitch_e)value; + return CCtxParams->useBlockSplitter; case ZSTD_c_useRowMatchFinder: BOUNDCHECK(ZSTD_c_useRowMatchFinder, value); - CCtxParams->useRowMatchFinder = (ZSTD_useRowMatchFinderMode_e)value; + CCtxParams->useRowMatchFinder = (ZSTD_paramSwitch_e)value; return CCtxParams->useRowMatchFinder; case ZSTD_c_deterministicRefPrefix: @@ -909,6 +985,27 @@ size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* CCtxParams, CCtxParams->deterministicRefPrefix = !!value; return CCtxParams->deterministicRefPrefix; + case ZSTD_c_prefetchCDictTables: + BOUNDCHECK(ZSTD_c_prefetchCDictTables, value); + CCtxParams->prefetchCDictTables = (ZSTD_paramSwitch_e)value; + return CCtxParams->prefetchCDictTables; + + case ZSTD_c_enableSeqProducerFallback: + BOUNDCHECK(ZSTD_c_enableSeqProducerFallback, value); + CCtxParams->enableMatchFinderFallback = value; + return CCtxParams->enableMatchFinderFallback; + + case ZSTD_c_maxBlockSize: + if (value!=0) /* 0 ==> default */ + BOUNDCHECK(ZSTD_c_maxBlockSize, value); + CCtxParams->maxBlockSize = value; + return CCtxParams->maxBlockSize; + + case ZSTD_c_searchForExternalRepcodes: + BOUNDCHECK(ZSTD_c_searchForExternalRepcodes, value); + CCtxParams->searchForExternalRepcodes = (ZSTD_paramSwitch_e)value; + return CCtxParams->searchForExternalRepcodes; + default: RETURN_ERROR(parameter_unsupported, "unknown parameter"); } } @@ -1032,8 +1129,8 @@ size_t ZSTD_CCtxParams_getParameter( case ZSTD_c_validateSequences : *value = (int)CCtxParams->validateSequences; break; - case ZSTD_c_splitBlocks : - *value = (int)CCtxParams->splitBlocks; + case ZSTD_c_useBlockSplitter : + *value = (int)CCtxParams->useBlockSplitter; break; case ZSTD_c_useRowMatchFinder : *value = (int)CCtxParams->useRowMatchFinder; @@ -1041,6 +1138,18 @@ size_t ZSTD_CCtxParams_getParameter( case ZSTD_c_deterministicRefPrefix: *value = (int)CCtxParams->deterministicRefPrefix; break; + case ZSTD_c_prefetchCDictTables: + *value = (int)CCtxParams->prefetchCDictTables; + break; + case ZSTD_c_enableSeqProducerFallback: + *value = CCtxParams->enableMatchFinderFallback; + break; + case ZSTD_c_maxBlockSize: + *value = (int)CCtxParams->maxBlockSize; + break; + case ZSTD_c_searchForExternalRepcodes: + *value = (int)CCtxParams->searchForExternalRepcodes; + break; default: RETURN_ERROR(parameter_unsupported, "unknown parameter"); } return 0; @@ -1067,9 +1176,47 @@ size_t ZSTD_CCtx_setParametersUsingCCtxParams( return 0; } -ZSTDLIB_API size_t ZSTD_CCtx_setPledgedSrcSize(ZSTD_CCtx* cctx, unsigned long long pledgedSrcSize) +size_t ZSTD_CCtx_setCParams(ZSTD_CCtx* cctx, ZSTD_compressionParameters cparams) +{ + ZSTD_STATIC_ASSERT(sizeof(cparams) == 7 * 4 /* all params are listed below */); + DEBUGLOG(4, "ZSTD_CCtx_setCParams"); + /* only update if all parameters are valid */ + FORWARD_IF_ERROR(ZSTD_checkCParams(cparams), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, cparams.windowLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_chainLog, cparams.chainLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_hashLog, cparams.hashLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_searchLog, cparams.searchLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_minMatch, cparams.minMatch), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_targetLength, cparams.targetLength), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_strategy, cparams.strategy), ""); + return 0; +} + +size_t ZSTD_CCtx_setFParams(ZSTD_CCtx* cctx, ZSTD_frameParameters fparams) { - DEBUGLOG(4, "ZSTD_CCtx_setPledgedSrcSize to %u bytes", (U32)pledgedSrcSize); + ZSTD_STATIC_ASSERT(sizeof(fparams) == 3 * 4 /* all params are listed below */); + DEBUGLOG(4, "ZSTD_CCtx_setFParams"); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_contentSizeFlag, fparams.contentSizeFlag != 0), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, fparams.checksumFlag != 0), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_dictIDFlag, fparams.noDictIDFlag == 0), ""); + return 0; +} + +size_t ZSTD_CCtx_setParams(ZSTD_CCtx* cctx, ZSTD_parameters params) +{ + DEBUGLOG(4, "ZSTD_CCtx_setParams"); + /* First check cParams, because we want to update all or none. */ + FORWARD_IF_ERROR(ZSTD_checkCParams(params.cParams), ""); + /* Next set fParams, because this could fail if the cctx isn't in init stage. */ + FORWARD_IF_ERROR(ZSTD_CCtx_setFParams(cctx, params.fParams), ""); + /* Finally set cParams, which should succeed. */ + FORWARD_IF_ERROR(ZSTD_CCtx_setCParams(cctx, params.cParams), ""); + return 0; +} + +size_t ZSTD_CCtx_setPledgedSrcSize(ZSTD_CCtx* cctx, unsigned long long pledgedSrcSize) +{ + DEBUGLOG(4, "ZSTD_CCtx_setPledgedSrcSize to %llu bytes", pledgedSrcSize); RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, "Can't set pledgedSrcSize when not in init stage."); cctx->pledgedSrcSizePlusOne = pledgedSrcSize+1; @@ -1085,9 +1232,9 @@ static void ZSTD_dedicatedDictSearch_revertCParams( ZSTD_compressionParameters* cParams); /** - * Initializes the local dict using the requested parameters. - * NOTE: This does not use the pledged src size, because it may be used for more - * than one compression. + * Initializes the local dictionary using requested parameters. + * NOTE: Initialization does not employ the pledged src size, + * because the dictionary may be used for multiple compressions. */ static size_t ZSTD_initLocalDict(ZSTD_CCtx* cctx) { @@ -1100,8 +1247,8 @@ static size_t ZSTD_initLocalDict(ZSTD_CCtx* cctx) return 0; } if (dl->cdict != NULL) { - assert(cctx->cdict == dl->cdict); /* Local dictionary already initialized. */ + assert(cctx->cdict == dl->cdict); return 0; } assert(dl->dictSize > 0); @@ -1121,40 +1268,44 @@ static size_t ZSTD_initLocalDict(ZSTD_CCtx* cctx) } size_t ZSTD_CCtx_loadDictionary_advanced( - ZSTD_CCtx* cctx, const void* dict, size_t dictSize, - ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType) + ZSTD_CCtx* cctx, + const void* dict, size_t dictSize, + ZSTD_dictLoadMethod_e dictLoadMethod, + ZSTD_dictContentType_e dictContentType) { - RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, - "Can't load a dictionary when ctx is not in init stage."); DEBUGLOG(4, "ZSTD_CCtx_loadDictionary_advanced (size: %u)", (U32)dictSize); - ZSTD_clearAllDicts(cctx); /* in case one already exists */ - if (dict == NULL || dictSize == 0) /* no dictionary mode */ + RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, + "Can't load a dictionary when cctx is not in init stage."); + ZSTD_clearAllDicts(cctx); /* erase any previously set dictionary */ + if (dict == NULL || dictSize == 0) /* no dictionary */ return 0; if (dictLoadMethod == ZSTD_dlm_byRef) { cctx->localDict.dict = dict; } else { + /* copy dictionary content inside CCtx to own its lifetime */ void* dictBuffer; RETURN_ERROR_IF(cctx->staticSize, memory_allocation, - "no malloc for static CCtx"); + "static CCtx can't allocate for an internal copy of dictionary"); dictBuffer = ZSTD_customMalloc(dictSize, cctx->customMem); - RETURN_ERROR_IF(!dictBuffer, memory_allocation, "NULL pointer!"); + RETURN_ERROR_IF(dictBuffer==NULL, memory_allocation, + "allocation failed for dictionary content"); ZSTD_memcpy(dictBuffer, dict, dictSize); - cctx->localDict.dictBuffer = dictBuffer; - cctx->localDict.dict = dictBuffer; + cctx->localDict.dictBuffer = dictBuffer; /* owned ptr to free */ + cctx->localDict.dict = dictBuffer; /* read-only reference */ } cctx->localDict.dictSize = dictSize; cctx->localDict.dictContentType = dictContentType; return 0; } -ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary_byReference( +size_t ZSTD_CCtx_loadDictionary_byReference( ZSTD_CCtx* cctx, const void* dict, size_t dictSize) { return ZSTD_CCtx_loadDictionary_advanced( cctx, dict, dictSize, ZSTD_dlm_byRef, ZSTD_dct_auto); } -ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, size_t dictSize) +size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, size_t dictSize) { return ZSTD_CCtx_loadDictionary_advanced( cctx, dict, dictSize, ZSTD_dlm_byCopy, ZSTD_dct_auto); @@ -1210,8 +1361,9 @@ size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset) if ( (reset == ZSTD_reset_parameters) || (reset == ZSTD_reset_session_and_parameters) ) { RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, - "Can't reset parameters only when not in init stage."); + "Reset parameters is only possible during init stage."); ZSTD_clearAllDicts(cctx); + ZSTD_memset(&cctx->externalMatchCtx, 0, sizeof(cctx->externalMatchCtx)); return ZSTD_CCtxParams_reset(&cctx->requestedParams); } return 0; @@ -1308,7 +1460,8 @@ static ZSTD_compressionParameters ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar, unsigned long long srcSize, size_t dictSize, - ZSTD_cParamMode_e mode) + ZSTD_cParamMode_e mode, + ZSTD_paramSwitch_e useRowMatchFinder) { const U64 minSrcSize = 513; /* (1<<9) + 1 */ const U64 maxWindowResize = 1ULL << (ZSTD_WINDOWLOG_MAX-1); @@ -1324,7 +1477,7 @@ ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar, break; case ZSTD_cpm_createCDict: /* Assume a small source size when creating a dictionary - * with an unkown source size. + * with an unknown source size. */ if (dictSize && srcSize == ZSTD_CONTENTSIZE_UNKNOWN) srcSize = minSrcSize; @@ -1342,8 +1495,8 @@ ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar, } /* resize windowLog if input is small enough, to use less memory */ - if ( (srcSize < maxWindowResize) - && (dictSize < maxWindowResize) ) { + if ( (srcSize <= maxWindowResize) + && (dictSize <= maxWindowResize) ) { U32 const tSize = (U32)(srcSize + dictSize); static U32 const hashSizeMin = 1 << ZSTD_HASHLOG_MIN; U32 const srcLog = (tSize < hashSizeMin) ? ZSTD_HASHLOG_MIN : @@ -1361,6 +1514,42 @@ ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar, if (cPar.windowLog < ZSTD_WINDOWLOG_ABSOLUTEMIN) cPar.windowLog = ZSTD_WINDOWLOG_ABSOLUTEMIN; /* minimum wlog required for valid frame header */ + /* We can't use more than 32 bits of hash in total, so that means that we require: + * (hashLog + 8) <= 32 && (chainLog + 8) <= 32 + */ + if (mode == ZSTD_cpm_createCDict && ZSTD_CDictIndicesAreTagged(&cPar)) { + U32 const maxShortCacheHashLog = 32 - ZSTD_SHORT_CACHE_TAG_BITS; + if (cPar.hashLog > maxShortCacheHashLog) { + cPar.hashLog = maxShortCacheHashLog; + } + if (cPar.chainLog > maxShortCacheHashLog) { + cPar.chainLog = maxShortCacheHashLog; + } + } + + + /* At this point, we aren't 100% sure if we are using the row match finder. + * Unless it is explicitly disabled, conservatively assume that it is enabled. + * In this case it will only be disabled for small sources, so shrinking the + * hash log a little bit shouldn't result in any ratio loss. + */ + if (useRowMatchFinder == ZSTD_ps_auto) + useRowMatchFinder = ZSTD_ps_enable; + + /* We can't hash more than 32-bits in total. So that means that we require: + * (hashLog - rowLog + 8) <= 32 + */ + if (ZSTD_rowMatchFinderUsed(cPar.strategy, useRowMatchFinder)) { + /* Switch to 32-entry rows if searchLog is 5 (or more) */ + U32 const rowLog = BOUNDED(4, cPar.searchLog, 6); + U32 const maxRowHashLog = 32 - ZSTD_ROW_HASH_TAG_BITS; + U32 const maxHashLog = maxRowHashLog + rowLog; + assert(cPar.hashLog >= rowLog); + if (cPar.hashLog > maxHashLog) { + cPar.hashLog = maxHashLog; + } + } + return cPar; } @@ -1371,7 +1560,7 @@ ZSTD_adjustCParams(ZSTD_compressionParameters cPar, { cPar = ZSTD_clampCParams(cPar); /* resulting cPar is necessarily valid (all parameters within range) */ if (srcSize == 0) srcSize = ZSTD_CONTENTSIZE_UNKNOWN; - return ZSTD_adjustCParams_internal(cPar, srcSize, dictSize, ZSTD_cpm_unknown); + return ZSTD_adjustCParams_internal(cPar, srcSize, dictSize, ZSTD_cpm_unknown, ZSTD_ps_auto); } static ZSTD_compressionParameters ZSTD_getCParams_internal(int compressionLevel, unsigned long long srcSizeHint, size_t dictSize, ZSTD_cParamMode_e mode); @@ -1398,16 +1587,16 @@ ZSTD_compressionParameters ZSTD_getCParamsFromCCtxParams( srcSizeHint = CCtxParams->srcSizeHint; } cParams = ZSTD_getCParams_internal(CCtxParams->compressionLevel, srcSizeHint, dictSize, mode); - if (CCtxParams->ldmParams.enableLdm) cParams.windowLog = ZSTD_LDM_DEFAULT_WINDOW_LOG; + if (CCtxParams->ldmParams.enableLdm == ZSTD_ps_enable) cParams.windowLog = ZSTD_LDM_DEFAULT_WINDOW_LOG; ZSTD_overrideCParams(&cParams, &CCtxParams->cParams); assert(!ZSTD_checkCParams(cParams)); /* srcSizeHint == 0 means 0 */ - return ZSTD_adjustCParams_internal(cParams, srcSizeHint, dictSize, mode); + return ZSTD_adjustCParams_internal(cParams, srcSizeHint, dictSize, mode, CCtxParams->useRowMatchFinder); } static size_t ZSTD_sizeof_matchState(const ZSTD_compressionParameters* const cParams, - const ZSTD_useRowMatchFinderMode_e useRowMatchFinder, + const ZSTD_paramSwitch_e useRowMatchFinder, const U32 enableDedicatedDictSearch, const U32 forCCtx) { @@ -1431,7 +1620,7 @@ ZSTD_sizeof_matchState(const ZSTD_compressionParameters* const cParams, + ZSTD_cwksp_aligned_alloc_size((ZSTD_OPT_NUM+1) * sizeof(ZSTD_match_t)) + ZSTD_cwksp_aligned_alloc_size((ZSTD_OPT_NUM+1) * sizeof(ZSTD_optimal_t)); size_t const lazyAdditionalSpace = ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder) - ? ZSTD_cwksp_aligned_alloc_size(hSize*sizeof(U16)) + ? ZSTD_cwksp_aligned_alloc_size(hSize) : 0; size_t const optSpace = (forCCtx && (cParams->strategy >= ZSTD_btopt)) ? optPotentialSpace @@ -1440,26 +1629,34 @@ ZSTD_sizeof_matchState(const ZSTD_compressionParameters* const cParams, /* tables are guaranteed to be sized in multiples of 64 bytes (or 16 uint32_t) */ ZSTD_STATIC_ASSERT(ZSTD_HASHLOG_MIN >= 4 && ZSTD_WINDOWLOG_MIN >= 4 && ZSTD_CHAINLOG_MIN >= 4); - assert(useRowMatchFinder != ZSTD_urm_auto); + assert(useRowMatchFinder != ZSTD_ps_auto); DEBUGLOG(4, "chainSize: %u - hSize: %u - h3Size: %u", (U32)chainSize, (U32)hSize, (U32)h3Size); return tableSpace + optSpace + slackSpace + lazyAdditionalSpace; } +/* Helper function for calculating memory requirements. + * Gives a tighter bound than ZSTD_sequenceBound() by taking minMatch into account. */ +static size_t ZSTD_maxNbSeq(size_t blockSize, unsigned minMatch, int useSequenceProducer) { + U32 const divider = (minMatch==3 || useSequenceProducer) ? 3 : 4; + return blockSize / divider; +} + static size_t ZSTD_estimateCCtxSize_usingCCtxParams_internal( const ZSTD_compressionParameters* cParams, const ldmParams_t* ldmParams, const int isStatic, - const ZSTD_useRowMatchFinderMode_e useRowMatchFinder, + const ZSTD_paramSwitch_e useRowMatchFinder, const size_t buffInSize, const size_t buffOutSize, - const U64 pledgedSrcSize) + const U64 pledgedSrcSize, + int useSequenceProducer, + size_t maxBlockSize) { - size_t const windowSize = MAX(1, (size_t)MIN(((U64)1 << cParams->windowLog), pledgedSrcSize)); - size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, windowSize); - U32 const divider = (cParams->minMatch==3) ? 3 : 4; - size_t const maxNbSeq = blockSize / divider; + size_t const windowSize = (size_t) BOUNDED(1ULL, 1ULL << cParams->windowLog, pledgedSrcSize); + size_t const blockSize = MIN(ZSTD_resolveMaxBlockSize(maxBlockSize), windowSize); + size_t const maxNbSeq = ZSTD_maxNbSeq(blockSize, cParams->minMatch, useSequenceProducer); size_t const tokenSpace = ZSTD_cwksp_alloc_size(WILDCOPY_OVERLENGTH + blockSize) + ZSTD_cwksp_aligned_alloc_size(maxNbSeq * sizeof(seqDef)) + 3 * ZSTD_cwksp_alloc_size(maxNbSeq * sizeof(BYTE)); @@ -1469,7 +1666,7 @@ static size_t ZSTD_estimateCCtxSize_usingCCtxParams_internal( size_t const ldmSpace = ZSTD_ldm_getTableSize(*ldmParams); size_t const maxNbLdmSeq = ZSTD_ldm_getMaxNbSeq(*ldmParams, blockSize); - size_t const ldmSeqSpace = ldmParams->enableLdm ? + size_t const ldmSeqSpace = ldmParams->enableLdm == ZSTD_ps_enable ? ZSTD_cwksp_aligned_alloc_size(maxNbLdmSeq * sizeof(rawSeq)) : 0; @@ -1478,6 +1675,11 @@ static size_t ZSTD_estimateCCtxSize_usingCCtxParams_internal( size_t const cctxSpace = isStatic ? ZSTD_cwksp_alloc_size(sizeof(ZSTD_CCtx)) : 0; + size_t const maxNbExternalSeq = ZSTD_sequenceBound(blockSize); + size_t const externalSeqSpace = useSequenceProducer + ? ZSTD_cwksp_aligned_alloc_size(maxNbExternalSeq * sizeof(ZSTD_Sequence)) + : 0; + size_t const neededSpace = cctxSpace + entropySpace + @@ -1486,7 +1688,8 @@ static size_t ZSTD_estimateCCtxSize_usingCCtxParams_internal( ldmSeqSpace + matchStateSize + tokenSpace + - bufferSpace; + bufferSpace + + externalSeqSpace; DEBUGLOG(5, "estimate workspace : %u", (U32)neededSpace); return neededSpace; @@ -1496,15 +1699,15 @@ size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params) { ZSTD_compressionParameters const cParams = ZSTD_getCParamsFromCCtxParams(params, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_cpm_noAttachDict); - ZSTD_useRowMatchFinderMode_e const useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(params->useRowMatchFinder, - &cParams); + ZSTD_paramSwitch_e const useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(params->useRowMatchFinder, + &cParams); RETURN_ERROR_IF(params->nbWorkers > 0, GENERIC, "Estimate CCtx size is supported for single-threaded compression only."); /* estimateCCtxSize is for one-shot compression. So no buffers should * be needed. However, we still allocate two 0-sized buffers, which can * take space under ASAN. */ return ZSTD_estimateCCtxSize_usingCCtxParams_internal( - &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, 0, 0, ZSTD_CONTENTSIZE_UNKNOWN); + &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, 0, 0, ZSTD_CONTENTSIZE_UNKNOWN, params->useSequenceProducer, params->maxBlockSize); } size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams) @@ -1514,9 +1717,9 @@ size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams) /* Pick bigger of not using and using row-based matchfinder for greedy and lazy strategies */ size_t noRowCCtxSize; size_t rowCCtxSize; - initialParams.useRowMatchFinder = ZSTD_urm_disableRowMatchFinder; + initialParams.useRowMatchFinder = ZSTD_ps_disable; noRowCCtxSize = ZSTD_estimateCCtxSize_usingCCtxParams(&initialParams); - initialParams.useRowMatchFinder = ZSTD_urm_enableRowMatchFinder; + initialParams.useRowMatchFinder = ZSTD_ps_enable; rowCCtxSize = ZSTD_estimateCCtxSize_usingCCtxParams(&initialParams); return MAX(noRowCCtxSize, rowCCtxSize); } else { @@ -1554,18 +1757,18 @@ size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params) RETURN_ERROR_IF(params->nbWorkers > 0, GENERIC, "Estimate CCtx size is supported for single-threaded compression only."); { ZSTD_compressionParameters const cParams = ZSTD_getCParamsFromCCtxParams(params, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_cpm_noAttachDict); - size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, (size_t)1 << cParams.windowLog); + size_t const blockSize = MIN(ZSTD_resolveMaxBlockSize(params->maxBlockSize), (size_t)1 << cParams.windowLog); size_t const inBuffSize = (params->inBufferMode == ZSTD_bm_buffered) ? ((size_t)1 << cParams.windowLog) + blockSize : 0; size_t const outBuffSize = (params->outBufferMode == ZSTD_bm_buffered) ? ZSTD_compressBound(blockSize) + 1 : 0; - ZSTD_useRowMatchFinderMode_e const useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(params->useRowMatchFinder, ¶ms->cParams); + ZSTD_paramSwitch_e const useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(params->useRowMatchFinder, ¶ms->cParams); return ZSTD_estimateCCtxSize_usingCCtxParams_internal( &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, inBuffSize, outBuffSize, - ZSTD_CONTENTSIZE_UNKNOWN); + ZSTD_CONTENTSIZE_UNKNOWN, params->useSequenceProducer, params->maxBlockSize); } } @@ -1576,9 +1779,9 @@ size_t ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams) /* Pick bigger of not using and using row-based matchfinder for greedy and lazy strategies */ size_t noRowCCtxSize; size_t rowCCtxSize; - initialParams.useRowMatchFinder = ZSTD_urm_disableRowMatchFinder; + initialParams.useRowMatchFinder = ZSTD_ps_disable; noRowCCtxSize = ZSTD_estimateCStreamSize_usingCCtxParams(&initialParams); - initialParams.useRowMatchFinder = ZSTD_urm_enableRowMatchFinder; + initialParams.useRowMatchFinder = ZSTD_ps_enable; rowCCtxSize = ZSTD_estimateCStreamSize_usingCCtxParams(&initialParams); return MAX(noRowCCtxSize, rowCCtxSize); } else { @@ -1708,12 +1911,25 @@ typedef enum { ZSTD_resetTarget_CCtx } ZSTD_resetTarget_e; +/* Mixes bits in a 64 bits in a value, based on XXH3_rrmxmx */ +static U64 ZSTD_bitmix(U64 val, U64 len) { + val ^= ZSTD_rotateRight_U64(val, 49) ^ ZSTD_rotateRight_U64(val, 24); + val *= 0x9FB21C651E98DF25ULL; + val ^= (val >> 35) + len ; + val *= 0x9FB21C651E98DF25ULL; + return val ^ (val >> 28); +} + +/* Mixes in the hashSalt and hashSaltEntropy to create a new hashSalt */ +static void ZSTD_advanceHashSalt(ZSTD_matchState_t* ms) { + ms->hashSalt = ZSTD_bitmix(ms->hashSalt, 8) ^ ZSTD_bitmix((U64) ms->hashSaltEntropy, 4); +} static size_t ZSTD_reset_matchState(ZSTD_matchState_t* ms, ZSTD_cwksp* ws, const ZSTD_compressionParameters* cParams, - const ZSTD_useRowMatchFinderMode_e useRowMatchFinder, + const ZSTD_paramSwitch_e useRowMatchFinder, const ZSTD_compResetPolicy_e crp, const ZSTD_indexResetPolicy_e forceResetIndex, const ZSTD_resetTarget_e forWho) @@ -1728,13 +1944,14 @@ ZSTD_reset_matchState(ZSTD_matchState_t* ms, size_t const h3Size = hashLog3 ? ((size_t)1) << hashLog3 : 0; DEBUGLOG(4, "reset indices : %u", forceResetIndex == ZSTDirp_reset); - assert(useRowMatchFinder != ZSTD_urm_auto); + assert(useRowMatchFinder != ZSTD_ps_auto); if (forceResetIndex == ZSTDirp_reset) { ZSTD_window_init(&ms->window); ZSTD_cwksp_mark_tables_dirty(ws); } ms->hashLog3 = hashLog3; + ms->lazySkipping = 0; ZSTD_invalidateMatchState(ms); @@ -1756,6 +1973,27 @@ ZSTD_reset_matchState(ZSTD_matchState_t* ms, ZSTD_cwksp_clean_tables(ws); } + if (ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)) { + /* Row match finder needs an additional table of hashes ("tags") */ + size_t const tagTableSize = hSize; + /* We want to generate a new salt in case we reset a Cctx, but we always want to use + * 0 when we reset a Cdict */ + if(forWho == ZSTD_resetTarget_CCtx) { + ms->tagTable = (BYTE*) ZSTD_cwksp_reserve_aligned_init_once(ws, tagTableSize); + ZSTD_advanceHashSalt(ms); + } else { + /* When we are not salting we want to always memset the memory */ + ms->tagTable = (BYTE*) ZSTD_cwksp_reserve_aligned(ws, tagTableSize); + ZSTD_memset(ms->tagTable, 0, tagTableSize); + ms->hashSalt = 0; + } + { /* Switch to 32-entry rows if searchLog is 5 (or more) */ + U32 const rowLog = BOUNDED(4, cParams->searchLog, 6); + assert(cParams->hashLog >= rowLog); + ms->rowHashLog = cParams->hashLog - rowLog; + } + } + /* opt parser space */ if ((forWho == ZSTD_resetTarget_CCtx) && (cParams->strategy >= ZSTD_btopt)) { DEBUGLOG(4, "reserving optimal parser space"); @@ -1767,19 +2005,6 @@ ZSTD_reset_matchState(ZSTD_matchState_t* ms, ms->opt.priceTable = (ZSTD_optimal_t*)ZSTD_cwksp_reserve_aligned(ws, (ZSTD_OPT_NUM+1) * sizeof(ZSTD_optimal_t)); } - if (ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)) { - { /* Row match finder needs an additional table of hashes ("tags") */ - size_t const tagTableSize = hSize*sizeof(U16); - ms->tagTable = (U16*)ZSTD_cwksp_reserve_aligned(ws, tagTableSize); - if (ms->tagTable) ZSTD_memset(ms->tagTable, 0, tagTableSize); - } - { /* Switch to 32-entry rows if searchLog is 5 (or more) */ - U32 const rowLog = cParams->searchLog < 5 ? 4 : 5; - assert(cParams->hashLog > rowLog); - ms->rowHashLog = cParams->hashLog - rowLog; - } - } - ms->cParams = *cParams; RETURN_ERROR_IF(ZSTD_cwksp_reserve_failed(ws), memory_allocation, @@ -1824,8 +2049,8 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, ZSTD_buffered_policy_e const zbuff) { ZSTD_cwksp* const ws = &zc->workspace; - DEBUGLOG(4, "ZSTD_resetCCtx_internal: pledgedSrcSize=%u, wlog=%u, useRowMatchFinder=%d", - (U32)pledgedSrcSize, params->cParams.windowLog, (int)params->useRowMatchFinder); + DEBUGLOG(4, "ZSTD_resetCCtx_internal: pledgedSrcSize=%u, wlog=%u, useRowMatchFinder=%d useBlockSplitter=%d", + (U32)pledgedSrcSize, params->cParams.windowLog, (int)params->useRowMatchFinder, (int)params->useBlockSplitter); assert(!ZSTD_isError(ZSTD_checkCParams(params->cParams))); zc->isFirstBlock = 1; @@ -1836,8 +2061,11 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, zc->appliedParams = *params; params = &zc->appliedParams; - assert(params->useRowMatchFinder != ZSTD_urm_auto); - if (params->ldmParams.enableLdm) { + assert(params->useRowMatchFinder != ZSTD_ps_auto); + assert(params->useBlockSplitter != ZSTD_ps_auto); + assert(params->ldmParams.enableLdm != ZSTD_ps_auto); + assert(params->maxBlockSize != 0); + if (params->ldmParams.enableLdm == ZSTD_ps_enable) { /* Adjust long distance matching parameters */ ZSTD_ldm_adjustParameters(&zc->appliedParams.ldmParams, ¶ms->cParams); assert(params->ldmParams.hashLog >= params->ldmParams.bucketSizeLog); @@ -1845,9 +2073,8 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, } { size_t const windowSize = MAX(1, (size_t)MIN(((U64)1 << params->cParams.windowLog), pledgedSrcSize)); - size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, windowSize); - U32 const divider = (params->cParams.minMatch==3) ? 3 : 4; - size_t const maxNbSeq = blockSize / divider; + size_t const blockSize = MIN(params->maxBlockSize, windowSize); + size_t const maxNbSeq = ZSTD_maxNbSeq(blockSize, params->cParams.minMatch, params->useSequenceProducer); size_t const buffOutSize = (zbuff == ZSTDb_buffered && params->outBufferMode == ZSTD_bm_buffered) ? ZSTD_compressBound(blockSize) + 1 : 0; @@ -1864,7 +2091,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const neededSpace = ZSTD_estimateCCtxSize_usingCCtxParams_internal( ¶ms->cParams, ¶ms->ldmParams, zc->staticSize != 0, params->useRowMatchFinder, - buffInSize, buffOutSize, pledgedSrcSize); + buffInSize, buffOutSize, pledgedSrcSize, params->useSequenceProducer, params->maxBlockSize); int resizeWorkspace; FORWARD_IF_ERROR(neededSpace, "cctx size estimate failed!"); @@ -1900,13 +2127,14 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, zc->blockState.nextCBlock = (ZSTD_compressedBlockState_t*) ZSTD_cwksp_reserve_object(ws, sizeof(ZSTD_compressedBlockState_t)); RETURN_ERROR_IF(zc->blockState.nextCBlock == NULL, memory_allocation, "couldn't allocate nextCBlock"); zc->entropyWorkspace = (U32*) ZSTD_cwksp_reserve_object(ws, ENTROPY_WORKSPACE_SIZE); - RETURN_ERROR_IF(zc->blockState.nextCBlock == NULL, memory_allocation, "couldn't allocate entropyWorkspace"); + RETURN_ERROR_IF(zc->entropyWorkspace == NULL, memory_allocation, "couldn't allocate entropyWorkspace"); } } ZSTD_cwksp_clear(ws); /* init params */ zc->blockState.matchState.cParams = params->cParams; + zc->blockState.matchState.prefetchCDictTables = params->prefetchCDictTables == ZSTD_ps_enable; zc->pledgedSrcSizePlusOne = pledgedSrcSize+1; zc->consumedSrcSize = 0; zc->producedCSize = 0; @@ -1923,13 +2151,46 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, ZSTD_reset_compressedBlockState(zc->blockState.prevCBlock); + FORWARD_IF_ERROR(ZSTD_reset_matchState( + &zc->blockState.matchState, + ws, + ¶ms->cParams, + params->useRowMatchFinder, + crp, + needsIndexReset, + ZSTD_resetTarget_CCtx), ""); + + zc->seqStore.sequencesStart = (seqDef*)ZSTD_cwksp_reserve_aligned(ws, maxNbSeq * sizeof(seqDef)); + + /* ldm hash table */ + if (params->ldmParams.enableLdm == ZSTD_ps_enable) { + /* TODO: avoid memset? */ + size_t const ldmHSize = ((size_t)1) << params->ldmParams.hashLog; + zc->ldmState.hashTable = (ldmEntry_t*)ZSTD_cwksp_reserve_aligned(ws, ldmHSize * sizeof(ldmEntry_t)); + ZSTD_memset(zc->ldmState.hashTable, 0, ldmHSize * sizeof(ldmEntry_t)); + zc->ldmSequences = (rawSeq*)ZSTD_cwksp_reserve_aligned(ws, maxNbLdmSeq * sizeof(rawSeq)); + zc->maxNbLdmSequences = maxNbLdmSeq; + + ZSTD_window_init(&zc->ldmState.window); + zc->ldmState.loadedDictEnd = 0; + } + + /* reserve space for block-level external sequences */ + if (params->useSequenceProducer) { + size_t const maxNbExternalSeq = ZSTD_sequenceBound(blockSize); + zc->externalMatchCtx.seqBufferCapacity = maxNbExternalSeq; + zc->externalMatchCtx.seqBuffer = + (ZSTD_Sequence*)ZSTD_cwksp_reserve_aligned(ws, maxNbExternalSeq * sizeof(ZSTD_Sequence)); + } + + /* buffers */ + /* ZSTD_wildcopy() is used to copy into the literals buffer, * so we have to oversize the buffer by WILDCOPY_OVERLENGTH bytes. */ zc->seqStore.litStart = ZSTD_cwksp_reserve_buffer(ws, blockSize + WILDCOPY_OVERLENGTH); zc->seqStore.maxNbLit = blockSize; - /* buffers */ zc->bufferedPolicy = zbuff; zc->inBuffSize = buffInSize; zc->inBuff = (char*)ZSTD_cwksp_reserve_buffer(ws, buffInSize); @@ -1937,7 +2198,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, zc->outBuff = (char*)ZSTD_cwksp_reserve_buffer(ws, buffOutSize); /* ldm bucketOffsets table */ - if (params->ldmParams.enableLdm) { + if (params->ldmParams.enableLdm == ZSTD_ps_enable) { /* TODO: avoid memset? */ size_t const numBuckets = ((size_t)1) << (params->ldmParams.hashLog - @@ -1952,32 +2213,9 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, zc->seqStore.llCode = ZSTD_cwksp_reserve_buffer(ws, maxNbSeq * sizeof(BYTE)); zc->seqStore.mlCode = ZSTD_cwksp_reserve_buffer(ws, maxNbSeq * sizeof(BYTE)); zc->seqStore.ofCode = ZSTD_cwksp_reserve_buffer(ws, maxNbSeq * sizeof(BYTE)); - zc->seqStore.sequencesStart = (seqDef*)ZSTD_cwksp_reserve_aligned(ws, maxNbSeq * sizeof(seqDef)); - - FORWARD_IF_ERROR(ZSTD_reset_matchState( - &zc->blockState.matchState, - ws, - ¶ms->cParams, - params->useRowMatchFinder, - crp, - needsIndexReset, - ZSTD_resetTarget_CCtx), ""); - /* ldm hash table */ - if (params->ldmParams.enableLdm) { - /* TODO: avoid memset? */ - size_t const ldmHSize = ((size_t)1) << params->ldmParams.hashLog; - zc->ldmState.hashTable = (ldmEntry_t*)ZSTD_cwksp_reserve_aligned(ws, ldmHSize * sizeof(ldmEntry_t)); - ZSTD_memset(zc->ldmState.hashTable, 0, ldmHSize * sizeof(ldmEntry_t)); - zc->ldmSequences = (rawSeq*)ZSTD_cwksp_reserve_aligned(ws, maxNbLdmSeq * sizeof(rawSeq)); - zc->maxNbLdmSequences = maxNbLdmSeq; - - ZSTD_window_init(&zc->ldmState.window); - zc->ldmState.loadedDictEnd = 0; - } - - assert(ZSTD_cwksp_estimated_space_within_bounds(ws, neededSpace, resizeWorkspace)); DEBUGLOG(3, "wksp: finished allocating, %zd bytes remain available", ZSTD_cwksp_available_space(ws)); + assert(ZSTD_cwksp_estimated_space_within_bounds(ws, neededSpace)); zc->initialized = 1; @@ -2049,7 +2287,8 @@ ZSTD_resetCCtx_byAttachingCDict(ZSTD_CCtx* cctx, } params.cParams = ZSTD_adjustCParams_internal(adjusted_cdict_cParams, pledgedSrcSize, - cdict->dictContentSize, ZSTD_cpm_attachDict); + cdict->dictContentSize, ZSTD_cpm_attachDict, + params.useRowMatchFinder); params.cParams.windowLog = windowLog; params.useRowMatchFinder = cdict->useRowMatchFinder; /* cdict overrides */ FORWARD_IF_ERROR(ZSTD_resetCCtx_internal(cctx, ¶ms, pledgedSrcSize, @@ -2088,6 +2327,22 @@ ZSTD_resetCCtx_byAttachingCDict(ZSTD_CCtx* cctx, return 0; } +static void ZSTD_copyCDictTableIntoCCtx(U32* dst, U32 const* src, size_t tableSize, + ZSTD_compressionParameters const* cParams) { + if (ZSTD_CDictIndicesAreTagged(cParams)){ + /* Remove tags from the CDict table if they are present. + * See docs on "short cache" in zstd_compress_internal.h for context. */ + size_t i; + for (i = 0; i < tableSize; i++) { + U32 const taggedIndex = src[i]; + U32 const index = taggedIndex >> ZSTD_SHORT_CACHE_TAG_BITS; + dst[i] = index; + } + } else { + ZSTD_memcpy(dst, src, tableSize * sizeof(U32)); + } +} + static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict, ZSTD_CCtx_params params, @@ -2115,7 +2370,7 @@ static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx, } ZSTD_cwksp_mark_tables_dirty(&cctx->workspace); - assert(params.useRowMatchFinder != ZSTD_urm_auto); + assert(params.useRowMatchFinder != ZSTD_ps_auto); /* copy tables */ { size_t const chainSize = ZSTD_allocateChainTable(cdict_cParams->strategy, cdict->useRowMatchFinder, 0 /* DDS guaranteed disabled */) @@ -2123,21 +2378,23 @@ static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx, : 0; size_t const hSize = (size_t)1 << cdict_cParams->hashLog; - ZSTD_memcpy(cctx->blockState.matchState.hashTable, - cdict->matchState.hashTable, - hSize * sizeof(U32)); + ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.hashTable, + cdict->matchState.hashTable, + hSize, cdict_cParams); + /* Do not copy cdict's chainTable if cctx has parameters such that it would not use chainTable */ if (ZSTD_allocateChainTable(cctx->appliedParams.cParams.strategy, cctx->appliedParams.useRowMatchFinder, 0 /* forDDSDict */)) { - ZSTD_memcpy(cctx->blockState.matchState.chainTable, - cdict->matchState.chainTable, - chainSize * sizeof(U32)); + ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.chainTable, + cdict->matchState.chainTable, + chainSize, cdict_cParams); } /* copy tag table */ if (ZSTD_rowMatchFinderUsed(cdict_cParams->strategy, cdict->useRowMatchFinder)) { - size_t const tagTableSize = hSize*sizeof(U16); + size_t const tagTableSize = hSize; ZSTD_memcpy(cctx->blockState.matchState.tagTable, - cdict->matchState.tagTable, - tagTableSize); + cdict->matchState.tagTable, + tagTableSize); + cctx->blockState.matchState.hashSalt = cdict->matchState.hashSalt; } } @@ -2209,9 +2466,14 @@ static size_t ZSTD_copyCCtx_internal(ZSTD_CCtx* dstCCtx, { ZSTD_CCtx_params params = dstCCtx->requestedParams; /* Copy only compression parameters related to tables. */ params.cParams = srcCCtx->appliedParams.cParams; - assert(srcCCtx->appliedParams.useRowMatchFinder != ZSTD_urm_auto); + assert(srcCCtx->appliedParams.useRowMatchFinder != ZSTD_ps_auto); + assert(srcCCtx->appliedParams.useBlockSplitter != ZSTD_ps_auto); + assert(srcCCtx->appliedParams.ldmParams.enableLdm != ZSTD_ps_auto); params.useRowMatchFinder = srcCCtx->appliedParams.useRowMatchFinder; + params.useBlockSplitter = srcCCtx->appliedParams.useBlockSplitter; + params.ldmParams = srcCCtx->appliedParams.ldmParams; params.fParams = fParams; + params.maxBlockSize = srcCCtx->appliedParams.maxBlockSize; ZSTD_resetCCtx_internal(dstCCtx, ¶ms, pledgedSrcSize, /* loadedDictSize */ 0, ZSTDcrp_leaveDirty, zbuff); @@ -2296,6 +2558,8 @@ ZSTD_reduceTable_internal (U32* const table, U32 const size, U32 const reducerVa int const nbRows = (int)size / ZSTD_ROWSIZE; int cellNb = 0; int rowNb; + /* Protect special index values < ZSTD_WINDOW_START_INDEX. */ + U32 const reducerThreshold = reducerValue + ZSTD_WINDOW_START_INDEX; assert((size & (ZSTD_ROWSIZE-1)) == 0); /* multiple of ZSTD_ROWSIZE */ assert(size < (1U<<31)); /* can be casted to int */ @@ -2315,12 +2579,17 @@ ZSTD_reduceTable_internal (U32* const table, U32 const size, U32 const reducerVa for (rowNb=0 ; rowNb < nbRows ; rowNb++) { int column; for (column=0; columnsequencesStart; BYTE* const llCodeTable = seqStorePtr->llCode; @@ -2372,18 +2641,24 @@ void ZSTD_seqToCodes(const seqStore_t* seqStorePtr) BYTE* const mlCodeTable = seqStorePtr->mlCode; U32 const nbSeq = (U32)(seqStorePtr->sequences - seqStorePtr->sequencesStart); U32 u; + int longOffsets = 0; assert(nbSeq <= seqStorePtr->maxNbSeq); for (u=0; u= STREAM_ACCUMULATOR_MIN)); + if (MEM_32bits() && ofCode >= STREAM_ACCUMULATOR_MIN) + longOffsets = 1; } if (seqStorePtr->longLengthType==ZSTD_llt_literalLength) llCodeTable[seqStorePtr->longLengthPos] = MaxLL; if (seqStorePtr->longLengthType==ZSTD_llt_matchLength) mlCodeTable[seqStorePtr->longLengthPos] = MaxML; + return longOffsets; } /* ZSTD_useTargetCBlockSize(): @@ -2399,11 +2674,13 @@ static int ZSTD_useTargetCBlockSize(const ZSTD_CCtx_params* cctxParams) /* ZSTD_blockSplitterEnabled(): * Returns if block splitting param is being used * If used, compression will do best effort to split a block in order to improve compression ratio. + * At the time this function is called, the parameter must be finalized. * Returns 1 if true, 0 otherwise. */ static int ZSTD_blockSplitterEnabled(ZSTD_CCtx_params* cctxParams) { - DEBUGLOG(5, "ZSTD_blockSplitterEnabled(splitBlocks=%d)", cctxParams->splitBlocks); - return (cctxParams->splitBlocks != 0); + DEBUGLOG(5, "ZSTD_blockSplitterEnabled (useBlockSplitter=%d)", cctxParams->useBlockSplitter); + assert(cctxParams->useBlockSplitter != ZSTD_ps_auto); + return (cctxParams->useBlockSplitter == ZSTD_ps_enable); } /* Type returned by ZSTD_buildSequencesStatistics containing finalized symbol encoding types @@ -2415,6 +2692,7 @@ typedef struct { U32 MLtype; size_t size; size_t lastCountSize; /* Accounts for bug in 1.3.4. More detail in ZSTD_entropyCompressSeqStore_internal() */ + int longOffsets; } ZSTD_symbolEncodingTypeStats_t; /* ZSTD_buildSequencesStatistics(): @@ -2425,11 +2703,13 @@ typedef struct { * entropyWkspSize must be of size at least ENTROPY_WORKSPACE_SIZE - (MaxSeq + 1)*sizeof(U32) */ static ZSTD_symbolEncodingTypeStats_t -ZSTD_buildSequencesStatistics(seqStore_t* seqStorePtr, size_t nbSeq, - const ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy, - BYTE* dst, const BYTE* const dstEnd, - ZSTD_strategy strategy, unsigned* countWorkspace, - void* entropyWorkspace, size_t entropyWkspSize) { +ZSTD_buildSequencesStatistics( + const seqStore_t* seqStorePtr, size_t nbSeq, + const ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy, + BYTE* dst, const BYTE* const dstEnd, + ZSTD_strategy strategy, unsigned* countWorkspace, + void* entropyWorkspace, size_t entropyWkspSize) +{ BYTE* const ostart = dst; const BYTE* const oend = dstEnd; BYTE* op = ostart; @@ -2443,7 +2723,7 @@ ZSTD_buildSequencesStatistics(seqStore_t* seqStorePtr, size_t nbSeq, stats.lastCountSize = 0; /* convert length/distances into codes */ - ZSTD_seqToCodes(seqStorePtr); + stats.longOffsets = ZSTD_seqToCodes(seqStorePtr); assert(op <= oend); assert(nbSeq != 0); /* ZSTD_selectEncodingType() divides by nbSeq */ /* build CTable for Literal Lengths */ @@ -2546,23 +2826,24 @@ ZSTD_buildSequencesStatistics(seqStore_t* seqStorePtr, size_t nbSeq, * compresses both literals and sequences * Returns compressed size of block, or a zstd error. */ +#define SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO 20 MEM_STATIC size_t -ZSTD_entropyCompressSeqStore_internal(seqStore_t* seqStorePtr, - const ZSTD_entropyCTables_t* prevEntropy, - ZSTD_entropyCTables_t* nextEntropy, - const ZSTD_CCtx_params* cctxParams, - void* dst, size_t dstCapacity, - void* entropyWorkspace, size_t entropyWkspSize, - const int bmi2) +ZSTD_entropyCompressSeqStore_internal( + const seqStore_t* seqStorePtr, + const ZSTD_entropyCTables_t* prevEntropy, + ZSTD_entropyCTables_t* nextEntropy, + const ZSTD_CCtx_params* cctxParams, + void* dst, size_t dstCapacity, + void* entropyWorkspace, size_t entropyWkspSize, + const int bmi2) { - const int longOffsets = cctxParams->cParams.windowLog > STREAM_ACCUMULATOR_MIN; ZSTD_strategy const strategy = cctxParams->cParams.strategy; unsigned* count = (unsigned*)entropyWorkspace; FSE_CTable* CTable_LitLength = nextEntropy->fse.litlengthCTable; FSE_CTable* CTable_OffsetBits = nextEntropy->fse.offcodeCTable; FSE_CTable* CTable_MatchLength = nextEntropy->fse.matchlengthCTable; const seqDef* const sequences = seqStorePtr->sequencesStart; - const size_t nbSeq = seqStorePtr->sequences - seqStorePtr->sequencesStart; + const size_t nbSeq = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart); const BYTE* const ofCodeTable = seqStorePtr->ofCode; const BYTE* const llCodeTable = seqStorePtr->llCode; const BYTE* const mlCodeTable = seqStorePtr->mlCode; @@ -2570,25 +2851,31 @@ ZSTD_entropyCompressSeqStore_internal(seqStore_t* seqStorePtr, BYTE* const oend = ostart + dstCapacity; BYTE* op = ostart; size_t lastCountSize; + int longOffsets = 0; entropyWorkspace = count + (MaxSeq + 1); entropyWkspSize -= (MaxSeq + 1) * sizeof(*count); - DEBUGLOG(4, "ZSTD_entropyCompressSeqStore_internal (nbSeq=%zu)", nbSeq); + DEBUGLOG(5, "ZSTD_entropyCompressSeqStore_internal (nbSeq=%zu, dstCapacity=%zu)", nbSeq, dstCapacity); ZSTD_STATIC_ASSERT(HUF_WORKSPACE_SIZE >= (1<= HUF_WORKSPACE_SIZE); /* Compress literals */ { const BYTE* const literals = seqStorePtr->litStart; + size_t const numSequences = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart); + size_t const numLiterals = (size_t)(seqStorePtr->lit - seqStorePtr->litStart); + /* Base suspicion of uncompressibility on ratio of literals to sequences */ + unsigned const suspectUncompressible = (numSequences == 0) || (numLiterals / numSequences >= SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO); size_t const litSize = (size_t)(seqStorePtr->lit - literals); + size_t const cSize = ZSTD_compressLiterals( - &prevEntropy->huf, &nextEntropy->huf, - cctxParams->cParams.strategy, - ZSTD_disableLiteralsCompression(cctxParams), op, dstCapacity, literals, litSize, entropyWorkspace, entropyWkspSize, - bmi2); + &prevEntropy->huf, &nextEntropy->huf, + cctxParams->cParams.strategy, + ZSTD_literalsCompressionIsDisabled(cctxParams), + suspectUncompressible, bmi2); FORWARD_IF_ERROR(cSize, "ZSTD_compressLiterals failed"); assert(cSize <= dstCapacity); op += cSize; @@ -2614,11 +2901,10 @@ ZSTD_entropyCompressSeqStore_internal(seqStore_t* seqStorePtr, ZSTD_memcpy(&nextEntropy->fse, &prevEntropy->fse, sizeof(prevEntropy->fse)); return (size_t)(op - ostart); } - { - ZSTD_symbolEncodingTypeStats_t stats; - BYTE* seqHead = op++; + { BYTE* const seqHead = op++; /* build stats for sequences */ - stats = ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq, + const ZSTD_symbolEncodingTypeStats_t stats = + ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq, &prevEntropy->fse, &nextEntropy->fse, op, oend, strategy, count, @@ -2627,6 +2913,7 @@ ZSTD_entropyCompressSeqStore_internal(seqStore_t* seqStorePtr, *seqHead = (BYTE)((stats.LLtype<<6) + (stats.Offtype<<4) + (stats.MLtype<<2)); lastCountSize = stats.lastCountSize; op += stats.size; + longOffsets = stats.longOffsets; } { size_t const bitstreamSize = ZSTD_encodeSequences( @@ -2661,14 +2948,15 @@ ZSTD_entropyCompressSeqStore_internal(seqStore_t* seqStorePtr, } MEM_STATIC size_t -ZSTD_entropyCompressSeqStore(seqStore_t* seqStorePtr, - const ZSTD_entropyCTables_t* prevEntropy, - ZSTD_entropyCTables_t* nextEntropy, - const ZSTD_CCtx_params* cctxParams, - void* dst, size_t dstCapacity, - size_t srcSize, - void* entropyWorkspace, size_t entropyWkspSize, - int bmi2) +ZSTD_entropyCompressSeqStore( + const seqStore_t* seqStorePtr, + const ZSTD_entropyCTables_t* prevEntropy, + ZSTD_entropyCTables_t* nextEntropy, + const ZSTD_CCtx_params* cctxParams, + void* dst, size_t dstCapacity, + size_t srcSize, + void* entropyWorkspace, size_t entropyWkspSize, + int bmi2) { size_t const cSize = ZSTD_entropyCompressSeqStore_internal( seqStorePtr, prevEntropy, nextEntropy, cctxParams, @@ -2678,22 +2966,28 @@ ZSTD_entropyCompressSeqStore(seqStore_t* seqStorePtr, /* When srcSize <= dstCapacity, there is enough space to write a raw uncompressed block. * Since we ran out of space, block must be not compressible, so fall back to raw uncompressed block. */ - if ((cSize == ERROR(dstSize_tooSmall)) & (srcSize <= dstCapacity)) + if ((cSize == ERROR(dstSize_tooSmall)) & (srcSize <= dstCapacity)) { + DEBUGLOG(4, "not enough dstCapacity (%zu) for ZSTD_entropyCompressSeqStore_internal()=> do not compress block", dstCapacity); return 0; /* block not compressed */ + } FORWARD_IF_ERROR(cSize, "ZSTD_entropyCompressSeqStore_internal failed"); /* Check compressibility */ { size_t const maxCSize = srcSize - ZSTD_minGain(srcSize, cctxParams->cParams.strategy); if (cSize >= maxCSize) return 0; /* block not compressed */ } - DEBUGLOG(4, "ZSTD_entropyCompressSeqStore() cSize: %zu", cSize); + DEBUGLOG(5, "ZSTD_entropyCompressSeqStore() cSize: %zu", cSize); + /* libzstd decoder before > v1.5.4 is not compatible with compressed blocks of size ZSTD_BLOCKSIZE_MAX exactly. + * This restriction is indirectly already fulfilled by respecting ZSTD_minGain() condition above. + */ + assert(cSize < ZSTD_BLOCKSIZE_MAX); return cSize; } /* ZSTD_selectBlockCompressor() : * Not static, but internal use only (used by long distance matcher) * assumption : strat is a valid strategy */ -ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_useRowMatchFinderMode_e useRowMatchFinder, ZSTD_dictMode_e dictMode) +ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_paramSwitch_e useRowMatchFinder, ZSTD_dictMode_e dictMode) { static const ZSTD_blockCompressor blockCompressor[4][ZSTD_STRATEGY_MAX+1] = { { ZSTD_compressBlock_fast /* default for 0 */, @@ -2758,7 +3052,7 @@ ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_useRow ZSTD_compressBlock_lazy2_dedicatedDictSearch_row } }; DEBUGLOG(4, "Selecting a row-based matchfinder"); - assert(useRowMatchFinder != ZSTD_urm_auto); + assert(useRowMatchFinder != ZSTD_ps_auto); selectedCompressor = rowBasedBlockCompressors[(int)dictMode][(int)strat - (int)ZSTD_greedy]; } else { selectedCompressor = blockCompressor[(int)dictMode][(int)strat]; @@ -2781,6 +3075,72 @@ void ZSTD_resetSeqStore(seqStore_t* ssPtr) ssPtr->longLengthType = ZSTD_llt_none; } +/* ZSTD_postProcessSequenceProducerResult() : + * Validates and post-processes sequences obtained through the external matchfinder API: + * - Checks whether nbExternalSeqs represents an error condition. + * - Appends a block delimiter to outSeqs if one is not already present. + * See zstd.h for context regarding block delimiters. + * Returns the number of sequences after post-processing, or an error code. */ +static size_t ZSTD_postProcessSequenceProducerResult( + ZSTD_Sequence* outSeqs, size_t nbExternalSeqs, size_t outSeqsCapacity, size_t srcSize +) { + RETURN_ERROR_IF( + nbExternalSeqs > outSeqsCapacity, + sequenceProducer_failed, + "External sequence producer returned error code %lu", + (unsigned long)nbExternalSeqs + ); + + RETURN_ERROR_IF( + nbExternalSeqs == 0 && srcSize > 0, + sequenceProducer_failed, + "Got zero sequences from external sequence producer for a non-empty src buffer!" + ); + + if (srcSize == 0) { + ZSTD_memset(&outSeqs[0], 0, sizeof(ZSTD_Sequence)); + return 1; + } + + { + ZSTD_Sequence const lastSeq = outSeqs[nbExternalSeqs - 1]; + + /* We can return early if lastSeq is already a block delimiter. */ + if (lastSeq.offset == 0 && lastSeq.matchLength == 0) { + return nbExternalSeqs; + } + + /* This error condition is only possible if the external matchfinder + * produced an invalid parse, by definition of ZSTD_sequenceBound(). */ + RETURN_ERROR_IF( + nbExternalSeqs == outSeqsCapacity, + sequenceProducer_failed, + "nbExternalSeqs == outSeqsCapacity but lastSeq is not a block delimiter!" + ); + + /* lastSeq is not a block delimiter, so we need to append one. */ + ZSTD_memset(&outSeqs[nbExternalSeqs], 0, sizeof(ZSTD_Sequence)); + return nbExternalSeqs + 1; + } +} + +/* ZSTD_fastSequenceLengthSum() : + * Returns sum(litLen) + sum(matchLen) + lastLits for *seqBuf*. + * Similar to another function in zstd_compress.c (determine_blockSize), + * except it doesn't check for a block delimiter to end summation. + * Removing the early exit allows the compiler to auto-vectorize (https://godbolt.org/z/cY1cajz9P). + * This function can be deleted and replaced by determine_blockSize after we resolve issue #3456. */ +static size_t ZSTD_fastSequenceLengthSum(ZSTD_Sequence const* seqBuf, size_t seqBufSize) { + size_t matchLenSum, litLenSum, i; + matchLenSum = 0; + litLenSum = 0; + for (i = 0; i < seqBufSize; i++) { + litLenSum += seqBuf[i].litLength; + matchLenSum += seqBuf[i].matchLength; + } + return litLenSum + matchLenSum; +} + typedef enum { ZSTDbss_compress, ZSTDbss_noCompress } ZSTD_buildSeqStore_e; static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) @@ -2790,7 +3150,9 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) assert(srcSize <= ZSTD_BLOCKSIZE_MAX); /* Assert that we have correctly flushed the ctx params into the ms's copy */ ZSTD_assertEqualCParams(zc->appliedParams.cParams, ms->cParams); - if (srcSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1) { + /* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding + * additional 1. We need to revisit and change this logic to be more consistent */ + if (srcSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1+1) { if (zc->appliedParams.cParams.strategy >= ZSTD_btopt) { ZSTD_ldm_skipRawSeqStoreBytes(&zc->externSeqStore, srcSize); } else { @@ -2825,7 +3187,16 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) zc->blockState.nextCBlock->rep[i] = zc->blockState.prevCBlock->rep[i]; } if (zc->externSeqStore.pos < zc->externSeqStore.size) { - assert(!zc->appliedParams.ldmParams.enableLdm); + assert(zc->appliedParams.ldmParams.enableLdm == ZSTD_ps_disable); + + /* External matchfinder + LDM is technically possible, just not implemented yet. + * We need to revisit soon and implement it. */ + RETURN_ERROR_IF( + zc->appliedParams.useSequenceProducer, + parameter_combination_unsupported, + "Long-distance matching with external sequence producer enabled is not currently supported." + ); + /* Updates ldmSeqStore.pos */ lastLLSize = ZSTD_ldm_blockCompress(&zc->externSeqStore, @@ -2834,9 +3205,17 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) zc->appliedParams.useRowMatchFinder, src, srcSize); assert(zc->externSeqStore.pos <= zc->externSeqStore.size); - } else if (zc->appliedParams.ldmParams.enableLdm) { + } else if (zc->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable) { rawSeqStore_t ldmSeqStore = kNullRawSeqStore; + /* External matchfinder + LDM is technically possible, just not implemented yet. + * We need to revisit soon and implement it. */ + RETURN_ERROR_IF( + zc->appliedParams.useSequenceProducer, + parameter_combination_unsupported, + "Long-distance matching with external sequence producer enabled is not currently supported." + ); + ldmSeqStore.seq = zc->ldmSequences; ldmSeqStore.capacity = zc->maxNbLdmSequences; /* Updates ldmSeqStore.size */ @@ -2851,7 +3230,68 @@ static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) zc->appliedParams.useRowMatchFinder, src, srcSize); assert(ldmSeqStore.pos == ldmSeqStore.size); - } else { /* not long range mode */ + } else if (zc->appliedParams.useSequenceProducer) { + assert( + zc->externalMatchCtx.seqBufferCapacity >= ZSTD_sequenceBound(srcSize) + ); + assert(zc->externalMatchCtx.mFinder != NULL); + + { U32 const windowSize = (U32)1 << zc->appliedParams.cParams.windowLog; + + size_t const nbExternalSeqs = (zc->externalMatchCtx.mFinder)( + zc->externalMatchCtx.mState, + zc->externalMatchCtx.seqBuffer, + zc->externalMatchCtx.seqBufferCapacity, + src, srcSize, + NULL, 0, /* dict and dictSize, currently not supported */ + zc->appliedParams.compressionLevel, + windowSize + ); + + size_t const nbPostProcessedSeqs = ZSTD_postProcessSequenceProducerResult( + zc->externalMatchCtx.seqBuffer, + nbExternalSeqs, + zc->externalMatchCtx.seqBufferCapacity, + srcSize + ); + + /* Return early if there is no error, since we don't need to worry about last literals */ + if (!ZSTD_isError(nbPostProcessedSeqs)) { + ZSTD_sequencePosition seqPos = {0,0,0}; + size_t const seqLenSum = ZSTD_fastSequenceLengthSum(zc->externalMatchCtx.seqBuffer, nbPostProcessedSeqs); + RETURN_ERROR_IF(seqLenSum > srcSize, externalSequences_invalid, "External sequences imply too large a block!"); + FORWARD_IF_ERROR( + ZSTD_copySequencesToSeqStoreExplicitBlockDelim( + zc, &seqPos, + zc->externalMatchCtx.seqBuffer, nbPostProcessedSeqs, + src, srcSize, + zc->appliedParams.searchForExternalRepcodes + ), + "Failed to copy external sequences to seqStore!" + ); + ms->ldmSeqStore = NULL; + DEBUGLOG(5, "Copied %lu sequences from external sequence producer to internal seqStore.", (unsigned long)nbExternalSeqs); + return ZSTDbss_compress; + } + + /* Propagate the error if fallback is disabled */ + if (!zc->appliedParams.enableMatchFinderFallback) { + return nbPostProcessedSeqs; + } + + /* Fallback to software matchfinder */ + { ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, + zc->appliedParams.useRowMatchFinder, + dictMode); + ms->ldmSeqStore = NULL; + DEBUGLOG( + 5, + "External sequence producer returned error code %lu. Falling back to internal parser.", + (unsigned long)nbExternalSeqs + ); + lastLLSize = blockCompressor(ms, &zc->seqStore, zc->blockState.nextCBlock->rep, src, srcSize); + } } + } else { /* not long range mode and no external matchfinder */ ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, zc->appliedParams.useRowMatchFinder, dictMode); @@ -2882,9 +3322,9 @@ static void ZSTD_copyBlockSequences(ZSTD_CCtx* zc) assert(zc->seqCollector.maxSequences >= seqStoreSeqSize + 1); ZSTD_memcpy(updatedRepcodes.rep, zc->blockState.prevCBlock->rep, sizeof(repcodes_t)); for (i = 0; i < seqStoreSeqSize; ++i) { - U32 rawOffset = seqStoreSeqs[i].offset - ZSTD_REP_NUM; + U32 rawOffset = seqStoreSeqs[i].offBase - ZSTD_REP_NUM; outSeqs[i].litLength = seqStoreSeqs[i].litLength; - outSeqs[i].matchLength = seqStoreSeqs[i].matchLength + MINMATCH; + outSeqs[i].matchLength = seqStoreSeqs[i].mlBase + MINMATCH; outSeqs[i].rep = 0; if (i == seqStore->longLengthPos) { @@ -2895,9 +3335,9 @@ static void ZSTD_copyBlockSequences(ZSTD_CCtx* zc) } } - if (seqStoreSeqs[i].offset <= ZSTD_REP_NUM) { + if (seqStoreSeqs[i].offBase <= ZSTD_REP_NUM) { /* Derive the correct offset corresponding to a repcode */ - outSeqs[i].rep = seqStoreSeqs[i].offset; + outSeqs[i].rep = seqStoreSeqs[i].offBase; if (outSeqs[i].litLength != 0) { rawOffset = updatedRepcodes.rep[outSeqs[i].rep - 1]; } else { @@ -2911,9 +3351,9 @@ static void ZSTD_copyBlockSequences(ZSTD_CCtx* zc) outSeqs[i].offset = rawOffset; /* seqStoreSeqs[i].offset == offCode+1, and ZSTD_updateRep() expects offCode so we provide seqStoreSeqs[i].offset - 1 */ - updatedRepcodes = ZSTD_updateRep(updatedRepcodes.rep, - seqStoreSeqs[i].offset - 1, - seqStoreSeqs[i].litLength == 0); + ZSTD_updateRep(updatedRepcodes.rep, + seqStoreSeqs[i].offBase, + seqStoreSeqs[i].litLength == 0); literalsRead += outSeqs[i].litLength; } /* Insert last literals (if any exist) in the block as a sequence with ml == off == 0. @@ -2928,6 +3368,10 @@ static void ZSTD_copyBlockSequences(ZSTD_CCtx* zc) zc->seqCollector.seqIndex += seqStoreSeqSize; } +size_t ZSTD_sequenceBound(size_t srcSize) { + return (srcSize / ZSTD_MINMATCH_MIN) + 1; +} + size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, size_t outSeqsSize, const void* src, size_t srcSize) { @@ -2973,19 +3417,17 @@ static int ZSTD_isRLE(const BYTE* src, size_t length) { const size_t unrollMask = unrollSize - 1; const size_t prefixLength = length & unrollMask; size_t i; - size_t u; if (length == 1) return 1; /* Check if prefix is RLE first before using unrolled loop */ if (prefixLength && ZSTD_count(ip+1, ip, ip+prefixLength) != prefixLength-1) { return 0; } for (i = prefixLength; i != length; i += unrollSize) { + size_t u; for (u = 0; u < unrollSize; u += sizeof(size_t)) { if (MEM_readST(ip + i + u) != valueST) { return 0; - } - } - } + } } } return 1; } @@ -3001,7 +3443,8 @@ static int ZSTD_maybeRLE(seqStore_t const* seqStore) return nbSeqs < 4 && nbLits < 10; } -static void ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs) +static void +ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs) { ZSTD_compressedBlockState_t* const tmp = bs->prevCBlock; bs->prevCBlock = bs->nextCBlock; @@ -3009,7 +3452,9 @@ static void ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* c } /* Writes the block header */ -static void writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastBlock) { +static void +writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastBlock) +{ U32 const cBlockHeader = cSize == 1 ? lastBlock + (((U32)bt_rle)<<1) + (U32)(blockSize << 3) : lastBlock + (((U32)bt_compressed)<<1) + (U32)(cSize << 3); @@ -3022,13 +3467,16 @@ static void writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastB * Stores literals block type (raw, rle, compressed, repeat) and * huffman description table to hufMetadata. * Requires ENTROPY_WORKSPACE_SIZE workspace - * @return : size of huffman description table or error code */ -static size_t ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSize, - const ZSTD_hufCTables_t* prevHuf, - ZSTD_hufCTables_t* nextHuf, - ZSTD_hufCTablesMetadata_t* hufMetadata, - const int disableLiteralsCompression, - void* workspace, size_t wkspSize) + * @return : size of huffman description table, or an error code + */ +static size_t +ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSize, + const ZSTD_hufCTables_t* prevHuf, + ZSTD_hufCTables_t* nextHuf, + ZSTD_hufCTablesMetadata_t* hufMetadata, + const int literalsCompressionIsDisabled, + void* workspace, size_t wkspSize, + int hufFlags) { BYTE* const wkspStart = (BYTE*)workspace; BYTE* const wkspEnd = wkspStart + wkspSize; @@ -3036,16 +3484,16 @@ static size_t ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSi unsigned* const countWksp = (unsigned*)workspace; const size_t countWkspSize = (HUF_SYMBOLVALUE_MAX + 1) * sizeof(unsigned); BYTE* const nodeWksp = countWkspStart + countWkspSize; - const size_t nodeWkspSize = wkspEnd-nodeWksp; + const size_t nodeWkspSize = (size_t)(wkspEnd - nodeWksp); unsigned maxSymbolValue = HUF_SYMBOLVALUE_MAX; - unsigned huffLog = HUF_TABLELOG_DEFAULT; + unsigned huffLog = LitHufLog; HUF_repeat repeat = prevHuf->repeatMode; DEBUGLOG(5, "ZSTD_buildBlockEntropyStats_literals (srcSize=%zu)", srcSize); /* Prepare nextEntropy assuming reusing the existing table */ ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - if (disableLiteralsCompression) { + if (literalsCompressionIsDisabled) { DEBUGLOG(5, "set_basic - disabled"); hufMetadata->hType = set_basic; return 0; @@ -3053,73 +3501,77 @@ static size_t ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSi /* small ? don't even attempt compression (speed opt) */ #ifndef COMPRESS_LITERALS_SIZE_MIN -#define COMPRESS_LITERALS_SIZE_MIN 63 +# define COMPRESS_LITERALS_SIZE_MIN 63 /* heuristic */ #endif { size_t const minLitSize = (prevHuf->repeatMode == HUF_repeat_valid) ? 6 : COMPRESS_LITERALS_SIZE_MIN; if (srcSize <= minLitSize) { DEBUGLOG(5, "set_basic - too small"); hufMetadata->hType = set_basic; return 0; - } - } + } } /* Scan input and build symbol stats */ - { size_t const largest = HIST_count_wksp (countWksp, &maxSymbolValue, (const BYTE*)src, srcSize, workspace, wkspSize); + { size_t const largest = + HIST_count_wksp (countWksp, &maxSymbolValue, + (const BYTE*)src, srcSize, + workspace, wkspSize); FORWARD_IF_ERROR(largest, "HIST_count_wksp failed"); if (largest == srcSize) { + /* only one literal symbol */ DEBUGLOG(5, "set_rle"); hufMetadata->hType = set_rle; return 0; } if (largest <= (srcSize >> 7)+4) { + /* heuristic: likely not compressible */ DEBUGLOG(5, "set_basic - no gain"); hufMetadata->hType = set_basic; return 0; - } - } + } } /* Validate the previous Huffman table */ - if (repeat == HUF_repeat_check && !HUF_validateCTable((HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue)) { + if (repeat == HUF_repeat_check + && !HUF_validateCTable((HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue)) { repeat = HUF_repeat_none; } /* Build Huffman Tree */ ZSTD_memset(nextHuf->CTable, 0, sizeof(nextHuf->CTable)); - huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue); + huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue, nodeWksp, nodeWkspSize, nextHuf->CTable, countWksp, hufFlags); + assert(huffLog <= LitHufLog); { size_t const maxBits = HUF_buildCTable_wksp((HUF_CElt*)nextHuf->CTable, countWksp, maxSymbolValue, huffLog, nodeWksp, nodeWkspSize); FORWARD_IF_ERROR(maxBits, "HUF_buildCTable_wksp"); huffLog = (U32)maxBits; - { /* Build and write the CTable */ - size_t const newCSize = HUF_estimateCompressedSize( - (HUF_CElt*)nextHuf->CTable, countWksp, maxSymbolValue); - size_t const hSize = HUF_writeCTable_wksp( - hufMetadata->hufDesBuffer, sizeof(hufMetadata->hufDesBuffer), - (HUF_CElt*)nextHuf->CTable, maxSymbolValue, huffLog, - nodeWksp, nodeWkspSize); - /* Check against repeating the previous CTable */ - if (repeat != HUF_repeat_none) { - size_t const oldCSize = HUF_estimateCompressedSize( - (HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue); - if (oldCSize < srcSize && (oldCSize <= hSize + newCSize || hSize + 12 >= srcSize)) { - DEBUGLOG(5, "set_repeat - smaller"); - ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - hufMetadata->hType = set_repeat; - return 0; - } - } - if (newCSize + hSize >= srcSize) { - DEBUGLOG(5, "set_basic - no gains"); + } + { /* Build and write the CTable */ + size_t const newCSize = HUF_estimateCompressedSize( + (HUF_CElt*)nextHuf->CTable, countWksp, maxSymbolValue); + size_t const hSize = HUF_writeCTable_wksp( + hufMetadata->hufDesBuffer, sizeof(hufMetadata->hufDesBuffer), + (HUF_CElt*)nextHuf->CTable, maxSymbolValue, huffLog, + nodeWksp, nodeWkspSize); + /* Check against repeating the previous CTable */ + if (repeat != HUF_repeat_none) { + size_t const oldCSize = HUF_estimateCompressedSize( + (HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue); + if (oldCSize < srcSize && (oldCSize <= hSize + newCSize || hSize + 12 >= srcSize)) { + DEBUGLOG(5, "set_repeat - smaller"); ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - hufMetadata->hType = set_basic; + hufMetadata->hType = set_repeat; return 0; - } - DEBUGLOG(5, "set_compressed (hSize=%u)", (U32)hSize); - hufMetadata->hType = set_compressed; - nextHuf->repeatMode = HUF_repeat_check; - return hSize; + } } + if (newCSize + hSize >= srcSize) { + DEBUGLOG(5, "set_basic - no gains"); + ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); + hufMetadata->hType = set_basic; + return 0; } + DEBUGLOG(5, "set_compressed (hSize=%u)", (U32)hSize); + hufMetadata->hType = set_compressed; + nextHuf->repeatMode = HUF_repeat_check; + return hSize; } } @@ -3129,8 +3581,9 @@ static size_t ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSi * and updates nextEntropy to the appropriate repeatMode. */ static ZSTD_symbolEncodingTypeStats_t -ZSTD_buildDummySequencesStatistics(ZSTD_fseCTables_t* nextEntropy) { - ZSTD_symbolEncodingTypeStats_t stats = {set_basic, set_basic, set_basic, 0, 0}; +ZSTD_buildDummySequencesStatistics(ZSTD_fseCTables_t* nextEntropy) +{ + ZSTD_symbolEncodingTypeStats_t stats = {set_basic, set_basic, set_basic, 0, 0, 0}; nextEntropy->litlength_repeatMode = FSE_repeat_none; nextEntropy->offcode_repeatMode = FSE_repeat_none; nextEntropy->matchlength_repeatMode = FSE_repeat_none; @@ -3141,16 +3594,18 @@ ZSTD_buildDummySequencesStatistics(ZSTD_fseCTables_t* nextEntropy) { * Builds entropy for the sequences. * Stores symbol compression modes and fse table to fseMetadata. * Requires ENTROPY_WORKSPACE_SIZE wksp. - * @return : size of fse tables or error code */ -static size_t ZSTD_buildBlockEntropyStats_sequences(seqStore_t* seqStorePtr, - const ZSTD_fseCTables_t* prevEntropy, - ZSTD_fseCTables_t* nextEntropy, - const ZSTD_CCtx_params* cctxParams, - ZSTD_fseCTablesMetadata_t* fseMetadata, - void* workspace, size_t wkspSize) + * @return : size of fse tables or error code */ +static size_t +ZSTD_buildBlockEntropyStats_sequences( + const seqStore_t* seqStorePtr, + const ZSTD_fseCTables_t* prevEntropy, + ZSTD_fseCTables_t* nextEntropy, + const ZSTD_CCtx_params* cctxParams, + ZSTD_fseCTablesMetadata_t* fseMetadata, + void* workspace, size_t wkspSize) { ZSTD_strategy const strategy = cctxParams->cParams.strategy; - size_t const nbSeq = seqStorePtr->sequences - seqStorePtr->sequencesStart; + size_t const nbSeq = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart); BYTE* const ostart = fseMetadata->fseTablesBuffer; BYTE* const oend = ostart + sizeof(fseMetadata->fseTablesBuffer); BYTE* op = ostart; @@ -3177,23 +3632,28 @@ static size_t ZSTD_buildBlockEntropyStats_sequences(seqStore_t* seqStorePtr, /** ZSTD_buildBlockEntropyStats() : * Builds entropy for the block. * Requires workspace size ENTROPY_WORKSPACE_SIZE - * - * @return : 0 on success or error code + * @return : 0 on success, or an error code + * Note : also employed in superblock */ -size_t ZSTD_buildBlockEntropyStats(seqStore_t* seqStorePtr, - const ZSTD_entropyCTables_t* prevEntropy, - ZSTD_entropyCTables_t* nextEntropy, - const ZSTD_CCtx_params* cctxParams, - ZSTD_entropyCTablesMetadata_t* entropyMetadata, - void* workspace, size_t wkspSize) -{ - size_t const litSize = seqStorePtr->lit - seqStorePtr->litStart; +size_t ZSTD_buildBlockEntropyStats( + const seqStore_t* seqStorePtr, + const ZSTD_entropyCTables_t* prevEntropy, + ZSTD_entropyCTables_t* nextEntropy, + const ZSTD_CCtx_params* cctxParams, + ZSTD_entropyCTablesMetadata_t* entropyMetadata, + void* workspace, size_t wkspSize) +{ + size_t const litSize = (size_t)(seqStorePtr->lit - seqStorePtr->litStart); + int const huf_useOptDepth = (cctxParams->cParams.strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD); + int const hufFlags = huf_useOptDepth ? HUF_flags_optimalDepth : 0; + entropyMetadata->hufMetadata.hufDesSize = ZSTD_buildBlockEntropyStats_literals(seqStorePtr->litStart, litSize, &prevEntropy->huf, &nextEntropy->huf, &entropyMetadata->hufMetadata, - ZSTD_disableLiteralsCompression(cctxParams), - workspace, wkspSize); + ZSTD_literalsCompressionIsDisabled(cctxParams), + workspace, wkspSize, hufFlags); + FORWARD_IF_ERROR(entropyMetadata->hufMetadata.hufDesSize, "ZSTD_buildBlockEntropyStats_literals failed"); entropyMetadata->fseMetadata.fseTablesSize = ZSTD_buildBlockEntropyStats_sequences(seqStorePtr, @@ -3206,11 +3666,12 @@ size_t ZSTD_buildBlockEntropyStats(seqStore_t* seqStorePtr, } /* Returns the size estimate for the literals section (header + content) of a block */ -static size_t ZSTD_estimateBlockSize_literal(const BYTE* literals, size_t litSize, - const ZSTD_hufCTables_t* huf, - const ZSTD_hufCTablesMetadata_t* hufMetadata, - void* workspace, size_t wkspSize, - int writeEntropy) +static size_t +ZSTD_estimateBlockSize_literal(const BYTE* literals, size_t litSize, + const ZSTD_hufCTables_t* huf, + const ZSTD_hufCTablesMetadata_t* hufMetadata, + void* workspace, size_t wkspSize, + int writeEntropy) { unsigned* const countWksp = (unsigned*)workspace; unsigned maxSymbolValue = HUF_SYMBOLVALUE_MAX; @@ -3232,12 +3693,13 @@ static size_t ZSTD_estimateBlockSize_literal(const BYTE* literals, size_t litSiz } /* Returns the size estimate for the FSE-compressed symbols (of, ml, ll) of a block */ -static size_t ZSTD_estimateBlockSize_symbolType(symbolEncodingType_e type, - const BYTE* codeTable, size_t nbSeq, unsigned maxCode, - const FSE_CTable* fseCTable, - const U32* additionalBits, - short const* defaultNorm, U32 defaultNormLog, U32 defaultMax, - void* workspace, size_t wkspSize) +static size_t +ZSTD_estimateBlockSize_symbolType(symbolEncodingType_e type, + const BYTE* codeTable, size_t nbSeq, unsigned maxCode, + const FSE_CTable* fseCTable, + const U8* additionalBits, + short const* defaultNorm, U32 defaultNormLog, U32 defaultMax, + void* workspace, size_t wkspSize) { unsigned* const countWksp = (unsigned*)workspace; const BYTE* ctp = codeTable; @@ -3269,98 +3731,107 @@ static size_t ZSTD_estimateBlockSize_symbolType(symbolEncodingType_e type, } /* Returns the size estimate for the sequences section (header + content) of a block */ -static size_t ZSTD_estimateBlockSize_sequences(const BYTE* ofCodeTable, - const BYTE* llCodeTable, - const BYTE* mlCodeTable, - size_t nbSeq, - const ZSTD_fseCTables_t* fseTables, - const ZSTD_fseCTablesMetadata_t* fseMetadata, - void* workspace, size_t wkspSize, - int writeEntropy) +static size_t +ZSTD_estimateBlockSize_sequences(const BYTE* ofCodeTable, + const BYTE* llCodeTable, + const BYTE* mlCodeTable, + size_t nbSeq, + const ZSTD_fseCTables_t* fseTables, + const ZSTD_fseCTablesMetadata_t* fseMetadata, + void* workspace, size_t wkspSize, + int writeEntropy) { size_t sequencesSectionHeaderSize = 1 /* seqHead */ + 1 /* min seqSize size */ + (nbSeq >= 128) + (nbSeq >= LONGNBSEQ); size_t cSeqSizeEstimate = 0; cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->ofType, ofCodeTable, nbSeq, MaxOff, - fseTables->offcodeCTable, NULL, - OF_defaultNorm, OF_defaultNormLog, DefaultMaxOff, - workspace, wkspSize); + fseTables->offcodeCTable, NULL, + OF_defaultNorm, OF_defaultNormLog, DefaultMaxOff, + workspace, wkspSize); cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->llType, llCodeTable, nbSeq, MaxLL, - fseTables->litlengthCTable, LL_bits, - LL_defaultNorm, LL_defaultNormLog, MaxLL, - workspace, wkspSize); + fseTables->litlengthCTable, LL_bits, + LL_defaultNorm, LL_defaultNormLog, MaxLL, + workspace, wkspSize); cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->mlType, mlCodeTable, nbSeq, MaxML, - fseTables->matchlengthCTable, ML_bits, - ML_defaultNorm, ML_defaultNormLog, MaxML, - workspace, wkspSize); + fseTables->matchlengthCTable, ML_bits, + ML_defaultNorm, ML_defaultNormLog, MaxML, + workspace, wkspSize); if (writeEntropy) cSeqSizeEstimate += fseMetadata->fseTablesSize; return cSeqSizeEstimate + sequencesSectionHeaderSize; } /* Returns the size estimate for a given stream of literals, of, ll, ml */ -static size_t ZSTD_estimateBlockSize(const BYTE* literals, size_t litSize, - const BYTE* ofCodeTable, - const BYTE* llCodeTable, - const BYTE* mlCodeTable, - size_t nbSeq, - const ZSTD_entropyCTables_t* entropy, - const ZSTD_entropyCTablesMetadata_t* entropyMetadata, - void* workspace, size_t wkspSize, - int writeLitEntropy, int writeSeqEntropy) { +static size_t +ZSTD_estimateBlockSize(const BYTE* literals, size_t litSize, + const BYTE* ofCodeTable, + const BYTE* llCodeTable, + const BYTE* mlCodeTable, + size_t nbSeq, + const ZSTD_entropyCTables_t* entropy, + const ZSTD_entropyCTablesMetadata_t* entropyMetadata, + void* workspace, size_t wkspSize, + int writeLitEntropy, int writeSeqEntropy) +{ size_t const literalsSize = ZSTD_estimateBlockSize_literal(literals, litSize, - &entropy->huf, &entropyMetadata->hufMetadata, - workspace, wkspSize, writeLitEntropy); + &entropy->huf, &entropyMetadata->hufMetadata, + workspace, wkspSize, writeLitEntropy); size_t const seqSize = ZSTD_estimateBlockSize_sequences(ofCodeTable, llCodeTable, mlCodeTable, - nbSeq, &entropy->fse, &entropyMetadata->fseMetadata, - workspace, wkspSize, writeSeqEntropy); + nbSeq, &entropy->fse, &entropyMetadata->fseMetadata, + workspace, wkspSize, writeSeqEntropy); return seqSize + literalsSize + ZSTD_blockHeaderSize; } /* Builds entropy statistics and uses them for blocksize estimation. * - * Returns the estimated compressed size of the seqStore, or a zstd error. + * @return: estimated compressed size of the seqStore, or a zstd error. */ -static size_t ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(seqStore_t* seqStore, const ZSTD_CCtx* zc) { - ZSTD_entropyCTablesMetadata_t entropyMetadata; +static size_t +ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(seqStore_t* seqStore, ZSTD_CCtx* zc) +{ + ZSTD_entropyCTablesMetadata_t* const entropyMetadata = &zc->blockSplitCtx.entropyMetadata; + DEBUGLOG(6, "ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize()"); FORWARD_IF_ERROR(ZSTD_buildBlockEntropyStats(seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, - &entropyMetadata, - zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE /* statically allocated in resetCCtx */), ""); - return ZSTD_estimateBlockSize(seqStore->litStart, (size_t)(seqStore->lit - seqStore->litStart), + entropyMetadata, + zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE), ""); + return ZSTD_estimateBlockSize( + seqStore->litStart, (size_t)(seqStore->lit - seqStore->litStart), seqStore->ofCode, seqStore->llCode, seqStore->mlCode, (size_t)(seqStore->sequences - seqStore->sequencesStart), - &zc->blockState.nextCBlock->entropy, &entropyMetadata, zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE, - (int)(entropyMetadata.hufMetadata.hType == set_compressed), 1); + &zc->blockState.nextCBlock->entropy, + entropyMetadata, + zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE, + (int)(entropyMetadata->hufMetadata.hType == set_compressed), 1); } /* Returns literals bytes represented in a seqStore */ -static size_t ZSTD_countSeqStoreLiteralsBytes(const seqStore_t* const seqStore) { +static size_t ZSTD_countSeqStoreLiteralsBytes(const seqStore_t* const seqStore) +{ size_t literalsBytes = 0; - size_t const nbSeqs = seqStore->sequences - seqStore->sequencesStart; + size_t const nbSeqs = (size_t)(seqStore->sequences - seqStore->sequencesStart); size_t i; for (i = 0; i < nbSeqs; ++i) { - seqDef seq = seqStore->sequencesStart[i]; + seqDef const seq = seqStore->sequencesStart[i]; literalsBytes += seq.litLength; if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_llt_literalLength) { literalsBytes += 0x10000; - } - } + } } return literalsBytes; } /* Returns match bytes represented in a seqStore */ -static size_t ZSTD_countSeqStoreMatchBytes(const seqStore_t* const seqStore) { +static size_t ZSTD_countSeqStoreMatchBytes(const seqStore_t* const seqStore) +{ size_t matchBytes = 0; - size_t const nbSeqs = seqStore->sequences - seqStore->sequencesStart; + size_t const nbSeqs = (size_t)(seqStore->sequences - seqStore->sequencesStart); size_t i; for (i = 0; i < nbSeqs; ++i) { seqDef seq = seqStore->sequencesStart[i]; - matchBytes += seq.matchLength + MINMATCH; + matchBytes += seq.mlBase + MINMATCH; if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_llt_matchLength) { matchBytes += 0x10000; - } - } + } } return matchBytes; } @@ -3369,15 +3840,12 @@ static size_t ZSTD_countSeqStoreMatchBytes(const seqStore_t* const seqStore) { */ static void ZSTD_deriveSeqStoreChunk(seqStore_t* resultSeqStore, const seqStore_t* originalSeqStore, - size_t startIdx, size_t endIdx) { - BYTE* const litEnd = originalSeqStore->lit; - size_t literalsBytes; - size_t literalsBytesPreceding = 0; - + size_t startIdx, size_t endIdx) +{ *resultSeqStore = *originalSeqStore; if (startIdx > 0) { resultSeqStore->sequences = originalSeqStore->sequencesStart + startIdx; - literalsBytesPreceding = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); + resultSeqStore->litStart += ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); } /* Move longLengthPos into the correct position if necessary */ @@ -3390,13 +3858,12 @@ static void ZSTD_deriveSeqStoreChunk(seqStore_t* resultSeqStore, } resultSeqStore->sequencesStart = originalSeqStore->sequencesStart + startIdx; resultSeqStore->sequences = originalSeqStore->sequencesStart + endIdx; - literalsBytes = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); - resultSeqStore->litStart += literalsBytesPreceding; if (endIdx == (size_t)(originalSeqStore->sequences - originalSeqStore->sequencesStart)) { /* This accounts for possible last literals if the derived chunk reaches the end of the block */ - resultSeqStore->lit = litEnd; + assert(resultSeqStore->lit == originalSeqStore->lit); } else { - resultSeqStore->lit = resultSeqStore->litStart+literalsBytes; + size_t const literalsBytes = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); + resultSeqStore->lit = resultSeqStore->litStart + literalsBytes; } resultSeqStore->llCode += startIdx; resultSeqStore->mlCode += startIdx; @@ -3404,52 +3871,68 @@ static void ZSTD_deriveSeqStoreChunk(seqStore_t* resultSeqStore, } /** - * Returns the raw offset represented by the combination of offCode, ll0, and repcode history. - * offCode must be an offCode representing a repcode, therefore in the range of [0, 2]. + * Returns the raw offset represented by the combination of offBase, ll0, and repcode history. + * offBase must represent a repcode in the numeric representation of ZSTD_storeSeq(). */ -static U32 ZSTD_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], const U32 offCode, const U32 ll0) { - U32 const adjustedOffCode = offCode + ll0; - assert(offCode < ZSTD_REP_NUM); - if (adjustedOffCode == ZSTD_REP_NUM) { - /* litlength == 0 and offCode == 2 implies selection of first repcode - 1 */ - assert(rep[0] > 0); +static U32 +ZSTD_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], const U32 offBase, const U32 ll0) +{ + U32 const adjustedRepCode = OFFBASE_TO_REPCODE(offBase) - 1 + ll0; /* [ 0 - 3 ] */ + assert(OFFBASE_IS_REPCODE(offBase)); + if (adjustedRepCode == ZSTD_REP_NUM) { + assert(ll0); + /* litlength == 0 and offCode == 2 implies selection of first repcode - 1 + * This is only valid if it results in a valid offset value, aka > 0. + * Note : it may happen that `rep[0]==1` in exceptional circumstances. + * In which case this function will return 0, which is an invalid offset. + * It's not an issue though, since this value will be + * compared and discarded within ZSTD_seqStore_resolveOffCodes(). + */ return rep[0] - 1; } - return rep[adjustedOffCode]; + return rep[adjustedRepCode]; } /** * ZSTD_seqStore_resolveOffCodes() reconciles any possible divergences in offset history that may arise - * due to emission of RLE/raw blocks that disturb the offset history, and replaces any repcodes within - * the seqStore that may be invalid. + * due to emission of RLE/raw blocks that disturb the offset history, + * and replaces any repcodes within the seqStore that may be invalid. * - * dRepcodes are updated as would be on the decompression side. cRepcodes are updated exactly in - * accordance with the seqStore. + * dRepcodes are updated as would be on the decompression side. + * cRepcodes are updated exactly in accordance with the seqStore. + * + * Note : this function assumes seq->offBase respects the following numbering scheme : + * 0 : invalid + * 1-3 : repcode 1-3 + * 4+ : real_offset+3 */ -static void ZSTD_seqStore_resolveOffCodes(repcodes_t* const dRepcodes, repcodes_t* const cRepcodes, - seqStore_t* const seqStore, U32 const nbSeq) { +static void +ZSTD_seqStore_resolveOffCodes(repcodes_t* const dRepcodes, repcodes_t* const cRepcodes, + const seqStore_t* const seqStore, U32 const nbSeq) +{ U32 idx = 0; + U32 const longLitLenIdx = seqStore->longLengthType == ZSTD_llt_literalLength ? seqStore->longLengthPos : nbSeq; for (; idx < nbSeq; ++idx) { seqDef* const seq = seqStore->sequencesStart + idx; - U32 const ll0 = (seq->litLength == 0); - U32 offCode = seq->offset - 1; - assert(seq->offset > 0); - if (offCode <= ZSTD_REP_MOVE) { - U32 const dRawOffset = ZSTD_resolveRepcodeToRawOffset(dRepcodes->rep, offCode, ll0); - U32 const cRawOffset = ZSTD_resolveRepcodeToRawOffset(cRepcodes->rep, offCode, ll0); + U32 const ll0 = (seq->litLength == 0) && (idx != longLitLenIdx); + U32 const offBase = seq->offBase; + assert(offBase > 0); + if (OFFBASE_IS_REPCODE(offBase)) { + U32 const dRawOffset = ZSTD_resolveRepcodeToRawOffset(dRepcodes->rep, offBase, ll0); + U32 const cRawOffset = ZSTD_resolveRepcodeToRawOffset(cRepcodes->rep, offBase, ll0); /* Adjust simulated decompression repcode history if we come across a mismatch. Replace * the repcode with the offset it actually references, determined by the compression * repcode history. */ if (dRawOffset != cRawOffset) { - seq->offset = cRawOffset + ZSTD_REP_NUM; + seq->offBase = OFFSET_TO_OFFBASE(cRawOffset); } } /* Compression repcode history is always updated with values directly from the unmodified seqStore. * Decompression repcode history may use modified seq->offset value taken from compression repcode history. */ - *dRepcodes = ZSTD_updateRep(dRepcodes->rep, seq->offset - 1, ll0); - *cRepcodes = ZSTD_updateRep(cRepcodes->rep, offCode, ll0); + ZSTD_updateRep(dRepcodes->rep, seq->offBase, ll0); + ZSTD_updateRep(cRepcodes->rep, offBase, ll0); } } @@ -3458,11 +3941,14 @@ static void ZSTD_seqStore_resolveOffCodes(repcodes_t* const dRepcodes, repcodes_ * * Returns the total size of that block (including header) or a ZSTD error code. */ -static size_t ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, seqStore_t* const seqStore, - repcodes_t* const dRep, repcodes_t* const cRep, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - U32 lastBlock, U32 isPartition) { +static size_t +ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, + const seqStore_t* const seqStore, + repcodes_t* const dRep, repcodes_t* const cRep, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + U32 lastBlock, U32 isPartition) +{ const U32 rleMaxLength = 25; BYTE* op = (BYTE*)dst; const BYTE* ip = (const BYTE*)src; @@ -3471,9 +3957,11 @@ static size_t ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, seqStore_t* const /* In case of an RLE or raw block, the simulated decompression repcode history must be reset */ repcodes_t const dRepOriginal = *dRep; + DEBUGLOG(5, "ZSTD_compressSeqStore_singleBlock"); if (isPartition) ZSTD_seqStore_resolveOffCodes(dRep, cRep, seqStore, (U32)(seqStore->sequences - seqStore->sequencesStart)); + RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize, dstSize_tooSmall, "Block header doesn't fit"); cSeqsSize = ZSTD_entropyCompressSeqStore(seqStore, &zc->blockState.prevCBlock->entropy, &zc->blockState.nextCBlock->entropy, &zc->appliedParams, @@ -3499,9 +3987,6 @@ static size_t ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, seqStore_t* const return 0; } - if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid) - zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check; - if (cSeqsSize == 0) { cSize = ZSTD_noCompressBlock(op, dstCapacity, ip, srcSize, lastBlock); FORWARD_IF_ERROR(cSize, "Nocompress block failed"); @@ -3518,6 +4003,10 @@ static size_t ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, seqStore_t* const cSize = ZSTD_blockHeaderSize + cSeqsSize; DEBUGLOG(4, "Writing out compressed block, size: %zu", cSize); } + + if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid) + zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check; + return cSize; } @@ -3528,45 +4017,52 @@ typedef struct { } seqStoreSplits; #define MIN_SEQUENCES_BLOCK_SPLITTING 300 -#define MAX_NB_SPLITS 196 /* Helper function to perform the recursive search for block splits. * Estimates the cost of seqStore prior to split, and estimates the cost of splitting the sequences in half. - * If advantageous to split, then we recurse down the two sub-blocks. If not, or if an error occurred in estimation, then - * we do not recurse. + * If advantageous to split, then we recurse down the two sub-blocks. + * If not, or if an error occurred in estimation, then we do not recurse. * - * Note: The recursion depth is capped by a heuristic minimum number of sequences, defined by MIN_SEQUENCES_BLOCK_SPLITTING. + * Note: The recursion depth is capped by a heuristic minimum number of sequences, + * defined by MIN_SEQUENCES_BLOCK_SPLITTING. * In theory, this means the absolute largest recursion depth is 10 == log2(maxNbSeqInBlock/MIN_SEQUENCES_BLOCK_SPLITTING). * In practice, recursion depth usually doesn't go beyond 4. * - * Furthermore, the number of splits is capped by MAX_NB_SPLITS. At MAX_NB_SPLITS == 196 with the current existing blockSize + * Furthermore, the number of splits is capped by ZSTD_MAX_NB_BLOCK_SPLITS. + * At ZSTD_MAX_NB_BLOCK_SPLITS == 196 with the current existing blockSize * maximum of 128 KB, this value is actually impossible to reach. */ -static void ZSTD_deriveBlockSplitsHelper(seqStoreSplits* splits, size_t startIdx, size_t endIdx, - const ZSTD_CCtx* zc, const seqStore_t* origSeqStore) { - seqStore_t fullSeqStoreChunk; - seqStore_t firstHalfSeqStore; - seqStore_t secondHalfSeqStore; +static void +ZSTD_deriveBlockSplitsHelper(seqStoreSplits* splits, size_t startIdx, size_t endIdx, + ZSTD_CCtx* zc, const seqStore_t* origSeqStore) +{ + seqStore_t* const fullSeqStoreChunk = &zc->blockSplitCtx.fullSeqStoreChunk; + seqStore_t* const firstHalfSeqStore = &zc->blockSplitCtx.firstHalfSeqStore; + seqStore_t* const secondHalfSeqStore = &zc->blockSplitCtx.secondHalfSeqStore; size_t estimatedOriginalSize; size_t estimatedFirstHalfSize; size_t estimatedSecondHalfSize; size_t midIdx = (startIdx + endIdx)/2; - if (endIdx - startIdx < MIN_SEQUENCES_BLOCK_SPLITTING || splits->idx >= MAX_NB_SPLITS) { + DEBUGLOG(5, "ZSTD_deriveBlockSplitsHelper: startIdx=%zu endIdx=%zu", startIdx, endIdx); + assert(endIdx >= startIdx); + if (endIdx - startIdx < MIN_SEQUENCES_BLOCK_SPLITTING || splits->idx >= ZSTD_MAX_NB_BLOCK_SPLITS) { + DEBUGLOG(6, "ZSTD_deriveBlockSplitsHelper: Too few sequences (%zu)", endIdx - startIdx); return; } - ZSTD_deriveSeqStoreChunk(&fullSeqStoreChunk, origSeqStore, startIdx, endIdx); - ZSTD_deriveSeqStoreChunk(&firstHalfSeqStore, origSeqStore, startIdx, midIdx); - ZSTD_deriveSeqStoreChunk(&secondHalfSeqStore, origSeqStore, midIdx, endIdx); - estimatedOriginalSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(&fullSeqStoreChunk, zc); - estimatedFirstHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(&firstHalfSeqStore, zc); - estimatedSecondHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(&secondHalfSeqStore, zc); + ZSTD_deriveSeqStoreChunk(fullSeqStoreChunk, origSeqStore, startIdx, endIdx); + ZSTD_deriveSeqStoreChunk(firstHalfSeqStore, origSeqStore, startIdx, midIdx); + ZSTD_deriveSeqStoreChunk(secondHalfSeqStore, origSeqStore, midIdx, endIdx); + estimatedOriginalSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(fullSeqStoreChunk, zc); + estimatedFirstHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(firstHalfSeqStore, zc); + estimatedSecondHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(secondHalfSeqStore, zc); DEBUGLOG(5, "Estimated original block size: %zu -- First half split: %zu -- Second half split: %zu", estimatedOriginalSize, estimatedFirstHalfSize, estimatedSecondHalfSize); if (ZSTD_isError(estimatedOriginalSize) || ZSTD_isError(estimatedFirstHalfSize) || ZSTD_isError(estimatedSecondHalfSize)) { return; } if (estimatedFirstHalfSize + estimatedSecondHalfSize < estimatedOriginalSize) { + DEBUGLOG(5, "split decided at seqNb:%zu", midIdx); ZSTD_deriveBlockSplitsHelper(splits, startIdx, midIdx, zc, origSeqStore); splits->splitLocations[splits->idx] = (U32)midIdx; splits->idx++; @@ -3574,14 +4070,18 @@ static void ZSTD_deriveBlockSplitsHelper(seqStoreSplits* splits, size_t startIdx } } -/* Base recursive function. Populates a table with intra-block partition indices that can improve compression ratio. +/* Base recursive function. + * Populates a table with intra-block partition indices that can improve compression ratio. * - * Returns the number of splits made (which equals the size of the partition table - 1). + * @return: number of splits made (which equals the size of the partition table - 1). */ -static size_t ZSTD_deriveBlockSplits(ZSTD_CCtx* zc, U32 partitions[], U32 nbSeq) { - seqStoreSplits splits = {partitions, 0}; +static size_t ZSTD_deriveBlockSplits(ZSTD_CCtx* zc, U32 partitions[], U32 nbSeq) +{ + seqStoreSplits splits; + splits.splitLocations = partitions; + splits.idx = 0; if (nbSeq <= 4) { - DEBUGLOG(4, "ZSTD_deriveBlockSplits: Too few sequences to split"); + DEBUGLOG(5, "ZSTD_deriveBlockSplits: Too few sequences to split (%u <= 4)", nbSeq); /* Refuse to try and split anything with less than 4 sequences */ return 0; } @@ -3596,17 +4096,21 @@ static size_t ZSTD_deriveBlockSplits(ZSTD_CCtx* zc, U32 partitions[], U32 nbSeq) * * Returns combined size of all blocks (which includes headers), or a ZSTD error code. */ -static size_t ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx* zc, void* dst, size_t dstCapacity, - const void* src, size_t blockSize, U32 lastBlock, U32 nbSeq) { +static size_t +ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx* zc, + void* dst, size_t dstCapacity, + const void* src, size_t blockSize, + U32 lastBlock, U32 nbSeq) +{ size_t cSize = 0; const BYTE* ip = (const BYTE*)src; BYTE* op = (BYTE*)dst; - U32 partitions[MAX_NB_SPLITS]; size_t i = 0; size_t srcBytesTotal = 0; - size_t numSplits = ZSTD_deriveBlockSplits(zc, partitions, nbSeq); - seqStore_t nextSeqStore; - seqStore_t currSeqStore; + U32* const partitions = zc->blockSplitCtx.partitions; /* size == ZSTD_MAX_NB_BLOCK_SPLITS */ + seqStore_t* const nextSeqStore = &zc->blockSplitCtx.nextSeqStore; + seqStore_t* const currSeqStore = &zc->blockSplitCtx.currSeqStore; + size_t const numSplits = ZSTD_deriveBlockSplits(zc, partitions, nbSeq); /* If a block is split and some partitions are emitted as RLE/uncompressed, then repcode history * may become invalid. In order to reconcile potentially invalid repcodes, we keep track of two @@ -3626,77 +4130,81 @@ static size_t ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx* zc, void* dst, s repcodes_t cRep; ZSTD_memcpy(dRep.rep, zc->blockState.prevCBlock->rep, sizeof(repcodes_t)); ZSTD_memcpy(cRep.rep, zc->blockState.prevCBlock->rep, sizeof(repcodes_t)); + ZSTD_memset(nextSeqStore, 0, sizeof(seqStore_t)); - DEBUGLOG(4, "ZSTD_compressBlock_splitBlock_internal (dstCapacity=%u, dictLimit=%u, nextToUpdate=%u)", + DEBUGLOG(5, "ZSTD_compressBlock_splitBlock_internal (dstCapacity=%u, dictLimit=%u, nextToUpdate=%u)", (unsigned)dstCapacity, (unsigned)zc->blockState.matchState.window.dictLimit, (unsigned)zc->blockState.matchState.nextToUpdate); if (numSplits == 0) { - size_t cSizeSingleBlock = ZSTD_compressSeqStore_singleBlock(zc, &zc->seqStore, - &dRep, &cRep, - op, dstCapacity, - ip, blockSize, - lastBlock, 0 /* isPartition */); + size_t cSizeSingleBlock = + ZSTD_compressSeqStore_singleBlock(zc, &zc->seqStore, + &dRep, &cRep, + op, dstCapacity, + ip, blockSize, + lastBlock, 0 /* isPartition */); FORWARD_IF_ERROR(cSizeSingleBlock, "Compressing single block from splitBlock_internal() failed!"); DEBUGLOG(5, "ZSTD_compressBlock_splitBlock_internal: No splits"); - assert(cSizeSingleBlock <= ZSTD_BLOCKSIZE_MAX + ZSTD_blockHeaderSize); + assert(zc->blockSize <= ZSTD_BLOCKSIZE_MAX); + assert(cSizeSingleBlock <= zc->blockSize + ZSTD_blockHeaderSize); return cSizeSingleBlock; } - ZSTD_deriveSeqStoreChunk(&currSeqStore, &zc->seqStore, 0, partitions[0]); + ZSTD_deriveSeqStoreChunk(currSeqStore, &zc->seqStore, 0, partitions[0]); for (i = 0; i <= numSplits; ++i) { - size_t srcBytes; size_t cSizeChunk; U32 const lastPartition = (i == numSplits); U32 lastBlockEntireSrc = 0; - srcBytes = ZSTD_countSeqStoreLiteralsBytes(&currSeqStore) + ZSTD_countSeqStoreMatchBytes(&currSeqStore); + size_t srcBytes = ZSTD_countSeqStoreLiteralsBytes(currSeqStore) + ZSTD_countSeqStoreMatchBytes(currSeqStore); srcBytesTotal += srcBytes; if (lastPartition) { /* This is the final partition, need to account for possible last literals */ srcBytes += blockSize - srcBytesTotal; lastBlockEntireSrc = lastBlock; } else { - ZSTD_deriveSeqStoreChunk(&nextSeqStore, &zc->seqStore, partitions[i], partitions[i+1]); + ZSTD_deriveSeqStoreChunk(nextSeqStore, &zc->seqStore, partitions[i], partitions[i+1]); } - cSizeChunk = ZSTD_compressSeqStore_singleBlock(zc, &currSeqStore, + cSizeChunk = ZSTD_compressSeqStore_singleBlock(zc, currSeqStore, &dRep, &cRep, op, dstCapacity, ip, srcBytes, lastBlockEntireSrc, 1 /* isPartition */); - DEBUGLOG(5, "Estimated size: %zu actual size: %zu", ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(&currSeqStore, zc), cSizeChunk); + DEBUGLOG(5, "Estimated size: %zu vs %zu : actual size", + ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(currSeqStore, zc), cSizeChunk); FORWARD_IF_ERROR(cSizeChunk, "Compressing chunk failed!"); ip += srcBytes; op += cSizeChunk; dstCapacity -= cSizeChunk; cSize += cSizeChunk; - currSeqStore = nextSeqStore; - assert(cSizeChunk <= ZSTD_BLOCKSIZE_MAX + ZSTD_blockHeaderSize); + *currSeqStore = *nextSeqStore; + assert(cSizeChunk <= zc->blockSize + ZSTD_blockHeaderSize); } - /* cRep and dRep may have diverged during the compression. If so, we use the dRep repcodes - * for the next block. + /* cRep and dRep may have diverged during the compression. + * If so, we use the dRep repcodes for the next block. */ ZSTD_memcpy(zc->blockState.prevCBlock->rep, dRep.rep, sizeof(repcodes_t)); return cSize; } -static size_t ZSTD_compressBlock_splitBlock(ZSTD_CCtx* zc, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, U32 lastBlock) { - const BYTE* ip = (const BYTE*)src; - BYTE* op = (BYTE*)dst; +static size_t +ZSTD_compressBlock_splitBlock(ZSTD_CCtx* zc, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, U32 lastBlock) +{ U32 nbSeq; size_t cSize; DEBUGLOG(4, "ZSTD_compressBlock_splitBlock"); + assert(zc->appliedParams.useBlockSplitter == ZSTD_ps_enable); { const size_t bss = ZSTD_buildSeqStore(zc, src, srcSize); FORWARD_IF_ERROR(bss, "ZSTD_buildSeqStore failed"); if (bss == ZSTDbss_noCompress) { if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid) zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check; - cSize = ZSTD_noCompressBlock(op, dstCapacity, ip, srcSize, lastBlock); + cSize = ZSTD_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); FORWARD_IF_ERROR(cSize, "ZSTD_noCompressBlock failed"); DEBUGLOG(4, "ZSTD_compressBlock_splitBlock: Nocompress block"); return cSize; @@ -3704,19 +4212,19 @@ static size_t ZSTD_compressBlock_splitBlock(ZSTD_CCtx* zc, nbSeq = (U32)(zc->seqStore.sequences - zc->seqStore.sequencesStart); } - assert(zc->appliedParams.splitBlocks == 1); cSize = ZSTD_compressBlock_splitBlock_internal(zc, dst, dstCapacity, src, srcSize, lastBlock, nbSeq); FORWARD_IF_ERROR(cSize, "Splitting blocks failed!"); return cSize; } -static size_t ZSTD_compressBlock_internal(ZSTD_CCtx* zc, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, U32 frame) +static size_t +ZSTD_compressBlock_internal(ZSTD_CCtx* zc, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, U32 frame) { - /* This the upper bound for the length of an rle block. - * This isn't the actual upper bound. Finding the real threshold - * needs further investigation. + /* This is an estimated upper bound for the length of an rle block. + * This isn't the actual upper bound. + * Finding the real threshold needs further investigation. */ const U32 rleMaxLength = 25; size_t cSize; @@ -3746,12 +4254,6 @@ static size_t ZSTD_compressBlock_internal(ZSTD_CCtx* zc, zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE /* statically allocated in resetCCtx */, zc->bmi2); - if (zc->seqCollector.collectSequences) { - ZSTD_copyBlockSequences(zc); - return 0; - } - - if (frame && /* We don't want to emit our first block as a RLE even if it qualifies because * doing so will cause the decoder (cli only) to throw a "should consume all input error." @@ -3814,10 +4316,11 @@ static size_t ZSTD_compressBlock_targetCBlockSize_body(ZSTD_CCtx* zc, * * cSize >= blockBound(srcSize): We have expanded the block too much so * emit an uncompressed block. */ - { - size_t const cSize = ZSTD_compressSuperBlock(zc, dst, dstCapacity, src, srcSize, lastBlock); + { size_t const cSize = + ZSTD_compressSuperBlock(zc, dst, dstCapacity, src, srcSize, lastBlock); if (cSize != ERROR(dstSize_tooSmall)) { - size_t const maxCSize = srcSize - ZSTD_minGain(srcSize, zc->appliedParams.cParams.strategy); + size_t const maxCSize = + srcSize - ZSTD_minGain(srcSize, zc->appliedParams.cParams.strategy); FORWARD_IF_ERROR(cSize, "ZSTD_compressSuperBlock failed"); if (cSize != 0 && cSize < maxCSize + ZSTD_blockHeaderSize) { ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); @@ -3825,7 +4328,7 @@ static size_t ZSTD_compressBlock_targetCBlockSize_body(ZSTD_CCtx* zc, } } } - } + } /* if (bss == ZSTDbss_compress)*/ DEBUGLOG(6, "Resorting to ZSTD_noCompressBlock()"); /* Superblock compression failed, attempt to emit a single no compress block. @@ -3883,7 +4386,7 @@ static void ZSTD_overflowCorrectIfNeeded(ZSTD_matchState_t* ms, * All blocks will be terminated, all input will be consumed. * Function will issue an error if there is not enough `dstCapacity` to hold the compressed content. * Frame is supposed already started (header already produced) -* @return : compressed size, or an error code +* @return : compressed size, or an error code */ static size_t ZSTD_compress_frameChunk(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, @@ -3907,7 +4410,9 @@ static size_t ZSTD_compress_frameChunk(ZSTD_CCtx* cctx, ZSTD_matchState_t* const ms = &cctx->blockState.matchState; U32 const lastBlock = lastFrameChunk & (blockSize >= remaining); - RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize + MIN_CBLOCK_SIZE, + /* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding + * additional 1. We need to revisit and change this logic to be more consistent */ + RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize + MIN_CBLOCK_SIZE + 1, dstSize_tooSmall, "not enough space to store compressed block"); if (remaining < blockSize) blockSize = remaining; @@ -3915,6 +4420,7 @@ static size_t ZSTD_compress_frameChunk(ZSTD_CCtx* cctx, ZSTD_overflowCorrectIfNeeded( ms, &cctx->workspace, &cctx->appliedParams, ip, ip + blockSize); ZSTD_checkDictValidity(&ms->window, ip + blockSize, maxDist, &ms->loadedDictEnd, &ms->dictMatchState); + ZSTD_window_enforceMaxDist(&ms->window, ip, maxDist, &ms->loadedDictEnd, &ms->dictMatchState); /* Ensure hash/chain table insertion resumes no sooner than lowlimit */ if (ms->nextToUpdate < ms->window.lowLimit) ms->nextToUpdate = ms->window.lowLimit; @@ -3945,7 +4451,7 @@ static size_t ZSTD_compress_frameChunk(ZSTD_CCtx* cctx, MEM_writeLE24(op, cBlockHeader); cSize += ZSTD_blockHeaderSize; } - } + } /* if (ZSTD_useTargetCBlockSize(&cctx->appliedParams))*/ ip += blockSize; @@ -3991,7 +4497,9 @@ static size_t ZSTD_writeFrameHeader(void* dst, size_t dstCapacity, if (!singleSegment) op[pos++] = windowLogByte; switch(dictIDSizeCode) { - default: assert(0); /* impossible */ + default: + assert(0); /* impossible */ + ZSTD_FALLTHROUGH; case 0 : break; case 1 : op[pos] = (BYTE)(dictID); pos++; break; case 2 : MEM_writeLE16(op+pos, (U16)dictID); pos+=2; break; @@ -3999,7 +4507,9 @@ static size_t ZSTD_writeFrameHeader(void* dst, size_t dstCapacity, } switch(fcsCode) { - default: assert(0); /* impossible */ + default: + assert(0); /* impossible */ + ZSTD_FALLTHROUGH; case 0 : if (singleSegment) op[pos++] = (BYTE)(pledgedSrcSize); break; case 1 : MEM_writeLE16(op+pos, (U16)(pledgedSrcSize-256)); pos+=2; break; case 2 : MEM_writeLE32(op+pos, (U32)(pledgedSrcSize)); pos+=4; break; @@ -4047,7 +4557,7 @@ size_t ZSTD_referenceExternalSequences(ZSTD_CCtx* cctx, rawSeq* seq, size_t nbSe { RETURN_ERROR_IF(cctx->stage != ZSTDcs_init, stage_wrong, "wrong cctx stage"); - RETURN_ERROR_IF(cctx->appliedParams.ldmParams.enableLdm, + RETURN_ERROR_IF(cctx->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable, parameter_unsupported, "incompatible with ldm"); cctx->externSeqStore.seq = seq; @@ -4088,7 +4598,7 @@ static size_t ZSTD_compressContinue_internal (ZSTD_CCtx* cctx, ms->forceNonContiguous = 0; ms->nextToUpdate = ms->window.dictLimit; } - if (cctx->appliedParams.ldmParams.enableLdm) { + if (cctx->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable) { ZSTD_window_update(&cctx->ldmState.window, src, srcSize, /* forceNonContiguous */ 0); } @@ -4120,31 +4630,51 @@ static size_t ZSTD_compressContinue_internal (ZSTD_CCtx* cctx, } } -size_t ZSTD_compressContinue (ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize) +size_t ZSTD_compressContinue_public(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) { DEBUGLOG(5, "ZSTD_compressContinue (srcSize=%u)", (unsigned)srcSize); return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 1 /* frame mode */, 0 /* last chunk */); } +/* NOTE: Must just wrap ZSTD_compressContinue_public() */ +size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) +{ + return ZSTD_compressContinue_public(cctx, dst, dstCapacity, src, srcSize); +} -size_t ZSTD_getBlockSize(const ZSTD_CCtx* cctx) +static size_t ZSTD_getBlockSize_deprecated(const ZSTD_CCtx* cctx) { ZSTD_compressionParameters const cParams = cctx->appliedParams.cParams; assert(!ZSTD_checkCParams(cParams)); - return MIN (ZSTD_BLOCKSIZE_MAX, (U32)1 << cParams.windowLog); + return MIN(cctx->appliedParams.maxBlockSize, (size_t)1 << cParams.windowLog); } -size_t ZSTD_compressBlock(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) +/* NOTE: Must just wrap ZSTD_getBlockSize_deprecated() */ +size_t ZSTD_getBlockSize(const ZSTD_CCtx* cctx) +{ + return ZSTD_getBlockSize_deprecated(cctx); +} + +/* NOTE: Must just wrap ZSTD_compressBlock_deprecated() */ +size_t ZSTD_compressBlock_deprecated(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) { DEBUGLOG(5, "ZSTD_compressBlock: srcSize = %u", (unsigned)srcSize); - { size_t const blockSizeMax = ZSTD_getBlockSize(cctx); + { size_t const blockSizeMax = ZSTD_getBlockSize_deprecated(cctx); RETURN_ERROR_IF(srcSize > blockSizeMax, srcSize_wrong, "input is larger than a block"); } return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 0 /* frame mode */, 0 /* last chunk */); } +/* NOTE: Must just wrap ZSTD_compressBlock_deprecated() */ +size_t ZSTD_compressBlock(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) +{ + return ZSTD_compressBlock_deprecated(cctx, dst, dstCapacity, src, srcSize); +} + /*! ZSTD_loadDictionaryContent() : * @return : 0, or an error code */ @@ -4153,25 +4683,36 @@ static size_t ZSTD_loadDictionaryContent(ZSTD_matchState_t* ms, ZSTD_cwksp* ws, ZSTD_CCtx_params const* params, const void* src, size_t srcSize, - ZSTD_dictTableLoadMethod_e dtlm) + ZSTD_dictTableLoadMethod_e dtlm, + ZSTD_tableFillPurpose_e tfp) { const BYTE* ip = (const BYTE*) src; const BYTE* const iend = ip + srcSize; - int const loadLdmDict = params->ldmParams.enableLdm && ls != NULL; + int const loadLdmDict = params->ldmParams.enableLdm == ZSTD_ps_enable && ls != NULL; - /* Assert that we the ms params match the params we're being given */ + /* Assert that the ms params match the params we're being given */ ZSTD_assertEqualCParams(params->cParams, ms->cParams); - if (srcSize > ZSTD_CHUNKSIZE_MAX) { + { /* Ensure large dictionaries can't cause index overflow */ + /* Allow the dictionary to set indices up to exactly ZSTD_CURRENT_MAX. * Dictionaries right at the edge will immediately trigger overflow * correction, but I don't want to insert extra constraints here. */ - U32 const maxDictSize = ZSTD_CURRENT_MAX - 1; - /* We must have cleared our windows when our source is this large. */ - assert(ZSTD_window_isEmpty(ms->window)); - if (loadLdmDict) - assert(ZSTD_window_isEmpty(ls->window)); + U32 maxDictSize = ZSTD_CURRENT_MAX - ZSTD_WINDOW_START_INDEX; + + int const CDictTaggedIndices = ZSTD_CDictIndicesAreTagged(¶ms->cParams); + if (CDictTaggedIndices && tfp == ZSTD_tfp_forCDict) { + /* Some dictionary matchfinders in zstd use "short cache", + * which treats the lower ZSTD_SHORT_CACHE_TAG_BITS of each + * CDict hashtable entry as a tag rather than as part of an index. + * When short cache is used, we need to truncate the dictionary + * so that its indices don't overlap with the tag. */ + U32 const shortCacheMaxDictSize = (1u << (32 - ZSTD_SHORT_CACHE_TAG_BITS)) - ZSTD_WINDOW_START_INDEX; + maxDictSize = MIN(maxDictSize, shortCacheMaxDictSize); + assert(!loadLdmDict); + } + /* If the dictionary is too large, only load the suffix of the dictionary. */ if (srcSize > maxDictSize) { ip = iend - maxDictSize; @@ -4180,30 +4721,46 @@ static size_t ZSTD_loadDictionaryContent(ZSTD_matchState_t* ms, } } - DEBUGLOG(4, "ZSTD_loadDictionaryContent(): useRowMatchFinder=%d", (int)params->useRowMatchFinder); + if (srcSize > ZSTD_CHUNKSIZE_MAX) { + /* We must have cleared our windows when our source is this large. */ + assert(ZSTD_window_isEmpty(ms->window)); + if (loadLdmDict) assert(ZSTD_window_isEmpty(ls->window)); + } ZSTD_window_update(&ms->window, src, srcSize, /* forceNonContiguous */ 0); - ms->loadedDictEnd = params->forceWindow ? 0 : (U32)(iend - ms->window.base); - ms->forceNonContiguous = params->deterministicRefPrefix; - if (loadLdmDict) { + DEBUGLOG(4, "ZSTD_loadDictionaryContent(): useRowMatchFinder=%d", (int)params->useRowMatchFinder); + + if (loadLdmDict) { /* Load the entire dict into LDM matchfinders. */ ZSTD_window_update(&ls->window, src, srcSize, /* forceNonContiguous */ 0); ls->loadedDictEnd = params->forceWindow ? 0 : (U32)(iend - ls->window.base); + ZSTD_ldm_fillHashTable(ls, ip, iend, ¶ms->ldmParams); + } + + /* If the dict is larger than we can reasonably index in our tables, only load the suffix. */ + if (params->cParams.strategy < ZSTD_btultra) { + U32 maxDictSize = 8U << MIN(MAX(params->cParams.hashLog, params->cParams.chainLog), 28); + if (srcSize > maxDictSize) { + ip = iend - maxDictSize; + /* src = ip; deadcode.DeadStores */ + srcSize = maxDictSize; + } } + ms->nextToUpdate = (U32)(ip - ms->window.base); + ms->loadedDictEnd = params->forceWindow ? 0 : (U32)(iend - ms->window.base); + ms->forceNonContiguous = params->deterministicRefPrefix; + if (srcSize <= HASH_READ_SIZE) return 0; ZSTD_overflowCorrectIfNeeded(ms, ws, params, ip, iend); - if (loadLdmDict) - ZSTD_ldm_fillHashTable(ls, ip, iend, ¶ms->ldmParams); - switch(params->cParams.strategy) { case ZSTD_fast: - ZSTD_fillHashTable(ms, iend, dtlm); + ZSTD_fillHashTable(ms, iend, dtlm, tfp); break; case ZSTD_dfast: - ZSTD_fillDoubleHashTable(ms, iend, dtlm); + ZSTD_fillDoubleHashTable(ms, iend, dtlm, tfp); break; case ZSTD_greedy: @@ -4214,9 +4771,9 @@ static size_t ZSTD_loadDictionaryContent(ZSTD_matchState_t* ms, assert(ms->chainTable != NULL); ZSTD_dedicatedDictSearch_lazy_loadDictionary(ms, iend-HASH_READ_SIZE); } else { - assert(params->useRowMatchFinder != ZSTD_urm_auto); - if (params->useRowMatchFinder == ZSTD_urm_enableRowMatchFinder) { - size_t const tagTableSize = ((size_t)1 << params->cParams.hashLog) * sizeof(U16); + assert(params->useRowMatchFinder != ZSTD_ps_auto); + if (params->useRowMatchFinder == ZSTD_ps_enable) { + size_t const tagTableSize = ((size_t)1 << params->cParams.hashLog); ZSTD_memset(ms->tagTable, 0, tagTableSize); ZSTD_row_update(ms, iend-HASH_READ_SIZE); DEBUGLOG(4, "Using row-based hash table for lazy dict"); @@ -4369,6 +4926,7 @@ static size_t ZSTD_loadZstdDictionary(ZSTD_compressedBlockState_t* bs, ZSTD_CCtx_params const* params, const void* dict, size_t dictSize, ZSTD_dictTableLoadMethod_e dtlm, + ZSTD_tableFillPurpose_e tfp, void* workspace) { const BYTE* dictPtr = (const BYTE*)dict; @@ -4387,7 +4945,7 @@ static size_t ZSTD_loadZstdDictionary(ZSTD_compressedBlockState_t* bs, { size_t const dictContentSize = (size_t)(dictEnd - dictPtr); FORWARD_IF_ERROR(ZSTD_loadDictionaryContent( - ms, NULL, ws, params, dictPtr, dictContentSize, dtlm), ""); + ms, NULL, ws, params, dictPtr, dictContentSize, dtlm, tfp), ""); } return dictID; } @@ -4403,6 +4961,7 @@ ZSTD_compress_insertDictionary(ZSTD_compressedBlockState_t* bs, const void* dict, size_t dictSize, ZSTD_dictContentType_e dictContentType, ZSTD_dictTableLoadMethod_e dtlm, + ZSTD_tableFillPurpose_e tfp, void* workspace) { DEBUGLOG(4, "ZSTD_compress_insertDictionary (dictSize=%u)", (U32)dictSize); @@ -4415,13 +4974,13 @@ ZSTD_compress_insertDictionary(ZSTD_compressedBlockState_t* bs, /* dict restricted modes */ if (dictContentType == ZSTD_dct_rawContent) - return ZSTD_loadDictionaryContent(ms, ls, ws, params, dict, dictSize, dtlm); + return ZSTD_loadDictionaryContent(ms, ls, ws, params, dict, dictSize, dtlm, tfp); if (MEM_readLE32(dict) != ZSTD_MAGIC_DICTIONARY) { if (dictContentType == ZSTD_dct_auto) { DEBUGLOG(4, "raw content dictionary detected"); return ZSTD_loadDictionaryContent( - ms, ls, ws, params, dict, dictSize, dtlm); + ms, ls, ws, params, dict, dictSize, dtlm, tfp); } RETURN_ERROR_IF(dictContentType == ZSTD_dct_fullDict, dictionary_wrong, ""); assert(0); /* impossible */ @@ -4429,13 +4988,14 @@ ZSTD_compress_insertDictionary(ZSTD_compressedBlockState_t* bs, /* dict as full zstd dictionary */ return ZSTD_loadZstdDictionary( - bs, ms, ws, params, dict, dictSize, dtlm, workspace); + bs, ms, ws, params, dict, dictSize, dtlm, tfp, workspace); } #define ZSTD_USE_CDICT_PARAMS_SRCSIZE_CUTOFF (128 KB) #define ZSTD_USE_CDICT_PARAMS_DICTSIZE_MULTIPLIER (6ULL) /*! ZSTD_compressBegin_internal() : + * Assumption : either @dict OR @cdict (or none) is non-NULL, never both * @return : 0, or an error code */ static size_t ZSTD_compressBegin_internal(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, @@ -4471,11 +5031,11 @@ static size_t ZSTD_compressBegin_internal(ZSTD_CCtx* cctx, cctx->blockState.prevCBlock, &cctx->blockState.matchState, &cctx->ldmState, &cctx->workspace, &cctx->appliedParams, cdict->dictContent, cdict->dictContentSize, cdict->dictContentType, dtlm, - cctx->entropyWorkspace) + ZSTD_tfp_forCCtx, cctx->entropyWorkspace) : ZSTD_compress_insertDictionary( cctx->blockState.prevCBlock, &cctx->blockState.matchState, &cctx->ldmState, &cctx->workspace, &cctx->appliedParams, dict, dictSize, - dictContentType, dtlm, cctx->entropyWorkspace); + dictContentType, dtlm, ZSTD_tfp_forCCtx, cctx->entropyWorkspace); FORWARD_IF_ERROR(dictID, "ZSTD_compress_insertDictionary failed"); assert(dictID <= UINT_MAX); cctx->dictID = (U32)dictID; @@ -4516,11 +5076,11 @@ size_t ZSTD_compressBegin_advanced(ZSTD_CCtx* cctx, &cctxParams, pledgedSrcSize); } -size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel) +static size_t +ZSTD_compressBegin_usingDict_deprecated(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel) { ZSTD_CCtx_params cctxParams; - { - ZSTD_parameters const params = ZSTD_getParams_internal(compressionLevel, ZSTD_CONTENTSIZE_UNKNOWN, dictSize, ZSTD_cpm_noAttachDict); + { ZSTD_parameters const params = ZSTD_getParams_internal(compressionLevel, ZSTD_CONTENTSIZE_UNKNOWN, dictSize, ZSTD_cpm_noAttachDict); ZSTD_CCtxParams_init_internal(&cctxParams, ¶ms, (compressionLevel == 0) ? ZSTD_CLEVEL_DEFAULT : compressionLevel); } DEBUGLOG(4, "ZSTD_compressBegin_usingDict (dictSize=%u)", (unsigned)dictSize); @@ -4528,9 +5088,15 @@ size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t di &cctxParams, ZSTD_CONTENTSIZE_UNKNOWN, ZSTDb_not_buffered); } +size_t +ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel) +{ + return ZSTD_compressBegin_usingDict_deprecated(cctx, dict, dictSize, compressionLevel); +} + size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel) { - return ZSTD_compressBegin_usingDict(cctx, NULL, 0, compressionLevel); + return ZSTD_compressBegin_usingDict_deprecated(cctx, NULL, 0, compressionLevel); } @@ -4600,9 +5166,9 @@ void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize) #endif } -size_t ZSTD_compressEnd (ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize) +size_t ZSTD_compressEnd_public(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) { size_t endResult; size_t const cSize = ZSTD_compressContinue_internal(cctx, @@ -4626,6 +5192,14 @@ size_t ZSTD_compressEnd (ZSTD_CCtx* cctx, return cSize + endResult; } +/* NOTE: Must just wrap ZSTD_compressEnd_public() */ +size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) +{ + return ZSTD_compressEnd_public(cctx, dst, dstCapacity, src, srcSize); +} + size_t ZSTD_compress_advanced (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, @@ -4654,7 +5228,7 @@ size_t ZSTD_compress_advanced_internal( FORWARD_IF_ERROR( ZSTD_compressBegin_internal(cctx, dict, dictSize, ZSTD_dct_auto, ZSTD_dtlm_fast, NULL, params, srcSize, ZSTDb_not_buffered) , ""); - return ZSTD_compressEnd(cctx, dst, dstCapacity, src, srcSize); + return ZSTD_compressEnd_public(cctx, dst, dstCapacity, src, srcSize); } size_t ZSTD_compress_usingDict(ZSTD_CCtx* cctx, @@ -4715,7 +5289,7 @@ size_t ZSTD_estimateCDictSize_advanced( + ZSTD_cwksp_alloc_size(HUF_WORKSPACE_SIZE) /* enableDedicatedDictSearch == 1 ensures that CDict estimation will not be too small * in case we are using DDS with row-hash. */ - + ZSTD_sizeof_matchState(&cParams, ZSTD_resolveRowMatchFinderMode(ZSTD_urm_auto, &cParams), + + ZSTD_sizeof_matchState(&cParams, ZSTD_resolveRowMatchFinderMode(ZSTD_ps_auto, &cParams), /* enableDedicatedDictSearch */ 1, /* forCCtx */ 0) + (dictLoadMethod == ZSTD_dlm_byRef ? 0 : ZSTD_cwksp_alloc_size(ZSTD_cwksp_align(dictSize, sizeof(void *)))); @@ -4779,7 +5353,7 @@ static size_t ZSTD_initCDict_internal( { size_t const dictID = ZSTD_compress_insertDictionary( &cdict->cBlockState, &cdict->matchState, NULL, &cdict->workspace, ¶ms, cdict->dictContent, cdict->dictContentSize, - dictContentType, ZSTD_dtlm_full, cdict->entropyWorkspace); + dictContentType, ZSTD_dtlm_full, ZSTD_tfp_forCDict, cdict->entropyWorkspace); FORWARD_IF_ERROR(dictID, "ZSTD_compress_insertDictionary failed"); assert(dictID <= (size_t)(U32)-1); cdict->dictID = (U32)dictID; @@ -4792,7 +5366,7 @@ static size_t ZSTD_initCDict_internal( static ZSTD_CDict* ZSTD_createCDict_advanced_internal(size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_compressionParameters cParams, - ZSTD_useRowMatchFinderMode_e useRowMatchFinder, + ZSTD_paramSwitch_e useRowMatchFinder, U32 enableDedicatedDictSearch, ZSTD_customMem customMem) { @@ -4842,7 +5416,7 @@ ZSTD_CDict* ZSTD_createCDict_advanced(const void* dictBuffer, size_t dictSize, &cctxParams, customMem); } -ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict_advanced2( +ZSTD_CDict* ZSTD_createCDict_advanced2( const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, @@ -4947,7 +5521,7 @@ const ZSTD_CDict* ZSTD_initStaticCDict( ZSTD_dictContentType_e dictContentType, ZSTD_compressionParameters cParams) { - ZSTD_useRowMatchFinderMode_e const useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(ZSTD_urm_auto, &cParams); + ZSTD_paramSwitch_e const useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(ZSTD_ps_auto, &cParams); /* enableDedicatedDictSearch == 1 ensures matchstate is not too small in case this CDict will be used for DDS + row hash */ size_t const matchStateSize = ZSTD_sizeof_matchState(&cParams, useRowMatchFinder, /* enableDedicatedDictSearch */ 1, /* forCCtx */ 0); size_t const neededSize = ZSTD_cwksp_alloc_size(sizeof(ZSTD_CDict)) @@ -4976,6 +5550,7 @@ const ZSTD_CDict* ZSTD_initStaticCDict( params.cParams = cParams; params.useRowMatchFinder = useRowMatchFinder; cdict->useRowMatchFinder = useRowMatchFinder; + cdict->compressionLevel = ZSTD_NO_CLEVEL; if (ZSTD_isError( ZSTD_initCDict_internal(cdict, dict, dictSize, @@ -5055,12 +5630,17 @@ size_t ZSTD_compressBegin_usingCDict_advanced( /* ZSTD_compressBegin_usingCDict() : * cdict must be != NULL */ -size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict) +size_t ZSTD_compressBegin_usingCDict_deprecated(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict) { ZSTD_frameParameters const fParams = { 0 /*content*/, 0 /*checksum*/, 0 /*noDictID*/ }; return ZSTD_compressBegin_usingCDict_internal(cctx, cdict, fParams, ZSTD_CONTENTSIZE_UNKNOWN); } +size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict) +{ + return ZSTD_compressBegin_usingCDict_deprecated(cctx, cdict); +} + /*! ZSTD_compress_usingCDict_internal(): * Implementation of various ZSTD_compress_usingCDict* functions. */ @@ -5070,7 +5650,7 @@ static size_t ZSTD_compress_usingCDict_internal(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict, ZSTD_frameParameters fParams) { FORWARD_IF_ERROR(ZSTD_compressBegin_usingCDict_internal(cctx, cdict, fParams, srcSize), ""); /* will check if cdict != NULL */ - return ZSTD_compressEnd(cctx, dst, dstCapacity, src, srcSize); + return ZSTD_compressEnd_public(cctx, dst, dstCapacity, src, srcSize); } /*! ZSTD_compress_usingCDict_advanced(): @@ -5267,30 +5847,41 @@ size_t ZSTD_initCStream(ZSTD_CStream* zcs, int compressionLevel) static size_t ZSTD_nextInputSizeHint(const ZSTD_CCtx* cctx) { - size_t hintInSize = cctx->inBuffTarget - cctx->inBuffPos; - if (hintInSize==0) hintInSize = cctx->blockSize; - return hintInSize; + if (cctx->appliedParams.inBufferMode == ZSTD_bm_stable) { + return cctx->blockSize - cctx->stableIn_notConsumed; + } + assert(cctx->appliedParams.inBufferMode == ZSTD_bm_buffered); + { size_t hintInSize = cctx->inBuffTarget - cctx->inBuffPos; + if (hintInSize==0) hintInSize = cctx->blockSize; + return hintInSize; + } } /** ZSTD_compressStream_generic(): * internal function for all *compressStream*() variants - * non-static, because can be called from zstdmt_compress.c - * @return : hint size for next input */ + * @return : hint size for next input to complete ongoing block */ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, ZSTD_outBuffer* output, ZSTD_inBuffer* input, ZSTD_EndDirective const flushMode) { - const char* const istart = (const char*)input->src; - const char* const iend = input->size != 0 ? istart + input->size : istart; - const char* ip = input->pos != 0 ? istart + input->pos : istart; - char* const ostart = (char*)output->dst; - char* const oend = output->size != 0 ? ostart + output->size : ostart; - char* op = output->pos != 0 ? ostart + output->pos : ostart; + const char* const istart = (assert(input != NULL), (const char*)input->src); + const char* const iend = (istart != NULL) ? istart + input->size : istart; + const char* ip = (istart != NULL) ? istart + input->pos : istart; + char* const ostart = (assert(output != NULL), (char*)output->dst); + char* const oend = (ostart != NULL) ? ostart + output->size : ostart; + char* op = (ostart != NULL) ? ostart + output->pos : ostart; U32 someMoreWork = 1; /* check expectations */ - DEBUGLOG(5, "ZSTD_compressStream_generic, flush=%u", (unsigned)flushMode); + DEBUGLOG(5, "ZSTD_compressStream_generic, flush=%i, srcSize = %zu", (int)flushMode, input->size - input->pos); + assert(zcs != NULL); + if (zcs->appliedParams.inBufferMode == ZSTD_bm_stable) { + assert(input->pos >= zcs->stableIn_notConsumed); + input->pos -= zcs->stableIn_notConsumed; + ip -= zcs->stableIn_notConsumed; + zcs->stableIn_notConsumed = 0; + } if (zcs->appliedParams.inBufferMode == ZSTD_bm_buffered) { assert(zcs->inBuff != NULL); assert(zcs->inBuffSize > 0); @@ -5299,8 +5890,10 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, assert(zcs->outBuff != NULL); assert(zcs->outBuffSize > 0); } - assert(output->pos <= output->size); + if (input->src == NULL) assert(input->size == 0); assert(input->pos <= input->size); + if (output->dst == NULL) assert(output->size == 0); + assert(output->pos <= output->size); assert((U32)flushMode <= (U32)ZSTD_e_end); while (someMoreWork) { @@ -5315,7 +5908,7 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, || zcs->appliedParams.outBufferMode == ZSTD_bm_stable) /* OR we are allowed to return dstSizeTooSmall */ && (zcs->inBuffPos == 0) ) { /* shortcut to compression pass directly into output buffer */ - size_t const cSize = ZSTD_compressEnd(zcs, + size_t const cSize = ZSTD_compressEnd_public(zcs, op, oend-op, ip, iend-ip); DEBUGLOG(4, "ZSTD_compressEnd : cSize=%u", (unsigned)cSize); FORWARD_IF_ERROR(cSize, "ZSTD_compressEnd failed"); @@ -5332,8 +5925,7 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, zcs->inBuff + zcs->inBuffPos, toLoad, ip, iend-ip); zcs->inBuffPos += loaded; - if (loaded != 0) - ip += loaded; + if (ip) ip += loaded; if ( (flushMode == ZSTD_e_continue) && (zcs->inBuffPos < zcs->inBuffTarget) ) { /* not enough input to fill full block : stop here */ @@ -5344,6 +5936,20 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, /* empty */ someMoreWork = 0; break; } + } else { + assert(zcs->appliedParams.inBufferMode == ZSTD_bm_stable); + if ( (flushMode == ZSTD_e_continue) + && ( (size_t)(iend - ip) < zcs->blockSize) ) { + /* can't compress a full block : stop here */ + zcs->stableIn_notConsumed = (size_t)(iend - ip); + ip = iend; /* pretend to have consumed input */ + someMoreWork = 0; break; + } + if ( (flushMode == ZSTD_e_flush) + && (ip == iend) ) { + /* empty */ + someMoreWork = 0; break; + } } /* compress current block (note : this stage cannot be stopped in the middle) */ DEBUGLOG(5, "stream compression stage (flushMode==%u)", flushMode); @@ -5351,9 +5957,8 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, void* cDst; size_t cSize; size_t oSize = oend-op; - size_t const iSize = inputBuffered - ? zcs->inBuffPos - zcs->inToCompress - : MIN((size_t)(iend - ip), zcs->blockSize); + size_t const iSize = inputBuffered ? zcs->inBuffPos - zcs->inToCompress + : MIN((size_t)(iend - ip), zcs->blockSize); if (oSize >= ZSTD_compressBound(iSize) || zcs->appliedParams.outBufferMode == ZSTD_bm_stable) cDst = op; /* compress into output buffer, to skip flush stage */ else @@ -5361,9 +5966,9 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, if (inputBuffered) { unsigned const lastBlock = (flushMode == ZSTD_e_end) && (ip==iend); cSize = lastBlock ? - ZSTD_compressEnd(zcs, cDst, oSize, + ZSTD_compressEnd_public(zcs, cDst, oSize, zcs->inBuff + zcs->inToCompress, iSize) : - ZSTD_compressContinue(zcs, cDst, oSize, + ZSTD_compressContinue_public(zcs, cDst, oSize, zcs->inBuff + zcs->inToCompress, iSize); FORWARD_IF_ERROR(cSize, "%s", lastBlock ? "ZSTD_compressEnd failed" : "ZSTD_compressContinue failed"); zcs->frameEnded = lastBlock; @@ -5376,19 +5981,16 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, if (!lastBlock) assert(zcs->inBuffTarget <= zcs->inBuffSize); zcs->inToCompress = zcs->inBuffPos; - } else { - unsigned const lastBlock = (ip + iSize == iend); - assert(flushMode == ZSTD_e_end /* Already validated */); + } else { /* !inputBuffered, hence ZSTD_bm_stable */ + unsigned const lastBlock = (flushMode == ZSTD_e_end) && (ip + iSize == iend); cSize = lastBlock ? - ZSTD_compressEnd(zcs, cDst, oSize, ip, iSize) : - ZSTD_compressContinue(zcs, cDst, oSize, ip, iSize); + ZSTD_compressEnd_public(zcs, cDst, oSize, ip, iSize) : + ZSTD_compressContinue_public(zcs, cDst, oSize, ip, iSize); /* Consume the input prior to error checking to mirror buffered mode. */ - if (iSize > 0) - ip += iSize; + if (ip) ip += iSize; FORWARD_IF_ERROR(cSize, "%s", lastBlock ? "ZSTD_compressEnd failed" : "ZSTD_compressContinue failed"); zcs->frameEnded = lastBlock; - if (lastBlock) - assert(ip == iend); + if (lastBlock) assert(ip == iend); } if (cDst == op) { /* no need to flush */ op += cSize; @@ -5403,7 +6005,7 @@ static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, zcs->outBuffFlushedSize = 0; zcs->streamStage = zcss_flush; /* pass-through to flush stage */ } - /* fall-through */ + ZSTD_FALLTHROUGH; case zcss_flush: DEBUGLOG(5, "flush stage"); assert(zcs->appliedParams.outBufferMode == ZSTD_bm_buffered); @@ -5464,8 +6066,10 @@ size_t ZSTD_compressStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output, ZSTD_inBuf /* After a compression call set the expected input/output buffer. * This is validated at the start of the next compression call. */ -static void ZSTD_setBufferExpectations(ZSTD_CCtx* cctx, ZSTD_outBuffer const* output, ZSTD_inBuffer const* input) +static void +ZSTD_setBufferExpectations(ZSTD_CCtx* cctx, const ZSTD_outBuffer* output, const ZSTD_inBuffer* input) { + DEBUGLOG(5, "ZSTD_setBufferExpectations (for advanced stable in/out modes)"); if (cctx->appliedParams.inBufferMode == ZSTD_bm_stable) { cctx->expectedInBuffer = *input; } @@ -5484,22 +6088,22 @@ static size_t ZSTD_checkBufferStability(ZSTD_CCtx const* cctx, { if (cctx->appliedParams.inBufferMode == ZSTD_bm_stable) { ZSTD_inBuffer const expect = cctx->expectedInBuffer; - if (expect.src != input->src || expect.pos != input->pos || expect.size != input->size) - RETURN_ERROR(srcBuffer_wrong, "ZSTD_c_stableInBuffer enabled but input differs!"); - if (endOp != ZSTD_e_end) - RETURN_ERROR(srcBuffer_wrong, "ZSTD_c_stableInBuffer can only be used with ZSTD_e_end!"); + if (expect.src != input->src || expect.pos != input->pos) + RETURN_ERROR(stabilityCondition_notRespected, "ZSTD_c_stableInBuffer enabled but input differs!"); } + (void)endOp; if (cctx->appliedParams.outBufferMode == ZSTD_bm_stable) { size_t const outBufferSize = output->size - output->pos; if (cctx->expectedOutBufferSize != outBufferSize) - RETURN_ERROR(dstBuffer_wrong, "ZSTD_c_stableOutBuffer enabled but output size differs!"); + RETURN_ERROR(stabilityCondition_notRespected, "ZSTD_c_stableOutBuffer enabled but output size differs!"); } return 0; } static size_t ZSTD_CCtx_init_compressStream2(ZSTD_CCtx* cctx, ZSTD_EndDirective endOp, - size_t inSize) { + size_t inSize) +{ ZSTD_CCtx_params params = cctx->requestedParams; ZSTD_prefixDict const prefixDict = cctx->prefixDict; FORWARD_IF_ERROR( ZSTD_initLocalDict(cctx) , ""); /* Init the local dict if present. */ @@ -5513,9 +6117,9 @@ static size_t ZSTD_CCtx_init_compressStream2(ZSTD_CCtx* cctx, params.compressionLevel = cctx->cdict->compressionLevel; } DEBUGLOG(4, "ZSTD_compressStream2 : transparent init stage"); - if (endOp == ZSTD_e_end) cctx->pledgedSrcSizePlusOne = inSize + 1; /* auto-fix pledgedSrcSize */ - { - size_t const dictSize = prefixDict.dict + if (endOp == ZSTD_e_end) cctx->pledgedSrcSizePlusOne = inSize + 1; /* auto-determine pledgedSrcSize */ + + { size_t const dictSize = prefixDict.dict ? prefixDict.dictSize : (cctx->cdict ? cctx->cdict->dictContentSize : 0); ZSTD_cParamMode_e const mode = ZSTD_getCParamMode(cctx->cdict, ¶ms, cctx->pledgedSrcSizePlusOne - 1); @@ -5524,20 +6128,21 @@ static size_t ZSTD_CCtx_init_compressStream2(ZSTD_CCtx* cctx, dictSize, mode); } - if (ZSTD_CParams_shouldEnableLdm(¶ms.cParams)) { - /* Enable LDM by default for optimal parser and window size >= 128MB */ - DEBUGLOG(4, "LDM enabled by default (window size >= 128MB, strategy >= btopt)"); - params.ldmParams.enableLdm = 1; - } - - if (ZSTD_CParams_useBlockSplitter(¶ms.cParams)) { - DEBUGLOG(4, "Block splitter enabled by default (window size >= 128K, strategy >= btopt)"); - params.splitBlocks = 1; - } - + params.useBlockSplitter = ZSTD_resolveBlockSplitterMode(params.useBlockSplitter, ¶ms.cParams); + params.ldmParams.enableLdm = ZSTD_resolveEnableLdm(params.ldmParams.enableLdm, ¶ms.cParams); params.useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(params.useRowMatchFinder, ¶ms.cParams); + params.validateSequences = ZSTD_resolveExternalSequenceValidation(params.validateSequences); + params.maxBlockSize = ZSTD_resolveMaxBlockSize(params.maxBlockSize); + params.searchForExternalRepcodes = ZSTD_resolveExternalRepcodeSearch(params.searchForExternalRepcodes, params.compressionLevel); #ifdef ZSTD_MULTITHREAD + /* If external matchfinder is enabled, make sure to fail before checking job size (for consistency) */ + RETURN_ERROR_IF( + params.useSequenceProducer == 1 && params.nbWorkers >= 1, + parameter_combination_unsupported, + "External sequence producer isn't supported with nbWorkers >= 1" + ); + if ((cctx->pledgedSrcSizePlusOne-1) <= ZSTDMT_JOBSIZE_MIN) { params.nbWorkers = 0; /* do not invoke multi-threading when src size is too small */ } @@ -5565,7 +6170,7 @@ static size_t ZSTD_CCtx_init_compressStream2(ZSTD_CCtx* cctx, cctx->streamStage = zcss_load; cctx->appliedParams = params; } else -#endif +#endif /* ZSTD_MULTITHREAD */ { U64 const pledgedSrcSize = cctx->pledgedSrcSizePlusOne - 1; assert(!ZSTD_isError(ZSTD_checkCParams(params.cParams))); FORWARD_IF_ERROR( ZSTD_compressBegin_internal(cctx, @@ -5591,6 +6196,8 @@ static size_t ZSTD_CCtx_init_compressStream2(ZSTD_CCtx* cctx, return 0; } +/* @return provides a minimum amount of data remaining to be flushed from internal buffers + */ size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, ZSTD_outBuffer* output, ZSTD_inBuffer* input, @@ -5605,8 +6212,27 @@ size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, /* transparent initialization stage */ if (cctx->streamStage == zcss_init) { - FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, endOp, input->size), "CompressStream2 initialization failed"); - ZSTD_setBufferExpectations(cctx, output, input); /* Set initial buffer expectations now that we've initialized */ + size_t const inputSize = input->size - input->pos; /* no obligation to start from pos==0 */ + size_t const totalInputSize = inputSize + cctx->stableIn_notConsumed; + if ( (cctx->requestedParams.inBufferMode == ZSTD_bm_stable) /* input is presumed stable, across invocations */ + && (endOp == ZSTD_e_continue) /* no flush requested, more input to come */ + && (totalInputSize < ZSTD_BLOCKSIZE_MAX) ) { /* not even reached one block yet */ + if (cctx->stableIn_notConsumed) { /* not the first time */ + /* check stable source guarantees */ + RETURN_ERROR_IF(input->src != cctx->expectedInBuffer.src, stabilityCondition_notRespected, "stableInBuffer condition not respected: wrong src pointer"); + RETURN_ERROR_IF(input->pos != cctx->expectedInBuffer.size, stabilityCondition_notRespected, "stableInBuffer condition not respected: externally modified pos"); + } + /* pretend input was consumed, to give a sense forward progress */ + input->pos = input->size; + /* save stable inBuffer, for later control, and flush/end */ + cctx->expectedInBuffer = *input; + /* but actually input wasn't consumed, so keep track of position from where compression shall resume */ + cctx->stableIn_notConsumed += inputSize; + /* don't initialize yet, wait for the first block of flush() order, for better parameters adaptation */ + return ZSTD_FRAMEHEADERSIZE_MIN(cctx->requestedParams.format); /* at least some header to produce */ + } + FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, endOp, totalInputSize), "compressStream2 initialization failed"); + ZSTD_setBufferExpectations(cctx, output, input); /* Set initial buffer expectations now that we've initialized */ } /* end of transparent initialization stage */ @@ -5619,6 +6245,13 @@ size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, ZSTDMT_updateCParams_whileCompressing(cctx->mtctx, &cctx->requestedParams); cctx->cParamsChanged = 0; } + if (cctx->stableIn_notConsumed) { + assert(cctx->appliedParams.inBufferMode == ZSTD_bm_stable); + /* some early data was skipped - make it available for consumption */ + assert(input->pos >= cctx->stableIn_notConsumed); + input->pos -= cctx->stableIn_notConsumed; + cctx->stableIn_notConsumed = 0; + } for (;;) { size_t const ipos = input->pos; size_t const opos = output->pos; @@ -5657,7 +6290,7 @@ size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, ZSTD_setBufferExpectations(cctx, output, input); return flushMin; } -#endif +#endif /* ZSTD_MULTITHREAD */ FORWARD_IF_ERROR( ZSTD_compressStream_generic(cctx, output, input, endOp) , ""); DEBUGLOG(5, "completed ZSTD_compressStream2"); ZSTD_setBufferExpectations(cctx, output, input); @@ -5670,13 +6303,20 @@ size_t ZSTD_compressStream2_simpleArgs ( const void* src, size_t srcSize, size_t* srcPos, ZSTD_EndDirective endOp) { - ZSTD_outBuffer output = { dst, dstCapacity, *dstPos }; - ZSTD_inBuffer input = { src, srcSize, *srcPos }; + ZSTD_outBuffer output; + ZSTD_inBuffer input; + output.dst = dst; + output.size = dstCapacity; + output.pos = *dstPos; + input.src = src; + input.size = srcSize; + input.pos = *srcPos; /* ZSTD_compressStream2() will check validity of dstPos and srcPos */ - size_t const cErr = ZSTD_compressStream2(cctx, &output, &input, endOp); - *dstPos = output.pos; - *srcPos = input.pos; - return cErr; + { size_t const cErr = ZSTD_compressStream2(cctx, &output, &input, endOp); + *dstPos = output.pos; + *srcPos = input.pos; + return cErr; + } } size_t ZSTD_compress2(ZSTD_CCtx* cctx, @@ -5699,6 +6339,7 @@ size_t ZSTD_compress2(ZSTD_CCtx* cctx, /* Reset to the original values. */ cctx->requestedParams.inBufferMode = originalInBufferMode; cctx->requestedParams.outBufferMode = originalOutBufferMode; + FORWARD_IF_ERROR(result, "ZSTD_compressStream2_simpleArgs failed"); if (result != 0) { /* compression not completed, due to lack of output space */ assert(oPos == dstCapacity); @@ -5709,64 +6350,60 @@ size_t ZSTD_compress2(ZSTD_CCtx* cctx, } } -typedef struct { - U32 idx; /* Index in array of ZSTD_Sequence */ - U32 posInSequence; /* Position within sequence at idx */ - size_t posInSrc; /* Number of bytes given by sequences provided so far */ -} ZSTD_sequencePosition; - -/* Returns a ZSTD error code if sequence is not valid */ -static size_t ZSTD_validateSequence(U32 offCode, U32 matchLength, - size_t posInSrc, U32 windowLog, size_t dictSize, U32 minMatch) { - size_t offsetBound; - U32 windowSize = 1 << windowLog; - /* posInSrc represents the amount of data the the decoder would decode up to this point. +/* ZSTD_validateSequence() : + * @offCode : is presumed to follow format required by ZSTD_storeSeq() + * @returns a ZSTD error code if sequence is not valid + */ +static size_t +ZSTD_validateSequence(U32 offCode, U32 matchLength, U32 minMatch, + size_t posInSrc, U32 windowLog, size_t dictSize, int useSequenceProducer) +{ + U32 const windowSize = 1u << windowLog; + /* posInSrc represents the amount of data the decoder would decode up to this point. * As long as the amount of data decoded is less than or equal to window size, offsets may be * larger than the total length of output decoded in order to reference the dict, even larger than * window size. After output surpasses windowSize, we're limited to windowSize offsets again. */ - offsetBound = posInSrc > windowSize ? (size_t)windowSize : posInSrc + (size_t)dictSize; - RETURN_ERROR_IF(offCode > offsetBound + ZSTD_REP_MOVE, corruption_detected, "Offset too large!"); - RETURN_ERROR_IF(matchLength < minMatch, corruption_detected, "Matchlength too small"); + size_t const offsetBound = posInSrc > windowSize ? (size_t)windowSize : posInSrc + (size_t)dictSize; + size_t const matchLenLowerBound = (minMatch == 3 || useSequenceProducer) ? 3 : 4; + RETURN_ERROR_IF(offCode > OFFSET_TO_OFFBASE(offsetBound), externalSequences_invalid, "Offset too large!"); + /* Validate maxNbSeq is large enough for the given matchLength and minMatch */ + RETURN_ERROR_IF(matchLength < matchLenLowerBound, externalSequences_invalid, "Matchlength too small for the minMatch"); return 0; } /* Returns an offset code, given a sequence's raw offset, the ongoing repcode array, and whether litLength == 0 */ -static U32 ZSTD_finalizeOffCode(U32 rawOffset, const U32 rep[ZSTD_REP_NUM], U32 ll0) { - U32 offCode = rawOffset + ZSTD_REP_MOVE; - U32 repCode = 0; +static U32 ZSTD_finalizeOffBase(U32 rawOffset, const U32 rep[ZSTD_REP_NUM], U32 ll0) +{ + U32 offBase = OFFSET_TO_OFFBASE(rawOffset); if (!ll0 && rawOffset == rep[0]) { - repCode = 1; + offBase = REPCODE1_TO_OFFBASE; } else if (rawOffset == rep[1]) { - repCode = 2 - ll0; + offBase = REPCODE_TO_OFFBASE(2 - ll0); } else if (rawOffset == rep[2]) { - repCode = 3 - ll0; + offBase = REPCODE_TO_OFFBASE(3 - ll0); } else if (ll0 && rawOffset == rep[0] - 1) { - repCode = 3; - } - if (repCode) { - /* ZSTD_storeSeq expects a number in the range [0, 2] to represent a repcode */ - offCode = repCode - 1; + offBase = REPCODE3_TO_OFFBASE; } - return offCode; + return offBase; } -/* Returns 0 on success, and a ZSTD_error otherwise. This function scans through an array of - * ZSTD_Sequence, storing the sequences it finds, until it reaches a block delimiter. - */ -static size_t ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, - const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, - const void* src, size_t blockSize) { +size_t +ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, + ZSTD_sequencePosition* seqPos, + const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, + const void* src, size_t blockSize, + ZSTD_paramSwitch_e externalRepSearch) +{ U32 idx = seqPos->idx; + U32 const startIdx = idx; BYTE const* ip = (BYTE const*)(src); const BYTE* const iend = ip + blockSize; repcodes_t updatedRepcodes; U32 dictSize; - U32 litLength; - U32 matchLength; - U32 ll0; - U32 offCode; + + DEBUGLOG(5, "ZSTD_copySequencesToSeqStoreExplicitBlockDelim (blockSize = %zu)", blockSize); if (cctx->cdict) { dictSize = (U32)cctx->cdict->dictContentSize; @@ -5776,26 +6413,55 @@ static size_t ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, ZS dictSize = 0; } ZSTD_memcpy(updatedRepcodes.rep, cctx->blockState.prevCBlock->rep, sizeof(repcodes_t)); - for (; (inSeqs[idx].matchLength != 0 || inSeqs[idx].offset != 0) && idx < inSeqsSize; ++idx) { - litLength = inSeqs[idx].litLength; - matchLength = inSeqs[idx].matchLength; - ll0 = litLength == 0; - offCode = ZSTD_finalizeOffCode(inSeqs[idx].offset, updatedRepcodes.rep, ll0); - updatedRepcodes = ZSTD_updateRep(updatedRepcodes.rep, offCode, ll0); - - DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offCode, matchLength, litLength); + for (; idx < inSeqsSize && (inSeqs[idx].matchLength != 0 || inSeqs[idx].offset != 0); ++idx) { + U32 const litLength = inSeqs[idx].litLength; + U32 const matchLength = inSeqs[idx].matchLength; + U32 offBase; + + if (externalRepSearch == ZSTD_ps_disable) { + offBase = OFFSET_TO_OFFBASE(inSeqs[idx].offset); + } else { + U32 const ll0 = (litLength == 0); + offBase = ZSTD_finalizeOffBase(inSeqs[idx].offset, updatedRepcodes.rep, ll0); + ZSTD_updateRep(updatedRepcodes.rep, offBase, ll0); + } + + DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offBase, matchLength, litLength); if (cctx->appliedParams.validateSequences) { seqPos->posInSrc += litLength + matchLength; - FORWARD_IF_ERROR(ZSTD_validateSequence(offCode, matchLength, seqPos->posInSrc, - cctx->appliedParams.cParams.windowLog, dictSize, - cctx->appliedParams.cParams.minMatch), + FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, + cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useSequenceProducer), "Sequence validation failed"); } - RETURN_ERROR_IF(idx - seqPos->idx > cctx->seqStore.maxNbSeq, memory_allocation, + RETURN_ERROR_IF(idx - seqPos->idx >= cctx->seqStore.maxNbSeq, externalSequences_invalid, "Not enough memory allocated. Try adjusting ZSTD_c_minMatch."); - ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offCode, matchLength - MINMATCH); + ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offBase, matchLength); ip += matchLength + litLength; } + + /* If we skipped repcode search while parsing, we need to update repcodes now */ + assert(externalRepSearch != ZSTD_ps_auto); + assert(idx >= startIdx); + if (externalRepSearch == ZSTD_ps_disable && idx != startIdx) { + U32* const rep = updatedRepcodes.rep; + U32 lastSeqIdx = idx - 1; /* index of last non-block-delimiter sequence */ + + if (lastSeqIdx >= startIdx + 2) { + rep[2] = inSeqs[lastSeqIdx - 2].offset; + rep[1] = inSeqs[lastSeqIdx - 1].offset; + rep[0] = inSeqs[lastSeqIdx].offset; + } else if (lastSeqIdx == startIdx + 1) { + rep[2] = rep[0]; + rep[1] = inSeqs[lastSeqIdx - 1].offset; + rep[0] = inSeqs[lastSeqIdx].offset; + } else { + assert(lastSeqIdx == startIdx); + rep[2] = rep[1]; + rep[1] = rep[0]; + rep[0] = inSeqs[lastSeqIdx].offset; + } + } + ZSTD_memcpy(cctx->blockState.nextCBlock->rep, updatedRepcodes.rep, sizeof(repcodes_t)); if (inSeqs[idx].litLength) { @@ -5804,25 +6470,16 @@ static size_t ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, ZS ip += inSeqs[idx].litLength; seqPos->posInSrc += inSeqs[idx].litLength; } - RETURN_ERROR_IF(ip != iend, corruption_detected, "Blocksize doesn't agree with block delimiter!"); + RETURN_ERROR_IF(ip != iend, externalSequences_invalid, "Blocksize doesn't agree with block delimiter!"); seqPos->idx = idx+1; return 0; } -/* Returns the number of bytes to move the current read position back by. Only non-zero - * if we ended up splitting a sequence. Otherwise, it may return a ZSTD error if something - * went wrong. - * - * This function will attempt to scan through blockSize bytes represented by the sequences - * in inSeqs, storing any (partial) sequences. - * - * Occasionally, we may want to change the actual number of bytes we consumed from inSeqs to - * avoid splitting a match, or to avoid splitting a match such that it would produce a match - * smaller than MINMATCH. In this case, we return the number of bytes that we didn't read from this block. - */ -static size_t ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, - const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, - const void* src, size_t blockSize) { +size_t +ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, + const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, + const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch) +{ U32 idx = seqPos->idx; U32 startPosInSequence = seqPos->posInSequence; U32 endPosInSequence = seqPos->posInSequence + (U32)blockSize; @@ -5832,10 +6489,9 @@ static size_t ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_seq repcodes_t updatedRepcodes; U32 bytesAdjustment = 0; U32 finalMatchSplit = 0; - U32 litLength; - U32 matchLength; - U32 rawOffset; - U32 offCode; + + /* TODO(embg) support fast parsing mode in noBlockDelim mode */ + (void)externalRepSearch; if (cctx->cdict) { dictSize = cctx->cdict->dictContentSize; @@ -5844,14 +6500,15 @@ static size_t ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_seq } else { dictSize = 0; } - DEBUGLOG(5, "ZSTD_copySequencesToSeqStore: idx: %u PIS: %u blockSize: %zu", idx, startPosInSequence, blockSize); + DEBUGLOG(5, "ZSTD_copySequencesToSeqStoreNoBlockDelim: idx: %u PIS: %u blockSize: %zu", idx, startPosInSequence, blockSize); DEBUGLOG(5, "Start seq: idx: %u (of: %u ml: %u ll: %u)", idx, inSeqs[idx].offset, inSeqs[idx].matchLength, inSeqs[idx].litLength); ZSTD_memcpy(updatedRepcodes.rep, cctx->blockState.prevCBlock->rep, sizeof(repcodes_t)); while (endPosInSequence && idx < inSeqsSize && !finalMatchSplit) { const ZSTD_Sequence currSeq = inSeqs[idx]; - litLength = currSeq.litLength; - matchLength = currSeq.matchLength; - rawOffset = currSeq.offset; + U32 litLength = currSeq.litLength; + U32 matchLength = currSeq.matchLength; + U32 const rawOffset = currSeq.offset; + U32 offBase; /* Modify the sequence depending on where endPosInSequence lies */ if (endPosInSequence >= currSeq.litLength + currSeq.matchLength) { @@ -5865,7 +6522,6 @@ static size_t ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_seq /* Move to the next sequence */ endPosInSequence -= currSeq.litLength + currSeq.matchLength; startPosInSequence = 0; - idx++; } else { /* This is the final (partial) sequence we're adding from inSeqs, and endPosInSequence does not reach the end of the match. So, we have to split the sequence */ @@ -5904,23 +6560,24 @@ static size_t ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_seq } } /* Check if this offset can be represented with a repcode */ - { U32 ll0 = (litLength == 0); - offCode = ZSTD_finalizeOffCode(rawOffset, updatedRepcodes.rep, ll0); - updatedRepcodes = ZSTD_updateRep(updatedRepcodes.rep, offCode, ll0); + { U32 const ll0 = (litLength == 0); + offBase = ZSTD_finalizeOffBase(rawOffset, updatedRepcodes.rep, ll0); + ZSTD_updateRep(updatedRepcodes.rep, offBase, ll0); } if (cctx->appliedParams.validateSequences) { seqPos->posInSrc += litLength + matchLength; - FORWARD_IF_ERROR(ZSTD_validateSequence(offCode, matchLength, seqPos->posInSrc, - cctx->appliedParams.cParams.windowLog, dictSize, - cctx->appliedParams.cParams.minMatch), + FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, + cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useSequenceProducer), "Sequence validation failed"); } - DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offCode, matchLength, litLength); - RETURN_ERROR_IF(idx - seqPos->idx > cctx->seqStore.maxNbSeq, memory_allocation, + DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offBase, matchLength, litLength); + RETURN_ERROR_IF(idx - seqPos->idx >= cctx->seqStore.maxNbSeq, externalSequences_invalid, "Not enough memory allocated. Try adjusting ZSTD_c_minMatch."); - ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offCode, matchLength - MINMATCH); + ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offBase, matchLength); ip += matchLength + litLength; + if (!finalMatchSplit) + idx++; /* Next Sequence */ } DEBUGLOG(5, "Ending seq: idx: %u (of: %u ml: %u ll: %u)", idx, inSeqs[idx].offset, inSeqs[idx].matchLength, inSeqs[idx].litLength); assert(idx == inSeqsSize || endPosInSequence <= inSeqs[idx].litLength + inSeqs[idx].matchLength); @@ -5943,8 +6600,9 @@ static size_t ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_seq typedef size_t (*ZSTD_sequenceCopier) (ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, - const void* src, size_t blockSize); -static ZSTD_sequenceCopier ZSTD_selectSequenceCopier(ZSTD_sequenceFormat_e mode) { + const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch); +static ZSTD_sequenceCopier ZSTD_selectSequenceCopier(ZSTD_sequenceFormat_e mode) +{ ZSTD_sequenceCopier sequenceCopier = NULL; assert(ZSTD_cParam_withinBounds(ZSTD_c_blockDelimiters, mode)); if (mode == ZSTD_sf_explicitBlockDelimiters) { @@ -5956,24 +6614,75 @@ static ZSTD_sequenceCopier ZSTD_selectSequenceCopier(ZSTD_sequenceFormat_e mode) return sequenceCopier; } +/* Discover the size of next block by searching for the delimiter. + * Note that a block delimiter **must** exist in this mode, + * otherwise it's an input error. + * The block size retrieved will be later compared to ensure it remains within bounds */ +static size_t +blockSize_explicitDelimiter(const ZSTD_Sequence* inSeqs, size_t inSeqsSize, ZSTD_sequencePosition seqPos) +{ + int end = 0; + size_t blockSize = 0; + size_t spos = seqPos.idx; + DEBUGLOG(6, "blockSize_explicitDelimiter : seq %zu / %zu", spos, inSeqsSize); + assert(spos <= inSeqsSize); + while (spos < inSeqsSize) { + end = (inSeqs[spos].offset == 0); + blockSize += inSeqs[spos].litLength + inSeqs[spos].matchLength; + if (end) { + if (inSeqs[spos].matchLength != 0) + RETURN_ERROR(externalSequences_invalid, "delimiter format error : both matchlength and offset must be == 0"); + break; + } + spos++; + } + if (!end) + RETURN_ERROR(externalSequences_invalid, "Reached end of sequences without finding a block delimiter"); + return blockSize; +} + +/* More a "target" block size */ +static size_t blockSize_noDelimiter(size_t blockSize, size_t remaining) +{ + int const lastBlock = (remaining <= blockSize); + return lastBlock ? remaining : blockSize; +} + +static size_t determine_blockSize(ZSTD_sequenceFormat_e mode, + size_t blockSize, size_t remaining, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, ZSTD_sequencePosition seqPos) +{ + DEBUGLOG(6, "determine_blockSize : remainingSize = %zu", remaining); + if (mode == ZSTD_sf_noBlockDelimiters) + return blockSize_noDelimiter(blockSize, remaining); + { size_t const explicitBlockSize = blockSize_explicitDelimiter(inSeqs, inSeqsSize, seqPos); + FORWARD_IF_ERROR(explicitBlockSize, "Error while determining block size with explicit delimiters"); + if (explicitBlockSize > blockSize) + RETURN_ERROR(externalSequences_invalid, "sequences incorrectly define a too large block"); + if (explicitBlockSize > remaining) + RETURN_ERROR(externalSequences_invalid, "sequences define a frame longer than source"); + return explicitBlockSize; + } +} + /* Compress, block-by-block, all of the sequences given. * - * Returns the cumulative size of all compressed blocks (including their headers), otherwise a ZSTD error. + * Returns the cumulative size of all compressed blocks (including their headers), + * otherwise a ZSTD error. */ -static size_t ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const ZSTD_Sequence* inSeqs, size_t inSeqsSize, - const void* src, size_t srcSize) { +static size_t +ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t srcSize) +{ size_t cSize = 0; - U32 lastBlock; - size_t blockSize; - size_t compressedSeqsSize; size_t remaining = srcSize; ZSTD_sequencePosition seqPos = {0, 0, 0}; BYTE const* ip = (BYTE const*)src; BYTE* op = (BYTE*)dst; - ZSTD_sequenceCopier sequenceCopier = ZSTD_selectSequenceCopier(cctx->appliedParams.blockDelimiters); + ZSTD_sequenceCopier const sequenceCopier = ZSTD_selectSequenceCopier(cctx->appliedParams.blockDelimiters); DEBUGLOG(4, "ZSTD_compressSequences_internal srcSize: %zu, inSeqsSize: %zu", srcSize, inSeqsSize); /* Special case: empty frame */ @@ -5987,22 +6696,29 @@ static size_t ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, } while (remaining) { + size_t compressedSeqsSize; size_t cBlockSize; size_t additionalByteAdjustment; - lastBlock = remaining <= cctx->blockSize; - blockSize = lastBlock ? (U32)remaining : (U32)cctx->blockSize; + size_t blockSize = determine_blockSize(cctx->appliedParams.blockDelimiters, + cctx->blockSize, remaining, + inSeqs, inSeqsSize, seqPos); + U32 const lastBlock = (blockSize == remaining); + FORWARD_IF_ERROR(blockSize, "Error while trying to determine block size"); + assert(blockSize <= remaining); ZSTD_resetSeqStore(&cctx->seqStore); - DEBUGLOG(4, "Working on new block. Blocksize: %zu", blockSize); + DEBUGLOG(5, "Working on new block. Blocksize: %zu (total:%zu)", blockSize, (ip - (const BYTE*)src) + blockSize); - additionalByteAdjustment = sequenceCopier(cctx, &seqPos, inSeqs, inSeqsSize, ip, blockSize); + additionalByteAdjustment = sequenceCopier(cctx, &seqPos, inSeqs, inSeqsSize, ip, blockSize, cctx->appliedParams.searchForExternalRepcodes); FORWARD_IF_ERROR(additionalByteAdjustment, "Bad sequence copy"); blockSize -= additionalByteAdjustment; /* If blocks are too small, emit as a nocompress block */ - if (blockSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1) { + /* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding + * additional 1. We need to revisit and change this logic to be more consistent */ + if (blockSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1+1) { cBlockSize = ZSTD_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); FORWARD_IF_ERROR(cBlockSize, "Nocompress block failed"); - DEBUGLOG(4, "Block too small, writing out nocompress block: cSize: %zu", cBlockSize); + DEBUGLOG(5, "Block too small, writing out nocompress block: cSize: %zu", cBlockSize); cSize += cBlockSize; ip += blockSize; op += cBlockSize; @@ -6011,6 +6727,7 @@ static size_t ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, continue; } + RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize, dstSize_tooSmall, "not enough dstCapacity to write a new compressed block"); compressedSeqsSize = ZSTD_entropyCompressSeqStore(&cctx->seqStore, &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, &cctx->appliedParams, @@ -6019,11 +6736,11 @@ static size_t ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, cctx->entropyWorkspace, ENTROPY_WORKSPACE_SIZE /* statically allocated in resetCCtx */, cctx->bmi2); FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); - DEBUGLOG(4, "Compressed sequences size: %zu", compressedSeqsSize); + DEBUGLOG(5, "Compressed sequences size: %zu", compressedSeqsSize); if (!cctx->isFirstBlock && ZSTD_maybeRLE(&cctx->seqStore) && - ZSTD_isRLE((BYTE const*)src, srcSize)) { + ZSTD_isRLE(ip, blockSize)) { /* We don't want to emit our first block as a RLE even if it qualifies because * doing so will cause the decoder (cli only) to throw a "should consume all input error." * This is only an issue for zstd <= v1.4.3 @@ -6034,12 +6751,12 @@ static size_t ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, if (compressedSeqsSize == 0) { /* ZSTD_noCompressBlock writes the block header as well */ cBlockSize = ZSTD_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); - FORWARD_IF_ERROR(cBlockSize, "Nocompress block failed"); - DEBUGLOG(4, "Writing out nocompress block, size: %zu", cBlockSize); + FORWARD_IF_ERROR(cBlockSize, "ZSTD_noCompressBlock failed"); + DEBUGLOG(5, "Writing out nocompress block, size: %zu", cBlockSize); } else if (compressedSeqsSize == 1) { cBlockSize = ZSTD_rleCompressBlock(op, dstCapacity, *ip, blockSize, lastBlock); - FORWARD_IF_ERROR(cBlockSize, "RLE compress block failed"); - DEBUGLOG(4, "Writing out RLE block, size: %zu", cBlockSize); + FORWARD_IF_ERROR(cBlockSize, "ZSTD_rleCompressBlock failed"); + DEBUGLOG(5, "Writing out RLE block, size: %zu", cBlockSize); } else { U32 cBlockHeader; /* Error checking and repcodes update */ @@ -6051,11 +6768,10 @@ static size_t ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, cBlockHeader = lastBlock + (((U32)bt_compressed)<<1) + (U32)(compressedSeqsSize << 3); MEM_writeLE24(op, cBlockHeader); cBlockSize = ZSTD_blockHeaderSize + compressedSeqsSize; - DEBUGLOG(4, "Writing out compressed block, size: %zu", cBlockSize); + DEBUGLOG(5, "Writing out compressed block, size: %zu", cBlockSize); } cSize += cBlockSize; - DEBUGLOG(4, "cSize running total: %zu", cSize); if (lastBlock) { break; @@ -6066,21 +6782,25 @@ static size_t ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, dstCapacity -= cBlockSize; cctx->isFirstBlock = 0; } + DEBUGLOG(5, "cSize running total: %zu (remaining dstCapacity=%zu)", cSize, dstCapacity); } + DEBUGLOG(4, "cSize final total: %zu", cSize); return cSize; } -size_t ZSTD_compressSequences(ZSTD_CCtx* const cctx, void* dst, size_t dstCapacity, +size_t ZSTD_compressSequences(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, const ZSTD_Sequence* inSeqs, size_t inSeqsSize, - const void* src, size_t srcSize) { + const void* src, size_t srcSize) +{ BYTE* op = (BYTE*)dst; size_t cSize = 0; size_t compressedBlocksSize = 0; size_t frameHeaderSize = 0; /* Transparent initialization stage, same as compressStream2() */ - DEBUGLOG(3, "ZSTD_compressSequences()"); + DEBUGLOG(4, "ZSTD_compressSequences (dstCapacity=%zu)", dstCapacity); assert(cctx != NULL); FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, ZSTD_e_end, srcSize), "CCtx initialization failed"); /* Begin writing output, starting with frame header */ @@ -6108,26 +6828,34 @@ size_t ZSTD_compressSequences(ZSTD_CCtx* const cctx, void* dst, size_t dstCapaci cSize += 4; } - DEBUGLOG(3, "Final compressed size: %zu", cSize); + DEBUGLOG(4, "Final compressed size: %zu", cSize); return cSize; } /*====== Finalize ======*/ +static ZSTD_inBuffer inBuffer_forEndFlush(const ZSTD_CStream* zcs) +{ + const ZSTD_inBuffer nullInput = { NULL, 0, 0 }; + const int stableInput = (zcs->appliedParams.inBufferMode == ZSTD_bm_stable); + return stableInput ? zcs->expectedInBuffer : nullInput; +} + /*! ZSTD_flushStream() : * @return : amount of data remaining to flush */ size_t ZSTD_flushStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output) { - ZSTD_inBuffer input = { NULL, 0, 0 }; + ZSTD_inBuffer input = inBuffer_forEndFlush(zcs); + input.size = input.pos; /* do not ingest more input during flush */ return ZSTD_compressStream2(zcs, output, &input, ZSTD_e_flush); } size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output) { - ZSTD_inBuffer input = { NULL, 0, 0 }; + ZSTD_inBuffer input = inBuffer_forEndFlush(zcs); size_t const remainingToFlush = ZSTD_compressStream2(zcs, output, &input, ZSTD_e_end); - FORWARD_IF_ERROR( remainingToFlush , "ZSTD_compressStream2 failed"); + FORWARD_IF_ERROR(remainingToFlush , "ZSTD_compressStream2(,,ZSTD_e_end) failed"); if (zcs->appliedParams.nbWorkers > 0) return remainingToFlush; /* minimal estimation */ /* single thread mode : attempt to calculate remaining to flush more precisely */ { size_t const lastBlockSize = zcs->frameEnded ? 0 : ZSTD_BLOCKHEADERSIZE; @@ -6140,119 +6868,12 @@ size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output) /*-===== Pre-defined compression levels =====-*/ +#include "clevels.h" -#define ZSTD_MAX_CLEVEL 22 int ZSTD_maxCLevel(void) { return ZSTD_MAX_CLEVEL; } int ZSTD_minCLevel(void) { return (int)-ZSTD_TARGETLENGTH_MAX; } int ZSTD_defaultCLevel(void) { return ZSTD_CLEVEL_DEFAULT; } -static const ZSTD_compressionParameters ZSTD_defaultCParameters[4][ZSTD_MAX_CLEVEL+1] = { -{ /* "default" - for any srcSize > 256 KB */ - /* W, C, H, S, L, TL, strat */ - { 19, 12, 13, 1, 6, 1, ZSTD_fast }, /* base for negative levels */ - { 19, 13, 14, 1, 7, 0, ZSTD_fast }, /* level 1 */ - { 20, 15, 16, 1, 6, 0, ZSTD_fast }, /* level 2 */ - { 21, 16, 17, 1, 5, 0, ZSTD_dfast }, /* level 3 */ - { 21, 18, 18, 1, 5, 0, ZSTD_dfast }, /* level 4 */ - { 21, 18, 19, 2, 5, 2, ZSTD_greedy }, /* level 5 */ - { 21, 19, 19, 3, 5, 4, ZSTD_greedy }, /* level 6 */ - { 21, 19, 19, 3, 5, 8, ZSTD_lazy }, /* level 7 */ - { 21, 19, 19, 3, 5, 16, ZSTD_lazy2 }, /* level 8 */ - { 21, 19, 20, 4, 5, 16, ZSTD_lazy2 }, /* level 9 */ - { 22, 20, 21, 4, 5, 16, ZSTD_lazy2 }, /* level 10 */ - { 22, 21, 22, 4, 5, 16, ZSTD_lazy2 }, /* level 11 */ - { 22, 21, 22, 5, 5, 16, ZSTD_lazy2 }, /* level 12 */ - { 22, 21, 22, 5, 5, 32, ZSTD_btlazy2 }, /* level 13 */ - { 22, 22, 23, 5, 5, 32, ZSTD_btlazy2 }, /* level 14 */ - { 22, 23, 23, 6, 5, 32, ZSTD_btlazy2 }, /* level 15 */ - { 22, 22, 22, 5, 5, 48, ZSTD_btopt }, /* level 16 */ - { 23, 23, 22, 5, 4, 64, ZSTD_btopt }, /* level 17 */ - { 23, 23, 22, 6, 3, 64, ZSTD_btultra }, /* level 18 */ - { 23, 24, 22, 7, 3,256, ZSTD_btultra2}, /* level 19 */ - { 25, 25, 23, 7, 3,256, ZSTD_btultra2}, /* level 20 */ - { 26, 26, 24, 7, 3,512, ZSTD_btultra2}, /* level 21 */ - { 27, 27, 25, 9, 3,999, ZSTD_btultra2}, /* level 22 */ -}, -{ /* for srcSize <= 256 KB */ - /* W, C, H, S, L, T, strat */ - { 18, 12, 13, 1, 5, 1, ZSTD_fast }, /* base for negative levels */ - { 18, 13, 14, 1, 6, 0, ZSTD_fast }, /* level 1 */ - { 18, 14, 14, 1, 5, 0, ZSTD_dfast }, /* level 2 */ - { 18, 16, 16, 1, 4, 0, ZSTD_dfast }, /* level 3 */ - { 18, 16, 17, 2, 5, 2, ZSTD_greedy }, /* level 4.*/ - { 18, 18, 18, 3, 5, 2, ZSTD_greedy }, /* level 5.*/ - { 18, 18, 19, 3, 5, 4, ZSTD_lazy }, /* level 6.*/ - { 18, 18, 19, 4, 4, 4, ZSTD_lazy }, /* level 7 */ - { 18, 18, 19, 4, 4, 8, ZSTD_lazy2 }, /* level 8 */ - { 18, 18, 19, 5, 4, 8, ZSTD_lazy2 }, /* level 9 */ - { 18, 18, 19, 6, 4, 8, ZSTD_lazy2 }, /* level 10 */ - { 18, 18, 19, 5, 4, 12, ZSTD_btlazy2 }, /* level 11.*/ - { 18, 19, 19, 7, 4, 12, ZSTD_btlazy2 }, /* level 12.*/ - { 18, 18, 19, 4, 4, 16, ZSTD_btopt }, /* level 13 */ - { 18, 18, 19, 4, 3, 32, ZSTD_btopt }, /* level 14.*/ - { 18, 18, 19, 6, 3,128, ZSTD_btopt }, /* level 15.*/ - { 18, 19, 19, 6, 3,128, ZSTD_btultra }, /* level 16.*/ - { 18, 19, 19, 8, 3,256, ZSTD_btultra }, /* level 17.*/ - { 18, 19, 19, 6, 3,128, ZSTD_btultra2}, /* level 18.*/ - { 18, 19, 19, 8, 3,256, ZSTD_btultra2}, /* level 19.*/ - { 18, 19, 19, 10, 3,512, ZSTD_btultra2}, /* level 20.*/ - { 18, 19, 19, 12, 3,512, ZSTD_btultra2}, /* level 21.*/ - { 18, 19, 19, 13, 3,999, ZSTD_btultra2}, /* level 22.*/ -}, -{ /* for srcSize <= 128 KB */ - /* W, C, H, S, L, T, strat */ - { 17, 12, 12, 1, 5, 1, ZSTD_fast }, /* base for negative levels */ - { 17, 12, 13, 1, 6, 0, ZSTD_fast }, /* level 1 */ - { 17, 13, 15, 1, 5, 0, ZSTD_fast }, /* level 2 */ - { 17, 15, 16, 2, 5, 0, ZSTD_dfast }, /* level 3 */ - { 17, 17, 17, 2, 4, 0, ZSTD_dfast }, /* level 4 */ - { 17, 16, 17, 3, 4, 2, ZSTD_greedy }, /* level 5 */ - { 17, 17, 17, 3, 4, 4, ZSTD_lazy }, /* level 6 */ - { 17, 17, 17, 3, 4, 8, ZSTD_lazy2 }, /* level 7 */ - { 17, 17, 17, 4, 4, 8, ZSTD_lazy2 }, /* level 8 */ - { 17, 17, 17, 5, 4, 8, ZSTD_lazy2 }, /* level 9 */ - { 17, 17, 17, 6, 4, 8, ZSTD_lazy2 }, /* level 10 */ - { 17, 17, 17, 5, 4, 8, ZSTD_btlazy2 }, /* level 11 */ - { 17, 18, 17, 7, 4, 12, ZSTD_btlazy2 }, /* level 12 */ - { 17, 18, 17, 3, 4, 12, ZSTD_btopt }, /* level 13.*/ - { 17, 18, 17, 4, 3, 32, ZSTD_btopt }, /* level 14.*/ - { 17, 18, 17, 6, 3,256, ZSTD_btopt }, /* level 15.*/ - { 17, 18, 17, 6, 3,128, ZSTD_btultra }, /* level 16.*/ - { 17, 18, 17, 8, 3,256, ZSTD_btultra }, /* level 17.*/ - { 17, 18, 17, 10, 3,512, ZSTD_btultra }, /* level 18.*/ - { 17, 18, 17, 5, 3,256, ZSTD_btultra2}, /* level 19.*/ - { 17, 18, 17, 7, 3,512, ZSTD_btultra2}, /* level 20.*/ - { 17, 18, 17, 9, 3,512, ZSTD_btultra2}, /* level 21.*/ - { 17, 18, 17, 11, 3,999, ZSTD_btultra2}, /* level 22.*/ -}, -{ /* for srcSize <= 16 KB */ - /* W, C, H, S, L, T, strat */ - { 14, 12, 13, 1, 5, 1, ZSTD_fast }, /* base for negative levels */ - { 14, 14, 15, 1, 5, 0, ZSTD_fast }, /* level 1 */ - { 14, 14, 15, 1, 4, 0, ZSTD_fast }, /* level 2 */ - { 14, 14, 15, 2, 4, 0, ZSTD_dfast }, /* level 3 */ - { 14, 14, 14, 4, 4, 2, ZSTD_greedy }, /* level 4 */ - { 14, 14, 14, 3, 4, 4, ZSTD_lazy }, /* level 5.*/ - { 14, 14, 14, 4, 4, 8, ZSTD_lazy2 }, /* level 6 */ - { 14, 14, 14, 6, 4, 8, ZSTD_lazy2 }, /* level 7 */ - { 14, 14, 14, 8, 4, 8, ZSTD_lazy2 }, /* level 8.*/ - { 14, 15, 14, 5, 4, 8, ZSTD_btlazy2 }, /* level 9.*/ - { 14, 15, 14, 9, 4, 8, ZSTD_btlazy2 }, /* level 10.*/ - { 14, 15, 14, 3, 4, 12, ZSTD_btopt }, /* level 11.*/ - { 14, 15, 14, 4, 3, 24, ZSTD_btopt }, /* level 12.*/ - { 14, 15, 14, 5, 3, 32, ZSTD_btultra }, /* level 13.*/ - { 14, 15, 15, 6, 3, 64, ZSTD_btultra }, /* level 14.*/ - { 14, 15, 15, 7, 3,256, ZSTD_btultra }, /* level 15.*/ - { 14, 15, 15, 5, 3, 48, ZSTD_btultra2}, /* level 16.*/ - { 14, 15, 15, 6, 3,128, ZSTD_btultra2}, /* level 17.*/ - { 14, 15, 15, 7, 3,256, ZSTD_btultra2}, /* level 18.*/ - { 14, 15, 15, 8, 3,256, ZSTD_btultra2}, /* level 19.*/ - { 14, 15, 15, 8, 3,512, ZSTD_btultra2}, /* level 20.*/ - { 14, 15, 15, 9, 3,512, ZSTD_btultra2}, /* level 21.*/ - { 14, 15, 15, 10, 3,999, ZSTD_btultra2}, /* level 22.*/ -}, -}; - static ZSTD_compressionParameters ZSTD_dedicatedDictSearch_getCParams(int const compressionLevel, size_t const dictSize) { ZSTD_compressionParameters cParams = ZSTD_getCParams_internal(compressionLevel, 0, dictSize, ZSTD_cpm_createCDict); @@ -6356,7 +6977,7 @@ static ZSTD_compressionParameters ZSTD_getCParams_internal(int compressionLevel, cp.targetLength = (unsigned)(-clampedCompressionLevel); } /* refine parameters based on srcSize & dictSize */ - return ZSTD_adjustCParams_internal(cp, srcSizeHint, dictSize, mode); + return ZSTD_adjustCParams_internal(cp, srcSizeHint, dictSize, mode, ZSTD_ps_auto); } } @@ -6391,3 +7012,21 @@ ZSTD_parameters ZSTD_getParams(int compressionLevel, unsigned long long srcSizeH if (srcSizeHint == 0) srcSizeHint = ZSTD_CONTENTSIZE_UNKNOWN; return ZSTD_getParams_internal(compressionLevel, srcSizeHint, dictSize, ZSTD_cpm_unknown); } + +void ZSTD_registerSequenceProducer( + ZSTD_CCtx* zc, void* mState, + ZSTD_sequenceProducer_F* mFinder +) { + if (mFinder != NULL) { + ZSTD_externalMatchCtx emctx; + emctx.mState = mState; + emctx.mFinder = mFinder; + emctx.seqBuffer = NULL; + emctx.seqBufferCapacity = 0; + zc->externalMatchCtx = emctx; + zc->requestedParams.useSequenceProducer = 1; + } else { + ZSTD_memset(&zc->externalMatchCtx, 0, sizeof(zc->externalMatchCtx)); + zc->requestedParams.useSequenceProducer = 0; + } +} diff --git a/Utilities/cmzstd/lib/compress/zstd_compress_internal.h b/Utilities/cmzstd/lib/compress/zstd_compress_internal.h index 3b04fd09f64..10f68d010ec 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress_internal.h +++ b/Utilities/cmzstd/lib/compress/zstd_compress_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -23,6 +23,7 @@ #ifdef ZSTD_MULTITHREAD # include "zstdmt_compress.h" #endif +#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_NbCommonBytes */ #if defined (__cplusplus) extern "C" { @@ -63,7 +64,7 @@ typedef struct { } ZSTD_localDict; typedef struct { - HUF_CElt CTable[HUF_CTABLE_SIZE_U32(255)]; + HUF_CElt CTable[HUF_CTABLE_SIZE_ST(255)]; HUF_repeat repeatMode; } ZSTD_hufCTables_t; @@ -117,19 +118,20 @@ typedef struct { /** ZSTD_buildBlockEntropyStats() : * Builds entropy for the block. * @return : 0 on success or error code */ -size_t ZSTD_buildBlockEntropyStats(seqStore_t* seqStorePtr, - const ZSTD_entropyCTables_t* prevEntropy, - ZSTD_entropyCTables_t* nextEntropy, - const ZSTD_CCtx_params* cctxParams, - ZSTD_entropyCTablesMetadata_t* entropyMetadata, - void* workspace, size_t wkspSize); +size_t ZSTD_buildBlockEntropyStats( + const seqStore_t* seqStorePtr, + const ZSTD_entropyCTables_t* prevEntropy, + ZSTD_entropyCTables_t* nextEntropy, + const ZSTD_CCtx_params* cctxParams, + ZSTD_entropyCTablesMetadata_t* entropyMetadata, + void* workspace, size_t wkspSize); /********************************* * Compression internals structs * *********************************/ typedef struct { - U32 off; /* Offset code (offset + ZSTD_REP_MOVE) for the match */ + U32 off; /* Offset sumtype code for the match, using ZSTD_storeSeq() format */ U32 len; /* Raw length of match */ } ZSTD_match_t; @@ -148,6 +150,12 @@ typedef struct { size_t capacity; /* The capacity starting from `seq` pointer */ } rawSeqStore_t; +typedef struct { + U32 idx; /* Index in array of ZSTD_Sequence */ + U32 posInSequence; /* Position within sequence at idx */ + size_t posInSrc; /* Number of bytes given by sequences provided so far */ +} ZSTD_sequencePosition; + UNUSED_ATTR static const rawSeqStore_t kNullRawSeqStore = {NULL, 0, 0, 0, 0}; typedef struct { @@ -179,7 +187,7 @@ typedef struct { U32 offCodeSumBasePrice; /* to compare to log2(offreq) */ ZSTD_OptPrice_e priceType; /* prices can be determined dynamically, or follow a pre-defined cost structure */ const ZSTD_entropyCTables_t* symbolCosts; /* pre-calculated dictionary statistics */ - ZSTD_literalCompressionMode_e literalCompressionMode; + ZSTD_paramSwitch_e literalCompressionMode; } optState_t; typedef struct { @@ -199,6 +207,8 @@ typedef struct { */ } ZSTD_window_t; +#define ZSTD_WINDOW_START_INDEX 2 + typedef struct ZSTD_matchState_t ZSTD_matchState_t; #define ZSTD_ROW_HASH_CACHE_SIZE 8 /* Size of prefetching hash cache for row-based matchfinder */ @@ -216,8 +226,10 @@ struct ZSTD_matchState_t { U32 hashLog3; /* dispatch table for matches of len==3 : larger == faster, more memory */ U32 rowHashLog; /* For row-based matchfinder: Hashlog based on nb of rows in the hashTable.*/ - U16* tagTable; /* For row-based matchFinder: A row-based table containing the hashes and head index. */ + BYTE* tagTable; /* For row-based matchFinder: A row-based table containing the hashes and head index. */ U32 hashCache[ZSTD_ROW_HASH_CACHE_SIZE]; /* For row-based matchFinder: a cache of hashes to improve speed */ + U64 hashSalt; /* For row-based matchFinder: salts the hash for re-use of tag table */ + U32 hashSaltEntropy; /* For row-based matchFinder: collects entropy for salt generation */ U32* hashTable; U32* hashTable3; @@ -232,6 +244,18 @@ struct ZSTD_matchState_t { const ZSTD_matchState_t* dictMatchState; ZSTD_compressionParameters cParams; const rawSeqStore_t* ldmSeqStore; + + /* Controls prefetching in some dictMatchState matchfinders. + * This behavior is controlled from the cctx ms. + * This parameter has no effect in the cdict ms. */ + int prefetchCDictTables; + + /* When == 0, lazy match finders insert every position. + * When != 0, lazy match finders only insert positions they search. + * This allows them to skip much faster over incompressible data, + * at a small cost to compression ratio. + */ + int lazySkipping; }; typedef struct { @@ -264,7 +288,7 @@ typedef struct { } ldmState_t; typedef struct { - U32 enableLdm; /* 1 if enable long distance matching */ + ZSTD_paramSwitch_e enableLdm; /* ZSTD_ps_enable to enable LDM. ZSTD_ps_auto by default */ U32 hashLog; /* Log size of hashTable */ U32 bucketSizeLog; /* Log bucket size for collision resolution, at most 8 */ U32 minMatchLength; /* Minimum match length */ @@ -295,7 +319,7 @@ struct ZSTD_CCtx_params_s { * There is no guarantee that hint is close to actual source size */ ZSTD_dictAttachPref_e attachDictPref; - ZSTD_literalCompressionMode_e literalCompressionMode; + ZSTD_paramSwitch_e literalCompressionMode; /* Multithreading: used to pass parameters to mtctx */ int nbWorkers; @@ -318,16 +342,34 @@ struct ZSTD_CCtx_params_s { int validateSequences; /* Block splitting */ - int splitBlocks; + ZSTD_paramSwitch_e useBlockSplitter; /* Param for deciding whether to use row-based matchfinder */ - ZSTD_useRowMatchFinderMode_e useRowMatchFinder; + ZSTD_paramSwitch_e useRowMatchFinder; /* Always load a dictionary in ext-dict mode (not prefix mode)? */ int deterministicRefPrefix; /* Internal use, for createCCtxParams() and freeCCtxParams() only */ ZSTD_customMem customMem; + + /* Controls prefetching in some dictMatchState matchfinders */ + ZSTD_paramSwitch_e prefetchCDictTables; + + /* Controls whether zstd will fall back to an internal matchfinder + * if the external matchfinder returns an error code. */ + int enableMatchFinderFallback; + + /* Indicates whether an external matchfinder has been referenced. + * Users can't set this externally. + * It is set internally in ZSTD_registerSequenceProducer(). */ + int useSequenceProducer; + + /* Adjust the max block size*/ + size_t maxBlockSize; + + /* Controls repcode search in external sequence parsing */ + ZSTD_paramSwitch_e searchForExternalRepcodes; }; /* typedef'd to ZSTD_CCtx_params within "zstd.h" */ #define COMPRESS_SEQUENCES_WORKSPACE_SIZE (sizeof(unsigned) * (MaxSeq + 2)) @@ -343,6 +385,30 @@ typedef enum { ZSTDb_buffered } ZSTD_buffered_policy_e; +/** + * Struct that contains all elements of block splitter that should be allocated + * in a wksp. + */ +#define ZSTD_MAX_NB_BLOCK_SPLITS 196 +typedef struct { + seqStore_t fullSeqStoreChunk; + seqStore_t firstHalfSeqStore; + seqStore_t secondHalfSeqStore; + seqStore_t currSeqStore; + seqStore_t nextSeqStore; + + U32 partitions[ZSTD_MAX_NB_BLOCK_SPLITS]; + ZSTD_entropyCTablesMetadata_t entropyMetadata; +} ZSTD_blockSplitCtx; + +/* Context for block-level external matchfinder API */ +typedef struct { + void* mState; + ZSTD_sequenceProducer_F* mFinder; + ZSTD_Sequence* seqBuffer; + size_t seqBufferCapacity; +} ZSTD_externalMatchCtx; + struct ZSTD_CCtx_s { ZSTD_compressionStage_e stage; int cParamsChanged; /* == 1 if cParams(except wlog) or compression level are changed in requestedParams. Triggers transmission of new params to ZSTDMT (if available) then reset to 0. */ @@ -374,7 +440,7 @@ struct ZSTD_CCtx_s { ZSTD_blockState_t blockState; U32* entropyWorkspace; /* entropy workspace of ENTROPY_WORKSPACE_SIZE bytes */ - /* Wether we are streaming or not */ + /* Whether we are streaming or not */ ZSTD_buffered_policy_e bufferedPolicy; /* streaming */ @@ -392,6 +458,7 @@ struct ZSTD_CCtx_s { /* Stable in/out buffer verification */ ZSTD_inBuffer expectedInBuffer; + size_t stableIn_notConsumed; /* nb bytes within stable input buffer that are said to be consumed but are not */ size_t expectedOutBufferSize; /* Dictionary */ @@ -408,9 +475,16 @@ struct ZSTD_CCtx_s { #if ZSTD_TRACE ZSTD_TraceCtx traceCtx; #endif + + /* Workspace for block splitter */ + ZSTD_blockSplitCtx blockSplitCtx; + + /* Workspace for external matchfinder */ + ZSTD_externalMatchCtx externalMatchCtx; }; typedef enum { ZSTD_dtlm_fast, ZSTD_dtlm_full } ZSTD_dictTableLoadMethod_e; +typedef enum { ZSTD_tfp_forCCtx, ZSTD_tfp_forCDict } ZSTD_tableFillPurpose_e; typedef enum { ZSTD_noDict = 0, @@ -432,7 +506,7 @@ typedef enum { * In this mode we take both the source size and the dictionary size * into account when selecting and adjusting the parameters. */ - ZSTD_cpm_unknown = 3, /* ZSTD_getCParams, ZSTD_getParams, ZSTD_adjustParams. + ZSTD_cpm_unknown = 3 /* ZSTD_getCParams, ZSTD_getParams, ZSTD_adjustParams. * We don't know what these parameters are for. We default to the legacy * behavior of taking both the source size and the dict size into account * when selecting and adjusting parameters. @@ -442,7 +516,7 @@ typedef enum { typedef size_t (*ZSTD_blockCompressor) ( ZSTD_matchState_t* bs, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); -ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_useRowMatchFinderMode_e rowMatchfinderMode, ZSTD_dictMode_e dictMode); +ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, ZSTD_paramSwitch_e rowMatchfinderMode, ZSTD_dictMode_e dictMode); MEM_STATIC U32 ZSTD_LLcode(U32 litLength) @@ -476,31 +550,6 @@ MEM_STATIC U32 ZSTD_MLcode(U32 mlBase) return (mlBase > 127) ? ZSTD_highbit32(mlBase) + ML_deltaCode : ML_Code[mlBase]; } -typedef struct repcodes_s { - U32 rep[3]; -} repcodes_t; - -MEM_STATIC repcodes_t ZSTD_updateRep(U32 const rep[3], U32 const offset, U32 const ll0) -{ - repcodes_t newReps; - if (offset >= ZSTD_REP_NUM) { /* full offset */ - newReps.rep[2] = rep[1]; - newReps.rep[1] = rep[0]; - newReps.rep[0] = offset - ZSTD_REP_MOVE; - } else { /* repcode */ - U32 const repCode = offset + ll0; - if (repCode > 0) { /* note : if repCode==0, no change */ - U32 const currentOffset = (repCode==ZSTD_REP_NUM) ? (rep[0] - 1) : rep[repCode]; - newReps.rep[2] = (repCode >= 2) ? rep[1] : rep[2]; - newReps.rep[1] = rep[0]; - newReps.rep[0] = currentOffset; - } else { /* repCode == 0 */ - ZSTD_memcpy(&newReps, rep, sizeof(newReps)); - } - } - return newReps; -} - /* ZSTD_cParam_withinBounds: * @return 1 if value is within cParam bounds, * 0 otherwise */ @@ -516,9 +565,11 @@ MEM_STATIC int ZSTD_cParam_withinBounds(ZSTD_cParameter cParam, int value) /* ZSTD_noCompressBlock() : * Writes uncompressed block to dst buffer from given src. * Returns the size of the block */ -MEM_STATIC size_t ZSTD_noCompressBlock (void* dst, size_t dstCapacity, const void* src, size_t srcSize, U32 lastBlock) +MEM_STATIC size_t +ZSTD_noCompressBlock(void* dst, size_t dstCapacity, const void* src, size_t srcSize, U32 lastBlock) { U32 const cBlockHeader24 = lastBlock + (((U32)bt_raw)<<1) + (U32)(srcSize << 3); + DEBUGLOG(5, "ZSTD_noCompressBlock (srcSize=%zu, dstCapacity=%zu)", srcSize, dstCapacity); RETURN_ERROR_IF(srcSize + ZSTD_blockHeaderSize > dstCapacity, dstSize_tooSmall, "dst buf too small for uncompressed block"); MEM_writeLE24(dst, cBlockHeader24); @@ -526,7 +577,8 @@ MEM_STATIC size_t ZSTD_noCompressBlock (void* dst, size_t dstCapacity, const voi return ZSTD_blockHeaderSize + srcSize; } -MEM_STATIC size_t ZSTD_rleCompressBlock (void* dst, size_t dstCapacity, BYTE src, size_t srcSize, U32 lastBlock) +MEM_STATIC size_t +ZSTD_rleCompressBlock(void* dst, size_t dstCapacity, BYTE src, size_t srcSize, U32 lastBlock) { BYTE* const op = (BYTE*)dst; U32 const cBlockHeader = lastBlock + (((U32)bt_rle)<<1) + (U32)(srcSize << 3); @@ -545,21 +597,21 @@ MEM_STATIC size_t ZSTD_minGain(size_t srcSize, ZSTD_strategy strat) { U32 const minlog = (strat>=ZSTD_btultra) ? (U32)(strat) - 1 : 6; ZSTD_STATIC_ASSERT(ZSTD_btultra == 8); - assert(ZSTD_cParam_withinBounds(ZSTD_c_strategy, strat)); + assert(ZSTD_cParam_withinBounds(ZSTD_c_strategy, (int)strat)); return (srcSize >> minlog) + 2; } -MEM_STATIC int ZSTD_disableLiteralsCompression(const ZSTD_CCtx_params* cctxParams) +MEM_STATIC int ZSTD_literalsCompressionIsDisabled(const ZSTD_CCtx_params* cctxParams) { switch (cctxParams->literalCompressionMode) { - case ZSTD_lcm_huffman: + case ZSTD_ps_enable: return 0; - case ZSTD_lcm_uncompressed: + case ZSTD_ps_disable: return 1; default: assert(0 /* impossible: pre-validated */); - /* fall-through */ - case ZSTD_lcm_auto: + ZSTD_FALLTHROUGH; + case ZSTD_ps_auto: return (cctxParams->cParams.strategy == ZSTD_fast) && (cctxParams->cParams.targetLength > 0); } } @@ -569,7 +621,9 @@ MEM_STATIC int ZSTD_disableLiteralsCompression(const ZSTD_CCtx_params* cctxParam * Only called when the sequence ends past ilimit_w, so it only needs to be optimized for single * large copies. */ -static void ZSTD_safecopyLiterals(BYTE* op, BYTE const* ip, BYTE const* const iend, BYTE const* ilimit_w) { +static void +ZSTD_safecopyLiterals(BYTE* op, BYTE const* ip, BYTE const* const iend, BYTE const* ilimit_w) +{ assert(iend > ilimit_w); if (ip <= ilimit_w) { ZSTD_wildcopy(op, ip, ilimit_w - ip, ZSTD_no_overlap); @@ -579,14 +633,28 @@ static void ZSTD_safecopyLiterals(BYTE* op, BYTE const* ip, BYTE const* const ie while (ip < iend) *op++ = *ip++; } + +#define REPCODE1_TO_OFFBASE REPCODE_TO_OFFBASE(1) +#define REPCODE2_TO_OFFBASE REPCODE_TO_OFFBASE(2) +#define REPCODE3_TO_OFFBASE REPCODE_TO_OFFBASE(3) +#define REPCODE_TO_OFFBASE(r) (assert((r)>=1), assert((r)<=ZSTD_REP_NUM), (r)) /* accepts IDs 1,2,3 */ +#define OFFSET_TO_OFFBASE(o) (assert((o)>0), o + ZSTD_REP_NUM) +#define OFFBASE_IS_OFFSET(o) ((o) > ZSTD_REP_NUM) +#define OFFBASE_IS_REPCODE(o) ( 1 <= (o) && (o) <= ZSTD_REP_NUM) +#define OFFBASE_TO_OFFSET(o) (assert(OFFBASE_IS_OFFSET(o)), (o) - ZSTD_REP_NUM) +#define OFFBASE_TO_REPCODE(o) (assert(OFFBASE_IS_REPCODE(o)), (o)) /* returns ID 1,2,3 */ + /*! ZSTD_storeSeq() : - * Store a sequence (litlen, litPtr, offCode and mlBase) into seqStore_t. - * `offCode` : distance to match + ZSTD_REP_MOVE (values <= ZSTD_REP_MOVE are repCodes). - * `mlBase` : matchLength - MINMATCH - * Allowed to overread literals up to litLimit. + * Store a sequence (litlen, litPtr, offBase and matchLength) into seqStore_t. + * @offBase : Users should employ macros REPCODE_TO_OFFBASE() and OFFSET_TO_OFFBASE(). + * @matchLength : must be >= MINMATCH + * Allowed to over-read literals up to litLimit. */ -HINT_INLINE UNUSED_ATTR -void ZSTD_storeSeq(seqStore_t* seqStorePtr, size_t litLength, const BYTE* literals, const BYTE* litLimit, U32 offCode, size_t mlBase) +HINT_INLINE UNUSED_ATTR void +ZSTD_storeSeq(seqStore_t* seqStorePtr, + size_t litLength, const BYTE* literals, const BYTE* litLimit, + U32 offBase, + size_t matchLength) { BYTE const* const litLimit_w = litLimit - WILDCOPY_OVERLENGTH; BYTE const* const litEnd = literals + litLength; @@ -594,8 +662,8 @@ void ZSTD_storeSeq(seqStore_t* seqStorePtr, size_t litLength, const BYTE* litera static const BYTE* g_start = NULL; if (g_start==NULL) g_start = (const BYTE*)literals; /* note : index only works for compression within a single segment */ { U32 const pos = (U32)((const BYTE*)literals - g_start); - DEBUGLOG(6, "Cpos%7u :%3u literals, match%4u bytes at offCode%7u", - pos, (U32)litLength, (U32)mlBase+MINMATCH, (U32)offCode); + DEBUGLOG(6, "Cpos%7u :%3u literals, match%4u bytes at offBase%7u", + pos, (U32)litLength, (U32)matchLength, (U32)offBase); } #endif assert((size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart) < seqStorePtr->maxNbSeq); @@ -605,9 +673,9 @@ void ZSTD_storeSeq(seqStore_t* seqStorePtr, size_t litLength, const BYTE* litera assert(literals + litLength <= litLimit); if (litEnd <= litLimit_w) { /* Common case we can use wildcopy. - * First copy 16 bytes, because literals are likely short. - */ - assert(WILDCOPY_OVERLENGTH >= 16); + * First copy 16 bytes, because literals are likely short. + */ + ZSTD_STATIC_ASSERT(WILDCOPY_OVERLENGTH >= 16); ZSTD_copy16(seqStorePtr->lit, literals); if (litLength > 16) { ZSTD_wildcopy(seqStorePtr->lit+16, literals+16, (ptrdiff_t)litLength-16, ZSTD_no_overlap); @@ -626,96 +694,63 @@ void ZSTD_storeSeq(seqStore_t* seqStorePtr, size_t litLength, const BYTE* litera seqStorePtr->sequences[0].litLength = (U16)litLength; /* match offset */ - seqStorePtr->sequences[0].offset = offCode + 1; + seqStorePtr->sequences[0].offBase = offBase; /* match Length */ - if (mlBase>0xFFFF) { - assert(seqStorePtr->longLengthType == ZSTD_llt_none); /* there can only be a single long length */ - seqStorePtr->longLengthType = ZSTD_llt_matchLength; - seqStorePtr->longLengthPos = (U32)(seqStorePtr->sequences - seqStorePtr->sequencesStart); + assert(matchLength >= MINMATCH); + { size_t const mlBase = matchLength - MINMATCH; + if (mlBase>0xFFFF) { + assert(seqStorePtr->longLengthType == ZSTD_llt_none); /* there can only be a single long length */ + seqStorePtr->longLengthType = ZSTD_llt_matchLength; + seqStorePtr->longLengthPos = (U32)(seqStorePtr->sequences - seqStorePtr->sequencesStart); + } + seqStorePtr->sequences[0].mlBase = (U16)mlBase; } - seqStorePtr->sequences[0].matchLength = (U16)mlBase; seqStorePtr->sequences++; } - -/*-************************************* -* Match length counter -***************************************/ -static unsigned ZSTD_NbCommonBytes (size_t val) +/* ZSTD_updateRep() : + * updates in-place @rep (array of repeat offsets) + * @offBase : sum-type, using numeric representation of ZSTD_storeSeq() + */ +MEM_STATIC void +ZSTD_updateRep(U32 rep[ZSTD_REP_NUM], U32 const offBase, U32 const ll0) { - if (MEM_isLittleEndian()) { - if (MEM_64bits()) { -# if defined(_MSC_VER) && defined(_WIN64) -# if STATIC_BMI2 - return _tzcnt_u64(val) >> 3; -# else - unsigned long r = 0; - return _BitScanForward64( &r, (U64)val ) ? (unsigned)(r >> 3) : 0; -# endif -# elif defined(__GNUC__) && (__GNUC__ >= 4) - return (__builtin_ctzll((U64)val) >> 3); -# else - static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, - 0, 3, 1, 3, 1, 4, 2, 7, - 0, 2, 3, 6, 1, 5, 3, 5, - 1, 3, 4, 4, 2, 5, 6, 7, - 7, 0, 1, 2, 3, 3, 4, 6, - 2, 6, 5, 5, 3, 4, 5, 6, - 7, 1, 2, 4, 6, 4, 4, 5, - 7, 2, 6, 5, 7, 6, 7, 7 }; - return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; -# endif - } else { /* 32 bits */ -# if defined(_MSC_VER) - unsigned long r=0; - return _BitScanForward( &r, (U32)val ) ? (unsigned)(r >> 3) : 0; -# elif defined(__GNUC__) && (__GNUC__ >= 3) - return (__builtin_ctz((U32)val) >> 3); -# else - static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, - 3, 2, 2, 1, 3, 2, 0, 1, - 3, 3, 1, 2, 2, 2, 2, 0, - 3, 1, 2, 0, 1, 0, 1, 1 }; - return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; -# endif + if (OFFBASE_IS_OFFSET(offBase)) { /* full offset */ + rep[2] = rep[1]; + rep[1] = rep[0]; + rep[0] = OFFBASE_TO_OFFSET(offBase); + } else { /* repcode */ + U32 const repCode = OFFBASE_TO_REPCODE(offBase) - 1 + ll0; + if (repCode > 0) { /* note : if repCode==0, no change */ + U32 const currentOffset = (repCode==ZSTD_REP_NUM) ? (rep[0] - 1) : rep[repCode]; + rep[2] = (repCode >= 2) ? rep[1] : rep[2]; + rep[1] = rep[0]; + rep[0] = currentOffset; + } else { /* repCode == 0 */ + /* nothing to do */ } - } else { /* Big Endian CPU */ - if (MEM_64bits()) { -# if defined(_MSC_VER) && defined(_WIN64) -# if STATIC_BMI2 - return _lzcnt_u64(val) >> 3; -# else - unsigned long r = 0; - return _BitScanReverse64(&r, (U64)val) ? (unsigned)(r >> 3) : 0; -# endif -# elif defined(__GNUC__) && (__GNUC__ >= 4) - return (__builtin_clzll(val) >> 3); -# else - unsigned r; - const unsigned n32 = sizeof(size_t)*4; /* calculate this way due to compiler complaining in 32-bits mode */ - if (!(val>>n32)) { r=4; } else { r=0; val>>=n32; } - if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } - r += (!val); - return r; -# endif - } else { /* 32 bits */ -# if defined(_MSC_VER) - unsigned long r = 0; - return _BitScanReverse( &r, (unsigned long)val ) ? (unsigned)(r >> 3) : 0; -# elif defined(__GNUC__) && (__GNUC__ >= 3) - return (__builtin_clz((U32)val) >> 3); -# else - unsigned r; - if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } - r += (!val); - return r; -# endif - } } + } +} + +typedef struct repcodes_s { + U32 rep[3]; +} repcodes_t; + +MEM_STATIC repcodes_t +ZSTD_newRep(U32 const rep[ZSTD_REP_NUM], U32 const offBase, U32 const ll0) +{ + repcodes_t newReps; + ZSTD_memcpy(&newReps, rep, sizeof(newReps)); + ZSTD_updateRep(newReps.rep, offBase, ll0); + return newReps; } +/*-************************************* +* Match length counter +***************************************/ MEM_STATIC size_t ZSTD_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* const pInLimit) { const BYTE* const pStart = pIn; @@ -761,32 +796,43 @@ ZSTD_count_2segments(const BYTE* ip, const BYTE* match, * Hashes ***************************************/ static const U32 prime3bytes = 506832829U; -static U32 ZSTD_hash3(U32 u, U32 h) { return ((u << (32-24)) * prime3bytes) >> (32-h) ; } -MEM_STATIC size_t ZSTD_hash3Ptr(const void* ptr, U32 h) { return ZSTD_hash3(MEM_readLE32(ptr), h); } /* only in zstd_opt.h */ +static U32 ZSTD_hash3(U32 u, U32 h, U32 s) { assert(h <= 32); return (((u << (32-24)) * prime3bytes) ^ s) >> (32-h) ; } +MEM_STATIC size_t ZSTD_hash3Ptr(const void* ptr, U32 h) { return ZSTD_hash3(MEM_readLE32(ptr), h, 0); } /* only in zstd_opt.h */ +MEM_STATIC size_t ZSTD_hash3PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash3(MEM_readLE32(ptr), h, s); } static const U32 prime4bytes = 2654435761U; -static U32 ZSTD_hash4(U32 u, U32 h) { return (u * prime4bytes) >> (32-h) ; } -static size_t ZSTD_hash4Ptr(const void* ptr, U32 h) { return ZSTD_hash4(MEM_read32(ptr), h); } +static U32 ZSTD_hash4(U32 u, U32 h, U32 s) { assert(h <= 32); return ((u * prime4bytes) ^ s) >> (32-h) ; } +static size_t ZSTD_hash4Ptr(const void* ptr, U32 h) { return ZSTD_hash4(MEM_readLE32(ptr), h, 0); } +static size_t ZSTD_hash4PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash4(MEM_readLE32(ptr), h, s); } static const U64 prime5bytes = 889523592379ULL; -static size_t ZSTD_hash5(U64 u, U32 h) { return (size_t)(((u << (64-40)) * prime5bytes) >> (64-h)) ; } -static size_t ZSTD_hash5Ptr(const void* p, U32 h) { return ZSTD_hash5(MEM_readLE64(p), h); } +static size_t ZSTD_hash5(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-40)) * prime5bytes) ^ s) >> (64-h)) ; } +static size_t ZSTD_hash5Ptr(const void* p, U32 h) { return ZSTD_hash5(MEM_readLE64(p), h, 0); } +static size_t ZSTD_hash5PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash5(MEM_readLE64(p), h, s); } static const U64 prime6bytes = 227718039650203ULL; -static size_t ZSTD_hash6(U64 u, U32 h) { return (size_t)(((u << (64-48)) * prime6bytes) >> (64-h)) ; } -static size_t ZSTD_hash6Ptr(const void* p, U32 h) { return ZSTD_hash6(MEM_readLE64(p), h); } +static size_t ZSTD_hash6(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-48)) * prime6bytes) ^ s) >> (64-h)) ; } +static size_t ZSTD_hash6Ptr(const void* p, U32 h) { return ZSTD_hash6(MEM_readLE64(p), h, 0); } +static size_t ZSTD_hash6PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash6(MEM_readLE64(p), h, s); } static const U64 prime7bytes = 58295818150454627ULL; -static size_t ZSTD_hash7(U64 u, U32 h) { return (size_t)(((u << (64-56)) * prime7bytes) >> (64-h)) ; } -static size_t ZSTD_hash7Ptr(const void* p, U32 h) { return ZSTD_hash7(MEM_readLE64(p), h); } +static size_t ZSTD_hash7(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-56)) * prime7bytes) ^ s) >> (64-h)) ; } +static size_t ZSTD_hash7Ptr(const void* p, U32 h) { return ZSTD_hash7(MEM_readLE64(p), h, 0); } +static size_t ZSTD_hash7PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash7(MEM_readLE64(p), h, s); } static const U64 prime8bytes = 0xCF1BBCDCB7A56463ULL; -static size_t ZSTD_hash8(U64 u, U32 h) { return (size_t)(((u) * prime8bytes) >> (64-h)) ; } -static size_t ZSTD_hash8Ptr(const void* p, U32 h) { return ZSTD_hash8(MEM_readLE64(p), h); } +static size_t ZSTD_hash8(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u) * prime8bytes) ^ s) >> (64-h)) ; } +static size_t ZSTD_hash8Ptr(const void* p, U32 h) { return ZSTD_hash8(MEM_readLE64(p), h, 0); } +static size_t ZSTD_hash8PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash8(MEM_readLE64(p), h, s); } + MEM_STATIC FORCE_INLINE_ATTR size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls) { + /* Although some of these hashes do support hBits up to 64, some do not. + * To be on the safe side, always avoid hBits > 32. */ + assert(hBits <= 32); + switch(mls) { default: @@ -798,6 +844,24 @@ size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls) } } +MEM_STATIC FORCE_INLINE_ATTR +size_t ZSTD_hashPtrSalted(const void* p, U32 hBits, U32 mls, const U64 hashSalt) { + /* Although some of these hashes do support hBits up to 64, some do not. + * To be on the safe side, always avoid hBits > 32. */ + assert(hBits <= 32); + + switch(mls) + { + default: + case 4: return ZSTD_hash4PtrS(p, hBits, (U32)hashSalt); + case 5: return ZSTD_hash5PtrS(p, hBits, hashSalt); + case 6: return ZSTD_hash6PtrS(p, hBits, hashSalt); + case 7: return ZSTD_hash7PtrS(p, hBits, hashSalt); + case 8: return ZSTD_hash8PtrS(p, hBits, hashSalt); + } +} + + /** ZSTD_ipow() : * Return base^exponent. */ @@ -884,9 +948,9 @@ MEM_STATIC void ZSTD_window_clear(ZSTD_window_t* window) MEM_STATIC U32 ZSTD_window_isEmpty(ZSTD_window_t const window) { - return window.dictLimit == 1 && - window.lowLimit == 1 && - (window.nextSrc - window.base) == 1; + return window.dictLimit == ZSTD_WINDOW_START_INDEX && + window.lowLimit == ZSTD_WINDOW_START_INDEX && + (window.nextSrc - window.base) == ZSTD_WINDOW_START_INDEX; } /** @@ -937,7 +1001,9 @@ MEM_STATIC U32 ZSTD_window_canOverflowCorrect(ZSTD_window_t const window, { U32 const cycleSize = 1u << cycleLog; U32 const curr = (U32)((BYTE const*)src - window.base); - U32 const minIndexToOverflowCorrect = cycleSize + MAX(maxDist, cycleSize); + U32 const minIndexToOverflowCorrect = cycleSize + + MAX(maxDist, cycleSize) + + ZSTD_WINDOW_START_INDEX; /* Adjust the min index to backoff the overflow correction frequency, * so we don't waste too much CPU in overflow correction. If this @@ -1012,10 +1078,14 @@ MEM_STATIC U32 ZSTD_window_correctOverflow(ZSTD_window_t* window, U32 cycleLog, U32 const cycleSize = 1u << cycleLog; U32 const cycleMask = cycleSize - 1; U32 const curr = (U32)((BYTE const*)src - window->base); - U32 const currentCycle0 = curr & cycleMask; - /* Exclude zero so that newCurrent - maxDist >= 1. */ - U32 const currentCycle1 = currentCycle0 == 0 ? cycleSize : currentCycle0; - U32 const newCurrent = currentCycle1 + MAX(maxDist, cycleSize); + U32 const currentCycle = curr & cycleMask; + /* Ensure newCurrent - maxDist >= ZSTD_WINDOW_START_INDEX. */ + U32 const currentCycleCorrection = currentCycle < ZSTD_WINDOW_START_INDEX + ? MAX(cycleSize, ZSTD_WINDOW_START_INDEX) + : 0; + U32 const newCurrent = currentCycle + + currentCycleCorrection + + MAX(maxDist, cycleSize); U32 const correction = curr - newCurrent; /* maxDist must be a power of two so that: * (newCurrent & cycleMask) == (curr & cycleMask) @@ -1031,14 +1101,20 @@ MEM_STATIC U32 ZSTD_window_correctOverflow(ZSTD_window_t* window, U32 cycleLog, window->base += correction; window->dictBase += correction; - if (window->lowLimit <= correction) window->lowLimit = 1; - else window->lowLimit -= correction; - if (window->dictLimit <= correction) window->dictLimit = 1; - else window->dictLimit -= correction; + if (window->lowLimit < correction + ZSTD_WINDOW_START_INDEX) { + window->lowLimit = ZSTD_WINDOW_START_INDEX; + } else { + window->lowLimit -= correction; + } + if (window->dictLimit < correction + ZSTD_WINDOW_START_INDEX) { + window->dictLimit = ZSTD_WINDOW_START_INDEX; + } else { + window->dictLimit -= correction; + } /* Ensure we can still reference the full window. */ assert(newCurrent >= maxDist); - assert(newCurrent - maxDist >= 1); + assert(newCurrent - maxDist >= ZSTD_WINDOW_START_INDEX); /* Ensure that lowLimit and dictLimit didn't underflow. */ assert(window->lowLimit <= newCurrent); assert(window->dictLimit <= newCurrent); @@ -1133,10 +1209,15 @@ ZSTD_checkDictValidity(const ZSTD_window_t* window, (unsigned)blockEndIdx, (unsigned)maxDist, (unsigned)loadedDictEnd); assert(blockEndIdx >= loadedDictEnd); - if (blockEndIdx > loadedDictEnd + maxDist) { + if (blockEndIdx > loadedDictEnd + maxDist || loadedDictEnd != window->dictLimit) { /* On reaching window size, dictionaries are invalidated. * For simplification, if window size is reached anywhere within next block, * the dictionary is invalidated for the full block. + * + * We also have to invalidate the dictionary if ZSTD_window_update() has detected + * non-contiguous segments, which means that loadedDictEnd != window->dictLimit. + * loadedDictEnd may be 0, if forceWindow is true, but in that case we never use + * dictMatchState, so setting it to NULL is not a problem. */ DEBUGLOG(6, "invalidating dictionary for current block (distance > windowSize)"); *loadedDictEndPtr = 0; @@ -1149,11 +1230,12 @@ ZSTD_checkDictValidity(const ZSTD_window_t* window, MEM_STATIC void ZSTD_window_init(ZSTD_window_t* window) { ZSTD_memset(window, 0, sizeof(*window)); - window->base = (BYTE const*)""; - window->dictBase = (BYTE const*)""; - window->dictLimit = 1; /* start from 1, so that 1st position is valid */ - window->lowLimit = 1; /* it ensures first and later CCtx usages compress the same */ - window->nextSrc = window->base + 1; /* see issue #1241 */ + window->base = (BYTE const*)" "; + window->dictBase = (BYTE const*)" "; + ZSTD_STATIC_ASSERT(ZSTD_DUBT_UNSORTED_MARK < ZSTD_WINDOW_START_INDEX); /* Start above ZSTD_DUBT_UNSORTED_MARK */ + window->dictLimit = ZSTD_WINDOW_START_INDEX; /* start from >0, so that 1st position is valid */ + window->lowLimit = ZSTD_WINDOW_START_INDEX; /* it ensures first and later CCtx usages compress the same */ + window->nextSrc = window->base + ZSTD_WINDOW_START_INDEX; /* see issue #1241 */ window->nbOverflowCorrections = 0; } @@ -1206,15 +1288,15 @@ MEM_STATIC U32 ZSTD_window_update(ZSTD_window_t* window, */ MEM_STATIC U32 ZSTD_getLowestMatchIndex(const ZSTD_matchState_t* ms, U32 curr, unsigned windowLog) { - U32 const maxDistance = 1U << windowLog; - U32 const lowestValid = ms->window.lowLimit; - U32 const withinWindow = (curr - lowestValid > maxDistance) ? curr - maxDistance : lowestValid; - U32 const isDictionary = (ms->loadedDictEnd != 0); + U32 const maxDistance = 1U << windowLog; + U32 const lowestValid = ms->window.lowLimit; + U32 const withinWindow = (curr - lowestValid > maxDistance) ? curr - maxDistance : lowestValid; + U32 const isDictionary = (ms->loadedDictEnd != 0); /* When using a dictionary the entire dictionary is valid if a single byte of the dictionary * is within the window. We invalidate the dictionary (and set loadedDictEnd to 0) when it isn't * valid for the entire block. So this check is sufficient to find the lowest valid match index. */ - U32 const matchLowest = isDictionary ? lowestValid : withinWindow; + U32 const matchLowest = isDictionary ? lowestValid : withinWindow; return matchLowest; } @@ -1267,6 +1349,42 @@ MEM_STATIC void ZSTD_debugTable(const U32* table, U32 max) #endif +/* Short Cache */ + +/* Normally, zstd matchfinders follow this flow: + * 1. Compute hash at ip + * 2. Load index from hashTable[hash] + * 3. Check if *ip == *(base + index) + * In dictionary compression, loading *(base + index) is often an L2 or even L3 miss. + * + * Short cache is an optimization which allows us to avoid step 3 most of the time + * when the data doesn't actually match. With short cache, the flow becomes: + * 1. Compute (hash, currentTag) at ip. currentTag is an 8-bit independent hash at ip. + * 2. Load (index, matchTag) from hashTable[hash]. See ZSTD_writeTaggedIndex to understand how this works. + * 3. Only if currentTag == matchTag, check *ip == *(base + index). Otherwise, continue. + * + * Currently, short cache is only implemented in CDict hashtables. Thus, its use is limited to + * dictMatchState matchfinders. + */ +#define ZSTD_SHORT_CACHE_TAG_BITS 8 +#define ZSTD_SHORT_CACHE_TAG_MASK ((1u << ZSTD_SHORT_CACHE_TAG_BITS) - 1) + +/* Helper function for ZSTD_fillHashTable and ZSTD_fillDoubleHashTable. + * Unpacks hashAndTag into (hash, tag), then packs (index, tag) into hashTable[hash]. */ +MEM_STATIC void ZSTD_writeTaggedIndex(U32* const hashTable, size_t hashAndTag, U32 index) { + size_t const hash = hashAndTag >> ZSTD_SHORT_CACHE_TAG_BITS; + U32 const tag = (U32)(hashAndTag & ZSTD_SHORT_CACHE_TAG_MASK); + assert(index >> (32 - ZSTD_SHORT_CACHE_TAG_BITS) == 0); + hashTable[hash] = (index << ZSTD_SHORT_CACHE_TAG_BITS) | tag; +} + +/* Helper function for short cache matchfinders. + * Unpacks tag1 and tag2 from lower bits of packedTag1 and packedTag2, then checks if the tags match. */ +MEM_STATIC int ZSTD_comparePackedTags(size_t packedTag1, size_t packedTag2) { + U32 const tag1 = packedTag1 & ZSTD_SHORT_CACHE_TAG_MASK; + U32 const tag2 = packedTag2 & ZSTD_SHORT_CACHE_TAG_MASK; + return tag1 == tag2; +} #if defined (__cplusplus) } @@ -1364,4 +1482,51 @@ U32 ZSTD_cycleLog(U32 hashLog, ZSTD_strategy strat); */ void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize); +/* Returns 0 on success, and a ZSTD_error otherwise. This function scans through an array of + * ZSTD_Sequence, storing the sequences it finds, until it reaches a block delimiter. + * Note that the block delimiter must include the last literals of the block. + */ +size_t +ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, + ZSTD_sequencePosition* seqPos, + const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, + const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch); + +/* Returns the number of bytes to move the current read position back by. + * Only non-zero if we ended up splitting a sequence. + * Otherwise, it may return a ZSTD error if something went wrong. + * + * This function will attempt to scan through blockSize bytes + * represented by the sequences in @inSeqs, + * storing any (partial) sequences. + * + * Occasionally, we may want to change the actual number of bytes we consumed from inSeqs to + * avoid splitting a match, or to avoid splitting a match such that it would produce a match + * smaller than MINMATCH. In this case, we return the number of bytes that we didn't read from this block. + */ +size_t +ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, + const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, + const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch); + + +/* =============================================================== + * Deprecated definitions that are still used internally to avoid + * deprecation warnings. These functions are exactly equivalent to + * their public variants, but avoid the deprecation warnings. + * =============================================================== */ + +size_t ZSTD_compressBegin_usingCDict_deprecated(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); + +size_t ZSTD_compressContinue_public(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); + +size_t ZSTD_compressEnd_public(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); + +size_t ZSTD_compressBlock_deprecated(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); + + #endif /* ZSTD_COMPRESS_H */ diff --git a/Utilities/cmzstd/lib/compress/zstd_compress_literals.c b/Utilities/cmzstd/lib/compress/zstd_compress_literals.c index 008337bb1b3..bfd4f11abe4 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress_literals.c +++ b/Utilities/cmzstd/lib/compress/zstd_compress_literals.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -13,11 +13,36 @@ ***************************************/ #include "zstd_compress_literals.h" + +/* ************************************************************** +* Debug Traces +****************************************************************/ +#if DEBUGLEVEL >= 2 + +static size_t showHexa(const void* src, size_t srcSize) +{ + const BYTE* const ip = (const BYTE*)src; + size_t u; + for (u=0; u31) + (srcSize>4095); + DEBUGLOG(5, "ZSTD_noCompressLiterals: srcSize=%zu, dstCapacity=%zu", srcSize, dstCapacity); + RETURN_ERROR_IF(srcSize + flSize > dstCapacity, dstSize_tooSmall, ""); switch(flSize) @@ -36,16 +61,30 @@ size_t ZSTD_noCompressLiterals (void* dst, size_t dstCapacity, const void* src, } ZSTD_memcpy(ostart + flSize, src, srcSize); - DEBUGLOG(5, "Raw literals: %u -> %u", (U32)srcSize, (U32)(srcSize + flSize)); + DEBUGLOG(5, "Raw (uncompressed) literals: %u -> %u", (U32)srcSize, (U32)(srcSize + flSize)); return srcSize + flSize; } +static int allBytesIdentical(const void* src, size_t srcSize) +{ + assert(srcSize >= 1); + assert(src != NULL); + { const BYTE b = ((const BYTE*)src)[0]; + size_t p; + for (p=1; p31) + (srcSize>4095); - (void)dstCapacity; /* dstCapacity already guaranteed to be >=4, hence large enough */ + assert(dstCapacity >= 4); (void)dstCapacity; + assert(allBytesIdentical(src, srcSize)); switch(flSize) { @@ -63,27 +102,51 @@ size_t ZSTD_compressRleLiteralsBlock (void* dst, size_t dstCapacity, const void* } ostart[flSize] = *(const BYTE*)src; - DEBUGLOG(5, "RLE literals: %u -> %u", (U32)srcSize, (U32)flSize + 1); + DEBUGLOG(5, "RLE : Repeated Literal (%02X: %u times) -> %u bytes encoded", ((const BYTE*)src)[0], (U32)srcSize, (U32)flSize + 1); return flSize+1; } -size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf, - ZSTD_hufCTables_t* nextHuf, - ZSTD_strategy strategy, int disableLiteralCompression, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - void* entropyWorkspace, size_t entropyWorkspaceSize, - const int bmi2) +/* ZSTD_minLiteralsToCompress() : + * returns minimal amount of literals + * for literal compression to even be attempted. + * Minimum is made tighter as compression strategy increases. + */ +static size_t +ZSTD_minLiteralsToCompress(ZSTD_strategy strategy, HUF_repeat huf_repeat) +{ + assert((int)strategy >= 0); + assert((int)strategy <= 9); + /* btultra2 : min 8 bytes; + * then 2x larger for each successive compression strategy + * max threshold 64 bytes */ + { int const shift = MIN(9-(int)strategy, 3); + size_t const mintc = (huf_repeat == HUF_repeat_valid) ? 6 : (size_t)8 << shift; + DEBUGLOG(7, "minLiteralsToCompress = %zu", mintc); + return mintc; + } +} + +size_t ZSTD_compressLiterals ( + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + void* entropyWorkspace, size_t entropyWorkspaceSize, + const ZSTD_hufCTables_t* prevHuf, + ZSTD_hufCTables_t* nextHuf, + ZSTD_strategy strategy, + int disableLiteralCompression, + int suspectUncompressible, + int bmi2) { - size_t const minGain = ZSTD_minGain(srcSize, strategy); size_t const lhSize = 3 + (srcSize >= 1 KB) + (srcSize >= 16 KB); BYTE* const ostart = (BYTE*)dst; U32 singleStream = srcSize < 256; symbolEncodingType_e hType = set_compressed; size_t cLitSize; - DEBUGLOG(5,"ZSTD_compressLiterals (disableLiteralCompression=%i srcSize=%u)", - disableLiteralCompression, (U32)srcSize); + DEBUGLOG(5,"ZSTD_compressLiterals (disableLiteralCompression=%i, srcSize=%u, dstCapacity=%zu)", + disableLiteralCompression, (U32)srcSize, dstCapacity); + + DEBUGLOG(6, "Completed literals listing (%zu bytes)", showHexa(src, srcSize)); /* Prepare nextEntropy assuming reusing the existing table */ ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); @@ -91,40 +154,51 @@ size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf, if (disableLiteralCompression) return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); - /* small ? don't even attempt compression (speed opt) */ -# define COMPRESS_LITERALS_SIZE_MIN 63 - { size_t const minLitSize = (prevHuf->repeatMode == HUF_repeat_valid) ? 6 : COMPRESS_LITERALS_SIZE_MIN; - if (srcSize <= minLitSize) return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); - } + /* if too small, don't even attempt compression (speed opt) */ + if (srcSize < ZSTD_minLiteralsToCompress(strategy, prevHuf->repeatMode)) + return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); RETURN_ERROR_IF(dstCapacity < lhSize+1, dstSize_tooSmall, "not enough space for compression"); { HUF_repeat repeat = prevHuf->repeatMode; - int const preferRepeat = strategy < ZSTD_lazy ? srcSize <= 1024 : 0; + int const flags = 0 + | (bmi2 ? HUF_flags_bmi2 : 0) + | (strategy < ZSTD_lazy && srcSize <= 1024 ? HUF_flags_preferRepeat : 0) + | (strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD ? HUF_flags_optimalDepth : 0) + | (suspectUncompressible ? HUF_flags_suspectUncompressible : 0); + + typedef size_t (*huf_compress_f)(void*, size_t, const void*, size_t, unsigned, unsigned, void*, size_t, HUF_CElt*, HUF_repeat*, int); + huf_compress_f huf_compress; if (repeat == HUF_repeat_valid && lhSize == 3) singleStream = 1; - cLitSize = singleStream ? - HUF_compress1X_repeat( - ostart+lhSize, dstCapacity-lhSize, src, srcSize, - HUF_SYMBOLVALUE_MAX, HUF_TABLELOG_DEFAULT, entropyWorkspace, entropyWorkspaceSize, - (HUF_CElt*)nextHuf->CTable, &repeat, preferRepeat, bmi2) : - HUF_compress4X_repeat( - ostart+lhSize, dstCapacity-lhSize, src, srcSize, - HUF_SYMBOLVALUE_MAX, HUF_TABLELOG_DEFAULT, entropyWorkspace, entropyWorkspaceSize, - (HUF_CElt*)nextHuf->CTable, &repeat, preferRepeat, bmi2); + huf_compress = singleStream ? HUF_compress1X_repeat : HUF_compress4X_repeat; + cLitSize = huf_compress(ostart+lhSize, dstCapacity-lhSize, + src, srcSize, + HUF_SYMBOLVALUE_MAX, LitHufLog, + entropyWorkspace, entropyWorkspaceSize, + (HUF_CElt*)nextHuf->CTable, + &repeat, flags); + DEBUGLOG(5, "%zu literals compressed into %zu bytes (before header)", srcSize, cLitSize); if (repeat != HUF_repeat_none) { /* reused the existing table */ - DEBUGLOG(5, "Reusing previous huffman table"); + DEBUGLOG(5, "reusing statistics from previous huffman block"); hType = set_repeat; } } - if ((cLitSize==0) || (cLitSize >= srcSize - minGain) || ERR_isError(cLitSize)) { - ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); - } + { size_t const minGain = ZSTD_minGain(srcSize, strategy); + if ((cLitSize==0) || (cLitSize >= srcSize - minGain) || ERR_isError(cLitSize)) { + ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); + return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); + } } if (cLitSize==1) { - ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); - return ZSTD_compressRleLiteralsBlock(dst, dstCapacity, src, srcSize); - } + /* A return value of 1 signals that the alphabet consists of a single symbol. + * However, in some rare circumstances, it could be the compressed size (a single byte). + * For that outcome to have a chance to happen, it's necessary that `srcSize < 8`. + * (it's also necessary to not generate statistics). + * Therefore, in such a case, actively check that all bytes are identical. */ + if ((srcSize >= 8) || allBytesIdentical(src, srcSize)) { + ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); + return ZSTD_compressRleLiteralsBlock(dst, dstCapacity, src, srcSize); + } } if (hType == set_compressed) { /* using a newly constructed table */ @@ -135,16 +209,19 @@ size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf, switch(lhSize) { case 3: /* 2 - 2 - 10 - 10 */ - { U32 const lhc = hType + ((!singleStream) << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<14); + if (!singleStream) assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); + { U32 const lhc = hType + ((U32)(!singleStream) << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<14); MEM_writeLE24(ostart, lhc); break; } case 4: /* 2 - 2 - 14 - 14 */ + assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); { U32 const lhc = hType + (2 << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<18); MEM_writeLE32(ostart, lhc); break; } case 5: /* 2 - 2 - 18 - 18 */ + assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); { U32 const lhc = hType + (3 << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<22); MEM_writeLE32(ostart, lhc); ostart[4] = (BYTE)(cLitSize >> 10); diff --git a/Utilities/cmzstd/lib/compress/zstd_compress_literals.h b/Utilities/cmzstd/lib/compress/zstd_compress_literals.h index 9904c0cd30a..b060c8ad218 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress_literals.h +++ b/Utilities/cmzstd/lib/compress/zstd_compress_literals.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -16,14 +16,24 @@ size_t ZSTD_noCompressLiterals (void* dst, size_t dstCapacity, const void* src, size_t srcSize); +/* ZSTD_compressRleLiteralsBlock() : + * Conditions : + * - All bytes in @src are identical + * - dstCapacity >= 4 */ size_t ZSTD_compressRleLiteralsBlock (void* dst, size_t dstCapacity, const void* src, size_t srcSize); -size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf, - ZSTD_hufCTables_t* nextHuf, - ZSTD_strategy strategy, int disableLiteralCompression, - void* dst, size_t dstCapacity, +/* ZSTD_compressLiterals(): + * @entropyWorkspace: must be aligned on 4-bytes boundaries + * @entropyWorkspaceSize : must be >= HUF_WORKSPACE_SIZE + * @suspectUncompressible: sampling checks, to potentially skip huffman coding + */ +size_t ZSTD_compressLiterals (void* dst, size_t dstCapacity, const void* src, size_t srcSize, void* entropyWorkspace, size_t entropyWorkspaceSize, - const int bmi2); + const ZSTD_hufCTables_t* prevHuf, + ZSTD_hufCTables_t* nextHuf, + ZSTD_strategy strategy, int disableLiteralCompression, + int suspectUncompressible, + int bmi2); #endif /* ZSTD_COMPRESS_LITERALS_H */ diff --git a/Utilities/cmzstd/lib/compress/zstd_compress_sequences.c b/Utilities/cmzstd/lib/compress/zstd_compress_sequences.c index 611eabdcbbb..8872d4d354a 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress_sequences.c +++ b/Utilities/cmzstd/lib/compress/zstd_compress_sequences.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -58,7 +58,7 @@ static unsigned ZSTD_useLowProbCount(size_t const nbSeq) { /* Heuristic: This should cover most blocks <= 16K and * start to fade out after 16K to about 32K depending on - * comprssibility. + * compressibility. */ return nbSeq >= 2048; } @@ -166,7 +166,7 @@ ZSTD_selectEncodingType( if (mostFrequent == nbSeq) { *repeatMode = FSE_repeat_none; if (isDefaultAllowed && nbSeq <= 2) { - /* Prefer set_basic over set_rle when there are 2 or less symbols, + /* Prefer set_basic over set_rle when there are 2 or fewer symbols, * since RLE uses 1 byte, but set_basic uses 5-6 bits per symbol. * If basic encoding isn't possible, always choose RLE. */ @@ -275,10 +275,11 @@ ZSTD_buildCTable(void* dst, size_t dstCapacity, assert(nbSeq_1 > 1); assert(entropyWorkspaceSize >= sizeof(ZSTD_BuildCTableWksp)); (void)entropyWorkspaceSize; - FORWARD_IF_ERROR(FSE_normalizeCount(wksp->norm, tableLog, count, nbSeq_1, max, ZSTD_useLowProbCount(nbSeq_1)), ""); - { size_t const NCountSize = FSE_writeNCount(op, oend - op, wksp->norm, max, tableLog); /* overflow protected */ + FORWARD_IF_ERROR(FSE_normalizeCount(wksp->norm, tableLog, count, nbSeq_1, max, ZSTD_useLowProbCount(nbSeq_1)), "FSE_normalizeCount failed"); + assert(oend >= op); + { size_t const NCountSize = FSE_writeNCount(op, (size_t)(oend - op), wksp->norm, max, tableLog); /* overflow protected */ FORWARD_IF_ERROR(NCountSize, "FSE_writeNCount failed"); - FORWARD_IF_ERROR(FSE_buildCTable_wksp(nextCTable, wksp->norm, max, tableLog, wksp->wksp, sizeof(wksp->wksp)), ""); + FORWARD_IF_ERROR(FSE_buildCTable_wksp(nextCTable, wksp->norm, max, tableLog, wksp->wksp, sizeof(wksp->wksp)), "FSE_buildCTable_wksp failed"); return NCountSize; } } @@ -312,19 +313,19 @@ ZSTD_encodeSequences_body( FSE_initCState2(&stateLitLength, CTable_LitLength, llCodeTable[nbSeq-1]); BIT_addBits(&blockStream, sequences[nbSeq-1].litLength, LL_bits[llCodeTable[nbSeq-1]]); if (MEM_32bits()) BIT_flushBits(&blockStream); - BIT_addBits(&blockStream, sequences[nbSeq-1].matchLength, ML_bits[mlCodeTable[nbSeq-1]]); + BIT_addBits(&blockStream, sequences[nbSeq-1].mlBase, ML_bits[mlCodeTable[nbSeq-1]]); if (MEM_32bits()) BIT_flushBits(&blockStream); if (longOffsets) { U32 const ofBits = ofCodeTable[nbSeq-1]; unsigned const extraBits = ofBits - MIN(ofBits, STREAM_ACCUMULATOR_MIN-1); if (extraBits) { - BIT_addBits(&blockStream, sequences[nbSeq-1].offset, extraBits); + BIT_addBits(&blockStream, sequences[nbSeq-1].offBase, extraBits); BIT_flushBits(&blockStream); } - BIT_addBits(&blockStream, sequences[nbSeq-1].offset >> extraBits, + BIT_addBits(&blockStream, sequences[nbSeq-1].offBase >> extraBits, ofBits - extraBits); } else { - BIT_addBits(&blockStream, sequences[nbSeq-1].offset, ofCodeTable[nbSeq-1]); + BIT_addBits(&blockStream, sequences[nbSeq-1].offBase, ofCodeTable[nbSeq-1]); } BIT_flushBits(&blockStream); @@ -338,8 +339,8 @@ ZSTD_encodeSequences_body( U32 const mlBits = ML_bits[mlCode]; DEBUGLOG(6, "encoding: litlen:%2u - matchlen:%2u - offCode:%7u", (unsigned)sequences[n].litLength, - (unsigned)sequences[n].matchLength + MINMATCH, - (unsigned)sequences[n].offset); + (unsigned)sequences[n].mlBase + MINMATCH, + (unsigned)sequences[n].offBase); /* 32b*/ /* 64b*/ /* (7)*/ /* (7)*/ FSE_encodeSymbol(&blockStream, &stateOffsetBits, ofCode); /* 15 */ /* 15 */ @@ -350,18 +351,18 @@ ZSTD_encodeSequences_body( BIT_flushBits(&blockStream); /* (7)*/ BIT_addBits(&blockStream, sequences[n].litLength, llBits); if (MEM_32bits() && ((llBits+mlBits)>24)) BIT_flushBits(&blockStream); - BIT_addBits(&blockStream, sequences[n].matchLength, mlBits); + BIT_addBits(&blockStream, sequences[n].mlBase, mlBits); if (MEM_32bits() || (ofBits+mlBits+llBits > 56)) BIT_flushBits(&blockStream); if (longOffsets) { unsigned const extraBits = ofBits - MIN(ofBits, STREAM_ACCUMULATOR_MIN-1); if (extraBits) { - BIT_addBits(&blockStream, sequences[n].offset, extraBits); + BIT_addBits(&blockStream, sequences[n].offBase, extraBits); BIT_flushBits(&blockStream); /* (7)*/ } - BIT_addBits(&blockStream, sequences[n].offset >> extraBits, + BIT_addBits(&blockStream, sequences[n].offBase >> extraBits, ofBits - extraBits); /* 31 */ } else { - BIT_addBits(&blockStream, sequences[n].offset, ofBits); /* 31 */ + BIT_addBits(&blockStream, sequences[n].offBase, ofBits); /* 31 */ } BIT_flushBits(&blockStream); /* (7)*/ DEBUGLOG(7, "remaining space : %i", (int)(blockStream.endPtr - blockStream.ptr)); @@ -398,7 +399,7 @@ ZSTD_encodeSequences_default( #if DYNAMIC_BMI2 -static TARGET_ATTRIBUTE("bmi2") size_t +static BMI2_TARGET_ATTRIBUTE size_t ZSTD_encodeSequences_bmi2( void* dst, size_t dstCapacity, FSE_CTable const* CTable_MatchLength, BYTE const* mlCodeTable, diff --git a/Utilities/cmzstd/lib/compress/zstd_compress_sequences.h b/Utilities/cmzstd/lib/compress/zstd_compress_sequences.h index 7991364c2f7..4a3a05da948 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress_sequences.h +++ b/Utilities/cmzstd/lib/compress/zstd_compress_sequences.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/compress/zstd_compress_superblock.c b/Utilities/cmzstd/lib/compress/zstd_compress_superblock.c index e4e45069bc1..638c4acbe70 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress_superblock.c +++ b/Utilities/cmzstd/lib/compress/zstd_compress_superblock.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -36,13 +36,14 @@ * If it is set_compressed, first sub-block's literals section will be Treeless_Literals_Block * and the following sub-blocks' literals sections will be Treeless_Literals_Block. * @return : compressed size of literals section of a sub-block - * Or 0 if it unable to compress. + * Or 0 if unable to compress. * Or error code */ -static size_t ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, - const ZSTD_hufCTablesMetadata_t* hufMetadata, - const BYTE* literals, size_t litSize, - void* dst, size_t dstSize, - const int bmi2, int writeEntropy, int* entropyWritten) +static size_t +ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, + const ZSTD_hufCTablesMetadata_t* hufMetadata, + const BYTE* literals, size_t litSize, + void* dst, size_t dstSize, + const int bmi2, int writeEntropy, int* entropyWritten) { size_t const header = writeEntropy ? 200 : 0; size_t const lhSize = 3 + (litSize >= (1 KB - header)) + (litSize >= (16 KB - header)); @@ -53,8 +54,6 @@ static size_t ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, symbolEncodingType_e hType = writeEntropy ? hufMetadata->hType : set_repeat; size_t cLitSize = 0; - (void)bmi2; /* TODO bmi2... */ - DEBUGLOG(5, "ZSTD_compressSubBlock_literal (litSize=%zu, lhSize=%zu, writeEntropy=%d)", litSize, lhSize, writeEntropy); *entropyWritten = 0; @@ -76,9 +75,9 @@ static size_t ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, DEBUGLOG(5, "ZSTD_compressSubBlock_literal (hSize=%zu)", hufMetadata->hufDesSize); } - /* TODO bmi2 */ - { const size_t cSize = singleStream ? HUF_compress1X_usingCTable(op, oend-op, literals, litSize, hufTable) - : HUF_compress4X_usingCTable(op, oend-op, literals, litSize, hufTable); + { int const flags = bmi2 ? HUF_flags_bmi2 : 0; + const size_t cSize = singleStream ? HUF_compress1X_usingCTable(op, oend-op, literals, litSize, hufTable, flags) + : HUF_compress4X_usingCTable(op, oend-op, literals, litSize, hufTable, flags); op += cSize; cLitSize += cSize; if (cSize == 0 || ERR_isError(cSize)) { @@ -126,12 +125,17 @@ static size_t ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, return op-ostart; } -static size_t ZSTD_seqDecompressedSize(seqStore_t const* seqStore, const seqDef* sequences, size_t nbSeq, size_t litSize, int lastSequence) { +static size_t +ZSTD_seqDecompressedSize(seqStore_t const* seqStore, + const seqDef* sequences, size_t nbSeq, + size_t litSize, int lastSequence) +{ const seqDef* const sstart = sequences; const seqDef* const send = sequences + nbSeq; const seqDef* sp = sstart; size_t matchLengthSum = 0; size_t litLengthSum = 0; + (void)(litLengthSum); /* suppress unused variable warning on some environments */ while (send-sp > 0) { ZSTD_sequenceLength const seqLen = ZSTD_getSequenceLength(seqStore, sp); litLengthSum += seqLen.litLength; @@ -155,13 +159,14 @@ static size_t ZSTD_seqDecompressedSize(seqStore_t const* seqStore, const seqDef* * @return : compressed size of sequences section of a sub-block * Or 0 if it is unable to compress * Or error code. */ -static size_t ZSTD_compressSubBlock_sequences(const ZSTD_fseCTables_t* fseTables, - const ZSTD_fseCTablesMetadata_t* fseMetadata, - const seqDef* sequences, size_t nbSeq, - const BYTE* llCode, const BYTE* mlCode, const BYTE* ofCode, - const ZSTD_CCtx_params* cctxParams, - void* dst, size_t dstCapacity, - const int bmi2, int writeEntropy, int* entropyWritten) +static size_t +ZSTD_compressSubBlock_sequences(const ZSTD_fseCTables_t* fseTables, + const ZSTD_fseCTablesMetadata_t* fseMetadata, + const seqDef* sequences, size_t nbSeq, + const BYTE* llCode, const BYTE* mlCode, const BYTE* ofCode, + const ZSTD_CCtx_params* cctxParams, + void* dst, size_t dstCapacity, + const int bmi2, int writeEntropy, int* entropyWritten) { const int longOffsets = cctxParams->cParams.windowLog > STREAM_ACCUMULATOR_MIN; BYTE* const ostart = (BYTE*)dst; @@ -324,7 +329,7 @@ static size_t ZSTD_estimateSubBlockSize_literal(const BYTE* literals, size_t lit static size_t ZSTD_estimateSubBlockSize_symbolType(symbolEncodingType_e type, const BYTE* codeTable, unsigned maxCode, size_t nbSeq, const FSE_CTable* fseCTable, - const U32* additionalBits, + const U8* additionalBits, short const* defaultNorm, U32 defaultNormLog, U32 defaultMax, void* workspace, size_t wkspSize) { @@ -474,7 +479,7 @@ static size_t ZSTD_compressSubBlock_multi(const seqStore_t* seqStorePtr, /* I think there is an optimization opportunity here. * Calling ZSTD_estimateSubBlockSize for every sequence can be wasteful * since it recalculates estimate from scratch. - * For example, it would recount literal distribution and symbol codes everytime. + * For example, it would recount literal distribution and symbol codes every time. */ cBlockSizeEstimate = ZSTD_estimateSubBlockSize(lp, litSize, ofCodePtr, llCodePtr, mlCodePtr, seqCount, &nextCBlock->entropy, entropyMetadata, @@ -538,7 +543,7 @@ static size_t ZSTD_compressSubBlock_multi(const seqStore_t* seqStorePtr, repcodes_t rep; ZSTD_memcpy(&rep, prevCBlock->rep, sizeof(rep)); for (seq = sstart; seq < sp; ++seq) { - rep = ZSTD_updateRep(rep.rep, seq->offset - 1, ZSTD_getSequenceLength(seqStorePtr, seq).litLength == 0); + ZSTD_updateRep(rep.rep, seq->offBase, ZSTD_getSequenceLength(seqStorePtr, seq).litLength == 0); } ZSTD_memcpy(nextCBlock->rep, &rep, sizeof(rep)); } diff --git a/Utilities/cmzstd/lib/compress/zstd_compress_superblock.h b/Utilities/cmzstd/lib/compress/zstd_compress_superblock.h index 176f9b106f3..8e494f0d5e6 100644 --- a/Utilities/cmzstd/lib/compress/zstd_compress_superblock.h +++ b/Utilities/cmzstd/lib/compress/zstd_compress_superblock.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/compress/zstd_cwksp.h b/Utilities/cmzstd/lib/compress/zstd_cwksp.h index 2656d26ca2e..cc7fb1c715c 100644 --- a/Utilities/cmzstd/lib/compress/zstd_cwksp.h +++ b/Utilities/cmzstd/lib/compress/zstd_cwksp.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -14,7 +14,9 @@ /*-************************************* * Dependencies ***************************************/ +#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customFree */ #include "../common/zstd_internal.h" +#include "../common/portability_macros.h" #if defined (__cplusplus) extern "C" { @@ -44,8 +46,9 @@ extern "C" { ***************************************/ typedef enum { ZSTD_cwksp_alloc_objects, - ZSTD_cwksp_alloc_buffers, - ZSTD_cwksp_alloc_aligned + ZSTD_cwksp_alloc_aligned_init_once, + ZSTD_cwksp_alloc_aligned, + ZSTD_cwksp_alloc_buffers } ZSTD_cwksp_alloc_phase_e; /** @@ -98,8 +101,8 @@ typedef enum { * * Workspace Layout: * - * [ ... workspace ... ] - * [objects][tables ... ->] free space [<- ... aligned][<- ... buffers] + * [ ... workspace ... ] + * [objects][tables ->] free space [<- buffers][<- aligned][<- init once] * * The various objects that live in the workspace are divided into the * following categories, and are allocated separately: @@ -123,9 +126,18 @@ typedef enum { * uint32_t arrays, all of whose values are between 0 and (nextSrc - base). * Their sizes depend on the cparams. These tables are 64-byte aligned. * - * - Aligned: these buffers are used for various purposes that require 4 byte - * alignment, but don't require any initialization before they're used. These - * buffers are each aligned to 64 bytes. + * - Init once: these buffers require to be initialized at least once before + * use. They should be used when we want to skip memory initialization + * while not triggering memory checkers (like Valgrind) when reading from + * from this memory without writing to it first. + * These buffers should be used carefully as they might contain data + * from previous compressions. + * Buffers are aligned to 64 bytes. + * + * - Aligned: these buffers don't require any initialization before they're + * used. The user of the buffer should make sure they write into a buffer + * location before reading from it. + * Buffers are aligned to 64 bytes. * * - Buffers: these buffers are used for various purposes that don't require * any alignment or initialization before they're used. This means they can @@ -137,8 +149,9 @@ typedef enum { * correctly packed into the workspace buffer. That order is: * * 1. Objects - * 2. Buffers - * 3. Aligned/Tables + * 2. Init once / Tables + * 3. Aligned / Tables + * 4. Buffers / Tables * * Attempts to reserve objects of different types out of order will fail. */ @@ -150,6 +163,7 @@ typedef struct { void* tableEnd; void* tableValidEnd; void* allocStart; + void* initOnceStart; BYTE allocFailed; int workspaceOversizedDuration; @@ -162,6 +176,7 @@ typedef struct { ***************************************/ MEM_STATIC size_t ZSTD_cwksp_available_space(ZSTD_cwksp* ws); +MEM_STATIC void* ZSTD_cwksp_initialAllocStart(ZSTD_cwksp* ws); MEM_STATIC void ZSTD_cwksp_assert_internal_consistency(ZSTD_cwksp* ws) { (void)ws; @@ -171,6 +186,20 @@ MEM_STATIC void ZSTD_cwksp_assert_internal_consistency(ZSTD_cwksp* ws) { assert(ws->tableEnd <= ws->allocStart); assert(ws->tableValidEnd <= ws->allocStart); assert(ws->allocStart <= ws->workspaceEnd); + assert(ws->initOnceStart <= ZSTD_cwksp_initialAllocStart(ws)); + assert(ws->workspace <= ws->initOnceStart); +#if ZSTD_MEMORY_SANITIZER + { + intptr_t const offset = __msan_test_shadow(ws->initOnceStart, + (U8*)ZSTD_cwksp_initialAllocStart(ws) - (U8*)ws->initOnceStart); +#if defined(ZSTD_MSAN_PRINT) + if(offset!=-1) { + __msan_print_shadow((U8*)ws->initOnceStart + offset - 8, 32); + } +#endif + assert(offset==-1); + }; +#endif } /** @@ -217,14 +246,10 @@ MEM_STATIC size_t ZSTD_cwksp_aligned_alloc_size(size_t size) { * for internal purposes (currently only alignment). */ MEM_STATIC size_t ZSTD_cwksp_slack_space_required(void) { - /* For alignment, the wksp will always allocate an additional n_1=[1, 64] bytes - * to align the beginning of tables section, as well as another n_2=[0, 63] bytes - * to align the beginning of the aligned secion. - * - * n_1 + n_2 == 64 bytes if the cwksp is freshly allocated, due to tables and - * aligneds being sized in multiples of 64 bytes. + /* For alignment, the wksp will always allocate an additional 2*ZSTD_CWKSP_ALIGNMENT_BYTES + * bytes to align the beginning of tables section and end of buffers; */ - size_t const slackSpace = ZSTD_CWKSP_ALIGNMENT_BYTES; + size_t const slackSpace = ZSTD_CWKSP_ALIGNMENT_BYTES * 2; return slackSpace; } @@ -237,18 +262,28 @@ MEM_STATIC size_t ZSTD_cwksp_bytes_to_align_ptr(void* ptr, const size_t alignByt size_t const alignBytesMask = alignBytes - 1; size_t const bytes = (alignBytes - ((size_t)ptr & (alignBytesMask))) & alignBytesMask; assert((alignBytes & alignBytesMask) == 0); - assert(bytes != ZSTD_CWKSP_ALIGNMENT_BYTES); + assert(bytes < alignBytes); return bytes; } +/** + * Returns the initial value for allocStart which is used to determine the position from + * which we can allocate from the end of the workspace. + */ +MEM_STATIC void* ZSTD_cwksp_initialAllocStart(ZSTD_cwksp* ws) { + return (void*)((size_t)ws->workspaceEnd & ~(ZSTD_CWKSP_ALIGNMENT_BYTES-1)); +} + /** * Internal function. Do not use directly. - * Reserves the given number of bytes within the aligned/buffer segment of the wksp, which - * counts from the end of the wksp. (as opposed to the object/table segment) + * Reserves the given number of bytes within the aligned/buffer segment of the wksp, + * which counts from the end of the wksp (as opposed to the object/table segment). * * Returns a pointer to the beginning of that space. */ -MEM_STATIC void* ZSTD_cwksp_reserve_internal_buffer_space(ZSTD_cwksp* ws, size_t const bytes) { +MEM_STATIC void* +ZSTD_cwksp_reserve_internal_buffer_space(ZSTD_cwksp* ws, size_t const bytes) +{ void* const alloc = (BYTE*)ws->allocStart - bytes; void* const bottom = ws->tableEnd; DEBUGLOG(5, "cwksp: reserving %p %zd bytes, %zd bytes remaining", @@ -260,6 +295,8 @@ MEM_STATIC void* ZSTD_cwksp_reserve_internal_buffer_space(ZSTD_cwksp* ws, size_t ws->allocFailed = 1; return NULL; } + /* the area is reserved from the end of wksp. + * If it overlaps with tableValidEnd, it voids guarantees on values' range */ if (alloc < ws->tableValidEnd) { ws->tableValidEnd = alloc; } @@ -269,39 +306,32 @@ MEM_STATIC void* ZSTD_cwksp_reserve_internal_buffer_space(ZSTD_cwksp* ws, size_t /** * Moves the cwksp to the next phase, and does any necessary allocations. + * cwksp initialization must necessarily go through each phase in order. * Returns a 0 on success, or zstd error */ -MEM_STATIC size_t ZSTD_cwksp_internal_advance_phase( - ZSTD_cwksp* ws, ZSTD_cwksp_alloc_phase_e phase) { +MEM_STATIC size_t +ZSTD_cwksp_internal_advance_phase(ZSTD_cwksp* ws, ZSTD_cwksp_alloc_phase_e phase) +{ assert(phase >= ws->phase); if (phase > ws->phase) { - /* Going from allocating objects to allocating buffers */ - if (ws->phase < ZSTD_cwksp_alloc_buffers && - phase >= ZSTD_cwksp_alloc_buffers) { + /* Going from allocating objects to allocating initOnce / tables */ + if (ws->phase < ZSTD_cwksp_alloc_aligned_init_once && + phase >= ZSTD_cwksp_alloc_aligned_init_once) { ws->tableValidEnd = ws->objectEnd; - } + ws->initOnceStart = ZSTD_cwksp_initialAllocStart(ws); - /* Going from allocating buffers to allocating aligneds/tables */ - if (ws->phase < ZSTD_cwksp_alloc_aligned && - phase >= ZSTD_cwksp_alloc_aligned) { - { /* Align the start of the "aligned" to 64 bytes. Use [1, 64] bytes. */ - size_t const bytesToAlign = - ZSTD_CWKSP_ALIGNMENT_BYTES - ZSTD_cwksp_bytes_to_align_ptr(ws->allocStart, ZSTD_CWKSP_ALIGNMENT_BYTES); - DEBUGLOG(5, "reserving aligned alignment addtl space: %zu", bytesToAlign); - ZSTD_STATIC_ASSERT((ZSTD_CWKSP_ALIGNMENT_BYTES & (ZSTD_CWKSP_ALIGNMENT_BYTES - 1)) == 0); /* power of 2 */ - RETURN_ERROR_IF(!ZSTD_cwksp_reserve_internal_buffer_space(ws, bytesToAlign), - memory_allocation, "aligned phase - alignment initial allocation failed!"); - } { /* Align the start of the tables to 64 bytes. Use [0, 63] bytes */ - void* const alloc = ws->objectEnd; + void *const alloc = ws->objectEnd; size_t const bytesToAlign = ZSTD_cwksp_bytes_to_align_ptr(alloc, ZSTD_CWKSP_ALIGNMENT_BYTES); - void* const end = (BYTE*)alloc + bytesToAlign; + void *const objectEnd = (BYTE *) alloc + bytesToAlign; DEBUGLOG(5, "reserving table alignment addtl space: %zu", bytesToAlign); - RETURN_ERROR_IF(end > ws->workspaceEnd, memory_allocation, + RETURN_ERROR_IF(objectEnd > ws->workspaceEnd, memory_allocation, "table phase - alignment initial allocation failed!"); - ws->objectEnd = end; - ws->tableEnd = end; - ws->tableValidEnd = end; + ws->objectEnd = objectEnd; + ws->tableEnd = objectEnd; /* table area starts being empty */ + if (ws->tableValidEnd < ws->tableEnd) { + ws->tableValidEnd = ws->tableEnd; + } } } ws->phase = phase; @@ -313,15 +343,17 @@ MEM_STATIC size_t ZSTD_cwksp_internal_advance_phase( /** * Returns whether this object/buffer/etc was allocated in this workspace. */ -MEM_STATIC int ZSTD_cwksp_owns_buffer(const ZSTD_cwksp* ws, const void* ptr) { - return (ptr != NULL) && (ws->workspace <= ptr) && (ptr <= ws->workspaceEnd); +MEM_STATIC int ZSTD_cwksp_owns_buffer(const ZSTD_cwksp* ws, const void* ptr) +{ + return (ptr != NULL) && (ws->workspace <= ptr) && (ptr < ws->workspaceEnd); } /** * Internal function. Do not use directly. */ -MEM_STATIC void* ZSTD_cwksp_reserve_internal( - ZSTD_cwksp* ws, size_t bytes, ZSTD_cwksp_alloc_phase_e phase) { +MEM_STATIC void* +ZSTD_cwksp_reserve_internal(ZSTD_cwksp* ws, size_t bytes, ZSTD_cwksp_alloc_phase_e phase) +{ void* alloc; if (ZSTD_isError(ZSTD_cwksp_internal_advance_phase(ws, phase)) || bytes == 0) { return NULL; @@ -340,7 +372,9 @@ MEM_STATIC void* ZSTD_cwksp_reserve_internal( if (alloc) { alloc = (BYTE *)alloc + ZSTD_CWKSP_ASAN_REDZONE_SIZE; if (ws->isStatic == ZSTD_cwksp_dynamic_alloc) { - __asan_unpoison_memory_region(alloc, bytes); + /* We need to keep the redzone poisoned while unpoisoning the bytes that + * are actually allocated. */ + __asan_unpoison_memory_region(alloc, bytes - 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE); } } #endif @@ -351,14 +385,46 @@ MEM_STATIC void* ZSTD_cwksp_reserve_internal( /** * Reserves and returns unaligned memory. */ -MEM_STATIC BYTE* ZSTD_cwksp_reserve_buffer(ZSTD_cwksp* ws, size_t bytes) { +MEM_STATIC BYTE* ZSTD_cwksp_reserve_buffer(ZSTD_cwksp* ws, size_t bytes) +{ return (BYTE*)ZSTD_cwksp_reserve_internal(ws, bytes, ZSTD_cwksp_alloc_buffers); } +/** + * Reserves and returns memory sized on and aligned on ZSTD_CWKSP_ALIGNMENT_BYTES (64 bytes). + * This memory has been initialized at least once in the past. + * This doesn't mean it has been initialized this time, and it might contain data from previous + * operations. + * The main usage is for algorithms that might need read access into uninitialized memory. + * The algorithm must maintain safety under these conditions and must make sure it doesn't + * leak any of the past data (directly or in side channels). + */ +MEM_STATIC void* ZSTD_cwksp_reserve_aligned_init_once(ZSTD_cwksp* ws, size_t bytes) +{ + size_t const alignedBytes = ZSTD_cwksp_align(bytes, ZSTD_CWKSP_ALIGNMENT_BYTES); + void* ptr = ZSTD_cwksp_reserve_internal(ws, alignedBytes, ZSTD_cwksp_alloc_aligned_init_once); + assert(((size_t)ptr & (ZSTD_CWKSP_ALIGNMENT_BYTES-1))== 0); + if(ptr && ptr < ws->initOnceStart) { + /* We assume the memory following the current allocation is either: + * 1. Not usable as initOnce memory (end of workspace) + * 2. Another initOnce buffer that has been allocated before (and so was previously memset) + * 3. An ASAN redzone, in which case we don't want to write on it + * For these reasons it should be fine to not explicitly zero every byte up to ws->initOnceStart. + * Note that we assume here that MSAN and ASAN cannot run in the same time. */ + ZSTD_memset(ptr, 0, MIN((size_t)((U8*)ws->initOnceStart - (U8*)ptr), alignedBytes)); + ws->initOnceStart = ptr; + } +#if ZSTD_MEMORY_SANITIZER + assert(__msan_test_shadow(ptr, bytes) == -1); +#endif + return ptr; +} + /** * Reserves and returns memory sized on and aligned on ZSTD_CWKSP_ALIGNMENT_BYTES (64 bytes). */ -MEM_STATIC void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, size_t bytes) { +MEM_STATIC void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, size_t bytes) +{ void* ptr = ZSTD_cwksp_reserve_internal(ws, ZSTD_cwksp_align(bytes, ZSTD_CWKSP_ALIGNMENT_BYTES), ZSTD_cwksp_alloc_aligned); assert(((size_t)ptr & (ZSTD_CWKSP_ALIGNMENT_BYTES-1))== 0); @@ -370,14 +436,19 @@ MEM_STATIC void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, size_t bytes) { * their values remain constrained, allowing us to re-use them without * memset()-ing them. */ -MEM_STATIC void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, size_t bytes) { - const ZSTD_cwksp_alloc_phase_e phase = ZSTD_cwksp_alloc_aligned; +MEM_STATIC void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, size_t bytes) +{ + const ZSTD_cwksp_alloc_phase_e phase = ZSTD_cwksp_alloc_aligned_init_once; void* alloc; void* end; void* top; - if (ZSTD_isError(ZSTD_cwksp_internal_advance_phase(ws, phase))) { - return NULL; + /* We can only start allocating tables after we are done reserving space for objects at the + * start of the workspace */ + if(ws->phase < phase) { + if (ZSTD_isError(ZSTD_cwksp_internal_advance_phase(ws, phase))) { + return NULL; + } } alloc = ws->tableEnd; end = (BYTE *)alloc + bytes; @@ -408,9 +479,11 @@ MEM_STATIC void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, size_t bytes) { /** * Aligned on sizeof(void*). + * Note : should happen only once, at workspace first initialization */ -MEM_STATIC void* ZSTD_cwksp_reserve_object(ZSTD_cwksp* ws, size_t bytes) { - size_t roundedBytes = ZSTD_cwksp_align(bytes, sizeof(void*)); +MEM_STATIC void* ZSTD_cwksp_reserve_object(ZSTD_cwksp* ws, size_t bytes) +{ + size_t const roundedBytes = ZSTD_cwksp_align(bytes, sizeof(void*)); void* alloc = ws->objectEnd; void* end = (BYTE*)alloc + roundedBytes; @@ -419,15 +492,15 @@ MEM_STATIC void* ZSTD_cwksp_reserve_object(ZSTD_cwksp* ws, size_t bytes) { end = (BYTE *)end + 2 * ZSTD_CWKSP_ASAN_REDZONE_SIZE; #endif - DEBUGLOG(5, + DEBUGLOG(4, "cwksp: reserving %p object %zd bytes (rounded to %zd), %zd bytes remaining", alloc, bytes, roundedBytes, ZSTD_cwksp_available_space(ws) - roundedBytes); - assert(((size_t)alloc & (sizeof(void*)-1)) == 0); - assert((bytes & (sizeof(void*)-1)) == 0); + assert((size_t)alloc % ZSTD_ALIGNOF(void*) == 0); + assert(bytes % ZSTD_ALIGNOF(void*) == 0); ZSTD_cwksp_assert_internal_consistency(ws); /* we must be in the first phase, no advance is possible */ if (ws->phase != ZSTD_cwksp_alloc_objects || end > ws->workspaceEnd) { - DEBUGLOG(4, "cwksp: object alloc failed!"); + DEBUGLOG(3, "cwksp: object alloc failed!"); ws->allocFailed = 1; return NULL; } @@ -438,7 +511,7 @@ MEM_STATIC void* ZSTD_cwksp_reserve_object(ZSTD_cwksp* ws, size_t bytes) { #if ZSTD_ADDRESS_SANITIZER && !defined (ZSTD_ASAN_DONT_POISON_WORKSPACE) /* Move alloc so there's ZSTD_CWKSP_ASAN_REDZONE_SIZE unused space on * either size. */ - alloc = (BYTE *)alloc + ZSTD_CWKSP_ASAN_REDZONE_SIZE; + alloc = (BYTE*)alloc + ZSTD_CWKSP_ASAN_REDZONE_SIZE; if (ws->isStatic == ZSTD_cwksp_dynamic_alloc) { __asan_unpoison_memory_region(alloc, bytes); } @@ -447,17 +520,26 @@ MEM_STATIC void* ZSTD_cwksp_reserve_object(ZSTD_cwksp* ws, size_t bytes) { return alloc; } -MEM_STATIC void ZSTD_cwksp_mark_tables_dirty(ZSTD_cwksp* ws) { +MEM_STATIC void ZSTD_cwksp_mark_tables_dirty(ZSTD_cwksp* ws) +{ DEBUGLOG(4, "cwksp: ZSTD_cwksp_mark_tables_dirty"); #if ZSTD_MEMORY_SANITIZER && !defined (ZSTD_MSAN_DONT_POISON_WORKSPACE) /* To validate that the table re-use logic is sound, and that we don't * access table space that we haven't cleaned, we re-"poison" the table - * space every time we mark it dirty. */ + * space every time we mark it dirty. + * Since tableValidEnd space and initOnce space may overlap we don't poison + * the initOnce portion as it break its promise. This means that this poisoning + * check isn't always applied fully. */ { size_t size = (BYTE*)ws->tableValidEnd - (BYTE*)ws->objectEnd; assert(__msan_test_shadow(ws->objectEnd, size) == -1); - __msan_poison(ws->objectEnd, size); + if((BYTE*)ws->tableValidEnd < (BYTE*)ws->initOnceStart) { + __msan_poison(ws->objectEnd, size); + } else { + assert(ws->initOnceStart >= ws->objectEnd); + __msan_poison(ws->objectEnd, (BYTE*)ws->initOnceStart - (BYTE*)ws->objectEnd); + } } #endif @@ -485,7 +567,7 @@ MEM_STATIC void ZSTD_cwksp_clean_tables(ZSTD_cwksp* ws) { assert(ws->tableValidEnd >= ws->objectEnd); assert(ws->tableValidEnd <= ws->allocStart); if (ws->tableValidEnd < ws->tableEnd) { - ZSTD_memset(ws->tableValidEnd, 0, (BYTE*)ws->tableEnd - (BYTE*)ws->tableValidEnd); + ZSTD_memset(ws->tableValidEnd, 0, (size_t)((BYTE*)ws->tableEnd - (BYTE*)ws->tableValidEnd)); } ZSTD_cwksp_mark_tables_clean(ws); } @@ -522,11 +604,14 @@ MEM_STATIC void ZSTD_cwksp_clear(ZSTD_cwksp* ws) { #if ZSTD_MEMORY_SANITIZER && !defined (ZSTD_MSAN_DONT_POISON_WORKSPACE) /* To validate that the context re-use logic is sound, and that we don't * access stuff that this compression hasn't initialized, we re-"poison" - * the workspace (or at least the non-static, non-table parts of it) - * every time we start a new compression. */ + * the workspace except for the areas in which we expect memory re-use + * without initialization (objects, valid tables area and init once + * memory). */ { - size_t size = (BYTE*)ws->workspaceEnd - (BYTE*)ws->tableValidEnd; - __msan_poison(ws->tableValidEnd, size); + if((BYTE*)ws->tableValidEnd < (BYTE*)ws->initOnceStart) { + size_t size = (BYTE*)ws->initOnceStart - (BYTE*)ws->tableValidEnd; + __msan_poison(ws->tableValidEnd, size); + } } #endif @@ -542,10 +627,10 @@ MEM_STATIC void ZSTD_cwksp_clear(ZSTD_cwksp* ws) { #endif ws->tableEnd = ws->objectEnd; - ws->allocStart = ws->workspaceEnd; + ws->allocStart = ZSTD_cwksp_initialAllocStart(ws); ws->allocFailed = 0; - if (ws->phase > ZSTD_cwksp_alloc_buffers) { - ws->phase = ZSTD_cwksp_alloc_buffers; + if (ws->phase > ZSTD_cwksp_alloc_aligned_init_once) { + ws->phase = ZSTD_cwksp_alloc_aligned_init_once; } ZSTD_cwksp_assert_internal_consistency(ws); } @@ -562,6 +647,7 @@ MEM_STATIC void ZSTD_cwksp_init(ZSTD_cwksp* ws, void* start, size_t size, ZSTD_c ws->workspaceEnd = (BYTE*)start + size; ws->objectEnd = ws->workspace; ws->tableValidEnd = ws->objectEnd; + ws->initOnceStart = ZSTD_cwksp_initialAllocStart(ws); ws->phase = ZSTD_cwksp_alloc_objects; ws->isStatic = isStatic; ZSTD_cwksp_clear(ws); @@ -614,17 +700,11 @@ MEM_STATIC int ZSTD_cwksp_reserve_failed(const ZSTD_cwksp* ws) { * Returns if the estimated space needed for a wksp is within an acceptable limit of the * actual amount of space used. */ -MEM_STATIC int ZSTD_cwksp_estimated_space_within_bounds(const ZSTD_cwksp* const ws, - size_t const estimatedSpace, int resizedWorkspace) { - if (resizedWorkspace) { - /* Resized/newly allocated wksp should have exact bounds */ - return ZSTD_cwksp_used(ws) == estimatedSpace; - } else { - /* Due to alignment, when reusing a workspace, we can actually consume 63 fewer or more bytes - * than estimatedSpace. See the comments in zstd_cwksp.h for details. - */ - return (ZSTD_cwksp_used(ws) >= estimatedSpace - 63) && (ZSTD_cwksp_used(ws) <= estimatedSpace + 63); - } +MEM_STATIC int ZSTD_cwksp_estimated_space_within_bounds(const ZSTD_cwksp *const ws, size_t const estimatedSpace) { + /* We have an alignment space between objects and tables between tables and buffers, so we can have up to twice + * the alignment bytes difference between estimation and actual usage */ + return (estimatedSpace - ZSTD_cwksp_slack_space_required()) <= ZSTD_cwksp_used(ws) && + ZSTD_cwksp_used(ws) <= estimatedSpace; } diff --git a/Utilities/cmzstd/lib/compress/zstd_double_fast.c b/Utilities/cmzstd/lib/compress/zstd_double_fast.c index d0d3a784dd2..0ad88ffc7bd 100644 --- a/Utilities/cmzstd/lib/compress/zstd_double_fast.c +++ b/Utilities/cmzstd/lib/compress/zstd_double_fast.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -11,8 +11,43 @@ #include "zstd_compress_internal.h" #include "zstd_double_fast.h" +static void ZSTD_fillDoubleHashTableForCDict(ZSTD_matchState_t* ms, + void const* end, ZSTD_dictTableLoadMethod_e dtlm) +{ + const ZSTD_compressionParameters* const cParams = &ms->cParams; + U32* const hashLarge = ms->hashTable; + U32 const hBitsL = cParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; + U32 const mls = cParams->minMatch; + U32* const hashSmall = ms->chainTable; + U32 const hBitsS = cParams->chainLog + ZSTD_SHORT_CACHE_TAG_BITS; + const BYTE* const base = ms->window.base; + const BYTE* ip = base + ms->nextToUpdate; + const BYTE* const iend = ((const BYTE*)end) - HASH_READ_SIZE; + const U32 fastHashFillStep = 3; -void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, + /* Always insert every fastHashFillStep position into the hash tables. + * Insert the other positions into the large hash table if their entry + * is empty. + */ + for (; ip + fastHashFillStep - 1 <= iend; ip += fastHashFillStep) { + U32 const curr = (U32)(ip - base); + U32 i; + for (i = 0; i < fastHashFillStep; ++i) { + size_t const smHashAndTag = ZSTD_hashPtr(ip + i, hBitsS, mls); + size_t const lgHashAndTag = ZSTD_hashPtr(ip + i, hBitsL, 8); + if (i == 0) { + ZSTD_writeTaggedIndex(hashSmall, smHashAndTag, curr + i); + } + if (i == 0 || hashLarge[lgHashAndTag >> ZSTD_SHORT_CACHE_TAG_BITS] == 0) { + ZSTD_writeTaggedIndex(hashLarge, lgHashAndTag, curr + i); + } + /* Only load extra positions for ZSTD_dtlm_full */ + if (dtlm == ZSTD_dtlm_fast) + break; + } } +} + +static void ZSTD_fillDoubleHashTableForCCtx(ZSTD_matchState_t* ms, void const* end, ZSTD_dictTableLoadMethod_e dtlm) { const ZSTD_compressionParameters* const cParams = &ms->cParams; @@ -43,15 +78,237 @@ void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, /* Only load extra positions for ZSTD_dtlm_full */ if (dtlm == ZSTD_dtlm_fast) break; - } } + } } +} + +void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, + const void* const end, + ZSTD_dictTableLoadMethod_e dtlm, + ZSTD_tableFillPurpose_e tfp) +{ + if (tfp == ZSTD_tfp_forCDict) { + ZSTD_fillDoubleHashTableForCDict(ms, end, dtlm); + } else { + ZSTD_fillDoubleHashTableForCCtx(ms, end, dtlm); + } +} + + +FORCE_INLINE_TEMPLATE +size_t ZSTD_compressBlock_doubleFast_noDict_generic( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize, U32 const mls /* template */) +{ + ZSTD_compressionParameters const* cParams = &ms->cParams; + U32* const hashLong = ms->hashTable; + const U32 hBitsL = cParams->hashLog; + U32* const hashSmall = ms->chainTable; + const U32 hBitsS = cParams->chainLog; + const BYTE* const base = ms->window.base; + const BYTE* const istart = (const BYTE*)src; + const BYTE* anchor = istart; + const U32 endIndex = (U32)((size_t)(istart - base) + srcSize); + /* presumes that, if there is a dictionary, it must be using Attach mode */ + const U32 prefixLowestIndex = ZSTD_getLowestPrefixIndex(ms, endIndex, cParams->windowLog); + const BYTE* const prefixLowest = base + prefixLowestIndex; + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - HASH_READ_SIZE; + U32 offset_1=rep[0], offset_2=rep[1]; + U32 offsetSaved1 = 0, offsetSaved2 = 0; + + size_t mLength; + U32 offset; + U32 curr; + + /* how many positions to search before increasing step size */ + const size_t kStepIncr = 1 << kSearchStrength; + /* the position at which to increment the step size if no match is found */ + const BYTE* nextStep; + size_t step; /* the current step size */ + + size_t hl0; /* the long hash at ip */ + size_t hl1; /* the long hash at ip1 */ + + U32 idxl0; /* the long match index for ip */ + U32 idxl1; /* the long match index for ip1 */ + + const BYTE* matchl0; /* the long match for ip */ + const BYTE* matchs0; /* the short match for ip */ + const BYTE* matchl1; /* the long match for ip1 */ + + const BYTE* ip = istart; /* the current position */ + const BYTE* ip1; /* the next position */ + + DEBUGLOG(5, "ZSTD_compressBlock_doubleFast_noDict_generic"); + + /* init */ + ip += ((ip - prefixLowest) == 0); + { + U32 const current = (U32)(ip - base); + U32 const windowLow = ZSTD_getLowestPrefixIndex(ms, current, cParams->windowLog); + U32 const maxRep = current - windowLow; + if (offset_2 > maxRep) offsetSaved2 = offset_2, offset_2 = 0; + if (offset_1 > maxRep) offsetSaved1 = offset_1, offset_1 = 0; + } + + /* Outer Loop: one iteration per match found and stored */ + while (1) { + step = 1; + nextStep = ip + kStepIncr; + ip1 = ip + step; + + if (ip1 > ilimit) { + goto _cleanup; + } + + hl0 = ZSTD_hashPtr(ip, hBitsL, 8); + idxl0 = hashLong[hl0]; + matchl0 = base + idxl0; + + /* Inner Loop: one iteration per search / position */ + do { + const size_t hs0 = ZSTD_hashPtr(ip, hBitsS, mls); + const U32 idxs0 = hashSmall[hs0]; + curr = (U32)(ip-base); + matchs0 = base + idxs0; + + hashLong[hl0] = hashSmall[hs0] = curr; /* update hash tables */ + + /* check noDict repcode */ + if ((offset_1 > 0) & (MEM_read32(ip+1-offset_1) == MEM_read32(ip+1))) { + mLength = ZSTD_count(ip+1+4, ip+1+4-offset_1, iend) + 4; + ip++; + ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); + goto _match_stored; + } + + hl1 = ZSTD_hashPtr(ip1, hBitsL, 8); + + if (idxl0 > prefixLowestIndex) { + /* check prefix long match */ + if (MEM_read64(matchl0) == MEM_read64(ip)) { + mLength = ZSTD_count(ip+8, matchl0+8, iend) + 8; + offset = (U32)(ip-matchl0); + while (((ip>anchor) & (matchl0>prefixLowest)) && (ip[-1] == matchl0[-1])) { ip--; matchl0--; mLength++; } /* catch up */ + goto _match_found; + } + } + + idxl1 = hashLong[hl1]; + matchl1 = base + idxl1; + + if (idxs0 > prefixLowestIndex) { + /* check prefix short match */ + if (MEM_read32(matchs0) == MEM_read32(ip)) { + goto _search_next_long; + } + } + + if (ip1 >= nextStep) { + PREFETCH_L1(ip1 + 64); + PREFETCH_L1(ip1 + 128); + step++; + nextStep += kStepIncr; + } + ip = ip1; + ip1 += step; + + hl0 = hl1; + idxl0 = idxl1; + matchl0 = matchl1; + #if defined(__aarch64__) + PREFETCH_L1(ip+256); + #endif + } while (ip1 <= ilimit); + +_cleanup: + /* If offset_1 started invalid (offsetSaved1 != 0) and became valid (offset_1 != 0), + * rotate saved offsets. See comment in ZSTD_compressBlock_fast_noDict for more context. */ + offsetSaved2 = ((offsetSaved1 != 0) && (offset_1 != 0)) ? offsetSaved1 : offsetSaved2; + + /* save reps for next block */ + rep[0] = offset_1 ? offset_1 : offsetSaved1; + rep[1] = offset_2 ? offset_2 : offsetSaved2; + + /* Return the last literals size */ + return (size_t)(iend - anchor); + +_search_next_long: + + /* check prefix long +1 match */ + if (idxl1 > prefixLowestIndex) { + if (MEM_read64(matchl1) == MEM_read64(ip1)) { + ip = ip1; + mLength = ZSTD_count(ip+8, matchl1+8, iend) + 8; + offset = (U32)(ip-matchl1); + while (((ip>anchor) & (matchl1>prefixLowest)) && (ip[-1] == matchl1[-1])) { ip--; matchl1--; mLength++; } /* catch up */ + goto _match_found; + } + } + + /* if no long +1 match, explore the short match we found */ + mLength = ZSTD_count(ip+4, matchs0+4, iend) + 4; + offset = (U32)(ip - matchs0); + while (((ip>anchor) & (matchs0>prefixLowest)) && (ip[-1] == matchs0[-1])) { ip--; matchs0--; mLength++; } /* catch up */ + + /* fall-through */ + +_match_found: /* requires ip, offset, mLength */ + offset_2 = offset_1; + offset_1 = offset; + + if (step < 4) { + /* It is unsafe to write this value back to the hashtable when ip1 is + * greater than or equal to the new ip we will have after we're done + * processing this match. Rather than perform that test directly + * (ip1 >= ip + mLength), which costs speed in practice, we do a simpler + * more predictable test. The minmatch even if we take a short match is + * 4 bytes, so as long as step, the distance between ip and ip1 + * (initially) is less than 4, we know ip1 < new ip. */ + hashLong[hl1] = (U32)(ip1 - base); + } + + ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); + +_match_stored: + /* match found */ + ip += mLength; + anchor = ip; + + if (ip <= ilimit) { + /* Complementary insertion */ + /* done after iLimit test, as candidates could be > iend-8 */ + { U32 const indexToInsert = curr+2; + hashLong[ZSTD_hashPtr(base+indexToInsert, hBitsL, 8)] = indexToInsert; + hashLong[ZSTD_hashPtr(ip-2, hBitsL, 8)] = (U32)(ip-2-base); + hashSmall[ZSTD_hashPtr(base+indexToInsert, hBitsS, mls)] = indexToInsert; + hashSmall[ZSTD_hashPtr(ip-1, hBitsS, mls)] = (U32)(ip-1-base); + } + + /* check immediate repcode */ + while ( (ip <= ilimit) + && ( (offset_2>0) + & (MEM_read32(ip) == MEM_read32(ip - offset_2)) )) { + /* store sequence */ + size_t const rLength = ZSTD_count(ip+4, ip+4-offset_2, iend) + 4; + U32 const tmpOff = offset_2; offset_2 = offset_1; offset_1 = tmpOff; /* swap offset_2 <=> offset_1 */ + hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = (U32)(ip-base); + hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = (U32)(ip-base); + ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, rLength); + ip += rLength; + anchor = ip; + continue; /* faster when present ... (?) */ + } + } + } } FORCE_INLINE_TEMPLATE -size_t ZSTD_compressBlock_doubleFast_generic( +size_t ZSTD_compressBlock_doubleFast_dictMatchState_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, - U32 const mls /* template */, ZSTD_dictMode_e const dictMode) + U32 const mls /* template */) { ZSTD_compressionParameters const* cParams = &ms->cParams; U32* const hashLong = ms->hashTable; @@ -69,57 +326,39 @@ size_t ZSTD_compressBlock_doubleFast_generic( const BYTE* const iend = istart + srcSize; const BYTE* const ilimit = iend - HASH_READ_SIZE; U32 offset_1=rep[0], offset_2=rep[1]; - U32 offsetSaved = 0; const ZSTD_matchState_t* const dms = ms->dictMatchState; - const ZSTD_compressionParameters* const dictCParams = - dictMode == ZSTD_dictMatchState ? - &dms->cParams : NULL; - const U32* const dictHashLong = dictMode == ZSTD_dictMatchState ? - dms->hashTable : NULL; - const U32* const dictHashSmall = dictMode == ZSTD_dictMatchState ? - dms->chainTable : NULL; - const U32 dictStartIndex = dictMode == ZSTD_dictMatchState ? - dms->window.dictLimit : 0; - const BYTE* const dictBase = dictMode == ZSTD_dictMatchState ? - dms->window.base : NULL; - const BYTE* const dictStart = dictMode == ZSTD_dictMatchState ? - dictBase + dictStartIndex : NULL; - const BYTE* const dictEnd = dictMode == ZSTD_dictMatchState ? - dms->window.nextSrc : NULL; - const U32 dictIndexDelta = dictMode == ZSTD_dictMatchState ? - prefixLowestIndex - (U32)(dictEnd - dictBase) : - 0; - const U32 dictHBitsL = dictMode == ZSTD_dictMatchState ? - dictCParams->hashLog : hBitsL; - const U32 dictHBitsS = dictMode == ZSTD_dictMatchState ? - dictCParams->chainLog : hBitsS; + const ZSTD_compressionParameters* const dictCParams = &dms->cParams; + const U32* const dictHashLong = dms->hashTable; + const U32* const dictHashSmall = dms->chainTable; + const U32 dictStartIndex = dms->window.dictLimit; + const BYTE* const dictBase = dms->window.base; + const BYTE* const dictStart = dictBase + dictStartIndex; + const BYTE* const dictEnd = dms->window.nextSrc; + const U32 dictIndexDelta = prefixLowestIndex - (U32)(dictEnd - dictBase); + const U32 dictHBitsL = dictCParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; + const U32 dictHBitsS = dictCParams->chainLog + ZSTD_SHORT_CACHE_TAG_BITS; const U32 dictAndPrefixLength = (U32)((ip - prefixLowest) + (dictEnd - dictStart)); - DEBUGLOG(5, "ZSTD_compressBlock_doubleFast_generic"); - - assert(dictMode == ZSTD_noDict || dictMode == ZSTD_dictMatchState); + DEBUGLOG(5, "ZSTD_compressBlock_doubleFast_dictMatchState_generic"); /* if a dictionary is attached, it must be within window range */ - if (dictMode == ZSTD_dictMatchState) { - assert(ms->window.dictLimit + (1U << cParams->windowLog) >= endIndex); + assert(ms->window.dictLimit + (1U << cParams->windowLog) >= endIndex); + + if (ms->prefetchCDictTables) { + size_t const hashTableBytes = (((size_t)1) << dictCParams->hashLog) * sizeof(U32); + size_t const chainTableBytes = (((size_t)1) << dictCParams->chainLog) * sizeof(U32); + PREFETCH_AREA(dictHashLong, hashTableBytes) + PREFETCH_AREA(dictHashSmall, chainTableBytes) } /* init */ ip += (dictAndPrefixLength == 0); - if (dictMode == ZSTD_noDict) { - U32 const curr = (U32)(ip - base); - U32 const windowLow = ZSTD_getLowestPrefixIndex(ms, curr, cParams->windowLog); - U32 const maxRep = curr - windowLow; - if (offset_2 > maxRep) offsetSaved = offset_2, offset_2 = 0; - if (offset_1 > maxRep) offsetSaved = offset_1, offset_1 = 0; - } - if (dictMode == ZSTD_dictMatchState) { - /* dictMatchState repCode checks don't currently handle repCode == 0 - * disabling. */ - assert(offset_1 <= dictAndPrefixLength); - assert(offset_2 <= dictAndPrefixLength); - } + + /* dictMatchState repCode checks don't currently handle repCode == 0 + * disabling. */ + assert(offset_1 <= dictAndPrefixLength); + assert(offset_2 <= dictAndPrefixLength); /* Main Search Loop */ while (ip < ilimit) { /* < instead of <=, because repcode check at (ip+1) */ @@ -127,37 +366,30 @@ size_t ZSTD_compressBlock_doubleFast_generic( U32 offset; size_t const h2 = ZSTD_hashPtr(ip, hBitsL, 8); size_t const h = ZSTD_hashPtr(ip, hBitsS, mls); - size_t const dictHL = ZSTD_hashPtr(ip, dictHBitsL, 8); - size_t const dictHS = ZSTD_hashPtr(ip, dictHBitsS, mls); + size_t const dictHashAndTagL = ZSTD_hashPtr(ip, dictHBitsL, 8); + size_t const dictHashAndTagS = ZSTD_hashPtr(ip, dictHBitsS, mls); + U32 const dictMatchIndexAndTagL = dictHashLong[dictHashAndTagL >> ZSTD_SHORT_CACHE_TAG_BITS]; + U32 const dictMatchIndexAndTagS = dictHashSmall[dictHashAndTagS >> ZSTD_SHORT_CACHE_TAG_BITS]; + int const dictTagsMatchL = ZSTD_comparePackedTags(dictMatchIndexAndTagL, dictHashAndTagL); + int const dictTagsMatchS = ZSTD_comparePackedTags(dictMatchIndexAndTagS, dictHashAndTagS); U32 const curr = (U32)(ip-base); U32 const matchIndexL = hashLong[h2]; U32 matchIndexS = hashSmall[h]; const BYTE* matchLong = base + matchIndexL; const BYTE* match = base + matchIndexS; const U32 repIndex = curr + 1 - offset_1; - const BYTE* repMatch = (dictMode == ZSTD_dictMatchState - && repIndex < prefixLowestIndex) ? + const BYTE* repMatch = (repIndex < prefixLowestIndex) ? dictBase + (repIndex - dictIndexDelta) : base + repIndex; hashLong[h2] = hashSmall[h] = curr; /* update hash tables */ - /* check dictMatchState repcode */ - if (dictMode == ZSTD_dictMatchState - && ((U32)((prefixLowestIndex-1) - repIndex) >= 3 /* intentional underflow */) + /* check repcode */ + if (((U32)((prefixLowestIndex-1) - repIndex) >= 3 /* intentional underflow */) && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) { const BYTE* repMatchEnd = repIndex < prefixLowestIndex ? dictEnd : iend; mLength = ZSTD_count_2segments(ip+1+4, repMatch+4, iend, repMatchEnd, prefixLowest) + 4; ip++; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, 0, mLength-MINMATCH); - goto _match_stored; - } - - /* check noDict repcode */ - if ( dictMode == ZSTD_noDict - && ((offset_1 > 0) & (MEM_read32(ip+1-offset_1) == MEM_read32(ip+1)))) { - mLength = ZSTD_count(ip+1+4, ip+1+4-offset_1, iend) + 4; - ip++; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, 0, mLength-MINMATCH); + ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); goto _match_stored; } @@ -169,9 +401,9 @@ size_t ZSTD_compressBlock_doubleFast_generic( while (((ip>anchor) & (matchLong>prefixLowest)) && (ip[-1] == matchLong[-1])) { ip--; matchLong--; mLength++; } /* catch up */ goto _match_found; } - } else if (dictMode == ZSTD_dictMatchState) { + } else if (dictTagsMatchL) { /* check dictMatchState long match */ - U32 const dictMatchIndexL = dictHashLong[dictHL]; + U32 const dictMatchIndexL = dictMatchIndexAndTagL >> ZSTD_SHORT_CACHE_TAG_BITS; const BYTE* dictMatchL = dictBase + dictMatchIndexL; assert(dictMatchL < dictEnd); @@ -187,9 +419,9 @@ size_t ZSTD_compressBlock_doubleFast_generic( if (MEM_read32(match) == MEM_read32(ip)) { goto _search_next_long; } - } else if (dictMode == ZSTD_dictMatchState) { + } else if (dictTagsMatchS) { /* check dictMatchState short match */ - U32 const dictMatchIndexS = dictHashSmall[dictHS]; + U32 const dictMatchIndexS = dictMatchIndexAndTagS >> ZSTD_SHORT_CACHE_TAG_BITS; match = dictBase + dictMatchIndexS; matchIndexS = dictMatchIndexS + dictIndexDelta; @@ -204,10 +436,11 @@ size_t ZSTD_compressBlock_doubleFast_generic( continue; _search_next_long: - { size_t const hl3 = ZSTD_hashPtr(ip+1, hBitsL, 8); - size_t const dictHLNext = ZSTD_hashPtr(ip+1, dictHBitsL, 8); + size_t const dictHashAndTagL3 = ZSTD_hashPtr(ip+1, dictHBitsL, 8); U32 const matchIndexL3 = hashLong[hl3]; + U32 const dictMatchIndexAndTagL3 = dictHashLong[dictHashAndTagL3 >> ZSTD_SHORT_CACHE_TAG_BITS]; + int const dictTagsMatchL3 = ZSTD_comparePackedTags(dictMatchIndexAndTagL3, dictHashAndTagL3); const BYTE* matchL3 = base + matchIndexL3; hashLong[hl3] = curr + 1; @@ -220,9 +453,9 @@ size_t ZSTD_compressBlock_doubleFast_generic( while (((ip>anchor) & (matchL3>prefixLowest)) && (ip[-1] == matchL3[-1])) { ip--; matchL3--; mLength++; } /* catch up */ goto _match_found; } - } else if (dictMode == ZSTD_dictMatchState) { + } else if (dictTagsMatchL3) { /* check dict long +1 match */ - U32 const dictMatchIndexL3 = dictHashLong[dictHLNext]; + U32 const dictMatchIndexL3 = dictMatchIndexAndTagL3 >> ZSTD_SHORT_CACHE_TAG_BITS; const BYTE* dictMatchL3 = dictBase + dictMatchIndexL3; assert(dictMatchL3 < dictEnd); if (dictMatchL3 > dictStart && MEM_read64(dictMatchL3) == MEM_read64(ip+1)) { @@ -234,7 +467,7 @@ size_t ZSTD_compressBlock_doubleFast_generic( } } } /* if no long +1 match, explore the short match we found */ - if (dictMode == ZSTD_dictMatchState && matchIndexS < prefixLowestIndex) { + if (matchIndexS < prefixLowestIndex) { mLength = ZSTD_count_2segments(ip+4, match+4, iend, dictEnd, prefixLowest) + 4; offset = (U32)(curr - matchIndexS); while (((ip>anchor) & (match>dictStart)) && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ @@ -244,13 +477,11 @@ size_t ZSTD_compressBlock_doubleFast_generic( while (((ip>anchor) & (match>prefixLowest)) && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ } - /* fall-through */ - _match_found: offset_2 = offset_1; offset_1 = offset; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, offset + ZSTD_REP_MOVE, mLength-MINMATCH); + ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); _match_stored: /* match found */ @@ -268,53 +499,55 @@ size_t ZSTD_compressBlock_doubleFast_generic( } /* check immediate repcode */ - if (dictMode == ZSTD_dictMatchState) { - while (ip <= ilimit) { - U32 const current2 = (U32)(ip-base); - U32 const repIndex2 = current2 - offset_2; - const BYTE* repMatch2 = dictMode == ZSTD_dictMatchState - && repIndex2 < prefixLowestIndex ? - dictBase + repIndex2 - dictIndexDelta : - base + repIndex2; - if ( ((U32)((prefixLowestIndex-1) - (U32)repIndex2) >= 3 /* intentional overflow */) - && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { - const BYTE* const repEnd2 = repIndex2 < prefixLowestIndex ? dictEnd : iend; - size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixLowest) + 4; - U32 tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; /* swap offset_2 <=> offset_1 */ - ZSTD_storeSeq(seqStore, 0, anchor, iend, 0, repLength2-MINMATCH); - hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = current2; - hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = current2; - ip += repLength2; - anchor = ip; - continue; - } - break; - } } - - if (dictMode == ZSTD_noDict) { - while ( (ip <= ilimit) - && ( (offset_2>0) - & (MEM_read32(ip) == MEM_read32(ip - offset_2)) )) { - /* store sequence */ - size_t const rLength = ZSTD_count(ip+4, ip+4-offset_2, iend) + 4; - U32 const tmpOff = offset_2; offset_2 = offset_1; offset_1 = tmpOff; /* swap offset_2 <=> offset_1 */ - hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = (U32)(ip-base); - hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = (U32)(ip-base); - ZSTD_storeSeq(seqStore, 0, anchor, iend, 0, rLength-MINMATCH); - ip += rLength; + while (ip <= ilimit) { + U32 const current2 = (U32)(ip-base); + U32 const repIndex2 = current2 - offset_2; + const BYTE* repMatch2 = repIndex2 < prefixLowestIndex ? + dictBase + repIndex2 - dictIndexDelta : + base + repIndex2; + if ( ((U32)((prefixLowestIndex-1) - (U32)repIndex2) >= 3 /* intentional overflow */) + && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { + const BYTE* const repEnd2 = repIndex2 < prefixLowestIndex ? dictEnd : iend; + size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixLowest) + 4; + U32 tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; /* swap offset_2 <=> offset_1 */ + ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); + hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = current2; + hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = current2; + ip += repLength2; anchor = ip; - continue; /* faster when present ... (?) */ - } } } + continue; + } + break; + } + } } /* while (ip < ilimit) */ /* save reps for next block */ - rep[0] = offset_1 ? offset_1 : offsetSaved; - rep[1] = offset_2 ? offset_2 : offsetSaved; + rep[0] = offset_1; + rep[1] = offset_2; /* Return the last literals size */ return (size_t)(iend - anchor); } +#define ZSTD_GEN_DFAST_FN(dictMode, mls) \ + static size_t ZSTD_compressBlock_doubleFast_##dictMode##_##mls( \ + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], \ + void const* src, size_t srcSize) \ + { \ + return ZSTD_compressBlock_doubleFast_##dictMode##_generic(ms, seqStore, rep, src, srcSize, mls); \ + } + +ZSTD_GEN_DFAST_FN(noDict, 4) +ZSTD_GEN_DFAST_FN(noDict, 5) +ZSTD_GEN_DFAST_FN(noDict, 6) +ZSTD_GEN_DFAST_FN(noDict, 7) + +ZSTD_GEN_DFAST_FN(dictMatchState, 4) +ZSTD_GEN_DFAST_FN(dictMatchState, 5) +ZSTD_GEN_DFAST_FN(dictMatchState, 6) +ZSTD_GEN_DFAST_FN(dictMatchState, 7) + size_t ZSTD_compressBlock_doubleFast( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -325,13 +558,13 @@ size_t ZSTD_compressBlock_doubleFast( { default: /* includes case 3 */ case 4 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 4, ZSTD_noDict); + return ZSTD_compressBlock_doubleFast_noDict_4(ms, seqStore, rep, src, srcSize); case 5 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 5, ZSTD_noDict); + return ZSTD_compressBlock_doubleFast_noDict_5(ms, seqStore, rep, src, srcSize); case 6 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 6, ZSTD_noDict); + return ZSTD_compressBlock_doubleFast_noDict_6(ms, seqStore, rep, src, srcSize); case 7 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 7, ZSTD_noDict); + return ZSTD_compressBlock_doubleFast_noDict_7(ms, seqStore, rep, src, srcSize); } } @@ -345,13 +578,13 @@ size_t ZSTD_compressBlock_doubleFast_dictMatchState( { default: /* includes case 3 */ case 4 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 4, ZSTD_dictMatchState); + return ZSTD_compressBlock_doubleFast_dictMatchState_4(ms, seqStore, rep, src, srcSize); case 5 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 5, ZSTD_dictMatchState); + return ZSTD_compressBlock_doubleFast_dictMatchState_5(ms, seqStore, rep, src, srcSize); case 6 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 6, ZSTD_dictMatchState); + return ZSTD_compressBlock_doubleFast_dictMatchState_6(ms, seqStore, rep, src, srcSize); case 7 : - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, 7, ZSTD_dictMatchState); + return ZSTD_compressBlock_doubleFast_dictMatchState_7(ms, seqStore, rep, src, srcSize); } } @@ -387,7 +620,7 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic( /* if extDict is invalidated due to maxDistance, switch to "regular" variant */ if (prefixStartIndex == dictStartIndex) - return ZSTD_compressBlock_doubleFast_generic(ms, seqStore, rep, src, srcSize, mls, ZSTD_noDict); + return ZSTD_compressBlock_doubleFast(ms, seqStore, rep, src, srcSize); /* Search Loop */ while (ip < ilimit) { /* < instead of <=, because (ip+1) */ @@ -409,12 +642,12 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic( hashSmall[hSmall] = hashLong[hLong] = curr; /* update hash table */ if ((((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow : ensure repIndex doesn't overlap dict + prefix */ - & (offset_1 < curr+1 - dictStartIndex)) /* note: we are searching at curr+1 */ + & (offset_1 <= curr+1 - dictStartIndex)) /* note: we are searching at curr+1 */ && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) { const BYTE* repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; mLength = ZSTD_count_2segments(ip+1+4, repMatch+4, iend, repMatchEnd, prefixStart) + 4; ip++; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, 0, mLength-MINMATCH); + ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); } else { if ((matchLongIndex > dictStartIndex) && (MEM_read64(matchLong) == MEM_read64(ip))) { const BYTE* const matchEnd = matchLongIndex < prefixStartIndex ? dictEnd : iend; @@ -425,7 +658,7 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic( while (((ip>anchor) & (matchLong>lowMatchPtr)) && (ip[-1] == matchLong[-1])) { ip--; matchLong--; mLength++; } /* catch up */ offset_2 = offset_1; offset_1 = offset; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, offset + ZSTD_REP_MOVE, mLength-MINMATCH); + ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); } else if ((matchIndex > dictStartIndex) && (MEM_read32(match) == MEM_read32(ip))) { size_t const h3 = ZSTD_hashPtr(ip+1, hBitsL, 8); @@ -450,7 +683,7 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic( } offset_2 = offset_1; offset_1 = offset; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, offset + ZSTD_REP_MOVE, mLength-MINMATCH); + ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); } else { ip += ((ip-anchor) >> kSearchStrength) + 1; @@ -477,12 +710,12 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic( U32 const repIndex2 = current2 - offset_2; const BYTE* repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : base + repIndex2; if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3) /* intentional overflow : ensure repIndex2 doesn't overlap dict + prefix */ - & (offset_2 < current2 - dictStartIndex)) + & (offset_2 <= current2 - dictStartIndex)) && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; U32 const tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; /* swap offset_2 <=> offset_1 */ - ZSTD_storeSeq(seqStore, 0, anchor, iend, 0, repLength2-MINMATCH); + ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = current2; hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = current2; ip += repLength2; @@ -500,6 +733,10 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic( return (size_t)(iend - anchor); } +ZSTD_GEN_DFAST_FN(extDict, 4) +ZSTD_GEN_DFAST_FN(extDict, 5) +ZSTD_GEN_DFAST_FN(extDict, 6) +ZSTD_GEN_DFAST_FN(extDict, 7) size_t ZSTD_compressBlock_doubleFast_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -510,12 +747,12 @@ size_t ZSTD_compressBlock_doubleFast_extDict( { default: /* includes case 3 */ case 4 : - return ZSTD_compressBlock_doubleFast_extDict_generic(ms, seqStore, rep, src, srcSize, 4); + return ZSTD_compressBlock_doubleFast_extDict_4(ms, seqStore, rep, src, srcSize); case 5 : - return ZSTD_compressBlock_doubleFast_extDict_generic(ms, seqStore, rep, src, srcSize, 5); + return ZSTD_compressBlock_doubleFast_extDict_5(ms, seqStore, rep, src, srcSize); case 6 : - return ZSTD_compressBlock_doubleFast_extDict_generic(ms, seqStore, rep, src, srcSize, 6); + return ZSTD_compressBlock_doubleFast_extDict_6(ms, seqStore, rep, src, srcSize); case 7 : - return ZSTD_compressBlock_doubleFast_extDict_generic(ms, seqStore, rep, src, srcSize, 7); + return ZSTD_compressBlock_doubleFast_extDict_7(ms, seqStore, rep, src, srcSize); } } diff --git a/Utilities/cmzstd/lib/compress/zstd_double_fast.h b/Utilities/cmzstd/lib/compress/zstd_double_fast.h index e16b7b03a32..6f0047c4ba7 100644 --- a/Utilities/cmzstd/lib/compress/zstd_double_fast.h +++ b/Utilities/cmzstd/lib/compress/zstd_double_fast.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -19,7 +19,8 @@ extern "C" { #include "zstd_compress_internal.h" /* ZSTD_CCtx, size_t */ void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, - void const* end, ZSTD_dictTableLoadMethod_e dtlm); + void const* end, ZSTD_dictTableLoadMethod_e dtlm, + ZSTD_tableFillPurpose_e tfp); size_t ZSTD_compressBlock_doubleFast( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); diff --git a/Utilities/cmzstd/lib/compress/zstd_fast.c b/Utilities/cmzstd/lib/compress/zstd_fast.c index 4edc04dccdf..5f2c6a2edad 100644 --- a/Utilities/cmzstd/lib/compress/zstd_fast.c +++ b/Utilities/cmzstd/lib/compress/zstd_fast.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -11,8 +11,42 @@ #include "zstd_compress_internal.h" /* ZSTD_hashPtr, ZSTD_count, ZSTD_storeSeq */ #include "zstd_fast.h" +static void ZSTD_fillHashTableForCDict(ZSTD_matchState_t* ms, + const void* const end, + ZSTD_dictTableLoadMethod_e dtlm) +{ + const ZSTD_compressionParameters* const cParams = &ms->cParams; + U32* const hashTable = ms->hashTable; + U32 const hBits = cParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; + U32 const mls = cParams->minMatch; + const BYTE* const base = ms->window.base; + const BYTE* ip = base + ms->nextToUpdate; + const BYTE* const iend = ((const BYTE*)end) - HASH_READ_SIZE; + const U32 fastHashFillStep = 3; -void ZSTD_fillHashTable(ZSTD_matchState_t* ms, + /* Currently, we always use ZSTD_dtlm_full for filling CDict tables. + * Feel free to remove this assert if there's a good reason! */ + assert(dtlm == ZSTD_dtlm_full); + + /* Always insert every fastHashFillStep position into the hash table. + * Insert the other positions if their hash entry is empty. + */ + for ( ; ip + fastHashFillStep < iend + 2; ip += fastHashFillStep) { + U32 const curr = (U32)(ip - base); + { size_t const hashAndTag = ZSTD_hashPtr(ip, hBits, mls); + ZSTD_writeTaggedIndex(hashTable, hashAndTag, curr); } + + if (dtlm == ZSTD_dtlm_fast) continue; + /* Only load extra positions for ZSTD_dtlm_full */ + { U32 p; + for (p = 1; p < fastHashFillStep; ++p) { + size_t const hashAndTag = ZSTD_hashPtr(ip + p, hBits, mls); + if (hashTable[hashAndTag >> ZSTD_SHORT_CACHE_TAG_BITS] == 0) { /* not yet filled */ + ZSTD_writeTaggedIndex(hashTable, hashAndTag, curr + p); + } } } } +} + +static void ZSTD_fillHashTableForCCtx(ZSTD_matchState_t* ms, const void* const end, ZSTD_dictTableLoadMethod_e dtlm) { @@ -25,6 +59,10 @@ void ZSTD_fillHashTable(ZSTD_matchState_t* ms, const BYTE* const iend = ((const BYTE*)end) - HASH_READ_SIZE; const U32 fastHashFillStep = 3; + /* Currently, we always use ZSTD_dtlm_fast for filling CCtx tables. + * Feel free to remove this assert if there's a good reason! */ + assert(dtlm == ZSTD_dtlm_fast); + /* Always insert every fastHashFillStep position into the hash table. * Insert the other positions if their hash entry is empty. */ @@ -42,146 +80,344 @@ void ZSTD_fillHashTable(ZSTD_matchState_t* ms, } } } } } +void ZSTD_fillHashTable(ZSTD_matchState_t* ms, + const void* const end, + ZSTD_dictTableLoadMethod_e dtlm, + ZSTD_tableFillPurpose_e tfp) +{ + if (tfp == ZSTD_tfp_forCDict) { + ZSTD_fillHashTableForCDict(ms, end, dtlm); + } else { + ZSTD_fillHashTableForCCtx(ms, end, dtlm); + } +} + +/** + * If you squint hard enough (and ignore repcodes), the search operation at any + * given position is broken into 4 stages: + * + * 1. Hash (map position to hash value via input read) + * 2. Lookup (map hash val to index via hashtable read) + * 3. Load (map index to value at that position via input read) + * 4. Compare + * + * Each of these steps involves a memory read at an address which is computed + * from the previous step. This means these steps must be sequenced and their + * latencies are cumulative. + * + * Rather than do 1->2->3->4 sequentially for a single position before moving + * onto the next, this implementation interleaves these operations across the + * next few positions: + * + * R = Repcode Read & Compare + * H = Hash + * T = Table Lookup + * M = Match Read & Compare + * + * Pos | Time --> + * ----+------------------- + * N | ... M + * N+1 | ... TM + * N+2 | R H T M + * N+3 | H TM + * N+4 | R H T M + * N+5 | H ... + * N+6 | R ... + * + * This is very much analogous to the pipelining of execution in a CPU. And just + * like a CPU, we have to dump the pipeline when we find a match (i.e., take a + * branch). + * + * When this happens, we throw away our current state, and do the following prep + * to re-enter the loop: + * + * Pos | Time --> + * ----+------------------- + * N | H T + * N+1 | H + * + * This is also the work we do at the beginning to enter the loop initially. + */ FORCE_INLINE_TEMPLATE size_t -ZSTD_compressBlock_fast_generic( +ZSTD_compressBlock_fast_noDict_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize, - U32 const mls) + U32 const mls, U32 const hasStep) { const ZSTD_compressionParameters* const cParams = &ms->cParams; U32* const hashTable = ms->hashTable; U32 const hlog = cParams->hashLog; /* support stepSize of 0 */ - size_t const stepSize = cParams->targetLength + !(cParams->targetLength) + 1; + size_t const stepSize = hasStep ? (cParams->targetLength + !(cParams->targetLength) + 1) : 2; const BYTE* const base = ms->window.base; const BYTE* const istart = (const BYTE*)src; - /* We check ip0 (ip + 0) and ip1 (ip + 1) each loop */ - const BYTE* ip0 = istart; - const BYTE* ip1; - const BYTE* anchor = istart; const U32 endIndex = (U32)((size_t)(istart - base) + srcSize); const U32 prefixStartIndex = ZSTD_getLowestPrefixIndex(ms, endIndex, cParams->windowLog); const BYTE* const prefixStart = base + prefixStartIndex; const BYTE* const iend = istart + srcSize; const BYTE* const ilimit = iend - HASH_READ_SIZE; - U32 offset_1=rep[0], offset_2=rep[1]; - U32 offsetSaved = 0; - /* init */ + const BYTE* anchor = istart; + const BYTE* ip0 = istart; + const BYTE* ip1; + const BYTE* ip2; + const BYTE* ip3; + U32 current0; + + U32 rep_offset1 = rep[0]; + U32 rep_offset2 = rep[1]; + U32 offsetSaved1 = 0, offsetSaved2 = 0; + + size_t hash0; /* hash for ip0 */ + size_t hash1; /* hash for ip1 */ + U32 idx; /* match idx for ip0 */ + U32 mval; /* src value at match idx */ + + U32 offcode; + const BYTE* match0; + size_t mLength; + + /* ip0 and ip1 are always adjacent. The targetLength skipping and + * uncompressibility acceleration is applied to every other position, + * matching the behavior of #1562. step therefore represents the gap + * between pairs of positions, from ip0 to ip2 or ip1 to ip3. */ + size_t step; + const BYTE* nextStep; + const size_t kStepIncr = (1 << (kSearchStrength - 1)); + DEBUGLOG(5, "ZSTD_compressBlock_fast_generic"); ip0 += (ip0 == prefixStart); - ip1 = ip0 + 1; { U32 const curr = (U32)(ip0 - base); U32 const windowLow = ZSTD_getLowestPrefixIndex(ms, curr, cParams->windowLog); U32 const maxRep = curr - windowLow; - if (offset_2 > maxRep) offsetSaved = offset_2, offset_2 = 0; - if (offset_1 > maxRep) offsetSaved = offset_1, offset_1 = 0; + if (rep_offset2 > maxRep) offsetSaved2 = rep_offset2, rep_offset2 = 0; + if (rep_offset1 > maxRep) offsetSaved1 = rep_offset1, rep_offset1 = 0; } - /* Main Search Loop */ -#ifdef __INTEL_COMPILER - /* From intel 'The vector pragma indicates that the loop should be - * vectorized if it is legal to do so'. Can be used together with - * #pragma ivdep (but have opted to exclude that because intel - * warns against using it).*/ - #pragma vector always -#endif - while (ip1 < ilimit) { /* < instead of <=, because check at ip0+2 */ - size_t mLength; - BYTE const* ip2 = ip0 + 2; - size_t const h0 = ZSTD_hashPtr(ip0, hlog, mls); - U32 const val0 = MEM_read32(ip0); - size_t const h1 = ZSTD_hashPtr(ip1, hlog, mls); - U32 const val1 = MEM_read32(ip1); - U32 const current0 = (U32)(ip0-base); - U32 const current1 = (U32)(ip1-base); - U32 const matchIndex0 = hashTable[h0]; - U32 const matchIndex1 = hashTable[h1]; - BYTE const* repMatch = ip2 - offset_1; - const BYTE* match0 = base + matchIndex0; - const BYTE* match1 = base + matchIndex1; - U32 offcode; - -#if defined(__aarch64__) - PREFETCH_L1(ip0+256); -#endif - - hashTable[h0] = current0; /* update hash table */ - hashTable[h1] = current1; /* update hash table */ - - assert(ip0 + 1 == ip1); - - if ((offset_1 > 0) & (MEM_read32(repMatch) == MEM_read32(ip2))) { - mLength = (ip2[-1] == repMatch[-1]) ? 1 : 0; - ip0 = ip2 - mLength; - match0 = repMatch - mLength; + /* start each op */ +_start: /* Requires: ip0 */ + + step = stepSize; + nextStep = ip0 + kStepIncr; + + /* calculate positions, ip0 - anchor == 0, so we skip step calc */ + ip1 = ip0 + 1; + ip2 = ip0 + step; + ip3 = ip2 + 1; + + if (ip3 >= ilimit) { + goto _cleanup; + } + + hash0 = ZSTD_hashPtr(ip0, hlog, mls); + hash1 = ZSTD_hashPtr(ip1, hlog, mls); + + idx = hashTable[hash0]; + + do { + /* load repcode match for ip[2]*/ + const U32 rval = MEM_read32(ip2 - rep_offset1); + + /* write back hash table entry */ + current0 = (U32)(ip0 - base); + hashTable[hash0] = current0; + + /* check repcode at ip[2] */ + if ((MEM_read32(ip2) == rval) & (rep_offset1 > 0)) { + ip0 = ip2; + match0 = ip0 - rep_offset1; + mLength = ip0[-1] == match0[-1]; + ip0 -= mLength; + match0 -= mLength; + offcode = REPCODE1_TO_OFFBASE; mLength += 4; - offcode = 0; + + /* First write next hash table entry; we've already calculated it. + * This write is known to be safe because the ip1 is before the + * repcode (ip2). */ + hashTable[hash1] = (U32)(ip1 - base); + goto _match; } - if ((matchIndex0 > prefixStartIndex) && MEM_read32(match0) == val0) { - /* found a regular match */ - goto _offset; + + /* load match for ip[0] */ + if (idx >= prefixStartIndex) { + mval = MEM_read32(base + idx); + } else { + mval = MEM_read32(ip0) ^ 1; /* guaranteed to not match. */ } - if ((matchIndex1 > prefixStartIndex) && MEM_read32(match1) == val1) { - /* found a regular match after one literal */ - ip0 = ip1; - match0 = match1; + + /* check match at ip[0] */ + if (MEM_read32(ip0) == mval) { + /* found a match! */ + + /* First write next hash table entry; we've already calculated it. + * This write is known to be safe because the ip1 == ip0 + 1, so + * we know we will resume searching after ip1 */ + hashTable[hash1] = (U32)(ip1 - base); + goto _offset; } - { size_t const step = ((size_t)(ip0-anchor) >> (kSearchStrength - 1)) + stepSize; - assert(step >= 2); - ip0 += step; - ip1 += step; - continue; + + /* lookup ip[1] */ + idx = hashTable[hash1]; + + /* hash ip[2] */ + hash0 = hash1; + hash1 = ZSTD_hashPtr(ip2, hlog, mls); + + /* advance to next positions */ + ip0 = ip1; + ip1 = ip2; + ip2 = ip3; + + /* write back hash table entry */ + current0 = (U32)(ip0 - base); + hashTable[hash0] = current0; + + /* load match for ip[0] */ + if (idx >= prefixStartIndex) { + mval = MEM_read32(base + idx); + } else { + mval = MEM_read32(ip0) ^ 1; /* guaranteed to not match. */ } -_offset: /* Requires: ip0, match0 */ - /* Compute the offset code */ - offset_2 = offset_1; - offset_1 = (U32)(ip0-match0); - offcode = offset_1 + ZSTD_REP_MOVE; - mLength = 4; - /* Count the backwards match length */ - while (((ip0>anchor) & (match0>prefixStart)) - && (ip0[-1] == match0[-1])) { ip0--; match0--; mLength++; } /* catch up */ -_match: /* Requires: ip0, match0, offcode */ - /* Count the forward length */ - mLength += ZSTD_count(ip0+mLength, match0+mLength, iend); - ZSTD_storeSeq(seqStore, (size_t)(ip0-anchor), anchor, iend, offcode, mLength-MINMATCH); - /* match found */ - ip0 += mLength; - anchor = ip0; + /* check match at ip[0] */ + if (MEM_read32(ip0) == mval) { + /* found a match! */ + + /* first write next hash table entry; we've already calculated it */ + if (step <= 4) { + /* We need to avoid writing an index into the hash table >= the + * position at which we will pick up our searching after we've + * taken this match. + * + * The minimum possible match has length 4, so the earliest ip0 + * can be after we take this match will be the current ip0 + 4. + * ip1 is ip0 + step - 1. If ip1 is >= ip0 + 4, we can't safely + * write this position. + */ + hashTable[hash1] = (U32)(ip1 - base); + } - if (ip0 <= ilimit) { - /* Fill Table */ - assert(base+current0+2 > istart); /* check base overflow */ - hashTable[ZSTD_hashPtr(base+current0+2, hlog, mls)] = current0+2; /* here because current+2 could be > iend-8 */ - hashTable[ZSTD_hashPtr(ip0-2, hlog, mls)] = (U32)(ip0-2-base); + goto _offset; + } - if (offset_2 > 0) { /* offset_2==0 means offset_2 is invalidated */ - while ( (ip0 <= ilimit) && (MEM_read32(ip0) == MEM_read32(ip0 - offset_2)) ) { - /* store sequence */ - size_t const rLength = ZSTD_count(ip0+4, ip0+4-offset_2, iend) + 4; - { U32 const tmpOff = offset_2; offset_2 = offset_1; offset_1 = tmpOff; } /* swap offset_2 <=> offset_1 */ - hashTable[ZSTD_hashPtr(ip0, hlog, mls)] = (U32)(ip0-base); - ip0 += rLength; - ZSTD_storeSeq(seqStore, 0 /*litLen*/, anchor, iend, 0 /*offCode*/, rLength-MINMATCH); - anchor = ip0; - continue; /* faster when present (confirmed on gcc-8) ... (?) */ - } } } - ip1 = ip0 + 1; - } + /* lookup ip[1] */ + idx = hashTable[hash1]; + + /* hash ip[2] */ + hash0 = hash1; + hash1 = ZSTD_hashPtr(ip2, hlog, mls); + + /* advance to next positions */ + ip0 = ip1; + ip1 = ip2; + ip2 = ip0 + step; + ip3 = ip1 + step; + + /* calculate step */ + if (ip2 >= nextStep) { + step++; + PREFETCH_L1(ip1 + 64); + PREFETCH_L1(ip1 + 128); + nextStep += kStepIncr; + } + } while (ip3 < ilimit); + +_cleanup: + /* Note that there are probably still a couple positions we could search. + * However, it seems to be a meaningful performance hit to try to search + * them. So let's not. */ + + /* When the repcodes are outside of the prefix, we set them to zero before the loop. + * When the offsets are still zero, we need to restore them after the block to have a correct + * repcode history. If only one offset was invalid, it is easy. The tricky case is when both + * offsets were invalid. We need to figure out which offset to refill with. + * - If both offsets are zero they are in the same order. + * - If both offsets are non-zero, we won't restore the offsets from `offsetSaved[12]`. + * - If only one is zero, we need to decide which offset to restore. + * - If rep_offset1 is non-zero, then rep_offset2 must be offsetSaved1. + * - It is impossible for rep_offset2 to be non-zero. + * + * So if rep_offset1 started invalid (offsetSaved1 != 0) and became valid (rep_offset1 != 0), then + * set rep[0] = rep_offset1 and rep[1] = offsetSaved1. + */ + offsetSaved2 = ((offsetSaved1 != 0) && (rep_offset1 != 0)) ? offsetSaved1 : offsetSaved2; /* save reps for next block */ - rep[0] = offset_1 ? offset_1 : offsetSaved; - rep[1] = offset_2 ? offset_2 : offsetSaved; + rep[0] = rep_offset1 ? rep_offset1 : offsetSaved1; + rep[1] = rep_offset2 ? rep_offset2 : offsetSaved2; /* Return the last literals size */ return (size_t)(iend - anchor); + +_offset: /* Requires: ip0, idx */ + + /* Compute the offset code. */ + match0 = base + idx; + rep_offset2 = rep_offset1; + rep_offset1 = (U32)(ip0-match0); + offcode = OFFSET_TO_OFFBASE(rep_offset1); + mLength = 4; + + /* Count the backwards match length. */ + while (((ip0>anchor) & (match0>prefixStart)) && (ip0[-1] == match0[-1])) { + ip0--; + match0--; + mLength++; + } + +_match: /* Requires: ip0, match0, offcode */ + + /* Count the forward length. */ + mLength += ZSTD_count(ip0 + mLength, match0 + mLength, iend); + + ZSTD_storeSeq(seqStore, (size_t)(ip0 - anchor), anchor, iend, offcode, mLength); + + ip0 += mLength; + anchor = ip0; + + /* Fill table and check for immediate repcode. */ + if (ip0 <= ilimit) { + /* Fill Table */ + assert(base+current0+2 > istart); /* check base overflow */ + hashTable[ZSTD_hashPtr(base+current0+2, hlog, mls)] = current0+2; /* here because current+2 could be > iend-8 */ + hashTable[ZSTD_hashPtr(ip0-2, hlog, mls)] = (U32)(ip0-2-base); + + if (rep_offset2 > 0) { /* rep_offset2==0 means rep_offset2 is invalidated */ + while ( (ip0 <= ilimit) && (MEM_read32(ip0) == MEM_read32(ip0 - rep_offset2)) ) { + /* store sequence */ + size_t const rLength = ZSTD_count(ip0+4, ip0+4-rep_offset2, iend) + 4; + { U32 const tmpOff = rep_offset2; rep_offset2 = rep_offset1; rep_offset1 = tmpOff; } /* swap rep_offset2 <=> rep_offset1 */ + hashTable[ZSTD_hashPtr(ip0, hlog, mls)] = (U32)(ip0-base); + ip0 += rLength; + ZSTD_storeSeq(seqStore, 0 /*litLen*/, anchor, iend, REPCODE1_TO_OFFBASE, rLength); + anchor = ip0; + continue; /* faster when present (confirmed on gcc-8) ... (?) */ + } } } + + goto _start; } +#define ZSTD_GEN_FAST_FN(dictMode, mls, step) \ + static size_t ZSTD_compressBlock_fast_##dictMode##_##mls##_##step( \ + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], \ + void const* src, size_t srcSize) \ + { \ + return ZSTD_compressBlock_fast_##dictMode##_generic(ms, seqStore, rep, src, srcSize, mls, step); \ + } + +ZSTD_GEN_FAST_FN(noDict, 4, 1) +ZSTD_GEN_FAST_FN(noDict, 5, 1) +ZSTD_GEN_FAST_FN(noDict, 6, 1) +ZSTD_GEN_FAST_FN(noDict, 7, 1) + +ZSTD_GEN_FAST_FN(noDict, 4, 0) +ZSTD_GEN_FAST_FN(noDict, 5, 0) +ZSTD_GEN_FAST_FN(noDict, 6, 0) +ZSTD_GEN_FAST_FN(noDict, 7, 0) size_t ZSTD_compressBlock_fast( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], @@ -189,24 +425,40 @@ size_t ZSTD_compressBlock_fast( { U32 const mls = ms->cParams.minMatch; assert(ms->dictMatchState == NULL); - switch(mls) - { - default: /* includes case 3 */ - case 4 : - return ZSTD_compressBlock_fast_generic(ms, seqStore, rep, src, srcSize, 4); - case 5 : - return ZSTD_compressBlock_fast_generic(ms, seqStore, rep, src, srcSize, 5); - case 6 : - return ZSTD_compressBlock_fast_generic(ms, seqStore, rep, src, srcSize, 6); - case 7 : - return ZSTD_compressBlock_fast_generic(ms, seqStore, rep, src, srcSize, 7); + if (ms->cParams.targetLength > 1) { + switch(mls) + { + default: /* includes case 3 */ + case 4 : + return ZSTD_compressBlock_fast_noDict_4_1(ms, seqStore, rep, src, srcSize); + case 5 : + return ZSTD_compressBlock_fast_noDict_5_1(ms, seqStore, rep, src, srcSize); + case 6 : + return ZSTD_compressBlock_fast_noDict_6_1(ms, seqStore, rep, src, srcSize); + case 7 : + return ZSTD_compressBlock_fast_noDict_7_1(ms, seqStore, rep, src, srcSize); + } + } else { + switch(mls) + { + default: /* includes case 3 */ + case 4 : + return ZSTD_compressBlock_fast_noDict_4_0(ms, seqStore, rep, src, srcSize); + case 5 : + return ZSTD_compressBlock_fast_noDict_5_0(ms, seqStore, rep, src, srcSize); + case 6 : + return ZSTD_compressBlock_fast_noDict_6_0(ms, seqStore, rep, src, srcSize); + case 7 : + return ZSTD_compressBlock_fast_noDict_7_0(ms, seqStore, rep, src, srcSize); + } + } } FORCE_INLINE_TEMPLATE size_t ZSTD_compressBlock_fast_dictMatchState_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize, U32 const mls) + void const* src, size_t srcSize, U32 const mls, U32 const hasStep) { const ZSTD_compressionParameters* const cParams = &ms->cParams; U32* const hashTable = ms->hashTable; @@ -215,14 +467,14 @@ size_t ZSTD_compressBlock_fast_dictMatchState_generic( U32 const stepSize = cParams->targetLength + !(cParams->targetLength); const BYTE* const base = ms->window.base; const BYTE* const istart = (const BYTE*)src; - const BYTE* ip = istart; + const BYTE* ip0 = istart; + const BYTE* ip1 = ip0 + stepSize; /* we assert below that stepSize >= 1 */ const BYTE* anchor = istart; const U32 prefixStartIndex = ms->window.dictLimit; const BYTE* const prefixStart = base + prefixStartIndex; const BYTE* const iend = istart + srcSize; const BYTE* const ilimit = iend - HASH_READ_SIZE; U32 offset_1=rep[0], offset_2=rep[1]; - U32 offsetSaved = 0; const ZSTD_matchState_t* const dms = ms->dictMatchState; const ZSTD_compressionParameters* const dictCParams = &dms->cParams ; @@ -232,125 +484,182 @@ size_t ZSTD_compressBlock_fast_dictMatchState_generic( const BYTE* const dictStart = dictBase + dictStartIndex; const BYTE* const dictEnd = dms->window.nextSrc; const U32 dictIndexDelta = prefixStartIndex - (U32)(dictEnd - dictBase); - const U32 dictAndPrefixLength = (U32)(ip - prefixStart + dictEnd - dictStart); - const U32 dictHLog = dictCParams->hashLog; + const U32 dictAndPrefixLength = (U32)(istart - prefixStart + dictEnd - dictStart); + const U32 dictHBits = dictCParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; /* if a dictionary is still attached, it necessarily means that * it is within window size. So we just check it. */ const U32 maxDistance = 1U << cParams->windowLog; - const U32 endIndex = (U32)((size_t)(ip - base) + srcSize); + const U32 endIndex = (U32)((size_t)(istart - base) + srcSize); assert(endIndex - prefixStartIndex <= maxDistance); (void)maxDistance; (void)endIndex; /* these variables are not used when assert() is disabled */ + (void)hasStep; /* not currently specialized on whether it's accelerated */ + /* ensure there will be no underflow * when translating a dict index into a local index */ assert(prefixStartIndex >= (U32)(dictEnd - dictBase)); + if (ms->prefetchCDictTables) { + size_t const hashTableBytes = (((size_t)1) << dictCParams->hashLog) * sizeof(U32); + PREFETCH_AREA(dictHashTable, hashTableBytes) + } + /* init */ DEBUGLOG(5, "ZSTD_compressBlock_fast_dictMatchState_generic"); - ip += (dictAndPrefixLength == 0); + ip0 += (dictAndPrefixLength == 0); /* dictMatchState repCode checks don't currently handle repCode == 0 * disabling. */ assert(offset_1 <= dictAndPrefixLength); assert(offset_2 <= dictAndPrefixLength); - /* Main Search Loop */ - while (ip < ilimit) { /* < instead of <=, because repcode check at (ip+1) */ + /* Outer search loop */ + assert(stepSize >= 1); + while (ip1 <= ilimit) { /* repcode check at (ip0 + 1) is safe because ip0 < ip1 */ size_t mLength; - size_t const h = ZSTD_hashPtr(ip, hlog, mls); - U32 const curr = (U32)(ip-base); - U32 const matchIndex = hashTable[h]; - const BYTE* match = base + matchIndex; - const U32 repIndex = curr + 1 - offset_1; - const BYTE* repMatch = (repIndex < prefixStartIndex) ? - dictBase + (repIndex - dictIndexDelta) : - base + repIndex; - hashTable[h] = curr; /* update hash table */ - - if ( ((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow : ensure repIndex isn't overlapping dict + prefix */ - && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) { - const BYTE* const repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; - mLength = ZSTD_count_2segments(ip+1+4, repMatch+4, iend, repMatchEnd, prefixStart) + 4; - ip++; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, 0, mLength-MINMATCH); - } else if ( (matchIndex <= prefixStartIndex) ) { - size_t const dictHash = ZSTD_hashPtr(ip, dictHLog, mls); - U32 const dictMatchIndex = dictHashTable[dictHash]; - const BYTE* dictMatch = dictBase + dictMatchIndex; - if (dictMatchIndex <= dictStartIndex || - MEM_read32(dictMatch) != MEM_read32(ip)) { - assert(stepSize >= 1); - ip += ((ip-anchor) >> kSearchStrength) + stepSize; - continue; - } else { - /* found a dict match */ - U32 const offset = (U32)(curr-dictMatchIndex-dictIndexDelta); - mLength = ZSTD_count_2segments(ip+4, dictMatch+4, iend, dictEnd, prefixStart) + 4; - while (((ip>anchor) & (dictMatch>dictStart)) - && (ip[-1] == dictMatch[-1])) { - ip--; dictMatch--; mLength++; + size_t hash0 = ZSTD_hashPtr(ip0, hlog, mls); + + size_t const dictHashAndTag0 = ZSTD_hashPtr(ip0, dictHBits, mls); + U32 dictMatchIndexAndTag = dictHashTable[dictHashAndTag0 >> ZSTD_SHORT_CACHE_TAG_BITS]; + int dictTagsMatch = ZSTD_comparePackedTags(dictMatchIndexAndTag, dictHashAndTag0); + + U32 matchIndex = hashTable[hash0]; + U32 curr = (U32)(ip0 - base); + size_t step = stepSize; + const size_t kStepIncr = 1 << kSearchStrength; + const BYTE* nextStep = ip0 + kStepIncr; + + /* Inner search loop */ + while (1) { + const BYTE* match = base + matchIndex; + const U32 repIndex = curr + 1 - offset_1; + const BYTE* repMatch = (repIndex < prefixStartIndex) ? + dictBase + (repIndex - dictIndexDelta) : + base + repIndex; + const size_t hash1 = ZSTD_hashPtr(ip1, hlog, mls); + size_t const dictHashAndTag1 = ZSTD_hashPtr(ip1, dictHBits, mls); + hashTable[hash0] = curr; /* update hash table */ + + if (((U32) ((prefixStartIndex - 1) - repIndex) >= + 3) /* intentional underflow : ensure repIndex isn't overlapping dict + prefix */ + && (MEM_read32(repMatch) == MEM_read32(ip0 + 1))) { + const BYTE* const repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; + mLength = ZSTD_count_2segments(ip0 + 1 + 4, repMatch + 4, iend, repMatchEnd, prefixStart) + 4; + ip0++; + ZSTD_storeSeq(seqStore, (size_t) (ip0 - anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); + break; + } + + if (dictTagsMatch) { + /* Found a possible dict match */ + const U32 dictMatchIndex = dictMatchIndexAndTag >> ZSTD_SHORT_CACHE_TAG_BITS; + const BYTE* dictMatch = dictBase + dictMatchIndex; + if (dictMatchIndex > dictStartIndex && + MEM_read32(dictMatch) == MEM_read32(ip0)) { + /* To replicate extDict parse behavior, we only use dict matches when the normal matchIndex is invalid */ + if (matchIndex <= prefixStartIndex) { + U32 const offset = (U32) (curr - dictMatchIndex - dictIndexDelta); + mLength = ZSTD_count_2segments(ip0 + 4, dictMatch + 4, iend, dictEnd, prefixStart) + 4; + while (((ip0 > anchor) & (dictMatch > dictStart)) + && (ip0[-1] == dictMatch[-1])) { + ip0--; + dictMatch--; + mLength++; + } /* catch up */ + offset_2 = offset_1; + offset_1 = offset; + ZSTD_storeSeq(seqStore, (size_t) (ip0 - anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); + break; + } + } + } + + if (matchIndex > prefixStartIndex && MEM_read32(match) == MEM_read32(ip0)) { + /* found a regular match */ + U32 const offset = (U32) (ip0 - match); + mLength = ZSTD_count(ip0 + 4, match + 4, iend) + 4; + while (((ip0 > anchor) & (match > prefixStart)) + && (ip0[-1] == match[-1])) { + ip0--; + match--; + mLength++; } /* catch up */ offset_2 = offset_1; offset_1 = offset; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, offset + ZSTD_REP_MOVE, mLength-MINMATCH); + ZSTD_storeSeq(seqStore, (size_t) (ip0 - anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); + break; } - } else if (MEM_read32(match) != MEM_read32(ip)) { - /* it's not a match, and we're not going to check the dictionary */ - assert(stepSize >= 1); - ip += ((ip-anchor) >> kSearchStrength) + stepSize; - continue; - } else { - /* found a regular match */ - U32 const offset = (U32)(ip-match); - mLength = ZSTD_count(ip+4, match+4, iend) + 4; - while (((ip>anchor) & (match>prefixStart)) - && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ - offset_2 = offset_1; - offset_1 = offset; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, offset + ZSTD_REP_MOVE, mLength-MINMATCH); - } + + /* Prepare for next iteration */ + dictMatchIndexAndTag = dictHashTable[dictHashAndTag1 >> ZSTD_SHORT_CACHE_TAG_BITS]; + dictTagsMatch = ZSTD_comparePackedTags(dictMatchIndexAndTag, dictHashAndTag1); + matchIndex = hashTable[hash1]; + + if (ip1 >= nextStep) { + step++; + nextStep += kStepIncr; + } + ip0 = ip1; + ip1 = ip1 + step; + if (ip1 > ilimit) goto _cleanup; + + curr = (U32)(ip0 - base); + hash0 = hash1; + } /* end inner search loop */ /* match found */ - ip += mLength; - anchor = ip; + assert(mLength); + ip0 += mLength; + anchor = ip0; - if (ip <= ilimit) { + if (ip0 <= ilimit) { /* Fill Table */ assert(base+curr+2 > istart); /* check base overflow */ hashTable[ZSTD_hashPtr(base+curr+2, hlog, mls)] = curr+2; /* here because curr+2 could be > iend-8 */ - hashTable[ZSTD_hashPtr(ip-2, hlog, mls)] = (U32)(ip-2-base); + hashTable[ZSTD_hashPtr(ip0-2, hlog, mls)] = (U32)(ip0-2-base); /* check immediate repcode */ - while (ip <= ilimit) { - U32 const current2 = (U32)(ip-base); + while (ip0 <= ilimit) { + U32 const current2 = (U32)(ip0-base); U32 const repIndex2 = current2 - offset_2; const BYTE* repMatch2 = repIndex2 < prefixStartIndex ? dictBase - dictIndexDelta + repIndex2 : base + repIndex2; if ( ((U32)((prefixStartIndex-1) - (U32)repIndex2) >= 3 /* intentional overflow */) - && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { + && (MEM_read32(repMatch2) == MEM_read32(ip0))) { const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; - size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; + size_t const repLength2 = ZSTD_count_2segments(ip0+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; U32 tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; /* swap offset_2 <=> offset_1 */ - ZSTD_storeSeq(seqStore, 0, anchor, iend, 0, repLength2-MINMATCH); - hashTable[ZSTD_hashPtr(ip, hlog, mls)] = current2; - ip += repLength2; - anchor = ip; + ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); + hashTable[ZSTD_hashPtr(ip0, hlog, mls)] = current2; + ip0 += repLength2; + anchor = ip0; continue; } break; } } + + /* Prepare for next iteration */ + assert(ip0 == anchor); + ip1 = ip0 + stepSize; } +_cleanup: /* save reps for next block */ - rep[0] = offset_1 ? offset_1 : offsetSaved; - rep[1] = offset_2 ? offset_2 : offsetSaved; + rep[0] = offset_1; + rep[1] = offset_2; /* Return the last literals size */ return (size_t)(iend - anchor); } + +ZSTD_GEN_FAST_FN(dictMatchState, 4, 0) +ZSTD_GEN_FAST_FN(dictMatchState, 5, 0) +ZSTD_GEN_FAST_FN(dictMatchState, 6, 0) +ZSTD_GEN_FAST_FN(dictMatchState, 7, 0) + size_t ZSTD_compressBlock_fast_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) @@ -361,30 +670,29 @@ size_t ZSTD_compressBlock_fast_dictMatchState( { default: /* includes case 3 */ case 4 : - return ZSTD_compressBlock_fast_dictMatchState_generic(ms, seqStore, rep, src, srcSize, 4); + return ZSTD_compressBlock_fast_dictMatchState_4_0(ms, seqStore, rep, src, srcSize); case 5 : - return ZSTD_compressBlock_fast_dictMatchState_generic(ms, seqStore, rep, src, srcSize, 5); + return ZSTD_compressBlock_fast_dictMatchState_5_0(ms, seqStore, rep, src, srcSize); case 6 : - return ZSTD_compressBlock_fast_dictMatchState_generic(ms, seqStore, rep, src, srcSize, 6); + return ZSTD_compressBlock_fast_dictMatchState_6_0(ms, seqStore, rep, src, srcSize); case 7 : - return ZSTD_compressBlock_fast_dictMatchState_generic(ms, seqStore, rep, src, srcSize, 7); + return ZSTD_compressBlock_fast_dictMatchState_7_0(ms, seqStore, rep, src, srcSize); } } static size_t ZSTD_compressBlock_fast_extDict_generic( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - void const* src, size_t srcSize, U32 const mls) + void const* src, size_t srcSize, U32 const mls, U32 const hasStep) { const ZSTD_compressionParameters* const cParams = &ms->cParams; U32* const hashTable = ms->hashTable; U32 const hlog = cParams->hashLog; /* support stepSize of 0 */ - U32 const stepSize = cParams->targetLength + !(cParams->targetLength); + size_t const stepSize = cParams->targetLength + !(cParams->targetLength) + 1; const BYTE* const base = ms->window.base; const BYTE* const dictBase = ms->window.dictBase; const BYTE* const istart = (const BYTE*)src; - const BYTE* ip = istart; const BYTE* anchor = istart; const U32 endIndex = (U32)((size_t)(istart - base) + srcSize); const U32 lowLimit = ZSTD_getLowestMatchIndex(ms, endIndex, cParams->windowLog); @@ -397,100 +705,256 @@ static size_t ZSTD_compressBlock_fast_extDict_generic( const BYTE* const iend = istart + srcSize; const BYTE* const ilimit = iend - 8; U32 offset_1=rep[0], offset_2=rep[1]; + U32 offsetSaved1 = 0, offsetSaved2 = 0; + + const BYTE* ip0 = istart; + const BYTE* ip1; + const BYTE* ip2; + const BYTE* ip3; + U32 current0; + + + size_t hash0; /* hash for ip0 */ + size_t hash1; /* hash for ip1 */ + U32 idx; /* match idx for ip0 */ + const BYTE* idxBase; /* base pointer for idx */ + + U32 offcode; + const BYTE* match0; + size_t mLength; + const BYTE* matchEnd = 0; /* initialize to avoid warning, assert != 0 later */ + + size_t step; + const BYTE* nextStep; + const size_t kStepIncr = (1 << (kSearchStrength - 1)); + + (void)hasStep; /* not currently specialized on whether it's accelerated */ DEBUGLOG(5, "ZSTD_compressBlock_fast_extDict_generic (offset_1=%u)", offset_1); /* switch to "regular" variant if extDict is invalidated due to maxDistance */ if (prefixStartIndex == dictStartIndex) - return ZSTD_compressBlock_fast_generic(ms, seqStore, rep, src, srcSize, mls); - - /* Search Loop */ - while (ip < ilimit) { /* < instead of <=, because (ip+1) */ - const size_t h = ZSTD_hashPtr(ip, hlog, mls); - const U32 matchIndex = hashTable[h]; - const BYTE* const matchBase = matchIndex < prefixStartIndex ? dictBase : base; - const BYTE* match = matchBase + matchIndex; - const U32 curr = (U32)(ip-base); - const U32 repIndex = curr + 1 - offset_1; - const BYTE* const repBase = repIndex < prefixStartIndex ? dictBase : base; - const BYTE* const repMatch = repBase + repIndex; - hashTable[h] = curr; /* update hash table */ - DEBUGLOG(7, "offset_1 = %u , curr = %u", offset_1, curr); - - if ( ( ((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow */ - & (offset_1 < curr+1 - dictStartIndex) ) /* note: we are searching at curr+1 */ - && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) { - const BYTE* const repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; - size_t const rLength = ZSTD_count_2segments(ip+1 +4, repMatch +4, iend, repMatchEnd, prefixStart) + 4; - ip++; - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, 0, rLength-MINMATCH); - ip += rLength; - anchor = ip; - } else { - if ( (matchIndex < dictStartIndex) || - (MEM_read32(match) != MEM_read32(ip)) ) { - assert(stepSize >= 1); - ip += ((ip-anchor) >> kSearchStrength) + stepSize; - continue; + return ZSTD_compressBlock_fast(ms, seqStore, rep, src, srcSize); + + { U32 const curr = (U32)(ip0 - base); + U32 const maxRep = curr - dictStartIndex; + if (offset_2 >= maxRep) offsetSaved2 = offset_2, offset_2 = 0; + if (offset_1 >= maxRep) offsetSaved1 = offset_1, offset_1 = 0; + } + + /* start each op */ +_start: /* Requires: ip0 */ + + step = stepSize; + nextStep = ip0 + kStepIncr; + + /* calculate positions, ip0 - anchor == 0, so we skip step calc */ + ip1 = ip0 + 1; + ip2 = ip0 + step; + ip3 = ip2 + 1; + + if (ip3 >= ilimit) { + goto _cleanup; + } + + hash0 = ZSTD_hashPtr(ip0, hlog, mls); + hash1 = ZSTD_hashPtr(ip1, hlog, mls); + + idx = hashTable[hash0]; + idxBase = idx < prefixStartIndex ? dictBase : base; + + do { + { /* load repcode match for ip[2] */ + U32 const current2 = (U32)(ip2 - base); + U32 const repIndex = current2 - offset_1; + const BYTE* const repBase = repIndex < prefixStartIndex ? dictBase : base; + U32 rval; + if ( ((U32)(prefixStartIndex - repIndex) >= 4) /* intentional underflow */ + & (offset_1 > 0) ) { + rval = MEM_read32(repBase + repIndex); + } else { + rval = MEM_read32(ip2) ^ 1; /* guaranteed to not match. */ } - { const BYTE* const matchEnd = matchIndex < prefixStartIndex ? dictEnd : iend; - const BYTE* const lowMatchPtr = matchIndex < prefixStartIndex ? dictStart : prefixStart; - U32 const offset = curr - matchIndex; - size_t mLength = ZSTD_count_2segments(ip+4, match+4, iend, matchEnd, prefixStart) + 4; - while (((ip>anchor) & (match>lowMatchPtr)) && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ - offset_2 = offset_1; offset_1 = offset; /* update offset history */ - ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, offset + ZSTD_REP_MOVE, mLength-MINMATCH); - ip += mLength; - anchor = ip; + + /* write back hash table entry */ + current0 = (U32)(ip0 - base); + hashTable[hash0] = current0; + + /* check repcode at ip[2] */ + if (MEM_read32(ip2) == rval) { + ip0 = ip2; + match0 = repBase + repIndex; + matchEnd = repIndex < prefixStartIndex ? dictEnd : iend; + assert((match0 != prefixStart) & (match0 != dictStart)); + mLength = ip0[-1] == match0[-1]; + ip0 -= mLength; + match0 -= mLength; + offcode = REPCODE1_TO_OFFBASE; + mLength += 4; + goto _match; } } - if (ip <= ilimit) { - /* Fill Table */ - hashTable[ZSTD_hashPtr(base+curr+2, hlog, mls)] = curr+2; - hashTable[ZSTD_hashPtr(ip-2, hlog, mls)] = (U32)(ip-2-base); - /* check immediate repcode */ - while (ip <= ilimit) { - U32 const current2 = (U32)(ip-base); - U32 const repIndex2 = current2 - offset_2; - const BYTE* const repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : base + repIndex2; - if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3) & (offset_2 < curr - dictStartIndex)) /* intentional overflow */ - && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { - const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; - size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; - { U32 const tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; } /* swap offset_2 <=> offset_1 */ - ZSTD_storeSeq(seqStore, 0 /*litlen*/, anchor, iend, 0 /*offcode*/, repLength2-MINMATCH); - hashTable[ZSTD_hashPtr(ip, hlog, mls)] = current2; - ip += repLength2; - anchor = ip; - continue; - } - break; - } } } + { /* load match for ip[0] */ + U32 const mval = idx >= dictStartIndex ? + MEM_read32(idxBase + idx) : + MEM_read32(ip0) ^ 1; /* guaranteed not to match */ + + /* check match at ip[0] */ + if (MEM_read32(ip0) == mval) { + /* found a match! */ + goto _offset; + } } + + /* lookup ip[1] */ + idx = hashTable[hash1]; + idxBase = idx < prefixStartIndex ? dictBase : base; + + /* hash ip[2] */ + hash0 = hash1; + hash1 = ZSTD_hashPtr(ip2, hlog, mls); + + /* advance to next positions */ + ip0 = ip1; + ip1 = ip2; + ip2 = ip3; + + /* write back hash table entry */ + current0 = (U32)(ip0 - base); + hashTable[hash0] = current0; + + { /* load match for ip[0] */ + U32 const mval = idx >= dictStartIndex ? + MEM_read32(idxBase + idx) : + MEM_read32(ip0) ^ 1; /* guaranteed not to match */ + + /* check match at ip[0] */ + if (MEM_read32(ip0) == mval) { + /* found a match! */ + goto _offset; + } } + + /* lookup ip[1] */ + idx = hashTable[hash1]; + idxBase = idx < prefixStartIndex ? dictBase : base; + + /* hash ip[2] */ + hash0 = hash1; + hash1 = ZSTD_hashPtr(ip2, hlog, mls); + + /* advance to next positions */ + ip0 = ip1; + ip1 = ip2; + ip2 = ip0 + step; + ip3 = ip1 + step; + + /* calculate step */ + if (ip2 >= nextStep) { + step++; + PREFETCH_L1(ip1 + 64); + PREFETCH_L1(ip1 + 128); + nextStep += kStepIncr; + } + } while (ip3 < ilimit); + +_cleanup: + /* Note that there are probably still a couple positions we could search. + * However, it seems to be a meaningful performance hit to try to search + * them. So let's not. */ + + /* If offset_1 started invalid (offsetSaved1 != 0) and became valid (offset_1 != 0), + * rotate saved offsets. See comment in ZSTD_compressBlock_fast_noDict for more context. */ + offsetSaved2 = ((offsetSaved1 != 0) && (offset_1 != 0)) ? offsetSaved1 : offsetSaved2; /* save reps for next block */ - rep[0] = offset_1; - rep[1] = offset_2; + rep[0] = offset_1 ? offset_1 : offsetSaved1; + rep[1] = offset_2 ? offset_2 : offsetSaved2; /* Return the last literals size */ return (size_t)(iend - anchor); + +_offset: /* Requires: ip0, idx, idxBase */ + + /* Compute the offset code. */ + { U32 const offset = current0 - idx; + const BYTE* const lowMatchPtr = idx < prefixStartIndex ? dictStart : prefixStart; + matchEnd = idx < prefixStartIndex ? dictEnd : iend; + match0 = idxBase + idx; + offset_2 = offset_1; + offset_1 = offset; + offcode = OFFSET_TO_OFFBASE(offset); + mLength = 4; + + /* Count the backwards match length. */ + while (((ip0>anchor) & (match0>lowMatchPtr)) && (ip0[-1] == match0[-1])) { + ip0--; + match0--; + mLength++; + } } + +_match: /* Requires: ip0, match0, offcode, matchEnd */ + + /* Count the forward length. */ + assert(matchEnd != 0); + mLength += ZSTD_count_2segments(ip0 + mLength, match0 + mLength, iend, matchEnd, prefixStart); + + ZSTD_storeSeq(seqStore, (size_t)(ip0 - anchor), anchor, iend, offcode, mLength); + + ip0 += mLength; + anchor = ip0; + + /* write next hash table entry */ + if (ip1 < ip0) { + hashTable[hash1] = (U32)(ip1 - base); + } + + /* Fill table and check for immediate repcode. */ + if (ip0 <= ilimit) { + /* Fill Table */ + assert(base+current0+2 > istart); /* check base overflow */ + hashTable[ZSTD_hashPtr(base+current0+2, hlog, mls)] = current0+2; /* here because current+2 could be > iend-8 */ + hashTable[ZSTD_hashPtr(ip0-2, hlog, mls)] = (U32)(ip0-2-base); + + while (ip0 <= ilimit) { + U32 const repIndex2 = (U32)(ip0-base) - offset_2; + const BYTE* const repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : base + repIndex2; + if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3) & (offset_2 > 0)) /* intentional underflow */ + && (MEM_read32(repMatch2) == MEM_read32(ip0)) ) { + const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; + size_t const repLength2 = ZSTD_count_2segments(ip0+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; + { U32 const tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; } /* swap offset_2 <=> offset_1 */ + ZSTD_storeSeq(seqStore, 0 /*litlen*/, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); + hashTable[ZSTD_hashPtr(ip0, hlog, mls)] = (U32)(ip0-base); + ip0 += repLength2; + anchor = ip0; + continue; + } + break; + } } + + goto _start; } +ZSTD_GEN_FAST_FN(extDict, 4, 0) +ZSTD_GEN_FAST_FN(extDict, 5, 0) +ZSTD_GEN_FAST_FN(extDict, 6, 0) +ZSTD_GEN_FAST_FN(extDict, 7, 0) size_t ZSTD_compressBlock_fast_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) { U32 const mls = ms->cParams.minMatch; + assert(ms->dictMatchState == NULL); switch(mls) { default: /* includes case 3 */ case 4 : - return ZSTD_compressBlock_fast_extDict_generic(ms, seqStore, rep, src, srcSize, 4); + return ZSTD_compressBlock_fast_extDict_4_0(ms, seqStore, rep, src, srcSize); case 5 : - return ZSTD_compressBlock_fast_extDict_generic(ms, seqStore, rep, src, srcSize, 5); + return ZSTD_compressBlock_fast_extDict_5_0(ms, seqStore, rep, src, srcSize); case 6 : - return ZSTD_compressBlock_fast_extDict_generic(ms, seqStore, rep, src, srcSize, 6); + return ZSTD_compressBlock_fast_extDict_6_0(ms, seqStore, rep, src, srcSize); case 7 : - return ZSTD_compressBlock_fast_extDict_generic(ms, seqStore, rep, src, srcSize, 7); + return ZSTD_compressBlock_fast_extDict_7_0(ms, seqStore, rep, src, srcSize); } } diff --git a/Utilities/cmzstd/lib/compress/zstd_fast.h b/Utilities/cmzstd/lib/compress/zstd_fast.h index 0d4a0c1090f..9e4236b4728 100644 --- a/Utilities/cmzstd/lib/compress/zstd_fast.h +++ b/Utilities/cmzstd/lib/compress/zstd_fast.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -19,7 +19,8 @@ extern "C" { #include "zstd_compress_internal.h" void ZSTD_fillHashTable(ZSTD_matchState_t* ms, - void const* end, ZSTD_dictTableLoadMethod_e dtlm); + void const* end, ZSTD_dictTableLoadMethod_e dtlm, + ZSTD_tableFillPurpose_e tfp); size_t ZSTD_compressBlock_fast( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); diff --git a/Utilities/cmzstd/lib/compress/zstd_lazy.c b/Utilities/cmzstd/lib/compress/zstd_lazy.c index 6ce897dc89a..5ba88e8678f 100644 --- a/Utilities/cmzstd/lib/compress/zstd_lazy.c +++ b/Utilities/cmzstd/lib/compress/zstd_lazy.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -10,6 +10,9 @@ #include "zstd_compress_internal.h" #include "zstd_lazy.h" +#include "../common/bits.h" /* ZSTD_countTrailingZeros64 */ + +#define kLazySkippingStep 8 /*-************************************* @@ -61,7 +64,7 @@ ZSTD_updateDUBT(ZSTD_matchState_t* ms, * assumption : curr >= btlow == (curr - btmask) * doesn't fail */ static void -ZSTD_insertDUBT1(ZSTD_matchState_t* ms, +ZSTD_insertDUBT1(const ZSTD_matchState_t* ms, U32 curr, const BYTE* inputEnd, U32 nbCompares, U32 btLow, const ZSTD_dictMode_e dictMode) @@ -93,7 +96,7 @@ ZSTD_insertDUBT1(ZSTD_matchState_t* ms, assert(curr >= btLow); assert(ip < iend); /* condition for ZSTD_count */ - while (nbCompares-- && (matchIndex > windowLow)) { + for (; nbCompares && (matchIndex > windowLow); --nbCompares) { U32* const nextPtr = bt + 2*(matchIndex & btMask); size_t matchLength = MIN(commonLengthSmaller, commonLengthLarger); /* guaranteed minimum nb of common bytes */ assert(matchIndex < curr); @@ -151,7 +154,7 @@ ZSTD_insertDUBT1(ZSTD_matchState_t* ms, static size_t ZSTD_DUBT_findBetterDictMatch ( - ZSTD_matchState_t* ms, + const ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iend, size_t* offsetPtr, size_t bestLength, @@ -185,7 +188,7 @@ ZSTD_DUBT_findBetterDictMatch ( (void)dictMode; assert(dictMode == ZSTD_dictMatchState); - while (nbCompares-- && (dictMatchIndex > dictLowLimit)) { + for (; nbCompares && (dictMatchIndex > dictLowLimit); --nbCompares) { U32* const nextPtr = dictBt + 2*(dictMatchIndex & btMask); size_t matchLength = MIN(commonLengthSmaller, commonLengthLarger); /* guaranteed minimum nb of common bytes */ const BYTE* match = dictBase + dictMatchIndex; @@ -197,8 +200,8 @@ ZSTD_DUBT_findBetterDictMatch ( U32 matchIndex = dictMatchIndex + dictIndexDelta; if ( (4*(int)(matchLength-bestLength)) > (int)(ZSTD_highbit32(curr-matchIndex+1) - ZSTD_highbit32((U32)offsetPtr[0]+1)) ) { DEBUGLOG(9, "ZSTD_DUBT_findBetterDictMatch(%u) : found better match length %u -> %u and offsetCode %u -> %u (dictMatchIndex %u, matchIndex %u)", - curr, (U32)bestLength, (U32)matchLength, (U32)*offsetPtr, ZSTD_REP_MOVE + curr - matchIndex, dictMatchIndex, matchIndex); - bestLength = matchLength, *offsetPtr = ZSTD_REP_MOVE + curr - matchIndex; + curr, (U32)bestLength, (U32)matchLength, (U32)*offsetPtr, OFFSET_TO_OFFBASE(curr - matchIndex), dictMatchIndex, matchIndex); + bestLength = matchLength, *offsetPtr = OFFSET_TO_OFFBASE(curr - matchIndex); } if (ip+matchLength == iend) { /* reached end of input : ip[matchLength] is not valid, no way to know if it's larger or smaller than match */ break; /* drop, to guarantee consistency (miss a little bit of compression) */ @@ -218,7 +221,7 @@ ZSTD_DUBT_findBetterDictMatch ( } if (bestLength >= MINMATCH) { - U32 const mIndex = curr - ((U32)*offsetPtr - ZSTD_REP_MOVE); (void)mIndex; + U32 const mIndex = curr - (U32)OFFBASE_TO_OFFSET(*offsetPtr); (void)mIndex; DEBUGLOG(8, "ZSTD_DUBT_findBetterDictMatch(%u) : found match of length %u and offsetCode %u (pos %u)", curr, (U32)bestLength, (U32)*offsetPtr, mIndex); } @@ -230,7 +233,7 @@ ZSTD_DUBT_findBetterDictMatch ( static size_t ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iend, - size_t* offsetPtr, + size_t* offBasePtr, U32 const mls, const ZSTD_dictMode_e dictMode) { @@ -309,7 +312,7 @@ ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, matchIndex = hashTable[h]; hashTable[h] = curr; /* Update Hash Table */ - while (nbCompares-- && (matchIndex > windowLow)) { + for (; nbCompares && (matchIndex > windowLow); --nbCompares) { U32* const nextPtr = bt + 2*(matchIndex & btMask); size_t matchLength = MIN(commonLengthSmaller, commonLengthLarger); /* guaranteed minimum nb of common bytes */ const BYTE* match; @@ -327,8 +330,8 @@ ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, if (matchLength > bestLength) { if (matchLength > matchEndIdx - matchIndex) matchEndIdx = matchIndex + (U32)matchLength; - if ( (4*(int)(matchLength-bestLength)) > (int)(ZSTD_highbit32(curr-matchIndex+1) - ZSTD_highbit32((U32)offsetPtr[0]+1)) ) - bestLength = matchLength, *offsetPtr = ZSTD_REP_MOVE + curr - matchIndex; + if ( (4*(int)(matchLength-bestLength)) > (int)(ZSTD_highbit32(curr - matchIndex + 1) - ZSTD_highbit32((U32)*offBasePtr)) ) + bestLength = matchLength, *offBasePtr = OFFSET_TO_OFFBASE(curr - matchIndex); if (ip+matchLength == iend) { /* equal : no way to know if inf or sup */ if (dictMode == ZSTD_dictMatchState) { nbCompares = 0; /* in addition to avoiding checking any @@ -357,19 +360,20 @@ ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, *smallerPtr = *largerPtr = 0; + assert(nbCompares <= (1U << ZSTD_SEARCHLOG_MAX)); /* Check we haven't underflowed. */ if (dictMode == ZSTD_dictMatchState && nbCompares) { bestLength = ZSTD_DUBT_findBetterDictMatch( ms, ip, iend, - offsetPtr, bestLength, nbCompares, + offBasePtr, bestLength, nbCompares, mls, dictMode); } assert(matchEndIdx > curr+8); /* ensure nextToUpdate is increased */ ms->nextToUpdate = matchEndIdx - 8; /* skip repetitive patterns */ if (bestLength >= MINMATCH) { - U32 const mIndex = curr - ((U32)*offsetPtr - ZSTD_REP_MOVE); (void)mIndex; + U32 const mIndex = curr - (U32)OFFBASE_TO_OFFSET(*offBasePtr); (void)mIndex; DEBUGLOG(8, "ZSTD_DUBT_findBestMatch(%u) : found match of length %u and offsetCode %u (pos %u)", - curr, (U32)bestLength, (U32)*offsetPtr, mIndex); + curr, (U32)bestLength, (U32)*offBasePtr, mIndex); } return bestLength; } @@ -380,62 +384,14 @@ ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, FORCE_INLINE_TEMPLATE size_t ZSTD_BtFindBestMatch( ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iLimit, - size_t* offsetPtr, + size_t* offBasePtr, const U32 mls /* template */, const ZSTD_dictMode_e dictMode) { DEBUGLOG(7, "ZSTD_BtFindBestMatch"); if (ip < ms->window.base + ms->nextToUpdate) return 0; /* skipped area */ ZSTD_updateDUBT(ms, ip, iLimit, mls); - return ZSTD_DUBT_findBestMatch(ms, ip, iLimit, offsetPtr, mls, dictMode); -} - - -static size_t -ZSTD_BtFindBestMatch_selectMLS ( ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 4, ZSTD_noDict); - case 5 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 5, ZSTD_noDict); - case 7 : - case 6 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 6, ZSTD_noDict); - } -} - - -static size_t ZSTD_BtFindBestMatch_dictMatchState_selectMLS ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMatchState); - case 5 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMatchState); - case 7 : - case 6 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMatchState); - } -} - - -static size_t ZSTD_BtFindBestMatch_extDict_selectMLS ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 4, ZSTD_extDict); - case 5 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 5, ZSTD_extDict); - case 7 : - case 6 : return ZSTD_BtFindBestMatch(ms, ip, iLimit, offsetPtr, 6, ZSTD_extDict); - } + return ZSTD_DUBT_findBestMatch(ms, ip, iLimit, offBasePtr, mls, dictMode); } /*********************************** @@ -450,7 +406,7 @@ void ZSTD_dedicatedDictSearch_lazy_loadDictionary(ZSTD_matchState_t* ms, const B U32* const chainTable = ms->chainTable; U32 const chainSize = 1 << ms->cParams.chainLog; U32 idx = ms->nextToUpdate; - U32 const minChain = chainSize < target ? target - chainSize : idx; + U32 const minChain = chainSize < target - idx ? target - chainSize : idx; U32 const bucketSize = 1 << ZSTD_LAZY_DDSS_BUCKET_LOG; U32 const cacheSize = bucketSize - 1; U32 const chainAttempts = (1 << ms->cParams.searchLog) - cacheSize; @@ -464,7 +420,7 @@ void ZSTD_dedicatedDictSearch_lazy_loadDictionary(ZSTD_matchState_t* ms, const B U32 const hashLog = ms->cParams.hashLog - ZSTD_LAZY_DDSS_BUCKET_LOG; U32* const tmpHashTable = hashTable; U32* const tmpChainTable = hashTable + ((size_t)1 << hashLog); - U32 const tmpChainSize = ((1 << ZSTD_LAZY_DDSS_BUCKET_LOG) - 1) << hashLog; + U32 const tmpChainSize = (U32)((1 << ZSTD_LAZY_DDSS_BUCKET_LOG) - 1) << hashLog; U32 const tmpMinChain = tmpChainSize < target ? target - tmpChainSize : idx; U32 hashIdx; @@ -608,7 +564,7 @@ size_t ZSTD_dedicatedDictSearch_lazy_search(size_t* offsetPtr, size_t ml, U32 nb /* save best solution */ if (currentMl > ml) { ml = currentMl; - *offsetPtr = curr - (matchIndex + ddsIndexDelta) + ZSTD_REP_MOVE; + *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + ddsIndexDelta)); if (ip+currentMl == iLimit) { /* best possible, avoids read overflow on next attempt */ return ml; @@ -645,7 +601,7 @@ size_t ZSTD_dedicatedDictSearch_lazy_search(size_t* offsetPtr, size_t ml, U32 nb /* save best solution */ if (currentMl > ml) { ml = currentMl; - *offsetPtr = curr - (matchIndex + ddsIndexDelta) + ZSTD_REP_MOVE; + *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + ddsIndexDelta)); if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ } } @@ -664,7 +620,7 @@ size_t ZSTD_dedicatedDictSearch_lazy_search(size_t* offsetPtr, size_t ml, U32 nb FORCE_INLINE_TEMPLATE U32 ZSTD_insertAndFindFirstIndex_internal( ZSTD_matchState_t* ms, const ZSTD_compressionParameters* const cParams, - const BYTE* ip, U32 const mls) + const BYTE* ip, U32 const mls, U32 const lazySkipping) { U32* const hashTable = ms->hashTable; const U32 hashLog = cParams->hashLog; @@ -679,6 +635,9 @@ FORCE_INLINE_TEMPLATE U32 ZSTD_insertAndFindFirstIndex_internal( NEXT_IN_CHAIN(idx, chainMask) = hashTable[h]; hashTable[h] = idx; idx++; + /* Stop inserting every position when in the lazy skipping mode. */ + if (lazySkipping) + break; } ms->nextToUpdate = target; @@ -687,12 +646,12 @@ FORCE_INLINE_TEMPLATE U32 ZSTD_insertAndFindFirstIndex_internal( U32 ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, const BYTE* ip) { const ZSTD_compressionParameters* const cParams = &ms->cParams; - return ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, ms->cParams.minMatch); + return ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, ms->cParams.minMatch, /* lazySkipping*/ 0); } /* inlining is important to hardwire a hot branch (template emulation) */ FORCE_INLINE_TEMPLATE -size_t ZSTD_HcFindBestMatch_generic ( +size_t ZSTD_HcFindBestMatch( ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iLimit, size_t* offsetPtr, @@ -731,14 +690,15 @@ size_t ZSTD_HcFindBestMatch_generic ( } /* HC4 match finder */ - matchIndex = ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, mls); + matchIndex = ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, mls, ms->lazySkipping); for ( ; (matchIndex>=lowLimit) & (nbAttempts>0) ; nbAttempts--) { size_t currentMl=0; if ((dictMode != ZSTD_extDict) || matchIndex >= dictLimit) { const BYTE* const match = base + matchIndex; assert(matchIndex >= dictLimit); /* ensures this is true if dictMode != ZSTD_extDict */ - if (match[ml] == ip[ml]) /* potentially better */ + /* read 4B starting from (match + ml + 1 - sizeof(U32)) */ + if (MEM_read32(match + ml - 3) == MEM_read32(ip + ml - 3)) /* potentially better */ currentMl = ZSTD_count(ip, match, iLimit); } else { const BYTE* const match = dictBase + matchIndex; @@ -750,7 +710,7 @@ size_t ZSTD_HcFindBestMatch_generic ( /* save best solution */ if (currentMl > ml) { ml = currentMl; - *offsetPtr = curr - matchIndex + ZSTD_REP_MOVE; + *offsetPtr = OFFSET_TO_OFFBASE(curr - matchIndex); if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ } @@ -758,6 +718,7 @@ size_t ZSTD_HcFindBestMatch_generic ( matchIndex = NEXT_IN_CHAIN(matchIndex, chainMask); } + assert(nbAttempts <= (1U << ZSTD_SEARCHLOG_MAX)); /* Check we haven't underflowed. */ if (dictMode == ZSTD_dedicatedDictSearch) { ml = ZSTD_dedicatedDictSearch_lazy_search(offsetPtr, ml, nbAttempts, dms, ip, iLimit, prefixStart, curr, dictLimit, ddsIdx); @@ -784,7 +745,8 @@ size_t ZSTD_HcFindBestMatch_generic ( /* save best solution */ if (currentMl > ml) { ml = currentMl; - *offsetPtr = curr - (matchIndex + dmsIndexDelta) + ZSTD_REP_MOVE; + assert(curr > matchIndex + dmsIndexDelta); + *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + dmsIndexDelta)); if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ } @@ -797,321 +759,34 @@ size_t ZSTD_HcFindBestMatch_generic ( return ml; } - -FORCE_INLINE_TEMPLATE size_t ZSTD_HcFindBestMatch_selectMLS ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_noDict); - case 5 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_noDict); - case 7 : - case 6 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_noDict); - } -} - - -static size_t ZSTD_HcFindBestMatch_dictMatchState_selectMLS ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_dictMatchState); - case 5 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_dictMatchState); - case 7 : - case 6 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_dictMatchState); - } -} - - -static size_t ZSTD_HcFindBestMatch_dedicatedDictSearch_selectMLS ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_dedicatedDictSearch); - case 5 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_dedicatedDictSearch); - case 7 : - case 6 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_dedicatedDictSearch); - } -} - - -FORCE_INLINE_TEMPLATE size_t ZSTD_HcFindBestMatch_extDict_selectMLS ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, ZSTD_extDict); - case 5 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, ZSTD_extDict); - case 7 : - case 6 : return ZSTD_HcFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, ZSTD_extDict); - } -} - /* ********************************* * (SIMD) Row-based matchfinder ***********************************/ /* Constants for row-based hash */ -#define ZSTD_ROW_HASH_TAG_OFFSET 1 /* byte offset of hashes in the match state's tagTable from the beginning of a row */ -#define ZSTD_ROW_HASH_TAG_BITS 8 /* nb bits to use for the tag */ #define ZSTD_ROW_HASH_TAG_MASK ((1u << ZSTD_ROW_HASH_TAG_BITS) - 1) +#define ZSTD_ROW_HASH_MAX_ENTRIES 64 /* absolute maximum number of entries per row, for all configurations */ #define ZSTD_ROW_HASH_CACHE_MASK (ZSTD_ROW_HASH_CACHE_SIZE - 1) -typedef U32 ZSTD_VecMask; /* Clarifies when we are interacting with a U32 representing a mask of matches */ - -#if !defined(ZSTD_NO_INTRINSICS) && defined(__SSE2__) /* SIMD SSE version */ - -#include -typedef __m128i ZSTD_Vec128; - -/* Returns a 128-bit container with 128-bits from src */ -static ZSTD_Vec128 ZSTD_Vec128_read(const void* const src) { - return _mm_loadu_si128((ZSTD_Vec128 const*)src); -} - -/* Returns a ZSTD_Vec128 with the byte "val" packed 16 times */ -static ZSTD_Vec128 ZSTD_Vec128_set8(BYTE val) { - return _mm_set1_epi8((char)val); -} - -/* Do byte-by-byte comparison result of x and y. Then collapse 128-bit resultant mask - * into a 32-bit mask that is the MSB of each byte. - * */ -static ZSTD_VecMask ZSTD_Vec128_cmpMask8(ZSTD_Vec128 x, ZSTD_Vec128 y) { - return (ZSTD_VecMask)_mm_movemask_epi8(_mm_cmpeq_epi8(x, y)); -} - -typedef struct { - __m128i fst; - __m128i snd; -} ZSTD_Vec256; - -static ZSTD_Vec256 ZSTD_Vec256_read(const void* const ptr) { - ZSTD_Vec256 v; - v.fst = ZSTD_Vec128_read(ptr); - v.snd = ZSTD_Vec128_read((ZSTD_Vec128 const*)ptr + 1); - return v; -} - -static ZSTD_Vec256 ZSTD_Vec256_set8(BYTE val) { - ZSTD_Vec256 v; - v.fst = ZSTD_Vec128_set8(val); - v.snd = ZSTD_Vec128_set8(val); - return v; -} - -static ZSTD_VecMask ZSTD_Vec256_cmpMask8(ZSTD_Vec256 x, ZSTD_Vec256 y) { - ZSTD_VecMask fstMask; - ZSTD_VecMask sndMask; - fstMask = ZSTD_Vec128_cmpMask8(x.fst, y.fst); - sndMask = ZSTD_Vec128_cmpMask8(x.snd, y.snd); - return fstMask | (sndMask << 16); -} - -#elif !defined(ZSTD_NO_INTRINSICS) && defined(__ARM_NEON) /* SIMD ARM NEON Version */ - -#include -typedef uint8x16_t ZSTD_Vec128; - -static ZSTD_Vec128 ZSTD_Vec128_read(const void* const src) { - return vld1q_u8((const BYTE* const)src); -} - -static ZSTD_Vec128 ZSTD_Vec128_set8(BYTE val) { - return vdupq_n_u8(val); -} - -/* Mimics '_mm_movemask_epi8()' from SSE */ -static U32 ZSTD_vmovmaskq_u8(ZSTD_Vec128 val) { - /* Shift out everything but the MSB bits in each byte */ - uint16x8_t highBits = vreinterpretq_u16_u8(vshrq_n_u8(val, 7)); - /* Merge the even lanes together with vsra (right shift and add) */ - uint32x4_t paired16 = vreinterpretq_u32_u16(vsraq_n_u16(highBits, highBits, 7)); - uint64x2_t paired32 = vreinterpretq_u64_u32(vsraq_n_u32(paired16, paired16, 14)); - uint8x16_t paired64 = vreinterpretq_u8_u64(vsraq_n_u64(paired32, paired32, 28)); - /* Extract the low 8 bits from each lane, merge */ - return vgetq_lane_u8(paired64, 0) | ((U32)vgetq_lane_u8(paired64, 8) << 8); -} - -static ZSTD_VecMask ZSTD_Vec128_cmpMask8(ZSTD_Vec128 x, ZSTD_Vec128 y) { - return (ZSTD_VecMask)ZSTD_vmovmaskq_u8(vceqq_u8(x, y)); -} - -typedef struct { - uint8x16_t fst; - uint8x16_t snd; -} ZSTD_Vec256; - -static ZSTD_Vec256 ZSTD_Vec256_read(const void* const ptr) { - ZSTD_Vec256 v; - v.fst = ZSTD_Vec128_read(ptr); - v.snd = ZSTD_Vec128_read((ZSTD_Vec128 const*)ptr + 1); - return v; -} - -static ZSTD_Vec256 ZSTD_Vec256_set8(BYTE val) { - ZSTD_Vec256 v; - v.fst = ZSTD_Vec128_set8(val); - v.snd = ZSTD_Vec128_set8(val); - return v; -} - -static ZSTD_VecMask ZSTD_Vec256_cmpMask8(ZSTD_Vec256 x, ZSTD_Vec256 y) { - ZSTD_VecMask fstMask; - ZSTD_VecMask sndMask; - fstMask = ZSTD_Vec128_cmpMask8(x.fst, y.fst); - sndMask = ZSTD_Vec128_cmpMask8(x.snd, y.snd); - return fstMask | (sndMask << 16); -} - -#else /* Scalar fallback version */ - -#define VEC128_NB_SIZE_T (16 / sizeof(size_t)) -typedef struct { - size_t vec[VEC128_NB_SIZE_T]; -} ZSTD_Vec128; - -static ZSTD_Vec128 ZSTD_Vec128_read(const void* const src) { - ZSTD_Vec128 ret; - ZSTD_memcpy(ret.vec, src, VEC128_NB_SIZE_T*sizeof(size_t)); - return ret; -} - -static ZSTD_Vec128 ZSTD_Vec128_set8(BYTE val) { - ZSTD_Vec128 ret = { {0} }; - int startBit = sizeof(size_t) * 8 - 8; - for (;startBit >= 0; startBit -= 8) { - unsigned j = 0; - for (;j < VEC128_NB_SIZE_T; ++j) { - ret.vec[j] |= ((size_t)val << startBit); - } - } - return ret; -} - -/* Compare x to y, byte by byte, generating a "matches" bitfield */ -static ZSTD_VecMask ZSTD_Vec128_cmpMask8(ZSTD_Vec128 x, ZSTD_Vec128 y) { - ZSTD_VecMask res = 0; - unsigned i = 0; - unsigned l = 0; - for (; i < VEC128_NB_SIZE_T; ++i) { - const size_t cmp1 = x.vec[i]; - const size_t cmp2 = y.vec[i]; - unsigned j = 0; - for (; j < sizeof(size_t); ++j, ++l) { - if (((cmp1 >> j*8) & 0xFF) == ((cmp2 >> j*8) & 0xFF)) { - res |= ((U32)1 << (j+i*sizeof(size_t))); - } - } - } - return res; -} - -#define VEC256_NB_SIZE_T 2*VEC128_NB_SIZE_T -typedef struct { - size_t vec[VEC256_NB_SIZE_T]; -} ZSTD_Vec256; - -static ZSTD_Vec256 ZSTD_Vec256_read(const void* const src) { - ZSTD_Vec256 ret; - ZSTD_memcpy(ret.vec, src, VEC256_NB_SIZE_T*sizeof(size_t)); - return ret; -} - -static ZSTD_Vec256 ZSTD_Vec256_set8(BYTE val) { - ZSTD_Vec256 ret = { {0} }; - int startBit = sizeof(size_t) * 8 - 8; - for (;startBit >= 0; startBit -= 8) { - unsigned j = 0; - for (;j < VEC256_NB_SIZE_T; ++j) { - ret.vec[j] |= ((size_t)val << startBit); - } - } - return ret; -} - -/* Compare x to y, byte by byte, generating a "matches" bitfield */ -static ZSTD_VecMask ZSTD_Vec256_cmpMask8(ZSTD_Vec256 x, ZSTD_Vec256 y) { - ZSTD_VecMask res = 0; - unsigned i = 0; - unsigned l = 0; - for (; i < VEC256_NB_SIZE_T; ++i) { - const size_t cmp1 = x.vec[i]; - const size_t cmp2 = y.vec[i]; - unsigned j = 0; - for (; j < sizeof(size_t); ++j, ++l) { - if (((cmp1 >> j*8) & 0xFF) == ((cmp2 >> j*8) & 0xFF)) { - res |= ((U32)1 << (j+i*sizeof(size_t))); - } - } - } - return res; -} - -#endif /* !defined(ZSTD_NO_INTRINSICS) && defined(__SSE2__) */ +typedef U64 ZSTD_VecMask; /* Clarifies when we are interacting with a U64 representing a mask of matches */ /* ZSTD_VecMask_next(): * Starting from the LSB, returns the idx of the next non-zero bit. * Basically counting the nb of trailing zeroes. */ -static U32 ZSTD_VecMask_next(ZSTD_VecMask val) { -# if defined(_MSC_VER) /* Visual */ - unsigned long r=0; - return _BitScanForward(&r, val) ? (U32)r : 0; -# elif defined(__GNUC__) && (__GNUC__ >= 3) - return (U32)__builtin_ctz(val); -# else - /* Software ctz version: http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup */ - static const U32 multiplyDeBruijnBitPosition[32] = - { - 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, - 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 - }; - return multiplyDeBruijnBitPosition[((U32)((val & -(int)val) * 0x077CB531U)) >> 27]; - -# endif -} - -/* ZSTD_VecMask_rotateRight(): - * Rotates a bitfield to the right by "rotation" bits. - * If the rotation is greater than totalBits, the returned mask is 0. - */ -FORCE_INLINE_TEMPLATE ZSTD_VecMask -ZSTD_VecMask_rotateRight(ZSTD_VecMask mask, U32 const rotation, U32 const totalBits) { - if (rotation == 0) - return mask; - switch (totalBits) { - default: - assert(0); - case 16: - return (mask >> rotation) | (U16)(mask << (16 - rotation)); - case 32: - return (mask >> rotation) | (U32)(mask << (32 - rotation)); - } +MEM_STATIC U32 ZSTD_VecMask_next(ZSTD_VecMask val) { + return ZSTD_countTrailingZeros64(val); } /* ZSTD_row_nextIndex(): * Returns the next index to insert at within a tagTable row, and updates the "head" - * value to reflect the update. Essentially cycles backwards from [0, {entries per row}) + * value to reflect the update. Essentially cycles backwards from [1, {entries per row}) */ FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextIndex(BYTE* const tagRow, U32 const rowMask) { - U32 const next = (*tagRow - 1) & rowMask; - *tagRow = (BYTE)next; - return next; + U32 next = (*tagRow-1) & rowMask; + next += (next == 0) ? rowMask : 0; /* skip first position */ + *tagRow = (BYTE)next; + return next; } /* ZSTD_isAligned(): @@ -1125,33 +800,37 @@ MEM_STATIC int ZSTD_isAligned(void const* ptr, size_t align) { /* ZSTD_row_prefetch(): * Performs prefetching for the hashTable and tagTable at a given row. */ -FORCE_INLINE_TEMPLATE void ZSTD_row_prefetch(U32 const* hashTable, U16 const* tagTable, U32 const relRow, U32 const rowLog) { +FORCE_INLINE_TEMPLATE void ZSTD_row_prefetch(U32 const* hashTable, BYTE const* tagTable, U32 const relRow, U32 const rowLog) { PREFETCH_L1(hashTable + relRow); - if (rowLog == 5) { + if (rowLog >= 5) { PREFETCH_L1(hashTable + relRow + 16); + /* Note: prefetching more of the hash table does not appear to be beneficial for 128-entry rows */ } PREFETCH_L1(tagTable + relRow); - assert(rowLog == 4 || rowLog == 5); + if (rowLog == 6) { + PREFETCH_L1(tagTable + relRow + 32); + } + assert(rowLog == 4 || rowLog == 5 || rowLog == 6); assert(ZSTD_isAligned(hashTable + relRow, 64)); /* prefetched hash row always 64-byte aligned */ - assert(ZSTD_isAligned(tagTable + relRow, (size_t)1 << rowLog)); /* prefetched tagRow sits on a multiple of 32 or 64 bytes */ + assert(ZSTD_isAligned(tagTable + relRow, (size_t)1 << rowLog)); /* prefetched tagRow sits on correct multiple of bytes (32,64,128) */ } /* ZSTD_row_fillHashCache(): * Fill up the hash cache starting at idx, prefetching up to ZSTD_ROW_HASH_CACHE_SIZE entries, * but not beyond iLimit. */ -static void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const BYTE* base, +FORCE_INLINE_TEMPLATE void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const BYTE* base, U32 const rowLog, U32 const mls, U32 idx, const BYTE* const iLimit) { U32 const* const hashTable = ms->hashTable; - U16 const* const tagTable = ms->tagTable; + BYTE const* const tagTable = ms->tagTable; U32 const hashLog = ms->rowHashLog; U32 const maxElemsToPrefetch = (base + idx) > iLimit ? 0 : (U32)(iLimit - (base + idx) + 1); U32 const lim = idx + MIN(ZSTD_ROW_HASH_CACHE_SIZE, maxElemsToPrefetch); for (; idx < lim; ++idx) { - U32 const hash = (U32)ZSTD_hashPtr(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls); + U32 const hash = (U32)ZSTD_hashPtrSalted(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt); U32 const row = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; ZSTD_row_prefetch(hashTable, tagTable, row, rowLog); ms->hashCache[idx & ZSTD_ROW_HASH_CACHE_MASK] = hash; @@ -1167,11 +846,12 @@ static void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const BYTE* base, * base + idx + ZSTD_ROW_HASH_CACHE_SIZE. Also prefetches the appropriate rows from hashTable and tagTable. */ FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTable, - U16 const* tagTable, BYTE const* base, + BYTE const* tagTable, BYTE const* base, U32 idx, U32 const hashLog, - U32 const rowLog, U32 const mls) + U32 const rowLog, U32 const mls, + U64 const hashSalt) { - U32 const newHash = (U32)ZSTD_hashPtr(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls); + U32 const newHash = (U32)ZSTD_hashPtrSalted(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, hashSalt); U32 const row = (newHash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; ZSTD_row_prefetch(hashTable, tagTable, row, rowLog); { U32 const hash = cache[idx & ZSTD_ROW_HASH_CACHE_MASK]; @@ -1180,35 +860,64 @@ FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTab } } -/* ZSTD_row_update_internal(): - * Inserts the byte at ip into the appropriate position in the hash table. - * Determines the relative row, and the position within the {16, 32} entry row to insert at. +/* ZSTD_row_update_internalImpl(): + * Updates the hash table with positions starting from updateStartIdx until updateEndIdx. */ -FORCE_INLINE_TEMPLATE void ZSTD_row_update_internal(ZSTD_matchState_t* ms, const BYTE* ip, - U32 const mls, U32 const rowLog, - U32 const rowMask, U32 const useCache) +FORCE_INLINE_TEMPLATE void ZSTD_row_update_internalImpl(ZSTD_matchState_t* ms, + U32 updateStartIdx, U32 const updateEndIdx, + U32 const mls, U32 const rowLog, + U32 const rowMask, U32 const useCache) { U32* const hashTable = ms->hashTable; - U16* const tagTable = ms->tagTable; + BYTE* const tagTable = ms->tagTable; U32 const hashLog = ms->rowHashLog; const BYTE* const base = ms->window.base; - const U32 target = (U32)(ip - base); - U32 idx = ms->nextToUpdate; - DEBUGLOG(6, "ZSTD_row_update_internal(): nextToUpdate=%u, current=%u", idx, target); - for (; idx < target; ++idx) { - U32 const hash = useCache ? ZSTD_row_nextCachedHash(ms->hashCache, hashTable, tagTable, base, idx, hashLog, rowLog, mls) - : (U32)ZSTD_hashPtr(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls); + DEBUGLOG(6, "ZSTD_row_update_internalImpl(): updateStartIdx=%u, updateEndIdx=%u", updateStartIdx, updateEndIdx); + for (; updateStartIdx < updateEndIdx; ++updateStartIdx) { + U32 const hash = useCache ? ZSTD_row_nextCachedHash(ms->hashCache, hashTable, tagTable, base, updateStartIdx, hashLog, rowLog, mls, ms->hashSalt) + : (U32)ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt); U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; U32* const row = hashTable + relRow; - BYTE* tagRow = (BYTE*)(tagTable + relRow); /* Though tagTable is laid out as a table of U16, each tag is only 1 byte. - Explicit cast allows us to get exact desired position within each row */ + BYTE* tagRow = tagTable + relRow; U32 const pos = ZSTD_row_nextIndex(tagRow, rowMask); - assert(hash == ZSTD_hashPtr(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls)); - ((BYTE*)tagRow)[pos + ZSTD_ROW_HASH_TAG_OFFSET] = hash & ZSTD_ROW_HASH_TAG_MASK; - row[pos] = idx; + assert(hash == ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt)); + tagRow[pos] = hash & ZSTD_ROW_HASH_TAG_MASK; + row[pos] = updateStartIdx; + } +} + +/* ZSTD_row_update_internal(): + * Inserts the byte at ip into the appropriate position in the hash table, and updates ms->nextToUpdate. + * Skips sections of long matches as is necessary. + */ +FORCE_INLINE_TEMPLATE void ZSTD_row_update_internal(ZSTD_matchState_t* ms, const BYTE* ip, + U32 const mls, U32 const rowLog, + U32 const rowMask, U32 const useCache) +{ + U32 idx = ms->nextToUpdate; + const BYTE* const base = ms->window.base; + const U32 target = (U32)(ip - base); + const U32 kSkipThreshold = 384; + const U32 kMaxMatchStartPositionsToUpdate = 96; + const U32 kMaxMatchEndPositionsToUpdate = 32; + + if (useCache) { + /* Only skip positions when using hash cache, i.e. + * if we are loading a dict, don't skip anything. + * If we decide to skip, then we only update a set number + * of positions at the beginning and end of the match. + */ + if (UNLIKELY(target - idx > kSkipThreshold)) { + U32 const bound = idx + kMaxMatchStartPositionsToUpdate; + ZSTD_row_update_internalImpl(ms, idx, bound, mls, rowLog, rowMask, useCache); + idx = target - kMaxMatchEndPositionsToUpdate; + ZSTD_row_fillHashCache(ms, base, rowLog, mls, idx, ip+1); + } } + assert(target >= idx); + ZSTD_row_update_internalImpl(ms, idx, target, mls, rowLog, rowMask, useCache); ms->nextToUpdate = target; } @@ -1217,34 +926,178 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_update_internal(ZSTD_matchState_t* ms, const * processing. */ void ZSTD_row_update(ZSTD_matchState_t* const ms, const BYTE* ip) { - const U32 rowLog = ms->cParams.searchLog < 5 ? 4 : 5; + const U32 rowLog = BOUNDED(4, ms->cParams.searchLog, 6); const U32 rowMask = (1u << rowLog) - 1; const U32 mls = MIN(ms->cParams.minMatch, 6 /* mls caps out at 6 */); DEBUGLOG(5, "ZSTD_row_update(), rowLog=%u", rowLog); - ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 0 /* dont use cache */); + ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 0 /* don't use cache */); } -/* Returns a ZSTD_VecMask (U32) that has the nth bit set to 1 if the newly-computed "tag" matches - * the hash at the nth position in a row of the tagTable. +/* Returns the mask width of bits group of which will be set to 1. Given not all + * architectures have easy movemask instruction, this helps to iterate over + * groups of bits easier and faster. */ -FORCE_INLINE_TEMPLATE -ZSTD_VecMask ZSTD_row_getMatchMask(const BYTE* const tagRow, const BYTE tag, const U32 head, const U32 rowEntries) { - ZSTD_VecMask matches = 0; +FORCE_INLINE_TEMPLATE U32 +ZSTD_row_matchMaskGroupWidth(const U32 rowEntries) +{ + assert((rowEntries == 16) || (rowEntries == 32) || rowEntries == 64); + assert(rowEntries <= ZSTD_ROW_HASH_MAX_ENTRIES); + (void)rowEntries; +#if defined(ZSTD_ARCH_ARM_NEON) + /* NEON path only works for little endian */ + if (!MEM_isLittleEndian()) { + return 1; + } if (rowEntries == 16) { - ZSTD_Vec128 hashes = ZSTD_Vec128_read(tagRow + ZSTD_ROW_HASH_TAG_OFFSET); - ZSTD_Vec128 expandedTags = ZSTD_Vec128_set8(tag); - matches = ZSTD_Vec128_cmpMask8(hashes, expandedTags); + return 4; + } + if (rowEntries == 32) { + return 2; + } + if (rowEntries == 64) { + return 1; + } +#endif + return 1; +} + +#if defined(ZSTD_ARCH_X86_SSE2) +FORCE_INLINE_TEMPLATE ZSTD_VecMask +ZSTD_row_getSSEMask(int nbChunks, const BYTE* const src, const BYTE tag, const U32 head) +{ + const __m128i comparisonMask = _mm_set1_epi8((char)tag); + int matches[4] = {0}; + int i; + assert(nbChunks == 1 || nbChunks == 2 || nbChunks == 4); + for (i=0; i> chunkSize; + do { + size_t chunk = MEM_readST(&src[i]); + chunk ^= splatChar; + chunk = (((chunk | x80) - x01) | chunk) & x80; + matches <<= chunkSize; + matches |= (chunk * extractMagic) >> shiftAmount; + i -= chunkSize; + } while (i >= 0); + } else { /* big endian: reverse bits during extraction */ + const size_t msb = xFF ^ (xFF >> 1); + const size_t extractMagic = (msb / 0x1FF) | msb; + do { + size_t chunk = MEM_readST(&src[i]); + chunk ^= splatChar; + chunk = (((chunk | x80) - x01) | chunk) & x80; + matches <<= chunkSize; + matches |= ((chunk >> 7) * extractMagic) >> shiftAmount; + i -= chunkSize; + } while (i >= 0); + } + matches = ~matches; + if (rowEntries == 16) { + return ZSTD_rotateRight_U16((U16)matches, headGrouped); + } else if (rowEntries == 32) { + return ZSTD_rotateRight_U32((U32)matches, headGrouped); + } else { + return ZSTD_rotateRight_U64((U64)matches, headGrouped); + } + } +#endif } /* The high-level approach of the SIMD row based match finder is as follows: @@ -1263,7 +1116,7 @@ ZSTD_VecMask ZSTD_row_getMatchMask(const BYTE* const tagRow, const BYTE tag, con * - Pick the longest match. */ FORCE_INLINE_TEMPLATE -size_t ZSTD_RowFindBestMatch_generic ( +size_t ZSTD_RowFindBestMatch( ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iLimit, size_t* offsetPtr, @@ -1271,7 +1124,7 @@ size_t ZSTD_RowFindBestMatch_generic ( const U32 rowLog) { U32* const hashTable = ms->hashTable; - U16* const tagTable = ms->tagTable; + BYTE* const tagTable = ms->tagTable; U32* const hashCache = ms->hashCache; const U32 hashLog = ms->rowHashLog; const ZSTD_compressionParameters* const cParams = &ms->cParams; @@ -1289,16 +1142,21 @@ size_t ZSTD_RowFindBestMatch_generic ( const U32 rowEntries = (1U << rowLog); const U32 rowMask = rowEntries - 1; const U32 cappedSearchLog = MIN(cParams->searchLog, rowLog); /* nb of searches is capped at nb entries per row */ + const U32 groupWidth = ZSTD_row_matchMaskGroupWidth(rowEntries); + const U64 hashSalt = ms->hashSalt; U32 nbAttempts = 1U << cappedSearchLog; size_t ml=4-1; + U32 hash; /* DMS/DDS variables that may be referenced laster */ const ZSTD_matchState_t* const dms = ms->dictMatchState; - size_t ddsIdx; - U32 ddsExtraAttempts; /* cctx hash tables are limited in searches, but allow extra searches into DDS */ - U32 dmsTag; - U32* dmsRow; - BYTE* dmsTagRow; + + /* Initialize the following variables to satisfy static analyzer */ + size_t ddsIdx = 0; + U32 ddsExtraAttempts = 0; /* cctx hash tables are limited in searches, but allow extra searches into DDS */ + U32 dmsTag = 0; + U32* dmsRow = NULL; + BYTE* dmsTagRow = NULL; if (dictMode == ZSTD_dedicatedDictSearch) { const U32 ddsHashLog = dms->cParams.hashLog - ZSTD_LAZY_DDSS_BUCKET_LOG; @@ -1312,7 +1170,7 @@ size_t ZSTD_RowFindBestMatch_generic ( if (dictMode == ZSTD_dictMatchState) { /* Prefetch DMS rows */ U32* const dmsHashTable = dms->hashTable; - U16* const dmsTagTable = dms->tagTable; + BYTE* const dmsTagTable = dms->tagTable; U32 const dmsHash = (U32)ZSTD_hashPtr(ip, dms->rowHashLog + ZSTD_ROW_HASH_TAG_BITS, mls); U32 const dmsRelRow = (dmsHash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; dmsTag = dmsHash & ZSTD_ROW_HASH_TAG_MASK; @@ -1322,23 +1180,34 @@ size_t ZSTD_RowFindBestMatch_generic ( } /* Update the hashTable and tagTable up to (but not including) ip */ - ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 1 /* useCache */); + if (!ms->lazySkipping) { + ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 1 /* useCache */); + hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls, hashSalt); + } else { + /* Stop inserting every position when in the lazy skipping mode. + * The hash cache is also not kept up to date in this mode. + */ + hash = (U32)ZSTD_hashPtrSalted(ip, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, hashSalt); + ms->nextToUpdate = curr; + } + ms->hashSaltEntropy += hash; /* collect salt entropy */ + { /* Get the hash for ip, compute the appropriate row */ - U32 const hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls); U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; U32 const tag = hash & ZSTD_ROW_HASH_TAG_MASK; U32* const row = hashTable + relRow; BYTE* tagRow = (BYTE*)(tagTable + relRow); - U32 const head = *tagRow & rowMask; - U32 matchBuffer[32 /* maximum nb entries per row */]; + U32 const headGrouped = (*tagRow & rowMask) * groupWidth; + U32 matchBuffer[ZSTD_ROW_HASH_MAX_ENTRIES]; size_t numMatches = 0; size_t currMatch = 0; - ZSTD_VecMask matches = ZSTD_row_getMatchMask(tagRow, (BYTE)tag, head, rowEntries); + ZSTD_VecMask matches = ZSTD_row_getMatchMask(tagRow, (BYTE)tag, headGrouped, rowEntries); /* Cycle through the matches and prefetch */ - for (; (matches > 0) && (nbAttempts > 0); --nbAttempts, matches &= (matches - 1)) { - U32 const matchPos = (head + ZSTD_VecMask_next(matches)) & rowMask; + for (; (matches > 0) && (nbAttempts > 0); matches &= (matches - 1)) { + U32 const matchPos = ((headGrouped + ZSTD_VecMask_next(matches)) / groupWidth) & rowMask; U32 const matchIndex = row[matchPos]; + if(matchPos == 0) continue; assert(numMatches < rowEntries); if (matchIndex < lowLimit) break; @@ -1348,13 +1217,14 @@ size_t ZSTD_RowFindBestMatch_generic ( PREFETCH_L1(dictBase + matchIndex); } matchBuffer[numMatches++] = matchIndex; + --nbAttempts; } /* Speed opt: insert current byte into hashtable too. This allows us to avoid one iteration of the loop in ZSTD_row_update_internal() at the next search. */ { U32 const pos = ZSTD_row_nextIndex(tagRow, rowMask); - tagRow[pos + ZSTD_ROW_HASH_TAG_OFFSET] = (BYTE)tag; + tagRow[pos] = (BYTE)tag; row[pos] = ms->nextToUpdate++; } @@ -1368,7 +1238,8 @@ size_t ZSTD_RowFindBestMatch_generic ( if ((dictMode != ZSTD_extDict) || matchIndex >= dictLimit) { const BYTE* const match = base + matchIndex; assert(matchIndex >= dictLimit); /* ensures this is true if dictMode != ZSTD_extDict */ - if (match[ml] == ip[ml]) /* potentially better */ + /* read 4B starting from (match + ml + 1 - sizeof(U32)) */ + if (MEM_read32(match + ml - 3) == MEM_read32(ip + ml - 3)) /* potentially better */ currentMl = ZSTD_count(ip, match, iLimit); } else { const BYTE* const match = dictBase + matchIndex; @@ -1380,12 +1251,13 @@ size_t ZSTD_RowFindBestMatch_generic ( /* Save best solution */ if (currentMl > ml) { ml = currentMl; - *offsetPtr = curr - matchIndex + ZSTD_REP_MOVE; + *offsetPtr = OFFSET_TO_OFFBASE(curr - matchIndex); if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ } } } + assert(nbAttempts <= (1U << ZSTD_SEARCHLOG_MAX)); /* Check we haven't underflowed. */ if (dictMode == ZSTD_dedicatedDictSearch) { ml = ZSTD_dedicatedDictSearch_lazy_search(offsetPtr, ml, nbAttempts + ddsExtraAttempts, dms, ip, iLimit, prefixStart, curr, dictLimit, ddsIdx); @@ -1397,19 +1269,21 @@ size_t ZSTD_RowFindBestMatch_generic ( const U32 dmsSize = (U32)(dmsEnd - dmsBase); const U32 dmsIndexDelta = dictLimit - dmsSize; - { U32 const head = *dmsTagRow & rowMask; - U32 matchBuffer[32 /* maximum nb row entries */]; + { U32 const headGrouped = (*dmsTagRow & rowMask) * groupWidth; + U32 matchBuffer[ZSTD_ROW_HASH_MAX_ENTRIES]; size_t numMatches = 0; size_t currMatch = 0; - ZSTD_VecMask matches = ZSTD_row_getMatchMask(dmsTagRow, (BYTE)dmsTag, head, rowEntries); + ZSTD_VecMask matches = ZSTD_row_getMatchMask(dmsTagRow, (BYTE)dmsTag, headGrouped, rowEntries); - for (; (matches > 0) && (nbAttempts > 0); --nbAttempts, matches &= (matches - 1)) { - U32 const matchPos = (head + ZSTD_VecMask_next(matches)) & rowMask; + for (; (matches > 0) && (nbAttempts > 0); matches &= (matches - 1)) { + U32 const matchPos = ((headGrouped + ZSTD_VecMask_next(matches)) / groupWidth) & rowMask; U32 const matchIndex = dmsRow[matchPos]; + if(matchPos == 0) continue; if (matchIndex < dmsLowestIndex) break; PREFETCH_L1(dmsBase + matchIndex); matchBuffer[numMatches++] = matchIndex; + --nbAttempts; } /* Return the longest match */ @@ -1427,7 +1301,8 @@ size_t ZSTD_RowFindBestMatch_generic ( if (currentMl > ml) { ml = currentMl; - *offsetPtr = curr - (matchIndex + dmsIndexDelta) + ZSTD_REP_MOVE; + assert(curr > matchIndex + dmsIndexDelta); + *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + dmsIndexDelta)); if (ip+currentMl == iLimit) break; } } @@ -1436,83 +1311,183 @@ size_t ZSTD_RowFindBestMatch_generic ( return ml; } -/* Inlining is important to hardwire a hot branch (template emulation) */ -FORCE_INLINE_TEMPLATE size_t ZSTD_RowFindBestMatch_selectMLS ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - const ZSTD_dictMode_e dictMode, size_t* offsetPtr, const U32 rowLog) -{ - switch(ms->cParams.minMatch) - { - default : /* includes case 3 */ - case 4 : return ZSTD_RowFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 4, dictMode, rowLog); - case 5 : return ZSTD_RowFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 5, dictMode, rowLog); - case 7 : - case 6 : return ZSTD_RowFindBestMatch_generic(ms, ip, iLimit, offsetPtr, 6, dictMode, rowLog); - } -} -FORCE_INLINE_TEMPLATE size_t ZSTD_RowFindBestMatch_selectRowLog ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - const U32 cappedSearchLog = MIN(ms->cParams.searchLog, 5); - switch(cappedSearchLog) - { - default : - case 4 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_noDict, offsetPtr, 4); - case 5 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_noDict, offsetPtr, 5); - } -} +/** + * Generate search functions templated on (dictMode, mls, rowLog). + * These functions are outlined for code size & compilation time. + * ZSTD_searchMax() dispatches to the correct implementation function. + * + * TODO: The start of the search function involves loading and calculating a + * bunch of constants from the ZSTD_matchState_t. These computations could be + * done in an initialization function, and saved somewhere in the match state. + * Then we could pass a pointer to the saved state instead of the match state, + * and avoid duplicate computations. + * + * TODO: Move the match re-winding into searchMax. This improves compression + * ratio, and unlocks further simplifications with the next TODO. + * + * TODO: Try moving the repcode search into searchMax. After the re-winding + * and repcode search are in searchMax, there is no more logic in the match + * finder loop that requires knowledge about the dictMode. So we should be + * able to avoid force inlining it, and we can join the extDict loop with + * the single segment loop. It should go in searchMax instead of its own + * function to avoid having multiple virtual function calls per search. + */ -FORCE_INLINE_TEMPLATE size_t ZSTD_RowFindBestMatch_dictMatchState_selectRowLog( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - const U32 cappedSearchLog = MIN(ms->cParams.searchLog, 5); - switch(cappedSearchLog) - { - default : - case 4 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dictMatchState, offsetPtr, 4); - case 5 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dictMatchState, offsetPtr, 5); - } -} +#define ZSTD_BT_SEARCH_FN(dictMode, mls) ZSTD_BtFindBestMatch_##dictMode##_##mls +#define ZSTD_HC_SEARCH_FN(dictMode, mls) ZSTD_HcFindBestMatch_##dictMode##_##mls +#define ZSTD_ROW_SEARCH_FN(dictMode, mls, rowLog) ZSTD_RowFindBestMatch_##dictMode##_##mls##_##rowLog + +#define ZSTD_SEARCH_FN_ATTRS FORCE_NOINLINE + +#define GEN_ZSTD_BT_SEARCH_FN(dictMode, mls) \ + ZSTD_SEARCH_FN_ATTRS size_t ZSTD_BT_SEARCH_FN(dictMode, mls)( \ + ZSTD_matchState_t* ms, \ + const BYTE* ip, const BYTE* const iLimit, \ + size_t* offBasePtr) \ + { \ + assert(MAX(4, MIN(6, ms->cParams.minMatch)) == mls); \ + return ZSTD_BtFindBestMatch(ms, ip, iLimit, offBasePtr, mls, ZSTD_##dictMode); \ + } \ + +#define GEN_ZSTD_HC_SEARCH_FN(dictMode, mls) \ + ZSTD_SEARCH_FN_ATTRS size_t ZSTD_HC_SEARCH_FN(dictMode, mls)( \ + ZSTD_matchState_t* ms, \ + const BYTE* ip, const BYTE* const iLimit, \ + size_t* offsetPtr) \ + { \ + assert(MAX(4, MIN(6, ms->cParams.minMatch)) == mls); \ + return ZSTD_HcFindBestMatch(ms, ip, iLimit, offsetPtr, mls, ZSTD_##dictMode); \ + } \ + +#define GEN_ZSTD_ROW_SEARCH_FN(dictMode, mls, rowLog) \ + ZSTD_SEARCH_FN_ATTRS size_t ZSTD_ROW_SEARCH_FN(dictMode, mls, rowLog)( \ + ZSTD_matchState_t* ms, \ + const BYTE* ip, const BYTE* const iLimit, \ + size_t* offsetPtr) \ + { \ + assert(MAX(4, MIN(6, ms->cParams.minMatch)) == mls); \ + assert(MAX(4, MIN(6, ms->cParams.searchLog)) == rowLog); \ + return ZSTD_RowFindBestMatch(ms, ip, iLimit, offsetPtr, mls, ZSTD_##dictMode, rowLog); \ + } \ + +#define ZSTD_FOR_EACH_ROWLOG(X, dictMode, mls) \ + X(dictMode, mls, 4) \ + X(dictMode, mls, 5) \ + X(dictMode, mls, 6) + +#define ZSTD_FOR_EACH_MLS_ROWLOG(X, dictMode) \ + ZSTD_FOR_EACH_ROWLOG(X, dictMode, 4) \ + ZSTD_FOR_EACH_ROWLOG(X, dictMode, 5) \ + ZSTD_FOR_EACH_ROWLOG(X, dictMode, 6) + +#define ZSTD_FOR_EACH_MLS(X, dictMode) \ + X(dictMode, 4) \ + X(dictMode, 5) \ + X(dictMode, 6) + +#define ZSTD_FOR_EACH_DICT_MODE(X, ...) \ + X(__VA_ARGS__, noDict) \ + X(__VA_ARGS__, extDict) \ + X(__VA_ARGS__, dictMatchState) \ + X(__VA_ARGS__, dedicatedDictSearch) + +/* Generate row search fns for each combination of (dictMode, mls, rowLog) */ +ZSTD_FOR_EACH_DICT_MODE(ZSTD_FOR_EACH_MLS_ROWLOG, GEN_ZSTD_ROW_SEARCH_FN) +/* Generate binary Tree search fns for each combination of (dictMode, mls) */ +ZSTD_FOR_EACH_DICT_MODE(ZSTD_FOR_EACH_MLS, GEN_ZSTD_BT_SEARCH_FN) +/* Generate hash chain search fns for each combination of (dictMode, mls) */ +ZSTD_FOR_EACH_DICT_MODE(ZSTD_FOR_EACH_MLS, GEN_ZSTD_HC_SEARCH_FN) -FORCE_INLINE_TEMPLATE size_t ZSTD_RowFindBestMatch_dedicatedDictSearch_selectRowLog( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) -{ - const U32 cappedSearchLog = MIN(ms->cParams.searchLog, 5); - switch(cappedSearchLog) - { - default : - case 4 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dedicatedDictSearch, offsetPtr, 4); - case 5 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_dedicatedDictSearch, offsetPtr, 5); +typedef enum { search_hashChain=0, search_binaryTree=1, search_rowHash=2 } searchMethod_e; + +#define GEN_ZSTD_CALL_BT_SEARCH_FN(dictMode, mls) \ + case mls: \ + return ZSTD_BT_SEARCH_FN(dictMode, mls)(ms, ip, iend, offsetPtr); +#define GEN_ZSTD_CALL_HC_SEARCH_FN(dictMode, mls) \ + case mls: \ + return ZSTD_HC_SEARCH_FN(dictMode, mls)(ms, ip, iend, offsetPtr); +#define GEN_ZSTD_CALL_ROW_SEARCH_FN(dictMode, mls, rowLog) \ + case rowLog: \ + return ZSTD_ROW_SEARCH_FN(dictMode, mls, rowLog)(ms, ip, iend, offsetPtr); + +#define ZSTD_SWITCH_MLS(X, dictMode) \ + switch (mls) { \ + ZSTD_FOR_EACH_MLS(X, dictMode) \ } -} -FORCE_INLINE_TEMPLATE size_t ZSTD_RowFindBestMatch_extDict_selectRowLog ( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* const iLimit, - size_t* offsetPtr) +#define ZSTD_SWITCH_ROWLOG(dictMode, mls) \ + case mls: \ + switch (rowLog) { \ + ZSTD_FOR_EACH_ROWLOG(GEN_ZSTD_CALL_ROW_SEARCH_FN, dictMode, mls) \ + } \ + ZSTD_UNREACHABLE; \ + break; + +#define ZSTD_SWITCH_SEARCH_METHOD(dictMode) \ + switch (searchMethod) { \ + case search_hashChain: \ + ZSTD_SWITCH_MLS(GEN_ZSTD_CALL_HC_SEARCH_FN, dictMode) \ + break; \ + case search_binaryTree: \ + ZSTD_SWITCH_MLS(GEN_ZSTD_CALL_BT_SEARCH_FN, dictMode) \ + break; \ + case search_rowHash: \ + ZSTD_SWITCH_MLS(ZSTD_SWITCH_ROWLOG, dictMode) \ + break; \ + } \ + ZSTD_UNREACHABLE; + +/** + * Searches for the longest match at @p ip. + * Dispatches to the correct implementation function based on the + * (searchMethod, dictMode, mls, rowLog). We use switch statements + * here instead of using an indirect function call through a function + * pointer because after Spectre and Meltdown mitigations, indirect + * function calls can be very costly, especially in the kernel. + * + * NOTE: dictMode and searchMethod should be templated, so those switch + * statements should be optimized out. Only the mls & rowLog switches + * should be left. + * + * @param ms The match state. + * @param ip The position to search at. + * @param iend The end of the input data. + * @param[out] offsetPtr Stores the match offset into this pointer. + * @param mls The minimum search length, in the range [4, 6]. + * @param rowLog The row log (if applicable), in the range [4, 6]. + * @param searchMethod The search method to use (templated). + * @param dictMode The dictMode (templated). + * + * @returns The length of the longest match found, or < mls if no match is found. + * If a match is found its offset is stored in @p offsetPtr. + */ +FORCE_INLINE_TEMPLATE size_t ZSTD_searchMax( + ZSTD_matchState_t* ms, + const BYTE* ip, + const BYTE* iend, + size_t* offsetPtr, + U32 const mls, + U32 const rowLog, + searchMethod_e const searchMethod, + ZSTD_dictMode_e const dictMode) { - const U32 cappedSearchLog = MIN(ms->cParams.searchLog, 5); - switch(cappedSearchLog) - { - default : - case 4 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_extDict, offsetPtr, 4); - case 5 : return ZSTD_RowFindBestMatch_selectMLS(ms, ip, iLimit, ZSTD_extDict, offsetPtr, 5); + if (dictMode == ZSTD_noDict) { + ZSTD_SWITCH_SEARCH_METHOD(noDict) + } else if (dictMode == ZSTD_extDict) { + ZSTD_SWITCH_SEARCH_METHOD(extDict) + } else if (dictMode == ZSTD_dictMatchState) { + ZSTD_SWITCH_SEARCH_METHOD(dictMatchState) + } else if (dictMode == ZSTD_dedicatedDictSearch) { + ZSTD_SWITCH_SEARCH_METHOD(dedicatedDictSearch) } + ZSTD_UNREACHABLE; + return 0; } - /* ******************************* * Common parser - lazy strategy *********************************/ -typedef enum { search_hashChain=0, search_binaryTree=1, search_rowHash=2 } searchMethod_e; FORCE_INLINE_TEMPLATE size_t ZSTD_compressBlock_lazy_generic( @@ -1526,47 +1501,15 @@ ZSTD_compressBlock_lazy_generic( const BYTE* ip = istart; const BYTE* anchor = istart; const BYTE* const iend = istart + srcSize; - const BYTE* const ilimit = searchMethod == search_rowHash ? iend - 8 - ZSTD_ROW_HASH_CACHE_SIZE : iend - 8; + const BYTE* const ilimit = (searchMethod == search_rowHash) ? iend - 8 - ZSTD_ROW_HASH_CACHE_SIZE : iend - 8; const BYTE* const base = ms->window.base; const U32 prefixLowestIndex = ms->window.dictLimit; const BYTE* const prefixLowest = base + prefixLowestIndex; - const U32 rowLog = ms->cParams.searchLog < 5 ? 4 : 5; + const U32 mls = BOUNDED(4, ms->cParams.minMatch, 6); + const U32 rowLog = BOUNDED(4, ms->cParams.searchLog, 6); - typedef size_t (*searchMax_f)( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* iLimit, size_t* offsetPtr); - - /** - * This table is indexed first by the four ZSTD_dictMode_e values, and then - * by the two searchMethod_e values. NULLs are placed for configurations - * that should never occur (extDict modes go to the other implementation - * below and there is no DDSS for binary tree search yet). - */ - const searchMax_f searchFuncs[4][3] = { - { - ZSTD_HcFindBestMatch_selectMLS, - ZSTD_BtFindBestMatch_selectMLS, - ZSTD_RowFindBestMatch_selectRowLog - }, - { - NULL, - NULL, - NULL - }, - { - ZSTD_HcFindBestMatch_dictMatchState_selectMLS, - ZSTD_BtFindBestMatch_dictMatchState_selectMLS, - ZSTD_RowFindBestMatch_dictMatchState_selectRowLog - }, - { - ZSTD_HcFindBestMatch_dedicatedDictSearch_selectMLS, - NULL, - ZSTD_RowFindBestMatch_dedicatedDictSearch_selectRowLog - } - }; - - searchMax_f const searchMax = searchFuncs[dictMode][(int)searchMethod]; - U32 offset_1 = rep[0], offset_2 = rep[1], savedOffset=0; + U32 offset_1 = rep[0], offset_2 = rep[1]; + U32 offsetSaved1 = 0, offsetSaved2 = 0; const int isDMS = dictMode == ZSTD_dictMatchState; const int isDDS = dictMode == ZSTD_dedicatedDictSearch; @@ -1581,16 +1524,14 @@ ZSTD_compressBlock_lazy_generic( 0; const U32 dictAndPrefixLength = (U32)((ip - prefixLowest) + (dictEnd - dictLowest)); - assert(searchMax != NULL); - DEBUGLOG(5, "ZSTD_compressBlock_lazy_generic (dictMode=%u) (searchFunc=%u)", (U32)dictMode, (U32)searchMethod); ip += (dictAndPrefixLength == 0); if (dictMode == ZSTD_noDict) { U32 const curr = (U32)(ip - base); U32 const windowLow = ZSTD_getLowestPrefixIndex(ms, curr, ms->cParams.windowLog); U32 const maxRep = curr - windowLow; - if (offset_2 > maxRep) savedOffset = offset_2, offset_2 = 0; - if (offset_1 > maxRep) savedOffset = offset_1, offset_1 = 0; + if (offset_2 > maxRep) offsetSaved2 = offset_2, offset_2 = 0; + if (offset_1 > maxRep) offsetSaved1 = offset_1, offset_1 = 0; } if (isDxS) { /* dictMatchState repCode checks don't currently handle repCode == 0 @@ -1599,10 +1540,11 @@ ZSTD_compressBlock_lazy_generic( assert(offset_2 <= dictAndPrefixLength); } + /* Reset the lazy skipping state */ + ms->lazySkipping = 0; + if (searchMethod == search_rowHash) { - ZSTD_row_fillHashCache(ms, base, rowLog, - MIN(ms->cParams.minMatch, 6 /* mls caps out at 6 */), - ms->nextToUpdate, ilimit); + ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); } /* Match Loop */ @@ -1614,8 +1556,9 @@ ZSTD_compressBlock_lazy_generic( #endif while (ip < ilimit) { size_t matchLength=0; - size_t offset=0; + size_t offBase = REPCODE1_TO_OFFBASE; const BYTE* start=ip+1; + DEBUGLOG(7, "search baseline (depth 0)"); /* check repCode */ if (isDxS) { @@ -1638,28 +1581,38 @@ ZSTD_compressBlock_lazy_generic( } /* first search (depth 0) */ - { size_t offsetFound = 999999999; - size_t const ml2 = searchMax(ms, ip, iend, &offsetFound); + { size_t offbaseFound = 999999999; + size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offbaseFound, mls, rowLog, searchMethod, dictMode); if (ml2 > matchLength) - matchLength = ml2, start = ip, offset=offsetFound; + matchLength = ml2, start = ip, offBase = offbaseFound; } if (matchLength < 4) { - ip += ((ip-anchor) >> kSearchStrength) + 1; /* jump faster over incompressible sections */ + size_t const step = ((size_t)(ip-anchor) >> kSearchStrength) + 1; /* jump faster over incompressible sections */; + ip += step; + /* Enter the lazy skipping mode once we are skipping more than 8 bytes at a time. + * In this mode we stop inserting every position into our tables, and only insert + * positions that we search, which is one in step positions. + * The exact cutoff is flexible, I've just chosen a number that is reasonably high, + * so we minimize the compression ratio loss in "normal" scenarios. This mode gets + * triggered once we've gone 2KB without finding any matches. + */ + ms->lazySkipping = step > kLazySkippingStep; continue; } /* let's try to find a better solution */ if (depth>=1) while (ip0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { + && (offBase) && ((offset_1>0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { size_t const mlRep = ZSTD_count(ip+4, ip+4-offset_1, iend) + 4; int const gain2 = (int)(mlRep * 3); - int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offset+1) + 1); + int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offBase) + 1); if ((mlRep >= 4) && (gain2 > gain1)) - matchLength = mlRep, offset = 0, start = ip; + matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; } if (isDxS) { const U32 repIndex = (U32)(ip - base) - offset_1; @@ -1671,30 +1624,31 @@ ZSTD_compressBlock_lazy_generic( const BYTE* repMatchEnd = repIndex < prefixLowestIndex ? dictEnd : iend; size_t const mlRep = ZSTD_count_2segments(ip+4, repMatch+4, iend, repMatchEnd, prefixLowest) + 4; int const gain2 = (int)(mlRep * 3); - int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offset+1) + 1); + int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offBase) + 1); if ((mlRep >= 4) && (gain2 > gain1)) - matchLength = mlRep, offset = 0, start = ip; + matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; } } - { size_t offset2=999999999; - size_t const ml2 = searchMax(ms, ip, iend, &offset2); - int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)offset2+1)); /* raw approx */ - int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offset+1) + 4); + { size_t ofbCandidate=999999999; + size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, dictMode); + int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ + int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 4); if ((ml2 >= 4) && (gain2 > gain1)) { - matchLength = ml2, offset = offset2, start = ip; + matchLength = ml2, offBase = ofbCandidate, start = ip; continue; /* search a better one */ } } /* let's find an even better one */ if ((depth==2) && (ip0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { + && (offBase) && ((offset_1>0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { size_t const mlRep = ZSTD_count(ip+4, ip+4-offset_1, iend) + 4; int const gain2 = (int)(mlRep * 4); - int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offset+1) + 1); + int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 1); if ((mlRep >= 4) && (gain2 > gain1)) - matchLength = mlRep, offset = 0, start = ip; + matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; } if (isDxS) { const U32 repIndex = (U32)(ip - base) - offset_1; @@ -1706,48 +1660,54 @@ ZSTD_compressBlock_lazy_generic( const BYTE* repMatchEnd = repIndex < prefixLowestIndex ? dictEnd : iend; size_t const mlRep = ZSTD_count_2segments(ip+4, repMatch+4, iend, repMatchEnd, prefixLowest) + 4; int const gain2 = (int)(mlRep * 4); - int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offset+1) + 1); + int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 1); if ((mlRep >= 4) && (gain2 > gain1)) - matchLength = mlRep, offset = 0, start = ip; + matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; } } - { size_t offset2=999999999; - size_t const ml2 = searchMax(ms, ip, iend, &offset2); - int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)offset2+1)); /* raw approx */ - int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offset+1) + 7); + { size_t ofbCandidate=999999999; + size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, dictMode); + int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ + int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 7); if ((ml2 >= 4) && (gain2 > gain1)) { - matchLength = ml2, offset = offset2, start = ip; + matchLength = ml2, offBase = ofbCandidate, start = ip; continue; } } } break; /* nothing found : store previous solution */ } /* NOTE: - * start[-offset+ZSTD_REP_MOVE-1] is undefined behavior. - * (-offset+ZSTD_REP_MOVE-1) is unsigned, and is added to start, which - * overflows the pointer, which is undefined behavior. + * Pay attention that `start[-value]` can lead to strange undefined behavior + * notably if `value` is unsigned, resulting in a large positive `-value`. */ /* catch up */ - if (offset) { + if (OFFBASE_IS_OFFSET(offBase)) { if (dictMode == ZSTD_noDict) { - while ( ((start > anchor) & (start - (offset-ZSTD_REP_MOVE) > prefixLowest)) - && (start[-1] == (start-(offset-ZSTD_REP_MOVE))[-1]) ) /* only search for offset within prefix */ + while ( ((start > anchor) & (start - OFFBASE_TO_OFFSET(offBase) > prefixLowest)) + && (start[-1] == (start-OFFBASE_TO_OFFSET(offBase))[-1]) ) /* only search for offset within prefix */ { start--; matchLength++; } } if (isDxS) { - U32 const matchIndex = (U32)((start-base) - (offset - ZSTD_REP_MOVE)); + U32 const matchIndex = (U32)((size_t)(start-base) - OFFBASE_TO_OFFSET(offBase)); const BYTE* match = (matchIndex < prefixLowestIndex) ? dictBase + matchIndex - dictIndexDelta : base + matchIndex; const BYTE* const mStart = (matchIndex < prefixLowestIndex) ? dictLowest : prefixLowest; while ((start>anchor) && (match>mStart) && (start[-1] == match[-1])) { start--; match--; matchLength++; } /* catch up */ } - offset_2 = offset_1; offset_1 = (U32)(offset - ZSTD_REP_MOVE); + offset_2 = offset_1; offset_1 = (U32)OFFBASE_TO_OFFSET(offBase); } /* store sequence */ _storeSequence: - { size_t const litLength = start - anchor; - ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offset, matchLength-MINMATCH); + { size_t const litLength = (size_t)(start - anchor); + ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offBase, matchLength); anchor = ip = start + matchLength; } + if (ms->lazySkipping) { + /* We've found a match, disable lazy skipping mode, and refill the hash cache. */ + if (searchMethod == search_rowHash) { + ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); + } + ms->lazySkipping = 0; + } /* check immediate repcode */ if (isDxS) { @@ -1761,8 +1721,8 @@ ZSTD_compressBlock_lazy_generic( && (MEM_read32(repMatch) == MEM_read32(ip)) ) { const BYTE* const repEnd2 = repIndex < prefixLowestIndex ? dictEnd : iend; matchLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd2, prefixLowest) + 4; - offset = offset_2; offset_2 = offset_1; offset_1 = (U32)offset; /* swap offset_2 <=> offset_1 */ - ZSTD_storeSeq(seqStore, 0, anchor, iend, 0, matchLength-MINMATCH); + offBase = offset_2; offset_2 = offset_1; offset_1 = (U32)offBase; /* swap offset_2 <=> offset_1 */ + ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, matchLength); ip += matchLength; anchor = ip; continue; @@ -1776,16 +1736,20 @@ ZSTD_compressBlock_lazy_generic( && (MEM_read32(ip) == MEM_read32(ip - offset_2)) ) { /* store sequence */ matchLength = ZSTD_count(ip+4, ip+4-offset_2, iend) + 4; - offset = offset_2; offset_2 = offset_1; offset_1 = (U32)offset; /* swap repcodes */ - ZSTD_storeSeq(seqStore, 0, anchor, iend, 0, matchLength-MINMATCH); + offBase = offset_2; offset_2 = offset_1; offset_1 = (U32)offBase; /* swap repcodes */ + ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, matchLength); ip += matchLength; anchor = ip; continue; /* faster when present ... (?) */ } } } - /* Save reps for next block */ - rep[0] = offset_1 ? offset_1 : savedOffset; - rep[1] = offset_2 ? offset_2 : savedOffset; + /* If offset_1 started invalid (offsetSaved1 != 0) and became valid (offset_1 != 0), + * rotate saved offsets. See comment in ZSTD_compressBlock_fast_noDict for more context. */ + offsetSaved2 = ((offsetSaved1 != 0) && (offset_1 != 0)) ? offsetSaved1 : offsetSaved2; + + /* save reps for next block */ + rep[0] = offset_1 ? offset_1 : offsetSaved1; + rep[1] = offset_2 ? offset_2 : offsetSaved2; /* Return the last literals size */ return (size_t)(iend - anchor); @@ -1954,27 +1918,20 @@ size_t ZSTD_compressBlock_lazy_extDict_generic( const BYTE* const dictEnd = dictBase + dictLimit; const BYTE* const dictStart = dictBase + ms->window.lowLimit; const U32 windowLog = ms->cParams.windowLog; - const U32 rowLog = ms->cParams.searchLog < 5 ? 4 : 5; + const U32 mls = BOUNDED(4, ms->cParams.minMatch, 6); + const U32 rowLog = BOUNDED(4, ms->cParams.searchLog, 6); - typedef size_t (*searchMax_f)( - ZSTD_matchState_t* ms, - const BYTE* ip, const BYTE* iLimit, size_t* offsetPtr); - const searchMax_f searchFuncs[3] = { - ZSTD_HcFindBestMatch_extDict_selectMLS, - ZSTD_BtFindBestMatch_extDict_selectMLS, - ZSTD_RowFindBestMatch_extDict_selectRowLog - }; - searchMax_f searchMax = searchFuncs[(int)searchMethod]; U32 offset_1 = rep[0], offset_2 = rep[1]; DEBUGLOG(5, "ZSTD_compressBlock_lazy_extDict_generic (searchFunc=%u)", (U32)searchMethod); + /* Reset the lazy skipping state */ + ms->lazySkipping = 0; + /* init */ ip += (ip == prefixStart); if (searchMethod == search_rowHash) { - ZSTD_row_fillHashCache(ms, base, rowLog, - MIN(ms->cParams.minMatch, 6 /* mls caps out at 6 */), - ms->nextToUpdate, ilimit); + ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); } /* Match Loop */ @@ -1986,7 +1943,7 @@ size_t ZSTD_compressBlock_lazy_extDict_generic( #endif while (ip < ilimit) { size_t matchLength=0; - size_t offset=0; + size_t offBase = REPCODE1_TO_OFFBASE; const BYTE* start=ip+1; U32 curr = (U32)(ip-base); @@ -1996,7 +1953,7 @@ size_t ZSTD_compressBlock_lazy_extDict_generic( const BYTE* const repBase = repIndex < dictLimit ? dictBase : base; const BYTE* const repMatch = repBase + repIndex; if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow */ - & (offset_1 < curr+1 - windowLow) ) /* note: we are searching at curr+1 */ + & (offset_1 <= curr+1 - windowLow) ) /* note: we are searching at curr+1 */ if (MEM_read32(ip+1) == MEM_read32(repMatch)) { /* repcode detected we should take it */ const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend; @@ -2005,14 +1962,23 @@ size_t ZSTD_compressBlock_lazy_extDict_generic( } } /* first search (depth 0) */ - { size_t offsetFound = 999999999; - size_t const ml2 = searchMax(ms, ip, iend, &offsetFound); + { size_t ofbCandidate = 999999999; + size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, ZSTD_extDict); if (ml2 > matchLength) - matchLength = ml2, start = ip, offset=offsetFound; + matchLength = ml2, start = ip, offBase = ofbCandidate; } - if (matchLength < 4) { - ip += ((ip-anchor) >> kSearchStrength) + 1; /* jump faster over incompressible sections */ + if (matchLength < 4) { + size_t const step = ((size_t)(ip-anchor) >> kSearchStrength); + ip += step + 1; /* jump faster over incompressible sections */ + /* Enter the lazy skipping mode once we are skipping more than 8 bytes at a time. + * In this mode we stop inserting every position into our tables, and only insert + * positions that we search, which is one in step positions. + * The exact cutoff is flexible, I've just chosen a number that is reasonably high, + * so we minimize the compression ratio loss in "normal" scenarios. This mode gets + * triggered once we've gone 2KB without finding any matches. + */ + ms->lazySkipping = step > kLazySkippingStep; continue; } @@ -2022,30 +1988,30 @@ size_t ZSTD_compressBlock_lazy_extDict_generic( ip ++; curr++; /* check repCode */ - if (offset) { + if (offBase) { const U32 windowLow = ZSTD_getLowestMatchIndex(ms, curr, windowLog); const U32 repIndex = (U32)(curr - offset_1); const BYTE* const repBase = repIndex < dictLimit ? dictBase : base; const BYTE* const repMatch = repBase + repIndex; if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow : do not test positions overlapping 2 memory segments */ - & (offset_1 < curr - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */ + & (offset_1 <= curr - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */ if (MEM_read32(ip) == MEM_read32(repMatch)) { /* repcode detected */ const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend; size_t const repLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd, prefixStart) + 4; int const gain2 = (int)(repLength * 3); - int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offset+1) + 1); + int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offBase) + 1); if ((repLength >= 4) && (gain2 > gain1)) - matchLength = repLength, offset = 0, start = ip; + matchLength = repLength, offBase = REPCODE1_TO_OFFBASE, start = ip; } } /* search match, depth 1 */ - { size_t offset2=999999999; - size_t const ml2 = searchMax(ms, ip, iend, &offset2); - int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)offset2+1)); /* raw approx */ - int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offset+1) + 4); + { size_t ofbCandidate = 999999999; + size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, ZSTD_extDict); + int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ + int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 4); if ((ml2 >= 4) && (gain2 > gain1)) { - matchLength = ml2, offset = offset2, start = ip; + matchLength = ml2, offBase = ofbCandidate, start = ip; continue; /* search a better one */ } } @@ -2054,50 +2020,57 @@ size_t ZSTD_compressBlock_lazy_extDict_generic( ip ++; curr++; /* check repCode */ - if (offset) { + if (offBase) { const U32 windowLow = ZSTD_getLowestMatchIndex(ms, curr, windowLog); const U32 repIndex = (U32)(curr - offset_1); const BYTE* const repBase = repIndex < dictLimit ? dictBase : base; const BYTE* const repMatch = repBase + repIndex; if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow : do not test positions overlapping 2 memory segments */ - & (offset_1 < curr - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */ + & (offset_1 <= curr - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */ if (MEM_read32(ip) == MEM_read32(repMatch)) { /* repcode detected */ const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend; size_t const repLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd, prefixStart) + 4; int const gain2 = (int)(repLength * 4); - int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offset+1) + 1); + int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 1); if ((repLength >= 4) && (gain2 > gain1)) - matchLength = repLength, offset = 0, start = ip; + matchLength = repLength, offBase = REPCODE1_TO_OFFBASE, start = ip; } } /* search match, depth 2 */ - { size_t offset2=999999999; - size_t const ml2 = searchMax(ms, ip, iend, &offset2); - int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)offset2+1)); /* raw approx */ - int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offset+1) + 7); + { size_t ofbCandidate = 999999999; + size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, ZSTD_extDict); + int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ + int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 7); if ((ml2 >= 4) && (gain2 > gain1)) { - matchLength = ml2, offset = offset2, start = ip; + matchLength = ml2, offBase = ofbCandidate, start = ip; continue; } } } break; /* nothing found : store previous solution */ } /* catch up */ - if (offset) { - U32 const matchIndex = (U32)((start-base) - (offset - ZSTD_REP_MOVE)); + if (OFFBASE_IS_OFFSET(offBase)) { + U32 const matchIndex = (U32)((size_t)(start-base) - OFFBASE_TO_OFFSET(offBase)); const BYTE* match = (matchIndex < dictLimit) ? dictBase + matchIndex : base + matchIndex; const BYTE* const mStart = (matchIndex < dictLimit) ? dictStart : prefixStart; while ((start>anchor) && (match>mStart) && (start[-1] == match[-1])) { start--; match--; matchLength++; } /* catch up */ - offset_2 = offset_1; offset_1 = (U32)(offset - ZSTD_REP_MOVE); + offset_2 = offset_1; offset_1 = (U32)OFFBASE_TO_OFFSET(offBase); } /* store sequence */ _storeSequence: - { size_t const litLength = start - anchor; - ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offset, matchLength-MINMATCH); + { size_t const litLength = (size_t)(start - anchor); + ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offBase, matchLength); anchor = ip = start + matchLength; } + if (ms->lazySkipping) { + /* We've found a match, disable lazy skipping mode, and refill the hash cache. */ + if (searchMethod == search_rowHash) { + ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); + } + ms->lazySkipping = 0; + } /* check immediate repcode */ while (ip <= ilimit) { @@ -2107,13 +2080,13 @@ size_t ZSTD_compressBlock_lazy_extDict_generic( const BYTE* const repBase = repIndex < dictLimit ? dictBase : base; const BYTE* const repMatch = repBase + repIndex; if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow : do not test positions overlapping 2 memory segments */ - & (offset_2 < repCurrent - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */ + & (offset_2 <= repCurrent - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */ if (MEM_read32(ip) == MEM_read32(repMatch)) { /* repcode detected we should take it */ const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend; matchLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd, prefixStart) + 4; - offset = offset_2; offset_2 = offset_1; offset_1 = (U32)offset; /* swap offset history */ - ZSTD_storeSeq(seqStore, 0, anchor, iend, 0, matchLength-MINMATCH); + offBase = offset_2; offset_2 = offset_1; offset_1 = (U32)offBase; /* swap offset history */ + ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, matchLength); ip += matchLength; anchor = ip; continue; /* faster when present ... (?) */ @@ -2179,7 +2152,6 @@ size_t ZSTD_compressBlock_lazy_extDict_row( size_t ZSTD_compressBlock_lazy2_extDict_row( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize) - { return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2); } diff --git a/Utilities/cmzstd/lib/compress/zstd_lazy.h b/Utilities/cmzstd/lib/compress/zstd_lazy.h index 150f7b390b8..3bde67331e4 100644 --- a/Utilities/cmzstd/lib/compress/zstd_lazy.h +++ b/Utilities/cmzstd/lib/compress/zstd_lazy.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -25,6 +25,8 @@ extern "C" { */ #define ZSTD_LAZY_DDSS_BUCKET_LOG 2 +#define ZSTD_ROW_HASH_TAG_BITS 8 /* nb bits to use for the tag */ + U32 ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, const BYTE* ip); void ZSTD_row_update(ZSTD_matchState_t* const ms, const BYTE* ip); @@ -116,7 +118,7 @@ size_t ZSTD_compressBlock_lazy2_extDict_row( size_t ZSTD_compressBlock_btlazy2_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); - + #if defined (__cplusplus) } diff --git a/Utilities/cmzstd/lib/compress/zstd_ldm.c b/Utilities/cmzstd/lib/compress/zstd_ldm.c index fa4ebeabd70..3d74ff19e3c 100644 --- a/Utilities/cmzstd/lib/compress/zstd_ldm.c +++ b/Utilities/cmzstd/lib/compress/zstd_ldm.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -159,12 +159,12 @@ size_t ZSTD_ldm_getTableSize(ldmParams_t params) size_t const ldmBucketSize = ((size_t)1) << (params.hashLog - ldmBucketSizeLog); size_t const totalSize = ZSTD_cwksp_alloc_size(ldmBucketSize) + ZSTD_cwksp_alloc_size(ldmHSize * sizeof(ldmEntry_t)); - return params.enableLdm ? totalSize : 0; + return params.enableLdm == ZSTD_ps_enable ? totalSize : 0; } size_t ZSTD_ldm_getMaxNbSeq(ldmParams_t params, size_t maxChunkSize) { - return params.enableLdm ? (maxChunkSize / params.minMatchLength) : 0; + return params.enableLdm == ZSTD_ps_enable ? (maxChunkSize / params.minMatchLength) : 0; } /** ZSTD_ldm_getBucket() : @@ -242,11 +242,11 @@ static size_t ZSTD_ldm_fillFastTables(ZSTD_matchState_t* ms, switch(ms->cParams.strategy) { case ZSTD_fast: - ZSTD_fillHashTable(ms, iend, ZSTD_dtlm_fast); + ZSTD_fillHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); break; case ZSTD_dfast: - ZSTD_fillDoubleHashTable(ms, iend, ZSTD_dtlm_fast); + ZSTD_fillDoubleHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); break; case ZSTD_greedy: @@ -478,7 +478,7 @@ static size_t ZSTD_ldm_generateSequences_internal( */ if (anchor > ip + hashed) { ZSTD_ldm_gear_reset(&hashState, anchor - minMatchLength, minMatchLength); - /* Continue the outter loop at anchor (ip + hashed == anchor). */ + /* Continue the outer loop at anchor (ip + hashed == anchor). */ ip = anchor - hashed; break; } @@ -549,7 +549,7 @@ size_t ZSTD_ldm_generateSequences( * the window through early invalidation. * TODO: * Test the chunk size. * * Try invalidation after the sequence generation and test the - * the offset against maxDist directly. + * offset against maxDist directly. * * NOTE: Because of dictionaries + sequence splitting we MUST make sure * that any offset used is valid at the END of the sequence, since it may @@ -579,7 +579,9 @@ size_t ZSTD_ldm_generateSequences( return 0; } -void ZSTD_ldm_skipSequences(rawSeqStore_t* rawSeqStore, size_t srcSize, U32 const minMatch) { +void +ZSTD_ldm_skipSequences(rawSeqStore_t* rawSeqStore, size_t srcSize, U32 const minMatch) +{ while (srcSize > 0 && rawSeqStore->pos < rawSeqStore->size) { rawSeq* seq = rawSeqStore->seq + rawSeqStore->pos; if (srcSize <= seq->litLength) { @@ -657,7 +659,7 @@ void ZSTD_ldm_skipRawSeqStoreBytes(rawSeqStore_t* rawSeqStore, size_t nbBytes) { size_t ZSTD_ldm_blockCompress(rawSeqStore_t* rawSeqStore, ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - ZSTD_useRowMatchFinderMode_e useRowMatchFinder, + ZSTD_paramSwitch_e useRowMatchFinder, void const* src, size_t srcSize) { const ZSTD_compressionParameters* const cParams = &ms->cParams; @@ -709,8 +711,8 @@ size_t ZSTD_ldm_blockCompress(rawSeqStore_t* rawSeqStore, rep[0] = sequence.offset; /* Store the sequence */ ZSTD_storeSeq(seqStore, newLitLength, ip - newLitLength, iend, - sequence.offset + ZSTD_REP_MOVE, - sequence.matchLength - MINMATCH); + OFFSET_TO_OFFBASE(sequence.offset), + sequence.matchLength); ip += sequence.matchLength; } } diff --git a/Utilities/cmzstd/lib/compress/zstd_ldm.h b/Utilities/cmzstd/lib/compress/zstd_ldm.h index 393466fa9f5..f147021d296 100644 --- a/Utilities/cmzstd/lib/compress/zstd_ldm.h +++ b/Utilities/cmzstd/lib/compress/zstd_ldm.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -66,7 +66,7 @@ size_t ZSTD_ldm_generateSequences( */ size_t ZSTD_ldm_blockCompress(rawSeqStore_t* rawSeqStore, ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], - ZSTD_useRowMatchFinderMode_e useRowMatchFinder, + ZSTD_paramSwitch_e useRowMatchFinder, void const* src, size_t srcSize); /** diff --git a/Utilities/cmzstd/lib/compress/zstd_ldm_geartab.h b/Utilities/cmzstd/lib/compress/zstd_ldm_geartab.h index e5c24d856b0..ef34bc5c923 100644 --- a/Utilities/cmzstd/lib/compress/zstd_ldm_geartab.h +++ b/Utilities/cmzstd/lib/compress/zstd_ldm_geartab.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -11,7 +11,10 @@ #ifndef ZSTD_LDM_GEARTAB_H #define ZSTD_LDM_GEARTAB_H -static U64 ZSTD_ldm_gearTab[256] = { +#include "../common/compiler.h" /* UNUSED_ATTR */ +#include "../common/mem.h" /* U64 */ + +static UNUSED_ATTR const U64 ZSTD_ldm_gearTab[256] = { 0xf5b8f72c5f77775c, 0x84935f266b7ac412, 0xb647ada9ca730ccc, 0xb065bb4b114fb1de, 0x34584e7e8c3a9fd0, 0x4e97e17c6ae26b05, 0x3a03d743bc99a604, 0xcecd042422c4044f, 0x76de76c58524259e, diff --git a/Utilities/cmzstd/lib/compress/zstd_opt.c b/Utilities/cmzstd/lib/compress/zstd_opt.c index 402a7e5c76b..f02a760946e 100644 --- a/Utilities/cmzstd/lib/compress/zstd_opt.c +++ b/Utilities/cmzstd/lib/compress/zstd_opt.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Przemyslaw Skibinski, Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -14,40 +14,47 @@ #define ZSTD_LITFREQ_ADD 2 /* scaling factor for litFreq, so that frequencies adapt faster to new stats */ -#define ZSTD_FREQ_DIV 4 /* log factor when using previous stats to init next stats */ #define ZSTD_MAX_PRICE (1<<30) -#define ZSTD_PREDEF_THRESHOLD 1024 /* if srcSize < ZSTD_PREDEF_THRESHOLD, symbols' cost is assumed static, directly determined by pre-defined distributions */ +#define ZSTD_PREDEF_THRESHOLD 8 /* if srcSize < ZSTD_PREDEF_THRESHOLD, symbols' cost is assumed static, directly determined by pre-defined distributions */ /*-************************************* * Price functions for optimal parser ***************************************/ -#if 0 /* approximation at bit level */ +#if 0 /* approximation at bit level (for tests) */ # define BITCOST_ACCURACY 0 # define BITCOST_MULTIPLIER (1 << BITCOST_ACCURACY) -# define WEIGHT(stat) ((void)opt, ZSTD_bitWeight(stat)) -#elif 0 /* fractional bit accuracy */ +# define WEIGHT(stat, opt) ((void)(opt), ZSTD_bitWeight(stat)) +#elif 0 /* fractional bit accuracy (for tests) */ # define BITCOST_ACCURACY 8 # define BITCOST_MULTIPLIER (1 << BITCOST_ACCURACY) -# define WEIGHT(stat,opt) ((void)opt, ZSTD_fracWeight(stat)) +# define WEIGHT(stat,opt) ((void)(opt), ZSTD_fracWeight(stat)) #else /* opt==approx, ultra==accurate */ # define BITCOST_ACCURACY 8 # define BITCOST_MULTIPLIER (1 << BITCOST_ACCURACY) -# define WEIGHT(stat,opt) (opt ? ZSTD_fracWeight(stat) : ZSTD_bitWeight(stat)) +# define WEIGHT(stat,opt) ((opt) ? ZSTD_fracWeight(stat) : ZSTD_bitWeight(stat)) #endif +/* ZSTD_bitWeight() : + * provide estimated "cost" of a stat in full bits only */ MEM_STATIC U32 ZSTD_bitWeight(U32 stat) { return (ZSTD_highbit32(stat+1) * BITCOST_MULTIPLIER); } +/* ZSTD_fracWeight() : + * provide fractional-bit "cost" of a stat, + * using linear interpolation approximation */ MEM_STATIC U32 ZSTD_fracWeight(U32 rawStat) { U32 const stat = rawStat + 1; U32 const hb = ZSTD_highbit32(stat); U32 const BWeight = hb * BITCOST_MULTIPLIER; + /* Fweight was meant for "Fractional weight" + * but it's effectively a value between 1 and 2 + * using fixed point arithmetic */ U32 const FWeight = (stat << BITCOST_ACCURACY) >> hb; U32 const weight = BWeight + FWeight; assert(hb + BITCOST_ACCURACY < 31); @@ -58,7 +65,7 @@ MEM_STATIC U32 ZSTD_fracWeight(U32 rawStat) /* debugging function, * @return price in bytes as fractional value * for debug messages only */ -MEM_STATIC double ZSTD_fCost(U32 price) +MEM_STATIC double ZSTD_fCost(int price) { return (double)price / (BITCOST_MULTIPLIER*8); } @@ -66,7 +73,7 @@ MEM_STATIC double ZSTD_fCost(U32 price) static int ZSTD_compressedLiterals(optState_t const* const optPtr) { - return optPtr->literalCompressionMode != ZSTD_lcm_uncompressed; + return optPtr->literalCompressionMode != ZSTD_ps_disable; } static void ZSTD_setBasePrices(optState_t* optPtr, int optLevel) @@ -79,25 +86,52 @@ static void ZSTD_setBasePrices(optState_t* optPtr, int optLevel) } -/* ZSTD_downscaleStat() : - * reduce all elements in table by a factor 2^(ZSTD_FREQ_DIV+malus) - * return the resulting sum of elements */ -static U32 ZSTD_downscaleStat(unsigned* table, U32 lastEltIndex, int malus) +static U32 sum_u32(const unsigned table[], size_t nbElts) +{ + size_t n; + U32 total = 0; + for (n=0; n 0 && ZSTD_FREQ_DIV+malus < 31); + DEBUGLOG(5, "ZSTD_downscaleStats (nbElts=%u, shift=%u)", + (unsigned)lastEltIndex+1, (unsigned)shift ); + assert(shift < 30); for (s=0; s> (ZSTD_FREQ_DIV+malus)); - sum += table[s]; + unsigned const base = base1 ? 1 : (table[s]>0); + unsigned const newStat = base + (table[s] >> shift); + sum += newStat; + table[s] = newStat; } return sum; } +/* ZSTD_scaleStats() : + * reduce all elt frequencies in table if sum too large + * return the resulting sum of elements */ +static U32 ZSTD_scaleStats(unsigned* table, U32 lastEltIndex, U32 logTarget) +{ + U32 const prevsum = sum_u32(table, lastEltIndex+1); + U32 const factor = prevsum >> logTarget; + DEBUGLOG(5, "ZSTD_scaleStats (nbElts=%u, target=%u)", (unsigned)lastEltIndex+1, (unsigned)logTarget); + assert(logTarget < 30); + if (factor <= 1) return prevsum; + return ZSTD_downscaleStats(table, lastEltIndex, ZSTD_highbit32(factor), base_1guaranteed); +} + /* ZSTD_rescaleFreqs() : * if first block (detected by optPtr->litLengthSum == 0) : init statistics * take hints from dictionary if there is one - * or init from zero, using src for literals stats, or flat 1 for match symbols + * and init from zero if there is none, + * using src for literals stats, and baseline stats for sequence symbols * otherwise downscale existing stats, to be used as seed for next block. */ static void @@ -109,24 +143,28 @@ ZSTD_rescaleFreqs(optState_t* const optPtr, DEBUGLOG(5, "ZSTD_rescaleFreqs (srcSize=%u)", (unsigned)srcSize); optPtr->priceType = zop_dynamic; - if (optPtr->litLengthSum == 0) { /* first block : init */ - if (srcSize <= ZSTD_PREDEF_THRESHOLD) { /* heuristic */ - DEBUGLOG(5, "(srcSize <= ZSTD_PREDEF_THRESHOLD) => zop_predef"); + if (optPtr->litLengthSum == 0) { /* no literals stats collected -> first block assumed -> init */ + + /* heuristic: use pre-defined stats for too small inputs */ + if (srcSize <= ZSTD_PREDEF_THRESHOLD) { + DEBUGLOG(5, "srcSize <= %i : use predefined stats", ZSTD_PREDEF_THRESHOLD); optPtr->priceType = zop_predef; } assert(optPtr->symbolCosts != NULL); if (optPtr->symbolCosts->huf.repeatMode == HUF_repeat_valid) { - /* huffman table presumed generated by dictionary */ + + /* huffman stats covering the full value set : table presumed generated by dictionary */ optPtr->priceType = zop_dynamic; if (compressedLiterals) { + /* generate literals statistics from huffman table */ unsigned lit; assert(optPtr->litFreq != NULL); optPtr->litSum = 0; for (lit=0; lit<=MaxLit; lit++) { U32 const scaleLog = 11; /* scale to 2K */ - U32 const bitCost = HUF_getNbBits(optPtr->symbolCosts->huf.CTable, lit); + U32 const bitCost = HUF_getNbBitsFromCTable(optPtr->symbolCosts->huf.CTable, lit); assert(bitCost <= scaleLog); optPtr->litFreq[lit] = bitCost ? 1 << (scaleLog-bitCost) : 1 /*minimum to calculate cost*/; optPtr->litSum += optPtr->litFreq[lit]; @@ -168,20 +206,26 @@ ZSTD_rescaleFreqs(optState_t* const optPtr, optPtr->offCodeSum += optPtr->offCodeFreq[of]; } } - } else { /* not a dictionary */ + } else { /* first block, no dictionary */ assert(optPtr->litFreq != NULL); if (compressedLiterals) { + /* base initial cost of literals on direct frequency within src */ unsigned lit = MaxLit; HIST_count_simple(optPtr->litFreq, &lit, src, srcSize); /* use raw first block to init statistics */ - optPtr->litSum = ZSTD_downscaleStat(optPtr->litFreq, MaxLit, 1); + optPtr->litSum = ZSTD_downscaleStats(optPtr->litFreq, MaxLit, 8, base_0possible); } - { unsigned ll; - for (ll=0; ll<=MaxLL; ll++) - optPtr->litLengthFreq[ll] = 1; + { unsigned const baseLLfreqs[MaxLL+1] = { + 4, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1 + }; + ZSTD_memcpy(optPtr->litLengthFreq, baseLLfreqs, sizeof(baseLLfreqs)); + optPtr->litLengthSum = sum_u32(baseLLfreqs, MaxLL+1); } - optPtr->litLengthSum = MaxLL+1; { unsigned ml; for (ml=0; ml<=MaxML; ml++) @@ -189,21 +233,25 @@ ZSTD_rescaleFreqs(optState_t* const optPtr, } optPtr->matchLengthSum = MaxML+1; - { unsigned of; - for (of=0; of<=MaxOff; of++) - optPtr->offCodeFreq[of] = 1; + { unsigned const baseOFCfreqs[MaxOff+1] = { + 6, 2, 1, 1, 2, 3, 4, 4, + 4, 3, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1 + }; + ZSTD_memcpy(optPtr->offCodeFreq, baseOFCfreqs, sizeof(baseOFCfreqs)); + optPtr->offCodeSum = sum_u32(baseOFCfreqs, MaxOff+1); } - optPtr->offCodeSum = MaxOff+1; } - } else { /* new block : re-use previous statistics, scaled down */ + } else { /* new block : scale down accumulated statistics */ if (compressedLiterals) - optPtr->litSum = ZSTD_downscaleStat(optPtr->litFreq, MaxLit, 1); - optPtr->litLengthSum = ZSTD_downscaleStat(optPtr->litLengthFreq, MaxLL, 0); - optPtr->matchLengthSum = ZSTD_downscaleStat(optPtr->matchLengthFreq, MaxML, 0); - optPtr->offCodeSum = ZSTD_downscaleStat(optPtr->offCodeFreq, MaxOff, 0); + optPtr->litSum = ZSTD_scaleStats(optPtr->litFreq, MaxLit, 12); + optPtr->litLengthSum = ZSTD_scaleStats(optPtr->litLengthFreq, MaxLL, 11); + optPtr->matchLengthSum = ZSTD_scaleStats(optPtr->matchLengthFreq, MaxML, 11); + optPtr->offCodeSum = ZSTD_scaleStats(optPtr->offCodeFreq, MaxOff, 11); } ZSTD_setBasePrices(optPtr, optLevel); @@ -225,11 +273,14 @@ static U32 ZSTD_rawLiteralsCost(const BYTE* const literals, U32 const litLength, return (litLength*6) * BITCOST_MULTIPLIER; /* 6 bit per literal - no statistic used */ /* dynamic statistics */ - { U32 price = litLength * optPtr->litSumBasePrice; + { U32 price = optPtr->litSumBasePrice * litLength; + U32 const litPriceMax = optPtr->litSumBasePrice - BITCOST_MULTIPLIER; U32 u; + assert(optPtr->litSumBasePrice >= BITCOST_MULTIPLIER); for (u=0; u < litLength; u++) { - assert(WEIGHT(optPtr->litFreq[literals[u]], optLevel) <= optPtr->litSumBasePrice); /* literal cost should never be negative */ - price -= WEIGHT(optPtr->litFreq[literals[u]], optLevel); + U32 litPrice = WEIGHT(optPtr->litFreq[literals[u]], optLevel); + if (UNLIKELY(litPrice > litPriceMax)) litPrice = litPriceMax; + price -= litPrice; } return price; } @@ -239,7 +290,17 @@ static U32 ZSTD_rawLiteralsCost(const BYTE* const literals, U32 const litLength, * cost of literalLength symbol */ static U32 ZSTD_litLengthPrice(U32 const litLength, const optState_t* const optPtr, int optLevel) { - if (optPtr->priceType == zop_predef) return WEIGHT(litLength, optLevel); + assert(litLength <= ZSTD_BLOCKSIZE_MAX); + if (optPtr->priceType == zop_predef) + return WEIGHT(litLength, optLevel); + + /* ZSTD_LLcode() can't compute litLength price for sizes >= ZSTD_BLOCKSIZE_MAX + * because it isn't representable in the zstd format. + * So instead just pretend it would cost 1 bit more than ZSTD_BLOCKSIZE_MAX - 1. + * In such a case, the block would be all literals. + */ + if (litLength == ZSTD_BLOCKSIZE_MAX) + return BITCOST_MULTIPLIER + ZSTD_litLengthPrice(ZSTD_BLOCKSIZE_MAX - 1, optPtr, optLevel); /* dynamic statistics */ { U32 const llCode = ZSTD_LLcode(litLength); @@ -250,22 +311,25 @@ static U32 ZSTD_litLengthPrice(U32 const litLength, const optState_t* const optP } /* ZSTD_getMatchPrice() : - * Provides the cost of the match part (offset + matchLength) of a sequence + * Provides the cost of the match part (offset + matchLength) of a sequence. * Must be combined with ZSTD_fullLiteralsCost() to get the full cost of a sequence. - * optLevel: when <2, favors small offset for decompression speed (improved cache efficiency) */ + * @offBase : sumtype, representing an offset or a repcode, and using numeric representation of ZSTD_storeSeq() + * @optLevel: when <2, favors small offset for decompression speed (improved cache efficiency) + */ FORCE_INLINE_TEMPLATE U32 -ZSTD_getMatchPrice(U32 const offset, +ZSTD_getMatchPrice(U32 const offBase, U32 const matchLength, const optState_t* const optPtr, int const optLevel) { U32 price; - U32 const offCode = ZSTD_highbit32(offset+1); + U32 const offCode = ZSTD_highbit32(offBase); U32 const mlBase = matchLength - MINMATCH; assert(matchLength >= MINMATCH); - if (optPtr->priceType == zop_predef) /* fixed scheme, do not use statistics */ - return WEIGHT(mlBase, optLevel) + ((16 + offCode) * BITCOST_MULTIPLIER); + if (optPtr->priceType == zop_predef) /* fixed scheme, does not use statistics */ + return WEIGHT(mlBase, optLevel) + + ((16 + offCode) * BITCOST_MULTIPLIER); /* emulated offset cost */ /* dynamic statistics */ price = (offCode * BITCOST_MULTIPLIER) + (optPtr->offCodeSumBasePrice - WEIGHT(optPtr->offCodeFreq[offCode], optLevel)); @@ -284,10 +348,10 @@ ZSTD_getMatchPrice(U32 const offset, } /* ZSTD_updateStats() : - * assumption : literals + litLengtn <= iend */ + * assumption : literals + litLength <= iend */ static void ZSTD_updateStats(optState_t* const optPtr, U32 litLength, const BYTE* literals, - U32 offsetCode, U32 matchLength) + U32 offBase, U32 matchLength) { /* literals */ if (ZSTD_compressedLiterals(optPtr)) { @@ -303,8 +367,8 @@ static void ZSTD_updateStats(optState_t* const optPtr, optPtr->litLengthSum++; } - /* match offset code (0-2=>repCode; 3+=>offset+2) */ - { U32 const offCode = ZSTD_highbit32(offsetCode+1); + /* offset code : follows storeSeq() numeric representation */ + { U32 const offCode = ZSTD_highbit32(offBase); assert(offCode <= MaxOff); optPtr->offCodeFreq[offCode]++; optPtr->offCodeSum++; @@ -338,7 +402,7 @@ MEM_STATIC U32 ZSTD_readMINMATCH(const void* memPtr, U32 length) /* Update hashTable3 up to ip (excluded) Assumption : always within prefix (i.e. not within extDict) */ -static U32 ZSTD_insertAndFindFirstIndexHash3 (ZSTD_matchState_t* ms, +static U32 ZSTD_insertAndFindFirstIndexHash3 (const ZSTD_matchState_t* ms, U32* nextToUpdate3, const BYTE* const ip) { @@ -364,11 +428,13 @@ static U32 ZSTD_insertAndFindFirstIndexHash3 (ZSTD_matchState_t* ms, * Binary Tree search ***************************************/ /** ZSTD_insertBt1() : add one or multiple positions to tree. - * ip : assumed <= iend-8 . + * @param ip assumed <= iend-8 . + * @param target The target of ZSTD_updateTree_internal() - we are filling to this position * @return : nb of positions added */ static U32 ZSTD_insertBt1( - ZSTD_matchState_t* ms, + const ZSTD_matchState_t* ms, const BYTE* const ip, const BYTE* const iend, + U32 const target, U32 const mls, const int extDict) { const ZSTD_compressionParameters* const cParams = &ms->cParams; @@ -391,7 +457,10 @@ static U32 ZSTD_insertBt1( U32* smallerPtr = bt + 2*(curr&btMask); U32* largerPtr = smallerPtr + 1; U32 dummy32; /* to be nullified at the end */ - U32 const windowLow = ms->window.lowLimit; + /* windowLow is based on target because + * we only need positions that will be in the window at the end of the tree update. + */ + U32 const windowLow = ZSTD_getLowestMatchIndex(ms, target, cParams->windowLog); U32 matchEndIdx = curr+8+1; size_t bestLength = 8; U32 nbCompares = 1U << cParams->searchLog; @@ -404,11 +473,12 @@ static U32 ZSTD_insertBt1( DEBUGLOG(8, "ZSTD_insertBt1 (%u)", curr); + assert(curr <= target); assert(ip <= iend-8); /* required for h calculation */ hashTable[h] = curr; /* Update Hash Table */ assert(windowLow > 0); - while (nbCompares-- && (matchIndex >= windowLow)) { + for (; nbCompares && (matchIndex >= windowLow); --nbCompares) { U32* const nextPtr = bt + 2*(matchIndex & btMask); size_t matchLength = MIN(commonLengthSmaller, commonLengthLarger); /* guaranteed minimum nb of common bytes */ assert(matchIndex < curr); @@ -492,7 +562,7 @@ void ZSTD_updateTree_internal( idx, target, dictMode); while(idx < target) { - U32 const forward = ZSTD_insertBt1(ms, base+idx, iend, mls, dictMode == ZSTD_extDict); + U32 const forward = ZSTD_insertBt1(ms, base+idx, iend, target, mls, dictMode == ZSTD_extDict); assert(idx < (U32)(idx + forward)); idx += forward; } @@ -505,16 +575,17 @@ void ZSTD_updateTree(ZSTD_matchState_t* ms, const BYTE* ip, const BYTE* iend) { ZSTD_updateTree_internal(ms, ip, iend, ms->cParams.minMatch, ZSTD_noDict); } -FORCE_INLINE_TEMPLATE -U32 ZSTD_insertBtAndGetAllMatches ( - ZSTD_match_t* matches, /* store result (found matches) in this table (presumed large enough) */ - ZSTD_matchState_t* ms, - U32* nextToUpdate3, - const BYTE* const ip, const BYTE* const iLimit, const ZSTD_dictMode_e dictMode, - const U32 rep[ZSTD_REP_NUM], - U32 const ll0, /* tells if associated literal length is 0 or not. This value must be 0 or 1 */ - const U32 lengthToBeat, - U32 const mls /* template */) +FORCE_INLINE_TEMPLATE U32 +ZSTD_insertBtAndGetAllMatches ( + ZSTD_match_t* matches, /* store result (found matches) in this table (presumed large enough) */ + ZSTD_matchState_t* ms, + U32* nextToUpdate3, + const BYTE* const ip, const BYTE* const iLimit, + const ZSTD_dictMode_e dictMode, + const U32 rep[ZSTD_REP_NUM], + const U32 ll0, /* tells if associated literal length is 0 or not. This value must be 0 or 1 */ + const U32 lengthToBeat, + const U32 mls /* template */) { const ZSTD_compressionParameters* const cParams = &ms->cParams; U32 const sufficient_len = MIN(cParams->targetLength, ZSTD_OPT_NUM -1); @@ -597,7 +668,7 @@ U32 ZSTD_insertBtAndGetAllMatches ( DEBUGLOG(8, "found repCode %u (ll0:%u, offset:%u) of length %u", repCode, ll0, repOffset, repLen); bestLength = repLen; - matches[mnum].off = repCode - ll0; + matches[mnum].off = REPCODE_TO_OFFBASE(repCode - ll0 + 1); /* expect value between 1 and 3 */ matches[mnum].len = (U32)repLen; mnum++; if ( (repLen > sufficient_len) @@ -626,7 +697,7 @@ U32 ZSTD_insertBtAndGetAllMatches ( bestLength = mlen; assert(curr > matchIndex3); assert(mnum==0); /* no prior solution */ - matches[0].off = (curr - matchIndex3) + ZSTD_REP_MOVE; + matches[0].off = OFFSET_TO_OFFBASE(curr - matchIndex3); matches[0].len = (U32)mlen; mnum = 1; if ( (mlen > sufficient_len) | @@ -635,11 +706,11 @@ U32 ZSTD_insertBtAndGetAllMatches ( return 1; } } } /* no dictMatchState lookup: dicts don't have a populated HC3 table */ - } + } /* if (mls == 3) */ hashTable[h] = curr; /* Update Hash Table */ - while (nbCompares-- && (matchIndex >= matchLow)) { + for (; nbCompares && (matchIndex >= matchLow); --nbCompares) { U32* const nextPtr = bt + 2*(matchIndex & btMask); const BYTE* match; size_t matchLength = MIN(commonLengthSmaller, commonLengthLarger); /* guaranteed minimum nb of common bytes */ @@ -659,21 +730,20 @@ U32 ZSTD_insertBtAndGetAllMatches ( } if (matchLength > bestLength) { - DEBUGLOG(8, "found match of length %u at distance %u (offCode=%u)", - (U32)matchLength, curr - matchIndex, curr - matchIndex + ZSTD_REP_MOVE); + DEBUGLOG(8, "found match of length %u at distance %u (offBase=%u)", + (U32)matchLength, curr - matchIndex, OFFSET_TO_OFFBASE(curr - matchIndex)); assert(matchEndIdx > matchIndex); if (matchLength > matchEndIdx - matchIndex) matchEndIdx = matchIndex + (U32)matchLength; bestLength = matchLength; - matches[mnum].off = (curr - matchIndex) + ZSTD_REP_MOVE; + matches[mnum].off = OFFSET_TO_OFFBASE(curr - matchIndex); matches[mnum].len = (U32)matchLength; mnum++; if ( (matchLength > ZSTD_OPT_NUM) | (ip+matchLength == iLimit) /* equal : no way to know if inf or sup */) { if (dictMode == ZSTD_dictMatchState) nbCompares = 0; /* break should also skip searching dms */ break; /* drop, to preserve bt consistency (miss a little bit of compression) */ - } - } + } } if (match[matchLength] < ip[matchLength]) { /* match smaller than current */ @@ -692,12 +762,13 @@ U32 ZSTD_insertBtAndGetAllMatches ( *smallerPtr = *largerPtr = 0; + assert(nbCompares <= (1U << ZSTD_SEARCHLOG_MAX)); /* Check we haven't underflowed. */ if (dictMode == ZSTD_dictMatchState && nbCompares) { size_t const dmsH = ZSTD_hashPtr(ip, dmsHashLog, mls); U32 dictMatchIndex = dms->hashTable[dmsH]; const U32* const dmsBt = dms->chainTable; commonLengthSmaller = commonLengthLarger = 0; - while (nbCompares-- && (dictMatchIndex > dmsLowLimit)) { + for (; nbCompares && (dictMatchIndex > dmsLowLimit); --nbCompares) { const U32* const nextPtr = dmsBt + 2*(dictMatchIndex & dmsBtMask); size_t matchLength = MIN(commonLengthSmaller, commonLengthLarger); /* guaranteed minimum nb of common bytes */ const BYTE* match = dmsBase + dictMatchIndex; @@ -707,19 +778,18 @@ U32 ZSTD_insertBtAndGetAllMatches ( if (matchLength > bestLength) { matchIndex = dictMatchIndex + dmsIndexDelta; - DEBUGLOG(8, "found dms match of length %u at distance %u (offCode=%u)", - (U32)matchLength, curr - matchIndex, curr - matchIndex + ZSTD_REP_MOVE); + DEBUGLOG(8, "found dms match of length %u at distance %u (offBase=%u)", + (U32)matchLength, curr - matchIndex, OFFSET_TO_OFFBASE(curr - matchIndex)); if (matchLength > matchEndIdx - matchIndex) matchEndIdx = matchIndex + (U32)matchLength; bestLength = matchLength; - matches[mnum].off = (curr - matchIndex) + ZSTD_REP_MOVE; + matches[mnum].off = OFFSET_TO_OFFBASE(curr - matchIndex); matches[mnum].len = (U32)matchLength; mnum++; if ( (matchLength > ZSTD_OPT_NUM) | (ip+matchLength == iLimit) /* equal : no way to know if inf or sup */) { break; /* drop, to guarantee consistency (miss a little bit of compression) */ - } - } + } } if (dictMatchIndex <= dmsBtLow) { break; } /* beyond tree size, stop the search */ if (match[matchLength] < ip[matchLength]) { @@ -729,39 +799,91 @@ U32 ZSTD_insertBtAndGetAllMatches ( /* match is larger than current */ commonLengthLarger = matchLength; dictMatchIndex = nextPtr[0]; - } - } - } + } } } /* if (dictMode == ZSTD_dictMatchState) */ assert(matchEndIdx > curr+8); ms->nextToUpdate = matchEndIdx - 8; /* skip repetitive patterns */ return mnum; } - -FORCE_INLINE_TEMPLATE U32 ZSTD_BtGetAllMatches ( - ZSTD_match_t* matches, /* store result (match found, increasing size) in this table */ - ZSTD_matchState_t* ms, - U32* nextToUpdate3, - const BYTE* ip, const BYTE* const iHighLimit, const ZSTD_dictMode_e dictMode, - const U32 rep[ZSTD_REP_NUM], - U32 const ll0, - U32 const lengthToBeat) +typedef U32 (*ZSTD_getAllMatchesFn)( + ZSTD_match_t*, + ZSTD_matchState_t*, + U32*, + const BYTE*, + const BYTE*, + const U32 rep[ZSTD_REP_NUM], + U32 const ll0, + U32 const lengthToBeat); + +FORCE_INLINE_TEMPLATE U32 ZSTD_btGetAllMatches_internal( + ZSTD_match_t* matches, + ZSTD_matchState_t* ms, + U32* nextToUpdate3, + const BYTE* ip, + const BYTE* const iHighLimit, + const U32 rep[ZSTD_REP_NUM], + U32 const ll0, + U32 const lengthToBeat, + const ZSTD_dictMode_e dictMode, + const U32 mls) { - const ZSTD_compressionParameters* const cParams = &ms->cParams; - U32 const matchLengthSearch = cParams->minMatch; - DEBUGLOG(8, "ZSTD_BtGetAllMatches"); - if (ip < ms->window.base + ms->nextToUpdate) return 0; /* skipped area */ - ZSTD_updateTree_internal(ms, ip, iHighLimit, matchLengthSearch, dictMode); - switch(matchLengthSearch) - { - case 3 : return ZSTD_insertBtAndGetAllMatches(matches, ms, nextToUpdate3, ip, iHighLimit, dictMode, rep, ll0, lengthToBeat, 3); - default : - case 4 : return ZSTD_insertBtAndGetAllMatches(matches, ms, nextToUpdate3, ip, iHighLimit, dictMode, rep, ll0, lengthToBeat, 4); - case 5 : return ZSTD_insertBtAndGetAllMatches(matches, ms, nextToUpdate3, ip, iHighLimit, dictMode, rep, ll0, lengthToBeat, 5); - case 7 : - case 6 : return ZSTD_insertBtAndGetAllMatches(matches, ms, nextToUpdate3, ip, iHighLimit, dictMode, rep, ll0, lengthToBeat, 6); + assert(BOUNDED(3, ms->cParams.minMatch, 6) == mls); + DEBUGLOG(8, "ZSTD_BtGetAllMatches(dictMode=%d, mls=%u)", (int)dictMode, mls); + if (ip < ms->window.base + ms->nextToUpdate) + return 0; /* skipped area */ + ZSTD_updateTree_internal(ms, ip, iHighLimit, mls, dictMode); + return ZSTD_insertBtAndGetAllMatches(matches, ms, nextToUpdate3, ip, iHighLimit, dictMode, rep, ll0, lengthToBeat, mls); +} + +#define ZSTD_BT_GET_ALL_MATCHES_FN(dictMode, mls) ZSTD_btGetAllMatches_##dictMode##_##mls + +#define GEN_ZSTD_BT_GET_ALL_MATCHES_(dictMode, mls) \ + static U32 ZSTD_BT_GET_ALL_MATCHES_FN(dictMode, mls)( \ + ZSTD_match_t* matches, \ + ZSTD_matchState_t* ms, \ + U32* nextToUpdate3, \ + const BYTE* ip, \ + const BYTE* const iHighLimit, \ + const U32 rep[ZSTD_REP_NUM], \ + U32 const ll0, \ + U32 const lengthToBeat) \ + { \ + return ZSTD_btGetAllMatches_internal( \ + matches, ms, nextToUpdate3, ip, iHighLimit, \ + rep, ll0, lengthToBeat, ZSTD_##dictMode, mls); \ } + +#define GEN_ZSTD_BT_GET_ALL_MATCHES(dictMode) \ + GEN_ZSTD_BT_GET_ALL_MATCHES_(dictMode, 3) \ + GEN_ZSTD_BT_GET_ALL_MATCHES_(dictMode, 4) \ + GEN_ZSTD_BT_GET_ALL_MATCHES_(dictMode, 5) \ + GEN_ZSTD_BT_GET_ALL_MATCHES_(dictMode, 6) + +GEN_ZSTD_BT_GET_ALL_MATCHES(noDict) +GEN_ZSTD_BT_GET_ALL_MATCHES(extDict) +GEN_ZSTD_BT_GET_ALL_MATCHES(dictMatchState) + +#define ZSTD_BT_GET_ALL_MATCHES_ARRAY(dictMode) \ + { \ + ZSTD_BT_GET_ALL_MATCHES_FN(dictMode, 3), \ + ZSTD_BT_GET_ALL_MATCHES_FN(dictMode, 4), \ + ZSTD_BT_GET_ALL_MATCHES_FN(dictMode, 5), \ + ZSTD_BT_GET_ALL_MATCHES_FN(dictMode, 6) \ + } + +static ZSTD_getAllMatchesFn +ZSTD_selectBtGetAllMatches(ZSTD_matchState_t const* ms, ZSTD_dictMode_e const dictMode) +{ + ZSTD_getAllMatchesFn const getAllMatchesFns[3][4] = { + ZSTD_BT_GET_ALL_MATCHES_ARRAY(noDict), + ZSTD_BT_GET_ALL_MATCHES_ARRAY(extDict), + ZSTD_BT_GET_ALL_MATCHES_ARRAY(dictMatchState) + }; + U32 const mls = BOUNDED(3, ms->cParams.minMatch, 6); + assert((U32)dictMode < 3); + assert(mls - 3 < 4); + return getAllMatchesFns[(int)dictMode][mls - 3]; } /************************* @@ -770,16 +892,18 @@ FORCE_INLINE_TEMPLATE U32 ZSTD_BtGetAllMatches ( /* Struct containing info needed to make decision about ldm inclusion */ typedef struct { - rawSeqStore_t seqStore; /* External match candidates store for this block */ - U32 startPosInBlock; /* Start position of the current match candidate */ - U32 endPosInBlock; /* End position of the current match candidate */ - U32 offset; /* Offset of the match candidate */ + rawSeqStore_t seqStore; /* External match candidates store for this block */ + U32 startPosInBlock; /* Start position of the current match candidate */ + U32 endPosInBlock; /* End position of the current match candidate */ + U32 offset; /* Offset of the match candidate */ } ZSTD_optLdm_t; /* ZSTD_optLdm_skipRawSeqStoreBytes(): - * Moves forward in rawSeqStore by nbBytes, which will update the fields 'pos' and 'posInSequence'. + * Moves forward in @rawSeqStore by @nbBytes, + * which will update the fields 'pos' and 'posInSequence'. */ -static void ZSTD_optLdm_skipRawSeqStoreBytes(rawSeqStore_t* rawSeqStore, size_t nbBytes) { +static void ZSTD_optLdm_skipRawSeqStoreBytes(rawSeqStore_t* rawSeqStore, size_t nbBytes) +{ U32 currPos = (U32)(rawSeqStore->posInSequence + nbBytes); while (currPos && rawSeqStore->pos < rawSeqStore->size) { rawSeq currSeq = rawSeqStore->seq[rawSeqStore->pos]; @@ -800,8 +924,10 @@ static void ZSTD_optLdm_skipRawSeqStoreBytes(rawSeqStore_t* rawSeqStore, size_t * Calculates the beginning and end of the next match in the current block. * Updates 'pos' and 'posInSequence' of the ldmSeqStore. */ -static void ZSTD_opt_getNextMatchAndUpdateSeqStore(ZSTD_optLdm_t* optLdm, U32 currPosInBlock, - U32 blockBytesRemaining) { +static void +ZSTD_opt_getNextMatchAndUpdateSeqStore(ZSTD_optLdm_t* optLdm, U32 currPosInBlock, + U32 blockBytesRemaining) +{ rawSeq currSeq; U32 currBlockEndPos; U32 literalsBytesRemaining; @@ -813,8 +939,8 @@ static void ZSTD_opt_getNextMatchAndUpdateSeqStore(ZSTD_optLdm_t* optLdm, U32 cu optLdm->endPosInBlock = UINT_MAX; return; } - /* Calculate appropriate bytes left in matchLength and litLength after adjusting - based on ldmSeqStore->posInSequence */ + /* Calculate appropriate bytes left in matchLength and litLength + * after adjusting based on ldmSeqStore->posInSequence */ currSeq = optLdm->seqStore.seq[optLdm->seqStore.pos]; assert(optLdm->seqStore.posInSequence <= currSeq.litLength + currSeq.matchLength); currBlockEndPos = currPosInBlock + blockBytesRemaining; @@ -850,15 +976,16 @@ static void ZSTD_opt_getNextMatchAndUpdateSeqStore(ZSTD_optLdm_t* optLdm, U32 cu } /* ZSTD_optLdm_maybeAddMatch(): - * Adds a match if it's long enough, based on it's 'matchStartPosInBlock' - * and 'matchEndPosInBlock', into 'matches'. Maintains the correct ordering of 'matches' + * Adds a match if it's long enough, + * based on it's 'matchStartPosInBlock' and 'matchEndPosInBlock', + * into 'matches'. Maintains the correct ordering of 'matches'. */ static void ZSTD_optLdm_maybeAddMatch(ZSTD_match_t* matches, U32* nbMatches, - ZSTD_optLdm_t* optLdm, U32 currPosInBlock) { - U32 posDiff = currPosInBlock - optLdm->startPosInBlock; - /* Note: ZSTD_match_t actually contains offCode and matchLength (before subtracting MINMATCH) */ - U32 candidateMatchLength = optLdm->endPosInBlock - optLdm->startPosInBlock - posDiff; - U32 candidateOffCode = optLdm->offset + ZSTD_REP_MOVE; + const ZSTD_optLdm_t* optLdm, U32 currPosInBlock) +{ + U32 const posDiff = currPosInBlock - optLdm->startPosInBlock; + /* Note: ZSTD_match_t actually contains offBase and matchLength (before subtracting MINMATCH) */ + U32 const candidateMatchLength = optLdm->endPosInBlock - optLdm->startPosInBlock - posDiff; /* Ensure that current block position is not outside of the match */ if (currPosInBlock < optLdm->startPosInBlock @@ -868,10 +995,11 @@ static void ZSTD_optLdm_maybeAddMatch(ZSTD_match_t* matches, U32* nbMatches, } if (*nbMatches == 0 || ((candidateMatchLength > matches[*nbMatches-1].len) && *nbMatches < ZSTD_OPT_NUM)) { - DEBUGLOG(6, "ZSTD_optLdm_maybeAddMatch(): Adding ldm candidate match (offCode: %u matchLength %u) at block position=%u", - candidateOffCode, candidateMatchLength, currPosInBlock); + U32 const candidateOffBase = OFFSET_TO_OFFBASE(optLdm->offset); + DEBUGLOG(6, "ZSTD_optLdm_maybeAddMatch(): Adding ldm candidate match (offBase: %u matchLength %u) at block position=%u", + candidateOffBase, candidateMatchLength, currPosInBlock); matches[*nbMatches].len = candidateMatchLength; - matches[*nbMatches].off = candidateOffCode; + matches[*nbMatches].off = candidateOffBase; (*nbMatches)++; } } @@ -879,8 +1007,11 @@ static void ZSTD_optLdm_maybeAddMatch(ZSTD_match_t* matches, U32* nbMatches, /* ZSTD_optLdm_processMatchCandidate(): * Wrapper function to update ldm seq store and call ldm functions as necessary. */ -static void ZSTD_optLdm_processMatchCandidate(ZSTD_optLdm_t* optLdm, ZSTD_match_t* matches, U32* nbMatches, - U32 currPosInBlock, U32 remainingBytes) { +static void +ZSTD_optLdm_processMatchCandidate(ZSTD_optLdm_t* optLdm, + ZSTD_match_t* matches, U32* nbMatches, + U32 currPosInBlock, U32 remainingBytes) +{ if (optLdm->seqStore.size == 0 || optLdm->seqStore.pos >= optLdm->seqStore.size) { return; } @@ -891,19 +1022,19 @@ static void ZSTD_optLdm_processMatchCandidate(ZSTD_optLdm_t* optLdm, ZSTD_match_ * at the end of a match from the ldm seq store, and will often be some bytes * over beyond matchEndPosInBlock. As such, we need to correct for these "overshoots" */ - U32 posOvershoot = currPosInBlock - optLdm->endPosInBlock; + U32 const posOvershoot = currPosInBlock - optLdm->endPosInBlock; ZSTD_optLdm_skipRawSeqStoreBytes(&optLdm->seqStore, posOvershoot); - } + } ZSTD_opt_getNextMatchAndUpdateSeqStore(optLdm, currPosInBlock, remainingBytes); } ZSTD_optLdm_maybeAddMatch(matches, nbMatches, optLdm, currPosInBlock); } + /*-******************************* * Optimal parser *********************************/ - static U32 ZSTD_totalLen(ZSTD_optimal_t sol) { return sol.litlen + sol.mlen; @@ -944,6 +1075,8 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, const BYTE* const prefixStart = base + ms->window.dictLimit; const ZSTD_compressionParameters* const cParams = &ms->cParams; + ZSTD_getAllMatchesFn getAllMatches = ZSTD_selectBtGetAllMatches(ms, dictMode); + U32 const sufficient_len = MIN(cParams->targetLength, ZSTD_OPT_NUM -1); U32 const minMatch = (cParams->minMatch == 3) ? 3 : 4; U32 nextToUpdate3 = ms->nextToUpdate; @@ -953,6 +1086,8 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, ZSTD_optimal_t lastSequence; ZSTD_optLdm_t optLdm; + ZSTD_memset(&lastSequence, 0, sizeof(ZSTD_optimal_t)); + optLdm.seqStore = ms->ldmSeqStore ? *ms->ldmSeqStore : kNullRawSeqStore; optLdm.endPosInBlock = optLdm.startPosInBlock = optLdm.offset = 0; ZSTD_opt_getNextMatchAndUpdateSeqStore(&optLdm, (U32)(ip-istart), (U32)(iend-ip)); @@ -971,7 +1106,7 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, /* find first match */ { U32 const litlen = (U32)(ip - anchor); U32 const ll0 = !litlen; - U32 nbMatches = ZSTD_BtGetAllMatches(matches, ms, &nextToUpdate3, ip, iend, dictMode, rep, ll0, minMatch); + U32 nbMatches = getAllMatches(matches, ms, &nextToUpdate3, ip, iend, rep, ll0, minMatch); ZSTD_optLdm_processMatchCandidate(&optLdm, matches, &nbMatches, (U32)(ip-istart), (U32)(iend - ip)); if (!nbMatches) { ip++; continue; } @@ -985,18 +1120,18 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, * in every price. We include the literal length to avoid negative * prices when we subtract the previous literal length. */ - opt[0].price = ZSTD_litLengthPrice(litlen, optStatePtr, optLevel); + opt[0].price = (int)ZSTD_litLengthPrice(litlen, optStatePtr, optLevel); /* large match -> immediate encoding */ { U32 const maxML = matches[nbMatches-1].len; - U32 const maxOffset = matches[nbMatches-1].off; - DEBUGLOG(6, "found %u matches of maxLength=%u and maxOffCode=%u at cPos=%u => start new series", - nbMatches, maxML, maxOffset, (U32)(ip-prefixStart)); + U32 const maxOffBase = matches[nbMatches-1].off; + DEBUGLOG(6, "found %u matches of maxLength=%u and maxOffBase=%u at cPos=%u => start new series", + nbMatches, maxML, maxOffBase, (U32)(ip-prefixStart)); if (maxML > sufficient_len) { lastSequence.litlen = litlen; lastSequence.mlen = maxML; - lastSequence.off = maxOffset; + lastSequence.off = maxOffBase; DEBUGLOG(6, "large match (%u>%u), immediate encoding", maxML, sufficient_len); cur = 0; @@ -1005,24 +1140,25 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, } } /* set prices for first matches starting position == 0 */ - { U32 const literalsPrice = opt[0].price + ZSTD_litLengthPrice(0, optStatePtr, optLevel); + assert(opt[0].price >= 0); + { U32 const literalsPrice = (U32)opt[0].price + ZSTD_litLengthPrice(0, optStatePtr, optLevel); U32 pos; U32 matchNb; for (pos = 1; pos < minMatch; pos++) { opt[pos].price = ZSTD_MAX_PRICE; /* mlen, litlen and price will be fixed during forward scanning */ } for (matchNb = 0; matchNb < nbMatches; matchNb++) { - U32 const offset = matches[matchNb].off; + U32 const offBase = matches[matchNb].off; U32 const end = matches[matchNb].len; for ( ; pos <= end ; pos++ ) { - U32 const matchPrice = ZSTD_getMatchPrice(offset, pos, optStatePtr, optLevel); + U32 const matchPrice = ZSTD_getMatchPrice(offBase, pos, optStatePtr, optLevel); U32 const sequencePrice = literalsPrice + matchPrice; DEBUGLOG(7, "rPos:%u => set initial price : %.2f", - pos, ZSTD_fCost(sequencePrice)); + pos, ZSTD_fCost((int)sequencePrice)); opt[pos].mlen = pos; - opt[pos].off = offset; + opt[pos].off = offBase; opt[pos].litlen = litlen; - opt[pos].price = sequencePrice; + opt[pos].price = (int)sequencePrice; } } last_pos = pos-1; } @@ -1037,9 +1173,9 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, /* Fix current position with one literal if cheaper */ { U32 const litlen = (opt[cur-1].mlen == 0) ? opt[cur-1].litlen + 1 : 1; int const price = opt[cur-1].price - + ZSTD_rawLiteralsCost(ip+cur-1, 1, optStatePtr, optLevel) - + ZSTD_litLengthPrice(litlen, optStatePtr, optLevel) - - ZSTD_litLengthPrice(litlen-1, optStatePtr, optLevel); + + (int)ZSTD_rawLiteralsCost(ip+cur-1, 1, optStatePtr, optLevel) + + (int)ZSTD_litLengthPrice(litlen, optStatePtr, optLevel) + - (int)ZSTD_litLengthPrice(litlen-1, optStatePtr, optLevel); assert(price < 1000000000); /* overflow check */ if (price <= opt[cur].price) { DEBUGLOG(7, "cPos:%zi==rPos:%u : better price (%.2f<=%.2f) using literal (ll==%u) (hist:%u,%u,%u)", @@ -1065,7 +1201,7 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, assert(cur >= opt[cur].mlen); if (opt[cur].mlen != 0) { U32 const prev = cur - opt[cur].mlen; - repcodes_t newReps = ZSTD_updateRep(opt[prev].rep, opt[cur].off, opt[cur].litlen==0); + repcodes_t const newReps = ZSTD_newRep(opt[prev].rep, opt[cur].off, opt[cur].litlen==0); ZSTD_memcpy(opt[cur].rep, &newReps, sizeof(repcodes_t)); } else { ZSTD_memcpy(opt[cur].rep, opt[cur - 1].rep, sizeof(repcodes_t)); @@ -1082,11 +1218,12 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, continue; /* skip unpromising positions; about ~+6% speed, -0.01 ratio */ } + assert(opt[cur].price >= 0); { U32 const ll0 = (opt[cur].mlen != 0); U32 const litlen = (opt[cur].mlen == 0) ? opt[cur].litlen : 0; - U32 const previousPrice = opt[cur].price; + U32 const previousPrice = (U32)opt[cur].price; U32 const basePrice = previousPrice + ZSTD_litLengthPrice(0, optStatePtr, optLevel); - U32 nbMatches = ZSTD_BtGetAllMatches(matches, ms, &nextToUpdate3, inr, iend, dictMode, opt[cur].rep, ll0, minMatch); + U32 nbMatches = getAllMatches(matches, ms, &nextToUpdate3, inr, iend, opt[cur].rep, ll0, minMatch); U32 matchNb; ZSTD_optLdm_processMatchCandidate(&optLdm, matches, &nbMatches, @@ -1119,12 +1256,12 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, U32 const startML = (matchNb>0) ? matches[matchNb-1].len+1 : minMatch; U32 mlen; - DEBUGLOG(7, "testing match %u => offCode=%4u, mlen=%2u, llen=%2u", + DEBUGLOG(7, "testing match %u => offBase=%4u, mlen=%2u, llen=%2u", matchNb, matches[matchNb].off, lastML, litlen); for (mlen = lastML; mlen >= startML; mlen--) { /* scan downward */ U32 const pos = cur + mlen; - int const price = basePrice + ZSTD_getMatchPrice(offset, mlen, optStatePtr, optLevel); + int const price = (int)basePrice + (int)ZSTD_getMatchPrice(offset, mlen, optStatePtr, optLevel); if ((pos > last_pos) || (price < opt[pos].price)) { DEBUGLOG(7, "rPos:%u (ml=%2u) => new better price (%.2f<%.2f)", @@ -1154,7 +1291,7 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, * update them while traversing the sequences. */ if (lastSequence.mlen != 0) { - repcodes_t reps = ZSTD_updateRep(opt[cur].rep, lastSequence.off, lastSequence.litlen==0); + repcodes_t const reps = ZSTD_newRep(opt[cur].rep, lastSequence.off, lastSequence.litlen==0); ZSTD_memcpy(rep, &reps, sizeof(reps)); } else { ZSTD_memcpy(rep, opt[cur].rep, sizeof(repcodes_t)); @@ -1185,7 +1322,7 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, for (storePos=storeStart; storePos <= storeEnd; storePos++) { U32 const llen = opt[storePos].litlen; U32 const mlen = opt[storePos].mlen; - U32 const offCode = opt[storePos].off; + U32 const offBase = opt[storePos].off; U32 const advance = llen + mlen; DEBUGLOG(6, "considering seq starting at %zi, llen=%u, mlen=%u", anchor - istart, (unsigned)llen, (unsigned)mlen); @@ -1197,8 +1334,8 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, } assert(anchor + llen <= iend); - ZSTD_updateStats(optStatePtr, llen, anchor, offCode, mlen); - ZSTD_storeSeq(seqStore, llen, anchor, iend, offCode, mlen-MINMATCH); + ZSTD_updateStats(optStatePtr, llen, anchor, offBase, mlen); + ZSTD_storeSeq(seqStore, llen, anchor, iend, offBase, mlen); anchor += advance; ip = anchor; } } @@ -1210,43 +1347,35 @@ ZSTD_compressBlock_opt_generic(ZSTD_matchState_t* ms, return (size_t)(iend - anchor); } +static size_t ZSTD_compressBlock_opt0( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + const void* src, size_t srcSize, const ZSTD_dictMode_e dictMode) +{ + return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 0 /* optLevel */, dictMode); +} + +static size_t ZSTD_compressBlock_opt2( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + const void* src, size_t srcSize, const ZSTD_dictMode_e dictMode) +{ + return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 2 /* optLevel */, dictMode); +} size_t ZSTD_compressBlock_btopt( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) { DEBUGLOG(5, "ZSTD_compressBlock_btopt"); - return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 0 /*optLevel*/, ZSTD_noDict); + return ZSTD_compressBlock_opt0(ms, seqStore, rep, src, srcSize, ZSTD_noDict); } -/* used in 2-pass strategy */ -static U32 ZSTD_upscaleStat(unsigned* table, U32 lastEltIndex, int bonus) -{ - U32 s, sum=0; - assert(ZSTD_FREQ_DIV+bonus >= 0); - for (s=0; slitSum = ZSTD_upscaleStat(optPtr->litFreq, MaxLit, 0); - optPtr->litLengthSum = ZSTD_upscaleStat(optPtr->litLengthFreq, MaxLL, 0); - optPtr->matchLengthSum = ZSTD_upscaleStat(optPtr->matchLengthFreq, MaxML, 0); - optPtr->offCodeSum = ZSTD_upscaleStat(optPtr->offCodeFreq, MaxOff, 0); -} /* ZSTD_initStats_ultra(): * make a first compression pass, just to seed stats with more accurate starting values. * only works on first block, with no dictionary and no ldm. - * this function cannot error, hence its contract must be respected. + * this function cannot error out, its narrow contract must be respected. */ static void ZSTD_initStats_ultra(ZSTD_matchState_t* ms, @@ -1263,17 +1392,15 @@ ZSTD_initStats_ultra(ZSTD_matchState_t* ms, assert(ms->window.dictLimit == ms->window.lowLimit); /* no dictionary */ assert(ms->window.dictLimit - ms->nextToUpdate <= 1); /* no prefix (note: intentional overflow, defined as 2-complement) */ - ZSTD_compressBlock_opt_generic(ms, seqStore, tmpRep, src, srcSize, 2 /*optLevel*/, ZSTD_noDict); /* generate stats into ms->opt*/ + ZSTD_compressBlock_opt2(ms, seqStore, tmpRep, src, srcSize, ZSTD_noDict); /* generate stats into ms->opt*/ - /* invalidate first scan from history */ + /* invalidate first scan from history, only keep entropy stats */ ZSTD_resetSeqStore(seqStore); ms->window.base -= srcSize; ms->window.dictLimit += (U32)srcSize; ms->window.lowLimit = ms->window.dictLimit; ms->nextToUpdate = ms->window.dictLimit; - /* re-inforce weight of collected statistics */ - ZSTD_upscaleStats(&ms->opt); } size_t ZSTD_compressBlock_btultra( @@ -1281,7 +1408,7 @@ size_t ZSTD_compressBlock_btultra( const void* src, size_t srcSize) { DEBUGLOG(5, "ZSTD_compressBlock_btultra (srcSize=%zu)", srcSize); - return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 2 /*optLevel*/, ZSTD_noDict); + return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_noDict); } size_t ZSTD_compressBlock_btultra2( @@ -1291,53 +1418,53 @@ size_t ZSTD_compressBlock_btultra2( U32 const curr = (U32)((const BYTE*)src - ms->window.base); DEBUGLOG(5, "ZSTD_compressBlock_btultra2 (srcSize=%zu)", srcSize); - /* 2-pass strategy: + /* 2-passes strategy: * this strategy makes a first pass over first block to collect statistics - * and seed next round's statistics with it. - * After 1st pass, function forgets everything, and starts a new block. + * in order to seed next round's statistics with it. + * After 1st pass, function forgets history, and starts a new block. * Consequently, this can only work if no data has been previously loaded in tables, * aka, no dictionary, no prefix, no ldm preprocessing. * The compression ratio gain is generally small (~0.5% on first block), - * the cost is 2x cpu time on first block. */ + ** the cost is 2x cpu time on first block. */ assert(srcSize <= ZSTD_BLOCKSIZE_MAX); if ( (ms->opt.litLengthSum==0) /* first block */ && (seqStore->sequences == seqStore->sequencesStart) /* no ldm */ && (ms->window.dictLimit == ms->window.lowLimit) /* no dictionary */ - && (curr == ms->window.dictLimit) /* start of frame, nothing already loaded nor skipped */ - && (srcSize > ZSTD_PREDEF_THRESHOLD) + && (curr == ms->window.dictLimit) /* start of frame, nothing already loaded nor skipped */ + && (srcSize > ZSTD_PREDEF_THRESHOLD) /* input large enough to not employ default stats */ ) { ZSTD_initStats_ultra(ms, seqStore, rep, src, srcSize); } - return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 2 /*optLevel*/, ZSTD_noDict); + return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_noDict); } size_t ZSTD_compressBlock_btopt_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) { - return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 0 /*optLevel*/, ZSTD_dictMatchState); + return ZSTD_compressBlock_opt0(ms, seqStore, rep, src, srcSize, ZSTD_dictMatchState); } size_t ZSTD_compressBlock_btultra_dictMatchState( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) { - return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 2 /*optLevel*/, ZSTD_dictMatchState); + return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_dictMatchState); } size_t ZSTD_compressBlock_btopt_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) { - return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 0 /*optLevel*/, ZSTD_extDict); + return ZSTD_compressBlock_opt0(ms, seqStore, rep, src, srcSize, ZSTD_extDict); } size_t ZSTD_compressBlock_btultra_extDict( ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], const void* src, size_t srcSize) { - return ZSTD_compressBlock_opt_generic(ms, seqStore, rep, src, srcSize, 2 /*optLevel*/, ZSTD_extDict); + return ZSTD_compressBlock_opt2(ms, seqStore, rep, src, srcSize, ZSTD_extDict); } /* note : no btultra2 variant for extDict nor dictMatchState, diff --git a/Utilities/cmzstd/lib/compress/zstd_opt.h b/Utilities/cmzstd/lib/compress/zstd_opt.h index 627255f53de..342e5a31127 100644 --- a/Utilities/cmzstd/lib/compress/zstd_opt.h +++ b/Utilities/cmzstd/lib/compress/zstd_opt.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/compress/zstdmt_compress.c b/Utilities/cmzstd/lib/compress/zstdmt_compress.c index 22aa3e1245a..6786075569b 100644 --- a/Utilities/cmzstd/lib/compress/zstdmt_compress.c +++ b/Utilities/cmzstd/lib/compress/zstdmt_compress.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -20,6 +20,7 @@ /* ====== Dependencies ====== */ +#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset, INT_MAX, UINT_MAX */ #include "../common/mem.h" /* MEM_STATIC */ #include "../common/pool.h" /* threadpool */ @@ -102,9 +103,8 @@ typedef struct ZSTDMT_bufferPool_s { buffer_t bTable[1]; /* variable size */ } ZSTDMT_bufferPool; -static ZSTDMT_bufferPool* ZSTDMT_createBufferPool(unsigned nbWorkers, ZSTD_customMem cMem) +static ZSTDMT_bufferPool* ZSTDMT_createBufferPool(unsigned maxNbBuffers, ZSTD_customMem cMem) { - unsigned const maxNbBuffers = 2*nbWorkers + 3; ZSTDMT_bufferPool* const bufPool = (ZSTDMT_bufferPool*)ZSTD_customCalloc( sizeof(ZSTDMT_bufferPool) + (maxNbBuffers-1) * sizeof(buffer_t), cMem); if (bufPool==NULL) return NULL; @@ -160,9 +160,8 @@ static void ZSTDMT_setBufferSize(ZSTDMT_bufferPool* const bufPool, size_t const } -static ZSTDMT_bufferPool* ZSTDMT_expandBufferPool(ZSTDMT_bufferPool* srcBufPool, U32 nbWorkers) +static ZSTDMT_bufferPool* ZSTDMT_expandBufferPool(ZSTDMT_bufferPool* srcBufPool, unsigned maxNbBuffers) { - unsigned const maxNbBuffers = 2*nbWorkers + 3; if (srcBufPool==NULL) return NULL; if (srcBufPool->totalBuffers >= maxNbBuffers) /* good enough */ return srcBufPool; @@ -171,7 +170,7 @@ static ZSTDMT_bufferPool* ZSTDMT_expandBufferPool(ZSTDMT_bufferPool* srcBufPool, size_t const bSize = srcBufPool->bufferSize; /* forward parameters */ ZSTDMT_bufferPool* newBufPool; ZSTDMT_freeBufferPool(srcBufPool); - newBufPool = ZSTDMT_createBufferPool(nbWorkers, cMem); + newBufPool = ZSTDMT_createBufferPool(maxNbBuffers, cMem); if (newBufPool==NULL) return newBufPool; ZSTDMT_setBufferSize(newBufPool, bSize); return newBufPool; @@ -263,6 +262,16 @@ static void ZSTDMT_releaseBuffer(ZSTDMT_bufferPool* bufPool, buffer_t buf) ZSTD_customFree(buf.start, bufPool->cMem); } +/* We need 2 output buffers per worker since each dstBuff must be flushed after it is released. + * The 3 additional buffers are as follows: + * 1 buffer for input loading + * 1 buffer for "next input" when submitting current one + * 1 buffer stuck in queue */ +#define BUF_POOL_MAX_NB_BUFFERS(nbWorkers) (2*(nbWorkers) + 3) + +/* After a worker releases its rawSeqStore, it is immediately ready for reuse. + * So we only need one seq buffer per worker. */ +#define SEQ_POOL_MAX_NB_BUFFERS(nbWorkers) (nbWorkers) /* ===== Seq Pool Wrapper ====== */ @@ -316,7 +325,7 @@ static void ZSTDMT_setNbSeq(ZSTDMT_seqPool* const seqPool, size_t const nbSeq) static ZSTDMT_seqPool* ZSTDMT_createSeqPool(unsigned nbWorkers, ZSTD_customMem cMem) { - ZSTDMT_seqPool* const seqPool = ZSTDMT_createBufferPool(nbWorkers, cMem); + ZSTDMT_seqPool* const seqPool = ZSTDMT_createBufferPool(SEQ_POOL_MAX_NB_BUFFERS(nbWorkers), cMem); if (seqPool == NULL) return NULL; ZSTDMT_setNbSeq(seqPool, 0); return seqPool; @@ -329,7 +338,7 @@ static void ZSTDMT_freeSeqPool(ZSTDMT_seqPool* seqPool) static ZSTDMT_seqPool* ZSTDMT_expandSeqPool(ZSTDMT_seqPool* pool, U32 nbWorkers) { - return ZSTDMT_expandBufferPool(pool, nbWorkers); + return ZSTDMT_expandBufferPool(pool, SEQ_POOL_MAX_NB_BUFFERS(nbWorkers)); } @@ -467,7 +476,7 @@ ZSTDMT_serialState_reset(serialState_t* serialState, ZSTD_dictContentType_e dictContentType) { /* Adjust parameters */ - if (params.ldmParams.enableLdm) { + if (params.ldmParams.enableLdm == ZSTD_ps_enable) { DEBUGLOG(4, "LDM window size = %u KB", (1U << params.cParams.windowLog) >> 10); ZSTD_ldm_adjustParameters(¶ms.ldmParams, ¶ms.cParams); assert(params.ldmParams.hashLog >= params.ldmParams.bucketSizeLog); @@ -478,7 +487,7 @@ ZSTDMT_serialState_reset(serialState_t* serialState, serialState->nextJobID = 0; if (params.fParams.checksumFlag) XXH64_reset(&serialState->xxhState, 0); - if (params.ldmParams.enableLdm) { + if (params.ldmParams.enableLdm == ZSTD_ps_enable) { ZSTD_customMem cMem = params.customMem; unsigned const hashLog = params.ldmParams.hashLog; size_t const hashSize = ((size_t)1 << hashLog) * sizeof(ldmEntry_t); @@ -564,7 +573,7 @@ static void ZSTDMT_serialState_update(serialState_t* serialState, /* A future job may error and skip our job */ if (serialState->nextJobID == jobID) { /* It is now our turn, do any processing necessary */ - if (serialState->params.ldmParams.enableLdm) { + if (serialState->params.ldmParams.enableLdm == ZSTD_ps_enable) { size_t error; assert(seqStore.seq != NULL && seqStore.pos == 0 && seqStore.size == 0 && seqStore.capacity > 0); @@ -594,7 +603,7 @@ static void ZSTDMT_serialState_update(serialState_t* serialState, if (seqStore.size > 0) { size_t const err = ZSTD_referenceExternalSequences( jobCCtx, seqStore.seq, seqStore.size); - assert(serialState->params.ldmParams.enableLdm); + assert(serialState->params.ldmParams.enableLdm == ZSTD_ps_enable); assert(!ZSTD_isError(err)); (void)err; } @@ -672,7 +681,7 @@ static void ZSTDMT_compressionJob(void* jobDescription) if (dstBuff.start==NULL) JOB_ERROR(ERROR(memory_allocation)); job->dstBuff = dstBuff; /* this value can be read in ZSTDMT_flush, when it copies the whole job */ } - if (jobParams.ldmParams.enableLdm && rawSeqStore.seq == NULL) + if (jobParams.ldmParams.enableLdm == ZSTD_ps_enable && rawSeqStore.seq == NULL) JOB_ERROR(ERROR(memory_allocation)); /* Don't compute the checksum for chunks, since we compute it externally, @@ -680,7 +689,7 @@ static void ZSTDMT_compressionJob(void* jobDescription) */ if (job->jobID != 0) jobParams.fParams.checksumFlag = 0; /* Don't run LDM for the chunks, since we handle it externally */ - jobParams.ldmParams.enableLdm = 0; + jobParams.ldmParams.enableLdm = ZSTD_ps_disable; /* Correct nbWorkers to 0. */ jobParams.nbWorkers = 0; @@ -711,7 +720,7 @@ static void ZSTDMT_compressionJob(void* jobDescription) ZSTDMT_serialState_update(job->serial, cctx, rawSeqStore, job->src, job->jobID); if (!job->firstJob) { /* flush and overwrite frame header when it's not first job */ - size_t const hSize = ZSTD_compressContinue(cctx, dstBuff.start, dstBuff.capacity, job->src.start, 0); + size_t const hSize = ZSTD_compressContinue_public(cctx, dstBuff.start, dstBuff.capacity, job->src.start, 0); if (ZSTD_isError(hSize)) JOB_ERROR(hSize); DEBUGLOG(5, "ZSTDMT_compressionJob: flush and overwrite %u bytes of frame header (not first job)", (U32)hSize); ZSTD_invalidateRepCodes(cctx); @@ -729,7 +738,7 @@ static void ZSTDMT_compressionJob(void* jobDescription) DEBUGLOG(5, "ZSTDMT_compressionJob: compress %u bytes in %i blocks", (U32)job->src.size, nbChunks); assert(job->cSize == 0); for (chunkNb = 1; chunkNb < nbChunks; chunkNb++) { - size_t const cSize = ZSTD_compressContinue(cctx, op, oend-op, ip, chunkSize); + size_t const cSize = ZSTD_compressContinue_public(cctx, op, oend-op, ip, chunkSize); if (ZSTD_isError(cSize)) JOB_ERROR(cSize); ip += chunkSize; op += cSize; assert(op < oend); @@ -749,8 +758,8 @@ static void ZSTDMT_compressionJob(void* jobDescription) size_t const lastBlockSize1 = job->src.size & (chunkSize-1); size_t const lastBlockSize = ((lastBlockSize1==0) & (job->src.size>=chunkSize)) ? chunkSize : lastBlockSize1; size_t const cSize = (job->lastJob) ? - ZSTD_compressEnd (cctx, op, oend-op, ip, lastBlockSize) : - ZSTD_compressContinue(cctx, op, oend-op, ip, lastBlockSize); + ZSTD_compressEnd_public(cctx, op, oend-op, ip, lastBlockSize) : + ZSTD_compressContinue_public(cctx, op, oend-op, ip, lastBlockSize); if (ZSTD_isError(cSize)) JOB_ERROR(cSize); lastCBlockSize = cSize; } } @@ -807,6 +816,15 @@ typedef struct { static const roundBuff_t kNullRoundBuff = {NULL, 0, 0}; #define RSYNC_LENGTH 32 +/* Don't create chunks smaller than the zstd block size. + * This stops us from regressing compression ratio too much, + * and ensures our output fits in ZSTD_compressBound(). + * + * If this is shrunk < ZSTD_BLOCKSIZELOG_MIN then + * ZSTD_COMPRESSBOUND() will need to be updated. + */ +#define RSYNC_MIN_BLOCK_LOG ZSTD_BLOCKSIZELOG_MAX +#define RSYNC_MIN_BLOCK_SIZE (1<jobs = ZSTDMT_createJobsTable(&nbJobs, cMem); assert(nbJobs > 0); assert((nbJobs & (nbJobs - 1)) == 0); /* ensure nbJobs is a power of 2 */ mtctx->jobIDMask = nbJobs - 1; - mtctx->bufPool = ZSTDMT_createBufferPool(nbWorkers, cMem); + mtctx->bufPool = ZSTDMT_createBufferPool(BUF_POOL_MAX_NB_BUFFERS(nbWorkers), cMem); mtctx->cctxPool = ZSTDMT_createCCtxPool(nbWorkers, cMem); mtctx->seqPool = ZSTDMT_createSeqPool(nbWorkers, cMem); initError = ZSTDMT_serialState_init(&mtctx->serial); @@ -1030,7 +1048,7 @@ static size_t ZSTDMT_resize(ZSTDMT_CCtx* mtctx, unsigned nbWorkers) { if (POOL_resize(mtctx->factory, nbWorkers)) return ERROR(memory_allocation); FORWARD_IF_ERROR( ZSTDMT_expandJobsTable(mtctx, nbWorkers) , ""); - mtctx->bufPool = ZSTDMT_expandBufferPool(mtctx->bufPool, nbWorkers); + mtctx->bufPool = ZSTDMT_expandBufferPool(mtctx->bufPool, BUF_POOL_MAX_NB_BUFFERS(nbWorkers)); if (mtctx->bufPool == NULL) return ERROR(memory_allocation); mtctx->cctxPool = ZSTDMT_expandCCtxPool(mtctx->cctxPool, nbWorkers); if (mtctx->cctxPool == NULL) return ERROR(memory_allocation); @@ -1135,7 +1153,7 @@ size_t ZSTDMT_toFlushNow(ZSTDMT_CCtx* mtctx) static unsigned ZSTDMT_computeTargetJobLog(const ZSTD_CCtx_params* params) { unsigned jobLog; - if (params->ldmParams.enableLdm) { + if (params->ldmParams.enableLdm == ZSTD_ps_enable) { /* In Long Range Mode, the windowLog is typically oversized. * In which case, it's preferable to determine the jobSize * based on cycleLog instead. */ @@ -1179,7 +1197,7 @@ static size_t ZSTDMT_computeOverlapSize(const ZSTD_CCtx_params* params) int const overlapRLog = 9 - ZSTDMT_overlapLog(params->overlapLog, params->cParams.strategy); int ovLog = (overlapRLog >= 8) ? 0 : (params->cParams.windowLog - overlapRLog); assert(0 <= overlapRLog && overlapRLog <= 8); - if (params->ldmParams.enableLdm) { + if (params->ldmParams.enableLdm == ZSTD_ps_enable) { /* In Long Range Mode, the windowLog is typically oversized. * In which case, it's preferable to determine the jobSize * based on chainLog instead. @@ -1252,6 +1270,9 @@ size_t ZSTDMT_initCStream_internal( /* Aim for the targetsectionSize as the average job size. */ U32 const jobSizeKB = (U32)(mtctx->targetSectionSize >> 10); U32 const rsyncBits = (assert(jobSizeKB >= 1), ZSTD_highbit32(jobSizeKB) + 10); + /* We refuse to create jobs < RSYNC_MIN_BLOCK_SIZE bytes, so make sure our + * expected job size is at least 4x larger. */ + assert(rsyncBits >= RSYNC_MIN_BLOCK_LOG + 2); DEBUGLOG(4, "rsyncLog = %u", rsyncBits); mtctx->rsync.hash = 0; mtctx->rsync.hitMask = (1ULL << rsyncBits) - 1; @@ -1263,7 +1284,7 @@ size_t ZSTDMT_initCStream_internal( ZSTDMT_setBufferSize(mtctx->bufPool, ZSTD_compressBound(mtctx->targetSectionSize)); { /* If ldm is enabled we need windowSize space. */ - size_t const windowSize = mtctx->params.ldmParams.enableLdm ? (1U << mtctx->params.cParams.windowLog) : 0; + size_t const windowSize = mtctx->params.ldmParams.enableLdm == ZSTD_ps_enable ? (1U << mtctx->params.cParams.windowLog) : 0; /* Two buffers of slack, plus extra space for the overlap * This is the minimum slack that LDM works with. One extra because * flush might waste up to targetSectionSize-1 bytes. Another extra @@ -1538,17 +1559,21 @@ static range_t ZSTDMT_getInputDataInUse(ZSTDMT_CCtx* mtctx) static int ZSTDMT_isOverlapped(buffer_t buffer, range_t range) { BYTE const* const bufferStart = (BYTE const*)buffer.start; - BYTE const* const bufferEnd = bufferStart + buffer.capacity; BYTE const* const rangeStart = (BYTE const*)range.start; - BYTE const* const rangeEnd = range.size != 0 ? rangeStart + range.size : rangeStart; if (rangeStart == NULL || bufferStart == NULL) return 0; - /* Empty ranges cannot overlap */ - if (bufferStart == bufferEnd || rangeStart == rangeEnd) - return 0; - return bufferStart < rangeEnd && rangeStart < bufferEnd; + { + BYTE const* const bufferEnd = bufferStart + buffer.capacity; + BYTE const* const rangeEnd = rangeStart + range.size; + + /* Empty ranges cannot overlap */ + if (bufferStart == bufferEnd || rangeStart == rangeEnd) + return 0; + + return bufferStart < rangeEnd && rangeStart < bufferEnd; + } } static int ZSTDMT_doesOverlapWindow(buffer_t buffer, ZSTD_window_t window) @@ -1575,7 +1600,7 @@ static int ZSTDMT_doesOverlapWindow(buffer_t buffer, ZSTD_window_t window) static void ZSTDMT_waitForLdmComplete(ZSTDMT_CCtx* mtctx, buffer_t buffer) { - if (mtctx->params.ldmParams.enableLdm) { + if (mtctx->params.ldmParams.enableLdm == ZSTD_ps_enable) { ZSTD_pthread_mutex_t* mutex = &mtctx->serial.ldmWindowMutex; DEBUGLOG(5, "ZSTDMT_waitForLdmComplete"); DEBUGLOG(5, "source [0x%zx, 0x%zx)", @@ -1678,6 +1703,11 @@ findSynchronizationPoint(ZSTDMT_CCtx const* mtctx, ZSTD_inBuffer const input) if (!mtctx->params.rsyncable) /* Rsync is disabled. */ return syncPoint; + if (mtctx->inBuff.filled + input.size - input.pos < RSYNC_MIN_BLOCK_SIZE) + /* We don't emit synchronization points if it would produce too small blocks. + * We don't have enough input to find a synchronization point, so don't look. + */ + return syncPoint; if (mtctx->inBuff.filled + syncPoint.toLoad < RSYNC_LENGTH) /* Not enough to compute the hash. * We will miss any synchronization points in this RSYNC_LENGTH byte @@ -1688,10 +1718,28 @@ findSynchronizationPoint(ZSTDMT_CCtx const* mtctx, ZSTD_inBuffer const input) */ return syncPoint; /* Initialize the loop variables. */ - if (mtctx->inBuff.filled >= RSYNC_LENGTH) { - /* We have enough bytes buffered to initialize the hash. + if (mtctx->inBuff.filled < RSYNC_MIN_BLOCK_SIZE) { + /* We don't need to scan the first RSYNC_MIN_BLOCK_SIZE positions + * because they can't possibly be a sync point. So we can start + * part way through the input buffer. + */ + pos = RSYNC_MIN_BLOCK_SIZE - mtctx->inBuff.filled; + if (pos >= RSYNC_LENGTH) { + prev = istart + pos - RSYNC_LENGTH; + hash = ZSTD_rollingHash_compute(prev, RSYNC_LENGTH); + } else { + assert(mtctx->inBuff.filled >= RSYNC_LENGTH); + prev = (BYTE const*)mtctx->inBuff.buffer.start + mtctx->inBuff.filled - RSYNC_LENGTH; + hash = ZSTD_rollingHash_compute(prev + pos, (RSYNC_LENGTH - pos)); + hash = ZSTD_rollingHash_append(hash, istart, pos); + } + } else { + /* We have enough bytes buffered to initialize the hash, + * and have processed enough bytes to find a sync point. * Start scanning at the beginning of the input. */ + assert(mtctx->inBuff.filled >= RSYNC_MIN_BLOCK_SIZE); + assert(RSYNC_MIN_BLOCK_SIZE >= RSYNC_LENGTH); pos = 0; prev = (BYTE const*)mtctx->inBuff.buffer.start + mtctx->inBuff.filled - RSYNC_LENGTH; hash = ZSTD_rollingHash_compute(prev, RSYNC_LENGTH); @@ -1705,16 +1753,6 @@ findSynchronizationPoint(ZSTDMT_CCtx const* mtctx, ZSTD_inBuffer const input) syncPoint.flush = 1; return syncPoint; } - } else { - /* We don't have enough bytes buffered to initialize the hash, but - * we know we have at least RSYNC_LENGTH bytes total. - * Start scanning after the first RSYNC_LENGTH bytes less the bytes - * already buffered. - */ - pos = RSYNC_LENGTH - mtctx->inBuff.filled; - prev = (BYTE const*)mtctx->inBuff.buffer.start - pos; - hash = ZSTD_rollingHash_compute(mtctx->inBuff.buffer.start, mtctx->inBuff.filled); - hash = ZSTD_rollingHash_append(hash, istart, pos); } /* Starting with the hash of the previous RSYNC_LENGTH bytes, roll * through the input. If we hit a synchronization point, then cut the @@ -1724,16 +1762,24 @@ findSynchronizationPoint(ZSTDMT_CCtx const* mtctx, ZSTD_inBuffer const input) * then a block will be emitted anyways, but this is okay, since if we * are already synchronized we will remain synchronized. */ + assert(pos < RSYNC_LENGTH || ZSTD_rollingHash_compute(istart + pos - RSYNC_LENGTH, RSYNC_LENGTH) == hash); for (; pos < syncPoint.toLoad; ++pos) { BYTE const toRemove = pos < RSYNC_LENGTH ? prev[pos] : istart[pos - RSYNC_LENGTH]; - /* if (pos >= RSYNC_LENGTH) assert(ZSTD_rollingHash_compute(istart + pos - RSYNC_LENGTH, RSYNC_LENGTH) == hash); */ + /* This assert is very expensive, and Debian compiles with asserts enabled. + * So disable it for now. We can get similar coverage by checking it at the + * beginning & end of the loop. + * assert(pos < RSYNC_LENGTH || ZSTD_rollingHash_compute(istart + pos - RSYNC_LENGTH, RSYNC_LENGTH) == hash); + */ hash = ZSTD_rollingHash_rotate(hash, toRemove, istart[pos], primePower); + assert(mtctx->inBuff.filled + pos >= RSYNC_MIN_BLOCK_SIZE); if ((hash & hitMask) == hitMask) { syncPoint.toLoad = pos + 1; syncPoint.flush = 1; + ++pos; /* for assert */ break; } } + assert(pos < RSYNC_LENGTH || ZSTD_rollingHash_compute(istart + pos - RSYNC_LENGTH, RSYNC_LENGTH) == hash); return syncPoint; } diff --git a/Utilities/cmzstd/lib/compress/zstdmt_compress.h b/Utilities/cmzstd/lib/compress/zstdmt_compress.h index 2fee2ec7455..ed4dc0e99df 100644 --- a/Utilities/cmzstd/lib/compress/zstdmt_compress.h +++ b/Utilities/cmzstd/lib/compress/zstdmt_compress.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -65,8 +65,11 @@ size_t ZSTDMT_nextInputSizeHint(const ZSTDMT_CCtx* mtctx); * Private use only. Init streaming operation. * expects params to be valid. * must receive dict, or cdict, or none, but not both. + * mtctx can be freshly constructed or reused from a prior compression. + * If mtctx is reused, memory allocations from the prior compression may not be freed, + * even if they are not needed for the current compression. * @return : 0, or an error code */ -size_t ZSTDMT_initCStream_internal(ZSTDMT_CCtx* zcs, +size_t ZSTDMT_initCStream_internal(ZSTDMT_CCtx* mtctx, const void* dict, size_t dictSize, ZSTD_dictContentType_e dictContentType, const ZSTD_CDict* cdict, ZSTD_CCtx_params params, unsigned long long pledgedSrcSize); diff --git a/Utilities/cmzstd/lib/decompress/huf_decompress.c b/Utilities/cmzstd/lib/decompress/huf_decompress.c index b93c9a003b4..5b217ac586c 100644 --- a/Utilities/cmzstd/lib/decompress/huf_decompress.c +++ b/Utilities/cmzstd/lib/decompress/huf_decompress.c @@ -1,7 +1,7 @@ /* ****************************************************************** * huff0 huffman decoder, * part of Finite State Entropy library - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * You can contact the author at : * - FSE+HUF source repository : https://github.com/Cyan4973/FiniteStateEntropy @@ -19,9 +19,16 @@ #include "../common/compiler.h" #include "../common/bitstream.h" /* BIT_* */ #include "../common/fse.h" /* to compress headers */ -#define HUF_STATIC_LINKING_ONLY #include "../common/huf.h" #include "../common/error_private.h" +#include "../common/zstd_internal.h" +#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_countTrailingZeros64 */ + +/* ************************************************************** +* Constants +****************************************************************/ + +#define HUF_DECODER_FAST_TABLELOG 11 /* ************************************************************** * Macros @@ -36,6 +43,28 @@ #error "Cannot force the use of the X1 and X2 decoders at the same time!" #endif +/* When DYNAMIC_BMI2 is enabled, fast decoders are only called when bmi2 is + * supported at runtime, so we can add the BMI2 target attribute. + * When it is disabled, we will still get BMI2 if it is enabled statically. + */ +#if DYNAMIC_BMI2 +# define HUF_FAST_BMI2_ATTRS BMI2_TARGET_ATTRIBUTE +#else +# define HUF_FAST_BMI2_ATTRS +#endif + +#ifdef __cplusplus +# define HUF_EXTERN_C extern "C" +#else +# define HUF_EXTERN_C +#endif +#define HUF_ASM_DECL HUF_EXTERN_C + +#if DYNAMIC_BMI2 +# define HUF_NEED_BMI2_FUNCTION 1 +#else +# define HUF_NEED_BMI2_FUNCTION 0 +#endif /* ************************************************************** * Error Management @@ -53,6 +82,11 @@ /* ************************************************************** * BMI2 Variant Wrappers ****************************************************************/ +typedef size_t (*HUF_DecompressUsingDTableFn)(void *dst, size_t dstSize, + const void *cSrc, + size_t cSrcSize, + const HUF_DTable *DTable); + #if DYNAMIC_BMI2 #define HUF_DGEN(fn) \ @@ -65,7 +99,7 @@ return fn##_body(dst, dstSize, cSrc, cSrcSize, DTable); \ } \ \ - static TARGET_ATTRIBUTE("bmi2") size_t fn##_bmi2( \ + static BMI2_TARGET_ATTRIBUTE size_t fn##_bmi2( \ void* dst, size_t dstSize, \ const void* cSrc, size_t cSrcSize, \ const HUF_DTable* DTable) \ @@ -74,9 +108,9 @@ } \ \ static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ - size_t cSrcSize, HUF_DTable const* DTable, int bmi2) \ + size_t cSrcSize, HUF_DTable const* DTable, int flags) \ { \ - if (bmi2) { \ + if (flags & HUF_flags_bmi2) { \ return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \ } \ return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \ @@ -86,9 +120,9 @@ #define HUF_DGEN(fn) \ static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ - size_t cSrcSize, HUF_DTable const* DTable, int bmi2) \ + size_t cSrcSize, HUF_DTable const* DTable, int flags) \ { \ - (void)bmi2; \ + (void)flags; \ return fn##_body(dst, dstSize, cSrc, cSrcSize, DTable); \ } @@ -107,13 +141,164 @@ static DTableDesc HUF_getDTableDesc(const HUF_DTable* table) return dtd; } +static size_t HUF_initFastDStream(BYTE const* ip) { + BYTE const lastByte = ip[7]; + size_t const bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0; + size_t const value = MEM_readLEST(ip) | 1; + assert(bitsConsumed <= 8); + assert(sizeof(size_t) == 8); + return value << bitsConsumed; +} + + +/** + * The input/output arguments to the Huffman fast decoding loop: + * + * ip [in/out] - The input pointers, must be updated to reflect what is consumed. + * op [in/out] - The output pointers, must be updated to reflect what is written. + * bits [in/out] - The bitstream containers, must be updated to reflect the current state. + * dt [in] - The decoding table. + * ilimit [in] - The input limit, stop when any input pointer is below ilimit. + * oend [in] - The end of the output stream. op[3] must not cross oend. + * iend [in] - The end of each input stream. ip[i] may cross iend[i], + * as long as it is above ilimit, but that indicates corruption. + */ +typedef struct { + BYTE const* ip[4]; + BYTE* op[4]; + U64 bits[4]; + void const* dt; + BYTE const* ilimit; + BYTE* oend; + BYTE const* iend[4]; +} HUF_DecompressFastArgs; + +typedef void (*HUF_DecompressFastLoopFn)(HUF_DecompressFastArgs*); + +/** + * Initializes args for the fast decoding loop. + * @returns 1 on success + * 0 if the fallback implementation should be used. + * Or an error code on failure. + */ +static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* dst, size_t dstSize, void const* src, size_t srcSize, const HUF_DTable* DTable) +{ + void const* dt = DTable + 1; + U32 const dtLog = HUF_getDTableDesc(DTable).tableLog; + + const BYTE* const ilimit = (const BYTE*)src + 6 + 8; + + BYTE* const oend = (BYTE*)dst + dstSize; + + /* The fast decoding loop assumes 64-bit little-endian. + * This condition is false on x32. + */ + if (!MEM_isLittleEndian() || MEM_32bits()) + return 0; + + /* strict minimum : jump table + 1 byte per stream */ + if (srcSize < 10) + return ERROR(corruption_detected); + + /* Must have at least 8 bytes per stream because we don't handle initializing smaller bit containers. + * If table log is not correct at this point, fallback to the old decoder. + * On small inputs we don't have enough data to trigger the fast loop, so use the old decoder. + */ + if (dtLog != HUF_DECODER_FAST_TABLELOG) + return 0; + + /* Read the jump table. */ + { + const BYTE* const istart = (const BYTE*)src; + size_t const length1 = MEM_readLE16(istart); + size_t const length2 = MEM_readLE16(istart+2); + size_t const length3 = MEM_readLE16(istart+4); + size_t const length4 = srcSize - (length1 + length2 + length3 + 6); + args->iend[0] = istart + 6; /* jumpTable */ + args->iend[1] = args->iend[0] + length1; + args->iend[2] = args->iend[1] + length2; + args->iend[3] = args->iend[2] + length3; + + /* HUF_initFastDStream() requires this, and this small of an input + * won't benefit from the ASM loop anyways. + * length1 must be >= 16 so that ip[0] >= ilimit before the loop + * starts. + */ + if (length1 < 16 || length2 < 8 || length3 < 8 || length4 < 8) + return 0; + if (length4 > srcSize) return ERROR(corruption_detected); /* overflow */ + } + /* ip[] contains the position that is currently loaded into bits[]. */ + args->ip[0] = args->iend[1] - sizeof(U64); + args->ip[1] = args->iend[2] - sizeof(U64); + args->ip[2] = args->iend[3] - sizeof(U64); + args->ip[3] = (BYTE const*)src + srcSize - sizeof(U64); + + /* op[] contains the output pointers. */ + args->op[0] = (BYTE*)dst; + args->op[1] = args->op[0] + (dstSize+3)/4; + args->op[2] = args->op[1] + (dstSize+3)/4; + args->op[3] = args->op[2] + (dstSize+3)/4; + + /* No point to call the ASM loop for tiny outputs. */ + if (args->op[3] >= oend) + return 0; + + /* bits[] is the bit container. + * It is read from the MSB down to the LSB. + * It is shifted left as it is read, and zeros are + * shifted in. After the lowest valid bit a 1 is + * set, so that CountTrailingZeros(bits[]) can be used + * to count how many bits we've consumed. + */ + args->bits[0] = HUF_initFastDStream(args->ip[0]); + args->bits[1] = HUF_initFastDStream(args->ip[1]); + args->bits[2] = HUF_initFastDStream(args->ip[2]); + args->bits[3] = HUF_initFastDStream(args->ip[3]); + + /* If ip[] >= ilimit, it is guaranteed to be safe to + * reload bits[]. It may be beyond its section, but is + * guaranteed to be valid (>= istart). + */ + args->ilimit = ilimit; + + args->oend = oend; + args->dt = dt; + + return 1; +} + +static size_t HUF_initRemainingDStream(BIT_DStream_t* bit, HUF_DecompressFastArgs const* args, int stream, BYTE* segmentEnd) +{ + /* Validate that we haven't overwritten. */ + if (args->op[stream] > segmentEnd) + return ERROR(corruption_detected); + /* Validate that we haven't read beyond iend[]. + * Note that ip[] may be < iend[] because the MSB is + * the next bit to read, and we may have consumed 100% + * of the stream, so down to iend[i] - 8 is valid. + */ + if (args->ip[stream] < args->iend[stream] - 8) + return ERROR(corruption_detected); + + /* Construct the BIT_DStream_t. */ + assert(sizeof(size_t) == 8); + bit->bitContainer = MEM_readLEST(args->ip[stream]); + bit->bitsConsumed = ZSTD_countTrailingZeros64(args->bits[stream]); + bit->start = (const char*)args->iend[0]; + bit->limitPtr = bit->start + sizeof(size_t); + bit->ptr = (const char*)args->ip[stream]; + + return 0; +} + #ifndef HUF_FORCE_DECOMPRESS_X2 /*-***************************/ /* single-symbol decoding */ /*-***************************/ -typedef struct { BYTE byte; BYTE nbBits; } HUF_DEltX1; /* single-symbol decoding */ +typedef struct { BYTE nbBits; BYTE byte; } HUF_DEltX1; /* single-symbol decoding */ /** * Packs 4 HUF_DEltX1 structs into a U64. This is used to lay down 4 entries at @@ -122,14 +307,45 @@ typedef struct { BYTE byte; BYTE nbBits; } HUF_DEltX1; /* single-symbol decodi static U64 HUF_DEltX1_set4(BYTE symbol, BYTE nbBits) { U64 D4; if (MEM_isLittleEndian()) { - D4 = symbol + (nbBits << 8); + D4 = (U64)((symbol << 8) + nbBits); } else { - D4 = (symbol << 8) + nbBits; + D4 = (U64)(symbol + (nbBits << 8)); } + assert(D4 < (1U << 16)); D4 *= 0x0001000100010001ULL; return D4; } +/** + * Increase the tableLog to targetTableLog and rescales the stats. + * If tableLog > targetTableLog this is a no-op. + * @returns New tableLog + */ +static U32 HUF_rescaleStats(BYTE* huffWeight, U32* rankVal, U32 nbSymbols, U32 tableLog, U32 targetTableLog) +{ + if (tableLog > targetTableLog) + return tableLog; + if (tableLog < targetTableLog) { + U32 const scale = targetTableLog - tableLog; + U32 s; + /* Increase the weight for all non-zero probability symbols by scale. */ + for (s = 0; s < nbSymbols; ++s) { + huffWeight[s] += (BYTE)((huffWeight[s] == 0) ? 0 : scale); + } + /* Update rankVal to reflect the new weights. + * All weights except 0 get moved to weight + scale. + * Weights [1, scale] are empty. + */ + for (s = targetTableLog; s > scale; --s) { + rankVal[s] = rankVal[s - scale]; + } + for (s = scale; s > 0; --s) { + rankVal[s] = 0; + } + } + return targetTableLog; +} + typedef struct { U32 rankVal[HUF_TABLELOG_ABSOLUTEMAX + 1]; U32 rankStart[HUF_TABLELOG_ABSOLUTEMAX + 1]; @@ -138,13 +354,7 @@ typedef struct { BYTE huffWeight[HUF_SYMBOLVALUE_MAX + 1]; } HUF_ReadDTableX1_Workspace; - -size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize) -{ - return HUF_readDTableX1_wksp_bmi2(DTable, src, srcSize, workSpace, wkspSize, /* bmi2 */ 0); -} - -size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int bmi2) +size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags) { U32 tableLog = 0; U32 nbSymbols = 0; @@ -159,11 +369,15 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr DEBUG_STATIC_ASSERT(sizeof(DTableDesc) == sizeof(HUF_DTable)); /* ZSTD_memset(huffWeight, 0, sizeof(huffWeight)); */ /* is not necessary, even though some analyzer complain ... */ - iSize = HUF_readStats_wksp(wksp->huffWeight, HUF_SYMBOLVALUE_MAX + 1, wksp->rankVal, &nbSymbols, &tableLog, src, srcSize, wksp->statsWksp, sizeof(wksp->statsWksp), bmi2); + iSize = HUF_readStats_wksp(wksp->huffWeight, HUF_SYMBOLVALUE_MAX + 1, wksp->rankVal, &nbSymbols, &tableLog, src, srcSize, wksp->statsWksp, sizeof(wksp->statsWksp), flags); if (HUF_isError(iSize)) return iSize; + /* Table header */ { DTableDesc dtd = HUF_getDTableDesc(DTable); + U32 const maxTableLog = dtd.maxTableLog + 1; + U32 const targetTableLog = MIN(maxTableLog, HUF_DECODER_FAST_TABLELOG); + tableLog = HUF_rescaleStats(wksp->huffWeight, wksp->rankVal, nbSymbols, tableLog, targetTableLog); if (tableLog > (U32)(dtd.maxTableLog+1)) return ERROR(tableLog_tooLarge); /* DTable too small, Huffman tree cannot fit in */ dtd.tableType = 0; dtd.tableLog = (BYTE)tableLog; @@ -182,9 +396,8 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr * rankStart[0] is not filled because there are no entries in the table for * weight 0. */ - { - int n; - int nextRankStart = 0; + { int n; + U32 nextRankStart = 0; int const unroll = 4; int const nLimit = (int)nbSymbols - unroll + 1; for (n=0; n<(int)tableLog+1; n++) { @@ -207,14 +420,13 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr /* fill DTable * We fill all entries of each weight in order. - * That way length is a constant for each iteration of the outter loop. + * That way length is a constant for each iteration of the outer loop. * We can switch based on the length to a different inner loop which is * optimized for that particular case. */ - { - U32 w; - int symbol=wksp->rankVal[0]; - int rankStart=0; + { U32 w; + int symbol = wksp->rankVal[0]; + int rankStart = 0; for (w=1; wrankVal[w]; int const length = (1 << w) >> 1; @@ -304,11 +516,15 @@ HUF_decodeStreamX1(BYTE* p, BIT_DStream_t* const bitDPtr, BYTE* const pEnd, cons BYTE* const pStart = p; /* up to 4 symbols at a time */ - while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-3)) { - HUF_DECODE_SYMBOLX1_2(p, bitDPtr); - HUF_DECODE_SYMBOLX1_1(p, bitDPtr); - HUF_DECODE_SYMBOLX1_2(p, bitDPtr); - HUF_DECODE_SYMBOLX1_0(p, bitDPtr); + if ((pEnd - p) > 3) { + while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-3)) { + HUF_DECODE_SYMBOLX1_2(p, bitDPtr); + HUF_DECODE_SYMBOLX1_1(p, bitDPtr); + HUF_DECODE_SYMBOLX1_2(p, bitDPtr); + HUF_DECODE_SYMBOLX1_0(p, bitDPtr); + } + } else { + BIT_reloadDStream(bitDPtr); } /* [0-3] symbols remaining */ @@ -320,7 +536,7 @@ HUF_decodeStreamX1(BYTE* p, BIT_DStream_t* const bitDPtr, BYTE* const pEnd, cons while (p < pEnd) HUF_DECODE_SYMBOLX1_0(p, bitDPtr); - return pEnd-pStart; + return (size_t)(pEnd-pStart); } FORCE_INLINE_TEMPLATE size_t @@ -346,6 +562,10 @@ HUF_decompress1X1_usingDTable_internal_body( return dstSize; } +/* HUF_decompress4X1_usingDTable_internal_body(): + * Conditions : + * @dstSize >= 6 + */ FORCE_INLINE_TEMPLATE size_t HUF_decompress4X1_usingDTable_internal_body( void* dst, size_t dstSize, @@ -388,33 +608,37 @@ HUF_decompress4X1_usingDTable_internal_body( U32 endSignal = 1; if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ + if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ + if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ CHECK_F( BIT_initDStream(&bitD1, istart1, length1) ); CHECK_F( BIT_initDStream(&bitD2, istart2, length2) ); CHECK_F( BIT_initDStream(&bitD3, istart3, length3) ); CHECK_F( BIT_initDStream(&bitD4, istart4, length4) ); /* up to 16 symbols per loop (4 symbols per stream) in 64-bit mode */ - for ( ; (endSignal) & (op4 < olimit) ; ) { - HUF_DECODE_SYMBOLX1_2(op1, &bitD1); - HUF_DECODE_SYMBOLX1_2(op2, &bitD2); - HUF_DECODE_SYMBOLX1_2(op3, &bitD3); - HUF_DECODE_SYMBOLX1_2(op4, &bitD4); - HUF_DECODE_SYMBOLX1_1(op1, &bitD1); - HUF_DECODE_SYMBOLX1_1(op2, &bitD2); - HUF_DECODE_SYMBOLX1_1(op3, &bitD3); - HUF_DECODE_SYMBOLX1_1(op4, &bitD4); - HUF_DECODE_SYMBOLX1_2(op1, &bitD1); - HUF_DECODE_SYMBOLX1_2(op2, &bitD2); - HUF_DECODE_SYMBOLX1_2(op3, &bitD3); - HUF_DECODE_SYMBOLX1_2(op4, &bitD4); - HUF_DECODE_SYMBOLX1_0(op1, &bitD1); - HUF_DECODE_SYMBOLX1_0(op2, &bitD2); - HUF_DECODE_SYMBOLX1_0(op3, &bitD3); - HUF_DECODE_SYMBOLX1_0(op4, &bitD4); - endSignal &= BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished; - endSignal &= BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished; - endSignal &= BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished; - endSignal &= BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished; + if ((size_t)(oend - op4) >= sizeof(size_t)) { + for ( ; (endSignal) & (op4 < olimit) ; ) { + HUF_DECODE_SYMBOLX1_2(op1, &bitD1); + HUF_DECODE_SYMBOLX1_2(op2, &bitD2); + HUF_DECODE_SYMBOLX1_2(op3, &bitD3); + HUF_DECODE_SYMBOLX1_2(op4, &bitD4); + HUF_DECODE_SYMBOLX1_1(op1, &bitD1); + HUF_DECODE_SYMBOLX1_1(op2, &bitD2); + HUF_DECODE_SYMBOLX1_1(op3, &bitD3); + HUF_DECODE_SYMBOLX1_1(op4, &bitD4); + HUF_DECODE_SYMBOLX1_2(op1, &bitD1); + HUF_DECODE_SYMBOLX1_2(op2, &bitD2); + HUF_DECODE_SYMBOLX1_2(op3, &bitD3); + HUF_DECODE_SYMBOLX1_2(op4, &bitD4); + HUF_DECODE_SYMBOLX1_0(op1, &bitD1); + HUF_DECODE_SYMBOLX1_0(op2, &bitD2); + HUF_DECODE_SYMBOLX1_0(op3, &bitD3); + HUF_DECODE_SYMBOLX1_0(op4, &bitD4); + endSignal &= BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished; + endSignal &= BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished; + endSignal &= BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished; + endSignal &= BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished; + } } /* check corruption */ @@ -440,74 +664,232 @@ HUF_decompress4X1_usingDTable_internal_body( } } +#if HUF_NEED_BMI2_FUNCTION +static BMI2_TARGET_ATTRIBUTE +size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); +} +#endif -typedef size_t (*HUF_decompress_usingDTable_t)(void *dst, size_t dstSize, - const void *cSrc, - size_t cSrcSize, - const HUF_DTable *DTable); +static +size_t HUF_decompress4X1_usingDTable_internal_default(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); +} -HUF_DGEN(HUF_decompress1X1_usingDTable_internal) -HUF_DGEN(HUF_decompress4X1_usingDTable_internal) +#if ZSTD_ENABLE_ASM_X86_64_BMI2 +HUF_ASM_DECL void HUF_decompress4X1_usingDTable_internal_fast_asm_loop(HUF_DecompressFastArgs* args) ZSTDLIB_HIDDEN; +#endif -size_t HUF_decompress1X1_usingDTable( - void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - const HUF_DTable* DTable) +static HUF_FAST_BMI2_ATTRS +void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* args) { - DTableDesc dtd = HUF_getDTableDesc(DTable); - if (dtd.tableType != 0) return ERROR(GENERIC); - return HUF_decompress1X1_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); + U64 bits[4]; + BYTE const* ip[4]; + BYTE* op[4]; + U16 const* const dtable = (U16 const*)args->dt; + BYTE* const oend = args->oend; + BYTE const* const ilimit = args->ilimit; + + /* Copy the arguments to local variables */ + ZSTD_memcpy(&bits, &args->bits, sizeof(bits)); + ZSTD_memcpy((void*)(&ip), &args->ip, sizeof(ip)); + ZSTD_memcpy(&op, &args->op, sizeof(op)); + + assert(MEM_isLittleEndian()); + assert(!MEM_32bits()); + + for (;;) { + BYTE* olimit; + int stream; + int symbol; + + /* Assert loop preconditions */ +#ifndef NDEBUG + for (stream = 0; stream < 4; ++stream) { + assert(op[stream] <= (stream == 3 ? oend : op[stream + 1])); + assert(ip[stream] >= ilimit); + } +#endif + /* Compute olimit */ + { + /* Each iteration produces 5 output symbols per stream */ + size_t const oiters = (size_t)(oend - op[3]) / 5; + /* Each iteration consumes up to 11 bits * 5 = 55 bits < 7 bytes + * per stream. + */ + size_t const iiters = (size_t)(ip[0] - ilimit) / 7; + /* We can safely run iters iterations before running bounds checks */ + size_t const iters = MIN(oiters, iiters); + size_t const symbols = iters * 5; + + /* We can simply check that op[3] < olimit, instead of checking all + * of our bounds, since we can't hit the other bounds until we've run + * iters iterations, which only happens when op[3] == olimit. + */ + olimit = op[3] + symbols; + + /* Exit fast decoding loop once we get close to the end. */ + if (op[3] + 20 > olimit) + break; + + /* Exit the decoding loop if any input pointer has crossed the + * previous one. This indicates corruption, and a precondition + * to our loop is that ip[i] >= ip[0]. + */ + for (stream = 1; stream < 4; ++stream) { + if (ip[stream] < ip[stream - 1]) + goto _out; + } + } + +#ifndef NDEBUG + for (stream = 1; stream < 4; ++stream) { + assert(ip[stream] >= ip[stream - 1]); + } +#endif + + do { + /* Decode 5 symbols in each of the 4 streams */ + for (symbol = 0; symbol < 5; ++symbol) { + for (stream = 0; stream < 4; ++stream) { + int const index = (int)(bits[stream] >> 53); + int const entry = (int)dtable[index]; + bits[stream] <<= (entry & 63); + op[stream][symbol] = (BYTE)((entry >> 8) & 0xFF); + } + } + /* Reload the bitstreams */ + for (stream = 0; stream < 4; ++stream) { + int const ctz = ZSTD_countTrailingZeros64(bits[stream]); + int const nbBits = ctz & 7; + int const nbBytes = ctz >> 3; + op[stream] += 5; + ip[stream] -= nbBytes; + bits[stream] = MEM_read64(ip[stream]) | 1; + bits[stream] <<= nbBits; + } + } while (op[3] < olimit); + } + +_out: + + /* Save the final values of each of the state variables back to args. */ + ZSTD_memcpy(&args->bits, &bits, sizeof(bits)); + ZSTD_memcpy((void*)(&args->ip), &ip, sizeof(ip)); + ZSTD_memcpy(&args->op, &op, sizeof(op)); } -size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* DCtx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize) +/** + * @returns @p dstSize on success (>= 6) + * 0 if the fallback implementation should be used + * An error if an error occurred + */ +static HUF_FAST_BMI2_ATTRS +size_t +HUF_decompress4X1_usingDTable_internal_fast( + void* dst, size_t dstSize, + const void* cSrc, size_t cSrcSize, + const HUF_DTable* DTable, + HUF_DecompressFastLoopFn loopFn) { - const BYTE* ip = (const BYTE*) cSrc; + void const* dt = DTable + 1; + const BYTE* const iend = (const BYTE*)cSrc + 6; + BYTE* const oend = (BYTE*)dst + dstSize; + HUF_DecompressFastArgs args; + { size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); + FORWARD_IF_ERROR(ret, "Failed to init fast loop args"); + if (ret == 0) + return 0; + } - size_t const hSize = HUF_readDTableX1_wksp(DCtx, cSrc, cSrcSize, workSpace, wkspSize); - if (HUF_isError(hSize)) return hSize; - if (hSize >= cSrcSize) return ERROR(srcSize_wrong); - ip += hSize; cSrcSize -= hSize; + assert(args.ip[0] >= args.ilimit); + loopFn(&args); + + /* Our loop guarantees that ip[] >= ilimit and that we haven't + * overwritten any op[]. + */ + assert(args.ip[0] >= iend); + assert(args.ip[1] >= iend); + assert(args.ip[2] >= iend); + assert(args.ip[3] >= iend); + assert(args.op[3] <= oend); + (void)iend; + + /* finish bit streams one by one. */ + { size_t const segmentSize = (dstSize+3) / 4; + BYTE* segmentEnd = (BYTE*)dst; + int i; + for (i = 0; i < 4; ++i) { + BIT_DStream_t bit; + if (segmentSize <= (size_t)(oend - segmentEnd)) + segmentEnd += segmentSize; + else + segmentEnd = oend; + FORWARD_IF_ERROR(HUF_initRemainingDStream(&bit, &args, i, segmentEnd), "corruption"); + /* Decompress and validate that we've produced exactly the expected length. */ + args.op[i] += HUF_decodeStreamX1(args.op[i], &bit, segmentEnd, (HUF_DEltX1 const*)dt, HUF_DECODER_FAST_TABLELOG); + if (args.op[i] != segmentEnd) return ERROR(corruption_detected); + } + } - return HUF_decompress1X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, /* bmi2 */ 0); + /* decoded size */ + assert(dstSize != 0); + return dstSize; } +HUF_DGEN(HUF_decompress1X1_usingDTable_internal) -size_t HUF_decompress4X1_usingDTable( - void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - const HUF_DTable* DTable) +static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable, int flags) { - DTableDesc dtd = HUF_getDTableDesc(DTable); - if (dtd.tableType != 0) return ERROR(GENERIC); - return HUF_decompress4X1_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X1_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X1_usingDTable_internal_fast_c_loop; + +#if DYNAMIC_BMI2 + if (flags & HUF_flags_bmi2) { + fallbackFn = HUF_decompress4X1_usingDTable_internal_bmi2; +# if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; + } +# endif + } else { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +#endif + +#if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; + } +#endif + + if (!(flags & HUF_flags_disableFast)) { + size_t const ret = HUF_decompress4X1_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); + if (ret != 0) + return ret; + } + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); } -static size_t HUF_decompress4X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, +static size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize, int bmi2) + void* workSpace, size_t wkspSize, int flags) { const BYTE* ip = (const BYTE*) cSrc; - size_t const hSize = HUF_readDTableX1_wksp_bmi2(dctx, cSrc, cSrcSize, workSpace, wkspSize, bmi2); + size_t const hSize = HUF_readDTableX1_wksp(dctx, cSrc, cSrcSize, workSpace, wkspSize, flags); if (HUF_isError(hSize)) return hSize; if (hSize >= cSrcSize) return ERROR(srcSize_wrong); ip += hSize; cSrcSize -= hSize; - return HUF_decompress4X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, bmi2); + return HUF_decompress4X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags); } -size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize) -{ - return HUF_decompress4X1_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, 0); -} - - #endif /* HUF_FORCE_DECOMPRESS_X2 */ @@ -518,106 +900,226 @@ size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, /* *************************/ typedef struct { U16 sequence; BYTE nbBits; BYTE length; } HUF_DEltX2; /* double-symbols decoding */ -typedef struct { BYTE symbol; BYTE weight; } sortedSymbol_t; +typedef struct { BYTE symbol; } sortedSymbol_t; typedef U32 rankValCol_t[HUF_TABLELOG_MAX + 1]; typedef rankValCol_t rankVal_t[HUF_TABLELOG_MAX]; +/** + * Constructs a HUF_DEltX2 in a U32. + */ +static U32 HUF_buildDEltX2U32(U32 symbol, U32 nbBits, U32 baseSeq, int level) +{ + U32 seq; + DEBUG_STATIC_ASSERT(offsetof(HUF_DEltX2, sequence) == 0); + DEBUG_STATIC_ASSERT(offsetof(HUF_DEltX2, nbBits) == 2); + DEBUG_STATIC_ASSERT(offsetof(HUF_DEltX2, length) == 3); + DEBUG_STATIC_ASSERT(sizeof(HUF_DEltX2) == sizeof(U32)); + if (MEM_isLittleEndian()) { + seq = level == 1 ? symbol : (baseSeq + (symbol << 8)); + return seq + (nbBits << 16) + ((U32)level << 24); + } else { + seq = level == 1 ? (symbol << 8) : ((baseSeq << 8) + symbol); + return (seq << 16) + (nbBits << 8) + (U32)level; + } +} -/* HUF_fillDTableX2Level2() : - * `rankValOrigin` must be a table of at least (HUF_TABLELOG_MAX + 1) U32 */ -static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, U32 sizeLog, const U32 consumed, - const U32* rankValOrigin, const int minWeight, - const sortedSymbol_t* sortedSymbols, const U32 sortedListSize, - U32 nbBitsBaseline, U16 baseSeq, U32* wksp, size_t wkspSize) +/** + * Constructs a HUF_DEltX2. + */ +static HUF_DEltX2 HUF_buildDEltX2(U32 symbol, U32 nbBits, U32 baseSeq, int level) { HUF_DEltX2 DElt; - U32* rankVal = wksp; + U32 const val = HUF_buildDEltX2U32(symbol, nbBits, baseSeq, level); + DEBUG_STATIC_ASSERT(sizeof(DElt) == sizeof(val)); + ZSTD_memcpy(&DElt, &val, sizeof(val)); + return DElt; +} - assert(wkspSize >= HUF_TABLELOG_MAX + 1); - (void)wkspSize; - /* get pre-calculated rankVal */ - ZSTD_memcpy(rankVal, rankValOrigin, sizeof(U32) * (HUF_TABLELOG_MAX + 1)); +/** + * Constructs 2 HUF_DEltX2s and packs them into a U64. + */ +static U64 HUF_buildDEltX2U64(U32 symbol, U32 nbBits, U16 baseSeq, int level) +{ + U32 DElt = HUF_buildDEltX2U32(symbol, nbBits, baseSeq, level); + return (U64)DElt + ((U64)DElt << 32); +} - /* fill skipped values */ +/** + * Fills the DTable rank with all the symbols from [begin, end) that are each + * nbBits long. + * + * @param DTableRank The start of the rank in the DTable. + * @param begin The first symbol to fill (inclusive). + * @param end The last symbol to fill (exclusive). + * @param nbBits Each symbol is nbBits long. + * @param tableLog The table log. + * @param baseSeq If level == 1 { 0 } else { the first level symbol } + * @param level The level in the table. Must be 1 or 2. + */ +static void HUF_fillDTableX2ForWeight( + HUF_DEltX2* DTableRank, + sortedSymbol_t const* begin, sortedSymbol_t const* end, + U32 nbBits, U32 tableLog, + U16 baseSeq, int const level) +{ + U32 const length = 1U << ((tableLog - nbBits) & 0x1F /* quiet static-analyzer */); + const sortedSymbol_t* ptr; + assert(level >= 1 && level <= 2); + switch (length) { + case 1: + for (ptr = begin; ptr != end; ++ptr) { + HUF_DEltX2 const DElt = HUF_buildDEltX2(ptr->symbol, nbBits, baseSeq, level); + *DTableRank++ = DElt; + } + break; + case 2: + for (ptr = begin; ptr != end; ++ptr) { + HUF_DEltX2 const DElt = HUF_buildDEltX2(ptr->symbol, nbBits, baseSeq, level); + DTableRank[0] = DElt; + DTableRank[1] = DElt; + DTableRank += 2; + } + break; + case 4: + for (ptr = begin; ptr != end; ++ptr) { + U64 const DEltX2 = HUF_buildDEltX2U64(ptr->symbol, nbBits, baseSeq, level); + ZSTD_memcpy(DTableRank + 0, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTableRank + 2, &DEltX2, sizeof(DEltX2)); + DTableRank += 4; + } + break; + case 8: + for (ptr = begin; ptr != end; ++ptr) { + U64 const DEltX2 = HUF_buildDEltX2U64(ptr->symbol, nbBits, baseSeq, level); + ZSTD_memcpy(DTableRank + 0, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTableRank + 2, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTableRank + 4, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTableRank + 6, &DEltX2, sizeof(DEltX2)); + DTableRank += 8; + } + break; + default: + for (ptr = begin; ptr != end; ++ptr) { + U64 const DEltX2 = HUF_buildDEltX2U64(ptr->symbol, nbBits, baseSeq, level); + HUF_DEltX2* const DTableRankEnd = DTableRank + length; + for (; DTableRank != DTableRankEnd; DTableRank += 8) { + ZSTD_memcpy(DTableRank + 0, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTableRank + 2, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTableRank + 4, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTableRank + 6, &DEltX2, sizeof(DEltX2)); + } + } + break; + } +} + +/* HUF_fillDTableX2Level2() : + * `rankValOrigin` must be a table of at least (HUF_TABLELOG_MAX + 1) U32 */ +static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, U32 targetLog, const U32 consumedBits, + const U32* rankVal, const int minWeight, const int maxWeight1, + const sortedSymbol_t* sortedSymbols, U32 const* rankStart, + U32 nbBitsBaseline, U16 baseSeq) +{ + /* Fill skipped values (all positions up to rankVal[minWeight]). + * These are positions only get a single symbol because the combined weight + * is too large. + */ if (minWeight>1) { - U32 i, skipSize = rankVal[minWeight]; - MEM_writeLE16(&(DElt.sequence), baseSeq); - DElt.nbBits = (BYTE)(consumed); - DElt.length = 1; - for (i = 0; i < skipSize; i++) - DTable[i] = DElt; + U32 const length = 1U << ((targetLog - consumedBits) & 0x1F /* quiet static-analyzer */); + U64 const DEltX2 = HUF_buildDEltX2U64(baseSeq, consumedBits, /* baseSeq */ 0, /* level */ 1); + int const skipSize = rankVal[minWeight]; + assert(length > 1); + assert((U32)skipSize < length); + switch (length) { + case 2: + assert(skipSize == 1); + ZSTD_memcpy(DTable, &DEltX2, sizeof(DEltX2)); + break; + case 4: + assert(skipSize <= 4); + ZSTD_memcpy(DTable + 0, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTable + 2, &DEltX2, sizeof(DEltX2)); + break; + default: + { + int i; + for (i = 0; i < skipSize; i += 8) { + ZSTD_memcpy(DTable + i + 0, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTable + i + 2, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTable + i + 4, &DEltX2, sizeof(DEltX2)); + ZSTD_memcpy(DTable + i + 6, &DEltX2, sizeof(DEltX2)); + } + } + } } - /* fill DTable */ - { U32 s; for (s=0; s= 1 */ - - rankVal[weight] += length; - } } + /* Fill each of the second level symbols by weight. */ + { + int w; + for (w = minWeight; w < maxWeight1; ++w) { + int const begin = rankStart[w]; + int const end = rankStart[w+1]; + U32 const nbBits = nbBitsBaseline - w; + U32 const totalBits = nbBits + consumedBits; + HUF_fillDTableX2ForWeight( + DTable + rankVal[w], + sortedSymbols + begin, sortedSymbols + end, + totalBits, targetLog, + baseSeq, /* level */ 2); + } + } } - static void HUF_fillDTableX2(HUF_DEltX2* DTable, const U32 targetLog, - const sortedSymbol_t* sortedList, const U32 sortedListSize, - const U32* rankStart, rankVal_t rankValOrigin, const U32 maxWeight, - const U32 nbBitsBaseline, U32* wksp, size_t wkspSize) + const sortedSymbol_t* sortedList, + const U32* rankStart, rankValCol_t* rankValOrigin, const U32 maxWeight, + const U32 nbBitsBaseline) { - U32* rankVal = wksp; + U32* const rankVal = rankValOrigin[0]; const int scaleLog = nbBitsBaseline - targetLog; /* note : targetLog >= srcLog, hence scaleLog <= 1 */ const U32 minBits = nbBitsBaseline - maxWeight; - U32 s; - - assert(wkspSize >= HUF_TABLELOG_MAX + 1); - wksp += HUF_TABLELOG_MAX + 1; - wkspSize -= HUF_TABLELOG_MAX + 1; - - ZSTD_memcpy(rankVal, rankValOrigin, sizeof(U32) * (HUF_TABLELOG_MAX + 1)); - - /* fill DTable */ - for (s=0; s= minBits) { /* enough room for a second symbol */ - U32 sortedRank; + int w; + int const wEnd = (int)maxWeight + 1; + + /* Fill DTable in order of weight. */ + for (w = 1; w < wEnd; ++w) { + int const begin = (int)rankStart[w]; + int const end = (int)rankStart[w+1]; + U32 const nbBits = nbBitsBaseline - w; + + if (targetLog-nbBits >= minBits) { + /* Enough room for a second symbol. */ + int start = rankVal[w]; + U32 const length = 1U << ((targetLog - nbBits) & 0x1F /* quiet static-analyzer */); int minWeight = nbBits + scaleLog; + int s; if (minWeight < 1) minWeight = 1; - sortedRank = rankStart[minWeight]; - HUF_fillDTableX2Level2(DTable+start, targetLog-nbBits, nbBits, - rankValOrigin[nbBits], minWeight, - sortedList+sortedRank, sortedListSize-sortedRank, - nbBitsBaseline, symbol, wksp, wkspSize); + /* Fill the DTable for every symbol of weight w. + * These symbols get at least 1 second symbol. + */ + for (s = begin; s != end; ++s) { + HUF_fillDTableX2Level2( + DTable + start, targetLog, nbBits, + rankValOrigin[nbBits], minWeight, wEnd, + sortedList, rankStart, + nbBitsBaseline, sortedList[s].symbol); + start += length; + } } else { - HUF_DEltX2 DElt; - MEM_writeLE16(&(DElt.sequence), symbol); - DElt.nbBits = (BYTE)(nbBits); - DElt.length = 1; - { U32 const end = start + length; - U32 u; - for (u = start; u < end; u++) DTable[u] = DElt; - } } - rankVal[weight] += length; + /* Only a single symbol. */ + HUF_fillDTableX2ForWeight( + DTable + rankVal[w], + sortedList + begin, sortedList + end, + nbBits, targetLog, + /* baseSeq */ 0, /* level */ 1); + } } } typedef struct { rankValCol_t rankVal[HUF_TABLELOG_MAX]; U32 rankStats[HUF_TABLELOG_MAX + 1]; - U32 rankStart0[HUF_TABLELOG_MAX + 2]; + U32 rankStart0[HUF_TABLELOG_MAX + 3]; sortedSymbol_t sortedSymbol[HUF_SYMBOLVALUE_MAX + 1]; BYTE weightList[HUF_SYMBOLVALUE_MAX + 1]; U32 calleeWksp[HUF_READ_STATS_WORKSPACE_SIZE_U32]; @@ -625,11 +1127,11 @@ typedef struct { size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, - void* workSpace, size_t wkspSize) + void* workSpace, size_t wkspSize, int flags) { - U32 tableLog, maxW, sizeOfSort, nbSymbols; + U32 tableLog, maxW, nbSymbols; DTableDesc dtd = HUF_getDTableDesc(DTable); - U32 const maxTableLog = dtd.maxTableLog; + U32 maxTableLog = dtd.maxTableLog; size_t iSize; void* dtPtr = DTable+1; /* force compiler to avoid strict-aliasing */ HUF_DEltX2* const dt = (HUF_DEltX2*)dtPtr; @@ -647,11 +1149,12 @@ size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, if (maxTableLog > HUF_TABLELOG_MAX) return ERROR(tableLog_tooLarge); /* ZSTD_memset(weightList, 0, sizeof(weightList)); */ /* is not necessary, even though some analyzer complain ... */ - iSize = HUF_readStats_wksp(wksp->weightList, HUF_SYMBOLVALUE_MAX + 1, wksp->rankStats, &nbSymbols, &tableLog, src, srcSize, wksp->calleeWksp, sizeof(wksp->calleeWksp), /* bmi2 */ 0); + iSize = HUF_readStats_wksp(wksp->weightList, HUF_SYMBOLVALUE_MAX + 1, wksp->rankStats, &nbSymbols, &tableLog, src, srcSize, wksp->calleeWksp, sizeof(wksp->calleeWksp), flags); if (HUF_isError(iSize)) return iSize; /* check result */ if (tableLog > maxTableLog) return ERROR(tableLog_tooLarge); /* DTable can't fit code depth */ + if (tableLog <= HUF_DECODER_FAST_TABLELOG && maxTableLog > HUF_DECODER_FAST_TABLELOG) maxTableLog = HUF_DECODER_FAST_TABLELOG; /* find maxWeight */ for (maxW = tableLog; wksp->rankStats[maxW]==0; maxW--) {} /* necessarily finds a solution before 0 */ @@ -664,7 +1167,7 @@ size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, rankStart[w] = curr; } rankStart[0] = nextRankStart; /* put all 0w symbols at the end of sorted list*/ - sizeOfSort = nextRankStart; + rankStart[maxW+1] = nextRankStart; } /* sort symbols by weight */ @@ -673,7 +1176,6 @@ size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, U32 const w = wksp->weightList[s]; U32 const r = rankStart[w]++; wksp->sortedSymbol[r].symbol = (BYTE)s; - wksp->sortedSymbol[r].weight = (BYTE)w; } rankStart[0] = 0; /* forget 0w symbols; this is beginning of weight(1) */ } @@ -698,10 +1200,9 @@ size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, } } } } HUF_fillDTableX2(dt, maxTableLog, - wksp->sortedSymbol, sizeOfSort, + wksp->sortedSymbol, wksp->rankStart0, wksp->rankVal, maxW, - tableLog+1, - wksp->calleeWksp, sizeof(wksp->calleeWksp) / sizeof(U32)); + tableLog+1); dtd.tableLog = (BYTE)maxTableLog; dtd.tableType = 1; @@ -714,7 +1215,7 @@ FORCE_INLINE_TEMPLATE U32 HUF_decodeSymbolX2(void* op, BIT_DStream_t* DStream, const HUF_DEltX2* dt, const U32 dtLog) { size_t const val = BIT_lookBitsFast(DStream, dtLog); /* note : dtLog >= 1 */ - ZSTD_memcpy(op, dt+val, 2); + ZSTD_memcpy(op, &dt[val].sequence, 2); BIT_skipBits(DStream, dt[val].nbBits); return dt[val].length; } @@ -723,15 +1224,17 @@ FORCE_INLINE_TEMPLATE U32 HUF_decodeLastSymbolX2(void* op, BIT_DStream_t* DStream, const HUF_DEltX2* dt, const U32 dtLog) { size_t const val = BIT_lookBitsFast(DStream, dtLog); /* note : dtLog >= 1 */ - ZSTD_memcpy(op, dt+val, 1); - if (dt[val].length==1) BIT_skipBits(DStream, dt[val].nbBits); - else { + ZSTD_memcpy(op, &dt[val].sequence, 1); + if (dt[val].length==1) { + BIT_skipBits(DStream, dt[val].nbBits); + } else { if (DStream->bitsConsumed < (sizeof(DStream->bitContainer)*8)) { BIT_skipBits(DStream, dt[val].nbBits); if (DStream->bitsConsumed > (sizeof(DStream->bitContainer)*8)) /* ugly hack; works only because it's the last symbol. Note : can't easily extract nbBits from just this symbol */ DStream->bitsConsumed = (sizeof(DStream->bitContainer)*8); - } } + } + } return 1; } @@ -753,19 +1256,37 @@ HUF_decodeStreamX2(BYTE* p, BIT_DStream_t* bitDPtr, BYTE* const pEnd, BYTE* const pStart = p; /* up to 8 symbols at a time */ - while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-(sizeof(bitDPtr->bitContainer)-1))) { - HUF_DECODE_SYMBOLX2_2(p, bitDPtr); - HUF_DECODE_SYMBOLX2_1(p, bitDPtr); - HUF_DECODE_SYMBOLX2_2(p, bitDPtr); - HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + if ((size_t)(pEnd - p) >= sizeof(bitDPtr->bitContainer)) { + if (dtLog <= 11 && MEM_64bits()) { + /* up to 10 symbols at a time */ + while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-9)) { + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + } + } else { + /* up to 8 symbols at a time */ + while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-(sizeof(bitDPtr->bitContainer)-1))) { + HUF_DECODE_SYMBOLX2_2(p, bitDPtr); + HUF_DECODE_SYMBOLX2_1(p, bitDPtr); + HUF_DECODE_SYMBOLX2_2(p, bitDPtr); + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + } + } + } else { + BIT_reloadDStream(bitDPtr); } /* closer to end : up to 2 symbols at a time */ - while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p <= pEnd-2)) - HUF_DECODE_SYMBOLX2_0(p, bitDPtr); + if ((size_t)(pEnd - p) >= 2) { + while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p <= pEnd-2)) + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); - while (p <= pEnd-2) - HUF_DECODE_SYMBOLX2_0(p, bitDPtr); /* no need to reload : reached the end of DStream */ + while (p <= pEnd-2) + HUF_DECODE_SYMBOLX2_0(p, bitDPtr); /* no need to reload : reached the end of DStream */ + } if (p < pEnd) p += HUF_decodeLastSymbolX2(p, bitDPtr, dt, dtLog); @@ -800,6 +1321,10 @@ HUF_decompress1X2_usingDTable_internal_body( return dstSize; } +/* HUF_decompress4X2_usingDTable_internal_body(): + * Conditions: + * @dstSize >= 6 + */ FORCE_INLINE_TEMPLATE size_t HUF_decompress4X2_usingDTable_internal_body( void* dst, size_t dstSize, @@ -840,58 +1365,62 @@ HUF_decompress4X2_usingDTable_internal_body( DTableDesc const dtd = HUF_getDTableDesc(DTable); U32 const dtLog = dtd.tableLog; - if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ + if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ + if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ + if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ CHECK_F( BIT_initDStream(&bitD1, istart1, length1) ); CHECK_F( BIT_initDStream(&bitD2, istart2, length2) ); CHECK_F( BIT_initDStream(&bitD3, istart3, length3) ); CHECK_F( BIT_initDStream(&bitD4, istart4, length4) ); /* 16-32 symbols per loop (4-8 symbols per stream) */ - for ( ; (endSignal) & (op4 < olimit); ) { + if ((size_t)(oend - op4) >= sizeof(size_t)) { + for ( ; (endSignal) & (op4 < olimit); ) { #if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) - HUF_DECODE_SYMBOLX2_2(op1, &bitD1); - HUF_DECODE_SYMBOLX2_1(op1, &bitD1); - HUF_DECODE_SYMBOLX2_2(op1, &bitD1); - HUF_DECODE_SYMBOLX2_0(op1, &bitD1); - HUF_DECODE_SYMBOLX2_2(op2, &bitD2); - HUF_DECODE_SYMBOLX2_1(op2, &bitD2); - HUF_DECODE_SYMBOLX2_2(op2, &bitD2); - HUF_DECODE_SYMBOLX2_0(op2, &bitD2); - endSignal &= BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished; - endSignal &= BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished; - HUF_DECODE_SYMBOLX2_2(op3, &bitD3); - HUF_DECODE_SYMBOLX2_1(op3, &bitD3); - HUF_DECODE_SYMBOLX2_2(op3, &bitD3); - HUF_DECODE_SYMBOLX2_0(op3, &bitD3); - HUF_DECODE_SYMBOLX2_2(op4, &bitD4); - HUF_DECODE_SYMBOLX2_1(op4, &bitD4); - HUF_DECODE_SYMBOLX2_2(op4, &bitD4); - HUF_DECODE_SYMBOLX2_0(op4, &bitD4); - endSignal &= BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished; - endSignal &= BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished; + HUF_DECODE_SYMBOLX2_2(op1, &bitD1); + HUF_DECODE_SYMBOLX2_1(op1, &bitD1); + HUF_DECODE_SYMBOLX2_2(op1, &bitD1); + HUF_DECODE_SYMBOLX2_0(op1, &bitD1); + HUF_DECODE_SYMBOLX2_2(op2, &bitD2); + HUF_DECODE_SYMBOLX2_1(op2, &bitD2); + HUF_DECODE_SYMBOLX2_2(op2, &bitD2); + HUF_DECODE_SYMBOLX2_0(op2, &bitD2); + endSignal &= BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished; + endSignal &= BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished; + HUF_DECODE_SYMBOLX2_2(op3, &bitD3); + HUF_DECODE_SYMBOLX2_1(op3, &bitD3); + HUF_DECODE_SYMBOLX2_2(op3, &bitD3); + HUF_DECODE_SYMBOLX2_0(op3, &bitD3); + HUF_DECODE_SYMBOLX2_2(op4, &bitD4); + HUF_DECODE_SYMBOLX2_1(op4, &bitD4); + HUF_DECODE_SYMBOLX2_2(op4, &bitD4); + HUF_DECODE_SYMBOLX2_0(op4, &bitD4); + endSignal &= BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished; + endSignal &= BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished; #else - HUF_DECODE_SYMBOLX2_2(op1, &bitD1); - HUF_DECODE_SYMBOLX2_2(op2, &bitD2); - HUF_DECODE_SYMBOLX2_2(op3, &bitD3); - HUF_DECODE_SYMBOLX2_2(op4, &bitD4); - HUF_DECODE_SYMBOLX2_1(op1, &bitD1); - HUF_DECODE_SYMBOLX2_1(op2, &bitD2); - HUF_DECODE_SYMBOLX2_1(op3, &bitD3); - HUF_DECODE_SYMBOLX2_1(op4, &bitD4); - HUF_DECODE_SYMBOLX2_2(op1, &bitD1); - HUF_DECODE_SYMBOLX2_2(op2, &bitD2); - HUF_DECODE_SYMBOLX2_2(op3, &bitD3); - HUF_DECODE_SYMBOLX2_2(op4, &bitD4); - HUF_DECODE_SYMBOLX2_0(op1, &bitD1); - HUF_DECODE_SYMBOLX2_0(op2, &bitD2); - HUF_DECODE_SYMBOLX2_0(op3, &bitD3); - HUF_DECODE_SYMBOLX2_0(op4, &bitD4); - endSignal = (U32)LIKELY( - (BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished) - & (BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished) - & (BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished) - & (BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished)); + HUF_DECODE_SYMBOLX2_2(op1, &bitD1); + HUF_DECODE_SYMBOLX2_2(op2, &bitD2); + HUF_DECODE_SYMBOLX2_2(op3, &bitD3); + HUF_DECODE_SYMBOLX2_2(op4, &bitD4); + HUF_DECODE_SYMBOLX2_1(op1, &bitD1); + HUF_DECODE_SYMBOLX2_1(op2, &bitD2); + HUF_DECODE_SYMBOLX2_1(op3, &bitD3); + HUF_DECODE_SYMBOLX2_1(op4, &bitD4); + HUF_DECODE_SYMBOLX2_2(op1, &bitD1); + HUF_DECODE_SYMBOLX2_2(op2, &bitD2); + HUF_DECODE_SYMBOLX2_2(op3, &bitD3); + HUF_DECODE_SYMBOLX2_2(op4, &bitD4); + HUF_DECODE_SYMBOLX2_0(op1, &bitD1); + HUF_DECODE_SYMBOLX2_0(op2, &bitD2); + HUF_DECODE_SYMBOLX2_0(op3, &bitD3); + HUF_DECODE_SYMBOLX2_0(op4, &bitD4); + endSignal = (U32)LIKELY((U32) + (BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished) + & (BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished) + & (BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished) + & (BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished)); #endif + } } /* check corruption */ @@ -915,68 +1444,281 @@ HUF_decompress4X2_usingDTable_internal_body( } } -HUF_DGEN(HUF_decompress1X2_usingDTable_internal) -HUF_DGEN(HUF_decompress4X2_usingDTable_internal) +#if HUF_NEED_BMI2_FUNCTION +static BMI2_TARGET_ATTRIBUTE +size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); +} +#endif + +static +size_t HUF_decompress4X2_usingDTable_internal_default(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); +} + +#if ZSTD_ENABLE_ASM_X86_64_BMI2 + +HUF_ASM_DECL void HUF_decompress4X2_usingDTable_internal_fast_asm_loop(HUF_DecompressFastArgs* args) ZSTDLIB_HIDDEN; -size_t HUF_decompress1X2_usingDTable( +#endif + +static HUF_FAST_BMI2_ATTRS +void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* args) +{ + U64 bits[4]; + BYTE const* ip[4]; + BYTE* op[4]; + BYTE* oend[4]; + HUF_DEltX2 const* const dtable = (HUF_DEltX2 const*)args->dt; + BYTE const* const ilimit = args->ilimit; + + /* Copy the arguments to local registers. */ + ZSTD_memcpy(&bits, &args->bits, sizeof(bits)); + ZSTD_memcpy((void*)(&ip), &args->ip, sizeof(ip)); + ZSTD_memcpy(&op, &args->op, sizeof(op)); + + oend[0] = op[1]; + oend[1] = op[2]; + oend[2] = op[3]; + oend[3] = args->oend; + + assert(MEM_isLittleEndian()); + assert(!MEM_32bits()); + + for (;;) { + BYTE* olimit; + int stream; + int symbol; + + /* Assert loop preconditions */ +#ifndef NDEBUG + for (stream = 0; stream < 4; ++stream) { + assert(op[stream] <= oend[stream]); + assert(ip[stream] >= ilimit); + } +#endif + /* Compute olimit */ + { + /* Each loop does 5 table lookups for each of the 4 streams. + * Each table lookup consumes up to 11 bits of input, and produces + * up to 2 bytes of output. + */ + /* We can consume up to 7 bytes of input per iteration per stream. + * We also know that each input pointer is >= ip[0]. So we can run + * iters loops before running out of input. + */ + size_t iters = (size_t)(ip[0] - ilimit) / 7; + /* Each iteration can produce up to 10 bytes of output per stream. + * Each output stream my advance at different rates. So take the + * minimum number of safe iterations among all the output streams. + */ + for (stream = 0; stream < 4; ++stream) { + size_t const oiters = (size_t)(oend[stream] - op[stream]) / 10; + iters = MIN(iters, oiters); + } + + /* Each iteration produces at least 5 output symbols. So until + * op[3] crosses olimit, we know we haven't executed iters + * iterations yet. This saves us maintaining an iters counter, + * at the expense of computing the remaining # of iterations + * more frequently. + */ + olimit = op[3] + (iters * 5); + + /* Exit the fast decoding loop if we are too close to the end. */ + if (op[3] + 10 > olimit) + break; + + /* Exit the decoding loop if any input pointer has crossed the + * previous one. This indicates corruption, and a precondition + * to our loop is that ip[i] >= ip[0]. + */ + for (stream = 1; stream < 4; ++stream) { + if (ip[stream] < ip[stream - 1]) + goto _out; + } + } + +#ifndef NDEBUG + for (stream = 1; stream < 4; ++stream) { + assert(ip[stream] >= ip[stream - 1]); + } +#endif + + do { + /* Do 5 table lookups for each of the first 3 streams */ + for (symbol = 0; symbol < 5; ++symbol) { + for (stream = 0; stream < 3; ++stream) { + int const index = (int)(bits[stream] >> 53); + HUF_DEltX2 const entry = dtable[index]; + MEM_write16(op[stream], entry.sequence); + bits[stream] <<= (entry.nbBits); + op[stream] += (entry.length); + } + } + /* Do 1 table lookup from the final stream */ + { + int const index = (int)(bits[3] >> 53); + HUF_DEltX2 const entry = dtable[index]; + MEM_write16(op[3], entry.sequence); + bits[3] <<= (entry.nbBits); + op[3] += (entry.length); + } + /* Do 4 table lookups from the final stream & reload bitstreams */ + for (stream = 0; stream < 4; ++stream) { + /* Do a table lookup from the final stream. + * This is interleaved with the reloading to reduce register + * pressure. This shouldn't be necessary, but compilers can + * struggle with codegen with high register pressure. + */ + { + int const index = (int)(bits[3] >> 53); + HUF_DEltX2 const entry = dtable[index]; + MEM_write16(op[3], entry.sequence); + bits[3] <<= (entry.nbBits); + op[3] += (entry.length); + } + /* Reload the bistreams. The final bitstream must be reloaded + * after the 5th symbol was decoded. + */ + { + int const ctz = ZSTD_countTrailingZeros64(bits[stream]); + int const nbBits = ctz & 7; + int const nbBytes = ctz >> 3; + ip[stream] -= nbBytes; + bits[stream] = MEM_read64(ip[stream]) | 1; + bits[stream] <<= nbBits; + } + } + } while (op[3] < olimit); + } + +_out: + + /* Save the final values of each of the state variables back to args. */ + ZSTD_memcpy(&args->bits, &bits, sizeof(bits)); + ZSTD_memcpy((void*)(&args->ip), &ip, sizeof(ip)); + ZSTD_memcpy(&args->op, &op, sizeof(op)); +} + + +static HUF_FAST_BMI2_ATTRS size_t +HUF_decompress4X2_usingDTable_internal_fast( void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, - const HUF_DTable* DTable) + const HUF_DTable* DTable, + HUF_DecompressFastLoopFn loopFn) { + void const* dt = DTable + 1; + const BYTE* const iend = (const BYTE*)cSrc + 6; + BYTE* const oend = (BYTE*)dst + dstSize; + HUF_DecompressFastArgs args; + { + size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); + FORWARD_IF_ERROR(ret, "Failed to init asm args"); + if (ret == 0) + return 0; + } + + assert(args.ip[0] >= args.ilimit); + loopFn(&args); + + /* note : op4 already verified within main loop */ + assert(args.ip[0] >= iend); + assert(args.ip[1] >= iend); + assert(args.ip[2] >= iend); + assert(args.ip[3] >= iend); + assert(args.op[3] <= oend); + (void)iend; + + /* finish bitStreams one by one */ + { + size_t const segmentSize = (dstSize+3) / 4; + BYTE* segmentEnd = (BYTE*)dst; + int i; + for (i = 0; i < 4; ++i) { + BIT_DStream_t bit; + if (segmentSize <= (size_t)(oend - segmentEnd)) + segmentEnd += segmentSize; + else + segmentEnd = oend; + FORWARD_IF_ERROR(HUF_initRemainingDStream(&bit, &args, i, segmentEnd), "corruption"); + args.op[i] += HUF_decodeStreamX2(args.op[i], &bit, segmentEnd, (HUF_DEltX2 const*)dt, HUF_DECODER_FAST_TABLELOG); + if (args.op[i] != segmentEnd) + return ERROR(corruption_detected); + } + } + + /* decoded size */ + return dstSize; +} + +static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable, int flags) { - DTableDesc dtd = HUF_getDTableDesc(DTable); - if (dtd.tableType != 1) return ERROR(GENERIC); - return HUF_decompress1X2_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X2_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X2_usingDTable_internal_fast_c_loop; + +#if DYNAMIC_BMI2 + if (flags & HUF_flags_bmi2) { + fallbackFn = HUF_decompress4X2_usingDTable_internal_bmi2; +# if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; + } +# endif + } else { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +#endif + +#if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; + } +#endif + + if (!(flags & HUF_flags_disableFast)) { + size_t const ret = HUF_decompress4X2_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); + if (ret != 0) + return ret; + } + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); } +HUF_DGEN(HUF_decompress1X2_usingDTable_internal) + size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* DCtx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize) + void* workSpace, size_t wkspSize, int flags) { const BYTE* ip = (const BYTE*) cSrc; size_t const hSize = HUF_readDTableX2_wksp(DCtx, cSrc, cSrcSize, - workSpace, wkspSize); + workSpace, wkspSize, flags); if (HUF_isError(hSize)) return hSize; if (hSize >= cSrcSize) return ERROR(srcSize_wrong); ip += hSize; cSrcSize -= hSize; - return HUF_decompress1X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, /* bmi2 */ 0); + return HUF_decompress1X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, flags); } - -size_t HUF_decompress4X2_usingDTable( - void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - const HUF_DTable* DTable) -{ - DTableDesc dtd = HUF_getDTableDesc(DTable); - if (dtd.tableType != 1) return ERROR(GENERIC); - return HUF_decompress4X2_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -} - -static size_t HUF_decompress4X2_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, +static size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize, int bmi2) + void* workSpace, size_t wkspSize, int flags) { const BYTE* ip = (const BYTE*) cSrc; size_t hSize = HUF_readDTableX2_wksp(dctx, cSrc, cSrcSize, - workSpace, wkspSize); + workSpace, wkspSize, flags); if (HUF_isError(hSize)) return hSize; if (hSize >= cSrcSize) return ERROR(srcSize_wrong); ip += hSize; cSrcSize -= hSize; - return HUF_decompress4X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, bmi2); -} - -size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize) -{ - return HUF_decompress4X2_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, /* bmi2 */ 0); + return HUF_decompress4X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags); } - #endif /* HUF_FORCE_DECOMPRESS_X1 */ @@ -984,66 +1726,28 @@ size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, /* Universal decompression selectors */ /* ***********************************/ -size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, - const void* cSrc, size_t cSrcSize, - const HUF_DTable* DTable) -{ - DTableDesc const dtd = HUF_getDTableDesc(DTable); -#if defined(HUF_FORCE_DECOMPRESS_X1) - (void)dtd; - assert(dtd.tableType == 0); - return HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -#elif defined(HUF_FORCE_DECOMPRESS_X2) - (void)dtd; - assert(dtd.tableType == 1); - return HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -#else - return dtd.tableType ? HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0) : - HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -#endif -} - -size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, - const void* cSrc, size_t cSrcSize, - const HUF_DTable* DTable) -{ - DTableDesc const dtd = HUF_getDTableDesc(DTable); -#if defined(HUF_FORCE_DECOMPRESS_X1) - (void)dtd; - assert(dtd.tableType == 0); - return HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -#elif defined(HUF_FORCE_DECOMPRESS_X2) - (void)dtd; - assert(dtd.tableType == 1); - return HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -#else - return dtd.tableType ? HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0) : - HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -#endif -} - #if !defined(HUF_FORCE_DECOMPRESS_X1) && !defined(HUF_FORCE_DECOMPRESS_X2) typedef struct { U32 tableTime; U32 decode256Time; } algo_time_t; -static const algo_time_t algoTime[16 /* Quantization */][3 /* single, double, quad */] = +static const algo_time_t algoTime[16 /* Quantization */][2 /* single, double */] = { /* single, double, quad */ - {{0,0}, {1,1}, {2,2}}, /* Q==0 : impossible */ - {{0,0}, {1,1}, {2,2}}, /* Q==1 : impossible */ - {{ 38,130}, {1313, 74}, {2151, 38}}, /* Q == 2 : 12-18% */ - {{ 448,128}, {1353, 74}, {2238, 41}}, /* Q == 3 : 18-25% */ - {{ 556,128}, {1353, 74}, {2238, 47}}, /* Q == 4 : 25-32% */ - {{ 714,128}, {1418, 74}, {2436, 53}}, /* Q == 5 : 32-38% */ - {{ 883,128}, {1437, 74}, {2464, 61}}, /* Q == 6 : 38-44% */ - {{ 897,128}, {1515, 75}, {2622, 68}}, /* Q == 7 : 44-50% */ - {{ 926,128}, {1613, 75}, {2730, 75}}, /* Q == 8 : 50-56% */ - {{ 947,128}, {1729, 77}, {3359, 77}}, /* Q == 9 : 56-62% */ - {{1107,128}, {2083, 81}, {4006, 84}}, /* Q ==10 : 62-69% */ - {{1177,128}, {2379, 87}, {4785, 88}}, /* Q ==11 : 69-75% */ - {{1242,128}, {2415, 93}, {5155, 84}}, /* Q ==12 : 75-81% */ - {{1349,128}, {2644,106}, {5260,106}}, /* Q ==13 : 81-87% */ - {{1455,128}, {2422,124}, {4174,124}}, /* Q ==14 : 87-93% */ - {{ 722,128}, {1891,145}, {1936,146}}, /* Q ==15 : 93-99% */ + {{0,0}, {1,1}}, /* Q==0 : impossible */ + {{0,0}, {1,1}}, /* Q==1 : impossible */ + {{ 150,216}, { 381,119}}, /* Q == 2 : 12-18% */ + {{ 170,205}, { 514,112}}, /* Q == 3 : 18-25% */ + {{ 177,199}, { 539,110}}, /* Q == 4 : 25-32% */ + {{ 197,194}, { 644,107}}, /* Q == 5 : 32-38% */ + {{ 221,192}, { 735,107}}, /* Q == 6 : 38-44% */ + {{ 256,189}, { 881,106}}, /* Q == 7 : 44-50% */ + {{ 359,188}, {1167,109}}, /* Q == 8 : 50-56% */ + {{ 582,187}, {1570,114}}, /* Q == 9 : 56-62% */ + {{ 688,187}, {1712,122}}, /* Q ==10 : 62-69% */ + {{ 825,186}, {1965,136}}, /* Q ==11 : 69-75% */ + {{ 976,185}, {2131,150}}, /* Q ==12 : 75-81% */ + {{1180,186}, {2070,175}}, /* Q ==13 : 81-87% */ + {{1377,185}, {1731,202}}, /* Q ==14 : 87-93% */ + {{1412,185}, {1695,202}}, /* Q ==15 : 93-99% */ }; #endif @@ -1070,42 +1774,15 @@ U32 HUF_selectDecoder (size_t dstSize, size_t cSrcSize) U32 const D256 = (U32)(dstSize >> 8); U32 const DTime0 = algoTime[Q][0].tableTime + (algoTime[Q][0].decode256Time * D256); U32 DTime1 = algoTime[Q][1].tableTime + (algoTime[Q][1].decode256Time * D256); - DTime1 += DTime1 >> 3; /* advantage to algorithm using less memory, to reduce cache eviction */ + DTime1 += DTime1 >> 5; /* small advantage to algorithm using less memory, to reduce cache eviction */ return DTime1 < DTime0; } #endif } - -size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, - size_t dstSize, const void* cSrc, - size_t cSrcSize, void* workSpace, - size_t wkspSize) -{ - /* validation checks */ - if (dstSize == 0) return ERROR(dstSize_tooSmall); - if (cSrcSize == 0) return ERROR(corruption_detected); - - { U32 const algoNb = HUF_selectDecoder(dstSize, cSrcSize); -#if defined(HUF_FORCE_DECOMPRESS_X1) - (void)algoNb; - assert(algoNb == 0); - return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize); -#elif defined(HUF_FORCE_DECOMPRESS_X2) - (void)algoNb; - assert(algoNb == 1); - return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize); -#else - return algoNb ? HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, - cSrcSize, workSpace, wkspSize): - HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize); -#endif - } -} - size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize) + void* workSpace, size_t wkspSize, int flags) { /* validation checks */ if (dstSize == 0) return ERROR(dstSize_tooSmall); @@ -1118,71 +1795,71 @@ size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, (void)algoNb; assert(algoNb == 0); return HUF_decompress1X1_DCtx_wksp(dctx, dst, dstSize, cSrc, - cSrcSize, workSpace, wkspSize); + cSrcSize, workSpace, wkspSize, flags); #elif defined(HUF_FORCE_DECOMPRESS_X2) (void)algoNb; assert(algoNb == 1); return HUF_decompress1X2_DCtx_wksp(dctx, dst, dstSize, cSrc, - cSrcSize, workSpace, wkspSize); + cSrcSize, workSpace, wkspSize, flags); #else return algoNb ? HUF_decompress1X2_DCtx_wksp(dctx, dst, dstSize, cSrc, - cSrcSize, workSpace, wkspSize): + cSrcSize, workSpace, wkspSize, flags): HUF_decompress1X1_DCtx_wksp(dctx, dst, dstSize, cSrc, - cSrcSize, workSpace, wkspSize); + cSrcSize, workSpace, wkspSize, flags); #endif } } -size_t HUF_decompress1X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2) +size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags) { DTableDesc const dtd = HUF_getDTableDesc(DTable); #if defined(HUF_FORCE_DECOMPRESS_X1) (void)dtd; assert(dtd.tableType == 0); - return HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); + return HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); #elif defined(HUF_FORCE_DECOMPRESS_X2) (void)dtd; assert(dtd.tableType == 1); - return HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); + return HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); #else - return dtd.tableType ? HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2) : - HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); + return dtd.tableType ? HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags) : + HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); #endif } #ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_decompress1X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2) +size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags) { const BYTE* ip = (const BYTE*) cSrc; - size_t const hSize = HUF_readDTableX1_wksp_bmi2(dctx, cSrc, cSrcSize, workSpace, wkspSize, bmi2); + size_t const hSize = HUF_readDTableX1_wksp(dctx, cSrc, cSrcSize, workSpace, wkspSize, flags); if (HUF_isError(hSize)) return hSize; if (hSize >= cSrcSize) return ERROR(srcSize_wrong); ip += hSize; cSrcSize -= hSize; - return HUF_decompress1X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, bmi2); + return HUF_decompress1X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags); } #endif -size_t HUF_decompress4X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2) +size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags) { DTableDesc const dtd = HUF_getDTableDesc(DTable); #if defined(HUF_FORCE_DECOMPRESS_X1) (void)dtd; assert(dtd.tableType == 0); - return HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); + return HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); #elif defined(HUF_FORCE_DECOMPRESS_X2) (void)dtd; assert(dtd.tableType == 1); - return HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); + return HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); #else - return dtd.tableType ? HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2) : - HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); + return dtd.tableType ? HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags) : + HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); #endif } -size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2) +size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags) { /* validation checks */ if (dstSize == 0) return ERROR(dstSize_tooSmall); @@ -1192,160 +1869,14 @@ size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t ds #if defined(HUF_FORCE_DECOMPRESS_X1) (void)algoNb; assert(algoNb == 0); - return HUF_decompress4X1_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2); + return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags); #elif defined(HUF_FORCE_DECOMPRESS_X2) (void)algoNb; assert(algoNb == 1); - return HUF_decompress4X2_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2); + return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags); #else - return algoNb ? HUF_decompress4X2_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2) : - HUF_decompress4X1_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2); + return algoNb ? HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags) : + HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags); #endif } } - -#ifndef ZSTD_NO_UNUSED_FUNCTIONS -#ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_readDTableX1(HUF_DTable* DTable, const void* src, size_t srcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_readDTableX1_wksp(DTable, src, srcSize, - workSpace, sizeof(workSpace)); -} - -size_t HUF_decompress1X1_DCtx(HUF_DTable* DCtx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_decompress1X1_DCtx_wksp(DCtx, dst, dstSize, cSrc, cSrcSize, - workSpace, sizeof(workSpace)); -} - -size_t HUF_decompress1X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ - HUF_CREATE_STATIC_DTABLEX1(DTable, HUF_TABLELOG_MAX); - return HUF_decompress1X1_DCtx (DTable, dst, dstSize, cSrc, cSrcSize); -} -#endif - -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_readDTableX2(HUF_DTable* DTable, const void* src, size_t srcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_readDTableX2_wksp(DTable, src, srcSize, - workSpace, sizeof(workSpace)); -} - -size_t HUF_decompress1X2_DCtx(HUF_DTable* DCtx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_decompress1X2_DCtx_wksp(DCtx, dst, dstSize, cSrc, cSrcSize, - workSpace, sizeof(workSpace)); -} - -size_t HUF_decompress1X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ - HUF_CREATE_STATIC_DTABLEX2(DTable, HUF_TABLELOG_MAX); - return HUF_decompress1X2_DCtx(DTable, dst, dstSize, cSrc, cSrcSize); -} -#endif - -#ifndef HUF_FORCE_DECOMPRESS_X2 -size_t HUF_decompress4X1_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, - workSpace, sizeof(workSpace)); -} -size_t HUF_decompress4X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ - HUF_CREATE_STATIC_DTABLEX1(DTable, HUF_TABLELOG_MAX); - return HUF_decompress4X1_DCtx(DTable, dst, dstSize, cSrc, cSrcSize); -} -#endif - -#ifndef HUF_FORCE_DECOMPRESS_X1 -size_t HUF_decompress4X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, - workSpace, sizeof(workSpace)); -} - -size_t HUF_decompress4X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ - HUF_CREATE_STATIC_DTABLEX2(DTable, HUF_TABLELOG_MAX); - return HUF_decompress4X2_DCtx(DTable, dst, dstSize, cSrc, cSrcSize); -} -#endif - -typedef size_t (*decompressionAlgo)(void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); - -size_t HUF_decompress (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ -#if !defined(HUF_FORCE_DECOMPRESS_X1) && !defined(HUF_FORCE_DECOMPRESS_X2) - static const decompressionAlgo decompress[2] = { HUF_decompress4X1, HUF_decompress4X2 }; -#endif - - /* validation checks */ - if (dstSize == 0) return ERROR(dstSize_tooSmall); - if (cSrcSize > dstSize) return ERROR(corruption_detected); /* invalid */ - if (cSrcSize == dstSize) { ZSTD_memcpy(dst, cSrc, dstSize); return dstSize; } /* not compressed */ - if (cSrcSize == 1) { ZSTD_memset(dst, *(const BYTE*)cSrc, dstSize); return dstSize; } /* RLE */ - - { U32 const algoNb = HUF_selectDecoder(dstSize, cSrcSize); -#if defined(HUF_FORCE_DECOMPRESS_X1) - (void)algoNb; - assert(algoNb == 0); - return HUF_decompress4X1(dst, dstSize, cSrc, cSrcSize); -#elif defined(HUF_FORCE_DECOMPRESS_X2) - (void)algoNb; - assert(algoNb == 1); - return HUF_decompress4X2(dst, dstSize, cSrc, cSrcSize); -#else - return decompress[algoNb](dst, dstSize, cSrc, cSrcSize); -#endif - } -} - -size_t HUF_decompress4X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ - /* validation checks */ - if (dstSize == 0) return ERROR(dstSize_tooSmall); - if (cSrcSize > dstSize) return ERROR(corruption_detected); /* invalid */ - if (cSrcSize == dstSize) { ZSTD_memcpy(dst, cSrc, dstSize); return dstSize; } /* not compressed */ - if (cSrcSize == 1) { ZSTD_memset(dst, *(const BYTE*)cSrc, dstSize); return dstSize; } /* RLE */ - - { U32 const algoNb = HUF_selectDecoder(dstSize, cSrcSize); -#if defined(HUF_FORCE_DECOMPRESS_X1) - (void)algoNb; - assert(algoNb == 0); - return HUF_decompress4X1_DCtx(dctx, dst, dstSize, cSrc, cSrcSize); -#elif defined(HUF_FORCE_DECOMPRESS_X2) - (void)algoNb; - assert(algoNb == 1); - return HUF_decompress4X2_DCtx(dctx, dst, dstSize, cSrc, cSrcSize); -#else - return algoNb ? HUF_decompress4X2_DCtx(dctx, dst, dstSize, cSrc, cSrcSize) : - HUF_decompress4X1_DCtx(dctx, dst, dstSize, cSrc, cSrcSize) ; -#endif - } -} - -size_t HUF_decompress4X_hufOnly(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_decompress4X_hufOnly_wksp(dctx, dst, dstSize, cSrc, cSrcSize, - workSpace, sizeof(workSpace)); -} - -size_t HUF_decompress1X_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize) -{ - U32 workSpace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; - return HUF_decompress1X_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, - workSpace, sizeof(workSpace)); -} -#endif diff --git a/Utilities/cmzstd/lib/decompress/zstd_ddict.c b/Utilities/cmzstd/lib/decompress/zstd_ddict.c index ce335477b32..309ec0d0364 100644 --- a/Utilities/cmzstd/lib/decompress/zstd_ddict.c +++ b/Utilities/cmzstd/lib/decompress/zstd_ddict.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -14,12 +14,12 @@ /*-******************************************************* * Dependencies *********************************************************/ +#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customFree */ #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ #include "../common/cpu.h" /* bmi2 */ #include "../common/mem.h" /* low level memory routines */ #define FSE_STATIC_LINKING_ONLY #include "../common/fse.h" -#define HUF_STATIC_LINKING_ONLY #include "../common/huf.h" #include "zstd_decompress_internal.h" #include "zstd_ddict.h" @@ -134,7 +134,7 @@ static size_t ZSTD_initDDict_internal(ZSTD_DDict* ddict, ZSTD_memcpy(internalBuffer, dict, dictSize); } ddict->dictSize = dictSize; - ddict->entropy.hufTable[0] = (HUF_DTable)((HufLog)*0x1000001); /* cover both little and big endian */ + ddict->entropy.hufTable[0] = (HUF_DTable)((ZSTD_HUFFDTABLE_CAPACITY_LOG)*0x1000001); /* cover both little and big endian */ /* parse dictionary content */ FORWARD_IF_ERROR( ZSTD_loadEntropy_intoDDict(ddict, dictContentType) , ""); @@ -240,5 +240,5 @@ size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict) unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ddict) { if (ddict==NULL) return 0; - return ZSTD_getDictID_fromDict(ddict->dictContent, ddict->dictSize); + return ddict->dictID; } diff --git a/Utilities/cmzstd/lib/decompress/zstd_ddict.h b/Utilities/cmzstd/lib/decompress/zstd_ddict.h index bd03268b508..c4ca8877a07 100644 --- a/Utilities/cmzstd/lib/decompress/zstd_ddict.h +++ b/Utilities/cmzstd/lib/decompress/zstd_ddict.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/decompress/zstd_decompress.c b/Utilities/cmzstd/lib/decompress/zstd_decompress.c index 910bc034c00..7bc2713429d 100644 --- a/Utilities/cmzstd/lib/decompress/zstd_decompress.c +++ b/Utilities/cmzstd/lib/decompress/zstd_decompress.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -55,18 +55,18 @@ /*-******************************************************* * Dependencies *********************************************************/ +#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ -#include "../common/cpu.h" /* bmi2 */ #include "../common/mem.h" /* low level memory routines */ #define FSE_STATIC_LINKING_ONLY #include "../common/fse.h" -#define HUF_STATIC_LINKING_ONLY #include "../common/huf.h" #include "../common/xxhash.h" /* XXH64_reset, XXH64_update, XXH64_digest, XXH64 */ #include "../common/zstd_internal.h" /* blockProperties_t */ #include "zstd_decompress_internal.h" /* ZSTD_DCtx */ #include "zstd_ddict.h" /* ZSTD_DDictDictContent */ #include "zstd_decompress_block.h" /* ZSTD_decompressBlock_internal */ +#include "../common/bits.h" /* ZSTD_highbit32 */ #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) # include "../legacy/zstd_legacy.h" @@ -79,11 +79,11 @@ *************************************/ #define DDICT_HASHSET_MAX_LOAD_FACTOR_COUNT_MULT 4 -#define DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT 3 /* These two constants represent SIZE_MULT/COUNT_MULT load factor without using a float. - * Currently, that means a 0.75 load factor. - * So, if count * COUNT_MULT / size * SIZE_MULT != 0, then we've exceeded - * the load factor of the ddict hash set. - */ +#define DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT 3 /* These two constants represent SIZE_MULT/COUNT_MULT load factor without using a float. + * Currently, that means a 0.75 load factor. + * So, if count * COUNT_MULT / size * SIZE_MULT != 0, then we've exceeded + * the load factor of the ddict hash set. + */ #define DDICT_HASHSET_TABLE_BASE_SIZE 64 #define DDICT_HASHSET_RESIZE_FACTOR 2 @@ -177,12 +177,15 @@ static const ZSTD_DDict* ZSTD_DDictHashSet_getDDict(ZSTD_DDictHashSet* hashSet, static ZSTD_DDictHashSet* ZSTD_createDDictHashSet(ZSTD_customMem customMem) { ZSTD_DDictHashSet* ret = (ZSTD_DDictHashSet*)ZSTD_customMalloc(sizeof(ZSTD_DDictHashSet), customMem); DEBUGLOG(4, "Allocating new hash set"); + if (!ret) + return NULL; ret->ddictPtrTable = (const ZSTD_DDict**)ZSTD_customCalloc(DDICT_HASHSET_TABLE_BASE_SIZE * sizeof(ZSTD_DDict*), customMem); - ret->ddictPtrTableSize = DDICT_HASHSET_TABLE_BASE_SIZE; - ret->ddictPtrCount = 0; - if (!ret || !ret->ddictPtrTable) { + if (!ret->ddictPtrTable) { + ZSTD_customFree(ret, customMem); return NULL; } + ret->ddictPtrTableSize = DDICT_HASHSET_TABLE_BASE_SIZE; + ret->ddictPtrCount = 0; return ret; } @@ -241,6 +244,7 @@ static void ZSTD_DCtx_resetParameters(ZSTD_DCtx* dctx) dctx->outBufferMode = ZSTD_bm_buffered; dctx->forceIgnoreChecksum = ZSTD_d_validateChecksum; dctx->refMultipleDDicts = ZSTD_rmd_refSingleDDict; + dctx->disableHufAsm = 0; } static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) @@ -255,11 +259,15 @@ static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) dctx->inBuffSize = 0; dctx->outBuffSize = 0; dctx->streamStage = zdss_init; +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) dctx->legacyContext = NULL; dctx->previousLegacyVersion = 0; +#endif dctx->noForwardProgress = 0; dctx->oversizedDuration = 0; - dctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid()); +#if DYNAMIC_BMI2 + dctx->bmi2 = ZSTD_cpuSupportsBmi2(); +#endif dctx->ddictSet = NULL; ZSTD_DCtx_resetParameters(dctx); #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION @@ -280,8 +288,7 @@ ZSTD_DCtx* ZSTD_initStaticDCtx(void *workspace, size_t workspaceSize) return dctx; } -ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem) -{ +static ZSTD_DCtx* ZSTD_createDCtx_internal(ZSTD_customMem customMem) { if ((!customMem.customAlloc) ^ (!customMem.customFree)) return NULL; { ZSTD_DCtx* const dctx = (ZSTD_DCtx*)ZSTD_customMalloc(sizeof(*dctx), customMem); @@ -292,10 +299,15 @@ ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem) } } +ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem) +{ + return ZSTD_createDCtx_internal(customMem); +} + ZSTD_DCtx* ZSTD_createDCtx(void) { DEBUGLOG(3, "ZSTD_createDCtx"); - return ZSTD_createDCtx_advanced(ZSTD_defaultCMem); + return ZSTD_createDCtx_internal(ZSTD_defaultCMem); } static void ZSTD_clearDict(ZSTD_DCtx* dctx) @@ -380,6 +392,19 @@ unsigned ZSTD_isFrame(const void* buffer, size_t size) return 0; } +/*! ZSTD_isSkippableFrame() : + * Tells if the content of `buffer` starts with a valid Frame Identifier for a skippable frame. + * Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0. + */ +unsigned ZSTD_isSkippableFrame(const void* buffer, size_t size) +{ + if (size < ZSTD_FRAMEIDSIZE) return 0; + { U32 const magic = MEM_readLE32(buffer); + if ((magic & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) return 1; + } + return 0; +} + /** ZSTD_frameHeaderSize_internal() : * srcSize must be large enough to reach header size fields. * note : only works for formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless. @@ -415,16 +440,40 @@ size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize) * note : only works for formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless * @return : 0, `zfhPtr` is correctly filled, * >0, `srcSize` is too small, value is wanted `srcSize` amount, - * or an error code, which can be tested using ZSTD_isError() */ +** or an error code, which can be tested using ZSTD_isError() */ size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format) { const BYTE* ip = (const BYTE*)src; size_t const minInputSize = ZSTD_startingInputLength(format); - ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr)); /* not strictly necessary, but static analyzer do not understand that zfhPtr is only going to be read only if return value is zero, since they are 2 different signals */ - if (srcSize < minInputSize) return minInputSize; - RETURN_ERROR_IF(src==NULL, GENERIC, "invalid parameter"); + DEBUGLOG(5, "ZSTD_getFrameHeader_advanced: minInputSize = %zu, srcSize = %zu", minInputSize, srcSize); + if (srcSize > 0) { + /* note : technically could be considered an assert(), since it's an invalid entry */ + RETURN_ERROR_IF(src==NULL, GENERIC, "invalid parameter : src==NULL, but srcSize>0"); + } + if (srcSize < minInputSize) { + if (srcSize > 0 && format != ZSTD_f_zstd1_magicless) { + /* when receiving less than @minInputSize bytes, + * control these bytes at least correspond to a supported magic number + * in order to error out early if they don't. + **/ + size_t const toCopy = MIN(4, srcSize); + unsigned char hbuf[4]; MEM_writeLE32(hbuf, ZSTD_MAGICNUMBER); + assert(src != NULL); + ZSTD_memcpy(hbuf, src, toCopy); + if ( MEM_readLE32(hbuf) != ZSTD_MAGICNUMBER ) { + /* not a zstd frame : let's check if it's a skippable frame */ + MEM_writeLE32(hbuf, ZSTD_MAGIC_SKIPPABLE_START); + ZSTD_memcpy(hbuf, src, toCopy); + if ((MEM_readLE32(hbuf) & ZSTD_MAGIC_SKIPPABLE_MASK) != ZSTD_MAGIC_SKIPPABLE_START) { + RETURN_ERROR(prefix_unknown, + "first bytes don't correspond to any supported magic number"); + } } } + return minInputSize; + } + + ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr)); /* not strictly necessary, but static analyzers may not understand that zfhPtr will be read only if return value is zero, since they are 2 different signals */ if ( (format != ZSTD_f_zstd1_magicless) && (MEM_readLE32(src) != ZSTD_MAGICNUMBER) ) { if ((MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { @@ -466,7 +515,9 @@ size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, s } switch(dictIDSizeCode) { - default: assert(0); /* impossible */ + default: + assert(0); /* impossible */ + ZSTD_FALLTHROUGH; case 0 : break; case 1 : dictID = ip[pos]; pos++; break; case 2 : dictID = MEM_readLE16(ip+pos); pos+=2; break; @@ -474,7 +525,9 @@ size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, s } switch(fcsID) { - default: assert(0); /* impossible */ + default: + assert(0); /* impossible */ + ZSTD_FALLTHROUGH; case 0 : if (singleSegment) frameContentSize = ip[pos]; break; case 1 : frameContentSize = MEM_readLE16(ip+pos)+256; break; case 2 : frameContentSize = MEM_readLE32(ip+pos); break; @@ -503,7 +556,6 @@ size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t src return ZSTD_getFrameHeader_advanced(zfhPtr, src, srcSize, ZSTD_f_zstd1); } - /** ZSTD_getFrameContentSize() : * compatible with legacy mode * @return : decompressed size of the single frame pointed to be `src` if known, otherwise @@ -537,18 +589,52 @@ static size_t readSkippableFrameSize(void const* src, size_t srcSize) sizeU32 = MEM_readLE32((BYTE const*)src + ZSTD_FRAMEIDSIZE); RETURN_ERROR_IF((U32)(sizeU32 + ZSTD_SKIPPABLEHEADERSIZE) < sizeU32, frameParameter_unsupported, ""); - { - size_t const skippableSize = skippableHeaderSize + sizeU32; + { size_t const skippableSize = skippableHeaderSize + sizeU32; RETURN_ERROR_IF(skippableSize > srcSize, srcSize_wrong, ""); return skippableSize; } } +/*! ZSTD_readSkippableFrame() : + * Retrieves content of a skippable frame, and writes it to dst buffer. + * + * The parameter magicVariant will receive the magicVariant that was supplied when the frame was written, + * i.e. magicNumber - ZSTD_MAGIC_SKIPPABLE_START. This can be NULL if the caller is not interested + * in the magicVariant. + * + * Returns an error if destination buffer is not large enough, or if this is not a valid skippable frame. + * + * @return : number of bytes written or a ZSTD error. + */ +size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity, + unsigned* magicVariant, /* optional, can be NULL */ + const void* src, size_t srcSize) +{ + RETURN_ERROR_IF(srcSize < ZSTD_SKIPPABLEHEADERSIZE, srcSize_wrong, ""); + + { U32 const magicNumber = MEM_readLE32(src); + size_t skippableFrameSize = readSkippableFrameSize(src, srcSize); + size_t skippableContentSize = skippableFrameSize - ZSTD_SKIPPABLEHEADERSIZE; + + /* check input validity */ + RETURN_ERROR_IF(!ZSTD_isSkippableFrame(src, srcSize), frameParameter_unsupported, ""); + RETURN_ERROR_IF(skippableFrameSize < ZSTD_SKIPPABLEHEADERSIZE || skippableFrameSize > srcSize, srcSize_wrong, ""); + RETURN_ERROR_IF(skippableContentSize > dstCapacity, dstSize_tooSmall, ""); + + /* deliver payload */ + if (skippableContentSize > 0 && dst != NULL) + ZSTD_memcpy(dst, (const BYTE *)src + ZSTD_SKIPPABLEHEADERSIZE, skippableContentSize); + if (magicVariant != NULL) + *magicVariant = magicNumber - ZSTD_MAGIC_SKIPPABLE_START; + return skippableContentSize; + } +} + /** ZSTD_findDecompressedSize() : - * compatible with legacy mode * `srcSize` must be the exact length of some number of ZSTD compressed and/or * skippable frames - * @return : decompressed size of the frames contained */ + * note: compatible with legacy mode + * @return : decompressed size of the frames contained */ unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize) { unsigned long long totalDstSize = 0; @@ -558,9 +644,7 @@ unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize) if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { size_t const skippableSize = readSkippableFrameSize(src, srcSize); - if (ZSTD_isError(skippableSize)) { - return ZSTD_CONTENTSIZE_ERROR; - } + if (ZSTD_isError(skippableSize)) return ZSTD_CONTENTSIZE_ERROR; assert(skippableSize <= srcSize); src = (const BYTE *)src + skippableSize; @@ -568,17 +652,17 @@ unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize) continue; } - { unsigned long long const ret = ZSTD_getFrameContentSize(src, srcSize); - if (ret >= ZSTD_CONTENTSIZE_ERROR) return ret; + { unsigned long long const fcs = ZSTD_getFrameContentSize(src, srcSize); + if (fcs >= ZSTD_CONTENTSIZE_ERROR) return fcs; - /* check for overflow */ - if (totalDstSize + ret < totalDstSize) return ZSTD_CONTENTSIZE_ERROR; - totalDstSize += ret; + if (totalDstSize + fcs < totalDstSize) + return ZSTD_CONTENTSIZE_ERROR; /* check for overflow */ + totalDstSize += fcs; } + /* skip to next frame */ { size_t const frameSrcSize = ZSTD_findFrameCompressedSize(src, srcSize); - if (ZSTD_isError(frameSrcSize)) { - return ZSTD_CONTENTSIZE_ERROR; - } + if (ZSTD_isError(frameSrcSize)) return ZSTD_CONTENTSIZE_ERROR; + assert(frameSrcSize <= srcSize); src = (const BYTE *)src + frameSrcSize; srcSize -= frameSrcSize; @@ -700,10 +784,11 @@ static ZSTD_frameSizeInfo ZSTD_findFrameSizeInfo(const void* src, size_t srcSize ip += 4; } + frameSizeInfo.nbBlocks = nbBlocks; frameSizeInfo.compressedSize = (size_t)(ip - ipstart); frameSizeInfo.decompressedBound = (zfh.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN) ? zfh.frameContentSize - : nbBlocks * zfh.blockSizeMax; + : (unsigned long long)nbBlocks * zfh.blockSizeMax; return frameSizeInfo; } } @@ -743,6 +828,48 @@ unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize) return bound; } +size_t ZSTD_decompressionMargin(void const* src, size_t srcSize) +{ + size_t margin = 0; + unsigned maxBlockSize = 0; + + /* Iterate over each frame */ + while (srcSize > 0) { + ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize); + size_t const compressedSize = frameSizeInfo.compressedSize; + unsigned long long const decompressedBound = frameSizeInfo.decompressedBound; + ZSTD_frameHeader zfh; + + FORWARD_IF_ERROR(ZSTD_getFrameHeader(&zfh, src, srcSize), ""); + if (ZSTD_isError(compressedSize) || decompressedBound == ZSTD_CONTENTSIZE_ERROR) + return ERROR(corruption_detected); + + if (zfh.frameType == ZSTD_frame) { + /* Add the frame header to our margin */ + margin += zfh.headerSize; + /* Add the checksum to our margin */ + margin += zfh.checksumFlag ? 4 : 0; + /* Add 3 bytes per block */ + margin += 3 * frameSizeInfo.nbBlocks; + + /* Compute the max block size */ + maxBlockSize = MAX(maxBlockSize, zfh.blockSizeMax); + } else { + assert(zfh.frameType == ZSTD_skippableFrame); + /* Add the entire skippable frame size to our margin. */ + margin += compressedSize; + } + + assert(srcSize >= compressedSize); + src = (const BYTE*)src + compressedSize; + srcSize -= compressedSize; + } + + /* Add the max block size back to the margin. */ + margin += maxBlockSize; + + return margin; +} /*-************************************************************* * Frame decoding @@ -768,7 +895,7 @@ static size_t ZSTD_copyRawBlock(void* dst, size_t dstCapacity, if (srcSize == 0) return 0; RETURN_ERROR(dstBuffer_null, ""); } - ZSTD_memcpy(dst, src, srcSize); + ZSTD_memmove(dst, src, srcSize); return srcSize; } @@ -846,6 +973,7 @@ static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx, /* Loop on each block */ while (1) { + BYTE* oBlockEnd = oend; size_t decodedSize; blockProperties_t blockProperties; size_t const cBlockSize = ZSTD_getcBlockSize(ip, remainingSrcSize, &blockProperties); @@ -855,16 +983,34 @@ static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx, remainingSrcSize -= ZSTD_blockHeaderSize; RETURN_ERROR_IF(cBlockSize > remainingSrcSize, srcSize_wrong, ""); + if (ip >= op && ip < oBlockEnd) { + /* We are decompressing in-place. Limit the output pointer so that we + * don't overwrite the block that we are currently reading. This will + * fail decompression if the input & output pointers aren't spaced + * far enough apart. + * + * This is important to set, even when the pointers are far enough + * apart, because ZSTD_decompressBlock_internal() can decide to store + * literals in the output buffer, after the block it is decompressing. + * Since we don't want anything to overwrite our input, we have to tell + * ZSTD_decompressBlock_internal to never write past ip. + * + * See ZSTD_allocateLiteralsBuffer() for reference. + */ + oBlockEnd = op + (ip - op); + } + switch(blockProperties.blockType) { case bt_compressed: - decodedSize = ZSTD_decompressBlock_internal(dctx, op, (size_t)(oend-op), ip, cBlockSize, /* frame */ 1); + decodedSize = ZSTD_decompressBlock_internal(dctx, op, (size_t)(oBlockEnd-op), ip, cBlockSize, /* frame */ 1, not_streaming); break; case bt_raw : + /* Use oend instead of oBlockEnd because this function is safe to overlap. It uses memmove. */ decodedSize = ZSTD_copyRawBlock(op, (size_t)(oend-op), ip, cBlockSize); break; case bt_rle : - decodedSize = ZSTD_setRleBlock(op, (size_t)(oend-op), *ip, blockProperties.origSize); + decodedSize = ZSTD_setRleBlock(op, (size_t)(oBlockEnd-op), *ip, blockProperties.origSize); break; case bt_reserved : default: @@ -899,6 +1045,7 @@ static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx, } ZSTD_DCtx_trace_end(dctx, (U64)(op-ostart), (U64)(ip-istart), /* streaming */ 0); /* Allow caller to get size read */ + DEBUGLOG(4, "ZSTD_decompressFrame: decompressed frame of size %zi, consuming %zi bytes of input", op-ostart, ip - (const BYTE*)*srcPtr); *srcPtr = ip; *srcSizePtr = remainingSrcSize; return (size_t)(op-ostart); @@ -945,17 +1092,18 @@ static size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx, } #endif - { U32 const magicNumber = MEM_readLE32(src); - DEBUGLOG(4, "reading magic number %08X (expecting %08X)", - (unsigned)magicNumber, ZSTD_MAGICNUMBER); + if (srcSize >= 4) { + U32 const magicNumber = MEM_readLE32(src); + DEBUGLOG(5, "reading magic number %08X", (unsigned)magicNumber); if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { + /* skippable frame detected : skip it */ size_t const skippableSize = readSkippableFrameSize(src, srcSize); - FORWARD_IF_ERROR(skippableSize, "readSkippableFrameSize failed"); + FORWARD_IF_ERROR(skippableSize, "invalid skippable frame"); assert(skippableSize <= srcSize); src = (const BYTE *)src + skippableSize; srcSize -= skippableSize; - continue; + continue; /* check next frame */ } } if (ddict) { @@ -1009,7 +1157,7 @@ static ZSTD_DDict const* ZSTD_getDDict(ZSTD_DCtx* dctx) switch (dctx->dictUses) { default: assert(0 /* Impossible */); - /* fall-through */ + ZSTD_FALLTHROUGH; case ZSTD_dont_use: ZSTD_clearDict(dctx); return NULL; @@ -1031,7 +1179,7 @@ size_t ZSTD_decompress(void* dst, size_t dstCapacity, const void* src, size_t sr { #if defined(ZSTD_HEAPMODE) && (ZSTD_HEAPMODE>=1) size_t regenSize; - ZSTD_DCtx* const dctx = ZSTD_createDCtx(); + ZSTD_DCtx* const dctx = ZSTD_createDCtx_internal(ZSTD_defaultCMem); RETURN_ERROR_IF(dctx==NULL, memory_allocation, "NULL pointer!"); regenSize = ZSTD_decompressDCtx(dctx, dst, dstCapacity, src, srcSize); ZSTD_freeDCtx(dctx); @@ -1051,8 +1199,8 @@ size_t ZSTD_decompress(void* dst, size_t dstCapacity, const void* src, size_t sr size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx) { return dctx->expected; } /** - * Similar to ZSTD_nextSrcSizeToDecompress(), but when when a block input can be streamed, - * we allow taking a partial block as the input. Currently only raw uncompressed blocks can + * Similar to ZSTD_nextSrcSizeToDecompress(), but when a block input can be streamed, we + * allow taking a partial block as the input. Currently only raw uncompressed blocks can * be streamed. * * For blocks that can be streamed, this allows us to reduce the latency until we produce @@ -1065,7 +1213,7 @@ static size_t ZSTD_nextSrcSizeToDecompressWithInputSize(ZSTD_DCtx* dctx, size_t return dctx->expected; if (dctx->bType != bt_raw) return dctx->expected; - return MIN(MAX(inputSize, 1), dctx->expected); + return BOUNDED(1, inputSize, dctx->expected); } ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx) { @@ -1073,7 +1221,9 @@ ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx) { { default: /* should not happen */ assert(0); + ZSTD_FALLTHROUGH; case ZSTDds_getFrameHeaderSize: + ZSTD_FALLTHROUGH; case ZSTDds_decodeFrameHeader: return ZSTDnit_frameHeader; case ZSTDds_decodeBlockHeader: @@ -1085,6 +1235,7 @@ ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx) { case ZSTDds_checkChecksum: return ZSTDnit_checksum; case ZSTDds_decodeSkippableHeader: + ZSTD_FALLTHROUGH; case ZSTDds_skipFrame: return ZSTDnit_skippableFrame; } @@ -1168,7 +1319,7 @@ size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, c { case bt_compressed: DEBUGLOG(5, "ZSTD_decompressContinue: case bt_compressed"); - rSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, /* frame */ 1); + rSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, /* frame */ 1, is_streaming); dctx->expected = 0; /* Streaming not supported */ break; case bt_raw : @@ -1249,7 +1400,7 @@ size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, c default: assert(0); /* impossible */ - RETURN_ERROR(GENERIC, "impossible to reach"); /* some compiler require default to do something */ + RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */ } } @@ -1290,11 +1441,11 @@ ZSTD_loadDEntropy(ZSTD_entropyDTables_t* entropy, /* in minimal huffman, we always use X1 variants */ size_t const hSize = HUF_readDTableX1_wksp(entropy->hufTable, dictPtr, dictEnd - dictPtr, - workspace, workspaceSize); + workspace, workspaceSize, /* flags */ 0); #else size_t const hSize = HUF_readDTableX2_wksp(entropy->hufTable, dictPtr, (size_t)(dictEnd - dictPtr), - workspace, workspaceSize); + workspace, workspaceSize, /* flags */ 0); #endif RETURN_ERROR_IF(HUF_isError(hSize), dictionary_corrupted, ""); dictPtr += hSize; @@ -1393,7 +1544,7 @@ size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx) dctx->prefixStart = NULL; dctx->virtualStart = NULL; dctx->dictEnd = NULL; - dctx->entropy.hufTable[0] = (HUF_DTable)((HufLog)*0x1000001); /* cover both little and big endian */ + dctx->entropy.hufTable[0] = (HUF_DTable)((ZSTD_HUFFDTABLE_CAPACITY_LOG)*0x1000001); /* cover both little and big endian */ dctx->litEntropy = dctx->fseEntropy = 0; dctx->dictID = 0; dctx->bType = bt_reserved; @@ -1455,7 +1606,7 @@ unsigned ZSTD_getDictID_fromDict(const void* dict, size_t dictSize) * This could for one of the following reasons : * - The frame does not require a dictionary (most common case). * - The frame was built with dictID intentionally removed. - * Needed dictionary is a hidden information. + * Needed dictionary is a hidden piece of information. * Note : this use case also happens when using a non-conformant dictionary. * - `srcSize` is too small, and as a result, frame header could not be decoded. * Note : possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`. @@ -1464,7 +1615,7 @@ unsigned ZSTD_getDictID_fromDict(const void* dict, size_t dictSize) * ZSTD_getFrameHeader(), which will provide a more precise error code. */ unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize) { - ZSTD_frameHeader zfp = { 0, 0, 0, ZSTD_frame, 0, 0, 0 }; + ZSTD_frameHeader zfp = { 0, 0, 0, ZSTD_frame, 0, 0, 0, 0, 0 }; size_t const hError = ZSTD_getFrameHeader(&zfp, src, srcSize); if (ZSTD_isError(hError)) return 0; return zfp.dictID; @@ -1493,7 +1644,7 @@ size_t ZSTD_decompress_usingDDict(ZSTD_DCtx* dctx, ZSTD_DStream* ZSTD_createDStream(void) { DEBUGLOG(3, "ZSTD_createDStream"); - return ZSTD_createDStream_advanced(ZSTD_defaultCMem); + return ZSTD_createDCtx_internal(ZSTD_defaultCMem); } ZSTD_DStream* ZSTD_initStaticDStream(void *workspace, size_t workspaceSize) @@ -1503,7 +1654,7 @@ ZSTD_DStream* ZSTD_initStaticDStream(void *workspace, size_t workspaceSize) ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem) { - return ZSTD_createDCtx_advanced(customMem); + return ZSTD_createDCtx_internal(customMem); } size_t ZSTD_freeDStream(ZSTD_DStream* zds) @@ -1571,7 +1722,9 @@ size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t di size_t ZSTD_initDStream(ZSTD_DStream* zds) { DEBUGLOG(4, "ZSTD_initDStream"); - return ZSTD_initDStream_usingDDict(zds, NULL); + FORWARD_IF_ERROR(ZSTD_DCtx_reset(zds, ZSTD_reset_session_only), ""); + FORWARD_IF_ERROR(ZSTD_DCtx_refDDict(zds, NULL), ""); + return ZSTD_startingInputLength(zds->format); } /* ZSTD_initDStream_usingDDict() : @@ -1579,6 +1732,7 @@ size_t ZSTD_initDStream(ZSTD_DStream* zds) * this function cannot fail */ size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* dctx, const ZSTD_DDict* ddict) { + DEBUGLOG(4, "ZSTD_initDStream_usingDDict"); FORWARD_IF_ERROR( ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only) , ""); FORWARD_IF_ERROR( ZSTD_DCtx_refDDict(dctx, ddict) , ""); return ZSTD_startingInputLength(dctx->format); @@ -1589,6 +1743,7 @@ size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* dctx, const ZSTD_DDict* ddict) * this function cannot fail */ size_t ZSTD_resetDStream(ZSTD_DStream* dctx) { + DEBUGLOG(4, "ZSTD_resetDStream"); FORWARD_IF_ERROR(ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only), ""); return ZSTD_startingInputLength(dctx->format); } @@ -1660,6 +1815,11 @@ ZSTD_bounds ZSTD_dParam_getBounds(ZSTD_dParameter dParam) bounds.lowerBound = (int)ZSTD_rmd_refSingleDDict; bounds.upperBound = (int)ZSTD_rmd_refMultipleDDicts; return bounds; + case ZSTD_d_disableHuffmanAssembly: + bounds.lowerBound = 0; + bounds.upperBound = 1; + return bounds; + default:; } bounds.error = ERROR(parameter_unsupported); @@ -1700,6 +1860,9 @@ size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value case ZSTD_d_refMultipleDDicts: *value = (int)dctx->refMultipleDDicts; return 0; + case ZSTD_d_disableHuffmanAssembly: + *value = (int)dctx->disableHufAsm; + return 0; default:; } RETURN_ERROR(parameter_unsupported, ""); @@ -1733,6 +1896,10 @@ size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value } dctx->refMultipleDDicts = (ZSTD_refMultipleDDicts_e)value; return 0; + case ZSTD_d_disableHuffmanAssembly: + CHECK_DBOUNDS(ZSTD_d_disableHuffmanAssembly, value); + dctx->disableHufAsm = value != 0; + return 0; default:; } RETURN_ERROR(parameter_unsupported, ""); @@ -1763,7 +1930,8 @@ size_t ZSTD_sizeof_DStream(const ZSTD_DStream* dctx) size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize) { size_t const blockSize = (size_t) MIN(windowSize, ZSTD_BLOCKSIZE_MAX); - unsigned long long const neededRBSize = windowSize + blockSize + (WILDCOPY_OVERLENGTH * 2); + /* space is needed to store the litbuffer after the output of a given block without stomping the extDict of a previous run, as well as to cover both windows against wildcopy*/ + unsigned long long const neededRBSize = windowSize + blockSize + ZSTD_BLOCKSIZE_MAX + (WILDCOPY_OVERLENGTH * 2); unsigned long long const neededSize = MIN(frameContentSize, neededRBSize); size_t const minRBSize = (size_t) neededSize; RETURN_ERROR_IF((unsigned long long)minRBSize != neededSize, @@ -1897,10 +2065,12 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB DEBUGLOG(5, "stage zdss_init => transparent reset "); zds->streamStage = zdss_loadHeader; zds->lhSize = zds->inPos = zds->outStart = zds->outEnd = 0; +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) zds->legacyVersion = 0; +#endif zds->hostageByte = 0; zds->expectedOutBuffer = *output; - /* fall-through */ + ZSTD_FALLTHROUGH; case zdss_loadHeader : DEBUGLOG(5, "stage zdss_loadHeader (srcSize : %u)", (U32)(iend - ip)); @@ -1917,7 +2087,6 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB if (zds->refMultipleDDicts && zds->ddictSet) { ZSTD_DCtx_selectFrameDDict(zds); } - DEBUGLOG(5, "header size : %u", (U32)hSize); if (ZSTD_isError(hSize)) { #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) U32 const legacyVersion = ZSTD_isLegacy(istart, iend-istart); @@ -1949,6 +2118,11 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB zds->lhSize += remainingInput; } input->pos = input->size; + /* check first few bytes */ + FORWARD_IF_ERROR( + ZSTD_getFrameHeader_advanced(&zds->fParams, zds->headerBuffer, zds->lhSize, zds->format), + "First few bytes detected incorrect" ); + /* return hint input size */ return (MAX((size_t)ZSTD_FRAMEHEADERSIZE_MIN(zds->format), hSize) - zds->lhSize) + ZSTD_blockHeaderSize; /* remaining header bytes + next block header */ } assert(ip != NULL); @@ -1966,8 +2140,9 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB size_t const decompressedSize = ZSTD_decompress_usingDDict(zds, op, (size_t)(oend-op), istart, cSize, ZSTD_getDDict(zds)); if (ZSTD_isError(decompressedSize)) return decompressedSize; DEBUGLOG(4, "shortcut to single-pass ZSTD_decompress_usingDDict()") + assert(istart != NULL); ip = istart + cSize; - op += decompressedSize; + op = op ? op + decompressedSize : op; /* can occur if frameContentSize = 0 (empty frame) */ zds->expected = 0; zds->streamStage = zdss_init; someMoreWork = 0; @@ -2038,7 +2213,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB zds->outBuffSize = neededOutBuffSize; } } } zds->streamStage = zdss_read; - /* fall-through */ + ZSTD_FALLTHROUGH; case zdss_read: DEBUGLOG(5, "stage zdss_read"); @@ -2051,13 +2226,14 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB } if ((size_t)(iend-ip) >= neededInSize) { /* decode directly from src */ FORWARD_IF_ERROR(ZSTD_decompressContinueStream(zds, &op, oend, ip, neededInSize), ""); + assert(ip != NULL); ip += neededInSize; /* Function modifies the stage so we must break */ break; } } if (ip==iend) { someMoreWork = 0; break; } /* no more input */ zds->streamStage = zdss_load; - /* fall-through */ + ZSTD_FALLTHROUGH; case zdss_load: { size_t const neededInSize = ZSTD_nextSrcSizeToDecompress(zds); @@ -2065,7 +2241,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB int const isSkipFrame = ZSTD_isSkipFrame(zds); size_t loadedSize; /* At this point we shouldn't be decompressing a block that we can stream. */ - assert(neededInSize == ZSTD_nextSrcSizeToDecompressWithInputSize(zds, iend - ip)); + assert(neededInSize == ZSTD_nextSrcSizeToDecompressWithInputSize(zds, (size_t)(iend - ip))); if (isSkipFrame) { loadedSize = MIN(toLoad, (size_t)(iend-ip)); } else { @@ -2074,8 +2250,11 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB "should never happen"); loadedSize = ZSTD_limitCopy(zds->inBuff + zds->inPos, toLoad, ip, (size_t)(iend-ip)); } - ip += loadedSize; - zds->inPos += loadedSize; + if (loadedSize != 0) { + /* ip may be NULL */ + ip += loadedSize; + zds->inPos += loadedSize; + } if (loadedSize < toLoad) { someMoreWork = 0; break; } /* not enough input, wait for more */ /* decode loaded input */ @@ -2085,14 +2264,17 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB break; } case zdss_flush: - { size_t const toFlushSize = zds->outEnd - zds->outStart; + { + size_t const toFlushSize = zds->outEnd - zds->outStart; size_t const flushedSize = ZSTD_limitCopy(op, (size_t)(oend-op), zds->outBuff + zds->outStart, toFlushSize); - op += flushedSize; + + op = op ? op + flushedSize : op; + zds->outStart += flushedSize; if (flushedSize == toFlushSize) { /* flush completed */ zds->streamStage = zdss_read; if ( (zds->outBuffSize < zds->fParams.frameContentSize) - && (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) { + && (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) { DEBUGLOG(5, "restart filling outBuff from beginning (left:%i, needed:%u)", (int)(zds->outBuffSize - zds->outStart), (U32)zds->fParams.blockSizeMax); @@ -2106,7 +2288,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB default: assert(0); /* impossible */ - RETURN_ERROR(GENERIC, "impossible to reach"); /* some compiler require default to do something */ + RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */ } } /* result */ @@ -2119,8 +2301,8 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB if ((ip==istart) && (op==ostart)) { /* no forward progress */ zds->noForwardProgress ++; if (zds->noForwardProgress >= ZSTD_NO_FORWARD_PROGRESS_MAX) { - RETURN_ERROR_IF(op==oend, dstSize_tooSmall, ""); - RETURN_ERROR_IF(ip==iend, srcSize_wrong, ""); + RETURN_ERROR_IF(op==oend, noForwardProgress_destFull, ""); + RETURN_ERROR_IF(ip==iend, noForwardProgress_inputEmpty, ""); assert(0); } } else { @@ -2157,11 +2339,17 @@ size_t ZSTD_decompressStream_simpleArgs ( void* dst, size_t dstCapacity, size_t* dstPos, const void* src, size_t srcSize, size_t* srcPos) { - ZSTD_outBuffer output = { dst, dstCapacity, *dstPos }; - ZSTD_inBuffer input = { src, srcSize, *srcPos }; - /* ZSTD_compress_generic() will check validity of dstPos and srcPos */ - size_t const cErr = ZSTD_decompressStream(dctx, &output, &input); - *dstPos = output.pos; - *srcPos = input.pos; - return cErr; + ZSTD_outBuffer output; + ZSTD_inBuffer input; + output.dst = dst; + output.size = dstCapacity; + output.pos = *dstPos; + input.src = src; + input.size = srcSize; + input.pos = *srcPos; + { size_t const cErr = ZSTD_decompressStream(dctx, &output, &input); + *dstPos = output.pos; + *srcPos = input.pos; + return cErr; + } } diff --git a/Utilities/cmzstd/lib/decompress/zstd_decompress_block.c b/Utilities/cmzstd/lib/decompress/zstd_decompress_block.c index 349dcdc3336..09896a931e2 100644 --- a/Utilities/cmzstd/lib/decompress/zstd_decompress_block.c +++ b/Utilities/cmzstd/lib/decompress/zstd_decompress_block.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -20,12 +20,12 @@ #include "../common/mem.h" /* low level memory routines */ #define FSE_STATIC_LINKING_ONLY #include "../common/fse.h" -#define HUF_STATIC_LINKING_ONLY #include "../common/huf.h" #include "../common/zstd_internal.h" #include "zstd_decompress_internal.h" /* ZSTD_DCtx */ #include "zstd_ddict.h" /* ZSTD_DDictDictContent */ #include "zstd_decompress_block.h" +#include "../common/bits.h" /* ZSTD_highbit32 */ /*_******************************************************* * Macros @@ -69,15 +69,56 @@ size_t ZSTD_getcBlockSize(const void* src, size_t srcSize, } } +/* Allocate buffer for literals, either overlapping current dst, or split between dst and litExtraBuffer, or stored entirely within litExtraBuffer */ +static void ZSTD_allocateLiteralsBuffer(ZSTD_DCtx* dctx, void* const dst, const size_t dstCapacity, const size_t litSize, + const streaming_operation streaming, const size_t expectedWriteSize, const unsigned splitImmediately) +{ + if (streaming == not_streaming && dstCapacity > ZSTD_BLOCKSIZE_MAX + WILDCOPY_OVERLENGTH + litSize + WILDCOPY_OVERLENGTH) + { + /* room for litbuffer to fit without read faulting */ + dctx->litBuffer = (BYTE*)dst + ZSTD_BLOCKSIZE_MAX + WILDCOPY_OVERLENGTH; + dctx->litBufferEnd = dctx->litBuffer + litSize; + dctx->litBufferLocation = ZSTD_in_dst; + } + else if (litSize > ZSTD_LITBUFFEREXTRASIZE) + { + /* won't fit in litExtraBuffer, so it will be split between end of dst and extra buffer */ + if (splitImmediately) { + /* won't fit in litExtraBuffer, so it will be split between end of dst and extra buffer */ + dctx->litBuffer = (BYTE*)dst + expectedWriteSize - litSize + ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH; + dctx->litBufferEnd = dctx->litBuffer + litSize - ZSTD_LITBUFFEREXTRASIZE; + } + else { + /* initially this will be stored entirely in dst during huffman decoding, it will partially be shifted to litExtraBuffer after */ + dctx->litBuffer = (BYTE*)dst + expectedWriteSize - litSize; + dctx->litBufferEnd = (BYTE*)dst + expectedWriteSize; + } + dctx->litBufferLocation = ZSTD_split; + } + else + { + /* fits entirely within litExtraBuffer, so no split is necessary */ + dctx->litBuffer = dctx->litExtraBuffer; + dctx->litBufferEnd = dctx->litBuffer + litSize; + dctx->litBufferLocation = ZSTD_not_in_dst; + } +} /* Hidden declaration for fullbench */ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, - const void* src, size_t srcSize); + const void* src, size_t srcSize, + void* dst, size_t dstCapacity, const streaming_operation streaming); /*! ZSTD_decodeLiteralsBlock() : + * Where it is possible to do so without being stomped by the output during decompression, the literals block will be stored + * in the dstBuffer. If there is room to do so, it will be stored in full in the excess dst space after where the current + * block will be output. Otherwise it will be stored at the end of the current dst blockspace, with a small portion being + * stored in dctx->litExtraBuffer to help keep it "ahead" of the current output write. + * * @return : nb of bytes read from src (< srcSize ) * note : symbol not declared but exposed for fullbench */ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, - const void* src, size_t srcSize) /* note : srcSize < BLOCKSIZE */ + const void* src, size_t srcSize, /* note : srcSize < BLOCKSIZE */ + void* dst, size_t dstCapacity, const streaming_operation streaming) { DEBUGLOG(5, "ZSTD_decodeLiteralsBlock"); RETURN_ERROR_IF(srcSize < MIN_CBLOCK_SIZE, corruption_detected, ""); @@ -90,15 +131,19 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, case set_repeat: DEBUGLOG(5, "set_repeat flag : re-using stats from previous compressed literals block"); RETURN_ERROR_IF(dctx->litEntropy==0, dictionary_corrupted, ""); - /* fall-through */ + ZSTD_FALLTHROUGH; case set_compressed: - RETURN_ERROR_IF(srcSize < 5, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 3; here we need up to 5 for case 3"); + RETURN_ERROR_IF(srcSize < 5, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need up to 5 for case 3"); { size_t lhSize, litSize, litCSize; U32 singleStream=0; U32 const lhlCode = (istart[0] >> 2) & 3; U32 const lhc = MEM_readLE32(istart); size_t hufSuccess; + size_t expectedWriteSize = MIN(ZSTD_BLOCKSIZE_MAX, dstCapacity); + int const flags = 0 + | (ZSTD_DCtx_get_bmi2(dctx) ? HUF_flags_bmi2 : 0) + | (dctx->disableHufAsm ? HUF_flags_disableAsm : 0); switch(lhlCode) { case 0: case 1: default: /* note : default is impossible, since lhlCode into [0..3] */ @@ -121,8 +166,15 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, litCSize = (lhc >> 22) + ((size_t)istart[4] << 10); break; } + RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); RETURN_ERROR_IF(litSize > ZSTD_BLOCKSIZE_MAX, corruption_detected, ""); + if (!singleStream) + RETURN_ERROR_IF(litSize < MIN_LITERALS_FOR_4_STREAMS, literals_headerWrong, + "Not enough literals (%zu) for the 4-streams mode (min %u)", + litSize, MIN_LITERALS_FOR_4_STREAMS); RETURN_ERROR_IF(litCSize + lhSize > srcSize, corruption_detected, ""); + RETURN_ERROR_IF(expectedWriteSize < litSize , dstSize_tooSmall, ""); + ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 0); /* prefetch huffman table if cold */ if (dctx->ddictIsCold && (litSize > 768 /* heuristic */)) { @@ -131,13 +183,14 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, if (litEncType==set_repeat) { if (singleStream) { - hufSuccess = HUF_decompress1X_usingDTable_bmi2( + hufSuccess = HUF_decompress1X_usingDTable( dctx->litBuffer, litSize, istart+lhSize, litCSize, - dctx->HUFptr, dctx->bmi2); + dctx->HUFptr, flags); } else { - hufSuccess = HUF_decompress4X_usingDTable_bmi2( + assert(litSize >= MIN_LITERALS_FOR_4_STREAMS); + hufSuccess = HUF_decompress4X_usingDTable( dctx->litBuffer, litSize, istart+lhSize, litCSize, - dctx->HUFptr, dctx->bmi2); + dctx->HUFptr, flags); } } else { if (singleStream) { @@ -145,20 +198,27 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, hufSuccess = HUF_decompress1X_DCtx_wksp( dctx->entropy.hufTable, dctx->litBuffer, litSize, istart+lhSize, litCSize, dctx->workspace, - sizeof(dctx->workspace)); + sizeof(dctx->workspace), flags); #else - hufSuccess = HUF_decompress1X1_DCtx_wksp_bmi2( + hufSuccess = HUF_decompress1X1_DCtx_wksp( dctx->entropy.hufTable, dctx->litBuffer, litSize, istart+lhSize, litCSize, dctx->workspace, - sizeof(dctx->workspace), dctx->bmi2); + sizeof(dctx->workspace), flags); #endif } else { - hufSuccess = HUF_decompress4X_hufOnly_wksp_bmi2( + hufSuccess = HUF_decompress4X_hufOnly_wksp( dctx->entropy.hufTable, dctx->litBuffer, litSize, istart+lhSize, litCSize, dctx->workspace, - sizeof(dctx->workspace), dctx->bmi2); + sizeof(dctx->workspace), flags); } } + if (dctx->litBufferLocation == ZSTD_split) + { + ZSTD_memcpy(dctx->litExtraBuffer, dctx->litBufferEnd - ZSTD_LITBUFFEREXTRASIZE, ZSTD_LITBUFFEREXTRASIZE); + ZSTD_memmove(dctx->litBuffer + ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH, dctx->litBuffer, litSize - ZSTD_LITBUFFEREXTRASIZE); + dctx->litBuffer += ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH; + dctx->litBufferEnd -= WILDCOPY_OVERLENGTH; + } RETURN_ERROR_IF(HUF_isError(hufSuccess), corruption_detected, ""); @@ -166,13 +226,13 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, dctx->litSize = litSize; dctx->litEntropy = 1; if (litEncType==set_compressed) dctx->HUFptr = dctx->entropy.hufTable; - ZSTD_memset(dctx->litBuffer + dctx->litSize, 0, WILDCOPY_OVERLENGTH); return litCSize + lhSize; } case set_basic: { size_t litSize, lhSize; U32 const lhlCode = ((istart[0]) >> 2) & 3; + size_t expectedWriteSize = MIN(ZSTD_BLOCKSIZE_MAX, dstCapacity); switch(lhlCode) { case 0: case 2: default: /* note : default is impossible, since lhlCode into [0..3] */ @@ -185,27 +245,41 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, break; case 3: lhSize = 3; + RETURN_ERROR_IF(srcSize<3, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize = 3"); litSize = MEM_readLE24(istart) >> 4; break; } + RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); + RETURN_ERROR_IF(expectedWriteSize < litSize, dstSize_tooSmall, ""); + ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 1); if (lhSize+litSize+WILDCOPY_OVERLENGTH > srcSize) { /* risk reading beyond src buffer with wildcopy */ RETURN_ERROR_IF(litSize+lhSize > srcSize, corruption_detected, ""); - ZSTD_memcpy(dctx->litBuffer, istart+lhSize, litSize); + if (dctx->litBufferLocation == ZSTD_split) + { + ZSTD_memcpy(dctx->litBuffer, istart + lhSize, litSize - ZSTD_LITBUFFEREXTRASIZE); + ZSTD_memcpy(dctx->litExtraBuffer, istart + lhSize + litSize - ZSTD_LITBUFFEREXTRASIZE, ZSTD_LITBUFFEREXTRASIZE); + } + else + { + ZSTD_memcpy(dctx->litBuffer, istart + lhSize, litSize); + } dctx->litPtr = dctx->litBuffer; dctx->litSize = litSize; - ZSTD_memset(dctx->litBuffer + dctx->litSize, 0, WILDCOPY_OVERLENGTH); return lhSize+litSize; } /* direct reference into compressed stream */ dctx->litPtr = istart+lhSize; dctx->litSize = litSize; + dctx->litBufferEnd = dctx->litPtr + litSize; + dctx->litBufferLocation = ZSTD_not_in_dst; return lhSize+litSize; } case set_rle: { U32 const lhlCode = ((istart[0]) >> 2) & 3; size_t litSize, lhSize; + size_t expectedWriteSize = MIN(ZSTD_BLOCKSIZE_MAX, dstCapacity); switch(lhlCode) { case 0: case 2: default: /* note : default is impossible, since lhlCode into [0..3] */ @@ -214,16 +288,28 @@ size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx, break; case 1: lhSize = 2; + RETURN_ERROR_IF(srcSize<3, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize+1 = 3"); litSize = MEM_readLE16(istart) >> 4; break; case 3: lhSize = 3; + RETURN_ERROR_IF(srcSize<4, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize+1 = 4"); litSize = MEM_readLE24(istart) >> 4; - RETURN_ERROR_IF(srcSize<4, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 3; here we need lhSize+1 = 4"); break; } + RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); RETURN_ERROR_IF(litSize > ZSTD_BLOCKSIZE_MAX, corruption_detected, ""); - ZSTD_memset(dctx->litBuffer, istart[lhSize], litSize + WILDCOPY_OVERLENGTH); + RETURN_ERROR_IF(expectedWriteSize < litSize, dstSize_tooSmall, ""); + ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 1); + if (dctx->litBufferLocation == ZSTD_split) + { + ZSTD_memset(dctx->litBuffer, istart[lhSize], litSize - ZSTD_LITBUFFEREXTRASIZE); + ZSTD_memset(dctx->litExtraBuffer, istart[lhSize], ZSTD_LITBUFFEREXTRASIZE); + } + else + { + ZSTD_memset(dctx->litBuffer, istart[lhSize], litSize); + } dctx->litPtr = dctx->litBuffer; dctx->litSize = litSize; return lhSize+1; @@ -343,7 +429,7 @@ static const ZSTD_seqSymbol ML_defaultDTable[(1<nbBits = 0; cell->nextState = 0; assert(nbAddBits < 255); - cell->nbAdditionalBits = (BYTE)nbAddBits; + cell->nbAdditionalBits = nbAddBits; cell->baseValue = baseValue; } @@ -367,7 +453,7 @@ static void ZSTD_buildSeqTable_rle(ZSTD_seqSymbol* dt, U32 baseValue, U32 nbAddB FORCE_INLINE_TEMPLATE void ZSTD_buildFSETable_body(ZSTD_seqSymbol* dt, const short* normalizedCounter, unsigned maxSymbolValue, - const U32* baseValue, const U32* nbAdditionalBits, + const U32* baseValue, const U8* nbAdditionalBits, unsigned tableLog, void* wksp, size_t wkspSize) { ZSTD_seqSymbol* const tableDecode = dt+1; @@ -430,14 +516,15 @@ void ZSTD_buildFSETable_body(ZSTD_seqSymbol* dt, for (i = 8; i < n; i += 8) { MEM_write64(spread + pos + i, sv); } - pos += n; + assert(n>=0); + pos += (size_t)n; } } /* Now we spread those positions across the table. - * The benefit of doing it in two stages is that we avoid the the + * The benefit of doing it in two stages is that we avoid the * variable size inner loop, which caused lots of branch misses. * Now we can run through all the positions without any branch misses. - * We unroll the loop twice, since that is what emperically worked best. + * We unroll the loop twice, since that is what empirically worked best. */ { size_t position = 0; @@ -464,7 +551,7 @@ void ZSTD_buildFSETable_body(ZSTD_seqSymbol* dt, for (i=0; i highThreshold) position = (position + step) & tableMask; /* lowprob area */ + while (UNLIKELY(position > highThreshold)) position = (position + step) & tableMask; /* lowprob area */ } } assert(position == 0); /* position must reach all cells once, otherwise normalizedCounter is incorrect */ } @@ -475,10 +562,10 @@ void ZSTD_buildFSETable_body(ZSTD_seqSymbol* dt, for (u=0; u max, corruption_detected, ""); { U32 const symbol = *(const BYTE*)src; U32 const baseline = baseValue[symbol]; - U32 const nbBits = nbAdditionalBits[symbol]; + U8 const nbBits = nbAdditionalBits[symbol]; ZSTD_buildSeqTable_rle(DTableSpace, baseline, nbBits); } *DTablePtr = DTableSpace; @@ -620,7 +707,7 @@ size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, LL_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, dctx->workspace, sizeof(dctx->workspace), - dctx->bmi2); + ZSTD_DCtx_get_bmi2(dctx)); RETURN_ERROR_IF(ZSTD_isError(llhSize), corruption_detected, "ZSTD_buildSeqTable failed"); ip += llhSize; } @@ -632,7 +719,7 @@ size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, OF_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, dctx->workspace, sizeof(dctx->workspace), - dctx->bmi2); + ZSTD_DCtx_get_bmi2(dctx)); RETURN_ERROR_IF(ZSTD_isError(ofhSize), corruption_detected, "ZSTD_buildSeqTable failed"); ip += ofhSize; } @@ -644,7 +731,7 @@ size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, ML_defaultDTable, dctx->fseEntropy, dctx->ddictIsCold, nbSeq, dctx->workspace, sizeof(dctx->workspace), - dctx->bmi2); + ZSTD_DCtx_get_bmi2(dctx)); RETURN_ERROR_IF(ZSTD_isError(mlhSize), corruption_detected, "ZSTD_buildSeqTable failed"); ip += mlhSize; } @@ -713,7 +800,7 @@ HINT_INLINE void ZSTD_overlapCopy8(BYTE** op, BYTE const** ip, size_t offset) { * - ZSTD_overlap_src_before_dst: The src and dst may overlap and may be any distance apart. * The src buffer must be before the dst buffer. */ -static void ZSTD_safecopy(BYTE* op, BYTE* const oend_w, BYTE const* ip, ptrdiff_t length, ZSTD_overlap_e ovtype) { +static void ZSTD_safecopy(BYTE* op, const BYTE* const oend_w, BYTE const* ip, ptrdiff_t length, ZSTD_overlap_e ovtype) { ptrdiff_t const diff = op - ip; BYTE* const oend = op + length; @@ -729,6 +816,7 @@ static void ZSTD_safecopy(BYTE* op, BYTE* const oend_w, BYTE const* ip, ptrdiff_ /* Copy 8 bytes and ensure the offset >= 8 when there can be overlap. */ assert(length >= 8); ZSTD_overlapCopy8(&op, &ip, diff); + length -= 8; assert(op - ip >= 8); assert(op <= oend); } @@ -743,12 +831,35 @@ static void ZSTD_safecopy(BYTE* op, BYTE* const oend_w, BYTE const* ip, ptrdiff_ assert(oend > oend_w); ZSTD_wildcopy(op, ip, oend_w - op, ovtype); ip += oend_w - op; - op = oend_w; + op += oend_w - op; } /* Handle the leftovers. */ while (op < oend) *op++ = *ip++; } +/* ZSTD_safecopyDstBeforeSrc(): + * This version allows overlap with dst before src, or handles the non-overlap case with dst after src + * Kept separate from more common ZSTD_safecopy case to avoid performance impact to the safecopy common case */ +static void ZSTD_safecopyDstBeforeSrc(BYTE* op, BYTE const* ip, ptrdiff_t length) { + ptrdiff_t const diff = op - ip; + BYTE* const oend = op + length; + + if (length < 8 || diff > -8) { + /* Handle short lengths, close overlaps, and dst not before src. */ + while (op < oend) *op++ = *ip++; + return; + } + + if (op <= oend - WILDCOPY_OVERLENGTH && diff < -WILDCOPY_VECLEN) { + ZSTD_wildcopy(op, ip, oend - WILDCOPY_OVERLENGTH - op, ZSTD_no_overlap); + ip += oend - WILDCOPY_OVERLENGTH - op; + op += oend - WILDCOPY_OVERLENGTH - op; + } + + /* Handle the leftovers. */ + while (op < oend) *op++ = *ip++; +} + /* ZSTD_execSequenceEnd(): * This version handles cases that are near the end of the output buffer. It requires * more careful checks to make sure there is no overflow. By separating out these hard @@ -759,9 +870,9 @@ static void ZSTD_safecopy(BYTE* op, BYTE* const oend_w, BYTE const* ip, ptrdiff_ */ FORCE_NOINLINE size_t ZSTD_execSequenceEnd(BYTE* op, - BYTE* const oend, seq_t sequence, - const BYTE** litPtr, const BYTE* const litLimit, - const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd) + BYTE* const oend, seq_t sequence, + const BYTE** litPtr, const BYTE* const litLimit, + const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd) { BYTE* const oLitEnd = op + sequence.litLength; size_t const sequenceLength = sequence.litLength + sequence.matchLength; @@ -784,27 +895,76 @@ size_t ZSTD_execSequenceEnd(BYTE* op, if (sequence.offset > (size_t)(oLitEnd - prefixStart)) { /* offset beyond prefix */ RETURN_ERROR_IF(sequence.offset > (size_t)(oLitEnd - virtualStart), corruption_detected, ""); - match = dictEnd - (prefixStart-match); + match = dictEnd - (prefixStart - match); if (match + sequence.matchLength <= dictEnd) { ZSTD_memmove(oLitEnd, match, sequence.matchLength); return sequenceLength; } /* span extDict & currentPrefixSegment */ { size_t const length1 = dictEnd - match; - ZSTD_memmove(oLitEnd, match, length1); - op = oLitEnd + length1; - sequence.matchLength -= length1; - match = prefixStart; - } } + ZSTD_memmove(oLitEnd, match, length1); + op = oLitEnd + length1; + sequence.matchLength -= length1; + match = prefixStart; + } + } + ZSTD_safecopy(op, oend_w, match, sequence.matchLength, ZSTD_overlap_src_before_dst); + return sequenceLength; +} + +/* ZSTD_execSequenceEndSplitLitBuffer(): + * This version is intended to be used during instances where the litBuffer is still split. It is kept separate to avoid performance impact for the good case. + */ +FORCE_NOINLINE +size_t ZSTD_execSequenceEndSplitLitBuffer(BYTE* op, + BYTE* const oend, const BYTE* const oend_w, seq_t sequence, + const BYTE** litPtr, const BYTE* const litLimit, + const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd) +{ + BYTE* const oLitEnd = op + sequence.litLength; + size_t const sequenceLength = sequence.litLength + sequence.matchLength; + const BYTE* const iLitEnd = *litPtr + sequence.litLength; + const BYTE* match = oLitEnd - sequence.offset; + + + /* bounds checks : careful of address space overflow in 32-bit mode */ + RETURN_ERROR_IF(sequenceLength > (size_t)(oend - op), dstSize_tooSmall, "last match must fit within dstBuffer"); + RETURN_ERROR_IF(sequence.litLength > (size_t)(litLimit - *litPtr), corruption_detected, "try to read beyond literal buffer"); + assert(op < op + sequenceLength); + assert(oLitEnd < op + sequenceLength); + + /* copy literals */ + RETURN_ERROR_IF(op > *litPtr && op < *litPtr + sequence.litLength, dstSize_tooSmall, "output should not catch up to and overwrite literal buffer"); + ZSTD_safecopyDstBeforeSrc(op, *litPtr, sequence.litLength); + op = oLitEnd; + *litPtr = iLitEnd; + + /* copy Match */ + if (sequence.offset > (size_t)(oLitEnd - prefixStart)) { + /* offset beyond prefix */ + RETURN_ERROR_IF(sequence.offset > (size_t)(oLitEnd - virtualStart), corruption_detected, ""); + match = dictEnd - (prefixStart - match); + if (match + sequence.matchLength <= dictEnd) { + ZSTD_memmove(oLitEnd, match, sequence.matchLength); + return sequenceLength; + } + /* span extDict & currentPrefixSegment */ + { size_t const length1 = dictEnd - match; + ZSTD_memmove(oLitEnd, match, length1); + op = oLitEnd + length1; + sequence.matchLength -= length1; + match = prefixStart; + } + } ZSTD_safecopy(op, oend_w, match, sequence.matchLength, ZSTD_overlap_src_before_dst); return sequenceLength; } HINT_INLINE size_t ZSTD_execSequence(BYTE* op, - BYTE* const oend, seq_t sequence, - const BYTE** litPtr, const BYTE* const litLimit, - const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd) + BYTE* const oend, seq_t sequence, + const BYTE** litPtr, const BYTE* const litLimit, + const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd) { BYTE* const oLitEnd = op + sequence.litLength; size_t const sequenceLength = sequence.litLength + sequence.matchLength; @@ -815,6 +975,103 @@ size_t ZSTD_execSequence(BYTE* op, assert(op != NULL /* Precondition */); assert(oend_w < oend /* No underflow */); + +#if defined(__aarch64__) + /* prefetch sequence starting from match that will be used for copy later */ + PREFETCH_L1(match); +#endif + /* Handle edge cases in a slow path: + * - Read beyond end of literals + * - Match end is within WILDCOPY_OVERLIMIT of oend + * - 32-bit mode and the match length overflows + */ + if (UNLIKELY( + iLitEnd > litLimit || + oMatchEnd > oend_w || + (MEM_32bits() && (size_t)(oend - op) < sequenceLength + WILDCOPY_OVERLENGTH))) + return ZSTD_execSequenceEnd(op, oend, sequence, litPtr, litLimit, prefixStart, virtualStart, dictEnd); + + /* Assumptions (everything else goes into ZSTD_execSequenceEnd()) */ + assert(op <= oLitEnd /* No overflow */); + assert(oLitEnd < oMatchEnd /* Non-zero match & no overflow */); + assert(oMatchEnd <= oend /* No underflow */); + assert(iLitEnd <= litLimit /* Literal length is in bounds */); + assert(oLitEnd <= oend_w /* Can wildcopy literals */); + assert(oMatchEnd <= oend_w /* Can wildcopy matches */); + + /* Copy Literals: + * Split out litLength <= 16 since it is nearly always true. +1.6% on gcc-9. + * We likely don't need the full 32-byte wildcopy. + */ + assert(WILDCOPY_OVERLENGTH >= 16); + ZSTD_copy16(op, (*litPtr)); + if (UNLIKELY(sequence.litLength > 16)) { + ZSTD_wildcopy(op + 16, (*litPtr) + 16, sequence.litLength - 16, ZSTD_no_overlap); + } + op = oLitEnd; + *litPtr = iLitEnd; /* update for next sequence */ + + /* Copy Match */ + if (sequence.offset > (size_t)(oLitEnd - prefixStart)) { + /* offset beyond prefix -> go into extDict */ + RETURN_ERROR_IF(UNLIKELY(sequence.offset > (size_t)(oLitEnd - virtualStart)), corruption_detected, ""); + match = dictEnd + (match - prefixStart); + if (match + sequence.matchLength <= dictEnd) { + ZSTD_memmove(oLitEnd, match, sequence.matchLength); + return sequenceLength; + } + /* span extDict & currentPrefixSegment */ + { size_t const length1 = dictEnd - match; + ZSTD_memmove(oLitEnd, match, length1); + op = oLitEnd + length1; + sequence.matchLength -= length1; + match = prefixStart; + } + } + /* Match within prefix of 1 or more bytes */ + assert(op <= oMatchEnd); + assert(oMatchEnd <= oend_w); + assert(match >= prefixStart); + assert(sequence.matchLength >= 1); + + /* Nearly all offsets are >= WILDCOPY_VECLEN bytes, which means we can use wildcopy + * without overlap checking. + */ + if (LIKELY(sequence.offset >= WILDCOPY_VECLEN)) { + /* We bet on a full wildcopy for matches, since we expect matches to be + * longer than literals (in general). In silesia, ~10% of matches are longer + * than 16 bytes. + */ + ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength, ZSTD_no_overlap); + return sequenceLength; + } + assert(sequence.offset < WILDCOPY_VECLEN); + + /* Copy 8 bytes and spread the offset to be >= 8. */ + ZSTD_overlapCopy8(&op, &match, sequence.offset); + + /* If the match length is > 8 bytes, then continue with the wildcopy. */ + if (sequence.matchLength > 8) { + assert(op < oMatchEnd); + ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength - 8, ZSTD_overlap_src_before_dst); + } + return sequenceLength; +} + +HINT_INLINE +size_t ZSTD_execSequenceSplitLitBuffer(BYTE* op, + BYTE* const oend, const BYTE* const oend_w, seq_t sequence, + const BYTE** litPtr, const BYTE* const litLimit, + const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd) +{ + BYTE* const oLitEnd = op + sequence.litLength; + size_t const sequenceLength = sequence.litLength + sequence.matchLength; + BYTE* const oMatchEnd = op + sequenceLength; /* risk : address space overflow (32-bits) */ + const BYTE* const iLitEnd = *litPtr + sequence.litLength; + const BYTE* match = oLitEnd - sequence.offset; + + assert(op != NULL /* Precondition */); + assert(oend_w < oend /* No underflow */); /* Handle edge cases in a slow path: * - Read beyond end of literals * - Match end is within WILDCOPY_OVERLIMIT of oend @@ -824,7 +1081,7 @@ size_t ZSTD_execSequence(BYTE* op, iLitEnd > litLimit || oMatchEnd > oend_w || (MEM_32bits() && (size_t)(oend - op) < sequenceLength + WILDCOPY_OVERLENGTH))) - return ZSTD_execSequenceEnd(op, oend, sequence, litPtr, litLimit, prefixStart, virtualStart, dictEnd); + return ZSTD_execSequenceEndSplitLitBuffer(op, oend, oend_w, sequence, litPtr, litLimit, prefixStart, virtualStart, dictEnd); /* Assumptions (everything else goes into ZSTD_execSequenceEnd()) */ assert(op <= oLitEnd /* No overflow */); @@ -892,6 +1149,7 @@ size_t ZSTD_execSequence(BYTE* op, return sequenceLength; } + static void ZSTD_initFseState(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, const ZSTD_seqSymbol* dt) { @@ -905,24 +1163,14 @@ ZSTD_initFseState(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, const ZSTD_seqS } FORCE_INLINE_TEMPLATE void -ZSTD_updateFseState(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD) +ZSTD_updateFseStateWithDInfo(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, U16 nextState, U32 nbBits) { - ZSTD_seqSymbol const DInfo = DStatePtr->table[DStatePtr->state]; - U32 const nbBits = DInfo.nbBits; size_t const lowBits = BIT_readBits(bitD, nbBits); - DStatePtr->state = DInfo.nextState + lowBits; -} - -FORCE_INLINE_TEMPLATE void -ZSTD_updateFseStateWithDInfo(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, ZSTD_seqSymbol const DInfo) -{ - U32 const nbBits = DInfo.nbBits; - size_t const lowBits = BIT_readBits(bitD, nbBits); - DStatePtr->state = DInfo.nextState + lowBits; + DStatePtr->state = nextState + lowBits; } /* We need to add at most (ZSTD_WINDOWLOG_MAX_32 - 1) bits to read the maximum - * offset bits. But we can only read at most (STREAM_ACCUMULATOR_MIN_32 - 1) + * offset bits. But we can only read at most STREAM_ACCUMULATOR_MIN_32 * bits before reloading. This value is the maximum number of bytes we read * after reloading when we are decoding long offsets. */ @@ -937,102 +1185,113 @@ FORCE_INLINE_TEMPLATE seq_t ZSTD_decodeSequence(seqState_t* seqState, const ZSTD_longOffset_e longOffsets) { seq_t seq; - ZSTD_seqSymbol const llDInfo = seqState->stateLL.table[seqState->stateLL.state]; - ZSTD_seqSymbol const mlDInfo = seqState->stateML.table[seqState->stateML.state]; - ZSTD_seqSymbol const ofDInfo = seqState->stateOffb.table[seqState->stateOffb.state]; - U32 const llBase = llDInfo.baseValue; - U32 const mlBase = mlDInfo.baseValue; - U32 const ofBase = ofDInfo.baseValue; - BYTE const llBits = llDInfo.nbAdditionalBits; - BYTE const mlBits = mlDInfo.nbAdditionalBits; - BYTE const ofBits = ofDInfo.nbAdditionalBits; - BYTE const totalBits = llBits+mlBits+ofBits; - - /* sequence */ - { size_t offset; - if (ofBits > 1) { - ZSTD_STATIC_ASSERT(ZSTD_lo_isLongOffset == 1); - ZSTD_STATIC_ASSERT(LONG_OFFSETS_MAX_EXTRA_BITS_32 == 5); - assert(ofBits <= MaxOff); - if (MEM_32bits() && longOffsets && (ofBits >= STREAM_ACCUMULATOR_MIN_32)) { - U32 const extraBits = ofBits - MIN(ofBits, 32 - seqState->DStream.bitsConsumed); - offset = ofBase + (BIT_readBitsFast(&seqState->DStream, ofBits - extraBits) << extraBits); - BIT_reloadDStream(&seqState->DStream); - if (extraBits) offset += BIT_readBitsFast(&seqState->DStream, extraBits); - assert(extraBits <= LONG_OFFSETS_MAX_EXTRA_BITS_32); /* to avoid another reload */ - } else { - offset = ofBase + BIT_readBitsFast(&seqState->DStream, ofBits/*>0*/); /* <= (ZSTD_WINDOWLOG_MAX-1) bits */ - if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); - } - seqState->prevOffset[2] = seqState->prevOffset[1]; - seqState->prevOffset[1] = seqState->prevOffset[0]; - seqState->prevOffset[0] = offset; - } else { - U32 const ll0 = (llBase == 0); - if (LIKELY((ofBits == 0))) { - if (LIKELY(!ll0)) - offset = seqState->prevOffset[0]; - else { - offset = seqState->prevOffset[1]; - seqState->prevOffset[1] = seqState->prevOffset[0]; - seqState->prevOffset[0] = offset; - } - } else { - offset = ofBase + ll0 + BIT_readBitsFast(&seqState->DStream, 1); - { size_t temp = (offset==3) ? seqState->prevOffset[0] - 1 : seqState->prevOffset[offset]; - temp += !temp; /* 0 is not valid; input is corrupted; force offset to 1 */ - if (offset != 1) seqState->prevOffset[2] = seqState->prevOffset[1]; - seqState->prevOffset[1] = seqState->prevOffset[0]; - seqState->prevOffset[0] = offset = temp; - } } } - seq.offset = offset; - } - - seq.matchLength = mlBase; - if (mlBits > 0) - seq.matchLength += BIT_readBitsFast(&seqState->DStream, mlBits/*>0*/); - - if (MEM_32bits() && (mlBits+llBits >= STREAM_ACCUMULATOR_MIN_32-LONG_OFFSETS_MAX_EXTRA_BITS_32)) - BIT_reloadDStream(&seqState->DStream); - if (MEM_64bits() && UNLIKELY(totalBits >= STREAM_ACCUMULATOR_MIN_64-(LLFSELog+MLFSELog+OffFSELog))) - BIT_reloadDStream(&seqState->DStream); - /* Ensure there are enough bits to read the rest of data in 64-bit mode. */ - ZSTD_STATIC_ASSERT(16+LLFSELog+MLFSELog+OffFSELog < STREAM_ACCUMULATOR_MIN_64); - - seq.litLength = llBase; - if (llBits > 0) - seq.litLength += BIT_readBitsFast(&seqState->DStream, llBits/*>0*/); - - if (MEM_32bits()) - BIT_reloadDStream(&seqState->DStream); - - DEBUGLOG(6, "seq: litL=%u, matchL=%u, offset=%u", - (U32)seq.litLength, (U32)seq.matchLength, (U32)seq.offset); - - /* ANS state update - * gcc-9.0.0 does 2.5% worse with ZSTD_updateFseStateWithDInfo(). - * clang-9.2.0 does 7% worse with ZSTD_updateFseState(). - * Naturally it seems like ZSTD_updateFseStateWithDInfo() should be the - * better option, so it is the default for other compilers. But, if you - * measure that it is worse, please put up a pull request. + /* + * ZSTD_seqSymbol is a structure with a total of 64 bits wide. So it can be + * loaded in one operation and extracted its fields by simply shifting or + * bit-extracting on aarch64. + * GCC doesn't recognize this and generates more unnecessary ldr/ldrb/ldrh + * operations that cause performance drop. This can be avoided by using this + * ZSTD_memcpy hack. */ - { -#if defined(__GNUC__) && !defined(__clang__) - const int kUseUpdateFseState = 1; +#if defined(__aarch64__) && (defined(__GNUC__) && !defined(__clang__)) + ZSTD_seqSymbol llDInfoS, mlDInfoS, ofDInfoS; + ZSTD_seqSymbol* const llDInfo = &llDInfoS; + ZSTD_seqSymbol* const mlDInfo = &mlDInfoS; + ZSTD_seqSymbol* const ofDInfo = &ofDInfoS; + ZSTD_memcpy(llDInfo, seqState->stateLL.table + seqState->stateLL.state, sizeof(ZSTD_seqSymbol)); + ZSTD_memcpy(mlDInfo, seqState->stateML.table + seqState->stateML.state, sizeof(ZSTD_seqSymbol)); + ZSTD_memcpy(ofDInfo, seqState->stateOffb.table + seqState->stateOffb.state, sizeof(ZSTD_seqSymbol)); #else - const int kUseUpdateFseState = 0; + const ZSTD_seqSymbol* const llDInfo = seqState->stateLL.table + seqState->stateLL.state; + const ZSTD_seqSymbol* const mlDInfo = seqState->stateML.table + seqState->stateML.state; + const ZSTD_seqSymbol* const ofDInfo = seqState->stateOffb.table + seqState->stateOffb.state; #endif - if (kUseUpdateFseState) { - ZSTD_updateFseState(&seqState->stateLL, &seqState->DStream); /* <= 9 bits */ - ZSTD_updateFseState(&seqState->stateML, &seqState->DStream); /* <= 9 bits */ - if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); /* <= 18 bits */ - ZSTD_updateFseState(&seqState->stateOffb, &seqState->DStream); /* <= 8 bits */ - } else { - ZSTD_updateFseStateWithDInfo(&seqState->stateLL, &seqState->DStream, llDInfo); /* <= 9 bits */ - ZSTD_updateFseStateWithDInfo(&seqState->stateML, &seqState->DStream, mlDInfo); /* <= 9 bits */ - if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); /* <= 18 bits */ - ZSTD_updateFseStateWithDInfo(&seqState->stateOffb, &seqState->DStream, ofDInfo); /* <= 8 bits */ + seq.matchLength = mlDInfo->baseValue; + seq.litLength = llDInfo->baseValue; + { U32 const ofBase = ofDInfo->baseValue; + BYTE const llBits = llDInfo->nbAdditionalBits; + BYTE const mlBits = mlDInfo->nbAdditionalBits; + BYTE const ofBits = ofDInfo->nbAdditionalBits; + BYTE const totalBits = llBits+mlBits+ofBits; + + U16 const llNext = llDInfo->nextState; + U16 const mlNext = mlDInfo->nextState; + U16 const ofNext = ofDInfo->nextState; + U32 const llnbBits = llDInfo->nbBits; + U32 const mlnbBits = mlDInfo->nbBits; + U32 const ofnbBits = ofDInfo->nbBits; + + assert(llBits <= MaxLLBits); + assert(mlBits <= MaxMLBits); + assert(ofBits <= MaxOff); + /* + * As gcc has better branch and block analyzers, sometimes it is only + * valuable to mark likeliness for clang, it gives around 3-4% of + * performance. + */ + + /* sequence */ + { size_t offset; + if (ofBits > 1) { + ZSTD_STATIC_ASSERT(ZSTD_lo_isLongOffset == 1); + ZSTD_STATIC_ASSERT(LONG_OFFSETS_MAX_EXTRA_BITS_32 == 5); + ZSTD_STATIC_ASSERT(STREAM_ACCUMULATOR_MIN_32 > LONG_OFFSETS_MAX_EXTRA_BITS_32); + ZSTD_STATIC_ASSERT(STREAM_ACCUMULATOR_MIN_32 - LONG_OFFSETS_MAX_EXTRA_BITS_32 >= MaxMLBits); + if (MEM_32bits() && longOffsets && (ofBits >= STREAM_ACCUMULATOR_MIN_32)) { + /* Always read extra bits, this keeps the logic simple, + * avoids branches, and avoids accidentally reading 0 bits. + */ + U32 const extraBits = LONG_OFFSETS_MAX_EXTRA_BITS_32; + offset = ofBase + (BIT_readBitsFast(&seqState->DStream, ofBits - extraBits) << extraBits); + BIT_reloadDStream(&seqState->DStream); + offset += BIT_readBitsFast(&seqState->DStream, extraBits); + } else { + offset = ofBase + BIT_readBitsFast(&seqState->DStream, ofBits/*>0*/); /* <= (ZSTD_WINDOWLOG_MAX-1) bits */ + if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); + } + seqState->prevOffset[2] = seqState->prevOffset[1]; + seqState->prevOffset[1] = seqState->prevOffset[0]; + seqState->prevOffset[0] = offset; + } else { + U32 const ll0 = (llDInfo->baseValue == 0); + if (LIKELY((ofBits == 0))) { + offset = seqState->prevOffset[ll0]; + seqState->prevOffset[1] = seqState->prevOffset[!ll0]; + seqState->prevOffset[0] = offset; + } else { + offset = ofBase + ll0 + BIT_readBitsFast(&seqState->DStream, 1); + { size_t temp = (offset==3) ? seqState->prevOffset[0] - 1 : seqState->prevOffset[offset]; + temp += !temp; /* 0 is not valid; input is corrupted; force offset to 1 */ + if (offset != 1) seqState->prevOffset[2] = seqState->prevOffset[1]; + seqState->prevOffset[1] = seqState->prevOffset[0]; + seqState->prevOffset[0] = offset = temp; + } } } + seq.offset = offset; } + + if (mlBits > 0) + seq.matchLength += BIT_readBitsFast(&seqState->DStream, mlBits/*>0*/); + + if (MEM_32bits() && (mlBits+llBits >= STREAM_ACCUMULATOR_MIN_32-LONG_OFFSETS_MAX_EXTRA_BITS_32)) + BIT_reloadDStream(&seqState->DStream); + if (MEM_64bits() && UNLIKELY(totalBits >= STREAM_ACCUMULATOR_MIN_64-(LLFSELog+MLFSELog+OffFSELog))) + BIT_reloadDStream(&seqState->DStream); + /* Ensure there are enough bits to read the rest of data in 64-bit mode. */ + ZSTD_STATIC_ASSERT(16+LLFSELog+MLFSELog+OffFSELog < STREAM_ACCUMULATOR_MIN_64); + + if (llBits > 0) + seq.litLength += BIT_readBitsFast(&seqState->DStream, llBits/*>0*/); + + if (MEM_32bits()) + BIT_reloadDStream(&seqState->DStream); + + DEBUGLOG(6, "seq: litL=%u, matchL=%u, offset=%u", + (U32)seq.litLength, (U32)seq.matchLength, (U32)seq.offset); + + ZSTD_updateFseStateWithDInfo(&seqState->stateLL, &seqState->DStream, llNext, llnbBits); /* <= 9 bits */ + ZSTD_updateFseStateWithDInfo(&seqState->stateML, &seqState->DStream, mlNext, mlnbBits); /* <= 9 bits */ + if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); /* <= 18 bits */ + ZSTD_updateFseStateWithDInfo(&seqState->stateOffb, &seqState->DStream, ofNext, ofnbBits); /* <= 8 bits */ } return seq; @@ -1085,9 +1344,11 @@ MEM_STATIC void ZSTD_assertValidSequence( #endif #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG + + FORCE_INLINE_TEMPLATE size_t DONT_VECTORIZE -ZSTD_decompressSequences_body( ZSTD_DCtx* dctx, +ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, const ZSTD_longOffset_e isLongOffset, @@ -1099,11 +1360,11 @@ ZSTD_decompressSequences_body( ZSTD_DCtx* dctx, BYTE* const oend = ostart + maxDstSize; BYTE* op = ostart; const BYTE* litPtr = dctx->litPtr; - const BYTE* const litEnd = litPtr + dctx->litSize; + const BYTE* litBufferEnd = dctx->litBufferEnd; const BYTE* const prefixStart = (const BYTE*) (dctx->prefixStart); const BYTE* const vBase = (const BYTE*) (dctx->virtualStart); const BYTE* const dictEnd = (const BYTE*) (dctx->dictEnd); - DEBUGLOG(5, "ZSTD_decompressSequences_body"); + DEBUGLOG(5, "ZSTD_decompressSequences_bodySplitLitBuffer"); (void)frame; /* Regen sequences */ @@ -1124,55 +1385,237 @@ ZSTD_decompressSequences_body( ZSTD_DCtx* dctx, BIT_DStream_endOfBuffer < BIT_DStream_completed && BIT_DStream_completed < BIT_DStream_overflow); + /* decompress without overrunning litPtr begins */ + { + seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + /* Align the decompression loop to 32 + 16 bytes. + * + * zstd compiled with gcc-9 on an Intel i9-9900k shows 10% decompression + * speed swings based on the alignment of the decompression loop. This + * performance swing is caused by parts of the decompression loop falling + * out of the DSB. The entire decompression loop should fit in the DSB, + * when it can't we get much worse performance. You can measure if you've + * hit the good case or the bad case with this perf command for some + * compressed file test.zst: + * + * perf stat -e cycles -e instructions -e idq.all_dsb_cycles_any_uops \ + * -e idq.all_mite_cycles_any_uops -- ./zstd -tq test.zst + * + * If you see most cycles served out of the MITE you've hit the bad case. + * If you see most cycles served out of the DSB you've hit the good case. + * If it is pretty even then you may be in an okay case. + * + * This issue has been reproduced on the following CPUs: + * - Kabylake: Macbook Pro (15-inch, 2019) 2.4 GHz Intel Core i9 + * Use Instruments->Counters to get DSB/MITE cycles. + * I never got performance swings, but I was able to + * go from the good case of mostly DSB to half of the + * cycles served from MITE. + * - Coffeelake: Intel i9-9900k + * - Coffeelake: Intel i7-9700k + * + * I haven't been able to reproduce the instability or DSB misses on any + * of the following CPUS: + * - Haswell + * - Broadwell: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GH + * - Skylake + * + * Alignment is done for each of the three major decompression loops: + * - ZSTD_decompressSequences_bodySplitLitBuffer - presplit section of the literal buffer + * - ZSTD_decompressSequences_bodySplitLitBuffer - postsplit section of the literal buffer + * - ZSTD_decompressSequences_body + * Alignment choices are made to minimize large swings on bad cases and influence on performance + * from changes external to this code, rather than to overoptimize on the current commit. + * + * If you are seeing performance stability this script can help test. + * It tests on 4 commits in zstd where I saw performance change. + * + * https://gist.github.com/terrelln/9889fc06a423fd5ca6e99351564473f4 + */ #if defined(__GNUC__) && defined(__x86_64__) - /* Align the decompression loop to 32 + 16 bytes. - * - * zstd compiled with gcc-9 on an Intel i9-9900k shows 10% decompression - * speed swings based on the alignment of the decompression loop. This - * performance swing is caused by parts of the decompression loop falling - * out of the DSB. The entire decompression loop should fit in the DSB, - * when it can't we get much worse performance. You can measure if you've - * hit the good case or the bad case with this perf command for some - * compressed file test.zst: - * - * perf stat -e cycles -e instructions -e idq.all_dsb_cycles_any_uops \ - * -e idq.all_mite_cycles_any_uops -- ./zstd -tq test.zst - * - * If you see most cycles served out of the MITE you've hit the bad case. - * If you see most cycles served out of the DSB you've hit the good case. - * If it is pretty even then you may be in an okay case. - * - * This issue has been reproduced on the following CPUs: - * - Kabylake: Macbook Pro (15-inch, 2019) 2.4 GHz Intel Core i9 - * Use Instruments->Counters to get DSB/MITE cycles. - * I never got performance swings, but I was able to - * go from the good case of mostly DSB to half of the - * cycles served from MITE. - * - Coffeelake: Intel i9-9900k - * - Coffeelake: Intel i7-9700k - * - * I haven't been able to reproduce the instability or DSB misses on any - * of the following CPUS: - * - Haswell - * - Broadwell: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GH - * - Skylake - * - * If you are seeing performance stability this script can help test. - * It tests on 4 commits in zstd where I saw performance change. - * - * https://gist.github.com/terrelln/9889fc06a423fd5ca6e99351564473f4 - */ - __asm__(".p2align 6"); - __asm__("nop"); - __asm__(".p2align 5"); - __asm__("nop"); -# if __GNUC__ >= 9 - /* better for gcc-9 and gcc-10, worse for clang and gcc-8 */ - __asm__(".p2align 3"); + __asm__(".p2align 6"); +# if __GNUC__ >= 7 + /* good for gcc-7, gcc-9, and gcc-11 */ + __asm__("nop"); + __asm__(".p2align 5"); + __asm__("nop"); + __asm__(".p2align 4"); +# if __GNUC__ == 8 || __GNUC__ == 10 + /* good for gcc-8 and gcc-10 */ + __asm__("nop"); + __asm__(".p2align 3"); +# endif +# endif +#endif + + /* Handle the initial state where litBuffer is currently split between dst and litExtraBuffer */ + for (; litPtr + sequence.litLength <= dctx->litBufferEnd; ) { + size_t const oneSeqSize = ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequence.litLength - WILDCOPY_OVERLENGTH, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) + assert(!ZSTD_isError(oneSeqSize)); + if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); +#endif + if (UNLIKELY(ZSTD_isError(oneSeqSize))) + return oneSeqSize; + DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); + op += oneSeqSize; + if (UNLIKELY(!--nbSeq)) + break; + BIT_reloadDStream(&(seqState.DStream)); + sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + } + + /* If there are more sequences, they will need to read literals from litExtraBuffer; copy over the remainder from dst and update litPtr and litEnd */ + if (nbSeq > 0) { + const size_t leftoverLit = dctx->litBufferEnd - litPtr; + if (leftoverLit) + { + RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer"); + ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit); + sequence.litLength -= leftoverLit; + op += leftoverLit; + } + litPtr = dctx->litExtraBuffer; + litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; + dctx->litBufferLocation = ZSTD_not_in_dst; + { + size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) + assert(!ZSTD_isError(oneSeqSize)); + if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); +#endif + if (UNLIKELY(ZSTD_isError(oneSeqSize))) + return oneSeqSize; + DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); + op += oneSeqSize; + if (--nbSeq) + BIT_reloadDStream(&(seqState.DStream)); + } + } + } + + if (nbSeq > 0) /* there is remaining lit from extra buffer */ + { + +#if defined(__GNUC__) && defined(__x86_64__) + __asm__(".p2align 6"); + __asm__("nop"); +# if __GNUC__ != 7 + /* worse for gcc-7 better for gcc-8, gcc-9, and gcc-10 and clang */ + __asm__(".p2align 4"); + __asm__("nop"); + __asm__(".p2align 3"); +# elif __GNUC__ >= 11 + __asm__(".p2align 3"); # else - __asm__(".p2align 4"); + __asm__(".p2align 5"); + __asm__("nop"); + __asm__(".p2align 3"); # endif #endif + + for (; ; ) { + seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset); + size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd); +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) + assert(!ZSTD_isError(oneSeqSize)); + if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase); +#endif + if (UNLIKELY(ZSTD_isError(oneSeqSize))) + return oneSeqSize; + DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize); + op += oneSeqSize; + if (UNLIKELY(!--nbSeq)) + break; + BIT_reloadDStream(&(seqState.DStream)); + } + } + + /* check if reached exact end */ + DEBUGLOG(5, "ZSTD_decompressSequences_bodySplitLitBuffer: after decode loop, remaining nbSeq : %i", nbSeq); + RETURN_ERROR_IF(nbSeq, corruption_detected, ""); + RETURN_ERROR_IF(BIT_reloadDStream(&seqState.DStream) < BIT_DStream_completed, corruption_detected, ""); + /* save reps for next block */ + { U32 i; for (i=0; ientropy.rep[i] = (U32)(seqState.prevOffset[i]); } + } + + /* last literal segment */ + if (dctx->litBufferLocation == ZSTD_split) /* split hasn't been reached yet, first get dst then copy litExtraBuffer */ + { + size_t const lastLLSize = litBufferEnd - litPtr; + RETURN_ERROR_IF(lastLLSize > (size_t)(oend - op), dstSize_tooSmall, ""); + if (op != NULL) { + ZSTD_memmove(op, litPtr, lastLLSize); + op += lastLLSize; + } + litPtr = dctx->litExtraBuffer; + litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; + dctx->litBufferLocation = ZSTD_not_in_dst; + } + { size_t const lastLLSize = litBufferEnd - litPtr; + RETURN_ERROR_IF(lastLLSize > (size_t)(oend-op), dstSize_tooSmall, ""); + if (op != NULL) { + ZSTD_memcpy(op, litPtr, lastLLSize); + op += lastLLSize; + } + } + + return op-ostart; +} + +FORCE_INLINE_TEMPLATE size_t +DONT_VECTORIZE +ZSTD_decompressSequences_body(ZSTD_DCtx* dctx, + void* dst, size_t maxDstSize, + const void* seqStart, size_t seqSize, int nbSeq, + const ZSTD_longOffset_e isLongOffset, + const int frame) +{ + const BYTE* ip = (const BYTE*)seqStart; + const BYTE* const iend = ip + seqSize; + BYTE* const ostart = (BYTE*)dst; + BYTE* const oend = dctx->litBufferLocation == ZSTD_not_in_dst ? ostart + maxDstSize : dctx->litBuffer; + BYTE* op = ostart; + const BYTE* litPtr = dctx->litPtr; + const BYTE* const litEnd = litPtr + dctx->litSize; + const BYTE* const prefixStart = (const BYTE*)(dctx->prefixStart); + const BYTE* const vBase = (const BYTE*)(dctx->virtualStart); + const BYTE* const dictEnd = (const BYTE*)(dctx->dictEnd); + DEBUGLOG(5, "ZSTD_decompressSequences_body: nbSeq = %d", nbSeq); + (void)frame; + + /* Regen sequences */ + if (nbSeq) { + seqState_t seqState; + dctx->fseEntropy = 1; + { U32 i; for (i = 0; i < ZSTD_REP_NUM; i++) seqState.prevOffset[i] = dctx->entropy.rep[i]; } + RETURN_ERROR_IF( + ERR_isError(BIT_initDStream(&seqState.DStream, ip, iend - ip)), + corruption_detected, ""); + ZSTD_initFseState(&seqState.stateLL, &seqState.DStream, dctx->LLTptr); + ZSTD_initFseState(&seqState.stateOffb, &seqState.DStream, dctx->OFTptr); + ZSTD_initFseState(&seqState.stateML, &seqState.DStream, dctx->MLTptr); + assert(dst != NULL); + + ZSTD_STATIC_ASSERT( + BIT_DStream_unfinished < BIT_DStream_completed && + BIT_DStream_endOfBuffer < BIT_DStream_completed && + BIT_DStream_completed < BIT_DStream_overflow); + +#if defined(__GNUC__) && defined(__x86_64__) + __asm__(".p2align 6"); + __asm__("nop"); +# if __GNUC__ >= 7 + __asm__(".p2align 5"); + __asm__("nop"); + __asm__(".p2align 3"); +# else + __asm__(".p2align 4"); + __asm__("nop"); + __asm__(".p2align 3"); +# endif +#endif + for ( ; ; ) { seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset); size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litEnd, prefixStart, vBase, dictEnd); @@ -1218,6 +1661,16 @@ ZSTD_decompressSequences_default(ZSTD_DCtx* dctx, { return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); } + +static size_t +ZSTD_decompressSequencesSplitLitBuffer_default(ZSTD_DCtx* dctx, + void* dst, size_t maxDstSize, + const void* seqStart, size_t seqSize, int nbSeq, + const ZSTD_longOffset_e isLongOffset, + const int frame) +{ + return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); +} #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT @@ -1250,10 +1703,10 @@ ZSTD_decompressSequencesLong_body( const BYTE* ip = (const BYTE*)seqStart; const BYTE* const iend = ip + seqSize; BYTE* const ostart = (BYTE*)dst; - BYTE* const oend = ostart + maxDstSize; + BYTE* const oend = dctx->litBufferLocation == ZSTD_in_dst ? dctx->litBuffer : ostart + maxDstSize; BYTE* op = ostart; const BYTE* litPtr = dctx->litPtr; - const BYTE* const litEnd = litPtr + dctx->litSize; + const BYTE* litBufferEnd = dctx->litBufferEnd; const BYTE* const prefixStart = (const BYTE*) (dctx->prefixStart); const BYTE* const dictStart = (const BYTE*) (dctx->virtualStart); const BYTE* const dictEnd = (const BYTE*) (dctx->dictEnd); @@ -1289,32 +1742,94 @@ ZSTD_decompressSequencesLong_body( } RETURN_ERROR_IF(seqNblitBufferLocation == ZSTD_split && litPtr + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength > dctx->litBufferEnd) + { + /* lit buffer is reaching split point, empty out the first buffer and transition to litExtraBuffer */ + const size_t leftoverLit = dctx->litBufferEnd - litPtr; + if (leftoverLit) + { + RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer"); + ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit); + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength -= leftoverLit; + op += leftoverLit; + } + litPtr = dctx->litExtraBuffer; + litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; + dctx->litBufferLocation = ZSTD_not_in_dst; + oneSeqSize = ZSTD_execSequence(op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) - assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb-ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart); + assert(!ZSTD_isError(oneSeqSize)); + if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart); #endif - if (ZSTD_isError(oneSeqSize)) return oneSeqSize; + if (ZSTD_isError(oneSeqSize)) return oneSeqSize; - prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd); - sequences[seqNb & STORED_SEQS_MASK] = sequence; - op += oneSeqSize; + prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd); + sequences[seqNb & STORED_SEQS_MASK] = sequence; + op += oneSeqSize; + } + else + { + /* lit buffer is either wholly contained in first or second split, or not split at all*/ + oneSeqSize = dctx->litBufferLocation == ZSTD_split ? + ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength - WILDCOPY_OVERLENGTH, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd) : + ZSTD_execSequence(op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) + assert(!ZSTD_isError(oneSeqSize)); + if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart); +#endif + if (ZSTD_isError(oneSeqSize)) return oneSeqSize; + + prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd); + sequences[seqNb & STORED_SEQS_MASK] = sequence; + op += oneSeqSize; + } } RETURN_ERROR_IF(seqNblitBufferLocation == ZSTD_split && litPtr + sequence->litLength > dctx->litBufferEnd) + { + const size_t leftoverLit = dctx->litBufferEnd - litPtr; + if (leftoverLit) + { + RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer"); + ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit); + sequence->litLength -= leftoverLit; + op += leftoverLit; + } + litPtr = dctx->litExtraBuffer; + litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; + dctx->litBufferLocation = ZSTD_not_in_dst; + { + size_t const oneSeqSize = ZSTD_execSequence(op, oend, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) - assert(!ZSTD_isError(oneSeqSize)); - if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart); + assert(!ZSTD_isError(oneSeqSize)); + if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart); #endif - if (ZSTD_isError(oneSeqSize)) return oneSeqSize; - op += oneSeqSize; + if (ZSTD_isError(oneSeqSize)) return oneSeqSize; + op += oneSeqSize; + } + } + else + { + size_t const oneSeqSize = dctx->litBufferLocation == ZSTD_split ? + ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequence->litLength - WILDCOPY_OVERLENGTH, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd) : + ZSTD_execSequence(op, oend, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd); +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE) + assert(!ZSTD_isError(oneSeqSize)); + if (frame) ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart); +#endif + if (ZSTD_isError(oneSeqSize)) return oneSeqSize; + op += oneSeqSize; + } } /* save reps for next block */ @@ -1322,10 +1837,21 @@ ZSTD_decompressSequencesLong_body( } /* last literal segment */ - { size_t const lastLLSize = litEnd - litPtr; + if (dctx->litBufferLocation == ZSTD_split) /* first deplete literal buffer in dst, then copy litExtraBuffer */ + { + size_t const lastLLSize = litBufferEnd - litPtr; + RETURN_ERROR_IF(lastLLSize > (size_t)(oend - op), dstSize_tooSmall, ""); + if (op != NULL) { + ZSTD_memmove(op, litPtr, lastLLSize); + op += lastLLSize; + } + litPtr = dctx->litExtraBuffer; + litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE; + } + { size_t const lastLLSize = litBufferEnd - litPtr; RETURN_ERROR_IF(lastLLSize > (size_t)(oend-op), dstSize_tooSmall, ""); if (op != NULL) { - ZSTD_memcpy(op, litPtr, lastLLSize); + ZSTD_memmove(op, litPtr, lastLLSize); op += lastLLSize; } } @@ -1349,7 +1875,7 @@ ZSTD_decompressSequencesLong_default(ZSTD_DCtx* dctx, #if DYNAMIC_BMI2 #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG -static TARGET_ATTRIBUTE("bmi2") size_t +static BMI2_TARGET_ATTRIBUTE size_t DONT_VECTORIZE ZSTD_decompressSequences_bmi2(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, @@ -1359,10 +1885,20 @@ ZSTD_decompressSequences_bmi2(ZSTD_DCtx* dctx, { return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); } +static BMI2_TARGET_ATTRIBUTE size_t +DONT_VECTORIZE +ZSTD_decompressSequencesSplitLitBuffer_bmi2(ZSTD_DCtx* dctx, + void* dst, size_t maxDstSize, + const void* seqStart, size_t seqSize, int nbSeq, + const ZSTD_longOffset_e isLongOffset, + const int frame) +{ + return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); +} #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT -static TARGET_ATTRIBUTE("bmi2") size_t +static BMI2_TARGET_ATTRIBUTE size_t ZSTD_decompressSequencesLong_bmi2(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, const void* seqStart, size_t seqSize, int nbSeq, @@ -1391,11 +1927,25 @@ ZSTD_decompressSequences(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, { DEBUGLOG(5, "ZSTD_decompressSequences"); #if DYNAMIC_BMI2 - if (dctx->bmi2) { + if (ZSTD_DCtx_get_bmi2(dctx)) { return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); } #endif - return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); +} +static size_t +ZSTD_decompressSequencesSplitLitBuffer(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, + const void* seqStart, size_t seqSize, int nbSeq, + const ZSTD_longOffset_e isLongOffset, + const int frame) +{ + DEBUGLOG(5, "ZSTD_decompressSequencesSplitLitBuffer"); +#if DYNAMIC_BMI2 + if (ZSTD_DCtx_get_bmi2(dctx)) { + return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); + } +#endif + return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); } #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ @@ -1415,7 +1965,7 @@ ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx, { DEBUGLOG(5, "ZSTD_decompressSequencesLong"); #if DYNAMIC_BMI2 - if (dctx->bmi2) { + if (ZSTD_DCtx_get_bmi2(dctx)) { return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset, frame); } #endif @@ -1424,55 +1974,101 @@ ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx, #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ +/** + * @returns The total size of the history referenceable by zstd, including + * both the prefix and the extDict. At @p op any offset larger than this + * is invalid. + */ +static size_t ZSTD_totalHistorySize(BYTE* op, BYTE const* virtualStart) +{ + return (size_t)(op - virtualStart); +} -#if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ - !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) -/* ZSTD_getLongOffsetsShare() : +typedef struct { + unsigned longOffsetShare; + unsigned maxNbAdditionalBits; +} ZSTD_OffsetInfo; + +/* ZSTD_getOffsetInfo() : * condition : offTable must be valid * @return : "share" of long offsets (arbitrarily defined as > (1<<23)) - * compared to maximum possible of (1< 22) total += 1; + ZSTD_OffsetInfo info = {0, 0}; + /* If nbSeq == 0, then the offTable is uninitialized, but we have + * no sequences, so both values should be 0. + */ + if (nbSeq != 0) { + const void* ptr = offTable; + U32 const tableLog = ((const ZSTD_seqSymbol_header*)ptr)[0].tableLog; + const ZSTD_seqSymbol* table = offTable + 1; + U32 const max = 1 << tableLog; + U32 u; + DEBUGLOG(5, "ZSTD_getLongOffsetsShare: (tableLog=%u)", tableLog); + + assert(max <= (1 << OffFSELog)); /* max not too large */ + for (u=0; u 22) info.longOffsetShare += 1; + } + + assert(tableLog <= OffFSELog); + info.longOffsetShare <<= (OffFSELog - tableLog); /* scale to OffFSELog */ } - assert(tableLog <= OffFSELog); - total <<= (OffFSELog - tableLog); /* scale to OffFSELog */ + return info; +} - return total; +/** + * @returns The maximum offset we can decode in one read of our bitstream, without + * reloading more bits in the middle of the offset bits read. Any offsets larger + * than this must use the long offset decoder. + */ +static size_t ZSTD_maxShortOffset(void) +{ + if (MEM_64bits()) { + /* We can decode any offset without reloading bits. + * This might change if the max window size grows. + */ + ZSTD_STATIC_ASSERT(ZSTD_WINDOWLOG_MAX <= 31); + return (size_t)-1; + } else { + /* The maximum offBase is (1 << (STREAM_ACCUMULATOR_MIN + 1)) - 1. + * This offBase would require STREAM_ACCUMULATOR_MIN extra bits. + * Then we have to subtract ZSTD_REP_NUM to get the maximum possible offset. + */ + size_t const maxOffbase = ((size_t)1 << (STREAM_ACCUMULATOR_MIN + 1)) - 1; + size_t const maxOffset = maxOffbase - ZSTD_REP_NUM; + assert(ZSTD_highbit32((U32)maxOffbase) == STREAM_ACCUMULATOR_MIN); + return maxOffset; + } } -#endif size_t ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, - const void* src, size_t srcSize, const int frame) + const void* src, size_t srcSize, const int frame, const streaming_operation streaming) { /* blockType == blockCompressed */ const BYTE* ip = (const BYTE*)src; - /* isLongOffset must be true if there are long offsets. - * Offsets are long if they are larger than 2^STREAM_ACCUMULATOR_MIN. - * We don't expect that to be the case in 64-bit mode. - * In block mode, window size is not known, so we have to be conservative. - * (note: but it could be evaluated from current-lowLimit) - */ - ZSTD_longOffset_e const isLongOffset = (ZSTD_longOffset_e)(MEM_32bits() && (!frame || (dctx->fParams.windowSize > (1ULL << STREAM_ACCUMULATOR_MIN)))); DEBUGLOG(5, "ZSTD_decompressBlock_internal (size : %u)", (U32)srcSize); - RETURN_ERROR_IF(srcSize >= ZSTD_BLOCKSIZE_MAX, srcSize_wrong, ""); + /* Note : the wording of the specification + * allows compressed block to be sized exactly ZSTD_BLOCKSIZE_MAX. + * This generally does not happen, as it makes little sense, + * since an uncompressed block would feature same size and have no decompression cost. + * Also, note that decoder from reference libzstd before < v1.5.4 + * would consider this edge case as an error. + * As a consequence, avoid generating compressed blocks of size ZSTD_BLOCKSIZE_MAX + * for broader compatibility with the deployed ecosystem of zstd decoders */ + RETURN_ERROR_IF(srcSize > ZSTD_BLOCKSIZE_MAX, srcSize_wrong, ""); /* Decode literals section */ - { size_t const litCSize = ZSTD_decodeLiteralsBlock(dctx, src, srcSize); - DEBUGLOG(5, "ZSTD_decodeLiteralsBlock : %u", (U32)litCSize); + { size_t const litCSize = ZSTD_decodeLiteralsBlock(dctx, src, srcSize, dst, dstCapacity, streaming); + DEBUGLOG(5, "ZSTD_decodeLiteralsBlock : cSize=%u, nbLiterals=%zu", (U32)litCSize, dctx->litSize); if (ZSTD_isError(litCSize)) return litCSize; ip += litCSize; srcSize -= litCSize; @@ -1480,6 +2076,23 @@ ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, /* Build Decoding Tables */ { + /* Compute the maximum block size, which must also work when !frame and fParams are unset. + * Additionally, take the min with dstCapacity to ensure that the totalHistorySize fits in a size_t. + */ + size_t const blockSizeMax = MIN(dstCapacity, (frame ? dctx->fParams.blockSizeMax : ZSTD_BLOCKSIZE_MAX)); + size_t const totalHistorySize = ZSTD_totalHistorySize((BYTE*)dst + blockSizeMax, (BYTE const*)dctx->virtualStart); + /* isLongOffset must be true if there are long offsets. + * Offsets are long if they are larger than ZSTD_maxShortOffset(). + * We don't expect that to be the case in 64-bit mode. + * + * We check here to see if our history is large enough to allow long offsets. + * If it isn't, then we can't possible have (valid) long offsets. If the offset + * is invalid, then it is okay to read it incorrectly. + * + * If isLongOffsets is true, then we will later check our decoding table to see + * if it is even possible to generate long offsets. + */ + ZSTD_longOffset_e isLongOffset = (ZSTD_longOffset_e)(MEM_32bits() && (totalHistorySize > ZSTD_maxShortOffset())); /* These macros control at build-time which decompressor implementation * we use. If neither is defined, we do some inspection and dispatch at * runtime. @@ -1487,6 +2100,11 @@ ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, #if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) int usePrefetchDecoder = dctx->ddictIsCold; +#else + /* Set to 1 to avoid computing offset info if we don't need to. + * Otherwise this value is ignored. + */ + int usePrefetchDecoder = 1; #endif int nbSeq; size_t const seqHSize = ZSTD_decodeSeqHeaders(dctx, &nbSeq, ip, srcSize); @@ -1494,32 +2112,49 @@ ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, ip += seqHSize; srcSize -= seqHSize; - RETURN_ERROR_IF(dst == NULL && nbSeq > 0, dstSize_tooSmall, "NULL not handled"); + RETURN_ERROR_IF((dst == NULL || dstCapacity == 0) && nbSeq > 0, dstSize_tooSmall, "NULL not handled"); + RETURN_ERROR_IF(MEM_64bits() && sizeof(size_t) == sizeof(void*) && (size_t)(-1) - (size_t)dst < (size_t)(1 << 20), dstSize_tooSmall, + "invalid dst"); -#if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ - !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) - if ( !usePrefetchDecoder - && (!frame || (dctx->fParams.windowSize > (1<<24))) - && (nbSeq>ADVANCED_SEQS) ) { /* could probably use a larger nbSeq limit */ - U32 const shareLongOffsets = ZSTD_getLongOffsetsShare(dctx->OFTptr); - U32 const minShare = MEM_64bits() ? 7 : 20; /* heuristic values, correspond to 2.73% and 7.81% */ - usePrefetchDecoder = (shareLongOffsets >= minShare); + /* If we could potentially have long offsets, or we might want to use the prefetch decoder, + * compute information about the share of long offsets, and the maximum nbAdditionalBits. + * NOTE: could probably use a larger nbSeq limit + */ + if (isLongOffset || (!usePrefetchDecoder && (totalHistorySize > (1u << 24)) && (nbSeq > 8))) { + ZSTD_OffsetInfo const info = ZSTD_getOffsetInfo(dctx->OFTptr, nbSeq); + if (isLongOffset && info.maxNbAdditionalBits <= STREAM_ACCUMULATOR_MIN) { + /* If isLongOffset, but the maximum number of additional bits that we see in our table is small + * enough, then we know it is impossible to have too long an offset in this block, so we can + * use the regular offset decoder. + */ + isLongOffset = ZSTD_lo_isRegularOffset; + } + if (!usePrefetchDecoder) { + U32 const minShare = MEM_64bits() ? 7 : 20; /* heuristic values, correspond to 2.73% and 7.81% */ + usePrefetchDecoder = (info.longOffsetShare >= minShare); + } } -#endif dctx->ddictIsCold = 0; #if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) - if (usePrefetchDecoder) + if (usePrefetchDecoder) { +#else + (void)usePrefetchDecoder; + { #endif #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT return ZSTD_decompressSequencesLong(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); #endif + } #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG /* else */ - return ZSTD_decompressSequences(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); + if (dctx->litBufferLocation == ZSTD_split) + return ZSTD_decompressSequencesSplitLitBuffer(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); + else + return ZSTD_decompressSequences(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); #endif } } @@ -1536,13 +2171,22 @@ void ZSTD_checkContinuity(ZSTD_DCtx* dctx, const void* dst, size_t dstSize) } -size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize) +size_t ZSTD_decompressBlock_deprecated(ZSTD_DCtx* dctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) { size_t dSize; ZSTD_checkContinuity(dctx, dst, dstCapacity); - dSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, /* frame */ 0); + dSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, /* frame */ 0, not_streaming); dctx->previousDstEnd = (char*)dst + dSize; return dSize; } + + +/* NOTE: Must just wrap ZSTD_decompressBlock_deprecated() */ +size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) +{ + return ZSTD_decompressBlock_deprecated(dctx, dst, dstCapacity, src, srcSize); +} diff --git a/Utilities/cmzstd/lib/decompress/zstd_decompress_block.h b/Utilities/cmzstd/lib/decompress/zstd_decompress_block.h index 049a0cd84c7..9d1318882d0 100644 --- a/Utilities/cmzstd/lib/decompress/zstd_decompress_block.h +++ b/Utilities/cmzstd/lib/decompress/zstd_decompress_block.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -33,6 +33,12 @@ */ + /* Streaming state is used to inform allocation of the literal buffer */ +typedef enum { + not_streaming = 0, + is_streaming = 1 +} streaming_operation; + /* ZSTD_decompressBlock_internal() : * decompress block, starting at `src`, * into destination buffer `dst`. @@ -41,7 +47,7 @@ */ size_t ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, - const void* src, size_t srcSize, const int frame); + const void* src, size_t srcSize, const int frame, const streaming_operation streaming); /* ZSTD_buildFSETable() : * generate FSE decoding table for one symbol (ll, ml or off) @@ -54,9 +60,14 @@ size_t ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, */ void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, const short* normalizedCounter, unsigned maxSymbolValue, - const U32* baseValue, const U32* nbAdditionalBits, + const U32* baseValue, const U8* nbAdditionalBits, unsigned tableLog, void* wksp, size_t wkspSize, int bmi2); +/* Internal definition of ZSTD_decompressBlock() to avoid deprecation warnings. */ +size_t ZSTD_decompressBlock_deprecated(ZSTD_DCtx* dctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); + #endif /* ZSTD_DEC_BLOCK_H */ diff --git a/Utilities/cmzstd/lib/decompress/zstd_decompress_internal.h b/Utilities/cmzstd/lib/decompress/zstd_decompress_internal.h index ebda0c9031d..c2ec5d9fbef 100644 --- a/Utilities/cmzstd/lib/decompress/zstd_decompress_internal.h +++ b/Utilities/cmzstd/lib/decompress/zstd_decompress_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -20,7 +20,7 @@ * Dependencies *********************************************************/ #include "../common/mem.h" /* BYTE, U16, U32 */ -#include "../common/zstd_internal.h" /* ZSTD_seqSymbol */ +#include "../common/zstd_internal.h" /* constants : MaxLL, MaxML, MaxOff, LLFSELog, etc. */ @@ -40,7 +40,7 @@ static UNUSED_ATTR const U32 OF_base[MaxOff+1] = { 0xFFFD, 0x1FFFD, 0x3FFFD, 0x7FFFD, 0xFFFFD, 0x1FFFFD, 0x3FFFFD, 0x7FFFFD, 0xFFFFFD, 0x1FFFFFD, 0x3FFFFFD, 0x7FFFFFD, 0xFFFFFFD, 0x1FFFFFFD, 0x3FFFFFFD, 0x7FFFFFFD }; -static UNUSED_ATTR const U32 OF_bits[MaxOff+1] = { +static UNUSED_ATTR const U8 OF_bits[MaxOff+1] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, @@ -75,12 +75,13 @@ static UNUSED_ATTR const U32 ML_base[MaxML+1] = { #define ZSTD_BUILD_FSE_TABLE_WKSP_SIZE (sizeof(S16) * (MaxSeq + 1) + (1u << MaxFSELog) + sizeof(U64)) #define ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32 ((ZSTD_BUILD_FSE_TABLE_WKSP_SIZE + sizeof(U32) - 1) / sizeof(U32)) +#define ZSTD_HUFFDTABLE_CAPACITY_LOG 12 typedef struct { ZSTD_seqSymbol LLTable[SEQSYMBOL_TABLE_SIZE(LLFSELog)]; /* Note : Space reserved for FSE Tables */ ZSTD_seqSymbol OFTable[SEQSYMBOL_TABLE_SIZE(OffFSELog)]; /* is also used as temporary workspace while building hufTable during DDict creation */ ZSTD_seqSymbol MLTable[SEQSYMBOL_TABLE_SIZE(MLFSELog)]; /* and therefore must be at least HUF_DECOMPRESS_WORKSPACE_SIZE large */ - HUF_DTable hufTable[HUF_DTABLE_SIZE(HufLog)]; /* can accommodate HUF_decompress4X */ + HUF_DTable hufTable[HUF_DTABLE_SIZE(ZSTD_HUFFDTABLE_CAPACITY_LOG)]; /* can accommodate HUF_decompress4X */ U32 rep[ZSTD_REP_NUM]; U32 workspace[ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32]; } ZSTD_entropyDTables_t; @@ -106,6 +107,22 @@ typedef struct { size_t ddictPtrCount; } ZSTD_DDictHashSet; +#ifndef ZSTD_DECODER_INTERNAL_BUFFER +# define ZSTD_DECODER_INTERNAL_BUFFER (1 << 16) +#endif + +#define ZSTD_LBMIN 64 +#define ZSTD_LBMAX (128 << 10) + +/* extra buffer, compensates when dst is not large enough to store litBuffer */ +#define ZSTD_LITBUFFEREXTRASIZE BOUNDED(ZSTD_LBMIN, ZSTD_DECODER_INTERNAL_BUFFER, ZSTD_LBMAX) + +typedef enum { + ZSTD_not_in_dst = 0, /* Stored entirely within litExtraBuffer */ + ZSTD_in_dst = 1, /* Stored entirely within dst (in memory after current output write) */ + ZSTD_split = 2 /* Split between litExtraBuffer and dst */ +} ZSTD_litLocation_e; + struct ZSTD_DCtx_s { const ZSTD_seqSymbol* LLTptr; @@ -136,7 +153,9 @@ struct ZSTD_DCtx_s size_t litSize; size_t rleSize; size_t staticSize; +#if DYNAMIC_BMI2 != 0 int bmi2; /* == 1 if the CPU supports BMI2 and 0 otherwise. CPU support is determined dynamically once per context lifetime. */ +#endif /* dictionary */ ZSTD_DDict* ddictLocal; @@ -146,6 +165,7 @@ struct ZSTD_DCtx_s ZSTD_dictUses_e dictUses; ZSTD_DDictHashSet* ddictSet; /* Hash set for multiple ddicts */ ZSTD_refMultipleDDicts_e refMultipleDDicts; /* User specified: if == 1, will allow references to multiple DDicts. Default == 0 (disabled) */ + int disableHufAsm; /* streaming */ ZSTD_dStreamStage streamStage; @@ -158,16 +178,21 @@ struct ZSTD_DCtx_s size_t outStart; size_t outEnd; size_t lhSize; +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) void* legacyContext; U32 previousLegacyVersion; U32 legacyVersion; +#endif U32 hostageByte; int noForwardProgress; ZSTD_bufferMode_e outBufferMode; ZSTD_outBuffer expectedOutBuffer; /* workspace */ - BYTE litBuffer[ZSTD_BLOCKSIZE_MAX + WILDCOPY_OVERLENGTH]; + BYTE* litBuffer; + const BYTE* litBufferEnd; + ZSTD_litLocation_e litBufferLocation; + BYTE litExtraBuffer[ZSTD_LITBUFFEREXTRASIZE + WILDCOPY_OVERLENGTH]; /* literal buffer can be split between storage within dst and within this scratch buffer */ BYTE headerBuffer[ZSTD_FRAMEHEADERSIZE_MAX]; size_t oversizedDuration; @@ -183,6 +208,14 @@ struct ZSTD_DCtx_s #endif }; /* typedef'd to ZSTD_DCtx within "zstd.h" */ +MEM_STATIC int ZSTD_DCtx_get_bmi2(const struct ZSTD_DCtx_s *dctx) { +#if DYNAMIC_BMI2 != 0 + return dctx->bmi2; +#else + (void)dctx; + return 0; +#endif +} /*-******************************************************* * Shared internal functions diff --git a/Utilities/cmzstd/lib/deprecated/zbuff.h b/Utilities/cmzstd/lib/deprecated/zbuff.h index b83ea0fed58..a968245b36a 100644 --- a/Utilities/cmzstd/lib/deprecated/zbuff.h +++ b/Utilities/cmzstd/lib/deprecated/zbuff.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/deprecated/zbuff_common.c b/Utilities/cmzstd/lib/deprecated/zbuff_common.c index e7d01a08180..5a2f2db354f 100644 --- a/Utilities/cmzstd/lib/deprecated/zbuff_common.c +++ b/Utilities/cmzstd/lib/deprecated/zbuff_common.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/deprecated/zbuff_compress.c b/Utilities/cmzstd/lib/deprecated/zbuff_compress.c index 2e722673568..1d8682150b2 100644 --- a/Utilities/cmzstd/lib/deprecated/zbuff_compress.c +++ b/Utilities/cmzstd/lib/deprecated/zbuff_compress.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -15,6 +15,7 @@ ***************************************/ #define ZBUFF_STATIC_LINKING_ONLY #include "zbuff.h" +#include "../common/error_private.h" /*-*********************************************************** @@ -73,13 +74,32 @@ size_t ZBUFF_compressInit_advanced(ZBUFF_CCtx* zbc, ZSTD_parameters params, unsigned long long pledgedSrcSize) { if (pledgedSrcSize==0) pledgedSrcSize = ZSTD_CONTENTSIZE_UNKNOWN; /* preserve "0 == unknown" behavior */ - return ZSTD_initCStream_advanced(zbc, dict, dictSize, params, pledgedSrcSize); + FORWARD_IF_ERROR(ZSTD_CCtx_reset(zbc, ZSTD_reset_session_only), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setPledgedSrcSize(zbc, pledgedSrcSize), ""); + + FORWARD_IF_ERROR(ZSTD_checkCParams(params.cParams), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_windowLog, params.cParams.windowLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_hashLog, params.cParams.hashLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_chainLog, params.cParams.chainLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_searchLog, params.cParams.searchLog), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_minMatch, params.cParams.minMatch), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_targetLength, params.cParams.targetLength), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_strategy, params.cParams.strategy), ""); + + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_contentSizeFlag, params.fParams.contentSizeFlag), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_checksumFlag, params.fParams.checksumFlag), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_dictIDFlag, params.fParams.noDictIDFlag), ""); + + FORWARD_IF_ERROR(ZSTD_CCtx_loadDictionary(zbc, dict, dictSize), ""); + return 0; } - size_t ZBUFF_compressInitDictionary(ZBUFF_CCtx* zbc, const void* dict, size_t dictSize, int compressionLevel) { - return ZSTD_initCStream_usingDict(zbc, dict, dictSize, compressionLevel); + FORWARD_IF_ERROR(ZSTD_CCtx_reset(zbc, ZSTD_reset_session_only), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(zbc, ZSTD_c_compressionLevel, compressionLevel), ""); + FORWARD_IF_ERROR(ZSTD_CCtx_loadDictionary(zbc, dict, dictSize), ""); + return 0; } size_t ZBUFF_compressInit(ZBUFF_CCtx* zbc, int compressionLevel) diff --git a/Utilities/cmzstd/lib/deprecated/zbuff_decompress.c b/Utilities/cmzstd/lib/deprecated/zbuff_decompress.c index d73c0f35fac..12a66af7412 100644 --- a/Utilities/cmzstd/lib/deprecated/zbuff_decompress.c +++ b/Utilities/cmzstd/lib/deprecated/zbuff_decompress.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -13,6 +13,8 @@ /* ************************************* * Dependencies ***************************************/ +#define ZSTD_DISABLE_DEPRECATE_WARNINGS /* suppress warning on ZSTD_initDStream_usingDict */ +#include "../zstd.h" /* ZSTD_CStream, ZSTD_DStream, ZSTDLIB_API */ #define ZBUFF_STATIC_LINKING_ONLY #include "zbuff.h" diff --git a/Utilities/cmzstd/lib/dictBuilder/cover.c b/Utilities/cmzstd/lib/dictBuilder/cover.c index 8364444d159..9e5e7d5b55d 100644 --- a/Utilities/cmzstd/lib/dictBuilder/cover.c +++ b/Utilities/cmzstd/lib/dictBuilder/cover.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -34,12 +34,20 @@ #include "../common/pool.h" #include "../common/threading.h" #include "../common/zstd_internal.h" /* includes zstd.h */ +#include "../common/bits.h" /* ZSTD_highbit32 */ #include "../zdict.h" #include "cover.h" /*-************************************* * Constants ***************************************/ +/** +* There are 32bit indexes used to ref samples, so limit samples size to 4GB +* on 64bit builds. +* For 32bit builds we choose 1 GB. +* Most 32bit platforms have 2GB user-mode addressable space and we allocate a large +* contiguous buffer, so 1GB is already a high limit. +*/ #define COVER_MAX_SAMPLES_SIZE (sizeof(size_t) == 8 ? ((unsigned)-1) : ((unsigned)1 GB)) #define COVER_DEFAULT_SPLITPOINT 1.0 @@ -47,7 +55,7 @@ * Console display ***************************************/ #ifndef LOCALDISPLAYLEVEL -static int g_displayLevel = 2; +static int g_displayLevel = 0; #endif #undef DISPLAY #define DISPLAY(...) \ @@ -534,7 +542,7 @@ static void COVER_ctx_destroy(COVER_ctx_t *ctx) { /** * Prepare a context for dictionary building. - * The context is only dependent on the parameter `d` and can used multiple + * The context is only dependent on the parameter `d` and can be used multiple * times. * Returns 0 on success or error code on error. * The context must be destroyed with `COVER_ctx_destroy()`. @@ -639,7 +647,7 @@ static size_t COVER_ctx_init(COVER_ctx_t *ctx, const void *samplesBuffer, void COVER_warnOnSmallCorpus(size_t maxDictSize, size_t nbDmers, int displayLevel) { - const double ratio = (double)nbDmers / maxDictSize; + const double ratio = (double)nbDmers / (double)maxDictSize; if (ratio >= 10) { return; } @@ -735,7 +743,7 @@ ZDICTLIB_API size_t ZDICT_trainFromBuffer_cover( COVER_map_t activeDmers; parameters.splitPoint = 1.0; /* Initialize global data */ - g_displayLevel = parameters.zParams.notificationLevel; + g_displayLevel = (int)parameters.zParams.notificationLevel; /* Checks */ if (!COVER_checkParameters(parameters, dictBufferCapacity)) { DISPLAYLEVEL(1, "Cover parameters incorrect\n"); @@ -943,9 +951,17 @@ void COVER_best_finish(COVER_best_t *best, ZDICT_cover_params_t parameters, } } +static COVER_dictSelection_t setDictSelection(BYTE* buf, size_t s, size_t csz) +{ + COVER_dictSelection_t ds; + ds.dictContent = buf; + ds.dictSize = s; + ds.totalCompressedSize = csz; + return ds; +} + COVER_dictSelection_t COVER_dictSelectionError(size_t error) { - COVER_dictSelection_t selection = { NULL, 0, error }; - return selection; + return setDictSelection(NULL, 0, error); } unsigned COVER_dictSelectionIsError(COVER_dictSelection_t selection) { @@ -998,9 +1014,8 @@ COVER_dictSelection_t COVER_selectDict(BYTE* customDictContent, size_t dictBuffe } if (params.shrinkDict == 0) { - COVER_dictSelection_t selection = { largestDictbuffer, dictContentSize, totalCompressedSize }; free(candidateDictBuffer); - return selection; + return setDictSelection(largestDictbuffer, dictContentSize, totalCompressedSize); } largestDict = dictContentSize; @@ -1032,20 +1047,16 @@ COVER_dictSelection_t COVER_selectDict(BYTE* customDictContent, size_t dictBuffe return COVER_dictSelectionError(totalCompressedSize); } - if (totalCompressedSize <= largestCompressed * regressionTolerance) { - COVER_dictSelection_t selection = { candidateDictBuffer, dictContentSize, totalCompressedSize }; + if ((double)totalCompressedSize <= (double)largestCompressed * regressionTolerance) { free(largestDictbuffer); - return selection; + return setDictSelection( candidateDictBuffer, dictContentSize, totalCompressedSize ); } dictContentSize *= 2; } dictContentSize = largestDict; totalCompressedSize = largestCompressed; - { - COVER_dictSelection_t selection = { largestDictbuffer, dictContentSize, totalCompressedSize }; - free(candidateDictBuffer); - return selection; - } + free(candidateDictBuffer); + return setDictSelection( largestDictbuffer, dictContentSize, totalCompressedSize ); } /** diff --git a/Utilities/cmzstd/lib/dictBuilder/cover.h b/Utilities/cmzstd/lib/dictBuilder/cover.h index 1aacdddd6fe..252624bdeb5 100644 --- a/Utilities/cmzstd/lib/dictBuilder/cover.h +++ b/Utilities/cmzstd/lib/dictBuilder/cover.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the diff --git a/Utilities/cmzstd/lib/dictBuilder/fastcover.c b/Utilities/cmzstd/lib/dictBuilder/fastcover.c index ed789f92f48..46bba0120b0 100644 --- a/Utilities/cmzstd/lib/dictBuilder/fastcover.c +++ b/Utilities/cmzstd/lib/dictBuilder/fastcover.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -32,6 +32,13 @@ /*-************************************* * Constants ***************************************/ +/** +* There are 32bit indexes used to ref samples, so limit samples size to 4GB +* on 64bit builds. +* For 32bit builds we choose 1 GB. +* Most 32bit platforms have 2GB user-mode addressable space and we allocate a large +* contiguous buffer, so 1GB is already a high limit. +*/ #define FASTCOVER_MAX_SAMPLES_SIZE (sizeof(size_t) == 8 ? ((unsigned)-1) : ((unsigned)1 GB)) #define FASTCOVER_MAX_F 31 #define FASTCOVER_MAX_ACCEL 10 @@ -44,7 +51,7 @@ * Console display ***************************************/ #ifndef LOCALDISPLAYLEVEL -static int g_displayLevel = 2; +static int g_displayLevel = 0; #endif #undef DISPLAY #define DISPLAY(...) \ @@ -297,7 +304,7 @@ FASTCOVER_computeFrequency(U32* freqs, const FASTCOVER_ctx_t* ctx) /** * Prepare a context for dictionary building. - * The context is only dependent on the parameter `d` and can used multiple + * The context is only dependent on the parameter `d` and can be used multiple * times. * Returns 0 on success or error code on error. * The context must be destroyed with `FASTCOVER_ctx_destroy()`. @@ -549,7 +556,7 @@ ZDICT_trainFromBuffer_fastCover(void* dictBuffer, size_t dictBufferCapacity, ZDICT_cover_params_t coverParams; FASTCOVER_accel_t accelParams; /* Initialize global data */ - g_displayLevel = parameters.zParams.notificationLevel; + g_displayLevel = (int)parameters.zParams.notificationLevel; /* Assign splitPoint and f if not provided */ parameters.splitPoint = 1.0; parameters.f = parameters.f == 0 ? DEFAULT_F : parameters.f; @@ -632,7 +639,7 @@ ZDICT_optimizeTrainFromBuffer_fastCover( const unsigned accel = parameters->accel == 0 ? DEFAULT_ACCEL : parameters->accel; const unsigned shrinkDict = 0; /* Local variables */ - const int displayLevel = parameters->zParams.notificationLevel; + const int displayLevel = (int)parameters->zParams.notificationLevel; unsigned iteration = 1; unsigned d; unsigned k; @@ -716,7 +723,7 @@ ZDICT_optimizeTrainFromBuffer_fastCover( data->parameters.splitPoint = splitPoint; data->parameters.steps = kSteps; data->parameters.shrinkDict = shrinkDict; - data->parameters.zParams.notificationLevel = g_displayLevel; + data->parameters.zParams.notificationLevel = (unsigned)g_displayLevel; /* Check the parameters */ if (!FASTCOVER_checkParameters(data->parameters, dictBufferCapacity, data->ctx->f, accel)) { diff --git a/Utilities/cmzstd/lib/dictBuilder/zdict.c b/Utilities/cmzstd/lib/dictBuilder/zdict.c index 459cbe4d19b..58290f450d6 100644 --- a/Utilities/cmzstd/lib/dictBuilder/zdict.c +++ b/Utilities/cmzstd/lib/dictBuilder/zdict.c @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -44,7 +44,6 @@ #ifndef ZDICT_STATIC_LINKING_ONLY # define ZDICT_STATIC_LINKING_ONLY #endif -#define HUF_STATIC_LINKING_ONLY #include "../common/mem.h" /* read */ #include "../common/fse.h" /* FSE_normalizeCount, FSE_writeNCount */ @@ -54,6 +53,7 @@ #include "../compress/zstd_compress_internal.h" /* ZSTD_loadCEntropy() */ #include "../zdict.h" #include "divsufsort.h" +#include "../common/bits.h" /* ZSTD_NbCommonBytes */ /*-************************************* @@ -130,65 +130,6 @@ size_t ZDICT_getDictHeaderSize(const void* dictBuffer, size_t dictSize) /*-******************************************************** * Dictionary training functions **********************************************************/ -static unsigned ZDICT_NbCommonBytes (size_t val) -{ - if (MEM_isLittleEndian()) { - if (MEM_64bits()) { -# if defined(_MSC_VER) && defined(_WIN64) - unsigned long r = 0; - _BitScanForward64( &r, (U64)val ); - return (unsigned)(r>>3); -# elif defined(__GNUC__) && (__GNUC__ >= 3) - return (__builtin_ctzll((U64)val) >> 3); -# else - static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, 0, 3, 1, 3, 1, 4, 2, 7, 0, 2, 3, 6, 1, 5, 3, 5, 1, 3, 4, 4, 2, 5, 6, 7, 7, 0, 1, 2, 3, 3, 4, 6, 2, 6, 5, 5, 3, 4, 5, 6, 7, 1, 2, 4, 6, 4, 4, 5, 7, 2, 6, 5, 7, 6, 7, 7 }; - return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; -# endif - } else { /* 32 bits */ -# if defined(_MSC_VER) - unsigned long r=0; - _BitScanForward( &r, (U32)val ); - return (unsigned)(r>>3); -# elif defined(__GNUC__) && (__GNUC__ >= 3) - return (__builtin_ctz((U32)val) >> 3); -# else - static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 1, 3, 2, 0, 1, 3, 3, 1, 2, 2, 2, 2, 0, 3, 1, 2, 0, 1, 0, 1, 1 }; - return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; -# endif - } - } else { /* Big Endian CPU */ - if (MEM_64bits()) { -# if defined(_MSC_VER) && defined(_WIN64) - unsigned long r = 0; - _BitScanReverse64( &r, val ); - return (unsigned)(r>>3); -# elif defined(__GNUC__) && (__GNUC__ >= 3) - return (__builtin_clzll(val) >> 3); -# else - unsigned r; - const unsigned n32 = sizeof(size_t)*4; /* calculate this way due to compiler complaining in 32-bits mode */ - if (!(val>>n32)) { r=4; } else { r=0; val>>=n32; } - if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } - r += (!val); - return r; -# endif - } else { /* 32 bits */ -# if defined(_MSC_VER) - unsigned long r = 0; - _BitScanReverse( &r, (unsigned long)val ); - return (unsigned)(r>>3); -# elif defined(__GNUC__) && (__GNUC__ >= 3) - return (__builtin_clz((U32)val) >> 3); -# else - unsigned r; - if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } - r += (!val); - return r; -# endif - } } -} - - /*! ZDICT_count() : Count the nb of common bytes between 2 pointers. Note : this function presumes end of buffer followed by noisy guard band. @@ -203,7 +144,7 @@ static size_t ZDICT_count(const void* pIn, const void* pMatch) pMatch = (const char*)pMatch+sizeof(size_t); continue; } - pIn = (const char*)pIn+ZDICT_NbCommonBytes(diff); + pIn = (const char*)pIn+ZSTD_NbCommonBytes(diff); return (size_t)((const char*)pIn - pStart); } } @@ -235,7 +176,7 @@ static dictItem ZDICT_analyzePos( U32 savings[LLIMIT] = {0}; const BYTE* b = (const BYTE*)buffer; size_t maxLength = LLIMIT; - size_t pos = suffix[start]; + size_t pos = (size_t)suffix[start]; U32 end = start; dictItem solution; @@ -369,7 +310,7 @@ static dictItem ZDICT_analyzePos( savings[i] = savings[i-1] + (lengthList[i] * (i-3)); DISPLAYLEVEL(4, "Selected dict at position %u, of length %u : saves %u (ratio: %.2f) \n", - (unsigned)pos, (unsigned)maxLength, (unsigned)savings[maxLength], (double)savings[maxLength] / maxLength); + (unsigned)pos, (unsigned)maxLength, (unsigned)savings[maxLength], (double)savings[maxLength] / (double)maxLength); solution.pos = (U32)pos; solution.length = (U32)maxLength; @@ -379,7 +320,7 @@ static dictItem ZDICT_analyzePos( { U32 id; for (id=start; id1) && (table[u-1].savings < elt.savings)) - table[u] = table[u-1], u--; + table[u] = table[u-1], u--; table[u] = elt; return u; } } @@ -442,7 +383,7 @@ static U32 ZDICT_tryMerge(dictItem* table, dictItem elt, U32 eltNbToSkip, const if ((table[u].pos + table[u].length >= elt.pos) && (table[u].pos < elt.pos)) { /* overlap, existing < new */ /* append */ - int const addedLength = (int)eltEnd - (table[u].pos + table[u].length); + int const addedLength = (int)eltEnd - (int)(table[u].pos + table[u].length); table[u].savings += elt.length / 8; /* rough approx bonus */ if (addedLength > 0) { /* otherwise, elt fully included into existing */ table[u].length += addedLength; @@ -582,7 +523,7 @@ static size_t ZDICT_trainBuffer_legacy(dictItem* dictList, U32 dictListSize, if (solution.length==0) { cursor++; continue; } ZDICT_insertDictItem(dictList, dictListSize, solution, buffer); cursor += solution.length; - DISPLAYUPDATE(2, "\r%4.2f %% \r", (double)cursor / bufferSize * 100); + DISPLAYUPDATE(2, "\r%4.2f %% \r", (double)cursor / (double)bufferSize * 100.0); } } _cleanup: @@ -625,11 +566,11 @@ static void ZDICT_countEStats(EStats_ress_t esr, const ZSTD_parameters* params, size_t cSize; if (srcSize > blockSizeMax) srcSize = blockSizeMax; /* protection vs large samples */ - { size_t const errorCode = ZSTD_compressBegin_usingCDict(esr.zc, esr.dict); + { size_t const errorCode = ZSTD_compressBegin_usingCDict_deprecated(esr.zc, esr.dict); if (ZSTD_isError(errorCode)) { DISPLAYLEVEL(1, "warning : ZSTD_compressBegin_usingCDict failed \n"); return; } } - cSize = ZSTD_compressBlock(esr.zc, esr.workPlace, ZSTD_BLOCKSIZE_MAX, src, srcSize); + cSize = ZSTD_compressBlock_deprecated(esr.zc, esr.workPlace, ZSTD_BLOCKSIZE_MAX, src, srcSize); if (ZSTD_isError(cSize)) { DISPLAYLEVEL(3, "warning : could not compress sample size %u \n", (unsigned)srcSize); return; } if (cSize) { /* if == 0; block is not compressible */ @@ -662,8 +603,8 @@ static void ZDICT_countEStats(EStats_ress_t esr, const ZSTD_parameters* params, if (nbSeq >= 2) { /* rep offsets */ const seqDef* const seq = seqStorePtr->sequencesStart; - U32 offset1 = seq[0].offset - 3; - U32 offset2 = seq[1].offset - 3; + U32 offset1 = seq[0].offBase - ZSTD_REP_NUM; + U32 offset2 = seq[1].offBase - ZSTD_REP_NUM; if (offset1 >= MAXREPOFFSET) offset1 = 0; if (offset2 >= MAXREPOFFSET) offset2 = 0; repOffsets[offset1] += 3; @@ -734,6 +675,7 @@ static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize, size_t const totalSrcSize = ZDICT_totalSampleSize(fileSizes, nbFiles); size_t const averageSampleSize = totalSrcSize / (nbFiles + !nbFiles); BYTE* dstPtr = (BYTE*)dstBuffer; + U32 wksp[HUF_CTABLE_WORKSPACE_SIZE_U32]; /* init */ DEBUGLOG(4, "ZDICT_analyzeEntropy"); @@ -766,8 +708,15 @@ static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize, pos += fileSizes[u]; } + if (notificationLevel >= 4) { + /* writeStats */ + DISPLAYLEVEL(4, "Offset Code Frequencies : \n"); + for (u=0; u<=offcodeMax; u++) { + DISPLAYLEVEL(4, "%2u :%7u \n", u, offcodeCount[u]); + } } + /* analyze, build stats, starting with literals */ - { size_t maxNbBits = HUF_buildCTable (hufTable, countLit, 255, huffLog); + { size_t maxNbBits = HUF_buildCTable_wksp(hufTable, countLit, 255, huffLog, wksp, sizeof(wksp)); if (HUF_isError(maxNbBits)) { eSize = maxNbBits; DISPLAYLEVEL(1, " HUF_buildCTable error \n"); @@ -776,7 +725,7 @@ static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize, if (maxNbBits==8) { /* not compressible : will fail on HUF_writeCTable() */ DISPLAYLEVEL(2, "warning : pathological dataset : literals are not compressible : samples are noisy or too regular \n"); ZDICT_flatLit(countLit); /* replace distribution by a fake "mostly flat but still compressible" distribution, that HUF_writeCTable() can encode */ - maxNbBits = HUF_buildCTable (hufTable, countLit, 255, huffLog); + maxNbBits = HUF_buildCTable_wksp(hufTable, countLit, 255, huffLog, wksp, sizeof(wksp)); assert(maxNbBits==9); } huffLog = (U32)maxNbBits; @@ -817,7 +766,7 @@ static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize, llLog = (U32)errorCode; /* write result to buffer */ - { size_t const hhSize = HUF_writeCTable(dstPtr, maxDstSize, hufTable, 255, huffLog); + { size_t const hhSize = HUF_writeCTable_wksp(dstPtr, maxDstSize, hufTable, 255, huffLog, wksp, sizeof(wksp)); if (HUF_isError(hhSize)) { eSize = hhSize; DISPLAYLEVEL(1, "HUF_writeCTable error \n"); @@ -872,7 +821,7 @@ static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize, MEM_writeLE32(dstPtr+8, bestRepOffset[2].offset); #else /* at this stage, we don't use the result of "most common first offset", - as the impact of statistics is not properly evaluated */ + * as the impact of statistics is not properly evaluated */ MEM_writeLE32(dstPtr+0, repStartValue[0]); MEM_writeLE32(dstPtr+4, repStartValue[1]); MEM_writeLE32(dstPtr+8, repStartValue[2]); @@ -888,6 +837,17 @@ static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize, } +/** + * @returns the maximum repcode value + */ +static U32 ZDICT_maxRep(U32 const reps[ZSTD_REP_NUM]) +{ + U32 maxRep = reps[0]; + int r; + for (r = 1; r < ZSTD_REP_NUM; ++r) + maxRep = MAX(maxRep, reps[r]); + return maxRep; +} size_t ZDICT_finalizeDictionary(void* dictBuffer, size_t dictBufferCapacity, const void* customDictContent, size_t dictContentSize, @@ -899,11 +859,13 @@ size_t ZDICT_finalizeDictionary(void* dictBuffer, size_t dictBufferCapacity, BYTE header[HBUFFSIZE]; int const compressionLevel = (params.compressionLevel == 0) ? ZSTD_CLEVEL_DEFAULT : params.compressionLevel; U32 const notificationLevel = params.notificationLevel; + /* The final dictionary content must be at least as large as the largest repcode */ + size_t const minContentSize = (size_t)ZDICT_maxRep(repStartValue); + size_t paddingSize; /* check conditions */ DEBUGLOG(4, "ZDICT_finalizeDictionary"); if (dictBufferCapacity < dictContentSize) return ERROR(dstSize_tooSmall); - if (dictContentSize < ZDICT_CONTENTSIZE_MIN) return ERROR(srcSize_wrong); if (dictBufferCapacity < ZDICT_DICTSIZE_MIN) return ERROR(dstSize_tooSmall); /* dictionary header */ @@ -927,12 +889,43 @@ size_t ZDICT_finalizeDictionary(void* dictBuffer, size_t dictBufferCapacity, hSize += eSize; } - /* copy elements in final buffer ; note : src and dst buffer can overlap */ - if (hSize + dictContentSize > dictBufferCapacity) dictContentSize = dictBufferCapacity - hSize; - { size_t const dictSize = hSize + dictContentSize; - char* dictEnd = (char*)dictBuffer + dictSize; - memmove(dictEnd - dictContentSize, customDictContent, dictContentSize); - memcpy(dictBuffer, header, hSize); + /* Shrink the content size if it doesn't fit in the buffer */ + if (hSize + dictContentSize > dictBufferCapacity) { + dictContentSize = dictBufferCapacity - hSize; + } + + /* Pad the dictionary content with zeros if it is too small */ + if (dictContentSize < minContentSize) { + RETURN_ERROR_IF(hSize + minContentSize > dictBufferCapacity, dstSize_tooSmall, + "dictBufferCapacity too small to fit max repcode"); + paddingSize = minContentSize - dictContentSize; + } else { + paddingSize = 0; + } + + { + size_t const dictSize = hSize + paddingSize + dictContentSize; + + /* The dictionary consists of the header, optional padding, and the content. + * The padding comes before the content because the "best" position in the + * dictionary is the last byte. + */ + BYTE* const outDictHeader = (BYTE*)dictBuffer; + BYTE* const outDictPadding = outDictHeader + hSize; + BYTE* const outDictContent = outDictPadding + paddingSize; + + assert(dictSize <= dictBufferCapacity); + assert(outDictContent + dictContentSize == (BYTE*)dictBuffer + dictSize); + + /* First copy the customDictContent into its final location. + * `customDictContent` and `dictBuffer` may overlap, so we must + * do this before any other writes into the output buffer. + * Then copy the header & padding into the output buffer. + */ + memmove(outDictContent, customDictContent, dictContentSize); + memcpy(outDictHeader, header, hSize); + memset(outDictPadding, 0, paddingSize); + return dictSize; } } diff --git a/Utilities/cmzstd/lib/zdict.h b/Utilities/cmzstd/lib/zdict.h index 75b05dbf43e..2268f948a5d 100644 --- a/Utilities/cmzstd/lib/zdict.h +++ b/Utilities/cmzstd/lib/zdict.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -8,32 +8,43 @@ * You may select, at your option, one of the above-listed licenses. */ -#ifndef DICTBUILDER_H_001 -#define DICTBUILDER_H_001 - #if defined (__cplusplus) extern "C" { #endif +#ifndef ZSTD_ZDICT_H +#define ZSTD_ZDICT_H /*====== Dependencies ======*/ #include /* size_t */ /* ===== ZDICTLIB_API : control library symbols visibility ===== */ -#ifndef ZDICTLIB_VISIBILITY -# if defined(__GNUC__) && (__GNUC__ >= 4) -# define ZDICTLIB_VISIBILITY __attribute__ ((visibility ("default"))) +#ifndef ZDICTLIB_VISIBLE + /* Backwards compatibility with old macro name */ +# ifdef ZDICTLIB_VISIBILITY +# define ZDICTLIB_VISIBLE ZDICTLIB_VISIBILITY +# elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) +# define ZDICTLIB_VISIBLE __attribute__ ((visibility ("default"))) +# else +# define ZDICTLIB_VISIBLE +# endif +#endif + +#ifndef ZDICTLIB_HIDDEN +# if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) +# define ZDICTLIB_HIDDEN __attribute__ ((visibility ("hidden"))) # else -# define ZDICTLIB_VISIBILITY +# define ZDICTLIB_HIDDEN # endif #endif + #if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) -# define ZDICTLIB_API __declspec(dllexport) ZDICTLIB_VISIBILITY +# define ZDICTLIB_API __declspec(dllexport) ZDICTLIB_VISIBLE #elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) -# define ZDICTLIB_API __declspec(dllimport) ZDICTLIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +# define ZDICTLIB_API __declspec(dllimport) ZDICTLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ #else -# define ZDICTLIB_API ZDICTLIB_VISIBILITY +# define ZDICTLIB_API ZDICTLIB_VISIBLE #endif /******************************************************************************* @@ -46,7 +57,7 @@ extern "C" { * * Zstd can use dictionaries to improve compression ratio of small data. * Traditionally small files don't compress well because there is very little - * repetion in a single sample, since it is small. But, if you are compressing + * repetition in a single sample, since it is small. But, if you are compressing * many similar files, like a bunch of JSON records that share the same * structure, you can train a dictionary on ahead of time on some samples of * these files. Then, zstd can use the dictionary to find repetitions that are @@ -110,8 +121,8 @@ extern "C" { * The zstd CLI defaults to a 110KB dictionary. You likely don't need a * dictionary larger than that. But, most use cases can get away with a * smaller dictionary. The advanced dictionary builders can automatically - * shrink the dictionary for you, and select a the smallest size that - * doesn't hurt compression ratio too much. See the `shrinkDict` parameter. + * shrink the dictionary for you, and select the smallest size that doesn't + * hurt compression ratio too much. See the `shrinkDict` parameter. * A smaller dictionary can save memory, and potentially speed up * compression. * @@ -132,7 +143,7 @@ extern "C" { * * # Benchmark levels 1-3 without a dictionary * zstd -b1e3 -r /path/to/my/files - * # Benchmark levels 1-3 with a dictioanry + * # Benchmark levels 1-3 with a dictionary * zstd -b1e3 -r /path/to/my/files -D /path/to/my/dictionary * * When should I retrain a dictionary? @@ -201,9 +212,9 @@ ZDICTLIB_API size_t ZDICT_trainFromBuffer(void* dictBuffer, size_t dictBufferCap const size_t* samplesSizes, unsigned nbSamples); typedef struct { - int compressionLevel; /*< optimize for a specific zstd compression level; 0 means default */ - unsigned notificationLevel; /*< Write log to stderr; 0 = none (default); 1 = errors; 2 = progression; 3 = details; 4 = debug; */ - unsigned dictID; /*< force dictID value; 0 means auto mode (32-bits random value) + int compressionLevel; /**< optimize for a specific zstd compression level; 0 means default */ + unsigned notificationLevel; /**< Write log to stderr; 0 = none (default); 1 = errors; 2 = progression; 3 = details; 4 = debug; */ + unsigned dictID; /**< force dictID value; 0 means auto mode (32-bits random value) * NOTE: The zstd format reserves some dictionary IDs for future use. * You may use them in private settings, but be warned that they * may be used by zstd in a public dictionary registry in the future. @@ -237,7 +248,6 @@ typedef struct { * is presumed that the most profitable content is at the end of the dictionary, * since that is the cheapest to reference. * - * `dictContentSize` must be >= ZDICT_CONTENTSIZE_MIN bytes. * `maxDictSize` must be >= max(dictContentSize, ZSTD_DICTSIZE_MIN). * * @return: size of dictionary stored into `dstDictBuffer` (<= `maxDictSize`), @@ -261,9 +271,21 @@ ZDICTLIB_API size_t ZDICT_getDictHeaderSize(const void* dictBuffer, size_t dictS ZDICTLIB_API unsigned ZDICT_isError(size_t errorCode); ZDICTLIB_API const char* ZDICT_getErrorName(size_t errorCode); +#endif /* ZSTD_ZDICT_H */ +#if defined(ZDICT_STATIC_LINKING_ONLY) && !defined(ZSTD_ZDICT_H_STATIC) +#define ZSTD_ZDICT_H_STATIC -#ifdef ZDICT_STATIC_LINKING_ONLY +/* This can be overridden externally to hide static symbols. */ +#ifndef ZDICTLIB_STATIC_API +# if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) +# define ZDICTLIB_STATIC_API __declspec(dllexport) ZDICTLIB_VISIBLE +# elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) +# define ZDICTLIB_STATIC_API __declspec(dllimport) ZDICTLIB_VISIBLE +# else +# define ZDICTLIB_STATIC_API ZDICTLIB_VISIBLE +# endif +#endif /* ==================================================================================== * The definitions in this section are considered experimental. @@ -272,8 +294,9 @@ ZDICTLIB_API const char* ZDICT_getErrorName(size_t errorCode); * Use them only in association with static linking. * ==================================================================================== */ -#define ZDICT_CONTENTSIZE_MIN 128 #define ZDICT_DICTSIZE_MIN 256 +/* Deprecated: Remove in v1.6.0 */ +#define ZDICT_CONTENTSIZE_MIN 128 /*! ZDICT_cover_params_t: * k and d are the only required parameters. @@ -318,7 +341,7 @@ typedef struct { * In general, it's recommended to provide a few thousands samples, though this can vary a lot. * It's recommended that total size of all samples be about ~x100 times the target size of dictionary. */ -ZDICTLIB_API size_t ZDICT_trainFromBuffer_cover( +ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_cover( void *dictBuffer, size_t dictBufferCapacity, const void *samplesBuffer, const size_t *samplesSizes, unsigned nbSamples, ZDICT_cover_params_t parameters); @@ -340,7 +363,7 @@ ZDICTLIB_API size_t ZDICT_trainFromBuffer_cover( * See ZDICT_trainFromBuffer() for details on failure modes. * Note: ZDICT_optimizeTrainFromBuffer_cover() requires about 8 bytes of memory for each input byte and additionally another 5 bytes of memory for each byte of memory for each thread. */ -ZDICTLIB_API size_t ZDICT_optimizeTrainFromBuffer_cover( +ZDICTLIB_STATIC_API size_t ZDICT_optimizeTrainFromBuffer_cover( void* dictBuffer, size_t dictBufferCapacity, const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, ZDICT_cover_params_t* parameters); @@ -361,7 +384,7 @@ ZDICTLIB_API size_t ZDICT_optimizeTrainFromBuffer_cover( * In general, it's recommended to provide a few thousands samples, though this can vary a lot. * It's recommended that total size of all samples be about ~x100 times the target size of dictionary. */ -ZDICTLIB_API size_t ZDICT_trainFromBuffer_fastCover(void *dictBuffer, +ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_fastCover(void *dictBuffer, size_t dictBufferCapacity, const void *samplesBuffer, const size_t *samplesSizes, unsigned nbSamples, ZDICT_fastCover_params_t parameters); @@ -384,7 +407,7 @@ ZDICTLIB_API size_t ZDICT_trainFromBuffer_fastCover(void *dictBuffer, * See ZDICT_trainFromBuffer() for details on failure modes. * Note: ZDICT_optimizeTrainFromBuffer_fastCover() requires about 6 * 2^f bytes of memory for each thread. */ -ZDICTLIB_API size_t ZDICT_optimizeTrainFromBuffer_fastCover(void* dictBuffer, +ZDICTLIB_STATIC_API size_t ZDICT_optimizeTrainFromBuffer_fastCover(void* dictBuffer, size_t dictBufferCapacity, const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, ZDICT_fastCover_params_t* parameters); @@ -409,7 +432,7 @@ typedef struct { * It's recommended that total size of all samples be about ~x100 times the target size of dictionary. * Note: ZDICT_trainFromBuffer_legacy() will send notifications into stderr if instructed to, using notificationLevel>0. */ -ZDICTLIB_API size_t ZDICT_trainFromBuffer_legacy( +ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_legacy( void* dictBuffer, size_t dictBufferCapacity, const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, ZDICT_legacy_params_t parameters); @@ -421,32 +444,31 @@ ZDICTLIB_API size_t ZDICT_trainFromBuffer_legacy( or _CRT_SECURE_NO_WARNINGS in Visual. Otherwise, it's also possible to manually define ZDICT_DISABLE_DEPRECATE_WARNINGS */ #ifdef ZDICT_DISABLE_DEPRECATE_WARNINGS -# define ZDICT_DEPRECATED(message) ZDICTLIB_API /* disable deprecation warnings */ +# define ZDICT_DEPRECATED(message) /* disable deprecation warnings */ #else # define ZDICT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) # if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ -# define ZDICT_DEPRECATED(message) [[deprecated(message)]] ZDICTLIB_API +# define ZDICT_DEPRECATED(message) [[deprecated(message)]] # elif defined(__clang__) || (ZDICT_GCC_VERSION >= 405) -# define ZDICT_DEPRECATED(message) ZDICTLIB_API __attribute__((deprecated(message))) +# define ZDICT_DEPRECATED(message) __attribute__((deprecated(message))) # elif (ZDICT_GCC_VERSION >= 301) -# define ZDICT_DEPRECATED(message) ZDICTLIB_API __attribute__((deprecated)) +# define ZDICT_DEPRECATED(message) __attribute__((deprecated)) # elif defined(_MSC_VER) -# define ZDICT_DEPRECATED(message) ZDICTLIB_API __declspec(deprecated(message)) +# define ZDICT_DEPRECATED(message) __declspec(deprecated(message)) # else # pragma message("WARNING: You need to implement ZDICT_DEPRECATED for this compiler") -# define ZDICT_DEPRECATED(message) ZDICTLIB_API +# define ZDICT_DEPRECATED(message) # endif #endif /* ZDICT_DISABLE_DEPRECATE_WARNINGS */ ZDICT_DEPRECATED("use ZDICT_finalizeDictionary() instead") +ZDICTLIB_STATIC_API size_t ZDICT_addEntropyTablesFromBuffer(void* dictBuffer, size_t dictContentSize, size_t dictBufferCapacity, const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples); -#endif /* ZDICT_STATIC_LINKING_ONLY */ +#endif /* ZSTD_ZDICT_H_STATIC */ #if defined (__cplusplus) } #endif - -#endif /* DICTBUILDER_H_001 */ diff --git a/Utilities/cmzstd/lib/zstd.h b/Utilities/cmzstd/lib/zstd.h index 4651e6c4dc7..e5c3f8b68b7 100644 --- a/Utilities/cmzstd/lib/zstd.h +++ b/Utilities/cmzstd/lib/zstd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -14,27 +14,61 @@ extern "C" { #ifndef ZSTD_H_235446 #define ZSTD_H_235446 -/* ====== Dependency ======*/ +/* ====== Dependencies ======*/ #include /* INT_MAX */ #include /* size_t */ /* ===== ZSTDLIB_API : control library symbols visibility ===== */ -#ifndef ZSTDLIB_VISIBILITY -# if defined(__GNUC__) && (__GNUC__ >= 4) -# define ZSTDLIB_VISIBILITY __attribute__ ((visibility ("default"))) +#ifndef ZSTDLIB_VISIBLE + /* Backwards compatibility with old macro name */ +# ifdef ZSTDLIB_VISIBILITY +# define ZSTDLIB_VISIBLE ZSTDLIB_VISIBILITY +# elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) +# define ZSTDLIB_VISIBLE __attribute__ ((visibility ("default"))) # else -# define ZSTDLIB_VISIBILITY +# define ZSTDLIB_VISIBLE # endif #endif + +#ifndef ZSTDLIB_HIDDEN +# if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) +# define ZSTDLIB_HIDDEN __attribute__ ((visibility ("hidden"))) +# else +# define ZSTDLIB_HIDDEN +# endif +#endif + #if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) -# define ZSTDLIB_API __declspec(dllexport) ZSTDLIB_VISIBILITY +# define ZSTDLIB_API __declspec(dllexport) ZSTDLIB_VISIBLE #elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) -# define ZSTDLIB_API __declspec(dllimport) ZSTDLIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +# define ZSTDLIB_API __declspec(dllimport) ZSTDLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ #else -# define ZSTDLIB_API ZSTDLIB_VISIBILITY +# define ZSTDLIB_API ZSTDLIB_VISIBLE #endif +/* Deprecation warnings : + * Should these warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc or _CRT_SECURE_NO_WARNINGS in Visual. + * Otherwise, it's also possible to define ZSTD_DISABLE_DEPRECATE_WARNINGS. + */ +#ifdef ZSTD_DISABLE_DEPRECATE_WARNINGS +# define ZSTD_DEPRECATED(message) /* disable deprecation warnings */ +#else +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define ZSTD_DEPRECATED(message) [[deprecated(message)]] +# elif (defined(GNUC) && (GNUC > 4 || (GNUC == 4 && GNUC_MINOR >= 5))) || defined(__clang__) +# define ZSTD_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ >= 3) +# define ZSTD_DEPRECATED(message) __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define ZSTD_DEPRECATED(message) __declspec(deprecated(message)) +# else +# pragma message("WARNING: You need to implement ZSTD_DEPRECATED for this compiler") +# define ZSTD_DEPRECATED(message) +# endif +#endif /* ZSTD_DISABLE_DEPRECATE_WARNINGS */ + /******************************************************************************* Introduction @@ -72,7 +106,7 @@ extern "C" { /*------ Version ------*/ #define ZSTD_VERSION_MAJOR 1 #define ZSTD_VERSION_MINOR 5 -#define ZSTD_VERSION_RELEASE 0 +#define ZSTD_VERSION_RELEASE 5 #define ZSTD_VERSION_NUMBER (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE) /*! ZSTD_versionNumber() : @@ -114,7 +148,8 @@ ZSTDLIB_API const char* ZSTD_versionString(void); ***************************************/ /*! ZSTD_compress() : * Compresses `src` content as a single zstd compressed frame into already allocated `dst`. - * Hint : compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`. + * NOTE: Providing `dstCapacity >= ZSTD_compressBound(srcSize)` guarantees that zstd will have + * enough space to successfully compress the data. * @return : compressed size written into `dst` (<= `dstCapacity), * or an error code if it fails (which can be tested using ZSTD_isError()). */ ZSTDLIB_API size_t ZSTD_compress( void* dst, size_t dstCapacity, @@ -163,7 +198,9 @@ ZSTDLIB_API unsigned long long ZSTD_getFrameContentSize(const void *src, size_t * "empty", "unknown" and "error" results to the same return value (0), * while ZSTD_getFrameContentSize() gives them separate return values. * @return : decompressed size of `src` frame content _if known and not empty_, 0 otherwise. */ -ZSTDLIB_API unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize); +ZSTD_DEPRECATED("Replaced by ZSTD_getFrameContentSize") +ZSTDLIB_API +unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize); /*! ZSTD_findFrameCompressedSize() : Requires v1.4.0+ * `src` should point to the start of a ZSTD frame or skippable frame. @@ -175,8 +212,30 @@ ZSTDLIB_API size_t ZSTD_findFrameCompressedSize(const void* src, size_t srcSize) /*====== Helper functions ======*/ -#define ZSTD_COMPRESSBOUND(srcSize) ((srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) /* margin, from 64 to 0 */ : 0)) /* this formula ensures that bound(A) + bound(B) <= bound(A+B) as long as A and B >= 128 KB */ -ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize); /*!< maximum compressed size in worst case single-pass scenario */ +/* ZSTD_compressBound() : + * maximum compressed size in worst case single-pass scenario. + * When invoking `ZSTD_compress()` or any other one-pass compression function, + * it's recommended to provide @dstCapacity >= ZSTD_compressBound(srcSize) + * as it eliminates one potential failure scenario, + * aka not enough room in dst buffer to write the compressed frame. + * Note : ZSTD_compressBound() itself can fail, if @srcSize > ZSTD_MAX_INPUT_SIZE . + * In which case, ZSTD_compressBound() will return an error code + * which can be tested using ZSTD_isError(). + * + * ZSTD_COMPRESSBOUND() : + * same as ZSTD_compressBound(), but as a macro. + * It can be used to produce constants, which can be useful for static allocation, + * for example to size a static array on stack. + * Will produce constant value 0 if srcSize too large. + */ +#define ZSTD_MAX_INPUT_SIZE ((sizeof(size_t)==8) ? 0xFF00FF00FF00FF00LLU : 0xFF00FF00U) +#define ZSTD_COMPRESSBOUND(srcSize) (((size_t)(srcSize) >= ZSTD_MAX_INPUT_SIZE) ? 0 : (srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) /* margin, from 64 to 0 */ : 0)) /* this formula ensures that bound(A) + bound(B) <= bound(A+B) as long as A and B >= 128 KB */ +ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize); /*!< maximum compressed size in worst case single-pass scenario */ +/* ZSTD_isError() : + * Most ZSTD_* functions returning a size_t value can be tested for error, + * using ZSTD_isError(). + * @return 1 if error, 0 otherwise + */ ZSTDLIB_API unsigned ZSTD_isError(size_t code); /*!< tells if a `size_t` function result is an error code */ ZSTDLIB_API const char* ZSTD_getErrorName(size_t code); /*!< provides readable string from an error code */ ZSTDLIB_API int ZSTD_minCLevel(void); /*!< minimum negative compression level allowed, requires v1.4.0+ */ @@ -247,7 +306,7 @@ ZSTDLIB_API size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, * * It's possible to reset all parameters to "default" using ZSTD_CCtx_reset(). * - * This API supercedes all other "advanced" API entry points in the experimental section. + * This API supersedes all other "advanced" API entry points in the experimental section. * In the future, we expect to remove from experimental API entry points which are redundant with this API. */ @@ -417,8 +476,11 @@ typedef enum { * ZSTD_c_stableOutBuffer * ZSTD_c_blockDelimiters * ZSTD_c_validateSequences - * ZSTD_c_splitBlocks + * ZSTD_c_useBlockSplitter * ZSTD_c_useRowMatchFinder + * ZSTD_c_prefetchCDictTables + * ZSTD_c_enableSeqProducerFallback + * ZSTD_c_maxBlockSize * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them. * note : never ever use experimentalParam? names directly; * also, the enums values themselves are unstable and can still change. @@ -437,7 +499,11 @@ typedef enum { ZSTD_c_experimentalParam12=1009, ZSTD_c_experimentalParam13=1010, ZSTD_c_experimentalParam14=1011, - ZSTD_c_experimentalParam15=1012 + ZSTD_c_experimentalParam15=1012, + ZSTD_c_experimentalParam16=1013, + ZSTD_c_experimentalParam17=1014, + ZSTD_c_experimentalParam18=1015, + ZSTD_c_experimentalParam19=1016 } ZSTD_cParameter; typedef struct { @@ -500,7 +566,7 @@ typedef enum { * They will be used to compress next frame. * Resetting session never fails. * - The parameters : changes all parameters back to "default". - * This removes any reference to any dictionary too. + * This also removes any reference to any dictionary or external sequence producer. * Parameters can only be changed between 2 sessions (i.e. no compression is currently ongoing) * otherwise the reset fails, and function returns an error value (which can be tested using ZSTD_isError()) * - Both : similar to resetting the session, followed by resetting parameters. @@ -513,7 +579,8 @@ ZSTDLIB_API size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset); * Should cctx hold data from a previously unfinished frame, everything about it is forgotten. * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*() * - The function is always blocking, returns when compression is completed. - * Hint : compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`. + * NOTE: Providing `dstCapacity >= ZSTD_compressBound(srcSize)` guarantees that zstd will have + * enough space to successfully compress the data, though it is possible it fails for other reasons. * @return : compressed size written into `dst` (<= `dstCapacity), * or an error code if it fails (which can be tested using ZSTD_isError()). */ @@ -550,13 +617,15 @@ typedef enum { * ZSTD_d_stableOutBuffer * ZSTD_d_forceIgnoreChecksum * ZSTD_d_refMultipleDDicts + * ZSTD_d_disableHuffmanAssembly * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them. * note : never ever use experimentalParam? names directly */ ZSTD_d_experimentalParam1=1000, ZSTD_d_experimentalParam2=1001, ZSTD_d_experimentalParam3=1002, - ZSTD_d_experimentalParam4=1003 + ZSTD_d_experimentalParam4=1003, + ZSTD_d_experimentalParam5=1004 } ZSTD_dParameter; @@ -735,8 +804,6 @@ ZSTDLIB_API size_t ZSTD_CStreamOutSize(void); /**< recommended size for output * This following is a legacy streaming API, available since v1.0+ . * It can be replaced by ZSTD_CCtx_reset() and ZSTD_compressStream2(). * It is redundant, but remains fully supported. - * Streaming in combination with advanced parameters and dictionary compression - * can only be used through the new API. ******************************************************************************/ /*! @@ -745,6 +812,9 @@ ZSTDLIB_API size_t ZSTD_CStreamOutSize(void); /**< recommended size for output * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); * ZSTD_CCtx_refCDict(zcs, NULL); // clear the dictionary (if any) * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel); + * + * Note that ZSTD_initCStream() clears any previously set dictionary. Use the new API + * to compress with a dictionary. */ ZSTDLIB_API size_t ZSTD_initCStream(ZSTD_CStream* zcs, int compressionLevel); /*! @@ -795,13 +865,31 @@ ZSTDLIB_API size_t ZSTD_freeDStream(ZSTD_DStream* zds); /* accept NULL pointer /*===== Streaming decompression functions =====*/ -/* This function is redundant with the advanced API and equivalent to: +/*! ZSTD_initDStream() : + * Initialize/reset DStream state for new decompression operation. + * Call before new decompression operation using same DStream. * + * Note : This function is redundant with the advanced API and equivalent to: * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); * ZSTD_DCtx_refDDict(zds, NULL); */ ZSTDLIB_API size_t ZSTD_initDStream(ZSTD_DStream* zds); +/*! ZSTD_decompressStream() : + * Streaming decompression function. + * Call repetitively to consume full input updating it as necessary. + * Function will update both input and output `pos` fields exposing current state via these fields: + * - `input.pos < input.size`, some input remaining and caller should provide remaining input + * on the next call. + * - `output.pos < output.size`, decoder finished and flushed all remaining buffers. + * - `output.pos == output.size`, potentially uncflushed data present in the internal buffers, + * call ZSTD_decompressStream() again to flush remaining data to output. + * Note : with no additional input, amount of data flushed <= ZSTD_BLOCKSIZE_MAX. + * + * @return : 0 when a frame is completely decoded and fully flushed, + * or an error code, which can be tested using ZSTD_isError(), + * or any other value > 0, which means there is some decoding or flushing to do to complete current frame. + */ ZSTDLIB_API size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input); ZSTDLIB_API size_t ZSTD_DStreamInSize(void); /*!< recommended size for input buffer */ @@ -920,7 +1008,7 @@ ZSTDLIB_API unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ddict); * If @return == 0, the dictID could not be decoded. * This could for one of the following reasons : * - The frame does not require a dictionary to be decoded (most common case). - * - The frame was built with dictID intentionally removed. Whatever dictionary is necessary is a hidden information. + * - The frame was built with dictID intentionally removed. Whatever dictionary is necessary is a hidden piece of information. * Note : this use case also happens when using a non-conformant dictionary. * - `srcSize` is too small, and as a result, the frame header could not be decoded (only possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`). * - This is not a Zstandard frame. @@ -932,9 +1020,11 @@ ZSTDLIB_API unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize); * Advanced dictionary and prefix API (Requires v1.4.0+) * * This API allows dictionaries to be used with ZSTD_compress2(), - * ZSTD_compressStream2(), and ZSTD_decompress(). Dictionaries are sticky, and - * only reset with the context is reset with ZSTD_reset_parameters or - * ZSTD_reset_session_and_parameters. Prefixes are single-use. + * ZSTD_compressStream2(), and ZSTD_decompressDCtx(). + * Dictionaries are sticky, they remain valid when same context is re-used, + * they only reset when the context is reset + * with ZSTD_reset_parameters or ZSTD_reset_session_and_parameters. + * In contrast, Prefixes are single-use. ******************************************************************************/ @@ -944,8 +1034,9 @@ ZSTDLIB_API unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize); * @result : 0, or an error code (which can be tested with ZSTD_isError()). * Special: Loading a NULL (or 0-size) dictionary invalidates previous dictionary, * meaning "return to no-dictionary mode". - * Note 1 : Dictionary is sticky, it will be used for all future compressed frames. - * To return to "no-dictionary" situation, load a NULL dictionary (or reset parameters). + * Note 1 : Dictionary is sticky, it will be used for all future compressed frames, + * until parameters are reset, a new dictionary is loaded, or the dictionary + * is explicitly invalidated by loading a NULL dictionary. * Note 2 : Loading a dictionary involves building tables. * It's also a CPU consuming operation, with non-negligible impact on latency. * Tables are dependent on compression parameters, and for this reason, @@ -954,11 +1045,15 @@ ZSTDLIB_API unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize); * Use experimental ZSTD_CCtx_loadDictionary_byReference() to reference content instead. * In such a case, dictionary buffer must outlive its users. * Note 4 : Use ZSTD_CCtx_loadDictionary_advanced() - * to precisely select how dictionary content must be interpreted. */ + * to precisely select how dictionary content must be interpreted. + * Note 5 : This method does not benefit from LDM (long distance mode). + * If you want to employ LDM on some large dictionary content, + * prefer employing ZSTD_CCtx_refPrefix() described below. + */ ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); /*! ZSTD_CCtx_refCDict() : Requires v1.4.0+ - * Reference a prepared dictionary, to be used for all next compressed frames. + * Reference a prepared dictionary, to be used for all future compressed frames. * Note that compression parameters are enforced from within CDict, * and supersede any compression parameter previously set within CCtx. * The parameters ignored are labelled as "superseded-by-cdict" in the ZSTD_cParameter enum docs. @@ -977,6 +1072,7 @@ ZSTDLIB_API size_t ZSTD_CCtx_refCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); * Decompression will need same prefix to properly regenerate data. * Compressing with a prefix is similar in outcome as performing a diff and compressing it, * but performs much faster, especially during decompression (compression speed is tunable with compression level). + * This method is compatible with LDM (long distance mode). * @result : 0, or an error code (which can be tested with ZSTD_isError()). * Special: Adding any prefix (including NULL) invalidates any previous prefix or dictionary * Note 1 : Prefix buffer is referenced. It **must** outlive compression. @@ -993,9 +1089,9 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix(ZSTD_CCtx* cctx, const void* prefix, size_t prefixSize); /*! ZSTD_DCtx_loadDictionary() : Requires v1.4.0+ - * Create an internal DDict from dict buffer, - * to be used to decompress next frames. - * The dictionary remains valid for all future frames, until explicitly invalidated. + * Create an internal DDict from dict buffer, to be used to decompress all future frames. + * The dictionary remains valid for all future frames, until explicitly invalidated, or + * a new dictionary is loaded. * @result : 0, or an error code (which can be tested with ZSTD_isError()). * Special : Adding a NULL (or 0-size) dictionary invalidates any previous dictionary, * meaning "return to no-dictionary mode". @@ -1019,9 +1115,10 @@ ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, const void* dict, s * The memory for the table is allocated on the first call to refDDict, and can be * freed with ZSTD_freeDCtx(). * + * If called with ZSTD_d_refMultipleDDicts disabled (the default), only one dictionary + * will be managed, and referencing a dictionary effectively "discards" any previous one. + * * @result : 0, or an error code (which can be tested with ZSTD_isError()). - * Note 1 : Currently, only one dictionary can be managed. - * Referencing a new dictionary effectively "discards" any previous one. * Special: referencing a NULL DDict means "return to no-dictionary mode". * Note 2 : DDict is just referenced, its lifetime must outlive its usage from DCtx. */ @@ -1073,27 +1170,16 @@ ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); #if defined(ZSTD_STATIC_LINKING_ONLY) && !defined(ZSTD_H_ZSTD_STATIC_LINKING_ONLY) #define ZSTD_H_ZSTD_STATIC_LINKING_ONLY -/* Deprecation warnings : - * Should these warnings be a problem, it is generally possible to disable them, - * typically with -Wno-deprecated-declarations for gcc or _CRT_SECURE_NO_WARNINGS in Visual. - * Otherwise, it's also possible to define ZSTD_DISABLE_DEPRECATE_WARNINGS. - */ -#ifdef ZSTD_DISABLE_DEPRECATE_WARNINGS -# define ZSTD_DEPRECATED(message) ZSTDLIB_API /* disable deprecation warnings */ -#else -# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ -# define ZSTD_DEPRECATED(message) [[deprecated(message)]] ZSTDLIB_API -# elif (defined(GNUC) && (GNUC > 4 || (GNUC == 4 && GNUC_MINOR >= 5))) || defined(__clang__) -# define ZSTD_DEPRECATED(message) ZSTDLIB_API __attribute__((deprecated(message))) -# elif defined(__GNUC__) && (__GNUC__ >= 3) -# define ZSTD_DEPRECATED(message) ZSTDLIB_API __attribute__((deprecated)) -# elif defined(_MSC_VER) -# define ZSTD_DEPRECATED(message) ZSTDLIB_API __declspec(deprecated(message)) +/* This can be overridden externally to hide static symbols. */ +#ifndef ZSTDLIB_STATIC_API +# if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) +# define ZSTDLIB_STATIC_API __declspec(dllexport) ZSTDLIB_VISIBLE +# elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) +# define ZSTDLIB_STATIC_API __declspec(dllimport) ZSTDLIB_VISIBLE # else -# pragma message("WARNING: You need to implement ZSTD_DEPRECATED for this compiler") -# define ZSTD_DEPRECATED(message) ZSTDLIB_API +# define ZSTDLIB_STATIC_API ZSTDLIB_VISIBLE # endif -#endif /* ZSTD_DISABLE_DEPRECATE_WARNINGS */ +#endif /**************************************************************************************** * experimental API (static linking only) @@ -1129,6 +1215,7 @@ ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); #define ZSTD_TARGETLENGTH_MIN 0 /* note : comparing this constant to an unsigned results in a tautological test */ #define ZSTD_STRATEGY_MIN ZSTD_fast #define ZSTD_STRATEGY_MAX ZSTD_btultra2 +#define ZSTD_BLOCKSIZE_MAX_MIN (1 << 10) /* The minimum valid max blocksize. Maximum blocksizes smaller than this make compressBound() inaccurate. */ #define ZSTD_OVERLAPLOG_MIN 0 @@ -1157,9 +1244,6 @@ ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); #define ZSTD_SRCSIZEHINT_MIN 0 #define ZSTD_SRCSIZEHINT_MAX INT_MAX -/* internal */ -#define ZSTD_HASHLOG3_MAX 17 - /* --- Advanced types --- */ @@ -1302,13 +1386,17 @@ typedef enum { } ZSTD_literalCompressionMode_e; typedef enum { - ZSTD_urm_auto = 0, /* Automatically determine whether or not we use row matchfinder */ - ZSTD_urm_disableRowMatchFinder = 1, /* Never use row matchfinder */ - ZSTD_urm_enableRowMatchFinder = 2 /* Always use row matchfinder when applicable */ -} ZSTD_useRowMatchFinderMode_e; + /* Note: This enum controls features which are conditionally beneficial. Zstd typically will make a final + * decision on whether or not to enable the feature (ZSTD_ps_auto), but setting the switch to ZSTD_ps_enable + * or ZSTD_ps_disable allow for a force enable/disable the feature. + */ + ZSTD_ps_auto = 0, /* Let the library automatically determine whether the feature shall be enabled */ + ZSTD_ps_enable = 1, /* Force-enable the feature */ + ZSTD_ps_disable = 2 /* Do not use the feature */ +} ZSTD_paramSwitch_e; /*************************************** -* Frame size functions +* Frame header and size functions ***************************************/ /*! ZSTD_findDecompressedSize() : @@ -1332,7 +1420,7 @@ typedef enum { * note 5 : ZSTD_findDecompressedSize handles multiple frames, and so it must traverse the input to * read each contained frame header. This is fast as most of the data is skipped, * however it does mean that all frame data must be present and valid. */ -ZSTDLIB_API unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize); +ZSTDLIB_STATIC_API unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize); /*! ZSTD_decompressBound() : * `src` should point to the start of a series of ZSTD encoded and/or skippable frames @@ -1347,41 +1435,121 @@ ZSTDLIB_API unsigned long long ZSTD_findDecompressedSize(const void* src, size_t * note 3 : when the decompressed size field isn't available, the upper-bound for that frame is calculated by: * upper-bound = # blocks * min(128 KB, Window_Size) */ -ZSTDLIB_API unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize); +ZSTDLIB_STATIC_API unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize); /*! ZSTD_frameHeaderSize() : * srcSize must be >= ZSTD_FRAMEHEADERSIZE_PREFIX. * @return : size of the Frame Header, * or an error code (if srcSize is too small) */ -ZSTDLIB_API size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize); +ZSTDLIB_STATIC_API size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize); + +typedef enum { ZSTD_frame, ZSTD_skippableFrame } ZSTD_frameType_e; +typedef struct { + unsigned long long frameContentSize; /* if == ZSTD_CONTENTSIZE_UNKNOWN, it means this field is not available. 0 means "empty" */ + unsigned long long windowSize; /* can be very large, up to <= frameContentSize */ + unsigned blockSizeMax; + ZSTD_frameType_e frameType; /* if == ZSTD_skippableFrame, frameContentSize is the size of skippable content */ + unsigned headerSize; + unsigned dictID; + unsigned checksumFlag; + unsigned _reserved1; + unsigned _reserved2; +} ZSTD_frameHeader; + +/*! ZSTD_getFrameHeader() : + * decode Frame Header, or requires larger `srcSize`. + * @return : 0, `zfhPtr` is correctly filled, + * >0, `srcSize` is too small, value is wanted `srcSize` amount, + * or an error code, which can be tested using ZSTD_isError() */ +ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize); /**< doesn't consume input */ +/*! ZSTD_getFrameHeader_advanced() : + * same as ZSTD_getFrameHeader(), + * with added capability to select a format (like ZSTD_f_zstd1_magicless) */ +ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format); + +/*! ZSTD_decompressionMargin() : + * Zstd supports in-place decompression, where the input and output buffers overlap. + * In this case, the output buffer must be at least (Margin + Output_Size) bytes large, + * and the input buffer must be at the end of the output buffer. + * + * _______________________ Output Buffer ________________________ + * | | + * | ____ Input Buffer ____| + * | | | + * v v v + * |---------------------------------------|-----------|----------| + * ^ ^ ^ + * |___________________ Output_Size ___________________|_ Margin _| + * + * NOTE: See also ZSTD_DECOMPRESSION_MARGIN(). + * NOTE: This applies only to single-pass decompression through ZSTD_decompress() or + * ZSTD_decompressDCtx(). + * NOTE: This function supports multi-frame input. + * + * @param src The compressed frame(s) + * @param srcSize The size of the compressed frame(s) + * @returns The decompression margin or an error that can be checked with ZSTD_isError(). + */ +ZSTDLIB_STATIC_API size_t ZSTD_decompressionMargin(const void* src, size_t srcSize); + +/*! ZSTD_DECOMPRESS_MARGIN() : + * Similar to ZSTD_decompressionMargin(), but instead of computing the margin from + * the compressed frame, compute it from the original size and the blockSizeLog. + * See ZSTD_decompressionMargin() for details. + * + * WARNING: This macro does not support multi-frame input, the input must be a single + * zstd frame. If you need that support use the function, or implement it yourself. + * + * @param originalSize The original uncompressed size of the data. + * @param blockSize The block size == MIN(windowSize, ZSTD_BLOCKSIZE_MAX). + * Unless you explicitly set the windowLog smaller than + * ZSTD_BLOCKSIZELOG_MAX you can just use ZSTD_BLOCKSIZE_MAX. + */ +#define ZSTD_DECOMPRESSION_MARGIN(originalSize, blockSize) ((size_t)( \ + ZSTD_FRAMEHEADERSIZE_MAX /* Frame header */ + \ + 4 /* checksum */ + \ + ((originalSize) == 0 ? 0 : 3 * (((originalSize) + (blockSize) - 1) / blockSize)) /* 3 bytes per block */ + \ + (blockSize) /* One block of margin */ \ + )) typedef enum { ZSTD_sf_noBlockDelimiters = 0, /* Representation of ZSTD_Sequence has no block delimiters, sequences only */ ZSTD_sf_explicitBlockDelimiters = 1 /* Representation of ZSTD_Sequence contains explicit block delimiters */ } ZSTD_sequenceFormat_e; +/*! ZSTD_sequenceBound() : + * `srcSize` : size of the input buffer + * @return : upper-bound for the number of sequences that can be generated + * from a buffer of srcSize bytes + * + * note : returns number of sequences - to get bytes, multiply by sizeof(ZSTD_Sequence). + */ +ZSTDLIB_STATIC_API size_t ZSTD_sequenceBound(size_t srcSize); + /*! ZSTD_generateSequences() : - * Generate sequences using ZSTD_compress2, given a source buffer. + * Generate sequences using ZSTD_compress2(), given a source buffer. * * Each block will end with a dummy sequence * with offset == 0, matchLength == 0, and litLength == length of last literals. * litLength may be == 0, and if so, then the sequence of (of: 0 ml: 0 ll: 0) * simply acts as a block delimiter. * - * zc can be used to insert custom compression params. - * This function invokes ZSTD_compress2 + * @zc can be used to insert custom compression params. + * This function invokes ZSTD_compress2(). * * The output of this function can be fed into ZSTD_compressSequences() with CCtx * setting of ZSTD_c_blockDelimiters as ZSTD_sf_explicitBlockDelimiters * @return : number of sequences generated */ -ZSTDLIB_API size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, - size_t outSeqsSize, const void* src, size_t srcSize); +ZSTDLIB_STATIC_API size_t +ZSTD_generateSequences( ZSTD_CCtx* zc, + ZSTD_Sequence* outSeqs, size_t outSeqsSize, + const void* src, size_t srcSize); /*! ZSTD_mergeBlockDelimiters() : * Given an array of ZSTD_Sequence, remove all sequences that represent block delimiters/last literals - * by merging them into into the literals of the next sequence. + * by merging them into the literals of the next sequence. * * As such, the final generated result has no explicit representation of block boundaries, * and the final last literals segment is not represented in the sequences. @@ -1390,10 +1558,12 @@ ZSTDLIB_API size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, * setting of ZSTD_c_blockDelimiters as ZSTD_sf_noBlockDelimiters * @return : number of sequences left after merging */ -ZSTDLIB_API size_t ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, size_t seqsSize); +ZSTDLIB_STATIC_API size_t ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, size_t seqsSize); /*! ZSTD_compressSequences() : - * Compress an array of ZSTD_Sequence, generated from the original source buffer, into dst. + * Compress an array of ZSTD_Sequence, associated with @src buffer, into dst. + * @src contains the entire input (not just the literals). + * If @srcSize > sum(sequence.length), the remaining bytes are considered all literals * If a dictionary is included, then the cctx should reference the dict. (see: ZSTD_CCtx_refCDict(), ZSTD_CCtx_loadDictionary(), etc.) * The entire source is compressed into a single frame. * @@ -1418,17 +1588,18 @@ ZSTDLIB_API size_t ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, size_t se * Note: Repcodes are, as of now, always re-calculated within this function, so ZSTD_Sequence::rep is unused. * Note 2: Once we integrate ability to ingest repcodes, the explicit block delims mode must respect those repcodes exactly, * and cannot emit an RLE block that disagrees with the repcode history - * @return : final compressed size or a ZSTD error. + * @return : final compressed size, or a ZSTD error code. */ -ZSTDLIB_API size_t ZSTD_compressSequences(ZSTD_CCtx* const cctx, void* dst, size_t dstSize, - const ZSTD_Sequence* inSeqs, size_t inSeqsSize, - const void* src, size_t srcSize); +ZSTDLIB_STATIC_API size_t +ZSTD_compressSequences( ZSTD_CCtx* cctx, void* dst, size_t dstSize, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t srcSize); /*! ZSTD_writeSkippableFrame() : * Generates a zstd skippable frame containing data given by src, and writes it to dst buffer. * - * Skippable frames begin with a a 4-byte magic number. There are 16 possible choices of magic number, + * Skippable frames begin with a 4-byte magic number. There are 16 possible choices of magic number, * ranging from ZSTD_MAGIC_SKIPPABLE_START to ZSTD_MAGIC_SKIPPABLE_START+15. * As such, the parameter magicVariant controls the exact skippable frame magic number variant used, so * the magic number used will be ZSTD_MAGIC_SKIPPABLE_START + magicVariant. @@ -1438,9 +1609,29 @@ ZSTDLIB_API size_t ZSTD_compressSequences(ZSTD_CCtx* const cctx, void* dst, size * * @return : number of bytes written or a ZSTD error. */ -ZSTDLIB_API size_t ZSTD_writeSkippableFrame(void* dst, size_t dstCapacity, +ZSTDLIB_STATIC_API size_t ZSTD_writeSkippableFrame(void* dst, size_t dstCapacity, const void* src, size_t srcSize, unsigned magicVariant); +/*! ZSTD_readSkippableFrame() : + * Retrieves a zstd skippable frame containing data given by src, and writes it to dst buffer. + * + * The parameter magicVariant will receive the magicVariant that was supplied when the frame was written, + * i.e. magicNumber - ZSTD_MAGIC_SKIPPABLE_START. This can be NULL if the caller is not interested + * in the magicVariant. + * + * Returns an error if destination buffer is not large enough, or if the frame is not skippable. + * + * @return : number of bytes written or a ZSTD error. + */ +ZSTDLIB_API size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity, unsigned* magicVariant, + const void* src, size_t srcSize); + +/*! ZSTD_isSkippableFrame() : + * Tells if the content of `buffer` starts with a valid Frame Identifier for a skippable frame. + */ +ZSTDLIB_API unsigned ZSTD_isSkippableFrame(const void* buffer, size_t size); + + /*************************************** * Memory management @@ -1466,13 +1657,16 @@ ZSTDLIB_API size_t ZSTD_writeSkippableFrame(void* dst, size_t dstCapacity, * and ZSTD_estimateCCtxSize_usingCCtxParams(), which can be used in tandem with ZSTD_CCtxParams_setParameter(). * Both can be used to estimate memory using custom compression parameters and arbitrary srcSize limits. * - * Note 2 : only single-threaded compression is supported. + * Note : only single-threaded compression is supported. * ZSTD_estimateCCtxSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. + * + * Note 2 : ZSTD_estimateCCtxSize* functions are not compatible with the Block-Level Sequence Producer API at this time. + * Size estimates assume that no external sequence producer is registered. */ -ZSTDLIB_API size_t ZSTD_estimateCCtxSize(int compressionLevel); -ZSTDLIB_API size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams); -ZSTDLIB_API size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params); -ZSTDLIB_API size_t ZSTD_estimateDCtxSize(void); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize(int compressionLevel); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params); +ZSTDLIB_STATIC_API size_t ZSTD_estimateDCtxSize(void); /*! ZSTD_estimateCStreamSize() : * ZSTD_estimateCStreamSize() will provide a budget large enough for any compression level up to selected one. @@ -1486,21 +1680,26 @@ ZSTDLIB_API size_t ZSTD_estimateDCtxSize(void); * or deducted from a valid frame Header, using ZSTD_estimateDStreamSize_fromFrame(); * Note : if streaming is init with function ZSTD_init?Stream_usingDict(), * an internal ?Dict will be created, which additional size is not estimated here. - * In this case, get total size by adding ZSTD_estimate?DictSize */ -ZSTDLIB_API size_t ZSTD_estimateCStreamSize(int compressionLevel); -ZSTDLIB_API size_t ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams); -ZSTDLIB_API size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params); -ZSTDLIB_API size_t ZSTD_estimateDStreamSize(size_t windowSize); -ZSTDLIB_API size_t ZSTD_estimateDStreamSize_fromFrame(const void* src, size_t srcSize); + * In this case, get total size by adding ZSTD_estimate?DictSize + * Note 2 : only single-threaded compression is supported. + * ZSTD_estimateCStreamSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. + * Note 3 : ZSTD_estimateCStreamSize* functions are not compatible with the Block-Level Sequence Producer API at this time. + * Size estimates assume that no external sequence producer is registered. + */ +ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize(int compressionLevel); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params); +ZSTDLIB_STATIC_API size_t ZSTD_estimateDStreamSize(size_t windowSize); +ZSTDLIB_STATIC_API size_t ZSTD_estimateDStreamSize_fromFrame(const void* src, size_t srcSize); /*! ZSTD_estimate?DictSize() : * ZSTD_estimateCDictSize() will bet that src size is relatively "small", and content is copied, like ZSTD_createCDict(). * ZSTD_estimateCDictSize_advanced() makes it possible to control compression parameters precisely, like ZSTD_createCDict_advanced(). * Note : dictionaries created by reference (`ZSTD_dlm_byRef`) are logically smaller. */ -ZSTDLIB_API size_t ZSTD_estimateCDictSize(size_t dictSize, int compressionLevel); -ZSTDLIB_API size_t ZSTD_estimateCDictSize_advanced(size_t dictSize, ZSTD_compressionParameters cParams, ZSTD_dictLoadMethod_e dictLoadMethod); -ZSTDLIB_API size_t ZSTD_estimateDDictSize(size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCDictSize(size_t dictSize, int compressionLevel); +ZSTDLIB_STATIC_API size_t ZSTD_estimateCDictSize_advanced(size_t dictSize, ZSTD_compressionParameters cParams, ZSTD_dictLoadMethod_e dictLoadMethod); +ZSTDLIB_STATIC_API size_t ZSTD_estimateDDictSize(size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod); /*! ZSTD_initStatic*() : * Initialize an object using a pre-allocated fixed-size buffer. @@ -1523,20 +1722,20 @@ ZSTDLIB_API size_t ZSTD_estimateDDictSize(size_t dictSize, ZSTD_dictLoadMethod_e * Limitation 2 : static cctx currently not compatible with multi-threading. * Limitation 3 : static dctx is incompatible with legacy support. */ -ZSTDLIB_API ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize); -ZSTDLIB_API ZSTD_CStream* ZSTD_initStaticCStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticCCtx() */ +ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize); +ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_initStaticCStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticCCtx() */ -ZSTDLIB_API ZSTD_DCtx* ZSTD_initStaticDCtx(void* workspace, size_t workspaceSize); -ZSTDLIB_API ZSTD_DStream* ZSTD_initStaticDStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticDCtx() */ +ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_initStaticDCtx(void* workspace, size_t workspaceSize); +ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_initStaticDStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticDCtx() */ -ZSTDLIB_API const ZSTD_CDict* ZSTD_initStaticCDict( +ZSTDLIB_STATIC_API const ZSTD_CDict* ZSTD_initStaticCDict( void* workspace, size_t workspaceSize, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, ZSTD_compressionParameters cParams); -ZSTDLIB_API const ZSTD_DDict* ZSTD_initStaticDDict( +ZSTDLIB_STATIC_API const ZSTD_DDict* ZSTD_initStaticDDict( void* workspace, size_t workspaceSize, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, @@ -1557,44 +1756,44 @@ __attribute__((__unused__)) #endif ZSTD_customMem const ZSTD_defaultCMem = { NULL, NULL, NULL }; /**< this constant defers to stdlib's functions */ -ZSTDLIB_API ZSTD_CCtx* ZSTD_createCCtx_advanced(ZSTD_customMem customMem); -ZSTDLIB_API ZSTD_CStream* ZSTD_createCStream_advanced(ZSTD_customMem customMem); -ZSTDLIB_API ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem); -ZSTDLIB_API ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_createCCtx_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_createCStream_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem); -ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict_advanced(const void* dict, size_t dictSize, +ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_advanced(const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, ZSTD_compressionParameters cParams, ZSTD_customMem customMem); -/* ! Thread pool : - * These prototypes make it possible to share a thread pool among multiple compression contexts. - * This can limit resources for applications with multiple threads where each one uses - * a threaded compression mode (via ZSTD_c_nbWorkers parameter). - * ZSTD_createThreadPool creates a new thread pool with a given number of threads. - * Note that the lifetime of such pool must exist while being used. - * ZSTD_CCtx_refThreadPool assigns a thread pool to a context (use NULL argument value - * to use an internal thread pool). - * ZSTD_freeThreadPool frees a thread pool, accepts NULL pointer. +/*! Thread pool : + * These prototypes make it possible to share a thread pool among multiple compression contexts. + * This can limit resources for applications with multiple threads where each one uses + * a threaded compression mode (via ZSTD_c_nbWorkers parameter). + * ZSTD_createThreadPool creates a new thread pool with a given number of threads. + * Note that the lifetime of such pool must exist while being used. + * ZSTD_CCtx_refThreadPool assigns a thread pool to a context (use NULL argument value + * to use an internal thread pool). + * ZSTD_freeThreadPool frees a thread pool, accepts NULL pointer. */ typedef struct POOL_ctx_s ZSTD_threadPool; -ZSTDLIB_API ZSTD_threadPool* ZSTD_createThreadPool(size_t numThreads); -ZSTDLIB_API void ZSTD_freeThreadPool (ZSTD_threadPool* pool); /* accept NULL pointer */ -ZSTDLIB_API size_t ZSTD_CCtx_refThreadPool(ZSTD_CCtx* cctx, ZSTD_threadPool* pool); +ZSTDLIB_STATIC_API ZSTD_threadPool* ZSTD_createThreadPool(size_t numThreads); +ZSTDLIB_STATIC_API void ZSTD_freeThreadPool (ZSTD_threadPool* pool); /* accept NULL pointer */ +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refThreadPool(ZSTD_CCtx* cctx, ZSTD_threadPool* pool); /* * This API is temporary and is expected to change or disappear in the future! */ -ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict_advanced2( +ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_advanced2( const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, const ZSTD_CCtx_params* cctxParams, ZSTD_customMem customMem); -ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict_advanced( +ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_createDDict_advanced( const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, @@ -1611,22 +1810,22 @@ ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict_advanced( * As a consequence, `dictBuffer` **must** outlive CDict, * and its content must remain unmodified throughout the lifetime of CDict. * note: equivalent to ZSTD_createCDict_advanced(), with dictLoadMethod==ZSTD_dlm_byRef */ -ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict_byReference(const void* dictBuffer, size_t dictSize, int compressionLevel); +ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_byReference(const void* dictBuffer, size_t dictSize, int compressionLevel); /*! ZSTD_getCParams() : * @return ZSTD_compressionParameters structure for a selected compression level and estimated srcSize. * `estimatedSrcSize` value is optional, select 0 if not known */ -ZSTDLIB_API ZSTD_compressionParameters ZSTD_getCParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize); +ZSTDLIB_STATIC_API ZSTD_compressionParameters ZSTD_getCParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize); /*! ZSTD_getParams() : * same as ZSTD_getCParams(), but @return a full `ZSTD_parameters` object instead of sub-component `ZSTD_compressionParameters`. * All fields of `ZSTD_frameParameters` are set to default : contentSize=1, checksum=0, noDictID=0 */ -ZSTDLIB_API ZSTD_parameters ZSTD_getParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize); +ZSTDLIB_STATIC_API ZSTD_parameters ZSTD_getParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize); /*! ZSTD_checkCParams() : * Ensure param values remain within authorized range. * @return 0 on success, or an error code (can be checked with ZSTD_isError()) */ -ZSTDLIB_API size_t ZSTD_checkCParams(ZSTD_compressionParameters params); +ZSTDLIB_STATIC_API size_t ZSTD_checkCParams(ZSTD_compressionParameters params); /*! ZSTD_adjustCParams() : * optimize params for a given `srcSize` and `dictSize`. @@ -1634,24 +1833,47 @@ ZSTDLIB_API size_t ZSTD_checkCParams(ZSTD_compressionParameters params); * `dictSize` must be `0` when there is no dictionary. * cPar can be invalid : all parameters will be clamped within valid range in the @return struct. * This function never fails (wide contract) */ -ZSTDLIB_API ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, unsigned long long srcSize, size_t dictSize); +ZSTDLIB_STATIC_API ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, unsigned long long srcSize, size_t dictSize); + +/*! ZSTD_CCtx_setCParams() : + * Set all parameters provided within @p cparams into the working @p cctx. + * Note : if modifying parameters during compression (MT mode only), + * note that changes to the .windowLog parameter will be ignored. + * @return 0 on success, or an error code (can be checked with ZSTD_isError()). + * On failure, no parameters are updated. + */ +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setCParams(ZSTD_CCtx* cctx, ZSTD_compressionParameters cparams); + +/*! ZSTD_CCtx_setFParams() : + * Set all parameters provided within @p fparams into the working @p cctx. + * @return 0 on success, or an error code (can be checked with ZSTD_isError()). + */ +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setFParams(ZSTD_CCtx* cctx, ZSTD_frameParameters fparams); + +/*! ZSTD_CCtx_setParams() : + * Set all parameters provided within @p params into the working @p cctx. + * @return 0 on success, or an error code (can be checked with ZSTD_isError()). + */ +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setParams(ZSTD_CCtx* cctx, ZSTD_parameters params); /*! ZSTD_compress_advanced() : * Note : this function is now DEPRECATED. * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_setParameter() and other parameter setters. * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_compress2") +ZSTDLIB_STATIC_API size_t ZSTD_compress_advanced(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - const void* dict,size_t dictSize, - ZSTD_parameters params); + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const void* dict,size_t dictSize, + ZSTD_parameters params); /*! ZSTD_compress_usingCDict_advanced() : * Note : this function is now DEPRECATED. * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_loadDictionary() and other parameter setters. * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_compress2 with ZSTD_CCtx_loadDictionary") +ZSTDLIB_STATIC_API size_t ZSTD_compress_usingCDict_advanced(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, @@ -1662,18 +1884,18 @@ size_t ZSTD_compress_usingCDict_advanced(ZSTD_CCtx* cctx, /*! ZSTD_CCtx_loadDictionary_byReference() : * Same as ZSTD_CCtx_loadDictionary(), but dictionary content is referenced, instead of being copied into CCtx. * It saves some memory, but also requires that `dict` outlives its usage within `cctx` */ -ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary_byReference(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_byReference(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); /*! ZSTD_CCtx_loadDictionary_advanced() : * Same as ZSTD_CCtx_loadDictionary(), but gives finer control over * how to load the dictionary (by copy ? by reference ?) * and how to interpret it (automatic ? force raw mode ? full mode only ?) */ -ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); /*! ZSTD_CCtx_refPrefix_advanced() : * Same as ZSTD_CCtx_refPrefix(), but gives finer control over * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */ -ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); /* === experimental parameters === */ /* these parameters can be used with ZSTD_setParameter() @@ -1712,9 +1934,15 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* pre * See the comments on that enum for an explanation of the feature. */ #define ZSTD_c_forceAttachDict ZSTD_c_experimentalParam4 -/* Controls how the literals are compressed (default is auto). - * The value must be of type ZSTD_literalCompressionMode_e. - * See ZSTD_literalCompressionMode_e enum definition for details. +/* Controlled with ZSTD_paramSwitch_e enum. + * Default is ZSTD_ps_auto. + * Set to ZSTD_ps_disable to never compress literals. + * Set to ZSTD_ps_enable to always compress literals. (Note: uncompressed literals + * may still be emitted if huffman is not beneficial to use.) + * + * By default, in ZSTD_ps_auto, the library will decide at runtime whether to use + * literals compression based on the compression parameters - specifically, + * negative compression levels do not use literal compression. */ #define ZSTD_c_literalCompressionMode ZSTD_c_experimentalParam5 @@ -1777,7 +2005,7 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* pre * * Note that this means that the CDict tables can no longer be copied into the * CCtx, so the dict attachment mode ZSTD_dictForceCopy will no longer be - * useable. The dictionary can only be attached or reloaded. + * usable. The dictionary can only be attached or reloaded. * * In general, you should expect compression to be faster--sometimes very much * so--and CDict creation to be slightly slower. Eventually, we will probably @@ -1789,13 +2017,16 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* pre * Experimental parameter. * Default is 0 == disabled. Set to 1 to enable. * - * Tells the compressor that the ZSTD_inBuffer will ALWAYS be the same - * between calls, except for the modifications that zstd makes to pos (the - * caller must not modify pos). This is checked by the compressor, and - * compression will fail if it ever changes. This means the only flush - * mode that makes sense is ZSTD_e_end, so zstd will error if ZSTD_e_end - * is not used. The data in the ZSTD_inBuffer in the range [src, src + pos) - * MUST not be modified during compression or you will get data corruption. + * Tells the compressor that input data presented with ZSTD_inBuffer + * will ALWAYS be the same between calls. + * Technically, the @src pointer must never be changed, + * and the @pos field can only be updated by zstd. + * However, it's possible to increase the @size field, + * allowing scenarios where more data can be appended after compressions starts. + * These conditions are checked by the compressor, + * and compression will fail if they are not respected. + * Also, data in the ZSTD_inBuffer within the range [src, src + pos) + * MUST not be modified during compression or it will result in data corruption. * * When this flag is enabled zstd won't allocate an input window buffer, * because the user guarantees it can reference the ZSTD_inBuffer until @@ -1803,18 +2034,15 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* pre * large enough to fit a block (see ZSTD_c_stableOutBuffer). This will also * avoid the memcpy() from the input buffer to the input window buffer. * - * NOTE: ZSTD_compressStream2() will error if ZSTD_e_end is not used. - * That means this flag cannot be used with ZSTD_compressStream(). - * * NOTE: So long as the ZSTD_inBuffer always points to valid memory, using * this flag is ALWAYS memory safe, and will never access out-of-bounds - * memory. However, compression WILL fail if you violate the preconditions. + * memory. However, compression WILL fail if conditions are not respected. * - * WARNING: The data in the ZSTD_inBuffer in the range [dst, dst + pos) MUST - * not be modified during compression or you will get data corruption. This - * is because zstd needs to reference data in the ZSTD_inBuffer to find + * WARNING: The data in the ZSTD_inBuffer in the range [src, src + pos) MUST + * not be modified during compression or it will result in data corruption. + * This is because zstd needs to reference data in the ZSTD_inBuffer to find * matches. Normally zstd maintains its own window buffer for this purpose, - * but passing this flag tells zstd to use the user provided buffer. + * but passing this flag tells zstd to rely on user provided buffer instead. */ #define ZSTD_c_stableInBuffer ZSTD_c_experimentalParam9 @@ -1859,30 +2087,33 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* pre * Without validation, providing a sequence that does not conform to the zstd spec will cause * undefined behavior, and may produce a corrupted block. * - * With validation enabled, a if sequence is invalid (see doc/zstd_compression_format.md for + * With validation enabled, if sequence is invalid (see doc/zstd_compression_format.md for * specifics regarding offset/matchlength requirements) then the function will bail out and * return an error. * */ #define ZSTD_c_validateSequences ZSTD_c_experimentalParam12 -/* ZSTD_c_splitBlocks - * Default is 0 == disabled. Set to 1 to enable block splitting. +/* ZSTD_c_useBlockSplitter + * Controlled with ZSTD_paramSwitch_e enum. + * Default is ZSTD_ps_auto. + * Set to ZSTD_ps_disable to never use block splitter. + * Set to ZSTD_ps_enable to always use block splitter. * - * Will attempt to split blocks in order to improve compression ratio at the cost of speed. + * By default, in ZSTD_ps_auto, the library will decide at runtime whether to use + * block splitting based on the compression parameters. */ -#define ZSTD_c_splitBlocks ZSTD_c_experimentalParam13 +#define ZSTD_c_useBlockSplitter ZSTD_c_experimentalParam13 /* ZSTD_c_useRowMatchFinder - * Default is ZSTD_urm_auto. - * Controlled with ZSTD_useRowMatchFinderMode_e enum. - * - * By default, in ZSTD_urm_auto, when finalizing the compression parameters, the library - * will decide at runtime whether to use the row-based matchfinder based on support for SIMD - * instructions as well as the windowLog. + * Controlled with ZSTD_paramSwitch_e enum. + * Default is ZSTD_ps_auto. + * Set to ZSTD_ps_disable to never use row-based matchfinder. + * Set to ZSTD_ps_enable to force usage of row-based matchfinder. * - * Set to ZSTD_urm_disableRowMatchFinder to never use row-based matchfinder. - * Set to ZSTD_urm_enableRowMatchFinder to force usage of row-based matchfinder. + * By default, in ZSTD_ps_auto, the library will decide at runtime whether to use + * the row-based matchfinder based on support for SIMD instructions and the window log. + * Note that this only pertains to compression strategies: greedy, lazy, and lazy2 */ #define ZSTD_c_useRowMatchFinder ZSTD_c_experimentalParam14 @@ -1906,12 +2137,85 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* pre */ #define ZSTD_c_deterministicRefPrefix ZSTD_c_experimentalParam15 +/* ZSTD_c_prefetchCDictTables + * Controlled with ZSTD_paramSwitch_e enum. Default is ZSTD_ps_auto. + * + * In some situations, zstd uses CDict tables in-place rather than copying them + * into the working context. (See docs on ZSTD_dictAttachPref_e above for details). + * In such situations, compression speed is seriously impacted when CDict tables are + * "cold" (outside CPU cache). This parameter instructs zstd to prefetch CDict tables + * when they are used in-place. + * + * For sufficiently small inputs, the cost of the prefetch will outweigh the benefit. + * For sufficiently large inputs, zstd will by default memcpy() CDict tables + * into the working context, so there is no need to prefetch. This parameter is + * targeted at a middle range of input sizes, where a prefetch is cheap enough to be + * useful but memcpy() is too expensive. The exact range of input sizes where this + * makes sense is best determined by careful experimentation. + * + * Note: for this parameter, ZSTD_ps_auto is currently equivalent to ZSTD_ps_disable, + * but in the future zstd may conditionally enable this feature via an auto-detection + * heuristic for cold CDicts. + * Use ZSTD_ps_disable to opt out of prefetching under any circumstances. + */ +#define ZSTD_c_prefetchCDictTables ZSTD_c_experimentalParam16 + +/* ZSTD_c_enableSeqProducerFallback + * Allowed values are 0 (disable) and 1 (enable). The default setting is 0. + * + * Controls whether zstd will fall back to an internal sequence producer if an + * external sequence producer is registered and returns an error code. This fallback + * is block-by-block: the internal sequence producer will only be called for blocks + * where the external sequence producer returns an error code. Fallback parsing will + * follow any other cParam settings, such as compression level, the same as in a + * normal (fully-internal) compression operation. + * + * The user is strongly encouraged to read the full Block-Level Sequence Producer API + * documentation (below) before setting this parameter. */ +#define ZSTD_c_enableSeqProducerFallback ZSTD_c_experimentalParam17 + +/* ZSTD_c_maxBlockSize + * Allowed values are between 1KB and ZSTD_BLOCKSIZE_MAX (128KB). + * The default is ZSTD_BLOCKSIZE_MAX, and setting to 0 will set to the default. + * + * This parameter can be used to set an upper bound on the blocksize + * that overrides the default ZSTD_BLOCKSIZE_MAX. It cannot be used to set upper + * bounds greater than ZSTD_BLOCKSIZE_MAX or bounds lower than 1KB (will make + * compressBound() inaccurate). Only currently meant to be used for testing. + * + */ +#define ZSTD_c_maxBlockSize ZSTD_c_experimentalParam18 + +/* ZSTD_c_searchForExternalRepcodes + * This parameter affects how zstd parses external sequences, such as sequences + * provided through the compressSequences() API or from an external block-level + * sequence producer. + * + * If set to ZSTD_ps_enable, the library will check for repeated offsets in + * external sequences, even if those repcodes are not explicitly indicated in + * the "rep" field. Note that this is the only way to exploit repcode matches + * while using compressSequences() or an external sequence producer, since zstd + * currently ignores the "rep" field of external sequences. + * + * If set to ZSTD_ps_disable, the library will not exploit repeated offsets in + * external sequences, regardless of whether the "rep" field has been set. This + * reduces sequence compression overhead by about 25% while sacrificing some + * compression ratio. + * + * The default value is ZSTD_ps_auto, for which the library will enable/disable + * based on compression level. + * + * Note: for now, this param only has an effect if ZSTD_c_blockDelimiters is + * set to ZSTD_sf_explicitBlockDelimiters. That may change in the future. + */ +#define ZSTD_c_searchForExternalRepcodes ZSTD_c_experimentalParam19 + /*! ZSTD_CCtx_getParameter() : * Get the requested compression parameter value, selected by enum ZSTD_cParameter, * and store it into int* value. * @return : 0, or an error code (which can be tested with ZSTD_isError()). */ -ZSTDLIB_API size_t ZSTD_CCtx_getParameter(const ZSTD_CCtx* cctx, ZSTD_cParameter param, int* value); +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_getParameter(const ZSTD_CCtx* cctx, ZSTD_cParameter param, int* value); /*! ZSTD_CCtx_params : @@ -1931,25 +2235,25 @@ ZSTDLIB_API size_t ZSTD_CCtx_getParameter(const ZSTD_CCtx* cctx, ZSTD_cParameter * This can be used with ZSTD_estimateCCtxSize_advanced_usingCCtxParams() * for static allocation of CCtx for single-threaded compression. */ -ZSTDLIB_API ZSTD_CCtx_params* ZSTD_createCCtxParams(void); -ZSTDLIB_API size_t ZSTD_freeCCtxParams(ZSTD_CCtx_params* params); /* accept NULL pointer */ +ZSTDLIB_STATIC_API ZSTD_CCtx_params* ZSTD_createCCtxParams(void); +ZSTDLIB_STATIC_API size_t ZSTD_freeCCtxParams(ZSTD_CCtx_params* params); /* accept NULL pointer */ /*! ZSTD_CCtxParams_reset() : * Reset params to default values. */ -ZSTDLIB_API size_t ZSTD_CCtxParams_reset(ZSTD_CCtx_params* params); +ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_reset(ZSTD_CCtx_params* params); /*! ZSTD_CCtxParams_init() : * Initializes the compression parameters of cctxParams according to * compression level. All other parameters are reset to their default values. */ -ZSTDLIB_API size_t ZSTD_CCtxParams_init(ZSTD_CCtx_params* cctxParams, int compressionLevel); +ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_init(ZSTD_CCtx_params* cctxParams, int compressionLevel); /*! ZSTD_CCtxParams_init_advanced() : * Initializes the compression and frame parameters of cctxParams according to * params. All other parameters are reset to their default values. */ -ZSTDLIB_API size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, ZSTD_parameters params); +ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, ZSTD_parameters params); /*! ZSTD_CCtxParams_setParameter() : Requires v1.4.0+ * Similar to ZSTD_CCtx_setParameter. @@ -1959,14 +2263,14 @@ ZSTDLIB_API size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, Z * @result : a code representing success or failure (which can be tested with * ZSTD_isError()). */ -ZSTDLIB_API size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, int value); +ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, int value); /*! ZSTD_CCtxParams_getParameter() : * Similar to ZSTD_CCtx_getParameter. * Get the requested value of one compression parameter, selected by enum ZSTD_cParameter. * @result : 0, or an error code (which can be tested with ZSTD_isError()). */ -ZSTDLIB_API size_t ZSTD_CCtxParams_getParameter(const ZSTD_CCtx_params* params, ZSTD_cParameter param, int* value); +ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_getParameter(const ZSTD_CCtx_params* params, ZSTD_cParameter param, int* value); /*! ZSTD_CCtx_setParametersUsingCCtxParams() : * Apply a set of ZSTD_CCtx_params to the compression context. @@ -1975,7 +2279,7 @@ ZSTDLIB_API size_t ZSTD_CCtxParams_getParameter(const ZSTD_CCtx_params* params, * if nbWorkers>=1, new parameters will be picked up at next job, * with a few restrictions (windowLog, pledgedSrcSize, nbWorkers, jobSize, and overlapLog are not updated). */ -ZSTDLIB_API size_t ZSTD_CCtx_setParametersUsingCCtxParams( +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setParametersUsingCCtxParams( ZSTD_CCtx* cctx, const ZSTD_CCtx_params* params); /*! ZSTD_compressStream2_simpleArgs() : @@ -1984,7 +2288,7 @@ ZSTDLIB_API size_t ZSTD_CCtx_setParametersUsingCCtxParams( * This variant might be helpful for binders from dynamic languages * which have troubles handling structures containing memory pointers. */ -ZSTDLIB_API size_t ZSTD_compressStream2_simpleArgs ( +ZSTDLIB_STATIC_API size_t ZSTD_compressStream2_simpleArgs ( ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, size_t* dstPos, const void* src, size_t srcSize, size_t* srcPos, @@ -2000,33 +2304,33 @@ ZSTDLIB_API size_t ZSTD_compressStream2_simpleArgs ( * Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0. * Note 2 : Legacy Frame Identifiers are considered valid only if Legacy Support is enabled. * Note 3 : Skippable Frame Identifiers are considered valid. */ -ZSTDLIB_API unsigned ZSTD_isFrame(const void* buffer, size_t size); +ZSTDLIB_STATIC_API unsigned ZSTD_isFrame(const void* buffer, size_t size); /*! ZSTD_createDDict_byReference() : * Create a digested dictionary, ready to start decompression operation without startup delay. * Dictionary content is referenced, and therefore stays in dictBuffer. * It is important that dictBuffer outlives DDict, * it must remain read accessible throughout the lifetime of DDict */ -ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict_byReference(const void* dictBuffer, size_t dictSize); +ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_createDDict_byReference(const void* dictBuffer, size_t dictSize); /*! ZSTD_DCtx_loadDictionary_byReference() : * Same as ZSTD_DCtx_loadDictionary(), * but references `dict` content instead of copying it into `dctx`. * This saves memory if `dict` remains around., * However, it's imperative that `dict` remains accessible (and unmodified) while being used, so it must outlive decompression. */ -ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); /*! ZSTD_DCtx_loadDictionary_advanced() : * Same as ZSTD_DCtx_loadDictionary(), * but gives direct control over * how to load the dictionary (by copy ? by reference ?) * and how to interpret it (automatic ? force raw mode ? full mode only ?). */ -ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); /*! ZSTD_DCtx_refPrefix_advanced() : * Same as ZSTD_DCtx_refPrefix(), but gives finer control over * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */ -ZSTDLIB_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); /*! ZSTD_DCtx_setMaxWindowSize() : * Refuses allocating internal buffers for frames requiring a window size larger than provided limit. @@ -2035,14 +2339,14 @@ ZSTDLIB_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* pre * By default, a decompression context accepts all window sizes <= (1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT) * @return : 0, or an error code (which can be tested using ZSTD_isError()). */ -ZSTDLIB_API size_t ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx* dctx, size_t maxWindowSize); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx* dctx, size_t maxWindowSize); /*! ZSTD_DCtx_getParameter() : * Get the requested decompression parameter value, selected by enum ZSTD_dParameter, * and store it into int* value. * @return : 0, or an error code (which can be tested with ZSTD_isError()). */ -ZSTDLIB_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value); /* ZSTD_d_format * experimental parameter, @@ -2062,7 +2366,7 @@ ZSTDLIB_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param * in the range [dst, dst + pos) MUST not be modified during decompression * or you will get data corruption. * - * When this flags is enabled zstd won't allocate an output buffer, because + * When this flag is enabled zstd won't allocate an output buffer, because * it can write directly to the ZSTD_outBuffer, but it will still allocate * an input buffer large enough to fit any compressed block. This will also * avoid the memcpy() from the internal output buffer to the ZSTD_outBuffer. @@ -2115,6 +2419,17 @@ ZSTDLIB_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param */ #define ZSTD_d_refMultipleDDicts ZSTD_d_experimentalParam4 +/* ZSTD_d_disableHuffmanAssembly + * Set to 1 to disable the Huffman assembly implementation. + * The default value is 0, which allows zstd to use the Huffman assembly + * implementation if available. + * + * This parameter can be used to disable Huffman assembly at runtime. + * If you want to disable it at compile time you can define the macro + * ZSTD_DISABLE_ASM. + */ +#define ZSTD_d_disableHuffmanAssembly ZSTD_d_experimentalParam5 + /*! ZSTD_DCtx_setFormat() : * This function is REDUNDANT. Prefer ZSTD_DCtx_setParameter(). @@ -2123,6 +2438,7 @@ ZSTDLIB_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param * such ZSTD_f_zstd1_magicless for example. * @return : 0, or an error code (which can be tested using ZSTD_isError()). */ ZSTD_DEPRECATED("use ZSTD_DCtx_setParameter() instead") +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format); /*! ZSTD_decompressStream_simpleArgs() : @@ -2131,7 +2447,7 @@ size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format); * This can be helpful for binders from dynamic languages * which have troubles handling structures containing memory pointers. */ -ZSTDLIB_API size_t ZSTD_decompressStream_simpleArgs ( +ZSTDLIB_STATIC_API size_t ZSTD_decompressStream_simpleArgs ( ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, size_t* dstPos, const void* src, size_t srcSize, size_t* srcPos); @@ -2159,6 +2475,7 @@ ZSTDLIB_API size_t ZSTD_decompressStream_simpleArgs ( * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_initCStream_srcSize(ZSTD_CStream* zcs, int compressionLevel, unsigned long long pledgedSrcSize); @@ -2176,17 +2493,15 @@ size_t ZSTD_initCStream_srcSize(ZSTD_CStream* zcs, * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_initCStream_usingDict(ZSTD_CStream* zcs, const void* dict, size_t dictSize, int compressionLevel); /*! ZSTD_initCStream_advanced() : - * This function is DEPRECATED, and is approximately equivalent to: + * This function is DEPRECATED, and is equivalent to: * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); - * // Pseudocode: Set each zstd parameter and leave the rest as-is. - * for ((param, value) : params) { - * ZSTD_CCtx_setParameter(zcs, param, value); - * } + * ZSTD_CCtx_setParams(zcs, params); * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); * ZSTD_CCtx_loadDictionary(zcs, dict, dictSize); * @@ -2196,6 +2511,7 @@ size_t ZSTD_initCStream_usingDict(ZSTD_CStream* zcs, * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_initCStream_advanced(ZSTD_CStream* zcs, const void* dict, size_t dictSize, ZSTD_parameters params, @@ -2205,20 +2521,18 @@ size_t ZSTD_initCStream_advanced(ZSTD_CStream* zcs, * This function is DEPRECATED, and equivalent to: * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); * ZSTD_CCtx_refCDict(zcs, cdict); - * + * * note : cdict will just be referenced, and must outlive compression session * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_CCtx_reset and ZSTD_CCtx_refCDict, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_initCStream_usingCDict(ZSTD_CStream* zcs, const ZSTD_CDict* cdict); /*! ZSTD_initCStream_usingCDict_advanced() : - * This function is DEPRECATED, and is approximately equivalent to: + * This function is DEPRECATED, and is equivalent to: * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); - * // Pseudocode: Set each zstd frame parameter and leave the rest as-is. - * for ((fParam, value) : fParams) { - * ZSTD_CCtx_setParameter(zcs, fParam, value); - * } + * ZSTD_CCtx_setFParams(zcs, fParams); * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); * ZSTD_CCtx_refCDict(zcs, cdict); * @@ -2228,6 +2542,7 @@ size_t ZSTD_initCStream_usingCDict(ZSTD_CStream* zcs, const ZSTD_CDict* cdict); * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_CCtx_reset and ZSTD_CCtx_refCDict, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_initCStream_usingCDict_advanced(ZSTD_CStream* zcs, const ZSTD_CDict* cdict, ZSTD_frameParameters fParams, @@ -2252,6 +2567,7 @@ size_t ZSTD_initCStream_usingCDict_advanced(ZSTD_CStream* zcs, * This prototype will generate compilation warnings. */ ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_resetCStream(ZSTD_CStream* zcs, unsigned long long pledgedSrcSize); @@ -2270,7 +2586,7 @@ typedef struct { * Note : (ingested - consumed) is amount of input data buffered internally, not yet compressed. * Aggregates progression inside active worker threads. */ -ZSTDLIB_API ZSTD_frameProgression ZSTD_getFrameProgression(const ZSTD_CCtx* cctx); +ZSTDLIB_STATIC_API ZSTD_frameProgression ZSTD_getFrameProgression(const ZSTD_CCtx* cctx); /*! ZSTD_toFlushNow() : * Tell how many bytes are ready to be flushed immediately. @@ -2285,7 +2601,7 @@ ZSTDLIB_API ZSTD_frameProgression ZSTD_getFrameProgression(const ZSTD_CCtx* cctx * therefore flush speed is limited by production speed of oldest job * irrespective of the speed of concurrent (and newer) jobs. */ -ZSTDLIB_API size_t ZSTD_toFlushNow(ZSTD_CCtx* cctx); +ZSTDLIB_STATIC_API size_t ZSTD_toFlushNow(ZSTD_CCtx* cctx); /*===== Advanced Streaming decompression functions =====*/ @@ -2297,9 +2613,9 @@ ZSTDLIB_API size_t ZSTD_toFlushNow(ZSTD_CCtx* cctx); * ZSTD_DCtx_loadDictionary(zds, dict, dictSize); * * note: no dictionary will be used if dict == NULL or dictSize < 8 - * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x */ -ZSTDLIB_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize); +ZSTD_DEPRECATED("use ZSTD_DCtx_reset + ZSTD_DCtx_loadDictionary, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize); /*! * This function is deprecated, and is equivalent to: @@ -2308,9 +2624,9 @@ ZSTDLIB_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dic * ZSTD_DCtx_refDDict(zds, ddict); * * note : ddict is referenced, it must outlive decompression session - * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x */ -ZSTDLIB_API size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* zds, const ZSTD_DDict* ddict); +ZSTD_DEPRECATED("use ZSTD_DCtx_reset + ZSTD_DCtx_refDDict, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* zds, const ZSTD_DDict* ddict); /*! * This function is deprecated, and is equivalent to: @@ -2318,17 +2634,185 @@ ZSTDLIB_API size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* zds, const ZSTD_DDi * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); * * re-use decompression parameters from previous init; saves dictionary loading - * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x */ -ZSTDLIB_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); +ZSTD_DEPRECATED("use ZSTD_DCtx_reset, see zstd.h for detailed instructions") +ZSTDLIB_STATIC_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); + + +/* ********************* BLOCK-LEVEL SEQUENCE PRODUCER API ********************* + * + * *** OVERVIEW *** + * The Block-Level Sequence Producer API allows users to provide their own custom + * sequence producer which libzstd invokes to process each block. The produced list + * of sequences (literals and matches) is then post-processed by libzstd to produce + * valid compressed blocks. + * + * This block-level offload API is a more granular complement of the existing + * frame-level offload API compressSequences() (introduced in v1.5.1). It offers + * an easier migration story for applications already integrated with libzstd: the + * user application continues to invoke the same compression functions + * ZSTD_compress2() or ZSTD_compressStream2() as usual, and transparently benefits + * from the specific advantages of the external sequence producer. For example, + * the sequence producer could be tuned to take advantage of known characteristics + * of the input, to offer better speed / ratio, or could leverage hardware + * acceleration not available within libzstd itself. + * + * See contrib/externalSequenceProducer for an example program employing the + * Block-Level Sequence Producer API. + * + * *** USAGE *** + * The user is responsible for implementing a function of type + * ZSTD_sequenceProducer_F. For each block, zstd will pass the following + * arguments to the user-provided function: + * + * - sequenceProducerState: a pointer to a user-managed state for the sequence + * producer. + * + * - outSeqs, outSeqsCapacity: an output buffer for the sequence producer. + * outSeqsCapacity is guaranteed >= ZSTD_sequenceBound(srcSize). The memory + * backing outSeqs is managed by the CCtx. + * + * - src, srcSize: an input buffer for the sequence producer to parse. + * srcSize is guaranteed to be <= ZSTD_BLOCKSIZE_MAX. + * + * - dict, dictSize: a history buffer, which may be empty, which the sequence + * producer may reference as it parses the src buffer. Currently, zstd will + * always pass dictSize == 0 into external sequence producers, but this will + * change in the future. + * + * - compressionLevel: a signed integer representing the zstd compression level + * set by the user for the current operation. The sequence producer may choose + * to use this information to change its compression strategy and speed/ratio + * tradeoff. Note: the compression level does not reflect zstd parameters set + * through the advanced API. + * + * - windowSize: a size_t representing the maximum allowed offset for external + * sequences. Note that sequence offsets are sometimes allowed to exceed the + * windowSize if a dictionary is present, see doc/zstd_compression_format.md + * for details. + * + * The user-provided function shall return a size_t representing the number of + * sequences written to outSeqs. This return value will be treated as an error + * code if it is greater than outSeqsCapacity. The return value must be non-zero + * if srcSize is non-zero. The ZSTD_SEQUENCE_PRODUCER_ERROR macro is provided + * for convenience, but any value greater than outSeqsCapacity will be treated as + * an error code. + * + * If the user-provided function does not return an error code, the sequences + * written to outSeqs must be a valid parse of the src buffer. Data corruption may + * occur if the parse is not valid. A parse is defined to be valid if the + * following conditions hold: + * - The sum of matchLengths and literalLengths must equal srcSize. + * - All sequences in the parse, except for the final sequence, must have + * matchLength >= ZSTD_MINMATCH_MIN. The final sequence must have + * matchLength >= ZSTD_MINMATCH_MIN or matchLength == 0. + * - All offsets must respect the windowSize parameter as specified in + * doc/zstd_compression_format.md. + * - If the final sequence has matchLength == 0, it must also have offset == 0. + * + * zstd will only validate these conditions (and fail compression if they do not + * hold) if the ZSTD_c_validateSequences cParam is enabled. Note that sequence + * validation has a performance cost. + * + * If the user-provided function returns an error, zstd will either fall back + * to an internal sequence producer or fail the compression operation. The user can + * choose between the two behaviors by setting the ZSTD_c_enableSeqProducerFallback + * cParam. Fallback compression will follow any other cParam settings, such as + * compression level, the same as in a normal compression operation. + * + * The user shall instruct zstd to use a particular ZSTD_sequenceProducer_F + * function by calling + * ZSTD_registerSequenceProducer(cctx, + * sequenceProducerState, + * sequenceProducer) + * This setting will persist until the next parameter reset of the CCtx. + * + * The sequenceProducerState must be initialized by the user before calling + * ZSTD_registerSequenceProducer(). The user is responsible for destroying the + * sequenceProducerState. + * + * *** LIMITATIONS *** + * This API is compatible with all zstd compression APIs which respect advanced parameters. + * However, there are three limitations: + * + * First, the ZSTD_c_enableLongDistanceMatching cParam is not currently supported. + * COMPRESSION WILL FAIL if it is enabled and the user tries to compress with a block-level + * external sequence producer. + * - Note that ZSTD_c_enableLongDistanceMatching is auto-enabled by default in some + * cases (see its documentation for details). Users must explicitly set + * ZSTD_c_enableLongDistanceMatching to ZSTD_ps_disable in such cases if an external + * sequence producer is registered. + * - As of this writing, ZSTD_c_enableLongDistanceMatching is disabled by default + * whenever ZSTD_c_windowLog < 128MB, but that's subject to change. Users should + * check the docs on ZSTD_c_enableLongDistanceMatching whenever the Block-Level Sequence + * Producer API is used in conjunction with advanced settings (like ZSTD_c_windowLog). + * + * Second, history buffers are not currently supported. Concretely, zstd will always pass + * dictSize == 0 to the external sequence producer (for now). This has two implications: + * - Dictionaries are not currently supported. Compression will *not* fail if the user + * references a dictionary, but the dictionary won't have any effect. + * - Stream history is not currently supported. All advanced compression APIs, including + * streaming APIs, work with external sequence producers, but each block is treated as + * an independent chunk without history from previous blocks. + * + * Third, multi-threading within a single compression is not currently supported. In other words, + * COMPRESSION WILL FAIL if ZSTD_c_nbWorkers > 0 and an external sequence producer is registered. + * Multi-threading across compressions is fine: simply create one CCtx per thread. + * + * Long-term, we plan to overcome all three limitations. There is no technical blocker to + * overcoming them. It is purely a question of engineering effort. + */ + +#define ZSTD_SEQUENCE_PRODUCER_ERROR ((size_t)(-1)) + +typedef size_t ZSTD_sequenceProducer_F ( + void* sequenceProducerState, + ZSTD_Sequence* outSeqs, size_t outSeqsCapacity, + const void* src, size_t srcSize, + const void* dict, size_t dictSize, + int compressionLevel, + size_t windowSize +); + +/*! ZSTD_registerSequenceProducer() : + * Instruct zstd to use a block-level external sequence producer function. + * + * The sequenceProducerState must be initialized by the caller, and the caller is + * responsible for managing its lifetime. This parameter is sticky across + * compressions. It will remain set until the user explicitly resets compression + * parameters. + * + * Sequence producer registration is considered to be an "advanced parameter", + * part of the "advanced API". This means it will only have an effect on compression + * APIs which respect advanced parameters, such as compress2() and compressStream2(). + * Older compression APIs such as compressCCtx(), which predate the introduction of + * "advanced parameters", will ignore any external sequence producer setting. + * + * The sequence producer can be "cleared" by registering a NULL function pointer. This + * removes all limitations described above in the "LIMITATIONS" section of the API docs. + * + * The user is strongly encouraged to read the full API documentation (above) before + * calling this function. */ +ZSTDLIB_STATIC_API void +ZSTD_registerSequenceProducer( + ZSTD_CCtx* cctx, + void* sequenceProducerState, + ZSTD_sequenceProducer_F* sequenceProducer +); /********************************************************************* -* Buffer-less and synchronous inner streaming functions +* Buffer-less and synchronous inner streaming functions (DEPRECATED) +* +* This API is deprecated, and will be removed in a future version. +* It allows streaming (de)compression with user allocated buffers. +* However, it is hard to use, and not as well tested as the rest of +* our API. * -* This is an advanced API, giving full control over buffer management, for users which need direct control over memory. -* But it's also a complex one, with several restrictions, documented below. -* Prefer normal streaming API for an easier experience. +* Please use the normal streaming API instead: ZSTD_compressStream2, +* and ZSTD_decompressStream. +* If there is functionality that you need, but it doesn't provide, +* please open an issue on our GitHub. ********************************************************************* */ /** @@ -2340,7 +2824,6 @@ ZSTDLIB_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); Start by initializing a context. Use ZSTD_compressBegin(), or ZSTD_compressBegin_usingDict() for dictionary compression. - It's also possible to duplicate a reference context which has already been initialized, using ZSTD_copyCCtx() Then, consume your input using ZSTD_compressContinue(). There are some important considerations to keep in mind when using this advanced function : @@ -2362,18 +2845,28 @@ ZSTDLIB_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); */ /*===== Buffer-less streaming compression functions =====*/ -ZSTDLIB_API size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel); -ZSTDLIB_API size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel); -ZSTDLIB_API size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); /**< note: fails if cdict==NULL */ -ZSTDLIB_API size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, unsigned long long pledgedSrcSize); /**< note: if pledgedSrcSize is not known, use ZSTD_CONTENTSIZE_UNKNOWN */ - -ZSTDLIB_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); -ZSTDLIB_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel); +ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel); +ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); /**< note: fails if cdict==NULL */ + +ZSTD_DEPRECATED("This function will likely be removed in a future release. It is misleading and has very limited utility.") +ZSTDLIB_STATIC_API +size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, unsigned long long pledgedSrcSize); /**< note: if pledgedSrcSize is not known, use ZSTD_CONTENTSIZE_UNKNOWN */ + +ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); /* The ZSTD_compressBegin_advanced() and ZSTD_compressBegin_usingCDict_advanced() are now DEPRECATED and will generate a compiler warning */ ZSTD_DEPRECATED("use advanced API to access custom parameters") +ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_parameters params, unsigned long long pledgedSrcSize); /**< pledgedSrcSize : If srcSize is not known at init time, use ZSTD_CONTENTSIZE_UNKNOWN */ ZSTD_DEPRECATED("use advanced API to access custom parameters") +ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_CDict* const cdict, ZSTD_frameParameters const fParams, unsigned long long const pledgedSrcSize); /* compression parameters are already set within cdict. pledgedSrcSize must be correct. If srcSize is not known, use macro ZSTD_CONTENTSIZE_UNKNOWN */ /** Buffer-less streaming decompression (synchronous mode) @@ -2386,8 +2879,8 @@ size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_ Frame header is extracted from the beginning of compressed frame, so providing only the frame's beginning is enough. Data fragment must be large enough to ensure successful decoding. `ZSTD_frameHeaderSize_max` bytes is guaranteed to always be large enough. - @result : 0 : successful decoding, the `ZSTD_frameHeader` structure is correctly filled. - >0 : `srcSize` is too small, please provide at least @result bytes on next attempt. + result : 0 : successful decoding, the `ZSTD_frameHeader` structure is correctly filled. + >0 : `srcSize` is too small, please provide at least result bytes on next attempt. errorCode, which can be tested using ZSTD_isError(). It fills a ZSTD_frameHeader structure with important information to correctly decode the frame, @@ -2406,7 +2899,7 @@ size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_ The most memory efficient way is to use a round buffer of sufficient size. Sufficient size is determined by invoking ZSTD_decodingBufferSize_min(), - which can @return an error code if required value is too large for current system (in 32-bits mode). + which can return an error code if required value is too large for current system (in 32-bits mode). In a round buffer methodology, ZSTD_decompressContinue() decompresses each block next to previous one, up to the moment there is not enough room left in the buffer to guarantee decoding another full block, which maximum size is provided in `ZSTD_frameHeader` structure, field `blockSizeMax`. @@ -2426,7 +2919,7 @@ size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_ ZSTD_nextSrcSizeToDecompress() tells how many bytes to provide as 'srcSize' to ZSTD_decompressContinue(). ZSTD_decompressContinue() requires this _exact_ amount of bytes, or it will fail. - @result of ZSTD_decompressContinue() is the number of bytes regenerated within 'dst' (necessarily <= dstCapacity). + result of ZSTD_decompressContinue() is the number of bytes regenerated within 'dst' (necessarily <= dstCapacity). It can be zero : it just means ZSTD_decompressContinue() has decoded some metadata item. It can also be an error code, which can be tested with ZSTD_isError(). @@ -2449,49 +2942,42 @@ size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_ */ /*===== Buffer-less streaming decompression functions =====*/ -typedef enum { ZSTD_frame, ZSTD_skippableFrame } ZSTD_frameType_e; -typedef struct { - unsigned long long frameContentSize; /* if == ZSTD_CONTENTSIZE_UNKNOWN, it means this field is not available. 0 means "empty" */ - unsigned long long windowSize; /* can be very large, up to <= frameContentSize */ - unsigned blockSizeMax; - ZSTD_frameType_e frameType; /* if == ZSTD_skippableFrame, frameContentSize is the size of skippable content */ - unsigned headerSize; - unsigned dictID; - unsigned checksumFlag; -} ZSTD_frameHeader; -/*! ZSTD_getFrameHeader() : - * decode Frame Header, or requires larger `srcSize`. - * @return : 0, `zfhPtr` is correctly filled, - * >0, `srcSize` is too small, value is wanted `srcSize` amount, - * or an error code, which can be tested using ZSTD_isError() */ -ZSTDLIB_API size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize); /**< doesn't consume input */ -/*! ZSTD_getFrameHeader_advanced() : - * same as ZSTD_getFrameHeader(), - * with added capability to select a format (like ZSTD_f_zstd1_magicless) */ -ZSTDLIB_API size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format); -ZSTDLIB_API size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize); /**< when frame content size is not known, pass in frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN */ +ZSTDLIB_STATIC_API size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize); /**< when frame content size is not known, pass in frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN */ -ZSTDLIB_API size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx); -ZSTDLIB_API size_t ZSTD_decompressBegin_usingDict(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); -ZSTDLIB_API size_t ZSTD_decompressBegin_usingDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict); +ZSTDLIB_STATIC_API size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx); +ZSTDLIB_STATIC_API size_t ZSTD_decompressBegin_usingDict(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); +ZSTDLIB_STATIC_API size_t ZSTD_decompressBegin_usingDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict); -ZSTDLIB_API size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx); -ZSTDLIB_API size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTDLIB_STATIC_API size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx); +ZSTDLIB_STATIC_API size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); /* misc */ -ZSTDLIB_API void ZSTD_copyDCtx(ZSTD_DCtx* dctx, const ZSTD_DCtx* preparedDCtx); +ZSTD_DEPRECATED("This function will likely be removed in the next minor release. It is misleading and has very limited utility.") +ZSTDLIB_STATIC_API void ZSTD_copyDCtx(ZSTD_DCtx* dctx, const ZSTD_DCtx* preparedDCtx); typedef enum { ZSTDnit_frameHeader, ZSTDnit_blockHeader, ZSTDnit_block, ZSTDnit_lastBlock, ZSTDnit_checksum, ZSTDnit_skippableFrame } ZSTD_nextInputType_e; -ZSTDLIB_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx); +ZSTDLIB_STATIC_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx); -/* ============================ */ -/** Block level API */ -/* ============================ */ +/* ========================================= */ +/** Block level API (DEPRECATED) */ +/* ========================================= */ /*! + + This API is deprecated in favor of the regular compression API. + You can get the frame header down to 2 bytes by setting: + - ZSTD_c_format = ZSTD_f_zstd1_magicless + - ZSTD_c_contentSizeFlag = 0 + - ZSTD_c_checksumFlag = 0 + - ZSTD_c_dictIDFlag = 0 + + This API is not as well tested as our normal API, so we recommend not using it. + We will be removing it in a future version. If the normal API doesn't provide + the functionality you need, please open a GitHub issue. + Block functions produce and decode raw zstd blocks, without frame metadata. Frame metadata cost is typically ~12 bytes, which can be non-negligible for very small blocks (< 100 bytes). But users will have to take in charge needed metadata to regenerate data, such as compressed and content sizes. @@ -2502,7 +2988,6 @@ ZSTDLIB_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx); - It is necessary to init context before starting + compression : any ZSTD_compressBegin*() variant, including with dictionary + decompression : any ZSTD_decompressBegin*() variant, including with dictionary - + copyCCtx() and copyDCtx() can be used too - Block size is limited, it must be <= ZSTD_getBlockSize() <= ZSTD_BLOCKSIZE_MAX == 128 KB + If input is larger than a block size, it's necessary to split input data into multiple blocks + For inputs larger than a single block, consider using regular ZSTD_compress() instead. @@ -2519,11 +3004,14 @@ ZSTDLIB_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx); */ /*===== Raw zstd block functions =====*/ -ZSTDLIB_API size_t ZSTD_getBlockSize (const ZSTD_CCtx* cctx); -ZSTDLIB_API size_t ZSTD_compressBlock (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); -ZSTDLIB_API size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); -ZSTDLIB_API size_t ZSTD_insertBlock (ZSTD_DCtx* dctx, const void* blockStart, size_t blockSize); /**< insert uncompressed block into `dctx` history. Useful for multi-blocks decompression. */ - +ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_getBlockSize (const ZSTD_CCtx* cctx); +ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_compressBlock (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") +ZSTDLIB_STATIC_API size_t ZSTD_insertBlock (ZSTD_DCtx* dctx, const void* blockStart, size_t blockSize); /**< insert uncompressed block into `dctx` history. Useful for multi-blocks decompression. */ #endif /* ZSTD_H_ZSTD_STATIC_LINKING_ONLY */ diff --git a/Utilities/cmzstd/lib/zstd_errors.h b/Utilities/cmzstd/lib/zstd_errors.h index fa3686b7724..dc75eeebad9 100644 --- a/Utilities/cmzstd/lib/zstd_errors.h +++ b/Utilities/cmzstd/lib/zstd_errors.h @@ -1,5 +1,5 @@ /* - * Copyright (c) Yann Collet, Facebook, Inc. + * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the @@ -20,19 +20,31 @@ extern "C" { /* ===== ZSTDERRORLIB_API : control library symbols visibility ===== */ -#ifndef ZSTDERRORLIB_VISIBILITY -# if defined(__GNUC__) && (__GNUC__ >= 4) -# define ZSTDERRORLIB_VISIBILITY __attribute__ ((visibility ("default"))) +#ifndef ZSTDERRORLIB_VISIBLE + /* Backwards compatibility with old macro name */ +# ifdef ZSTDERRORLIB_VISIBILITY +# define ZSTDERRORLIB_VISIBLE ZSTDERRORLIB_VISIBILITY +# elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) +# define ZSTDERRORLIB_VISIBLE __attribute__ ((visibility ("default"))) # else -# define ZSTDERRORLIB_VISIBILITY +# define ZSTDERRORLIB_VISIBLE # endif #endif + +#ifndef ZSTDERRORLIB_HIDDEN +# if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__) +# define ZSTDERRORLIB_HIDDEN __attribute__ ((visibility ("hidden"))) +# else +# define ZSTDERRORLIB_HIDDEN +# endif +#endif + #if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) -# define ZSTDERRORLIB_API __declspec(dllexport) ZSTDERRORLIB_VISIBILITY +# define ZSTDERRORLIB_API __declspec(dllexport) ZSTDERRORLIB_VISIBLE #elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) -# define ZSTDERRORLIB_API __declspec(dllimport) ZSTDERRORLIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +# define ZSTDERRORLIB_API __declspec(dllimport) ZSTDERRORLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ #else -# define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBILITY +# define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBLE #endif /*-********************************************* @@ -58,14 +70,17 @@ typedef enum { ZSTD_error_frameParameter_windowTooLarge = 16, ZSTD_error_corruption_detected = 20, ZSTD_error_checksum_wrong = 22, + ZSTD_error_literals_headerWrong = 24, ZSTD_error_dictionary_corrupted = 30, ZSTD_error_dictionary_wrong = 32, ZSTD_error_dictionaryCreation_failed = 34, ZSTD_error_parameter_unsupported = 40, + ZSTD_error_parameter_combination_unsupported = 41, ZSTD_error_parameter_outOfBound = 42, ZSTD_error_tableLog_tooLarge = 44, ZSTD_error_maxSymbolValue_tooLarge = 46, ZSTD_error_maxSymbolValue_tooSmall = 48, + ZSTD_error_stabilityCondition_notRespected = 50, ZSTD_error_stage_wrong = 60, ZSTD_error_init_missing = 62, ZSTD_error_memory_allocation = 64, @@ -73,11 +88,15 @@ typedef enum { ZSTD_error_dstSize_tooSmall = 70, ZSTD_error_srcSize_wrong = 72, ZSTD_error_dstBuffer_null = 74, + ZSTD_error_noForwardProgress_destFull = 80, + ZSTD_error_noForwardProgress_inputEmpty = 82, /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */ ZSTD_error_frameIndex_tooLarge = 100, ZSTD_error_seekableIO = 102, ZSTD_error_dstBuffer_wrong = 104, ZSTD_error_srcBuffer_wrong = 105, + ZSTD_error_sequenceProducer_failed = 106, + ZSTD_error_externalSequences_invalid = 107, ZSTD_error_maxCode = 120 /* never EVER use this value directly, it can change in future versions! Use ZSTD_isError() instead */ } ZSTD_ErrorCode; diff --git a/Utilities/std/cm/algorithm b/Utilities/std/cm/algorithm index 93fe224c9fd..107bf854148 100644 --- a/Utilities/std/cm/algorithm +++ b/Utilities/std/cm/algorithm @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/array b/Utilities/std/cm/array index f344ee73127..4e3a29d3e7f 100644 --- a/Utilities/std/cm/array +++ b/Utilities/std/cm/array @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/bits/container_helpers.hxx b/Utilities/std/cm/bits/container_helpers.hxx index abcdacbfe60..15307aa63fe 100644 --- a/Utilities/std/cm/bits/container_helpers.hxx +++ b/Utilities/std/cm/bits/container_helpers.hxx @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once @@ -25,9 +25,9 @@ using std::end; template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto cbegin(const C& c) +inline auto cbegin(C const& c) # else -inline constexpr auto cbegin(const C& c) noexcept(noexcept(std::begin(c))) +inline constexpr auto cbegin(C const& c) noexcept(noexcept(std::begin(c))) # endif -> decltype(std::begin(c)) { @@ -36,9 +36,9 @@ inline constexpr auto cbegin(const C& c) noexcept(noexcept(std::begin(c))) template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto cend(const C& c) +inline auto cend(C const& c) # else -inline constexpr auto cend(const C& c) noexcept(noexcept(std::end(c))) +inline constexpr auto cend(C const& c) noexcept(noexcept(std::end(c))) # endif -> decltype(std::end(c)) { @@ -57,9 +57,9 @@ inline constexpr auto rbegin(C& c) } template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto rbegin(const C& c) +inline auto rbegin(C const& c) # else -inline constexpr auto rbegin(const C& c) +inline constexpr auto rbegin(C const& c) # endif -> decltype(c.rbegin()) { @@ -76,13 +76,13 @@ inline constexpr std::reverse_iterator rbegin(T (&array)[N]) noexcept } template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline std::reverse_iterator rbegin(std::initializer_list il) +inline std::reverse_iterator rbegin(std::initializer_list il) # else -inline constexpr std::reverse_iterator rbegin( +inline constexpr std::reverse_iterator rbegin( std::initializer_list il) noexcept # endif { - return std::reverse_iterator(il.end()); + return std::reverse_iterator(il.end()); } template @@ -98,9 +98,9 @@ inline constexpr auto rend(C& c) } template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto rend(const C& c) +inline auto rend(C const& c) # else -inline constexpr auto rend(const C& c) +inline constexpr auto rend(C const& c) # endif -> decltype(c.rend()) { @@ -117,20 +117,20 @@ inline constexpr std::reverse_iterator rend(T (&array)[N]) noexcept } template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline std::reverse_iterator rend(std::initializer_list il) +inline std::reverse_iterator rend(std::initializer_list il) # else -inline constexpr std::reverse_iterator rend( +inline constexpr std::reverse_iterator rend( std::initializer_list il) noexcept # endif { - return std::reverse_iterator(il.begin()); + return std::reverse_iterator(il.begin()); } template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto crbegin(const C& c) +inline auto crbegin(C const& c) # else -inline constexpr auto crbegin(const C& c) +inline constexpr auto crbegin(C const& c) # endif -> decltype(cm::rbegin(c)) { @@ -139,9 +139,9 @@ inline constexpr auto crbegin(const C& c) template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto crend(const C& c) +inline auto crend(C const& c) # else -inline constexpr auto crend(const C& c) +inline constexpr auto crend(C const& c) # endif -> decltype(cm::rend(c)) { @@ -165,9 +165,9 @@ using std::crend; template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto size(const C& c) +inline auto size(C const& c) # else -inline constexpr auto size(const C& c) noexcept(noexcept(c.size())) +inline constexpr auto size(C const& c) noexcept(noexcept(c.size())) # endif -> decltype(c.size()) { @@ -176,9 +176,9 @@ inline constexpr auto size(const C& c) noexcept(noexcept(c.size())) template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline std::size_t size(const T (&)[N]) +inline std::size_t size(T const (&)[N]) # else -inline constexpr std::size_t size(const T (&)[N]) noexcept +inline constexpr std::size_t size(T const (&)[N]) noexcept # endif { return N; @@ -186,9 +186,9 @@ inline constexpr std::size_t size(const T (&)[N]) noexcept template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto empty(const C& c) +inline auto empty(C const& c) # else -inline constexpr auto empty(const C& c) noexcept(noexcept(c.empty())) +inline constexpr auto empty(C const& c) noexcept(noexcept(c.empty())) # endif -> decltype(c.empty()) { @@ -197,9 +197,9 @@ inline constexpr auto empty(const C& c) noexcept(noexcept(c.empty())) template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline bool empty(const T (&)[N]) +inline bool empty(T const (&)[N]) # else -inline constexpr bool empty(const T (&)[N]) noexcept +inline constexpr bool empty(T const (&)[N]) noexcept # endif { return false; @@ -221,16 +221,16 @@ inline auto data(C& c) -> decltype(c.data()) # else inline constexpr auto data(C& c) noexcept(noexcept(c.data())) # endif - -> decltype(c.data()) + -> decltype(c.data()) { return c.data(); } template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto data(const C& c) +inline auto data(C const& c) # else -inline constexpr auto data(const C& c) noexcept(noexcept(c.data())) +inline constexpr auto data(C const& c) noexcept(noexcept(c.data())) # endif -> decltype(c.data()) { @@ -249,9 +249,9 @@ inline constexpr T* data(T (&array)[N]) noexcept template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline const E* data(std::initializer_list il) +inline E const* data(std::initializer_list il) # else -inline constexpr const E* data(std::initializer_list il) noexcept +inline constexpr E const* data(std::initializer_list il) noexcept # endif { return il.begin(); @@ -269,9 +269,9 @@ using std::data; template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline auto ssize(const C& c) +inline auto ssize(C const& c) # else -inline constexpr auto ssize(const C& c) +inline constexpr auto ssize(C const& c) # endif -> typename std::common_type< std::ptrdiff_t, typename std::make_signed::type>::type @@ -285,9 +285,9 @@ inline constexpr auto ssize(const C& c) template # if defined(_MSC_VER) && _MSC_VER < 1900 -inline std::ptrdiff_t ssize(const T (&)[N]) +inline std::ptrdiff_t ssize(T const (&)[N]) # else -inline constexpr std::ptrdiff_t ssize(const T (&)[N]) noexcept +inline constexpr std::ptrdiff_t ssize(T const (&)[N]) noexcept # endif { return N; diff --git a/Utilities/std/cm/bits/erase_if.hxx b/Utilities/std/cm/bits/erase_if.hxx index 354b0c22b47..ced6107d37d 100644 --- a/Utilities/std/cm/bits/erase_if.hxx +++ b/Utilities/std/cm/bits/erase_if.hxx @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once diff --git a/Utilities/std/cm/bits/fs_path.cxx b/Utilities/std/cm/bits/fs_path.cxx index 17af643dba6..b5523081834 100644 --- a/Utilities/std/cm/bits/fs_path.cxx +++ b/Utilities/std/cm/bits/fs_path.cxx @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #include // IWYU pragma: associated @@ -68,7 +68,7 @@ class path_parser { } - path_parser(const path_parser&) = default; + path_parser(path_parser const&) = default; ~path_parser() = default; @@ -76,8 +76,8 @@ class path_parser void increment() noexcept { - const pointer start = this->next_token(); - const pointer end = this->after_end(); + pointer const start = this->next_token(); + pointer const end = this->after_end(); if (start == end) { this->set_state(state::at_end); @@ -138,8 +138,8 @@ class path_parser void decrement() noexcept { - const pointer rstart = this->current_token() - 1; - const pointer rend = this->before_start(); + pointer const rstart = this->current_token() - 1; + pointer const rend = this->before_start(); if (rstart == rend) { this->set_state(state::before_begin); @@ -327,7 +327,7 @@ class path_parser )) { return nullptr; } - const auto step = ptr < end ? 1 : -1; + auto const step = ptr < end ? 1 : -1; ptr += step; while (ptr != end && (*ptr == '/' @@ -357,7 +357,7 @@ class path_parser ) { return nullptr; } - const auto step = ptr < end ? 1 : -1; + auto const step = ptr < end ? 1 : -1; ptr += step; while (ptr != end && *ptr != '/' # if defined(_WIN32) @@ -476,7 +476,7 @@ class path_parser } state State; - const cm::string_view Path; + cm::string_view const Path; cm::string_view Entry; }; @@ -503,11 +503,11 @@ void unicode_helper::append(std::string& str, std::uint32_t codepoint) } } -unicode_helper::utf8_state unicode_helper::decode(const utf8_state state, - const std::uint8_t fragment, +unicode_helper::utf8_state unicode_helper::decode(utf8_state const state, + std::uint8_t const fragment, std::uint32_t& codepoint) { - const std::uint32_t utf8_state_info[] = { + std::uint32_t const utf8_state_info[] = { // encoded states 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, @@ -531,7 +531,7 @@ unicode_helper::utf8_state unicode_helper::decode(const utf8_state state, } // internals // Class path -path& path::operator/=(const path& p) +path& path::operator/=(path const& p) { if (p.is_absolute() || (p.has_root_name() && p.get_root_name() != this->get_root_name())) { @@ -577,8 +577,8 @@ path path::lexically_normal() const return *this; } - const cm::string_view dot = "."_s; - const cm::string_view dotdot = ".."_s; + cm::string_view const dot = "."_s; + cm::string_view const dotdot = ".."_s; std::vector root_parts; std::vector parts; @@ -632,7 +632,7 @@ path path::lexically_normal() const std::string np; np.reserve(path_size); - for (const auto& p : root_parts) { + for (auto const& p : root_parts) { np += p; } // convert any slash to the preferred_separator @@ -641,7 +641,7 @@ path path::lexically_normal() const np.begin(), np.end(), '/', static_cast(this->preferred_separator)); } - for (const auto& p : parts) { + for (auto const& p : parts) { if (!p.empty()) { np += p; np += static_cast(this->preferred_separator); @@ -658,7 +658,7 @@ path path::lexically_normal() const return path(std::move(np)); } -path path::lexically_relative(const path& base) const +path path::lexically_relative(path const& base) const { internals::path_parser parser(this->path_); ++parser; @@ -723,8 +723,8 @@ path path::lexically_relative(const path& base) const } # endif - const cm::string_view dot = "."_s; - const cm::string_view dotdot = ".."_s; + cm::string_view const dot = "."_s; + cm::string_view const dotdot = ".."_s; auto a = this->begin(), aend = this->end(); auto b = base.begin(), bend = base.end(); @@ -929,7 +929,7 @@ path::iterator::iterator() : path_(nullptr) { } -path::iterator::iterator(const iterator& other) +path::iterator::iterator(iterator const& other) { this->path_ = other.path_; if (other.parser_) { @@ -937,7 +937,7 @@ path::iterator::iterator(const iterator& other) this->path_element_ = path(**this->parser_); } } -path::iterator::iterator(const path* p, bool at_end) +path::iterator::iterator(path const* p, bool at_end) : path_(p) , parser_(cm::make_unique(p->path_, at_end)) { @@ -949,7 +949,7 @@ path::iterator::iterator(const path* p, bool at_end) path::iterator::~iterator() = default; -path::iterator& path::iterator::operator=(const iterator& other) +path::iterator& path::iterator::operator=(iterator const& other) { this->path_ = other.path_; if (other.parser_) { @@ -996,7 +996,7 @@ path::iterator& path::iterator::operator--() return *this; } -bool operator==(const path::iterator& lhs, const path::iterator& rhs) +bool operator==(path::iterator const& lhs, path::iterator const& rhs) { return lhs.path_ == rhs.path_ && lhs.parser_ != nullptr && ((lhs.parser_->at_end() && rhs.parser_->at_end()) || @@ -1004,7 +1004,7 @@ bool operator==(const path::iterator& lhs, const path::iterator& rhs) ((**lhs.parser_).data() == (**rhs.parser_).data())); } -std::size_t hash_value(const path& p) noexcept +std::size_t hash_value(path const& p) noexcept { internals::path_parser parser(p.path_); std::hash hasher; diff --git a/Utilities/std/cm/bits/string_view.cxx b/Utilities/std/cm/bits/string_view.cxx index af5ae78c40b..bd5fb605f0a 100644 --- a/Utilities/std/cm/bits/string_view.cxx +++ b/Utilities/std/cm/bits/string_view.cxx @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #include // IWYU pragma: associated @@ -66,17 +66,17 @@ int string_view::compare(size_type pos1, size_type count1, string_view v, return substr(pos1, count1).compare(v.substr(pos2, count2)); } -int string_view::compare(const char* s) const +int string_view::compare(char const* s) const { return compare(string_view(s)); } -int string_view::compare(size_type pos1, size_type count1, const char* s) const +int string_view::compare(size_type pos1, size_type count1, char const* s) const { return substr(pos1, count1).compare(string_view(s)); } -int string_view::compare(size_type pos1, size_type count1, const char* s, +int string_view::compare(size_type pos1, size_type count1, char const* s, size_type count2) const { return substr(pos1, count1).compare(string_view(s, count2)); @@ -98,13 +98,13 @@ string_view::size_type string_view::find(char c, size_type pos) const noexcept return find(string_view(&c, 1), pos); } -string_view::size_type string_view::find(const char* s, size_type pos, +string_view::size_type string_view::find(char const* s, size_type pos, size_type count) const { return find(string_view(s, count), pos); } -string_view::size_type string_view::find(const char* s, size_type pos) const +string_view::size_type string_view::find(char const* s, size_type pos) const { return find(string_view(s), pos); } @@ -129,13 +129,13 @@ string_view::size_type string_view::rfind(char c, size_type pos) const noexcept return rfind(string_view(&c, 1), pos); } -string_view::size_type string_view::rfind(const char* s, size_type pos, +string_view::size_type string_view::rfind(char const* s, size_type pos, size_type count) const { return rfind(string_view(s, count), pos); } -string_view::size_type string_view::rfind(const char* s, size_type pos) const +string_view::size_type string_view::rfind(char const* s, size_type pos) const { return rfind(string_view(s), pos); } @@ -157,13 +157,13 @@ string_view::size_type string_view::find_first_of(char c, return find_first_of(string_view(&c, 1), pos); } -string_view::size_type string_view::find_first_of(const char* s, size_type pos, +string_view::size_type string_view::find_first_of(char const* s, size_type pos, size_type count) const { return find_first_of(string_view(s, count), pos); } -string_view::size_type string_view::find_first_of(const char* s, +string_view::size_type string_view::find_first_of(char const* s, size_type pos) const { return find_first_of(string_view(s), pos); @@ -189,13 +189,13 @@ string_view::size_type string_view::find_last_of(char c, return find_last_of(string_view(&c, 1), pos); } -string_view::size_type string_view::find_last_of(const char* s, size_type pos, +string_view::size_type string_view::find_last_of(char const* s, size_type pos, size_type count) const { return find_last_of(string_view(s, count), pos); } -string_view::size_type string_view::find_last_of(const char* s, +string_view::size_type string_view::find_last_of(char const* s, size_type pos) const { return find_last_of(string_view(s), pos); @@ -218,14 +218,14 @@ string_view::size_type string_view::find_first_not_of( return find_first_not_of(string_view(&c, 1), pos); } -string_view::size_type string_view::find_first_not_of(const char* s, +string_view::size_type string_view::find_first_not_of(char const* s, size_type pos, size_type count) const { return find_first_not_of(string_view(s, count), pos); } -string_view::size_type string_view::find_first_not_of(const char* s, +string_view::size_type string_view::find_first_not_of(char const* s, size_type pos) const { return find_first_not_of(string_view(s), pos); @@ -251,14 +251,14 @@ string_view::size_type string_view::find_last_not_of( return find_last_not_of(string_view(&c, 1), pos); } -string_view::size_type string_view::find_last_not_of(const char* s, +string_view::size_type string_view::find_last_not_of(char const* s, size_type pos, size_type count) const { return find_last_not_of(string_view(s, count), pos); } -string_view::size_type string_view::find_last_not_of(const char* s, +string_view::size_type string_view::find_last_not_of(char const* s, size_type pos) const { return find_last_not_of(string_view(s), pos); diff --git a/Utilities/std/cm/deque b/Utilities/std/cm/deque index df5f8dffe87..54d186f1633 100644 --- a/Utilities/std/cm/deque +++ b/Utilities/std/cm/deque @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -23,7 +23,7 @@ using std::erase_if; #else template -inline void erase(std::deque& cont, const V& value) +inline void erase(std::deque& cont, V const& value) { cont.erase(std::remove(cont.begin(), cont.end(), value), cont.end()); } diff --git a/Utilities/std/cm/filesystem b/Utilities/std/cm/filesystem index b1cb3668f44..81edd71906c 100644 --- a/Utilities/std/cm/filesystem +++ b/Utilities/std/cm/filesystem @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include "cmSTL.hxx" // IWYU pragma: keep @@ -57,8 +57,8 @@ class unicode_helper { protected: using utf8_state = unsigned char; - static const utf8_state s_start = 0; - static const utf8_state s_reject = 8; + static utf8_state const s_start = 0; + static utf8_state const s_reject = 8; static inline bool in_range(std::uint32_t c, std::uint32_t lo, std::uint32_t hi) @@ -83,7 +83,7 @@ protected: static void append(std::string& str, std::uint32_t codepoint); - static utf8_state decode(const utf8_state state, const std::uint8_t fragment, + static utf8_state decode(utf8_state const state, std::uint8_t const fragment, std::uint32_t& codepoint); }; @@ -98,7 +98,7 @@ class unicode::type> { public: // UTF32 -> UTF8 - static std::string to_utf8(const std::wstring& str) + static std::string to_utf8(std::wstring const& str) { std::string result; for (auto c : str) { @@ -114,7 +114,7 @@ public: } // UTF8 -> UTF32 - static std::wstring from_utf8(const std::string& str) + static std::wstring from_utf8(std::string const& str) { std::wstring result; result.reserve(str.length()); @@ -159,7 +159,7 @@ class unicode::type> { public: // UTF16 -> UTF8 - static std::string to_utf8(const std::wstring& str) + static std::string to_utf8(std::wstring const& str) { std::string result; for (auto iter = str.begin(); iter != str.end(); ++iter) { @@ -193,7 +193,7 @@ public: } // UTF8 -> UTF16 - static std::wstring from_utf8(const std::string& str) + static std::wstring from_utf8(std::string const& str) { std::wstring result; result.reserve(str.length()); @@ -254,11 +254,11 @@ template <> class unicode_converter { public: - std::wstring operator()(const std::string& in) + std::wstring operator()(std::string const& in) { return unicode::from_utf8(in); } - std::wstring operator()(const char* in) + std::wstring operator()(char const* in) { return unicode::from_utf8(in); } @@ -268,11 +268,11 @@ template <> class unicode_converter { public: - std::string operator()(const std::wstring& in) + std::string operator()(std::wstring const& in) { return unicode::to_utf8(in); } - std::string operator()(const wchar_t* in) + std::string operator()(wchar_t const* in) { return unicode::to_utf8(in); } @@ -282,16 +282,16 @@ template <> class unicode_converter { public: - std::string operator()(const std::string& in) { return in; } - std::string operator()(const char* in) { return std::string(in); } + std::string operator()(std::string const& in) { return in; } + std::string operator()(char const* in) { return std::string(in); } std::string operator()(char in) { return std::string(1, in); } }; template <> class unicode_converter { public: - std::wstring operator()(const std::wstring& in) { return in; } - std::wstring operator()(const wchar_t* in) { return std::wstring(in); } + std::wstring operator()(std::wstring const& in) { return in; } + std::wstring operator()(wchar_t const* in) { return std::wstring(in); } std::wstring operator()(wchar_t in) { return std::wstring(1, in); } }; @@ -308,33 +308,33 @@ struct string_converter // std::string::string(const string&, const Allocator&) // As workaround, use char* pointer. template - static std::basic_string to(const std::string& in, - const Alloc& a) + static std::basic_string to(std::string const& in, + Alloc const& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template - static std::basic_string to(const char* in, - const Alloc& a) + static std::basic_string to(char const* in, + Alloc const& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template - static std::basic_string to(char in, const Alloc& a) + static std::basic_string to(char in, Alloc const& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template - static std::basic_string to(const std::string& in) + static std::basic_string to(std::string const& in) { return std::basic_string(unicode_converter()(in)); } template - static std::basic_string to(const char* in) + static std::basic_string to(char const* in) { return std::basic_string(unicode_converter()(in)); } @@ -352,33 +352,33 @@ struct string_converter // std::string::string(const string&, const Allocator&) // As workaround, use char* pointer. template - static std::basic_string to(const std::wstring& in, - const Alloc& a) + static std::basic_string to(std::wstring const& in, + Alloc const& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template - static std::basic_string to(const wchar_t* in, - const Alloc& a) + static std::basic_string to(wchar_t const* in, + Alloc const& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template - static std::basic_string to(wchar_t in, const Alloc& a) + static std::basic_string to(wchar_t in, Alloc const& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template - static std::basic_string to(const std::wstring& in) + static std::basic_string to(std::wstring const& in) { return std::basic_string(unicode_converter()(in)); } template - static std::basic_string to(const wchar_t* in) + static std::basic_string to(wchar_t const* in) { return std::basic_string(unicode_converter()(in)); } @@ -451,18 +451,18 @@ struct source_converter } } - static void append_source(std::string& p, const cm::string_view s) + static void append_source(std::string& p, cm::string_view const s) { append_range(p, s.begin(), s.end()); } template static void append_source(std::string& p, - const std::basic_string& s) + std::basic_string const& s) { append_range(p, s.begin(), s.end()); } template - static void append_source(std::string& p, const Source& s) + static void append_source(std::string& p, Source const& s) { append_range(p, s); } @@ -503,12 +503,12 @@ struct source_converter template static void append_source(std::string& p, - const std::basic_string& s) + std::basic_string const& s) { append_range(p, s.begin(), s.end()); } template - static void append_source(std::string& p, const Source& s) + static void append_source(std::string& p, Source const& s) { append_range(p, s); } @@ -641,7 +641,7 @@ public: // Constructors // ============ path() noexcept {} - path(const path& p) + path(path const& p) : path_(p.path_) { } @@ -656,7 +656,7 @@ public: this->path_, std::move(source)); } template > - path(const Source& source, format fmt = auto_format) + path(Source const& source, format fmt = auto_format) { (void)fmt; internals::source_converter< @@ -664,7 +664,7 @@ public: path_type::value_type>::append_source(this->path_, source); } template > - path(const Iterator first, Iterator last, format fmt = auto_format) + path(Iterator const first, Iterator last, format fmt = auto_format) { (void)fmt; internals::source_converter< @@ -676,7 +676,7 @@ public: // Assignments // =========== - path& operator=(const path& p) + path& operator=(path const& p) { if (this != &p) { this->path_ = p.path_; @@ -692,7 +692,7 @@ public: } path& operator=(string_type&& source) { return this->assign(source); } template > - path& operator=(const Source& source) + path& operator=(Source const& source) { return this->assign(source); } @@ -704,7 +704,7 @@ public: return *this; } template > - path& assign(const Source& source) + path& assign(Source const& source) { this->path_.clear(); internals::source_converter< @@ -724,15 +724,15 @@ public: // Concatenation // ============= - path& operator/=(const path& p); + path& operator/=(path const& p); template > - path& append(const Source& source) + path& append(Source const& source) { return this->operator/=(path(source)); } template - path& operator/=(const Source& source) + path& operator/=(Source const& source) { return this->append(source); } @@ -743,12 +743,12 @@ public: return this->operator/=(path(first, last)); } - path& operator+=(const path& p) + path& operator+=(path const& p) { this->path_ += p.path_; return *this; } - path& operator+=(const string_type& str) + path& operator+=(string_type const& str) { this->path_ += internals::string_converter::to(str); @@ -759,20 +759,20 @@ public: this->path_.append(str.begin(), str.end()); return *this; } - path& operator+=(const value_type* str) + path& operator+=(value_type const* str) { this->path_ += internals::string_converter::to(str); return *this; } - path& operator+=(const value_type c) + path& operator+=(value_type const c) { this->path_ += internals::string_converter::to(c); return *this; } template > - path& concat(const Source& source) + path& concat(Source const& source) { internals::source_converter< typename internals::source_traits::value_type, @@ -780,7 +780,7 @@ public: return *this; } template - path& operator+=(const Source& source) + path& operator+=(Source const& source) { return this->concat(source); } @@ -818,14 +818,14 @@ public: return *this; } - path& replace_filename(const path& replacement) + path& replace_filename(path const& replacement) { this->remove_filename(); this->operator/=(replacement); return *this; } - path& replace_extension(const path& replacement = path()) + path& replace_extension(path const& replacement = path()) { auto ext = this->get_filename_fragment(filename_fragment::extension); if (!ext.empty()) { @@ -846,7 +846,7 @@ public: // Format observers // ================ - const string_type& native() const noexcept + string_type const& native() const noexcept { # if defined(_WIN32) && !defined(__CYGWIN__) this->native_path_ = internals::string_converter< @@ -856,7 +856,7 @@ public: return this->path_; # endif } - const value_type* c_str() const noexcept { return this->native().c_str(); } + value_type const* c_str() const noexcept { return this->native().c_str(); } operator string_type() const { return this->native(); } template < @@ -867,13 +867,13 @@ public: (std::is_same::value && std::is_same>::value), int> = 1> - std::basic_string string(const Alloc& a = Alloc()) const + std::basic_string string(Alloc const& a = Alloc()) const { return internals::string_converter::to( this->path_, a); } - const std::string string() const { return this->path_; } + std::string const string() const { return this->path_; } std::wstring wstring() const { std::string out = this->string(); @@ -890,7 +890,7 @@ public: std::is_same>::value), int> = 1> std::basic_string generic_string( - const Alloc& a = Alloc()) const + Alloc const& a = Alloc()) const { return internals::string_converter::to( @@ -906,16 +906,16 @@ public: // Compare // ======= - int compare(const path& p) const noexcept + int compare(path const& p) const noexcept { return this->compare_path(p.path_); } - int compare(const string_type& str) const + int compare(string_type const& str) const { return this->compare_path( internals::string_converter::to(str)); } - int compare(const value_type* str) const + int compare(value_type const* str) const { return this->compare_path( internals::string_converter::to(str)); @@ -926,9 +926,9 @@ public: // ========== path lexically_normal() const; - path lexically_relative(const path& base) const; + path lexically_relative(path const& base) const; - path lexically_proximate(const path& base) const + path lexically_proximate(path const& base) const { path result = this->lexically_relative(base); return result.empty() ? *this : result; @@ -1011,32 +1011,32 @@ public: // Non-members // =========== - friend inline bool operator==(const path& lhs, const path& rhs) noexcept + friend inline bool operator==(path const& lhs, path const& rhs) noexcept { return lhs.compare(rhs) == 0; } - friend inline bool operator!=(const path& lhs, const path& rhs) noexcept + friend inline bool operator!=(path const& lhs, path const& rhs) noexcept { return lhs.compare(rhs) != 0; } - friend inline bool operator<(const path& lhs, const path& rhs) noexcept + friend inline bool operator<(path const& lhs, path const& rhs) noexcept { return lhs.compare(rhs) < 0; } - friend inline bool operator<=(const path& lhs, const path& rhs) noexcept + friend inline bool operator<=(path const& lhs, path const& rhs) noexcept { return lhs.compare(rhs) <= 0; } - friend inline bool operator>(const path& lhs, const path& rhs) noexcept + friend inline bool operator>(path const& lhs, path const& rhs) noexcept { return lhs.compare(rhs) > 0; } - friend inline bool operator>=(const path& lhs, const path& rhs) noexcept + friend inline bool operator>=(path const& lhs, path const& rhs) noexcept { return lhs.compare(rhs) >= 0; } - friend inline path operator/(const path& lhs, const path& rhs) + friend inline path operator/(path const& lhs, path const& rhs) { path result(lhs); result /= rhs; @@ -1052,7 +1052,7 @@ public: std::is_same>::value), std::basic_ostream&> - operator<<(std::basic_ostream& os, const path& p) + operator<<(std::basic_ostream& os, path const& p) { os << cm::quoted(p.string()); return os; @@ -1076,7 +1076,7 @@ public: private: friend class iterator; - friend std::size_t hash_value(const path& p) noexcept; + friend std::size_t hash_value(path const& p) noexcept; path_type get_generic() const; @@ -1102,15 +1102,15 @@ public: using value_type = path; using difference_type = std::ptrdiff_t; - using pointer = const path*; - using reference = const path&; + using pointer = path const*; + using reference = path const&; iterator(); - iterator(const iterator& other); + iterator(iterator const& other); ~iterator(); - iterator& operator=(const iterator& other); + iterator& operator=(iterator const& other); reference operator*() const { return this->path_element_; } @@ -1136,11 +1136,11 @@ public: private: friend class path; - friend bool operator==(const iterator&, const iterator&); + friend bool operator==(iterator const&, iterator const&); - iterator(const path* p, bool at_end = false); + iterator(path const* p, bool at_end = false); - const path* path_; + path const* path_; std::unique_ptr parser_; path path_element_; }; @@ -1156,9 +1156,9 @@ inline path::iterator path::end() const // Non-member functions // ==================== -bool operator==(const path::iterator& lhs, const path::iterator& rhs); +bool operator==(path::iterator const& lhs, path::iterator const& rhs); -inline bool operator!=(const path::iterator& lhs, const path::iterator& rhs) +inline bool operator!=(path::iterator const& lhs, path::iterator const& rhs) { return !(lhs == rhs); } @@ -1168,7 +1168,7 @@ inline void swap(path& lhs, path& rhs) noexcept lhs.swap(rhs); } -std::size_t hash_value(const path& p) noexcept; +std::size_t hash_value(path const& p) noexcept; #endif diff --git a/Utilities/std/cm/forward_list b/Utilities/std/cm/forward_list index 3397a099546..ba7c536b8fc 100644 --- a/Utilities/std/cm/forward_list +++ b/Utilities/std/cm/forward_list @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/iomanip b/Utilities/std/cm/iomanip index 602b9125812..0eda611c62f 100644 --- a/Utilities/std/cm/iomanip +++ b/Utilities/std/cm/iomanip @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export @@ -78,11 +78,11 @@ struct quoted_string template std::basic_ostream& operator<<( std::basic_ostream& os, - const quoted_string& str) + quoted_string const& str) { std::basic_ostringstream ostr; ostr << str.delim_; - for (const Char* c = str.string_; *c; ++c) { + for (Char const* c = str.string_; *c; ++c) { if (*c == str.delim_ || *c == str.escape_) ostr << str.escape_; ostr << *c; @@ -94,7 +94,7 @@ std::basic_ostream& operator<<( template std::basic_ostream& operator<<( - std::basic_ostream& os, const quoted_string& str) + std::basic_ostream& os, quoted_string const& str) { std::basic_ostringstream ostr; ostr << str.delim_; @@ -111,7 +111,7 @@ std::basic_ostream& operator<<( template std::basic_istream& operator>>( std::basic_istream& is, - const quoted_string&, Char>& str) + quoted_string&, Char> const& str) { Char c; is >> c; @@ -144,20 +144,20 @@ std::basic_istream& operator>>( } template -inline internals::quoted_string quoted( - const Char* str, Char delim = Char('"'), Char escape = Char('\\')) +inline internals::quoted_string quoted( + Char const* str, Char delim = Char('"'), Char escape = Char('\\')) { - return internals::quoted_string(str, delim, escape); + return internals::quoted_string(str, delim, escape); } template -inline internals::quoted_string&, +inline internals::quoted_string const&, Char> -quoted(const std::basic_string& str, +quoted(std::basic_string const& str, Char delim = Char('"'), Char escape = Char('\\')) { return internals::quoted_string< - const std::basic_string&, Char>(str, delim, escape); + std::basic_string const&, Char>(str, delim, escape); } template diff --git a/Utilities/std/cm/iterator b/Utilities/std/cm/iterator index 3bfd9474e28..080cf29662a 100644 --- a/Utilities/std/cm/iterator +++ b/Utilities/std/cm/iterator @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/list b/Utilities/std/cm/list index bd02e868bb3..5983f614fff 100644 --- a/Utilities/std/cm/list +++ b/Utilities/std/cm/list @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export @@ -22,7 +22,7 @@ using std::erase_if; #else template -inline void erase(std::list& cont, const V& value) +inline void erase(std::list& cont, V const& value) { cont.remove_if([&](auto& elem) { return elem == value; }); } diff --git a/Utilities/std/cm/map b/Utilities/std/cm/map index 4270d789612..a2de211ec19 100644 --- a/Utilities/std/cm/map +++ b/Utilities/std/cm/map @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/memory b/Utilities/std/cm/memory index 005e6e2954d..7ed15964892 100644 --- a/Utilities/std/cm/memory +++ b/Utilities/std/cm/memory @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include "cmSTL.hxx" // IWYU pragma: keep diff --git a/Utilities/std/cm/optional b/Utilities/std/cm/optional index 2ebc78c0bb3..1577fa074fd 100644 --- a/Utilities/std/cm/optional +++ b/Utilities/std/cm/optional @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) @@ -49,7 +49,7 @@ public: optional() noexcept = default; optional(nullopt_t) noexcept; - optional(const optional& other); + optional(optional const& other); optional(optional&& other) noexcept; template @@ -67,7 +67,7 @@ public: ~optional(); optional& operator=(nullopt_t) noexcept; - optional& operator=(const optional& other); + optional& operator=(optional const& other); template typename std::enable_if::value && @@ -85,21 +85,21 @@ public: optional&>::type operator=(U&& v); - const T* operator->() const; + T const* operator->() const; T* operator->(); - const T& operator*() const&; + T const& operator*() const&; T& operator*() &; - const T&& operator*() const&&; + T const&& operator*() const&&; T&& operator*() &&; explicit operator bool() const noexcept; bool has_value() const noexcept; T& value() &; - const T& value() const&; + T const& value() const&; T&& value() &&; - const T&& value() const&&; + T const&& value() const&&; template T value_or(U&& default_value) const&; @@ -145,7 +145,7 @@ optional::optional(nullopt_t) noexcept } template -optional::optional(const optional& other) +optional::optional(optional const& other) { if (other.has_value()) { this->emplace(*other); @@ -188,7 +188,7 @@ optional& optional::operator=(nullopt_t) noexcept } template -optional& optional::operator=(const optional& other) +optional& optional::operator=(optional const& other) { if (other.has_value()) { if (this->has_value()) { @@ -241,7 +241,7 @@ optional::operator=(U&& v) } template -bool operator==(const optional& lhs, const optional& rhs) +bool operator==(optional const& lhs, optional const& rhs) { if (lhs.has_value()) { return rhs.has_value() && *lhs == *rhs; @@ -250,7 +250,7 @@ bool operator==(const optional& lhs, const optional& rhs) } template -bool operator!=(const optional& lhs, const optional& rhs) +bool operator!=(optional const& lhs, optional const& rhs) { if (lhs.has_value()) { return !rhs.has_value() || *lhs != *rhs; @@ -259,7 +259,7 @@ bool operator!=(const optional& lhs, const optional& rhs) } template -bool operator<(const optional& lhs, const optional& rhs) +bool operator<(optional const& lhs, optional const& rhs) { if (rhs.has_value()) { return !lhs.has_value() || *lhs < *rhs; @@ -268,7 +268,7 @@ bool operator<(const optional& lhs, const optional& rhs) } template -bool operator<=(const optional& lhs, const optional& rhs) +bool operator<=(optional const& lhs, optional const& rhs) { if (!lhs.has_value()) { return true; @@ -280,7 +280,7 @@ bool operator<=(const optional& lhs, const optional& rhs) } template -bool operator>(const optional& lhs, const optional& rhs) +bool operator>(optional const& lhs, optional const& rhs) { if (lhs.has_value()) { return !rhs.has_value() || *lhs > *rhs; @@ -289,7 +289,7 @@ bool operator>(const optional& lhs, const optional& rhs) } template -bool operator>=(const optional& lhs, const optional& rhs) +bool operator>=(optional const& lhs, optional const& rhs) { if (!rhs.has_value()) { return true; @@ -301,151 +301,151 @@ bool operator>=(const optional& lhs, const optional& rhs) } template -bool operator==(const optional& opt, nullopt_t) noexcept +bool operator==(optional const& opt, nullopt_t) noexcept { return !opt.has_value(); } template -bool operator!=(const optional& opt, nullopt_t) noexcept +bool operator!=(optional const& opt, nullopt_t) noexcept { return opt.has_value(); } template -bool operator<(const optional& /*opt*/, nullopt_t) noexcept +bool operator<(optional const& /*opt*/, nullopt_t) noexcept { return false; } template -bool operator<=(const optional& opt, nullopt_t) noexcept +bool operator<=(optional const& opt, nullopt_t) noexcept { return !opt.has_value(); } template -bool operator>(const optional& opt, nullopt_t) noexcept +bool operator>(optional const& opt, nullopt_t) noexcept { return opt.has_value(); } template -bool operator>=(const optional& /*opt*/, nullopt_t) noexcept +bool operator>=(optional const& /*opt*/, nullopt_t) noexcept { return true; } template -bool operator==(nullopt_t, const optional& opt) noexcept +bool operator==(nullopt_t, optional const& opt) noexcept { return !opt.has_value(); } template -bool operator!=(nullopt_t, const optional& opt) noexcept +bool operator!=(nullopt_t, optional const& opt) noexcept { return opt.has_value(); } template -bool operator<(nullopt_t, const optional& opt) noexcept +bool operator<(nullopt_t, optional const& opt) noexcept { return opt.has_value(); } template -bool operator<=(nullopt_t, const optional& /*opt*/) noexcept +bool operator<=(nullopt_t, optional const& /*opt*/) noexcept { return true; } template -bool operator>(nullopt_t, const optional& /*opt*/) noexcept +bool operator>(nullopt_t, optional const& /*opt*/) noexcept { return false; } template -bool operator>=(nullopt_t, const optional& opt) noexcept +bool operator>=(nullopt_t, optional const& opt) noexcept { return !opt.has_value(); } template -bool operator==(const optional& opt, const U& value) +bool operator==(optional const& opt, U const& value) { return opt.has_value() && *opt == value; } template -bool operator!=(const optional& opt, const U& value) +bool operator!=(optional const& opt, U const& value) { return !opt.has_value() || *opt != value; } template -bool operator<(const optional& opt, const U& value) +bool operator<(optional const& opt, U const& value) { return !opt.has_value() || *opt < value; } template -bool operator<=(const optional& opt, const U& value) +bool operator<=(optional const& opt, U const& value) { return !opt.has_value() || *opt <= value; } template -bool operator>(const optional& opt, const U& value) +bool operator>(optional const& opt, U const& value) { return opt.has_value() && *opt > value; } template -bool operator>=(const optional& opt, const U& value) +bool operator>=(optional const& opt, U const& value) { return opt.has_value() && *opt >= value; } template -bool operator==(const T& value, const optional& opt) +bool operator==(T const& value, optional const& opt) { return opt.has_value() && value == *opt; } template -bool operator!=(const T& value, const optional& opt) +bool operator!=(T const& value, optional const& opt) { return !opt.has_value() || value != *opt; } template -bool operator<(const T& value, const optional& opt) +bool operator<(T const& value, optional const& opt) { return opt.has_value() && value < *opt; } template -bool operator<=(const T& value, const optional& opt) +bool operator<=(T const& value, optional const& opt) { return opt.has_value() && value <= *opt; } template -bool operator>(const T& value, const optional& opt) +bool operator>(T const& value, optional const& opt) { return !opt.has_value() || value > *opt; } template -bool operator>=(const T& value, const optional& opt) +bool operator>=(T const& value, optional const& opt) { return !opt.has_value() || value >= *opt; } template -const T* optional::operator->() const +T const* optional::operator->() const { return &**this; } @@ -457,7 +457,7 @@ T* optional::operator->() } template -const T& optional::operator*() const& +T const& optional::operator*() const& { return this->_mem.value; } @@ -469,7 +469,7 @@ T& optional::operator*() & } template -const T&& optional::operator*() const&& +T const&& optional::operator*() const&& { return std::move(**this); } @@ -502,7 +502,7 @@ T& optional::value() & } template -const T& optional::value() const& +T const& optional::value() const& { if (!this->has_value()) { throw cm::bad_optional_access{}; diff --git a/Utilities/std/cm/set b/Utilities/std/cm/set index 70e2c49e950..018cc3c6bd7 100644 --- a/Utilities/std/cm/set +++ b/Utilities/std/cm/set @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/shared_mutex b/Utilities/std/cm/shared_mutex index a1204fa5571..b0544a5b60c 100644 --- a/Utilities/std/cm/shared_mutex +++ b/Utilities/std/cm/shared_mutex @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #if __cplusplus >= 201402L || defined(_MSVC_LANG) && _MSVC_LANG >= 201402L diff --git a/Utilities/std/cm/string b/Utilities/std/cm/string index d3d899fb716..e92a56aa258 100644 --- a/Utilities/std/cm/string +++ b/Utilities/std/cm/string @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -24,7 +24,7 @@ using std::erase_if; template inline void erase(std::basic_string& cont, - const V& value) + V const& value) { cont.erase(std::remove(cont.begin(), cont.end(), value), cont.end()); } diff --git a/Utilities/std/cm/string_view b/Utilities/std/cm/string_view index 320e1e7eb15..05618bf168f 100644 --- a/Utilities/std/cm/string_view +++ b/Utilities/std/cm/string_view @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #if __cplusplus >= 201703L || defined(_MSVC_LANG) && _MSVC_LANG >= 201703L @@ -31,10 +31,10 @@ public: using traits_type = std::string::traits_type; using value_type = char; using pointer = char*; - using const_pointer = const char*; + using const_pointer = char const*; using reference = char&; using const_reference = char const&; - using const_iterator = const char*; + using const_iterator = char const*; using iterator = const_iterator; using const_reverse_iterator = std::reverse_iterator; using reverse_iterator = const_reverse_iterator; @@ -46,13 +46,13 @@ public: string_view() noexcept = default; string_view(string_view const&) noexcept = default; - string_view(const char* s, size_t count) noexcept + string_view(char const* s, size_t count) noexcept : data_(s) , size_(count) { } - string_view(const char* s) noexcept + string_view(char const* s) noexcept : data_(s) , size_(traits_type::length(s)) { @@ -126,46 +126,46 @@ public: int compare(size_type pos1, size_type count1, string_view v) const; int compare(size_type pos1, size_type count1, string_view v, size_type pos2, size_type count2) const; - int compare(const char* s) const; - int compare(size_type pos1, size_type count1, const char* s) const; - int compare(size_type pos1, size_type count1, const char* s, + int compare(char const* s) const; + int compare(size_type pos1, size_type count1, char const* s) const; + int compare(size_type pos1, size_type count1, char const* s, size_type count2) const; size_type find(string_view v, size_type pos = 0) const noexcept; size_type find(char c, size_type pos = 0) const noexcept; - size_type find(const char* s, size_type pos, size_type count) const; - size_type find(const char* s, size_type pos = 0) const; + size_type find(char const* s, size_type pos, size_type count) const; + size_type find(char const* s, size_type pos = 0) const; size_type rfind(string_view v, size_type pos = npos) const noexcept; size_type rfind(char c, size_type pos = npos) const noexcept; - size_type rfind(const char* s, size_type pos, size_type count) const; - size_type rfind(const char* s, size_type pos = npos) const; + size_type rfind(char const* s, size_type pos, size_type count) const; + size_type rfind(char const* s, size_type pos = npos) const; size_type find_first_of(string_view v, size_type pos = 0) const noexcept; size_type find_first_of(char c, size_type pos = 0) const noexcept; - size_type find_first_of(const char* s, size_type pos, size_type count) const; - size_type find_first_of(const char* s, size_type pos = 0) const; + size_type find_first_of(char const* s, size_type pos, size_type count) const; + size_type find_first_of(char const* s, size_type pos = 0) const; size_type find_last_of(string_view v, size_type pos = npos) const noexcept; size_type find_last_of(char c, size_type pos = npos) const noexcept; - size_type find_last_of(const char* s, size_type pos, size_type count) const; - size_type find_last_of(const char* s, size_type pos = npos) const; + size_type find_last_of(char const* s, size_type pos, size_type count) const; + size_type find_last_of(char const* s, size_type pos = npos) const; size_type find_first_not_of(string_view v, size_type pos = 0) const noexcept; size_type find_first_not_of(char c, size_type pos = 0) const noexcept; - size_type find_first_not_of(const char* s, size_type pos, + size_type find_first_not_of(char const* s, size_type pos, size_type count) const; - size_type find_first_not_of(const char* s, size_type pos = 0) const; + size_type find_first_not_of(char const* s, size_type pos = 0) const; size_type find_last_not_of(string_view v, size_type pos = npos) const noexcept; size_type find_last_not_of(char c, size_type pos = npos) const noexcept; - size_type find_last_not_of(const char* s, size_type pos, + size_type find_last_not_of(char const* s, size_type pos, size_type count) const; - size_type find_last_not_of(const char* s, size_type pos = npos) const; + size_type find_last_not_of(char const* s, size_type pos = npos) const; private: - const char* data_ = nullptr; + char const* data_ = nullptr; size_type size_ = 0; }; diff --git a/Utilities/std/cm/type_traits b/Utilities/std/cm/type_traits index 56ec64fe46e..6bb6b5c7b34 100644 --- a/Utilities/std/cm/type_traits +++ b/Utilities/std/cm/type_traits @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export @@ -23,7 +23,7 @@ using enable_if_t = typename std::enable_if::type; #endif -#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703) +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) // Helper classes using std::bool_constant; @@ -57,4 +57,33 @@ using void_t = typename make_void::type; #endif +#if (__cplusplus >= 202302L || \ + (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)) && \ + __cpp_lib_is_scoped_enum == 202011L + +using std::is_scoped_enum; + +#else + +namespace internals { +template ::value> +struct is_scoped_enum_helper : std::false_type +{ +}; + +template +struct is_scoped_enum_helper + : public cm::bool_constant< + !std::is_convertible::type>::value> +{ +}; +} + +template +struct is_scoped_enum : public internals::is_scoped_enum_helper +{ +}; + +#endif + } // namespace cm diff --git a/Utilities/std/cm/unordered_map b/Utilities/std/cm/unordered_map index 0b085f37624..5c30bd83a23 100644 --- a/Utilities/std/cm/unordered_map +++ b/Utilities/std/cm/unordered_map @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/unordered_set b/Utilities/std/cm/unordered_set index 05930510f22..acaa3a87824 100644 --- a/Utilities/std/cm/unordered_set +++ b/Utilities/std/cm/unordered_set @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include // IWYU pragma: export diff --git a/Utilities/std/cm/utility b/Utilities/std/cm/utility index c257fc80689..a81a99a322d 100644 --- a/Utilities/std/cm/utility +++ b/Utilities/std/cm/utility @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) diff --git a/Utilities/std/cm/vector b/Utilities/std/cm/vector index efd44042eaf..5f6dd3d69a2 100644 --- a/Utilities/std/cm/vector +++ b/Utilities/std/cm/vector @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -23,7 +23,7 @@ using std::erase_if; #else template -inline void erase(std::vector& cont, const V& value) +inline void erase(std::vector& cont, V const& value) { cont.erase(std::remove(cont.begin(), cont.end(), value), cont.end()); } diff --git a/Utilities/std/cmSTL.hxx.in b/Utilities/std/cmSTL.hxx.in index 5e94864de5e..65d631211db 100644 --- a/Utilities/std/cmSTL.hxx.in +++ b/Utilities/std/cmSTL.hxx.in @@ -1,5 +1,5 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once /* Whether CMake is using its own STL implementation. */ diff --git a/Utilities/std/cmext/algorithm b/Utilities/std/cmext/algorithm index 11514fcf7b0..cc85b0ae251 100644 --- a/Utilities/std/cmext/algorithm +++ b/Utilities/std/cmext/algorithm @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -16,6 +16,7 @@ #if defined(__SUNPRO_CC) && defined(__sparc) # include +# include # include #endif @@ -67,11 +68,15 @@ namespace cm { APPEND_TWO(C1, C2) \ APPEND_TWO(C2, C1) -// For now, manage only support for std::vector and std::list. -// Other sequential container support can be added if needed. +// For now, manage only support for std::vector, std::list, and +// std::basic_string. Other sequential container support can be added if +// needed. APPEND(std::vector) APPEND(std::list) +APPEND(std::basic_string) APPEND_MIX(std::vector, std::list) +APPEND_MIX(std::vector, std::basic_string) +APPEND_MIX(std::list, std::basic_string) # undef APPEND # undef APPEND_MIX @@ -113,7 +118,7 @@ void append(Container1& v, Container2 const& r) { std::transform( r.begin(), r.end(), std::back_inserter(v), - [](const typename Container2::value_type& item) { return item.get(); }); + [](typename Container2::value_type const& item) { return item.get(); }); } template < @@ -207,8 +212,9 @@ bool contains(Iterator first, Iterator last, Key const& key) #if defined(__SUNPRO_CC) template -auto contains(Range const& range, Key const& key, detail::overload_selector<1>) - -> decltype(range.find(key) != range.end()) +auto contains(Range const& range, Key const& key, + detail::overload_selector<1>) -> decltype(range.find(key) != + range.end()) #else template < typename Range, typename Key, diff --git a/Utilities/std/cmext/enum_set b/Utilities/std/cmext/enum_set index d7b8b39b8ed..0f83d72a7eb 100644 --- a/Utilities/std/cmext/enum_set +++ b/Utilities/std/cmext/enum_set @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -15,15 +15,28 @@ #include // -// Class enum_set offers the capability to manage a set of enum values -// Only 'enum class' with unsigned base type are supported. +// Class enum_set offers the capability to manage a set of enum values. +// Only the 'enum class' type with unsigned base type is supported. Moreover, +// all definitions must be specified without a value. // // The methods offered by 'enum_set' are close as possible to the 'std::set' -// container plus some useful methods from 'std::bitset' like 'flip'. +// container as well as the methods from 'std::bitset'. // // Internally, this class use 'std::bitset' container to manage the -// set of enum. The size of the bitset is deduced from the underlying type of -// the enum. +// set of enum. +// +// The size of the bitset is deduced from the underlying type of +// the enum or can be set explicitly as template parameter: +// +// enum class Example : unsigned { A, B, C, D }; +// using ExampleSet = enum_set; +// +// To facilitate the usage of the enum_set, operators '+' and '|' can be used +// as alternate to the 'initializer_list': +// +// auto set1 = Example::A | Example::B | Example::C; +// auto set2 = Example::A + Example::B; +// set2.set(Example::C | Example::D); // namespace cm { @@ -33,7 +46,7 @@ class enum_set_iterator { public: enum_set_iterator() = default; - enum_set_iterator(const enum_set_iterator& other) = default; + enum_set_iterator(enum_set_iterator const& other) = default; using iterator_category = std::bidirectional_iterator_tag; using value_type = typename EnumSet::value_type; @@ -111,32 +124,51 @@ private: template < typename Enum, + std::size_t Size = + std::numeric_limits::type>::digits, typename cm::enable_if_t< - std::is_enum::value && + cm::is_scoped_enum::value && std::is_unsigned::type>::value, int> = 0> class enum_set { public: + static constexpr std::size_t set_size = Size; + using key_type = Enum; using value_type = Enum; using size_type = typename std::underlying_type::type; using difference_type = size_type; using reference = Enum; using const_reference = Enum; - using pointer = const Enum*; - using const_pointer = const Enum*; + using pointer = Enum const*; + using const_pointer = Enum const*; using iterator = enum_set_iterator; - using const_iterator = enum_set_iterator; + using const_iterator = enum_set_iterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - constexpr enum_set() noexcept = default; - enum_set(const enum_set& other) noexcept { this->insert(other); } + constexpr enum_set() noexcept {} + enum_set(key_type e) { this->insert(e); } + enum_set(enum_set const& other) noexcept { this->insert(other); } + template ::value, int> = 0> + enum_set(enum_set const& other) noexcept + { + static_assert(Size < enum_set::set_size, "Incompatible sizes"); + + this->insert(other.cbegin(), other.cend()); + } enum_set(std::initializer_list list) { this->insert(list); } - enum_set& operator=(const enum_set& other) noexcept + enum_set& operator=(key_type e) + { + this->Set.reset(); + this->insert(e); + return *this; + } + enum_set& operator=(enum_set const& other) noexcept { this->Set.reset(); this->Set |= other.Set; @@ -186,41 +218,116 @@ public: size_type max_size() const noexcept { return this->Set.size(); } // Modifiers - void clear() noexcept { this->Set.reset(); } - enum_set& operator+=(key_type e) + // set all elements + enum_set& set() + { + this->Set.set(); + return *this; + } + enum_set& set(key_type e) { this->insert(e); return *this; } - enum_set& operator+=(const enum_set& other) noexcept + enum_set& set(enum_set const& other) noexcept { - this->erase(other); + this->insert(other); return *this; } - enum_set& operator+=(std::initializer_list list) + enum_set& set(std::initializer_list list) { this->insert(list); return *this; } + // alternate syntax for bit set + enum_set& operator+=(key_type e) { return this->set(e); } + enum_set& operator+=(enum_set const& other) noexcept + { + return this->set(other); + } + enum_set& operator+=(std::initializer_list list) + { + return this->set(list); + } + // alternate syntax for bit set + enum_set& operator|=(key_type e) { return this->set(e); } + enum_set& operator|=(enum_set const& other) noexcept + { + return this->set(other); + } + enum_set& operator|=(std::initializer_list list) + { + return this->set(list); + } - enum_set& operator-=(key_type e) + // reset all elements + void clear() noexcept { this->Set.reset(); } + enum_set& reset() + { + this->Set.reset(); + return *this; + } + enum_set& reset(key_type e) { this->erase(e); return *this; } - enum_set& operator-=(const enum_set& other) noexcept + enum_set& reset(enum_set const& other) noexcept { this->erase(other); return *this; } - enum_set& operator-=(std::initializer_list list) + enum_set& reset(std::initializer_list list) { this->erase(list); return *this; } + // alternate syntax for bit reset + enum_set& operator-=(key_type e) { return this->reset(e); } + enum_set& operator-=(enum_set const& other) noexcept + { + return this->reset(other); + } + enum_set& operator-=(std::initializer_list list) + { + return this->reset(list); + } - std::pair insert(value_type value) + // toggle the specified enum + enum_set& flip(key_type e) + { + this->Set.flip(static_cast(e)); + return *this; + } + // toggle all the enums stored in the other enum_set + enum_set& flip(enum_set const& other) noexcept + { + this->Set ^= other.Set; + return *this; + } + // toggle all the enums specified in the list + enum_set& flip(std::initializer_list list) + { + for (auto e : list) { + this->Set.flip(static_cast(e)); + } + return *this; + } + // alternate syntax for bit toggle + enum_set& operator^=(key_type key) { return this->flip(key); } + // toggle all the enums stored in the other enum_set + enum_set& operator^=(enum_set const& other) noexcept + { + return this->flip(other); + } + // toggle all the enums specified in the list + enum_set& operator^=(std::initializer_list list) + { + return this->flip(list); + } + + std::pair insert(key_type value) { auto exist = this->contains(value); if (!exist) { @@ -236,7 +343,7 @@ public: this->insert(*i); } } - void insert(const enum_set& other) noexcept { this->Set |= other.Set; } + void insert(enum_set const& other) noexcept { this->Set |= other.Set; } void insert(std::initializer_list list) { for (auto e : list) { @@ -265,7 +372,7 @@ public: return pos == this->cend() ? this->end() : iterator(this, static_cast(*pos)); } - void erase(const enum_set& other) noexcept { this->Set &= ~other.Set; } + void erase(enum_set const& other) noexcept { this->Set &= ~other.Set; } void erase(std::initializer_list list) { for (auto e : list) { @@ -280,18 +387,6 @@ public: other.Set = tmp; } - // toggle the specified enum - void flip(key_type key) { this->Set.flip(static_cast(key)); } - // toggle all the enums stored in the other enum_set - void flip(const enum_set& other) noexcept { this->Set ^= other.Set; } - // toggle all the enums specified in the list - void flip(std::initializer_list list) - { - for (auto e : list) { - this->Set.flip(static_cast(e)); - } - } - // Lookup size_type count(key_type e) const { return this->contains(e) ? 1 : 0; } @@ -310,86 +405,152 @@ public: return this->end(); } + // Checks bool contains(key_type e) const { return this->Set.test(static_cast(e)); } + bool all() const { return this->Set.all(); } + bool any() const { return this->Set.any(); } + bool none() const { return this->Set.none(); } + // alternate syntax to none() + bool operator!() const { return this->Set.none(); } + + bool all_of(enum_set const& set) const + { + auto result = set; + result.Set &= this->Set; + return result == set; + } + bool any_of(enum_set const& set) const + { + auto result = set; + result.Set &= this->Set; + return result.any(); + } + bool none_of(enum_set const& set) const + { + auto result = set; + result.Set &= this->Set; + return result.none(); + } + private: - template - friend inline bool operator==(const enum_set& lhs, - const enum_set& rhs) noexcept; + template + friend inline bool operator==(enum_set const& lhs, + enum_set const& rhs) noexcept; - template - friend inline void erase_if(enum_set& set, Predicate pred); + template + friend inline void erase_if(enum_set& set, Predicate pred); friend class enum_set_iterator; - friend class enum_set_iterator; + friend class enum_set_iterator; bool test(size_type pos) const { return this->Set.test(pos); } - std::bitset::digits> Set; + std::bitset Set; }; // non-member functions for enum_set -template -inline enum_set operator+(const enum_set& lhs, Enum rhs) +template +inline enum_set operator+(enum_set const& lhs, + Enum rhs) { - return enum_set(lhs) += rhs; + return enum_set{ lhs } += rhs; } -template -inline enum_set operator+(const enum_set& lhs, - const enum_set& rhs) noexcept +template +inline enum_set operator+(enum_set const& lhs, + enum_set const& rhs) noexcept { - return enum_set(lhs) += rhs; + return enum_set{ lhs } += rhs; } -template -inline enum_set operator+(const enum_set& lhs, - const std::initializer_list rhs) +template +inline enum_set operator+(enum_set const& lhs, + std::initializer_list const rhs) { - return enum_set(lhs) += rhs; + return enum_set{ lhs } += rhs; } -template -inline enum_set operator-(const enum_set& lhs, Enum rhs) +template +inline cm::enum_set operator|(cm::enum_set const& lhs, + Enum rhs) { - return enum_set(lhs) -= rhs; + return enum_set{ lhs } |= rhs; } -template -inline enum_set operator-(const enum_set& lhs, - const enum_set& rhs) noexcept +template +inline cm::enum_set operator|(Enum lhs, + cm::enum_set const& rhs) { - return enum_set(lhs) -= rhs; + return enum_set{ lhs } |= rhs; } -template -inline enum_set operator-(const enum_set& lhs, - const std::initializer_list rhs) +template +inline cm::enum_set operator|(cm::enum_set const& lhs, + cm::enum_set const& rhs) { - return enum_set(lhs) -= rhs; + return enum_set{ lhs } |= rhs; } -template -inline bool operator==(const enum_set& lhs, - const enum_set& rhs) noexcept +template +inline enum_set operator-(enum_set const& lhs, + Enum rhs) +{ + return enum_set{ lhs } -= rhs; +} +template +inline enum_set operator-(enum_set const& lhs, + enum_set const& rhs) noexcept +{ + return enum_set{ lhs } -= rhs; +} +template +inline enum_set operator-(enum_set const& lhs, + std::initializer_list const rhs) +{ + return enum_set{ lhs } -= rhs; +} + +template +inline enum_set operator^(enum_set const& lhs, + Enum rhs) +{ + return enum_set{ lhs } ^= rhs; +} +template +inline enum_set operator^(enum_set const& lhs, + enum_set const& rhs) noexcept +{ + return enum_set{ lhs } ^= rhs; +} +template +inline enum_set operator^(enum_set const& lhs, + std::initializer_list const rhs) +{ + return enum_set{ lhs } ^= rhs; +} + +template +inline bool operator==(enum_set const& lhs, + enum_set const& rhs) noexcept { return lhs.Set == rhs.Set; } -template -inline bool operator!=(const enum_set& lhs, - const enum_set& rhs) noexcept +template +inline bool operator!=(enum_set const& lhs, + enum_set const& rhs) noexcept { return !(lhs == rhs); } -template -inline void erase(enum_set& set, Enum value) +template +inline void erase(enum_set& set, Enum value) { set.erase(value); } -template -inline void erase_if(enum_set& set, Predicate pred) +template +inline void erase_if(enum_set& set, Predicate pred) { for (std::size_t index = 0; index < set.Set.size(); ++index) { if (set.Set.test(index) && pred(static_cast(index))) { @@ -398,3 +559,81 @@ inline void erase_if(enum_set& set, Predicate pred) } } } // namespace cm + +// +// WARNING: the following two operators rely on the enum_set_traits +// struct definition. +// The macro CM_ENUM_SET_TRAITS(EnumSet) can be used to define this structure. +// +// Notes: +// When CM_ENUM_SET_TRAITS is used, the following restrictions applies: +// * Due to language constraints, the enum_set_traits specialization must +// occur outside of any namespace or function definition. +// * Only one enum_set instantiation is supported per enum class type. +// + +template +struct cm_enum_set_traits +{ +}; + +namespace cm { +template > +struct is_enum_set : std::false_type +{ +}; +template +struct is_enum_set::type>> + : std::true_type +{ +}; +} + +#if defined(__SUNPRO_CC) && defined(__sparc) +// Oracle DeveloperStudio C++ compiler on Solaris/Sparc crash on the following +// template declarations, so declare explicitly the operators. + +// Helper macro to define the enum_set_traits struct specialization. +# define CM_ENUM_SET_TRAITS(E) \ + template <> \ + struct cm_enum_set_traits \ + { \ + using type = E; \ + using value_type = E::value_type; \ + }; \ + \ + inline E operator+(E::value_type lhs, E::value_type rhs) \ + { \ + return { lhs, rhs }; \ + } \ + \ + inline E operator|(E::value_type lhs, E::value_type rhs) \ + { \ + return { lhs, rhs }; \ + } + +#else + +// Helper macro to define the enum_set_traits struct specialization. +# define CM_ENUM_SET_TRAITS(E) \ + template <> \ + struct cm_enum_set_traits \ + { \ + using type = E; \ + using value_type = E::value_type; \ + }; + +template ::value, int> = 0> +inline typename cm_enum_set_traits::type operator+(Enum lhs, Enum rhs) +{ + return { lhs, rhs }; +} +// Alternate syntax +template ::value, int> = 0> +inline typename cm_enum_set_traits::type operator|(Enum lhs, Enum rhs) +{ + return { lhs, rhs }; +} +#endif diff --git a/Utilities/std/cmext/iterator b/Utilities/std/cmext/iterator index eba10ddf74b..ad03cbc0fa2 100644 --- a/Utilities/std/cmext/iterator +++ b/Utilities/std/cmext/iterator @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -24,9 +24,18 @@ using is_input_iterator = // checks if a type is a range type: std::begin() and std::end() are supported template -using is_range = cm::bool_constant< - cm::is_iterator()))>::value && - cm::is_iterator()))>::value>; +using is_range = +#if defined(_MSC_VER) && _MSC_VER < 1920 + // MS C++ is not able to evaluate complex type introspection, + // so use a simplified version + cm::bool_constant::value || + std::is_array::value>; +#else + cm::bool_constant< + cm::is_iterator()))>::value && + cm::is_iterator()))>::value>; +#endif // checks if a type is an input range type: std::begin() and std::end() are // returning an input iterator @@ -40,9 +49,9 @@ using is_input_range = std::is_array::value>; #else cm::bool_constant()))>::value && + std::declval()))>::value && cm::is_input_iterator()))>::value>; + std::declval()))>::value>; #endif } // namespace cm diff --git a/Utilities/std/cmext/memory b/Utilities/std/cmext/memory index 3681d975239..2a7abe5c7ff 100644 --- a/Utilities/std/cmext/memory +++ b/Utilities/std/cmext/memory @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include diff --git a/Utilities/std/cmext/string_view b/Utilities/std/cmext/string_view index 369cc90dc15..29a1cf91736 100644 --- a/Utilities/std/cmext/string_view +++ b/Utilities/std/cmext/string_view @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -25,11 +25,11 @@ class static_string_view : public string_view { } - friend static_string_view operator"" _s(const char* data, size_t size); + friend static_string_view operator"" _s(char const* data, size_t size); }; /** Create a static_string_view using `""_s` literal syntax. */ -inline static_string_view operator"" _s(const char* data, size_t size) +inline static_string_view operator"" _s(char const* data, size_t size) { return string_view(data, size); } diff --git a/Utilities/std/cmext/type_traits b/Utilities/std/cmext/type_traits index 4468e3106da..07fa448658a 100644 --- a/Utilities/std/cmext/type_traits +++ b/Utilities/std/cmext/type_traits @@ -2,7 +2,7 @@ // vim: set ft=cpp: /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying - file Copyright.txt or https://cmake.org/licensing for details. */ + file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once #include @@ -82,4 +82,30 @@ using is_sequence_container = !cm::is_associative_container::value && !cm::is_unordered_associative_container::value>; +template +struct remove_member_pointer +{ + typedef T type; +}; +template +struct remove_member_pointer +{ + typedef T type; +}; +template +using remove_member_pointer_t = typename remove_member_pointer::type; + +template +struct member_pointer_class +{ + typedef T type; +}; +template +struct member_pointer_class +{ + typedef T type; +}; +template +using member_pointer_class_t = typename member_pointer_class::type; + } // namespace cm diff --git a/bootstrap b/bootstrap index fe10bd1fb52..62d3fb3712e 100755 --- a/bootstrap +++ b/bootstrap @@ -1,6 +1,6 @@ #!/bin/sh # Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. +# file LICENSE.rst or https://cmake.org/licensing for details. die() { echo "$@" 1>&2 ; exit 1 @@ -69,7 +69,8 @@ if test "$cmake_version_rc" != ""; then cmake_version="${cmake_version}-rc${cmake_version_rc}" fi -cmake_copyright="`grep '^Copyright .* Kitware' "${cmake_source_dir}/Copyright.txt"`" +cmake_copyright="`grep '^Copyright .* Kitware' "${cmake_source_dir}/LICENSE.rst" | + sed 's/\`Contributors.*\`_/Contributors/'`" cmake_bin_dir_keyword="OTHER" cmake_data_dir_keyword="OTHER" @@ -256,8 +257,8 @@ cmake_doc_dir_default="`cmake_install_dest_default DOC ${cmake_doc_dir_keyword}` cmake_man_dir_default="`cmake_install_dest_default MAN ${cmake_man_dir_keyword}`" cmake_xdgdata_dir_default="`cmake_install_dest_default XDGDATA ${cmake_xdgdata_dir_keyword}`" -CMAKE_KNOWN_C_COMPILERS="cc gcc clang xlc icc tcc" -CMAKE_KNOWN_CXX_COMPILERS="aCC xlC CC g++ clang++ c++ icc como " +CMAKE_KNOWN_C_COMPILERS="cc gcc clang xlc icx tcc" +CMAKE_KNOWN_CXX_COMPILERS="aCC xlC CC g++ clang++ c++ icpx" CMAKE_KNOWN_MAKE_PROCESSORS="gmake make smake" CMAKE_KNOWN_NINJA_PROCESSORS="ninja-build ninja samu" @@ -311,6 +312,7 @@ CMAKE_CXX_SOURCES="\ cmBlockCommand \ cmBreakCommand \ cmBuildCommand \ + cmBuildDatabase \ cmCMakeLanguageCommand \ cmCMakeMinimumRequired \ cmList \ @@ -319,15 +321,12 @@ CMAKE_CXX_SOURCES="\ cmCMakePolicyCommand \ cmCPackPropertiesGenerator \ cmCacheManager \ - cmCommand \ - cmCommandArgumentParserHelper \ cmCommands \ cmCommonTargetGenerator \ cmComputeComponentGraph \ cmComputeLinkDepends \ cmComputeLinkInformation \ cmComputeTargetDepends \ - cmConsoleBuf \ cmConditionEvaluator \ cmConfigureFileCommand \ cmContinueCommand \ @@ -338,6 +337,7 @@ CMAKE_CXX_SOURCES="\ cmCustomCommandGenerator \ cmCustomCommandLines \ cmCxxModuleMapper \ + cmCxxModuleUsageEffects \ cmDefinePropertyCommand \ cmDefinitions \ cmDocumentationFormatter \ @@ -349,14 +349,18 @@ CMAKE_CXX_SOURCES="\ cmExecuteProcessCommand \ cmExpandedCommandArgument \ cmExperimental \ + cmExportBuildCMakeConfigGenerator \ cmExportBuildFileGenerator \ + cmExportCMakeConfigGenerator \ cmExportFileGenerator \ + cmExportInstallCMakeConfigGenerator \ cmExportInstallFileGenerator \ cmExportSet \ cmExportTryCompileFileGenerator \ cmExprParserHelper \ cmExternalMakefileProjectGenerator \ cmFileCommand \ + cmFileCommand_ReadMacho \ cmFileCopier \ cmFileInstaller \ cmFileSet \ @@ -368,6 +372,7 @@ CMAKE_CXX_SOURCES="\ cmFindFileCommand \ cmFindLibraryCommand \ cmFindPackageCommand \ + cmFindPackageStack \ cmFindPathCommand \ cmFindProgramCommand \ cmForEachCommand \ @@ -375,8 +380,9 @@ CMAKE_CXX_SOURCES="\ cmFunctionCommand \ cmFSPermissions \ cmGeneratedFileStream \ + cmGenExContext \ + cmGenExEvaluation \ cmGeneratorExpression \ - cmGeneratorExpressionContext \ cmGeneratorExpressionDAGChecker \ cmGeneratorExpressionEvaluationFile \ cmGeneratorExpressionEvaluator \ @@ -384,6 +390,14 @@ CMAKE_CXX_SOURCES="\ cmGeneratorExpressionNode \ cmGeneratorExpressionParser \ cmGeneratorTarget \ + cmGeneratorTarget_CompatibleInterface \ + cmGeneratorTarget_IncludeDirectories \ + cmGeneratorTarget_Link \ + cmGeneratorTarget_LinkDirectories \ + cmGeneratorTarget_Options \ + cmGeneratorTarget_Sources \ + cmGeneratorTarget_TargetPropertyEntry \ + cmGeneratorTarget_TransitiveProperty \ cmGetCMakePropertyCommand \ cmGetDirectoryPropertyCommand \ cmGetFilenameComponentCommand \ @@ -397,10 +411,12 @@ CMAKE_CXX_SOURCES="\ cmGlobVerificationManager \ cmHexFileConverter \ cmIfCommand \ + cmImportedCxxModuleInfo \ cmIncludeCommand \ cmIncludeGuardCommand \ cmIncludeDirectoryCommand \ cmIncludeRegularExpressionCommand \ + cmInstallCMakeConfigExportGenerator \ cmInstallCommand \ cmInstallCommandArguments \ cmInstallCxxModuleBmiGenerator \ @@ -419,6 +435,8 @@ CMAKE_CXX_SOURCES="\ cmInstallTargetGenerator \ cmInstallTargetsCommand \ cmInstalledFile \ + cmJSONHelpers \ + cmJSONState \ cmLDConfigLDConfigTool \ cmLDConfigTool \ cmLinkDirectoriesCommand \ @@ -445,6 +463,7 @@ CMAKE_CXX_SOURCES="\ cmOutputConverter \ cmParseArgumentsCommand \ cmPathLabel \ + cmPathResolver \ cmPolicies \ cmProcessOutput \ cmProjectCommand \ @@ -454,7 +473,9 @@ CMAKE_CXX_SOURCES="\ cmGccDepfileLexerHelper \ cmGccDepfileReader \ cmReturnCommand \ + cmPackageInfoReader \ cmPlaceholderExpander \ + cmPlistParser \ cmRulePlaceholderExpander \ cmRuntimeDependencyArchive \ cmScriptGenerator \ @@ -473,6 +494,10 @@ CMAKE_CXX_SOURCES="\ cmState \ cmStateDirectory \ cmStateSnapshot \ + cmStdIoConsole \ + cmStdIoInit \ + cmStdIoStream \ + cmStdIoTerminal \ cmString \ cmStringAlgorithms \ cmStringReplaceHelper \ @@ -491,6 +516,7 @@ CMAKE_CXX_SOURCES="\ cmTargetPropCommandBase \ cmTargetPropertyComputer \ cmTargetSourcesCommand \ + cmTargetTraceDependencies \ cmTest \ cmTestGenerator \ cmTimestamp \ @@ -504,11 +530,19 @@ CMAKE_CXX_SOURCES="\ cmWhileCommand \ cmWindowsRegistry \ cmWorkingDirectory \ + cmXcFramework \ cmake \ cmakemain \ cmcmd \ + cm_fileno \ " +if ${cmake_system_darwin}; then + CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES}\ + cmMachO \ + " +fi + if ${cmake_system_mingw} || ${UNAME_OVERRIDE}; then CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES}\ cmGlobalMSYSMakefileGenerator \ @@ -517,6 +551,10 @@ if ${cmake_system_mingw} || ${UNAME_OVERRIDE}; then " fi +CMAKE_C_SOURCES="\ + cm_utf8 \ +" + CMAKE_STD_CXX_HEADERS="\ filesystem \ memory \ @@ -531,8 +569,6 @@ CMAKE_STD_CXX_SOURCES="\ " LexerParser_CXX_SOURCES="\ - cmCommandArgumentLexer \ - cmCommandArgumentParser \ cmExprLexer \ cmExprParser \ cmGccDepfileLexer \ @@ -548,14 +584,14 @@ if ${cmake_system_mingw} || ${UNAME_OVERRIDE}; then ProcessWin32 \ String \ System \ - Terminal" + " else KWSYS_C_SOURCES="\ EncodingC \ ProcessUNIX \ String \ System \ - Terminal" + " fi KWSYS_CXX_SOURCES="\ @@ -579,7 +615,7 @@ KWSYS_FILES="\ String.h \ System.h \ SystemTools.hxx \ - Terminal.h" + " LIBRHASH_C_SOURCES="\ librhash/algorithms.c \ @@ -591,6 +627,13 @@ LIBRHASH_C_SOURCES="\ librhash/sha256.c \ librhash/sha3.c \ librhash/sha512.c \ + librhash/util.c \ + " + +JSONCPP_CXX_SOURCES="\ + src/lib_json/json_reader.cpp \ + src/lib_json/json_value.cpp \ + src/lib_json/json_writer.cpp \ " if ${cmake_system_mingw} || ${UNAME_OVERRIDE}; then @@ -674,8 +717,8 @@ Configuration: (default) --system-cppdap use system-installed cppdap library --no-system-cppdap use cmake-provided cppdap library (default) - --system-curl use system-installed curl library - --no-system-curl use cmake-provided curl library (default) + --system-curl use system-installed curl library (default on macOS) + --no-system-curl use cmake-provided curl library (default elsewhere) --system-expat use system-installed expat library --no-system-expat use cmake-provided expat library (default) --system-jsoncpp use system-installed jsoncpp library @@ -810,9 +853,7 @@ cmake_kwsys_config_replace_string () s/@KWSYS_LFS_AVAILABLE@/${KWSYS_LFS_AVAILABLE}/g; s/@KWSYS_LFS_REQUESTED@/${KWSYS_LFS_REQUESTED}/g; s/@KWSYS_NAME_IS_KWSYS@/${KWSYS_NAME_IS_KWSYS}/g; - s/@KWSYS_STL_HAS_WSTRING@/${KWSYS_STL_HAS_WSTRING}/g; s/@KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H@/${KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H}/g; - s/@KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP@/${KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP}/g; }" "${INFILE}" >> "${OUTFILE}${_tmp}" if test -f "${OUTFILE}${_tmp}"; then if "${_diff}" "${OUTFILE}" "${OUTFILE}${_tmp}" > /dev/null 2> /dev/null ; then @@ -1096,12 +1137,6 @@ if test "${cmake_bootstrap_generator}" = "Ninja"; then cmFortranLexer \ cmFortranParser \ " - - JSONCPP_CXX_SOURCES="\ - src/lib_json/json_reader.cpp \ - src/lib_json/json_value.cpp \ - src/lib_json/json_writer.cpp \ - " else CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES} \ cmDepends \ @@ -1115,8 +1150,6 @@ else cmMakefileUtilityTargetGenerator \ cmProcessTools \ " - - JSONCPP_CXX_SOURCES= fi # Add Cygwin-specific flags @@ -1469,6 +1502,7 @@ else cmake_make_processors="${CMAKE_KNOWN_MAKE_PROCESSORS}" fi +tab="`printf '\t'`" TMPFILE="`cmake_tmp_file`_dir" rm -rf "${cmake_bootstrap_dir}/${TMPFILE}" mkdir "${cmake_bootstrap_dir}/${TMPFILE}" @@ -1482,7 +1516,7 @@ build test: cc test.c else echo ' test: test.c - '"${cmake_c_compiler}"' '"${cmake_ld_flags} ${cmake_c_flags}"' -o test test.c +'"${tab}${cmake_c_compiler}"' '"${cmake_ld_flags} ${cmake_c_flags}"' -o test test.c '>"Makefile" fi echo ' @@ -1533,14 +1567,12 @@ KWSYS_NAME_IS_KWSYS=0 KWSYS_BUILD_SHARED=0 KWSYS_LFS_AVAILABLE=0 KWSYS_LFS_REQUESTED=0 -KWSYS_STL_HAS_WSTRING=0 KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H=0 KWSYS_CXX_HAS_SETENV=0 KWSYS_CXX_HAS_UNSETENV=0 KWSYS_CXX_HAS_ENVIRON_IN_STDLIB_H=0 KWSYS_CXX_HAS_UTIMENSAT=0 KWSYS_CXX_HAS_UTIMES=0 -KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP=1 if cmake_try_run "${cmake_cxx_compiler}" \ "${cmake_cxx_flags} ${cmake_ld_flags} -DTEST_KWSYS_CXX_HAS_SETENV" \ @@ -1569,15 +1601,6 @@ else echo "${cmake_cxx_compiler} does not have environ in stdlib.h" fi -if cmake_try_run "${cmake_cxx_compiler}" \ - "${cmake_cxx_flags} ${cmake_ld_flags} -DTEST_KWSYS_STL_HAS_WSTRING" \ - "${cmake_source_dir}/Source/kwsys/kwsysPlatformTestsCXX.cxx" >> cmake_bootstrap.log 2>&1; then - KWSYS_STL_HAS_WSTRING=1 - echo "${cmake_cxx_compiler} has stl wstring" -else - echo "${cmake_cxx_compiler} does not have stl wstring" -fi - if cmake_try_run "${cmake_cxx_compiler}" \ "${cmake_cxx_flags} ${cmake_ld_flags} -DTEST_KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H" \ "${cmake_source_dir}/Source/kwsys/kwsysPlatformTestsCXX.cxx" >> cmake_bootstrap.log 2>&1; then @@ -1651,8 +1674,8 @@ cmake_report cmVersionConfig.h${_tmp} "#define CMake_VERSION \"${cmake_version}\ cmake_report cmConfigure.h${_tmp} "#define CMAKE_BOOTSTRAP_SOURCE_DIR \"${CMAKE_BOOTSTRAP_SOURCE_DIR}\"" cmake_report cmConfigure.h${_tmp} "#define CMAKE_BOOTSTRAP_BINARY_DIR \"${CMAKE_BOOTSTRAP_BINARY_DIR}\"" cmake_report cmConfigure.h${_tmp} "#define CMake_DEFAULT_RECURSION_LIMIT 400" -cmake_report cmConfigure.h${_tmp} "#define CMAKE_BIN_DIR \"/bootstrap-not-insalled\"" -cmake_report cmConfigure.h${_tmp} "#define CMAKE_DATA_DIR \"/bootstrap-not-insalled\"" +cmake_report cmConfigure.h${_tmp} "#define CMAKE_BIN_DIR \"/bootstrap-not-installed\"" +cmake_report cmConfigure.h${_tmp} "#define CMAKE_DATA_DIR \"/bootstrap-not-installed\"" cmake_report cmConfigure.h${_tmp} "#define CM_FALLTHROUGH" if test "${cmake_bootstrap_generator}" = "Ninja"; then @@ -1661,6 +1684,10 @@ else cmake_report cmConfigure.h${_tmp} "#define CMAKE_BOOTSTRAP_MAKEFILES" fi +if ${cmake_system_darwin}; then + cmake_report cmConfigure.h${_tmp} "#define CMake_USE_MACH_PARSER" +fi + if ${cmake_system_mingw} || ${UNAME_OVERRIDE}; then cmake_report cmConfigure.h${_tmp} "#if defined(_WIN32) && !defined(NOMINMAX)" cmake_report cmConfigure.h${_tmp} "# define NOMINMAX" @@ -1733,12 +1760,10 @@ if test "x${bootstrap_system_librhash}" = "x"; then objs="${objs} rhash-`cmake_obj ${a}`" done fi -if test "${cmake_bootstrap_generator}" = "Ninja"; then - if test "x${bootstrap_system_jsoncpp}" = "x"; then - for a in ${JSONCPP_CXX_SOURCES}; do - objs="${objs} jsoncpp-`cmake_obj ${a}`" - done - fi +if test "x${bootstrap_system_jsoncpp}" = "x"; then + for a in ${JSONCPP_CXX_SOURCES}; do + objs="${objs} jsoncpp-`cmake_obj ${a}`" + done fi libs="" @@ -1746,7 +1771,7 @@ libs="" uv_c_flags="" if ${cmake_system_mingw} || ${UNAME_OVERRIDE}; then uv_c_flags="${uv_c_flags} -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0600" - libs="${libs} -lws2_32 -lpsapi -liphlpapi -lshell32 -luserenv -lole32 -loleaut32" + libs="${libs} -lws2_32 -lpsapi -liphlpapi -lshell32 -luserenv -lole32 -loleaut32 -luuid" else case "${cmake_system}" in *AIX*) @@ -1763,15 +1788,12 @@ else uv_c_flags="${uv_c_flags} -D_GNU_SOURCE" libs="${libs} -ldl -lrt" ;; - *kFreeBSD*) - libs="${libs} -lkvm -lfreebsd-glue" + *NetBSD*) + libs="${libs} -lkvm" ;; *BSD*) - libs="${libs} -lkvm" ;; *SunOS*) - # Normally libuv uses '-D_XOPEN_SOURCE=500 -std=c90' on Solaris 5.10, - # but we do not need to do that because we bootstrap using POSIX APIs. uv_c_flags="${uv_c_flags} -D__EXTENSIONS__ -D_XOPEN_SOURCE=600" libs="${libs} -lkstat -lnsl -lsendfile -lsocket -lrt" ;; @@ -1792,32 +1814,40 @@ if test "x${bootstrap_system_libuv}" = "x"; then else if test `which pkg-config`; then use_uv_flags="`pkg-config --cflags libuv`" + use_uv_ldflags="`pkg-config --libs libuv`" cmake_c_flags="${cmake_c_flags} ${use_uv_flags}" cmake_cxx_flags="${cmake_cxx_flags} ${use_uv_flags}" + else + use_uv_ldflags="-luv" fi - libs="${libs} -luv" + libs="${libs} ${use_uv_ldflags}" fi +librhash_c_flags="-DNO_IMPORT_EXPORT" if test "x${bootstrap_system_librhash}" != "x"; then if test `which pkg-config`; then use_librhash_flags="`pkg-config --cflags librhash`" + use_librhash_ldflags="`pkg-config --libs librhash`" cmake_c_flags="${cmake_c_flags} ${use_librhash_flags}" cmake_cxx_flags="${cmake_cxx_flags} ${use_librhash_flags}" + else + use_librhash_ldflags="-lrhash" fi - libs="${libs} -lrhash" + libs="${libs} ${use_librhash_ldflags}" fi -if test "${cmake_bootstrap_generator}" = "Ninja"; then - jsoncpp_cxx_flags= - if test "x${bootstrap_system_jsoncpp}" = "x"; then - jsoncpp_cxx_flags="${jsoncpp_cxx_flags} `cmake_escape_shell "-I${cmake_source_dir}/Utilities/cmjsoncpp/include"`" +jsoncpp_cxx_flags= +if test "x${bootstrap_system_jsoncpp}" = "x"; then + jsoncpp_cxx_flags="${jsoncpp_cxx_flags} `cmake_escape_shell "-I${cmake_source_dir}/Utilities/cmjsoncpp/include"`" +else + if test `which pkg-config`; then + use_jsoncpp_flags="`pkg-config --cflags jsoncpp`" + use_jsoncpp_ldflags="`pkg-config --libs jsoncpp`" + cmake_cxx_flags="${cmake_cxx_flags} ${use_jsoncpp_flags}" else - if test `which pkg-config`; then - use_jsoncpp_flags="`pkg-config --cflags jsoncpp`" - cmake_cxx_flags="${cmake_cxx_flags} ${use_jsoncpp_flags}" - fi - libs="${libs} -ljsoncpp" + use_jsoncpp_ldflags="-ljsoncpp" fi + libs="${libs} ${use_jsoncpp_ldflags}" fi if test "x${cmake_ansi_cxx_flags}" != "x"; then @@ -1827,7 +1857,7 @@ fi system_flags='' case "${cmake_system}" in # Ensure filesystem access uses 64-bit offsets even on 32-bit hosts. - *Linux*) system_flags='-D_FILE_OFFSET_BITS=64' ;; + *Linux*) system_flags='-D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64' ;; esac if test "x${system_flags}" != "x"; then cmake_c_flags="${cmake_c_flags} ${system_flags}" @@ -1863,7 +1893,7 @@ write_source_rule() { echo " srcflags = ${src_flags}" >> "${cmake_bootstrap_dir}/build.ninja" else echo "${obj} : ${src} ${dep}" >> "${cmake_bootstrap_dir}/Makefile" - echo " ${compiler} ${flags} ${src_flags} -c ${src} -o ${obj}" >> "${cmake_bootstrap_dir}/Makefile" + echo "${tab}${compiler} ${flags} ${src_flags} -c ${src} -o ${obj}" >> "${cmake_bootstrap_dir}/Makefile" fi } @@ -1910,7 +1940,7 @@ if test "${cmake_bootstrap_generator}" = "Ninja"; then echo " libs = ${libs}" >> "${cmake_bootstrap_dir}/build.ninja" else echo "cmake: ${objs}" > "${cmake_bootstrap_dir}/Makefile" - echo " ${cmake_cxx_compiler} ${cmake_ld_flags} ${cmake_cxx_flags} ${objs} ${libs} -o cmake" >> "${cmake_bootstrap_dir}/Makefile" + echo "${tab}${cmake_cxx_compiler} ${cmake_ld_flags} ${cmake_cxx_flags} ${objs} ${libs} -o cmake" >> "${cmake_bootstrap_dir}/Makefile" fi for a in ${CMAKE_CXX_SOURCES}; do src=`cmake_escape_artifact "${cmake_source_dir}/Source/${a}.cxx"` @@ -1954,16 +1984,14 @@ fi if test "x${bootstrap_system_librhash}" = "x"; then for a in ${LIBRHASH_C_SOURCES}; do src=`cmake_escape_artifact "${cmake_source_dir}/Utilities/cmlibrhash/${a}"` - write_source_rule "c" "rhash-`cmake_obj ${a}`" "${src}" "" + write_source_rule "c" "rhash-`cmake_obj ${a}`" "${src}" "${librhash_c_flags}" done fi -if test "${cmake_bootstrap_generator}" = "Ninja"; then - if test "x${bootstrap_system_jsoncpp}" = "x"; then - for a in ${JSONCPP_CXX_SOURCES}; do - src=`cmake_escape_artifact "${cmake_source_dir}/Utilities/cmjsoncpp/${a}"` - write_source_rule "cxx" "jsoncpp-`cmake_obj ${a}`" "${src}" "${jsoncpp_cxx_flags}" - done - fi +if test "x${bootstrap_system_jsoncpp}" = "x"; then + for a in ${JSONCPP_CXX_SOURCES}; do + src=`cmake_escape_artifact "${cmake_source_dir}/Utilities/cmjsoncpp/${a}"` + write_source_rule "cxx" "jsoncpp-`cmake_obj ${a}`" "${src}" "${jsoncpp_cxx_flags}" + done fi if test "${cmake_bootstrap_generator}" = "Ninja"; then echo " @@ -1975,7 +2003,7 @@ build build.ninja : rebuild_cache else echo " rebuild_cache: - cd \"${cmake_binary_dir}\" && \"${cmake_source_dir}/bootstrap\" --generator=\"${cmake_bootstrap_generator}\" +${tab}cd \"${cmake_binary_dir}\" && \"${cmake_source_dir}/bootstrap\" --generator=\"${cmake_bootstrap_generator}\" " >> "${cmake_bootstrap_dir}/Makefile" fi diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in index b5fc700928e..9e61b00c745 100644 --- a/cmake_uninstall.cmake.in +++ b/cmake_uninstall.cmake.in @@ -7,10 +7,10 @@ string(REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") if(EXISTS "$ENV{DESTDIR}${file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E rm -f \"$ENV{DESTDIR}${file}\"" + execute_process( + COMMAND "@CMAKE_COMMAND@" -E rm -f "$ENV{DESTDIR}${file}" OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval + RESULT_VARIABLE rm_retval ) if("${rm_retval}" STREQUAL 0) else() diff --git a/esy.json b/esy.json index 67c0ba849d8..0fcf26376be 100644 --- a/esy.json +++ b/esy.json @@ -1,7 +1,7 @@ { "name": "esy-cmake", - "version": "3.27.4", - "description": "CMake v3.27.4 packaged for esy", + "version": "4.1.2", + "description": "CMake v4.1.2 packaged for esy", "esy": { "buildsInSource": true, "buildEnv": { diff --git a/esy.lock/index.json b/esy.lock/index.json index 8faa150ff93..b9effd06a74 100644 --- a/esy.lock/index.json +++ b/esy.lock/index.json @@ -1,10 +1,10 @@ { "checksum": "0cc961f41dbf4ff5ab5654751a4ba36e", - "root": "CMake@link-dev:./esy.json", + "root": "esy-cmake@link-dev:./esy.json", "node": { - "CMake@link-dev:./esy.json": { - "id": "CMake@link-dev:./esy.json", - "name": "CMake", + "esy-cmake@link-dev:./esy.json": { + "id": "esy-cmake@link-dev:./esy.json", + "name": "esy-cmake", "version": "link-dev:./esy.json", "source": { "type": "link-dev", "path": ".", "manifest": "esy.json" }, "overrides": [], diff --git a/sgconfig.yml b/sgconfig.yml new file mode 100644 index 00000000000..f3404117332 --- /dev/null +++ b/sgconfig.yml @@ -0,0 +1,6 @@ +ruleDirs: +- Utilities/ast-grep/rules +testConfigs: +- testDir: Utilities/ast-grep/rule-tests +utilDirs: +- Utilities/ast-grep/utils